Left: | ||
Right: |
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-2017 eyeo GmbH | 3 * Copyright (C) 2006-present 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 /* globals checkCollapse, elemhide, getURLsFromElement, typeMap */ | |
19 | |
20 "use strict"; | 18 "use strict"; |
19 | |
20 const {checkCollapse, elemhide, | |
21 getURLsFromElement, typeMap} = require("./include.preload"); | |
21 | 22 |
22 // The page ID for the popup filter selection dialog (top frame only). | 23 // The page ID for the popup filter selection dialog (top frame only). |
23 let blockelementPopupId = null; | 24 let blockelementPopupId = null; |
24 | 25 |
25 // Element picking state (top frame only). | 26 // Element picking state (top frame only). |
26 let currentlyPickingElement = false; | 27 let currentlyPickingElement = false; |
27 let lastMouseOverEvent = null; | 28 let lastMouseOverEvent = null; |
28 | 29 |
29 // During element picking this is the currently highlighted element. When | 30 // During element picking this is the currently highlighted element. When |
30 // element has been picked this is the element that is due to be blocked. | 31 // element has been picked this is the element that is due to be blocked. |
31 let currentElement = null; | 32 let currentElement = null; |
32 | 33 |
33 // Highlighting state, used by the top frame during element picking and all | 34 // Highlighting state, used by the top frame during element picking and all |
34 // frames when the chosen element is highlighted red. | 35 // frames when the chosen element is highlighted red. |
35 let highlightedElementsSelector = null; | 36 let highlightedElementsSelector = null; |
36 let highlightedElementsInterval = null; | 37 let highlightedElementsInterval = null; |
37 | 38 |
38 // Last right click state stored for element blocking via the context menu. | 39 // Last right click state stored for element blocking via the context menu. |
39 let lastRightClickEvent = null; | 40 let lastRightClickEvent = null; |
40 let lastRightClickEventIsMostRecent = false; | 41 let lastRightClickEventIsMostRecent = false; |
41 | 42 |
43 let perFrameMessagingSupported = false; | |
44 browser.runtime.sendMessage( | |
45 {type: "app.get", what: "application"}, | |
46 application => { perFrameMessagingSupported = application != "edge"; } | |
47 ); | |
42 | 48 |
43 /* Utilities */ | 49 /* Utilities */ |
44 | 50 |
45 function getFiltersForElement(element, callback) | 51 function getFiltersForElement(element, callback) |
46 { | 52 { |
47 let src = element.getAttribute("src"); | 53 let src = element.getAttribute("src"); |
48 ext.backgroundPage.sendMessage({ | 54 browser.runtime.sendMessage({ |
49 type: "composer.getFilters", | 55 type: "composer.getFilters", |
50 tagName: element.localName, | 56 tagName: element.localName, |
51 id: element.id, | 57 id: element.id, |
52 src: src && src.length <= 1000 ? src : null, | 58 src: src && src.length <= 1000 ? src : null, |
53 style: element.getAttribute("style"), | 59 style: element.getAttribute("style"), |
54 classes: Array.prototype.slice.call(element.classList), | 60 classes: Array.prototype.slice.call(element.classList), |
55 urls: getURLsFromElement(element), | 61 urls: getURLsFromElement(element), |
56 mediatype: typeMap.get(element.localName), | 62 mediatype: typeMap.get(element.localName), |
57 baseURL: document.location.href | 63 baseURL: document.location.href |
58 }, | 64 }, |
(...skipping 330 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
389 { | 395 { |
390 if (!currentElement) | 396 if (!currentElement) |
391 return; | 397 return; |
392 | 398 |
393 let element = currentElement.prisoner || currentElement; | 399 let element = currentElement.prisoner || currentElement; |
394 getFiltersForElement(element, (filters, selectors) => | 400 getFiltersForElement(element, (filters, selectors) => |
395 { | 401 { |
396 if (currentlyPickingElement) | 402 if (currentlyPickingElement) |
397 stopPickingElement(); | 403 stopPickingElement(); |
398 | 404 |
399 ext.backgroundPage.sendMessage({ | 405 browser.runtime.sendMessage({ |
400 type: "composer.openDialog" | 406 type: "composer.openDialog" |
401 }, | 407 }, |
402 popupId => | 408 popupId => |
403 { | 409 { |
404 ext.backgroundPage.sendMessage({ | 410 browser.runtime.sendMessage({ |
405 type: "forward", | 411 type: "forward", |
406 targetPageId: popupId, | 412 targetPageId: popupId, |
407 payload: {type: "composer.dialog.init", filters} | 413 payload: {type: "composer.dialog.init", filters} |
408 }); | 414 }); |
409 | 415 |
410 // Only the top frame keeps a record of the popup window's ID, | 416 // Only the top frame keeps a record of the popup window's ID, |
411 // so if this isn't the top frame we need to pass the ID on. | 417 // so if this isn't the top frame we need to pass the ID on. |
412 if (window == window.top) | 418 if (window == window.top) |
413 { | 419 { |
414 blockelementPopupId = popupId; | 420 blockelementPopupId = popupId; |
415 } | 421 } |
416 else | 422 else |
417 { | 423 { |
418 ext.backgroundPage.sendMessage({ | 424 browser.runtime.sendMessage({ |
419 type: "forward", | 425 type: "forward", |
420 payload: {type: "composer.content.dialogOpened", popupId} | 426 payload: {type: "composer.content.dialogOpened", popupId} |
421 }); | 427 }); |
422 } | 428 } |
423 }); | 429 }); |
424 | 430 |
425 if (selectors.length > 0) | 431 if (selectors.length > 0) |
426 highlightElements(selectors.join(",")); | 432 highlightElements(selectors.join(",")); |
427 | 433 |
428 highlightElement(currentElement, "#fd1708", "#f6a1b5"); | 434 highlightElement(currentElement, "#fd1708", "#f6a1b5"); |
(...skipping 25 matching lines...) Expand all Loading... | |
454 /* Core logic */ | 460 /* Core logic */ |
455 | 461 |
456 // We're done with the block element feature for now, tidy everything up. | 462 // We're done with the block element feature for now, tidy everything up. |
457 function deactivateBlockElement() | 463 function deactivateBlockElement() |
458 { | 464 { |
459 if (currentlyPickingElement) | 465 if (currentlyPickingElement) |
460 stopPickingElement(); | 466 stopPickingElement(); |
461 | 467 |
462 if (blockelementPopupId != null) | 468 if (blockelementPopupId != null) |
463 { | 469 { |
464 ext.backgroundPage.sendMessage({ | 470 browser.runtime.sendMessage({ |
465 type: "forward", | 471 type: "forward", |
466 targetPageId: blockelementPopupId, | 472 targetPageId: blockelementPopupId, |
467 payload: | 473 payload: |
468 { | 474 { |
469 type: "composer.dialog.close" | 475 type: "composer.dialog.close" |
470 } | 476 } |
471 }); | 477 }); |
472 | 478 |
473 blockelementPopupId = null; | 479 blockelementPopupId = null; |
474 } | 480 } |
475 | 481 |
476 lastRightClickEvent = null; | 482 lastRightClickEvent = null; |
477 | 483 |
478 if (currentElement) | 484 if (currentElement) |
479 { | 485 { |
480 unhighlightElement(currentElement); | 486 unhighlightElement(currentElement); |
481 currentElement = null; | 487 currentElement = null; |
482 } | 488 } |
483 unhighlightElements(); | 489 unhighlightElements(); |
484 | 490 |
485 let overlays = document.getElementsByClassName("__adblockplus__overlay"); | 491 let overlays = document.getElementsByClassName("__adblockplus__overlay"); |
486 while (overlays.length > 0) | 492 while (overlays.length > 0) |
487 overlays[0].parentNode.removeChild(overlays[0]); | 493 overlays[0].parentNode.removeChild(overlays[0]); |
488 | 494 |
489 ext.onExtensionUnloaded.removeListener(deactivateBlockElement); | 495 ext.onExtensionUnloaded.removeListener(deactivateBlockElement); |
490 } | 496 } |
491 | 497 |
492 if (document instanceof HTMLDocument) | 498 function initializeComposer() |
493 { | 499 { |
500 if (typeof ext == "undefined") | |
501 return false; | |
502 | |
494 // Use a contextmenu handler to save the last element the user right-clicked | 503 // Use a contextmenu handler to save the last element the user right-clicked |
495 // on. To make things easier, we actually save the DOM event. We have to do | 504 // on. To make things easier, we actually save the DOM event. We have to do |
496 // this because the contextMenu API only provides a URL, not the actual DOM | 505 // this because the contextMenu API only provides a URL, not the actual DOM |
497 // element. | 506 // element. |
498 // We also need to make sure that the previous right click event, | 507 // We also need to make sure that the previous right click event, |
499 // if there is one, is removed. We don't know which frame it is in so we must | 508 // if there is one, is removed. We don't know which frame it is in so we must |
500 // send a message to the other frames to clear their old right click events. | 509 // send a message to the other frames to clear their old right click events. |
501 document.addEventListener("contextmenu", event => | 510 document.addEventListener("contextmenu", event => |
502 { | 511 { |
503 lastRightClickEvent = event; | 512 lastRightClickEvent = event; |
504 lastRightClickEventIsMostRecent = true; | 513 lastRightClickEventIsMostRecent = true; |
505 | 514 |
506 ext.backgroundPage.sendMessage({ | 515 browser.runtime.sendMessage({ |
507 type: "forward", | 516 type: "forward", |
508 payload: | 517 payload: |
509 { | 518 { |
510 type: "composer.content.clearPreviousRightClickEvent" | 519 type: "composer.content.clearPreviousRightClickEvent" |
511 } | 520 } |
512 }); | 521 }); |
513 }, true); | 522 }, true); |
514 | 523 |
515 ext.onMessage.addListener((msg, sender, sendResponse) => | 524 ext.onMessage.addListener((message, sender, sendResponse) => |
516 { | 525 { |
517 switch (msg.type) | 526 switch (message.type) |
518 { | 527 { |
519 case "composer.content.getState": | 528 case "composer.content.getState": |
520 if (window == window.top) | 529 if (window == window.top) |
521 { | 530 { |
522 sendResponse({ | 531 sendResponse({ |
523 active: currentlyPickingElement || blockelementPopupId != null | 532 active: currentlyPickingElement || blockelementPopupId != null |
524 }); | 533 }); |
525 } | 534 } |
526 break; | 535 break; |
527 case "composer.content.startPickingElement": | 536 case "composer.content.startPickingElement": |
528 if (window == window.top) | 537 if (window == window.top) |
529 startPickingElement(); | 538 startPickingElement(); |
530 break; | 539 break; |
531 case "composer.content.contextMenuClicked": | 540 case "composer.content.contextMenuClicked": |
532 let event = lastRightClickEvent; | 541 let event = lastRightClickEvent; |
533 let target = event && event.target; | 542 let target = event && event.target; |
534 | 543 |
535 // When the user attempts to block an element inside an iframe for which | 544 // When the user attempts to block an element inside an iframe for which |
536 // our right click event listener was trashed the best we can do is to | 545 // our right click event listener was trashed the best we can do is to |
537 // offer to block the entire iframe. This of course only works if the | 546 // offer to block the entire iframe. This doesn't work for cross origin |
538 // parent frame is considered to be of the same origin. | 547 // frames, neither on Edge where per-frame messaging isn't supported |
539 // Note: Since Edge doesn't yet support per-frame messaging[1] we | 548 // yet[1], but it's the best we can do. |
540 // can't use this workaround there yet. (The contextMenuClicked message | |
541 // will be sent to all of the page's frames.) | |
542 // [1] - https://developer.microsoft.com/en-us/microsoft-edge/platform/d ocumentation/extensions/api-support/supported-apis/ | 549 // [1] - https://developer.microsoft.com/en-us/microsoft-edge/platform/d ocumentation/extensions/api-support/supported-apis/ |
543 if (!target && window.frameElement && typeof chrome != "undefined") | 550 if (!target && window.frameElement && perFrameMessagingSupported) |
kzar
2017/08/09 15:29:24
Note: This check `typeof chrome != "undefined"` is
kzar
2017/10/18 14:57:26
Done.
| |
544 target = addElementOverlay(window.frameElement); | 551 target = addElementOverlay(window.frameElement); |
545 | 552 |
546 deactivateBlockElement(); | 553 deactivateBlockElement(); |
547 if (target) | 554 if (target) |
548 { | 555 { |
549 getBlockableElementOrAncestor(target, element => | 556 getBlockableElementOrAncestor(target, element => |
550 { | 557 { |
551 if (element) | 558 if (element) |
552 { | 559 { |
553 currentElement = element; | 560 currentElement = element; |
554 elementPicked(event); | 561 elementPicked(event); |
555 } | 562 } |
556 }); | 563 }); |
557 } | 564 } |
558 break; | 565 break; |
559 case "composer.content.finished": | 566 case "composer.content.finished": |
560 if (currentElement && msg.remove) | 567 if (currentElement && message.remove) |
561 { | 568 { |
562 // Hide the selected element itself if an added blocking | 569 // Hide the selected element itself if an added blocking |
563 // filter is causing it to collapse. Note that this | 570 // filter is causing it to collapse. Note that this |
564 // behavior is incomplete, but the best we can do here, | 571 // behavior is incomplete, but the best we can do here, |
565 // e.g. if an added blocking filter matches other elements, | 572 // e.g. if an added blocking filter matches other elements, |
566 // the effect won't be visible until the page is is reloaded. | 573 // the effect won't be visible until the page is is reloaded. |
567 checkCollapse(currentElement.prisoner || currentElement); | 574 checkCollapse(currentElement.prisoner || currentElement); |
568 | 575 |
569 // Apply added element hiding filters. | 576 // Apply added element hiding filters. |
570 elemhide.apply(); | 577 elemhide.apply(); |
571 } | 578 } |
572 deactivateBlockElement(); | 579 deactivateBlockElement(); |
573 break; | 580 break; |
574 case "composer.content.clearPreviousRightClickEvent": | 581 case "composer.content.clearPreviousRightClickEvent": |
575 if (!lastRightClickEventIsMostRecent) | 582 if (!lastRightClickEventIsMostRecent) |
576 lastRightClickEvent = null; | 583 lastRightClickEvent = null; |
577 lastRightClickEventIsMostRecent = false; | 584 lastRightClickEventIsMostRecent = false; |
578 break; | 585 break; |
579 case "composer.content.dialogOpened": | 586 case "composer.content.dialogOpened": |
580 if (window == window.top) | 587 if (window == window.top) |
581 blockelementPopupId = msg.popupId; | 588 blockelementPopupId = message.popupId; |
582 break; | 589 break; |
583 case "composer.content.dialogClosed": | 590 case "composer.content.dialogClosed": |
584 // The onRemoved hook for the popup can create a race condition, so we | 591 // The onRemoved hook for the popup can create a race condition, so we |
585 // to be careful here. (This is not perfect, but best we can do.) | 592 // to be careful here. (This is not perfect, but best we can do.) |
586 if (window == window.top && blockelementPopupId == msg.popupId) | 593 if (window == window.top && blockelementPopupId == message.popupId) |
587 { | 594 { |
588 ext.backgroundPage.sendMessage({ | 595 browser.runtime.sendMessage({ |
589 type: "forward", | 596 type: "forward", |
590 payload: | 597 payload: |
591 { | 598 { |
592 type: "composer.content.finished" | 599 type: "composer.content.finished" |
593 } | 600 } |
594 }); | 601 }); |
595 } | 602 } |
596 break; | 603 break; |
597 } | 604 } |
598 }); | 605 }); |
599 | 606 |
600 if (window == window.top) | 607 if (window == window.top) |
601 ext.backgroundPage.sendMessage({type: "composer.ready"}); | 608 browser.runtime.sendMessage({type: "composer.ready"}); |
602 } | 609 |
610 return true; | |
611 } | |
612 | |
613 if (document instanceof HTMLDocument) | |
614 { | |
615 // There's a bug in Firefox that causes document_end content scripts to run | |
616 // before document_start content scripts on extension startup. In this case | |
617 // the ext object is undefined, we fail to initialize, and initializeComposer | |
618 // returns false. As a workaround, try again after a timeout. | |
619 // https://bugzilla.mozilla.org/show_bug.cgi?id=1395287 | |
620 if (!initializeComposer()) | |
621 setTimeout(initializeComposer, 2000); | |
622 } | |
LEFT | RIGHT |