Don't track me Google

Removes the annoying link-conversion at Google Search/maps/...

  1. // ==UserScript==
  2. // @name Don't track me Google
  3. // @namespace Rob W
  4. // @description Removes the annoying link-conversion at Google Search/maps/...
  5. // @version 4.28
  6. // @icon https://raw.githubusercontent.com/Rob--W/dont-track-me-google/master/icon48.png
  7. // @supportURL https://github.com/Rob--W/dont-track-me-google/issues
  8. // @license MIT
  9. // @run-at document-start
  10. // @match *://*.google.com/*
  11. // @match *://*.google.ad/*
  12. // @match *://*.google.ae/*
  13. // @match *://*.google.com.af/*
  14. // @match *://*.google.com.ag/*
  15. // @match *://*.google.com.ai/*
  16. // @match *://*.google.al/*
  17. // @match *://*.google.am/*
  18. // @match *://*.google.co.ao/*
  19. // @match *://*.google.com.ar/*
  20. // @match *://*.google.as/*
  21. // @match *://*.google.at/*
  22. // @match *://*.google.com.au/*
  23. // @match *://*.google.az/*
  24. // @match *://*.google.ba/*
  25. // @match *://*.google.com.bd/*
  26. // @match *://*.google.be/*
  27. // @match *://*.google.bf/*
  28. // @match *://*.google.bg/*
  29. // @match *://*.google.com.bh/*
  30. // @match *://*.google.bi/*
  31. // @match *://*.google.bj/*
  32. // @match *://*.google.com.bn/*
  33. // @match *://*.google.com.bo/*
  34. // @match *://*.google.com.br/*
  35. // @match *://*.google.bs/*
  36. // @match *://*.google.bt/*
  37. // @match *://*.google.co.bw/*
  38. // @match *://*.google.by/*
  39. // @match *://*.google.com.bz/*
  40. // @match *://*.google.ca/*
  41. // @match *://*.google.cd/*
  42. // @match *://*.google.cf/*
  43. // @match *://*.google.cg/*
  44. // @match *://*.google.ch/*
  45. // @match *://*.google.ci/*
  46. // @match *://*.google.co.ck/*
  47. // @match *://*.google.cl/*
  48. // @match *://*.google.cm/*
  49. // @match *://*.google.cn/*
  50. // @match *://*.google.com.co/*
  51. // @match *://*.google.co.cr/*
  52. // @match *://*.google.com.cu/*
  53. // @match *://*.google.cv/*
  54. // @match *://*.google.com.cy/*
  55. // @match *://*.google.cz/*
  56. // @match *://*.google.de/*
  57. // @match *://*.google.dj/*
  58. // @match *://*.google.dk/*
  59. // @match *://*.google.dm/*
  60. // @match *://*.google.com.do/*
  61. // @match *://*.google.dz/*
  62. // @match *://*.google.com.ec/*
  63. // @match *://*.google.ee/*
  64. // @match *://*.google.com.eg/*
  65. // @match *://*.google.es/*
  66. // @match *://*.google.com.et/*
  67. // @match *://*.google.fi/*
  68. // @match *://*.google.com.fj/*
  69. // @match *://*.google.fm/*
  70. // @match *://*.google.fr/*
  71. // @match *://*.google.ga/*
  72. // @match *://*.google.ge/*
  73. // @match *://*.google.gg/*
  74. // @match *://*.google.com.gh/*
  75. // @match *://*.google.com.gi/*
  76. // @match *://*.google.gl/*
  77. // @match *://*.google.gm/*
  78. // @match *://*.google.gp/*
  79. // @match *://*.google.gr/*
  80. // @match *://*.google.com.gt/*
  81. // @match *://*.google.gy/*
  82. // @match *://*.google.com.hk/*
  83. // @match *://*.google.hn/*
  84. // @match *://*.google.hr/*
  85. // @match *://*.google.ht/*
  86. // @match *://*.google.hu/*
  87. // @match *://*.google.co.id/*
  88. // @match *://*.google.ie/*
  89. // @match *://*.google.co.il/*
  90. // @match *://*.google.im/*
  91. // @match *://*.google.co.in/*
  92. // @match *://*.google.iq/*
  93. // @match *://*.google.is/*
  94. // @match *://*.google.it/*
  95. // @match *://*.google.je/*
  96. // @match *://*.google.com.jm/*
  97. // @match *://*.google.jo/*
  98. // @match *://*.google.co.jp/*
  99. // @match *://*.google.co.ke/*
  100. // @match *://*.google.com.kh/*
  101. // @match *://*.google.ki/*
  102. // @match *://*.google.kg/*
  103. // @match *://*.google.co.kr/*
  104. // @match *://*.google.com.kw/*
  105. // @match *://*.google.kz/*
  106. // @match *://*.google.la/*
  107. // @match *://*.google.com.lb/*
  108. // @match *://*.google.li/*
  109. // @match *://*.google.lk/*
  110. // @match *://*.google.co.ls/*
  111. // @match *://*.google.lt/*
  112. // @match *://*.google.lu/*
  113. // @match *://*.google.lv/*
  114. // @match *://*.google.com.ly/*
  115. // @match *://*.google.co.ma/*
  116. // @match *://*.google.md/*
  117. // @match *://*.google.me/*
  118. // @match *://*.google.mg/*
  119. // @match *://*.google.mk/*
  120. // @match *://*.google.ml/*
  121. // @match *://*.google.com.mm/*
  122. // @match *://*.google.mn/*
  123. // @match *://*.google.ms/*
  124. // @match *://*.google.com.mt/*
  125. // @match *://*.google.mu/*
  126. // @match *://*.google.mv/*
  127. // @match *://*.google.mw/*
  128. // @match *://*.google.com.mx/*
  129. // @match *://*.google.com.my/*
  130. // @match *://*.google.co.mz/*
  131. // @match *://*.google.com.na/*
  132. // @match *://*.google.com.nf/*
  133. // @match *://*.google.com.ng/*
  134. // @match *://*.google.com.ni/*
  135. // @match *://*.google.ne/*
  136. // @match *://*.google.nl/*
  137. // @match *://*.google.no/*
  138. // @match *://*.google.com.np/*
  139. // @match *://*.google.nr/*
  140. // @match *://*.google.nu/*
  141. // @match *://*.google.co.nz/*
  142. // @match *://*.google.com.om/*
  143. // @match *://*.google.com.pa/*
  144. // @match *://*.google.com.pe/*
  145. // @match *://*.google.com.pg/*
  146. // @match *://*.google.com.ph/*
  147. // @match *://*.google.com.pk/*
  148. // @match *://*.google.pl/*
  149. // @match *://*.google.pn/*
  150. // @match *://*.google.com.pr/*
  151. // @match *://*.google.ps/*
  152. // @match *://*.google.pt/*
  153. // @match *://*.google.com.py/*
  154. // @match *://*.google.com.qa/*
  155. // @match *://*.google.ro/*
  156. // @match *://*.google.ru/*
  157. // @match *://*.google.rw/*
  158. // @match *://*.google.com.sa/*
  159. // @match *://*.google.com.sb/*
  160. // @match *://*.google.sc/*
  161. // @match *://*.google.se/*
  162. // @match *://*.google.com.sg/*
  163. // @match *://*.google.sh/*
  164. // @match *://*.google.si/*
  165. // @match *://*.google.sk/*
  166. // @match *://*.google.com.sl/*
  167. // @match *://*.google.sn/*
  168. // @match *://*.google.so/*
  169. // @match *://*.google.sm/*
  170. // @match *://*.google.sr/*
  171. // @match *://*.google.st/*
  172. // @match *://*.google.com.sv/*
  173. // @match *://*.google.td/*
  174. // @match *://*.google.tg/*
  175. // @match *://*.google.co.th/*
  176. // @match *://*.google.com.tj/*
  177. // @match *://*.google.tk/*
  178. // @match *://*.google.tl/*
  179. // @match *://*.google.tm/*
  180. // @match *://*.google.tn/*
  181. // @match *://*.google.to/*
  182. // @match *://*.google.com.tr/*
  183. // @match *://*.google.tt/*
  184. // @match *://*.google.com.tw/*
  185. // @match *://*.google.co.tz/*
  186. // @match *://*.google.com.ua/*
  187. // @match *://*.google.co.ug/*
  188. // @match *://*.google.co.uk/*
  189. // @match *://*.google.com.uy/*
  190. // @match *://*.google.co.uz/*
  191. // @match *://*.google.com.vc/*
  192. // @match *://*.google.co.ve/*
  193. // @match *://*.google.vg/*
  194. // @match *://*.google.co.vi/*
  195. // @match *://*.google.com.vn/*
  196. // @match *://*.google.vu/*
  197. // @match *://*.google.ws/*
  198. // @match *://*.google.rs/*
  199. // @match *://*.google.co.za/*
  200. // @match *://*.google.co.zm/*
  201. // @match *://*.google.co.zw/*
  202. // @match *://*.google.cat/*
  203. // @match *://*.google.ng/*
  204. // ==/UserScript==
  205.  
  206. document.addEventListener('mousedown', handlePointerPress, true);
  207. document.addEventListener('touchstart', handlePointerPress, true);
  208. document.addEventListener('click', handleClick, true);
  209. var scriptCspNonce;
  210. var needsCspNonce = typeof browser !== 'undefined'; // Firefox.
  211. var preferenceObservers = [];
  212. setupAggresiveUglyLinkPreventer();
  213.  
  214. var forceNoReferrer = true;
  215. var noping = true;
  216. if (typeof chrome == 'object' && chrome.storage) {
  217. (chrome.storage.sync || chrome.storage.local).get({
  218. forceNoReferrer: true,
  219. // From version 4.7 until 4.11, the preference was the literal value of
  220. // the referrer policy.
  221. referrerPolicy: 'no-referrer',
  222. noping: true,
  223. }, function(items) {
  224. if (items) {
  225. // Migration code (to be removed in the future).
  226. if (items.referrerPolicy === '') {
  227. // User explicitly allowed referrers to be sent, respect that.
  228. items.forceNoReferrer = false;
  229. }
  230. forceNoReferrer = items.forceNoReferrer;
  231. noping = items.noping;
  232. callPreferenceObservers();
  233. }
  234. });
  235. chrome.storage.onChanged.addListener(function(changes) {
  236. if (changes.forceNoReferrer) {
  237. forceNoReferrer = changes.forceNoReferrer.newValue;
  238. }
  239. if (changes.noping) {
  240. noping = changes.noping.newValue;
  241. }
  242. callPreferenceObservers();
  243. });
  244. }
  245.  
  246. function callImmediatelyAndOnPreferenceUpdate(callback) {
  247. callback();
  248. preferenceObservers.push(callback);
  249. }
  250. function callPreferenceObservers() {
  251. // This method is usually once, and occasionally more than once if the user
  252. // changes a preference. For simplicity we don't check whether a pref was
  253. // changed before calling a callback - these are cheap anyway.
  254. preferenceObservers.forEach(function(callback) {
  255. callback();
  256. });
  257. }
  258.  
  259. function getReferrerPolicy() {
  260. return forceNoReferrer ? 'origin' : '';
  261. }
  262.  
  263. function updateReferrerPolicy(a) {
  264. if (a.referrerPolicy === 'no-referrer') {
  265. // "no-referrer" is more privacy-friendly than "origin".
  266. return;
  267. }
  268. var referrerPolicy = getReferrerPolicy();
  269. if (referrerPolicy) {
  270. a.referrerPolicy = referrerPolicy;
  271. }
  272. }
  273.  
  274. function handlePointerPress(e) {
  275. var a = e.target;
  276. while (a && !a.href) {
  277. a = a.parentElement;
  278. }
  279. if (!a) {
  280. return;
  281. }
  282. var inlineMousedown = a.getAttribute('onmousedown');
  283. // return rwt(....); // E.g Google search results.
  284. // return google.rwt(...); // E.g. sponsored search results
  285. // return google.arwt(this); // E.g. sponsored search results (dec 2016).
  286. if (inlineMousedown && /\ba?rwt\(/.test(inlineMousedown)) {
  287. a.removeAttribute('onmousedown');
  288. // Just in case:
  289. a.removeAttribute('ping');
  290. // In Chrome, removing onmousedown during event dispatch does not
  291. // prevent the inline listener from running... So we have to cancel
  292. // event propagation just in case.
  293. e.stopImmediatePropagation();
  294. }
  295. if (noping) {
  296. a.removeAttribute('ping');
  297. }
  298. var realLink = getRealLinkFromGoogleUrl(a);
  299. if (realLink) {
  300. a.href = realLink;
  301. // Sometimes, two fixups are needed, on old mobile user agents:
  302. // /url?q=https://googleweblight.com/fp?u=... -> ...
  303. realLink = getRealLinkFromGoogleUrl(a);
  304. if (realLink) {
  305. a.href = realLink;
  306. }
  307. }
  308. updateReferrerPolicy(a);
  309.  
  310. if (e.eventPhase === Event.CAPTURING_PHASE) {
  311. // Our event listener runs first, to sanitize the link.
  312. // But the page may have an event handler that modifies the link again.
  313. // We can append a listener to the bubbling phase of the (current)
  314. // event dispatch to fix the link up again, provided that the page did
  315. // not call stopPropagation() or stopImmediatePropagation().
  316. var eventOptions = { capture: false, once: true };
  317. a.addEventListener(e.type, handlePointerPress, eventOptions);
  318. document.addEventListener(e.type, handlePointerPress, eventOptions);
  319. }
  320. }
  321.  
  322. // This is specifically designed for catching clicks in Gmail.
  323. // Gmail binds a click handler to a <div> and cancels the event after opening
  324. // a window with an ugly URL. It uses a blank window + meta refresh in Firefox,
  325. // which is too crazy to patch. So we just make sure that the browser's default
  326. // click handler is activated (=open link in new tab).
  327. // The entry point for this crazy stuff is shown in my comment at
  328. // https://github.com/Rob--W/dont-track-me-google/issues/2
  329. function handleClick(e) {
  330. if (e.button !== 0) {
  331. return;
  332. }
  333. var a = e.target;
  334. while (a && !a.href) {
  335. a = a.parentElement;
  336. }
  337. if (!a) {
  338. return;
  339. }
  340. if (a.dataset && a.dataset.url) {
  341. var realLink = getSanitizedIntentUrl(a.dataset.url);
  342. if (realLink) {
  343. a.dataset.url = realLink;
  344. }
  345. }
  346. if (!location.hostname.startsWith('mail.')) {
  347. // This hack was designed for Gmail, but broke other Google sites:
  348. // - https://github.com/Rob--W/dont-track-me-google/issues/6
  349. // - https://github.com/Rob--W/dont-track-me-google/issues/19
  350. // So let's disable it for every domain except Gmail.
  351. return;
  352. }
  353. // TODO: Consider using a.baseURI instead of location in case Gmail ever
  354. // starts using <base href>?
  355. if (a.origin === location.origin) {
  356. // Same-origin link.
  357. // E.g. an in-page navigation at Google Docs (#...)
  358. // or an attachment at Gmail (https://mail.google.com/mail/u/0?ui=2&...)
  359. return;
  360. }
  361. if (a.protocol !== 'http:' &&
  362. a.protocol !== 'https:' &&
  363. a.protocol !== 'ftp:') {
  364. // Be conservative and don't block too much. E.g. Gmail has special
  365. // handling for mailto:-URLs, and using stopPropagation now would
  366. // cause mailto:-links to be opened by the platform's default mailto
  367. // handler instead of Gmail's handler (=open in new window).
  368. return;
  369. }
  370. if (a.target === '_blank') {
  371. e.stopPropagation();
  372. updateReferrerPolicy(a);
  373. }
  374. }
  375.  
  376. /**
  377. * @param {URL|HTMLHyperlinkElementUtils} a
  378. * @returns {String} the real URL if the given link is a Google redirect URL.
  379. */
  380. function getRealLinkFromGoogleUrl(a) {
  381. if (a.protocol !== 'https:' && a.protocol !== 'http:') {
  382. return;
  383. }
  384. var url;
  385. if ((a.hostname === location.hostname || a.hostname === 'www.google.com') &&
  386. (a.pathname === '/url' || a.pathname === '/local_url' ||
  387. a.pathname === '/searchurl/rr.html' ||
  388. a.pathname === '/linkredirect')) {
  389. // Google Maps / Dito (/local_url?q=<url>)
  390. // Mobile (/url?q=<url>)
  391. // Google Meet's chat (/linkredirect?authuser=0&dest=<url>)
  392. url = /[?&](?:q|url|dest)=((?:https?|ftp)[%:][^&]+)/.exec(a.search);
  393. if (url) {
  394. return decodeURIComponent(url[1]);
  395. }
  396. // Help pages, e.g. safe browsing (/url?...&q=%2Fsupport%2Fanswer...)
  397. url = /[?&](?:q|url)=((?:%2[Ff]|\/)[^&]+)/.exec(a.search);
  398. if (url) {
  399. return a.origin + decodeURIComponent(url[1]);
  400. }
  401. // Redirect pages for Android intents (/searchurl/rr.html#...&url=...)
  402. // rr.html only supports http(s). So restrict to http(s) only.
  403. url = /[#&]url=(https?[:%][^&]+)/.exec(a.hash);
  404. if (url) {
  405. return decodeURIComponent(url[1]);
  406. }
  407. }
  408. // Google Search with old mobile UA (e.g. Firefox 41).
  409. if (a.hostname === 'googleweblight.com' && a.pathname === '/fp') {
  410. url = /[?&]u=((?:https?|ftp)[%:][^&]+)/.exec(a.search);
  411. if (url) {
  412. return decodeURIComponent(url[1]);
  413. }
  414. }
  415. }
  416.  
  417. /**
  418. * @param {string} intentUrl
  419. * @returns {string|undefined} The sanitized intent:-URL if it was an intent URL
  420. * with embedded tracking link.
  421. */
  422. function getSanitizedIntentUrl(intentUrl) {
  423. if (!intentUrl.startsWith('intent:')) {
  424. return;
  425. }
  426. // https://developer.chrome.com/multidevice/android/intents#syntax
  427. var BROWSER_FALLBACK_URL = ';S.browser_fallback_url=';
  428. var indexStart = intentUrl.indexOf(BROWSER_FALLBACK_URL);
  429. if (indexStart === -1) {
  430. return;
  431. }
  432. indexStart += BROWSER_FALLBACK_URL.length;
  433. var indexEnd = intentUrl.indexOf(';', indexStart);
  434. indexEnd = indexEnd === -1 ? intentUrl.length : indexEnd;
  435.  
  436. var url = decodeURIComponent(intentUrl.substring(indexStart, indexEnd));
  437. var realUrl = getRealLinkFromGoogleUrl(newURL(url));
  438. if (!realUrl) {
  439. return;
  440. }
  441. return intentUrl.substring(0, indexStart) +
  442. encodeURIComponent(realUrl) +
  443. intentUrl.substring(indexEnd);
  444. }
  445.  
  446. /**
  447. * Intercept the .href setter in the page so that the page can never change the
  448. * URL to a tracking URL. Just intercepting mousedown/touchstart is not enough
  449. * because e.g. on Google Maps, the page rewrites the URL in the contextmenu
  450. * event at the bubbling event stage and then stops the event propagation. So
  451. * there is no event-driven way to fix the URL. The DOMAttrModified event could
  452. * be used, but the event is deprecated, so not a viable long-term solution.
  453. */
  454. function setupAggresiveUglyLinkPreventer() {
  455. // This content script runs as document_start, so we can have some assurance
  456. // that the methods in the page are reliable.
  457. var s = document.createElement('script');
  458. if (getScriptCspNonce()) {
  459. s.setAttribute('nonce', scriptCspNonce);
  460. } else if (document.readyState !== 'complete' && needsCspNonce) {
  461. // In Firefox, a page's CSP is enforced for content scripts, so we need
  462. // to wait for the document to be loaded (we may be at document_start)
  463. // and find a fitting CSP nonce.
  464. findScriptCspNonce(setupAggresiveUglyLinkPreventer);
  465. return;
  466. }
  467. s.textContent = '(' + function(getRealLinkFromGoogleUrl) {
  468. var proto = HTMLAnchorElement.prototype;
  469. // The link target can be changed in many ways, but let's only consider
  470. // the .href attribute since it's probably the only used setter.
  471. var hrefProp = Object.getOwnPropertyDescriptor(proto, 'href');
  472. var hrefGet = Function.prototype.call.bind(hrefProp.get);
  473. var hrefSet = Function.prototype.call.bind(hrefProp.set);
  474.  
  475. Object.defineProperty(proto, 'href', {
  476. configurable: true,
  477. enumerable: true,
  478. get() {
  479. return hrefGet(this);
  480. },
  481. set(v) {
  482. hrefSet(this, v);
  483. try {
  484. v = getRealLinkFromGoogleUrl(this);
  485. if (v) {
  486. hrefSet(this, v);
  487. }
  488. } catch (e) {
  489. // Not expected to happen, but don't break the setter if for
  490. // some reason the (hostile) page broke the link APIs.
  491. }
  492. updateReferrerPolicy(this);
  493. },
  494. });
  495. function replaceAMethod(methodName, methodFunc) {
  496. // Overwrite the methods without triggering setters, because that
  497. // may inadvertently overwrite the prototype, as observed in
  498. // https://github.com/Rob--W/dont-track-me-google/issues/52#issuecomment-1596207655
  499. Object.defineProperty(proto, methodName, {
  500. configurable: true,
  501. // All methods that we are overriding are not part of
  502. // HTMLAnchorElement.prototype, but inherit.
  503. enumerable: false,
  504. writable: true,
  505. value: methodFunc,
  506. });
  507. }
  508.  
  509. // proto inherits Element.prototype.setAttribute:
  510. var setAttribute = Function.prototype.call.bind(proto.setAttribute);
  511. replaceAMethod('setAttribute', function(name, value) {
  512. // Attribute names are not case-sensitive, but weird capitalizations
  513. // are unlikely, so only check all-lowercase and all-uppercase.
  514. if (name === 'href' || name === 'HREF') {
  515. this.href = value;
  516. } else {
  517. setAttribute(this, name, value);
  518. }
  519. });
  520.  
  521. // proto inherits EventTarget.prototype.dispatchEvent:
  522. var aDispatchEvent = Function.prototype.apply.bind(proto.dispatchEvent);
  523. replaceAMethod('dispatchEvent', function() {
  524. updateReferrerPolicy(this);
  525. return aDispatchEvent(this, arguments);
  526. });
  527.  
  528. // proto inherits HTMLElement.prototype.click:
  529. var aClick = Function.prototype.apply.bind(proto.click);
  530. replaceAMethod('click', function() {
  531. updateReferrerPolicy(this);
  532. return aClick(this, arguments);
  533. });
  534.  
  535. var rpProp = Object.getOwnPropertyDescriptor(proto, 'referrerPolicy');
  536. var rpGet = Function.prototype.call.bind(rpProp.get);
  537. var rpSet = Function.prototype.call.bind(rpProp.set);
  538.  
  539. var currentScript = document.currentScript;
  540. var getReferrerPolicy = Object.getOwnPropertyDescriptor(
  541. HTMLScriptElement.prototype,
  542. 'referrerPolicy'
  543. ).get.bind(currentScript);
  544.  
  545. function updateReferrerPolicy(a) {
  546. try {
  547. if (rpGet(a) === 'no-referrer') {
  548. // "no-referrer" is more privacy-friendly than "origin".
  549. return;
  550. }
  551. var referrerPolicy = getReferrerPolicy();
  552. if (referrerPolicy) {
  553. rpSet(a, referrerPolicy);
  554. }
  555. } catch (e) {
  556. // Not expected to happen, but don't break callers if it happens
  557. // anyway.
  558. }
  559. }
  560. currentScript.dataset.jsEnabled = 1;
  561. } + ')(' + getRealLinkFromGoogleUrl + ');';
  562. callImmediatelyAndOnPreferenceUpdate(function forceNoReferrerChanged() {
  563. // Send the desired referrerPolicy value to the injected script.
  564. s.referrerPolicy = getReferrerPolicy();
  565. });
  566. (document.head || document.documentElement).appendChild(s);
  567. s.remove();
  568. if (!s.dataset.jsEnabled) {
  569. cleanLinksWhenJsIsDisabled();
  570. if (!needsCspNonce) {
  571. needsCspNonce = true;
  572. // This is not Firefox, but the script was blocked. Perhaps a CSP
  573. // nonce is needed anyway.
  574. findScriptCspNonce(function() {
  575. if (scriptCspNonce) {
  576. setupAggresiveUglyLinkPreventer();
  577. }
  578. });
  579. }
  580. } else {
  581. // Scripts enabled (not blocked by CSP), run other inline scripts.
  582. blockTrackingBeacons();
  583. overwriteWindowOpen();
  584.  
  585. if (location.hostname === 'docs.google.com') {
  586. // Google Docs have simple non-JS interfaces where the ugly links
  587. // are hard-coded in the HTML. Remove them (#51).
  588. // https://docs.google.com/document/d/.../mobilebasic
  589. // https://docs.google.com/spreadsheets/d/.../htmlview
  590. cleanLinksWhenJsIsDisabled();
  591. }
  592. }
  593. }
  594.  
  595. // Block sendBeacon requests with destination /gen_204, because Google
  596. // asynchronously sends beacon requests in response to mouse events on links:
  597. // https://github.com/Rob--W/dont-track-me-google/issues/20
  598. //
  599. // This implementation also blocks other forms of tracking via gen_204 as a side
  600. // effect. That is not fully intentional, but given the lack of obvious ways to
  601. // discern such link-tracking events from others, I will block all of them.
  602. function blockTrackingBeacons() {
  603. var s = document.createElement('script');
  604. if (getScriptCspNonce()) {
  605. s.setAttribute('nonce', scriptCspNonce);
  606. }
  607. s.textContent = '(' + function() {
  608. var navProto = window.Navigator.prototype;
  609. var navProtoSendBeacon = navProto.sendBeacon;
  610. if (!navProtoSendBeacon) {
  611. return;
  612. }
  613. var sendBeacon = Function.prototype.apply.bind(navProtoSendBeacon);
  614.  
  615. // Blocks the following:
  616. // gen_204
  617. // /gen_204
  618. // https://www.google.com/gen_204
  619. var isTrackingUrl = RegExp.prototype.test.bind(
  620. /^(?:(?:https?:\/\/[^\/]+)?\/)?gen_204(?:[?#]|$)/);
  621.  
  622. navProto.sendBeacon = function(url, data) {
  623. if (isTrackingUrl(url) && isNoPingEnabled()) {
  624. // Lie that the data has been transmitted to avoid fallbacks.
  625. return true;
  626. }
  627. return sendBeacon(this, arguments);
  628. };
  629.  
  630. var currentScript = document.currentScript;
  631. var getElementId = Object.getOwnPropertyDescriptor(
  632. Element.prototype,
  633. 'id'
  634. ).get.bind(currentScript);
  635. function isNoPingEnabled() {
  636. try {
  637. return getElementId() !== '_dtmg_do_not_touch_ping';
  638. } catch (e) {
  639. return true;
  640. }
  641. }
  642. } + ')();';
  643. callImmediatelyAndOnPreferenceUpdate(function nopingChanged() {
  644. // Send the noping value to the injected script. The "id" property is
  645. // mirrored and can have an arbitrary (string) value, so we use that:
  646. s.id = noping ? '' : '_dtmg_do_not_touch_ping';
  647. });
  648. (document.head || document.documentElement).appendChild(s);
  649. s.remove();
  650. }
  651.  
  652. // Google sometimes uses window.open() to open ugly links.
  653. // https://github.com/Rob--W/dont-track-me-google/issues/18
  654. // https://github.com/Rob--W/dont-track-me-google/issues/41
  655. function overwriteWindowOpen() {
  656. var s = document.createElement('script');
  657. if (getScriptCspNonce()) {
  658. s.setAttribute('nonce', scriptCspNonce);
  659. }
  660. s.textContent = '(' + function() {
  661. var open = window.open;
  662. window.open = function(url, windowName, windowFeatures) {
  663. var isBlankUrl = !url || url === "about:blank";
  664. try {
  665. if (!isBlankUrl) {
  666. var a = document.createElement('a');
  667. // Triggers getRealLinkFromGoogleUrl via the href setter in
  668. // setupAggresiveUglyLinkPreventer.
  669. a.href = url;
  670. url = a.href;
  671. // The origin check exists to avoid adding "noreferrer" to
  672. // same-origin popups. That implies noopener and causes
  673. // https://github.com/Rob--W/dont-track-me-google/issues/43
  674. // And allow any Google domain to support auth popups:
  675. // https://github.com/Rob--W/dont-track-me-google/issues/45
  676. // And don't bother editing the list if it already contains
  677. // "opener" (it would be disabled by "noreferrer").
  678. if (a.referrerPolicy && a.origin !== location.origin &&
  679. !/\.google\.([a-z]+)$/.test(a.hostname) &&
  680. !/\bopener|noreferrer/.test(windowFeatures)) {
  681. if (windowFeatures) {
  682. windowFeatures += ',';
  683. } else {
  684. windowFeatures = '';
  685. }
  686. windowFeatures += 'noreferrer';
  687. }
  688. }
  689. } catch (e) {
  690. // Not expected to happen, but don't break callers if it does.
  691. }
  692. var win = open(url, windowName, windowFeatures);
  693. try {
  694. if (isBlankUrl && win) {
  695. // In Google Docs, sometimes a blank document is opened,
  696. // and document.write is used to insert a redirector.
  697. // https://github.com/Rob--W/dont-track-me-google/issues/41
  698. var doc = win.document;
  699. var docWrite = win.Function.prototype.call.bind(doc.write);
  700. doc.write = function(markup) {
  701. try {
  702. markup = fixupDocMarkup(markup);
  703. } catch (e) {
  704. // Not expected, but don't break callers otherwise.
  705. }
  706. return docWrite(this, markup);
  707. };
  708. }
  709. } catch (e) {
  710. // Not expected to happen, but don't break callers if it does.
  711. }
  712. return win;
  713. };
  714. function fixupDocMarkup(html) {
  715. html = html || '';
  716. html += '';
  717. return html.replace(
  718. /<meta [^>]*http-equiv=(["']?)refresh\1[^>]*>/i,
  719. function(m) {
  720. var doc = new DOMParser().parseFromString(m, 'text/html');
  721. var meta = doc.querySelector('meta[http-equiv=refresh]');
  722. return meta && fixupMetaUrl(meta) || m;
  723. });
  724. }
  725. function fixupMetaUrl(meta) {
  726. var parts = /^(\d*;\s*url=)(.+)$/i.exec(meta.content);
  727. if (!parts) {
  728. return;
  729. }
  730. var metaPrefix = parts[1];
  731. var url = parts[2];
  732. var a = document.createElement('a');
  733. // Triggers getRealLinkFromGoogleUrl via the href setter in
  734. // setupAggresiveUglyLinkPreventer.
  735. a.href = url;
  736. url = a.href;
  737. meta.content = metaPrefix + url;
  738.  
  739. var html = meta.outerHTML;
  740. if (a.referrerPolicy) {
  741. // Google appears to already append the no-referrer
  742. // meta tag, but add one just in case it doesn't.
  743. html = '<meta name="referrer" content="no-referrer">' + html;
  744. }
  745. return html;
  746. }
  747. } + ')();';
  748. (document.head || document.documentElement).appendChild(s);
  749. s.remove();
  750. }
  751.  
  752. function cleanLinksWhenJsIsDisabled() {
  753. // When JavaScript is disabled, Google sets the "href" attribute's value to
  754. // an ugly URL. Although the link is rewritten on click, we still need to
  755. // rewrite the link even earlier because otherwise the ugly URL is shown in
  756. // the tooltip upon hover.
  757.  
  758. if (document.readyState == 'complete') {
  759. cleanAllLinks();
  760. return;
  761. }
  762.  
  763. // When JS is disabled, the links won't change after the document finishes
  764. // loading. Until the DOM has finished loading, use the mouseover event to
  765. // beautify links (the DOMContentLoaded may be delayed on slow networks).
  766. document.addEventListener('mouseover', handleMouseOver);
  767. document.addEventListener('DOMContentLoaded', function() {
  768. document.removeEventListener('mouseover', handleMouseOver);
  769. cleanAllLinks();
  770. }, {once: true});
  771.  
  772. function cleanAllLinks() {
  773. var as = document.querySelectorAll('a[href]');
  774. for (var i = 0; i < as.length; ++i) {
  775. var href = getRealLinkFromGoogleUrl(as[i]);
  776. if (href) {
  777. as[i].href = href;
  778. }
  779. }
  780. }
  781.  
  782. function handleMouseOver(e) {
  783. var a = e.target;
  784. var href = a.href && getRealLinkFromGoogleUrl(a);
  785. if (href) {
  786. a.href = href;
  787. }
  788. }
  789. }
  790.  
  791. function getScriptCspNonce() {
  792. var scripts = document.querySelectorAll('script[nonce]');
  793. for (var i = 0; i < scripts.length && !scriptCspNonce; ++i) {
  794. scriptCspNonce = scripts[i].nonce;
  795. }
  796. return scriptCspNonce;
  797. }
  798.  
  799. function findScriptCspNonce(callback) {
  800. var timer;
  801. function checkDOM() {
  802. if (getScriptCspNonce() || document.readyState === 'complete') {
  803. document.removeEventListener('DOMContentLoaded', checkDOM, true);
  804. if (timer) {
  805. clearTimeout(timer);
  806. }
  807. callback();
  808. return;
  809. }
  810. timer = setTimeout(checkDOM, 50);
  811. }
  812. document.addEventListener('DOMContentLoaded', checkDOM, true);
  813. checkDOM();
  814. }
  815.  
  816. function newURL(href) {
  817. try {
  818. return new URL(href);
  819. } catch (e) {
  820. var a = document.createElement('a');
  821. a.href = href;
  822. return a;
  823. }
  824. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址