loader.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. module.metadata = {
  6. "stability": "unstable"
  7. };
  8. const { EventEmitter } = require('../deprecated/events');
  9. const { validateOptions } = require('../deprecated/api-utils');
  10. const { isValidURI, URL } = require('../url');
  11. const file = require('../io/file');
  12. const { contract } = require('../util/contract');
  13. const { isString, instanceOf } = require('../lang/type');
  14. const LOCAL_URI_SCHEMES = ['resource', 'data'];
  15. // Returns `null` if `value` is `null` or `undefined`, otherwise `value`.
  16. function ensureNull(value) value == null ? null : value
  17. // map of property validations
  18. const valid = {
  19. contentURL: {
  20. map: function(url) !url ? ensureNull(url) : url.toString(),
  21. is: ['undefined', 'null', 'string'],
  22. ok: function (url) {
  23. if (url === null)
  24. return true;
  25. return isValidURI(url);
  26. },
  27. msg: 'The `contentURL` option must be a valid URL.'
  28. },
  29. contentScriptFile: {
  30. is: ['undefined', 'null', 'string', 'array', 'object'],
  31. map: ensureNull,
  32. ok: function(value) {
  33. if (value === null)
  34. return true;
  35. value = [].concat(value);
  36. // Make sure every item is a string or an
  37. // URL instance, and also a local file URL.
  38. return value.every(function (item) {
  39. if (!isString(item) && !(item instanceof URL))
  40. return false;
  41. try {
  42. return ~LOCAL_URI_SCHEMES.indexOf(URL(item).scheme);
  43. }
  44. catch(e) {
  45. return false;
  46. }
  47. });
  48. },
  49. msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.'
  50. },
  51. contentScript: {
  52. is: ['undefined', 'null', 'string', 'array'],
  53. map: ensureNull,
  54. ok: function(value) {
  55. return !Array.isArray(value) || value.every(
  56. function(item) { return typeof item === 'string' }
  57. );
  58. },
  59. msg: 'The `contentScript` option must be a string or an array of strings.'
  60. },
  61. contentScriptWhen: {
  62. is: ['string'],
  63. ok: function(value) { return ~['start', 'ready', 'end'].indexOf(value) },
  64. map: function(value) {
  65. return value || 'end';
  66. },
  67. msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
  68. },
  69. contentScriptOptions: {
  70. ok: function(value) {
  71. if ( value === undefined ) { return true; }
  72. try { JSON.parse( JSON.stringify( value ) ); } catch(e) { return false; }
  73. return true;
  74. },
  75. map: function(value) 'undefined' === getTypeOf(value) ? null : value,
  76. msg: 'The contentScriptOptions should be a jsonable value.'
  77. }
  78. };
  79. exports.validationAttributes = valid;
  80. /**
  81. * Shortcut function to validate property with validation.
  82. * @param {Object|Number|String} suspect
  83. * value to validate
  84. * @param {Object} validation
  85. * validation rule passed to `api-utils`
  86. */
  87. function validate(suspect, validation) validateOptions(
  88. { $: suspect },
  89. { $: validation }
  90. ).$
  91. function Allow(script) ({
  92. get script() script,
  93. set script(value) script = !!value
  94. })
  95. /**
  96. * Trait is intended to be used in some composition. It provides set of core
  97. * properties and bounded validations to them. Trait is useful for all the
  98. * compositions providing high level APIs for interaction with content.
  99. * Property changes emit `"propertyChange"` events on instances.
  100. */
  101. const Loader = EventEmitter.compose({
  102. /**
  103. * Permissions for the content, with the following keys:
  104. * @property {Object} [allow = { script: true }]
  105. * @property {Boolean} [allow.script = true]
  106. * Whether or not to execute script in the content. Defaults to true.
  107. */
  108. get allow() this._allow || (this._allow = Allow(true)),
  109. set allow(value) this.allow.script = value && value.script,
  110. _allow: null,
  111. /**
  112. * The content to load. Either a string of HTML or a URL.
  113. * @type {String}
  114. */
  115. get contentURL() this._contentURL,
  116. set contentURL(value) {
  117. value = validate(value, valid.contentURL);
  118. if (this._contentURL != value) {
  119. this._emit('propertyChange', {
  120. contentURL: this._contentURL = value
  121. });
  122. }
  123. },
  124. _contentURL: null,
  125. /**
  126. * When to load the content scripts.
  127. * Possible values are "end" (default), which loads them once all page
  128. * contents have been loaded, "ready", which loads them once DOM nodes are
  129. * ready (ie like DOMContentLoaded event), and "start", which loads them once
  130. * the `window` object for the page has been created, but before any scripts
  131. * specified by the page have been loaded.
  132. * Property change emits `propertyChange` event on instance with this key
  133. * and new value.
  134. * @type {'start'|'ready'|'end'}
  135. */
  136. get contentScriptWhen() this._contentScriptWhen,
  137. set contentScriptWhen(value) {
  138. value = validate(value, valid.contentScriptWhen);
  139. if (value !== this._contentScriptWhen) {
  140. this._emit('propertyChange', {
  141. contentScriptWhen: this._contentScriptWhen = value
  142. });
  143. }
  144. },
  145. _contentScriptWhen: 'end',
  146. /**
  147. * Options avalaible from the content script as `self.options`.
  148. * The value of options can be of any type (object, array, string, etc.)
  149. * but only jsonable values will be available as frozen objects from the
  150. * content script.
  151. * Property change emits `propertyChange` event on instance with this key
  152. * and new value.
  153. * @type {Object}
  154. */
  155. get contentScriptOptions() this._contentScriptOptions,
  156. set contentScriptOptions(value) this._contentScriptOptions = value,
  157. _contentScriptOptions: null,
  158. /**
  159. * The URLs of content scripts.
  160. * Property change emits `propertyChange` event on instance with this key
  161. * and new value.
  162. * @type {String[]}
  163. */
  164. get contentScriptFile() this._contentScriptFile,
  165. set contentScriptFile(value) {
  166. value = validate(value, valid.contentScriptFile);
  167. if (value != this._contentScriptFile) {
  168. this._emit('propertyChange', {
  169. contentScriptFile: this._contentScriptFile = value
  170. });
  171. }
  172. },
  173. _contentScriptFile: null,
  174. /**
  175. * The texts of content script.
  176. * Property change emits `propertyChange` event on instance with this key
  177. * and new value.
  178. * @type {String|undefined}
  179. */
  180. get contentScript() this._contentScript,
  181. set contentScript(value) {
  182. value = validate(value, valid.contentScript);
  183. if (value != this._contentScript) {
  184. this._emit('propertyChange', {
  185. contentScript: this._contentScript = value
  186. });
  187. }
  188. },
  189. _contentScript: null
  190. });
  191. exports.Loader = Loader;
  192. exports.contract = contract(valid);