peerigon / phridge

A bridge between node and PhantomJS
The Unlicense
519 stars 50 forks source link

Adding support for calling back from phantom to node #55

Open reflectiz opened 8 years ago

reflectiz commented 8 years ago

I want to be able to call from the phantom code to the node module. The code added a function calls "nodeCallback" at the phantom side and property calls "onCallback" on the "page" object.

coveralls commented 8 years ago

Coverage Status

Coverage decreased (-1.7%) to 98.328% when pulling 397b6f2687469dacbc4d47cca52972411c469848 on ad-venture:master into 0aa95f81dffcaab48357ebf2aab42ce64ce69247 on peerigon:master.

coveralls commented 8 years ago

Coverage Status

Coverage decreased (-1.7%) to 98.328% when pulling ff308e25520c739a7120b95aceeba5c35d084e34 on ad-venture:master into 0aa95f81dffcaab48357ebf2aab42ce64ce69247 on peerigon:master.

jhnns commented 8 years ago

Thank you for your pull-request.

Can you describe a use-case where this nodeCallback is necessary and cannot be solved in other ways?

reflectiz commented 8 years ago

Hi jhnns

I've added this functionally because I needed to initiate an action base options for events triggered at the phantomjs client side code. I'm using MutationObserver to monitor changes on the page, and when change occurs, the nodeJS server need to react. I haven't found any other option in phridge current code.

Thanks Ysrael

jhnns commented 8 years ago

You're right. There is currently no way to achieve this.

However, there are some problems with your implementation. What happens if I have multiple instances of Page and I would like to install event listeners individually. We somehow need to have a record so that we now where "to call back".

For instance, we could have a third argument emit:

phantom.run(function (resolve, reject, emit) {
    setInterval(() => {
        emit("hi");
    }, 100);
    resolve();
});

somePage.run(function (resolve, reject, emit) {
    setInterval(() => {
        emit("hi");
    }, 100);
});

phantom.on("hi", function () { ... });
somePage.on("hi", function () { ... });

This way...

However, there might be another problem: When we're inside PhantomJS, we are not inside the actual page context. When using the evaluate function provided by PhantomJS, we're not allowed to access the scope either. Hence, this code will not work:

page.run(function (resolve, reject, emit) {
    this.evaluate(function () {
        // emit is not defined because we're in the actual page context now
        emit("hi");
    });
    resolve();
});

We would need to use the onCallback mechanism as described here:

page.run(function (resolve, reject, emit) {
    this.onCallback(function (message) {
        emit(message);
    });
    this.evaluate(function () {
        window.callPhantom('hi');
    });
    resolve();
});

This is very ugly – and again, we have the same problem that we need to collate callbacks from different sources...

I totally see the use-case and I wish, phridge would provide an easy API for this. But a proper implementation takes a lot of effort...

For instance, it would be cool to have a evaluate method on our page instance that mitigates all the cruft around it. This is probably what you would like to use:

page.evaluate(function (resolve, reject, emit) {
    var observer = new MutationObserver(function(mutations) {
      mutations.forEach(function(mutation) {
        emit("mutation", mutation.type);
      });    
    });

    observer.observe(document.body, {
      attributes: true,
      childList: true,
      characterData: true
    });
});

page.on("mutation", function (mutationType) {

});