From 4091cd3a1d45fe58bdf834dbaa4febd010c64f4a Mon Sep 17 00:00:00 2001 From: AlexVallat Date: Fri, 24 Apr 2015 18:57:30 +0100 Subject: [PATCH] Imported https://github.com/AlexVallat/uBlock/commit/5ac4ec21e9e960346b72889e842dbb6b0772aa76 --- .../firefox/css/legacy-toolbar-button.css | 37 ++ .../firefox/img/browsericons/icon32-off.svg | 12 + platform/firefox/img/browsericons/icon32.svg | 12 + platform/firefox/vapi-background.js | 316 ++++++++++++++++-- platform/firefox/vapi-common.js | 17 +- 5 files changed, 355 insertions(+), 39 deletions(-) create mode 100644 platform/firefox/css/legacy-toolbar-button.css create mode 100644 platform/firefox/img/browsericons/icon32-off.svg create mode 100644 platform/firefox/img/browsericons/icon32.svg diff --git a/platform/firefox/css/legacy-toolbar-button.css b/platform/firefox/css/legacy-toolbar-button.css new file mode 100644 index 000000000..2e2f34505 --- /dev/null +++ b/platform/firefox/css/legacy-toolbar-button.css @@ -0,0 +1,37 @@ +#uBlock-legacy-button { + list-style-image: url('../img/browsericons/icon32.svg'); +} +#uBlock-legacy-button.off { + list-style-image: url('../img/browsericons/icon32-off.svg'); +} + +toolbar[iconsize="small"] #uBlock-legacy-button { + list-style-image: url('../img/browsericons/icon16.svg'); +} +toolbar[iconsize="small"] #uBlock-legacy-button.off { + list-style-image: url('../img/browsericons/icon16-off.svg'); +} +#uBlock-legacy-button[badge]::before { + position: fixed; + margin-top: -2px; + padding: 1px 2px; + font-size: 9px; + font-weight: bold; + color: #fff; + background: #666; + content: attr(badge); +} +/* This hack required because if the before content changes it de-pops the popup (without firing any events). So just hide it instead. Note, can't actually *hide* it, or the same thing happens. '*/ +#uBlock-legacy-button[badge=""]::before { + padding: 0; +} + +/* Override off state when in palette */ +toolbarpaletteitem #uBlock-legacy-button.off { + list-style-image: url('../img/browsericons/icon32.svg'); +} + +/* Override badge when in palette */ +toolbarpaletteitem #uBlock-legacy-button[badge]::before { + content: none; +} \ No newline at end of file diff --git a/platform/firefox/img/browsericons/icon32-off.svg b/platform/firefox/img/browsericons/icon32-off.svg new file mode 100644 index 000000000..0b9030845 --- /dev/null +++ b/platform/firefox/img/browsericons/icon32-off.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/platform/firefox/img/browsericons/icon32.svg b/platform/firefox/img/browsericons/icon32.svg new file mode 100644 index 000000000..e70d65a3a --- /dev/null +++ b/platform/firefox/img/browsericons/icon32.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index 1ca8b737e..fecac3554 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -59,6 +59,11 @@ vAPI.app.restart = function() { /******************************************************************************/ +// Set default preferences for user to find in about:config +vAPI.localStorage.setDefaultBool("forceLegacyToolbarButton", false); + +/******************************************************************************/ + // List of things that needs to be destroyed when disabling the extension // Only functions should be added to it @@ -68,8 +73,8 @@ var cleanupTasks = []; // Fixed by github.com/AlexVallat: // https://github.com/AlexVallat/uBlock/commit/7b781248f00cbe3d61b1cc367c440db80fa06049 -// 8 instances of cleanupTasks.push, but one is unique to fennec, and one to desktop. -var expectedNumberOfCleanups = 7; +// several instances of cleanupTasks.push, but one is unique to fennec, and three to desktop. +var expectedNumberOfCleanups = vAPI.fennec ? 7 : 9; window.addEventListener('unload', function() { if ( typeof vAPI.app.onShutdown === 'function' ) { @@ -479,6 +484,88 @@ vAPI.storage = (function() { /******************************************************************************/ +var windowWatcher = { + onReady: function(e) { + if ( e ) { + this.removeEventListener(e.type, windowWatcher.onReady); + } + + var wintype = this.document.documentElement.getAttribute('windowtype'); + + if ( wintype !== 'navigator:browser' ) { + return; + } + + var attachToTabBrowser = function(window, tabBrowser) { + if (!tabBrowser) { + return; + } + + var tabContainer; + if ( tabBrowser.deck ) { + // Fennec + tabContainer = tabBrowser.deck; + } else if ( tabBrowser.tabContainer ) { + // desktop Firefox + tabContainer = tabBrowser.tabContainer; + vAPI.contextMenu.register(window.document); + if (vAPI.toolbarButton.attachToNewWindow) { + vAPI.toolbarButton.attachToNewWindow(window); + } + } else { + return; + } + + tabContainer.addEventListener('TabClose', tabWatcher.onTabClose); + tabContainer.addEventListener('TabSelect', tabWatcher.onTabSelect); + // when new window is opened TabSelect doesn't run on the selected tab? + } + + var win = this; + var tabBrowser = getTabBrowser(win); + if ( !tabBrowser ) { + // On some platforms, the tab browser isn't immediately available, try waiting a bit + win.setTimeout(function() { + attachToTabBrowser(win, getTabBrowser(win)); + }, 250); + } else { + attachToTabBrowser(win, tabBrowser); + } + + }, + + observe: function(win, topic) { + if ( topic === 'domwindowopened' ) { + win.addEventListener('DOMContentLoaded', this.onReady); + } + } +}; + +/******************************************************************************/ + +var tabWatcher = { + onTabClose: function({target}) { + // target is tab in Firefox, browser in Fennec + var tabId = vAPI.tabs.getTabId(target); + vAPI.tabs.onClosed(tabId); + delete vAPI.toolbarButton.tabs[tabId]; + }, + + onTabSelect: function({target}) { + vAPI.setIcon(vAPI.tabs.getTabId(target), getOwnerWindow(target)); + }, +}; + +/******************************************************************************/ + +vAPI.isBehindTheSceneTabId = function(tabId) { + return tabId.toString() === '-1'; +}; + +vAPI.noTabId = '-1'; + +/******************************************************************************/ + var getTabBrowser = function(win) { return vAPI.fennec && win.BrowserApp || win.gBrowser || null; }; @@ -1846,13 +1933,141 @@ vAPI.toolbarButton.init = function() { return; } + vAPI.messaging.globalMessageManager.addMessageListener( + location.host + ':closePopup', + vAPI.toolbarButton.onPopupCloseRequested + ); + + cleanupTasks.push(function() { + vAPI.messaging.globalMessageManager.removeMessageListener( + location.host + ':closePopup', + vAPI.toolbarButton.onPopupCloseRequested + ); + }); + var CustomizableUI; + + var forceLegacyToolbarButton = vAPI.localStorage.getBool("forceLegacyToolbarButton"); + if (!forceLegacyToolbarButton) { try { CustomizableUI = Cu.import('resource:///modules/CustomizableUI.jsm', null).CustomizableUI; } catch (ex) { + } + } + + if (!CustomizableUI) { + // Create a fallback non-customizable UI button + var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService); + var styleSheetUri = Services.io.newURI(vAPI.getURL("css/legacy-toolbar-button.css"), null, null); + var legacyButtonId = "uBlock-legacy-button"; // NOTE: must match legacy-toolbar-button.css + this.id = legacyButtonId; + this.viewId = legacyButtonId + "-panel"; + + if (!sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) { + sss.loadAndRegisterSheet(styleSheetUri, sss.AUTHOR_SHEET); // Register global so it works in all windows, including palette + } + + var addLegacyToolbarButton = function(window) { + var document = window.document; + var toolbox = document.getElementById('navigator-toolbox') || document.getElementById('mail-toolbox'); + + if (toolbox) { + var palette = toolbox.palette; + + if (!palette) { + // palette might take a little longer to appear on some platforms, give it a small delay and try again + window.setTimeout(function() { + if (toolbox.palette) { + addLegacyToolbarButton(window); + } + }, 250); return; } + var toolbarButton = document.createElement('toolbarbutton'); + toolbarButton.setAttribute('id', legacyButtonId); + toolbarButton.setAttribute('type', 'panel'); + toolbarButton.setAttribute('removable', 'true'); + toolbarButton.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional'); + toolbarButton.setAttribute('label', vAPI.toolbarButton.label); + + var toolbarButtonPanel = document.createElement("panel"); + toolbarButtonPanel.setAttribute('level', 'parent'); + vAPI.toolbarButton.populatePanel(document, toolbarButtonPanel); + toolbarButtonPanel.addEventListener('popupshowing', vAPI.toolbarButton.onViewShowing); + toolbarButtonPanel.addEventListener('popuphiding', vAPI.toolbarButton.onViewHiding); + toolbarButton.appendChild(toolbarButtonPanel); + + palette.appendChild(toolbarButton); + + vAPI.toolbarButton.closePopup = function() { + toolbarButtonPanel.hidePopup(); + } + + if (!vAPI.localStorage.getBool('legacyToolbarButtonAdded')) { + // 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 toolbar = !forceLegacyToolbarButton && document.getElementById('nav-bar'); + if (toolbar) { + toolbar.appendChild(toolbarButton); + toolbar.setAttribute('currentset', toolbar.currentSet); + document.persist(toolbar.id, 'currentset'); + } + vAPI.localStorage.setBool('legacyToolbarButtonAdded', 'true'); + } else { + // Find the place to put the button + var toolbars = toolbox.externalToolbars.slice(); + for (var child of toolbox.children) { + if (child.localName === 'toolbar') { + toolbars.push(child); + } + } + + for (var toolbar of toolbars) { + var currentsetString = toolbar.getAttribute('currentset'); + if (currentsetString) { + var currentset = currentsetString.split(','); + var index = currentset.indexOf(legacyButtonId); + if (index >= 0) { + // Found our button on this toolbar - but where on it? + var before = null; + for (var i = index + 1; i < currentset.length; i++) { + before = document.getElementById(currentset[i]); + if (before) { + toolbar.insertItem(legacyButtonId, before); + break; + } + } + if (!before) { + toolbar.insertItem(legacyButtonId); + } + } + } + } + } + } + } + + vAPI.toolbarButton.attachToNewWindow = function(win) { + addLegacyToolbarButton(win); + } + + cleanupTasks.push(function() { + for ( var win of vAPI.tabs.getWindows() ) { + var toolbarButton = win.document.getElementById(legacyButtonId); + if (toolbarButton) { + toolbarButton.parentNode.removeChild(toolbarButton); + } + } + + if (sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) { + sss.unregisterSheet(styleSheetUri, sss.AUTHOR_SHEET); + } + }.bind(this)); + return; + } + + this.CustomizableUI = CustomizableUI; + this.defaultArea = CustomizableUI.AREA_NAVBAR; this.styleURI = [ '#' + this.id + '.off {', @@ -1957,17 +2172,14 @@ vAPI.toolbarButton.init = function() { null ); - this.closePopup = function({target}) { + this.closePopup = function(tabBrowser) { CustomizableUI.hidePanelForNode( - target.ownerDocument.getElementById(vAPI.toolbarButton.viewId) + tabBrowser.ownerDocument.getElementById(vAPI.toolbarButton.viewId) ); }; CustomizableUI.createWidget(this); - vAPI.messaging.globalMessageManager.addMessageListener( - location.host + ':closePopup', - this.closePopup - ); + cleanupTasks.push(function() { if ( this.CUIEvents ) { @@ -1975,11 +2187,7 @@ vAPI.toolbarButton.init = function() { } CustomizableUI.destroyWidget(this.id); - vAPI.messaging.globalMessageManager.removeMessageListener( - location.host + ':closePopup', - this.closePopup - ); - + for ( var win of vAPI.tabs.getWindows() ) { var panel = win.document.getElementById(this.viewId); panel.parentNode.removeChild(panel); @@ -1994,35 +2202,66 @@ vAPI.toolbarButton.init = function() { /******************************************************************************/ +vAPI.toolbarButton.onPopupCloseRequested = function({target}) { + if (vAPI.toolbarButton.closePopup) { + vAPI.toolbarButton.closePopup(target); + } +} + +/******************************************************************************/ + vAPI.toolbarButton.onBeforeCreated = function(doc) { var panel = doc.createElement('panelview'); + + vAPI.toolbarButton.populatePanel(doc, panel); + + doc.getElementById('PanelUI-multiView').appendChild(panel); + + doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .loadSheet(this.styleURI, 1); +}; + +vAPI.toolbarButton.populatePanel = function(doc, panel) { panel.setAttribute('id', this.viewId); var iframe = doc.createElement('iframe'); iframe.setAttribute('type', 'content'); - doc.getElementById('PanelUI-multiView') - .appendChild(panel) - .appendChild(iframe); + panel.appendChild(iframe); var updateTimer = null; - var delayedResize = function() { + var delayedResize = function(attempts) { if ( updateTimer ) { return; } - updateTimer = vAPI.setTimeout(resizePopup, 10); - }; - var resizePopup = function() { + // Sanity check + attempts = (attempts || 0) + 1; + if (attempts > 1000) { + console.error('uBlock> delayedResize: giving up after too many attemps'); + return; + } + + updateTimer = vAPI.setTimeout(resizePopup, 10, attempts); }; + var resizePopup = function(attempts) { updateTimer = null; var body = iframe.contentDocument.body; panel.parentNode.style.maxWidth = 'none'; // https://github.com/chrisaljoudi/uBlock/issues/730 // Voodoo programming: this recipe works - panel.style.height = iframe.style.height = body.clientHeight.toString() + 'px'; - panel.style.width = iframe.style.width = body.clientWidth.toString() + 'px'; + var toPixelString = pixels => pixels.toString() + 'px'; + + var clientHeight = body.clientHeight; + iframe.style.height = toPixelString(clientHeight); + panel.style.height = toPixelString(clientHeight + (panel.boxObject.height - panel.clientHeight)); + + var clientWidth = body.clientWidth; + iframe.style.width = toPixelString(clientWidth); + panel.style.width = toPixelString(clientWidth + (panel.boxObject.width - panel.clientWidth)); + if ( iframe.clientHeight !== body.clientHeight || iframe.clientWidth !== body.clientWidth ) { - delayedResize(); + delayedResize(attempts); } }; var onPopupReady = function() { @@ -2032,16 +2271,23 @@ vAPI.toolbarButton.onBeforeCreated = function(doc) { return; } - // https://github.com/gorhill/uBlock/issues/83 - // Add `portrait` class if width is constrained. - try { - var CustomizableUI = Cu.import('resource:///modules/CustomizableUI.jsm', null).CustomizableUI; - iframe.contentDocument.body.classList.toggle( - 'portrait', - CustomizableUI.getWidget(vAPI.toolbarButton.id).areaType === CustomizableUI.TYPE_MENU_PANEL - ); - } catch (ex) { - /* noop */ + if (CustomizableUI) { + + // https://github.com/gorhill/uBlock/issues/83 + // Add `portrait` class if width is constrained. + try { + iframe.contentDocument.body.classList.toggle( + 'portrait', + CustomizableUI.getWidget(vAPI.toolbarButton.id).areaType === CustomizableUI.TYPE_MENU_PANEL + ); + } catch (ex) { + /* noop */ + } + var placement = CustomizableUI.getPlacementOfWidget(widgetId); + if (placement.area === CustomizableUI.AREA_PANEL) { + // Add some overrides for displaying the popup correctly in a panel + win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) + .loadSheet(Services.io.newURI(vAPI.getURL("css/popup-vertical.css"), null, null), Ci.nsIDOMWindowUtils.AUTHOR_SHEET); } new win.MutationObserver(delayedResize).observe(win.document.body, { @@ -2054,10 +2300,6 @@ vAPI.toolbarButton.onBeforeCreated = function(doc) { }; iframe.addEventListener('load', onPopupReady, true); - - doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .loadSheet(this.styleURI, 1); }; /******************************************************************************/ diff --git a/platform/firefox/vapi-common.js b/platform/firefox/vapi-common.js index 714a77e58..5c5bb6755 100644 --- a/platform/firefox/vapi-common.js +++ b/platform/firefox/vapi-common.js @@ -125,9 +125,9 @@ vAPI.closePopup = function() { // background page or auxiliary pages. // This storage is optional, but it is nice to have, for a more polished user // experience. - +const branchName = 'extensions.' + location.host + '.'; vAPI.localStorage = { - PB: Services.prefs.getBranch('extensions.' + location.host + '.'), + PB: Services.prefs.getBranch(branchName), str: Components.classes['@mozilla.org/supports-string;1'] .createInstance(Components.interfaces.nsISupportsString), getItem: function(key) { @@ -148,6 +148,19 @@ vAPI.localStorage = { this.str ); }, + getBool: function(key) { + try { + return this.PB.getBoolPref(key); + } catch (ex) { + return null; + } + }, + setBool: function(key, value) { + this.PB.setBoolPref(key, value); + }, + setDefaultBool: function(key, defaultValue) { + Services.prefs.getDefaultBranch(branchName).setBoolPref(key, defaultValue); + }, removeItem: function(key) { this.PB.clearUserPref(key); },