diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index 0875d0f..32e082c 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -51,6 +51,33 @@ vAPI.modernFirefox = Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97 /******************************************************************************/ +var deferUntil = function(testFn, mainFn, details) { + if ( typeof details !== 'object' ) { + details = {}; + } + + var now = 0; + var next = details.next || 200; + var until = details.until || 2000; + + var check = function() { + if ( testFn() === true || now >= until ) { + mainFn(); + return; + } + now += next; + vAPI.setTimeout(check, next); + }; + + if ( 'sync' in details && details.sync === true ) { + check(); + } else { + vAPI.setTimeout(check, 1); + } +}; + +/******************************************************************************/ + vAPI.app = { name: 'uMatrix', version: location.hash.slice(1) @@ -558,7 +585,6 @@ vAPI.storage = (function() { // This must be executed/setup early. var winWatcher = (function() { - var chromeWindowType = vAPI.thunderbird ? 'mail:3pane' : 'navigator:browser'; var windowToIdMap = new Map(); var windowIdGenerator = 1; var api = { @@ -566,6 +592,24 @@ var winWatcher = (function() { onCloseWindow: null }; + // https://github.com/gorhill/uMatrix/issues/586 + // This is necessary hack because on SeaMonkey 2.40, for unknown reasons + // private windows do not have the attribute `windowtype` set to + // `navigator:browser`. As a fallback, the code here will also test whether + // the id attribute is `main-window`. + api.toBrowserWindow = function(win) { + var docElement = win && win.document && win.document.documentElement; + if ( !docElement ) { + return null; + } + if ( vAPI.thunderbird ) { + return docElement.getAttribute('windowtype') === 'mail:3pane' ? win : null; + } + return docElement.getAttribute('windowtype') === 'navigator:browser' || + docElement.getAttribute('id') === 'main-window' ? + win : null; + }; + api.getWindows = function() { return windowToIdMap.keys(); }; @@ -575,7 +619,7 @@ var winWatcher = (function() { }; api.getCurrentWindow = function() { - return Services.wm.getMostRecentWindow(chromeWindowType) || null; + return this.toBrowserWindow(Services.wm.getMostRecentWindow(null)); }; var addWindow = function(win) { @@ -761,7 +805,7 @@ vAPI.tabs.get = function(tabId, callback) { return browser; } - if ( !browser ) { + if ( !browser || !browser.currentURI ) { callback(); return; } @@ -1224,56 +1268,12 @@ var tabWatcher = (function() { }); }; - var attachToTabBrowserLater = function(details) { - details.tryCount = details.tryCount ? details.tryCount + 1 : 1; - if ( details.tryCount > 8 ) { - return false; - } - vAPI.setTimeout(function(details) { - attachToTabBrowser(details.window, details.tryCount); - }, - 200, - details - ); - return true; - }; - - var attachToTabBrowser = function(window, tryCount) { - // Let's just be extra-paranoiac regarding whether all is right before - // trying to attach outself to the browser window. - var document = window && window.document; - var docElement = document && document.documentElement; - var wintype = docElement && docElement.getAttribute('windowtype'); - - if ( wintype !== 'navigator:browser' ) { - attachToTabBrowserLater({ window: window, tryCount: tryCount }); - return; - } - - // https://github.com/gorhill/uBlock/issues/906 - // This might have been the cause. Will see. - if ( document.readyState !== 'complete' ) { - attachToTabBrowserLater({ window: window, tryCount: tryCount }); - return; - } - - // On some platforms, the tab browser isn't immediately available, - // try waiting a bit if this happens. - // https://github.com/gorhill/uBlock/issues/763 - // Not getting a tab browser should not prevent from attaching ourself - // to the window. - var tabBrowser = getTabBrowser(window); - if ( - tabBrowser === null && - attachToTabBrowserLater({ window: window, tryCount: tryCount }) - ) { - return; - } - + var attachToTabBrowser = function(window) { if ( typeof vAPI.toolbarButton.attachToNewWindow === 'function' ) { vAPI.toolbarButton.attachToNewWindow(window); } + var tabBrowser = getTabBrowser(window); if ( tabBrowser === null ) { return; } @@ -1291,7 +1291,6 @@ var tabWatcher = (function() { // not set when a tab is opened as a result of session restore -- it is // set *after* the event is fired in such case. if ( tabContainer ) { - //tabContainer.addEventListener('TabOpen', onOpen); tabContainer.addEventListener('TabShow', onShow); tabContainer.addEventListener('TabClose', onClose); // when new window is opened TabSelect doesn't run on the selected tab? @@ -1299,8 +1298,32 @@ var tabWatcher = (function() { } }; + // https://github.com/gorhill/uBlock/issues/906 + // Ensure the environment is ready before trying to attaching. + var canAttachToTabBrowser = function(window) { + var document = window && window.document; + if ( !document || document.readyState !== 'complete' ) { + return false; + } + + // On some platforms, the tab browser isn't immediately available, + // try waiting a bit if this happens. + // https://github.com/gorhill/uBlock/issues/763 + // Not getting a tab browser should not prevent from attaching ourself + // to the window. + var tabBrowser = getTabBrowser(window); + if ( tabBrowser === null ) { + return false; + } + + return winWatcher.toBrowserWindow(window) !== null; + }; + var onWindowLoad = function(win) { - attachToTabBrowser(win); + deferUntil( + canAttachToTabBrowser.bind(null, win), + attachToTabBrowser.bind(null, win) + ); }; var onWindowUnload = function(win) { @@ -1313,7 +1336,6 @@ var tabWatcher = (function() { var tabContainer = tabBrowser.tabContainer; if ( tabContainer ) { - //tabContainer.removeEventListener('TabOpen', onOpen); tabContainer.removeEventListener('TabShow', onShow); tabContainer.removeEventListener('TabClose', onClose); tabContainer.removeEventListener('TabSelect', onSelect); @@ -2316,59 +2338,11 @@ vAPI.toolbarButton = { tbb.id = 'umatrix-legacy-button'; // NOTE: must match legacy-toolbar-button.css tbb.viewId = tbb.id + '-panel'; - var sss = null; var styleSheetUri = null; - var addLegacyToolbarButtonLater = function(details) { - details.tryCount = details.tryCount ? details.tryCount + 1 : 1; - if ( details.tryCount > 8 ) { - return false; - } - vAPI.setTimeout(function(details) { - addLegacyToolbarButton(details.window, details.tryCount); - }, - 200, - details - ); - return true; - }; - - var addLegacyToolbarButton = function(window, tryCount) { + var createToolbarButton = function(window) { var document = window.document; - // https://github.com/gorhill/uMatrix/issues/357 - // Already installed? - if ( document.getElementById(tbb.id) !== null ) { - return; - } - - var toolbox = document.getElementById('navigator-toolbox') || - document.getElementById('mail-toolbox'); - if ( - toolbox === null && - addLegacyToolbarButtonLater({ window: window, tryCount: tryCount }) - ) { - return; - } - - // palette might take a little longer to appear on some platforms, - // give it a small delay and try again. - var palette = toolbox.palette; - if ( - palette === null && - addLegacyToolbarButtonLater({ window: window, tryCount: tryCount }) - ) { - return; - } - - var navbar = document.getElementById('nav-bar'); - if ( - navbar === null && - addLegacyToolbarButtonLater({ window: window, tryCount: tryCount }) - ) { - return; - } - var toolbarButton = document.createElement('toolbarbutton'); toolbarButton.setAttribute('id', tbb.id); // type = panel would be more accurate, but doesn't look as good @@ -2387,21 +2361,49 @@ vAPI.toolbarButton = { toolbarButtonPanel.addEventListener('popuphiding', tbb.onViewHiding); toolbarButton.appendChild(toolbarButtonPanel); - if ( palette !== null && palette.querySelector('#' + tbb.id) === null ) { - palette.appendChild(toolbarButton); - } + return toolbarButton; + }; + + var addLegacyToolbarButton = function(window) { + // uMatrix'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); - tbb.closePopup = function() { - // `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(); + // Register global so it works in all windows, including palette + if ( !sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET) ) { + sss.loadAndRegisterSheet(styleSheetUri, sss.AUTHOR_SHEET); } - }; + } + + var document = window.document; + + // https://github.com/gorhill/uMatrix/issues/357 + // Already installed? + if ( document.getElementById(tbb.id) !== null ) { + return; + } + + var toolbox = document.getElementById('navigator-toolbox') || + document.getElementById('mail-toolbox'); + if ( toolbox === null ) { + return; + } - // Find the place to put the button - var toolbars = toolbox.externalToolbars.slice(); + var toolbarButton = createToolbarButton(window); + + // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/toolbarpalette + var 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 + var toolbars = toolbox.externalToolbars ? toolbox.externalToolbars.slice() : []; for ( var child of toolbox.children ) { if ( child.localName === 'toolbar' ) { toolbars.push(child); @@ -2418,6 +2420,11 @@ vAPI.toolbarButton = { 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? var before = null; for ( var i = index + 1; i < currentset.length; i++ ) { @@ -2426,6 +2433,7 @@ vAPI.toolbarButton = { break; } } + // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/insertItem toolbar.insertItem(tbb.id, before); break; } @@ -2440,6 +2448,7 @@ vAPI.toolbarButton = { // 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!) + var 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. @@ -2451,9 +2460,34 @@ vAPI.toolbarButton = { } }; + var canAddLegacyToolbarButton = function(window) { + var document = window.document; + if ( + !document || + document.readyState !== 'complete' || + document.getElementById('nav-bar') === null + ) { + return false; + } + var toolbox = document.getElementById('navigator-toolbox') || + document.getElementById('mail-toolbox'); + return toolbox !== null && !!toolbox.palette; + }; + var onPopupCloseRequested = function({target}) { - if ( typeof tbb.closePopup === 'function' ) { - tbb.closePopup(target); + var document = target.ownerDocument; + if ( !document ) { + return; + } + var 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(); } }; @@ -2480,7 +2514,10 @@ vAPI.toolbarButton = { }; tbb.attachToNewWindow = function(win) { - addLegacyToolbarButton(win); + deferUntil( + canAddLegacyToolbarButton.bind(null, win), + addLegacyToolbarButton.bind(null, win) + ); }; tbb.init = function() { @@ -2489,14 +2526,6 @@ vAPI.toolbarButton = { onPopupCloseRequested ); - 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); - } - cleanupTasks.push(shutdown); }; })(); @@ -3028,43 +3057,84 @@ vAPI.contextMenu.remove = function() { /******************************************************************************/ /******************************************************************************/ -var optionsObserver = { - addonId: 'uMatrix@raymondhill.net', - - register: function() { - Services.obs.addObserver(this, 'addon-options-displayed', false); - cleanupTasks.push(this.unregister.bind(this)); +var optionsObserver = (function() { + var addonId = 'uMatrix@raymondhill.net'; - var browser = tabWatcher.currentBrowser(); - if ( browser && browser.currentURI && browser.currentURI.spec === 'about:addons' ) { - this.observe(browser.contentDocument, 'addon-enabled', this.addonId); + var 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; } - }, - - unregister: function() { - Services.obs.removeObserver(this, 'addon-options-displayed'); - }, + }; - setupOptionsButton: function(doc, id, page) { + var setupOptionsButton = function(doc, id) { var button = doc.getElementById(id); if ( button === null ) { return; } - button.addEventListener('command', function() { - vAPI.tabs.open({ url: page, index: -1 }); - }); + button.addEventListener('command', commandHandler); button.label = vAPI.i18n(id); - }, + }; - observe: function(doc, topic, addonId) { - if ( addonId !== this.addonId ) { - return; + var setupOptionsButtons = function(doc) { + setupOptionsButton(doc, 'showDashboardButton'); + setupOptionsButton(doc, 'showLoggerButton'); + }; + + var observer = { + observe: function(doc, topic, id) { + if ( id !== addonId ) { + return; + } + + setupOptionsButtons(doc); } + }; - this.setupOptionsButton(doc, 'showDashboardButton', 'dashboard.html'); - this.setupOptionsButton(doc, 'showLoggerButton', 'logger-ui.html'); - } -}; + // https://github.com/gorhill/uBlock/issues/948 + // Older versions of Firefox can throw here when looking up `currentURI`. + + var canInit = function() { + try { + var tabBrowser = tabWatcher.currentBrowser(); + return tabBrowser && + tabBrowser.currentURI && + tabBrowser.currentURI.spec === 'about:addons' && + tabBrowser.contentDocument && + tabBrowser.contentDocument.readyState === 'complete'; + } catch (ex) { + } + }; + + // Manually add the buttons if the `about:addons` page is already opened. + + var init = function() { + if ( canInit() ) { + setupOptionsButtons(tabWatcher.currentBrowser().contentDocument); + } + }; + + var unregister = function() { + Services.obs.removeObserver(observer, 'addon-options-displayed'); + }; + + var register = function() { + Services.obs.addObserver(observer, 'addon-options-displayed', false); + cleanupTasks.push(unregister); + deferUntil(canInit, init, { next: 463 }); + }; + + return { + register: register, + unregister: unregister + }; +})(); optionsObserver.register();