OLD | NEW |
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-2015 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 // Click-to-hide stuff | 18 // Click-to-hide stuff |
19 var clickHide_activated = false; | 19 var clickHide_activated = false; |
20 var clickHide_filters = null; | 20 var clickHide_filters = null; |
21 var currentElement = null; | 21 var currentElement = null; |
22 var highlightedElementsSelector = null; | 22 var highlightedElementsSelector = null; |
23 var clickHideFiltersDialog = null; | 23 var clickHideFiltersDialog = null; |
24 var lastRightClickEvent = null; | 24 var lastRightClickEvent = null; |
25 var lastRightClickEventValid = false; | 25 var lastRightClickEventValid = false; |
| 26 var lastMouseOverEvent = null; |
26 | 27 |
27 function highlightElement(element, shadowColor, backgroundColor) | 28 function highlightElement(element, shadowColor, backgroundColor) |
28 { | 29 { |
29 unhighlightElement(element); | 30 unhighlightElement(element); |
30 | 31 |
31 var highlightWithOverlay = function() | 32 var highlightWithOverlay = function() |
32 { | 33 { |
33 var overlay = addElementOverlay(element); | 34 var overlay = addElementOverlay(element); |
34 | 35 |
35 // If the element isn't displayed no overlay will be added. | 36 // If the element isn't displayed no overlay will be added. |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
191 | 192 |
192 case "video": | 193 case "video": |
193 case "audio": | 194 case "audio": |
194 case "picture": | 195 case "picture": |
195 return getURLsFromMediaElement(element); | 196 return getURLsFromMediaElement(element); |
196 } | 197 } |
197 | 198 |
198 return getURLsFromAttributes(element); | 199 return getURLsFromAttributes(element); |
199 } | 200 } |
200 | 201 |
201 function isBlockable(element) | |
202 { | |
203 if (element.id) | |
204 return true; | |
205 if (element.classList.length > 0) | |
206 return true; | |
207 if (getURLsFromElement(element).length > 0) | |
208 return true; | |
209 | |
210 // We only generate filters based on the "style" attribute, | |
211 // if this is the only way we can generate a filter, and | |
212 // only if there are at least two CSS properties defined. | |
213 if (/:.+:/.test(element.getAttribute("style"))) | |
214 return true; | |
215 | |
216 return false; | |
217 } | |
218 | |
219 // Adds an overlay to an element, which is probably a Flash object | 202 // Adds an overlay to an element, which is probably a Flash object |
220 function addElementOverlay(elt) { | 203 function addElementOverlay(elt) { |
221 var zIndex = "auto"; | 204 var zIndex = "auto"; |
222 var position = "absolute"; | 205 var position = "absolute"; |
223 | 206 |
224 for (var e = elt; e; e = e.parentElement) | 207 for (var e = elt; e; e = e.parentElement) |
225 { | 208 { |
226 var style = getComputedStyle(e); | 209 var style = getComputedStyle(e); |
227 | 210 |
228 // If the element isn't rendered (since its or one of its ancestor's | 211 // If the element isn't rendered (since its or one of its ancestor's |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
305 function clickHide_activate() { | 288 function clickHide_activate() { |
306 if(document == null) | 289 if(document == null) |
307 return; | 290 return; |
308 | 291 |
309 // If we are already selecting, abort now | 292 // If we are already selecting, abort now |
310 if (clickHide_activated || clickHideFiltersDialog) | 293 if (clickHide_activated || clickHideFiltersDialog) |
311 clickHide_deactivate(); | 294 clickHide_deactivate(); |
312 | 295 |
313 // Add overlays for blockable elements that don't emit mouse events, | 296 // Add overlays for blockable elements that don't emit mouse events, |
314 // so that they can still be selected. | 297 // so that they can still be selected. |
315 var elts = document.querySelectorAll('object,embed,iframe,frame'); | 298 [].forEach.call( |
316 for(var i=0; i<elts.length; i++) | 299 document.querySelectorAll('object,embed,iframe,frame'), |
317 { | 300 function(element) |
318 var element = elts[i]; | 301 { |
319 if (isBlockable(element)) | 302 getFiltersForElement(element, function(filters) |
320 addElementOverlay(element); | 303 { |
321 } | 304 if (filters.length > 0) |
| 305 addElementOverlay(element); |
| 306 }); |
| 307 } |
| 308 ); |
322 | 309 |
323 clickHide_activated = true; | 310 clickHide_activated = true; |
324 document.addEventListener("mousedown", clickHide_stopPropagation, true); | 311 document.addEventListener("mousedown", clickHide_stopPropagation, true); |
325 document.addEventListener("mouseup", clickHide_stopPropagation, true); | 312 document.addEventListener("mouseup", clickHide_stopPropagation, true); |
326 document.addEventListener("mouseenter", clickHide_stopPropagation, true); | 313 document.addEventListener("mouseenter", clickHide_stopPropagation, true); |
327 document.addEventListener("mouseleave", clickHide_stopPropagation, true); | 314 document.addEventListener("mouseleave", clickHide_stopPropagation, true); |
328 document.addEventListener("mouseover", clickHide_mouseOver, true); | 315 document.addEventListener("mouseover", clickHide_mouseOver, true); |
329 document.addEventListener("mouseout", clickHide_mouseOut, true); | 316 document.addEventListener("mouseout", clickHide_mouseOut, true); |
330 document.addEventListener("click", clickHide_mouseClick, true); | 317 document.addEventListener("click", clickHide_mouseClick, true); |
331 document.addEventListener("keydown", clickHide_keyDown, true); | 318 document.addEventListener("keydown", clickHide_keyDown, true); |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
394 { | 381 { |
395 e.stopPropagation(); | 382 e.stopPropagation(); |
396 } | 383 } |
397 | 384 |
398 function clickHide_elementClickHandler(e) { | 385 function clickHide_elementClickHandler(e) { |
399 e.preventDefault(); | 386 e.preventDefault(); |
400 e.stopPropagation(); | 387 e.stopPropagation(); |
401 clickHide_mouseClick(e); | 388 clickHide_mouseClick(e); |
402 } | 389 } |
403 | 390 |
404 function getBlockableElementOrAncestor(element) | 391 function getBlockableElementOrAncestor(element, callback) |
405 { | 392 { |
| 393 // We assume that the user doesn't want to block the whole page. |
| 394 // So we never consider the <html> or <body> element. |
406 while (element && element != document.documentElement | 395 while (element && element != document.documentElement |
407 && element != document.body) | 396 && element != document.body) |
408 { | 397 { |
409 if (element instanceof HTMLElement && element.localName != "area") | 398 // We can't handle non-HTML (like SVG) elements, as well as |
| 399 // <area> elements (see below). So fall back to the parent element. |
| 400 if (!(element instanceof HTMLElement) || element.localName == "area") |
| 401 element = element.parentElement; |
| 402 |
| 403 // If image maps are used mouse events occur for the <area> element. |
| 404 // But we have to block the image associated with the <map> element. |
| 405 else if (element.localName == "map") |
410 { | 406 { |
411 // Handle <area> and their <map> elements specially, | 407 var images = document.querySelectorAll("img[usemap]"); |
412 // blocking the image they are associated with | 408 var image = null; |
413 if (element.localName == "map") | 409 |
| 410 for (var i = 0; i < images.length; i++) |
414 { | 411 { |
415 var images = document.querySelectorAll("img[usemap]"); | 412 var usemap = image.getAttribute("usemap"); |
416 for (var i = 0; i < images.length; i++) | 413 var index = usemap.indexOf("#"); |
| 414 |
| 415 if (index != -1 && usemap.substr(index + 1) == element.name) |
417 { | 416 { |
418 var image = images[i]; | 417 image = images[i]; |
419 var usemap = image.getAttribute("usemap"); | 418 break; |
420 var index = usemap.indexOf("#"); | |
421 | |
422 if (index != -1 && usemap.substr(index + 1) == element.name) | |
423 return getBlockableElementOrAncestor(image); | |
424 } | 419 } |
425 | |
426 return null; | |
427 } | 420 } |
428 | 421 |
429 if (isBlockable(element)) | 422 element = image; |
430 return element; | |
431 } | 423 } |
432 | 424 |
433 element = element.parentElement; | 425 // Finally, if none of the above is true, check whether we can generate |
| 426 // any filters for this element. Otherwise fall back to its parent element. |
| 427 else |
| 428 { |
| 429 getFiltersForElement(element, function(filters) |
| 430 { |
| 431 if (filters.length > 0) |
| 432 callback(element); |
| 433 else |
| 434 getBlockableElementOrAncestor(element.parentElement, callback); |
| 435 }); |
| 436 |
| 437 return; |
| 438 } |
434 } | 439 } |
435 | 440 |
436 return null; | 441 // We reached the document root without finding a blockable element. |
| 442 callback(null); |
437 } | 443 } |
438 | 444 |
439 // Hovering over an element so highlight it | 445 // Hovering over an element so highlight it |
440 function clickHide_mouseOver(e) | 446 function clickHide_mouseOver(e) |
441 { | 447 { |
442 if (clickHide_activated == false) | 448 lastMouseOverEvent = e; |
443 return; | |
444 | 449 |
445 var target = getBlockableElementOrAncestor(e.target); | 450 getBlockableElementOrAncestor(e.target, function(element) |
| 451 { |
| 452 if (e == lastMouseOverEvent) |
| 453 { |
| 454 lastMouseOverEvent = null; |
446 | 455 |
447 if (target) | 456 if (clickHide_activated) |
448 { | 457 { |
449 currentElement = target; | 458 if (currentElement) |
| 459 unhighlightElement(currentElement); |
450 | 460 |
451 highlightElement(target, "#d6d84b", "#f8fa47"); | 461 if (element) |
452 target.addEventListener("contextmenu", clickHide_elementClickHandler, true); | 462 { |
453 } | 463 highlightElement(element, "#d6d84b", "#f8fa47"); |
| 464 element.addEventListener("contextmenu", clickHide_elementClickHandler,
true); |
| 465 } |
| 466 |
| 467 currentElement = element; |
| 468 } |
| 469 } |
| 470 }); |
| 471 |
454 e.stopPropagation(); | 472 e.stopPropagation(); |
455 } | 473 } |
456 | 474 |
457 // No longer hovering over this element so unhighlight it | 475 // No longer hovering over this element so unhighlight it |
458 function clickHide_mouseOut(e) | 476 function clickHide_mouseOut(e) |
459 { | 477 { |
460 if (!clickHide_activated || !currentElement) | 478 if (!clickHide_activated || currentElement != e.target) |
461 return; | 479 return; |
462 | 480 |
463 unhighlightElement(currentElement); | 481 unhighlightElement(currentElement); |
464 currentElement.removeEventListener("contextmenu", clickHide_elementClickHandle
r, true); | 482 currentElement.removeEventListener("contextmenu", clickHide_elementClickHandle
r, true); |
465 e.stopPropagation(); | 483 e.stopPropagation(); |
466 } | 484 } |
467 | 485 |
468 // Selects the currently hovered-over filter or cancels selection | 486 // Selects the currently hovered-over filter or cancels selection |
469 function clickHide_keyDown(e) | 487 function clickHide_keyDown(e) |
470 { | 488 { |
(...skipping 13 matching lines...) Expand all Loading... |
484 e.stopPropagation(); | 502 e.stopPropagation(); |
485 } | 503 } |
486 } | 504 } |
487 | 505 |
488 function getFiltersForElement(element, callback) | 506 function getFiltersForElement(element, callback) |
489 { | 507 { |
490 ext.backgroundPage.sendMessage( | 508 ext.backgroundPage.sendMessage( |
491 { | 509 { |
492 type: "compose-filters", | 510 type: "compose-filters", |
493 tagName: element.localName, | 511 tagName: element.localName, |
494 id: element.id, | 512 attributes: { |
495 src: element.getAttribute("src"), | 513 id: element.id, |
496 style: element.getAttribute("style"), | 514 src: element.getAttribute("src"), |
| 515 style: element.getAttribute("style") |
| 516 }, |
497 classes: [].slice.call(element.classList), | 517 classes: [].slice.call(element.classList), |
498 urls: getURLsFromElement(element), | 518 urls: getURLsFromElement(element), |
| 519 mediatype: typeMap[element.localName], |
499 baseURL: document.location.href | 520 baseURL: document.location.href |
500 }, | 521 }, |
501 function(response) | 522 function(response) |
502 { | 523 { |
503 callback(response.filters, response.selectors); | 524 callback(response.filters, response.selectors); |
504 } | 525 } |
505 ); | 526 ); |
506 } | 527 } |
507 | 528 |
508 // When the user clicks, the currentElement is the one we want. | 529 // When the user clicks, the currentElement is the one we want. |
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
667 break; | 688 break; |
668 case "clickhide-activate": | 689 case "clickhide-activate": |
669 clickHide_activate(); | 690 clickHide_activate(); |
670 break; | 691 break; |
671 case "clickhide-deactivate": | 692 case "clickhide-deactivate": |
672 clickHide_deactivate(); | 693 clickHide_deactivate(); |
673 break; | 694 break; |
674 case "clickhide-new-filter": | 695 case "clickhide-new-filter": |
675 if(lastRightClickEvent) | 696 if(lastRightClickEvent) |
676 { | 697 { |
677 clickHide_activated = true; | 698 getBlockableElementOrAncestor(lastRightClickEvent.target, function(ele
ment) |
678 currentElement = getBlockableElementOrAncestor(lastRightClickEvent.tar
get); | 699 { |
679 clickHide_mouseClick(lastRightClickEvent); | 700 clickHide_activated = true; |
| 701 currentElement = element; |
| 702 clickHide_mouseClick(lastRightClickEvent); |
| 703 }); |
680 } | 704 } |
681 break; | 705 break; |
682 case "clickhide-init": | 706 case "clickhide-init": |
683 if (clickHideFiltersDialog) | 707 if (clickHideFiltersDialog) |
684 { | 708 { |
685 sendResponse({filters: clickHide_filters}); | 709 sendResponse({filters: clickHide_filters}); |
686 | 710 |
687 clickHideFiltersDialog.style.width = msg.width + "px"; | 711 clickHideFiltersDialog.style.width = msg.width + "px"; |
688 clickHideFiltersDialog.style.height = msg.height + "px"; | 712 clickHideFiltersDialog.style.height = msg.height + "px"; |
689 clickHideFiltersDialog.style.visibility = "visible"; | 713 clickHideFiltersDialog.style.visibility = "visible"; |
(...skipping 27 matching lines...) Expand all Loading... |
717 lastRightClickEventValid = false; | 741 lastRightClickEventValid = false; |
718 else | 742 else |
719 lastRightClickEvent = null; | 743 lastRightClickEvent = null; |
720 break; | 744 break; |
721 } | 745 } |
722 }); | 746 }); |
723 | 747 |
724 if (window == window.top) | 748 if (window == window.top) |
725 ext.backgroundPage.sendMessage({type: "report-html-page"}); | 749 ext.backgroundPage.sendMessage({type: "report-html-page"}); |
726 } | 750 } |
OLD | NEW |