1 | // Copyright 2012 Selenium committers |
2 | // Copyright 2012 Software Freedom Conservancy |
3 | // |
4 | // Licensed under the Apache License, Version 2.0 (the "License"); |
5 | // you may not use this file except in compliance with the License. |
6 | // You may obtain a copy of the License at |
7 | // |
8 | // http://www.apache.org/licenses/LICENSE-2.0 |
9 | // |
10 | // Unless required by applicable law or agreed to in writing, software |
11 | // distributed under the License is distributed on an "AS IS" BASIS, |
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | // See the License for the specific language governing permissions and |
14 | // limitations under the License. |
15 | |
16 | /** |
17 | * @fileoverview The base module responsible for bootstrapping the Closure |
18 | * library and providing a means of loading Closure-based modules. |
19 | * |
20 | * <p>Each script loaded by this module will be granted access to this module's |
21 | * {@code require} function; all required non-native modules must be specified |
22 | * relative to <em>this</em> module. |
23 | * |
24 | * <p>This module will load all scripts from the "lib" subdirectory, unless the |
25 | * SELENIUM_DEV_MODE environment variable has been set to 1, in which case all |
26 | * scripts will be loaded from the Selenium client containing this script. |
27 | */ |
28 | |
29 | 'use strict'; |
30 | |
31 | var fs = require('fs'), |
32 | path = require('path'), |
33 | vm = require('vm'); |
34 | |
35 | |
36 | /** |
37 | * If this script was loaded from the Selenium project repo, it will operate in |
38 | * development mode, adjusting how it loads Closure-based dependencies. |
39 | * @type {boolean} |
40 | */ |
41 | var devMode = (function() { |
42 | var buildDescFile = path.join(__dirname, '..', 'build.desc'); |
43 | return fs.existsSync(buildDescFile); |
44 | })(); |
45 | |
46 | |
47 | /** @return {boolean} Whether this script was loaded in dev mode. */ |
48 | function isDevMode() { |
49 | return devMode; |
50 | } |
51 | |
52 | |
53 | /** |
54 | * @type {string} Path to Closure's base file, relative to this module. |
55 | * @const |
56 | */ |
57 | var CLOSURE_BASE_FILE_PATH = (function() { |
58 | var relativePath = isDevMode() ? |
59 | '../../../third_party/closure/goog/base.js' : |
60 | './lib/goog/base.js'; |
61 | return path.join(__dirname, relativePath); |
62 | })(); |
63 | |
64 | |
65 | /** |
66 | * @type {string} Path to Closure's base file, relative to this module. |
67 | * @const |
68 | */ |
69 | var DEPS_FILE_PATH = (function() { |
70 | var relativePath = isDevMode() ? |
71 | '../../../javascript/deps.js' : |
72 | './lib/goog/deps.js'; |
73 | return path.join(__dirname, relativePath); |
74 | })(); |
75 | |
76 | |
77 | /** |
78 | * Maintains a unique context for Closure library-based code. |
79 | * @param {boolean=} opt_configureForTesting Whether to configure a fake DOM |
80 | * for Closure-testing code that (incorrectly) assumes a DOM is always |
81 | * present. |
82 | * @constructor |
83 | */ |
84 | function Context(opt_configureForTesting) { |
85 | var closure = this.closure = vm.createContext({ |
86 | console: console, |
87 | setTimeout: setTimeout, |
88 | setInterval: setInterval, |
89 | clearTimeout: clearTimeout, |
90 | clearInterval: clearInterval, |
91 | process: process, |
92 | require: require, |
93 | Buffer: Buffer, |
94 | Error: Error, |
95 | CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/', |
96 | CLOSURE_IMPORT_SCRIPT: function(src) { |
97 | loadScript(src); |
98 | return true; |
99 | }, |
100 | CLOSURE_NO_DEPS: !isDevMode(), |
101 | goog: {} |
102 | }); |
103 | closure.window = closure.top = closure; |
104 | |
105 | if (opt_configureForTesting) { |
106 | closure.document = { |
107 | body: {}, |
108 | createElement: function() { return {}; }, |
109 | getElementsByTagName: function() { return []; } |
110 | }; |
111 | closure.document.body.ownerDocument = closure.document; |
112 | } |
113 | |
114 | loadScript(CLOSURE_BASE_FILE_PATH); |
115 | loadScript(DEPS_FILE_PATH); |
116 | |
117 | /** |
118 | * Synchronously loads a script into the protected Closure context. |
119 | * @param {string} src Path to the file to load. |
120 | */ |
121 | function loadScript(src) { |
122 | src = path.normalize(src); |
123 | var contents = fs.readFileSync(src, 'utf8'); |
124 | vm.runInContext(contents, closure, src); |
125 | } |
126 | } |
127 | |
128 | |
129 | var context = new Context(); |
130 | |
131 | |
132 | /** |
133 | * Loads a symbol by name from the protected Closure context. |
134 | * @param {string} symbol The symbol to load. |
135 | * @return {?} The loaded symbol, or {@code null} if not found. |
136 | * @throws {Error} If the symbol has not been defined. |
137 | */ |
138 | function closureRequire(symbol) { |
139 | context.closure.goog.require(symbol); |
140 | return context.closure.goog.getObjectByName(symbol); |
141 | } |
142 | |
143 | |
144 | // PUBLIC API |
145 | |
146 | |
147 | /** |
148 | * Loads a symbol by name from the protected Closure context and exports its |
149 | * public API to the provided object. This function relies on Closure code |
150 | * conventions to define the public API of an object as those properties whose |
151 | * name does not end with "_". |
152 | * @param {string} symbol The symbol to load. This must resolve to an object. |
153 | * @return {!Object} An object with the exported API. |
154 | * @throws {Error} If the symbol has not been defined or does not resolve to |
155 | * an object. |
156 | */ |
157 | exports.exportPublicApi = function(symbol) { |
158 | var src = closureRequire(symbol); |
159 | if (typeof src != 'object' || src === null) { |
160 | throw Error('"' + symbol + '" must resolve to an object'); |
161 | } |
162 | |
163 | var dest = {}; |
164 | Object.keys(src).forEach(function(key) { |
165 | if (key[key.length - 1] != '_') { |
166 | dest[key] = src[key]; |
167 | } |
168 | }); |
169 | |
170 | return dest; |
171 | }; |
172 | |
173 | |
174 | if (isDevMode()) { |
175 | exports.closure = context.closure; |
176 | } |
177 | exports.Context = Context; |
178 | exports.isDevMode = isDevMode; |
179 | exports.require = closureRequire; |