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

Side by Side Diff: blockElement.postload.js

Issue 29336084: Issue 2426 - Open block.html as a popup window (Closed)
Patch Set: Reduce callback checking boilerplate Created Feb. 15, 2016, 3:45 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 | « block.js ('k') | chrome/ext/background.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 // The page ID for the popup filter selection dialog (top frame only).
21 let blockelementPopupId = null;
22
23 // Element picking state (top frame only).
24 let currentlyPickingElement = false;
25 let lastMouseOverEvent = null;
26
27 // 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.
29 let currentElement = null;
30
31 // Highlighting state, used by the top frame during element picking and all
32 // frames when the chosen element is highlighted red.
33 let highlightedElementsSelector = null;
34 let highlightedElementsInterval = null;
35
36 // Last right click state stored for element blocking via the context menu.
37 let lastRightClickEvent = null;
38 let lastRightClickEventIsMostRecent = false;
39
40
41 /* Utilities */
42
43 function getFiltersForElement(element, callback)
44 {
45 ext.backgroundPage.sendMessage(
46 {
47 type: "compose-filters",
48 tagName: element.localName,
49 id: element.id,
50 src: element.getAttribute("src"),
51 style: element.getAttribute("style"),
52 classes: Array.prototype.slice.call(element.classList),
53 urls: getURLsFromElement(element),
54 mediatype: typeMap[element.localName],
55 baseURL: document.location.href
56 },
57 response =>
58 {
59 callback(response.filters, response.selectors);
60 });
61 }
62
63 function getBlockableElementOrAncestor(element, callback)
64 {
65 // We assume that the user doesn't want to block the whole page.
66 // So we never consider the <html> or <body> element.
67 while (element && element != document.documentElement &&
68 element != document.body)
69 {
70 // We can't handle non-HTML (like SVG) elements, as well as
71 // <area> elements (see below). So fall back to the parent element.
72 if (!(element instanceof HTMLElement) || element.localName == "area")
73 element = element.parentElement;
74
75 // If image maps are used mouse events occur for the <area> element.
76 // But we have to block the image associated with the <map> element.
77 else if (element.localName == "map")
78 {
79 let images = document.querySelectorAll("img[usemap]");
80 let image = null;
81
82 for (let i = 0; i < images.length; i++)
83 {
84 let usemap = images[i].getAttribute("usemap");
85 let index = usemap.indexOf("#");
86
87 if (index != -1 && usemap.substr(index + 1) == element.name)
88 {
89 image = images[i];
90 break;
91 }
92 }
93
94 element = image;
95 }
96
97 // Finally, if none of the above is true, check whether we can generate
98 // any filters for this element. Otherwise fall back to its parent element.
99 else
100 {
101 getFiltersForElement(element, filters =>
102 {
103 if (filters.length > 0)
104 callback(element);
105 else
106 getBlockableElementOrAncestor(element.parentElement, callback);
107 });
108
109 return;
110 }
111 }
112
113 // We reached the document root without finding a blockable element.
114 callback(null);
115 }
116
117
118 /* Element highlighting */
119
120 // Adds an overlay to an element, which is probably a Flash object.
121 function addElementOverlay(element)
122 {
123 let position = "absolute";
124 let offsetX = window.scrollX;
125 let offsetY = window.scrollY;
126
127 for (let e = element; e; e = e.parentElement)
128 {
129 let style = getComputedStyle(e);
130
131 // If the element isn't rendered (since its or one of its ancestor's
132 // "display" property is "none"), the overlay wouldn't match the element.
133 if (style.display == "none")
134 return null;
135
136 // If the element or one of its ancestors uses fixed postioning, the overlay
137 // must too. Otherwise its position might not match the element's.
138 if (style.position == "fixed")
139 {
140 position = "fixed";
141 offsetX = offsetY = 0;
142 }
143 }
144
145 let overlay = document.createElement("div");
146 overlay.prisoner = element;
147 overlay.className = "__adblockplus__overlay";
148 overlay.setAttribute("style", "opacity:0.4; display:inline-box; " +
149 "overflow:hidden; box-sizing:border-box;");
150 let rect = element.getBoundingClientRect();
151 overlay.style.width = rect.width + "px";
152 overlay.style.height = rect.height + "px";
153 overlay.style.left = (rect.left + offsetX) + "px";
154 overlay.style.top = (rect.top + offsetY) + "px";
155 overlay.style.position = position;
156 overlay.style.zIndex = 0x7FFFFFFE;
157
158 document.documentElement.appendChild(overlay);
159 return overlay;
160 }
161
162 function highlightElement(element, shadowColor, backgroundColor)
163 {
164 unhighlightElement(element);
165
166 let highlightWithOverlay = function()
167 {
168 let overlay = addElementOverlay(element);
169
170 // If the element isn't displayed no overlay will be added.
171 // Moreover, we don't need to highlight anything then.
172 if (!overlay)
173 return;
174
175 highlightElement(overlay, shadowColor, backgroundColor);
176 overlay.style.pointerEvents = "none";
177
178 element._unhighlight = () =>
179 {
180 overlay.parentNode.removeChild(overlay);
181 };
182 };
183
184 let highlightWithStyleAttribute = function()
185 {
186 let originalBoxShadow = element.style.getPropertyValue("box-shadow");
187 let originalBoxShadowPriority =
188 element.style.getPropertyPriority("box-shadow");
189 let originalBackgroundColor =
190 element.style.getPropertyValue("background-color");
191 let originalBackgroundColorPriority =
192 element.style.getPropertyPriority("background-color");
193
194 element.style.setProperty("box-shadow", "inset 0px 0px 5px " + shadowColor,
195 "important");
196 element.style.setProperty("background-color", backgroundColor, "important");
197
198 element._unhighlight = () =>
199 {
200 element.style.removeProperty("box-shadow");
201 element.style.setProperty(
202 "box-shadow",
203 originalBoxShadow,
204 originalBoxShadowPriority
205 );
206
207 element.style.removeProperty("background-color");
208 element.style.setProperty(
209 "background-color",
210 originalBackgroundColor,
211 originalBackgroundColorPriority
212 );
213 };
214 };
215
216 if ("prisoner" in element)
217 highlightWithStyleAttribute();
218 else
219 highlightWithOverlay();
220 }
221
222 function unhighlightElement(element)
223 {
224 if (element && "_unhighlight" in element)
225 {
226 element._unhighlight();
227 delete element._unhighlight;
228 }
229 }
230
231 // Highlight elements matching the selector string red.
232 // (All elements that would be blocked by the proposed filters.)
233 function highlightElements(selectorString)
234 {
235 unhighlightElements();
236
237 let elements = Array.prototype.slice.call(
238 document.querySelectorAll(selectorString)
239 );
240 highlightedElementsSelector = selectorString;
241
242 // Highlight elements progressively. Otherwise the page freezes
243 // when a lot of elements get highlighted at the same time.
244 highlightedElementsInterval = setInterval(() =>
245 {
246 if (elements.length > 0)
247 {
248 let element = elements.shift();
249 if (element != currentElement)
250 highlightElement(element, "#fd6738", "#f6e1e5");
251 }
252 else
253 {
254 clearInterval(highlightedElementsInterval);
255 highlightedElementsInterval = null;
256 }
257 }, 0);
258 }
259
260 // Unhighlight the elements that were highlighted by selector string previously.
261 function unhighlightElements()
262 {
263 if (highlightedElementsInterval)
264 {
265 clearInterval(highlightedElementsInterval);
266 highlightedElementsInterval = null;
267 }
268
269 if (highlightedElementsSelector)
270 {
271 Array.prototype.forEach.call(
272 document.querySelectorAll(highlightedElementsSelector),
273 unhighlightElement
274 );
275
276 highlightedElementsSelector = null;
277 }
278 }
279
280
281 /* Input event handlers */
282
283 function stopEventPropagation(event)
284 {
285 event.stopPropagation();
286 }
287
288 // Hovering over an element so highlight it.
289 function mouseOver(event)
290 {
291 lastMouseOverEvent = event;
292
293 getBlockableElementOrAncestor(event.target, element =>
294 {
295 if (event == lastMouseOverEvent)
296 {
297 lastMouseOverEvent = null;
298
299 if (currentlyPickingElement)
300 {
301 if (currentElement)
302 unhighlightElement(currentElement);
303
304 if (element)
305 highlightElement(element, "#d6d84b", "#f8fa47");
306
307 currentElement = element;
308 }
309 }
310 });
311
312 event.stopPropagation();
313 }
314
315 // No longer hovering over this element so unhighlight it.
316 function mouseOut(event)
317 {
318 if (!currentlyPickingElement || currentElement != event.target)
319 return;
320
321 unhighlightElement(currentElement);
322 event.stopPropagation();
323 }
324
325 // Key events - Return selects currently hovered-over element, escape aborts.
326 function keyDown(event)
327 {
328 if (!event.ctrlKey && !event.altKey && !event.shiftKey)
329 {
330 if (event.keyCode == 13) // Return
331 elementPicked(event);
332 else if (event.keyCode == 27) // Escape
333 deactivateBlockElement();
334 }
335 }
336
337
338 /* Element selection */
339
340 // Start highlighting elements yellow as the mouse moves over them, when one is
341 // chosen launch the popup dialog for the user to confirm the generated filters.
342 function startPickingElement()
343 {
344 currentlyPickingElement = true;
345
346 // Add overlays for blockable elements that don't emit mouse events,
347 // so that they can still be selected.
348 Array.prototype.forEach.call(
349 document.querySelectorAll("object,embed,iframe,frame"),
350 element =>
351 {
352 getFiltersForElement(element, filters =>
353 {
354 if (filters.length > 0)
355 addElementOverlay(element);
356 });
357 }
358 );
359
360 document.addEventListener("mousedown", stopEventPropagation, true);
361 document.addEventListener("mouseup", stopEventPropagation, true);
362 document.addEventListener("mouseenter", stopEventPropagation, true);
363 document.addEventListener("mouseleave", stopEventPropagation, true);
364 document.addEventListener("mouseover", mouseOver, true);
365 document.addEventListener("mouseout", mouseOut, true);
366 document.addEventListener("click", elementPicked, true);
367 document.addEventListener("contextmenu", elementPicked, true);
368 document.addEventListener("keydown", keyDown, true);
369
370 ext.onExtensionUnloaded.addListener(deactivateBlockElement);
371 }
372
373 // The user has picked an element - currentElement. Highlight it red, generate
374 // filters for it and open a popup dialog so that the user can confirm.
375 function elementPicked(event)
376 {
377 if (!currentElement)
378 return;
379
380 let element = currentElement.prisoner || currentElement;
381 getFiltersForElement(element, (filters, selectors) =>
382 {
383 if (currentlyPickingElement)
384 stopPickingElement();
385
386 ext.backgroundPage.sendMessage(
387 {
388 type: "blockelement-open-popup",
389 filters: filters
390 });
391
392 if (selectors.length > 0)
393 highlightElements(selectors.join(","));
394
395 highlightElement(currentElement, "#fd1708", "#f6a1b5");
396 });
397
398 event.preventDefault();
399 event.stopPropagation();
400 }
401
402 function stopPickingElement()
403 {
404 currentlyPickingElement = false;
405
406 document.removeEventListener("mousedown", stopEventPropagation, true);
407 document.removeEventListener("mouseup", stopEventPropagation, true);
408 document.removeEventListener("mouseenter", stopEventPropagation, true);
409 document.removeEventListener("mouseleave", stopEventPropagation, true);
410 document.removeEventListener("mouseover", mouseOver, true);
411 document.removeEventListener("mouseout", mouseOut, true);
412 document.removeEventListener("click", elementPicked, true);
413 document.removeEventListener("contextmenu", elementPicked, true);
414 document.removeEventListener("keydown", keyDown, true);
415 }
416
417
418 /* Core logic */
419
420 // We're done with the block element feature for now, tidy everything up.
421 function deactivateBlockElement()
422 {
423 if (currentlyPickingElement)
424 stopPickingElement();
425
426 if (blockelementPopupId != null)
427 {
428 ext.backgroundPage.sendMessage(
429 {
430 type: "forward",
431 targetPageId: blockelementPopupId,
432 payload:
433 {
434 type: "blockelement-close-popup"
435 }
436 });
437
438 blockelementPopupId = null;
439 }
440
441 lastRightClickEvent = null;
442
443 if (currentElement)
444 {
445 unhighlightElement(currentElement);
446 currentElement = null;
447 }
448 unhighlightElements();
449
450 let overlays = document.getElementsByClassName("__adblockplus__overlay");
451 while (overlays.length > 0)
452 overlays[0].parentNode.removeChild(overlays[0]);
453
454 ext.onExtensionUnloaded.removeListener(deactivateBlockElement);
455 }
456
457 // In Chrome 37-40, the document_end content script (this one) runs properly,
458 // while the document_start content scripts (that defines ext) might not. Check
459 // whether variable ext exists before continuing to avoid
460 // "Uncaught ReferenceError: ext is not defined". See https://crbug.com/416907
461 if ("ext" in window && document instanceof HTMLDocument)
462 {
463 // Use a contextmenu handler to save the last element the user right-clicked
464 // on. To make things easier, we actually save the DOM event. We have to do
465 // this because the contextMenu API only provides a URL, not the actual DOM
466 // element.
467 // We also need to make sure that the previous right click event,
468 // if there is one, is removed. We don't know which frame it is in so we must
469 // send a message to the other frames to clear their old right click events.
470 document.addEventListener("contextmenu", event =>
471 {
472 lastRightClickEvent = event;
473 lastRightClickEventIsMostRecent = true;
474
475 ext.backgroundPage.sendMessage(
476 {
477 type: "forward",
478 payload:
479 {
480 type: "blockelement-clear-previous-right-click-event"
481 }
482 });
483 }, true);
484
485 ext.onMessage.addListener((msg, sender, sendResponse) =>
486 {
487 switch (msg.type)
488 {
489 case "blockelement-get-state":
490 if (window == window.top)
491 sendResponse({
492 active: currentlyPickingElement || blockelementPopupId != null
493 });
494 break;
495 case "blockelement-start-picking-element":
496 if (window == window.top)
497 startPickingElement();
498 break;
499 case "blockelement-context-menu-clicked":
500 let event = lastRightClickEvent;
501 deactivateBlockElement();
502 if (event)
503 {
504 getBlockableElementOrAncestor(event.target, element =>
505 {
506 if (element)
507 {
508 currentElement = element;
509 elementPicked(event);
510 }
511 });
512 }
513 break;
514 case "blockelement-finished":
515 if (currentElement && msg.remove)
516 {
517 // Hide the selected element itself if an added blocking
518 // filter is causing it to collapse. Note that this
519 // behavior is incomplete, but the best we can do here,
520 // e.g. if an added blocking filter matches other elements,
521 // the effect won't be visible until the page is is reloaded.
522 checkCollapse(currentElement.prisoner || currentElement);
523
524 // Apply added element hiding filters.
525 updateStylesheet();
526 }
527 deactivateBlockElement();
528 break;
529 case "blockelement-clear-previous-right-click-event":
530 if (!lastRightClickEventIsMostRecent)
531 lastRightClickEvent = null;
532 lastRightClickEventIsMostRecent = false;
533 break;
534 case "blockelement-popup-opened":
535 if (window == window.top)
536 blockelementPopupId = msg.popupId;
537 break;
538 case "blockelement-popup-closed":
539 // The onRemoved hook for the popup can create a race condition, so we
540 // to be careful here. (This is not perfect, but best we can do.)
541 if (window == window.top && blockelementPopupId == msg.popupId)
542 {
543 ext.backgroundPage.sendMessage(
544 {
545 type: "forward",
546 payload:
547 {
548 type: "blockelement-finished"
549 }
550 });
551 }
552 break;
553 }
554 });
555
556 if (window == window.top)
557 ext.backgroundPage.sendMessage({type: "report-html-page"});
558 }
OLDNEW
« no previous file with comments | « block.js ('k') | chrome/ext/background.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld