12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052 |
- /*******************************************************************************
- ηMatrix - a browser extension to black/white list requests.
- Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors
- Copyright (C) 2019-2022 Alessio Vanni
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see {http://www.gnu.org/licenses/}.
- Home: https://gitlab.com/vannilla/ematrix
- uMatrix Home: https://github.com/gorhill/uMatrix
- */
- /* jshint bitwise: false, esnext: true */
- /* global self, Components */
- // For background page
- 'use strict';
- /******************************************************************************/
- (function () {
- Cu.import('chrome://ematrix/content/lib/HttpRequestHeaders.jsm');
- Cu.import('chrome://ematrix/content/lib/PendingRequests.jsm');
- Cu.import('chrome://ematrix/content/lib/Punycode.jsm');
- Cu.import('chrome://ematrix/content/lib/HostMap.jsm');
- // Icon-related stuff
- vAPI.setIcon = function (tabId, iconId, badge) {
- // If badge is undefined, then setIcon was called from the
- // TabSelect event
- let win;
- if (badge === undefined) {
- win = iconId;
- } else {
- win = vAPI.window.getCurrentWindow();
- }
- let tabBrowser = vAPI.browser.getTabBrowser(win);
- if (tabBrowser === null) {
- return;
- }
- let curTabId = vAPI.tabs.manager.tabIdFromTarget(tabBrowser.selectedTab);
- let tb = vAPI.toolbarButton;
- // from 'TabSelect' event
- if (tabId === undefined) {
- tabId = curTabId;
- } else if (badge !== undefined) {
- tb.tabs[tabId] = {
- badge: badge, img: iconId
- };
- }
- if (tabId === curTabId) {
- tb.updateState(win, tabId);
- }
- };
- vAPI.httpObserver = {
- classDescription: 'net-channel-event-sinks for ' + location.host,
- classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'),
- contractID: '@' + location.host + '/net-channel-event-sinks;1',
- REQDATAKEY: location.host + 'reqdata',
- ABORT: Components.results.NS_BINDING_ABORTED,
- ACCEPT: Components.results.NS_SUCCEEDED,
- // Request types:
- // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants
- // eMatrix: we can just use nsIContentPolicy's built-in
- // constants, can we?
- frameTypeMap: {
- 6: 'main_frame',
- 7: 'sub_frame'
- },
- typeMap: {
- 1: 'other',
- 2: 'script',
- 3: 'image',
- 4: 'stylesheet',
- 5: 'object',
- 6: 'main_frame',
- 7: 'sub_frame',
- 9: 'xbl',
- 10: 'ping',
- 11: 'xmlhttprequest',
- 12: 'object',
- 13: 'xml_dtd',
- 14: 'font',
- 15: 'media',
- 16: 'websocket',
- 17: 'csp_report',
- 18: 'xslt',
- 19: 'beacon',
- 20: 'xmlhttprequest',
- 21: 'imageset',
- 22: 'web_manifest'
- },
- mimeTypeMap: {
- 'audio': 15,
- 'video': 15
- },
- get componentRegistrar () {
- return Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
- },
- get categoryManager () {
- return Cc['@mozilla.org/categorymanager;1']
- .getService(Ci.nsICategoryManager);
- },
- QueryInterface: (function () {
- var {XPCOMUtils} =
- Cu.import('resource://gre/modules/XPCOMUtils.jsm', null);
- return XPCOMUtils.generateQI([
- Ci.nsIFactory,
- Ci.nsIObserver,
- Ci.nsIChannelEventSink,
- Ci.nsISupportsWeakReference
- ]);
- })(),
- createInstance: function (outer, iid) {
- if (outer) {
- throw Components.results.NS_ERROR_NO_AGGREGATION;
- }
- return this.QueryInterface(iid);
- },
- register: function () {
- Services.obs.addObserver(this, 'http-on-modify-request', true);
- Services.obs.addObserver(this, 'http-on-examine-response', true);
- Services.obs.addObserver(this, 'http-on-examine-cached-response', true);
- // Guard against stale instances not having been unregistered
- if (this.componentRegistrar.isCIDRegistered(this.classID)) {
- try {
- this.componentRegistrar
- .unregisterFactory(this.classID,
- Components.manager
- .getClassObject(this.classID,
- Ci.nsIFactory));
- } catch (ex) {
- console.error('eMatrix> httpObserver > '
- +'unable to unregister stale instance: ', ex);
- }
- }
- this.componentRegistrar.registerFactory(this.classID,
- this.classDescription,
- this.contractID,
- this);
- this.categoryManager.addCategoryEntry('net-channel-event-sinks',
- this.contractID,
- this.contractID,
- false,
- true);
- },
- unregister: function () {
- Services.obs.removeObserver(this, 'http-on-modify-request');
- Services.obs.removeObserver(this, 'http-on-examine-response');
- Services.obs.removeObserver(this, 'http-on-examine-cached-response');
- this.componentRegistrar.unregisterFactory(this.classID, this);
- this.categoryManager.deleteCategoryEntry('net-channel-event-sinks',
- this.contractID,
- false);
- },
- handleRequest: function (channel, URI, tabId, rawType) {
- let type = this.typeMap[rawType] || 'other';
- let onBeforeRequest = vAPI.net.onBeforeRequest;
- if (onBeforeRequest.types === null
- || onBeforeRequest.types.has(type)) {
- let result = onBeforeRequest.callback({
- parentFrameId: type === 'main_frame' ? -1 : 0,
- tabId: tabId,
- type: type,
- url: URI.asciiSpec
- });
- if (typeof result === 'object') {
- channel.cancel(this.ABORT);
- return true;
- }
- }
- let onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders;
- if (onBeforeSendHeaders.types === null
- || onBeforeSendHeaders.types.has(type)) {
- let requestHeaders = HTTPRequestHeaders.factory(channel);
- let newHeaders = onBeforeSendHeaders.callback({
- parentFrameId: type === 'main_frame' ? -1 : 0,
- requestHeaders: requestHeaders.headers,
- tabId: tabId,
- type: type,
- url: URI.asciiSpec,
- method: channel.requestMethod
- });
- if (newHeaders) {
- requestHeaders.update();
- }
- requestHeaders.dispose();
- }
- return false;
- },
- channelDataFromChannel: function (channel) {
- if (channel instanceof Ci.nsIWritablePropertyBag) {
- try {
- return channel.getProperty(this.REQDATAKEY) || null;
- } catch (ex) {
- // Ignore
- }
- }
- return null;
- },
- // https://github.com/gorhill/uMatrix/issues/165
- // https://developer.mozilla.org/en-US/Firefox/Releases/3.5/Updating_extensions#Getting_a_load_context_from_a_request
- // Not sure `ematrix:shouldLoad` is still needed, eMatrix does
- // not care about embedded frames topography.
- // Also:
- // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts
- tabIdFromChannel: function (channel) {
- let lc;
- try {
- lc = channel.notificationCallbacks.getInterface(Ci.nsILoadContext);
- } catch(ex) {
- // Ignore
- }
- if (!lc) {
- try {
- lc = channel.loadGroup.notificationCallbacks
- .getInterface(Ci.nsILoadContext);
- } catch(ex) {
- // Ignore
- }
- if (!lc) {
- return vAPI.noTabId;
- }
- }
- if (lc.topFrameElement) {
- return vAPI.tabs.manager.tabIdFromTarget(lc.topFrameElement);
- }
- let win;
- try {
- win = lc.associatedWindow;
- } catch (ex) {
- // Ignore
- }
- if (!win) {
- return vAPI.noTabId;
- }
- if (win.top) {
- win = win.top;
- }
- let tabBrowser;
- try {
- tabBrowser =
- vAPI.browser.getTabBrowser
- (win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell).rootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindow));
- } catch (ex) {
- // Ignore
- }
- if (!tabBrowser) {
- return vAPI.noTabId;
- }
- if (tabBrowser.getBrowserForContentWindow) {
- return vAPI.tabs.manager
- .tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win));
- }
- // Falling back onto _getTabForContentWindow to ensure older
- // versions of Firefox work well.
- return tabBrowser._getTabForContentWindow
- ? vAPI.tabs.manager
- .tabIdFromTarget(tabBrowser._getTabForContentWindow(win))
- : vAPI.noTabId;
- },
- rawtypeFromContentType: function (channel) {
- let mime = channel.contentType;
- if (!mime) {
- return 0;
- }
- let pos = mime.indexOf('/');
- if (pos === -1) {
- pos = mime.length;
- }
- return this.mimeTypeMap[mime.slice(0, pos)] || 0;
- },
- operate: function (channel, URI, topic) {
- let channelData = this.channelDataFromChannel(channel);
- if (topic.lastIndexOf('http-on-examine-', 0) === 0) {
- if (channelData === null) {
- return;
- }
- let type = this.frameTypeMap[channelData[1]];
- if (!type) {
- return;
- }
- // topic = ['Content-Security-Policy',
- // 'Content-Security-Policy-Report-Only'];
- //
- // Can send empty responseHeaders as these headers are
- // only added to and then merged.
- //
- // TODO: Find better place for this, needs to be set
- // before onHeadersReceived.callback. Web workers not
- // blocked in Pale Moon as child-src currently
- // unavailable, see:
- //
- // https://github.com/MoonchildProductions/Pale-Moon/issues/949
- //
- // eMatrix: as of Pale Moon 28 it seems child-src is
- // available and depracated(?)
- if (ηMatrix.cspNoWorker === undefined) {
- // ηMatrix.cspNoWorker = "child-src 'none'; "
- // +"frame-src data: blob: *; "
- // +"report-uri about:blank";
- ηMatrix.cspNoWorker = "worker-src 'none'; "
- +"frame-src data: blob: *; "
- +"report-uri about:blank";
- }
- let result = vAPI.net.onHeadersReceived.callback({
- parentFrameId: type === 'main_frame' ? -1 : 0,
- responseHeaders: [],
- tabId: channelData[0],
- type: type,
- url: URI.asciiSpec
- });
- if (result) {
- for (let header of result.responseHeaders) {
- channel.setResponseHeader(header.name,
- header.value,
- true);
- }
- }
- return;
- }
- // http-on-modify-request
- // The channel was previously serviced.
- if (channelData !== null) {
- this.handleRequest(channel, URI,
- channelData[0], channelData[1]);
- return;
- }
- // The channel was never serviced.
- let tabId;
- let pendingRequest =
- PendingRequestBuffer.lookupRequest(URI.asciiSpec);
- let rawType = 1;
- let loadInfo = channel.loadInfo;
- // https://github.com/gorhill/uMatrix/issues/390#issuecomment-155717004
- if (loadInfo) {
- rawType = (loadInfo.externalContentPolicyType !== undefined)
- ? loadInfo.externalContentPolicyType
- : loadInfo.contentPolicyType;
- if (!rawType) {
- rawType = 1;
- }
- }
- if (pendingRequest !== null) {
- tabId = pendingRequest.tabId;
- // https://github.com/gorhill/uBlock/issues/654
- // Use the request type from the HTTP observer point of view.
- if (rawType !== 1) {
- pendingRequest.rawType = rawType;
- } else {
- rawType = pendingRequest.rawType;
- }
- } else {
- tabId = this.tabIdFromChannel(channel);
- }
- if (this.handleRequest(channel, URI, tabId, rawType)) {
- return;
- }
- if (channel instanceof Ci.nsIWritablePropertyBag === false) {
- return;
- }
- // Carry data for behind-the-scene redirects
- channel.setProperty(this.REQDATAKEY, [tabId, rawType]);
- },
- observe: function (channel, topic) {
- if (channel instanceof Ci.nsIHttpChannel === false) {
- return;
- }
- let URI = channel.URI;
- let channelData = this.channelDataFromChannel(channel);
- if (ηMatrix.userSettings.resolveCname === true) {
- let key = URI.scheme + '://' + URI.host;
- if (HostMap.get(key)) {
- this.operate(channel, HostMap.get(key), topic);
- return;
- }
- let CC = Components.classes;
- let CI = Components.interfaces;
- let tab = this.tabIdFromChannel(channel);
- let dns = CC['@mozilla.org/network/dns-service;1']
- .createInstance(CI.nsIDNSService);
- let listener = {
- onLookupComplete: function (req, rec, stat) {
- if (!Components.isSuccessCode(stat)) {
- console.error("can't resolve canonical name");
- return;
- }
- let addr = rec.canonicalName;
- ηMatrix.logger.writeOne(tab, 'info',
- URI.host + ' => ' + addr);
- let ios = CC['@mozilla.org/network/io-service;1']
- .createInstance(CI.nsIIOService);
- let uri = ios.newURI(URI.scheme+'://'+addr, null, null);
- HostMap.put(key, uri);
- vAPI.httpObserver.operate(channel, uri, topic);
- },
- };
- dns.asyncResolve(URI.host,
- CI.nsIDNSService.RESOLVE_CANONICAL_NAME,
- listener,
- null);
- } else {
- this.operate(channel, URI, topic);
- }
- },
- asyncOnChannelRedirect: function (oldChannel, newChannel,
- flags, callback) {
- // contentPolicy.shouldLoad doesn't detect redirects, this
- // needs to be used
- // If error thrown, the redirect will fail
- try {
- let URI = newChannel.URI;
- if (!URI.schemeIs('http') && !URI.schemeIs('https')) {
- return;
- }
- if (newChannel instanceof Ci.nsIWritablePropertyBag === false) {
- return;
- }
- let channelData = this.channelDataFromChannel(oldChannel);
- if (channelData === null) {
- return;
- }
- // Carry the data on in case of multiple redirects
- newChannel.setProperty(this.REQDATAKEY, channelData);
- } catch (ex) {
- // console.error(ex);
- // Ignore
- } finally {
- callback.onRedirectVerifyCallback(this.ACCEPT);
- }
- }
- };
- vAPI.toolbarButton = {
- id: location.host + '-button',
- type: 'view',
- viewId: location.host + '-panel',
- label: vAPI.app.name,
- tooltiptext: vAPI.app.name,
- tabs: {/*tabId: {badge: 0, img: boolean}*/},
- init: null,
- codePath: ''
- };
- (function () {
- let tbb = vAPI.toolbarButton;
- let popupCommittedWidth = 0;
- let popupCommittedHeight = 0;
- tbb.onViewShowing = function ({target}) {
- popupCommittedWidth = popupCommittedHeight = 0;
- target.firstChild.setAttribute('src', vAPI.getURL('popup.html'));
- };
- tbb.onViewHiding = function ({target}) {
- target.parentNode.style.maxWidth = '';
- target.firstChild.setAttribute('src', 'about:blank');
- };
- tbb.updateState = function (win, tabId) {
- let button = win.document.getElementById(this.id);
- if (!button) {
- return;
- }
- let icon = this.tabs[tabId];
- button.setAttribute('badge', icon && icon.badge || '');
- button.classList.toggle('off', !icon || !icon.img);
- let iconId = (ηMatrix.userSettings.disableUpdateIcon) ?
- icon && icon.img ? '19' : 'off' :
- icon && icon.img ? icon.img : 'off';
- icon = 'url('
- + vAPI.getURL('img/browsericons/icon19-'
- + iconId
- + '.png')
- + ')';
- button.style.listStyleImage = icon;
- };
- tbb.populatePanel = function (doc, panel) {
- panel.setAttribute('id', this.viewId);
- let iframe = doc.createElement('iframe');
- iframe.setAttribute('type', 'content');
- panel.appendChild(iframe);
- let toPx = function (pixels) {
- return pixels.toString() + 'px';
- };
- let scrollBarWidth = 0;
- let resizeTimer = null;
- let resizePopupDelayed = function (attempts) {
- if (resizeTimer !== null) {
- return false;
- }
- // Sanity check
- attempts = (attempts || 0) + 1;
- if ( attempts > 1/*000*/ ) {
- //console.error('eMatrix> resizePopupDelayed: giving up after too many attempts');
- return false;
- }
- resizeTimer = vAPI.setTimeout(resizePopup, 10, attempts);
- return true;
- };
- let resizePopup = function (attempts) {
- resizeTimer = null;
- panel.parentNode.style.maxWidth = 'none';
- let body = iframe.contentDocument.body;
- // https://github.com/gorhill/uMatrix/issues/301
- // Don't resize if committed size did not change.
- if (popupCommittedWidth === body.clientWidth
- && popupCommittedHeight === body.clientHeight) {
- return;
- }
- // We set a limit for height
- let height = Math.min(body.clientHeight, 600);
- // https://github.com/chrisaljoudi/uBlock/issues/730
- // Voodoo programming: this recipe works
- panel.style.setProperty('height', toPx(height));
- iframe.style.setProperty('height', toPx(height));
- // Adjust width for presence/absence of vertical scroll bar which may
- // have appeared as a result of last operation.
- let contentWindow = iframe.contentWindow;
- let width = body.clientWidth;
- if (contentWindow.scrollMaxY !== 0) {
- width += scrollBarWidth;
- }
- panel.style.setProperty('width', toPx(width));
- // scrollMaxX should always be zero once we know the scrollbar width
- if (contentWindow.scrollMaxX !== 0) {
- scrollBarWidth = contentWindow.scrollMaxX;
- width += scrollBarWidth;
- panel.style.setProperty('width', toPx(width));
- }
- if (iframe.clientHeight !== height
- || panel.clientWidth !== width) {
- if (resizePopupDelayed(attempts)) {
- return;
- }
- // resizePopupDelayed won't be called again, so commit
- // dimentsions.
- }
- popupCommittedWidth = body.clientWidth;
- popupCommittedHeight = body.clientHeight;
- };
- let onResizeRequested = function () {
- let body = iframe.contentDocument.body;
- if (body.getAttribute('data-resize-popup') !== 'true') {
- return;
- }
- body.removeAttribute('data-resize-popup');
- resizePopupDelayed();
- };
- let onPopupReady = function () {
- let win = this.contentWindow;
- if (!win || win.location.host !== location.host) {
- return;
- }
- if (typeof tbb.onBeforePopupReady === 'function') {
- tbb.onBeforePopupReady.call(this);
- }
- resizePopupDelayed();
- let body = win.document.body;
- body.removeAttribute('data-resize-popup');
- let mutationObserver =
- new win.MutationObserver(onResizeRequested);
- mutationObserver.observe(body, {
- attributes: true,
- attributeFilter: [ 'data-resize-popup' ]
- });
- };
- iframe.addEventListener('load', onPopupReady, true);
- };
- })();
- /******************************************************************************/
- (function () {
- let tbb = vAPI.toolbarButton;
- if (tbb.init !== null) {
- return;
- }
- tbb.codePath = 'legacy';
- tbb.viewId = tbb.id + '-panel';
- let styleSheetUri = null;
- let createToolbarButton = function (window) {
- let document = window.document;
- let toolbarButton = document.createElement('toolbarbutton');
- toolbarButton.setAttribute('id', tbb.id);
- // type = panel would be more accurate, but doesn't look as good
- toolbarButton.setAttribute('type', 'menu');
- toolbarButton.setAttribute('removable', 'true');
- toolbarButton.setAttribute('class', 'toolbarbutton-1 '
- +'chromeclass-toolbar-additional');
- toolbarButton.setAttribute('label', tbb.label);
- toolbarButton.setAttribute('tooltiptext', tbb.tooltiptext);
- let toolbarButtonPanel = document.createElement('panel');
- // NOTE: Setting level to parent breaks the popup for PaleMoon under
- // linux (mouse pointer misaligned with content). For some reason.
- // eMatrix: TODO check if it's still true
- // toolbarButtonPanel.setAttribute('level', 'parent');
- tbb.populatePanel(document, toolbarButtonPanel);
- toolbarButtonPanel.addEventListener('popupshowing',
- tbb.onViewShowing);
- toolbarButtonPanel.addEventListener('popuphiding',
- tbb.onViewHiding);
- toolbarButton.appendChild(toolbarButtonPanel);
- toolbarButtonPanel.setAttribute('tooltiptext', '');
- return toolbarButton;
- };
- let addLegacyToolbarButton = function (window) {
- // eMatrix's stylesheet lazily added.
- if (styleSheetUri === null) {
- var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
- .getService(Ci.nsIStyleSheetService);
- styleSheetUri = Services.io
- .newURI(vAPI.getURL("css/legacy-toolbar-button.css"),
- null, null);
- // Register global so it works in all windows, including palette
- if (!sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) {
- sss.loadAndRegisterSheet(styleSheetUri, sss.AUTHOR_SHEET);
- }
- }
- let document = window.document;
- // https://github.com/gorhill/uMatrix/issues/357
- // Already installed?
- if (document.getElementById(tbb.id) !== null) {
- return;
- }
- let toolbox = document.getElementById('navigator-toolbox')
- || document.getElementById('mail-toolbox');
- if (toolbox === null) {
- return;
- }
- let toolbarButton = createToolbarButton(window);
- let palette = toolbox.palette;
- if (palette && palette.querySelector('#' + tbb.id) === null) {
- palette.appendChild(toolbarButton);
- }
- // Find the place to put the button.
- // Pale Moon: `toolbox.externalToolbars` can be
- // undefined. Seen while testing popup test number 3:
- // http://raymondhill.net/ublock/popup.html
- let toolbars = toolbox.externalToolbars
- ? toolbox.externalToolbars.slice()
- : [];
- for (let child of toolbox.children) {
- if (child.localName === 'toolbar') {
- toolbars.push(child);
- }
- }
- for (let toolbar of toolbars) {
- let currentsetString = toolbar.getAttribute('currentset');
- if (!currentsetString) {
- continue;
- }
- let currentset = currentsetString.split(/\s*,\s*/);
- let index = currentset.indexOf(tbb.id);
- if (index === -1) {
- continue;
- }
- // This can occur with Pale Moon:
- // "TypeError: toolbar.insertItem is not a function"
- if (typeof toolbar.insertItem !== 'function') {
- continue;
- }
- // Found our button on this toolbar - but where on it?
- let before = null;
- for (let i=index+1; i<currentset.length; ++i) {
- // The [id=...] notation doesn't work on
- // space elements as they get a random ID each session
- // (or something like that)
- // https://gitlab.com/vannilla/ematrix/issues/5
- // https://gitlab.com/vannilla/ematrix/issues/6
- // Based on JustOff's snippet from the Pale Moon
- // forum. It was reorganized because I find it
- // more readable like this, but he did most of the
- // work.
- let space = /^(spring|spacer|separator)$/.exec(currentset[i]);
- if (space !== null) {
- let elems = toolbar.querySelectorAll('toolbar'+space[1]);
- let count = currentset.slice(i-currentset.length)
- .filter(function (x) {return x == space[1];})
- .length;
- before =
- toolbar.querySelector('[id="'
- + elems[elems.length-count].id
- + '"]');
- } else {
- before = toolbar.querySelector('[id="'+currentset[i]+'"]');
- }
- if ( before !== null ) {
- break;
- }
- }
- toolbar.insertItem(tbb.id, before);
- break;
- }
- // https://github.com/gorhill/uBlock/issues/763
- // We are done if our toolbar button is already installed
- // in one of the toolbar.
- if (palette !== null && toolbarButton.parentElement !== palette) {
- return;
- }
- // No button yet so give it a default location. If forcing
- // the button, just put in in the palette rather than on
- // any specific toolbar (who knows what toolbars will be
- // available or visible!)
- let navbar = document.getElementById('nav-bar');
- if (navbar !== null
- && !vAPI.localStorage.getBool('legacyToolbarButtonAdded')) {
- // https://github.com/gorhill/uBlock/issues/264
- // Find a child customizable palette, if any.
- navbar = navbar.querySelector('.customization-target') || navbar;
- navbar.appendChild(toolbarButton);
- navbar.setAttribute('currentset', navbar.currentSet);
- document.persist(navbar.id, 'currentset');
- vAPI.localStorage.setBool('legacyToolbarButtonAdded', 'true');
- }
- };
- let canAddLegacyToolbarButton = function (window) {
- let document = window.document;
- if (!document
- || document.readyState !== 'complete'
- || document.getElementById('nav-bar') === null) {
- return false;
- }
- let toolbox = document.getElementById('navigator-toolbox')
- || document.getElementById('mail-toolbox');
- return toolbox !== null && !!toolbox.palette;
- };
- let onPopupCloseRequested = function ({target}) {
- let document = target.ownerDocument;
- if (!document) {
- return;
- }
- let toolbarButtonPanel = document.getElementById(tbb.viewId);
- if (toolbarButtonPanel === null) {
- return;
- }
- // `hidePopup` reported as not existing while testing
- // legacy button on FF 41.0.2.
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1151796
- if (typeof toolbarButtonPanel.hidePopup === 'function') {
- toolbarButtonPanel.hidePopup();
- }
- };
- let shutdown = function () {
- for (let win of vAPI.window.getWindows()) {
- let toolbarButton = win.document.getElementById(tbb.id);
- if (toolbarButton) {
- toolbarButton.parentNode.removeChild(toolbarButton);
- }
- }
- if (styleSheetUri !== null) {
- var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
- .getService(Ci.nsIStyleSheetService);
- if (sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) {
- sss.unregisterSheet(styleSheetUri, sss.AUTHOR_SHEET);
- }
- styleSheetUri = null;
- }
- vAPI.messaging.globalMessageManager
- .removeMessageListener(location.host + ':closePopup',
- onPopupCloseRequested);
- };
- tbb.attachToNewWindow = function (win) {
- vAPI.deferUntil(canAddLegacyToolbarButton.bind(null, win),
- addLegacyToolbarButton.bind(null, win));
- };
- tbb.init = function () {
- vAPI.messaging.globalMessageManager
- .addMessageListener(location.host + ':closePopup',
- onPopupCloseRequested);
- vAPI.addCleanUpTask(shutdown);
- };
- })();
- // No toolbar button.
- (function () {
- // Just to ensure the number of cleanup tasks is as expected: toolbar
- // button code is one single cleanup task regardless of platform.
- // eMatrix: might not be needed anymore
- if (vAPI.toolbarButton.init === null) {
- vAPI.addCleanUpTask(function(){});
- }
- })();
- if (vAPI.toolbarButton.init !== null) {
- vAPI.toolbarButton.init();
- }
- let optionsObserver = (function () {
- let addonId = 'eMatrix@vannilla.org';
- let commandHandler = function () {
- switch (this.id) {
- case 'showDashboardButton':
- vAPI.tabs.open({
- url: 'dashboard.html',
- index: -1,
- });
- break;
- case 'showLoggerButton':
- vAPI.tabs.open({
- url: 'logger-ui.html',
- index: -1,
- });
- break;
- default:
- break;
- }
- };
- let setupOptionsButton = function (doc, id) {
- let button = doc.getElementById(id);
- if (button === null) {
- return;
- }
- button.addEventListener('command', commandHandler);
- button.label = vAPI.i18n(id);
- };
- let setupOptionsButtons = function (doc) {
- setupOptionsButton(doc, 'showDashboardButton');
- setupOptionsButton(doc, 'showLoggerButton');
- };
- let observer = {
- observe: function (doc, topic, id) {
- if (id !== addonId) {
- return;
- }
- setupOptionsButtons(doc);
- }
- };
- var canInit = function() {
- // https://github.com/gorhill/uBlock/issues/948
- // Older versions of Firefox can throw here when looking
- // up `currentURI`.
- try {
- let tabBrowser = vAPI.tabs.manager.currentBrowser();
- return tabBrowser
- && tabBrowser.currentURI
- && tabBrowser.currentURI.spec === 'about:addons'
- && tabBrowser.contentDocument
- && tabBrowser.contentDocument.readyState === 'complete';
- } catch (ex) {
- // Ignore
- }
- };
- // Manually add the buttons if the `about:addons` page is
- // already opened.
- let init = function () {
- if (canInit()) {
- setupOptionsButtons(vAPI.tabs.manager
- .currentBrowser().contentDocument);
- }
- };
- let unregister = function () {
- Services.obs.removeObserver(observer, 'addon-options-displayed');
- };
- let register = function () {
- Services.obs.addObserver(observer,
- 'addon-options-displayed',
- false);
- vAPI.addCleanUpTask(unregister);
- vAPI.deferUntil(canInit, init, { next: 463 });
- };
- return {
- register: register,
- unregister: unregister
- };
- })();
- optionsObserver.register();
- vAPI.onLoadAllCompleted = function() {
- // This is called only once, when everything has been loaded
- // in memory after the extension was launched. It can be used
- // to inject content scripts in already opened web pages, to
- // remove whatever nuisance could make it to the web pages
- // before uBlock was ready.
- for (let browser of vAPI.tabs.manager.browsers()) {
- browser.messageManager
- .sendAsyncMessage(location.host + '-load-completed');
- }
- };
- // Likelihood is that we do not have to punycode: given punycode overhead,
- // it's faster to check and skip than do it unconditionally all the time.
- var punycodeHostname = Punycode.toASCII;
- var isNotASCII = /[^\x21-\x7F]/;
- vAPI.punycodeHostname = function (hostname) {
- return isNotASCII.test(hostname)
- ? punycodeHostname(hostname)
- : hostname;
- };
- vAPI.punycodeURL = function (url) {
- if (isNotASCII.test(url)) {
- return Services.io.newURI(url, null, null).asciiSpec;
- }
- return url;
- };
- })();
|