Index: lib/content/snippets.js |
=================================================================== |
--- a/lib/content/snippets.js |
+++ b/lib/content/snippets.js |
@@ -92,16 +92,39 @@ |
*/ |
function makeInjector(injectable, ...dependencies) |
{ |
return (...args) => injectCode(stringifyFunctionCall(injectable, ...args), |
dependencies); |
} |
/** |
+ * Hides an HTML element by settings its <code>style</code> attribute to |
+ * <code>display: none !important</code>. |
+ * |
+ * @param {HTMLElement} element The HTML element to hide. |
+ */ |
+function hideElement(element) |
+{ |
+ element.style.setProperty("display", "none", "important"); |
+ |
+ // Listen for changes to the style property and if our values are unset |
+ // then reset them. |
+ 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.
|
+ { |
+ if (element.style.getPropertyValue("display") != "none" || |
+ element.style.getPropertyPriority("display") != "important") |
+ { |
+ element.style.setProperty("display", "none", "important"); |
+ } |
+ }) |
+ .observe(element, {attributes: true, attributeFilter: ["style"]}); |
+} |
+ |
+/** |
* Logs its arguments to the console. This may be used for testing and |
* debugging. |
* |
* @param {...*} [args] The arguments to log. |
*/ |
function log(...args) |
{ |
console.log(...args); |
@@ -120,8 +143,90 @@ |
function trace(...args) |
{ |
// We could simply use console.log here, but the goal is to demonstrate the |
// usage of snippet dependencies. |
log(...args); |
} |
exports.trace = makeInjector(trace, log); |
+ |
+/** |
+ * Hides any HTML element or one of its ancestors matching a CSS selector if |
+ * the text content of the element's shadow contains a given string. |
+ * |
+ * @param {string} search The string to look for in every HTML element's |
+ * shadow. |
+ * @param {string} selector The CSS selector that an HTML element must match |
+ * for it to be hidden. |
+ */ |
+function hideIfShadowContains(search, selector = "*") |
+{ |
+ let originalAttachShadow = Element.prototype.attachShadow; |
+ |
+ // If there's no Element.attachShadow API present then we don't care, it must |
+ // be Firefox or an older version of Chrome. |
+ if (!originalAttachShadow) |
+ return; |
+ |
+ let applyOriginalAttachShadow = |
+ 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'
|
+ |
+ // Mutation observers mapped to their corresponding shadow roots and their |
+ // hosts. |
+ let shadows = new WeakMap(); |
+ |
+ function observe(mutations, observer) |
+ { |
+ let {host, root} = shadows.get(observer) || {}; |
+ |
+ // Since it's a weak map, it's possible that either the element or its |
+ // shadow has been removed. |
+ 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
|
+ return; |
+ |
+ // If the shadow contains the given text, check if the host or one of its |
+ // ancestors matches the selector; if a matching element is found, hide |
+ // it. |
+ if (root.textContent.includes(search)) |
+ { |
+ let element = host; |
+ |
+ do |
+ { |
+ if (element.matches(selector)) |
+ { |
+ hideElement(element); |
+ break; |
+ } |
+ } |
+ while (element = element.parentElement); |
+ } |
+ } |
+ |
+ Object.defineProperty(Element.prototype, "attachShadow", { |
+ value(...args) |
+ { |
+ // Create the shadow root first. It doesn't matter if it's a closed |
+ // shadow root, we keep the reference in a weak map. |
+ let root = applyOriginalAttachShadow(this, args); |
+ |
+ // Listen for relevant DOM mutations in the shadow. |
+ let observer = new MutationObserver(observe); |
+ observer.observe(root, { |
+ childList: true, |
+ characterData: true, |
+ subtree: true |
+ }); |
+ |
+ // Keep references to the shadow root and its host in a weak map. If |
+ // either the shadow is detached or the host itself is removed from the |
+ // DOM, the mutation observer too will be freed eventually and the entry |
+ // will be removed. |
+ shadows.set(observer, {host: this, root}); |
+ |
+ return root; |
+ } |
+ }); |
+} |
+ |
+exports["hide-if-shadow-contains"] = makeInjector(hideIfShadowContains, |
+ hideElement); |