Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: ext/background.js

Issue 29403564: Issue 5083 - Merges chrome subdirectory to top level directory (Closed) Base URL: https://hg.adblockplus.org/adblockpluschrome
Patch Set: Created April 5, 2017, 10:55 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « devtools.js ('k') | ext/common.js » ('j') | ext/common.js » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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-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
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
62 this._delete(page.id); 62 this._delete(page.id);
63 } 63 }
64 }; 64 };
65 65
66 ext._removeFromAllPageMaps = pageId => 66 ext._removeFromAllPageMaps = pageId =>
67 { 67 {
68 for (let pageMapId in nonEmptyPageMaps) 68 for (let pageMapId in nonEmptyPageMaps)
69 nonEmptyPageMaps[pageMapId]._delete(pageId); 69 nonEmptyPageMaps[pageMapId]._delete(pageId);
70 }; 70 };
71 }()); 71 }());
72
73 (function()
Sebastian Noack 2017/04/05 11:05:49 No need to create a new IFEE. Just add the code to
Jon Sonesen 2017/04/05 11:39:00 Done.
74 {
75 /* Pages */
76
77 let Page = ext.Page = function(tab)
78 {
79 this.id = tab.id;
80 this._url = tab.url && new URL(tab.url);
81
82 this.browserAction = new BrowserAction(tab.id);
83 this.contextMenus = new ContextMenus(this);
84 };
85 Page.prototype = {
86 get url()
87 {
88 // usually our Page objects are created from Chrome's Tab objects, which
89 // provide the url. So we can return the url given in the constructor.
90 if (this._url)
91 return this._url;
92
93 // but sometimes we only have the tab id when we create a Page object.
94 // In that case we get the url from top frame of the tab, recorded by
95 // the onBeforeRequest handler.
96 let frames = framesOfTabs[this.id];
97 if (frames)
98 {
99 let frame = frames[0];
100 if (frame)
101 return frame.url;
102 }
103 },
104 sendMessage(message, responseCallback)
105 {
106 chrome.tabs.sendMessage(this.id, message, responseCallback);
107 }
108 };
109
110 ext.getPage = id => new Page({id: parseInt(id, 10)});
111
112 function afterTabLoaded(callback)
113 {
114 return openedTab =>
115 {
116 let onUpdated = (tabId, changeInfo, tab) =>
117 {
118 if (tabId == openedTab.id && changeInfo.status == "complete")
119 {
120 chrome.tabs.onUpdated.removeListener(onUpdated);
121 callback(new Page(openedTab));
122 }
123 };
124 chrome.tabs.onUpdated.addListener(onUpdated);
125 };
126 }
127
128 ext.pages = {
129 open(url, callback)
130 {
131 chrome.tabs.create({url}, callback && afterTabLoaded(callback));
132 },
133 query(info, callback)
134 {
135 let rawInfo = {};
136 for (let property in info)
137 {
138 switch (property)
139 {
140 case "active":
141 case "lastFocusedWindow":
142 rawInfo[property] = info[property];
143 }
144 }
145
146 chrome.tabs.query(rawInfo, tabs =>
147 {
148 callback(tabs.map(tab => new Page(tab)));
149 });
150 },
151 onLoading: new ext._EventTarget(),
152 onActivated: new ext._EventTarget(),
153 onRemoved: new ext._EventTarget()
154 };
155
156 chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) =>
157 {
158 if (changeInfo.status == "loading")
159 ext.pages.onLoading._dispatch(new Page(tab));
160 });
161
162 function createFrame(tabId, frameId)
163 {
164 let frames = framesOfTabs[tabId];
165 if (!frames)
166 frames = framesOfTabs[tabId] = Object.create(null);
167
168 let frame = frames[frameId];
169 if (!frame)
170 frame = frames[frameId] = {};
171
172 return frame;
173 }
174
175 function updatePageFrameStructure(frameId, tabId, url, parentFrameId)
176 {
177 if (frameId == 0)
178 {
179 let page = new Page({id: tabId, url});
180
181 ext._removeFromAllPageMaps(tabId);
182
183 chrome.tabs.get(tabId, () =>
184 {
185 // If the tab is prerendered, chrome.tabs.get() sets
186 // chrome.runtime.lastError and we have to dispatch the onLoading event,
187 // since the onUpdated event isn't dispatched for prerendered tabs.
188 // However, we have to keep relying on the unUpdated event for tabs that
189 // are already visible. Otherwise browser action changes get overridden
190 // when Chrome automatically resets them on navigation.
191 if (chrome.runtime.lastError)
192 ext.pages.onLoading._dispatch(page);
193 });
194 }
195
196 // Update frame parent and URL in frame structure
197 let frame = createFrame(tabId, frameId);
198 frame.url = new URL(url);
199 frame.parent = framesOfTabs[tabId][parentFrameId] || null;
200 }
201
202 chrome.webRequest.onHeadersReceived.addListener(details =>
203 {
204 // We have to update the frame structure when switching to a new
205 // document, so that we process any further requests made by that
206 // document in the right context. Unfortunately, we cannot rely
207 // on webNavigation.onCommitted since it isn't guaranteed to fire
208 // before any subresources start downloading[1]. As an
209 // alternative we use webRequest.onHeadersReceived for HTTP(S)
210 // URLs, being careful to ignore any responses that won't cause
211 // the document to be replaced.
212 // [1] - https://bugs.chromium.org/p/chromium/issues/detail?id=665843
213
214 // The request has been processed without replacing the document.
215 // https://chromium.googlesource.com/chromium/src/+/02d3f50b/content/browser /frame_host/navigation_request.cc#473
216 if (details.statusCode == 204 || details.statusCode == 205)
217 return;
218
219 for (let header of details.responseHeaders)
220 {
221 let headerName = header.name.toLowerCase();
222
223 // For redirects we must wait for the next response in order
224 // to know if the document will be replaced. Note: Chrome
225 // performs a redirect only if there is a "Location" header with
226 // a non-empty value and a known redirect status code.
227 // https://chromium.googlesource.com/chromium/src/+/39a7d96/net/http/http_ response_headers.cc#929
228 if (headerName == "location" && header.value &&
229 (details.statusCode == 301 || details.statusCode == 302 ||
230 details.statusCode == 303 || details.statusCode == 307 ||
231 details.statusCode == 308))
232 return;
233
234 // If the response initiates a download the document won't be
235 // replaced. Chrome initiates a download if there is a
236 // "Content-Disposition" with a valid and non-empty value other
237 // than "inline".
238 // https://chromium.googlesource.com/chromium/src/+/02d3f50b/content/brows er/loader/mime_sniffing_resource_handler.cc#534
239 // https://chromium.googlesource.com/chromium/src/+/02d3f50b/net/http/http _content_disposition.cc#374
240 // https://chromium.googlesource.com/chromium/src/+/16e2688e/net/http/http _util.cc#431
241 if (headerName == "content-disposition")
242 {
243 let disposition = header.value.split(";")[0].replace(/[ \t]+$/, "");
244 if (disposition.toLowerCase() != "inline" &&
245 /^[\x21-\x7E]+$/.test(disposition) &&
246 !/[()<>@,;:\\"/[\]?={}]/.test(disposition))
247 return;
248 }
249
250 // The value of the "Content-Type" header also determines if Chrome will
251 // initiate a download, or otherwise how the response will be rendered.
252 // We only need to consider responses which will result in a navigation
253 // and be rendered as HTML or similar.
254 // Note: Chrome might render the response as HTML if the "Content-Type"
255 // header is missing, invalid or unknown.
256 // https://chromium.googlesource.com/chromium/src/+/99f41af9/net/http/http _util.cc#66
257 // https://chromium.googlesource.com/chromium/src/+/3130418a/net/base/mime _sniffer.cc#667
258 if (headerName == "content-type")
259 {
260 let mediaType = header.value.split(/[ \t;(]/)[0].toLowerCase();
261 if (mediaType.includes("/") &&
262 mediaType != "*/*" &&
263 mediaType != "application/unknown" &&
264 mediaType != "unknown/unknown" &&
265 mediaType != "text/html" &&
266 mediaType != "text/xml" &&
267 mediaType != "application/xml" &&
268 mediaType != "application/xhtml+xml" &&
269 mediaType != "image/svg+xml")
270 return;
271 }
272 }
273
274 updatePageFrameStructure(details.frameId, details.tabId, details.url,
275 details.parentFrameId);
276 },
277 {types: ["main_frame", "sub_frame"], urls: ["http://*/*", "https://*/*"]},
278 ["responseHeaders"]);
279
280 chrome.webNavigation.onBeforeNavigate.addListener(details =>
281 {
282 // Since we can only listen for HTTP(S) responses using
283 // webRequest.onHeadersReceived we must update the page structure here for
284 // other navigations.
285 let url = new URL(details.url);
286 if (url.protocol != "http:" && url.protocol != "https:")
287 {
288 updatePageFrameStructure(details.frameId, details.tabId, details.url,
289 details.parentFrameId);
290 }
291 });
292
293 function forgetTab(tabId)
294 {
295 ext.pages.onRemoved._dispatch(tabId);
296
297 ext._removeFromAllPageMaps(tabId);
298 delete framesOfTabs[tabId];
299 }
300
301 chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) =>
302 {
303 forgetTab(removedTabId);
304 });
305
306 chrome.tabs.onRemoved.addListener(forgetTab);
307
308 chrome.tabs.onActivated.addListener(details =>
309 {
310 ext.pages.onActivated._dispatch(new Page({id: details.tabId}));
311 });
312
313
314 /* Browser actions */
315
316 let BrowserAction = function(tabId)
317 {
318 this._tabId = tabId;
319 this._changes = null;
320 };
321 BrowserAction.prototype = {
322 _applyChanges()
323 {
324 if ("iconPath" in this._changes)
325 {
326 chrome.browserAction.setIcon({
327 tabId: this._tabId,
328 path: {
329 16: this._changes.iconPath.replace("$size", "16"),
330 19: this._changes.iconPath.replace("$size", "19"),
331 20: this._changes.iconPath.replace("$size", "20"),
332 32: this._changes.iconPath.replace("$size", "32"),
333 38: this._changes.iconPath.replace("$size", "38"),
334 40: this._changes.iconPath.replace("$size", "40")
335 }
336 });
337 }
338
339 if ("badgeText" in this._changes)
340 {
341 chrome.browserAction.setBadgeText({
342 tabId: this._tabId,
343 text: this._changes.badgeText
344 });
345 }
346
347 if ("badgeColor" in this._changes)
348 {
349 chrome.browserAction.setBadgeBackgroundColor({
350 tabId: this._tabId,
351 color: this._changes.badgeColor
352 });
353 }
354
355 this._changes = null;
356 },
357 _queueChanges()
358 {
359 chrome.tabs.get(this._tabId, () =>
360 {
361 // If the tab is prerendered, chrome.tabs.get() sets
362 // chrome.runtime.lastError and we have to delay our changes
363 // until the currently visible tab is replaced with the
364 // prerendered tab. Otherwise chrome.browserAction.set* fails.
365 if (chrome.runtime.lastError)
366 {
367 let onReplaced = (addedTabId, removedTabId) =>
368 {
369 if (addedTabId == this._tabId)
370 {
371 chrome.tabs.onReplaced.removeListener(onReplaced);
372 this._applyChanges();
373 }
374 };
375 chrome.tabs.onReplaced.addListener(onReplaced);
376 }
377 else
378 {
379 this._applyChanges();
380 }
381 });
382 },
383 _addChange(name, value)
384 {
385 if (!this._changes)
386 {
387 this._changes = {};
388 this._queueChanges();
389 }
390
391 this._changes[name] = value;
392 },
393 setIcon(path)
394 {
395 this._addChange("iconPath", path);
396 },
397 setBadge(badge)
398 {
399 if (!badge)
400 {
401 this._addChange("badgeText", "");
402 }
403 else
404 {
405 if ("number" in badge)
406 this._addChange("badgeText", badge.number.toString());
407
408 if ("color" in badge)
409 this._addChange("badgeColor", badge.color);
410 }
411 }
412 };
413
414
415 /* Context menus */
416
417 let contextMenuItems = new ext.PageMap();
418 let contextMenuUpdating = false;
419
420 let updateContextMenu = () =>
421 {
422 if (contextMenuUpdating)
423 return;
424
425 contextMenuUpdating = true;
426
427 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs =>
428 {
429 chrome.contextMenus.removeAll(() =>
430 {
431 contextMenuUpdating = false;
432
433 if (tabs.length == 0)
434 return;
435
436 let items = contextMenuItems.get({id: tabs[0].id});
437
438 if (!items)
439 return;
440
441 items.forEach(item =>
442 {
443 chrome.contextMenus.create({
444 title: item.title,
445 contexts: item.contexts,
446 onclick(info, tab)
447 {
448 item.onclick(new Page(tab));
449 }
450 });
451 });
452 });
453 });
454 };
455
456 let ContextMenus = function(page)
457 {
458 this._page = page;
459 };
460 ContextMenus.prototype = {
461 create(item)
462 {
463 let items = contextMenuItems.get(this._page);
464 if (!items)
465 contextMenuItems.set(this._page, items = []);
466
467 items.push(item);
468 updateContextMenu();
469 },
470 remove(item)
471 {
472 let items = contextMenuItems.get(this._page);
473 if (items)
474 {
475 let index = items.indexOf(item);
476 if (index != -1)
477 {
478 items.splice(index, 1);
479 updateContextMenu();
480 }
481 }
482 }
483 };
484
485 chrome.tabs.onActivated.addListener(updateContextMenu);
486
487 chrome.windows.onFocusChanged.addListener(windowId =>
488 {
489 if (windowId != chrome.windows.WINDOW_ID_NONE)
490 updateContextMenu();
491 });
492
493
494 /* Web requests */
495
496 let framesOfTabs = Object.create(null);
497
498 ext.getFrame = (tabId, frameId) =>
499 {
500 return (framesOfTabs[tabId] || {})[frameId];
501 };
502
503 let handlerBehaviorChangedQuota =
504 chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES;
505
506 function propagateHandlerBehaviorChange()
507 {
508 // Make sure to not call handlerBehaviorChanged() more often than allowed
509 // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES.
510 // Otherwise Chrome notifies the user that this extension is causing issues.
511 if (handlerBehaviorChangedQuota > 0)
512 {
513 chrome.webNavigation.onBeforeNavigate.removeListener(
514 propagateHandlerBehaviorChange
515 );
516 chrome.webRequest.handlerBehaviorChanged();
517
518 handlerBehaviorChangedQuota--;
519 setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000);
520 }
521 }
522
523 ext.webRequest = {
524 onBeforeRequest: new ext._EventTarget(),
525 handlerBehaviorChanged()
526 {
527 // Defer handlerBehaviorChanged() until navigation occurs.
528 // There wouldn't be any visible effect when calling it earlier,
529 // but it's an expensive operation and that way we avoid to call
530 // it multiple times, if multiple filters are added/removed.
531 let {onBeforeNavigate} = chrome.webNavigation;
532 if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange))
533 onBeforeNavigate.addListener(propagateHandlerBehaviorChange);
534 }
535 };
536
537 chrome.tabs.query({}, tabs =>
538 {
539 tabs.forEach(tab =>
540 {
541 chrome.webNavigation.getAllFrames({tabId: tab.id}, details =>
542 {
543 if (details && details.length > 0)
544 {
545 let frames = framesOfTabs[tab.id] = Object.create(null);
546
547 for (let i = 0; i < details.length; i++)
548 {
549 frames[details[i].frameId] = {
550 url: new URL(details[i].url),
551 parent: null
552 };
553 }
554
555 for (let i = 0; i < details.length; i++)
556 {
557 let {parentFrameId} = details[i];
558
559 if (parentFrameId != -1)
560 frames[details[i].frameId].parent = frames[parentFrameId];
561 }
562 }
563 });
564 });
565 });
566
567 chrome.webRequest.onBeforeRequest.addListener(details =>
568 {
569 // The high-level code isn't interested in requests that aren't
570 // related to a tab or requests loading a top-level document,
571 // those should never be blocked.
572 if (details.tabId == -1 || details.type == "main_frame")
573 return;
574
575 // We are looking for the frame that contains the element which
576 // has triggered this request. For most requests (e.g. images) we
577 // can just use the request's frame ID, but for subdocument requests
578 // (e.g. iframes) we must instead use the request's parent frame ID.
579 let {frameId, type} = details;
580 if (type == "sub_frame")
581 {
582 frameId = details.parentFrameId;
583 type = "SUBDOCUMENT";
584 }
585
586 let frame = ext.getFrame(details.tabId, frameId);
587 if (frame)
588 {
589 let results = ext.webRequest.onBeforeRequest._dispatch(
590 new URL(details.url),
591 type.toUpperCase(),
592 new Page({id: details.tabId}),
593 frame
594 );
595
596 if (results.indexOf(false) != -1)
597 return {cancel: true};
598 }
599 }, {urls: ["<all_urls>"]}, ["blocking"]);
600
601
602 /* Message passing */
603
604 chrome.runtime.onMessage.addListener((message, rawSender, sendResponse) =>
605 {
606 let sender = {};
607
608 // Add "page" and "frame" if the message was sent by a content script.
609 // If sent by popup or the background page itself, there is no "tab".
610 if ("tab" in rawSender)
611 {
612 sender.page = new Page(rawSender.tab);
613 sender.frame = {
614 url: new URL(rawSender.url),
615 get parent()
616 {
617 let frames = framesOfTabs[rawSender.tab.id];
618
619 if (!frames)
620 return null;
621
622 let frame = frames[rawSender.frameId];
623 if (frame)
624 return frame.parent;
625
626 return frames[0];
627 }
628 };
629 }
630
631 return ext.onMessage._dispatch(
632 message, sender, sendResponse
633 ).indexOf(true) != -1;
634 });
635
636
637 /* Storage */
638
639 ext.storage = {
640 get(keys, callback)
641 {
642 chrome.storage.local.get(keys, callback);
643 },
644 set(key, value, callback)
645 {
646 let items = {};
647 items[key] = value;
648 chrome.storage.local.set(items, callback);
649 },
650 remove(key, callback)
651 {
652 chrome.storage.local.remove(key, callback);
653 },
654 onChanged: chrome.storage.onChanged
655 };
656
657 /* Options */
658
659 if ("openOptionsPage" in chrome.runtime)
660 {
661 ext.showOptions = callback =>
662 {
663 if (!callback)
664 {
665 chrome.runtime.openOptionsPage();
666 }
667 else
668 {
669 chrome.runtime.openOptionsPage(() =>
670 {
671 if (chrome.runtime.lastError)
672 return;
673
674 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs =>
675 {
676 if (tabs.length > 0)
677 {
678 if (tabs[0].status == "complete")
679 callback(new Page(tabs[0]));
680 else
681 afterTabLoaded(callback)(tabs[0]);
682 }
683 });
684 });
685 }
686 };
687 }
688 else
689 {
690 // Edge does not yet support runtime.openOptionsPage (tested version 38)
691 // and so this workaround needs to stay for now.
692 ext.showOptions = callback =>
693 {
694 chrome.windows.getLastFocused(win =>
695 {
696 let optionsUrl = chrome.extension.getURL("options.html");
697 let queryInfo = {url: optionsUrl};
698
699 // extension pages can't be accessed in incognito windows. In order to
700 // correctly mimic the way in which Chrome opens extension options,
701 // we have to focus the options page in any other window.
702 if (!win.incognito)
703 queryInfo.windowId = win.id;
704
705 chrome.tabs.query(queryInfo, tabs =>
706 {
707 if (tabs.length > 0)
708 {
709 let tab = tabs[0];
710
711 chrome.windows.update(tab.windowId, {focused: true});
712 chrome.tabs.update(tab.id, {active: true});
713
714 if (callback)
715 callback(new Page(tab));
716 }
717 else
718 {
719 ext.pages.open(optionsUrl, callback);
720 }
721 });
722 });
723 };
724 }
725
726 /* Windows */
727 ext.windows = {
728 create(createData, callback)
729 {
730 chrome.windows.create(createData, createdWindow =>
731 {
732 afterTabLoaded(callback)(createdWindow.tabs[0]);
733 });
734 }
735 };
736 }());
OLDNEW
« no previous file with comments | « devtools.js ('k') | ext/common.js » ('j') | ext/common.js » ('J')

Powered by Google App Engine
This is Rietveld