navigation-manager.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /* global CHROME FIREFOX URLS deepEqual ignoreChromeError */// toolbox.js
  2. /* global bgReady */// common.js
  3. /* global msg */
  4. 'use strict';
  5. /* exported navMan */
  6. const navMan = (() => {
  7. const listeners = new Set();
  8. let prevData = {};
  9. chrome.webNavigation.onCommitted.addListener(onNavigation.bind('committed'));
  10. chrome.webNavigation.onHistoryStateUpdated.addListener(onFakeNavigation.bind('history'));
  11. chrome.webNavigation.onReferenceFragmentUpdated.addListener(onFakeNavigation.bind('hash'));
  12. return {
  13. /** @param {function(data: Object, type: ('committed'|'history'|'hash'))} fn */
  14. onUrlChange(fn) {
  15. listeners.add(fn);
  16. },
  17. };
  18. /** @this {string} type */
  19. async function onNavigation(data) {
  20. if (CHROME && data.timeStamp === prevData.timeStamp && deepEqual(data, prevData)) {
  21. return; // Chrome bug: listener is called twice with identical data
  22. }
  23. prevData = data;
  24. if (CHROME &&
  25. URLS.chromeProtectsNTP &&
  26. data.url.startsWith('https://www.google.') &&
  27. data.url.includes('/_/chrome/newtab?')) {
  28. // Modern Chrome switched to WebUI NTP so this is obsolete, but there may be exceptions
  29. // TODO: investigate, and maybe use a separate listener for CHROME <= ver
  30. const tab = await browser.tabs.get(data.tabId);
  31. const url = tab.pendingUrl || tab.url;
  32. if (url === 'chrome://newtab/') {
  33. data.url = url;
  34. }
  35. }
  36. listeners.forEach(fn => fn(data, this));
  37. }
  38. /** @this {string} type */
  39. function onFakeNavigation(data) {
  40. const {url, frameId} = data;
  41. onNavigation.call(this, data);
  42. msg.sendTab(data.tabId, {method: 'urlChanged', url}, {frameId})
  43. .catch(msg.ignoreError);
  44. }
  45. })();
  46. bgReady.all.then(() => {
  47. /*
  48. * Expose style version on greasyfork/sleazyfork 1) info page and 2) code page
  49. * Not using manifest.json as adding a content script disables the extension on update.
  50. */
  51. const urlMatches = '/scripts/\\d+[^/]*(/code)?([?#].*)?$';
  52. chrome.webNavigation.onCommitted.addListener(({tabId}) => {
  53. chrome.tabs.executeScript(tabId, {
  54. file: '/content/install-hook-greasyfork.js',
  55. runAt: 'document_start',
  56. });
  57. }, {
  58. url: [
  59. {hostEquals: 'greasyfork.org', urlMatches},
  60. {hostEquals: 'sleazyfork.org', urlMatches},
  61. ],
  62. });
  63. /*
  64. * Removes the Get Stylus button on style pages.
  65. * Not using manifest.json as adding a content script disables the extension on update.
  66. */
  67. chrome.webNavigation.onCommitted.addListener(({tabId}) => {
  68. chrome.tabs.executeScript(tabId, {
  69. file: '/content/install-hook-userstylesworld.js',
  70. runAt: 'document_start',
  71. });
  72. }, {
  73. url: [
  74. {hostEquals: 'userstyles.world'},
  75. ],
  76. });
  77. /*
  78. * FF misses some about:blank iframes so we inject our content script explicitly
  79. */
  80. if (FIREFOX) {
  81. chrome.webNavigation.onDOMContentLoaded.addListener(async ({tabId, frameId}) => {
  82. if (frameId &&
  83. !await msg.sendTab(tabId, {method: 'ping'}, {frameId}).catch(ignoreChromeError)) {
  84. for (const file of chrome.runtime.getManifest().content_scripts[0].js) {
  85. chrome.tabs.executeScript(tabId, {
  86. frameId,
  87. file,
  88. matchAboutBlank: true,
  89. }, ignoreChromeError);
  90. }
  91. }
  92. }, {
  93. url: [{urlEquals: 'about:blank'}],
  94. });
  95. }
  96. });