| Index: ext/background.js |
| =================================================================== |
| --- a/ext/background.js |
| +++ b/ext/background.js |
| @@ -10,84 +10,92 @@ |
| * 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 Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| +/* global internal, defineNamespace */ |
| + |
| "use strict"; |
| { |
| let nonEmptyPageMaps = new Set(); |
| let PageMap = ext.PageMap = function() |
| { |
| - this._map = new Map(); |
| + defineNamespace(this, internal); |
| + |
| + this[internal].map = new Map(); |
| }; |
| PageMap.prototype = { |
| - _delete(id) |
| - { |
| - this._map.delete(id); |
| - |
| - if (this._map.size == 0) |
| - nonEmptyPageMaps.delete(this); |
| - }, |
| keys() |
| { |
| - return Array.from(this._map.keys()).map(ext.getPage); |
| + return Array.from(this[internal].map.keys()).map(ext.getPage); |
| }, |
| get(page) |
| { |
| - return this._map.get(page.id); |
| + return this[internal].map.get(page.id); |
| }, |
| set(page, value) |
| { |
| - this._map.set(page.id, value); |
| + this[internal].map.set(page.id, value); |
| nonEmptyPageMaps.add(this); |
| }, |
| has(page) |
| { |
| - return this._map.has(page.id); |
| + return this[internal].map.has(page.id); |
| }, |
| clear() |
| { |
| - this._map.clear(); |
| + this[internal].map.clear(); |
| nonEmptyPageMaps.delete(this); |
| }, |
| delete(page) |
| { |
| - this._delete(page.id); |
| + deletePage(this, page.id); |
| } |
| }; |
| - ext._removeFromAllPageMaps = pageId => |
| + function deletePage(pageMap, pageId) |
| + { |
| + pageMap[internal].map.delete(pageId); |
| + |
| + if (pageMap[internal].map.size == 0) |
| + nonEmptyPageMaps.delete(pageMap); |
| + } |
| + |
| + ext[internal].removeFromAllPageMaps = pageId => |
| { |
| for (let pageMap of nonEmptyPageMaps) |
| - pageMap._delete(pageId); |
| + deletePage(pageMap, pageId); |
| }; |
| /* Pages */ |
| let Page = ext.Page = function(tab) |
| { |
| + defineNamespace(this, internal); |
| + |
| this.id = tab.id; |
| - this._url = tab.url && new URL(tab.url); |
| + |
| + this[internal].url = tab.url && new URL(tab.url); |
| this.browserAction = new BrowserAction(tab.id); |
| this.contextMenus = new ContextMenus(this); |
| }; |
| Page.prototype = { |
| get url() |
| { |
| // usually our Page objects are created from Chrome's Tab objects, which |
| // provide the url. So we can return the url given in the constructor. |
| - if (this._url) |
| - return this._url; |
| + if (this[internal].url) |
| + return this[internal].url; |
| // but sometimes we only have the tab id when we create a Page object. |
| // In that case we get the url from top frame of the tab, recorded by |
| // the onBeforeRequest handler. |
| let frames = framesOfTabs.get(this.id); |
| if (frames) |
| { |
| let frame = frames.get(0); |
| @@ -115,25 +123,25 @@ |
| callback(new Page(openedTab)); |
| } |
| }; |
| browser.tabs.onUpdated.addListener(onUpdated); |
| }; |
| } |
| ext.pages = { |
| - onLoading: new ext._EventTarget(), |
| - onActivated: new ext._EventTarget(), |
| - onRemoved: new ext._EventTarget() |
| + onLoading: new ext[internal].EventTarget(), |
| + onActivated: new ext[internal].EventTarget(), |
| + onRemoved: new ext[internal].EventTarget() |
| }; |
| browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => |
| { |
| if (changeInfo.status == "loading") |
| - ext.pages.onLoading._dispatch(new Page(tab)); |
| + ext[internal].dispatchEvent(ext.pages.onLoading, new Page(tab)); |
| }); |
| function createFrame(tabId, frameId) |
| { |
| let frames = framesOfTabs.get(tabId); |
| if (!frames) |
| { |
| frames = new Map(); |
| @@ -151,28 +159,28 @@ |
| } |
| function updatePageFrameStructure(frameId, tabId, url, parentFrameId) |
| { |
| if (frameId == 0) |
| { |
| let page = new Page({id: tabId, url}); |
| - ext._removeFromAllPageMaps(tabId); |
| + ext[internal].removeFromAllPageMaps(tabId); |
| browser.tabs.get(tabId, () => |
| { |
| // If the tab is prerendered, browser.tabs.get() sets |
| // browser.runtime.lastError and we have to dispatch the onLoading |
| // event, since the onUpdated event isn't dispatched for prerendered |
| // tabs. However, we have to keep relying on the onUpdated event for |
| // tabs that are already visible. Otherwise browser action changes get |
| // overridden when Chrome automatically resets them on navigation. |
| if (browser.runtime.lastError) |
| - ext.pages.onLoading._dispatch(page); |
| + ext[internal].dispatchEvent(ext.pages.onLoading, page); |
| }); |
| } |
| // Update frame URL and parent in frame structure |
| let frame = createFrame(tabId, frameId); |
| frame.url = new URL(url); |
| let parentFrame = framesOfTabs.get(tabId).get(parentFrameId); |
| @@ -272,160 +280,172 @@ |
| { |
| updatePageFrameStructure(details.frameId, details.tabId, url, |
| details.parentFrameId); |
| } |
| }); |
| function forgetTab(tabId) |
| { |
| - ext.pages.onRemoved._dispatch(tabId); |
| + ext[internal].dispatchEvent(ext.pages.onRemoved, tabId); |
| - ext._removeFromAllPageMaps(tabId); |
| + ext[internal].removeFromAllPageMaps(tabId); |
| framesOfTabs.delete(tabId); |
| } |
| browser.tabs.onReplaced.addListener((addedTabId, removedTabId) => |
| { |
| forgetTab(removedTabId); |
| }); |
| browser.tabs.onRemoved.addListener(forgetTab); |
| browser.tabs.onActivated.addListener(details => |
| { |
| - ext.pages.onActivated._dispatch(new Page({id: details.tabId})); |
| + ext[internal].dispatchEvent(ext.pages.onActivated, |
| + new Page({id: details.tabId})); |
| }); |
| /* Browser actions */ |
| let BrowserAction = function(tabId) |
| { |
| - this._tabId = tabId; |
| - this._changes = null; |
| + defineNamespace(this, internal); |
| + |
| + this[internal].tabId = tabId; |
| + this[internal].changes = null; |
| }; |
| BrowserAction.prototype = { |
| - _applyChanges() |
| - { |
| - if ("iconPath" in this._changes) |
| - { |
| - // Firefox for Android displays the browser action not as an icon but |
| - // as a menu item. There is no icon, but such an option may be added in |
| - // the future. |
| - // https://bugzilla.mozilla.org/show_bug.cgi?id=1331746 |
| - if ("setIcon" in browser.browserAction) |
| - { |
| - let path = { |
| - 16: this._changes.iconPath.replace("$size", "16"), |
| - 19: this._changes.iconPath.replace("$size", "19"), |
| - 20: this._changes.iconPath.replace("$size", "20"), |
| - 32: this._changes.iconPath.replace("$size", "32"), |
| - 38: this._changes.iconPath.replace("$size", "38"), |
| - 40: this._changes.iconPath.replace("$size", "40") |
| - }; |
| - try |
| - { |
| - browser.browserAction.setIcon({tabId: this._tabId, path}); |
| - } |
| - catch (e) |
| - { |
| - // Edge throws if passed icon sizes different than 19,20,38,40px. |
| - delete path[16]; |
| - delete path[32]; |
| - browser.browserAction.setIcon({tabId: this._tabId, path}); |
| - } |
| - } |
| - } |
| - |
| - if ("badgeText" in this._changes) |
| - { |
| - // There is no badge on Firefox for Android; the browser action is |
| - // simply a menu item. |
| - if ("setBadgeText" in browser.browserAction) |
| - { |
| - browser.browserAction.setBadgeText({ |
| - tabId: this._tabId, |
| - text: this._changes.badgeText |
| - }); |
| - } |
| - } |
| - |
| - if ("badgeColor" in this._changes) |
| - { |
| - // There is no badge on Firefox for Android; the browser action is |
| - // simply a menu item. |
| - if ("setBadgeBackgroundColor" in browser.browserAction) |
| - { |
| - browser.browserAction.setBadgeBackgroundColor({ |
| - tabId: this._tabId, |
| - color: this._changes.badgeColor |
| - }); |
| - } |
| - } |
| - |
| - this._changes = null; |
| - }, |
| - _queueChanges() |
| - { |
| - browser.tabs.get(this._tabId, () => |
| - { |
| - // If the tab is prerendered, browser.tabs.get() sets |
| - // browser.runtime.lastError and we have to delay our changes |
| - // until the currently visible tab is replaced with the |
| - // prerendered tab. Otherwise browser.browserAction.set* fails. |
| - if (browser.runtime.lastError) |
| - { |
| - let onReplaced = (addedTabId, removedTabId) => |
| - { |
| - if (addedTabId == this._tabId) |
| - { |
| - browser.tabs.onReplaced.removeListener(onReplaced); |
| - this._applyChanges(); |
| - } |
| - }; |
| - browser.tabs.onReplaced.addListener(onReplaced); |
| - } |
| - else |
| - { |
| - this._applyChanges(); |
| - } |
| - }); |
| - }, |
| - _addChange(name, value) |
| - { |
| - if (!this._changes) |
| - { |
| - this._changes = {}; |
| - this._queueChanges(); |
| - } |
| - |
| - this._changes[name] = value; |
| - }, |
| setIcon(path) |
| { |
| - this._addChange("iconPath", path); |
| + addBrowserActionChange(this, "iconPath", path); |
| }, |
| setBadge(badge) |
| { |
| if (!badge) |
| { |
| - this._addChange("badgeText", ""); |
| + addBrowserActionChange(this, "badgeText", ""); |
| } |
| else |
| { |
| if ("number" in badge) |
| - this._addChange("badgeText", badge.number.toString()); |
| + addBrowserActionChange(this, "badgeText", badge.number.toString()); |
| if ("color" in badge) |
| - this._addChange("badgeColor", badge.color); |
| + addBrowserActionChange(this, "badgeColor", badge.color); |
| } |
| } |
| }; |
| + function queueBrowserActionChanges(browserAction) |
| + { |
| + browser.tabs.get(browserAction[internal].tabId, () => |
| + { |
| + // If the tab is prerendered, browser.tabs.get() sets |
| + // browser.runtime.lastError and we have to delay our changes |
| + // until the currently visible tab is replaced with the |
| + // prerendered tab. Otherwise browser.browserAction.set* fails. |
| + if (browser.runtime.lastError) |
| + { |
| + let onReplaced = (addedTabId, removedTabId) => |
| + { |
| + if (addedTabId == browserAction[internal].tabId) |
| + { |
| + browser.tabs.onReplaced.removeListener(onReplaced); |
| + applyBrowserActionChanges(browserAction); |
| + } |
| + }; |
| + browser.tabs.onReplaced.addListener(onReplaced); |
| + } |
| + else |
| + { |
| + applyBrowserActionChanges(browserAction); |
| + } |
| + }); |
| + } |
| + |
| + function applyBrowserActionChanges(browserAction) |
| + { |
| + if ("iconPath" in browserAction[internal].changes) |
| + { |
| + // Firefox for Android displays the browser action not as an icon but |
| + // as a menu item. There is no icon, but such an option may be added in |
| + // the future. |
| + // https://bugzilla.mozilla.org/show_bug.cgi?id=1331746 |
| + if ("setIcon" in browser.browserAction) |
| + { |
| + let path = { |
| + 16: browserAction[internal].changes.iconPath.replace("$size", "16"), |
| + 19: browserAction[internal].changes.iconPath.replace("$size", "19"), |
| + 20: browserAction[internal].changes.iconPath.replace("$size", "20"), |
| + 32: browserAction[internal].changes.iconPath.replace("$size", "32"), |
| + 38: browserAction[internal].changes.iconPath.replace("$size", "38"), |
| + 40: browserAction[internal].changes.iconPath.replace("$size", "40") |
| + }; |
| + try |
| + { |
| + browser.browserAction.setIcon({ |
| + tabId: browserAction[internal].tabId, |
| + path |
| + }); |
| + } |
| + catch (e) |
| + { |
| + // Edge throws if passed icon sizes different than 19,20,38,40px. |
| + delete path[16]; |
| + delete path[32]; |
| + browser.browserAction.setIcon({ |
| + tabId: browserAction[internal].tabId, |
| + path |
| + }); |
| + } |
| + } |
| + } |
| + |
| + if ("badgeText" in browserAction[internal].changes) |
| + { |
| + // There is no badge on Firefox for Android; the browser action is |
| + // simply a menu item. |
| + if ("setBadgeText" in browser.browserAction) |
| + { |
| + browser.browserAction.setBadgeText({ |
| + tabId: browserAction[internal].tabId, |
| + text: browserAction[internal].changes.badgeText |
| + }); |
| + } |
| + } |
| + |
| + if ("badgeColor" in browserAction[internal].changes) |
| + { |
| + // There is no badge on Firefox for Android; the browser action is |
| + // simply a menu item. |
| + if ("setBadgeBackgroundColor" in browser.browserAction) |
| + { |
| + browser.browserAction.setBadgeBackgroundColor({ |
| + tabId: browserAction[internal].tabId, |
| + color: browserAction[internal].changes.badgeColor |
| + }); |
| + } |
| + } |
| + |
| + browserAction[internal].changes = null; |
| + } |
| + |
| + function addBrowserActionChange(browserAction, name, value) |
| + { |
| + if (!browserAction[internal].changes) |
| + { |
| + browserAction[internal].changes = {}; |
| + queueBrowserActionChanges(browserAction); |
| + } |
| + |
| + browserAction[internal].changes[name] = value; |
| + } |
| + |
| /* Context menus */ |
| let contextMenuItems = new ext.PageMap(); |
| let contextMenuUpdating = false; |
| let updateContextMenu = () => |
| { |
| @@ -462,31 +482,33 @@ |
| }); |
| }); |
| }); |
| }); |
| }; |
| let ContextMenus = function(page) |
| { |
| - this._page = page; |
| + defineNamespace(this, internal); |
| + |
| + this[internal].page = page; |
| }; |
| ContextMenus.prototype = { |
| create(item) |
| { |
| - let items = contextMenuItems.get(this._page); |
| + let items = contextMenuItems.get(this[internal].page); |
| if (!items) |
| - contextMenuItems.set(this._page, items = []); |
| + contextMenuItems.set(this[internal].page, items = []); |
| items.push(item); |
| updateContextMenu(); |
| }, |
| remove(item) |
| { |
| - let items = contextMenuItems.get(this._page); |
| + let items = contextMenuItems.get(this[internal].page); |
| if (items) |
| { |
| let index = items.indexOf(item); |
| if (index != -1) |
| { |
| items.splice(index, 1); |
| updateContextMenu(); |
| } |
| @@ -532,17 +554,17 @@ |
| browser.webRequest.handlerBehaviorChanged(); |
| handlerBehaviorChangedQuota--; |
| setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000); |
| } |
| } |
| ext.webRequest = { |
| - onBeforeRequest: new ext._EventTarget(), |
| + onBeforeRequest: new ext[internal].EventTarget(), |
| handlerBehaviorChanged() |
| { |
| // Defer handlerBehaviorChanged() until navigation occurs. |
| // There wouldn't be any visible effect when calling it earlier, |
| // but it's an expensive operation and that way we avoid to call |
| // it multiple times, if multiple filters are added/removed. |
| let {onBeforeNavigate} = browser.webNavigation; |
| if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange)) |
| @@ -617,19 +639,21 @@ |
| let frame = null; |
| let page = null; |
| if (details.tabId != -1) |
| { |
| frame = ext.getFrame(details.tabId, frameId); |
| page = new Page({id: details.tabId}); |
| } |
| - if (ext.webRequest.onBeforeRequest._dispatch( |
| - url, type, page, frame).includes(false)) |
| + if (ext[internal].dispatchEvent(ext.webRequest.onBeforeRequest, |
| + url, type, page, frame).includes(false)) |
| + { |
| return {cancel: true}; |
| + } |
| }, {urls: ["<all_urls>"]}, ["blocking"]); |
| /* Message passing */ |
| browser.runtime.onMessage.addListener((message, rawSender, sendResponse) => |
| { |
| let sender = {}; |
| @@ -655,17 +679,18 @@ |
| if (frame) |
| return frame.parent || null; |
| return frames.get(0) || null; |
| } |
| }; |
| } |
| - return ext.onMessage._dispatch( |
| + return ext[internal].dispatchEvent( |
| + ext.onMessage, |
| message, sender, sendResponse |
| ).includes(true); |
| }); |
| /* Storage */ |
| ext.storage = { |