Skip to content

Instantly share code, notes, and snippets.

@tzuryby
Forked from JamesMGreene/0 - README.md
Created December 4, 2016 01:56
Show Gist options
  • Save tzuryby/41df1d4df3fdee02100b750d6b94a288 to your computer and use it in GitHub Desktop.
Save tzuryby/41df1d4df3fdee02100b750d6b94a288 to your computer and use it in GitHub Desktop.
A brief example of the latest idea for communicating with PhantomJS from the client-side.

A brief example of the latest idea for communicating with PhantomJS from the client-side.

This is the next revision of ideas discussed in a previous Gist:
    https://gist.github.com/JamesMGreene/3716654

This example demonstrates shimming in the new communication channels under discussion via existing methods (WebPage#onCallback and window.callPhantom).

The example does, however, take advantage of a yet-to-be-implemented EventEmitter API for core modules in the PhantomJS outer context. This API would add the following methods to core modules as appropriate:

  • on / addEventListener
  • off / removeEventListener
  • emit (strictly for triggering handlers within the PhantomJS context)
// PURPOSE:
// This code snippet shims new hooks into the WebPage (client-side) with existing functionality.
// Override some functionality within the "create" function of the "webpage"
// core module to immediately attach signal handlers within "create":
page.on("initialized", function() {
page.evaluate(function() {
// TODO: This needs to happen in frames as well!
(function(top) {
// Create a `phantom` object based on the prototype of `top.parent` (`top`)
var phantom = Object.create(top.parent);
// Override the `top.parent.dispatchEvent` method to communicate with Phantom
phantom.dispatchEvent = function(evt) {
// Reformat
var message = {
name: evt.type,
data: evt.detail
};
// Send to Phantom
// TODO: Use an equivalent future implementation to connect instead of `callPhantom`
var response = window.callPhantom(message);
// `dispatchEvent` is supposed to return a Boolean
// We will invoke client-side handlers to get that (if any)
var responseEvt = new CustomEvent(evt.type, { "detail": response });
var result = top.dispatchEvent(responseEvt);
// Keeping up appearances
if (evt.cancelable === true && !result) {
return false;
}
return true;
};
// Everything else will remain intact as if it were actually the `top` object
// Override the `top.parent` to create the wormhole connection to Phantom
top.parent = phantom;
})(top);
});
// Delegate to another handler, e.g. the `topSecret` event handler
page.on("callback", function(message) {
return page.emit(message.name, message.data);
};
}
// PURPOSE:
// This code snippet adds a handler for the `"topSecret"` event.
page.on("topSecret", function(data) {
if (data && data.secret === "sauce") {
return "Roger!"
}
// else `return undefined;`
});
<!DOCTYPE html>
<html>
<!--
PURPOSE:
This page demonstrates utilizing the new hooks to achieve bi-directional
communication with Phantom from the WebPage (client-side).
-->
<head>
<title>Test PhantomJS client messaging API</title>
</head>
<body>
<div>
<h1>Testing special concept of leveraging `top.parent` for PhantomJS client messaging API</h1>
</div>
<script type="text/javascript">
(function() {
// Grab a reference to this hooked PhantomJS context for convenience
var phantom = top.parent;
// Create custom event
var evt = new CustomEvent("topSecret", {
"detail": {
"secret": "sauce"
}
});
// Create a listener callback
var callback = function(evt) {
if (evt.detail === "Roger!") {
alert("Secret received.");
}
else {
alert("UH OH! Secret compromised!");
}
};
// Add a listener for the same event (i.e. PhantomJS response to message)
phantom.addEventListener(evt.type, callback, false);
// Send a client-to-PhantomJS message.
phantom.dispatchEvent(evt);
})();
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<!--
PURPOSE:
This page demonstrates utilizing the new hooks to achieve bi-directional
communication with Phantom from the WebPage (client-side).
-->
<head>
<title>Test PhantomJS client messaging API</title>
</head>
<body>
<div>
<h1>Testing special concept of leveraging `top.parent` for PhantomJS client messaging API</h1>
</div>
<script type="text/javascript">
(function() {
// Wrap this with a `callPhantom` convenience method for demonstration
// purposes. This wrapper would be excellent for one-time communications
// but less so for events that are received multiple times as it removes
// the handler each time.
var callPhantom = function(msg, callback) {
// Grab a reference to this hooked PhantomJS context for convenience
var phantom = top.parent;
// Handle a PhantomJS-to-client response.
// TODO: Should the name always be `msgName`, `msgName + ".response"`,
// or fully customizable via the `msgData` sent?
if (typeof callback === "function") {
var wrappedCallback = function() {
phantom.removeEventListener(msg.name, wrappedCallback, false);
callback.apply(this, arguments);
};
phantom.addEventListener(msg.name, wrappedCallback, false);
}
// Send a client-to-PhantomJS message.
var evt = new CustomEvent(msg.name, { "detail": msg.data });
phantom.dispatchEvent(evt);
};
var msg = {
"name": "topSecret",
"data": {
secret: "sauce"
}
};
var callback = function(evt) {
// `evt.type` === `msgName` + ".response"
// `evt.detail` === response
if (evt.detail === "Roger!") {
alert("Secret received.");
}
else {
alert("UH OH! Secret compromised!");
}
};
// Invoke!
callPhantom(msg, callback);
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment