OLD | NEW |
| (Empty) |
1 /* | |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | |
3 * Copyright (C) 2006-2016 Eyeo GmbH | |
4 * | |
5 * Adblock Plus is free software: you can redistribute it and/or modify | |
6 * it under the terms of the GNU General Public License version 3 as | |
7 * published by the Free Software Foundation. | |
8 * | |
9 * Adblock Plus is distributed in the hope that it will be useful, | |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 * GNU General Public License for more details. | |
13 * | |
14 * You should have received a copy of the GNU General Public License | |
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | |
16 */ | |
17 | |
18 // Click-to-hide stuff | |
19 var clickHide_activated = false; | |
20 var clickHide_filters = null; | |
21 var currentElement = null; | |
22 var highlightedElementsSelector = null; | |
23 var highlightedElementsInterval = null; | |
24 var clickHideFiltersDialog = null; | |
25 var lastRightClickEvent = null; | |
26 var lastRightClickEventValid = false; | |
27 var lastMouseOverEvent = null; | |
28 | |
29 function highlightElement(element, shadowColor, backgroundColor) | |
30 { | |
31 unhighlightElement(element); | |
32 | |
33 var highlightWithOverlay = function() | |
34 { | |
35 var overlay = addElementOverlay(element); | |
36 | |
37 // If the element isn't displayed no overlay will be added. | |
38 // Moreover, we don't need to highlight anything then. | |
39 if (!overlay) | |
40 return; | |
41 | |
42 highlightElement(overlay, shadowColor, backgroundColor); | |
43 overlay.style.pointerEvents = "none"; | |
44 | |
45 element._unhighlight = function() | |
46 { | |
47 overlay.parentNode.removeChild(overlay); | |
48 }; | |
49 }; | |
50 | |
51 var highlightWithStyleAttribute = function() | |
52 { | |
53 var originalBoxShadow = element.style.getPropertyValue("box-shadow"); | |
54 var originalBoxShadowPriority = element.style.getPropertyPriority("box-shado
w"); | |
55 var originalBackgroundColor = element.style.getPropertyValue("background-col
or"); | |
56 var originalBackgroundColorPriority = element.style.getPropertyPriority("bac
kground-color"); | |
57 | |
58 element.style.setProperty("box-shadow", "inset 0px 0px 5px " + shadowColor,
"important"); | |
59 element.style.setProperty("background-color", backgroundColor, "important"); | |
60 | |
61 element._unhighlight = function() | |
62 { | |
63 this.style.removeProperty("box-shadow"); | |
64 this.style.setProperty( | |
65 "box-shadow", | |
66 originalBoxShadow, | |
67 originalBoxShadowPriority | |
68 ); | |
69 | |
70 this.style.removeProperty("background-color"); | |
71 this.style.setProperty( | |
72 "background-color", | |
73 originalBackgroundColor, | |
74 originalBackgroundColorPriority | |
75 ); | |
76 }; | |
77 }; | |
78 | |
79 if ("prisoner" in element) | |
80 highlightWithStyleAttribute(); | |
81 else | |
82 highlightWithOverlay(); | |
83 } | |
84 | |
85 | |
86 function unhighlightElement(element) | |
87 { | |
88 if ("_unhighlight" in element) | |
89 { | |
90 element._unhighlight(); | |
91 delete element._unhighlight; | |
92 } | |
93 } | |
94 | |
95 // Highlight elements according to selector string. This would include | |
96 // all elements that would be affected by proposed filters. | |
97 function highlightElements(selectorString) { | |
98 unhighlightElements(); | |
99 | |
100 var elements = Array.prototype.slice.call(document.querySelectorAll(selectorSt
ring)); | |
101 highlightedElementsSelector = selectorString; | |
102 | |
103 // Highlight elements progressively. Otherwise the page freezes | |
104 // when a lot of elements get highlighted at the same time. | |
105 highlightedElementsInterval = setInterval(function() | |
106 { | |
107 if (elements.length > 0) | |
108 { | |
109 var element = elements.shift(); | |
110 if (element != currentElement) | |
111 highlightElement(element, "#fd6738", "#f6e1e5"); | |
112 } | |
113 else | |
114 { | |
115 clearInterval(highlightedElementsInterval); | |
116 highlightedElementsInterval = null; | |
117 } | |
118 }, 0); | |
119 } | |
120 | |
121 // Unhighlight all elements, including those that would be affected by | |
122 // the proposed filters | |
123 function unhighlightElements() { | |
124 if (highlightedElementsInterval) | |
125 { | |
126 clearInterval(highlightedElementsInterval) | |
127 highlightedElementsInterval = null; | |
128 } | |
129 | |
130 if (highlightedElementsSelector) | |
131 { | |
132 Array.prototype.forEach.call( | |
133 document.querySelectorAll(highlightedElementsSelector), | |
134 unhighlightElement | |
135 ); | |
136 | |
137 highlightedElementsSelector = null; | |
138 } | |
139 } | |
140 | |
141 // Adds an overlay to an element, which is probably a Flash object | |
142 function addElementOverlay(elt) { | |
143 var position = "absolute"; | |
144 var offsetX = window.scrollX; | |
145 var offsetY = window.scrollY; | |
146 | |
147 for (var e = elt; e; e = e.parentElement) | |
148 { | |
149 var style = getComputedStyle(e); | |
150 | |
151 // If the element isn't rendered (since its or one of its ancestor's | |
152 // "display" property is "none"), the overlay wouldn't match the element. | |
153 if (style.display == "none") | |
154 return null; | |
155 | |
156 // If the element or one of its ancestors uses fixed postioning, the overlay | |
157 // has to use fixed postioning too. Otherwise it might not match the element
. | |
158 if (style.position == "fixed") | |
159 { | |
160 position = "fixed"; | |
161 offsetX = offsetY = 0; | |
162 } | |
163 } | |
164 | |
165 var overlay = document.createElement('div'); | |
166 overlay.prisoner = elt; | |
167 overlay.className = "__adblockplus__overlay"; | |
168 overlay.setAttribute('style', 'opacity:0.4; display:inline-box; overflow:hidde
n; box-sizing:border-box;'); | |
169 var rect = elt.getBoundingClientRect(); | |
170 overlay.style.width = rect.width + "px"; | |
171 overlay.style.height = rect.height + "px"; | |
172 overlay.style.left = (rect.left + offsetX) + "px"; | |
173 overlay.style.top = (rect.top + offsetY) + "px"; | |
174 overlay.style.position = position; | |
175 overlay.style.zIndex = 0x7FFFFFFE; | |
176 | |
177 // elt.parentNode.appendChild(overlay, elt); | |
178 document.documentElement.appendChild(overlay); | |
179 return overlay; | |
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; | |
218 | |
219 // If we are already selecting, abort now | |
220 if (clickHide_activated || clickHideFiltersDialog) | |
221 clickHide_deactivate(); | |
222 | |
223 // Add overlays for blockable elements that don't emit mouse events, | |
224 // so that they can still be selected. | |
225 [].forEach.call( | |
226 document.querySelectorAll('object,embed,iframe,frame'), | |
227 function(element) | |
228 { | |
229 getFiltersForElement(element, function(filters) | |
230 { | |
231 if (filters.length > 0) | |
232 addElementOverlay(element); | |
233 }); | |
234 } | |
235 ); | |
236 | |
237 clickHide_activated = true; | |
238 document.addEventListener("mousedown", clickHide_stopPropagation, true); | |
239 document.addEventListener("mouseup", clickHide_stopPropagation, true); | |
240 document.addEventListener("mouseenter", clickHide_stopPropagation, true); | |
241 document.addEventListener("mouseleave", clickHide_stopPropagation, true); | |
242 document.addEventListener("mouseover", clickHide_mouseOver, true); | |
243 document.addEventListener("mouseout", clickHide_mouseOut, true); | |
244 document.addEventListener("click", clickHide_mouseClick, true); | |
245 document.addEventListener("keydown", clickHide_keyDown, true); | |
246 | |
247 ext.onExtensionUnloaded.addListener(clickHide_deactivate); | |
248 } | |
249 | |
250 // Called when user has clicked on something and we are waiting for confirmation | |
251 // on whether the user actually wants these filters | |
252 function clickHide_rulesPending() { | |
253 clickHide_activated = false; | |
254 | |
255 if (clickHideFiltersDialog) | |
256 { | |
257 document.documentElement.removeChild(clickHideFiltersDialog); | |
258 clickHideFiltersDialog = null; | |
259 } | |
260 | |
261 document.removeEventListener("mousedown", clickHide_stopPropagation, true); | |
262 document.removeEventListener("mouseup", clickHide_stopPropagation, true); | |
263 document.removeEventListener("mouseenter", clickHide_stopPropagation, true); | |
264 document.removeEventListener("mouseleave", clickHide_stopPropagation, true); | |
265 document.removeEventListener("mouseover", clickHide_mouseOver, true); | |
266 document.removeEventListener("mouseout", clickHide_mouseOut, true); | |
267 document.removeEventListener("click", clickHide_mouseClick, true); | |
268 document.removeEventListener("keydown", clickHide_keyDown, true); | |
269 } | |
270 | |
271 function clickHide_deactivate() | |
272 { | |
273 clickHide_rulesPending(); | |
274 | |
275 clickHide_filters = null; | |
276 lastRightClickEvent = null; | |
277 | |
278 if (currentElement) | |
279 { | |
280 currentElement.removeEventListener("contextmenu", clickHide_elementClickHan
dler, true); | |
281 unhighlightElement(currentElement); | |
282 currentElement = null; | |
283 } | |
284 unhighlightElements(); | |
285 | |
286 var overlays = document.getElementsByClassName("__adblockplus__overlay"); | |
287 while (overlays.length > 0) | |
288 overlays[0].parentNode.removeChild(overlays[0]); | |
289 | |
290 ext.onExtensionUnloaded.removeListener(clickHide_deactivate); | |
291 } | |
292 | |
293 function clickHide_stopPropagation(e) | |
294 { | |
295 e.stopPropagation(); | |
296 } | |
297 | |
298 function clickHide_elementClickHandler(e) { | |
299 e.preventDefault(); | |
300 e.stopPropagation(); | |
301 clickHide_mouseClick(e); | |
302 } | |
303 | |
304 function getBlockableElementOrAncestor(element, callback) | |
305 { | |
306 // We assume that the user doesn't want to block the whole page. | |
307 // So we never consider the <html> or <body> element. | |
308 while (element && element != document.documentElement | |
309 && element != document.body) | |
310 { | |
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( | |
407 { | |
408 type: "forward", | |
409 payload: | |
410 { | |
411 type: "clickhide-deactivate" | |
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 } | |
527 }); | |
528 }, true); | |
529 | |
530 document.addEventListener("click", function(event) | |
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 { | |
599 switch (msg.type) | |
600 { | |
601 case "get-clickhide-state": | |
602 sendResponse({active: clickHide_activated}); | |
603 break; | |
604 case "clickhide-activate": | |
605 clickHide_activate(); | |
606 break; | |
607 case "clickhide-deactivate": | |
608 clickHide_deactivate(); | |
609 break; | |
610 case "clickhide-new-filter": | |
611 if(lastRightClickEvent) | |
612 { | |
613 var event = lastRightClickEvent; | |
614 getBlockableElementOrAncestor(event.target, function(element) | |
615 { | |
616 clickHide_activate(); | |
617 currentElement = element; | |
618 clickHide_mouseClick(event); | |
619 }); | |
620 } | |
621 break; | |
622 case "clickhide-init": | |
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) | |
645 { | |
646 // Hide the selected element itself if an added blocking | |
647 // filter is causing it to collapse. Note that this | |
648 // behavior is incomplete, but the best we can do here, | |
649 // e.g. if an added blocking filter matches other elements, | |
650 // the effect won't be visible until the page is is reloaded. | |
651 checkCollapse(currentElement.prisoner || currentElement); | |
652 | |
653 // Apply added element hiding filters. | |
654 updateStylesheet(); | |
655 } | |
656 clickHide_deactivate(); | |
657 break; | |
658 case "clickhide-show-dialog": | |
659 clickHide_rulesPending(); | |
660 if (window.self == window.top) | |
661 clickHide_showDialog(msg.clickHideFilters); | |
662 break; | |
663 case "clickhide-clear-last-right-click-event": | |
664 if (lastRightClickEventValid) | |
665 lastRightClickEventValid = false; | |
666 else | |
667 lastRightClickEvent = null; | |
668 break; | |
669 } | |
670 }); | |
671 | |
672 if (window == window.top) | |
673 ext.backgroundPage.sendMessage({type: "report-html-page"}); | |
674 } | |
OLD | NEW |