1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/content/loader.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,207 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +module.metadata = { 1.11 + "stability": "unstable" 1.12 +}; 1.13 + 1.14 +const { EventEmitter } = require('../deprecated/events'); 1.15 +const { validateOptions } = require('../deprecated/api-utils'); 1.16 +const { isValidURI, URL } = require('../url'); 1.17 +const file = require('../io/file'); 1.18 +const { contract } = require('../util/contract'); 1.19 +const { isString, instanceOf } = require('../lang/type'); 1.20 + 1.21 +const LOCAL_URI_SCHEMES = ['resource', 'data']; 1.22 + 1.23 +// Returns `null` if `value` is `null` or `undefined`, otherwise `value`. 1.24 +function ensureNull(value) value == null ? null : value 1.25 + 1.26 +// map of property validations 1.27 +const valid = { 1.28 + contentURL: { 1.29 + map: function(url) !url ? ensureNull(url) : url.toString(), 1.30 + is: ['undefined', 'null', 'string'], 1.31 + ok: function (url) { 1.32 + if (url === null) 1.33 + return true; 1.34 + return isValidURI(url); 1.35 + }, 1.36 + msg: 'The `contentURL` option must be a valid URL.' 1.37 + }, 1.38 + contentScriptFile: { 1.39 + is: ['undefined', 'null', 'string', 'array', 'object'], 1.40 + map: ensureNull, 1.41 + ok: function(value) { 1.42 + if (value === null) 1.43 + return true; 1.44 + 1.45 + value = [].concat(value); 1.46 + 1.47 + // Make sure every item is a string or an 1.48 + // URL instance, and also a local file URL. 1.49 + return value.every(function (item) { 1.50 + 1.51 + if (!isString(item) && !(item instanceof URL)) 1.52 + return false; 1.53 + 1.54 + try { 1.55 + return ~LOCAL_URI_SCHEMES.indexOf(URL(item).scheme); 1.56 + } 1.57 + catch(e) { 1.58 + return false; 1.59 + } 1.60 + }); 1.61 + 1.62 + }, 1.63 + msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.' 1.64 + }, 1.65 + contentScript: { 1.66 + is: ['undefined', 'null', 'string', 'array'], 1.67 + map: ensureNull, 1.68 + ok: function(value) { 1.69 + return !Array.isArray(value) || value.every( 1.70 + function(item) { return typeof item === 'string' } 1.71 + ); 1.72 + }, 1.73 + msg: 'The `contentScript` option must be a string or an array of strings.' 1.74 + }, 1.75 + contentScriptWhen: { 1.76 + is: ['string'], 1.77 + ok: function(value) { return ~['start', 'ready', 'end'].indexOf(value) }, 1.78 + map: function(value) { 1.79 + return value || 'end'; 1.80 + }, 1.81 + msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".' 1.82 + }, 1.83 + contentScriptOptions: { 1.84 + ok: function(value) { 1.85 + if ( value === undefined ) { return true; } 1.86 + try { JSON.parse( JSON.stringify( value ) ); } catch(e) { return false; } 1.87 + return true; 1.88 + }, 1.89 + map: function(value) 'undefined' === getTypeOf(value) ? null : value, 1.90 + msg: 'The contentScriptOptions should be a jsonable value.' 1.91 + } 1.92 +}; 1.93 +exports.validationAttributes = valid; 1.94 + 1.95 +/** 1.96 + * Shortcut function to validate property with validation. 1.97 + * @param {Object|Number|String} suspect 1.98 + * value to validate 1.99 + * @param {Object} validation 1.100 + * validation rule passed to `api-utils` 1.101 + */ 1.102 +function validate(suspect, validation) validateOptions( 1.103 + { $: suspect }, 1.104 + { $: validation } 1.105 +).$ 1.106 + 1.107 +function Allow(script) ({ 1.108 + get script() script, 1.109 + set script(value) script = !!value 1.110 +}) 1.111 + 1.112 +/** 1.113 + * Trait is intended to be used in some composition. It provides set of core 1.114 + * properties and bounded validations to them. Trait is useful for all the 1.115 + * compositions providing high level APIs for interaction with content. 1.116 + * Property changes emit `"propertyChange"` events on instances. 1.117 + */ 1.118 +const Loader = EventEmitter.compose({ 1.119 + /** 1.120 + * Permissions for the content, with the following keys: 1.121 + * @property {Object} [allow = { script: true }] 1.122 + * @property {Boolean} [allow.script = true] 1.123 + * Whether or not to execute script in the content. Defaults to true. 1.124 + */ 1.125 + get allow() this._allow || (this._allow = Allow(true)), 1.126 + set allow(value) this.allow.script = value && value.script, 1.127 + _allow: null, 1.128 + /** 1.129 + * The content to load. Either a string of HTML or a URL. 1.130 + * @type {String} 1.131 + */ 1.132 + get contentURL() this._contentURL, 1.133 + set contentURL(value) { 1.134 + value = validate(value, valid.contentURL); 1.135 + if (this._contentURL != value) { 1.136 + this._emit('propertyChange', { 1.137 + contentURL: this._contentURL = value 1.138 + }); 1.139 + } 1.140 + }, 1.141 + _contentURL: null, 1.142 + /** 1.143 + * When to load the content scripts. 1.144 + * Possible values are "end" (default), which loads them once all page 1.145 + * contents have been loaded, "ready", which loads them once DOM nodes are 1.146 + * ready (ie like DOMContentLoaded event), and "start", which loads them once 1.147 + * the `window` object for the page has been created, but before any scripts 1.148 + * specified by the page have been loaded. 1.149 + * Property change emits `propertyChange` event on instance with this key 1.150 + * and new value. 1.151 + * @type {'start'|'ready'|'end'} 1.152 + */ 1.153 + get contentScriptWhen() this._contentScriptWhen, 1.154 + set contentScriptWhen(value) { 1.155 + value = validate(value, valid.contentScriptWhen); 1.156 + if (value !== this._contentScriptWhen) { 1.157 + this._emit('propertyChange', { 1.158 + contentScriptWhen: this._contentScriptWhen = value 1.159 + }); 1.160 + } 1.161 + }, 1.162 + _contentScriptWhen: 'end', 1.163 + /** 1.164 + * Options avalaible from the content script as `self.options`. 1.165 + * The value of options can be of any type (object, array, string, etc.) 1.166 + * but only jsonable values will be available as frozen objects from the 1.167 + * content script. 1.168 + * Property change emits `propertyChange` event on instance with this key 1.169 + * and new value. 1.170 + * @type {Object} 1.171 + */ 1.172 + get contentScriptOptions() this._contentScriptOptions, 1.173 + set contentScriptOptions(value) this._contentScriptOptions = value, 1.174 + _contentScriptOptions: null, 1.175 + /** 1.176 + * The URLs of content scripts. 1.177 + * Property change emits `propertyChange` event on instance with this key 1.178 + * and new value. 1.179 + * @type {String[]} 1.180 + */ 1.181 + get contentScriptFile() this._contentScriptFile, 1.182 + set contentScriptFile(value) { 1.183 + value = validate(value, valid.contentScriptFile); 1.184 + if (value != this._contentScriptFile) { 1.185 + this._emit('propertyChange', { 1.186 + contentScriptFile: this._contentScriptFile = value 1.187 + }); 1.188 + } 1.189 + }, 1.190 + _contentScriptFile: null, 1.191 + /** 1.192 + * The texts of content script. 1.193 + * Property change emits `propertyChange` event on instance with this key 1.194 + * and new value. 1.195 + * @type {String|undefined} 1.196 + */ 1.197 + get contentScript() this._contentScript, 1.198 + set contentScript(value) { 1.199 + value = validate(value, valid.contentScript); 1.200 + if (value != this._contentScript) { 1.201 + this._emit('propertyChange', { 1.202 + contentScript: this._contentScript = value 1.203 + }); 1.204 + } 1.205 + }, 1.206 + _contentScript: null 1.207 +}); 1.208 +exports.Loader = Loader; 1.209 + 1.210 +exports.contract = contract(valid);