# _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 |
33 | var 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 | */
43 | var 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. */
50 | function isDevMode() {
51 | return devMode;
52 | }
53 |
54 |
55 | /**
56 | * @type {string} Path to Closure's base file, relative to this module.
57 | * @const
58 | */
59 | var 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 | */
71 | var 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 | */
86 | function 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 |
150 | var 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 | */
159 | function 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 | */
178 | exports.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 |
195 | if (isDevMode()) {
196 | exports.closure = context.closure;
197 | }
198 | exports.Context = Context;
199 | exports.isDevMode = isDevMode;
200 | exports.require = closureRequire;