# _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 |
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 | 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 |
144 | var 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 | */
153 | function 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 | */
172 | exports.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 |
189 | if (isDevMode()) {
190 | exports.closure = context.closure;
191 | }
192 | exports.Context = Context;
193 | exports.isDevMode = isDevMode;
194 | exports.require = closureRequire;