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 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
248 { | 315 { |
249 if (addedTabId == this._tabId) | 316 if (addedTabId == this._tabId) |
250 { | 317 { |
251 chrome.tabs.onReplaced.removeListener(onReplaced); | 318 chrome.tabs.onReplaced.removeListener(onReplaced); |
252 this._applyChanges(); | 319 this._applyChanges(); |
253 } | 320 } |
254 }; | 321 }; |
255 chrome.tabs.onReplaced.addListener(onReplaced); | 322 chrome.tabs.onReplaced.addListener(onReplaced); |
256 } | 323 } |
257 else | 324 else |
| 325 { |
258 this._applyChanges(); | 326 this._applyChanges(); |
| 327 } |
259 }); | 328 }); |
260 }, | 329 }, |
261 _addChange(name, value) | 330 _addChange(name, value) |
262 { | 331 { |
263 if (!this._changes) | 332 if (!this._changes) |
264 { | 333 { |
265 this._changes = {}; | 334 this._changes = {}; |
266 this._queueChanges(); | 335 this._queueChanges(); |
267 } | 336 } |
268 | 337 |
269 this._changes[name] = value; | 338 this._changes[name] = value; |
270 }, | 339 }, |
271 setIcon(path) | 340 setIcon(path) |
272 { | 341 { |
273 this._addChange("iconPath", path); | 342 this._addChange("iconPath", path); |
274 }, | 343 }, |
275 setBadge(badge) | 344 setBadge(badge) |
276 { | 345 { |
277 if (!badge) | 346 if (!badge) |
| 347 { |
278 this._addChange("badgeText", ""); | 348 this._addChange("badgeText", ""); |
| 349 } |
279 else | 350 else |
280 { | 351 { |
281 if ("number" in badge) | 352 if ("number" in badge) |
282 this._addChange("badgeText", badge.number.toString()); | 353 this._addChange("badgeText", badge.number.toString()); |
283 | 354 |
284 if ("color" in badge) | 355 if ("color" in badge) |
285 this._addChange("badgeColor", badge.color); | 356 this._addChange("badgeColor", badge.color); |
286 } | 357 } |
287 } | 358 } |
288 }; | 359 }; |
(...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
465 let results = ext.webRequest.onBeforeRequest._dispatch( | 536 let results = ext.webRequest.onBeforeRequest._dispatch( |
466 new URL(details.url), | 537 new URL(details.url), |
467 type.toUpperCase(), | 538 type.toUpperCase(), |
468 new Page({id: details.tabId}), | 539 new Page({id: details.tabId}), |
469 frame | 540 frame |
470 ); | 541 ); |
471 | 542 |
472 if (results.indexOf(false) != -1) | 543 if (results.indexOf(false) != -1) |
473 return {cancel: true}; | 544 return {cancel: true}; |
474 } | 545 } |
475 }, {urls: ["http://*/*", "https://*/*"]}, ["blocking"]); | 546 }, {urls: ["<all_urls>"]}, ["blocking"]); |
476 | 547 |
477 | 548 |
478 /* Message passing */ | 549 /* Message passing */ |
479 | 550 |
480 chrome.runtime.onMessage.addListener((message, rawSender, sendResponse) => | 551 chrome.runtime.onMessage.addListener((message, rawSender, sendResponse) => |
481 { | 552 { |
482 let sender = {}; | 553 let sender = {}; |
483 | 554 |
484 // 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. |
485 // 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 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
530 onChanged: chrome.storage.onChanged | 601 onChanged: chrome.storage.onChanged |
531 }; | 602 }; |
532 | 603 |
533 /* Options */ | 604 /* Options */ |
534 | 605 |
535 if ("openOptionsPage" in chrome.runtime) | 606 if ("openOptionsPage" in chrome.runtime) |
536 { | 607 { |
537 ext.showOptions = callback => | 608 ext.showOptions = callback => |
538 { | 609 { |
539 if (!callback) | 610 if (!callback) |
| 611 { |
540 chrome.runtime.openOptionsPage(); | 612 chrome.runtime.openOptionsPage(); |
| 613 } |
541 else | 614 else |
542 { | 615 { |
543 chrome.runtime.openOptionsPage(() => | 616 chrome.runtime.openOptionsPage(() => |
544 { | 617 { |
545 if (chrome.runtime.lastError) | 618 if (chrome.runtime.lastError) |
546 return; | 619 return; |
547 | 620 |
548 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => | 621 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => |
549 { | 622 { |
550 if (tabs.length > 0) | 623 if (tabs.length > 0) |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
582 { | 655 { |
583 let tab = tabs[0]; | 656 let tab = tabs[0]; |
584 | 657 |
585 chrome.windows.update(tab.windowId, {focused: true}); | 658 chrome.windows.update(tab.windowId, {focused: true}); |
586 chrome.tabs.update(tab.id, {active: true}); | 659 chrome.tabs.update(tab.id, {active: true}); |
587 | 660 |
588 if (callback) | 661 if (callback) |
589 callback(new Page(tab)); | 662 callback(new Page(tab)); |
590 } | 663 } |
591 else | 664 else |
| 665 { |
592 ext.pages.open(optionsUrl, callback); | 666 ext.pages.open(optionsUrl, callback); |
| 667 } |
593 }); | 668 }); |
594 }); | 669 }); |
595 }; | 670 }; |
596 } | 671 } |
597 | 672 |
598 /* Windows */ | 673 /* Windows */ |
599 ext.windows = { | 674 ext.windows = { |
600 create(createData, callback) | 675 create(createData, callback) |
601 { | 676 { |
602 chrome.windows.create(createData, createdWindow => | 677 chrome.windows.create(createData, createdWindow => |
603 { | 678 { |
604 afterTabLoaded(callback)(createdWindow.tabs[0]); | 679 afterTabLoaded(callback)(createdWindow.tabs[0]); |
605 }); | 680 }); |
606 } | 681 } |
607 }; | 682 }; |
608 } | 683 }()); |
LEFT | RIGHT |