summaryrefslogtreecommitdiffstats
path: root/src/js/firstparties
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/firstparties')
-rw-r--r--src/js/firstparties/facebook.js56
-rw-r--r--src/js/firstparties/google-search.js39
-rw-r--r--src/js/firstparties/google-static.js41
-rw-r--r--src/js/firstparties/lib/utils.js62
4 files changed, 198 insertions, 0 deletions
diff --git a/src/js/firstparties/facebook.js b/src/js/firstparties/facebook.js
new file mode 100644
index 0000000..2c5d299
--- /dev/null
+++ b/src/js/firstparties/facebook.js
@@ -0,0 +1,56 @@
+/* globals findInAllFrames:false, observeMutations:false */
+// Adapted from https://github.com/mgziminsky/FacebookTrackingRemoval
+// this should only run on facebook.com, messenger.com, and
+// facebookcorewwwi.onion
+let fb_wrapped_link = `a[href*='${document.domain.split(".").slice(-2).join(".")}/l.php?']:not([aria-label])`;
+
+// remove all attributes from a link except for class and ARIA attributes
+function cleanAttrs(elem) {
+ for (let i = elem.attributes.length - 1; i >= 0; --i) {
+ const attr = elem.attributes[i];
+ if (attr.name !== 'class' && !attr.name.startsWith('aria-')) {
+ elem.removeAttribute(attr.name);
+ }
+ }
+}
+
+// Remove excessive attributes and event listeners from link a and replace
+// its destination with href
+function cleanLink(a) {
+ let href = new URL(a.href).searchParams.get('u');
+
+ // If we can't extract a good URL, abort without breaking the links
+ if (!window.isURL(href)) {
+ return;
+ }
+
+ let href_url = new URL(href);
+ href_url.searchParams.delete('fbclid');
+ href = href_url.toString();
+
+ cleanAttrs(a);
+ a.href = href;
+ a.rel = "noreferrer";
+ a.target = "_blank";
+ a.addEventListener("click", function (e) { e.stopImmediatePropagation(); }, true);
+ a.addEventListener("mousedown", function (e) { e.stopImmediatePropagation(); }, true);
+ a.addEventListener("mouseup", function (e) { e.stopImmediatePropagation(); }, true);
+ a.addEventListener("mouseover", function (e) { e.stopImmediatePropagation(); }, true);
+}
+
+// TODO race condition; fix waiting on https://crbug.com/478183
+chrome.runtime.sendMessage({
+ type: "checkEnabled"
+}, function (enabled) {
+ if (!enabled) {
+ return;
+ }
+
+ // unwrap wrapped links in the original page
+ findInAllFrames(fb_wrapped_link).forEach((link) => {
+ cleanLink(link);
+ });
+
+ // Execute redirect unwrapping each time new content is added to the page
+ observeMutations(fb_wrapped_link, cleanLink);
+});
diff --git a/src/js/firstparties/google-search.js b/src/js/firstparties/google-search.js
new file mode 100644
index 0000000..d5eea9a
--- /dev/null
+++ b/src/js/firstparties/google-search.js
@@ -0,0 +1,39 @@
+/* globals findInAllFrames:false */
+// In Firefox, outbound google links have the `rwt(...)` mousedown trigger.
+// In Chrome, they just have a `ping` attribute.
+let trap_link = "a[onmousedown^='return rwt(this,'], a[ping]";
+
+// Remove excessive attributes and event listeners from link a
+function cleanLink(a) {
+ // remove all attributes except for href,
+ // target (to support "Open each selected result in a new browser window"),
+ // class, style and ARIA attributes
+ for (let i = a.attributes.length - 1; i >= 0; --i) {
+ const attr = a.attributes[i];
+ if (attr.name !== 'href' && attr.name !== 'target' &&
+ attr.name !== 'class' && attr.name !== 'style' &&
+ !attr.name.startsWith('aria-')) {
+ a.removeAttribute(attr.name);
+ }
+ }
+ a.rel = "noreferrer noopener";
+
+ // block event listeners on the link
+ a.addEventListener("click", function (e) { e.stopImmediatePropagation(); }, true);
+ a.addEventListener("mousedown", function (e) { e.stopImmediatePropagation(); }, true);
+}
+
+// TODO race condition; fix waiting on https://crbug.com/478183
+chrome.runtime.sendMessage({
+ type: "checkEnabled"
+}, function (enabled) {
+ if (!enabled) {
+ return;
+ }
+
+ // since the page is rendered all at once, no need to set up a
+ // mutationObserver or setInterval
+ findInAllFrames(trap_link).forEach((link) => {
+ cleanLink(link);
+ });
+});
diff --git a/src/js/firstparties/google-static.js b/src/js/firstparties/google-static.js
new file mode 100644
index 0000000..cf73004
--- /dev/null
+++ b/src/js/firstparties/google-static.js
@@ -0,0 +1,41 @@
+let g_wrapped_link = "a[href^='https://www.google.com/url?']";
+
+// Unwrap a Hangouts tracking link
+function unwrapLink(a) {
+ let href = new URL(a.href).searchParams.get('q');
+ if (!window.isURL(href)) {
+ return;
+ }
+
+ // remove all attributes except for target, class, style and aria-*
+ // attributes. This should prevent the script from breaking styles and
+ // features for people with disabilities.
+ for (let i = a.attributes.length - 1; i >= 0; --i) {
+ const attr = a.attributes[i];
+ if (attr.name !== 'target' && attr.name !== 'class' &&
+ attr.name !== 'style' && !attr.name.startsWith('aria-')) {
+ a.removeAttribute(attr.name);
+ }
+ }
+
+ a.rel = "noreferrer";
+ a.href = href;
+}
+
+// Scan the page for all wrapped links
+function unwrapAll() {
+ document.querySelectorAll(g_wrapped_link).forEach((a) => {
+ unwrapLink(a);
+ });
+}
+
+// TODO race condition; fix waiting on https://crbug.com/478183
+chrome.runtime.sendMessage({
+ type: "checkEnabled"
+}, function (enabled) {
+ if (!enabled) {
+ return;
+ }
+ unwrapAll();
+ setInterval(unwrapAll, 2000);
+});
diff --git a/src/js/firstparties/lib/utils.js b/src/js/firstparties/lib/utils.js
new file mode 100644
index 0000000..3edb0c8
--- /dev/null
+++ b/src/js/firstparties/lib/utils.js
@@ -0,0 +1,62 @@
+window.isURL = function(url) {
+ // ensure the URL starts with HTTP or HTTPS; otherwise we might be vulnerable
+ // to XSS attacks
+ return (url && (url.startsWith("https://") || url.startsWith("http://")));
+};
+
+/**
+ * Search a window and all IFrames within it for a query selector, then return a
+ * list of all the elements in any frame that match.
+ */
+window.findInAllFrames = function(query) {
+ let out = [];
+ document.querySelectorAll(query).forEach((node) => {
+ out.push(node);
+ });
+ Array.from(document.getElementsByTagName('iframe')).forEach((iframe) => {
+ try {
+ iframe.contentDocument.querySelectorAll(query).forEach((node) => {
+ out.push(node);
+ });
+ } catch (e) {
+ // pass on cross origin iframe errors
+ }
+ });
+ return out;
+};
+
+/**
+ * Listen for mutations on a page. If any nodes that match `selector` are added
+ * to the page, execute the function `callback` on them.
+ * Used by first-party scripts to listen for new links being added to the page
+ * and strip them of tracking code immediately.
+ */
+window.observeMutations = function(selector, callback) {
+ // Check all new nodes added by a mutation for tracking links and unwrap them
+ function onMutation(mutation) {
+ if (!mutation.addedNodes.length) {
+ return;
+ }
+ for (let node of mutation.addedNodes) {
+ // Only act on element nodes, otherwise querySelectorAll won't work
+ if (node.nodeType != Node.ELEMENT_NODE) {
+ continue;
+ }
+
+ // check all child nodes against the selector first
+ node.querySelectorAll(selector).forEach((element) => {
+ callback(element);
+ });
+
+ // then check the node itself
+ if (node.matches(selector)) {
+ callback(node);
+ }
+ }
+ }
+
+ // Set up a mutation observer with the constructed callback
+ new MutationObserver(function(mutations) {
+ mutations.forEach(onMutation);
+ }).observe(document, {childList: true, subtree: true, attributes: false, characterData: false});
+};