2012-03-22 19:19:57 +04:00
/ * T h i s S o u r c e C o d e F o r m i s s u b j e c t t o t h e t e r m s o f t h e M o z i l l a P u b l i c
* 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/. */
2015-09-15 21:19:45 +03:00
var { classes : Cc , interfaces : Ci , utils : Cu , results : Cr } = Components ;
2012-05-12 00:06:53 +04:00
2015-09-15 21:19:45 +03:00
var uuidGen = Cc [ "@mozilla.org/uuid-generator;1" ]
2012-05-12 00:06:53 +04:00
. getService ( Ci . nsIUUIDGenerator ) ;
2015-09-15 21:19:45 +03:00
var loader = Cc [ "@mozilla.org/moz/jssubscript-loader;1" ]
2012-05-12 00:06:53 +04:00
. getService ( Ci . mozIJSSubScriptLoader ) ;
2012-03-22 19:19:57 +04:00
2015-03-24 00:32:03 +03:00
loader . loadSubScript ( "chrome://marionette/content/simpletest.js" ) ;
loader . loadSubScript ( "chrome://marionette/content/common.js" ) ;
loader . loadSubScript ( "chrome://marionette/content/actions.js" ) ;
Cu . import ( "chrome://marionette/content/elements.js" ) ;
2015-04-15 14:18:00 +03:00
Cu . import ( "chrome://marionette/content/error.js" ) ;
2012-06-04 21:50:06 +04:00
Cu . import ( "resource://gre/modules/FileUtils.jsm" ) ;
2013-01-23 23:26:33 +04:00
Cu . import ( "resource://gre/modules/NetUtil.jsm" ) ;
2015-08-21 17:00:29 +03:00
Cu . import ( "resource://gre/modules/Task.jsm" ) ;
2013-01-23 23:26:33 +04:00
Cu . import ( "resource://gre/modules/XPCOMUtils.jsm" ) ;
2015-09-15 21:19:45 +03:00
var utils = { } ;
2012-04-11 04:28:08 +04:00
utils . window = content ;
2012-05-12 00:06:53 +04:00
// Load Event/ChromeUtils for use with JS scripts:
loader . loadSubScript ( "chrome://marionette/content/EventUtils.js" , utils ) ;
2012-04-11 04:28:08 +04:00
loader . loadSubScript ( "chrome://marionette/content/ChromeUtils.js" , utils ) ;
loader . loadSubScript ( "chrome://marionette/content/atoms.js" , utils ) ;
2015-03-24 00:32:03 +03:00
loader . loadSubScript ( "chrome://marionette/content/sendkeys.js" , utils ) ;
2012-05-19 00:30:13 +04:00
2015-09-15 21:19:45 +03:00
var marionetteLogObj = new MarionetteLogObj ( ) ;
2012-03-22 19:19:57 +04:00
2015-09-15 21:19:45 +03:00
var isB2G = false ;
2012-03-22 19:19:57 +04:00
2015-09-15 21:19:45 +03:00
var marionetteTestName ;
var winUtil = content . QueryInterface ( Ci . nsIInterfaceRequestor )
2015-04-02 17:16:00 +03:00
. getInterface ( Ci . nsIDOMWindowUtils ) ;
2015-09-15 21:19:45 +03:00
var listenerId = null ; // unique ID of this listener
var curContainer = { frame : content , shadowRoot : null } ;
var isRemoteBrowser = ( ) => curContainer . frame . contentWindow !== null ;
var previousContainer = null ;
var elementManager = new ElementManager ( [ ] ) ;
var accessibility = new Accessibility ( ) ;
var actions = new ActionChain ( utils , checkForInterrupted ) ;
var importedScripts = null ;
2012-03-22 19:19:57 +04:00
2015-05-19 02:36:15 +03:00
// Contains the last file input element that was the target of
// sendKeysToElement.
2015-09-15 21:19:45 +03:00
var fileInputElement ;
2015-05-19 02:36:15 +03:00
2015-04-23 23:39:38 +03:00
// A dict of sandboxes used this session
2015-09-15 21:19:45 +03:00
var sandboxes = { } ;
2015-04-23 23:39:38 +03:00
// The name of the current sandbox
2015-09-15 21:19:45 +03:00
var sandboxName = 'default' ;
2012-05-09 23:05:39 +04:00
2013-02-12 22:10:03 +04:00
// the unload handler
2015-09-15 21:19:45 +03:00
var onunload ;
2013-02-12 22:10:03 +04:00
2012-05-09 23:05:39 +04:00
// Flag to indicate whether an async script is currently running or not.
2015-09-15 21:19:45 +03:00
var asyncTestRunning = false ;
var asyncTestCommandId ;
var asyncTestTimeoutId ;
2013-08-28 21:06:03 +04:00
2015-09-15 21:19:45 +03:00
var inactivityTimeoutId = null ;
var heartbeatCallback = function ( ) { } ; // Called by the simpletest methods.
2013-08-28 21:06:03 +04:00
2015-09-15 21:19:45 +03:00
var originalOnError ;
2012-11-14 22:35:44 +04:00
//timer for doc changes
2015-09-15 21:19:45 +03:00
var checkTimer = Cc [ "@mozilla.org/timer;1" ] . createInstance ( Ci . nsITimer ) ;
2013-10-17 19:25:11 +04:00
//timer for readystate
2015-09-15 21:19:45 +03:00
var readyStateTimer = Cc [ "@mozilla.org/timer;1" ] . createInstance ( Ci . nsITimer ) ;
2015-03-11 02:19:40 +03:00
// timer for navigation commands.
2015-09-15 21:19:45 +03:00
var navTimer = Cc [ "@mozilla.org/timer;1" ] . createInstance ( Ci . nsITimer ) ;
var onDOMContentLoaded ;
2013-01-22 23:27:44 +04:00
// Send move events about this often
2015-09-15 21:19:45 +03:00
var EVENT _INTERVAL = 30 ; // milliseconds
2013-03-19 00:42:46 +04:00
// last touch for each fingerId
2015-09-15 21:19:45 +03:00
var multiLast = { } ;
2013-05-15 00:54:07 +04:00
2013-08-26 22:55:58 +04:00
Cu . import ( "resource://gre/modules/Log.jsm" ) ;
2015-09-15 21:19:45 +03:00
var logger = Log . repository . getLogger ( "Marionette" ) ;
2015-03-24 00:32:03 +03:00
logger . info ( "loaded listener.js" ) ;
2015-09-15 21:19:45 +03:00
var modalHandler = function ( ) {
2014-04-10 18:26:08 +04:00
// This gets called on the system app only since it receives the mozbrowserprompt event
2013-09-19 21:35:19 +04:00
sendSyncMessage ( "Marionette:switchedToFrame" , { frameValue : null , storePrevious : true } ) ;
let isLocal = sendSyncMessage ( "MarionetteFrame:handleModal" , { } ) [ 0 ] . value ;
if ( isLocal ) {
2015-08-28 23:43:54 +03:00
previousContainer = curContainer ;
2013-09-19 21:35:19 +04:00
}
2015-08-28 23:43:54 +03:00
curContainer = { frame : content , shadowRoot : null } ;
2013-09-19 21:35:19 +04:00
} ;
2013-05-15 00:54:07 +04:00
2012-03-22 19:19:57 +04:00
/ * *
2014-01-21 20:40:20 +04:00
* Called when listener is first started up .
2012-03-22 19:19:57 +04:00
* The listener sends its unique window ID and its current URI to the actor .
* If the actor returns an ID , we start the listeners . Otherwise , nothing happens .
* /
function registerSelf ( ) {
2015-04-15 14:18:00 +03:00
let msg = { value : winUtil . outerWindowID } ;
2014-04-10 18:26:08 +04:00
// register will have the ID and a boolean describing if this is the main process or not
2012-11-27 06:19:04 +04:00
let register = sendSyncMessage ( "Marionette:register" , msg ) ;
2012-03-22 19:19:57 +04:00
if ( register [ 0 ] ) {
2015-03-11 02:19:40 +03:00
let { id , remotenessChange } = register [ 0 ] [ 0 ] ;
2015-09-09 18:53:08 +03:00
let { B2G , raisesAccessibilityExceptions } = register [ 0 ] [ 2 ] ;
isB2G = B2G ;
accessibility . strict = raisesAccessibilityExceptions ;
2015-03-11 02:19:40 +03:00
listenerId = id ;
if ( typeof id != "undefined" ) {
2014-10-21 19:22:26 +04:00
// check if we're the main process
if ( register [ 0 ] [ 1 ] == true ) {
addMessageListener ( "MarionetteMainListener:emitTouchEvent" , emitTouchEventForIFrame ) ;
}
importedScripts = FileUtils . getDir ( 'TmpD' , [ ] , false ) ;
importedScripts . append ( 'marionetteContentScripts' ) ;
startListeners ( ) ;
2015-03-20 00:12:58 +03:00
let rv = { } ;
2015-03-11 02:19:40 +03:00
if ( remotenessChange ) {
2015-03-20 00:12:58 +03:00
rv . listenerId = id ;
2015-03-11 02:19:40 +03:00
}
2015-03-20 00:12:58 +03:00
sendAsyncMessage ( "Marionette:listenersAttached" , rv ) ;
2014-04-10 18:26:08 +04:00
}
2012-03-22 19:19:57 +04:00
}
}
2014-04-10 18:26:08 +04:00
function emitTouchEventForIFrame ( message ) {
Bug 1001090 - Part 4: Fix errors in chrome code. (r=zombie,gavin,fitzgen,dcamp,bgrins,fabrice,gwagner,margaret,mrbkap,mak,njn,vicamo)
2014-09-16 03:30:46 +04:00
message = message . json ;
2015-03-20 04:41:19 +03:00
let identifier = actions . nextTouchId ;
2015-01-20 01:12:00 +03:00
2015-08-28 23:43:54 +03:00
let domWindowUtils = curContainer . frame .
2015-01-20 01:12:00 +03:00
QueryInterface ( Components . interfaces . nsIInterfaceRequestor ) .
getInterface ( Components . interfaces . nsIDOMWindowUtils ) ;
var ratio = domWindowUtils . screenPixelsPerCSSPixel ;
var typeForUtils ;
switch ( message . type ) {
case 'touchstart' :
typeForUtils = domWindowUtils . TOUCH _CONTACT ;
break ;
case 'touchend' :
typeForUtils = domWindowUtils . TOUCH _REMOVE ;
break ;
case 'touchcancel' :
typeForUtils = domWindowUtils . TOUCH _CANCEL ;
break ;
case 'touchmove' :
typeForUtils = domWindowUtils . TOUCH _CONTACT ;
break ;
}
domWindowUtils . sendNativeTouchPoint ( identifier , typeForUtils ,
Math . round ( message . screenX * ratio ) , Math . round ( message . screenY * ratio ) ,
message . force , 90 ) ;
2014-04-10 18:26:08 +04:00
}
2015-08-21 17:00:29 +03:00
// Eventually we will not have a closure for every single command, but
// use a generic dispatch for all listener commands.
//
// Perhaps one could even conceive having a separate instance of
// CommandProcessor for the listener, because the code is mostly the same.
2015-04-15 14:18:00 +03:00
function dispatch ( fn ) {
return function ( msg ) {
let id = msg . json . command _id ;
2015-08-21 17:00:29 +03:00
let req = Task . spawn ( function * ( ) {
2015-04-15 14:18:00 +03:00
let rv ;
if ( typeof msg . json == "undefined" || msg . json instanceof Array ) {
2015-08-21 17:00:29 +03:00
return yield fn . apply ( null , msg . json ) ;
2015-04-15 14:18:00 +03:00
} else {
2015-08-21 17:00:29 +03:00
return yield fn ( msg . json ) ;
2015-04-15 14:18:00 +03:00
}
2015-08-21 17:00:29 +03:00
} ) ;
2015-04-15 14:18:00 +03:00
2015-08-21 17:00:29 +03:00
let okOrValueResponse = rv => {
2015-04-15 14:18:00 +03:00
if ( typeof rv == "undefined" ) {
sendOk ( id ) ;
} else {
sendResponse ( { value : rv } , id ) ;
}
2015-08-21 17:00:29 +03:00
} ;
req . then ( okOrValueResponse , err => sendError ( err , id ) )
. catch ( error . report ) ;
2015-04-15 14:18:00 +03:00
} ;
}
2012-05-12 00:06:53 +04:00
/ * *
* Add a message listener that ' s tied to our listenerId .
* /
function addMessageListenerId ( messageName , handler ) {
addMessageListener ( messageName + listenerId , handler ) ;
}
/ * *
* Remove a message listener that ' s tied to our listenerId .
* /
function removeMessageListenerId ( messageName , handler ) {
removeMessageListener ( messageName + listenerId , handler ) ;
}
2015-09-17 19:12:29 +03:00
var getTitleFn = dispatch ( getTitle ) ;
var getPageSourceFn = dispatch ( getPageSource ) ;
var getActiveElementFn = dispatch ( getActiveElement ) ;
var clickElementFn = dispatch ( clickElement ) ;
var goBackFn = dispatch ( goBack ) ;
var getElementAttributeFn = dispatch ( getElementAttribute ) ;
var getElementTextFn = dispatch ( getElementText ) ;
var getElementTagNameFn = dispatch ( getElementTagName ) ;
var getElementRectFn = dispatch ( getElementRect ) ;
var isElementEnabledFn = dispatch ( isElementEnabled ) ;
var getCurrentUrlFn = dispatch ( getCurrentUrl ) ;
var findElementContentFn = dispatch ( findElementContent ) ;
var findElementsContentFn = dispatch ( findElementsContent ) ;
var isElementSelectedFn = dispatch ( isElementSelected ) ;
var clearElementFn = dispatch ( clearElement ) ;
var isElementDisplayedFn = dispatch ( isElementDisplayed ) ;
var getElementValueOfCssPropertyFn = dispatch ( getElementValueOfCssProperty ) ;
var switchToShadowRootFn = dispatch ( switchToShadowRoot ) ;
var getCookiesFn = dispatch ( getCookies ) ;
var singleTapFn = dispatch ( singleTap ) ;
2015-04-15 14:18:00 +03:00
2012-03-22 19:19:57 +04:00
/ * *
* Start all message listeners
* /
function startListeners ( ) {
2015-05-19 02:36:15 +03:00
addMessageListenerId ( "Marionette:receiveFiles" , receiveFiles ) ;
2012-05-12 00:06:53 +04:00
addMessageListenerId ( "Marionette:newSession" , newSession ) ;
addMessageListenerId ( "Marionette:executeScript" , executeScript ) ;
addMessageListenerId ( "Marionette:executeAsyncScript" , executeAsyncScript ) ;
addMessageListenerId ( "Marionette:executeJSScript" , executeJSScript ) ;
2015-09-10 18:45:33 +03:00
addMessageListenerId ( "Marionette:singleTap" , singleTapFn ) ;
2013-03-05 05:09:58 +04:00
addMessageListenerId ( "Marionette:actionChain" , actionChain ) ;
2013-03-19 00:42:46 +04:00
addMessageListenerId ( "Marionette:multiAction" , multiAction ) ;
2014-01-24 17:39:23 +04:00
addMessageListenerId ( "Marionette:get" , get ) ;
2015-03-11 02:19:40 +03:00
addMessageListenerId ( "Marionette:pollForReadyState" , pollForReadyState ) ;
addMessageListenerId ( "Marionette:cancelRequest" , cancelRequest ) ;
2015-08-21 12:57:06 +03:00
addMessageListenerId ( "Marionette:getCurrentUrl" , getCurrentUrlFn ) ;
2015-08-21 13:04:06 +03:00
addMessageListenerId ( "Marionette:getTitle" , getTitleFn ) ;
2015-08-21 13:11:36 +03:00
addMessageListenerId ( "Marionette:getPageSource" , getPageSourceFn ) ;
2015-08-21 13:19:53 +03:00
addMessageListenerId ( "Marionette:goBack" , goBackFn ) ;
2012-05-12 00:06:53 +04:00
addMessageListenerId ( "Marionette:goForward" , goForward ) ;
addMessageListenerId ( "Marionette:refresh" , refresh ) ;
2015-08-21 17:05:13 +03:00
addMessageListenerId ( "Marionette:findElementContent" , findElementContentFn ) ;
2015-08-21 17:03:03 +03:00
addMessageListenerId ( "Marionette:findElementsContent" , findElementsContentFn ) ;
2015-04-15 14:18:00 +03:00
addMessageListenerId ( "Marionette:getActiveElement" , getActiveElementFn ) ;
addMessageListenerId ( "Marionette:clickElement" , clickElementFn ) ;
addMessageListenerId ( "Marionette:getElementAttribute" , getElementAttributeFn ) ;
addMessageListenerId ( "Marionette:getElementText" , getElementTextFn ) ;
addMessageListenerId ( "Marionette:getElementTagName" , getElementTagNameFn ) ;
2015-08-21 17:49:47 +03:00
addMessageListenerId ( "Marionette:isElementDisplayed" , isElementDisplayedFn ) ;
2015-08-21 18:10:02 +03:00
addMessageListenerId ( "Marionette:getElementValueOfCssProperty" , getElementValueOfCssPropertyFn ) ;
2015-04-15 14:18:00 +03:00
addMessageListenerId ( "Marionette:getElementRect" , getElementRectFn ) ;
addMessageListenerId ( "Marionette:isElementEnabled" , isElementEnabledFn ) ;
2015-08-21 17:09:37 +03:00
addMessageListenerId ( "Marionette:isElementSelected" , isElementSelectedFn ) ;
2012-05-12 00:06:53 +04:00
addMessageListenerId ( "Marionette:sendKeysToElement" , sendKeysToElement ) ;
2015-08-21 17:37:04 +03:00
addMessageListenerId ( "Marionette:clearElement" , clearElementFn ) ;
2012-05-12 00:06:53 +04:00
addMessageListenerId ( "Marionette:switchToFrame" , switchToFrame ) ;
2015-08-28 23:43:54 +03:00
addMessageListenerId ( "Marionette:switchToShadowRoot" , switchToShadowRootFn ) ;
2012-05-12 00:06:53 +04:00
addMessageListenerId ( "Marionette:deleteSession" , deleteSession ) ;
addMessageListenerId ( "Marionette:sleepSession" , sleepSession ) ;
2012-05-19 00:30:43 +04:00
addMessageListenerId ( "Marionette:emulatorCmdResult" , emulatorCmdResult ) ;
2012-06-04 21:50:06 +04:00
addMessageListenerId ( "Marionette:importScript" , importScript ) ;
2012-08-24 02:07:16 +04:00
addMessageListenerId ( "Marionette:getAppCacheStatus" , getAppCacheStatus ) ;
2012-11-01 02:36:57 +04:00
addMessageListenerId ( "Marionette:setTestName" , setTestName ) ;
2015-10-05 20:36:54 +03:00
addMessageListenerId ( "Marionette:takeScreenshot" , takeScreenshot ) ;
2012-11-22 19:53:44 +04:00
addMessageListenerId ( "Marionette:addCookie" , addCookie ) ;
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 13:26:58 +03:00
addMessageListenerId ( "Marionette:getCookies" , getCookiesFn ) ;
2012-11-22 19:53:44 +04:00
addMessageListenerId ( "Marionette:deleteAllCookies" , deleteAllCookies ) ;
addMessageListenerId ( "Marionette:deleteCookie" , deleteCookie ) ;
2012-03-22 19:19:57 +04:00
}
2013-10-17 19:25:11 +04:00
/ * *
* Used during newSession and restart , called to set up the modal dialog listener in b2g
* /
function waitForReady ( ) {
if ( content . document . readyState == 'complete' ) {
readyStateTimer . cancel ( ) ;
content . addEventListener ( "mozbrowsershowmodalprompt" , modalHandler , false ) ;
content . addEventListener ( "unload" , waitForReady , false ) ;
}
else {
readyStateTimer . initWithCallback ( waitForReady , 100 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
}
}
2012-03-22 19:19:57 +04:00
/ * *
* Called when we start a new session . It registers the
* current environment , and resets all values
* /
function newSession ( msg ) {
isB2G = msg . json . B2G ;
2014-12-23 00:15:19 +03:00
accessibility . strict = msg . json . raisesAccessibilityExceptions ;
2012-03-22 19:19:57 +04:00
resetValues ( ) ;
2013-10-17 19:25:11 +04:00
if ( isB2G ) {
readyStateTimer . initWithCallback ( waitForReady , 100 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
2014-01-15 18:28:04 +04:00
// We have to set correct mouse event source to MOZ_SOURCE_TOUCH
// to offer a way for event listeners to differentiate
// events being the result of a physical mouse action.
// This is especially important for the touch event shim,
// in order to prevent creating touch event for these fake mouse events.
2015-03-20 04:41:19 +03:00
actions . inputSource = Ci . nsIDOMMouseEvent . MOZ _SOURCE _TOUCH ;
2013-10-17 19:25:11 +04:00
}
2012-03-22 19:19:57 +04:00
}
2014-01-21 20:40:20 +04:00
2012-03-22 19:19:57 +04:00
/ * *
* Puts the current session to sleep , so all listeners are removed except
* for the 'restart' listener . This is used to keep the content listener
* alive for reuse in B2G instead of reloading it each time .
* /
function sleepSession ( msg ) {
deleteSession ( ) ;
addMessageListener ( "Marionette:restart" , restart ) ;
}
/ * *
* Restarts all our listeners after this listener was put to sleep
* /
2012-11-27 06:19:04 +04:00
function restart ( msg ) {
2012-03-22 19:19:57 +04:00
removeMessageListener ( "Marionette:restart" , restart ) ;
2013-10-17 19:25:11 +04:00
if ( isB2G ) {
readyStateTimer . initWithCallback ( waitForReady , 100 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
}
2012-03-22 19:19:57 +04:00
registerSelf ( ) ;
}
/ * *
* Removes all listeners
* /
function deleteSession ( msg ) {
2015-05-19 02:36:15 +03:00
removeMessageListenerId ( "Marionette:receiveFiles" , receiveFiles ) ;
2012-05-12 00:06:53 +04:00
removeMessageListenerId ( "Marionette:newSession" , newSession ) ;
removeMessageListenerId ( "Marionette:executeScript" , executeScript ) ;
removeMessageListenerId ( "Marionette:executeAsyncScript" , executeAsyncScript ) ;
removeMessageListenerId ( "Marionette:executeJSScript" , executeJSScript ) ;
2015-09-10 18:45:33 +03:00
removeMessageListenerId ( "Marionette:singleTap" , singleTapFn ) ;
2013-03-05 05:09:58 +04:00
removeMessageListenerId ( "Marionette:actionChain" , actionChain ) ;
2013-03-19 00:42:46 +04:00
removeMessageListenerId ( "Marionette:multiAction" , multiAction ) ;
2014-01-24 17:39:23 +04:00
removeMessageListenerId ( "Marionette:get" , get ) ;
2015-03-11 02:19:40 +03:00
removeMessageListenerId ( "Marionette:pollForReadyState" , pollForReadyState ) ;
removeMessageListenerId ( "Marionette:cancelRequest" , cancelRequest ) ;
2015-08-21 13:04:06 +03:00
removeMessageListenerId ( "Marionette:getTitle" , getTitleFn ) ;
2015-08-21 13:11:36 +03:00
removeMessageListenerId ( "Marionette:getPageSource" , getPageSourceFn ) ;
2015-08-21 12:57:06 +03:00
removeMessageListenerId ( "Marionette:getCurrentUrl" , getCurrentUrlFn ) ;
2015-08-21 13:19:53 +03:00
removeMessageListenerId ( "Marionette:goBack" , goBackFn ) ;
2012-05-12 00:06:53 +04:00
removeMessageListenerId ( "Marionette:goForward" , goForward ) ;
removeMessageListenerId ( "Marionette:refresh" , refresh ) ;
2015-08-21 17:05:13 +03:00
removeMessageListenerId ( "Marionette:findElementContent" , findElementContentFn ) ;
2015-08-21 17:03:03 +03:00
removeMessageListenerId ( "Marionette:findElementsContent" , findElementsContentFn ) ;
2015-04-15 14:18:00 +03:00
removeMessageListenerId ( "Marionette:getActiveElement" , getActiveElementFn ) ;
removeMessageListenerId ( "Marionette:clickElement" , clickElementFn ) ;
removeMessageListenerId ( "Marionette:getElementAttribute" , getElementAttributeFn ) ;
removeMessageListenerId ( "Marionette:getElementText" , getElementTextFn ) ;
removeMessageListenerId ( "Marionette:getElementTagName" , getElementTagNameFn ) ;
2015-08-21 17:49:47 +03:00
removeMessageListenerId ( "Marionette:isElementDisplayed" , isElementDisplayedFn ) ;
2015-08-21 18:10:02 +03:00
removeMessageListenerId ( "Marionette:getElementValueOfCssProperty" , getElementValueOfCssPropertyFn ) ;
2015-04-15 14:18:00 +03:00
removeMessageListenerId ( "Marionette:getElementRect" , getElementRectFn ) ;
removeMessageListenerId ( "Marionette:isElementEnabled" , isElementEnabledFn ) ;
2015-08-21 17:09:37 +03:00
removeMessageListenerId ( "Marionette:isElementSelected" , isElementSelectedFn ) ;
2012-05-12 00:06:53 +04:00
removeMessageListenerId ( "Marionette:sendKeysToElement" , sendKeysToElement ) ;
2015-08-21 17:37:04 +03:00
removeMessageListenerId ( "Marionette:clearElement" , clearElementFn ) ;
2012-05-12 00:06:53 +04:00
removeMessageListenerId ( "Marionette:switchToFrame" , switchToFrame ) ;
2015-08-28 23:43:54 +03:00
removeMessageListenerId ( "Marionette:switchToShadowRoot" , switchToShadowRootFn ) ;
2012-05-12 00:06:53 +04:00
removeMessageListenerId ( "Marionette:deleteSession" , deleteSession ) ;
removeMessageListenerId ( "Marionette:sleepSession" , sleepSession ) ;
2012-05-19 00:30:43 +04:00
removeMessageListenerId ( "Marionette:emulatorCmdResult" , emulatorCmdResult ) ;
2012-06-04 21:50:06 +04:00
removeMessageListenerId ( "Marionette:importScript" , importScript ) ;
2012-08-24 02:07:16 +04:00
removeMessageListenerId ( "Marionette:getAppCacheStatus" , getAppCacheStatus ) ;
2012-11-01 02:36:57 +04:00
removeMessageListenerId ( "Marionette:setTestName" , setTestName ) ;
2015-10-05 20:36:54 +03:00
removeMessageListenerId ( "Marionette:takeScreenshot" , takeScreenshot ) ;
2012-11-22 19:53:44 +04:00
removeMessageListenerId ( "Marionette:addCookie" , addCookie ) ;
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 13:26:58 +03:00
removeMessageListenerId ( "Marionette:getCookies" , getCookiesFn ) ;
2012-11-22 19:53:44 +04:00
removeMessageListenerId ( "Marionette:deleteAllCookies" , deleteAllCookies ) ;
removeMessageListenerId ( "Marionette:deleteCookie" , deleteCookie ) ;
2013-10-17 19:25:11 +04:00
if ( isB2G ) {
content . removeEventListener ( "mozbrowsershowmodalprompt" , modalHandler , false ) ;
}
2013-11-24 09:32:27 +04:00
elementManager . reset ( ) ;
2015-08-28 23:43:54 +03:00
// reset container frame to the top-most frame
curContainer = { frame : content , shadowRoot : null } ;
curContainer . frame . focus ( ) ;
2015-03-20 04:41:19 +03:00
actions . touchIds = { } ;
2012-03-22 19:19:57 +04:00
}
/ *
2014-01-21 20:40:20 +04:00
* Helper methods
2012-03-22 19:19:57 +04:00
* /
/ * *
* Generic method to send a message to the server
* /
2015-04-15 14:18:00 +03:00
function sendToServer ( name , data , objs , id ) {
if ( ! data ) {
data = { }
2012-03-22 19:19:57 +04:00
}
2015-04-15 14:18:00 +03:00
if ( id ) {
data . command _id = id ;
}
sendAsyncMessage ( name , data , objs ) ;
2012-03-22 19:19:57 +04:00
}
/ * *
* Send response back to server
* /
function sendResponse ( value , command _id ) {
2015-04-15 14:18:00 +03:00
sendToServer ( "Marionette:done" , value , null , command _id ) ;
2012-03-22 19:19:57 +04:00
}
/ * *
* Send ack back to server
* /
function sendOk ( command _id ) {
2015-04-15 14:18:00 +03:00
sendToServer ( "Marionette:ok" , null , null , command _id ) ;
2012-03-22 19:19:57 +04:00
}
/ * *
* Send log message to server
* /
function sendLog ( msg ) {
2015-04-15 14:18:00 +03:00
sendToServer ( "Marionette:log" , { message : msg } ) ;
2012-03-22 19:19:57 +04:00
}
/ * *
* Send error message to server
* /
2015-04-15 14:18:00 +03:00
function sendError ( err , cmdId ) {
sendToServer ( "Marionette:error" , null , { error : err } , cmdId ) ;
2012-03-22 19:19:57 +04:00
}
/ * *
* Clear test values after completion of test
* /
function resetValues ( ) {
2015-04-23 23:39:38 +03:00
sandboxes = { } ;
2015-08-28 23:43:54 +03:00
curContainer = { frame : content , shadowRoot : null } ;
2015-03-20 04:41:19 +03:00
actions . mouseEventsOnly = false ;
2012-03-22 19:19:57 +04:00
}
2013-09-13 11:33:41 +04:00
/ * *
* Dump a logline to stdout . Prepends logline with a timestamp .
* /
function dumpLog ( logline ) {
dump ( Date . now ( ) + " Marionette: " + logline ) ;
}
2013-06-22 04:13:35 +04:00
2013-09-19 21:35:19 +04:00
/ * *
* Check if our context was interrupted
* /
function wasInterrupted ( ) {
2015-08-28 23:43:54 +03:00
if ( previousContainer ) {
2013-09-19 21:35:19 +04:00
let element = content . document . elementFromPoint ( ( content . innerWidth / 2 ) , ( content . innerHeight / 2 ) ) ;
if ( element . id . indexOf ( "modal-dialog" ) == - 1 ) {
return true ;
}
else {
return false ;
}
}
return sendSyncMessage ( "MarionetteFrame:getInterruptedState" , { } ) [ 0 ] . value ;
}
2015-03-20 04:41:19 +03:00
function checkForInterrupted ( ) {
if ( wasInterrupted ( ) ) {
2015-08-28 23:43:54 +03:00
if ( previousContainer ) {
// if previousContainer is set, then we're in a single process environment
curContainer = actions . container = previousContainer ;
previousContainer = null ;
2015-03-20 04:41:19 +03:00
}
else {
//else we're in OOP environment, so we'll switch to the original OOP frame
sendSyncMessage ( "Marionette:switchToModalOrigin" ) ;
}
sendSyncMessage ( "Marionette:switchedToFrame" , { restorePrevious : true } ) ;
}
}
2012-03-22 19:19:57 +04:00
/ *
* Marionette Methods
* /
/ * *
* Returns a content sandbox that can be used by the execute _foo functions .
* /
2015-04-28 21:16:46 +03:00
function createExecuteContentSandbox ( win , timeout ) {
let mn = new Marionette (
win ,
"content" ,
marionetteLogObj ,
timeout ,
heartbeatCallback ,
marionetteTestName ) ;
2015-04-23 23:39:38 +03:00
let principal = win ;
2015-09-08 19:12:18 +03:00
if ( sandboxName == "system" ) {
principal = Cc [ "@mozilla.org/systemprincipal;1" ] . createInstance ( Ci . nsIPrincipal ) ;
2015-04-23 23:39:38 +03:00
}
let sandbox = new Cu . Sandbox ( principal , { sandboxPrototype : win } ) ;
2012-05-09 23:05:39 +04:00
sandbox . global = sandbox ;
2015-04-28 21:16:46 +03:00
sandbox . window = win ;
2012-03-22 19:19:57 +04:00
sandbox . document = sandbox . window . document ;
sandbox . navigator = sandbox . window . navigator ;
2012-04-11 04:28:08 +04:00
sandbox . testUtils = utils ;
2013-03-25 22:09:16 +04:00
sandbox . asyncTestCommandId = asyncTestCommandId ;
2015-04-28 21:16:46 +03:00
sandbox . marionette = mn ;
2012-03-22 19:19:57 +04:00
2015-04-28 21:16:46 +03:00
mn . exports . forEach ( fn => {
if ( typeof mn [ fn ] == "function" ) {
sandbox [ fn ] = mn [ fn ] . bind ( mn ) ;
} else {
sandbox [ fn ] = mn [ fn ] ;
2012-09-15 01:34:38 +04:00
}
2012-03-22 19:19:57 +04:00
} ) ;
2015-09-08 19:12:18 +03:00
sandbox . runEmulatorCmd = ( cmd , cb ) => this . runEmulatorCmd ( cmd , cb ) ;
sandbox . runEmulatorShell = ( args , cb ) => this . runEmulatorShell ( args , cb ) ;
2012-03-22 19:19:57 +04:00
2015-04-28 21:16:46 +03:00
sandbox . asyncComplete = ( obj , id ) => {
2015-04-15 14:18:00 +03:00
if ( id == asyncTestCommandId ) {
2015-08-28 23:43:54 +03:00
curContainer . frame . removeEventListener ( "unload" , onunload , false ) ;
curContainer . frame . clearTimeout ( asyncTestTimeoutId ) ;
2012-05-09 23:05:39 +04:00
2013-08-28 21:06:03 +04:00
if ( inactivityTimeoutId != null ) {
2015-08-28 23:43:54 +03:00
curContainer . frame . clearTimeout ( inactivityTimeoutId ) ;
2013-08-28 21:06:03 +04:00
}
2013-04-29 20:38:54 +04:00
sendSyncMessage ( "Marionette:shareData" ,
2015-04-15 14:18:00 +03:00
{ log : elementManager . wrapValue ( marionetteLogObj . getLogs ( ) ) } ) ;
2013-03-25 22:09:16 +04:00
marionetteLogObj . clearLogs ( ) ;
2012-11-28 01:58:58 +04:00
2015-04-15 14:18:00 +03:00
if ( error . isError ( obj ) ) {
sendError ( obj , id ) ;
} else {
2013-03-25 22:09:16 +04:00
if ( Object . keys ( _emu _cbs ) . length ) {
_emu _cbs = { } ;
2015-04-15 14:18:00 +03:00
sendError ( new WebDriverError ( "Emulator callback still pending when finish() called" ) , id ) ;
} else {
sendResponse ( { value : elementManager . wrapValue ( obj ) } , id ) ;
2013-03-25 22:09:16 +04:00
}
2012-11-28 01:58:58 +04:00
}
2012-05-09 23:05:39 +04:00
2013-03-25 22:09:16 +04:00
asyncTestRunning = false ;
asyncTestTimeoutId = undefined ;
asyncTestCommandId = undefined ;
2013-08-28 21:06:03 +04:00
inactivityTimeoutId = null ;
2013-03-25 22:09:16 +04:00
}
2012-05-09 23:05:39 +04:00
} ;
2015-04-08 21:02:34 +03:00
sandbox . finish = function ( ) {
2012-05-09 23:05:39 +04:00
if ( asyncTestRunning ) {
2015-04-28 21:16:46 +03:00
sandbox . asyncComplete ( mn . generate _results ( ) , sandbox . asyncTestCommandId ) ;
2012-05-09 23:05:39 +04:00
} else {
2015-04-28 21:16:46 +03:00
return mn . generate _results ( ) ;
2012-05-09 23:05:39 +04:00
}
} ;
2015-04-15 14:18:00 +03:00
sandbox . marionetteScriptFinished = val =>
sandbox . asyncComplete ( val , sandbox . asyncTestCommandId ) ;
2012-05-09 23:05:39 +04:00
2015-04-23 23:39:38 +03:00
sandboxes [ sandboxName ] = sandbox ;
2012-03-22 19:19:57 +04:00
}
/ * *
* Execute the given script either as a function body ( executeScript )
2015-04-15 14:18:00 +03:00
* or directly ( for mochitest like JS Marionette tests ) .
2012-03-22 19:19:57 +04:00
* /
function executeScript ( msg , directInject ) {
2013-08-28 21:06:03 +04:00
// Set up inactivity timeout.
if ( msg . json . inactivityTimeout ) {
let setTimer = function ( ) {
2015-08-28 23:43:54 +03:00
inactivityTimeoutId = curContainer . frame . setTimeout ( function ( ) {
2015-04-15 14:18:00 +03:00
sendError ( new ScriptTimeoutError ( "timed out due to inactivity" ) , asyncTestCommandId ) ;
2013-08-28 21:06:03 +04:00
} , msg . json . inactivityTimeout ) ;
} ;
setTimer ( ) ;
2015-04-15 14:18:00 +03:00
heartbeatCallback = function ( ) {
2015-08-28 23:43:54 +03:00
curContainer . frame . clearTimeout ( inactivityTimeoutId ) ;
2013-08-28 21:06:03 +04:00
setTimer ( ) ;
} ;
}
2013-02-12 22:10:03 +04:00
asyncTestCommandId = msg . json . command _id ;
2013-10-01 19:13:04 +04:00
let script = msg . json . script ;
2015-09-29 13:02:48 +03:00
let filename = msg . json . filename ;
2015-04-23 23:39:38 +03:00
sandboxName = msg . json . sandboxName ;
2012-03-22 19:19:57 +04:00
2015-04-23 23:39:38 +03:00
if ( msg . json . newSandbox ||
! ( sandboxName in sandboxes ) ||
2015-08-28 23:43:54 +03:00
( sandboxes [ sandboxName ] . window != curContainer . frame ) ) {
createExecuteContentSandbox ( curContainer . frame , msg . json . timeout ) ;
2015-04-23 23:39:38 +03:00
if ( ! sandboxes [ sandboxName ] ) {
2015-04-15 14:18:00 +03:00
sendError ( new WebDriverError ( "Could not create sandbox!" ) , asyncTestCommandId ) ;
2012-05-09 23:05:39 +04:00
return ;
}
2015-04-15 14:18:00 +03:00
} else {
2015-04-23 23:39:38 +03:00
sandboxes [ sandboxName ] . asyncTestCommandId = asyncTestCommandId ;
2013-03-25 22:09:16 +04:00
}
2012-03-22 19:19:57 +04:00
2015-04-23 23:39:38 +03:00
let sandbox = sandboxes [ sandboxName ] ;
2012-03-22 19:19:57 +04:00
try {
if ( directInject ) {
2012-06-04 21:50:06 +04:00
if ( importedScripts . exists ( ) ) {
2014-01-21 20:40:20 +04:00
let stream = Components . classes [ "@mozilla.org/network/file-input-stream;1" ] .
2012-06-04 21:50:06 +04:00
createInstance ( Components . interfaces . nsIFileInputStream ) ;
stream . init ( importedScripts , - 1 , 0 , 0 ) ;
let data = NetUtil . readInputStreamToString ( stream , stream . available ( ) ) ;
2014-06-11 22:26:28 +04:00
stream . close ( ) ;
2012-06-04 21:50:06 +04:00
script = data + script ;
}
2015-09-29 13:02:48 +03:00
let res = Cu . evalInSandbox ( script , sandbox , "1.8" , filename ? filename : "dummy file" , 0 ) ;
2013-04-29 20:38:54 +04:00
sendSyncMessage ( "Marionette:shareData" ,
{ log : elementManager . wrapValue ( marionetteLogObj . getLogs ( ) ) } ) ;
2012-03-22 19:19:57 +04:00
marionetteLogObj . clearLogs ( ) ;
2013-04-29 20:38:54 +04:00
2012-03-22 19:19:57 +04:00
if ( res == undefined || res . passed == undefined ) {
2015-04-15 14:18:00 +03:00
sendError ( new JavaScriptError ( "Marionette.finish() not called" ) , asyncTestCommandId ) ;
2012-03-22 19:19:57 +04:00
}
else {
2012-11-27 06:19:04 +04:00
sendResponse ( { value : elementManager . wrapValue ( res ) } , asyncTestCommandId ) ;
2012-03-22 19:19:57 +04:00
}
}
else {
2012-05-09 23:05:39 +04:00
try {
2014-07-21 01:36:31 +04:00
sandbox . _ _marionetteParams = Cu . cloneInto ( elementManager . convertWrappedArguments (
2015-08-28 23:43:54 +03:00
msg . json . args , curContainer ) , sandbox , { wrapReflectors : true } ) ;
2015-04-15 14:18:00 +03:00
} catch ( e ) {
sendError ( e , asyncTestCommandId ) ;
2012-05-09 23:05:39 +04:00
return ;
}
2015-10-07 15:03:21 +03:00
script = "var __marionetteFunc = function(){" + script + "};" +
2013-06-22 04:13:35 +04:00
"__marionetteFunc.apply(null, __marionetteParams);" ;
2012-06-04 21:50:06 +04:00
if ( importedScripts . exists ( ) ) {
2014-01-21 20:40:20 +04:00
let stream = Components . classes [ "@mozilla.org/network/file-input-stream;1" ] .
2012-06-04 21:50:06 +04:00
createInstance ( Components . interfaces . nsIFileInputStream ) ;
stream . init ( importedScripts , - 1 , 0 , 0 ) ;
let data = NetUtil . readInputStreamToString ( stream , stream . available ( ) ) ;
2014-06-11 22:26:28 +04:00
stream . close ( ) ;
2013-06-22 04:13:35 +04:00
script = data + script ;
2012-06-04 21:50:06 +04:00
}
2015-09-29 13:02:48 +03:00
let res = Cu . evalInSandbox ( script , sandbox , "1.8" , filename ? filename : "dummy file" , 0 ) ;
2013-04-29 20:38:54 +04:00
sendSyncMessage ( "Marionette:shareData" ,
{ log : elementManager . wrapValue ( marionetteLogObj . getLogs ( ) ) } ) ;
2012-03-22 19:19:57 +04:00
marionetteLogObj . clearLogs ( ) ;
2012-11-27 06:19:04 +04:00
sendResponse ( { value : elementManager . wrapValue ( res ) } , asyncTestCommandId ) ;
2012-03-22 19:19:57 +04:00
}
2015-04-15 14:18:00 +03:00
} catch ( e ) {
let err = new JavaScriptError (
e ,
"execute_script" ,
msg . json . filename ,
msg . json . line ,
script ) ;
sendError ( err , asyncTestCommandId ) ;
2012-03-22 19:19:57 +04:00
}
}
2012-11-01 02:36:57 +04:00
/ * *
* Sets the test name , used in logging messages .
* /
function setTestName ( msg ) {
marionetteTestName = msg . json . value ;
2012-11-27 06:19:04 +04:00
sendOk ( msg . json . command _id ) ;
2012-11-01 02:36:57 +04:00
}
2012-03-22 19:19:57 +04:00
/ * *
* Execute async script
* /
function executeAsyncScript ( msg ) {
executeWithCallback ( msg ) ;
}
2015-05-19 02:36:15 +03:00
/ * *
* Receive file objects from chrome in order to complete a
* sendKeysToElement action on a file input element .
* /
function receiveFiles ( msg ) {
if ( 'error' in msg . json ) {
let err = new InvalidArgumentError ( msg . json . error ) ;
sendError ( err , msg . json . command _id ) ;
return ;
}
if ( ! fileInputElement ) {
let err = new InvalidElementStateError ( "receiveFiles called with no valid fileInputElement" ) ;
sendError ( err , msg . json . command _id ) ;
return ;
}
let fs = Array . prototype . slice . call ( fileInputElement . files ) ;
fs . push ( msg . json . file ) ;
fileInputElement . mozSetFileArray ( fs ) ;
fileInputElement = null ;
sendOk ( msg . json . command _id ) ;
}
2012-03-22 19:19:57 +04:00
/ * *
* Execute pure JS test . Handles both async and sync cases .
* /
function executeJSScript ( msg ) {
2012-11-29 01:31:23 +04:00
if ( msg . json . async ) {
executeWithCallback ( msg , msg . json . async ) ;
2012-03-22 19:19:57 +04:00
}
else {
executeScript ( msg , true ) ;
}
}
/ * *
* This function is used by executeAsync and executeJSScript to execute a script
2014-01-21 20:40:20 +04:00
* in a sandbox .
*
2012-03-22 19:19:57 +04:00
* For executeJSScript , it will return a message only when the finish ( ) method is called .
2014-01-21 20:40:20 +04:00
* For executeAsync , it will return a response when marionetteScriptFinished / arguments [ arguments . length - 1 ]
2012-03-22 19:19:57 +04:00
* method is called , or if it times out .
* /
2012-11-30 19:42:43 +04:00
function executeWithCallback ( msg , useFinish ) {
2013-08-28 21:06:03 +04:00
// Set up inactivity timeout.
if ( msg . json . inactivityTimeout ) {
let setTimer = function ( ) {
2015-08-28 23:43:54 +03:00
inactivityTimeoutId = curContainer . frame . setTimeout ( function ( ) {
2015-04-28 20:59:07 +03:00
sandbox . asyncComplete ( new ScriptTimeoutError ( "timed out due to inactivity" ) , asyncTestCommandId ) ;
2013-08-28 21:06:03 +04:00
} , msg . json . inactivityTimeout ) ;
} ;
setTimer ( ) ;
2015-04-15 14:18:00 +03:00
heartbeatCallback = function ( ) {
2015-08-28 23:43:54 +03:00
curContainer . frame . clearTimeout ( inactivityTimeoutId ) ;
2013-08-28 21:06:03 +04:00
setTimer ( ) ;
} ;
}
2013-10-01 19:13:04 +04:00
let script = msg . json . script ;
2015-09-29 13:02:48 +03:00
let filename = msg . json . filename ;
2013-02-12 22:10:03 +04:00
asyncTestCommandId = msg . json . command _id ;
2015-04-23 23:39:38 +03:00
sandboxName = msg . json . sandboxName ;
2013-02-12 22:10:03 +04:00
onunload = function ( ) {
2015-04-15 14:18:00 +03:00
sendError ( new JavaScriptError ( "unload was called" ) , asyncTestCommandId ) ;
2013-02-12 22:10:03 +04:00
} ;
2015-08-28 23:43:54 +03:00
curContainer . frame . addEventListener ( "unload" , onunload , false ) ;
2012-03-22 19:19:57 +04:00
2015-04-23 23:39:38 +03:00
if ( msg . json . newSandbox ||
! ( sandboxName in sandboxes ) ||
2015-08-28 23:43:54 +03:00
( sandboxes [ sandboxName ] . window != curContainer . frame ) ) {
createExecuteContentSandbox ( curContainer . frame , msg . json . timeout ) ;
2015-04-23 23:39:38 +03:00
if ( ! sandboxes [ sandboxName ] ) {
2015-04-15 14:18:00 +03:00
sendError ( new JavaScriptError ( "Could not create sandbox!" ) , asyncTestCommandId ) ;
2012-05-11 03:07:07 +04:00
return ;
}
}
2013-03-25 22:09:16 +04:00
else {
2015-04-23 23:39:38 +03:00
sandboxes [ sandboxName ] . asyncTestCommandId = asyncTestCommandId ;
2013-03-25 22:09:16 +04:00
}
2015-04-23 23:39:38 +03:00
let sandbox = sandboxes [ sandboxName ] ;
2012-11-27 06:19:04 +04:00
sandbox . tag = script ;
2012-05-11 03:07:07 +04:00
2015-08-28 23:43:54 +03:00
asyncTestTimeoutId = curContainer . frame . setTimeout ( function ( ) {
2015-04-15 14:18:00 +03:00
sandbox . asyncComplete ( new ScriptTimeoutError ( "timed out" ) , asyncTestCommandId ) ;
2012-11-29 01:31:23 +04:00
} , msg . json . timeout ) ;
2012-05-11 03:07:07 +04:00
2015-08-28 23:43:54 +03:00
originalOnError = curContainer . frame . onerror ;
curContainer . frame . onerror = function errHandler ( msg , url , line ) {
2015-04-15 14:18:00 +03:00
sandbox . asyncComplete ( new JavaScriptError ( msg + "@" + url + ", line " + line ) , asyncTestCommandId ) ;
2015-08-28 23:43:54 +03:00
curContainer . frame . onerror = originalOnError ;
2012-12-18 18:21:28 +04:00
} ;
2012-03-22 19:19:57 +04:00
let scriptSrc ;
2012-11-30 19:42:43 +04:00
if ( useFinish ) {
2012-11-29 01:31:23 +04:00
if ( msg . json . timeout == null || msg . json . timeout == 0 ) {
2015-04-15 14:18:00 +03:00
sendError ( new TimeoutError ( "Please set a timeout" ) , asyncTestCommandId ) ;
2012-03-22 19:19:57 +04:00
}
scriptSrc = script ;
}
else {
2012-05-09 23:05:39 +04:00
try {
2014-07-21 01:36:31 +04:00
sandbox . _ _marionetteParams = Cu . cloneInto ( elementManager . convertWrappedArguments (
2015-08-28 23:43:54 +03:00
msg . json . args , curContainer ) , sandbox , { wrapReflectors : true } ) ;
2015-04-15 14:18:00 +03:00
} catch ( e ) {
sendError ( e , asyncTestCommandId ) ;
2012-05-09 23:05:39 +04:00
return ;
}
scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" +
2015-10-07 15:03:21 +03:00
"var __marionetteFunc = function() { " + script + "};" +
2012-03-22 19:19:57 +04:00
"__marionetteFunc.apply(null, __marionetteParams); " ;
}
try {
2012-05-09 23:05:39 +04:00
asyncTestRunning = true ;
2012-06-04 21:50:06 +04:00
if ( importedScripts . exists ( ) ) {
2012-09-01 00:59:48 +04:00
let stream = Cc [ "@mozilla.org/network/file-input-stream;1" ] .
createInstance ( Ci . nsIFileInputStream ) ;
2012-06-04 21:50:06 +04:00
stream . init ( importedScripts , - 1 , 0 , 0 ) ;
let data = NetUtil . readInputStreamToString ( stream , stream . available ( ) ) ;
2014-06-11 22:26:28 +04:00
stream . close ( ) ;
2012-06-04 21:50:06 +04:00
scriptSrc = data + scriptSrc ;
}
2015-09-29 13:02:48 +03:00
Cu . evalInSandbox ( scriptSrc , sandbox , "1.8" , filename ? filename : "dummy file" , 0 ) ;
2012-03-22 19:19:57 +04:00
} catch ( e ) {
2015-04-15 14:18:00 +03:00
let err = new JavaScriptError (
e ,
"execute_async_script" ,
msg . json . filename ,
msg . json . line ,
scriptSrc ) ;
sandbox . asyncComplete ( err , asyncTestCommandId ) ;
2012-03-22 19:19:57 +04:00
}
}
2013-01-22 23:27:44 +04:00
/ * *
* This function creates a touch event given a touch type and a touch
* /
2014-06-10 19:34:27 +04:00
function emitTouchEvent ( type , touch ) {
2013-09-19 21:35:19 +04:00
if ( ! wasInterrupted ( ) ) {
2014-06-10 19:34:27 +04:00
let loggingInfo = "emitting Touch event of type " + type + " to element with id: " + touch . target . id + " and tag name: " + touch . target . tagName + " at coordinates (" + touch . clientX + ", " + touch . clientY + ") relative to the viewport" ;
dumpLog ( loggingInfo ) ;
2015-08-28 23:43:54 +03:00
var docShell = curContainer . frame . document . defaultView .
2014-04-10 18:26:08 +04:00
QueryInterface ( Components . interfaces . nsIInterfaceRequestor ) .
getInterface ( Components . interfaces . nsIWebNavigation ) .
QueryInterface ( Components . interfaces . nsIDocShell ) ;
2015-03-20 04:41:19 +03:00
if ( docShell . asyncPanZoomEnabled && actions . scrolling ) {
2014-04-10 18:26:08 +04:00
// if we're in APZ and we're scrolling, we must use injectTouchEvent to dispatch our touchmove events
let index = sendSyncMessage ( "MarionetteFrame:getCurrentFrameId" ) ;
// only call emitTouchEventForIFrame if we're inside an iframe.
if ( index != null ) {
2015-01-20 01:12:00 +03:00
sendSyncMessage ( "Marionette:emitTouchEvent" ,
{ index : index , type : type , id : touch . identifier ,
clientX : touch . clientX , clientY : touch . clientY ,
screenX : touch . screenX , screenY : touch . screenY ,
radiusX : touch . radiusX , radiusY : touch . radiusY ,
rotation : touch . rotationAngle , force : touch . force } ) ;
2014-06-10 19:34:27 +04:00
return ;
2014-04-10 18:26:08 +04:00
}
}
// we get here if we're not in asyncPacZoomEnabled land, or if we're the main process
2013-09-19 21:35:19 +04:00
/ *
Disabled per bug 888303
marionetteLogObj . log ( loggingInfo , "TRACE" ) ;
sendSyncMessage ( "Marionette:shareData" ,
{ log : elementManager . wrapValue ( marionetteLogObj . getLogs ( ) ) } ) ;
marionetteLogObj . clearLogs ( ) ;
* /
2015-08-28 23:43:54 +03:00
let domWindowUtils = curContainer . frame . QueryInterface ( Components . interfaces . nsIInterfaceRequestor ) . getInterface ( Components . interfaces . nsIDOMWindowUtils ) ;
2014-01-15 18:28:04 +04:00
domWindowUtils . sendTouchEvent ( type , [ touch . identifier ] , [ touch . clientX ] , [ touch . clientY ] , [ touch . radiusX ] , [ touch . radiusY ] , [ touch . rotationAngle ] , [ touch . force ] , 1 , 0 ) ;
2013-09-19 21:35:19 +04:00
}
2013-01-22 23:27:44 +04:00
}
/ * *
2013-05-27 21:12:13 +04:00
* This function generates a pair of coordinates relative to the viewport given a
* target element and coordinates relative to that element ' s top - left corner .
* @ param 'x' , and 'y' are the relative to the target .
2013-01-22 23:27:44 +04:00
* If they are not specified , then the center of the target is used .
* /
2013-05-27 21:12:13 +04:00
function coordinates ( target , x , y ) {
2013-03-05 05:09:58 +04:00
let box = target . getBoundingClientRect ( ) ;
2013-05-27 21:12:13 +04:00
if ( x == null ) {
x = box . width / 2 ;
2013-01-22 23:27:44 +04:00
}
2013-05-27 21:12:13 +04:00
if ( y == null ) {
y = box . height / 2 ;
2013-01-22 23:27:44 +04:00
}
2013-05-27 21:12:13 +04:00
let coords = { } ;
coords . x = box . left + x ;
coords . y = box . top + y ;
2013-01-22 23:27:44 +04:00
return coords ;
}
2015-03-20 04:41:19 +03:00
2013-01-22 23:27:44 +04:00
/ * *
2014-10-16 12:32:00 +04:00
* This function returns true if the given coordinates are in the viewport .
* @ param 'x' , and 'y' are the coordinates relative to the target .
* If they are not specified , then the center of the target is used .
2013-01-22 23:27:44 +04:00
* /
2014-10-16 12:32:00 +04:00
function elementInViewport ( el , x , y ) {
let c = coordinates ( el , x , y ) ;
2015-08-28 23:43:54 +03:00
let curFrame = curContainer . frame ;
2013-09-19 21:35:19 +04:00
let viewPort = { top : curFrame . pageYOffset ,
left : curFrame . pageXOffset ,
bottom : ( curFrame . pageYOffset + curFrame . innerHeight ) ,
right : ( curFrame . pageXOffset + curFrame . innerWidth ) } ;
2014-10-16 12:32:00 +04:00
return ( viewPort . left <= c . x + curFrame . pageXOffset &&
c . x + curFrame . pageXOffset <= viewPort . right &&
viewPort . top <= c . y + curFrame . pageYOffset &&
c . y + curFrame . pageYOffset <= viewPort . bottom ) ;
2013-01-22 23:27:44 +04:00
}
/ * *
2014-10-16 12:32:00 +04:00
* This function throws the visibility of the element error if the element is
* not displayed or the given coordinates are not within the viewport .
* @ param 'x' , and 'y' are the coordinates relative to the target .
* If they are not specified , then the center of the target is used .
2013-01-22 23:27:44 +04:00
* /
2014-10-16 12:32:00 +04:00
function checkVisible ( el , x , y ) {
2014-11-11 00:44:00 +03:00
// Bug 1094246 - Webdriver's isShown doesn't work with content xul
if ( utils . getElementAttribute ( el , "namespaceURI" ) . indexOf ( "there.is.only.xul" ) == - 1 ) {
//check if the element is visible
let visible = utils . isElementDisplayed ( el ) ;
if ( ! visible ) {
return false ;
}
2013-03-02 01:29:10 +04:00
}
2014-11-11 00:44:00 +03:00
2013-04-12 00:52:42 +04:00
if ( el . tagName . toLowerCase ( ) === 'body' ) {
return true ;
}
2014-10-16 12:32:00 +04:00
if ( ! elementInViewport ( el , x , y ) ) {
2013-01-22 23:27:44 +04:00
//check if scroll function exist. If so, call it.
if ( el . scrollIntoView ) {
2013-09-05 21:38:36 +04:00
el . scrollIntoView ( false ) ;
2013-03-02 01:29:10 +04:00
if ( ! elementInViewport ( el ) ) {
return false ;
}
2013-01-22 23:27:44 +04:00
}
2013-03-02 01:29:10 +04:00
else {
2013-01-22 23:27:44 +04:00
return false ;
}
2013-03-02 01:29:10 +04:00
}
return true ;
2013-01-22 23:27:44 +04:00
}
2013-05-27 21:12:13 +04:00
2013-01-22 23:27:44 +04:00
/ * *
* Function that perform a single tap
* /
2015-09-10 18:45:33 +03:00
function singleTap ( id , corx , cory ) {
let el = elementManager . getKnownElement ( id , curContainer ) ;
// after this block, the element will be scrolled into view
let visible = checkVisible ( el , corx , cory ) ;
if ( ! visible ) {
throw new ElementNotVisibleError ( "Element is not currently visible and may not be manipulated" ) ;
}
let acc = accessibility . getAccessibleObject ( el , true ) ;
checkVisibleAccessibility ( acc , el , visible ) ;
checkActionableAccessibility ( acc , el ) ;
if ( ! curContainer . frame . document . createTouch ) {
actions . mouseEventsOnly = true ;
}
let c = coordinates ( el , corx , cory ) ;
if ( ! actions . mouseEventsOnly ) {
let touchId = actions . nextTouchId ++ ;
let touch = createATouch ( el , c . x , c . y , touchId ) ;
emitTouchEvent ( 'touchstart' , touch ) ;
emitTouchEvent ( 'touchend' , touch ) ;
2013-01-22 23:27:44 +04:00
}
2015-09-10 18:45:33 +03:00
actions . mouseTap ( el . ownerDocument , c . x , c . y ) ;
2013-01-22 23:27:44 +04:00
}
2014-12-23 00:16:59 +03:00
/ * *
* Check if the element ' s unavailable accessibility state matches the enabled
* state
* @ param nsIAccessible object
2015-08-05 21:40:18 +03:00
* @ param WebElement corresponding to nsIAccessible object
2014-12-23 00:16:59 +03:00
* @ param Boolean enabled element ' s enabled state
* /
2015-08-05 21:40:18 +03:00
function checkEnabledAccessibility ( accesible , element , enabled ) {
2014-12-23 00:16:59 +03:00
if ( ! accesible ) {
return ;
}
2015-08-05 21:40:18 +03:00
let disabledAccessibility = accessibility . matchState (
accesible , 'STATE_UNAVAILABLE' ) ;
2015-08-28 23:43:54 +03:00
let explorable = curContainer . frame . document . defaultView . getComputedStyle (
2015-08-05 21:40:18 +03:00
element , null ) . getPropertyValue ( 'pointer-events' ) !== 'none' ;
let message ;
if ( ! explorable && ! disabledAccessibility ) {
message = 'Element is enabled but is not explorable via the ' +
'accessibility API' ;
} else if ( enabled && disabledAccessibility ) {
message = 'Element is enabled but disabled via the accessibility API' ;
} else if ( ! enabled && ! disabledAccessibility ) {
message = 'Element is disabled but enabled via the accessibility API' ;
2014-12-23 00:16:59 +03:00
}
2015-09-10 18:45:33 +03:00
accessibility . handleErrorMessage ( message , element ) ;
2014-12-23 00:16:59 +03:00
}
2014-12-23 00:15:19 +03:00
/ * *
* Check if the element ' s visible state corresponds to its accessibility API
* visibility
* @ param nsIAccessible object
2015-09-10 18:45:33 +03:00
* @ param WebElement corresponding to nsIAccessible object
2014-12-23 00:15:19 +03:00
* @ param Boolean visible element ' s visibility state
* /
2015-09-10 18:45:33 +03:00
function checkVisibleAccessibility ( accesible , element , visible ) {
2014-12-23 00:15:19 +03:00
if ( ! accesible ) {
return ;
}
let hiddenAccessibility = accessibility . isHidden ( accesible ) ;
let message ;
if ( visible && hiddenAccessibility ) {
message = 'Element is not currently visible via the accessibility API ' +
'and may not be manipulated by it' ;
} else if ( ! visible && ! hiddenAccessibility ) {
message = 'Element is currently only visible via the accessibility API ' +
'and can be manipulated by it' ;
}
2015-09-10 18:45:33 +03:00
accessibility . handleErrorMessage ( message , element ) ;
2014-12-23 00:15:19 +03:00
}
/ * *
* Check if it is possible to activate an element with the accessibility API
* @ param nsIAccessible object
2015-09-10 18:45:33 +03:00
* @ param WebElement corresponding to nsIAccessible object
2014-12-23 00:15:19 +03:00
* /
2015-09-10 18:45:33 +03:00
function checkActionableAccessibility ( accesible , element ) {
2014-12-23 00:15:19 +03:00
if ( ! accesible ) {
return ;
}
let message ;
if ( ! accessibility . hasActionCount ( accesible ) ) {
message = 'Element does not support any accessible actions' ;
} else if ( ! accessibility . isActionableRole ( accesible ) ) {
message = 'Element does not have a correct accessibility role ' +
'and may not be manipulated via the accessibility API' ;
} else if ( ! accessibility . hasValidName ( accesible ) ) {
message = 'Element is missing an accesible name' ;
2015-08-05 21:40:18 +03:00
} else if ( ! accessibility . matchState ( accesible , 'STATE_FOCUSABLE' ) ) {
message = 'Element is not focusable via the accessibility API' ;
}
2015-09-10 18:45:33 +03:00
accessibility . handleErrorMessage ( message , element ) ;
2015-08-05 21:40:18 +03:00
}
/ * *
* Check if element ' s selected state corresponds to its accessibility API
* selected state .
* @ param nsIAccessible object
2015-09-10 18:45:33 +03:00
* @ param WebElement corresponding to nsIAccessible object
2015-08-05 21:40:18 +03:00
* @ param Boolean selected element ' s selected state
* /
2015-09-10 18:45:33 +03:00
function checkSelectedAccessibility ( accessible , element , selected ) {
2015-08-05 21:40:18 +03:00
if ( ! accessible ) {
return ;
}
if ( ! accessibility . matchState ( accessible , 'STATE_SELECTABLE' ) ) {
// Element is not selectable via the accessibility API
return ;
}
let selectedAccessibility = accessibility . matchState (
accessible , 'STATE_SELECTED' ) ;
let message ;
if ( selected && ! selectedAccessibility ) {
message = 'Element is selected but not selected via the accessibility API' ;
} else if ( ! selected && selectedAccessibility ) {
message = 'Element is not selected but selected via the accessibility API' ;
2014-12-23 00:15:19 +03:00
}
2015-09-10 18:45:33 +03:00
accessibility . handleErrorMessage ( message , element ) ;
2014-12-23 00:15:19 +03:00
}
2014-09-22 19:41:51 +04:00
2013-02-06 23:58:14 +04:00
/ * *
* Function to create a touch based on the element
2013-05-27 21:12:13 +04:00
* corx and cory are relative to the viewport , id is the touchId
2013-02-06 23:58:14 +04:00
* /
2013-05-27 21:12:13 +04:00
function createATouch ( el , corx , cory , touchId ) {
2013-03-05 05:09:58 +04:00
let doc = el . ownerDocument ;
let win = doc . defaultView ;
2014-09-22 19:41:51 +04:00
let [ clientX , clientY , pageX , pageY , screenX , screenY ] =
2015-03-20 04:41:19 +03:00
actions . getCoordinateInfo ( el , corx , cory ) ;
2013-05-27 21:12:13 +04:00
let atouch = doc . createTouch ( win , el , touchId , pageX , pageY , screenX , screenY , clientX , clientY ) ;
2013-02-06 23:58:14 +04:00
return atouch ;
}
2013-03-05 05:09:58 +04:00
/ * *
2014-01-21 20:40:20 +04:00
* Function to start action chain on one finger
2013-03-05 05:09:58 +04:00
* /
function actionChain ( msg ) {
let command _id = msg . json . command _id ;
2013-03-22 22:29:02 +04:00
let args = msg . json . chain ;
let touchId = msg . json . nextId ;
2015-03-20 04:41:19 +03:00
let callbacks = { } ;
2015-04-15 14:18:00 +03:00
callbacks . onSuccess = value => sendResponse ( value , command _id ) ;
callbacks . onError = err => sendError ( err , command _id ) ;
2015-03-20 04:41:19 +03:00
let touchProvider = { } ;
touchProvider . createATouch = createATouch ;
touchProvider . emitTouchEvent = emitTouchEvent ;
2015-03-24 18:35:58 +03:00
try {
actions . dispatchActions (
args ,
touchId ,
2015-08-28 23:43:54 +03:00
curContainer ,
2015-03-24 18:35:58 +03:00
elementManager ,
callbacks ,
touchProvider ) ;
} catch ( e ) {
2015-04-15 14:18:00 +03:00
sendError ( e , command _id ) ;
2015-03-24 18:35:58 +03:00
}
2013-03-05 05:09:58 +04:00
}
2013-03-19 00:42:46 +04:00
/ * *
* Function to emit touch events which allow multi touch on the screen
* @ param type represents the type of event , touch represents the current touch , touches are all pending touches
* /
function emitMultiEvents ( type , touch , touches ) {
let target = touch . target ;
let doc = target . ownerDocument ;
let win = doc . defaultView ;
// touches that are in the same document
let documentTouches = doc . createTouchList ( touches . filter ( function ( t ) {
2013-06-04 21:47:53 +04:00
return ( ( t . target . ownerDocument === doc ) && ( type != 'touchcancel' ) ) ;
2013-03-19 00:42:46 +04:00
} ) ) ;
// touches on the same target
let targetTouches = doc . createTouchList ( touches . filter ( function ( t ) {
2013-06-04 21:47:53 +04:00
return ( ( t . target === target ) && ( ( type != 'touchcancel' ) || ( type != 'touchend' ) ) ) ;
2013-03-19 00:42:46 +04:00
} ) ) ;
// Create changed touches
let changedTouches = doc . createTouchList ( touch ) ;
// Create the event object
2014-01-15 18:28:04 +04:00
let event = doc . createEvent ( 'TouchEvent' ) ;
2013-03-19 00:42:46 +04:00
event . initTouchEvent ( type ,
true ,
true ,
win ,
0 ,
false , false , false , false ,
documentTouches ,
targetTouches ,
changedTouches ) ;
target . dispatchEvent ( event ) ;
}
/ * *
* Function to dispatch one set of actions
* @ param touches represents all pending touches , batchIndex represents the batch we are dispatching right now
* /
function setDispatch ( batches , touches , command _id , batchIndex ) {
if ( typeof batchIndex === "undefined" ) {
batchIndex = 0 ;
}
// check if all the sets have been fired
if ( batchIndex >= batches . length ) {
multiLast = { } ;
sendOk ( command _id ) ;
return ;
}
// a set of actions need to be done
let batch = batches [ batchIndex ] ;
// each action for some finger
let pack ;
// the touch id for the finger (pack)
let touchId ;
// command for the finger
let command ;
// touch that will be created for the finger
let el ;
let corx ;
let cory ;
let touch ;
let lastTouch ;
let touchIndex ;
let waitTime = 0 ;
let maxTime = 0 ;
2013-05-27 21:12:13 +04:00
let c ;
2013-03-19 00:42:46 +04:00
batchIndex ++ ;
// loop through the batch
for ( let i = 0 ; i < batch . length ; i ++ ) {
pack = batch [ i ] ;
touchId = pack [ 0 ] ;
command = pack [ 1 ] ;
switch ( command ) {
case 'press' :
2015-08-28 23:43:54 +03:00
el = elementManager . getKnownElement ( pack [ 2 ] , curContainer ) ;
2013-05-27 21:12:13 +04:00
c = coordinates ( el , pack [ 3 ] , pack [ 4 ] ) ;
touch = createATouch ( el , c . x , c . y , touchId ) ;
2013-03-19 00:42:46 +04:00
multiLast [ touchId ] = touch ;
touches . push ( touch ) ;
emitMultiEvents ( 'touchstart' , touch , touches ) ;
break ;
case 'release' :
touch = multiLast [ touchId ] ;
// the index of the previous touch for the finger may change in the touches array
touchIndex = touches . indexOf ( touch ) ;
touches . splice ( touchIndex , 1 ) ;
emitMultiEvents ( 'touchend' , touch , touches ) ;
break ;
case 'move' :
2015-08-28 23:43:54 +03:00
el = elementManager . getKnownElement ( pack [ 2 ] , curContainer ) ;
2013-05-27 21:12:13 +04:00
c = coordinates ( el ) ;
touch = createATouch ( multiLast [ touchId ] . target , c . x , c . y , touchId ) ;
2013-03-19 00:42:46 +04:00
touchIndex = touches . indexOf ( lastTouch ) ;
touches [ touchIndex ] = touch ;
multiLast [ touchId ] = touch ;
emitMultiEvents ( 'touchmove' , touch , touches ) ;
break ;
case 'moveByOffset' :
el = multiLast [ touchId ] . target ;
lastTouch = multiLast [ touchId ] ;
touchIndex = touches . indexOf ( lastTouch ) ;
let doc = el . ownerDocument ;
let win = doc . defaultView ;
// since x and y are relative to the last touch, therefore, it's relative to the position of the last touch
let clientX = lastTouch . clientX + pack [ 2 ] ,
clientY = lastTouch . clientY + pack [ 3 ] ;
let pageX = clientX + win . pageXOffset ,
pageY = clientY + win . pageYOffset ;
let screenX = clientX + win . mozInnerScreenX ,
screenY = clientY + win . mozInnerScreenY ;
touch = doc . createTouch ( win , el , touchId , pageX , pageY , screenX , screenY , clientX , clientY ) ;
touches [ touchIndex ] = touch ;
multiLast [ touchId ] = touch ;
emitMultiEvents ( 'touchmove' , touch , touches ) ;
break ;
case 'wait' :
if ( pack [ 2 ] != undefined ) {
waitTime = pack [ 2 ] * 1000 ;
if ( waitTime > maxTime ) {
maxTime = waitTime ;
}
}
break ;
} //end of switch block
} //end of for loop
if ( maxTime != 0 ) {
checkTimer . initWithCallback ( function ( ) { setDispatch ( batches , touches , command _id , batchIndex ) ; } , maxTime , Ci . nsITimer . TYPE _ONE _SHOT ) ;
}
else {
setDispatch ( batches , touches , command _id , batchIndex ) ;
}
}
/ * *
* Function to start multi - action
* /
function multiAction ( msg ) {
let command _id = msg . json . command _id ;
let args = msg . json . value ;
// maxlen is the longest action chain for one finger
let maxlen = msg . json . maxlen ;
try {
// unwrap the original nested array
2015-08-28 23:43:54 +03:00
let commandArray = elementManager . convertWrappedArguments ( args , curContainer ) ;
2013-03-19 00:42:46 +04:00
let concurrentEvent = [ ] ;
let temp ;
for ( let i = 0 ; i < maxlen ; i ++ ) {
let row = [ ] ;
for ( let j = 0 ; j < commandArray . length ; j ++ ) {
if ( commandArray [ j ] [ i ] != undefined ) {
// add finger id to the front of each action, i.e. [finger_id, action, element]
temp = commandArray [ j ] [ i ] ;
temp . unshift ( j ) ;
row . push ( temp ) ;
}
}
concurrentEvent . push ( row ) ;
}
// now concurrent event is made of sets where each set contain a list of actions that need to be fired.
// note: each action belongs to a different finger
// pendingTouches keeps track of current touches that's on the screen
let pendingTouches = [ ] ;
setDispatch ( concurrentEvent , pendingTouches , command _id ) ;
2015-04-15 14:18:00 +03:00
} catch ( e ) {
sendError ( e , command _id ) ;
2013-03-19 00:42:46 +04:00
}
}
2015-03-11 02:19:40 +03:00
/ *
* This implements the latter part of a get request ( for the case we need to resume one
* when a remoteness update happens in the middle of a navigate request ) . This is most of
* of the work of a navigate request , but doesn ' t assume DOMContentLoaded is yet to fire .
2012-03-22 19:19:57 +04:00
* /
2015-03-11 02:19:40 +03:00
function pollForReadyState ( msg , start , callback ) {
let { pageTimeout , url , command _id } = msg . json ;
start = start ? start : new Date ( ) . getTime ( ) ;
if ( ! callback ) {
callback = ( ) => { } ;
}
2013-06-05 10:59:05 +04:00
let end = null ;
2014-01-24 17:39:23 +04:00
function checkLoad ( ) {
2015-03-11 02:19:40 +03:00
navTimer . cancel ( ) ;
2013-06-05 10:59:05 +04:00
end = new Date ( ) . getTime ( ) ;
2014-12-12 20:08:32 +03:00
let aboutErrorRegex = /about:.+(error)\?/ ;
2013-06-05 10:59:05 +04:00
let elapse = end - start ;
2015-08-28 23:43:54 +03:00
let doc = curContainer . frame . document ;
2015-03-11 02:19:40 +03:00
if ( pageTimeout == null || elapse <= pageTimeout ) {
2015-08-28 23:43:54 +03:00
if ( doc . readyState == "complete" ) {
2015-03-11 02:19:40 +03:00
callback ( ) ;
2013-06-05 10:59:05 +04:00
sendOk ( command _id ) ;
2015-08-28 23:43:54 +03:00
} else if ( doc . readyState == "interactive" &&
aboutErrorRegex . exec ( doc . baseURI ) &&
! doc . baseURI . startsWith ( url ) ) {
2014-12-12 20:08:32 +03:00
// We have reached an error url without requesting it.
2015-03-11 02:19:40 +03:00
callback ( ) ;
2015-04-15 14:18:00 +03:00
sendError ( new UnknownError ( "Error loading page" ) , command _id ) ;
2015-08-28 23:43:54 +03:00
} else if ( doc . readyState == "interactive" &&
doc . baseURI . startsWith ( "about:" ) ) {
2015-03-11 02:19:40 +03:00
callback ( ) ;
2014-12-12 20:08:32 +03:00
sendOk ( command _id ) ;
} else {
2015-03-11 02:19:40 +03:00
navTimer . initWithCallback ( checkLoad , 100 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
2013-06-05 10:59:05 +04:00
}
2015-04-15 14:18:00 +03:00
} else {
2015-03-11 02:19:40 +03:00
callback ( ) ;
2015-04-15 14:18:00 +03:00
sendError ( new TimeoutError ( "Error loading page, timed out (checkLoad)" ) , command _id ) ;
2013-06-05 10:59:05 +04:00
}
}
2015-03-11 02:19:40 +03:00
checkLoad ( ) ;
}
/ * *
* Navigate to the given URL . The operation will be performed on the
2015-05-04 13:25:03 +03:00
* current browsing context , which means it handles the case where we
* navigate within an iframe . All other navigation is handled by the
* driver ( in chrome space ) .
2015-03-11 02:19:40 +03:00
* /
function get ( msg ) {
let start = new Date ( ) . getTime ( ) ;
2014-01-24 17:39:23 +04:00
// Prevent DOMContentLoaded events from frames from invoking this
// code, unless the event is coming from the frame associated with
// the current window (i.e. someone has used switch_to_frame).
2015-03-11 02:19:40 +03:00
onDOMContentLoaded = function onDOMContentLoaded ( event ) {
2013-01-11 23:33:58 +04:00
if ( ! event . originalTarget . defaultView . frameElement ||
2015-08-28 23:43:54 +03:00
event . originalTarget . defaultView . frameElement == curContainer . frame . frameElement ) {
2015-03-11 02:19:40 +03:00
pollForReadyState ( msg , start , ( ) => {
removeEventListener ( "DOMContentLoaded" , onDOMContentLoaded , false ) ;
onDOMContentLoaded = null ;
} ) ;
2012-08-22 01:00:44 +04:00
}
2013-01-11 23:33:58 +04:00
} ;
2013-06-05 10:59:05 +04:00
2014-01-24 17:39:23 +04:00
function timerFunc ( ) {
2013-01-11 23:33:58 +04:00
removeEventListener ( "DOMContentLoaded" , onDOMContentLoaded , false ) ;
2015-04-15 14:18:00 +03:00
sendError ( new TimeoutError ( "Error loading page, timed out (onDOMContentLoaded)" ) , msg . json . command _id ) ;
2013-01-11 23:33:58 +04:00
}
2014-01-24 17:39:23 +04:00
if ( msg . json . pageTimeout != null ) {
2015-03-11 02:19:40 +03:00
navTimer . initWithCallback ( timerFunc , msg . json . pageTimeout , Ci . nsITimer . TYPE _ONE _SHOT ) ;
2013-01-11 23:33:58 +04:00
}
addEventListener ( "DOMContentLoaded" , onDOMContentLoaded , false ) ;
2015-10-06 18:47:09 +03:00
if ( isB2G ) {
curContainer . frame . location = msg . json . url ;
} else {
// We need to move to the top frame before navigating
sendSyncMessage ( "Marionette:switchedToFrame" , { frameValue : null } ) ;
curContainer . frame = content ;
curContainer . frame . location = msg . json . url ;
}
2012-03-22 19:19:57 +04:00
}
2015-03-11 02:19:40 +03:00
/ * *
* Cancel the polling and remove the event listener associated with a current
* navigation request in case we ' re interupted by an onbeforeunload handler
* and navigation doesn ' t complete .
* /
function cancelRequest ( ) {
navTimer . cancel ( ) ;
if ( onDOMContentLoaded ) {
removeEventListener ( "DOMContentLoaded" , onDOMContentLoaded , false ) ;
}
}
2012-03-22 19:19:57 +04:00
/ * *
2015-08-21 12:57:06 +03:00
* Get URL of the top - level browsing context .
2012-03-22 19:19:57 +04:00
* /
2015-08-21 12:57:06 +03:00
function getCurrentUrl ( isB2G ) {
if ( isB2G ) {
2015-08-28 23:43:54 +03:00
return curContainer . frame . location . href ;
2015-03-11 02:19:40 +03:00
} else {
2015-08-21 12:57:06 +03:00
return content . location . href ;
2015-03-11 02:19:40 +03:00
}
2012-03-22 19:19:57 +04:00
}
2012-07-11 00:30:21 +04:00
/ * *
2015-08-21 13:04:06 +03:00
* Get the title of the current browsing context .
2012-07-11 00:30:21 +04:00
* /
2015-08-21 13:04:06 +03:00
function getTitle ( ) {
2015-08-28 23:43:54 +03:00
return curContainer . frame . top . document . title ;
2012-07-11 00:30:21 +04:00
}
2012-08-09 23:31:12 +04:00
/ * *
2015-08-21 13:11:36 +03:00
* Get source of the current browsing context ' s DOM .
2012-08-09 23:31:12 +04:00
* /
2015-08-21 13:11:36 +03:00
function getPageSource ( ) {
2015-08-28 23:43:54 +03:00
let XMLSerializer = curContainer . frame . XMLSerializer ;
let source = new XMLSerializer ( ) . serializeToString ( curContainer . frame . document ) ;
2015-08-21 13:11:36 +03:00
return source ;
2012-08-09 23:31:12 +04:00
}
2012-03-22 19:19:57 +04:00
/ * *
2015-08-21 13:19:53 +03:00
* Cause the browser to traverse one step backward in the joint history
* of the current top - level browsing context .
2012-03-22 19:19:57 +04:00
* /
2015-08-21 13:19:53 +03:00
function goBack ( ) {
2015-08-28 23:43:54 +03:00
curContainer . frame . history . back ( ) ;
2012-03-22 19:19:57 +04:00
}
/ * *
2014-01-21 20:40:20 +04:00
* Go forward in history
2012-03-22 19:19:57 +04:00
* /
function goForward ( msg ) {
2015-08-28 23:43:54 +03:00
curContainer . frame . history . forward ( ) ;
2012-11-27 06:19:04 +04:00
sendOk ( msg . json . command _id ) ;
2012-03-22 19:19:57 +04:00
}
/ * *
* Refresh the page
* /
function refresh ( msg ) {
2012-11-27 06:19:04 +04:00
let command _id = msg . json . command _id ;
2015-08-28 23:43:54 +03:00
curContainer . frame . location . reload ( true ) ;
2012-11-27 06:19:04 +04:00
let listen = function ( ) {
removeEventListener ( "DOMContentLoaded" , arguments . callee , false ) ;
sendOk ( command _id ) ;
} ;
2012-03-22 19:19:57 +04:00
addEventListener ( "DOMContentLoaded" , listen , false ) ;
}
/ * *
2015-08-21 17:05:13 +03:00
* Find an element in the current browsing context ' s document using the
* given search strategy .
2012-03-22 19:19:57 +04:00
* /
2015-08-21 17:05:13 +03:00
function findElementContent ( opts ) {
return new Promise ( ( resolve , reject ) => {
elementManager . find (
2015-08-28 23:43:54 +03:00
curContainer ,
2015-08-21 17:05:13 +03:00
opts ,
opts . searchTimeout ,
false /* all */ ,
resolve ,
reject ) ;
} ) ;
2012-03-22 19:19:57 +04:00
}
/ * *
2015-08-21 17:03:03 +03:00
* Find elements in the current browsing context ' s document using the
* given search strategy .
2012-03-22 19:19:57 +04:00
* /
2015-08-21 17:03:03 +03:00
function findElementsContent ( opts ) {
return new Promise ( ( resolve , reject ) => {
elementManager . find (
2015-08-28 23:43:54 +03:00
curContainer ,
2015-08-21 17:03:03 +03:00
opts ,
opts . searchTimeout ,
true /* all */ ,
resolve ,
reject ) ;
} ) ;
2012-03-22 19:19:57 +04:00
}
2013-01-14 18:57:54 +04:00
/ * *
2015-04-15 14:18:00 +03:00
* Find and return the active element on the page .
*
* @ return { WebElement }
* Reference to web element .
2013-01-14 18:57:54 +04:00
* /
2015-04-15 14:18:00 +03:00
function getActiveElement ( ) {
2015-08-28 23:43:54 +03:00
let el = curContainer . frame . document . activeElement ;
2015-04-15 14:18:00 +03:00
return elementManager . addToKnownElements ( el ) ;
2013-01-14 18:57:54 +04:00
}
2012-03-22 19:19:57 +04:00
/ * *
2015-04-15 14:18:00 +03:00
* Send click event to element .
*
* @ param { WebElement } id
* Reference to the web element to click .
* /
function clickElement ( id ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-04-15 14:18:00 +03:00
let visible = checkVisible ( el ) ;
if ( ! visible ) {
throw new ElementNotVisibleError ( "Element is not visible" ) ;
}
2015-09-10 18:45:33 +03:00
let acc = accessibility . getAccessibleObject ( el , true ) ;
checkVisibleAccessibility ( acc , el , visible ) ;
2015-04-15 14:18:00 +03:00
if ( utils . isElementEnabled ( el ) ) {
2015-08-05 21:40:18 +03:00
checkEnabledAccessibility ( acc , el , true ) ;
2015-09-10 18:45:33 +03:00
checkActionableAccessibility ( acc , el ) ;
2015-04-15 14:18:00 +03:00
utils . synthesizeMouseAtCenter ( el , { } , el . ownerDocument . defaultView ) ;
} else {
throw new InvalidElementStateError ( "Element is not Enabled" ) ;
2012-04-11 04:28:08 +04:00
}
}
/ * *
2015-04-15 14:18:00 +03:00
* Get a given attribute of an element .
*
* @ param { WebElement } id
* Reference to the web element to get the attribute of .
* @ param { string } name
* Name of the attribute .
*
* @ return { string }
* The value of the attribute .
2012-04-11 04:28:08 +04:00
* /
2015-04-15 14:18:00 +03:00
function getElementAttribute ( id , name ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-04-15 14:18:00 +03:00
return utils . getElementAttribute ( el , name ) ;
2012-04-11 04:28:08 +04:00
}
/ * *
* Get the text of this element . This includes text from child elements .
2015-04-15 14:18:00 +03:00
*
* @ param { WebElement } id
* Reference to web element .
*
* @ return { string }
* Text of element .
2012-04-11 04:28:08 +04:00
* /
2015-04-15 14:18:00 +03:00
function getElementText ( id ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-04-15 14:18:00 +03:00
return utils . getElementText ( el ) ;
2012-04-11 04:28:08 +04:00
}
2012-08-09 00:21:50 +04:00
/ * *
* Get the tag name of an element .
2015-04-15 14:18:00 +03:00
*
* @ param { WebElement } id
* Reference to web element .
*
* @ return { string }
* Tag name of element .
2012-08-09 00:21:50 +04:00
* /
2015-04-15 14:18:00 +03:00
function getElementTagName ( id ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-04-15 14:18:00 +03:00
return el . tagName . toLowerCase ( ) ;
2012-08-09 00:21:50 +04:00
}
2012-04-11 04:28:08 +04:00
/ * *
2015-08-21 17:49:47 +03:00
* Determine the element displayedness of the given web element .
*
* Also performs additional accessibility checks if enabled by session
* capability .
2012-04-11 04:28:08 +04:00
* /
2015-08-21 17:49:47 +03:00
function isElementDisplayed ( id ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-08-21 17:49:47 +03:00
let displayed = utils . isElementDisplayed ( el ) ;
2015-09-10 18:45:33 +03:00
checkVisibleAccessibility (
accessibility . getAccessibleObject ( el ) , el , displayed ) ;
2015-08-21 17:49:47 +03:00
return displayed ;
2012-04-11 04:28:08 +04:00
}
2013-05-08 01:38:21 +04:00
/ * *
2015-08-21 18:10:02 +03:00
* Retrieves the computed value of the given CSS property of the given
* web element .
2013-05-08 01:38:21 +04:00
*
2015-08-21 18:10:02 +03:00
* @ param { String } id
* Web element reference .
* @ param { String } prop
* The CSS property to get .
*
* @ return { String }
* Effective value of the requested CSS property .
2013-05-08 01:38:21 +04:00
* /
2015-08-21 18:10:02 +03:00
function getElementValueOfCssProperty ( id , prop ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
let st = curContainer . frame . document . defaultView . getComputedStyle ( el , null ) ;
2015-08-21 18:10:02 +03:00
return st . getPropertyValue ( prop ) ;
2013-05-08 01:38:21 +04:00
}
2012-10-20 00:59:15 +04:00
/ * *
2015-09-08 19:22:06 +03:00
* Get the position and dimensions of the element .
2015-04-15 14:18:00 +03:00
*
* @ param { WebElement } id
* Reference to web element .
*
* @ return { Object . < string , number > }
* The x , y , width , and height properties of the element .
* /
function getElementRect ( id ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-04-15 14:18:00 +03:00
let clientRect = el . getBoundingClientRect ( ) ;
return {
2015-08-28 23:43:54 +03:00
x : clientRect . x + curContainer . frame . pageXOffset ,
y : clientRect . y + curContainer . frame . pageYOffset ,
2015-04-15 14:18:00 +03:00
width : clientRect . width ,
height : clientRect . height
} ;
2014-07-16 23:58:37 +04:00
}
2012-04-11 04:28:08 +04:00
/ * *
2015-04-15 14:18:00 +03:00
* Check if element is enabled .
*
* @ param { WebElement } id
* Reference to web element .
*
* @ return { boolean }
* True if enabled , false otherwise .
2012-04-11 04:28:08 +04:00
* /
2015-04-15 14:18:00 +03:00
function isElementEnabled ( id ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-04-15 14:18:00 +03:00
let enabled = utils . isElementEnabled ( el ) ;
2015-08-05 21:40:18 +03:00
checkEnabledAccessibility (
accessibility . getAccessibleObject ( el ) , el , enabled ) ;
2015-04-15 14:18:00 +03:00
return enabled ;
2012-04-11 04:28:08 +04:00
}
/ * *
2015-08-21 17:09:37 +03:00
* Determines if the referenced element is selected or not .
*
* This operation only makes sense on input elements of the Checkbox -
* and Radio Button states , or option elements .
2012-04-11 04:28:08 +04:00
* /
2015-08-21 17:09:37 +03:00
function isElementSelected ( id ) {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-08-05 21:40:18 +03:00
let selected = utils . isElementSelected ( el ) ;
2015-09-10 18:45:33 +03:00
checkSelectedAccessibility (
accessibility . getAccessibleObject ( el ) , el , selected ) ;
2015-08-21 17:09:37 +03:00
return selected ;
2012-04-11 04:28:08 +04:00
}
/ * *
* Send keys to element
* /
function sendKeysToElement ( msg ) {
2012-11-27 06:19:04 +04:00
let command _id = msg . json . command _id ;
2015-04-02 17:16:00 +03:00
let val = msg . json . value ;
2013-12-16 20:40:51 +04:00
2015-08-05 21:40:18 +03:00
try {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( msg . json . id , curContainer ) ;
2015-08-05 21:40:18 +03:00
// Element should be actionable from the accessibility standpoint to be able
// to send keys to it.
2015-09-10 18:45:33 +03:00
checkActionableAccessibility (
accessibility . getAccessibleObject ( el , true ) , el ) ;
2015-08-05 21:40:18 +03:00
if ( el . type == "file" ) {
let p = val . join ( "" ) ;
fileInputElement = el ;
// In e10s, we can only construct File objects in the parent process,
// so pass the filename to driver.js, which in turn passes them back
// to this frame script in receiveFiles.
sendSyncMessage ( "Marionette:getFiles" ,
{ value : p , command _id : command _id } ) ;
} else {
2015-08-28 23:43:54 +03:00
utils . sendKeysToElement ( curContainer . frame , el , val , sendOk , sendError , command _id ) ;
2015-08-05 21:40:18 +03:00
}
} catch ( e ) {
sendError ( e , command _id ) ;
2015-04-02 17:16:00 +03:00
}
2012-04-11 04:28:08 +04:00
}
/ * *
2015-08-21 17:37:04 +03:00
* Clear the text of an element .
2012-04-11 04:28:08 +04:00
* /
2015-08-21 17:37:04 +03:00
function clearElement ( id ) {
2012-04-11 04:28:08 +04:00
try {
2015-08-28 23:43:54 +03:00
let el = elementManager . getKnownElement ( id , curContainer ) ;
2015-04-02 17:16:00 +03:00
if ( el . type == "file" ) {
el . value = null ;
} else {
utils . clearElement ( el ) ;
}
} catch ( e ) {
2015-04-08 21:02:34 +03:00
// Bug 964738: Newer atoms contain status codes which makes wrapping
// this in an error prototype that has a status property unnecessary
if ( e . name == "InvalidElementStateError" ) {
2015-08-21 17:37:04 +03:00
throw new InvalidElementStateError ( e . message ) ;
} else {
throw e ;
2015-04-08 21:02:34 +03:00
}
2012-03-22 19:19:57 +04:00
}
}
2015-08-28 23:43:54 +03:00
/ * *
* Switch the current context to the specified host ' s Shadow DOM .
* @ param { WebElement } id
* Reference to web element .
* /
function switchToShadowRoot ( id ) {
if ( ! id ) {
// If no host element is passed, attempt to find a parent shadow root or, if
// none found, unset the current shadow root
if ( curContainer . shadowRoot ) {
let parent = curContainer . shadowRoot . host ;
while ( parent && ! ( parent instanceof curContainer . frame . ShadowRoot ) ) {
parent = parent . parentNode ;
}
curContainer . shadowRoot = parent ;
}
return ;
}
let foundShadowRoot ;
let hostEl = elementManager . getKnownElement ( id , curContainer ) ;
foundShadowRoot = hostEl . shadowRoot ;
if ( ! foundShadowRoot ) {
throw new NoSuchElementError ( 'Unable to locate shadow root: ' + id ) ;
}
curContainer . shadowRoot = foundShadowRoot ;
}
2012-03-22 19:19:57 +04:00
/ * *
* Switch to frame given either the server - assigned element id ,
* its index in window . frames , or the iframe ' s name or id .
* /
function switchToFrame ( msg ) {
2012-11-27 06:19:04 +04:00
let command _id = msg . json . command _id ;
2013-09-19 21:35:19 +04:00
function checkLoad ( ) {
2012-11-14 22:35:44 +04:00
let errorRegex = /about:.+(error)|(blocked)\?/ ;
2015-08-28 23:43:54 +03:00
if ( curContainer . frame . document . readyState == "complete" ) {
2012-11-27 06:19:04 +04:00
sendOk ( command _id ) ;
2012-11-14 22:35:44 +04:00
return ;
2015-08-28 23:43:54 +03:00
} else if ( curContainer . frame . document . readyState == "interactive" &&
errorRegex . exec ( curContainer . frame . document . baseURI ) ) {
2015-04-15 14:18:00 +03:00
sendError ( new UnknownError ( "Error loading page" ) , command _id ) ;
2012-11-14 22:35:44 +04:00
return ;
}
checkTimer . initWithCallback ( checkLoad , 100 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
}
2012-03-22 19:19:57 +04:00
let foundFrame = null ;
2014-01-19 00:08:36 +04:00
let frames = [ ] ;
let parWindow = null ;
2015-08-28 23:43:54 +03:00
// Check of the curContainer.frame reference is dead
2013-01-16 19:00:00 +04:00
try {
2015-08-28 23:43:54 +03:00
frames = curContainer . frame . frames ;
2013-01-16 19:00:00 +04:00
//Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one.
//parWindow will refer to the iframe above the nested OOP frame.
2015-08-28 23:43:54 +03:00
parWindow = curContainer . frame . QueryInterface ( Ci . nsIInterfaceRequestor )
2013-01-16 19:00:00 +04:00
. getInterface ( Ci . nsIDOMWindowUtils ) . outerWindowID ;
} catch ( e ) {
// We probably have a dead compartment so accessing it is going to make Firefox
2014-01-21 20:40:20 +04:00
// very upset. Let's now try redirect everything to the top frame even if the
2013-01-16 19:00:00 +04:00
// user has given us a frame since search doesnt look up.
2013-10-01 19:13:04 +04:00
msg . json . id = null ;
2013-01-16 19:00:00 +04:00
msg . json . element = null ;
}
2014-01-19 00:08:36 +04:00
if ( ( msg . json . id === null || msg . json . id === undefined ) && ( msg . json . element == null ) ) {
2013-07-29 21:46:01 +04:00
// returning to root frame
sendSyncMessage ( "Marionette:switchedToFrame" , { frameValue : null } ) ;
2015-08-28 23:43:54 +03:00
curContainer . frame = content ;
2012-12-17 13:45:28 +04:00
if ( msg . json . focus == true ) {
2015-08-28 23:43:54 +03:00
curContainer . frame . focus ( ) ;
2012-12-17 13:45:28 +04:00
}
2015-04-23 23:39:38 +03:00
2012-11-14 22:35:44 +04:00
checkTimer . initWithCallback ( checkLoad , 100 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
2012-03-22 19:19:57 +04:00
return ;
}
if ( msg . json . element != undefined ) {
if ( elementManager . seenItems [ msg . json . element ] != undefined ) {
2013-02-04 23:40:51 +04:00
let wantedFrame ;
try {
2015-08-28 23:43:54 +03:00
wantedFrame = elementManager . getKnownElement ( msg . json . element , curContainer ) ; //Frame Element
2015-04-15 14:18:00 +03:00
} catch ( e ) {
sendError ( e , command _id ) ;
2013-02-04 23:40:51 +04:00
}
2014-01-19 00:08:36 +04:00
if ( frames . length > 0 ) {
2014-04-25 17:51:24 +04:00
for ( let i = 0 ; i < frames . length ; i ++ ) {
2014-01-19 00:08:36 +04:00
// use XPCNativeWrapper to compare elements; see bug 834266
if ( XPCNativeWrapper ( frames [ i ] . frameElement ) == XPCNativeWrapper ( wantedFrame ) ) {
2015-08-28 23:43:54 +03:00
curContainer . frame = frames [ i ] . frameElement ;
2014-04-25 17:51:24 +04:00
foundFrame = i ;
}
}
2014-01-19 00:08:36 +04:00
}
2014-05-09 18:01:34 +04:00
if ( foundFrame === null ) {
2014-01-19 00:08:36 +04:00
// Either the frame has been removed or we have a OOP frame
// so lets just get all the iframes and do a quick loop before
// throwing in the towel
2015-08-28 23:43:54 +03:00
let iframes = curContainer . frame . document . getElementsByTagName ( "iframe" ) ;
2014-01-19 00:08:36 +04:00
for ( var i = 0 ; i < iframes . length ; i ++ ) {
if ( XPCNativeWrapper ( iframes [ i ] ) == XPCNativeWrapper ( wantedFrame ) ) {
2015-08-28 23:43:54 +03:00
curContainer . frame = iframes [ i ] ;
2014-01-19 00:08:36 +04:00
foundFrame = i ;
}
2014-04-25 17:51:24 +04:00
}
2014-01-19 00:08:36 +04:00
}
2014-01-19 00:08:36 +04:00
}
}
2014-05-09 18:01:34 +04:00
if ( foundFrame === null ) {
2014-01-19 00:08:36 +04:00
if ( typeof ( msg . json . id ) === 'number' ) {
2014-05-01 01:59:25 +04:00
try {
foundFrame = frames [ msg . json . id ] . frameElement ;
2014-05-09 18:01:34 +04:00
if ( foundFrame !== null ) {
2015-08-28 23:43:54 +03:00
curContainer . frame = foundFrame ;
foundFrame = elementManager . addToKnownElements ( curContainer . frame ) ;
2014-05-09 18:01:34 +04:00
}
else {
// If foundFrame is null at this point then we have the top level browsing
// context so should treat it accordingly.
sendSyncMessage ( "Marionette:switchedToFrame" , { frameValue : null } ) ;
2015-08-28 23:43:54 +03:00
curContainer . frame = content ;
2014-05-09 18:01:34 +04:00
if ( msg . json . focus == true ) {
2015-08-28 23:43:54 +03:00
curContainer . frame . focus ( ) ;
2014-05-09 18:01:34 +04:00
}
2015-04-23 23:39:38 +03:00
2014-05-09 18:01:34 +04:00
checkTimer . initWithCallback ( checkLoad , 100 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
return ;
}
2014-05-01 01:59:25 +04:00
} catch ( e ) {
// Since window.frames does not return OOP frames it will throw
// and we land up here. Let's not give up and check if there are
// iframes and switch to the indexed frame there
2015-08-28 23:43:54 +03:00
let iframes = curContainer . frame . document . getElementsByTagName ( "iframe" ) ;
2014-10-28 12:41:00 +03:00
if ( msg . json . id >= 0 && msg . json . id < iframes . length ) {
2015-08-28 23:43:54 +03:00
curContainer . frame = iframes [ msg . json . id ] ;
2014-10-28 12:41:00 +03:00
foundFrame = msg . json . id ;
}
2014-05-01 01:59:25 +04:00
}
2014-01-19 00:08:36 +04:00
}
}
2015-04-15 14:18:00 +03:00
2014-05-09 18:01:34 +04:00
if ( foundFrame === null ) {
2015-04-15 14:18:00 +03:00
sendError ( new NoSuchFrameError ( "Unable to locate frame: " + ( msg . json . id || msg . json . element ) ) , command _id ) ;
2014-01-19 00:08:36 +04:00
return true ;
2012-03-22 19:19:57 +04:00
}
2012-05-09 23:05:39 +04:00
2013-07-29 21:46:01 +04:00
// send a synchronous message to let the server update the currently active
// frame element (for getActiveFrame)
2015-08-28 23:43:54 +03:00
let frameValue = elementManager . wrapValue ( curContainer . frame . wrappedJSObject ) [ 'ELEMENT' ] ;
2013-07-29 21:46:01 +04:00
sendSyncMessage ( "Marionette:switchedToFrame" , { frameValue : frameValue } ) ;
2015-03-20 00:12:58 +03:00
let rv = null ;
2015-08-28 23:43:54 +03:00
if ( curContainer . frame . contentWindow === null ) {
2015-03-20 00:12:58 +03:00
// The frame we want to switch to is a remote/OOP frame;
// notify our parent to handle the switch
2015-08-28 23:43:54 +03:00
curContainer . frame = content ;
2015-03-20 00:12:58 +03:00
rv = { win : parWindow , frame : foundFrame } ;
} else {
2015-08-28 23:43:54 +03:00
curContainer . frame = curContainer . frame . contentWindow ;
2015-03-20 00:12:58 +03:00
if ( msg . json . focus )
2015-08-28 23:43:54 +03:00
curContainer . frame . focus ( ) ;
2012-11-14 22:35:44 +04:00
checkTimer . initWithCallback ( checkLoad , 100 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
2012-09-29 03:16:22 +04:00
}
2015-03-20 00:12:58 +03:00
sendResponse ( { value : rv } , command _id ) ;
2012-03-22 19:19:57 +04:00
}
2012-11-22 19:53:44 +04:00
/ * *
* Add a cookie to the document
* /
function addCookie ( msg ) {
2014-11-11 21:15:02 +03:00
let cookie = msg . json . cookie ;
2012-11-22 19:53:44 +04:00
if ( ! cookie . expiry ) {
var date = new Date ( ) ;
var thePresent = new Date ( Date . now ( ) ) ;
date . setYear ( thePresent . getFullYear ( ) + 20 ) ;
cookie . expiry = date . getTime ( ) / 1000 ; // Stored in seconds.
}
if ( ! cookie . domain ) {
2015-08-28 23:43:54 +03:00
var location = curContainer . frame . document . location ;
2012-11-22 19:53:44 +04:00
cookie . domain = location . hostname ;
2015-04-15 14:18:00 +03:00
} else {
2015-08-28 23:43:54 +03:00
var currLocation = curContainer . frame . location ;
2012-11-22 19:53:44 +04:00
var currDomain = currLocation . host ;
if ( currDomain . indexOf ( cookie . domain ) == - 1 ) {
2015-04-15 14:18:00 +03:00
sendError ( new InvalidCookieDomainError ( "You may only set cookies for the current domain" ) , msg . json . command _id ) ;
2012-11-22 19:53:44 +04:00
}
}
// The cookie's domain may include a port. Which is bad. Remove it
// We'll catch ip6 addresses by mistake. Since no-one uses those
// this will be okay for now. See Bug 814416
if ( cookie . domain . match ( /:\d+$/ ) ) {
cookie . domain = cookie . domain . replace ( /:\d+$/ , '' ) ;
}
2015-08-28 23:43:54 +03:00
var document = curContainer . frame . document ;
2012-11-22 19:53:44 +04:00
if ( ! document || ! document . contentType . match ( /html/i ) ) {
2015-04-28 20:53:51 +03:00
sendError ( new UnableToSetCookieError ( "You may only set cookies on html documents" ) , msg . json . command _id ) ;
2012-11-22 19:53:44 +04:00
}
2014-11-11 21:15:02 +03:00
let added = sendSyncMessage ( "Marionette:addCookie" , { value : cookie } ) ;
if ( added [ 0 ] !== true ) {
2015-04-20 15:53:51 +03:00
sendError ( new UnableToSetCookieError ( ) , msg . json . command _id ) ;
2014-10-21 19:22:26 +04:00
return ;
}
2012-11-22 19:53:44 +04:00
sendOk ( msg . json . command _id ) ;
}
/ * *
2014-01-21 20:40:20 +04:00
* Get all cookies for the current domain .
2012-11-22 19:53:44 +04:00
* /
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 13:26:58 +03:00
function getCookies ( ) {
let rv = [ ] ;
let cookies = getVisibleCookies ( curContainer . frame . location ) ;
2014-11-11 21:15:02 +03:00
for ( let cookie of cookies ) {
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 13:26:58 +03:00
let expires = cookie . expires ;
// session cookie, don't return an expiry
if ( expires == 0 ) {
2012-11-22 19:53:44 +04:00
expires = null ;
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 13:26:58 +03:00
// date before epoch time, cap to epoch
} else if ( expires == 1 ) {
2012-11-22 19:53:44 +04:00
expires = 0 ;
}
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 13:26:58 +03:00
rv . push ( {
2012-11-22 19:53:44 +04:00
'name' : cookie . name ,
'value' : cookie . value ,
'path' : cookie . path ,
'domain' : cookie . host ,
'secure' : cookie . isSecure ,
2015-07-16 01:51:51 +03:00
'httpOnly' : cookie . httpOnly ,
2012-11-22 19:53:44 +04:00
'expiry' : expires
} ) ;
}
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 13:26:58 +03:00
return rv ;
2012-11-22 19:53:44 +04:00
}
/ * *
* Delete a cookie by name
* /
function deleteCookie ( msg ) {
2014-11-11 21:15:02 +03:00
let toDelete = msg . json . name ;
2015-08-28 23:43:54 +03:00
let cookies = getVisibleCookies ( curContainer . frame . location ) ;
2014-11-11 21:15:02 +03:00
for ( let cookie of cookies ) {
2012-11-22 19:53:44 +04:00
if ( cookie . name == toDelete ) {
2014-11-11 21:15:02 +03:00
let deleted = sendSyncMessage ( "Marionette:deleteCookie" , { value : cookie } ) ;
if ( deleted [ 0 ] !== true ) {
2015-04-15 14:18:00 +03:00
sendError ( new UnknownError ( "Could not delete cookie: " + msg . json . name ) , msg . json . command _id ) ;
2014-11-11 21:15:02 +03:00
return ;
}
2012-11-22 19:53:44 +04:00
}
}
sendOk ( msg . json . command _id ) ;
}
/ * *
* Delete all the visibile cookies on a page
* /
function deleteAllCookies ( msg ) {
2015-08-28 23:43:54 +03:00
let cookies = getVisibleCookies ( curContainer . frame . location ) ;
2014-11-11 21:15:02 +03:00
for ( let cookie of cookies ) {
let deleted = sendSyncMessage ( "Marionette:deleteCookie" , { value : cookie } ) ;
if ( ! deleted [ 0 ] ) {
2015-04-15 14:18:00 +03:00
sendError ( new UnknownError ( "Could not delete cookie: " + JSON . stringify ( cookie ) ) , msg . json . command _id ) ;
2014-11-11 21:15:02 +03:00
return ;
}
2012-11-22 19:53:44 +04:00
}
sendOk ( msg . json . command _id ) ;
}
/ * *
* Get all the visible cookies from a location
* /
function getVisibleCookies ( location ) {
2014-11-11 21:15:02 +03:00
let currentPath = location . pathname || '/' ;
let result = sendSyncMessage ( "Marionette:getVisibleCookies" ,
{ value : [ currentPath , location . hostname ] } ) ;
return result [ 0 ] ;
2012-11-22 19:53:44 +04:00
}
2012-03-22 19:19:57 +04:00
2012-11-27 06:19:04 +04:00
function getAppCacheStatus ( msg ) {
2015-08-28 23:43:54 +03:00
sendResponse ( { value : curContainer . frame . applicationCache . status } ,
2012-11-27 06:19:04 +04:00
msg . json . command _id ) ;
2014-01-21 20:40:20 +04:00
}
2012-08-24 02:07:16 +04:00
2012-05-24 04:23:36 +04:00
// emulator callbacks
2015-09-15 21:19:45 +03:00
var _emu _cb _id = 0 ;
var _emu _cbs = { } ;
2012-05-24 04:23:36 +04:00
2012-05-19 00:30:43 +04:00
function runEmulatorCmd ( cmd , callback ) {
2012-05-25 21:37:21 +04:00
if ( callback ) {
_emu _cbs [ _emu _cb _id ] = callback ;
2012-05-19 00:30:43 +04:00
}
sendAsyncMessage ( "Marionette:runEmulatorCmd" , { emulator _cmd : cmd , id : _emu _cb _id } ) ;
_emu _cb _id += 1 ;
}
2014-03-20 09:01:25 +04:00
function runEmulatorShell ( args , callback ) {
if ( callback ) {
_emu _cbs [ _emu _cb _id ] = callback ;
}
sendAsyncMessage ( "Marionette:runEmulatorShell" , { emulator _shell : args , id : _emu _cb _id } ) ;
_emu _cb _id += 1 ;
}
2012-05-19 00:30:43 +04:00
function emulatorCmdResult ( msg ) {
let message = msg . json ;
2015-04-23 23:39:38 +03:00
if ( ! sandboxes [ sandboxName ] ) {
2012-05-24 04:23:36 +04:00
return ;
}
2012-05-19 00:30:43 +04:00
let cb = _emu _cbs [ message . id ] ;
delete _emu _cbs [ message . id ] ;
2012-05-25 21:37:21 +04:00
if ( ! cb ) {
return ;
}
2012-05-19 00:30:43 +04:00
try {
cb ( message . result ) ;
2015-04-15 14:18:00 +03:00
} catch ( e ) {
sendError ( e , - 1 ) ;
2012-05-19 00:30:43 +04:00
return ;
}
}
2012-06-04 21:50:06 +04:00
function importScript ( msg ) {
2012-11-27 06:19:04 +04:00
let command _id = msg . json . command _id ;
2012-06-04 21:50:06 +04:00
let file ;
if ( importedScripts . exists ( ) ) {
2012-11-27 06:19:04 +04:00
file = FileUtils . openFileOutputStream ( importedScripts ,
FileUtils . MODE _APPEND | FileUtils . MODE _WRONLY ) ;
2012-06-04 21:50:06 +04:00
}
else {
2012-10-24 19:36:43 +04:00
//Note: The permission bits here don't actually get set (bug 804563)
2012-11-27 06:19:04 +04:00
importedScripts . createUnique ( Components . interfaces . nsIFile . NORMAL _FILE _TYPE ,
parseInt ( "0666" , 8 ) ) ;
file = FileUtils . openFileOutputStream ( importedScripts ,
FileUtils . MODE _WRONLY | FileUtils . MODE _CREATE ) ;
2012-10-24 19:36:43 +04:00
importedScripts . permissions = parseInt ( "0666" , 8 ) ; //actually set permissions
2012-06-04 21:50:06 +04:00
}
file . write ( msg . json . script , msg . json . script . length ) ;
file . close ( ) ;
2012-11-27 06:19:04 +04:00
sendOk ( command _id ) ;
2012-06-04 21:50:06 +04:00
}
2012-12-01 03:25:26 +04:00
/ * *
2014-01-24 17:45:58 +04:00
* Takes a screen capture of the given web element if < code > id < / c o d e >
* property exists in the message ' s JSON object , or if null captures
* the bounding box of the current frame .
*
* If given an array of web element references in
* < code > msg . json . highlights < / c o d e > , a r e d b o x w i l l b e p a i n t e d a r o u n d
* them to highlight their position .
2012-12-01 03:25:26 +04:00
* /
2015-10-05 20:36:54 +03:00
function takeScreenshot ( msg ) {
let node = null ;
if ( msg . json . id ) {
try {
node = elementManager . getKnownElement ( msg . json . id , curContainer )
}
catch ( e ) {
sendResponse ( e . message , e . code , e . stack , msg . json . command _id ) ;
return ;
}
}
else {
2015-08-28 23:43:54 +03:00
node = curContainer . frame ;
2012-12-01 03:25:26 +04:00
}
2015-10-05 20:36:54 +03:00
let highlights = msg . json . highlights ;
2012-12-01 03:25:26 +04:00
2015-10-05 20:36:54 +03:00
var document = curContainer . frame . document ;
var rect , win , width , height , left , top ;
2012-12-01 03:25:26 +04:00
// node can be either a window or an arbitrary DOM node
2015-08-28 23:43:54 +03:00
if ( node == curContainer . frame ) {
2012-12-01 03:25:26 +04:00
// node is a window
win = node ;
2015-10-05 20:36:54 +03:00
if ( msg . json . full ) {
2015-02-02 15:57:00 +03:00
// the full window
width = document . body . scrollWidth ;
height = document . body . scrollHeight ;
top = 0 ;
left = 0 ;
2015-10-05 20:36:54 +03:00
}
else {
2015-02-02 15:57:00 +03:00
// only the viewport
width = document . documentElement . clientWidth ;
height = document . documentElement . clientHeight ;
2015-08-28 23:43:54 +03:00
left = curContainer . frame . pageXOffset ;
top = curContainer . frame . pageYOffset ;
2015-02-02 15:57:00 +03:00
}
2015-10-05 20:36:54 +03:00
}
else {
2012-12-01 03:25:26 +04:00
// node is an arbitrary DOM node
win = node . ownerDocument . defaultView ;
rect = node . getBoundingClientRect ( ) ;
width = rect . width ;
height = rect . height ;
top = rect . top ;
left = rect . left ;
}
2015-10-05 20:36:54 +03:00
var canvas = document . createElementNS ( "http://www.w3.org/1999/xhtml" ,
"canvas" ) ;
2012-12-01 03:25:26 +04:00
canvas . width = width ;
canvas . height = height ;
2015-10-05 20:36:54 +03:00
var ctx = canvas . getContext ( "2d" ) ;
// Draws the DOM contents of the window to the canvas
2014-01-24 17:45:58 +04:00
ctx . drawWindow ( win , left , top , width , height , "rgb(255,255,255)" ) ;
2012-12-01 03:25:26 +04:00
2015-10-05 20:36:54 +03:00
// This section is for drawing a red rectangle around each element
2014-01-24 17:45:58 +04:00
// passed in via the highlights array
2012-12-01 03:25:26 +04:00
if ( highlights ) {
ctx . lineWidth = "2" ;
ctx . strokeStyle = "red" ;
ctx . save ( ) ;
for ( var i = 0 ; i < highlights . length ; ++ i ) {
2015-10-05 20:36:54 +03:00
var elem = elementManager . getKnownElement ( highlights [ i ] , curContainer ) ;
2012-12-01 03:25:26 +04:00
rect = elem . getBoundingClientRect ( ) ;
2015-10-05 20:36:54 +03:00
var offsetY = - top ;
var offsetX = - left ;
2012-12-01 03:25:26 +04:00
2015-10-05 20:36:54 +03:00
// Draw the rectangle
ctx . strokeRect ( rect . left + offsetX ,
rect . top + offsetY ,
rect . width ,
rect . height ) ;
2012-12-01 03:25:26 +04:00
}
}
2015-10-05 20:36:54 +03:00
// Return the Base64 encoded string back to the client so that it
// can save the file to disk if it is required
var dataUrl = canvas . toDataURL ( "image/png" , "" ) ;
var data = dataUrl . substring ( dataUrl . indexOf ( "," ) + 1 ) ;
sendResponse ( { value : data } , msg . json . command _id ) ;
2012-12-01 03:25:26 +04:00
}
2014-01-24 17:45:58 +04:00
// Call register self when we get loaded
2012-03-22 19:19:57 +04:00
registerSelf ( ) ;