LEFT | RIGHT |
1 /* | 1 /* |
2 * This file is part of Adblock Plus <http://adblockplus.org/>, | 2 * This file is part of Adblock Plus <https://adblockplus.org/>, |
3 * Copyright (C) 2006-2014 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 clickHideFilters = null; | |
23 var highlightedElementsSelector = null; | 22 var highlightedElementsSelector = null; |
24 var clickHideFiltersDialog = null; | 23 var clickHideFiltersDialog = null; |
25 var lastRightClickEvent = null; | 24 var lastRightClickEvent = null; |
26 | 25 var lastRightClickEventValid = false; |
27 function escapeChar(chr) | 26 var lastMouseOverEvent = null; |
28 { | |
29 var code = chr.charCodeAt(0); | |
30 | |
31 // Control characters and leading digits must be escaped based on | |
32 // their char code in CSS. Moreover, curly brackets aren't allowed | |
33 // in elemhide filters, and therefore must be escaped based on their | |
34 // char code as well. | |
35 if (code <= 0x1F || code == 0x7F || /[\d\{\}]/.test(chr)) | |
36 return "\\" + code.toString(16) + " "; | |
37 | |
38 return "\\" + chr; | |
39 } | |
40 | |
41 function quote(value) | |
42 { | |
43 return '"' + value.replace(/["\\\{\}\x00-\x1F\x7F]/g, escapeChar) + '"'; | |
44 } | |
45 | |
46 function escapeCSS(s) | |
47 { | |
48 return s.replace(/^[\d\-]|[^\w\-\u0080-\uFFFF]/g, escapeChar); | |
49 } | |
50 | |
51 function supportsShadowRoot(element) | |
52 { | |
53 if (!("createShadowRoot" in element)) | |
54 return false; | |
55 | |
56 // There are some elements (e.g. <textarea>), which don't | |
57 // support author created shadow roots and throw an exception. | |
58 var clone = element.cloneNode(false); | |
59 try | |
60 { | |
61 clone.createShadowRoot(); | |
62 } | |
63 catch (e) | |
64 { | |
65 return false; | |
66 } | |
67 | |
68 // There are some elements (e.g. <input>), which support | |
69 // author created shadow roots, but ignore insertion points. | |
70 var child = document.createTextNode(""); | |
71 clone.appendChild(child); | |
72 | |
73 var shadow = document.createElement("shadow"); | |
74 clone.shadowRoot.appendChild(shadow); | |
75 | |
76 return shadow.getDistributedNodes()[0] == child; | |
77 } | |
78 | |
79 function getOriginalStyle(element) | |
80 { | |
81 if ("_originalStyle" in element) | |
82 return element._originalStyle; | |
83 | |
84 return element.getAttribute("style"); | |
85 } | |
86 | 27 |
87 function highlightElement(element, shadowColor, backgroundColor) | 28 function highlightElement(element, shadowColor, backgroundColor) |
88 { | 29 { |
89 unhighlightElement(element); | 30 unhighlightElement(element); |
90 | 31 |
91 var originalBoxShadowPriority = element.style.getPropertyPriority("box-shadow"
); | 32 var highlightWithOverlay = function() |
92 var originalBackgroundColorPriority = element.style.getPropertyPriority("backg
round-color"); | 33 { |
93 | 34 var overlay = addElementOverlay(element); |
94 var boxShadow = "inset 0px 0px 5px " + shadowColor; | 35 |
95 | 36 // If the element isn't displayed no overlay will be added. |
96 var highlightWithShadowDOM = function() | 37 // Moreover, we don't need to highlight anything then. |
97 { | 38 if (!overlay) |
98 var style = document.createElement("style"); | 39 return; |
99 style.textContent = ":host {" + | 40 |
100 "box-shadow:" + boxShadow + " !important;" + | 41 highlightElement(overlay, shadowColor, backgroundColor); |
101 "background-color:" + backgroundColor + " !important;" + | 42 overlay.style.pointerEvents = "none"; |
102 "}"; | |
103 | |
104 var root = element.createShadowRoot(); | |
105 root.appendChild(document.createElement("shadow")); | |
106 root.appendChild(style); | |
107 | 43 |
108 element._unhighlight = function() | 44 element._unhighlight = function() |
109 { | 45 { |
110 root.removeChild(style); | 46 overlay.parentNode.removeChild(overlay); |
111 }; | 47 }; |
112 }; | 48 }; |
113 | 49 |
114 var highlightWithStyleAttribute = function() | 50 var highlightWithStyleAttribute = function() |
115 { | 51 { |
116 var originalBoxShadow = element.style.getPropertyValue("box-shadow"); | 52 var originalBoxShadow = element.style.getPropertyValue("box-shadow"); |
| 53 var originalBoxShadowPriority = element.style.getPropertyPriority("box-shado
w"); |
117 var originalBackgroundColor = element.style.getPropertyValue("background-col
or"); | 54 var originalBackgroundColor = element.style.getPropertyValue("background-col
or"); |
118 | 55 var originalBackgroundColorPriority = element.style.getPropertyPriority("bac
kground-color"); |
119 element._originalStyle = getOriginalStyle(element); | 56 |
120 | 57 element.style.setProperty("box-shadow", "inset 0px 0px 5px " + shadowColor,
"important"); |
121 element.style.setProperty("box-shadow", boxShadow, "important"); | |
122 element.style.setProperty("background-color", backgroundColor, "important"); | 58 element.style.setProperty("background-color", backgroundColor, "important"); |
123 | 59 |
124 element._unhighlight = function() | 60 element._unhighlight = function() |
125 { | 61 { |
126 this.style.removeProperty("box-shadow"); | 62 this.style.removeProperty("box-shadow"); |
127 this.style.setProperty( | 63 this.style.setProperty( |
128 "box-shadow", | 64 "box-shadow", |
129 originalBoxShadow, | 65 originalBoxShadow, |
130 originalBoxShadowPriority | 66 originalBoxShadowPriority |
131 ); | 67 ); |
132 | 68 |
133 this.style.removeProperty("background-color"); | 69 this.style.removeProperty("background-color"); |
134 this.style.setProperty( | 70 this.style.setProperty( |
135 "background-color", | 71 "background-color", |
136 originalBackgroundColor, | 72 originalBackgroundColor, |
137 originalBackgroundColorPriority | 73 originalBackgroundColorPriority |
138 ); | 74 ); |
139 }; | 75 }; |
140 }; | 76 }; |
141 | 77 |
142 // Use shadow DOM if posibble to avoid side effects when the | 78 if ("prisoner" in element) |
143 // web page updates style while highlighted. However, if the | 79 highlightWithStyleAttribute(); |
144 // element has important styles we can't override them with shadow DOM. | |
145 if (supportsShadowRoot(element) && originalBoxShadowPriority != "importa
nt" && | |
146 originalBackgroundColorPriority != "importa
nt") | |
147 highlightWithShadowDOM(); | |
148 else | 80 else |
149 highlightWithStyleAttribute(); | 81 highlightWithOverlay(); |
150 } | 82 } |
151 | 83 |
152 | 84 |
153 function unhighlightElement(element) | 85 function unhighlightElement(element) |
154 { | 86 { |
155 if ("_unhighlight" in element) | 87 if ("_unhighlight" in element) |
156 { | 88 { |
157 element._unhighlight(); | 89 element._unhighlight(); |
158 delete element._unhighlight; | 90 delete element._unhighlight; |
159 } | 91 } |
(...skipping 22 matching lines...) Expand all Loading... |
182 ); | 114 ); |
183 | 115 |
184 highlightedElementsSelector = null; | 116 highlightedElementsSelector = null; |
185 } | 117 } |
186 } | 118 } |
187 | 119 |
188 function getURLsFromObjectElement(element) | 120 function getURLsFromObjectElement(element) |
189 { | 121 { |
190 var url = element.getAttribute("data"); | 122 var url = element.getAttribute("data"); |
191 if (url) | 123 if (url) |
192 return [resolveURL(url)]; | 124 return [url]; |
193 | 125 |
194 for (var i = 0; i < element.children.length; i++) | 126 for (var i = 0; i < element.children.length; i++) |
195 { | 127 { |
196 var child = element.children[i]; | 128 var child = element.children[i]; |
197 if (child.localName != "param") | 129 if (child.localName != "param") |
198 continue; | 130 continue; |
199 | 131 |
200 var name = child.getAttribute("name"); | 132 var name = child.getAttribute("name"); |
201 if (name != "movie" && // Adobe Flash | 133 if (name != "movie" && // Adobe Flash |
202 name != "source" && // Silverlight | 134 name != "source" && // Silverlight |
203 name != "src" && // Real Media + Quicktime | 135 name != "src" && // Real Media + Quicktime |
204 name != "FileName") // Windows Media | 136 name != "FileName") // Windows Media |
205 continue; | 137 continue; |
206 | 138 |
207 var value = child.getAttribute("value"); | 139 var value = child.getAttribute("value"); |
208 if (!value) | 140 if (!value) |
209 continue; | 141 continue; |
210 | 142 |
211 return [resolveURL(value)]; | 143 return [value]; |
212 } | 144 } |
213 | 145 |
214 return []; | 146 return []; |
215 } | 147 } |
216 | 148 |
217 function getURLsFromAttributes(element) | 149 function getURLsFromAttributes(element) |
218 { | 150 { |
219 var urls = []; | 151 var urls = []; |
220 | 152 |
221 if (element.src) | 153 if (element.src) |
222 urls.push(element.src); | 154 urls.push(element.src); |
223 | 155 |
224 if (element.srcset) | 156 if (element.srcset) |
225 { | 157 { |
226 var candidates = element.srcset.split(","); | 158 var candidates = element.srcset.split(","); |
227 for (var i = 0; i < candidates.length; i++) | 159 for (var i = 0; i < candidates.length; i++) |
228 { | 160 { |
229 var url = candidates[i].trim().replace(/\s+\S+$/, ""); | 161 var url = candidates[i].trim().replace(/\s+\S+$/, ""); |
230 if (url) | 162 if (url) |
231 urls.push(resolveURL(url)); | 163 urls.push(url); |
232 } | 164 } |
233 } | 165 } |
234 | 166 |
235 return urls; | 167 return urls; |
236 } | 168 } |
237 | 169 |
238 function getURLsFromMediaElement(element) | 170 function getURLsFromMediaElement(element) |
239 { | 171 { |
240 var urls = getURLsFromAttributes(element); | 172 var urls = getURLsFromAttributes(element); |
241 | 173 |
(...skipping 18 matching lines...) Expand all Loading... |
260 | 192 |
261 case "video": | 193 case "video": |
262 case "audio": | 194 case "audio": |
263 case "picture": | 195 case "picture": |
264 return getURLsFromMediaElement(element); | 196 return getURLsFromMediaElement(element); |
265 } | 197 } |
266 | 198 |
267 return getURLsFromAttributes(element); | 199 return getURLsFromAttributes(element); |
268 } | 200 } |
269 | 201 |
270 // Gets the absolute position of an element by walking up the DOM tree, | |
271 // adding up offsets. | |
272 // I hope there's a better way because it just seems absolutely stupid | |
273 // that the DOM wouldn't have a direct way to get this, given that it | |
274 // has hundreds and hundreds of other methods that do random junk. | |
275 function getAbsolutePosition(elt) { | |
276 var l = 0; | |
277 var t = 0; | |
278 for(; elt; elt = elt.offsetParent) { | |
279 l += elt.offsetLeft; | |
280 t += elt.offsetTop; | |
281 } | |
282 return [l, t]; | |
283 } | |
284 | |
285 // 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 |
286 function addElementOverlay(elt, callback) { | 203 function addElementOverlay(elt) { |
287 // If this element is enclosed in an object tag, we prefer to block that inste
ad | 204 var zIndex = "auto"; |
288 if(!elt) | 205 var position = "absolute"; |
289 return; | 206 |
290 | 207 for (var e = elt; e; e = e.parentElement) |
291 // If the element isn't rendered (since its or one of its ancestor's | 208 { |
292 // "display" property is "none"), the overlay wouldn't match the element. | 209 var style = getComputedStyle(e); |
293 if (!elt.offsetParent) | 210 |
294 return; | 211 // If the element isn't rendered (since its or one of its ancestor's |
295 | 212 // "display" property is "none"), the overlay wouldn't match the element. |
296 generateFilters(elt, function(filters) | 213 if (style.display == "none") |
297 { | 214 return null; |
298 if (filters.length == 0) | 215 |
299 return; | 216 // If the element or one of its ancestors uses fixed postioning, the overlay |
300 | 217 // has to use fixed postioning too. Otherwise it might not match the element
. |
301 var thisStyle = getComputedStyle(elt, null); | 218 if (style.position == "fixed") |
302 var overlay = document.createElement('div'); | 219 position = "fixed"; |
303 overlay.prisoner = elt; | 220 |
304 overlay.className = "__adblockplus__overlay"; | 221 // Determine the effective z-index, which is the highest z-index used |
305 overlay.setAttribute('style', 'opacity:0.4; background-color:#ffffff; displa
y:inline-box; ' + 'width:' + thisStyle.width + '; height:' + thisStyle.height +
'; position:absolute; overflow:hidden; -webkit-box-sizing:border-box;'); | 222 // by the element and its offset ancestors, and increase it by one. |
306 var pos = getAbsolutePosition(elt); | 223 // When using a lower z-index the element would cover the overlay. |
307 overlay.style.left = pos[0] + "px"; | 224 // When using a higher z-index the overlay might also cover other elements. |
308 overlay.style.top = pos[1] + "px"; | 225 if (style.position != "static" && style.zIndex != "auto") |
309 | 226 { |
310 if (thisStyle.position != "static") | 227 var curZIndex = parseInt(style.zIndex, 10) + 1; |
311 overlay.style.zIndex = thisStyle.zIndex; | 228 |
312 else | 229 if (zIndex == "auto" || curZIndex > zIndex) |
313 overlay.style.zIndex = getComputedStyle(elt.offsetParent).zIndex; | 230 zIndex = curZIndex; |
314 | 231 } |
315 document.body.appendChild(overlay); | 232 } |
316 | 233 |
317 if (callback) | 234 var overlay = document.createElement('div'); |
318 callback(overlay); | 235 overlay.prisoner = elt; |
319 }); | 236 overlay.className = "__adblockplus__overlay"; |
| 237 overlay.setAttribute('style', 'opacity:0.4; display:inline-box; overflow:hidde
n; box-sizing:border-box;'); |
| 238 var rect = elt.getBoundingClientRect(); |
| 239 overlay.style.width = rect.width + "px"; |
| 240 overlay.style.height = rect.height + "px"; |
| 241 overlay.style.left = (rect.left + window.scrollX) + "px"; |
| 242 overlay.style.top = (rect.top + window.scrollY) + "px"; |
| 243 overlay.style.position = position; |
| 244 overlay.style.zIndex = zIndex; |
| 245 |
| 246 // elt.parentNode.appendChild(overlay, elt); |
| 247 document.documentElement.appendChild(overlay); |
| 248 return overlay; |
320 } | 249 } |
321 | 250 |
322 // Show dialog asking user whether she wants to add the proposed filters derived | 251 // Show dialog asking user whether she wants to add the proposed filters derived |
323 // from selected page element | 252 // from selected page element |
324 function clickHide_showDialog(left, top, filters) | 253 function clickHide_showDialog(left, top, filters) |
325 { | 254 { |
326 // If we are already selecting, abort now | 255 // If we are already selecting, abort now |
327 if (clickHide_activated || clickHideFiltersDialog) | 256 if (clickHide_activated || clickHideFiltersDialog) |
328 clickHide_deactivate(true); | 257 clickHide_deactivate(true); |
329 | 258 |
330 clickHide_filters = filters; | 259 clickHide_filters = filters; |
331 | 260 |
332 clickHideFiltersDialog = document.createElement("iframe"); | 261 clickHideFiltersDialog = document.createElement("iframe"); |
333 clickHideFiltersDialog.src = ext.getURL("block.html"); | 262 clickHideFiltersDialog.src = ext.getURL("block.html"); |
334 clickHideFiltersDialog.setAttribute("style", "position: fixed !important; visi
bility: hidden; display: block !important; border: 0px !important;"); | 263 clickHideFiltersDialog.setAttribute("style", "position: fixed !important; visi
bility: hidden; display: block !important; border: 0px !important;"); |
335 clickHideFiltersDialog.style.WebkitBoxShadow = "5px 5px 20px rgba(0,0,0,0.5)"; | 264 clickHideFiltersDialog.style.WebkitBoxShadow = "5px 5px 20px rgba(0,0,0,0.5)"; |
336 clickHideFiltersDialog.style.zIndex = 0x7FFFFFFF; | 265 clickHideFiltersDialog.style.zIndex = 0x7FFFFFFF; |
337 | 266 |
338 // Position in upper-left all the time | 267 // Position in upper-left all the time |
339 clickHideFiltersDialog.style.left = "50px"; | 268 clickHideFiltersDialog.style.left = "50px"; |
340 clickHideFiltersDialog.style.top = "50px"; | 269 clickHideFiltersDialog.style.top = "50px"; |
341 | 270 |
342 // Make dialog partly transparent when mouse isn't over it so user has a bette
r | 271 // Make dialog partly transparent when mouse isn't over it so user has a bette
r |
343 // view of what's going to be blocked | 272 // view of what's going to be blocked |
344 clickHideFiltersDialog.onmouseout = function() | 273 clickHideFiltersDialog.onmouseout = function() |
345 { | 274 { |
346 if (clickHideFiltersDialog) | 275 if (clickHideFiltersDialog) |
347 clickHideFiltersDialog.style.setProperty("opacity", "0.7"); | 276 clickHideFiltersDialog.style.setProperty("opacity", "0.7"); |
348 } | 277 }; |
349 clickHideFiltersDialog.onmouseover = function() | 278 clickHideFiltersDialog.onmouseover = function() |
350 { | 279 { |
351 if (clickHideFiltersDialog) | 280 if (clickHideFiltersDialog) |
352 clickHideFiltersDialog.style.setProperty("opacity", "1.0"); | 281 clickHideFiltersDialog.style.setProperty("opacity", "1.0"); |
353 } | 282 }; |
354 | 283 |
355 document.body.appendChild(clickHideFiltersDialog); | 284 document.documentElement.appendChild(clickHideFiltersDialog); |
356 } | 285 } |
357 | 286 |
358 // Turn on the choose element to create filter thing | 287 // Turn on the choose element to create filter thing |
359 function clickHide_activate() { | 288 function clickHide_activate() { |
360 if(document == null) | 289 if(document == null) |
361 return; | 290 return; |
362 | 291 |
363 // If we are already selecting, abort now | 292 // If we are already selecting, abort now |
364 if (clickHide_activated || clickHideFiltersDialog) | 293 if (clickHide_activated || clickHideFiltersDialog) |
365 clickHide_deactivate(); | 294 clickHide_deactivate(); |
366 | 295 |
367 // Add overlays for elements with URLs so user can easily click them | 296 // Add overlays for blockable elements that don't emit mouse events, |
368 var elts = document.querySelectorAll('object,embed,img,iframe,video,audio,pict
ure'); | 297 // so that they can still be selected. |
369 for(var i=0; i<elts.length; i++) | 298 [].forEach.call( |
370 addElementOverlay(elts[i]); | 299 document.querySelectorAll('object,embed,iframe,frame'), |
| 300 function(element) |
| 301 { |
| 302 getFiltersForElement(element, function(filters) |
| 303 { |
| 304 if (filters.length > 0) |
| 305 addElementOverlay(element); |
| 306 }); |
| 307 } |
| 308 ); |
371 | 309 |
372 clickHide_activated = true; | 310 clickHide_activated = true; |
| 311 document.addEventListener("mousedown", clickHide_stopPropagation, true); |
| 312 document.addEventListener("mouseup", clickHide_stopPropagation, true); |
| 313 document.addEventListener("mouseenter", clickHide_stopPropagation, true); |
| 314 document.addEventListener("mouseleave", clickHide_stopPropagation, true); |
373 document.addEventListener("mouseover", clickHide_mouseOver, true); | 315 document.addEventListener("mouseover", clickHide_mouseOver, true); |
374 document.addEventListener("mouseout", clickHide_mouseOut, true); | 316 document.addEventListener("mouseout", clickHide_mouseOut, true); |
375 document.addEventListener("click", clickHide_mouseClick, true); | 317 document.addEventListener("click", clickHide_mouseClick, true); |
376 document.addEventListener("keydown", clickHide_keyDown, true); | 318 document.addEventListener("keydown", clickHide_keyDown, true); |
| 319 |
| 320 ext.onExtensionUnloaded.addListener(clickHide_deactivate); |
377 } | 321 } |
378 | 322 |
379 // Called when user has clicked on something and we are waiting for confirmation | 323 // Called when user has clicked on something and we are waiting for confirmation |
380 // on whether the user actually wants these filters | 324 // on whether the user actually wants these filters |
381 function clickHide_rulesPending() { | 325 function clickHide_rulesPending() { |
382 clickHide_activated = false; | 326 clickHide_activated = false; |
| 327 document.removeEventListener("mousedown", clickHide_stopPropagation, true); |
| 328 document.removeEventListener("mouseup", clickHide_stopPropagation, true); |
| 329 document.removeEventListener("mouseenter", clickHide_stopPropagation, true); |
| 330 document.removeEventListener("mouseleave", clickHide_stopPropagation, true); |
383 document.removeEventListener("mouseover", clickHide_mouseOver, true); | 331 document.removeEventListener("mouseover", clickHide_mouseOver, true); |
384 document.removeEventListener("mouseout", clickHide_mouseOut, true); | 332 document.removeEventListener("mouseout", clickHide_mouseOut, true); |
385 document.removeEventListener("click", clickHide_mouseClick, true); | 333 document.removeEventListener("click", clickHide_mouseClick, true); |
386 document.removeEventListener("keydown", clickHide_keyDown, true); | 334 document.removeEventListener("keydown", clickHide_keyDown, true); |
387 } | 335 } |
388 | 336 |
389 // Turn off click-to-hide | 337 // Turn off click-to-hide |
390 function clickHide_deactivate(keepOverlays) | 338 function clickHide_deactivate(keepOverlays) |
391 { | 339 { |
392 if (clickHideFiltersDialog) | 340 if (clickHideFiltersDialog) |
393 { | 341 { |
394 document.body.removeChild(clickHideFiltersDialog); | 342 document.documentElement.removeChild(clickHideFiltersDialog); |
395 clickHideFiltersDialog = null; | 343 clickHideFiltersDialog = null; |
396 } | 344 } |
397 | 345 |
398 clickHide_activated = false; | 346 clickHide_activated = false; |
399 clickHide_filters = null; | 347 clickHide_filters = null; |
400 if(!document) | 348 if(!document) |
401 return; // This can happen inside a nuked iframe...I think | 349 return; // This can happen inside a nuked iframe...I think |
| 350 |
| 351 document.removeEventListener("mousedown", clickHide_stopPropagation, true); |
| 352 document.removeEventListener("mouseup", clickHide_stopPropagation, true); |
| 353 document.removeEventListener("mouseenter", clickHide_stopPropagation, true); |
| 354 document.removeEventListener("mouseleave", clickHide_stopPropagation, true); |
402 document.removeEventListener("mouseover", clickHide_mouseOver, true); | 355 document.removeEventListener("mouseover", clickHide_mouseOver, true); |
403 document.removeEventListener("mouseout", clickHide_mouseOut, true); | 356 document.removeEventListener("mouseout", clickHide_mouseOut, true); |
404 document.removeEventListener("click", clickHide_mouseClick, true); | 357 document.removeEventListener("click", clickHide_mouseClick, true); |
405 document.removeEventListener("keydown", clickHide_keyDown, true); | 358 document.removeEventListener("keydown", clickHide_keyDown, true); |
406 | 359 |
407 if (!keepOverlays) | 360 if (keepOverlays !== true) |
408 { | 361 { |
| 362 lastRightClickEvent = null; |
| 363 |
409 if (currentElement) { | 364 if (currentElement) { |
410 currentElement.removeEventListener("contextmenu", clickHide_elementClickH
andler, true); | 365 currentElement.removeEventListener("contextmenu", clickHide_elementClickH
andler, true); |
411 unhighlightElements(); | 366 unhighlightElements(); |
412 unhighlightElement(currentElement); | 367 unhighlightElement(currentElement); |
413 currentElement = null; | 368 currentElement = null; |
414 clickHideFilters = null; | |
415 } | 369 } |
416 unhighlightElements(); | 370 unhighlightElements(); |
417 | 371 |
418 var overlays = document.getElementsByClassName("__adblockplus__overlay"); | 372 var overlays = document.getElementsByClassName("__adblockplus__overlay"); |
419 while (overlays.length > 0) | 373 while (overlays.length > 0) |
420 overlays[0].parentNode.removeChild(overlays[0]); | 374 overlays[0].parentNode.removeChild(overlays[0]); |
421 } | 375 |
422 } | 376 ext.onExtensionUnloaded.removeListener(clickHide_deactivate); |
423 | 377 } |
424 function clickHide_elementClickHandler(ev) { | 378 } |
425 ev.preventDefault(); | 379 |
426 ev.stopPropagation(); | 380 function clickHide_stopPropagation(e) |
427 clickHide_mouseClick(ev); | 381 { |
| 382 e.stopPropagation(); |
| 383 } |
| 384 |
| 385 function clickHide_elementClickHandler(e) { |
| 386 e.preventDefault(); |
| 387 e.stopPropagation(); |
| 388 clickHide_mouseClick(e); |
428 } | 389 } |
429 | 390 |
430 function getBlockableElementOrAncestor(element, callback) | 391 function getBlockableElementOrAncestor(element, callback) |
431 { | 392 { |
432 if (element && element != document.documentElement | 393 // We assume that the user doesn't want to block the whole page. |
433 && element != document.body) | 394 // So we never consider the <html> or <body> element. |
434 { | 395 while (element && element != document.documentElement |
435 generateFilters(element, function(filters) | 396 && element != document.body) |
436 { | 397 { |
437 if (filters.length > 0) | 398 // We can't handle non-HTML (like SVG) elements, as well as |
438 callback(element); | 399 // <area> elements (see below). So fall back to the parent element. |
439 else | 400 if (!(element instanceof HTMLElement) || element.localName == "area") |
440 getBlockableElementOrAncestor(element.parentElement, callback); | 401 element = element.parentElement; |
441 }); | 402 |
442 } | 403 // If image maps are used mouse events occur for the <area> element. |
443 else | 404 // But we have to block the image associated with the <map> element. |
444 { | 405 else if (element.localName == "map") |
445 callback(null); | 406 { |
446 } | 407 var images = document.querySelectorAll("img[usemap]"); |
| 408 var image = null; |
| 409 |
| 410 for (var i = 0; i < images.length; i++) |
| 411 { |
| 412 var usemap = image.getAttribute("usemap"); |
| 413 var index = usemap.indexOf("#"); |
| 414 |
| 415 if (index != -1 && usemap.substr(index + 1) == element.name) |
| 416 { |
| 417 image = images[i]; |
| 418 break; |
| 419 } |
| 420 } |
| 421 |
| 422 element = image; |
| 423 } |
| 424 |
| 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 } |
| 439 } |
| 440 |
| 441 // We reached the document root without finding a blockable element. |
| 442 callback(null); |
447 } | 443 } |
448 | 444 |
449 // Hovering over an element so highlight it | 445 // Hovering over an element so highlight it |
450 function clickHide_mouseOver(e) | 446 function clickHide_mouseOver(e) |
451 { | 447 { |
452 if (clickHide_activated == false) | 448 lastMouseOverEvent = e; |
453 return; | |
454 | 449 |
455 getBlockableElementOrAncestor(e.target, function(element) | 450 getBlockableElementOrAncestor(e.target, function(element) |
456 { | 451 { |
457 if (currentElement) | 452 if (e == lastMouseOverEvent) |
458 unhighlightElement(currentElement); | 453 { |
459 | 454 lastMouseOverEvent = null; |
460 if (element) | 455 |
461 { | 456 if (clickHide_activated) |
462 highlightElement(element, "#d6d84b", "#f8fa47"); | 457 { |
463 element.addEventListener("contextmenu", clickHide_elementClickHandler, tru
e); | 458 if (currentElement) |
464 } | 459 unhighlightElement(currentElement); |
465 | 460 |
466 currentElement = element; | 461 if (element) |
| 462 { |
| 463 highlightElement(element, "#d6d84b", "#f8fa47"); |
| 464 element.addEventListener("contextmenu", clickHide_elementClickHandler,
true); |
| 465 } |
| 466 |
| 467 currentElement = element; |
| 468 } |
| 469 } |
467 }); | 470 }); |
| 471 |
| 472 e.stopPropagation(); |
468 } | 473 } |
469 | 474 |
470 // No longer hovering over this element so unhighlight it | 475 // No longer hovering over this element so unhighlight it |
471 function clickHide_mouseOut(e) | 476 function clickHide_mouseOut(e) |
472 { | 477 { |
473 if (!clickHide_activated || currentElement != e.target) | 478 if (!clickHide_activated || currentElement != e.target) |
474 return; | 479 return; |
475 | 480 |
476 unhighlightElement(currentElement); | 481 unhighlightElement(currentElement); |
477 currentElement.removeEventListener("contextmenu", clickHide_elementClickHandle
r, true); | 482 currentElement.removeEventListener("contextmenu", clickHide_elementClickHandle
r, true); |
| 483 e.stopPropagation(); |
478 } | 484 } |
479 | 485 |
480 // Selects the currently hovered-over filter or cancels selection | 486 // Selects the currently hovered-over filter or cancels selection |
481 function clickHide_keyDown(e) | 487 function clickHide_keyDown(e) |
482 { | 488 { |
483 if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode == 13 /*DOM_VK_RETURN*
/) | 489 if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode == 13 /*DOM_VK_RETURN*
/) |
484 clickHide_mouseClick(e); | 490 clickHide_mouseClick(e); |
485 else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode == 27 /*DOM_VK_ES
CAPE*/) | 491 else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode == 27 /*DOM_VK_ES
CAPE*/) |
486 { | 492 { |
487 ext.backgroundPage.sendMessage( | 493 ext.backgroundPage.sendMessage( |
488 { | 494 { |
489 type: "forward", | 495 type: "forward", |
490 payload: | 496 payload: |
491 { | 497 { |
492 type: "clickhide-deactivate" | 498 type: "clickhide-deactivate" |
493 } | 499 } |
494 }); | 500 }); |
495 e.preventDefault(); | 501 e.preventDefault(); |
496 e.stopPropagation(); | 502 e.stopPropagation(); |
497 } | 503 } |
498 } | 504 } |
499 | 505 |
500 function generateFilters(element, callback) | 506 function getFiltersForElement(element, callback) |
501 { | 507 { |
502 function addStyleAttributeFilter(filters, selectors) | 508 ext.backgroundPage.sendMessage( |
503 { | 509 { |
504 var style = getOriginalStyle(element); | 510 type: "compose-filters", |
505 if (style && filters.length == 0) | 511 tagName: element.localName, |
506 { | 512 id: element.id, |
507 ext.backgroundPage.sendMessage( | 513 src: element.getAttribute("src"), |
508 { | 514 style: element.getAttribute("style"), |
509 type: "get-filters-from-selectors", | 515 classes: [].slice.call(element.classList), |
510 selectors: [escapeCSS(element.localName) + '[style=' + quote(style) +
']'] | 516 urls: getURLsFromElement(element), |
511 }, | 517 mediatype: typeMap[element.localName], |
512 | 518 baseURL: document.location.href |
513 function(response) | 519 }, |
514 { | 520 function(response) |
515 callback(response.filters, response.selectors); | 521 { |
516 } | 522 callback(response.filters, response.selectors); |
517 ); | 523 } |
518 } | 524 ); |
519 else | |
520 callback(filters, selectors); | |
521 } | |
522 | |
523 function addElemHideFilters(filters, selectors) | |
524 { | |
525 if (selectors.length > 0) | |
526 { | |
527 ext.backgroundPage.sendMessage( | |
528 { | |
529 type: "get-filters-from-selectors", | |
530 selectors: selectors | |
531 }, | |
532 | |
533 function(response) | |
534 { | |
535 addStyleAttributeFilter(filters.concat(response.filters), response.sel
ectors); | |
536 } | |
537 ); | |
538 } | |
539 else | |
540 addStyleAttributeFilter(filters, selectors); | |
541 } | |
542 | |
543 var filters = []; | |
544 var selectors = []; | |
545 | |
546 if (element.id) | |
547 selectors.push("#" + escapeCSS(element.id)); | |
548 | |
549 if (element.classList.length > 0) | |
550 { | |
551 var selector = ""; | |
552 | |
553 for (var i = 0; i < element.classList.length; i++) | |
554 selector += "." + escapeCSS(element.classList[i]); | |
555 | |
556 selectors.push(selector); | |
557 } | |
558 | |
559 var urls = getURLsFromElement(element); | |
560 if (urls.length > 0) | |
561 { | |
562 ext.backgroundPage.sendMessage( | |
563 { | |
564 type: "check-whitelisted-urls", | |
565 mediatype: typeMap[element.localName], | |
566 urls: urls | |
567 }, | |
568 | |
569 function(whitelisted) | |
570 { | |
571 for (var i = 0; i < urls.length; i++) | |
572 { | |
573 var url = urls[i]; | |
574 | |
575 if (!whitelisted[url] && /^https?:/i.test(url)) | |
576 { | |
577 var filter = url.replace(/^[\w\-]+:\/+(?:www\.)?/, "||"); | |
578 | |
579 if (filters.indexOf(filter) == -1) | |
580 filters.push(filter); | |
581 | |
582 continue; | |
583 } | |
584 | |
585 if (url == element.src) | |
586 { | |
587 var selector = escapeCSS(element.localName) + '[src=' + quote(elemen
t.getAttribute("src")) + ']'; | |
588 | |
589 if (selectors.indexOf(selector) == -1) | |
590 selectors.push(selector); | |
591 } | |
592 } | |
593 | |
594 addElemHideFilters(filters, selectors); | |
595 } | |
596 ); | |
597 } | |
598 else | |
599 addElemHideFilters(filters, selectors); | |
600 } | 525 } |
601 | 526 |
602 // When the user clicks, the currentElement is the one we want. | 527 // When the user clicks, the currentElement is the one we want. |
603 // We should have ABP rules ready for when the | 528 // We should have ABP rules ready for when the |
604 // popup asks for them. | 529 // popup asks for them. |
605 function clickHide_mouseClick(e) | 530 function clickHide_mouseClick(e) |
606 { | 531 { |
607 if (!currentElement || !clickHide_activated) | 532 if (!currentElement || !clickHide_activated) |
608 return; | 533 return; |
609 | 534 |
610 var elt = currentElement; | 535 var elt = currentElement; |
611 if (currentElement.classList.contains("__adblockplus__overlay")) | 536 if (currentElement.classList.contains("__adblockplus__overlay")) |
612 elt = currentElement.prisoner; | 537 elt = currentElement.prisoner; |
613 | 538 |
614 generateFilters(elt, function(filters, selectors) | 539 getFiltersForElement(elt, function(filters, selectors) |
615 { | 540 { |
616 clickHide_showDialog(e.clientX, e.clientY, filters); | 541 ext.backgroundPage.sendMessage( |
617 | 542 { |
618 // Highlight the elements specified by selector in yellow | 543 type: "forward", |
| 544 payload: |
| 545 { |
| 546 type: "clickhide-show-dialog", |
| 547 screenX: e.screenX, |
| 548 screenY: e.screenY, |
| 549 clickHideFilters: filters |
| 550 } |
| 551 }); |
| 552 |
619 if (selectors.length > 0) | 553 if (selectors.length > 0) |
620 highlightElements(selectors.join(",")); | 554 highlightElements(selectors.join(",")); |
621 | 555 |
622 // Now, actually highlight the element the user clicked on in red | |
623 highlightElement(currentElement, "#fd1708", "#f6a1b5"); | 556 highlightElement(currentElement, "#fd1708", "#f6a1b5"); |
624 }); | 557 }); |
625 | 558 |
626 // Make sure the browser doesn't handle this click | 559 // Make sure the browser doesn't handle this click |
627 e.preventDefault(); | 560 e.preventDefault(); |
628 e.stopPropagation(); | 561 e.stopPropagation(); |
629 } | 562 } |
630 | 563 |
631 // This function Copyright (c) 2008 Jeni Tennison, from jquery.uri.js | 564 // This function Copyright (c) 2008 Jeni Tennison, from jquery.uri.js |
632 // and licensed under the MIT license. See jquery-*.min.js for details. | 565 // and licensed under the MIT license. See jquery-*.min.js for details. |
(...skipping 27 matching lines...) Expand all Loading... |
660 // In Chrome 37-40, the document_end content script (this one) runs properly, wh
ile the | 593 // In Chrome 37-40, the document_end content script (this one) runs properly, wh
ile the |
661 // document_start content scripts (that defines ext) might not. Check whether va
riable ext | 594 // document_start content scripts (that defines ext) might not. Check whether va
riable ext |
662 // exists before continuing to avoid "Uncaught ReferenceError: ext is not define
d". | 595 // exists before continuing to avoid "Uncaught ReferenceError: ext is not define
d". |
663 // See https://crbug.com/416907 | 596 // See https://crbug.com/416907 |
664 if ("ext" in window && document instanceof HTMLDocument) | 597 if ("ext" in window && document instanceof HTMLDocument) |
665 { | 598 { |
666 // Use a contextmenu handler to save the last element the user right-clicked o
n. | 599 // Use a contextmenu handler to save the last element the user right-clicked o
n. |
667 // To make things easier, we actually save the DOM event. | 600 // To make things easier, we actually save the DOM event. |
668 // We have to do this because the contextMenu API only provides a URL, not the
actual | 601 // We have to do this because the contextMenu API only provides a URL, not the
actual |
669 // DOM element. | 602 // DOM element. |
670 document.addEventListener('contextmenu', function(e) { | 603 document.addEventListener('contextmenu', function(e) |
| 604 { |
671 lastRightClickEvent = e; | 605 lastRightClickEvent = e; |
| 606 // We also need to ensure any old lastRightClickEvent variables in other |
| 607 // frames are cleared. |
| 608 lastRightClickEventValid = true; |
| 609 ext.backgroundPage.sendMessage( |
| 610 { |
| 611 type: "forward", |
| 612 payload: |
| 613 { |
| 614 type: "clickhide-clear-last-right-click-event" |
| 615 } |
| 616 }); |
672 }, true); | 617 }, true); |
673 | 618 |
674 document.addEventListener("click", function(event) | 619 document.addEventListener("click", function(event) |
675 { | 620 { |
676 // Ignore right-clicks | 621 // Ignore right-clicks |
677 if (event.button == 2) | 622 if (event.button == 2) |
678 return; | 623 return; |
679 | 624 |
680 // Search the link associated with the click | 625 // Search the link associated with the click |
681 var link = event.target; | 626 var link = event.target; |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
741 break; | 686 break; |
742 case "clickhide-activate": | 687 case "clickhide-activate": |
743 clickHide_activate(); | 688 clickHide_activate(); |
744 break; | 689 break; |
745 case "clickhide-deactivate": | 690 case "clickhide-deactivate": |
746 clickHide_deactivate(); | 691 clickHide_deactivate(); |
747 break; | 692 break; |
748 case "clickhide-new-filter": | 693 case "clickhide-new-filter": |
749 if(lastRightClickEvent) | 694 if(lastRightClickEvent) |
750 { | 695 { |
751 var event = lastRightClickEvent; | 696 getBlockableElementOrAncestor(lastRightClickEvent.target, function(ele
ment) |
752 | |
753 addElementOverlay(event.target, function(overlay) | |
754 { | 697 { |
755 clickHide_activated = true; | 698 clickHide_activated = true; |
756 currentElement = overlay; | 699 currentElement = element; |
757 clickHide_mouseClick(event); | 700 clickHide_mouseClick(lastRightClickEvent); |
758 }); | 701 }); |
759 } | 702 } |
760 break; | 703 break; |
761 case "clickhide-init": | 704 case "clickhide-init": |
762 if (clickHideFiltersDialog) | 705 if (clickHideFiltersDialog) |
763 { | 706 { |
764 sendResponse({filters: clickHide_filters}); | 707 sendResponse({filters: clickHide_filters}); |
765 | 708 |
766 clickHideFiltersDialog.style.width = msg.width + "px"; | 709 clickHideFiltersDialog.style.width = msg.width + "px"; |
767 clickHideFiltersDialog.style.height = msg.height + "px"; | 710 clickHideFiltersDialog.style.height = msg.height + "px"; |
768 clickHideFiltersDialog.style.visibility = "visible"; | 711 clickHideFiltersDialog.style.visibility = "visible"; |
769 } | 712 } |
770 break; | 713 break; |
771 case "clickhide-move": | 714 case "clickhide-move": |
772 if (clickHideFiltersDialog) | 715 if (clickHideFiltersDialog) |
773 { | 716 { |
774 clickHideFiltersDialog.style.left = (parseInt(clickHideFiltersDialog.s
tyle.left, 10) + msg.x) + "px"; | 717 clickHideFiltersDialog.style.left = (parseInt(clickHideFiltersDialog.s
tyle.left, 10) + msg.x) + "px"; |
775 clickHideFiltersDialog.style.top = (parseInt(clickHideFiltersDialog.st
yle.top, 10) + msg.y) + "px"; | 718 clickHideFiltersDialog.style.top = (parseInt(clickHideFiltersDialog.st
yle.top, 10) + msg.y) + "px"; |
776 } | 719 } |
777 break; | 720 break; |
778 case "clickhide-close": | 721 case "clickhide-close": |
779 if (clickHideFiltersDialog && msg.remove) | 722 if (currentElement && msg.remove) |
780 { | 723 { |
781 // Explicitly get rid of currentElement | 724 // Explicitly get rid of currentElement |
782 var element = currentElement.prisoner || currentElement; | 725 var element = currentElement.prisoner || currentElement; |
783 if (element && element.parentNode) | 726 if (element && element.parentNode) |
784 element.parentNode.removeChild(element); | 727 element.parentNode.removeChild(element); |
785 } | 728 } |
786 clickHide_deactivate(); | 729 clickHide_deactivate(); |
787 break; | 730 break; |
788 default: | 731 case "clickhide-show-dialog": |
789 sendResponse({}); | 732 if (window.self == window.top) |
| 733 clickHide_showDialog(msg.screenX + window.pageXOffset, |
| 734 msg.screenY + window.pageYOffset, |
| 735 msg.clickHideFilters); |
| 736 break; |
| 737 case "clickhide-clear-last-right-click-event": |
| 738 if (lastRightClickEventValid) |
| 739 lastRightClickEventValid = false; |
| 740 else |
| 741 lastRightClickEvent = null; |
790 break; | 742 break; |
791 } | 743 } |
792 }); | 744 }); |
793 | 745 |
794 if (window == window.top) | 746 if (window == window.top) |
795 ext.backgroundPage.sendMessage({type: "report-html-page"}); | 747 ext.backgroundPage.sendMessage({type: "report-html-page"}); |
796 } | 748 } |
LEFT | RIGHT |