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

Side by Side Diff: lib/content/snippets.js

Issue 29833630: Issue 6798 - Implement hide-if-shadow-contains snippet (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore/
Patch Set: Remove premature hardening Created July 19, 2018, 4:03 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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-present eyeo GmbH 3 * Copyright (C) 2006-present 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
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
90 * 90 *
91 * @returns {function} The generated injector. 91 * @returns {function} The generated injector.
92 */ 92 */
93 function makeInjector(injectable, ...dependencies) 93 function makeInjector(injectable, ...dependencies)
94 { 94 {
95 return (...args) => injectCode(stringifyFunctionCall(injectable, ...args), 95 return (...args) => injectCode(stringifyFunctionCall(injectable, ...args),
96 dependencies); 96 dependencies);
97 } 97 }
98 98
99 /** 99 /**
100 * Hides an HTML element by settings its <code>style</code> attribute to
101 * <code>display: none !important</code>.
102 *
103 * @param {HTMLElement} element The HTML element to hide.
104 */
105 function hideElement(element)
106 {
107 element.style.setProperty("display", "none", "important");
108
109 // Listen for changes to the style property and if our values are unset
110 // then reset them.
111 new MutationObserver(() =>
112 {
113 if (element.style.getPropertyValue("display") != "none" ||
114 element.style.getPropertyPriority("display") != "important")
115 {
116 element.style.setProperty("display", "none", "important");
117 }
118 })
119 .observe(element, {attributes: true, attributeFilter: ["style"]});
120 }
121
122 /**
100 * Logs its arguments to the console. This may be used for testing and 123 * Logs its arguments to the console. This may be used for testing and
101 * debugging. 124 * debugging.
102 * 125 *
103 * @param {...*} [args] The arguments to log. 126 * @param {...*} [args] The arguments to log.
104 */ 127 */
105 function log(...args) 128 function log(...args)
106 { 129 {
107 console.log(...args); 130 console.log(...args);
108 } 131 }
109 132
110 exports.log = log; 133 exports.log = log;
111 134
112 /** 135 /**
113 * Similar to {@link log}, but does the logging in the context of the document 136 * Similar to {@link log}, but does the logging in the context of the document
114 * rather than the content script. This may be used for testing and debugging, 137 * rather than the content script. This may be used for testing and debugging,
115 * especially to verify that the injection of snippets into the document is 138 * especially to verify that the injection of snippets into the document is
116 * working without any errors. 139 * working without any errors.
117 * 140 *
118 * @param {...*} [args] The arguments to log. 141 * @param {...*} [args] The arguments to log.
119 */ 142 */
120 function trace(...args) 143 function trace(...args)
121 { 144 {
122 // We could simply use console.log here, but the goal is to demonstrate the 145 // We could simply use console.log here, but the goal is to demonstrate the
123 // usage of snippet dependencies. 146 // usage of snippet dependencies.
124 log(...args); 147 log(...args);
125 } 148 }
126 149
127 exports.trace = makeInjector(trace, log); 150 exports.trace = makeInjector(trace, log);
151
152 /**
153 * Hides any HTML element or one of its ancestors matching a CSS selector if
154 * the text content of the element's shadow contains a given string.
155 *
156 * @param {string} search The string to look for in every HTML element's
157 * shadow.
158 * @param {string} selector The CSS selector that an HTML element must match
159 * for it to be hidden.
160 */
161 function hideIfShadowContains(search, selector = "*")
162 {
163 let originalAttachShadow = Element.prototype.attachShadow;
kzar 2018/07/19 16:11:41 With my suggestions below we can remove this line
164
165 // If there's no Element.attachShadow API present then we don't care, it must
166 // be Firefox or an older version of Chrome.
167 if (!originalAttachShadow)
kzar 2018/07/19 16:11:41 `if (!Element.prototype.attachShadow)`
168 return;
169
170 // Mutation observers mapped to their corresponding shadow roots and their
171 // hosts.
172 let shadows = new WeakMap();
173
174 function observe(mutations, observer)
175 {
176 let {host, root} = shadows.get(observer) || {};
177
178 // Since it's a weak map, it's possible that either the element or its
179 // shadow has been removed.
180 if (!host || !root)
181 return;
182
183 // If the shadow contains the given text, check if the host or one of its
184 // ancestors matches the selector; if a matching element is found, hide
185 // it.
186 if (root.textContent.includes(search))
187 {
188 let element = host;
189
190 do
191 {
192 if (element.matches(selector))
193 {
194 hideElement(element);
195 break;
196 }
197 }
198 while (element = element.parentElement);
199 }
200 }
201
202 Object.defineProperty(Element.prototype, "attachShadow", {
203 value(...args)
204 {
205 // Create the shadow root first. It doesn't matter if it's a closed
206 // shadow root, we keep the reference in a weak map.
207 let root = originalAttachShadow.apply(this, args);
kzar 2018/07/19 16:11:41 `let root = this.attachShadow(...args);`
Manish Jethani 2018/07/19 16:15:10 Isn't this going to be an infinite loop? this.atta
kzar 2018/07/19 16:28:51 Oh yea, good point we're overwriting it ourselves.
208
209 // Listen for relevant DOM mutations in the shadow.
210 let observer = new MutationObserver(observe);
211 observer.observe(root, {
212 childList: true,
213 characterData: true,
214 subtree: true
215 });
216
217 // Keep references to the shadow root and its host in a weak map. If
218 // either the shadow is detached or the host itself is removed from the
219 // DOM, the mutation observer too will be freed eventually and the entry
220 // will be removed.
221 shadows.set(observer, {host: this, root});
222
223 return root;
224 }
225 });
226 }
227
228 exports["hide-if-shadow-contains"] = makeInjector(hideIfShadowContains,
229 hideElement);
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld