polyfill.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. 'use strict';
  2. (() => {
  3. /* Chrome reinjects content script when documentElement is replaced so we ignore it
  4. by checking against a literal `1`, not just `if (truthy)`, because <html id="INJECTED">
  5. is exposed per HTML spec as a global `window.INJECTED` */
  6. if (window.INJECTED === 1) return;
  7. //#region for content scripts and our extension pages
  8. if (!((window.browser || {}).runtime || {}).sendMessage) {
  9. /* Auto-promisifier with a fallback to direct call on signature error.
  10. The fallback isn't used now since we call all synchronous methods via `chrome` */
  11. const directEvents = ['addListener', 'removeListener', 'hasListener', 'hasListeners'];
  12. // generated by tools/chrome-api-no-cb.js
  13. const directMethods = {
  14. alarms: ['create'],
  15. extension: ['getBackgroundPage', 'getExtensionTabs', 'getURL', 'getViews', 'setUpdateUrlData'],
  16. i18n: ['getMessage', 'getUILanguage'],
  17. identity: ['getRedirectURL'],
  18. runtime: ['connect', 'connectNative', 'getManifest', 'getURL', 'reload', 'restart'],
  19. tabs: ['connect'],
  20. };
  21. const promisify = function (fn, ...args) {
  22. let res;
  23. try {
  24. let resolve, reject;
  25. /* Some callbacks have 2 parameters so we're resolving as an array in that case.
  26. For example, chrome.runtime.requestUpdateCheck and chrome.webRequest.onAuthRequired */
  27. args.push((...results) =>
  28. chrome.runtime.lastError ?
  29. reject(new Error(chrome.runtime.lastError.message)) :
  30. resolve(results.length <= 1 ? results[0] : results));
  31. fn.apply(this, args);
  32. res = new Promise((...rr) => ([resolve, reject] = rr));
  33. } catch (err) {
  34. if (!err.message.includes('No matching signature')) {
  35. throw err;
  36. }
  37. args.pop();
  38. res = fn.apply(this, args);
  39. }
  40. return res;
  41. };
  42. const proxify = (src, srcName, target, key) => {
  43. let res = src[key];
  44. if (res && typeof res === 'object') {
  45. res = createProxy(res, key); // eslint-disable-line no-use-before-define
  46. } else if (typeof res === 'function') {
  47. res = (directMethods[srcName] || directEvents).includes(key)
  48. ? res.bind(src)
  49. : promisify.bind(src, res);
  50. }
  51. target[key] = res;
  52. return res;
  53. };
  54. const createProxy = (src, srcName) =>
  55. new Proxy({}, {
  56. get(target, key) {
  57. return target[key] || proxify(src, srcName, target, key);
  58. },
  59. });
  60. window.browser = createProxy(chrome);
  61. }
  62. //#endregion
  63. if (!chrome.tabs) return;
  64. //#region for our extension pages
  65. const reqPromise = {};
  66. window.require = async function require(urls, cb) {
  67. const promises = [];
  68. const all = [];
  69. const toLoad = [];
  70. for (let url of Array.isArray(urls) ? urls : [urls]) {
  71. const isCss = url.endsWith('.css');
  72. const tag = isCss ? 'link' : 'script';
  73. const attr = isCss ? 'href' : 'src';
  74. if (!isCss && !url.endsWith('.js')) url += '.js';
  75. if (url.startsWith('/')) url = url.slice(1);
  76. let el = document.head.querySelector(`${tag}[${attr}$="${url}"]`);
  77. if (!el) {
  78. el = document.createElement(tag);
  79. toLoad.push(el);
  80. reqPromise[url] = new Promise((resolve, reject) => {
  81. el.onload = resolve;
  82. el.onerror = reject;
  83. el[attr] = url;
  84. if (isCss) el.rel = 'stylesheet';
  85. }).catch(console.warn);
  86. }
  87. promises.push(reqPromise[url]);
  88. all.push(el);
  89. }
  90. if (toLoad.length) document.head.append(...toLoad);
  91. if (promises.length) await Promise.all(promises);
  92. if (cb) cb(...all);
  93. return all[0];
  94. };
  95. if (!(new URLSearchParams({foo: 1})).get('foo')) {
  96. // TODO: remove when minimum_chrome_version >= 61
  97. window.URLSearchParams = class extends URLSearchParams {
  98. constructor(init) {
  99. if (init && typeof init === 'object') {
  100. super();
  101. for (const [key, val] of Object.entries(init)) {
  102. this.set(key, val);
  103. }
  104. } else {
  105. super(...arguments);
  106. }
  107. }
  108. };
  109. }
  110. //#endregion
  111. })();