- // ==UserScript==
- // @name Don't track me Google
- // @namespace Rob W
- // @description Removes the annoying link-conversion at Google Search/maps/...
- // @version 4.28
- // @icon https://raw.githubusercontent.com/Rob--W/dont-track-me-google/master/icon48.png
- // @supportURL https://github.com/Rob--W/dont-track-me-google/issues
- // @license MIT
- // @run-at document-start
- // @match *://*.google.com/*
- // @match *://*.google.ad/*
- // @match *://*.google.ae/*
- // @match *://*.google.com.af/*
- // @match *://*.google.com.ag/*
- // @match *://*.google.com.ai/*
- // @match *://*.google.al/*
- // @match *://*.google.am/*
- // @match *://*.google.co.ao/*
- // @match *://*.google.com.ar/*
- // @match *://*.google.as/*
- // @match *://*.google.at/*
- // @match *://*.google.com.au/*
- // @match *://*.google.az/*
- // @match *://*.google.ba/*
- // @match *://*.google.com.bd/*
- // @match *://*.google.be/*
- // @match *://*.google.bf/*
- // @match *://*.google.bg/*
- // @match *://*.google.com.bh/*
- // @match *://*.google.bi/*
- // @match *://*.google.bj/*
- // @match *://*.google.com.bn/*
- // @match *://*.google.com.bo/*
- // @match *://*.google.com.br/*
- // @match *://*.google.bs/*
- // @match *://*.google.bt/*
- // @match *://*.google.co.bw/*
- // @match *://*.google.by/*
- // @match *://*.google.com.bz/*
- // @match *://*.google.ca/*
- // @match *://*.google.cd/*
- // @match *://*.google.cf/*
- // @match *://*.google.cg/*
- // @match *://*.google.ch/*
- // @match *://*.google.ci/*
- // @match *://*.google.co.ck/*
- // @match *://*.google.cl/*
- // @match *://*.google.cm/*
- // @match *://*.google.cn/*
- // @match *://*.google.com.co/*
- // @match *://*.google.co.cr/*
- // @match *://*.google.com.cu/*
- // @match *://*.google.cv/*
- // @match *://*.google.com.cy/*
- // @match *://*.google.cz/*
- // @match *://*.google.de/*
- // @match *://*.google.dj/*
- // @match *://*.google.dk/*
- // @match *://*.google.dm/*
- // @match *://*.google.com.do/*
- // @match *://*.google.dz/*
- // @match *://*.google.com.ec/*
- // @match *://*.google.ee/*
- // @match *://*.google.com.eg/*
- // @match *://*.google.es/*
- // @match *://*.google.com.et/*
- // @match *://*.google.fi/*
- // @match *://*.google.com.fj/*
- // @match *://*.google.fm/*
- // @match *://*.google.fr/*
- // @match *://*.google.ga/*
- // @match *://*.google.ge/*
- // @match *://*.google.gg/*
- // @match *://*.google.com.gh/*
- // @match *://*.google.com.gi/*
- // @match *://*.google.gl/*
- // @match *://*.google.gm/*
- // @match *://*.google.gp/*
- // @match *://*.google.gr/*
- // @match *://*.google.com.gt/*
- // @match *://*.google.gy/*
- // @match *://*.google.com.hk/*
- // @match *://*.google.hn/*
- // @match *://*.google.hr/*
- // @match *://*.google.ht/*
- // @match *://*.google.hu/*
- // @match *://*.google.co.id/*
- // @match *://*.google.ie/*
- // @match *://*.google.co.il/*
- // @match *://*.google.im/*
- // @match *://*.google.co.in/*
- // @match *://*.google.iq/*
- // @match *://*.google.is/*
- // @match *://*.google.it/*
- // @match *://*.google.je/*
- // @match *://*.google.com.jm/*
- // @match *://*.google.jo/*
- // @match *://*.google.co.jp/*
- // @match *://*.google.co.ke/*
- // @match *://*.google.com.kh/*
- // @match *://*.google.ki/*
- // @match *://*.google.kg/*
- // @match *://*.google.co.kr/*
- // @match *://*.google.com.kw/*
- // @match *://*.google.kz/*
- // @match *://*.google.la/*
- // @match *://*.google.com.lb/*
- // @match *://*.google.li/*
- // @match *://*.google.lk/*
- // @match *://*.google.co.ls/*
- // @match *://*.google.lt/*
- // @match *://*.google.lu/*
- // @match *://*.google.lv/*
- // @match *://*.google.com.ly/*
- // @match *://*.google.co.ma/*
- // @match *://*.google.md/*
- // @match *://*.google.me/*
- // @match *://*.google.mg/*
- // @match *://*.google.mk/*
- // @match *://*.google.ml/*
- // @match *://*.google.com.mm/*
- // @match *://*.google.mn/*
- // @match *://*.google.ms/*
- // @match *://*.google.com.mt/*
- // @match *://*.google.mu/*
- // @match *://*.google.mv/*
- // @match *://*.google.mw/*
- // @match *://*.google.com.mx/*
- // @match *://*.google.com.my/*
- // @match *://*.google.co.mz/*
- // @match *://*.google.com.na/*
- // @match *://*.google.com.nf/*
- // @match *://*.google.com.ng/*
- // @match *://*.google.com.ni/*
- // @match *://*.google.ne/*
- // @match *://*.google.nl/*
- // @match *://*.google.no/*
- // @match *://*.google.com.np/*
- // @match *://*.google.nr/*
- // @match *://*.google.nu/*
- // @match *://*.google.co.nz/*
- // @match *://*.google.com.om/*
- // @match *://*.google.com.pa/*
- // @match *://*.google.com.pe/*
- // @match *://*.google.com.pg/*
- // @match *://*.google.com.ph/*
- // @match *://*.google.com.pk/*
- // @match *://*.google.pl/*
- // @match *://*.google.pn/*
- // @match *://*.google.com.pr/*
- // @match *://*.google.ps/*
- // @match *://*.google.pt/*
- // @match *://*.google.com.py/*
- // @match *://*.google.com.qa/*
- // @match *://*.google.ro/*
- // @match *://*.google.ru/*
- // @match *://*.google.rw/*
- // @match *://*.google.com.sa/*
- // @match *://*.google.com.sb/*
- // @match *://*.google.sc/*
- // @match *://*.google.se/*
- // @match *://*.google.com.sg/*
- // @match *://*.google.sh/*
- // @match *://*.google.si/*
- // @match *://*.google.sk/*
- // @match *://*.google.com.sl/*
- // @match *://*.google.sn/*
- // @match *://*.google.so/*
- // @match *://*.google.sm/*
- // @match *://*.google.sr/*
- // @match *://*.google.st/*
- // @match *://*.google.com.sv/*
- // @match *://*.google.td/*
- // @match *://*.google.tg/*
- // @match *://*.google.co.th/*
- // @match *://*.google.com.tj/*
- // @match *://*.google.tk/*
- // @match *://*.google.tl/*
- // @match *://*.google.tm/*
- // @match *://*.google.tn/*
- // @match *://*.google.to/*
- // @match *://*.google.com.tr/*
- // @match *://*.google.tt/*
- // @match *://*.google.com.tw/*
- // @match *://*.google.co.tz/*
- // @match *://*.google.com.ua/*
- // @match *://*.google.co.ug/*
- // @match *://*.google.co.uk/*
- // @match *://*.google.com.uy/*
- // @match *://*.google.co.uz/*
- // @match *://*.google.com.vc/*
- // @match *://*.google.co.ve/*
- // @match *://*.google.vg/*
- // @match *://*.google.co.vi/*
- // @match *://*.google.com.vn/*
- // @match *://*.google.vu/*
- // @match *://*.google.ws/*
- // @match *://*.google.rs/*
- // @match *://*.google.co.za/*
- // @match *://*.google.co.zm/*
- // @match *://*.google.co.zw/*
- // @match *://*.google.cat/*
- // @match *://*.google.ng/*
- // ==/UserScript==
-
- document.addEventListener('mousedown', handlePointerPress, true);
- document.addEventListener('touchstart', handlePointerPress, true);
- document.addEventListener('click', handleClick, true);
- var scriptCspNonce;
- var needsCspNonce = typeof browser !== 'undefined'; // Firefox.
- var preferenceObservers = [];
- setupAggresiveUglyLinkPreventer();
-
- var forceNoReferrer = true;
- var noping = true;
- if (typeof chrome == 'object' && chrome.storage) {
- (chrome.storage.sync || chrome.storage.local).get({
- forceNoReferrer: true,
- // From version 4.7 until 4.11, the preference was the literal value of
- // the referrer policy.
- referrerPolicy: 'no-referrer',
- noping: true,
- }, function(items) {
- if (items) {
- // Migration code (to be removed in the future).
- if (items.referrerPolicy === '') {
- // User explicitly allowed referrers to be sent, respect that.
- items.forceNoReferrer = false;
- }
- forceNoReferrer = items.forceNoReferrer;
- noping = items.noping;
- callPreferenceObservers();
- }
- });
- chrome.storage.onChanged.addListener(function(changes) {
- if (changes.forceNoReferrer) {
- forceNoReferrer = changes.forceNoReferrer.newValue;
- }
- if (changes.noping) {
- noping = changes.noping.newValue;
- }
- callPreferenceObservers();
- });
- }
-
- function callImmediatelyAndOnPreferenceUpdate(callback) {
- callback();
- preferenceObservers.push(callback);
- }
- function callPreferenceObservers() {
- // This method is usually once, and occasionally more than once if the user
- // changes a preference. For simplicity we don't check whether a pref was
- // changed before calling a callback - these are cheap anyway.
- preferenceObservers.forEach(function(callback) {
- callback();
- });
- }
-
- function getReferrerPolicy() {
- return forceNoReferrer ? 'origin' : '';
- }
-
- function updateReferrerPolicy(a) {
- if (a.referrerPolicy === 'no-referrer') {
- // "no-referrer" is more privacy-friendly than "origin".
- return;
- }
- var referrerPolicy = getReferrerPolicy();
- if (referrerPolicy) {
- a.referrerPolicy = referrerPolicy;
- }
- }
-
- function handlePointerPress(e) {
- var a = e.target;
- while (a && !a.href) {
- a = a.parentElement;
- }
- if (!a) {
- return;
- }
- var inlineMousedown = a.getAttribute('onmousedown');
- // return rwt(....); // E.g Google search results.
- // return google.rwt(...); // E.g. sponsored search results
- // return google.arwt(this); // E.g. sponsored search results (dec 2016).
- if (inlineMousedown && /\ba?rwt\(/.test(inlineMousedown)) {
- a.removeAttribute('onmousedown');
- // Just in case:
- a.removeAttribute('ping');
- // In Chrome, removing onmousedown during event dispatch does not
- // prevent the inline listener from running... So we have to cancel
- // event propagation just in case.
- e.stopImmediatePropagation();
- }
- if (noping) {
- a.removeAttribute('ping');
- }
- var realLink = getRealLinkFromGoogleUrl(a);
- if (realLink) {
- a.href = realLink;
- // Sometimes, two fixups are needed, on old mobile user agents:
- // /url?q=https://googleweblight.com/fp?u=... -> ...
- realLink = getRealLinkFromGoogleUrl(a);
- if (realLink) {
- a.href = realLink;
- }
- }
- updateReferrerPolicy(a);
-
- if (e.eventPhase === Event.CAPTURING_PHASE) {
- // Our event listener runs first, to sanitize the link.
- // But the page may have an event handler that modifies the link again.
- // We can append a listener to the bubbling phase of the (current)
- // event dispatch to fix the link up again, provided that the page did
- // not call stopPropagation() or stopImmediatePropagation().
- var eventOptions = { capture: false, once: true };
- a.addEventListener(e.type, handlePointerPress, eventOptions);
- document.addEventListener(e.type, handlePointerPress, eventOptions);
- }
- }
-
- // This is specifically designed for catching clicks in Gmail.
- // Gmail binds a click handler to a <div> and cancels the event after opening
- // a window with an ugly URL. It uses a blank window + meta refresh in Firefox,
- // which is too crazy to patch. So we just make sure that the browser's default
- // click handler is activated (=open link in new tab).
- // The entry point for this crazy stuff is shown in my comment at
- // https://github.com/Rob--W/dont-track-me-google/issues/2
- function handleClick(e) {
- if (e.button !== 0) {
- return;
- }
- var a = e.target;
- while (a && !a.href) {
- a = a.parentElement;
- }
- if (!a) {
- return;
- }
- if (a.dataset && a.dataset.url) {
- var realLink = getSanitizedIntentUrl(a.dataset.url);
- if (realLink) {
- a.dataset.url = realLink;
- }
- }
- if (!location.hostname.startsWith('mail.')) {
- // This hack was designed for Gmail, but broke other Google sites:
- // - https://github.com/Rob--W/dont-track-me-google/issues/6
- // - https://github.com/Rob--W/dont-track-me-google/issues/19
- // So let's disable it for every domain except Gmail.
- return;
- }
- // TODO: Consider using a.baseURI instead of location in case Gmail ever
- // starts using <base href>?
- if (a.origin === location.origin) {
- // Same-origin link.
- // E.g. an in-page navigation at Google Docs (#...)
- // or an attachment at Gmail (https://mail.google.com/mail/u/0?ui=2&...)
- return;
- }
- if (a.protocol !== 'http:' &&
- a.protocol !== 'https:' &&
- a.protocol !== 'ftp:') {
- // Be conservative and don't block too much. E.g. Gmail has special
- // handling for mailto:-URLs, and using stopPropagation now would
- // cause mailto:-links to be opened by the platform's default mailto
- // handler instead of Gmail's handler (=open in new window).
- return;
- }
- if (a.target === '_blank') {
- e.stopPropagation();
- updateReferrerPolicy(a);
- }
- }
-
- /**
- * @param {URL|HTMLHyperlinkElementUtils} a
- * @returns {String} the real URL if the given link is a Google redirect URL.
- */
- function getRealLinkFromGoogleUrl(a) {
- if (a.protocol !== 'https:' && a.protocol !== 'http:') {
- return;
- }
- var url;
- if ((a.hostname === location.hostname || a.hostname === 'www.google.com') &&
- (a.pathname === '/url' || a.pathname === '/local_url' ||
- a.pathname === '/searchurl/rr.html' ||
- a.pathname === '/linkredirect')) {
- // Google Maps / Dito (/local_url?q=<url>)
- // Mobile (/url?q=<url>)
- // Google Meet's chat (/linkredirect?authuser=0&dest=<url>)
- url = /[?&](?:q|url|dest)=((?:https?|ftp)[%:][^&]+)/.exec(a.search);
- if (url) {
- return decodeURIComponent(url[1]);
- }
- // Help pages, e.g. safe browsing (/url?...&q=%2Fsupport%2Fanswer...)
- url = /[?&](?:q|url)=((?:%2[Ff]|\/)[^&]+)/.exec(a.search);
- if (url) {
- return a.origin + decodeURIComponent(url[1]);
- }
- // Redirect pages for Android intents (/searchurl/rr.html#...&url=...)
- // rr.html only supports http(s). So restrict to http(s) only.
- url = /[#&]url=(https?[:%][^&]+)/.exec(a.hash);
- if (url) {
- return decodeURIComponent(url[1]);
- }
- }
- // Google Search with old mobile UA (e.g. Firefox 41).
- if (a.hostname === 'googleweblight.com' && a.pathname === '/fp') {
- url = /[?&]u=((?:https?|ftp)[%:][^&]+)/.exec(a.search);
- if (url) {
- return decodeURIComponent(url[1]);
- }
- }
- }
-
- /**
- * @param {string} intentUrl
- * @returns {string|undefined} The sanitized intent:-URL if it was an intent URL
- * with embedded tracking link.
- */
- function getSanitizedIntentUrl(intentUrl) {
- if (!intentUrl.startsWith('intent:')) {
- return;
- }
- // https://developer.chrome.com/multidevice/android/intents#syntax
- var BROWSER_FALLBACK_URL = ';S.browser_fallback_url=';
- var indexStart = intentUrl.indexOf(BROWSER_FALLBACK_URL);
- if (indexStart === -1) {
- return;
- }
- indexStart += BROWSER_FALLBACK_URL.length;
- var indexEnd = intentUrl.indexOf(';', indexStart);
- indexEnd = indexEnd === -1 ? intentUrl.length : indexEnd;
-
- var url = decodeURIComponent(intentUrl.substring(indexStart, indexEnd));
- var realUrl = getRealLinkFromGoogleUrl(newURL(url));
- if (!realUrl) {
- return;
- }
- return intentUrl.substring(0, indexStart) +
- encodeURIComponent(realUrl) +
- intentUrl.substring(indexEnd);
- }
-
- /**
- * Intercept the .href setter in the page so that the page can never change the
- * URL to a tracking URL. Just intercepting mousedown/touchstart is not enough
- * because e.g. on Google Maps, the page rewrites the URL in the contextmenu
- * event at the bubbling event stage and then stops the event propagation. So
- * there is no event-driven way to fix the URL. The DOMAttrModified event could
- * be used, but the event is deprecated, so not a viable long-term solution.
- */
- function setupAggresiveUglyLinkPreventer() {
- // This content script runs as document_start, so we can have some assurance
- // that the methods in the page are reliable.
- var s = document.createElement('script');
- if (getScriptCspNonce()) {
- s.setAttribute('nonce', scriptCspNonce);
- } else if (document.readyState !== 'complete' && needsCspNonce) {
- // In Firefox, a page's CSP is enforced for content scripts, so we need
- // to wait for the document to be loaded (we may be at document_start)
- // and find a fitting CSP nonce.
- findScriptCspNonce(setupAggresiveUglyLinkPreventer);
- return;
- }
- s.textContent = '(' + function(getRealLinkFromGoogleUrl) {
- var proto = HTMLAnchorElement.prototype;
- // The link target can be changed in many ways, but let's only consider
- // the .href attribute since it's probably the only used setter.
- var hrefProp = Object.getOwnPropertyDescriptor(proto, 'href');
- var hrefGet = Function.prototype.call.bind(hrefProp.get);
- var hrefSet = Function.prototype.call.bind(hrefProp.set);
-
- Object.defineProperty(proto, 'href', {
- configurable: true,
- enumerable: true,
- get() {
- return hrefGet(this);
- },
- set(v) {
- hrefSet(this, v);
- try {
- v = getRealLinkFromGoogleUrl(this);
- if (v) {
- hrefSet(this, v);
- }
- } catch (e) {
- // Not expected to happen, but don't break the setter if for
- // some reason the (hostile) page broke the link APIs.
- }
- updateReferrerPolicy(this);
- },
- });
- function replaceAMethod(methodName, methodFunc) {
- // Overwrite the methods without triggering setters, because that
- // may inadvertently overwrite the prototype, as observed in
- // https://github.com/Rob--W/dont-track-me-google/issues/52#issuecomment-1596207655
- Object.defineProperty(proto, methodName, {
- configurable: true,
- // All methods that we are overriding are not part of
- // HTMLAnchorElement.prototype, but inherit.
- enumerable: false,
- writable: true,
- value: methodFunc,
- });
- }
-
- // proto inherits Element.prototype.setAttribute:
- var setAttribute = Function.prototype.call.bind(proto.setAttribute);
- replaceAMethod('setAttribute', function(name, value) {
- // Attribute names are not case-sensitive, but weird capitalizations
- // are unlikely, so only check all-lowercase and all-uppercase.
- if (name === 'href' || name === 'HREF') {
- this.href = value;
- } else {
- setAttribute(this, name, value);
- }
- });
-
- // proto inherits EventTarget.prototype.dispatchEvent:
- var aDispatchEvent = Function.prototype.apply.bind(proto.dispatchEvent);
- replaceAMethod('dispatchEvent', function() {
- updateReferrerPolicy(this);
- return aDispatchEvent(this, arguments);
- });
-
- // proto inherits HTMLElement.prototype.click:
- var aClick = Function.prototype.apply.bind(proto.click);
- replaceAMethod('click', function() {
- updateReferrerPolicy(this);
- return aClick(this, arguments);
- });
-
- var rpProp = Object.getOwnPropertyDescriptor(proto, 'referrerPolicy');
- var rpGet = Function.prototype.call.bind(rpProp.get);
- var rpSet = Function.prototype.call.bind(rpProp.set);
-
- var currentScript = document.currentScript;
- var getReferrerPolicy = Object.getOwnPropertyDescriptor(
- HTMLScriptElement.prototype,
- 'referrerPolicy'
- ).get.bind(currentScript);
-
- function updateReferrerPolicy(a) {
- try {
- if (rpGet(a) === 'no-referrer') {
- // "no-referrer" is more privacy-friendly than "origin".
- return;
- }
- var referrerPolicy = getReferrerPolicy();
- if (referrerPolicy) {
- rpSet(a, referrerPolicy);
- }
- } catch (e) {
- // Not expected to happen, but don't break callers if it happens
- // anyway.
- }
- }
- currentScript.dataset.jsEnabled = 1;
- } + ')(' + getRealLinkFromGoogleUrl + ');';
- callImmediatelyAndOnPreferenceUpdate(function forceNoReferrerChanged() {
- // Send the desired referrerPolicy value to the injected script.
- s.referrerPolicy = getReferrerPolicy();
- });
- (document.head || document.documentElement).appendChild(s);
- s.remove();
- if (!s.dataset.jsEnabled) {
- cleanLinksWhenJsIsDisabled();
- if (!needsCspNonce) {
- needsCspNonce = true;
- // This is not Firefox, but the script was blocked. Perhaps a CSP
- // nonce is needed anyway.
- findScriptCspNonce(function() {
- if (scriptCspNonce) {
- setupAggresiveUglyLinkPreventer();
- }
- });
- }
- } else {
- // Scripts enabled (not blocked by CSP), run other inline scripts.
- blockTrackingBeacons();
- overwriteWindowOpen();
-
- if (location.hostname === 'docs.google.com') {
- // Google Docs have simple non-JS interfaces where the ugly links
- // are hard-coded in the HTML. Remove them (#51).
- // https://docs.google.com/document/d/.../mobilebasic
- // https://docs.google.com/spreadsheets/d/.../htmlview
- cleanLinksWhenJsIsDisabled();
- }
- }
- }
-
- // Block sendBeacon requests with destination /gen_204, because Google
- // asynchronously sends beacon requests in response to mouse events on links:
- // https://github.com/Rob--W/dont-track-me-google/issues/20
- //
- // This implementation also blocks other forms of tracking via gen_204 as a side
- // effect. That is not fully intentional, but given the lack of obvious ways to
- // discern such link-tracking events from others, I will block all of them.
- function blockTrackingBeacons() {
- var s = document.createElement('script');
- if (getScriptCspNonce()) {
- s.setAttribute('nonce', scriptCspNonce);
- }
- s.textContent = '(' + function() {
- var navProto = window.Navigator.prototype;
- var navProtoSendBeacon = navProto.sendBeacon;
- if (!navProtoSendBeacon) {
- return;
- }
- var sendBeacon = Function.prototype.apply.bind(navProtoSendBeacon);
-
- // Blocks the following:
- // gen_204
- // /gen_204
- // https://www.google.com/gen_204
- var isTrackingUrl = RegExp.prototype.test.bind(
- /^(?:(?:https?:\/\/[^\/]+)?\/)?gen_204(?:[?#]|$)/);
-
- navProto.sendBeacon = function(url, data) {
- if (isTrackingUrl(url) && isNoPingEnabled()) {
- // Lie that the data has been transmitted to avoid fallbacks.
- return true;
- }
- return sendBeacon(this, arguments);
- };
-
- var currentScript = document.currentScript;
- var getElementId = Object.getOwnPropertyDescriptor(
- Element.prototype,
- 'id'
- ).get.bind(currentScript);
- function isNoPingEnabled() {
- try {
- return getElementId() !== '_dtmg_do_not_touch_ping';
- } catch (e) {
- return true;
- }
- }
- } + ')();';
- callImmediatelyAndOnPreferenceUpdate(function nopingChanged() {
- // Send the noping value to the injected script. The "id" property is
- // mirrored and can have an arbitrary (string) value, so we use that:
- s.id = noping ? '' : '_dtmg_do_not_touch_ping';
- });
- (document.head || document.documentElement).appendChild(s);
- s.remove();
- }
-
- // Google sometimes uses window.open() to open ugly links.
- // https://github.com/Rob--W/dont-track-me-google/issues/18
- // https://github.com/Rob--W/dont-track-me-google/issues/41
- function overwriteWindowOpen() {
- var s = document.createElement('script');
- if (getScriptCspNonce()) {
- s.setAttribute('nonce', scriptCspNonce);
- }
- s.textContent = '(' + function() {
- var open = window.open;
- window.open = function(url, windowName, windowFeatures) {
- var isBlankUrl = !url || url === "about:blank";
- try {
- if (!isBlankUrl) {
- var a = document.createElement('a');
- // Triggers getRealLinkFromGoogleUrl via the href setter in
- // setupAggresiveUglyLinkPreventer.
- a.href = url;
- url = a.href;
- // The origin check exists to avoid adding "noreferrer" to
- // same-origin popups. That implies noopener and causes
- // https://github.com/Rob--W/dont-track-me-google/issues/43
- // And allow any Google domain to support auth popups:
- // https://github.com/Rob--W/dont-track-me-google/issues/45
- // And don't bother editing the list if it already contains
- // "opener" (it would be disabled by "noreferrer").
- if (a.referrerPolicy && a.origin !== location.origin &&
- !/\.google\.([a-z]+)$/.test(a.hostname) &&
- !/\bopener|noreferrer/.test(windowFeatures)) {
- if (windowFeatures) {
- windowFeatures += ',';
- } else {
- windowFeatures = '';
- }
- windowFeatures += 'noreferrer';
- }
- }
- } catch (e) {
- // Not expected to happen, but don't break callers if it does.
- }
- var win = open(url, windowName, windowFeatures);
- try {
- if (isBlankUrl && win) {
- // In Google Docs, sometimes a blank document is opened,
- // and document.write is used to insert a redirector.
- // https://github.com/Rob--W/dont-track-me-google/issues/41
- var doc = win.document;
- var docWrite = win.Function.prototype.call.bind(doc.write);
- doc.write = function(markup) {
- try {
- markup = fixupDocMarkup(markup);
- } catch (e) {
- // Not expected, but don't break callers otherwise.
- }
- return docWrite(this, markup);
- };
- }
- } catch (e) {
- // Not expected to happen, but don't break callers if it does.
- }
- return win;
- };
- function fixupDocMarkup(html) {
- html = html || '';
- html += '';
- return html.replace(
- /<meta [^>]*http-equiv=(["']?)refresh\1[^>]*>/i,
- function(m) {
- var doc = new DOMParser().parseFromString(m, 'text/html');
- var meta = doc.querySelector('meta[http-equiv=refresh]');
- return meta && fixupMetaUrl(meta) || m;
- });
- }
- function fixupMetaUrl(meta) {
- var parts = /^(\d*;\s*url=)(.+)$/i.exec(meta.content);
- if (!parts) {
- return;
- }
- var metaPrefix = parts[1];
- var url = parts[2];
- var a = document.createElement('a');
- // Triggers getRealLinkFromGoogleUrl via the href setter in
- // setupAggresiveUglyLinkPreventer.
- a.href = url;
- url = a.href;
- meta.content = metaPrefix + url;
-
- var html = meta.outerHTML;
- if (a.referrerPolicy) {
- // Google appears to already append the no-referrer
- // meta tag, but add one just in case it doesn't.
- html = '<meta name="referrer" content="no-referrer">' + html;
- }
- return html;
- }
- } + ')();';
- (document.head || document.documentElement).appendChild(s);
- s.remove();
- }
-
- function cleanLinksWhenJsIsDisabled() {
- // When JavaScript is disabled, Google sets the "href" attribute's value to
- // an ugly URL. Although the link is rewritten on click, we still need to
- // rewrite the link even earlier because otherwise the ugly URL is shown in
- // the tooltip upon hover.
-
- if (document.readyState == 'complete') {
- cleanAllLinks();
- return;
- }
-
- // When JS is disabled, the links won't change after the document finishes
- // loading. Until the DOM has finished loading, use the mouseover event to
- // beautify links (the DOMContentLoaded may be delayed on slow networks).
- document.addEventListener('mouseover', handleMouseOver);
- document.addEventListener('DOMContentLoaded', function() {
- document.removeEventListener('mouseover', handleMouseOver);
- cleanAllLinks();
- }, {once: true});
-
- function cleanAllLinks() {
- var as = document.querySelectorAll('a[href]');
- for (var i = 0; i < as.length; ++i) {
- var href = getRealLinkFromGoogleUrl(as[i]);
- if (href) {
- as[i].href = href;
- }
- }
- }
-
- function handleMouseOver(e) {
- var a = e.target;
- var href = a.href && getRealLinkFromGoogleUrl(a);
- if (href) {
- a.href = href;
- }
- }
- }
-
- function getScriptCspNonce() {
- var scripts = document.querySelectorAll('script[nonce]');
- for (var i = 0; i < scripts.length && !scriptCspNonce; ++i) {
- scriptCspNonce = scripts[i].nonce;
- }
- return scriptCspNonce;
- }
-
- function findScriptCspNonce(callback) {
- var timer;
- function checkDOM() {
- if (getScriptCspNonce() || document.readyState === 'complete') {
- document.removeEventListener('DOMContentLoaded', checkDOM, true);
- if (timer) {
- clearTimeout(timer);
- }
- callback();
- return;
- }
- timer = setTimeout(checkDOM, 50);
- }
- document.addEventListener('DOMContentLoaded', checkDOM, true);
- checkDOM();
- }
-
- function newURL(href) {
- try {
- return new URL(href);
- } catch (e) {
- var a = document.createElement('a');
- a.href = href;
- return a;
- }
- }