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

Powered by Google App Engine
This is Rietveld