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

Side by Side Diff: chrome/ext/background.js

Issue 29452181: Noissue - Merge current tip to Edge bookmark (Closed)
Patch Set: Created May 30, 2017, 3:49 p.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 | « background.js ('k') | chrome/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
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH
4 *
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
7 * published by the Free Software Foundation.
8 *
9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
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/>.
16 */
17
18 "use strict";
19
20 {
21 /* Pages */
22
23 let Page = ext.Page = function(tab)
24 {
25 this.id = tab.id;
26 this._url = tab.url && new URL(tab.url);
27
28 this.browserAction = new BrowserAction(tab.id);
29 this.contextMenus = new ContextMenus(this);
30 };
31 Page.prototype = {
32 get url()
33 {
34 // usually our Page objects are created from Chrome's Tab objects, which
35 // provide the url. So we can return the url given in the constructor.
36 if (this._url)
37 return this._url;
38
39 // but sometimes we only have the tab id when we create a Page object.
40 // In that case we get the url from top frame of the tab, recorded by
41 // the onBeforeRequest handler.
42 let frames = framesOfTabs[this.id];
43 if (frames)
44 {
45 let frame = frames[0];
46 if (frame)
47 return frame.url;
48 }
49 },
50 sendMessage(message, responseCallback)
51 {
52 chrome.tabs.sendMessage(this.id, message, responseCallback);
53 }
54 };
55
56 ext.getPage = id => new Page({id: parseInt(id, 10)});
57
58 function afterTabLoaded(callback)
59 {
60 return openedTab =>
61 {
62 let onUpdated = (tabId, changeInfo, tab) =>
63 {
64 if (tabId == openedTab.id && changeInfo.status == "complete")
65 {
66 chrome.tabs.onUpdated.removeListener(onUpdated);
67 callback(new Page(openedTab));
68 }
69 };
70 chrome.tabs.onUpdated.addListener(onUpdated);
71 };
72 }
73
74 ext.pages = {
75 open(url, callback)
76 {
77 chrome.tabs.create({url: url}, callback && afterTabLoaded(callback));
78 },
79 query(info, callback)
80 {
81 let rawInfo = {};
82 for (let property in info)
83 {
84 switch (property)
85 {
86 case "active":
87 case "lastFocusedWindow":
88 rawInfo[property] = info[property];
89 }
90 }
91
92 chrome.tabs.query(rawInfo, tabs =>
93 {
94 callback(tabs.map(tab => new Page(tab)));
95 });
96 },
97 onLoading: new ext._EventTarget(),
98 onActivated: new ext._EventTarget(),
99 onRemoved: new ext._EventTarget()
100 };
101
102 chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) =>
103 {
104 if (changeInfo.status == "loading")
105 ext.pages.onLoading._dispatch(new Page(tab));
106 });
107
108 function createFrame(tabId, frameId)
109 {
110 let frames = framesOfTabs[tabId];
111 if (!frames)
112 frames = framesOfTabs[tabId] = Object.create(null);
113
114 let frame = frames[frameId];
115 if (!frame)
116 frame = frames[frameId] = {};
117
118 return frame;
119 }
120
121 chrome.webNavigation.onBeforeNavigate.addListener(details =>
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 {
132 if (frameId == 0)
133 {
134 let page = new Page({id: tabId, url: url});
135
136 if (eagerlyUpdatedPages.get(page) != url)
137 {
138 ext._removeFromAllPageMaps(tabId);
139
140 // When a sitekey header is received we must immediately update the page
141 // structure in order to record and use the key. We want to avoid
142 // trashing the page structure if the onCommitted event is then fired
143 // for the page.
144 if (eager)
145 eagerlyUpdatedPages.set(page, url);
146
147 chrome.tabs.get(tabId, () =>
148 {
149 // If the tab is prerendered, chrome.tabs.get() sets
150 // chrome.runtime.lastError and we have to dispatch the onLoading even t,
151 // since the onUpdated event isn't dispatched for prerendered tabs.
152 // However, we have to keep relying on the unUpdated event for tabs th at
153 // are already visible. Otherwise browser action changes get overridde n
154 // when Chrome automatically resets them on navigation.
155 if (chrome.runtime.lastError)
156 ext.pages.onLoading._dispatch(page);
157 });
158 }
159 }
160
161 // Update frame URL in frame structure
162 let frame = createFrame(tabId, frameId);
163 frame.url = new URL(url);
164 };
165
166 chrome.webNavigation.onCommitted.addListener(details =>
167 {
168 ext._updatePageFrameStructure(details.frameId, details.tabId, details.url);
169 });
170
171 function forgetTab(tabId)
172 {
173 ext.pages.onRemoved._dispatch(tabId);
174
175 ext._removeFromAllPageMaps(tabId);
176 delete framesOfTabs[tabId];
177 }
178
179 chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) =>
180 {
181 forgetTab(removedTabId);
182 });
183
184 chrome.tabs.onRemoved.addListener(forgetTab);
185
186 chrome.tabs.onActivated.addListener(details =>
187 {
188 ext.pages.onActivated._dispatch(new Page({id: details.tabId}));
189 });
190
191
192 /* Browser actions */
193
194 let BrowserAction = function(tabId)
195 {
196 this._tabId = tabId;
197 this._changes = null;
198 };
199 BrowserAction.prototype = {
200 _applyChanges()
201 {
202 if ("iconPath" in this._changes)
203 {
204 chrome.browserAction.setIcon({
205 tabId: this._tabId,
206 path: {
207 16: this._changes.iconPath.replace("$size", "16"),
208 19: this._changes.iconPath.replace("$size", "19"),
209 20: this._changes.iconPath.replace("$size", "20"),
210 32: this._changes.iconPath.replace("$size", "32"),
211 38: this._changes.iconPath.replace("$size", "38"),
212 40: this._changes.iconPath.replace("$size", "40")
213 }
214 });
215 }
216
217 if ("badgeText" in this._changes)
218 {
219 chrome.browserAction.setBadgeText({
220 tabId: this._tabId,
221 text: this._changes.badgeText
222 });
223 }
224
225 if ("badgeColor" in this._changes)
226 {
227 chrome.browserAction.setBadgeBackgroundColor({
228 tabId: this._tabId,
229 color: this._changes.badgeColor
230 });
231 }
232
233 this._changes = null;
234 },
235 _queueChanges()
236 {
237 chrome.tabs.get(this._tabId, function()
238 {
239 // If the tab is prerendered, chrome.tabs.get() sets
240 // chrome.runtime.lastError and we have to delay our changes
241 // until the currently visible tab is replaced with the
242 // prerendered tab. Otherwise chrome.browserAction.set* fails.
243 if (chrome.runtime.lastError)
244 {
245 let onReplaced = (addedTabId, removedTabId) =>
246 {
247 if (addedTabId == this._tabId)
248 {
249 chrome.tabs.onReplaced.removeListener(onReplaced);
250 this._applyChanges();
251 }
252 };
253 chrome.tabs.onReplaced.addListener(onReplaced);
254 }
255 else
256 {
257 this._applyChanges();
258 }
259 }.bind(this));
260 },
261 _addChange(name, value)
262 {
263 if (!this._changes)
264 {
265 this._changes = {};
266 this._queueChanges();
267 }
268
269 this._changes[name] = value;
270 },
271 setIcon(path)
272 {
273 this._addChange("iconPath", path);
274 },
275 setBadge(badge)
276 {
277 if (!badge)
278 {
279 this._addChange("badgeText", "");
280 }
281 else
282 {
283 if ("number" in badge)
284 this._addChange("badgeText", badge.number.toString());
285
286 if ("color" in badge)
287 this._addChange("badgeColor", badge.color);
288 }
289 }
290 };
291
292
293 /* Context menus */
294
295 let contextMenuItems = new ext.PageMap();
296 let contextMenuUpdating = false;
297
298 let updateContextMenu = () =>
299 {
300 if (contextMenuUpdating)
301 return;
302
303 contextMenuUpdating = true;
304
305 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs =>
306 {
307 chrome.contextMenus.removeAll(() =>
308 {
309 contextMenuUpdating = false;
310
311 if (tabs.length == 0)
312 return;
313
314 let items = contextMenuItems.get({id: tabs[0].id});
315
316 if (!items)
317 return;
318
319 items.forEach(item =>
320 {
321 chrome.contextMenus.create({
322 title: item.title,
323 contexts: item.contexts,
324 onclick(info, tab)
325 {
326 item.onclick(new Page(tab));
327 }
328 });
329 });
330 });
331 });
332 };
333
334 let ContextMenus = function(page)
335 {
336 this._page = page;
337 };
338 ContextMenus.prototype = {
339 create(item)
340 {
341 let items = contextMenuItems.get(this._page);
342 if (!items)
343 contextMenuItems.set(this._page, items = []);
344
345 items.push(item);
346 updateContextMenu();
347 },
348 remove(item)
349 {
350 let items = contextMenuItems.get(this._page);
351 if (items)
352 {
353 let index = items.indexOf(item);
354 if (index != -1)
355 {
356 items.splice(index, 1);
357 updateContextMenu();
358 }
359 }
360 }
361 };
362
363 chrome.tabs.onActivated.addListener(updateContextMenu);
364
365 chrome.windows.onFocusChanged.addListener(windowId =>
366 {
367 if (windowId != chrome.windows.WINDOW_ID_NONE)
368 updateContextMenu();
369 });
370
371
372 /* Web requests */
373
374 let framesOfTabs = Object.create(null);
375
376 ext.getFrame = (tabId, frameId) =>
377 {
378 return (framesOfTabs[tabId] || {})[frameId];
379 };
380
381 let handlerBehaviorChangedQuota = chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANG ED_CALLS_PER_10_MINUTES;
382
383 function propagateHandlerBehaviorChange()
384 {
385 // Make sure to not call handlerBehaviorChanged() more often than allowed
386 // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES.
387 // Otherwise Chrome notifies the user that this extension is causing issues.
388 if (handlerBehaviorChangedQuota > 0)
389 {
390 chrome.webNavigation.onBeforeNavigate.removeListener(propagateHandlerBehav iorChange);
391 chrome.webRequest.handlerBehaviorChanged();
392
393 handlerBehaviorChangedQuota--;
394 setTimeout(() => { handlerBehaviorChangedQuota++; }, 600000);
395 }
396 }
397
398 ext.webRequest = {
399 onBeforeRequest: new ext._EventTarget(),
400 handlerBehaviorChanged()
401 {
402 // Defer handlerBehaviorChanged() until navigation occurs.
403 // There wouldn't be any visible effect when calling it earlier,
404 // but it's an expensive operation and that way we avoid to call
405 // it multiple times, if multiple filters are added/removed.
406 let onBeforeNavigate = chrome.webNavigation.onBeforeNavigate;
407 if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange))
408 onBeforeNavigate.addListener(propagateHandlerBehaviorChange);
409 }
410 };
411
412 chrome.tabs.query({}, tabs =>
413 {
414 tabs.forEach(tab =>
415 {
416 chrome.webNavigation.getAllFrames({tabId: tab.id}, details =>
417 {
418 if (details && details.length > 0)
419 {
420 let frames = framesOfTabs[tab.id] = Object.create(null);
421
422 for (let i = 0; i < details.length; i++)
423 frames[details[i].frameId] = {url: new URL(details[i].url), parent: null};
424
425 for (let i = 0; i < details.length; i++)
426 {
427 let parentFrameId = details[i].parentFrameId;
428
429 if (parentFrameId != -1)
430 frames[details[i].frameId].parent = frames[parentFrameId];
431 }
432 }
433 });
434 });
435 });
436
437 chrome.webRequest.onBeforeRequest.addListener(details =>
438 {
439 // The high-level code isn't interested in requests that aren't
440 // related to a tab or requests loading a top-level document,
441 // those should never be blocked.
442 if (details.tabId == -1 || details.type == "main_frame")
443 return;
444
445 // We are looking for the frame that contains the element which
446 // has triggered this request. For most requests (e.g. images) we
447 // can just use the request's frame ID, but for subdocument requests
448 // (e.g. iframes) we must instead use the request's parent frame ID.
449 let frameId;
450 let requestType;
451 if (details.type == "sub_frame")
452 {
453 frameId = details.parentFrameId;
454 requestType = "SUBDOCUMENT";
455 }
456 else
457 {
458 frameId = details.frameId;
459 requestType = details.type.toUpperCase();
460 }
461
462 let frame = ext.getFrame(details.tabId, frameId);
463 if (frame)
464 {
465 let results = ext.webRequest.onBeforeRequest._dispatch(
466 new URL(details.url),
467 requestType,
468 new Page({id: details.tabId}),
469 frame
470 );
471
472 if (results.indexOf(false) != -1)
473 return {cancel: true};
474 }
475 }, {urls: ["http://*/*", "https://*/*"]}, ["blocking"]);
476
477
478 /* Message passing */
479
480 chrome.runtime.onMessage.addListener((message, rawSender, sendResponse) =>
481 {
482 let sender = {};
483
484 // 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".
486 if ("tab" in rawSender)
487 {
488 sender.page = new Page(rawSender.tab);
489 sender.frame = {
490 // In Edge requests from internal extension pages
491 // (protocol ms-browser-extension://) do no have a sender URL.
492 url: rawSender.url ? new URL(rawSender.url) : null,
493 get parent()
494 {
495 let frames = framesOfTabs[rawSender.tab.id];
496
497 if (!frames)
498 return null;
499
500 let frame = frames[rawSender.frameId];
501 if (frame)
502 return frame.parent;
503
504 return frames[0];
505 }
506 };
507 }
508
509 return ext.onMessage._dispatch(message, sender, sendResponse).indexOf(true) != -1;
510 });
511
512
513 /* Storage */
514
515 ext.storage = {
516 get(keys, callback)
517 {
518 chrome.storage.local.get(keys, callback);
519 },
520 set(key, value, callback)
521 {
522 let items = {};
523 items[key] = value;
524 chrome.storage.local.set(items, callback);
525 },
526 remove(key, callback)
527 {
528 chrome.storage.local.remove(key, callback);
529 },
530 onChanged: chrome.storage.onChanged
531 };
532
533 /* Options */
534
535 if ("openOptionsPage" in chrome.runtime)
536 {
537 ext.showOptions = callback =>
538 {
539 if (!callback)
540 {
541 chrome.runtime.openOptionsPage();
542 }
543 else
544 {
545 chrome.runtime.openOptionsPage(() =>
546 {
547 if (chrome.runtime.lastError)
548 return;
549
550 chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs =>
551 {
552 if (tabs.length > 0)
553 {
554 window.setTimeout(() =>
555 {
556 callback(new Page(tabs[0]));
557 });
558 }
559 });
560 });
561 }
562 };
563 }
564 else
565 {
566 // Edge does not yet support runtime.openOptionsPage (tested version 38)
567 // and so this workaround needs to stay for now.
568 ext.showOptions = callback =>
569 {
570 chrome.windows.getLastFocused(win =>
571 {
572 let optionsUrl = chrome.extension.getURL("options.html");
573 let queryInfo = {url: optionsUrl};
574
575 // extension pages can't be accessed in incognito windows. In order to
576 // correctly mimic the way in which Chrome opens extension options,
577 // we have to focus the options page in any other window.
578 if (!win.incognito)
579 queryInfo.windowId = win.id;
580
581 chrome.tabs.query(queryInfo, tabs =>
582 {
583 if (tabs.length > 0)
584 {
585 let tab = tabs[0];
586
587 chrome.windows.update(tab.windowId, {focused: true});
588 chrome.tabs.update(tab.id, {active: true});
589
590 if (callback)
591 callback(new Page(tab));
592 }
593 else
594 {
595 ext.pages.open(optionsUrl, callback);
596 }
597 });
598 });
599 };
600 }
601
602 /* Windows */
603 ext.windows = {
604 create(createData, callback)
605 {
606 if ("create" in chrome.windows){
607 chrome.windows.create(createData, createdWindow =>
608 {
609 afterTabLoaded(callback)(createdWindow.tabs[0]);
610 });
611 }
612 else
613 {
614 ext.pages.open(createData.url, callback);
615 }
616 }
617 };
618 }
OLDNEW
« no previous file with comments | « background.js ('k') | chrome/ext/common.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld