// ==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();
}