_base.js

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
31var 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 */
41var 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. */
48function isDevMode() {
49 return devMode;
50}
51
52
53/**
54 * @type {string} Path to Closure's base file, relative to this module.
55 * @const
56 */
57var 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 */
69var 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 */
84function 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 TypeError: TypeError,
96 CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/',
97 CLOSURE_IMPORT_SCRIPT: function(src, opt_srcText) {
98 if (opt_srcText !== undefined) {
99 vm.runInContext(opt_srcText, closure, src);
100 } else {
101 loadScript(src);
102 }
103 return true;
104 },
105 CLOSURE_NO_DEPS: !isDevMode(),
106 goog: {}
107 });
108 closure.window = closure.top = closure;
109
110 if (opt_configureForTesting) {
111 closure.document = {
112 body: {},
113 createElement: function() { return {}; },
114 getElementsByTagName: function() { return []; }
115 };
116 closure.document.body.ownerDocument = closure.document;
117 }
118
119 loadScript(CLOSURE_BASE_FILE_PATH);
120 loadScript(DEPS_FILE_PATH);
121
122 // Redefine retrieveAndExecModule_ to load modules. Closure's version
123 // assumes XMLHttpRequest is defined (and by extension that scripts
124 // are being loaded from a server).
125 closure.goog.retrieveAndExecModule_ = function(src) {
126 var normalizedSrc = path.normalize(src);
127 var contents = fs.readFileSync(normalizedSrc, 'utf8');
128 contents = closure.goog.wrapModule_(src, contents);
129 vm.runInContext(contents, closure, normalizedSrc);
130 };
131
132 /**
133 * Synchronously loads a script into the protected Closure context.
134 * @param {string} src Path to the file to load.
135 */
136 function loadScript(src) {
137 src = path.normalize(src);
138 var contents = fs.readFileSync(src, 'utf8');
139 vm.runInContext(contents, closure, src);
140 }
141}
142
143
144var context = new Context();
145
146
147/**
148 * Loads a symbol by name from the protected Closure context.
149 * @param {string} symbol The symbol to load.
150 * @return {?} The loaded symbol, or {@code null} if not found.
151 * @throws {Error} If the symbol has not been defined.
152 */
153function closureRequire(symbol) {
154 context.closure.goog.require(symbol);
155 return context.closure.goog.getObjectByName(symbol);
156}
157
158
159// PUBLIC API
160
161
162/**
163 * Loads a symbol by name from the protected Closure context and exports its
164 * public API to the provided object. This function relies on Closure code
165 * conventions to define the public API of an object as those properties whose
166 * name does not end with "_".
167 * @param {string} symbol The symbol to load. This must resolve to an object.
168 * @return {!Object} An object with the exported API.
169 * @throws {Error} If the symbol has not been defined or does not resolve to
170 * an object.
171 */
172exports.exportPublicApi = function(symbol) {
173 var src = closureRequire(symbol);
174 if (typeof src != 'object' || src === null) {
175 throw Error('"' + symbol + '" must resolve to an object');
176 }
177
178 var dest = {};
179 Object.keys(src).forEach(function(key) {
180 if (key[key.length - 1] != '_') {
181 dest[key] = src[key];
182 }
183 });
184
185 return dest;
186};
187
188
189if (isDevMode()) {
190 exports.closure = context.closure;
191}
192exports.Context = Context;
193exports.isDevMode = isDevMode;
194exports.require = closureRequire;