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: Created July 19, 2018, 2:29 a.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(() =>
kzar 2018/07/19 13:02:09 I wonder if we should just remove the element, ins
Manish Jethani 2018/07/19 13:30:09 I think it's probably best to mess as little as re
kzar 2018/07/19 13:38:11 Acknowledged.
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;
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)
168 return;
169
170 let applyOriginalAttachShadow =
171 originalAttachShadow.apply.bind(originalAttachShadow);
kzar 2018/07/19 13:02:09 This looks like an attempt at hardening the code t
Manish Jethani 2018/07/19 13:30:09 Yeah, I'm not really sure why this is needed. Even
kzar 2018/07/19 13:38:11 This reply kind of implies that you didn't write t
Manish Jethani 2018/07/19 14:20:54 No, it does not apply anything like that. I did wr
kzar 2018/07/19 15:02:52 Gotya, if the idea/code came from our inject.prelo
Manish Jethani 2018/07/19 15:28:34 OK, but why can't you just do this: originalFun
kzar 2018/07/19 15:33:33 In case the website messes with `Function.prototyp
Manish Jethani 2018/07/19 15:39:57 I see, in that case I'd like to keep it. Reason: o
kzar 2018/07/19 15:52:40 Well, in that case, what about all the other APIs
Manish Jethani 2018/07/19 16:04:58 Fair enough, let's keep it simple for now and let'
172
173 // Mutation observers mapped to their corresponding shadow roots and their
174 // hosts.
175 let shadows = new WeakMap();
176
177 function observe(mutations, observer)
178 {
179 let {host, root} = shadows.get(observer) || {};
180
181 // Since it's a weak map, it's possible that either the element or its
182 // shadow has been removed.
183 if (!host || !root)
kzar 2018/07/19 13:02:09 I thought WeakMap just avoided memory leaks if the
Manish Jethani 2018/07/19 13:30:09 This is just a hypothesis, but I think that if eit
184 return;
185
186 // If the shadow contains the given text, check if the host or one of its
187 // ancestors matches the selector; if a matching element is found, hide
188 // it.
189 if (root.textContent.includes(search))
190 {
191 let element = host;
192
193 do
194 {
195 if (element.matches(selector))
196 {
197 hideElement(element);
198 break;
199 }
200 }
201 while (element = element.parentElement);
202 }
203 }
204
205 Object.defineProperty(Element.prototype, "attachShadow", {
206 value(...args)
207 {
208 // Create the shadow root first. It doesn't matter if it's a closed
209 // shadow root, we keep the reference in a weak map.
210 let root = applyOriginalAttachShadow(this, args);
211
212 // Listen for relevant DOM mutations in the shadow.
213 let observer = new MutationObserver(observe);
214 observer.observe(root, {
215 childList: true,
216 characterData: true,
217 subtree: true
218 });
219
220 // Keep references to the shadow root and its host in a weak map. If
221 // either the shadow is detached or the host itself is removed from the
222 // DOM, the mutation observer too will be freed eventually and the entry
223 // will be removed.
224 shadows.set(observer, {host: this, root});
225
226 return root;
227 }
228 });
229 }
230
231 exports["hide-if-shadow-contains"] = makeInjector(hideIfShadowContains,
232 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