| 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-2016 Eyeo GmbH | 3 * Copyright (C) 2006-2017 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() |
| 20 { | 21 { |
| 21 /* Pages */ | 22 /* Pages */ |
| 22 | 23 |
| 23 let Page = ext.Page = function(tab) | 24 let Page = ext.Page = function(tab) |
| 24 { | 25 { |
| 25 this.id = tab.id; | 26 this.id = tab.id; |
| 26 this._url = tab.url && new URL(tab.url); | 27 this._url = tab.url && new URL(tab.url); |
| 27 | 28 |
| 28 this.browserAction = new BrowserAction(tab.id); | 29 this.browserAction = new BrowserAction(tab.id); |
| 29 this.contextMenus = new ContextMenus(this); | 30 this.contextMenus = new ContextMenus(this); |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 if (!frames) | 112 if (!frames) |
| 112 frames = framesOfTabs[tabId] = Object.create(null); | 113 frames = framesOfTabs[tabId] = Object.create(null); |
| 113 | 114 |
| 114 let frame = frames[frameId]; | 115 let frame = frames[frameId]; |
| 115 if (!frame) | 116 if (!frame) |
| 116 frame = frames[frameId] = {}; | 117 frame = frames[frameId] = {}; |
| 117 | 118 |
| 118 return frame; | 119 return frame; |
| 119 } | 120 } |
| 120 | 121 |
| 121 chrome.webNavigation.onBeforeNavigate.addListener(details => | 122 function updatePageFrameStructure(frameId, tabId, url, parentFrameId) |
| 122 { | |
| 123 // Capture parent frame here because onCommitted doesn't get this info. | |
| 124 let frame = createFrame(details.tabId, details.frameId); | |
| 125 frame.parent = framesOfTabs[details.tabId][details.parentFrameId] || null; | |
| 126 }); | |
| 127 | |
| 128 let eagerlyUpdatedPages = new ext.PageMap(); | |
| 129 | |
| 130 ext._updatePageFrameStructure = (frameId, tabId, url, eager) => | |
| 131 { | 123 { |
| 132 if (frameId == 0) | 124 if (frameId == 0) |
| 133 { | 125 { |
| 134 let page = new Page({id: tabId, url}); | 126 let page = new Page({id: tabId, url}); |
| 135 | 127 |
| 136 if (eagerlyUpdatedPages.get(page) != url) | 128 ext._removeFromAllPageMaps(tabId); |
| 137 { | 129 |
| 138 ext._removeFromAllPageMaps(tabId); | 130 chrome.tabs.get(tabId, () => |
| 139 | 131 { |
| 140 // When a sitekey header is received we must immediately update the page | 132 // If the tab is prerendered, chrome.tabs.get() sets |
| 141 // structure in order to record and use the key. We want to avoid | 133 // chrome.runtime.lastError and we have to dispatch the onLoading event, |
| 142 // trashing the page structure if the onCommitted event is then fired | 134 // since the onUpdated event isn't dispatched for prerendered tabs. |
| 143 // for the page. | 135 // However, we have to keep relying on the unUpdated event for tabs that |
| 144 if (eager) | 136 // are already visible. Otherwise browser action changes get overridden |
| 145 eagerlyUpdatedPages.set(page, url); | 137 // when Chrome automatically resets them on navigation. |
| 146 | 138 if (chrome.runtime.lastError) |
| 147 chrome.tabs.get(tabId, () => | 139 ext.pages.onLoading._dispatch(page); |
| 148 { | 140 }); |
| 149 // If the tab is prerendered, chrome.tabs.get() sets | 141 } |
| 150 // chrome.runtime.lastError and we have to dispatch the | 142 |
| 151 // onLoading event, since the onUpdated event isn't | 143 // Update frame parent and URL in frame structure |
| 152 // dispatched for prerendered tabs. However, we have to | |
| 153 // keep relying on the unUpdated event for tabs that are | |
| 154 // already visible. Otherwise browser action changes get | |
| 155 // overridden when Chrome automatically resets them on | |
| 156 // navigation. | |
| 157 if (chrome.runtime.lastError) | |
| 158 ext.pages.onLoading._dispatch(page); | |
| 159 }); | |
| 160 } | |
| 161 } | |
| 162 | |
| 163 // Update frame URL in frame structure | |
| 164 let frame = createFrame(tabId, frameId); | 144 let frame = createFrame(tabId, frameId); |
| 165 frame.url = new URL(url); | 145 frame.url = new URL(url); |
| 166 }; | 146 frame.parent = framesOfTabs[tabId][parentFrameId] || null; |
| 167 | 147 } |
| 168 chrome.webNavigation.onCommitted.addListener(details => | 148 |
| 169 { | 149 chrome.webRequest.onHeadersReceived.addListener(details => |
| 170 ext._updatePageFrameStructure(details.frameId, details.tabId, details.url); | 150 { |
| 151 // We have to update the frame structure when switching to a new |
| 152 // document, so that we process any further requests made by that |
| 153 // document in the right context. Unfortunately, we cannot rely |
| 154 // on webNavigation.onCommitted since it isn't guaranteed to fire |
| 155 // before any subresources start downloading[1]. As an |
| 156 // alternative we use webRequest.onHeadersReceived for HTTP(S) |
| 157 // URLs, being careful to ignore any responses that won't cause |
| 158 // the document to be replaced. |
| 159 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=665843 |
| 160 |
| 161 // The request has been processed without replacing the document. |
| 162 // https://chromium.googlesource.com/chromium/src/+/02d3f50b/content/browser
/frame_host/navigation_request.cc#473 |
| 163 if (details.statusCode == 204 || details.statusCode == 205) |
| 164 return; |
| 165 |
| 166 for (let header of details.responseHeaders) |
| 167 { |
| 168 let headerName = header.name.toLowerCase(); |
| 169 |
| 170 // For redirects we must wait for the next response in order |
| 171 // to know if the document will be replaced. Note: Chrome |
| 172 // performs a redirect only if there is a "Location" header with |
| 173 // a non-empty value and a known redirect status code. |
| 174 // https://chromium.googlesource.com/chromium/src/+/39a7d96/net/http/http_
response_headers.cc#929 |
| 175 if (headerName == "location" && header.value && |
| 176 (details.statusCode == 301 || details.statusCode == 302 || |
| 177 details.statusCode == 303 || details.statusCode == 307 || |
| 178 details.statusCode == 308)) |
| 179 return; |
| 180 |
| 181 // If the response initiates a download the document won't be |
| 182 // replaced. Chrome initiates a download if there is a |
| 183 // "Content-Disposition" with a valid and non-empty value other |
| 184 // than "inline". |
| 185 // https://chromium.googlesource.com/chromium/src/+/02d3f50b/content/brows
er/loader/mime_sniffing_resource_handler.cc#534 |
| 186 // https://chromium.googlesource.com/chromium/src/+/02d3f50b/net/http/http
_content_disposition.cc#374 |
| 187 // https://chromium.googlesource.com/chromium/src/+/16e2688e/net/http/http
_util.cc#431 |
| 188 if (headerName == "content-disposition") |
| 189 { |
| 190 let disposition = header.value.split(";")[0].replace(/[ \t]+$/, ""); |
| 191 if (disposition.toLowerCase() != "inline" && |
| 192 /^[\x21-\x7E]+$/.test(disposition) && |
| 193 !/[()<>@,;:\\"/[\]?={}]/.test(disposition)) |
| 194 return; |
| 195 } |
| 196 |
| 197 // The value of the "Content-Type" header also determines if Chrome will |
| 198 // initiate a download, or otherwise how the response will be rendered. |
| 199 // We only need to consider responses which will result in a navigation |
| 200 // and be rendered as HTML or similar. |
| 201 // Note: Chrome might render the response as HTML if the "Content-Type" |
| 202 // header is missing, invalid or unknown. |
| 203 // https://chromium.googlesource.com/chromium/src/+/99f41af9/net/http/http
_util.cc#66 |
| 204 // https://chromium.googlesource.com/chromium/src/+/3130418a/net/base/mime
_sniffer.cc#667 |
| 205 if (headerName == "content-type") |
| 206 { |
| 207 let mediaType = header.value.split(/[ \t;(]/)[0].toLowerCase(); |
| 208 if (mediaType.includes("/") && |
| 209 mediaType != "*/*" && |
| 210 mediaType != "application/unknown" && |
| 211 mediaType != "unknown/unknown" && |
| 212 mediaType != "text/html" && |
| 213 mediaType != "text/xml" && |
| 214 mediaType != "application/xml" && |
| 215 mediaType != "application/xhtml+xml" && |
| 216 mediaType != "image/svg+xml") |
| 217 return; |
| 218 } |
| 219 } |
| 220 |
| 221 updatePageFrameStructure(details.frameId, details.tabId, details.url, |
| 222 details.parentFrameId); |
| 223 }, |
| 224 {types: ["main_frame", "sub_frame"], urls: ["http://*/*", "https://*/*"]}, |
| 225 ["responseHeaders"]); |
| 226 |
| 227 chrome.webNavigation.onBeforeNavigate.addListener(details => |
| 228 { |
| 229 // Since we can only listen for HTTP(S) responses using |
| 230 // webRequest.onHeadersReceived we must update the page structure here for |
| 231 // other navigations. |
| 232 let url = new URL(details.url); |
| 233 if (url.protocol != "http:" && url.protocol != "https:") |
| 234 { |
| 235 updatePageFrameStructure(details.frameId, details.tabId, details.url, |
| 236 details.parentFrameId); |
| 237 } |
| 171 }); | 238 }); |
| 172 | 239 |
| 173 function forgetTab(tabId) | 240 function forgetTab(tabId) |
| 174 { | 241 { |
| 175 ext.pages.onRemoved._dispatch(tabId); | 242 ext.pages.onRemoved._dispatch(tabId); |
| 176 | 243 |
| 177 ext._removeFromAllPageMaps(tabId); | 244 ext._removeFromAllPageMaps(tabId); |
| 178 delete framesOfTabs[tabId]; | 245 delete framesOfTabs[tabId]; |
| 179 } | 246 } |
| 180 | 247 |
| (...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 373 | 440 |
| 374 /* Web requests */ | 441 /* Web requests */ |
| 375 | 442 |
| 376 let framesOfTabs = Object.create(null); | 443 let framesOfTabs = Object.create(null); |
| 377 | 444 |
| 378 ext.getFrame = (tabId, frameId) => | 445 ext.getFrame = (tabId, frameId) => |
| 379 { | 446 { |
| 380 return (framesOfTabs[tabId] || {})[frameId]; | 447 return (framesOfTabs[tabId] || {})[frameId]; |
| 381 }; | 448 }; |
| 382 | 449 |
| 383 let handlerBehaviorChangedQuota = ( | 450 let handlerBehaviorChangedQuota = |
| 384 chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES | 451 chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES; |
| 385 ); | |
| 386 | 452 |
| 387 function propagateHandlerBehaviorChange() | 453 function propagateHandlerBehaviorChange() |
| 388 { | 454 { |
| 389 // Make sure to not call handlerBehaviorChanged() more often than allowed | 455 // Make sure to not call handlerBehaviorChanged() more often than allowed |
| 390 // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. | 456 // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. |
| 391 // Otherwise Chrome notifies the user that this extension is causing issues. | 457 // Otherwise Chrome notifies the user that this extension is causing issues. |
| 392 if (handlerBehaviorChangedQuota > 0) | 458 if (handlerBehaviorChangedQuota > 0) |
| 393 { | 459 { |
| 394 chrome.webNavigation.onBeforeNavigate.removeListener( | 460 chrome.webNavigation.onBeforeNavigate.removeListener( |
| 395 propagateHandlerBehaviorChange | 461 propagateHandlerBehaviorChange |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 470 let results = ext.webRequest.onBeforeRequest._dispatch( | 536 let results = ext.webRequest.onBeforeRequest._dispatch( |
| 471 new URL(details.url), | 537 new URL(details.url), |
| 472 type.toUpperCase(), | 538 type.toUpperCase(), |
| 473 new Page({id: details.tabId}), | 539 new Page({id: details.tabId}), |
| 474 frame | 540 frame |
| 475 ); | 541 ); |
| 476 | 542 |
| 477 if (results.indexOf(false) != -1) | 543 if (results.indexOf(false) != -1) |
| 478 return {cancel: true}; | 544 return {cancel: true}; |
| 479 } | 545 } |
| 480 }, {urls: ["http://*/*", "https://*/*"]}, ["blocking"]); | 546 }, {urls: ["<all_urls>"]}, ["blocking"]); |
| 481 | 547 |
| 482 | 548 |
| 483 /* Message passing */ | 549 /* Message passing */ |
| 484 | 550 |
| 485 chrome.runtime.onMessage.addListener((message, rawSender, sendResponse) => | 551 chrome.runtime.onMessage.addListener((message, rawSender, sendResponse) => |
| 486 { | 552 { |
| 487 let sender = {}; | 553 let sender = {}; |
| 488 | 554 |
| 489 // Add "page" and "frame" if the message was sent by a content script. | 555 // Add "page" and "frame" if the message was sent by a content script. |
| 490 // If sent by popup or the background page itself, there is no "tab". | 556 // If sent by popup or the background page itself, there is no "tab". |
| (...skipping 11 matching lines...) Expand all Loading... |
| 502 | 568 |
| 503 let frame = frames[rawSender.frameId]; | 569 let frame = frames[rawSender.frameId]; |
| 504 if (frame) | 570 if (frame) |
| 505 return frame.parent; | 571 return frame.parent; |
| 506 | 572 |
| 507 return frames[0]; | 573 return frames[0]; |
| 508 } | 574 } |
| 509 }; | 575 }; |
| 510 } | 576 } |
| 511 | 577 |
| 512 let results = ext.onMessage._dispatch(message, sender, sendResponse); | 578 return ext.onMessage._dispatch( |
| 513 return results.indexOf(true) != -1; | 579 message, sender, sendResponse |
| 580 ).indexOf(true) != -1; |
| 514 }); | 581 }); |
| 515 | 582 |
| 516 | 583 |
| 517 /* Storage */ | 584 /* Storage */ |
| 518 | 585 |
| 519 ext.storage = { | 586 ext.storage = { |
| 520 get(keys, callback) | 587 get(keys, callback) |
| 521 { | 588 { |
| 522 chrome.storage.local.get(keys, callback); | 589 chrome.storage.local.get(keys, callback); |
| 523 }, | 590 }, |
| (...skipping 24 matching lines...) Expand all Loading... |
| 548 { | 615 { |
| 549 chrome.runtime.openOptionsPage(() => | 616 chrome.runtime.openOptionsPage(() => |
| 550 { | 617 { |
| 551 if (chrome.runtime.lastError) | 618 if (chrome.runtime.lastError) |
| 552 return; | 619 return; |
| 553 | 620 |
| 554 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => | 621 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => |
| 555 { | 622 { |
| 556 if (tabs.length > 0) | 623 if (tabs.length > 0) |
| 557 { | 624 { |
| 558 window.setTimeout(() => | 625 if (tabs[0].status == "complete") |
| 559 { | |
| 560 callback(new Page(tabs[0])); | 626 callback(new Page(tabs[0])); |
| 561 }); | 627 else |
| 628 afterTabLoaded(callback)(tabs[0]); |
| 562 } | 629 } |
| 563 }); | 630 }); |
| 564 }); | 631 }); |
| 565 } | 632 } |
| 566 }; | 633 }; |
| 567 } | 634 } |
| 568 else | 635 else |
| 569 { | 636 { |
| 570 // Edge does not yet support runtime.openOptionsPage (tested version 38) | 637 // Edge does not yet support runtime.openOptionsPage (tested version 38) |
| 571 // and so this workaround needs to stay for now. | 638 // and so this workaround needs to stay for now. |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 606 /* Windows */ | 673 /* Windows */ |
| 607 ext.windows = { | 674 ext.windows = { |
| 608 create(createData, callback) | 675 create(createData, callback) |
| 609 { | 676 { |
| 610 chrome.windows.create(createData, createdWindow => | 677 chrome.windows.create(createData, createdWindow => |
| 611 { | 678 { |
| 612 afterTabLoaded(callback)(createdWindow.tabs[0]); | 679 afterTabLoaded(callback)(createdWindow.tabs[0]); |
| 613 }); | 680 }); |
| 614 } | 681 } |
| 615 }; | 682 }; |
| 616 } | 683 }()); |
| LEFT | RIGHT |