c275b03b745833bbfe6bc545dcd77b11dfb02cbd
[yaffs-website] / vendor / jcalderonzumba / gastonjs / src / Client / web_page.js
1 var __slice = [].slice;
2 var __indexOf = [].indexOf || function (item) {
3     for (var i = 0, l = this.length; i < l; i++) {
4       if (i in this && this[i] === item) return i;
5     }
6     return -1;
7   };
8
9 Poltergeist.WebPage = (function () {
10   var command, delegate, commandFunctionBind, delegateFunctionBind, i, j, commandsLength, delegatesRefLength, commandsRef, delegatesRef,
11     _this = this;
12
13   //Native or not webpage callbacks
14   WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested',
15     'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing'];
16
17   // Delegates the execution to the phantomjs page native functions but directly available in the WebPage object
18   WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward', 'reload'];
19
20   //Commands to execute on behalf of the browser but on the current page
21   WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload', 'clearLocalStorage'];
22
23   WebPage.EXTENSIONS = [];
24
25   function WebPage(nativeWebPage) {
26     var callback, i, callBacksLength, callBacksRef;
27
28     //Lets create the native phantomjs webpage
29     if (nativeWebPage === null || typeof nativeWebPage == "undefined") {
30       this._native = require('webpage').create();
31     } else {
32       this._native = nativeWebPage;
33     }
34
35     this.id = 0;
36     this.source = null;
37     this.closed = false;
38     this.state = 'default';
39     this.urlBlacklist = [];
40     this.frames = [];
41     this.errors = [];
42     this._networkTraffic = {};
43     this._tempHeaders = {};
44     this._blockedUrls = [];
45
46     callBacksRef = WebPage.CALLBACKS;
47     for (i = 0, callBacksLength = callBacksRef.length; i < callBacksLength; i++) {
48       callback = callBacksRef[i];
49       this.bindCallback(callback);
50     }
51   }
52
53   //Bind the commands we can run from the browser to the current page
54   commandsRef = WebPage.COMMANDS;
55   commandFunctionBind = function (command) {
56     return WebPage.prototype[command] = function () {
57       var args;
58       args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
59       return this.runCommand(command, args);
60     };
61   };
62   for (i = 0, commandsLength = commandsRef.length; i < commandsLength; i++) {
63     command = commandsRef[i];
64     commandFunctionBind(command);
65   }
66
67   //Delegates bind applications
68   delegatesRef = WebPage.DELEGATES;
69   delegateFunctionBind = function (delegate) {
70     return WebPage.prototype[delegate] = function () {
71       return this._native[delegate].apply(this._native, arguments);
72     };
73   };
74   for (j = 0, delegatesRefLength = delegatesRef.length; j < delegatesRefLength; j++) {
75     delegate = delegatesRef[j];
76     delegateFunctionBind(delegate);
77   }
78
79   /**
80    * This callback is invoked after the web page is created but before a URL is loaded.
81    * The callback may be used to change global objects.
82    * @return {*}
83    */
84   WebPage.prototype.onInitializedNative = function () {
85     this.id += 1;
86     this.source = null;
87     this.injectAgent();
88     this.removeTempHeaders();
89     return this.setScrollPosition({
90       left: 0,
91       top: 0
92     });
93   };
94
95   /**
96    * This callback is invoked when the WebPage object is being closed,
97    * either via page.close in the PhantomJS outer space or via window.close in the page's client-side.
98    * @return {boolean}
99    */
100   WebPage.prototype.onClosingNative = function () {
101     this.handle = null;
102     return this.closed = true;
103   };
104
105   /**
106    * This callback is invoked when there is a JavaScript console message on the web page.
107    * The callback may accept up to three arguments: the string for the message, the line number, and the source identifier.
108    * @param message
109    * @param line
110    * @param sourceId
111    * @return {boolean}
112    */
113   WebPage.prototype.onConsoleMessageNative = function (message, line, sourceId) {
114     if (message === '__DOMContentLoaded') {
115       this.source = this._native.content;
116       return false;
117     }
118     console.log(message);
119     return true;
120   };
121
122   /**
123    * This callback is invoked when the page starts the loading. There is no argument passed to the callback.
124    * @return {number}
125    */
126   WebPage.prototype.onLoadStartedNative = function () {
127     this.state = 'loading';
128     return this.requestId = this.lastRequestId;
129   };
130
131   /**
132    * This callback is invoked when the page finishes the loading.
133    * It may accept a single argument indicating the page's status: 'success' if no network errors occurred, otherwise 'fail'.
134    * @param status
135    * @return {string}
136    */
137   WebPage.prototype.onLoadFinishedNative = function (status) {
138     this.status = status;
139     this.state = 'default';
140
141     if (this.source === null || typeof this.source == "undefined") {
142       this.source = this._native.content;
143     } else {
144       this.source = this._native.content;
145     }
146
147     return this.source;
148   };
149
150   /**
151    * This callback is invoked when there is a JavaScript execution error.
152    * It is a good way to catch problems when evaluating a script in the web page context.
153    * The arguments passed to the callback are the error message and the stack trace [as an Array].
154    * @param message
155    * @param stack
156    * @return {Number}
157    */
158   WebPage.prototype.onErrorNative = function (message, stack) {
159     var stackString;
160
161     stackString = message;
162     stack.forEach(function (frame) {
163       stackString += "\n";
164       stackString += "    at " + frame.file + ":" + frame.line;
165       if (frame["function"] && frame["function"] !== '') {
166         return stackString += " in " + frame["function"];
167       }
168     });
169
170     return this.errors.push({
171       message: message,
172       stack: stackString
173     });
174   };
175
176   /**
177    * This callback is invoked when the page requests a resource.
178    * The first argument to the callback is the requestData metadata object.
179    * The second argument is the networkRequest object itself.
180    * @param requestData
181    * @param networkRequest
182    * @return {*}
183    */
184   WebPage.prototype.onResourceRequestedNative = function (requestData, networkRequest) {
185     var abort;
186
187     abort = this.urlBlacklist.some(function (blacklistedUrl) {
188       return requestData.url.indexOf(blacklistedUrl) !== -1;
189     });
190
191     if (abort) {
192       if (this._blockedUrls.indexOf(requestData.url) === -1) {
193         this._blockedUrls.push(requestData.url);
194       }
195       //TODO: check this, as it raises onResourceError
196       return networkRequest.abort();
197     }
198
199     this.lastRequestId = requestData.id;
200     if (requestData.url === this.redirectURL) {
201       this.redirectURL = null;
202       this.requestId = requestData.id;
203     }
204
205     return this._networkTraffic[requestData.id] = {
206       request: requestData,
207       responseParts: []
208     };
209   };
210
211   /**
212    * This callback is invoked when a resource requested by the page is received.
213    * The only argument to the callback is the response metadata object.
214    * @param response
215    * @return {*}
216    */
217   WebPage.prototype.onResourceReceivedNative = function (response) {
218     var networkTrafficElement;
219
220     if ((networkTrafficElement = this._networkTraffic[response.id]) != null) {
221       networkTrafficElement.responseParts.push(response);
222     }
223
224     if (this.requestId === response.id) {
225       if (response.redirectURL) {
226         return this.redirectURL = response.redirectURL;
227       }
228
229       this.statusCode = response.status;
230       return this._responseHeaders = response.headers;
231     }
232   };
233
234   /**
235    * Inject the poltergeist agent into the webpage
236    * @return {Array}
237    */
238   WebPage.prototype.injectAgent = function () {
239     var extension, isAgentInjected, i, extensionsRefLength, extensionsRef, injectionResults;
240
241     isAgentInjected = this["native"]().evaluate(function () {
242       return typeof window.__poltergeist;
243     });
244
245     if (isAgentInjected === "undefined") {
246       this["native"]().injectJs("" + phantom.libraryPath + "/agent.js");
247       extensionsRef = WebPage.EXTENSIONS;
248       injectionResults = [];
249       for (i = 0, extensionsRefLength = extensionsRef.length; i < extensionsRefLength; i++) {
250         extension = extensionsRef[i];
251         injectionResults.push(this["native"]().injectJs(extension));
252       }
253       return injectionResults;
254     }
255   };
256
257   /**
258    * Injects a Javascript file extension into the
259    * @param file
260    * @return {*}
261    */
262   WebPage.prototype.injectExtension = function (file) {
263     //TODO: add error control, for example, check if file already in the extensions array, check if the file exists, etc.
264     WebPage.EXTENSIONS.push(file);
265     return this["native"]().injectJs(file);
266   };
267
268   /**
269    * Returns the native phantomjs webpage object
270    * @return {*}
271    */
272   WebPage.prototype["native"] = function () {
273     if (this.closed) {
274       throw new Poltergeist.NoSuchWindowError;
275     }
276
277     return this._native;
278   };
279
280   /**
281    * Returns the current page window name
282    * @return {*}
283    */
284   WebPage.prototype.windowName = function () {
285     return this["native"]().windowName;
286   };
287
288   /**
289    * Returns the keyCode of a given key as set in the phantomjs values
290    * @param name
291    * @return {number}
292    */
293   WebPage.prototype.keyCode = function (name) {
294     return this["native"]().event.key[name];
295   };
296
297   /**
298    * Waits for the page to reach a certain state
299    * @param state
300    * @param callback
301    * @return {*}
302    */
303   WebPage.prototype.waitState = function (state, callback) {
304     var self = this;
305     if (this.state === state) {
306       return callback.call();
307     } else {
308       return setTimeout((function () {
309         return self.waitState(state, callback);
310       }), 100);
311     }
312   };
313
314   /**
315    * Sets the browser header related to basic authentication protocol
316    * @param user
317    * @param password
318    * @return {boolean}
319    */
320   WebPage.prototype.setHttpAuth = function (user, password) {
321     var allHeaders = this.getCustomHeaders();
322
323     if (user === false || password === false) {
324       if (allHeaders.hasOwnProperty("Authorization")) {
325         delete allHeaders["Authorization"];
326       }
327       this.setCustomHeaders(allHeaders);
328       return true;
329     }
330
331     var userName = user || "";
332     var userPassword = password || "";
333
334     allHeaders["Authorization"] = "Basic " + btoa(userName + ":" + userPassword);
335     this.setCustomHeaders(allHeaders);
336     return true;
337   };
338
339   /**
340    * Returns all the network traffic associated to the rendering of this page
341    * @return {{}}
342    */
343   WebPage.prototype.networkTraffic = function () {
344     return this._networkTraffic;
345   };
346
347   /**
348    * Clears all the recorded network traffic related to the current page
349    * @return {{}}
350    */
351   WebPage.prototype.clearNetworkTraffic = function () {
352     return this._networkTraffic = {};
353   };
354
355   /**
356    * Returns the blocked urls that the page will not load
357    * @return {Array}
358    */
359   WebPage.prototype.blockedUrls = function () {
360     return this._blockedUrls;
361   };
362
363   /**
364    * Clean all the urls that should not be loaded
365    * @return {Array}
366    */
367   WebPage.prototype.clearBlockedUrls = function () {
368     return this._blockedUrls = [];
369   };
370
371   /**
372    * This property stores the content of the web page's currently active frame
373    * (which may or may not be the main frame), enclosed in an HTML/XML element.
374    * @return {string}
375    */
376   WebPage.prototype.content = function () {
377     return this["native"]().frameContent;
378   };
379
380   /**
381    * Returns the current active frame title
382    * @return {string}
383    */
384   WebPage.prototype.title = function () {
385     return this["native"]().frameTitle;
386   };
387
388   /**
389    * Returns if possible the frame url of the frame given by name
390    * @param frameName
391    * @return {string}
392    */
393   WebPage.prototype.frameUrl = function (frameName) {
394     var query;
395
396     query = function (frameName) {
397       var iframeReference;
398       if ((iframeReference = document.querySelector("iframe[name='" + frameName + "']")) != null) {
399         return iframeReference.src;
400       }
401       return void 0;
402     };
403
404     return this.evaluate(query, frameName);
405   };
406
407   /**
408    * Remove the errors caught on the page
409    * @return {Array}
410    */
411   WebPage.prototype.clearErrors = function () {
412     return this.errors = [];
413   };
414
415   /**
416    * Returns the response headers associated to this page
417    * @return {{}}
418    */
419   WebPage.prototype.responseHeaders = function () {
420     var headers;
421     headers = {};
422     this._responseHeaders.forEach(function (item) {
423       return headers[item.name] = item.value;
424     });
425     return headers;
426   };
427
428   /**
429    * Get Cookies visible to the current URL (though, for setting, use of page.addCookie is preferred).
430    * This array will be pre-populated by any existing Cookie data visible to this URL that is stored in the CookieJar, if any.
431    * @return {*}
432    */
433   WebPage.prototype.cookies = function () {
434     return this["native"]().cookies;
435   };
436
437   /**
438    * Delete any Cookies visible to the current URL with a 'name' property matching cookieName.
439    * Returns true if successfully deleted, otherwise false.
440    * @param name
441    * @return {*}
442    */
443   WebPage.prototype.deleteCookie = function (name) {
444     return this["native"]().deleteCookie(name);
445   };
446
447   /**
448    * This property gets the size of the viewport for the layout process.
449    * @return {*}
450    */
451   WebPage.prototype.viewportSize = function () {
452     return this["native"]().viewportSize;
453   };
454
455   /**
456    * This property sets the size of the viewport for the layout process.
457    * @param size
458    * @return {*}
459    */
460   WebPage.prototype.setViewportSize = function (size) {
461     return this["native"]().viewportSize = size;
462   };
463
464   /**
465    * This property specifies the scaling factor for the page.render and page.renderBase64 functions.
466    * @param zoomFactor
467    * @return {*}
468    */
469   WebPage.prototype.setZoomFactor = function (zoomFactor) {
470     return this["native"]().zoomFactor = zoomFactor;
471   };
472
473   /**
474    * This property defines the size of the web page when rendered as a PDF.
475    * See: http://phantomjs.org/api/webpage/property/paper-size.html
476    * @param size
477    * @return {*}
478    */
479   WebPage.prototype.setPaperSize = function (size) {
480     return this["native"]().paperSize = size;
481   };
482
483   /**
484    * This property gets the scroll position of the web page.
485    * @return {*}
486    */
487   WebPage.prototype.scrollPosition = function () {
488     return this["native"]().scrollPosition;
489   };
490
491   /**
492    * This property defines the scroll position of the web page.
493    * @param pos
494    * @return {*}
495    */
496   WebPage.prototype.setScrollPosition = function (pos) {
497     return this["native"]().scrollPosition = pos;
498   };
499
500
501   /**
502    * This property defines the rectangular area of the web page to be rasterized when page.render is invoked.
503    * If no clipping rectangle is set, page.render will process the entire web page.
504    * @return {*}
505    */
506   WebPage.prototype.clipRect = function () {
507     return this["native"]().clipRect;
508   };
509
510   /**
511    * This property defines the rectangular area of the web page to be rasterized when page.render is invoked.
512    * If no clipping rectangle is set, page.render will process the entire web page.
513    * @param rect
514    * @return {*}
515    */
516   WebPage.prototype.setClipRect = function (rect) {
517     return this["native"]().clipRect = rect;
518   };
519
520   /**
521    * Returns the size of an element given by a selector and its position relative to the viewport.
522    * @param selector
523    * @return {Object}
524    */
525   WebPage.prototype.elementBounds = function (selector) {
526     return this["native"]().evaluate(function (selector) {
527       return document.querySelector(selector).getBoundingClientRect();
528     }, selector);
529   };
530
531   /**
532    * Defines the user agent sent to server when the web page requests resources.
533    * @param userAgent
534    * @return {*}
535    */
536   WebPage.prototype.setUserAgent = function (userAgent) {
537     return this["native"]().settings.userAgent = userAgent;
538   };
539
540   /**
541    * Returns the additional HTTP request headers that will be sent to the server for EVERY request.
542    * @return {{}}
543    */
544   WebPage.prototype.getCustomHeaders = function () {
545     return this["native"]().customHeaders;
546   };
547
548   /**
549    * Gets the additional HTTP request headers that will be sent to the server for EVERY request.
550    * @param headers
551    * @return {*}
552    */
553   WebPage.prototype.setCustomHeaders = function (headers) {
554     return this["native"]().customHeaders = headers;
555   };
556
557   /**
558    * Adds a one time only request header, after being used it will be deleted
559    * @param header
560    * @return {Array}
561    */
562   WebPage.prototype.addTempHeader = function (header) {
563     var name, value, tempHeaderResult;
564     tempHeaderResult = [];
565     for (name in header) {
566       if (header.hasOwnProperty(name)) {
567         value = header[name];
568         tempHeaderResult.push(this._tempHeaders[name] = value);
569       }
570     }
571     return tempHeaderResult;
572   };
573
574   /**
575    * Remove the temporary headers we have set via addTempHeader
576    * @return {*}
577    */
578   WebPage.prototype.removeTempHeaders = function () {
579     var allHeaders, name, value, tempHeadersRef;
580     allHeaders = this.getCustomHeaders();
581     tempHeadersRef = this._tempHeaders;
582     for (name in tempHeadersRef) {
583       if (tempHeadersRef.hasOwnProperty(name)) {
584         value = tempHeadersRef[name];
585         delete allHeaders[name];
586       }
587     }
588
589     return this.setCustomHeaders(allHeaders);
590   };
591
592   /**
593    * If possible switch to the frame given by name
594    * @param name
595    * @return {boolean}
596    */
597   WebPage.prototype.pushFrame = function (name) {
598     if (this["native"]().switchToFrame(name)) {
599       this.frames.push(name);
600       return true;
601     }
602     return false;
603   };
604
605   /**
606    * Switch to parent frame, use with caution:
607    * popFrame assumes you are in frame, pop frame not being in a frame
608    * leaves unexpected behaviour
609    * @return {*}
610    */
611   WebPage.prototype.popFrame = function () {
612     //TODO: add some error control here, some way to check we are in a frame or not
613     this.frames.pop();
614     return this["native"]().switchToParentFrame();
615   };
616
617   /**
618    * Returns the webpage dimensions
619    * @return {{top: *, bottom: *, left: *, right: *, viewport: *, document: {height: number, width: number}}}
620    */
621   WebPage.prototype.dimensions = function () {
622     var scroll, viewport;
623     scroll = this.scrollPosition();
624     viewport = this.viewportSize();
625     return {
626       top: scroll.top,
627       bottom: scroll.top + viewport.height,
628       left: scroll.left,
629       right: scroll.left + viewport.width,
630       viewport: viewport,
631       document: this.documentSize()
632     };
633   };
634
635   /**
636    * Returns webpage dimensions that are valid
637    * @return {{top: *, bottom: *, left: *, right: *, viewport: *, document: {height: number, width: number}}}
638    */
639   WebPage.prototype.validatedDimensions = function () {
640     var dimensions, documentDimensions;
641
642     dimensions = this.dimensions();
643     documentDimensions = dimensions.document;
644
645     if (dimensions.right > documentDimensions.width) {
646       dimensions.left = Math.max(0, dimensions.left - (dimensions.right - documentDimensions.width));
647       dimensions.right = documentDimensions.width;
648     }
649
650     if (dimensions.bottom > documentDimensions.height) {
651       dimensions.top = Math.max(0, dimensions.top - (dimensions.bottom - documentDimensions.height));
652       dimensions.bottom = documentDimensions.height;
653     }
654
655     this.setScrollPosition({
656       left: dimensions.left,
657       top: dimensions.top
658     });
659
660     return dimensions;
661   };
662
663   /**
664    * Returns a Poltergeist.Node given by an id
665    * @param id
666    * @return {Poltergeist.Node}
667    */
668   WebPage.prototype.get = function (id) {
669     return new Poltergeist.Node(this, id);
670   };
671
672   /**
673    * Executes a phantomjs mouse event, for more info check: http://phantomjs.org/api/webpage/method/send-event.html
674    * @param name
675    * @param x
676    * @param y
677    * @param button
678    * @return {*}
679    */
680   WebPage.prototype.mouseEvent = function (name, x, y, button) {
681     if (button == null) {
682       button = 'left';
683     }
684     this.sendEvent('mousemove', x, y);
685     return this.sendEvent(name, x, y, button);
686   };
687
688   /**
689    * Evaluates a javascript and returns the evaluation of such script
690    * @return {*}
691    */
692   WebPage.prototype.evaluate = function () {
693     var args, fn;
694     fn = arguments[0];
695     args = [];
696
697     if (2 <= arguments.length) {
698       args = __slice.call(arguments, 1);
699     }
700
701     this.injectAgent();
702     return JSON.parse(this.sanitize(this["native"]().evaluate("function() { return PoltergeistAgent.stringify(" + (this.stringifyCall(fn, args)) + ") }")));
703   };
704
705   /**
706    * Does some string sanitation prior parsing
707    * @param potentialString
708    * @return {*}
709    */
710   WebPage.prototype.sanitize = function (potentialString) {
711     if (typeof potentialString === "string") {
712       return potentialString.replace("\n", "\\n").replace("\r", "\\r");
713     }
714
715     return potentialString;
716   };
717
718   /**
719    * Executes a script into the current page scope
720    * @param script
721    * @return {*}
722    */
723   WebPage.prototype.executeScript = function (script) {
724     return this["native"]().evaluateJavaScript(script);
725   };
726
727   /**
728    * Executes a script via phantomjs evaluation
729    * @return {*}
730    */
731   WebPage.prototype.execute = function () {
732     var args, fn;
733
734     fn = arguments[0];
735     args = [];
736
737     if (2 <= arguments.length) {
738       args = __slice.call(arguments, 1);
739     }
740
741     return this["native"]().evaluate("function() { " + (this.stringifyCall(fn, args)) + " }");
742   };
743
744   /**
745    * Helper methods to do script evaluation and execution
746    * @param fn
747    * @param args
748    * @return {string}
749    */
750   WebPage.prototype.stringifyCall = function (fn, args) {
751     if (args.length === 0) {
752       return "(" + (fn.toString()) + ")()";
753     }
754
755     return "(" + (fn.toString()) + ").apply(this, JSON.parse(" + (JSON.stringify(JSON.stringify(args))) + "))";
756   };
757
758   /**
759    * Binds callbacks to their respective Native implementations
760    * @param name
761    * @return {Function}
762    */
763   WebPage.prototype.bindCallback = function (name) {
764     var self;
765     self = this;
766
767     return this["native"]()[name] = function () {
768       var result;
769       if (self[name + 'Native'] != null) {
770         result = self[name + 'Native'].apply(self, arguments);
771       }
772       if (result !== false && (self[name] != null)) {
773         return self[name].apply(self, arguments);
774       }
775     };
776   };
777
778   /**
779    * Runs a command delegating to the PoltergeistAgent
780    * @param name
781    * @param args
782    * @return {*}
783    */
784   WebPage.prototype.runCommand = function (name, args) {
785     var method, result, selector;
786
787     result = this.evaluate(function (name, args) {
788       return window.__poltergeist.externalCall(name, args);
789     }, name, args);
790
791     if (result !== null) {
792       if (result.error != null) {
793         switch (result.error.message) {
794           case 'PoltergeistAgent.ObsoleteNode':
795             throw new Poltergeist.ObsoleteNode;
796             break;
797           case 'PoltergeistAgent.InvalidSelector':
798             method = args[0];
799             selector = args[1];
800             throw new Poltergeist.InvalidSelector(method, selector);
801             break;
802           default:
803             throw new Poltergeist.BrowserError(result.error.message, result.error.stack);
804         }
805       } else {
806         return result.value;
807       }
808     }
809   };
810
811   /**
812    * Tells if we can go back or not
813    * @return {boolean}
814    */
815   WebPage.prototype.canGoBack = function () {
816     return this["native"]().canGoBack;
817   };
818
819   /**
820    * Tells if we can go forward or not in the browser history
821    * @return {boolean}
822    */
823   WebPage.prototype.canGoForward = function () {
824     return this["native"]().canGoForward;
825   };
826
827   return WebPage;
828
829 }).call(this);