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: Rebased, replaced search param with proper messaging Created Feb. 17, 2016, 8:04 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
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 },
390 response =>
391 {
392 blockelementPopupId = response;
393 ext.backgroundPage.sendMessage(
394 {
395 type: "forward",
396 targetPageId: blockelementPopupId,
397 payload:
398 {
399 type: "blockelement-popup-init",
400 filters: filters
401 }
402 });
403 });
404
405 if (selectors.length > 0)
406 highlightElements(selectors.join(","));
407
408 highlightElement(currentElement, "#fd1708", "#f6a1b5");
409 });
410
411 event.preventDefault();
412 event.stopPropagation();
413 }
414
415 function stopPickingElement()
416 {
417 currentlyPickingElement = false;
418
419 document.removeEventListener("mousedown", stopEventPropagation, true);
420 document.removeEventListener("mouseup", stopEventPropagation, true);
421 document.removeEventListener("mouseenter", stopEventPropagation, true);
422 document.removeEventListener("mouseleave", stopEventPropagation, true);
423 document.removeEventListener("mouseover", mouseOver, true);
424 document.removeEventListener("mouseout", mouseOut, true);
425 document.removeEventListener("click", elementPicked, true);
426 document.removeEventListener("contextmenu", elementPicked, true);
427 document.removeEventListener("keydown", keyDown, true);
428 }
429
430
431 /* Core logic */
432
433 // We're done with the block element feature for now, tidy everything up.
434 function deactivateBlockElement()
435 {
436 if (currentlyPickingElement)
437 stopPickingElement();
438
439 if (blockelementPopupId != null)
440 {
441 ext.backgroundPage.sendMessage(
442 {
443 type: "forward",
444 targetPageId: blockelementPopupId,
445 payload:
446 {
447 type: "blockelement-close-popup"
448 }
449 });
450
451 blockelementPopupId = null;
452 }
453
454 lastRightClickEvent = null;
455
456 if (currentElement)
457 {
458 unhighlightElement(currentElement);
459 currentElement = null;
460 }
461 unhighlightElements();
462
463 let overlays = document.getElementsByClassName("__adblockplus__overlay");
464 while (overlays.length > 0)
465 overlays[0].parentNode.removeChild(overlays[0]);
466
467 ext.onExtensionUnloaded.removeListener(deactivateBlockElement);
468 }
469
470 // In Chrome 37-40, the document_end content script (this one) runs properly,
471 // while the document_start content scripts (that defines ext) might not. Check
472 // whether variable ext exists before continuing to avoid
473 // "Uncaught ReferenceError: ext is not defined". See https://crbug.com/416907
474 if ("ext" in window && document instanceof HTMLDocument)
475 {
476 // Use a contextmenu handler to save the last element the user right-clicked
477 // on. To make things easier, we actually save the DOM event. We have to do
478 // this because the contextMenu API only provides a URL, not the actual DOM
479 // element.
480 // We also need to make sure that the previous right click event,
481 // if there is one, is removed. We don't know which frame it is in so we must
482 // send a message to the other frames to clear their old right click events.
483 document.addEventListener("contextmenu", event =>
484 {
485 lastRightClickEvent = event;
486 lastRightClickEventIsMostRecent = true;
487
488 ext.backgroundPage.sendMessage(
489 {
490 type: "forward",
491 payload:
492 {
493 type: "blockelement-clear-previous-right-click-event"
494 }
495 });
496 }, true);
497
498 ext.onMessage.addListener((msg, sender, sendResponse) =>
499 {
500 switch (msg.type)
501 {
502 case "blockelement-get-state":
503 if (window == window.top)
504 sendResponse({
505 active: currentlyPickingElement || blockelementPopupId != null
506 });
507 break;
508 case "blockelement-start-picking-element":
509 if (window == window.top)
510 startPickingElement();
511 break;
512 case "blockelement-context-menu-clicked":
513 let event = lastRightClickEvent;
514 deactivateBlockElement();
515 if (event)
516 {
517 getBlockableElementOrAncestor(event.target, element =>
518 {
519 if (element)
520 {
521 currentElement = element;
522 elementPicked(event);
523 }
524 });
525 }
526 break;
527 case "blockelement-finished":
528 if (currentElement && msg.remove)
529 {
530 // Hide the selected element itself if an added blocking
531 // filter is causing it to collapse. Note that this
532 // behavior is incomplete, but the best we can do here,
533 // e.g. if an added blocking filter matches other elements,
534 // the effect won't be visible until the page is is reloaded.
535 checkCollapse(currentElement.prisoner || currentElement);
536
537 // Apply added element hiding filters.
538 updateStylesheet();
539 }
540 deactivateBlockElement();
541 break;
542 case "blockelement-clear-previous-right-click-event":
543 if (!lastRightClickEventIsMostRecent)
544 lastRightClickEvent = null;
545 lastRightClickEventIsMostRecent = false;
546 break;
547 case "blockelement-popup-closed":
548 // The onRemoved hook for the popup can create a race condition, so we
549 // to be careful here. (This is not perfect, but best we can do.)
550 if (window == window.top && blockelementPopupId == msg.popupId)
551 {
552 ext.backgroundPage.sendMessage(
553 {
554 type: "forward",
555 payload:
556 {
557 type: "blockelement-finished"
558 }
559 });
560 }
561 break;
562 }
563 });
564
565 if (window == window.top)
566 ext.backgroundPage.sendMessage({type: "report-html-page"});
567 }
OLDNEW
« no previous file with comments | « block.js ('k') | chrome/ext/background.js » ('j') | safari/ext/background.js » ('J')

Powered by Google App Engine
This is Rietveld