您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
显示当前页面连接IP 和SPDY、HTTP/2
// ==UserScript== // @name Network Indicator // @version 0.0.8 // @compatibility FF34+ // @description 显示当前页面连接IP 和SPDY、HTTP/2 // @include main // @namespace https://greasyfork.org/users/25642 // ==/UserScript== 'use strict'; if (location == 'chrome://browser/content/browser.xul') { const AUTO_POPUP = 600; //鼠标悬停图标上自动弹出面板的延时,非负整数,单位毫秒。0为禁用。 const DEBUG = false; //调试开关 const GET_LOCAL_IP = true; //是否启用获取显示内(如果有)外网IP。基于WebRTC, //如无法显示,请确保about:config中的media.peerconnection.enabled的值为true, //或者将上面的 “DEBUG”的值改为true,重启FF,打开浏览器控制台(ctrl+shift+j), //弹出面板后,将有关输出适宜打ma,复制发给我看看。 //还有可能会被AdBlock, Ghostery等扩展阻止。 //若关闭则只显示外网IP const HTML_NS = 'http://www.w3.org/1999/xhtml'; const XUL_PAGE = 'data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id="win"/>'; let promise = {}; try{ Cu.import('resource://gre/modules/PromiseUtils.jsm', promise); promise = promise.PromiseUtils; }catch(ex){ Cu.import('resource://gre/modules/Promise.jsm', promise); promise = promise.Promise; } let HiddenFrame = function() {}; HiddenFrame.prototype = { _frame: null, _deferred: null, _retryTimerId: null, get hiddenDOMDocument() { return Services.appShell.hiddenDOMWindow.document; }, get isReady() { return this.hiddenDOMDocument.readyState === 'complete'; }, get() { if (!this._deferred) { this._deferred = promise.defer(); this._create(); } return this._deferred.promise; }, destroy() { clearTimeout(this._retryTimerId); if (this._frame) { if (!Cu.isDeadWrapper(this._frame)) { this._frame.removeEventListener('load', this, true); this._frame.remove(); } this._frame = null; this._deferred = null; } }, handleEvent() { let contentWindow = this._frame.contentWindow; if (contentWindow.location.href === XUL_PAGE) { this._frame.removeEventListener('load', this, true); this._deferred.resolve(contentWindow); } else { contentWindow.location = XUL_PAGE; } }, _create() { if (this.isReady) { let doc = this.hiddenDOMDocument; this._frame = doc.createElementNS(HTML_NS, 'iframe'); this._frame.addEventListener('load', this, true); doc.documentElement.appendChild(this._frame); } else { this._retryTimerId = setTimeout(this._create.bind(this), 0); } } }; let networkIndicator = { autoPopup: AUTO_POPUP, _getLocalIP: GET_LOCAL_IP, init(){ if(this.icon) return; this.setStyle(); this.icon.addEventListener('click', this, false); if(this.autoPopup){ this.icon.addEventListener('mouseenter', this, false); this.icon.addEventListener('mouseleave', this, false); } ['dblclick', 'mouseover', 'mouseout', 'command', 'contextmenu'].forEach(event => { this.panel.addEventListener(event, this, false); }); gBrowser.tabContainer.addEventListener('TabSelect', this, false); ['content-document-global-created', 'inner-window-destroyed', 'outer-window-destroyed', 'http-on-examine-cached-response', 'http-on-examine-response'].forEach(topic => { Services.obs.addObserver(this, topic, false); }); }, _icon: null, _panel: null, get icon (){ if(!this._icon){ this._icon = document.getElementById('NetworkIndicator-icon') || this.createElement('image', {id: 'NetworkIndicator-icon', class: 'urlbar-icon'}, [document.getElementById('urlbar-icons')]); return false; } return this._icon; }, get panel (){ if(!this._panel){ let cE = this.createElement; this._panel = document.getElementById('NetworkIndicator-panel') || cE('panel', { id: 'NetworkIndicator-panel', type: 'arrow' }, document.getElementById('mainPopupSet')); this._panel._contextMenu = cE('menupopup', {id: 'NetworkIndicator-contextMenu'}, this._panel); cE('menuitem', {label: '复制全部'}, this._panel._contextMenu)._command = 'copyAll'; cE('menuitem', {label: '复制选中'}, this._panel._contextMenu)._command = 'copySelection'; this._panel._list = cE('ul', {}, cE('vbox', {context: 'NetworkIndicator-contextMenu'}, this._panel)); } return this._panel; }, currentBrowserPanel: new WeakMap(), _panelNeedUpdate: false, observe(subject, topic, data) { if(topic == 'http-on-examine-response' || topic == 'http-on-examine-cached-response'){ this.onExamineResponse(subject, topic); }else if(topic == 'inner-window-destroyed'){ let innerID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; delete this.recordInner[innerID]; if(this.getWinId().currentInnerWindowID != innerID){ this._panelNeedUpdate = true; this.updateState(); } }else if(topic == 'outer-window-destroyed'){ let outerID = subject.QueryInterface(Ci.nsISupportsPRUint64).data, cwId = this.getWinId(); delete this.recordOuter[outerID]; if(cwId.outerWindowID != outerID){ this._panelNeedUpdate = true; this.updateState(); //从一般网页后退到无网络请求的页面(例如about:xxx)应关闭面板。 if(!this.recordInner[cwId.currentInnerWindowID]) this.panel.hidePopup && this.panel.hidePopup(); } }else if(topic == 'content-document-global-created'){ let domWinUtils = subject.top .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils), outerID = domWinUtils.outerWindowID, innerID = domWinUtils.currentInnerWindowID, ro = this.recordOuter[outerID]; if(!ro) return; let mainHost = ro.pop(), ri = this.recordInner[innerID]; //标记主域名 mainHost.isMainHost = true; this.recordInner[innerID] = [mainHost]; delete this.recordOuter[outerID]; } }, //记录缓存对象 recordOuter: {}, recordInner: {}, onExamineResponse(subject, topic) { let channel = subject.QueryInterface(Ci.nsIHttpChannel), nc = channel.notificationCallbacks || channel.loadGroup && channel.loadGroup.notificationCallbacks, domWinUtils = null, domWindow = null; if(!nc || (channel.loadFlags & Ci.nsIChannel.LOAD_REQUESTMASK) == 5120){ //前进后退读取Cache需更新panel return this._panelNeedUpdate = topic == 'http-on-examine-cached-response'; } try{ domWindow = nc.getInterface(Ci.nsIDOMWindow); domWinUtils = domWindow.top .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); }catch(ex){ //XHR响应处理 let ww = null; try{ ww = subject.notificationCallbacks.getInterface(Ci.nsILoadContext); }catch(ex1){ try{ ww = subject.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); }catch(ex2){} } if(!ww) return; try{domWindow = ww.associatedWindow;}catch(ex3){} domWinUtils = this.getWinId(ww.topFrameElement); } let isMainHost = (channel.loadFlags & Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI && domWindow && domWindow == domWindow.top); //排除ChromeWindow的、unload等事件触发的请求响应 if(!domWinUtils || (channel.loadFlags == 640 && !subject.loadGroup) || domWindow instanceof Ci.nsIDOMChromeWindow || (!isMainHost && channel.loadInfo && channel.loadInfo.loadingDocument && channel.loadInfo.loadingDocument.ownerGlobal === null) ) return; let outerID = domWinUtils.outerWindowID, innerID = domWinUtils.currentInnerWindowID, newentry = Object.create(null), cwId = this.getWinId(); newentry.host = channel.URI.asciiHost; newentry.scheme = channel.URI.scheme; //newentry.url = channel.URI.asciiSpec; channel.remoteAddress && (newentry.ip = channel.remoteAddress); channel.QueryInterface(Ci.nsIHttpChannelInternal); try{ //获取响应头的服务器、SPDY、HTTP/2信息 channel.visitResponseHeaders({ visitHeader(name, value){ let lowerName = name.toLowerCase(); if (lowerName == 'server') { newentry.server = value }else if(lowerName == 'x-firefox-spdy'){ newentry.spdy = value } } }); }catch(ex){} if(isMainHost){ newentry.url = channel.URI.asciiSpec; outerID && (this.recordOuter[outerID] || (this.recordOuter[outerID] = [])).push(newentry); if(this.panel.state != 'closed'){ if(cwId.outerWindowID == outerID){ if(this.panel.hasAttribute('overflowY')) this.panel.removeAttribute('overflowY'); let list = this.panel._list; while(list.hasChildNodes()) list.removeChild(list.lastChild); list._minWidth = 0; } } }else{ innerID && (this.recordInner[innerID] || (this.recordInner[innerID] = [])).push(newentry); //newentry.loadFlags = channel.loadFlags } //更新图标状态 if(cwId.outerWindowID == outerID || cwId.currentInnerWindowID == innerID) this.updateState(cwId); //当且仅当主动点击打开显示面板时才查询IP位置、更新面板信息。 //避免每次刷新页面都请求查询网站的IP,以减少暴露隐私的可能、性能消耗。 if(this.panel.state != 'closed' && (cwId.outerWindowID == outerID || cwId.currentInnerWindowID == innerID)){ //标记下次点击显示时是否需更新面板内容 if(this._panelNeedUpdate = !(this.recordInner[cwId.currentInnerWindowID] || [{}]).some(re => re.isMainHost)) this.panel.hidePopup(); //类似about:addons页面情况下,刷新时必须关闭面板,避免计数叠加。 this.dnsDetect(newentry, isMainHost); }else{ this._panelNeedUpdate = true; } }, _nsIDNSService: Cc['@mozilla.org/network/dns-service;1'].createInstance(Ci.nsIDNSService), _nsIClipboardHelper: Cc['@mozilla.org/widget/clipboardhelper;1'].getService(Ci.nsIClipboardHelper), dnsDetect(obj, isMainHost){ if(obj.ip) return this.updatePanel(obj, isMainHost); this._nsIDNSService.asyncResolve(obj.host, this._nsIDNSService.RESOLVE_BYPASS_CACHE, { onLookupComplete: (request, records, status) => { if (!Components.isSuccessCode(status)) return; obj.ip = records.getNextAddrAsString(); this.updatePanel(obj, isMainHost); } }, null); }, updatePanel(record, isMainHost){ let cE = this.createElement, list = this.panel._list, li = list.querySelector(`li[ucni-ip="${record.ip}"]`), p = null; if(!li){//不存在相同的IP let fragment = document.createDocumentFragment(), ipSpan = null; li = cE('li', {'ucni-ip': record.ip}, fragment); cE('p', {class: 'ucni-ip', text: record.ip + '\n'}, ipSpan = cE('span', {}, li)); // + '\n' 复制时增加换行格式 p = cE('p', {class: 'ucni-host', host: record.host, scheme: record.scheme, counter: 1, text: record.host + '\n'}, cE('span', {}, li)); p._connCounter = 1; p._connScheme = [record.scheme]; if(isMainHost){ //标记主域名 li.classList.add('ucni-MainHost'); //主域名重排列至首位 list.insertBefore(fragment, list.firstChild); //更新主域名 IP位置 this.updateMainInfo(record, list); }else{ list.appendChild(fragment); //不存在相同的IP且非主域名 this.setTooltip(li, record); } //调整容器宽度以适应IP长度 let minWidth = record.ip.length - record.ip.split(/:|\./).length / 2 + 1; if(list._minWidth && minWidth > list._minWidth){ Array.prototype.forEach.call(list.querySelectorAll('li>span:first-child'), span => { if(!span._width || span._width < minWidth) span.style.minWidth = `${span._width = list._minWidth = minWidth}ch`;; }); }else{ if(!list._minWidth) list._minWidth = minWidth; ipSpan.style.minWidth = `${ipSpan._width = list._minWidth}ch`; } }else{//相同的IP p = li.querySelector(`.ucni-host[host="${record.host}"]`); if(!p){//同IP不同的域名 p = cE('p', {class: 'ucni-host', host: record.host, scheme: record.scheme, counter: 1, text: record.host + '\n'}, li.querySelector('.ucni-host').parentNode); p._connCounter = 1; p._connScheme = [record.scheme]; }else{//同IP同域名 p.setAttribute('counter', ++p._connCounter); //计数+1 if(p._connScheme.every(s => s != record.scheme)){ //同IP同域名不同的协议 p._connScheme.push(record.scheme); p.setAttribute('scheme', p._connScheme.join(' ')); } } if(isMainHost){ li.classList.add('ucni-MainHost'); if(list.firstChild != li){ list.insertBefore(li, list.firstChild); li.lastChild.insertBefore(p, li.lastChild.firstChild); } this.updateMainInfo(record, list); } } if(this.panel.popupBoxObject.height > 500 && !this.panel.hasAttribute('overflowY')){ this.panel.setAttribute('overflowY', true); } if(record.spdy && (!p.spdy || p.spdy.every(s => s != record.spdy))){ (p.spdy || (p.spdy = [])).push(record.spdy); p.setAttribute('spdy', p.spdy.join(' ')); } this.setTooltip(p, { counter: p._connCounter, server: record.server, scheme: p._connScheme || [record.scheme], spdy: p.spdy }); }, updateMainInfo(obj, list) { if(obj.location){ if(list.querySelector('#ucni-mplocation')) return; let cE = this.createElement, fm = document.createDocumentFragment(), li = cE('li', {id: 'ucni-mplocation'}, fm), timeStamp = new Date().getTime(), text = ['所在地', '服务器', '内网IP', '外网IP'], info = []; let setMainInfo = info => { let location = this.localAndPublicIPs.publicLocation; if(this.localAndPublicIPs._public){ info.push({value: this.localAndPublicIPs._public[0], text: text[3]}); location = this.localAndPublicIPs._public[1]; } for(let i of info){ if(!i.value) continue; let label = cE('label', {text: i.value + '\n'}, cE('span', {text: i.text + ': '}, cE('p', {}, li)).parentNode); if(i.text === text[3]) this.setTooltip(label, { ip: i.value, location: location }); } list.insertBefore(fm, list.firstChild); //同时更新第一个(主域名)tooltip this.setTooltip(list.querySelector('.ucni-MainHost'), obj); }; info.push({value: obj.location, text: text[0]}); obj.server && info.push({value: obj.server, text: text[1]}); if(this._getLocalIP && !this.localAndPublicIPs){ (new Promise(this.getLocalAndPublicIPs)).then(reslut => { this.localAndPublicIPs = reslut; info.push({value: reslut.localIP, text: text[2]}); info.push({value: reslut.publicIP, text: text[3]}); setMainInfo(info); }, () => {setMainInfo(info);}).catch(() => { setMainInfo(info); }); }else{ if(this.localAndPublicIPs){ info.push({value: this.localAndPublicIPs.localIP, text: text[2]}); info.push({value: this.localAndPublicIPs.publicIP, text: text[3]}); } setMainInfo(info); } }else{ this.queryLocation(obj.ip, result => { obj.location = result.location; this.localAndPublicIPs = this.localAndPublicIPs || {}; //如果不使用WebRCT方式获取内外网IP if(!this._getLocalIP){ this.localAndPublicIPs.publicIP = result.publicIP; this.localAndPublicIPs.publicLocation = result.publicLocation; }else{ this.localAndPublicIPs._public = [result.publicIP, result.publicLocation]; } this.updateMainInfo(obj, list); }); } }, queryLocation(ip, callback){ let req = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'] .createInstance(Ci.nsIXMLHttpRequest), url = '', regex = null; if(ip.indexOf(':') > -1){ regex = [/id="Span1">(?:IPv\d[^:]+:\s*)?([^<]+)(?=<br)/i, /"cz_ip">([^<]+)/i, /"cz_addr">(?:IPv\d[^:]+:\s*)?([^<]+)/i]; url = `http://ip.ipv6home.cn/?ip=${ip}`; }else{ regex = [/"InputIPAddrMessage">([^<]+)/i, /"cz_ip">([^<]+)/i, /"cz_addr">([^<]+)/i]; url = `http://www.cz88.net/ip/index.aspx?ip=${ip}`; } req.open('GET', url, true); req.send(null); req.timeout = 10000; req.ontimeout = req.onerror = () => { callback({ip: ip, location: 'ERR 查询过程中出错,请重试。'}); }; req.onload = () => { if (req.status == 200) { let match = regex.map(r => req.responseText.match(r)[1] .replace(/^[^>]+>(?:IPv\d[^:]+:\s*)?|\s*CZ88.NET.*/g, '')); try{ callback({ ip: ip, location: match[0], publicIP: match[1], publicLocation: match[2] }); }catch(ex){ req.onerror();} } }; }, localAndPublicIPs: null, getLocalAndPublicIPs(resolve, reject){ let hiddenFrame = new HiddenFrame(), _RTCtimeout = null, _failedTimeout = null; //chrome环境下会抛出异常 hiddenFrame.get().then(window => { let RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection; if(!RTCPeerConnection) { hiddenFrame.destroy(); hiddenFrame = null; if(DEBUG) { console.log('%cNetwork Indicator:\n', 'color:red; font-size:120%; background-color:#ccc;', 'WebRTC功能不可用!' ); } return reject(); } let pc = new RTCPeerConnection(undefined, { optional: [{RtpDataChannels: true}] }), onResolve = ips => { clearTimeout(_failedTimeout); hiddenFrame.destroy(); hiddenFrame = null; resolve(ips); }, ip = {}, debug = []; let regex1 = /(?:[a-z\d]+[\:\.]+){2,}[a-z\d]+/i, regex2 = /UDP \d+ ([\da-z\.\:]+).+srflx raddr ([\da-z\.\:]+)/i; //内网IPv4,应该没有用IPv6的吧 let lcRegex = /^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/; pc.onicecandidate = ice => { if(!ice.candidate) return; let _ip1 = ice.candidate.candidate.match(regex1), _ip2 = ice.candidate.candidate.match(regex2); if(DEBUG) debug.push(ice.candidate.candidate); if(Array.isArray(_ip1)){ clearTimeout(_RTCtimeout); if(Array.isArray(_ip2) && _ip2.length === 3) return onResolve({publicIP: _ip2[1], localIP: _ip2[2]}); ip[lcRegex.test(_ip1[0]) ? 'localIP' : 'publicIP'] = _ip1[0]; _RTCtimeout = setTimeout(()=>{ onResolve(ip); }, 1000); } }; //5s超时 _failedTimeout = setTimeout(()=>{ if(DEBUG) { console.log('%cNetwork Indicator:\n', 'color:red; font-size:120%; background-color:#ccc;', debug.join('\n') ); } reject(); hiddenFrame.destroy(); hiddenFrame = null; }, 5000); pc.createOffer(result => { pc.setLocalDescription(result);}, () => {}); pc.createDataChannel(''); }); }, updateState(cwId = this.getWinId()){ let records = this.recordInner[cwId.currentInnerWindowID] || [], state = this.getStateBySpdyVer((records.filter(re => re.isMainHost)[0] || {}).spdy), subDocsState = (records.filter(re => !re.isMainHost) || [{}]).map(re => this.getStateBySpdyVer(re.spdy)); if(state == 0 && subDocsState.some(st => st != 0)) state = subDocsState.some(st => st == 7) ? 2 : 1; state = ['unknown', 'subSpdy', 'subHttp2', 'active', 'spdy2', 'spdy3', 'spdy31', 'http2'][state]; if(this.icon.spdyState != state){ this.icon.setAttribute('state', this.icon.spdyState = state); } }, getStateBySpdyVer(version = '0'){ let state = 3; if(version === '0'){ state = 0; }else if(version === '2'){ state = 4; }else if(version === '3'){ state = 5; }else if(version === '3.1'){ state = 6; }else if(/^h2/.test(version)){ state = 7; } return state; }, openPopup(event){ if(event.button !== 0) return; event.view.clearTimeout(this.panel._showPanelTimeout); let currentBrowser = this.currentBrowserPanel.get(this.panel); if(gBrowser.selectedBrowser != currentBrowser || this._panelNeedUpdate){ let list = this.panel._list, cwId = this.getWinId(), ri = this.recordInner[cwId.currentInnerWindowID]; if(!ri) return; if(this.panel.hasAttribute('overflowY')) this.panel.removeAttribute('overflowY'); while(list.hasChildNodes()) list.removeChild(list.lastChild); list._minWidth = 0; let noneMainHost = !ri.some(re => re.isMainHost); ri.forEach((record, index) => { //类似about:addons无主域名的情况 if(index == 0 && noneMainHost) record.isMainHost = true; this.dnsDetect(record, record.isMainHost); }); this.currentBrowserPanel.set(this.panel, gBrowser.selectedBrowser); //更新完毕 this._panelNeedUpdate = false; } //弹出面板 let position = (this.icon.boxObject.y < (window.outerHeight / 2)) ? 'bottomcenter top' : 'topcenter bottom'; position += (this.icon.boxObject.x < (window.innerWidth / 2)) ? 'left' : 'right'; this.panel.openPopup(this.icon, position, 0, 0, false, false); }, updataLocation(event){ let target = event.target; while(!target.hasAttribute('ucni-ip')){ if(target == this.panel) return; target = target.parentNode; } let currentBrowser = this.currentBrowserPanel.get(this.panel), cwId = this.getWinId(), ri = this.recordInner[cwId.currentInnerWindowID]; if(target.matches('li[ucni-ip]')){ this.queryLocation(target.getAttribute('ucni-ip'), result => { //刷新所有同IP的location ri.forEach(record => { if(result.ip == record.ip){ record.location = result.location; let text = this.setTooltip(target, record); if(event.altKey){ this._nsIClipboardHelper.copyString(text); } } }); }); } }, highlightHosts(event){ let host = event.target.getAttribute('host'); if(!host) return; Array.prototype.forEach.call(this.panel._list.querySelectorAll(`p[host="${host}"]`), p => { let hover = p.classList.contains('ucni-hover'); if(event.type === 'mouseover' ? !hover : hover) p.classList.toggle('ucni-hover'); }); }, setTooltip(target, obj){ let text = []; if(obj.counter){ text.push('连接数: ' + obj.counter); obj.scheme && obj.scheme.length && text.push('Scheme: ' + obj.scheme.join(', ')); obj.spdy && obj.spdy.length && text.push('SPDY: ' + obj.spdy.join(', ')); }else{ text.push('所在地: ' + (obj.location || '双击获取, + Alt键同时复制。')); obj.server && text.push('服务器: ' + obj.server); obj.ip && text.push('IP地址: ' + obj.ip); } text = text.join('\n'); target.setAttribute('tooltiptext', text); return text; }, handleEvent(event){ switch(event.type){ case 'TabSelect': this.panel.hidePopup(); this.updateState(); break; case 'dblclick': let info = this.panel._list.querySelector('#ucni-mplocation > p:last-child'); if(info && info.contains(event.originalTarget)){ let publicIP = info.childNodes[1].textContent.trim(); if(/^[a-z\.\:\d]+$/i.test(publicIP)){ this.queryLocation(publicIP, result => { this.localAndPublicIPs.publicLocation = result.location; let text = this.setTooltip(info.childNodes[1], result); if(event.altKey) this._nsIClipboardHelper.copyString(text); }); } }else{ this.updataLocation(event); } break; case 'mouseover': case 'mouseout': this.highlightHosts(event); break; case 'mouseenter': case 'mouseleave': event.view.clearTimeout(this.panel._showPanelTimeout); if(event.type === 'mouseenter'){ this.panel._showPanelTimeout = event.view.setTimeout(this.openPopup.bind(this, event), this.autoPopup); } break; case 'command': this.onContextMenuCommand(event); break; case 'contextmenu': this.panel.focus(); let selection = event.view.getSelection(); this.panel._contextMenu.childNodes[1].setAttribute('hidden', !this.panel.contains(selection.anchorNode) || selection.toString().trim() === ''); break; default: this.openPopup(event); } }, getWinId(browser = gBrowser.selectedBrowser){ if(!browser) return {}; let windowUtils = browser.contentWindow .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); return { currentInnerWindowID: windowUtils.currentInnerWindowID, outerWindowID: windowUtils.outerWindowID }; }, onContextMenuCommand(event){ switch(event.originalTarget._command){ case 'copyAll': this._nsIClipboardHelper.copyString(this.panel._list.textContent.trim()); break; case 'copySelection': this._nsIClipboardHelper.copyString(event.view.getSelection() .toString().replace(/(?:\r\n)+/g, '\n').trim()); break; } }, createElement(name, attr, parent){ let ns = '', e = null; if(!~['ul', 'li', 'span', 'p'].indexOf(name)){ ns = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; }else{ ns = 'http://www.w3.org/1999/xhtml'; name = 'html:' + name; } e = document.createElementNS(ns , name); if(attr){ for (let i in attr) { if(i == 'text') e.textContent = attr[i]; else e.setAttribute(i, attr[i]); } } if(parent){ if(Array.isArray(parent)){ (parent.length == 2) ? parent[0].insertBefore(e, parent[1]) : parent[0].insertBefore(e, parent[0].firstChild); }else{ parent.appendChild(e); } } return e; }, setStyle(){ let sss = Cc['@mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService); sss.loadAndRegisterSheet(Services.io.newURI('data:text/css,' + encodeURIComponent(` @-moz-document url("chrome://browser/content/browser.xul"){ #NetworkIndicator-icon{ visibility: visible !important; list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAAAQCAMAAADphoe6AAABRFBMVEUAAAAAzhgAAPMAzhgAzyiUlJT/fQAAzxYAj84AzhgAzhgAzRgAzhkAzhcAzhgAWN4AzhgAzhgAzhgAzhkAzxcAzhoAzRcAk9D/fQBjs2MAj84AAPOUlJSUlJSTk5P/fQAAj84AAPP/fQAAzxgAzhkAzRkAAPMAzRgAzhcAzRcAj82Tk5P/fQAAAPKUlJQAAPUA1BGUlJT/fQAAAPMAj86Tk5MAjs7/fQAAjs6UlJQAAPIAAPMAjs6UlJQAkM4AAPSWlpabm5sAj87/fQAAAPMAkM4AAPMAj86UlJT/fQAAAPL/fQAAkM//fQD/fQAAj86UlJQAAPP/fQAAAPMAkM+UlJT/fQD/fQAAjc7/fQAAAPMAj83/fQD/fQD/fQCTk5P/fQCUlJT/fQCVlZX/fQAAkc6UlJT/fQAAAPL/fQCSkpIAAACm8FOxAAAAa3RSTlMAv79+Bb+/E7+8pkpCLLMFrJmLazcmHBMSAr2pqX4qBX58fHpycG5hWU1LS0srFhAOvLyzs7OmpnBwSkJCQiwWEAasrJmZi4uLi4CAenFrZWVdQjc3NzctJiYhHByzspqamJh5YVlZWU0rHiQB3/AAAAHvSURBVEjHzdNXk9owFAVgSSAhYIHt9tIhCUmoobN0tvfed9N79P/fM06i6N5kJg/mJefJ35wZj3w8Iv9Lvp0k4omJ8f1xupWOGN/l9mJ7wA9H++39lPHygifg8bo3WeW8mgTuSBkdAucYYyXgQyFqX4EfU+oPTeFxk/NVYKshZQe4UmcsB2y/EuII2BemdGEKkwLn8QlwXspWBLjLWAy6KEQ7BRykNOCdwuQ154XyZWGsvSll/v4ib2lvMNYdnX+paL8RovhwVbS15ygNLi8+f+HWySqvxps8sfLLw6iMrjXkgf7oEmMsVmcZ7euaqK2/FFk9QshP/bNh6vG6NNnhTuJl7W3pZM3S3mJOYiPtXeFk3daep05mfW698ulkkOB8oB35eNxPR6P93z497WUY62mnPn84ywpxpu199nTGQ+mMK5t7mEDuyDRyjmWQD0WW6Dh/7gn1uLBJOd4cQ1uthgVdidUr0HZb2YoopfS9CoT1oMC4x/75rE0u+YDAXMg+8jnrIV8JReALFs2gyOofRgd4v0NQDraxM1vY2V2l4AKP5k1njHtsvED5bZLAWJs3yKONO2T73a2zgNL2zS2Z0vjvXhmjBZIFgjLMY5e62NdFoghYIBTUDTLusX8saA4wISg3EezSH75NYS953fo7l/NVa/IOl+4AAAAASUVORK5CYII="); -moz-image-region: rect(0px 16px 16px 0px); } #NetworkIndicator-icon[state=subSpdy] { -moz-image-region: rect(0px 32px 16px 16px); } #NetworkIndicator-icon[state=subHttp2] { -moz-image-region: rect(0px 48px 16px 32px); } #NetworkIndicator-icon[state=http2] { -moz-image-region: rect(0px 64px 16px 48px); } #NetworkIndicator-icon[state=active] { -moz-image-region: rect(0px 80px 16px 64px); } #NetworkIndicator-icon[state=spdy2] { -moz-image-region: rect(0px 96px 16px 80px); } #NetworkIndicator-icon[state=spdy3] { -moz-image-region: rect(0px 112px 16px 96px); } #NetworkIndicator-icon[state=spdy31] { -moz-image-region: rect(0px 128px 16px 112px); } #NetworkIndicator-panel :-moz-any(ul, li, span, p){ margin:0; padding:0; } #NetworkIndicator-panel :-moz-any(p, label){ -moz-user-focus: normal; -moz-user-select: text; cursor: text!important; } #NetworkIndicator-panel .panel-arrowcontent{ margin: 0; padding:5px !important; } #NetworkIndicator-panel #ucni-mplocation{ flex-direction: column; } #NetworkIndicator-panel #ucni-mplocation>p{ display: flex; } #NetworkIndicator-panel p.ucni-ip{ font: bold 90%/1.5rem Helvetica, Arial !important; color: #2553B8; } #NetworkIndicator-panel #ucni-mplocation>p>:-moz-any(span, label){ color: #666; font-size:90%; font-weight:bold; } #NetworkIndicator-panel #ucni-mplocation>p>label{ color:#0055CC!important; flex:1!important; text-align: center!important; padding:0!important; margin:0 0 0 1ch!important; max-width:23em!important; } #NetworkIndicator-panel li:nth-child(2n-1){ background: #eee; } #NetworkIndicator-panel li:not(#ucni-mplocation):hover{ background-color: #ccc; } #NetworkIndicator-panel p.ucni-host, #NetworkIndicator-panel li{ display:flex; } #NetworkIndicator-panel li>span:last-child{ flex: 1; } #NetworkIndicator-panel p[scheme="http"]{ color:#629BED; } #NetworkIndicator-panel p[scheme="https"]{ color:#479900; text-shadow:0 0 1px #BDD700; } #NetworkIndicator-panel p[scheme~="https"][scheme~="http"]{ color:#7A62ED; font-weight: bold; } #NetworkIndicator-panel p[scheme="https"]{ color:#00CC00; } #NetworkIndicator-panel p.ucni-host[spdy]::after, #NetworkIndicator-panel p.ucni-host[counter]::before{ content: attr(spdy); color: #FFF; font-weight: bold; font-size:75%; display: block; top:1px; background: #6080DF; border-radius: 3px; float: right; padding: 0 2px; margin: 3px 0 2px; } #NetworkIndicator-panel p.ucni-host[counter]::before{ float: left; background: #FF9900; content: attr(counter); } #NetworkIndicator-panel p.ucni-hover:not(:hover){ text-decoration:underline wavy orange; } #NetworkIndicator-panel p.ucni-host.ucni-hover{ color: blue; text-shadow:0 0 1px rgba(0, 0, 255, .4); } #NetworkIndicator-panel[overflowY] .panel-arrowcontent{ height: 400px!important; overflow-y: scroll; } #NetworkIndicator-panel[overflowY] ul{ position: relative; } #NetworkIndicator-panel[overflowY] #ucni-mplocation{ position: sticky; top:-5px; margin-top: -5px; border-top:5px #FFF solid; } }`), null, null), sss.AGENT_SHEET); } }; networkIndicator.init(); }