newspaint

Documenting Problems That Were Difficult To Find The Answer To

Sampling Pixels on Web Page Using PhantomJS

Concerned your website might be hacked and defaced? How do you protect against this? One way might be to get a “snapshot” of your webpage as an image and then test pixels to see if they match the colours you expect at particular static locations. Not perfect but you’ll know quickly if someone wasn’t subtle when replacing the front-page content.

The technique I propose is this:

  • load the web page
  • render to a base-64 string
  • load “about:blank”
  • dynamically add an image to the DOM
  • assign the base-64 string to the image
  • dynamically add a canvas to the DOM
  • draw the image to the canvas’ context
  • sample points from the canvas’ context

First things first: load the web page and render it to a base 64 string:

var system = require('system');
var page = require('webpage').create();
page.onResourceError = function(resourceError) {
    page.reason = resourceError.errorString;
    page.reason_url = resourceError.url;
};

page.viewportSize = {
    'width': 1000,
    'height': 768
};

page.onConsoleMessage = function(msg) {
    system.stderr.writeLine('console: ' + msg);
};

page.open(
    'http://www.google.com/',
    function (status) {
        if ( status !== 'success' ) {
            console.log(
                "Error opening url \"" + page.reason_url
                + "\": " + page.reason
            );
            phantom.exit( 1 );
            return;
        }

        // take snapshot after 2 seconds to allow page AJAX to run
        window.setTimeout(
            function () {
                var imgBase64 = page.renderBase64();
                check_image( imgBase64 ); // function defined below
            },
            2 * 1000 // give page 2 seconds to execute before render
        );
    }
);

Now we have our base-64 string. Before proceeding I recommend that the page gets cleared to a blank page (so that the tested page’s JavaScript can’t wreak havoc on our image test function):

page.open(
    'about:blank',
    function ( status ) {
        check_image( imgBase64 ); // function defined below
    }
);

The pixel checking code must be done in a page.evaluate() function call because it will be getting the page to run JavaScript:

function check_image_js( imgString ) {
    // returns hex colour in ttrrggbb format (tt is opacity)
    function get_color( context, x, y ) {
        // return 2-character hex string (0-255)
        function decToHex( dd ) {
            var hex = Number(dd).toString(16);
            hex = "00".substr( 0, 2 - hex.length ) + hex;
            return hex;
        }

        var imgd = context.getImageData( x, y, 1, 1 ).data;
        var colorstr = (
            decToHex( imgd[3] ) + decToHex( imgd[0] ) +
            decToHex( imgd[1] ) + decToHex( imgd[2] )
        );

        return colorstr;
    }

    // list of pixels to check
    var pixels = [
        { x: 272, y: 135, col: 'ff000000', desc: 'Black in title' },
        { x: 97, y: 9, col: 'ffffffff', desc: 'White in button' }
    ];

    // create canvas
    var canvas = document.createElement( 'canvas' );
    canvas.width = 1000;
    canvas.height = 768;
    var context = canvas.getContext( '2d' );
    
    // load image
    var img = new Image();
    img.onload = function () { context.drawImage( img, 0, 0 ); };
    img.src = "data:image/png;base64," + imgString;

    // give time for image to load
    window.setTimeout(
        function () {
            var problemDetected = false;

            console.log( "- doing pixels" );

            var idx;
            for ( idx = 0; idx < pixels.length; idx++ ) {
                var row = pixels[idx];

                var actual = get_color( context, row.x, row.y );
                if ( actual !== row.col ) {
                    problemDetected = true;
                    console.log( "ERROR mismatch: expected \"" + row.col + "\", got \"" + actual + "\" (ttrrggbb) for \"" + row.desc + "\" at (" + row.x + ", " + row.y + ")" );
                } else {
                    console.log( "  - matched colour \"" + actual + "\" (ttrrggbb) for \"" + row.desc + "\" at (" + row.x + ", " + row.y + ")" );
                }
            }
        }, 500
    );
}

// call check_image_js in page.evaluate sandbox
function check_image( imgString ) {
    page.evaluate(
        function ( callback, string ) {
            callback( string );
        },
        check_image_js,
        imgString
    );
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: