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);
},