| LEFT | RIGHT |
| 1 /* | 1 /* |
| 2 * This file is part of Adblock Plus <https://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
| 3 * Copyright (C) 2006-2017 eyeo GmbH | 3 * Copyright (C) 2006-present eyeo GmbH |
| 4 * | 4 * |
| 5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
| 6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
| 7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
| 8 * | 8 * |
| 9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
| 13 * | 13 * |
| 14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
| 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| 16 */ | 16 */ |
| 17 | 17 |
| 18 "use strict"; | 18 "use strict"; |
| 19 | 19 |
| 20 (function() | |
| 21 { | 20 { |
| 22 let nonEmptyPageMaps = new Set(); | 21 let nonEmptyPageMaps = new Set(); |
| 23 | 22 |
| 24 let PageMap = ext.PageMap = function() | 23 let PageMap = ext.PageMap = function() |
| 25 { | 24 { |
| 26 this._map = new Map(); | 25 this._map = new Map(); |
| 27 }; | 26 }; |
| 28 PageMap.prototype = { | 27 PageMap.prototype = { |
| 29 _delete(id) | 28 _delete(id) |
| 30 { | 29 { |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 94 let frame = frames.get(0); | 93 let frame = frames.get(0); |
| 95 if (frame) | 94 if (frame) |
| 96 return frame.url; | 95 return frame.url; |
| 97 } | 96 } |
| 98 }, | 97 }, |
| 99 sendMessage(message, responseCallback, frameId) | 98 sendMessage(message, responseCallback, frameId) |
| 100 { | 99 { |
| 101 let options = {}; | 100 let options = {}; |
| 102 if (typeof frameId != "undefined") | 101 if (typeof frameId != "undefined") |
| 103 options.frameId = frameId; | 102 options.frameId = frameId; |
| 104 chrome.tabs.sendMessage(this.id, message, options, responseCallback); | 103 browser.tabs.sendMessage(this.id, message, options, responseCallback); |
| 105 } | 104 } |
| 106 }; | 105 }; |
| 107 | 106 |
| 108 ext.getPage = id => new Page({id: parseInt(id, 10)}); | 107 ext.getPage = id => new Page({id: parseInt(id, 10)}); |
| 109 | 108 |
| 110 function afterTabLoaded(callback) | 109 function afterTabLoaded(callback) |
| 111 { | 110 { |
| 112 return openedTab => | 111 return openedTab => |
| 113 { | 112 { |
| 114 let onUpdated = (tabId, changeInfo, tab) => | 113 let onUpdated = (tabId, changeInfo, tab) => |
| 115 { | 114 { |
| 116 if (tabId == openedTab.id && changeInfo.status == "complete") | 115 if (tabId == openedTab.id && changeInfo.status == "complete") |
| 117 { | 116 { |
| 118 chrome.tabs.onUpdated.removeListener(onUpdated); | 117 browser.tabs.onUpdated.removeListener(onUpdated); |
| 119 callback(new Page(openedTab)); | 118 callback(new Page(openedTab)); |
| 120 } | 119 } |
| 121 }; | 120 }; |
| 122 chrome.tabs.onUpdated.addListener(onUpdated); | 121 browser.tabs.onUpdated.addListener(onUpdated); |
| 123 }; | 122 }; |
| 124 } | 123 } |
| 125 | 124 |
| 126 ext.pages = { | 125 ext.pages = { |
| 127 open(url, callback) | |
| 128 { | |
| 129 chrome.tabs.create({url}, callback && afterTabLoaded(callback)); | |
| 130 }, | |
| 131 query(info, callback) | |
| 132 { | |
| 133 let rawInfo = {}; | |
| 134 for (let property in info) | |
| 135 { | |
| 136 switch (property) | |
| 137 { | |
| 138 case "active": | |
| 139 case "lastFocusedWindow": | |
| 140 rawInfo[property] = info[property]; | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 chrome.tabs.query(rawInfo, tabs => | |
| 145 { | |
| 146 callback(tabs.map(tab => new Page(tab))); | |
| 147 }); | |
| 148 }, | |
| 149 onLoading: new ext._EventTarget(), | 126 onLoading: new ext._EventTarget(), |
| 150 onActivated: new ext._EventTarget(), | 127 onActivated: new ext._EventTarget(), |
| 151 onRemoved: new ext._EventTarget() | 128 onRemoved: new ext._EventTarget() |
| 152 }; | 129 }; |
| 153 | 130 |
| 154 chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => | 131 browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => |
| 155 { | 132 { |
| 156 if (changeInfo.status == "loading") | 133 if (changeInfo.status == "loading") |
| 157 ext.pages.onLoading._dispatch(new Page(tab)); | 134 ext.pages.onLoading._dispatch(new Page(tab)); |
| 158 }); | 135 }); |
| 159 | 136 |
| 160 function createFrame(tabId, frameId) | 137 function createFrame(tabId, frameId) |
| 161 { | 138 { |
| 162 let frames = framesOfTabs.get(tabId); | 139 let frames = framesOfTabs.get(tabId); |
| 163 if (!frames) | 140 if (!frames) |
| 164 { | 141 { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 177 } | 154 } |
| 178 | 155 |
| 179 function updatePageFrameStructure(frameId, tabId, url, parentFrameId) | 156 function updatePageFrameStructure(frameId, tabId, url, parentFrameId) |
| 180 { | 157 { |
| 181 if (frameId == 0) | 158 if (frameId == 0) |
| 182 { | 159 { |
| 183 let page = new Page({id: tabId, url}); | 160 let page = new Page({id: tabId, url}); |
| 184 | 161 |
| 185 ext._removeFromAllPageMaps(tabId); | 162 ext._removeFromAllPageMaps(tabId); |
| 186 | 163 |
| 187 chrome.tabs.get(tabId, () => | 164 browser.tabs.get(tabId, () => |
| 188 { | 165 { |
| 189 // If the tab is prerendered, chrome.tabs.get() sets | 166 // If the tab is prerendered, browser.tabs.get() sets |
| 190 // chrome.runtime.lastError and we have to dispatch the onLoading event, | 167 // browser.runtime.lastError and we have to dispatch the onLoading |
| 191 // since the onUpdated event isn't dispatched for prerendered tabs. | 168 // event, since the onUpdated event isn't dispatched for prerendered |
| 192 // However, we have to keep relying on the unUpdated event for tabs that | 169 // tabs. However, we have to keep relying on the onUpdated event for |
| 193 // are already visible. Otherwise browser action changes get overridden | 170 // tabs that are already visible. Otherwise browser action changes get |
| 194 // when Chrome automatically resets them on navigation. | 171 // overridden when Chrome automatically resets them on navigation. |
| 195 if (chrome.runtime.lastError) | 172 if (browser.runtime.lastError) |
| 196 ext.pages.onLoading._dispatch(page); | 173 ext.pages.onLoading._dispatch(page); |
| 197 }); | 174 }); |
| 198 } | 175 } |
| 199 | 176 |
| 200 // Update frame URL and parent in frame structure | 177 // Update frame URL and parent in frame structure |
| 201 let frame = createFrame(tabId, frameId); | 178 let frame = createFrame(tabId, frameId); |
| 202 frame.url = new URL(url); | 179 frame.url = new URL(url); |
| 203 | 180 |
| 204 let parentFrame = framesOfTabs.get(tabId).get(parentFrameId); | 181 let parentFrame = framesOfTabs.get(tabId).get(parentFrameId); |
| 205 if (parentFrame) | 182 if (parentFrame) |
| 206 frame.parent = parentFrame; | 183 frame.parent = parentFrame; |
| 207 } | 184 } |
| 208 | 185 |
| 209 chrome.webRequest.onHeadersReceived.addListener(details => | 186 browser.webRequest.onHeadersReceived.addListener(details => |
| 210 { | 187 { |
| 211 // We have to update the frame structure when switching to a new | 188 // We have to update the frame structure when switching to a new |
| 212 // document, so that we process any further requests made by that | 189 // document, so that we process any further requests made by that |
| 213 // document in the right context. Unfortunately, we cannot rely | 190 // document in the right context. Unfortunately, we cannot rely |
| 214 // on webNavigation.onCommitted since it isn't guaranteed to fire | 191 // on webNavigation.onCommitted since it isn't guaranteed to fire |
| 215 // before any subresources start downloading[1]. As an | 192 // before any subresources start downloading[1]. As an |
| 216 // alternative we use webRequest.onHeadersReceived for HTTP(S) | 193 // alternative we use webRequest.onHeadersReceived for HTTP(S) |
| 217 // URLs, being careful to ignore any responses that won't cause | 194 // URLs, being careful to ignore any responses that won't cause |
| 218 // the document to be replaced. | 195 // the document to be replaced. |
| 219 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=665843 | 196 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=665843 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 277 return; | 254 return; |
| 278 } | 255 } |
| 279 } | 256 } |
| 280 | 257 |
| 281 updatePageFrameStructure(details.frameId, details.tabId, details.url, | 258 updatePageFrameStructure(details.frameId, details.tabId, details.url, |
| 282 details.parentFrameId); | 259 details.parentFrameId); |
| 283 }, | 260 }, |
| 284 {types: ["main_frame", "sub_frame"], urls: ["http://*/*", "https://*/*"]}, | 261 {types: ["main_frame", "sub_frame"], urls: ["http://*/*", "https://*/*"]}, |
| 285 ["responseHeaders"]); | 262 ["responseHeaders"]); |
| 286 | 263 |
| 287 chrome.webNavigation.onBeforeNavigate.addListener(details => | 264 browser.webNavigation.onBeforeNavigate.addListener(details => |
| 288 { | 265 { |
| 289 // Since we can only listen for HTTP(S) responses using | 266 // Since we can only listen for HTTP(S) responses using |
| 290 // webRequest.onHeadersReceived we must update the page structure here for | 267 // webRequest.onHeadersReceived we must update the page structure here for |
| 291 // other navigations. | 268 // other navigations. |
| 292 let url = new URL(details.url); | 269 let url = new URL(details.url); |
| 293 if (url.protocol != "http:" && url.protocol != "https:") | 270 if (url.protocol != "http:" && url.protocol != "https:") |
| 294 { | 271 { |
| 295 updatePageFrameStructure(details.frameId, details.tabId, details.url, | 272 updatePageFrameStructure(details.frameId, details.tabId, details.url, |
| 296 details.parentFrameId); | 273 details.parentFrameId); |
| 297 } | 274 } |
| 298 }); | 275 }); |
| 299 | 276 |
| 300 function forgetTab(tabId) | 277 function forgetTab(tabId) |
| 301 { | 278 { |
| 302 ext.pages.onRemoved._dispatch(tabId); | 279 ext.pages.onRemoved._dispatch(tabId); |
| 303 | 280 |
| 304 ext._removeFromAllPageMaps(tabId); | 281 ext._removeFromAllPageMaps(tabId); |
| 305 framesOfTabs.delete(tabId); | 282 framesOfTabs.delete(tabId); |
| 306 } | 283 } |
| 307 | 284 |
| 308 chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => | 285 browser.tabs.onReplaced.addListener((addedTabId, removedTabId) => |
| 309 { | 286 { |
| 310 forgetTab(removedTabId); | 287 forgetTab(removedTabId); |
| 311 }); | 288 }); |
| 312 | 289 |
| 313 chrome.tabs.onRemoved.addListener(forgetTab); | 290 browser.tabs.onRemoved.addListener(forgetTab); |
| 314 | 291 |
| 315 chrome.tabs.onActivated.addListener(details => | 292 browser.tabs.onActivated.addListener(details => |
| 316 { | 293 { |
| 317 ext.pages.onActivated._dispatch(new Page({id: details.tabId})); | 294 ext.pages.onActivated._dispatch(new Page({id: details.tabId})); |
| 318 }); | 295 }); |
| 319 | 296 |
| 320 | 297 |
| 321 /* Browser actions */ | 298 /* Browser actions */ |
| 322 | 299 |
| 323 let BrowserAction = function(tabId) | 300 let BrowserAction = function(tabId) |
| 324 { | 301 { |
| 325 this._tabId = tabId; | 302 this._tabId = tabId; |
| 326 this._changes = null; | 303 this._changes = null; |
| 327 }; | 304 }; |
| 328 BrowserAction.prototype = { | 305 BrowserAction.prototype = { |
| 329 _applyChanges() | 306 _applyChanges() |
| 330 { | 307 { |
| 331 if ("iconPath" in this._changes) | 308 if ("iconPath" in this._changes) |
| 332 { | 309 { |
| 333 chrome.browserAction.setIcon({ | 310 // Firefox for Android displays the browser action not as an icon but |
| 334 tabId: this._tabId, | 311 // as a menu item. There is no icon, but such an option may be added in |
| 335 path: { | 312 // the future. |
| 313 // https://bugzilla.mozilla.org/show_bug.cgi?id=1331746 |
| 314 if ("setIcon" in browser.browserAction) |
| 315 { |
| 316 let path = { |
| 336 16: this._changes.iconPath.replace("$size", "16"), | 317 16: this._changes.iconPath.replace("$size", "16"), |
| 337 19: this._changes.iconPath.replace("$size", "19"), | 318 19: this._changes.iconPath.replace("$size", "19"), |
| 338 20: this._changes.iconPath.replace("$size", "20"), | 319 20: this._changes.iconPath.replace("$size", "20"), |
| 339 32: this._changes.iconPath.replace("$size", "32"), | 320 32: this._changes.iconPath.replace("$size", "32"), |
| 340 38: this._changes.iconPath.replace("$size", "38"), | 321 38: this._changes.iconPath.replace("$size", "38"), |
| 341 40: this._changes.iconPath.replace("$size", "40") | 322 40: this._changes.iconPath.replace("$size", "40") |
| 323 }; |
| 324 try |
| 325 { |
| 326 browser.browserAction.setIcon({tabId: this._tabId, path}); |
| 342 } | 327 } |
| 343 }); | 328 catch (e) |
| 329 { |
| 330 // Edge throws if passed icon sizes different than 19,20,38,40px. |
| 331 delete path[16]; |
| 332 delete path[32]; |
| 333 browser.browserAction.setIcon({tabId: this._tabId, path}); |
| 334 } |
| 335 } |
| 344 } | 336 } |
| 345 | 337 |
| 346 if ("badgeText" in this._changes) | 338 if ("badgeText" in this._changes) |
| 347 { | 339 { |
| 348 chrome.browserAction.setBadgeText({ | 340 // There is no badge on Firefox for Android; the browser action is |
| 349 tabId: this._tabId, | 341 // simply a menu item. |
| 350 text: this._changes.badgeText | 342 if ("setBadgeText" in browser.browserAction) |
| 351 }); | 343 { |
| 344 browser.browserAction.setBadgeText({ |
| 345 tabId: this._tabId, |
| 346 text: this._changes.badgeText |
| 347 }); |
| 348 } |
| 352 } | 349 } |
| 353 | 350 |
| 354 if ("badgeColor" in this._changes) | 351 if ("badgeColor" in this._changes) |
| 355 { | 352 { |
| 356 chrome.browserAction.setBadgeBackgroundColor({ | 353 // There is no badge on Firefox for Android; the browser action is |
| 357 tabId: this._tabId, | 354 // simply a menu item. |
| 358 color: this._changes.badgeColor | 355 if ("setBadgeBackgroundColor" in browser.browserAction) |
| 359 }); | 356 { |
| 357 browser.browserAction.setBadgeBackgroundColor({ |
| 358 tabId: this._tabId, |
| 359 color: this._changes.badgeColor |
| 360 }); |
| 361 } |
| 360 } | 362 } |
| 361 | 363 |
| 362 this._changes = null; | 364 this._changes = null; |
| 363 }, | 365 }, |
| 364 _queueChanges() | 366 _queueChanges() |
| 365 { | 367 { |
| 366 chrome.tabs.get(this._tabId, () => | 368 browser.tabs.get(this._tabId, () => |
| 367 { | 369 { |
| 368 // If the tab is prerendered, chrome.tabs.get() sets | 370 // If the tab is prerendered, browser.tabs.get() sets |
| 369 // chrome.runtime.lastError and we have to delay our changes | 371 // browser.runtime.lastError and we have to delay our changes |
| 370 // until the currently visible tab is replaced with the | 372 // until the currently visible tab is replaced with the |
| 371 // prerendered tab. Otherwise chrome.browserAction.set* fails. | 373 // prerendered tab. Otherwise browser.browserAction.set* fails. |
| 372 if (chrome.runtime.lastError) | 374 if (browser.runtime.lastError) |
| 373 { | 375 { |
| 374 let onReplaced = (addedTabId, removedTabId) => | 376 let onReplaced = (addedTabId, removedTabId) => |
| 375 { | 377 { |
| 376 if (addedTabId == this._tabId) | 378 if (addedTabId == this._tabId) |
| 377 { | 379 { |
| 378 chrome.tabs.onReplaced.removeListener(onReplaced); | 380 browser.tabs.onReplaced.removeListener(onReplaced); |
| 379 this._applyChanges(); | 381 this._applyChanges(); |
| 380 } | 382 } |
| 381 }; | 383 }; |
| 382 chrome.tabs.onReplaced.addListener(onReplaced); | 384 browser.tabs.onReplaced.addListener(onReplaced); |
| 383 } | 385 } |
| 384 else | 386 else |
| 385 { | 387 { |
| 386 this._applyChanges(); | 388 this._applyChanges(); |
| 387 } | 389 } |
| 388 }); | 390 }); |
| 389 }, | 391 }, |
| 390 _addChange(name, value) | 392 _addChange(name, value) |
| 391 { | 393 { |
| 392 if (!this._changes) | 394 if (!this._changes) |
| (...skipping 26 matching lines...) Expand all Loading... |
| 419 }; | 421 }; |
| 420 | 422 |
| 421 | 423 |
| 422 /* Context menus */ | 424 /* Context menus */ |
| 423 | 425 |
| 424 let contextMenuItems = new ext.PageMap(); | 426 let contextMenuItems = new ext.PageMap(); |
| 425 let contextMenuUpdating = false; | 427 let contextMenuUpdating = false; |
| 426 | 428 |
| 427 let updateContextMenu = () => | 429 let updateContextMenu = () => |
| 428 { | 430 { |
| 429 if (contextMenuUpdating) | 431 // Firefox for Android does not support context menus. |
| 432 // https://bugzilla.mozilla.org/show_bug.cgi?id=1269062 |
| 433 if (!("contextMenus" in browser) || contextMenuUpdating) |
| 430 return; | 434 return; |
| 431 | 435 |
| 432 contextMenuUpdating = true; | 436 contextMenuUpdating = true; |
| 433 | 437 |
| 434 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => | 438 browser.tabs.query({active: true, lastFocusedWindow: true}, tabs => |
| 435 { | 439 { |
| 436 chrome.contextMenus.removeAll(() => | 440 browser.contextMenus.removeAll(() => |
| 437 { | 441 { |
| 438 contextMenuUpdating = false; | 442 contextMenuUpdating = false; |
| 439 | 443 |
| 440 if (tabs.length == 0) | 444 if (tabs.length == 0) |
| 441 return; | 445 return; |
| 442 | 446 |
| 443 let items = contextMenuItems.get({id: tabs[0].id}); | 447 let items = contextMenuItems.get({id: tabs[0].id}); |
| 444 | 448 |
| 445 if (!items) | 449 if (!items) |
| 446 return; | 450 return; |
| 447 | 451 |
| 448 items.forEach(item => | 452 items.forEach(item => |
| 449 { | 453 { |
| 450 chrome.contextMenus.create({ | 454 browser.contextMenus.create({ |
| 451 title: item.title, | 455 title: item.title, |
| 452 contexts: item.contexts, | 456 contexts: item.contexts, |
| 453 onclick(info, tab) | 457 onclick(info, tab) |
| 454 { | 458 { |
| 455 item.onclick(new Page(tab), info); | 459 item.onclick(new Page(tab), info); |
| 456 } | 460 } |
| 457 }); | 461 }); |
| 458 }); | 462 }); |
| 459 }); | 463 }); |
| 460 }); | 464 }); |
| (...skipping 21 matching lines...) Expand all Loading... |
| 482 let index = items.indexOf(item); | 486 let index = items.indexOf(item); |
| 483 if (index != -1) | 487 if (index != -1) |
| 484 { | 488 { |
| 485 items.splice(index, 1); | 489 items.splice(index, 1); |
| 486 updateContextMenu(); | 490 updateContextMenu(); |
| 487 } | 491 } |
| 488 } | 492 } |
| 489 } | 493 } |
| 490 }; | 494 }; |
| 491 | 495 |
| 492 chrome.tabs.onActivated.addListener(updateContextMenu); | 496 browser.tabs.onActivated.addListener(updateContextMenu); |
| 493 | 497 |
| 494 chrome.windows.onFocusChanged.addListener(windowId => | 498 if ("windows" in browser) |
| 495 { | 499 { |
| 496 if (windowId != chrome.windows.WINDOW_ID_NONE) | 500 browser.windows.onFocusChanged.addListener(windowId => |
| 497 updateContextMenu(); | 501 { |
| 498 }); | 502 if (windowId != browser.windows.WINDOW_ID_NONE) |
| 503 updateContextMenu(); |
| 504 }); |
| 505 } |
| 499 | 506 |
| 500 | 507 |
| 501 /* Web requests */ | 508 /* Web requests */ |
| 502 | 509 |
| 503 let framesOfTabs = new Map(); | 510 let framesOfTabs = new Map(); |
| 504 | 511 |
| 505 ext.getFrame = (tabId, frameId) => | 512 ext.getFrame = (tabId, frameId) => |
| 506 { | 513 { |
| 507 let frames = framesOfTabs.get(tabId); | 514 let frames = framesOfTabs.get(tabId); |
| 508 return frames && frames.get(frameId); | 515 return frames && frames.get(frameId); |
| 509 }; | 516 }; |
| 510 | 517 |
| 511 let handlerBehaviorChangedQuota = | 518 let handlerBehaviorChangedQuota = |
| 512 chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES; | 519 browser.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES; |
| 513 | 520 |
| 514 function propagateHandlerBehaviorChange() | 521 function propagateHandlerBehaviorChange() |
| 515 { | 522 { |
| 516 // Make sure to not call handlerBehaviorChanged() more often than allowed | 523 // Make sure to not call handlerBehaviorChanged() more often than allowed |
| 517 // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. | 524 // by browser.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. |
| 518 // Otherwise Chrome notifies the user that this extension is causing issues. | 525 // Otherwise Chrome notifies the user that this extension is causing issues. |
| 519 if (handlerBehaviorChangedQuota > 0) | 526 if (handlerBehaviorChangedQuota > 0) |
| 520 { | 527 { |
| 521 chrome.webNavigation.onBeforeNavigate.removeListener( | 528 browser.webNavigation.onBeforeNavigate.removeListener( |
| 522 propagateHandlerBehaviorChange | 529 propagateHandlerBehaviorChange |
| 523 ); | 530 ); |
| 524 chrome.webRequest.handlerBehaviorChanged(); | 531 browser.webRequest.handlerBehaviorChanged(); |
| 525 | 532 |
| 526 handlerBehaviorChangedQuota--; | 533 handlerBehaviorChangedQuota--; |
| 527 setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000); | 534 setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000); |
| 528 } | 535 } |
| 529 } | 536 } |
| 530 | 537 |
| 531 ext.webRequest = { | 538 ext.webRequest = { |
| 532 onBeforeRequest: new ext._EventTarget(), | 539 onBeforeRequest: new ext._EventTarget(), |
| 533 handlerBehaviorChanged() | 540 handlerBehaviorChanged() |
| 534 { | 541 { |
| 535 // Defer handlerBehaviorChanged() until navigation occurs. | 542 // Defer handlerBehaviorChanged() until navigation occurs. |
| 536 // There wouldn't be any visible effect when calling it earlier, | 543 // There wouldn't be any visible effect when calling it earlier, |
| 537 // but it's an expensive operation and that way we avoid to call | 544 // but it's an expensive operation and that way we avoid to call |
| 538 // it multiple times, if multiple filters are added/removed. | 545 // it multiple times, if multiple filters are added/removed. |
| 539 let {onBeforeNavigate} = chrome.webNavigation; | 546 let {onBeforeNavigate} = browser.webNavigation; |
| 540 if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange)) | 547 if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange)) |
| 541 onBeforeNavigate.addListener(propagateHandlerBehaviorChange); | 548 onBeforeNavigate.addListener(propagateHandlerBehaviorChange); |
| 542 } | 549 } |
| 543 }; | 550 }; |
| 544 | 551 |
| 545 chrome.tabs.query({}, tabs => | 552 browser.tabs.query({}, tabs => |
| 546 { | 553 { |
| 547 tabs.forEach(tab => | 554 tabs.forEach(tab => |
| 548 { | 555 { |
| 549 chrome.webNavigation.getAllFrames({tabId: tab.id}, details => | 556 browser.webNavigation.getAllFrames({tabId: tab.id}, details => |
| 550 { | 557 { |
| 551 if (details && details.length > 0) | 558 if (details && details.length > 0) |
| 552 { | 559 { |
| 553 let frames = new Map(); | 560 let frames = new Map(); |
| 554 framesOfTabs.set(tab.id, frames); | 561 framesOfTabs.set(tab.id, frames); |
| 555 | 562 |
| 556 for (let detail of details) | 563 for (let detail of details) |
| 557 { | 564 { |
| 558 let frame = {url: new URL(detail.url)}; | 565 let frame = {url: new URL(detail.url)}; |
| 559 frames.set(detail.frameId, frame); | 566 frames.set(detail.frameId, frame); |
| 560 | 567 |
| 561 if (detail.parentFrameId != -1) | 568 if (detail.parentFrameId != -1) |
| 562 frame.parent = frames.get(detail.parentFrameId); | 569 frame.parent = frames.get(detail.parentFrameId); |
| 563 } | 570 } |
| 564 } | 571 } |
| 565 }); | 572 }); |
| 566 }); | 573 }); |
| 567 }); | 574 }); |
| 568 | 575 |
| 569 chrome.webRequest.onBeforeRequest.addListener(details => | 576 browser.webRequest.onBeforeRequest.addListener(details => |
| 570 { | 577 { |
| 571 // The high-level code isn't interested in requests that aren't | 578 // The high-level code isn't interested in requests that aren't |
| 572 // related to a tab or requests loading a top-level document, | 579 // related to a tab or requests loading a top-level document, |
| 573 // those should never be blocked. | 580 // those should never be blocked. |
| 574 if (details.type == "main_frame") | 581 if (details.type == "main_frame") |
| 575 return; | 582 return; |
| 576 | 583 |
| 577 // Filter out requests from non web protocols. Ideally, we'd explicitly | 584 // Filter out requests from non web protocols. Ideally, we'd explicitly |
| 578 // specify the protocols we are interested in (i.e. http://, https://, | 585 // specify the protocols we are interested in (i.e. http://, https://, |
| 579 // ws:// and wss://) with the url patterns, given below, when adding this | 586 // ws:// and wss://) with the url patterns, given below, when adding this |
| (...skipping 23 matching lines...) Expand all Loading... |
| 603 } | 610 } |
| 604 | 611 |
| 605 if (ext.webRequest.onBeforeRequest._dispatch( | 612 if (ext.webRequest.onBeforeRequest._dispatch( |
| 606 url, type, page, frame).includes(false)) | 613 url, type, page, frame).includes(false)) |
| 607 return {cancel: true}; | 614 return {cancel: true}; |
| 608 }, {urls: ["<all_urls>"]}, ["blocking"]); | 615 }, {urls: ["<all_urls>"]}, ["blocking"]); |
| 609 | 616 |
| 610 | 617 |
| 611 /* Message passing */ | 618 /* Message passing */ |
| 612 | 619 |
| 613 chrome.runtime.onMessage.addListener((message, rawSender, sendResponse) => | 620 browser.runtime.onMessage.addListener((message, rawSender, sendResponse) => |
| 614 { | 621 { |
| 615 let sender = {}; | 622 let sender = {}; |
| 616 | 623 |
| 617 // Add "page" and "frame" if the message was sent by a content script. | 624 // Add "page" and "frame" if the message was sent by a content script. |
| 618 // If sent by popup or the background page itself, there is no "tab". | 625 // If sent by popup or the background page itself, there is no "tab". |
| 619 if ("tab" in rawSender) | 626 if ("tab" in rawSender) |
| 620 { | 627 { |
| 621 sender.page = new Page(rawSender.tab); | 628 sender.page = new Page(rawSender.tab); |
| 622 sender.frame = { | 629 sender.frame = { |
| 623 id: rawSender.frameId, | 630 id: rawSender.frameId, |
| (...skipping 11 matching lines...) Expand all Loading... |
| 635 if (frame) | 642 if (frame) |
| 636 return frame.parent || null; | 643 return frame.parent || null; |
| 637 | 644 |
| 638 return frames.get(0) || null; | 645 return frames.get(0) || null; |
| 639 } | 646 } |
| 640 }; | 647 }; |
| 641 } | 648 } |
| 642 | 649 |
| 643 return ext.onMessage._dispatch( | 650 return ext.onMessage._dispatch( |
| 644 message, sender, sendResponse | 651 message, sender, sendResponse |
| 645 ).indexOf(true) != -1; | 652 ).includes(true); |
| 646 }); | 653 }); |
| 647 | 654 |
| 648 | 655 |
| 649 /* Storage */ | 656 /* Storage */ |
| 650 | 657 |
| 651 ext.storage = { | 658 ext.storage = { |
| 652 get(keys, callback) | 659 get(keys, callback) |
| 653 { | 660 { |
| 654 chrome.storage.local.get(keys, callback); | 661 browser.storage.local.get(keys, callback); |
| 655 }, | 662 }, |
| 656 set(key, value, callback) | 663 set(key, value, callback) |
| 657 { | 664 { |
| 658 let items = {}; | 665 let items = {}; |
| 659 items[key] = value; | 666 items[key] = value; |
| 660 chrome.storage.local.set(items, callback); | 667 browser.storage.local.set(items, callback); |
| 661 }, | 668 }, |
| 662 remove(key, callback) | 669 remove(key, callback) |
| 663 { | 670 { |
| 664 chrome.storage.local.remove(key, callback); | 671 browser.storage.local.remove(key, callback); |
| 665 }, | 672 }, |
| 666 onChanged: chrome.storage.onChanged | 673 onChanged: browser.storage.onChanged |
| 667 }; | 674 }; |
| 668 | |
| 669 /* Options */ | |
| 670 | |
| 671 if ("openOptionsPage" in chrome.runtime) | |
| 672 { | |
| 673 ext.showOptions = callback => | |
| 674 { | |
| 675 if (!callback) | |
| 676 { | |
| 677 chrome.runtime.openOptionsPage(); | |
| 678 } | |
| 679 else | |
| 680 { | |
| 681 chrome.runtime.openOptionsPage(() => | |
| 682 { | |
| 683 if (chrome.runtime.lastError) | |
| 684 return; | |
| 685 | |
| 686 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => | |
| 687 { | |
| 688 if (tabs.length > 0) | |
| 689 { | |
| 690 if (tabs[0].status == "complete") | |
| 691 callback(new Page(tabs[0])); | |
| 692 else | |
| 693 afterTabLoaded(callback)(tabs[0]); | |
| 694 } | |
| 695 }); | |
| 696 }); | |
| 697 } | |
| 698 }; | |
| 699 } | |
| 700 else | |
| 701 { | |
| 702 // Edge does not yet support runtime.openOptionsPage (tested version 38) | |
| 703 // and so this workaround needs to stay for now. | |
| 704 // We are not using extension.getURL to get the absolute path here | |
| 705 // because of the Edge issue: | |
| 706 // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/1027
6332/ | |
| 707 ext.showOptions = callback => | |
| 708 { | |
| 709 chrome.windows.getLastFocused(win => | |
| 710 { | |
| 711 let optionsUrl = "options.html"; | |
| 712 let queryInfo = {url: optionsUrl}; | |
| 713 | |
| 714 // extension pages can't be accessed in incognito windows. In order to | |
| 715 // correctly mimic the way in which Chrome opens extension options, | |
| 716 // we have to focus the options page in any other window. | |
| 717 if (!win.incognito) | |
| 718 queryInfo.windowId = win.id; | |
| 719 | |
| 720 chrome.tabs.query(queryInfo, tabs => | |
| 721 { | |
| 722 if (tabs.length > 0) | |
| 723 { | |
| 724 let tab = tabs[0]; | |
| 725 | |
| 726 chrome.windows.update(tab.windowId, {focused: true}); | |
| 727 chrome.tabs.update(tab.id, {active: true}); | |
| 728 | |
| 729 if (callback) | |
| 730 callback(new Page(tab)); | |
| 731 } | |
| 732 else | |
| 733 { | |
| 734 ext.pages.open(optionsUrl, callback); | |
| 735 } | |
| 736 }); | |
| 737 }); | |
| 738 }; | |
| 739 } | |
| 740 | 675 |
| 741 /* Windows */ | 676 /* Windows */ |
| 742 ext.windows = { | 677 ext.windows = { |
| 743 create(createData, callback) | 678 create(createData, callback) |
| 744 { | 679 { |
| 745 chrome.windows.create(createData, createdWindow => | 680 browser.windows.create(createData, createdWindow => |
| 746 { | 681 { |
| 747 afterTabLoaded(callback)(createdWindow.tabs[0]); | 682 afterTabLoaded(callback)(createdWindow.tabs[0]); |
| 748 }); | 683 }); |
| 749 } | 684 } |
| 750 }; | 685 }; |
| 751 }()); | 686 } |
| LEFT | RIGHT |