Left: | ||
Right: |
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 ); | |
Sebastian Noack
2017/02/09 01:04:49
The parenthesis here seem unnecessary.
kzar
2017/02/20 10:27:29
Done.
| |
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 |
Sebastian Noack
2017/02/09 01:04:49
I wonder whether we can perhaps shorten the variab
kzar
2017/02/20 10:27:28
I don't mind the wrapping too much here and I can'
| |
396 ); | 462 ); |
397 chrome.webRequest.handlerBehaviorChanged(); | 463 chrome.webRequest.handlerBehaviorChanged(); |
398 | 464 |
399 handlerBehaviorChangedQuota--; | 465 handlerBehaviorChangedQuota--; |
400 setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000); | 466 setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000); |
401 } | 467 } |
402 } | 468 } |
403 | 469 |
404 ext.webRequest = { | 470 ext.webRequest = { |
405 onBeforeRequest: new ext._EventTarget(), | 471 onBeforeRequest: new ext._EventTarget(), |
(...skipping 64 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 |
Sebastian Noack
2017/02/09 01:04:48
This would be another way to wrap the code here, w
kzar
2017/02/20 10:27:29
I've wrapped it in a similar way which I preferred
| |
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 |