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

Delta Between Two Patch Sets: composer.postload.js

Issue 29370947: Issue 3138 - Improve how context menu "block element" handles iframes (Closed)
Left Patch Set: Created Jan. 10, 2017, 6:36 a.m.
Right Patch Set: Use messaging instead of requiring the info module Created Oct. 19, 2017, 10:52 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | ext/background.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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-2016 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 "use strict"; 18 "use strict";
19 19
20 const {checkCollapse, elemhide,
21 getURLsFromElement, typeMap} = require("./include.preload");
22
20 // 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).
21 let blockelementPopupId = null; 24 let blockelementPopupId = null;
22 25
23 // Element picking state (top frame only). 26 // Element picking state (top frame only).
24 let currentlyPickingElement = false; 27 let currentlyPickingElement = false;
25 let lastMouseOverEvent = null; 28 let lastMouseOverEvent = null;
26 29
27 // During element picking this is the currently highlighted element. When 30 // During element picking this is the currently highlighted element. When
28 // 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.
29 let currentElement = null; 32 let currentElement = null;
30 33
31 // 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
32 // frames when the chosen element is highlighted red. 35 // frames when the chosen element is highlighted red.
33 let highlightedElementsSelector = null; 36 let highlightedElementsSelector = null;
34 let highlightedElementsInterval = null; 37 let highlightedElementsInterval = null;
35 38
36 // 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.
37 let lastRightClickEvent = null; 40 let lastRightClickEvent = null;
38 let lastRightClickEventIsMostRecent = false; 41 let lastRightClickEventIsMostRecent = false;
39 42
43 let perFrameMessagingSupported = false;
44 browser.runtime.sendMessage(
45 {type: "app.get", what: "application"},
46 application => { perFrameMessagingSupported = application != "edge"; }
47 );
40 48
41 /* Utilities */ 49 /* Utilities */
42 50
43 function getFiltersForElement(element, callback) 51 function getFiltersForElement(element, callback)
44 { 52 {
45 let src = element.getAttribute("src"); 53 let src = element.getAttribute("src");
46 ext.backgroundPage.sendMessage( 54 browser.runtime.sendMessage({
47 {
48 type: "composer.getFilters", 55 type: "composer.getFilters",
49 tagName: element.localName, 56 tagName: element.localName,
50 id: element.id, 57 id: element.id,
51 src: src && src.length <= 1000 ? src : null, 58 src: src && src.length <= 1000 ? src : null,
52 style: element.getAttribute("style"), 59 style: element.getAttribute("style"),
53 classes: Array.prototype.slice.call(element.classList), 60 classes: Array.prototype.slice.call(element.classList),
54 urls: getURLsFromElement(element), 61 urls: getURLsFromElement(element),
55 mediatype: typeMap[element.localName], 62 mediatype: typeMap.get(element.localName),
56 baseURL: document.location.href 63 baseURL: document.location.href
57 }, 64 },
58 response => 65 response =>
59 { 66 {
60 callback(response.filters, response.selectors); 67 callback(response.filters, response.selectors);
61 }); 68 });
62 } 69 }
63 70
64 function getBlockableElementOrAncestor(element, callback) 71 function getBlockableElementOrAncestor(element, callback)
65 { 72 {
(...skipping 12 matching lines...) Expand all
78 if (!(element instanceof HTMLElement) || element.localName == "area") 85 if (!(element instanceof HTMLElement) || element.localName == "area")
79 element = element.parentElement; 86 element = element.parentElement;
80 87
81 // If image maps are used mouse events occur for the <area> element. 88 // If image maps are used mouse events occur for the <area> element.
82 // But we have to block the image associated with the <map> element. 89 // But we have to block the image associated with the <map> element.
83 else if (element.localName == "map") 90 else if (element.localName == "map")
84 { 91 {
85 let images = document.querySelectorAll("img[usemap]"); 92 let images = document.querySelectorAll("img[usemap]");
86 let image = null; 93 let image = null;
87 94
88 for (let i = 0; i < images.length; i++) 95 for (let currentImage of images)
89 { 96 {
90 let usemap = images[i].getAttribute("usemap"); 97 let usemap = currentImage.getAttribute("usemap");
91 let index = usemap.indexOf("#"); 98 let index = usemap.indexOf("#");
92 99
93 if (index != -1 && usemap.substr(index + 1) == element.name) 100 if (index != -1 && usemap.substr(index + 1) == element.name)
94 { 101 {
95 image = images[i]; 102 image = currentImage;
96 break; 103 break;
97 } 104 }
98 } 105 }
99 106
100 element = image; 107 element = image;
101 } 108 }
102 109
103 // Finally, if none of the above is true, check whether we can generate 110 // Finally, if none of the above is true, check whether we can generate
104 // any filters for this element. Otherwise fall back to its parent element. 111 // any filters for this element. Otherwise fall back to its parent element.
105 else 112 else
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 overlay.style.zIndex = 0x7FFFFFFE; 172 overlay.style.zIndex = 0x7FFFFFFE;
166 173
167 document.documentElement.appendChild(overlay); 174 document.documentElement.appendChild(overlay);
168 return overlay; 175 return overlay;
169 } 176 }
170 177
171 function highlightElement(element, shadowColor, backgroundColor) 178 function highlightElement(element, shadowColor, backgroundColor)
172 { 179 {
173 unhighlightElement(element); 180 unhighlightElement(element);
174 181
175 let highlightWithOverlay = function() 182 let highlightWithOverlay = () =>
176 { 183 {
177 let overlay = addElementOverlay(element); 184 let overlay = addElementOverlay(element);
178 185
179 // If the element isn't displayed no overlay will be added. 186 // If the element isn't displayed no overlay will be added.
180 // Moreover, we don't need to highlight anything then. 187 // Moreover, we don't need to highlight anything then.
181 if (!overlay) 188 if (!overlay)
182 return; 189 return;
183 190
184 highlightElement(overlay, shadowColor, backgroundColor); 191 highlightElement(overlay, shadowColor, backgroundColor);
185 overlay.style.pointerEvents = "none"; 192 overlay.style.pointerEvents = "none";
186 193
187 element._unhighlight = () => 194 element._unhighlight = () =>
188 { 195 {
189 overlay.parentNode.removeChild(overlay); 196 overlay.parentNode.removeChild(overlay);
190 }; 197 };
191 }; 198 };
192 199
193 let highlightWithStyleAttribute = function() 200 let highlightWithStyleAttribute = () =>
194 { 201 {
195 let originalBoxShadow = element.style.getPropertyValue("box-shadow"); 202 let originalBoxShadow = element.style.getPropertyValue("box-shadow");
196 let originalBoxShadowPriority = 203 let originalBoxShadowPriority =
197 element.style.getPropertyPriority("box-shadow"); 204 element.style.getPropertyPriority("box-shadow");
198 let originalBackgroundColor = 205 let originalBackgroundColor =
199 element.style.getPropertyValue("background-color"); 206 element.style.getPropertyValue("background-color");
200 let originalBackgroundColorPriority = 207 let originalBackgroundColorPriority =
201 element.style.getPropertyPriority("background-color"); 208 element.style.getPropertyPriority("background-color");
202 209
203 element.style.setProperty("box-shadow", "inset 0px 0px 5px " + shadowColor, 210 element.style.setProperty("box-shadow", "inset 0px 0px 5px " + shadowColor,
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after
388 { 395 {
389 if (!currentElement) 396 if (!currentElement)
390 return; 397 return;
391 398
392 let element = currentElement.prisoner || currentElement; 399 let element = currentElement.prisoner || currentElement;
393 getFiltersForElement(element, (filters, selectors) => 400 getFiltersForElement(element, (filters, selectors) =>
394 { 401 {
395 if (currentlyPickingElement) 402 if (currentlyPickingElement)
396 stopPickingElement(); 403 stopPickingElement();
397 404
398 ext.backgroundPage.sendMessage( 405 browser.runtime.sendMessage({
399 {
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 {
406 type: "forward", 411 type: "forward",
407 targetPageId: popupId, 412 targetPageId: popupId,
408 payload: 413 payload: {type: "composer.dialog.init", filters}
409 {
410 type: "composer.dialog.init",
411 filters: filters
412 }
413 }); 414 });
414 415
415 // 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,
416 // 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.
417 if (window == window.top) 418 if (window == window.top)
418 { 419 {
419 blockelementPopupId = popupId; 420 blockelementPopupId = popupId;
420 } 421 }
421 else 422 else
422 { 423 {
423 ext.backgroundPage.sendMessage( 424 browser.runtime.sendMessage({
424 {
425 type: "forward", 425 type: "forward",
426 payload: 426 payload: {type: "composer.content.dialogOpened", popupId}
427 {
428 type: "composer.content.dialogOpened",
429 popupId: popupId
430 }
431 }); 427 });
432 } 428 }
433 }); 429 });
434 430
435 if (selectors.length > 0) 431 if (selectors.length > 0)
436 highlightElements(selectors.join(",")); 432 highlightElements(selectors.join(","));
437 433
438 highlightElement(currentElement, "#fd1708", "#f6a1b5"); 434 highlightElement(currentElement, "#fd1708", "#f6a1b5");
439 }); 435 });
440 436
441 if (event) 437 if (event)
Sebastian Noack 2017/01/11 18:15:11 How is it possible that there is no event here?
kzar 2017/01/12 07:24:04 If the composer.content.contextMenuClicked handler
442 { 438 {
443 event.preventDefault(); 439 event.preventDefault();
444 event.stopPropagation(); 440 event.stopPropagation();
445 } 441 }
446 } 442 }
447 443
448 function stopPickingElement() 444 function stopPickingElement()
449 { 445 {
450 currentlyPickingElement = false; 446 currentlyPickingElement = false;
451 447
(...skipping 12 matching lines...) Expand all
464 /* Core logic */ 460 /* Core logic */
465 461
466 // 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.
467 function deactivateBlockElement() 463 function deactivateBlockElement()
468 { 464 {
469 if (currentlyPickingElement) 465 if (currentlyPickingElement)
470 stopPickingElement(); 466 stopPickingElement();
471 467
472 if (blockelementPopupId != null) 468 if (blockelementPopupId != null)
473 { 469 {
474 ext.backgroundPage.sendMessage( 470 browser.runtime.sendMessage({
475 {
476 type: "forward", 471 type: "forward",
477 targetPageId: blockelementPopupId, 472 targetPageId: blockelementPopupId,
478 payload: 473 payload:
479 { 474 {
480 type: "composer.dialog.close" 475 type: "composer.dialog.close"
481 } 476 }
482 }); 477 });
483 478
484 blockelementPopupId = null; 479 blockelementPopupId = null;
485 } 480 }
486 481
487 lastRightClickEvent = null; 482 lastRightClickEvent = null;
488 483
489 if (currentElement) 484 if (currentElement)
490 { 485 {
491 unhighlightElement(currentElement); 486 unhighlightElement(currentElement);
492 currentElement = null; 487 currentElement = null;
493 } 488 }
494 unhighlightElements(); 489 unhighlightElements();
495 490
496 let overlays = document.getElementsByClassName("__adblockplus__overlay"); 491 let overlays = document.getElementsByClassName("__adblockplus__overlay");
497 while (overlays.length > 0) 492 while (overlays.length > 0)
498 overlays[0].parentNode.removeChild(overlays[0]); 493 overlays[0].parentNode.removeChild(overlays[0]);
499 494
500 ext.onExtensionUnloaded.removeListener(deactivateBlockElement); 495 ext.onExtensionUnloaded.removeListener(deactivateBlockElement);
501 } 496 }
502 497
503 if (document instanceof HTMLDocument) 498 function initializeComposer()
504 { 499 {
500 if (typeof ext == "undefined")
501 return false;
502
505 // 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
506 // 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
507 // 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
508 // element. 506 // element.
509 // 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,
510 // 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
511 // 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.
512 document.addEventListener("contextmenu", event => 510 document.addEventListener("contextmenu", event =>
513 { 511 {
514 lastRightClickEvent = event; 512 lastRightClickEvent = event;
515 lastRightClickEventIsMostRecent = true; 513 lastRightClickEventIsMostRecent = true;
516 514
517 ext.backgroundPage.sendMessage( 515 browser.runtime.sendMessage({
518 {
519 type: "forward", 516 type: "forward",
520 payload: 517 payload:
521 { 518 {
522 type: "composer.content.clearPreviousRightClickEvent" 519 type: "composer.content.clearPreviousRightClickEvent"
523 } 520 }
524 }); 521 });
525 }, true); 522 }, true);
526 523
527 ext.onMessage.addListener((msg, sender, sendResponse) => 524 ext.onMessage.addListener((message, sender, sendResponse) =>
528 { 525 {
529 switch (msg.type) 526 switch (message.type)
530 { 527 {
531 case "composer.content.getState": 528 case "composer.content.getState":
532 if (window == window.top) 529 if (window == window.top)
530 {
533 sendResponse({ 531 sendResponse({
534 active: currentlyPickingElement || blockelementPopupId != null 532 active: currentlyPickingElement || blockelementPopupId != null
535 }); 533 });
534 }
536 break; 535 break;
537 case "composer.content.startPickingElement": 536 case "composer.content.startPickingElement":
538 if (window == window.top) 537 if (window == window.top)
539 startPickingElement(); 538 startPickingElement();
540 break; 539 break;
541 case "composer.content.contextMenuClicked": 540 case "composer.content.contextMenuClicked":
542 let event = lastRightClickEvent; 541 let event = lastRightClickEvent;
543 let target = event && event.target; 542 let target = event && event.target;
544 543
545 // 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
546 // 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
547 // 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
548 // parent frame is considered to be of the same origin. 547 // frames, neither on Edge where per-frame messaging isn't supported
549 if (!target && window.frameElement) 548 // yet[1], but it's the best we can do.
549 // [1] - https://developer.microsoft.com/en-us/microsoft-edge/platform/d ocumentation/extensions/api-support/supported-apis/
550 if (!target && window.frameElement && perFrameMessagingSupported)
550 target = addElementOverlay(window.frameElement); 551 target = addElementOverlay(window.frameElement);
551 552
552 deactivateBlockElement(); 553 deactivateBlockElement();
553 if (target) 554 if (target)
554 { 555 {
555 getBlockableElementOrAncestor(target, element => 556 getBlockableElementOrAncestor(target, element =>
556 { 557 {
557 if (element) 558 if (element)
558 { 559 {
559 currentElement = element; 560 currentElement = element;
560 elementPicked(event); 561 elementPicked(event);
561 } 562 }
562 }); 563 });
563 } 564 }
564 break; 565 break;
565 case "composer.content.finished": 566 case "composer.content.finished":
566 if (currentElement && msg.remove) 567 if (currentElement && message.remove)
567 { 568 {
568 // Hide the selected element itself if an added blocking 569 // Hide the selected element itself if an added blocking
569 // filter is causing it to collapse. Note that this 570 // filter is causing it to collapse. Note that this
570 // behavior is incomplete, but the best we can do here, 571 // behavior is incomplete, but the best we can do here,
571 // e.g. if an added blocking filter matches other elements, 572 // e.g. if an added blocking filter matches other elements,
572 // 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.
573 checkCollapse(currentElement.prisoner || currentElement); 574 checkCollapse(currentElement.prisoner || currentElement);
574 575
575 // Apply added element hiding filters. 576 // Apply added element hiding filters.
576 elemhide.apply(); 577 elemhide.apply();
577 } 578 }
578 deactivateBlockElement(); 579 deactivateBlockElement();
579 break; 580 break;
580 case "composer.content.clearPreviousRightClickEvent": 581 case "composer.content.clearPreviousRightClickEvent":
581 if (!lastRightClickEventIsMostRecent) 582 if (!lastRightClickEventIsMostRecent)
582 lastRightClickEvent = null; 583 lastRightClickEvent = null;
583 lastRightClickEventIsMostRecent = false; 584 lastRightClickEventIsMostRecent = false;
584 break; 585 break;
585 case "composer.content.dialogOpened": 586 case "composer.content.dialogOpened":
586 if (window == window.top) 587 if (window == window.top)
587 blockelementPopupId = msg.popupId; 588 blockelementPopupId = message.popupId;
588 break; 589 break;
589 case "composer.content.dialogClosed": 590 case "composer.content.dialogClosed":
590 // 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
591 // 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.)
592 if (window == window.top && blockelementPopupId == msg.popupId) 593 if (window == window.top && blockelementPopupId == message.popupId)
593 { 594 {
594 ext.backgroundPage.sendMessage( 595 browser.runtime.sendMessage({
595 {
596 type: "forward", 596 type: "forward",
597 payload: 597 payload:
598 { 598 {
599 type: "composer.content.finished" 599 type: "composer.content.finished"
600 } 600 }
601 }); 601 });
602 } 602 }
603 break; 603 break;
604 } 604 }
605 }); 605 });
606 606
607 if (window == window.top) 607 if (window == window.top)
608 ext.backgroundPage.sendMessage({type: "composer.ready"}); 608 browser.runtime.sendMessage({type: "composer.ready"});
609 } 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 }
LEFTRIGHT
« no previous file | ext/background.js » ('j') | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld