_base.js

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