gm4-polyfill.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /*
  2. This helper script bridges compatibility between the Greasemonkey 4 APIs and
  3. existing/legacy APIs. Say for example your user script includes
  4. // @grant GM_getValue
  5. And you'd like to be compatible with both Greasemonkey 3 and Greasemonkey 4
  6. (and for that matter all versions of Violentmonkey, Tampermonkey, and any other
  7. user script engine). Add:
  8. // @grant GM.getValue
  9. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  10. And switch to the new (GM-dot) APIs, which return promises. If your script
  11. is running in an engine that does not provide the new asynchronous APIs, this
  12. helper will add them, based on the old APIs.
  13. If you use `await` at the top level, you'll need to wrap your script in an
  14. `async` function to be compatible with any user script engine besides
  15. Greasemonkey 4.
  16. (async () => {
  17. let x = await GM.getValue('x');
  18. })();
  19. */
  20. if (typeof GM == 'undefined') {
  21. this.GM = {};
  22. }
  23. if (typeof GM_addStyle == 'undefined') {
  24. this.GM_addStyle = (aCss) => {
  25. 'use strict';
  26. let head = document.getElementsByTagName('head')[0];
  27. if (head) {
  28. let style = document.createElement('style');
  29. style.setAttribute('type', 'text/css');
  30. style.textContent = aCss;
  31. head.appendChild(style);
  32. return style;
  33. }
  34. return null;
  35. };
  36. }
  37. if (typeof GM_registerMenuCommand == 'undefined') {
  38. this.GM_registerMenuCommand = (caption, commandFunc, accessKey) => {
  39. if (!document.body) {
  40. if (document.readyState === 'loading'
  41. && document.documentElement && document.documentElement.localName === 'html') {
  42. new MutationObserver((mutations, observer) => {
  43. if (document.body) {
  44. observer.disconnect();
  45. GM_registerMenuCommand(caption, commandFunc, accessKey);
  46. }
  47. }).observe(document.documentElement, {childList: true});
  48. } else {
  49. console.error('GM_registerMenuCommand got no body.');
  50. }
  51. return;
  52. }
  53. let contextMenu = document.body.getAttribute('contextmenu');
  54. let menu = (contextMenu ? document.querySelector('menu#' + contextMenu) : null);
  55. if (!menu) {
  56. menu = document.createElement('menu');
  57. menu.setAttribute('id', 'gm-registered-menu');
  58. menu.setAttribute('type', 'context');
  59. document.body.appendChild(menu);
  60. document.body.setAttribute('contextmenu', 'gm-registered-menu');
  61. }
  62. let menuItem = document.createElement('menuitem');
  63. menuItem.textContent = caption;
  64. menuItem.addEventListener('click', commandFunc, true);
  65. menu.appendChild(menuItem);
  66. };
  67. }
  68. if (typeof GM_getResourceText == 'undefined') {
  69. this.GM_getResourceText = (aRes) => {
  70. 'use strict';
  71. return GM.getResourceUrl(aRes)
  72. .then(url => fetch(url))
  73. .then(resp => resp.text())
  74. .catch(function(error) {
  75. GM.log('Request failed', error);
  76. return null;
  77. });
  78. };
  79. }
  80. Object.entries({
  81. 'log': console.log.bind(console), // Pale Moon compatibility. See #13.
  82. 'info': GM_info,
  83. }).forEach(([newKey, old]) => {
  84. if (old && (typeof GM[newKey] == 'undefined')) {
  85. GM[newKey] = old;
  86. }
  87. });
  88. Object.entries({
  89. 'GM_addStyle': 'addStyle',
  90. 'GM_deleteValue': 'deleteValue',
  91. 'GM_getResourceURL': 'getResourceUrl',
  92. 'GM_getValue': 'getValue',
  93. 'GM_listValues': 'listValues',
  94. 'GM_notification': 'notification',
  95. 'GM_openInTab': 'openInTab',
  96. 'GM_registerMenuCommand': 'registerMenuCommand',
  97. 'GM_setClipboard': 'setClipboard',
  98. 'GM_setValue': 'setValue',
  99. 'GM_xmlhttpRequest': 'xmlHttpRequest',
  100. 'GM_getResourceText': 'getResourceText',
  101. }).forEach(([oldKey, newKey]) => {
  102. let old = this[oldKey];
  103. if (old && (typeof GM[newKey] == 'undefined')) {
  104. GM[newKey] = function(...args) {
  105. return new Promise((resolve, reject) => {
  106. try {
  107. resolve(old.apply(this, args));
  108. } catch (e) {
  109. reject(e);
  110. }
  111. });
  112. };
  113. }
  114. });