Commit 0c7a050c 0c7a050c96880b28bc1f35ed514c015bc043608c by Nicolas Perriault

PhantomJS 1.7 compatibility.

WARNING: PhantomJS 1.7 ships with its own implementation of the module
pattern, which is incompatible with what CasperJS provided.

So the CasperJS' `modules` directory has been renamed to `node_modules`
to allow loading CasperJS built-in modules natively.

CasperJS remains compatible with PhantomJS 1.6 though.
1 parent 7069852b
...@@ -30,11 +30,13 @@ ...@@ -30,11 +30,13 @@
30 30
31 /*global console phantom require*/ 31 /*global console phantom require*/
32 32
33 if (!phantom || phantom.version.major !== 1 || phantom.version.minor < 5) { 33 if (!phantom) {
34 console.error('CasperJS needs at least PhantomJS v1.5.0'); 34 console.error('CasperJS needs to be executed in a PhantomJS environment http://phantomjs.org/');
35 phantom.exit(1); 35 phantom.exit(1);
36 } else if (!phantom || phantom.version.major !== 1 || phantom.version.minor === 7) { 36 }
37 console.error('CasperJS is currently broken with PhantomJS 1.7, sorry.'); 37
38 if (phantom.version.major === 1 && phantom.version.minor < 6) {
39 console.error('CasperJS needs at least PhantomJS v1.6.0 or later.');
38 phantom.exit(1); 40 phantom.exit(1);
39 } else { 41 } else {
40 bootstrap(window); 42 bootstrap(window);
...@@ -43,6 +45,7 @@ if (!phantom || phantom.version.major !== 1 || phantom.version.minor < 5) { ...@@ -43,6 +45,7 @@ if (!phantom || phantom.version.major !== 1 || phantom.version.minor < 5) {
43 // Polyfills 45 // Polyfills
44 if (typeof Function.prototype.bind !== "function") { 46 if (typeof Function.prototype.bind !== "function") {
45 Function.prototype.bind = function(scope) { 47 Function.prototype.bind = function(scope) {
48 "use strict";
46 var _function = this; 49 var _function = this;
47 return function() { 50 return function() {
48 return _function.apply(scope, arguments); 51 return _function.apply(scope, arguments);
...@@ -50,8 +53,93 @@ if (typeof Function.prototype.bind !== "function") { ...@@ -50,8 +53,93 @@ if (typeof Function.prototype.bind !== "function") {
50 }; 53 };
51 } 54 }
52 55
56 /**
57 * Only for PhantomJS < 1.7: Patching require() to allow loading of other
58 * modules than PhantomJS' builtin ones.
59 *
60 */
61 function patchRequire(require, requireDir) {
62 "use strict";
63 var fs = require('fs');
64 var phantomBuiltins = ['fs', 'webpage', 'webserver', 'system'];
65 var phantomRequire = phantom.__orig__require = require;
66 var requireCache = {};
67 return function _require(path) {
68 var i, dir, paths = [],
69 fileGuesses = [],
70 file,
71 module = {
72 exports: {}
73 };
74 if (phantomBuiltins.indexOf(path) !== -1) {
75 return phantomRequire(path);
76 }
77 if (path[0] === '.') {
78 paths.push.apply(paths, [
79 fs.absolute(path),
80 fs.absolute(fs.pathJoin(requireDir, path))
81 ]);
82 } else if (path[0] === '/') {
83 paths.push(path);
84 } else {
85 dir = fs.absolute(requireDir);
86 while (dir !== '' && dir.lastIndexOf(':') !== dir.length - 1) {
87 // nodejs compatibility
88 paths.push(fs.pathJoin(dir, 'node_modules', path));
89 dir = fs.dirname(dir);
90 }
91 paths.push(fs.pathJoin(requireDir, 'lib', path));
92 paths.push(fs.pathJoin(requireDir, 'modules', path));
93 }
94 paths.forEach(function _forEach(testPath) {
95 fileGuesses.push.apply(fileGuesses, [
96 testPath,
97 testPath + '.js',
98 testPath + '.coffee',
99 fs.pathJoin(testPath, 'index.js'),
100 fs.pathJoin(testPath, 'index.coffee'),
101 fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
102 fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
103 ]);
104 });
105 file = null;
106 for (i = 0; i < fileGuesses.length && !file; ++i) {
107 if (fs.isFile(fileGuesses[i])) {
108 file = fileGuesses[i];
109 }
110 }
111 if (!file) {
112 throw new Error("CasperJS couldn't find module " + path);
113 }
114 if (file in requireCache) {
115 return requireCache[file].exports;
116 }
117 var scriptCode = (function getScriptCode(file) {
118 var scriptCode = fs.read(file);
119 if (/\.coffee$/i.test(file)) {
120 /*global CoffeeScript*/
121 scriptCode = CoffeeScript.compile(scriptCode);
122 }
123 return scriptCode;
124 })(file);
125 var fn = new Function('__file__', 'require', 'module', 'exports', scriptCode);
126 try {
127 fn(file, _require, module, module.exports);
128 } catch (e) {
129 var error = new window.CasperError('__mod_error(' + path + '):: ' + e);
130 error.file = file;
131 throw error;
132 }
133 requireCache[file] = module;
134 return module.exports;
135 };
136 }
137
53 function bootstrap(global) { 138 function bootstrap(global) {
54 "use strict"; 139 "use strict";
140
141 var phantomArgs = require('system').args;
142
55 /** 143 /**
56 * Loads and initialize the CasperJS environment. 144 * Loads and initialize the CasperJS environment.
57 */ 145 */
...@@ -103,7 +191,7 @@ function bootstrap(global) { ...@@ -103,7 +191,7 @@ function bootstrap(global) {
103 } 191 }
104 192
105 // Embedded, up-to-date, validatable & controlable CoffeeScript 193 // Embedded, up-to-date, validatable & controlable CoffeeScript
106 phantom.injectJs(fs.pathJoin(phantom.casperPath, 'modules', 'vendors', 'coffee-script.js')); 194 phantom.injectJs(fs.pathJoin(phantom.casperPath, 'node_modules', 'vendors', 'coffee-script.js'));
107 195
108 // custom global CasperError 196 // custom global CasperError
109 global.CasperError = function CasperError(msg) { 197 global.CasperError = function CasperError(msg) {
...@@ -154,100 +242,13 @@ function bootstrap(global) { ...@@ -154,100 +242,13 @@ function bootstrap(global) {
154 }; 242 };
155 })(phantom.casperPath); 243 })(phantom.casperPath);
156 244
157 /** 245 // patch require
158 * Retrieves the javascript source code from a given .js or .coffee file. 246 if (phantom.version.major === 1 && phantom.version.minor < 7) {
159 * 247 global.require = patchRequire(global.require, phantom.casperPath);
160 * @param String file The path to the file 248 }
161 * @param Function|null onError An error callback (optional)
162 */
163 phantom.getScriptCode = function getScriptCode(file, onError) {
164 var scriptCode = fs.read(file);
165 if (/\.coffee$/i.test(file)) {
166 /*global CoffeeScript*/
167 scriptCode = CoffeeScript.compile(scriptCode);
168 }
169 return scriptCode;
170 };
171
172 /**
173 * Patching require() to allow loading of other modules than PhantomJS'
174 * builtin ones.
175 * Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/
176 * TODO: remove when PhantomJS has full module support
177 */
178 require = (function _require(require, requireDir) {
179 var phantomBuiltins = ['fs', 'webpage', 'webserver', 'system'];
180 var phantomRequire = phantom.__orig__require = require;
181 var requireCache = {};
182 return function _require(path) {
183 var i, dir, paths = [],
184 fileGuesses = [],
185 file,
186 module = {
187 exports: {}
188 };
189 if (phantomBuiltins.indexOf(path) !== -1) {
190 return phantomRequire(path);
191 }
192 if (path[0] === '.') {
193 paths.push.apply(paths, [
194 fs.absolute(path),
195 fs.absolute(fs.pathJoin(requireDir, path))
196 ]);
197 } else if (path[0] === '/') {
198 paths.push(path);
199 } else {
200 dir = fs.absolute(requireDir);
201 while (dir !== '' && dir.lastIndexOf(':') !== dir.length - 1) {
202 // nodejs compatibility
203 paths.push(fs.pathJoin(dir, 'node_modules', path));
204 dir = fs.dirname(dir);
205 }
206 paths.push(fs.pathJoin(requireDir, 'lib', path));
207 paths.push(fs.pathJoin(requireDir, 'modules', path));
208 }
209 paths.forEach(function _forEach(testPath) {
210 fileGuesses.push.apply(fileGuesses, [
211 testPath,
212 testPath + '.js',
213 testPath + '.coffee',
214 fs.pathJoin(testPath, 'index.js'),
215 fs.pathJoin(testPath, 'index.coffee'),
216 fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
217 fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
218 ]);
219 });
220 file = null;
221 for (i = 0; i < fileGuesses.length && !file; ++i) {
222 if (fs.isFile(fileGuesses[i])) {
223 file = fileGuesses[i];
224 }
225 }
226 if (!file) {
227 throw new Error("CasperJS couldn't find module " + path);
228 }
229 if (file in requireCache) {
230 return requireCache[file].exports;
231 }
232 var scriptCode = phantom.getScriptCode(file);
233 var fn = new Function('__file__', 'require', 'module', 'exports', scriptCode);
234 try {
235 fn(file, _require, module, module.exports);
236 } catch (e) {
237 var error = new global.CasperError('__mod_error(' + path + '):: ' + e);
238 error.file = file;
239 throw error;
240 }
241 requireCache[file] = module;
242 return module.exports;
243 };
244 })(require, phantom.casperPath);
245
246 // BC < 0.6
247 phantom.Casper = require('casper').Casper;
248 249
249 // casper cli args 250 // casper cli args
250 phantom.casperArgs = require('cli').parse(phantom.args); 251 phantom.casperArgs = global.require('cli').parse(phantom.args);
251 252
252 // loaded status 253 // loaded status
253 phantom.casperLoaded = true; 254 phantom.casperLoaded = true;
...@@ -306,12 +307,7 @@ function bootstrap(global) { ...@@ -306,12 +307,7 @@ function bootstrap(global) {
306 }; 307 };
307 308
308 if (!phantom.casperLoaded) { 309 if (!phantom.casperLoaded) {
309 try { 310 phantom.loadCasper();
310 phantom.loadCasper();
311 } catch (e) {
312 console.error("Unable to load casper environment: " + e);
313 phantom.exit();
314 }
315 } 311 }
316 312
317 if (true === phantom.casperArgs.get('cli')) { 313 if (true === phantom.casperArgs.get('cli')) {
......
...@@ -836,7 +836,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() { ...@@ -836,7 +836,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
836 if (true === clientUtilsInjected) { 836 if (true === clientUtilsInjected) {
837 return; 837 return;
838 } 838 }
839 var clientUtilsPath = require('fs').pathJoin(phantom.casperPath, 'modules', 'clientutils.js'); 839 var clientUtilsPath = require('fs').pathJoin(phantom.casperPath, 'node_modules', 'clientutils.js');
840 if (true === this.page.injectJs(clientUtilsPath)) { 840 if (true === this.page.injectJs(clientUtilsPath)) {
841 this.log("Successfully injected Casper client-side utilities", "debug"); 841 this.log("Successfully injected Casper client-side utilities", "debug");
842 } else { 842 } else {
......
1 /*
2 * Copyright © 2007 Dominic Mitchell
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * Neither the name of the Dominic Mitchell nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 /*
32 * An URI datatype. Based upon examples in RFC3986.
33 *
34 * TODO %-escaping
35 * TODO split apart authority
36 * TODO split apart query_string (on demand, anyway)
37 * TODO handle parameters containing empty strings properly
38 * TODO keyword escaping
39 *
40 * @(#) $Id$
41 */
42
43 // Globals we introduce.
44 var URI;
45 var URIQuery;
46
47 // Introduce a new scope to define some private helper functions.
48 (function () {
49
50 //// HELPER FUNCTIONS /////
51
52 // RFC3986 §5.2.3 (Merge Paths)
53 function merge(base, rel_path) {
54 var dirname = /^(.*)\//;
55 if (base.authority && !base.path) {
56 return "/" + rel_path;
57 }
58 else {
59 return base.getPath().match(dirname)[0] + rel_path;
60 }
61 }
62
63 // Match two path segments, where the second is ".." and the first must
64 // not be "..".
65 var DoubleDot = /\/((?!\.\.\/)[^\/]*)\/\.\.\//;
66
67 function remove_dot_segments(path) {
68 if (!path) {
69 return "";
70 }
71 // Remove any single dots
72 var newpath = path.replace(/\/\.\//g, '/');
73 // Remove any trailing single dots.
74 newpath = newpath.replace(/\/\.$/, '/');
75 // Remove any double dots and the path previous. NB: We can't use
76 // the "g", modifier because we are changing the string that we're
77 // matching over.
78 while (newpath.match(DoubleDot)) {
79 newpath = newpath.replace(DoubleDot, '/');
80 }
81 // Remove any trailing double dots.
82 newpath = newpath.replace(/\/([^\/]*)\/\.\.$/, '/');
83 // If there are any remaining double dot bits, then they're wrong
84 // and must be nuked. Again, we can't use the g modifier.
85 while (newpath.match(/\/\.\.\//)) {
86 newpath = newpath.replace(/\/\.\.\//, '/');
87 }
88 return newpath;
89 }
90
91 // give me an ordered list of keys of this object
92 function hashkeys(obj) {
93 var list = [];
94 for (var key in obj) {
95 if (obj.hasOwnProperty(key)) {
96 list.push(key);
97 }
98 }
99 return list.sort();
100 }
101
102 // TODO: Make these do something
103 function uriEscape(source) {
104 return source;
105 }
106
107 function uriUnescape(source) {
108 return source;
109 }
110
111
112 //// URI CLASS /////
113
114 // Constructor for the URI object. Parse a string into its components.
115 // note that this 'exports' 'URI' to the 'global namespace'
116 URI = function (str) {
117 if (!str) {
118 str = "";
119 }
120 // Based on the regex in RFC2396 Appendix B.
121 var parser = /^(?:([^:\/?\#]+):)?(?:\/\/([^\/?\#]*))?([^?\#]*)(?:\?([^\#]*))?(?:\#(.*))?/;
122 var result = str.match(parser);
123
124 // Keep the results in private variables.
125 var scheme = result[1] || null;
126 var authority = result[2] || null;
127 var path = result[3] || null;
128 var query = result[4] || null;
129 var fragment = result[5] || null;
130
131 // Set up accessors.
132 this.getScheme = function () {
133 return scheme;
134 };
135 this.setScheme = function (newScheme) {
136 scheme = newScheme;
137 };
138 this.getAuthority = function () {
139 return authority;
140 };
141 this.setAuthority = function (newAuthority) {
142 authority = newAuthority;
143 };
144 this.getPath = function () {
145 return path;
146 };
147 this.setPath = function (newPath) {
148 path = newPath;
149 };
150 this.getQuery = function () {
151 return query;
152 };
153 this.setQuery = function (newQuery) {
154 query = newQuery;
155 };
156 this.getFragment = function () {
157 return fragment;
158 };
159 this.setFragment = function (newFragment) {
160 fragment = newFragment;
161 };
162 };
163
164 // Restore the URI to it's stringy glory.
165 URI.prototype.toString = function () {
166 var str = "";
167 if (this.getScheme()) {
168 str += this.getScheme() + ":";
169 }
170 if (this.getAuthority()) {
171 str += "//" + this.getAuthority();
172 }
173 if (this.getPath()) {
174 str += this.getPath();
175 }
176 if (this.getQuery()) {
177 str += "?" + this.getQuery();
178 }
179 if (this.getFragment()) {
180 str += "#" + this.getFragment();
181 }
182 return str;
183 };
184
185 // RFC3986 §5.2.2. Transform References;
186 URI.prototype.resolve = function (base) {
187 var target = new URI();
188 if (this.getScheme()) {
189 target.setScheme(this.getScheme());
190 target.setAuthority(this.getAuthority());
191 target.setPath(remove_dot_segments(this.getPath()));
192 target.setQuery(this.getQuery());
193 }
194 else {
195 if (this.getAuthority()) {
196 target.setAuthority(this.getAuthority());
197 target.setPath(remove_dot_segments(this.getPath()));
198 target.setQuery(this.getQuery());
199 }
200 else {
201 // XXX Original spec says "if defined and empty"…;
202 if (!this.getPath()) {
203 target.setPath(base.getPath());
204 if (this.getQuery()) {
205 target.setQuery(this.getQuery());
206 }
207 else {
208 target.setQuery(base.getQuery());
209 }
210 }
211 else {
212 if (this.getPath().charAt(0) === '/') {
213 target.setPath(remove_dot_segments(this.getPath()));
214 } else {
215 target.setPath(merge(base, this.getPath()));
216 target.setPath(remove_dot_segments(target.getPath()));
217 }
218 target.setQuery(this.getQuery());
219 }
220 target.setAuthority(base.getAuthority());
221 }
222 target.setScheme(base.getScheme());
223 }
224
225 target.setFragment(this.getFragment());
226
227 return target;
228 };
229
230 URI.prototype.parseQuery = function () {
231 return URIQuery.fromString(this.getQuery(), this.querySeparator);
232 };
233
234 //// URIQuery CLASS /////
235
236 URIQuery = function () {
237 this.params = {};
238 this.separator = "&";
239 };
240
241 URIQuery.fromString = function (sourceString, separator) {
242 var result = new URIQuery();
243 if (separator) {
244 result.separator = separator;
245 }
246 result.addStringParams(sourceString);
247 return result;
248 };
249
250
251 // From http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
252 // (application/x-www-form-urlencoded).
253 //
254 // NB: The user can get this.params and modify it directly.
255 URIQuery.prototype.addStringParams = function (sourceString) {
256 var kvp = sourceString.split(this.separator);
257 var list, key, value;
258 for (var i = 0; i < kvp.length; i++) {
259 // var [key,value] = kvp.split("=", 2) only works on >= JS 1.7
260 list = kvp[i].split("=", 2);
261 key = uriUnescape(list[0].replace(/\+/g, " "));
262 value = uriUnescape(list[1].replace(/\+/g, " "));
263 if (!this.params.hasOwnProperty(key)) {
264 this.params[key] = [];
265 }
266 this.params[key].push(value);
267 }
268 };
269
270 URIQuery.prototype.getParam = function (key) {
271 if (this.params.hasOwnProperty(key)) {
272 return this.params[key][0];
273 }
274 return null;
275 };
276
277 URIQuery.prototype.toString = function () {
278 var kvp = [];
279 var keys = hashkeys(this.params);
280 var ik, ip;
281 for (ik = 0; ik < keys.length; ik++) {
282 for (ip = 0; ip < this.params[keys[ik]].length; ip++) {
283 kvp.push(keys[ik].replace(/ /g, "+") + "=" + this.params[keys[ik]][ip].replace(/ /g, "+"));
284 }
285 }
286 return kvp.join(this.separator);
287 };
288
289 })();