gm-api-wrapper.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import bridge from './bridge';
  2. import { makeGmApi } from './gm-api';
  3. import { makeGlobalWrapper } from './gm-global-wrapper';
  4. import { makeComponentUtils, safeConcat } from './util-web';
  5. /** Name in Greasemonkey4 -> name in GM */
  6. const GM4_ALIAS = {
  7. __proto__: null,
  8. getResourceUrl: 'getResourceURL',
  9. xmlHttpRequest: 'xmlhttpRequest',
  10. };
  11. const GM4_ASYNC = {
  12. __proto__: null,
  13. getResourceUrl: 1,
  14. getValue: 1,
  15. deleteValue: 1,
  16. setValue: 1,
  17. listValues: 1,
  18. };
  19. let gmApi;
  20. let componentUtils;
  21. /**
  22. * @param {VMScript & VMInjectedScript} script
  23. * @returns {Object}
  24. */
  25. export function makeGmApiWrapper(script) {
  26. // Add GM functions
  27. // Reference: http://wiki.greasespot.net/Greasemonkey_Manual:API
  28. const { meta } = script;
  29. const grant = meta.grant;
  30. let wrapper;
  31. let numGrants = grant.length;
  32. if (numGrants === 1 && grant[0] === 'none') {
  33. numGrants = 0;
  34. grant.length = 0;
  35. }
  36. const { id } = script.props;
  37. const resources = assign(createNullObj(), meta.resources);
  38. /** @namespace VMInjectedScript.Context */
  39. const context = {
  40. id,
  41. script,
  42. resources,
  43. dataKey: script.dataKey,
  44. resCache: createNullObj(),
  45. };
  46. const gmInfo = makeGmInfo(script, resources);
  47. const gm = {
  48. __proto__: null,
  49. GM: {
  50. __proto__: null,
  51. info: gmInfo,
  52. },
  53. GM_info: gmInfo,
  54. unsafeWindow: global,
  55. };
  56. if (!componentUtils) {
  57. componentUtils = makeComponentUtils();
  58. }
  59. assign(gm, componentUtils);
  60. if (grant::indexOf(WINDOW_CLOSE) >= 0) {
  61. gm.close = vmOwnFunc(() => bridge.post('TabClose', 0, context));
  62. }
  63. if (grant::indexOf(WINDOW_FOCUS) >= 0) {
  64. gm.focus = vmOwnFunc(() => bridge.post('TabFocus', 0, context));
  65. }
  66. if (!gmApi && numGrants) gmApi = makeGmApi();
  67. grant::forEach((name) => {
  68. const gm4name = name::slice(0, 3) === 'GM.' && name::slice(3);
  69. const fn = gmApi[gm4name ? `GM_${GM4_ALIAS[gm4name] || gm4name}` : name];
  70. if (fn) {
  71. if (gm4name) {
  72. gm.GM[gm4name] = makeGmMethodCaller(fn, context, GM4_ASYNC[gm4name]);
  73. } else {
  74. gm[name] = makeGmMethodCaller(fn, context);
  75. }
  76. }
  77. });
  78. if (numGrants) {
  79. wrapper = makeGlobalWrapper(gm);
  80. /* Exposing the fast cache of resolved properties,
  81. * using a name that'll never be added to the web platform */
  82. gm.c = gm;
  83. }
  84. return { gm, wrapper };
  85. }
  86. function makeGmInfo(script, resources) {
  87. // TODO: move into background.js
  88. const { meta } = script;
  89. const { ua } = bridge;
  90. /* Making a copy with a standard Object prototype.
  91. * Not using assign({}, obj) because it can be spoofed/broken via Object prototype.
  92. * Not using JSON.stringify+parse as it calls toJSON which may break arrays inside. */
  93. const metaCopy = {};
  94. const uaCopy = {};
  95. objectKeys(ua)::forEach(key => {
  96. setOwnProp(uaCopy, key, ua[key]);
  97. });
  98. let val;
  99. objectKeys(meta)::forEach((key) => {
  100. val = meta[key];
  101. switch (key) {
  102. case 'match': // -> matches
  103. case 'excludeMatch': // -> excludeMatches
  104. key += 'e';
  105. // fallthrough
  106. case 'exclude': // -> excludes
  107. case 'include': // -> includes
  108. key += 's';
  109. val = safeConcat(val);
  110. break;
  111. default:
  112. }
  113. setOwnProp(metaCopy, key, val);
  114. });
  115. [
  116. 'description',
  117. 'name',
  118. 'namespace',
  119. 'runAt',
  120. 'version',
  121. ]::forEach((key) => {
  122. if (!getOwnProp(metaCopy, key)) setOwnProp(metaCopy, key, '');
  123. });
  124. val = objectKeys(resources);
  125. val::forEach((name, i) => {
  126. val[i] = { name, url: resources[name] };
  127. });
  128. setOwnProp(metaCopy, 'resources', val);
  129. return {
  130. // No __proto__:null because it's a standard object for userscripts
  131. uuid: script.props.uuid,
  132. scriptMetaStr: script.metaStr,
  133. scriptWillUpdate: !!script.config.shouldUpdate,
  134. scriptHandler: 'Violentmonkey',
  135. version: process.env.VM_VER,
  136. injectInto: bridge.mode,
  137. platform: uaCopy,
  138. script: metaCopy,
  139. };
  140. }
  141. function makeGmMethodCaller(gmMethod, context, isAsync) {
  142. // keeping the native console.log intact
  143. return gmMethod === gmApi.GM_log ? gmMethod : vmOwnFunc(
  144. isAsync
  145. ? (async (...args) => gmMethod::apply(context, args))
  146. : gmMethod::bind(context),
  147. );
  148. }