| Index: ext/background.js | 
| =================================================================== | 
| --- a/ext/background.js | 
| +++ b/ext/background.js | 
| @@ -93,45 +93,45 @@ | 
| { | 
| let frame = frames.get(0); | 
| if (frame) | 
| return frame.url; | 
| } | 
| }, | 
| sendMessage(message, responseCallback) | 
| { | 
| -      chrome.tabs.sendMessage(this.id, message, responseCallback); | 
| +      browser.tabs.sendMessage(this.id, message, responseCallback); | 
| } | 
| }; | 
|  | 
| ext.getPage = id => new Page({id: parseInt(id, 10)}); | 
|  | 
| function afterTabLoaded(callback) | 
| { | 
| return openedTab => | 
| { | 
| let onUpdated = (tabId, changeInfo, tab) => | 
| { | 
| if (tabId == openedTab.id && changeInfo.status == "complete") | 
| { | 
| -          chrome.tabs.onUpdated.removeListener(onUpdated); | 
| +          browser.tabs.onUpdated.removeListener(onUpdated); | 
| callback(new Page(openedTab)); | 
| } | 
| }; | 
| -      chrome.tabs.onUpdated.addListener(onUpdated); | 
| +      browser.tabs.onUpdated.addListener(onUpdated); | 
| }; | 
| } | 
|  | 
| ext.pages = { | 
| onLoading: new ext._EventTarget(), | 
| onActivated: new ext._EventTarget(), | 
| onRemoved: new ext._EventTarget() | 
| }; | 
|  | 
| -  chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => | 
| +  browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => | 
| { | 
| if (changeInfo.status == "loading") | 
| ext.pages.onLoading._dispatch(new Page(tab)); | 
| }); | 
|  | 
| function createFrame(tabId, frameId) | 
| { | 
| let frames = framesOfTabs.get(tabId); | 
| @@ -154,39 +154,39 @@ | 
| function updatePageFrameStructure(frameId, tabId, url, parentFrameId) | 
| { | 
| if (frameId == 0) | 
| { | 
| let page = new Page({id: tabId, url}); | 
|  | 
| ext._removeFromAllPageMaps(tabId); | 
|  | 
| -      chrome.tabs.get(tabId, () => | 
| +      browser.tabs.get(tabId, () => | 
| { | 
| -        // If the tab is prerendered, chrome.tabs.get() sets | 
| -        // chrome.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 unUpdated event for tabs that | 
| -        // are already visible. Otherwise browser action changes get overridden | 
| -        // when Chrome automatically resets them on navigation. | 
| -        if (chrome.runtime.lastError) | 
| +        // 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); | 
| }); | 
| } | 
|  | 
| // 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); | 
| if (parentFrame) | 
| frame.parent = parentFrame; | 
| } | 
|  | 
| -  chrome.webRequest.onHeadersReceived.addListener(details => | 
| +  browser.webRequest.onHeadersReceived.addListener(details => | 
| { | 
| // We have to update the frame structure when switching to a new | 
| // document, so that we process any further requests made by that | 
| // document in the right context. Unfortunately, we cannot rely | 
| // on webNavigation.onCommitted since it isn't guaranteed to fire | 
| // before any subresources start downloading[1]. As an | 
| // alternative we use webRequest.onHeadersReceived for HTTP(S) | 
| // URLs, being careful to ignore any responses that won't cause | 
| @@ -254,17 +254,17 @@ | 
| } | 
|  | 
| updatePageFrameStructure(details.frameId, details.tabId, details.url, | 
| details.parentFrameId); | 
| }, | 
| {types: ["main_frame", "sub_frame"], urls: ["http://*/*", "https://*/*"]}, | 
| ["responseHeaders"]); | 
|  | 
| -  chrome.webNavigation.onBeforeNavigate.addListener(details => | 
| +  browser.webNavigation.onBeforeNavigate.addListener(details => | 
| { | 
| // Since we can only listen for HTTP(S) responses using | 
| // webRequest.onHeadersReceived we must update the page structure here for | 
| // other navigations. | 
| let url = new URL(details.url); | 
| if (url.protocol != "http:" && url.protocol != "https:") | 
| { | 
| updatePageFrameStructure(details.frameId, details.tabId, details.url, | 
| @@ -275,24 +275,24 @@ | 
| function forgetTab(tabId) | 
| { | 
| ext.pages.onRemoved._dispatch(tabId); | 
|  | 
| ext._removeFromAllPageMaps(tabId); | 
| framesOfTabs.delete(tabId); | 
| } | 
|  | 
| -  chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => | 
| +  browser.tabs.onReplaced.addListener((addedTabId, removedTabId) => | 
| { | 
| forgetTab(removedTabId); | 
| }); | 
|  | 
| -  chrome.tabs.onRemoved.addListener(forgetTab); | 
| +  browser.tabs.onRemoved.addListener(forgetTab); | 
|  | 
| -  chrome.tabs.onActivated.addListener(details => | 
| +  browser.tabs.onActivated.addListener(details => | 
| { | 
| ext.pages.onActivated._dispatch(new Page({id: details.tabId})); | 
| }); | 
|  | 
|  | 
| /* Browser actions */ | 
|  | 
| let BrowserAction = function(tabId) | 
| @@ -304,87 +304,87 @@ | 
| _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 chrome.browserAction) | 
| +        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 | 
| { | 
| -            chrome.browserAction.setIcon({tabId: this._tabId, path}); | 
| +            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]; | 
| -            chrome.browserAction.setIcon({tabId: this._tabId, path}); | 
| +            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 chrome.browserAction) | 
| +        if ("setBadgeText" in browser.browserAction) | 
| { | 
| -          chrome.browserAction.setBadgeText({ | 
| +          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 chrome.browserAction) | 
| +        if ("setBadgeBackgroundColor" in browser.browserAction) | 
| { | 
| -          chrome.browserAction.setBadgeBackgroundColor({ | 
| +          browser.browserAction.setBadgeBackgroundColor({ | 
| tabId: this._tabId, | 
| color: this._changes.badgeColor | 
| }); | 
| } | 
| } | 
|  | 
| this._changes = null; | 
| }, | 
| _queueChanges() | 
| { | 
| -      chrome.tabs.get(this._tabId, () => | 
| +      browser.tabs.get(this._tabId, () => | 
| { | 
| -        // If the tab is prerendered, chrome.tabs.get() sets | 
| -        // chrome.runtime.lastError and we have to delay our changes | 
| +        // 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 chrome.browserAction.set* fails. | 
| -        if (chrome.runtime.lastError) | 
| +        // prerendered tab. Otherwise browser.browserAction.set* fails. | 
| +        if (browser.runtime.lastError) | 
| { | 
| let onReplaced = (addedTabId, removedTabId) => | 
| { | 
| if (addedTabId == this._tabId) | 
| { | 
| -              chrome.tabs.onReplaced.removeListener(onReplaced); | 
| +              browser.tabs.onReplaced.removeListener(onReplaced); | 
| this._applyChanges(); | 
| } | 
| }; | 
| -          chrome.tabs.onReplaced.addListener(onReplaced); | 
| +          browser.tabs.onReplaced.addListener(onReplaced); | 
| } | 
| else | 
| { | 
| this._applyChanges(); | 
| } | 
| }); | 
| }, | 
| _addChange(name, value) | 
| @@ -423,38 +423,38 @@ | 
|  | 
| let contextMenuItems = new ext.PageMap(); | 
| let contextMenuUpdating = false; | 
|  | 
| let updateContextMenu = () => | 
| { | 
| // Firefox for Android does not support context menus. | 
| // https://bugzilla.mozilla.org/show_bug.cgi?id=1269062 | 
| -    if (!("contextMenus" in chrome) || contextMenuUpdating) | 
| +    if (!("contextMenus" in browser) || contextMenuUpdating) | 
| return; | 
|  | 
| contextMenuUpdating = true; | 
|  | 
| -    chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => | 
| +    browser.tabs.query({active: true, lastFocusedWindow: true}, tabs => | 
| { | 
| -      chrome.contextMenus.removeAll(() => | 
| +      browser.contextMenus.removeAll(() => | 
| { | 
| contextMenuUpdating = false; | 
|  | 
| if (tabs.length == 0) | 
| return; | 
|  | 
| let items = contextMenuItems.get({id: tabs[0].id}); | 
|  | 
| if (!items) | 
| return; | 
|  | 
| items.forEach(item => | 
| { | 
| -          chrome.contextMenus.create({ | 
| +          browser.contextMenus.create({ | 
| title: item.title, | 
| contexts: item.contexts, | 
| onclick(info, tab) | 
| { | 
| item.onclick(new Page(tab)); | 
| } | 
| }); | 
| }); | 
| @@ -486,77 +486,77 @@ | 
| { | 
| items.splice(index, 1); | 
| updateContextMenu(); | 
| } | 
| } | 
| } | 
| }; | 
|  | 
| -  chrome.tabs.onActivated.addListener(updateContextMenu); | 
| +  browser.tabs.onActivated.addListener(updateContextMenu); | 
|  | 
| -  if ("windows" in chrome) | 
| +  if ("windows" in browser) | 
| { | 
| -    chrome.windows.onFocusChanged.addListener(windowId => | 
| +    browser.windows.onFocusChanged.addListener(windowId => | 
| { | 
| -      if (windowId != chrome.windows.WINDOW_ID_NONE) | 
| +      if (windowId != browser.windows.WINDOW_ID_NONE) | 
| updateContextMenu(); | 
| }); | 
| } | 
|  | 
|  | 
| /* Web requests */ | 
|  | 
| let framesOfTabs = new Map(); | 
|  | 
| ext.getFrame = (tabId, frameId) => | 
| { | 
| let frames = framesOfTabs.get(tabId); | 
| return frames && frames.get(frameId); | 
| }; | 
|  | 
| let handlerBehaviorChangedQuota = | 
| -    chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES; | 
| +    browser.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES; | 
|  | 
| function propagateHandlerBehaviorChange() | 
| { | 
| // Make sure to not call handlerBehaviorChanged() more often than allowed | 
| -    // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. | 
| +    // by browser.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. | 
| // Otherwise Chrome notifies the user that this extension is causing issues. | 
| if (handlerBehaviorChangedQuota > 0) | 
| { | 
| -      chrome.webNavigation.onBeforeNavigate.removeListener( | 
| +      browser.webNavigation.onBeforeNavigate.removeListener( | 
| propagateHandlerBehaviorChange | 
| ); | 
| -      chrome.webRequest.handlerBehaviorChanged(); | 
| +      browser.webRequest.handlerBehaviorChanged(); | 
|  | 
| handlerBehaviorChangedQuota--; | 
| setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000); | 
| } | 
| } | 
|  | 
| ext.webRequest = { | 
| onBeforeRequest: new ext._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} = chrome.webNavigation; | 
| +      let {onBeforeNavigate} = browser.webNavigation; | 
| if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange)) | 
| onBeforeNavigate.addListener(propagateHandlerBehaviorChange); | 
| } | 
| }; | 
|  | 
| -  chrome.tabs.query({}, tabs => | 
| +  browser.tabs.query({}, tabs => | 
| { | 
| tabs.forEach(tab => | 
| { | 
| -      chrome.webNavigation.getAllFrames({tabId: tab.id}, details => | 
| +      browser.webNavigation.getAllFrames({tabId: tab.id}, details => | 
| { | 
| if (details && details.length > 0) | 
| { | 
| let frames = new Map(); | 
| framesOfTabs.set(tab.id, frames); | 
|  | 
| for (let detail of details) | 
| { | 
| @@ -566,17 +566,17 @@ | 
| if (detail.parentFrameId != -1) | 
| frame.parent = frames.get(detail.parentFrameId); | 
| } | 
| } | 
| }); | 
| }); | 
| }); | 
|  | 
| -  chrome.webRequest.onBeforeRequest.addListener(details => | 
| +  browser.webRequest.onBeforeRequest.addListener(details => | 
| { | 
| // The high-level code isn't interested in requests that aren't | 
| // related to a tab or requests loading a top-level document, | 
| // those should never be blocked. | 
| if (details.type == "main_frame") | 
| return; | 
|  | 
| // Filter out requests from non web protocols. Ideally, we'd explicitly | 
| @@ -610,17 +610,17 @@ | 
| if (ext.webRequest.onBeforeRequest._dispatch( | 
| url, type, page, frame).includes(false)) | 
| return {cancel: true}; | 
| }, {urls: ["<all_urls>"]}, ["blocking"]); | 
|  | 
|  | 
| /* Message passing */ | 
|  | 
| -  chrome.runtime.onMessage.addListener((message, rawSender, sendResponse) => | 
| +  browser.runtime.onMessage.addListener((message, rawSender, sendResponse) => | 
| { | 
| let sender = {}; | 
|  | 
| // Add "page" and "frame" if the message was sent by a content script. | 
| // If sent by popup or the background page itself, there is no "tab". | 
| if ("tab" in rawSender) | 
| { | 
| sender.page = new Page(rawSender.tab); | 
| @@ -651,34 +651,34 @@ | 
| }); | 
|  | 
|  | 
| /* Storage */ | 
|  | 
| ext.storage = { | 
| get(keys, callback) | 
| { | 
| -      chrome.storage.local.get(keys, callback); | 
| +      browser.storage.local.get(keys, callback); | 
| }, | 
| set(key, value, callback) | 
| { | 
| let items = {}; | 
| items[key] = value; | 
| -      chrome.storage.local.set(items, callback); | 
| +      browser.storage.local.set(items, callback); | 
| }, | 
| remove(key, callback) | 
| { | 
| -      chrome.storage.local.remove(key, callback); | 
| +      browser.storage.local.remove(key, callback); | 
| }, | 
| -    onChanged: chrome.storage.onChanged | 
| +    onChanged: browser.storage.onChanged | 
| }; | 
|  | 
| /* Windows */ | 
| ext.windows = { | 
| create(createData, callback) | 
| { | 
| -      chrome.windows.create(createData, createdWindow => | 
| +      browser.windows.create(createData, createdWindow => | 
| { | 
| afterTabLoaded(callback)(createdWindow.tabs[0]); | 
| }); | 
| } | 
| }; | 
| }()); | 
|  |