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-2015 Eyeo GmbH | 3 * Copyright (C) 2006-2016 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 * |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 } | 92 } |
93 | 93 |
94 chrome.tabs.query(rawInfo, function(tabs) | 94 chrome.tabs.query(rawInfo, function(tabs) |
95 { | 95 { |
96 callback(tabs.map(function(tab) | 96 callback(tabs.map(function(tab) |
97 { | 97 { |
98 return new Page(tab); | 98 return new Page(tab); |
99 })); | 99 })); |
100 }); | 100 }); |
101 }, | 101 }, |
102 onLoading: new ext._EventTarget() | 102 onLoading: new ext._EventTarget(), |
| 103 onActivated: new ext._EventTarget() |
103 }; | 104 }; |
104 | 105 |
105 chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) | 106 chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) |
106 { | 107 { |
107 if (changeInfo.status == "loading") | 108 if (changeInfo.status == "loading") |
108 ext.pages.onLoading._dispatch(new Page(tab)); | 109 ext.pages.onLoading._dispatch(new Page(tab)); |
109 }); | 110 }); |
110 | 111 |
111 chrome.webNavigation.onBeforeNavigate.addListener(function(details) | 112 chrome.webNavigation.onBeforeNavigate.addListener(function(details) |
112 { | 113 { |
(...skipping 27 matching lines...) Expand all Loading... |
140 ext._removeFromAllPageMaps(tabId); | 141 ext._removeFromAllPageMaps(tabId); |
141 delete framesOfTabs[tabId]; | 142 delete framesOfTabs[tabId]; |
142 } | 143 } |
143 | 144 |
144 chrome.tabs.onReplaced.addListener(function(addedTabId, removedTabId) | 145 chrome.tabs.onReplaced.addListener(function(addedTabId, removedTabId) |
145 { | 146 { |
146 forgetTab(removedTabId); | 147 forgetTab(removedTabId); |
147 }); | 148 }); |
148 | 149 |
149 chrome.tabs.onRemoved.addListener(forgetTab); | 150 chrome.tabs.onRemoved.addListener(forgetTab); |
| 151 |
| 152 chrome.tabs.onActivated.addListener(details => |
| 153 { |
| 154 ext.pages.onActivated._dispatch(new Page({id: details.tabId})); |
| 155 }); |
150 | 156 |
151 | 157 |
152 /* Browser actions */ | 158 /* Browser actions */ |
153 | 159 |
154 var BrowserAction = function(tabId) | 160 var BrowserAction = function(tabId) |
155 { | 161 { |
156 this._tabId = tabId; | 162 this._tabId = tabId; |
157 this._changes = null; | 163 this._changes = null; |
158 }; | 164 }; |
159 BrowserAction.prototype = { | 165 BrowserAction.prototype = { |
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
294 ContextMenus.prototype = { | 300 ContextMenus.prototype = { |
295 create: function(item) | 301 create: function(item) |
296 { | 302 { |
297 var items = contextMenuItems.get(this._page); | 303 var items = contextMenuItems.get(this._page); |
298 if (!items) | 304 if (!items) |
299 contextMenuItems.set(this._page, items = []); | 305 contextMenuItems.set(this._page, items = []); |
300 | 306 |
301 items.push(item); | 307 items.push(item); |
302 updateContextMenu(); | 308 updateContextMenu(); |
303 }, | 309 }, |
304 removeAll: function() | 310 remove: function(item) |
305 { | 311 { |
306 contextMenuItems.delete(this._page); | 312 let items = contextMenuItems.get(this._page); |
307 updateContextMenu(); | 313 if (items) |
| 314 { |
| 315 let index = items.indexOf(item); |
| 316 if (index != -1) |
| 317 { |
| 318 items.splice(index, 1); |
| 319 updateContextMenu(); |
| 320 } |
| 321 } |
308 } | 322 } |
309 }; | 323 }; |
310 | 324 |
311 chrome.tabs.onActivated.addListener(updateContextMenu); | 325 chrome.tabs.onActivated.addListener(updateContextMenu); |
312 | 326 |
313 chrome.windows.onFocusChanged.addListener(function(windowId) | 327 chrome.windows.onFocusChanged.addListener(function(windowId) |
314 { | 328 { |
315 if (windowId != chrome.windows.WINDOW_ID_NONE) | 329 if (windowId != chrome.windows.WINDOW_ID_NONE) |
316 updateContextMenu(); | 330 updateContextMenu(); |
317 }); | 331 }); |
318 | 332 |
319 | 333 |
320 /* Web requests */ | 334 /* Web requests */ |
321 | 335 |
322 var framesOfTabs = Object.create(null); | 336 var framesOfTabs = Object.create(null); |
323 | 337 |
324 ext.getFrame = function(tabId, frameId) | 338 ext.getFrame = function(tabId, frameId) |
325 { | 339 { |
326 return (framesOfTabs[tabId] || {})[frameId]; | 340 return (framesOfTabs[tabId] || {})[frameId]; |
327 }; | 341 }; |
328 | 342 |
| 343 var handlerBehaviorChangedQuota = chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANG
ED_CALLS_PER_10_MINUTES; |
| 344 |
| 345 function propagateHandlerBehaviorChange() |
| 346 { |
| 347 // Make sure to not call handlerBehaviorChanged() more often than allowed |
| 348 // by chrome.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES. |
| 349 // Otherwise Chrome notifies the user that this extension is causing issues. |
| 350 if (handlerBehaviorChangedQuota > 0) |
| 351 { |
| 352 chrome.webNavigation.onBeforeNavigate.removeListener(propagateHandlerBehav
iorChange); |
| 353 chrome.webRequest.handlerBehaviorChanged(); |
| 354 |
| 355 handlerBehaviorChangedQuota--; |
| 356 setTimeout(function() { handlerBehaviorChangedQuota++; }, 600000); |
| 357 } |
| 358 } |
| 359 |
329 ext.webRequest = { | 360 ext.webRequest = { |
330 onBeforeRequest: new ext._EventTarget(), | 361 onBeforeRequest: new ext._EventTarget(), |
331 handlerBehaviorChanged: chrome.webRequest.handlerBehaviorChanged | 362 handlerBehaviorChanged: function() |
332 }; | 363 { |
333 | 364 // Defer handlerBehaviorChanged() until navigation occurs. |
334 // Since Chrome 38 requests of type 'object' (e.g. requests | 365 // There wouldn't be any visible effect when calling it earlier, |
335 // initiated by Flash) are mistakenly reported with the type 'other'. | 366 // but it's an expensive operation and that way we avoid to call |
336 // https://code.google.com/p/chromium/issues/detail?id=410382 | 367 // it multiple times, if multiple filters are added/removed. |
337 if (parseInt(navigator.userAgent.match(/\bChrome\/(\d+)/)[1], 10) >= 38) | 368 var onBeforeNavigate = chrome.webNavigation.onBeforeNavigate; |
338 { | 369 if (!onBeforeNavigate.hasListener(propagateHandlerBehaviorChange)) |
339 ext.webRequest.indistinguishableTypes = [ | 370 onBeforeNavigate.addListener(propagateHandlerBehaviorChange); |
340 ["OTHER", "OBJECT", "OBJECT_SUBREQUEST"] | 371 }, |
341 ]; | 372 getIndistinguishableTypes: function() |
342 } | 373 { |
343 else | 374 // Chrome 38-48 mistakenly reports requests of type `object` |
344 { | 375 // (e.g. requests initiated by Flash) with the type `other`. |
345 ext.webRequest.indistinguishableTypes = [ | 376 // https://code.google.com/p/chromium/issues/detail?id=410382 |
346 ["OBJECT", "OBJECT_SUBREQUEST"], | 377 var match = navigator.userAgent.match(/\bChrome\/(\d+)/); |
347 ["OTHER", "MEDIA", "FONT"] | 378 if (match) |
348 ]; | 379 { |
349 } | 380 var version = parseInt(match[1], 10); |
| 381 if (version >= 38 && version <= 48) |
| 382 return [["OTHER", "OBJECT", "OBJECT_SUBREQUEST"]]; |
| 383 } |
| 384 |
| 385 // Chrome <44 doesn't have ResourceType. |
| 386 var ResourceType = chrome.webRequest.ResourceType || {}; |
| 387 |
| 388 // Before Chrome 49, requests of the type `font` and `ping` |
| 389 // have been reported with the type `other`. |
| 390 // https://code.google.com/p/chromium/issues/detail?id=410382 |
| 391 var otherTypes = ["OTHER", "MEDIA"]; |
| 392 if (!("FONT" in ResourceType)) |
| 393 otherTypes.push("FONT"); |
| 394 if (!("PING" in ResourceType)) |
| 395 otherTypes.push("PING"); |
| 396 |
| 397 return [["OBJECT", "OBJECT_SUBREQUEST"], otherTypes]; |
| 398 } |
| 399 }; |
350 | 400 |
351 chrome.tabs.query({}, function(tabs) | 401 chrome.tabs.query({}, function(tabs) |
352 { | 402 { |
353 tabs.forEach(function(tab) | 403 tabs.forEach(function(tab) |
354 { | 404 { |
355 chrome.webNavigation.getAllFrames({tabId: tab.id}, function(details) | 405 chrome.webNavigation.getAllFrames({tabId: tab.id}, function(details) |
356 { | 406 { |
357 if (details && details.length > 0) | 407 if (details && details.length > 0) |
358 { | 408 { |
359 var frames = framesOfTabs[tab.id] = Object.create(null); | 409 var frames = framesOfTabs[tab.id] = Object.create(null); |
360 | 410 |
361 for (var i = 0; i < details.length; i++) | 411 for (var i = 0; i < details.length; i++) |
362 frames[details[i].frameId] = {url: new URL(details[i].url), parent:
null}; | 412 frames[details[i].frameId] = {url: new URL(details[i].url), parent:
null}; |
363 | 413 |
364 for (var i = 0; i < details.length; i++) | 414 for (var i = 0; i < details.length; i++) |
365 { | 415 { |
366 var parentFrameId = details[i].parentFrameId; | 416 var parentFrameId = details[i].parentFrameId; |
367 | 417 |
368 if (parentFrameId != -1) | 418 if (parentFrameId != -1) |
369 frames[details[i].frameId].parent = frames[parentFrameId]; | 419 frames[details[i].frameId].parent = frames[parentFrameId]; |
370 } | 420 } |
371 } | 421 } |
372 }); | 422 }); |
373 }); | 423 }); |
374 }); | 424 }); |
375 | 425 |
376 chrome.webRequest.onBeforeRequest.addListener(function(details) | 426 chrome.webRequest.onBeforeRequest.addListener(function(details) |
377 { | 427 { |
378 try | 428 // the high-level code isn't interested in requests that aren't related |
379 { | 429 // to a tab and since those can only be handled in Chrome, we ignore |
380 // the high-level code isn't interested in requests that aren't related | 430 // them here instead of in the browser independent high-level code. |
381 // to a tab and since those can only be handled in Chrome, we ignore | 431 if (details.tabId == -1) |
382 // them here instead of in the browser independent high-level code. | 432 return; |
383 if (details.tabId == -1) | 433 |
384 return; | 434 var isMainFrame = details.type == "main_frame" || ( |
385 | 435 |
386 var isMainFrame = details.type == "main_frame" || ( | 436 // assume that the first request belongs to the top frame. Chrome 29 |
387 | 437 // may give the top frame the type "object" instead of "main_frame". |
388 // assume that the first request belongs to the top frame. Chrome | 438 // https://code.google.com/p/chromium/issues/detail?id=281711 |
389 // may give the top frame the type "object" instead of "main_frame". | 439 details.frameId == 0 && !(details.tabId in framesOfTabs) |
390 // https://code.google.com/p/chromium/issues/detail?id=281711 | 440 ); |
391 details.frameId == 0 && !(details.tabId in framesOfTabs) | 441 |
392 ); | 442 var frames = null; |
393 | 443 if (!isMainFrame) |
394 var frames = null; | 444 frames = framesOfTabs[details.tabId]; |
395 if (!isMainFrame) | 445 if (!frames) |
396 frames = framesOfTabs[details.tabId]; | 446 frames = framesOfTabs[details.tabId] = Object.create(null); |
397 if (!frames) | 447 |
398 frames = framesOfTabs[details.tabId] = Object.create(null); | 448 var frame = null; |
399 | 449 var url = new URL(details.url); |
400 var frame = null; | 450 if (!isMainFrame) |
401 var url = new URL(details.url); | 451 { |
402 if (!isMainFrame) | 452 // we are looking for the frame that contains the element that |
403 { | 453 // is about to load, however if a frame is loading the surrounding |
404 // we are looking for the frame that contains the element that | 454 // frame is indicated by parentFrameId instead of frameId |
405 // is about to load, however if a frame is loading the surrounding | 455 var frameId; |
406 // frame is indicated by parentFrameId instead of frameId | 456 var requestType; |
407 var frameId; | 457 if (details.type == "sub_frame") |
408 var requestType; | 458 { |
409 if (details.type == "sub_frame") | 459 frameId = details.parentFrameId; |
410 { | 460 requestType = "SUBDOCUMENT"; |
411 frameId = details.parentFrameId; | 461 } |
412 requestType = "SUBDOCUMENT"; | 462 else |
413 } | 463 { |
414 else | 464 frameId = details.frameId; |
415 { | 465 requestType = details.type.toUpperCase(); |
416 frameId = details.frameId; | 466 } |
417 requestType = details.type.toUpperCase(); | 467 |
418 } | 468 frame = frames[frameId] || frames[Object.keys(frames)[0]]; |
419 | 469 |
420 frame = frames[frameId] || frames[Object.keys(frames)[0]]; | 470 if (frame) |
421 | 471 { |
422 if (frame) | 472 var results = ext.webRequest.onBeforeRequest._dispatch( |
423 { | 473 url, |
424 var results = ext.webRequest.onBeforeRequest._dispatch( | 474 requestType, |
425 url, | 475 new Page({id: details.tabId}), |
426 requestType, | 476 frame |
427 new Page({id: details.tabId}), | 477 ); |
428 frame | 478 |
429 ); | 479 if (results.indexOf(false) != -1) |
430 | 480 return {cancel: true}; |
431 if (results.indexOf(false) != -1) | 481 } |
432 return {cancel: true}; | 482 } |
433 } | 483 |
434 } | 484 if (isMainFrame || details.type == "sub_frame") |
435 | 485 frames[details.frameId] = {url: url, parent: frame}; |
436 if (isMainFrame || details.type == "sub_frame") | |
437 frames[details.frameId] = {url: url, parent: frame}; | |
438 } | |
439 catch (e) | |
440 { | |
441 // recent versions of Chrome cancel the request when an error occurs in | |
442 // the onBeforeRequest listener. However in our case it is preferred, to | |
443 // let potentially some ads through, rather than blocking legit requests. | |
444 console.error(e); | |
445 } | |
446 }, {urls: ["http://*/*", "https://*/*"]}, ["blocking"]); | 486 }, {urls: ["http://*/*", "https://*/*"]}, ["blocking"]); |
447 | 487 |
448 | 488 |
449 /* Message passing */ | 489 /* Message passing */ |
450 | 490 |
451 chrome.runtime.onMessage.addListener(function(message, rawSender, sendResponse
) | 491 chrome.runtime.onMessage.addListener(function(message, rawSender, sendResponse
) |
452 { | 492 { |
453 var sender = {}; | 493 var sender = {}; |
454 | 494 |
455 // Add "page" and "frame" if the message was sent by a content script. | 495 // Add "page" and "frame" if the message was sent by a content script. |
(...skipping 28 matching lines...) Expand all Loading... |
484 } | 524 } |
485 | 525 |
486 return frames[0]; | 526 return frames[0]; |
487 } | 527 } |
488 }; | 528 }; |
489 } | 529 } |
490 | 530 |
491 return ext.onMessage._dispatch(message, sender, sendResponse).indexOf(true)
!= -1; | 531 return ext.onMessage._dispatch(message, sender, sendResponse).indexOf(true)
!= -1; |
492 }); | 532 }); |
493 | 533 |
494 // We have to ensure there is at least one listener for the onConnect event. | |
495 // Otherwise we can't connect a port later, which we need to do in order to | |
496 // detect when the extension is reloaded, disabled or uninstalled. | |
497 chrome.runtime.onConnect.addListener(function() {}); | |
498 | |
499 | 534 |
500 /* Storage */ | 535 /* Storage */ |
501 | 536 |
502 ext.storage = localStorage; | 537 ext.storage = { |
503 | 538 get: function(keys, callback) |
| 539 { |
| 540 chrome.storage.local.get(keys, callback); |
| 541 }, |
| 542 set: function(key, value, callback) |
| 543 { |
| 544 let items = {}; |
| 545 items[key] = value; |
| 546 chrome.storage.local.set(items, callback); |
| 547 }, |
| 548 remove: function(key, callback) |
| 549 { |
| 550 chrome.storage.local.remove(key, callback); |
| 551 }, |
| 552 onChanged: chrome.storage.onChanged |
| 553 }; |
504 | 554 |
505 /* Options */ | 555 /* Options */ |
506 | 556 |
507 ext.showOptions = function(callback) | 557 ext.showOptions = function(callback) |
508 { | 558 { |
509 chrome.windows.getLastFocused(function(win) | 559 chrome.windows.getLastFocused(function(win) |
510 { | 560 { |
511 var optionsUrl = chrome.extension.getURL("options.html"); | 561 var optionsUrl = chrome.extension.getURL("options.html"); |
512 var queryInfo = {url: optionsUrl}; | 562 var queryInfo = {url: optionsUrl}; |
513 | 563 |
(...skipping 15 matching lines...) Expand all Loading... |
529 if (callback) | 579 if (callback) |
530 callback(new Page(tab)); | 580 callback(new Page(tab)); |
531 } | 581 } |
532 else | 582 else |
533 { | 583 { |
534 ext.pages.open(optionsUrl, callback); | 584 ext.pages.open(optionsUrl, callback); |
535 } | 585 } |
536 }); | 586 }); |
537 }); | 587 }); |
538 }; | 588 }; |
539 | |
540 | |
541 /* Devtools panel */ | |
542 | |
543 var Panel = function(inspectedTabId, port) | |
544 { | |
545 this.inspectedTabId = inspectedTabId; | |
546 this._port = port; | |
547 }; | |
548 Panel.prototype = { | |
549 sendMessage: function(message) | |
550 { | |
551 this._port.postMessage(message); | |
552 }, | |
553 get onRemoved() | |
554 { | |
555 return this._port.onDisconnect; | |
556 } | |
557 }; | |
558 | |
559 ext.devtools = { | |
560 onCreated: new ext._EventTarget() | |
561 }; | |
562 | |
563 chrome.runtime.onConnect.addListener(function(port) | |
564 { | |
565 var match = port.name.match(/^devtools-(\d+)$/); | |
566 if (match) | |
567 { | |
568 var panel = new Panel(parseInt(match[1], 10), port); | |
569 ext.devtools.onCreated._dispatch(panel); | |
570 } | |
571 }); | |
572 })(); | 589 })(); |
LEFT | RIGHT |