Permalink
Browse files

Initial draft.

  • Loading branch information...
1 parent e955c51 commit c5f97629dbabefadbe206e000a8a416295d4b1df @Gozala committed Nov 1, 2012
Showing with 222 additions and 0 deletions.
  1. +4 −0 bin/cmd.js
  2. +31 −0 phantom-bootstrap.js
  3. +168 −0 phantomify.js
  4. +11 −0 process.js
  5. +8 −0 test/index.js
View
@@ -0,0 +1,4 @@
+#!/usr/bin/env node
+
+var phantomify = require("../phantomify")
+phantomify(process.argv[2])
View
@@ -0,0 +1,31 @@
+"use strict";
+
+var system = require("system")
+var webpage = require("webpage")
+var fs = require("fs")
+
+var page = webpage.create()
+page.onError = function(msg, trace) {
+ var msgStack = ["ERROR: " + msg]
+ if (trace) {
+ msgStack.push("TRACE:")
+ trace.forEach(function(t) {
+ msgStack.push(" -> " + t.file + ": " + t.line +
+ (t.function ? " (in function '" + t.function + "')" : ""))
+ })
+ }
+ console.error(msgStack.join("\n"))
+}
+
+page.onConsoleMessage = function(message) {
+ console.log(message)
+}
+page.onClosing = function(closingPage) {
+ var code = closingPage.url.split("#")[1]
+ phantom.exit(code && parseInt(code))
+}
+page.open("about:blank", function onPage(status) {
+ console.log(system.args[1])
+ var source = fs.read(system.args[1])
+ page.evaluateAsync(Function(source))
+})
View
@@ -0,0 +1,168 @@
+"use strict";
+
+var fs = require("fs")
+var browserify = require("browserify")
+var spawn = require("child_process").spawn
+var path = require("path")
+
+function phantomify(main) {
+ /**
+ Browserifies module under given `path` and runs the created bundle
+ in a phantomify.
+ **/
+ main = path.resolve(main)
+ var processModule = path.join(path.dirname(module.filename), "process.js")
+ var bundle = browserify({
+ debug: true,
+ exports: [ "process", "require" ]
+ })
+ // Prepend a shim for a `Function.bind` that is missing in PhantomJS.
+ bundle.prepend("(" + function() {
+ var call = Function.prototype.call;
+ var prototypeOfArray = Array.prototype;
+ var prototypeOfObject = Object.prototype;
+ var slice = prototypeOfArray.slice;
+
+ if (!Function.prototype.bind) {
+ Function.prototype.bind = function bind(that) { // .length is 1
+ // 1. Let Target be the this value.
+ var target = this;
+ // 2. If IsCallable(Target) is false, throw a TypeError exception.
+ if (typeof target != "function") {
+ throw new TypeError("Function.prototype.bind called on incompatible " + target);
+ }
+ // 3. Let A be a new (possibly empty) internal list of all of the
+ // argument values provided after thisArg (arg1, arg2 etc), in order.
+ // XXX slicedArgs will stand in for "A" if used
+ var args = slice.call(arguments, 1); // for normal call
+ // 4. Let F be a new native ECMAScript object.
+ // 11. Set the [[Prototype]] internal property of F to the standard
+ // built-in Function prototype object as specified in 15.3.3.1.
+ // 12. Set the [[Call]] internal property of F as described in
+ // 15.3.4.5.1.
+ // 13. Set the [[Construct]] internal property of F as described in
+ // 15.3.4.5.2.
+ // 14. Set the [[HasInstance]] internal property of F as described in
+ // 15.3.4.5.3.
+ var bound = function () {
+
+ if (this instanceof bound) {
+ // 15.3.4.5.2 [[Construct]]
+ // When the [[Construct]] internal method of a function object,
+ // F that was created using the bind function is called with a
+ // list of arguments ExtraArgs, the following steps are taken:
+ // 1. Let target be the value of F's [[TargetFunction]]
+ // internal property.
+ // 2. If target has no [[Construct]] internal method, a
+ // TypeError exception is thrown.
+ // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
+ // property.
+ // 4. Let args be a new list containing the same values as the
+ // list boundArgs in the same order followed by the same
+ // values as the list ExtraArgs in the same order.
+ // 5. Return the result of calling the [[Construct]] internal
+ // method of target providing args as the arguments.
+
+ var result = target.apply(
+ this,
+ args.concat(slice.call(arguments))
+ );
+ if (Object(result) === result) {
+ return result;
+ }
+ return this;
+
+ } else {
+ // 15.3.4.5.1 [[Call]]
+ // When the [[Call]] internal method of a function object, F,
+ // which was created using the bind function is called with a
+ // this value and a list of arguments ExtraArgs, the following
+ // steps are taken:
+ // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
+ // property.
+ // 2. Let boundThis be the value of F's [[BoundThis]] internal
+ // property.
+ // 3. Let target be the value of F's [[TargetFunction]] internal
+ // property.
+ // 4. Let args be a new list containing the same values as the
+ // list boundArgs in the same order followed by the same
+ // values as the list ExtraArgs in the same order.
+ // 5. Return the result of calling the [[Call]] internal method
+ // of target providing boundThis as the this value and
+ // providing args as the arguments.
+
+ // equiv: target.call(this, ...boundArgs, ...args)
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+ if(target.prototype) {
+ bound.prototype = Object.create(target.prototype);
+ }
+ // XXX bound.length is never writable, so don't even try
+ //
+ // 15. If the [[Class]] internal property of Target is "Function", then
+ // a. Let L be the length property of Target minus the length of A.
+ // b. Set the length own property of F to either 0 or L, whichever is
+ // larger.
+ // 16. Else set the length own property of F to 0.
+ // 17. Set the attributes of the length own property of F to the values
+ // specified in 15.3.5.1.
+
+ // TODO
+ // 18. Set the [[Extensible]] internal property of F to true.
+
+ // TODO
+ // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
+ // 20. Call the [[DefineOwnProperty]] internal method of F with
+ // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
+ // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
+ // false.
+ // 21. Call the [[DefineOwnProperty]] internal method of F with
+ // arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
+ // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
+ // and false.
+
+ // TODO
+ // NOTE Function objects created using Function.prototype.bind do not
+ // have a prototype property or the [[Code]], [[FormalParameters]], and
+ // [[Scope]] internal properties.
+ // XXX can't delete prototype in pure-js.
+
+ // 22. Return F.
+ return bound;
+ };
+ }
+ } + ")();\n")
+ // Shim the process so that it actually exits phantomjs.
+ bundle.addEntry(processModule)
+ bundle.addEntry(main)
+ var source = bundle.bundle()
+ var directory = path.dirname(main)
+ var file = path.join(directory, "./.phantomify-" + Date.now() + ".js")
+ var bootstrap = path.join(path.dirname(module.filename),
+ "phantom-bootstrap.js")
+
+ if (bundle.ok) {
+ fs.writeFileSync(file, source)
+ var phantomjs = spawn("phantomjs", [ bootstrap, file ], {
+ stdio: "inherit"
+ })
+ phantomjs.once("exit", function(code) {
+ fs.unlinkSync(file)
+ process.exit(code)
+ })
+ } else {
+ console.error("Browserify failed to bundle")
+ process.exit(1)
+ }
+}
+
+module.exports = phantomify
+
+if (require.main === module)
+ phantomify(process.argv[2])
View
@@ -0,0 +1,11 @@
+/*jshint browser:true */
+
+"use strict";
+
+process.exit = function(code) {
+ // Communicate exit code via location hash.
+ if (code === void(0) || code === 0) window.location.hash = "0"
+ else window.location.hash = code
+ // close a window.
+ window.close()
+}
View
@@ -0,0 +1,8 @@
+"use strict";
+
+exports["test example"] = function(assert) {
+ assert.equal(2 + 2, 4, "2 + 2 = 4")
+ process.exit(0)
+}
+
+require("test").run(exports)

0 comments on commit c5f9762

Please sign in to comment.