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

Delta Between Two Patch Sets: blockElement.postload.js

Issue 29336084: Issue 2426 - Open block.html as a popup window (Closed)
Left Patch Set: Renamed include.postload.js to blockElement.postload.js Created Feb. 8, 2016, 12:30 p.m.
Right Patch Set: Assume createData parameter has been given Created Feb. 17, 2016, 8:50 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « block.js ('k') | chrome/ext/background.js » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 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 "use strict";
19 var clickHide_activated = false; 19
20 var clickHide_filters = null; 20 // The page ID for the popup filter selection dialog (top frame only).
21 var currentElement = null; 21 let blockelementPopupId = null;
22 var highlightedElementsSelector = null; 22
23 var highlightedElementsInterval = null; 23 // Element picking state (top frame only).
24 var clickHideFiltersDialog = null; 24 let currentlyPickingElement = false;
25 var lastRightClickEvent = null; 25 let lastMouseOverEvent = null;
26 var lastRightClickEventValid = false; 26
27 var lastMouseOverEvent = null; 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 }
28 161
29 function highlightElement(element, shadowColor, backgroundColor) 162 function highlightElement(element, shadowColor, backgroundColor)
30 { 163 {
31 unhighlightElement(element); 164 unhighlightElement(element);
32 165
33 var highlightWithOverlay = function() 166 let highlightWithOverlay = function()
34 { 167 {
35 var overlay = addElementOverlay(element); 168 let overlay = addElementOverlay(element);
36 169
37 // If the element isn't displayed no overlay will be added. 170 // If the element isn't displayed no overlay will be added.
38 // Moreover, we don't need to highlight anything then. 171 // Moreover, we don't need to highlight anything then.
39 if (!overlay) 172 if (!overlay)
40 return; 173 return;
41 174
42 highlightElement(overlay, shadowColor, backgroundColor); 175 highlightElement(overlay, shadowColor, backgroundColor);
43 overlay.style.pointerEvents = "none"; 176 overlay.style.pointerEvents = "none";
44 177
45 element._unhighlight = function() 178 element._unhighlight = () =>
46 { 179 {
47 overlay.parentNode.removeChild(overlay); 180 overlay.parentNode.removeChild(overlay);
48 }; 181 };
49 }; 182 };
50 183
51 var highlightWithStyleAttribute = function() 184 let highlightWithStyleAttribute = function()
52 { 185 {
53 var originalBoxShadow = element.style.getPropertyValue("box-shadow"); 186 let originalBoxShadow = element.style.getPropertyValue("box-shadow");
54 var originalBoxShadowPriority = element.style.getPropertyPriority("box-shado w"); 187 let originalBoxShadowPriority =
55 var originalBackgroundColor = element.style.getPropertyValue("background-col or"); 188 element.style.getPropertyPriority("box-shadow");
56 var originalBackgroundColorPriority = element.style.getPropertyPriority("bac kground-color"); 189 let originalBackgroundColor =
57 190 element.style.getPropertyValue("background-color");
58 element.style.setProperty("box-shadow", "inset 0px 0px 5px " + shadowColor, "important"); 191 let originalBackgroundColorPriority =
192 element.style.getPropertyPriority("background-color");
193
194 element.style.setProperty("box-shadow", "inset 0px 0px 5px " + shadowColor,
195 "important");
59 element.style.setProperty("background-color", backgroundColor, "important"); 196 element.style.setProperty("background-color", backgroundColor, "important");
60 197
61 element._unhighlight = function() 198 element._unhighlight = () =>
62 { 199 {
63 this.style.removeProperty("box-shadow"); 200 element.style.removeProperty("box-shadow");
64 this.style.setProperty( 201 element.style.setProperty(
65 "box-shadow", 202 "box-shadow",
66 originalBoxShadow, 203 originalBoxShadow,
67 originalBoxShadowPriority 204 originalBoxShadowPriority
68 ); 205 );
69 206
70 this.style.removeProperty("background-color"); 207 element.style.removeProperty("background-color");
71 this.style.setProperty( 208 element.style.setProperty(
72 "background-color", 209 "background-color",
73 originalBackgroundColor, 210 originalBackgroundColor,
74 originalBackgroundColorPriority 211 originalBackgroundColorPriority
75 ); 212 );
76 }; 213 };
77 }; 214 };
78 215
79 if ("prisoner" in element) 216 if ("prisoner" in element)
80 highlightWithStyleAttribute(); 217 highlightWithStyleAttribute();
81 else 218 else
82 highlightWithOverlay(); 219 highlightWithOverlay();
83 } 220 }
84 221
85
86 function unhighlightElement(element) 222 function unhighlightElement(element)
87 { 223 {
88 if ("_unhighlight" in element) 224 if (element && "_unhighlight" in element)
89 { 225 {
90 element._unhighlight(); 226 element._unhighlight();
91 delete element._unhighlight; 227 delete element._unhighlight;
92 } 228 }
93 } 229 }
94 230
95 // Highlight elements according to selector string. This would include 231 // Highlight elements matching the selector string red.
96 // all elements that would be affected by proposed filters. 232 // (All elements that would be blocked by the proposed filters.)
97 function highlightElements(selectorString) { 233 function highlightElements(selectorString)
234 {
98 unhighlightElements(); 235 unhighlightElements();
99 236
100 var elements = Array.prototype.slice.call(document.querySelectorAll(selectorSt ring)); 237 let elements = Array.prototype.slice.call(
238 document.querySelectorAll(selectorString)
239 );
101 highlightedElementsSelector = selectorString; 240 highlightedElementsSelector = selectorString;
102 241
103 // Highlight elements progressively. Otherwise the page freezes 242 // Highlight elements progressively. Otherwise the page freezes
104 // when a lot of elements get highlighted at the same time. 243 // when a lot of elements get highlighted at the same time.
105 highlightedElementsInterval = setInterval(function() 244 highlightedElementsInterval = setInterval(() =>
106 { 245 {
107 if (elements.length > 0) 246 if (elements.length > 0)
108 { 247 {
109 var element = elements.shift(); 248 let element = elements.shift();
110 if (element != currentElement) 249 if (element != currentElement)
111 highlightElement(element, "#fd6738", "#f6e1e5"); 250 highlightElement(element, "#fd6738", "#f6e1e5");
112 } 251 }
113 else 252 else
114 { 253 {
115 clearInterval(highlightedElementsInterval); 254 clearInterval(highlightedElementsInterval);
116 highlightedElementsInterval = null; 255 highlightedElementsInterval = null;
117 } 256 }
118 }, 0); 257 }, 0);
119 } 258 }
120 259
121 // Unhighlight all elements, including those that would be affected by 260 // Unhighlight the elements that were highlighted by selector string previously.
122 // the proposed filters 261 function unhighlightElements()
123 function unhighlightElements() { 262 {
124 if (highlightedElementsInterval) 263 if (highlightedElementsInterval)
125 { 264 {
126 clearInterval(highlightedElementsInterval) 265 clearInterval(highlightedElementsInterval);
127 highlightedElementsInterval = null; 266 highlightedElementsInterval = null;
128 } 267 }
129 268
130 if (highlightedElementsSelector) 269 if (highlightedElementsSelector)
131 { 270 {
132 Array.prototype.forEach.call( 271 Array.prototype.forEach.call(
133 document.querySelectorAll(highlightedElementsSelector), 272 document.querySelectorAll(highlightedElementsSelector),
134 unhighlightElement 273 unhighlightElement
135 ); 274 );
136 275
137 highlightedElementsSelector = null; 276 highlightedElementsSelector = null;
138 } 277 }
139 } 278 }
140 279
141 // Adds an overlay to an element, which is probably a Flash object 280
142 function addElementOverlay(elt) { 281 /* Input event handlers */
143 var position = "absolute"; 282
144 var offsetX = window.scrollX; 283 function stopEventPropagation(event)
145 var offsetY = window.scrollY; 284 {
146 285 event.stopPropagation();
147 for (var e = elt; e; e = e.parentElement) 286 }
148 { 287
149 var style = getComputedStyle(e); 288 // Hovering over an element so highlight it.
150 289 function mouseOver(event)
151 // If the element isn't rendered (since its or one of its ancestor's 290 {
152 // "display" property is "none"), the overlay wouldn't match the element. 291 lastMouseOverEvent = event;
153 if (style.display == "none") 292
154 return null; 293 getBlockableElementOrAncestor(event.target, element =>
155 294 {
156 // If the element or one of its ancestors uses fixed postioning, the overlay 295 if (event == lastMouseOverEvent)
157 // has to use fixed postioning too. Otherwise it might not match the element . 296 {
158 if (style.position == "fixed") 297 lastMouseOverEvent = null;
159 { 298
160 position = "fixed"; 299 if (currentlyPickingElement)
161 offsetX = offsetY = 0; 300 {
162 } 301 if (currentElement)
163 } 302 unhighlightElement(currentElement);
164 303
165 var overlay = document.createElement('div'); 304 if (element)
166 overlay.prisoner = elt; 305 highlightElement(element, "#d6d84b", "#f8fa47");
167 overlay.className = "__adblockplus__overlay"; 306
168 overlay.setAttribute('style', 'opacity:0.4; display:inline-box; overflow:hidde n; box-sizing:border-box;'); 307 currentElement = element;
169 var rect = elt.getBoundingClientRect(); 308 }
170 overlay.style.width = rect.width + "px"; 309 }
171 overlay.style.height = rect.height + "px"; 310 });
172 overlay.style.left = (rect.left + offsetX) + "px"; 311
173 overlay.style.top = (rect.top + offsetY) + "px"; 312 event.stopPropagation();
174 overlay.style.position = position; 313 }
175 overlay.style.zIndex = 0x7FFFFFFE; 314
176 315 // No longer hovering over this element so unhighlight it.
177 // elt.parentNode.appendChild(overlay, elt); 316 function mouseOut(event)
178 document.documentElement.appendChild(overlay); 317 {
179 return overlay; 318 if (!currentlyPickingElement || currentElement != event.target)
180 }
181
182 // Show dialog asking user whether she wants to add the proposed filters derived
183 // from selected page element
184 function clickHide_showDialog(filters)
185 {
186 clickHide_filters = filters;
187
188 clickHideFiltersDialog = document.createElement("iframe");
189 clickHideFiltersDialog.src = ext.getURL("block.html");
190 clickHideFiltersDialog.setAttribute("style", "position: fixed !important; visi bility: hidden; display: block !important; border: 0px !important;");
191 clickHideFiltersDialog.style.WebkitBoxShadow = "5px 5px 20px rgba(0,0,0,0.5)";
192 clickHideFiltersDialog.style.zIndex = 0x7FFFFFFF;
193
194 // Position in upper-left all the time
195 clickHideFiltersDialog.style.left = "50px";
196 clickHideFiltersDialog.style.top = "50px";
197
198 // Make dialog partly transparent when mouse isn't over it so user has a bette r
199 // view of what's going to be blocked
200 clickHideFiltersDialog.onmouseout = function()
201 {
202 if (clickHideFiltersDialog)
203 clickHideFiltersDialog.style.setProperty("opacity", "0.7");
204 };
205 clickHideFiltersDialog.onmouseover = function()
206 {
207 if (clickHideFiltersDialog)
208 clickHideFiltersDialog.style.setProperty("opacity", "1.0");
209 };
210
211 document.documentElement.appendChild(clickHideFiltersDialog);
212 }
213
214 // Turn on the choose element to create filter thing
215 function clickHide_activate() {
216 if(document == null)
217 return; 319 return;
218 320
219 // If we are already selecting, abort now 321 unhighlightElement(currentElement);
220 if (clickHide_activated || clickHideFiltersDialog) 322 event.stopPropagation();
221 clickHide_deactivate(); 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;
222 345
223 // Add overlays for blockable elements that don't emit mouse events, 346 // Add overlays for blockable elements that don't emit mouse events,
224 // so that they can still be selected. 347 // so that they can still be selected.
225 [].forEach.call( 348 Array.prototype.forEach.call(
226 document.querySelectorAll('object,embed,iframe,frame'), 349 document.querySelectorAll("object,embed,iframe,frame"),
227 function(element) 350 element =>
228 { 351 {
229 getFiltersForElement(element, function(filters) 352 getFiltersForElement(element, filters =>
230 { 353 {
231 if (filters.length > 0) 354 if (filters.length > 0)
232 addElementOverlay(element); 355 addElementOverlay(element);
233 }); 356 });
234 } 357 }
235 ); 358 );
236 359
237 clickHide_activated = true; 360 document.addEventListener("mousedown", stopEventPropagation, true);
238 document.addEventListener("mousedown", clickHide_stopPropagation, true); 361 document.addEventListener("mouseup", stopEventPropagation, true);
239 document.addEventListener("mouseup", clickHide_stopPropagation, true); 362 document.addEventListener("mouseenter", stopEventPropagation, true);
240 document.addEventListener("mouseenter", clickHide_stopPropagation, true); 363 document.addEventListener("mouseleave", stopEventPropagation, true);
241 document.addEventListener("mouseleave", clickHide_stopPropagation, true); 364 document.addEventListener("mouseover", mouseOver, true);
242 document.addEventListener("mouseover", clickHide_mouseOver, true); 365 document.addEventListener("mouseout", mouseOut, true);
243 document.addEventListener("mouseout", clickHide_mouseOut, true); 366 document.addEventListener("click", elementPicked, true);
244 document.addEventListener("click", clickHide_mouseClick, true); 367 document.addEventListener("contextmenu", elementPicked, true);
245 document.addEventListener("keydown", clickHide_keyDown, true); 368 document.addEventListener("keydown", keyDown, true);
246 369
247 ext.onExtensionUnloaded.addListener(clickHide_deactivate); 370 ext.onExtensionUnloaded.addListener(deactivateBlockElement);
248 } 371 }
249 372
250 // Called when user has clicked on something and we are waiting for confirmation 373 // The user has picked an element - currentElement. Highlight it red, generate
251 // on whether the user actually wants these filters 374 // filters for it and open a popup dialog so that the user can confirm.
252 function clickHide_rulesPending() { 375 function elementPicked(event)
253 clickHide_activated = false; 376 {
254 377 if (!currentElement)
255 if (clickHideFiltersDialog) 378 return;
256 { 379
257 document.documentElement.removeChild(clickHideFiltersDialog); 380 let element = currentElement.prisoner || currentElement;
258 clickHideFiltersDialog = null; 381 getFiltersForElement(element, (filters, selectors) =>
259 } 382 {
260 383 if (currentlyPickingElement)
261 document.removeEventListener("mousedown", clickHide_stopPropagation, true); 384 stopPickingElement();
262 document.removeEventListener("mouseup", clickHide_stopPropagation, true); 385
263 document.removeEventListener("mouseenter", clickHide_stopPropagation, true); 386 ext.backgroundPage.sendMessage(
264 document.removeEventListener("mouseleave", clickHide_stopPropagation, true); 387 {
265 document.removeEventListener("mouseover", clickHide_mouseOver, true); 388 type: "blockelement-open-popup"
266 document.removeEventListener("mouseout", clickHide_mouseOut, true); 389 },
267 document.removeEventListener("click", clickHide_mouseClick, true); 390 response =>
268 document.removeEventListener("keydown", clickHide_keyDown, true); 391 {
269 } 392 blockelementPopupId = response;
270 393 ext.backgroundPage.sendMessage(
271 function clickHide_deactivate() 394 {
272 { 395 type: "forward",
273 clickHide_rulesPending(); 396 targetPageId: blockelementPopupId,
274 397 payload:
275 clickHide_filters = null; 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
276 lastRightClickEvent = null; 454 lastRightClickEvent = null;
277 455
278 if (currentElement) 456 if (currentElement)
279 { 457 {
280 currentElement.removeEventListener("contextmenu", clickHide_elementClickHan dler, true);
281 unhighlightElement(currentElement); 458 unhighlightElement(currentElement);
282 currentElement = null; 459 currentElement = null;
283 } 460 }
284 unhighlightElements(); 461 unhighlightElements();
285 462
286 var overlays = document.getElementsByClassName("__adblockplus__overlay"); 463 let overlays = document.getElementsByClassName("__adblockplus__overlay");
287 while (overlays.length > 0) 464 while (overlays.length > 0)
288 overlays[0].parentNode.removeChild(overlays[0]); 465 overlays[0].parentNode.removeChild(overlays[0]);
289 466
290 ext.onExtensionUnloaded.removeListener(clickHide_deactivate); 467 ext.onExtensionUnloaded.removeListener(deactivateBlockElement);
291 } 468 }
292 469
293 function clickHide_stopPropagation(e) 470 // In Chrome 37-40, the document_end content script (this one) runs properly,
294 { 471 // while the document_start content scripts (that defines ext) might not. Check
295 e.stopPropagation(); 472 // whether variable ext exists before continuing to avoid
296 } 473 // "Uncaught ReferenceError: ext is not defined". See https://crbug.com/416907
297 474 if ("ext" in window && document instanceof HTMLDocument)
298 function clickHide_elementClickHandler(e) { 475 {
299 e.preventDefault(); 476 // Use a contextmenu handler to save the last element the user right-clicked
300 e.stopPropagation(); 477 // on. To make things easier, we actually save the DOM event. We have to do
301 clickHide_mouseClick(e); 478 // this because the contextMenu API only provides a URL, not the actual DOM
302 } 479 // element.
303 480 // We also need to make sure that the previous right click event,
304 function getBlockableElementOrAncestor(element, callback) 481 // if there is one, is removed. We don't know which frame it is in so we must
305 { 482 // send a message to the other frames to clear their old right click events.
306 // We assume that the user doesn't want to block the whole page. 483 document.addEventListener("contextmenu", event =>
307 // So we never consider the <html> or <body> element. 484 {
308 while (element && element != document.documentElement 485 lastRightClickEvent = event;
309 && element != document.body) 486 lastRightClickEventIsMostRecent = true;
310 { 487
311 // We can't handle non-HTML (like SVG) elements, as well as
312 // <area> elements (see below). So fall back to the parent element.
313 if (!(element instanceof HTMLElement) || element.localName == "area")
314 element = element.parentElement;
315
316 // If image maps are used mouse events occur for the <area> element.
317 // But we have to block the image associated with the <map> element.
318 else if (element.localName == "map")
319 {
320 var images = document.querySelectorAll("img[usemap]");
321 var image = null;
322
323 for (var i = 0; i < images.length; i++)
324 {
325 var usemap = images[i].getAttribute("usemap");
326 var index = usemap.indexOf("#");
327
328 if (index != -1 && usemap.substr(index + 1) == element.name)
329 {
330 image = images[i];
331 break;
332 }
333 }
334
335 element = image;
336 }
337
338 // Finally, if none of the above is true, check whether we can generate
339 // any filters for this element. Otherwise fall back to its parent element.
340 else
341 {
342 getFiltersForElement(element, function(filters)
343 {
344 if (filters.length > 0)
345 callback(element);
346 else
347 getBlockableElementOrAncestor(element.parentElement, callback);
348 });
349
350 return;
351 }
352 }
353
354 // We reached the document root without finding a blockable element.
355 callback(null);
356 }
357
358 // Hovering over an element so highlight it
359 function clickHide_mouseOver(e)
360 {
361 lastMouseOverEvent = e;
362
363 getBlockableElementOrAncestor(e.target, function(element)
364 {
365 if (e == lastMouseOverEvent)
366 {
367 lastMouseOverEvent = null;
368
369 if (clickHide_activated)
370 {
371 if (currentElement)
372 unhighlightElement(currentElement);
373
374 if (element)
375 {
376 highlightElement(element, "#d6d84b", "#f8fa47");
377 element.addEventListener("contextmenu", clickHide_elementClickHandler, true);
378 }
379
380 currentElement = element;
381 }
382 }
383 });
384
385 e.stopPropagation();
386 }
387
388 // No longer hovering over this element so unhighlight it
389 function clickHide_mouseOut(e)
390 {
391 if (!clickHide_activated || currentElement != e.target)
392 return;
393
394 unhighlightElement(currentElement);
395 currentElement.removeEventListener("contextmenu", clickHide_elementClickHandle r, true);
396 e.stopPropagation();
397 }
398
399 // Selects the currently hovered-over filter or cancels selection
400 function clickHide_keyDown(e)
401 {
402 if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode == 13 /*DOM_VK_RETURN* /)
403 clickHide_mouseClick(e);
404 else if (!e.ctrlKey && !e.altKey && !e.shiftKey && e.keyCode == 27 /*DOM_VK_ES CAPE*/)
405 {
406 ext.backgroundPage.sendMessage( 488 ext.backgroundPage.sendMessage(
407 { 489 {
408 type: "forward", 490 type: "forward",
409 payload: 491 payload:
410 { 492 {
411 type: "clickhide-deactivate" 493 type: "blockelement-clear-previous-right-click-event"
412 }
413 });
414 e.preventDefault();
415 e.stopPropagation();
416 }
417 }
418
419 function getFiltersForElement(element, callback)
420 {
421 ext.backgroundPage.sendMessage(
422 {
423 type: "compose-filters",
424 tagName: element.localName,
425 id: element.id,
426 src: element.getAttribute("src"),
427 style: element.getAttribute("style"),
428 classes: [].slice.call(element.classList),
429 urls: getURLsFromElement(element),
430 mediatype: typeMap[element.localName],
431 baseURL: document.location.href
432 },
433 function(response)
434 {
435 callback(response.filters, response.selectors);
436 }
437 );
438 }
439
440 // When the user clicks, the currentElement is the one we want.
441 // We should have ABP rules ready for when the
442 // popup asks for them.
443 function clickHide_mouseClick(e)
444 {
445 if (!currentElement || !clickHide_activated)
446 return;
447
448 var elt = currentElement;
449 if (currentElement.classList.contains("__adblockplus__overlay"))
450 elt = currentElement.prisoner;
451
452 getFiltersForElement(elt, function(filters, selectors)
453 {
454 ext.backgroundPage.sendMessage(
455 {
456 type: "forward",
457 payload:
458 {
459 type: "clickhide-show-dialog",
460 clickHideFilters: filters
461 }
462 });
463
464 if (selectors.length > 0)
465 highlightElements(selectors.join(","));
466
467 highlightElement(currentElement, "#fd1708", "#f6a1b5");
468 });
469
470 // Make sure the browser doesn't handle this click
471 e.preventDefault();
472 e.stopPropagation();
473 }
474
475 // This function Copyright (c) 2008 Jeni Tennison, from jquery.uri.js
476 // and licensed under the MIT license. See jquery-*.min.js for details.
477 function removeDotSegments(u) {
478 var r = '', m = [];
479 if (/\./.test(u)) {
480 while (u !== undefined && u !== '') {
481 if (u === '.' || u === '..') {
482 u = '';
483 } else if (/^\.\.\//.test(u)) { // starts with ../
484 u = u.substring(3);
485 } else if (/^\.\//.test(u)) { // starts with ./
486 u = u.substring(2);
487 } else if (/^\/\.(\/|$)/.test(u)) { // starts with /./ or consists of /.
488 u = '/' + u.substring(3);
489 } else if (/^\/\.\.(\/|$)/.test(u)) { // starts with /../ or consists of / ..
490 u = '/' + u.substring(4);
491 r = r.replace(/\/?[^\/]+$/, '');
492 } else {
493 m = u.match(/^(\/?[^\/]*)(\/.*)?$/);
494 u = m[2];
495 r = r + m[1];
496 }
497 }
498 return r;
499 } else {
500 return u;
501 }
502 }
503
504 // In Chrome 37-40, the document_end content script (this one) runs properly, wh ile the
505 // document_start content scripts (that defines ext) might not. Check whether va riable ext
506 // exists before continuing to avoid "Uncaught ReferenceError: ext is not define d".
507 // See https://crbug.com/416907
508 if ("ext" in window && document instanceof HTMLDocument)
509 {
510 // Use a contextmenu handler to save the last element the user right-clicked o n.
511 // To make things easier, we actually save the DOM event.
512 // We have to do this because the contextMenu API only provides a URL, not the actual
513 // DOM element.
514 document.addEventListener('contextmenu', function(e)
515 {
516 lastRightClickEvent = e;
517 // We also need to ensure any old lastRightClickEvent variables in other
518 // frames are cleared.
519 lastRightClickEventValid = true;
520 ext.backgroundPage.sendMessage(
521 {
522 type: "forward",
523 payload:
524 {
525 type: "clickhide-clear-last-right-click-event"
526 } 494 }
527 }); 495 });
528 }, true); 496 }, true);
529 497
530 document.addEventListener("click", function(event) 498 ext.onMessage.addListener((msg, sender, sendResponse) =>
531 {
532 // Ignore right-clicks
533 if (event.button == 2)
534 return;
535
536 // Search the link associated with the click
537 var link = event.target;
538 while (!(link instanceof HTMLAnchorElement))
539 {
540 link = link.parentNode;
541
542 if (!link)
543 return;
544 }
545
546 if (link.protocol == "http:" || link.protocol == "https:")
547 {
548 if (link.host != "subscribe.adblockplus.org" || link.pathname != "/")
549 return;
550 }
551 else if (!/^abp:\/*subscribe\/*\?/i.test(link.href))
552 return;
553
554 // This is our link - make sure the browser doesn't handle it
555 event.preventDefault();
556 event.stopPropagation();
557
558 // Decode URL parameters
559 var params = link.search.substr(1).split("&");
560 var title = null;
561 var url = null;
562 for (var i = 0; i < params.length; i++)
563 {
564 var parts = params[i].split("=", 2);
565 if (parts.length != 2 || !/\S/.test(parts[1]))
566 continue;
567 switch (parts[0])
568 {
569 case "title":
570 title = decodeURIComponent(parts[1]);
571 break;
572 case "location":
573 url = decodeURIComponent(parts[1]);
574 break;
575 }
576 }
577 if (!url)
578 return;
579
580 // Default title to the URL
581 if (!title)
582 title = url;
583
584 // Trim spaces in title and URL
585 title = title.trim();
586 url = url.trim();
587 if (!/^(https?|ftp):/.test(url))
588 return;
589
590 ext.backgroundPage.sendMessage({
591 type: "add-subscription",
592 title: title,
593 url: url
594 });
595 }, true);
596
597 ext.onMessage.addListener(function(msg, sender, sendResponse)
598 { 499 {
599 switch (msg.type) 500 switch (msg.type)
600 { 501 {
601 case "get-clickhide-state": 502 case "blockelement-get-state":
602 sendResponse({active: clickHide_activated}); 503 if (window == window.top)
504 sendResponse({
505 active: currentlyPickingElement || blockelementPopupId != null
506 });
603 break; 507 break;
604 case "clickhide-activate": 508 case "blockelement-start-picking-element":
605 clickHide_activate(); 509 if (window == window.top)
510 startPickingElement();
606 break; 511 break;
607 case "clickhide-deactivate": 512 case "blockelement-context-menu-clicked":
608 clickHide_deactivate(); 513 let event = lastRightClickEvent;
609 break; 514 deactivateBlockElement();
610 case "clickhide-new-filter": 515 if (event)
611 if(lastRightClickEvent)
612 { 516 {
613 var event = lastRightClickEvent; 517 getBlockableElementOrAncestor(event.target, element =>
614 getBlockableElementOrAncestor(event.target, function(element)
615 { 518 {
616 clickHide_activate(); 519 if (element)
617 currentElement = element; 520 {
618 clickHide_mouseClick(event); 521 currentElement = element;
522 elementPicked(event);
523 }
619 }); 524 });
620 } 525 }
621 break; 526 break;
622 case "clickhide-init": 527 case "blockelement-finished":
623 if (clickHideFiltersDialog)
624 {
625 sendResponse({filters: clickHide_filters});
626
627 clickHideFiltersDialog.style.width = msg.width + "px";
628 clickHideFiltersDialog.style.height = msg.height + "px";
629 clickHideFiltersDialog.style.visibility = "visible";
630 }
631 break;
632 case "clickhide-move":
633 if (clickHideFiltersDialog)
634 {
635 var rect = clickHideFiltersDialog.getBoundingClientRect();
636 var x = Math.max(0, Math.min(rect.left + msg.x, window.innerWidth - re ct.width));
637 var y = Math.max(0, Math.min(rect.top + msg.y, window.innerHeight - re ct.height));
638
639 clickHideFiltersDialog.style.left = x + "px";
640 clickHideFiltersDialog.style.top = y + "px";
641 }
642 break;
643 case "clickhide-close":
644 if (currentElement && msg.remove) 528 if (currentElement && msg.remove)
645 { 529 {
646 // Hide the selected element itself if an added blocking 530 // Hide the selected element itself if an added blocking
647 // filter is causing it to collapse. Note that this 531 // filter is causing it to collapse. Note that this
648 // behavior is incomplete, but the best we can do here, 532 // behavior is incomplete, but the best we can do here,
649 // e.g. if an added blocking filter matches other elements, 533 // e.g. if an added blocking filter matches other elements,
650 // the effect won't be visible until the page is is reloaded. 534 // the effect won't be visible until the page is is reloaded.
651 checkCollapse(currentElement.prisoner || currentElement); 535 checkCollapse(currentElement.prisoner || currentElement);
652 536
653 // Apply added element hiding filters. 537 // Apply added element hiding filters.
654 updateStylesheet(); 538 updateStylesheet();
655 } 539 }
656 clickHide_deactivate(); 540 deactivateBlockElement();
657 break; 541 break;
658 case "clickhide-show-dialog": 542 case "blockelement-clear-previous-right-click-event":
659 clickHide_rulesPending(); 543 if (!lastRightClickEventIsMostRecent)
660 if (window.self == window.top) 544 lastRightClickEvent = null;
661 clickHide_showDialog(msg.clickHideFilters); 545 lastRightClickEventIsMostRecent = false;
662 break; 546 break;
663 case "clickhide-clear-last-right-click-event": 547 case "blockelement-popup-closed":
664 if (lastRightClickEventValid) 548 // The onRemoved hook for the popup can create a race condition, so we
665 lastRightClickEventValid = false; 549 // to be careful here. (This is not perfect, but best we can do.)
666 else 550 if (window == window.top && blockelementPopupId == msg.popupId)
667 lastRightClickEvent = null; 551 {
552 ext.backgroundPage.sendMessage(
553 {
554 type: "forward",
555 payload:
556 {
557 type: "blockelement-finished"
558 }
559 });
560 }
668 break; 561 break;
669 } 562 }
670 }); 563 });
671 564
672 if (window == window.top) 565 if (window == window.top)
673 ext.backgroundPage.sendMessage({type: "report-html-page"}); 566 ext.backgroundPage.sendMessage({type: "report-html-page"});
674 } 567 }
LEFTRIGHT

Powered by Google App Engine
This is Rietveld