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 | /** |
79 | * Synchronously loads a script into the protected Closure context. |
80 | * @param {string} src Path to the file to load. |
81 | */ |
82 | function loadScript(src) { |
83 | src = path.normalize(src); |
84 | var contents = fs.readFileSync(src, 'utf8'); |
85 | vm.runInContext(contents, closure, src); |
86 | } |
87 | |
88 | |
89 | /** |
90 | * The protected context to host the Closure library. |
91 | * @type {!Object} |
92 | * @const |
93 | */ |
94 | var closure = vm.createContext({ |
95 | console: console, |
96 | setTimeout: setTimeout, |
97 | setInterval: setInterval, |
98 | clearTimeout: clearTimeout, |
99 | clearInterval: clearInterval, |
100 | process: process, |
101 | require: require, |
102 | Buffer: Buffer, |
103 | Error: Error, |
104 | CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/', |
105 | CLOSURE_IMPORT_SCRIPT: function(src) { |
106 | loadScript(src); |
107 | return true; |
108 | }, |
109 | CLOSURE_NO_DEPS: !isDevMode(), |
110 | goog: {} |
111 | }); |
112 | closure.window = closure; |
113 | |
114 | |
115 | loadScript(CLOSURE_BASE_FILE_PATH); |
116 | loadScript(DEPS_FILE_PATH); |
117 | |
118 | |
119 | /** |
120 | * Loads a symbol by name from the protected Closure context. |
121 | * @param {string} symbol The symbol to load. |
122 | * @return {?} The loaded symbol, or {@code null} if not found. |
123 | * @throws {Error} If the symbol has not been defined. |
124 | */ |
125 | function closureRequire(symbol) { |
126 | closure.goog.require(symbol); |
127 | return closure.goog.getObjectByName(symbol); |
128 | } |
129 | |
130 | |
131 | // PUBLIC API |
132 | |
133 | |
134 | /** |
135 | * Loads a symbol by name from the protected Closure context and exports its |
136 | * public API to the provided object. This function relies on Closure code |
137 | * conventions to define the public API of an object as those properties whose |
138 | * name does not end with "_". |
139 | * @param {string} symbol The symbol to load. This must resolve to an object. |
140 | * @return {!Object} An object with the exported API. |
141 | * @throws {Error} If the symbol has not been defined or does not resolve to |
142 | * an object. |
143 | */ |
144 | exports.exportPublicApi = function(symbol) { |
145 | var src = closureRequire(symbol); |
146 | if (typeof src != 'object' || src === null) { |
147 | throw Error('"' + symbol + '" must resolve to an object'); |
148 | } |
149 | |
150 | var dest = {}; |
151 | Object.keys(src).forEach(function(key) { |
152 | if (key[key.length - 1] != '_') { |
153 | dest[key] = src[key]; |
154 | } |
155 | }); |
156 | |
157 | return dest; |
158 | }; |
159 | |
160 | |
161 | if (isDevMode()) { |
162 | exports.closure = closure; |
163 | } |
164 | exports.isDevMode = isDevMode; |
165 | exports.require = closureRequire; |