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 = { |