UNPKG

slimerjs-firefox

Version:

This repo includes slimerjs as well as downloads a local copy of Firefox.

1,415 lines (1,263 loc) 67.3 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const { Cc, Ci, Cu, Cr } = require('chrome'); Cu.import('resource://slimerjs/slLauncher.jsm'); Cu.import('resource://slimerjs/slUtils.jsm'); Cu.import('resource://slimerjs/slConsole.jsm'); Cu.import('resource://slimerjs/slConfiguration.jsm'); Cu.import('resource://slimerjs/slimer-sdk/phantom.jsm'); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import('resource://slimerjs/slPhantomJSKeyCode.jsm'); Cu.import('resource://slimerjs/slQTKeyCodeToDOMCode.jsm'); Cu.import('resource://slimerjs/webpageUtils.jsm'); Cu.import('resource://slimerjs/slCookiesManager.jsm'); Cu.import('resource://slimerjs/slDebug.jsm'); const de = Ci.nsIDocumentEncoder const {validateOptions} = require("sdk/deprecated/api-utils"); const fs = require("sdk/io/file"); const base64 = require("sdk/base64"); const Q = require("sdk/core/promise"); const heritage = require("sdk/core/heritage"); const systemPrincipal = Cc['@mozilla.org/systemprincipal;1'] .createInstance(Ci.nsIPrincipal); const netLog = require('net-log'); netLog.startTracer(); /** * create a webpage object * @module webpage */ function create() { let [webpage, win] = _create(null); return webpage; } exports.create = create; /** * @return [webpage, window] */ function _create(parentWebpageInfo) { // ----------------------- private properties and functions for the webpage object /** * the <browser> element loading the webpage content */ var browser = null; var browserJustCreated = true; /** * library path */ var libPath = (slConfiguration.scriptFile ? slConfiguration.scriptFile.parent.clone(): null); /** * utility function to create a sandbox when executing a * user script in the webpage content */ function createSandBox(win) { let sandbox = Cu.Sandbox(win, { 'principal':systemPrincipal, 'sandboxName': browser.currentURI.spec, 'sandboxPrototype': win, 'wantXrays': false }); return sandbox; } var webPageSandbox = null; /** * evaluate javascript code into a sandbox * @see webpage.evaluate(), webpage.evaluateJavascript()... * @param string src the source code to evaluate * @param string file the file name associated to the source code */ function evalInSandbox (src, file) { if (!webPageSandbox) { webPageSandbox = new WeakMap(); } let win = getCurrentFrame(); if (!webPageSandbox.has(win)) { webPageSandbox.set(win, createSandBox(win)); } try { let res = Cu.evalInSandbox(src, webPageSandbox.get(win), 'ECMAv5', file, 1); // QWebFrame.evaluateJavascript() used by PhantomJS // always returns null when no value are returned by // the script. if (res === undefined) { return null; } return res; } catch(e) { if (webpage.onError) { let [msg, stackRes] = getTraceException(e, ''); executePageListener(webpage, 'onError', ['Error: '+msg, stackRes]); return null; } else { throw new Error('Error during javascript evaluation in the web page: '+e) } } } /** * an observer for the Observer Service. * It observes console events. */ var webpageObserver = { QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,Ci.nsIObserver]), observe: function webpageobserver_observe(aSubject, aTopic, aData) { if (aTopic == "console-api-log-event") { if (!webpage.onConsoleMessage) return; // aData == outer window id // aSubject == console event object. see http://mxr.mozilla.org/mozilla-central/source/devtools/shared/Console.jsm#526 var consoleEvent = aSubject.wrappedJSObject; if (webpageUtils.isOurWindow(browser, aData)) { var args = consoleEvent.arguments; if (!Array.isArray(args)) { args = Array.prototype.slice.call(args); } executePageListener(webpage, 'onConsoleMessage', [ args.join(' '), consoleEvent.lineNumber, consoleEvent.filename, consoleEvent.level, consoleEvent.functionName, consoleEvent.timeStamp]); return } return; } } } /** * a listener for the console service, to track errors in the content window. */ var jsErrorListener = { observe:function( aMessage ){ //dump(" ************** jsErrorListener\n"); if (!webpage.onError) return; try { let msg = aMessage.QueryInterface(Ci.nsIScriptError); /*dump(" on error:"+msg.errorMessage+ "("+msg.category+") f:"+msg.flags +" ow:"+msg.outerWindowID +" is:"+webpageUtils.isOurWindow(browser, msg.outerWindowID)+"\n")*/ let frameUrl = webpageUtils.isOurWindow(browser, msg.outerWindowID); if (msg instanceof Ci.nsIScriptError && !(msg.flags & Ci.nsIScriptError.warningFlag) && msg.outerWindowID && frameUrl && msg.category == "content javascript" ) { let [m, stack] = getTraceException(msg, null); webpage.onError(msg.errorMessage, stack); } } catch(e) { //dump("**************** jsErrorListener err:"+e+"\n") } }, QueryInterface: function (iid) { if (!iid.equals(Ci.nsIConsoleListener) && !iid.equals(Ci.nsISupports)) { throw Cr.NS_ERROR_NO_INTERFACE; } return this; } }; /** * build an object of options for the netlogger */ function getNetLoggerOptions(webpage, deferred, firstRequestHeaders) { var wycywigReg = /^wyciwyg:\/\//; var firstRequestHeadersUsed = false; return { _onRequest: function(request) { request = request.QueryInterface(Ci.nsIHttpChannel); if (webpage.settings.userAgent) request.setRequestHeader("User-Agent", webpage.settings.userAgent, false); let h; if (firstRequestHeadersUsed) { h = webpage.customHeaders; } else { h = firstRequestHeaders; firstRequestHeadersUsed = true; } for (var hname in h) { request.setRequestHeader(hname, h[hname], false); } }, onRequest: function(requestData, request) { webpage.resourceRequested(requestData, request); }, onResponse: function(res) { webpage.resourceReceived(res); }, onTimeout : function(res) { webpage.resourceTimeout(res); }, onError: function(err) { webpage.resourceError(err); }, getCaptureTypes: function() { return webpage.captureContent; }, onLoadStarted: function(url){ if (wycywigReg.test(url)) { return; } privProp.loadingProgress = 0; webpage.loadStarted(url, false); }, onURLChanged: function(url){ if (wycywigReg.test(url)) { return; } webpage.urlChanged(url); }, onTransferStarted :null, onContentLoaded: function(url, success){ if (wycywigReg.test(url)) { return; } try { Services.console.unregisterListener(jsErrorListener); }catch(e){} Services.console.registerListener(jsErrorListener); // phantomjs call onInitialized not only at the page creation // but also after the content loading.. don't know why. // let's imitate it. Only after a success if (success) { webpage.initialized(); } }, onLoadFinished: function(url, success){ if (wycywigReg.test(url)) { return; } webpage.loadFinished(success, url, false); if (privProp.staticContentLoading) { privProp.staticContentLoading = false; } if (deferred) deferred.resolve(success); }, onFrameLoadStarted : function(url, duringMainLoad) { if (wycywigReg.test(url)) { return; } if (!duringMainLoad) webpage.loadStarted(url, true) }, onFrameLoadFinished : function(url, success, frameWindow, duringMainLoad) { if (wycywigReg.test(url)) { return; } if (!duringMainLoad) webpage.loadFinished(success, url, true); }, onProgressChange : function(url, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { if (wycywigReg.test(url)) { return; } privProp.loadingProgress = Math.round(aCurTotalProgress/aMaxTotalProgress)*100; } } } /** * object that intercepts all window.open() of the web content */ var slBrowserDOMWindow = { QueryInterface : function(aIID) { if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports)) return this; throw Cr.NS_NOINTERFACE; }, /** * called by nsContentTreeOwner::ProvideWindow * when a window should be opened (window.open is invoked by a web page) * @param aURI in our case, it is always null * @param aWhere nsIBDW.OPEN_DEFAULTWINDOW, OPEN_CURRENTWINDOW OPEN_NEWWINDOW OPEN_NEWTAB OPEN_SWITCHTAB * @param aContext nsIBDW.OPEN_EXTERNAL (external app which ask to open the url), OPEN_NEW * @return the nsIDOMWindow object where to load the URI */ openURI : function(aURI, aOpener, aWhere, aContext) { // create the webpage object for this child window let opener = (webpage.ownsPages?aOpener:null); let parentWPInfo = null; let childPage, win; if (webpage.ownsPages) { parentWPInfo = { window: opener, detachChild:function(child){ let idx = privProp.childWindows.indexOf(child); if (idx != -1) { privProp.childWindows.splice(0,1); } } } } [childPage, win] = _create(parentWPInfo); if (webpage.ownsPages) { privProp.childWindows.push(childPage); } // call the callback webpage.rawPageCreated(childPage); // returns the contentWindow of the browser element // nsContentTreeOwner::ProvideWindow and other will // load the expected URI into it. return win.content; }, openURIInFrame : function(aURI, aOpener, aWhere, aContext) { return null; }, isTabContentWindow : function(aWindow) { return false; } } /** * some private parameters */ var privProp = { clipRect : null, framePath : [], childWindows : [], // list of webpage of child windows settings: {}, viewportSize : {}, staticContentLoading : false, paperSize : null, loadingProgress : 0 } let defaultViewportSize = slConfiguration.getDefaultViewportSize(); privProp.viewportSize.width = defaultViewportSize.width; privProp.viewportSize.height = defaultViewportSize.height; let defaultSettings = slConfiguration.getDefaultWebpageConfig(); for (let p in defaultSettings) { privProp.settings[p] = defaultSettings[p] } function getCurrentFrame() { if (!browser) return null; var win = browser.contentWindow; win.name = ''; // it seems that the root window take the name of the xul window privProp.framePath.forEach(function(frameName){ if (win == null) return; if ((typeof frameName) == 'number') { if (frameName < win.frames.length) { win = win.frames[frameName]; } else win = null; } else if ((typeof frameName) == 'string') { let found = false; for(let i=0; i < win.frames.length; i++) { if (win.frames[i].name == frameName) { win = win.frames[i]; found = true; break; } } if (!found) { win = null; } } else win = null; }); return win; } /** * @return array 0:the webpage object, 1:the chrome window */ function openBlankBrowser(noInitializedEvent) { let options = getNetLoggerOptions(webpage, null, webpage.customHeaders); let ready = false; let parentWindow = (parentWebpageInfo?parentWebpageInfo.window:null); let win = slLauncher.openBrowser(function(nav){ browser = nav; browser.webpage = webpage; browserJustCreated = true; browser.authAttempts = 0; Services.obs.addObserver(webpageObserver, "console-api-log-event", true); netLog.registerBrowser(browser, options); if (!noInitializedEvent) webpage.initialized(); ready = true; }, parentWindow, privProp.viewportSize); win.QueryInterface(Ci.nsIDOMChromeWindow) .browserDOMWindow = slBrowserDOMWindow; // we're waiting synchronously after the initialisation of the new window, because we need // to have a ready browser element and then to have an existing win.content. // slBrowserDOMWindow.openURI needs to return this win.content so the // caller will load the URI into this window object. let thread = Services.tm.currentThread; while (!ready) thread.processNextEvent(true); return [webpage, win]; } function prepareJSEval(func, args) { if (!(func instanceof Function || typeof func === 'function' || typeof func === 'string' || func instanceof String)) { return false; } let argsList = args.map( function(arg){ let type = typeof arg; switch(type) { case 'object': if (!arg || arg instanceof RegExp) { return ""+arg; } case 'array': case 'string': return JSON.stringify(arg); case "date": return "new Date(" + JSON.stringify(arg) + ")"; default: return ""+arg } }); return '('+func.toString()+').apply(this, [' + argsList.join(",") + ']);'; } // ----------------------------------- webpage /** * the webpage object itself * @module webpage */ var webpage = { /** * toString on a webpage object indicates * qtruntimeobject in PhantomJS. * here is an alternate way to know if the * object is a webpage object */ __type : 'qtruntimeobject', get objectName () { return "WebPage"; }, /** Object containing various settings of the web page - javascriptEnabled: false if scripts of the page should not be executed (defaults to true). - loadImages: false to not load images (defaults to true). - localToRemoteUrlAccessEnabled: defines whether local resource (e.g. from file) can access remote URLs or not (defaults to false). - userAgent defines the user agent sent to server when the web page requests resources. - userName sets the user name used for HTTP authentication. - password sets the password used for HTTP authentication. - XSSAuditingEnabled defines whether load requests should be monitored for cross-site scripting attempts (defaults to false). - webSecurityEnabled defines whether web security should be enabled or not (defaults to true). - maxAuthAttempts: integer - resourceTimeout: integer, in milliseconds. warning, it is converted into seconds - javascriptCanOpenWindows - javascriptCanCloseWindows Note: The settings apply only during the initial call to the WebPage#open function. Subsequent modification of the settings object will not have any impact. */ get settings (){ return privProp.settings; }, set settings (val){ privProp.settings = heritage.mix(privProp.settings, val); }, /** * list of regexp matching content types * of resources for which you want to retrieve the content. * The content is then set on the body property of the response * object received by your onResourceReceived callback */ captureContent : [], // ------------------------ cookies and headers /** * list of headers to set on every request for the webpage */ customHeaders : {}, /** * retrieve the list of cookies of the domain of the current url * @return cookie[] */ get cookies() { if (!browser || browserJustCreated || !browser.currentURI) return []; return slCookiesManager.getCookiesForUri(browser.currentURI); }, /** * set a list of cookies for the domain of the web page * @param cookie[] val */ set cookies(val) { if (!browser || browserJustCreated) return; slCookiesManager.setCookies(val, browser.currentURI); }, /** * add a cookie in the cookie manager for the current url * @param cookie cookie * @return boolean true if the cookie has been set */ addCookie: function(cookie) { if (!browser || browserJustCreated) return false; return slCookiesManager.addCookie(cookie, browser.currentURI); }, /** * erase all cookies of the current domain */ clearCookies: function() { if (browser && !browserJustCreated) slCookiesManager.clearCookies(browser.currentURI); }, /** * delete all cookies that have the given name * on the current domain * @param string cookieName the cookie name * @return boolean true if deletion is ok */ deleteCookie: function(cookieName) { if (!browser || browserJustCreated) return false; return slCookiesManager.deleteCookie(cookieName, browser.currentURI); }, // -------------------------------- History get canGoBack () { return browser.canGoBack; }, get canGoForward () { return browser.canGoForward; }, go : function(indexIncrement) { let h = browser.sessionHistory; let index = h.index + indexIncrement; if (index >= h.count || index < 0) return; browser.gotoIndex(index); }, goBack : function() { browser.goBack(); }, goForward : function() { browser.goForward(); }, navigationLocked : false, reload : function() { browser.reload(); }, stop : function() { browser.stop(); }, // -------------------------------- Window manipulation /** * Open a web page in a browser * * It can accept several arguments and only the first * one is required: * * open(url) * open(url, callback) * open(url, httpConf) * open(url, httpConf, callback) * open(url, operation, data) * open(url, operation, data, callback) * open(url, operation, data, headers, callback) * * @param string url the url of the page to open * @param function callback a function called when the page is loaded. it * receives "success" or "fail" as parameter. * @param string|object httpConf see httpConf arg of openUrl * @param string operation * @param string data * @param object headers */ open: function(url, arg1, arg2, arg3, arg4) { switch(arguments.length) { case 1: return this.openUrl(url, 'get'); break; case 2: if (typeof arg1 === 'function') { return this.openUrl(url, 'get', null, arg1); } else { return this.openUrl(url, arg1); } break; case 3: if (typeof arg2 === 'function') { return this.openUrl(url, arg1, null, arg2); } else { return this.openUrl(url, { operation: arg1, data: arg2 }); } break; case 4: return this.openUrl(url, { operation: arg1, data: arg2 }, null, arg3); break; case 5: return this.openUrl(url, { operation: arg1, data: arg2, headers: arg3 }, null, arg4); break; } throw "open: arguments are missing"; }, /** * open a webpage * @param string url the url of the page to load * @param string httpConf the http method 'get', 'post', 'head', 'post', 'delete' * @param object httpConf an object with two properties * operation: http method (default: get) * data: body of the request * headers: (optional) * encoding: (optional, default utf8) * @param object settings it replaces webpage.settings. * @return void */ openUrl: function(url, httpConf, settings, callback) { if (settings) { this.settings = settings; } if (!httpConf) { httpConf = { operation: 'get', } } else if (typeof httpConf == 'string') { httpConf = { operation: httpConf, } } var me = this; // create a promise that we will return let deferred = Q.defer(); deferred.promise.then(function(result) { if (callback) { try { callback(result); callback = null; } catch(e) { slLauncher.showError(e); } } return result; }); let options = getNetLoggerOptions(this, deferred, this.customHeaders); let loadUri = function() { netLog.registerBrowser(browser, options); try { webpageUtils.browserLoadURI(browser, url, httpConf); } catch(e) { // we simulate PhantomJS behavior on url errors options.onLoadStarted(''); options.onURLChanged('about:blank'); if (e.message == 'NS_ERROR_UNKNOWN_PROTOCOL') { options.onRequest({ id: 1, method: httpConf.operation, url: url, time: new Date(), headers: (('headers' in httpConf)?httpConf.headers:[]) }, null ); options.onError({id: 1, url: url, errorCode:301, errorString:"Protocol is unknown", status:null, statusText:null }); options.onResponse( { id: 1, url: url, time: new Date(), headers: [], bodySize: 0, contentType: null, contentCharset: null, redirectURL: null, stage: "end", status: null, statusText: null, // Extensions referrer: "", isFileDownloading : false, body: "" }); } options.onLoadFinished(url, "fail"); } } if (DEBUG_WEBPAGE) slDebugLog("webpage: openUrl "+url+" conf:"+slDebugGetObject(httpConf)); if (browser) { if (browserJustCreated){ webpage.initialized(); browserJustCreated = false; } // don't recreate a browser if already opened. loadUri(); return deferred.promise; } var win = slLauncher.openBrowser(function(nav){ browser = nav; browser.webpage = me; Services.obs.addObserver(webpageObserver, "console-api-log-event", true); browser.stop(); me.initialized(); browserJustCreated = false; browser.authAttempts = 0; loadUri(); }, null, privProp.viewportSize); // to catch window.open() win.QueryInterface(Ci.nsIDOMChromeWindow) .browserDOMWindow= slBrowserDOMWindow; return deferred.promise; }, get loading () { return (privProp.loadingProgress > 0 && privProp.loadingProgress < 100); }, get loadingProgress () { return privProp.loadingProgress; }, /** * close the browser */ close: function() { if (browser) { browser.closing = true; try { Services.console.unregisterListener(jsErrorListener); }catch(e){} try { Services.obs.removeObserver(webpageObserver, "console-api-log-event"); }catch(e){} netLog.unregisterBrowser(browser); this.closing(this); slLauncher.closeBrowser(browser); browser.webpage = null; if (parentWebpageInfo) { parentWebpageInfo.detachChild(this); } } webPageSandbox = null; browser=null; if (DEBUG_WEBPAGE) slDebugLog("webpage: close"); }, /** * function called when the browser is being closed, during a call of WebPage.close() * or during a call of window.close() inside the web page */ onClosing: null, /** * This boolean indicates if pages opening by the webpage (by window.open()) * should be children of the webpage (true) or not (false). Default is true. * * If true, children pages can be retrieved by getPage(), pages, pagesWindowName */ ownsPages : true, /** * Returns a Child Page that matches the given "window.name". * * @param string windowName * @return webpage the found webpage */ getPage: function (windowName) { let pages = privProp.childWindows.filter(function(page){ if(page.windowName == windowName) return true; return false; }); if (pages.length) return pages[0]; return null; }, /** * Returns a list of child pages that this page has currently opened * with `window.open()`. * If a child page is closed (by `window.close()` or by `webpage.close()`), * the page is automatically removed from this list. * * You should not keep a strong reference to this array since you obtain * only a copy, so you won't see changes. * * If "ownsPages" is "false", this list won't owns the child pages. * * @return array list of child pages currently opened. */ get pages () { return privProp.childWindows.filter(function(page){ return true;}); }, /** * Returns a list of window name of child pages. * * The window name is the name given to `window.open()`. * * The list is only from child pages that have been created when * ownsPages was true. * * @return array list of strings */ get pagesWindowName () { return privProp.childWindows.map(function(page){ return page.windowName;}); }, release : function() { this.close(); }, get scrollPosition() { let pos = {top:0, left:0} pos.top = browser.contentWindow.scrollY; pos.left = browser.contentWindow.scrollX; return pos; }, set scrollPosition(val) { let pos = heritage.mix({top:0, left:0}, val); browser.contentWindow.scrollTo(pos.left, pos.top); }, get url() { if (browser && !browserJustCreated) return browser.currentURI.spec; return ""; }, get viewportSize() { if (!browser) return { width: privProp.viewportSize.width, height: privProp.viewportSize.height }; let win = browser.ownerDocument.defaultView.top; return { width: win.innerWidth, height: win.innerHeight } }, set viewportSize(val) { if (typeof val != "object") throw new Error("Bad argument type"); val = heritage.mix({width:privProp.viewportSize.width, height:privProp.viewportSize.height}, val); let w = val.width || privProp.viewportSize.width; let h = val.height || privProp.viewportSize.height; if (typeof w != "number") { w = parseInt(w, 10); } if (typeof h != "number") { h = parseInt(h, 10); } if (w < 0 || h < 0) { return; } privProp.viewportSize.width = w; privProp.viewportSize.height = h; if (!browser) return; let win = browser.ownerDocument.defaultView.top; let domWindowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); if ('setCSSViewport' in domWindowUtils) { domWindowUtils. setCSSViewport(w,h); } win.resizeTo(w,h); domWindowUtils.redraw(1); }, get windowName () { if (!browser) return null; return browser.contentWindow.name; }, // -------------------------------- frames manipulation childFramesCount: function () { return this.framesCount; }, childFramesName : function () { return this.framesName; }, currentFrameName : function () { return this.frameName; }, get frameUrl() { var win = getCurrentFrame(); if (!win){ return ''; } return win.location.href; }, get focusedFrameName () { if (!browser) { return ''; } var win = webpageUtils.getFocusedWindow(); if (win && win.name && win.name != 'webpage') return win.name; return ''; }, get framesCount () { var win = getCurrentFrame(); if (!win){ return 0; } return win.frames.length; }, get frameName () { var win = getCurrentFrame(); if (!win){ return false; } return win.name; }, get framesName () { var win = getCurrentFrame(); if (!win){ return []; } let l = []; for(let i = 0; i < win.frames.length; i++) { l.push(win.frames[i].name); } return l; }, switchToFocusedFrame: function() { if (!browser) { return false; } var win = webpageUtils.getFocusedWindow(); if (!win) return -1; var l = []; while(browser.contentWindow != win) { if (win.name) { l.unshift(win.name) } else { let f = win.parent.frames; let found = false; for (let i=0; i < f.length;i++) { if (f[i] == win) { l.unshift(i); found = true; break; } } if (!found) return -2; } win = win.parent; } privProp.framePath = l; return true; }, switchToFrame: function(frameName) { privProp.framePath.push(frameName); var win = getCurrentFrame(); if (!win){ privProp.framePath.pop(); return false; } return true; }, switchToChildFrame: function(frame) { return this.switchToFrame(frame); }, switchToMainFrame: function() { privProp.framePath = []; }, switchToParentFrame: function() { if (privProp.framePath.length) { privProp.framePath.pop(); return true; } else return false; }, get frameContent() { var win = getCurrentFrame(); if (!win){ return false; } return webpageUtils.getWindowContent(win, null, false); }, set frameContent(val) { var win = getCurrentFrame(); if (!win){ return; } let webNav = win.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation); let docShell = webNav.QueryInterface(Ci.nsIDocShell); if ((typeof content) != "string") { // for given DOM node, serialize it let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/html"] .createInstance(Ci.nsIDocumentEncoder); encoder.init(document, "text/html", de.OutputLFLineBreak | de.OutputRaw); encoder.setNode(content); content = encoder.encodeToString(); } privProp.staticContentLoading = true; webpageUtils.setWindowContent (docShell, content, webNav.currentURI.clone()); // wait until the content is loaded let thread = Services.tm.currentThread; while (privProp.staticContentLoading) thread.processNextEvent(true); }, get framePlainText() { var win = getCurrentFrame(); if (!win){ return false; } return webpageUtils.getWindowContent(win, null, true, privProp.settings.plainTextAllContent); }, get frameTitle() { var win = getCurrentFrame(); if (!win){ return ''; } return win.document.title; }, // -------------------------------- Javascript evaluation /** * Evaluate the given function into the context of the web page content * * @param function func the function to evaluate * @param ... args arguments for the function * * FIXME: modifying a variable in a sandbox * that inherits of the context of a window, * does not propagate the modification into * this context. We have same * issue that https://bugzilla.mozilla.org/show_bug.cgi?id=783499 * the only solution is to do window.myvariable = something in the * given function, instead of myvariable = something * @see a solution used for the Firefox webconsole * https://hg.mozilla.org/mozilla-central/rev/f5d6c95a9de9#l6.374 */ evaluate: function(func, ...args) { if (!browser) throw new Error("WebPage not opened"); let f = prepareJSEval(func, args); if (f === false) { throw new Error("Wrong use of WebPage#evaluate"); } return evalInSandbox(f, 'phantomjs://webpage.evaluate()'); }, evaluateJavaScript: function(src) { if (!browser) throw new Error("WebPage not opened"); return evalInSandbox(src, 'phantomjs://webpage.evaluateJavaScript()'); }, /** * @param function func the function to evaluate * @param integer timeMs time to wait before execution * @param ... args other args are arguments for the function */ evaluateAsync: function(func, timeMs, ...args) { if (!browser) { throw new Error("WebPage not opened"); } let f = prepareJSEval(func, args); if (f === false) { throw new Error("Wrong use of WebPage#evaluateAsync"); } if (timeMs == undefined) { timeMs = 0; } browser.contentWindow.setTimeout(function() { evalInSandbox(f, 'phantomjs://webpage.evaluateAsync()'); }, timeMs) }, includeJs: function(url, callback) { if (!browser) throw new Error("WebPage not opened"); var win = getCurrentFrame(); if (!win){ throw new Error("No window available"); } webpageUtils.evalInWindow (win, null, url, callback); }, get libraryPath () { if (!libPath) return ""; return libPath.path; }, set libraryPath (path) { libPath = slUtils.getMozFile(path); }, /** * FIXME: modifying a variable in a sandbox * that inherits of the context of a window, * does not propagate the modification into * this context. We have same * issue that https://bugzilla.mozilla.org/show_bug.cgi?id=783499 * the only solution is to do window.myvariable = something in the * given function, instead of myvariable = something */ injectJs: function(filename) { if (!browser) { throw new Error("WebPage not opened"); } let f = slUtils.getAbsMozFile(filename, slUtils.workingDirectory); if (!f.exists()) { // filename resolved against the libraryPath property f = slUtils.getAbsMozFile(filename, libPath); if (!f.exists()) { dump("Error injectJs: can't open '"+filename+"'\n"); return false; } } let source = slUtils.readSyncStringFromFile(f); evalInSandbox(source, filename); return true; }, /** * Stop JavaScript within a onLongRunningScript callback. * Called outside of onLongRunningScript it does nothing. */ stopJavaScript : function stopJavaScript () { stopJavaScript.__interrupt__ = true; }, onError : phantom.defaultErrorHandler, // --------------------------------- content manipulation get content () { if (!browser) throw new Error("WebPage not opened"); return webpageUtils.getWindowContent(browser.contentWindow, browser.docShell, false); }, set content(val) { this.setContent(val, null); }, get offlineStoragePath() { return slConfiguration.offlineStoragePath; }, get offlineStorageQuota() { return slConfiguration.offlineStorageDefaultQuota; }, get plainText() { if (!browser) throw new Error("WebPage not opened"); return webpageUtils.getWindowContent(browser.contentWindow, browser.docShell, true, privProp.settings.plainTextAllContent); }, sendEvent: function(eventType, arg1, arg2, button, modifier) { if (!browser) throw new Error("WebPage not opened"); eventType = eventType.toLowerCase(); browser.contentWindow.focus(); let domWindowUtils = browser.contentWindow .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); if (modifier) { let m = 0; let mod = this.event.modifier; if (modifier & mod.shift) m |= domWindowUtils.MODIFIER_SHIFT; if (modifier & mod.alt) m |= domWindowUtils.MODIFIER_ALT; if (modifier & mod.ctrl) m |= domWindowUtils.MODIFIER_CONTROL; if (modifier & mod.meta) m |= domWindowUtils.MODIFIER_META; modifier = m; } else modifier = 0; if (eventType == 'keydown' || eventType == 'keyup') { var keyCode = arg1; if ((typeof keyCode) != "number") { if (keyCode.length == 0) return; keyCode = keyCode.charCodeAt(0); } let DOMKeyCode = convertQTKeyCode(keyCode); if (DOMKeyCode.modifier && modifier == 0) modifier = DOMKeyCode.modifier; if (DEBUG_WEBPAGE) { slDebugLog("webpage: sendEvent DOMEvent:"+eventType+" keycode:"+DOMKeyCode.keyCode+" charCode:"+DOMKeyCode.charCode+" mod:"+modifier) } domWindowUtils.sendKeyEvent(eventType, DOMKeyCode.keyCode, DOMKeyCode.charCode, modifier); return; } else if (eventType == 'keypress') { let key = arg1; if (typeof key == "number") { let DOMKeyCode = convertQTKeyCode(key); if (DEBUG_WEBPAGE) { slDebugLog("webpage: sendEvent DOMEvent:keypress keycode:"+DOMKeyCode.keyCode+" charCode:"+DOMKeyCode.charCode+" mod:"+modifier) } domWindowUtils.sendKeyEvent("keypress", DOMKeyCode.keyCode, DOMKeyCode.charCode, modifier); } else if (key.length == 1) { let charCode = key.charCodeAt(0); let DOMKeyCode = convertQTKeyCode(charCode); if (DEBUG_WEBPAGE) { slDebugLog("webpage: sendEvent DOMEvent:keypress keycode:"+DOMKeyCode.keyCode+" charCode:"+charCode+" mod:"+modifier) } domWindowUtils.sendKeyEvent("keypress", DOMKeyCode.keyCode, charCode, modifier); } else { if (DEBUG_WEBPAGE) { slDebugLog("webpage: sendEvent keydown,keypress,keyup for the string:'"+key+"' mod:"+modifier) } for(let i=0; i < key.length;i++) { let charCode = key.charCodeAt(i); let DOMKeyCode = convertQTKeyCode(charCode); domWindowUtils.sendKeyEvent("keydown", DOMKeyCode.keyCode, DOMKeyCode.charCode, modifier); domWindowUtils.sendKeyEvent("keypress", DOMKeyCode.keyCode, charCode, modifier); domWindowUtils.sendKeyEvent("keyup", DOMKeyCode.keyCode, DOMKeyCode.charCode, modifier); } } return; } let btn = 0; if (button == 'middle') btn = 1; else if (button == 'right') btn = 2; let x = arg1 || 0; let y = arg2 || 0; if (DEBUG_WEBPAGE) { slDebugLog("webpage: sendEvent "+eventType+" x:"+x+" y:"+y+" btn:"+btn+" mod:"+modifier) } // mouse events if (eventType == "mousedown" || eventType == "mouseup" || eventType == "mousemove") { domWindowUtils.sendMouseEvent(eventType, x, y, btn, 1, modifier); webpageUtils.sleepIfJavascriptURI(domWindowUtils, x, y) return; } else if (eventType == "mousedoubleclick") { // this type allowed by phantomjs has no really equivalence // and tests in phantomjs show that it is simply... buggy // note that is undocumented (2013-02-22) domWindowUtils.sendMouseEvent("mousedown", x, y, btn, 2, modifier); webpageUtils.sleepIfJavascriptURI(domWindowUtils, x, y) return; } else if (eventType == "doubleclick") { domWindowUtils.sendMouseEvent("mousedown", x, y, btn, 1, modifier); domWindowUtils.sendMouseEvent("mouseup", x, y, btn, 1, modifier); domWindowUtils.sendMouseEvent("mousedown", x, y, btn, 2, modifier); domWindowUtils.sendMouseEvent("mouseup", x, y, btn, 2, modifier); webpageUtils.sleepIfJavascriptURI(domWindowUtils, x, y) return; } else if (eventType == "click") { domWindowUtils.sendMouseEventToWindow("mousedown", x, y, btn, 1, modifier); domWindowUtils.sendMouseEventToWindow("mous