您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Removes the annoying link-conversion at Google Search/maps/...
// ==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; } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址