Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
8 let uuidGen = Cc["@mozilla.org/uuid-generator;1"]
9 .getService(Ci.nsIUUIDGenerator);
11 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
12 .getService(Ci.mozIJSSubScriptLoader);
14 loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
15 loader.loadSubScript("chrome://marionette/content/marionette-common.js");
16 Cu.import("chrome://marionette/content/marionette-elements.js");
17 Cu.import("resource://gre/modules/FileUtils.jsm");
18 Cu.import("resource://gre/modules/NetUtil.jsm");
19 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
20 let utils = {};
21 utils.window = content;
22 // Load Event/ChromeUtils for use with JS scripts:
23 loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
24 loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
25 loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
26 loader.loadSubScript("chrome://marionette/content/marionette-sendkeys.js", utils);
28 loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js");
29 loader.loadSubScript("chrome://specialpowers/content/specialpowers.js");
31 let marionetteLogObj = new MarionetteLogObj();
33 let isB2G = false;
35 let marionetteTestName;
36 let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
37 .getInterface(Ci.nsIDOMWindowUtils);
38 let listenerId = null; //unique ID of this listener
39 let curFrame = content;
40 let previousFrame = null;
41 let elementManager = new ElementManager([]);
42 let importedScripts = null;
43 let inputSource = null;
45 // The sandbox we execute test scripts in. Gets lazily created in
46 // createExecuteContentSandbox().
47 let sandbox;
49 // the unload handler
50 let onunload;
52 // Flag to indicate whether an async script is currently running or not.
53 let asyncTestRunning = false;
54 let asyncTestCommandId;
55 let asyncTestTimeoutId;
57 let inactivityTimeoutId = null;
58 let heartbeatCallback = function () {}; // Called by the simpletest methods.
60 let originalOnError;
61 //timer for doc changes
62 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
63 //timer for readystate
64 let readyStateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
65 // Send move events about this often
66 let EVENT_INTERVAL = 30; // milliseconds
67 // For assigning unique ids to all touches
68 let nextTouchId = 1000;
69 //Keep track of active Touches
70 let touchIds = {};
71 // last touch for each fingerId
72 let multiLast = {};
73 let lastCoordinates = null;
74 let isTap = false;
75 let scrolling = false;
76 // whether to send mouse event
77 let mouseEventsOnly = false;
79 Cu.import("resource://gre/modules/Log.jsm");
80 let logger = Log.repository.getLogger("Marionette");
81 logger.info("loaded marionette-listener.js");
82 let modalHandler = function() {
83 // This gets called on the system app only since it receives the mozbrowserprompt event
84 sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
85 let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
86 if (isLocal) {
87 previousFrame = curFrame;
88 }
89 curFrame = content;
90 sandbox = null;
91 };
93 /**
94 * Called when listener is first started up.
95 * The listener sends its unique window ID and its current URI to the actor.
96 * If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
97 */
98 function registerSelf() {
99 let msg = {value: winUtil.outerWindowID, href: content.location.href};
100 // register will have the ID and a boolean describing if this is the main process or not
101 let register = sendSyncMessage("Marionette:register", msg);
103 if (register[0]) {
104 listenerId = register[0][0].id;
105 // check if we're the main process
106 if (register[0][1] == true) {
107 addMessageListener("MarionetteMainListener:emitTouchEvent", emitTouchEventForIFrame);
108 }
109 importedScripts = FileUtils.getDir('TmpD', [], false);
110 importedScripts.append('marionetteContentScripts');
111 startListeners();
112 }
113 }
115 function emitTouchEventForIFrame(message) {
116 let message = message.json;
117 let frames = curFrame.document.getElementsByTagName("iframe");
118 let iframe = frames[message.index];
119 let identifier = touchId = nextTouchId++;
120 let tabParent = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.tabParent;
121 tabParent.injectTouchEvent(message.type, [identifier],
122 [message.clientX], [message.clientY],
123 [message.radiusX], [message.radiusY],
124 [message.rotationAngle], [message.force],
125 1, 0);
126 }
128 /**
129 * Add a message listener that's tied to our listenerId.
130 */
131 function addMessageListenerId(messageName, handler) {
132 addMessageListener(messageName + listenerId, handler);
133 }
135 /**
136 * Remove a message listener that's tied to our listenerId.
137 */
138 function removeMessageListenerId(messageName, handler) {
139 removeMessageListener(messageName + listenerId, handler);
140 }
142 /**
143 * Start all message listeners
144 */
145 function startListeners() {
146 addMessageListenerId("Marionette:newSession", newSession);
147 addMessageListenerId("Marionette:executeScript", executeScript);
148 addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
149 addMessageListenerId("Marionette:executeJSScript", executeJSScript);
150 addMessageListenerId("Marionette:singleTap", singleTap);
151 addMessageListenerId("Marionette:actionChain", actionChain);
152 addMessageListenerId("Marionette:multiAction", multiAction);
153 addMessageListenerId("Marionette:get", get);
154 addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
155 addMessageListenerId("Marionette:getTitle", getTitle);
156 addMessageListenerId("Marionette:getPageSource", getPageSource);
157 addMessageListenerId("Marionette:goBack", goBack);
158 addMessageListenerId("Marionette:goForward", goForward);
159 addMessageListenerId("Marionette:refresh", refresh);
160 addMessageListenerId("Marionette:findElementContent", findElementContent);
161 addMessageListenerId("Marionette:findElementsContent", findElementsContent);
162 addMessageListenerId("Marionette:getActiveElement", getActiveElement);
163 addMessageListenerId("Marionette:clickElement", clickElement);
164 addMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
165 addMessageListenerId("Marionette:getElementText", getElementText);
166 addMessageListenerId("Marionette:getElementTagName", getElementTagName);
167 addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
168 addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
169 addMessageListenerId("Marionette:submitElement", submitElement);
170 addMessageListenerId("Marionette:getElementSize", getElementSize);
171 addMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
172 addMessageListenerId("Marionette:isElementSelected", isElementSelected);
173 addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
174 addMessageListenerId("Marionette:getElementLocation", getElementLocation);
175 addMessageListenerId("Marionette:clearElement", clearElement);
176 addMessageListenerId("Marionette:switchToFrame", switchToFrame);
177 addMessageListenerId("Marionette:deleteSession", deleteSession);
178 addMessageListenerId("Marionette:sleepSession", sleepSession);
179 addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
180 addMessageListenerId("Marionette:importScript", importScript);
181 addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
182 addMessageListenerId("Marionette:setTestName", setTestName);
183 addMessageListenerId("Marionette:takeScreenshot", takeScreenshot);
184 addMessageListenerId("Marionette:addCookie", addCookie);
185 addMessageListenerId("Marionette:getCookies", getCookies);
186 addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
187 addMessageListenerId("Marionette:deleteCookie", deleteCookie);
188 }
190 /**
191 * Used during newSession and restart, called to set up the modal dialog listener in b2g
192 */
193 function waitForReady() {
194 if (content.document.readyState == 'complete') {
195 readyStateTimer.cancel();
196 content.addEventListener("mozbrowsershowmodalprompt", modalHandler, false);
197 content.addEventListener("unload", waitForReady, false);
198 }
199 else {
200 readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
201 }
202 }
204 /**
205 * Called when we start a new session. It registers the
206 * current environment, and resets all values
207 */
208 function newSession(msg) {
209 isB2G = msg.json.B2G;
210 resetValues();
211 if (isB2G) {
212 readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
213 // We have to set correct mouse event source to MOZ_SOURCE_TOUCH
214 // to offer a way for event listeners to differentiate
215 // events being the result of a physical mouse action.
216 // This is especially important for the touch event shim,
217 // in order to prevent creating touch event for these fake mouse events.
218 inputSource = Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH;
219 }
220 }
222 /**
223 * Puts the current session to sleep, so all listeners are removed except
224 * for the 'restart' listener. This is used to keep the content listener
225 * alive for reuse in B2G instead of reloading it each time.
226 */
227 function sleepSession(msg) {
228 deleteSession();
229 addMessageListener("Marionette:restart", restart);
230 }
232 /**
233 * Restarts all our listeners after this listener was put to sleep
234 */
235 function restart(msg) {
236 removeMessageListener("Marionette:restart", restart);
237 if (isB2G) {
238 readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
239 }
240 registerSelf();
241 }
243 /**
244 * Removes all listeners
245 */
246 function deleteSession(msg) {
247 removeMessageListenerId("Marionette:newSession", newSession);
248 removeMessageListenerId("Marionette:executeScript", executeScript);
249 removeMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
250 removeMessageListenerId("Marionette:executeJSScript", executeJSScript);
251 removeMessageListenerId("Marionette:singleTap", singleTap);
252 removeMessageListenerId("Marionette:actionChain", actionChain);
253 removeMessageListenerId("Marionette:multiAction", multiAction);
254 removeMessageListenerId("Marionette:get", get);
255 removeMessageListenerId("Marionette:getTitle", getTitle);
256 removeMessageListenerId("Marionette:getPageSource", getPageSource);
257 removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
258 removeMessageListenerId("Marionette:goBack", goBack);
259 removeMessageListenerId("Marionette:goForward", goForward);
260 removeMessageListenerId("Marionette:refresh", refresh);
261 removeMessageListenerId("Marionette:findElementContent", findElementContent);
262 removeMessageListenerId("Marionette:findElementsContent", findElementsContent);
263 removeMessageListenerId("Marionette:getActiveElement", getActiveElement);
264 removeMessageListenerId("Marionette:clickElement", clickElement);
265 removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
266 removeMessageListenerId("Marionette:getElementTagName", getElementTagName);
267 removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
268 removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
269 removeMessageListenerId("Marionette:submitElement", submitElement);
270 removeMessageListenerId("Marionette:getElementSize", getElementSize);
271 removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
272 removeMessageListenerId("Marionette:isElementSelected", isElementSelected);
273 removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
274 removeMessageListenerId("Marionette:getElementLocation", getElementLocation);
275 removeMessageListenerId("Marionette:clearElement", clearElement);
276 removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
277 removeMessageListenerId("Marionette:deleteSession", deleteSession);
278 removeMessageListenerId("Marionette:sleepSession", sleepSession);
279 removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
280 removeMessageListenerId("Marionette:importScript", importScript);
281 removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
282 removeMessageListenerId("Marionette:setTestName", setTestName);
283 removeMessageListenerId("Marionette:takeScreenshot", takeScreenshot);
284 removeMessageListenerId("Marionette:addCookie", addCookie);
285 removeMessageListenerId("Marionette:getCookies", getCookies);
286 removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
287 removeMessageListenerId("Marionette:deleteCookie", deleteCookie);
288 if (isB2G) {
289 content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false);
290 }
291 elementManager.reset();
292 // reset frame to the top-most frame
293 curFrame = content;
294 curFrame.focus();
295 touchIds = {};
296 }
298 /*
299 * Helper methods
300 */
302 /**
303 * Generic method to send a message to the server
304 */
305 function sendToServer(msg, value, command_id) {
306 if (command_id) {
307 value.command_id = command_id;
308 }
309 sendAsyncMessage(msg, value);
310 }
312 /**
313 * Send response back to server
314 */
315 function sendResponse(value, command_id) {
316 sendToServer("Marionette:done", value, command_id);
317 }
319 /**
320 * Send ack back to server
321 */
322 function sendOk(command_id) {
323 sendToServer("Marionette:ok", {}, command_id);
324 }
326 /**
327 * Send log message to server
328 */
329 function sendLog(msg) {
330 sendToServer("Marionette:log", { message: msg });
331 }
333 /**
334 * Send error message to server
335 */
336 function sendError(message, status, trace, command_id) {
337 let error_msg = { message: message, status: status, stacktrace: trace };
338 sendToServer("Marionette:error", error_msg, command_id);
339 }
341 /**
342 * Clear test values after completion of test
343 */
344 function resetValues() {
345 sandbox = null;
346 curFrame = content;
347 mouseEventsOnly = false;
348 }
350 /**
351 * Dump a logline to stdout. Prepends logline with a timestamp.
352 */
353 function dumpLog(logline) {
354 dump(Date.now() + " Marionette: " + logline);
355 }
357 /**
358 * Check if our context was interrupted
359 */
360 function wasInterrupted() {
361 if (previousFrame) {
362 let element = content.document.elementFromPoint((content.innerWidth/2), (content.innerHeight/2));
363 if (element.id.indexOf("modal-dialog") == -1) {
364 return true;
365 }
366 else {
367 return false;
368 }
369 }
370 return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
371 }
373 /*
374 * Marionette Methods
375 */
377 /**
378 * Returns a content sandbox that can be used by the execute_foo functions.
379 */
380 function createExecuteContentSandbox(aWindow, timeout) {
381 let sandbox = new Cu.Sandbox(aWindow, {sandboxPrototype: aWindow});
382 sandbox.global = sandbox;
383 sandbox.window = aWindow;
384 sandbox.document = sandbox.window.document;
385 sandbox.navigator = sandbox.window.navigator;
386 sandbox.testUtils = utils;
387 sandbox.asyncTestCommandId = asyncTestCommandId;
389 let marionette = new Marionette(this, aWindow, "content",
390 marionetteLogObj, timeout,
391 heartbeatCallback,
392 marionetteTestName);
393 sandbox.marionette = marionette;
394 marionette.exports.forEach(function(fn) {
395 try {
396 sandbox[fn] = marionette[fn].bind(marionette);
397 }
398 catch(e) {
399 sandbox[fn] = marionette[fn];
400 }
401 });
403 XPCOMUtils.defineLazyGetter(sandbox, 'SpecialPowers', function() {
404 return new SpecialPowers(aWindow);
405 });
407 sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) {
408 if (commandId == asyncTestCommandId) {
409 curFrame.removeEventListener("unload", onunload, false);
410 curFrame.clearTimeout(asyncTestTimeoutId);
412 if (inactivityTimeoutId != null) {
413 curFrame.clearTimeout(inactivityTimeoutId);
414 }
417 sendSyncMessage("Marionette:shareData",
418 {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
419 marionetteLogObj.clearLogs();
421 if (status == 0){
422 if (Object.keys(_emu_cbs).length) {
423 _emu_cbs = {};
424 sendError("Emulator callback still pending when finish() called",
425 500, null, commandId);
426 }
427 else {
428 sendResponse({value: elementManager.wrapValue(value), status: status},
429 commandId);
430 }
431 }
432 else {
433 sendError(value, status, stack, commandId);
434 }
436 asyncTestRunning = false;
437 asyncTestTimeoutId = undefined;
438 asyncTestCommandId = undefined;
439 inactivityTimeoutId = null;
440 }
441 };
442 sandbox.finish = function sandbox_finish() {
443 if (asyncTestRunning) {
444 sandbox.asyncComplete(marionette.generate_results(), 0, null, sandbox.asyncTestCommandId);
445 } else {
446 return marionette.generate_results();
447 }
448 };
449 sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) {
450 return sandbox.asyncComplete(value, 0, null, sandbox.asyncTestCommandId);
451 };
453 return sandbox;
454 }
456 /**
457 * Execute the given script either as a function body (executeScript)
458 * or directly (for 'mochitest' like JS Marionette tests)
459 */
460 function executeScript(msg, directInject) {
461 // Set up inactivity timeout.
462 if (msg.json.inactivityTimeout) {
463 let setTimer = function() {
464 inactivityTimeoutId = curFrame.setTimeout(function() {
465 sendError('timed out due to inactivity', 28, null, asyncTestCommandId);
466 }, msg.json.inactivityTimeout);
467 };
469 setTimer();
470 heartbeatCallback = function resetInactivityTimeout() {
471 curFrame.clearTimeout(inactivityTimeoutId);
472 setTimer();
473 };
474 }
476 asyncTestCommandId = msg.json.command_id;
477 let script = msg.json.script;
479 if (msg.json.newSandbox || !sandbox) {
480 sandbox = createExecuteContentSandbox(curFrame,
481 msg.json.timeout);
482 if (!sandbox) {
483 sendError("Could not create sandbox!", 500, null, asyncTestCommandId);
484 return;
485 }
486 }
487 else {
488 sandbox.asyncTestCommandId = asyncTestCommandId;
489 }
491 try {
492 if (directInject) {
493 if (importedScripts.exists()) {
494 let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
495 createInstance(Components.interfaces.nsIFileInputStream);
496 stream.init(importedScripts, -1, 0, 0);
497 let data = NetUtil.readInputStreamToString(stream, stream.available());
498 script = data + script;
499 }
500 let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file" ,0);
501 sendSyncMessage("Marionette:shareData",
502 {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
503 marionetteLogObj.clearLogs();
505 if (res == undefined || res.passed == undefined) {
506 sendError("Marionette.finish() not called", 17, null, asyncTestCommandId);
507 }
508 else {
509 sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
510 }
511 }
512 else {
513 try {
514 sandbox.__marionetteParams = elementManager.convertWrappedArguments(
515 msg.json.args, curFrame);
516 }
517 catch(e) {
518 sendError(e.message, e.code, e.stack, asyncTestCommandId);
519 return;
520 }
522 script = "let __marionetteFunc = function(){" + script + "};" +
523 "__marionetteFunc.apply(null, __marionetteParams);";
524 if (importedScripts.exists()) {
525 let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
526 createInstance(Components.interfaces.nsIFileInputStream);
527 stream.init(importedScripts, -1, 0, 0);
528 let data = NetUtil.readInputStreamToString(stream, stream.available());
529 script = data + script;
530 }
531 let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
532 sendSyncMessage("Marionette:shareData",
533 {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
534 marionetteLogObj.clearLogs();
535 sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
536 }
537 }
538 catch (e) {
539 // 17 = JavascriptException
540 let error = createStackMessage(e,
541 "execute_script",
542 msg.json.filename,
543 msg.json.line,
544 script);
545 sendError(error[0], 17, error[1], asyncTestCommandId);
546 }
547 }
549 /**
550 * Sets the test name, used in logging messages.
551 */
552 function setTestName(msg) {
553 marionetteTestName = msg.json.value;
554 sendOk(msg.json.command_id);
555 }
557 /**
558 * Execute async script
559 */
560 function executeAsyncScript(msg) {
561 executeWithCallback(msg);
562 }
564 /**
565 * Execute pure JS test. Handles both async and sync cases.
566 */
567 function executeJSScript(msg) {
568 if (msg.json.async) {
569 executeWithCallback(msg, msg.json.async);
570 }
571 else {
572 executeScript(msg, true);
573 }
574 }
576 /**
577 * This function is used by executeAsync and executeJSScript to execute a script
578 * in a sandbox.
579 *
580 * For executeJSScript, it will return a message only when the finish() method is called.
581 * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
582 * method is called, or if it times out.
583 */
584 function executeWithCallback(msg, useFinish) {
585 // Set up inactivity timeout.
586 if (msg.json.inactivityTimeout) {
587 let setTimer = function() {
588 inactivityTimeoutId = curFrame.setTimeout(function() {
589 sandbox.asyncComplete('timed out due to inactivity', 28, null, asyncTestCommandId);
590 }, msg.json.inactivityTimeout);
591 };
593 setTimer();
594 heartbeatCallback = function resetInactivityTimeout() {
595 curFrame.clearTimeout(inactivityTimeoutId);
596 setTimer();
597 };
598 }
600 let script = msg.json.script;
601 asyncTestCommandId = msg.json.command_id;
603 onunload = function() {
604 sendError("unload was called", 17, null, asyncTestCommandId);
605 };
606 curFrame.addEventListener("unload", onunload, false);
608 if (msg.json.newSandbox || !sandbox) {
609 sandbox = createExecuteContentSandbox(curFrame,
610 msg.json.timeout);
611 if (!sandbox) {
612 sendError("Could not create sandbox!", 17, null, asyncTestCommandId);
613 return;
614 }
615 }
616 else {
617 sandbox.asyncTestCommandId = asyncTestCommandId;
618 }
619 sandbox.tag = script;
621 // Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout),
622 // see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async.
623 // However Selenium code returns 28, see
624 // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
625 // We'll stay compatible with the Selenium code.
626 asyncTestTimeoutId = curFrame.setTimeout(function() {
627 sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId);
628 }, msg.json.timeout);
630 originalOnError = curFrame.onerror;
631 curFrame.onerror = function errHandler(errMsg, url, line) {
632 sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId);
633 curFrame.onerror = originalOnError;
634 };
636 let scriptSrc;
637 if (useFinish) {
638 if (msg.json.timeout == null || msg.json.timeout == 0) {
639 sendError("Please set a timeout", 21, null, asyncTestCommandId);
640 }
641 scriptSrc = script;
642 }
643 else {
644 try {
645 sandbox.__marionetteParams = elementManager.convertWrappedArguments(
646 msg.json.args, curFrame);
647 }
648 catch(e) {
649 sendError(e.message, e.code, e.stack, asyncTestCommandId);
650 return;
651 }
653 scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" +
654 "let __marionetteFunc = function() { " + script + "};" +
655 "__marionetteFunc.apply(null, __marionetteParams); ";
656 }
658 try {
659 asyncTestRunning = true;
660 if (importedScripts.exists()) {
661 let stream = Cc["@mozilla.org/network/file-input-stream;1"].
662 createInstance(Ci.nsIFileInputStream);
663 stream.init(importedScripts, -1, 0, 0);
664 let data = NetUtil.readInputStreamToString(stream, stream.available());
665 scriptSrc = data + scriptSrc;
666 }
667 Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0);
668 } catch (e) {
669 // 17 = JavascriptException
670 let error = createStackMessage(e,
671 "execute_async_script",
672 msg.json.filename,
673 msg.json.line,
674 scriptSrc);
675 sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId);
676 }
677 }
679 /**
680 * This function creates a touch event given a touch type and a touch
681 */
682 function emitTouchEvent(type, touch) {
683 if (!wasInterrupted()) {
684 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";
685 dumpLog(loggingInfo);
686 var docShell = curFrame.document.defaultView.
687 QueryInterface(Components.interfaces.nsIInterfaceRequestor).
688 getInterface(Components.interfaces.nsIWebNavigation).
689 QueryInterface(Components.interfaces.nsIDocShell);
690 if (docShell.asyncPanZoomEnabled && scrolling) {
691 // if we're in APZ and we're scrolling, we must use injectTouchEvent to dispatch our touchmove events
692 let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId");
693 // only call emitTouchEventForIFrame if we're inside an iframe.
694 if (index != null) {
695 sendSyncMessage("Marionette:emitTouchEvent", {index: index, type: type, id: touch.identifier,
696 clientX: touch.clientX, clientY: touch.clientY,
697 radiusX: touch.radiusX, radiusY: touch.radiusY,
698 rotation: touch.rotationAngle, force: touch.force});
699 return;
700 }
701 }
702 // we get here if we're not in asyncPacZoomEnabled land, or if we're the main process
703 /*
704 Disabled per bug 888303
705 marionetteLogObj.log(loggingInfo, "TRACE");
706 sendSyncMessage("Marionette:shareData",
707 {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
708 marionetteLogObj.clearLogs();
709 */
710 let domWindowUtils = curFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
711 domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
712 }
713 }
715 /**
716 * This function emit mouse event
717 * @param: doc is the current document
718 * type is the type of event to dispatch
719 * clickCount is the number of clicks, button notes the mouse button
720 * elClientX and elClientY are the coordinates of the mouse relative to the viewport
721 */
722 function emitMouseEvent(doc, type, elClientX, elClientY, clickCount, button) {
723 if (!wasInterrupted()) {
724 let loggingInfo = "emitting Mouse event of type " + type + " at coordinates (" + elClientX + ", " + elClientY + ") relative to the viewport";
725 dumpLog(loggingInfo);
726 /*
727 Disabled per bug 888303
728 marionetteLogObj.log(loggingInfo, "TRACE");
729 sendSyncMessage("Marionette:shareData",
730 {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
731 marionetteLogObj.clearLogs();
732 */
733 let win = doc.defaultView;
734 let domUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
735 domUtils.sendMouseEvent(type, elClientX, elClientY, button || 0, clickCount || 1, 0, false, 0, inputSource);
736 }
737 }
739 /**
740 * Helper function that perform a mouse tap
741 */
742 function mousetap(doc, x, y) {
743 emitMouseEvent(doc, 'mousemove', x, y);
744 emitMouseEvent(doc, 'mousedown', x, y);
745 emitMouseEvent(doc, 'mouseup', x, y);
746 }
749 /**
750 * This function generates a pair of coordinates relative to the viewport given a
751 * target element and coordinates relative to that element's top-left corner.
752 * @param 'x', and 'y' are the relative to the target.
753 * If they are not specified, then the center of the target is used.
754 */
755 function coordinates(target, x, y) {
756 let box = target.getBoundingClientRect();
757 if (x == null) {
758 x = box.width / 2;
759 }
760 if (y == null) {
761 y = box.height / 2;
762 }
763 let coords = {};
764 coords.x = box.left + x;
765 coords.y = box.top + y;
766 return coords;
767 }
769 /**
770 * This function returns if the element is in viewport
771 */
772 function elementInViewport(el) {
773 let rect = el.getBoundingClientRect();
774 let viewPort = {top: curFrame.pageYOffset,
775 left: curFrame.pageXOffset,
776 bottom: (curFrame.pageYOffset + curFrame.innerHeight),
777 right:(curFrame.pageXOffset + curFrame.innerWidth)};
778 return (viewPort.left <= rect.right + curFrame.pageXOffset &&
779 rect.left + curFrame.pageXOffset <= viewPort.right &&
780 viewPort.top <= rect.bottom + curFrame.pageYOffset &&
781 rect.top + curFrame.pageYOffset <= viewPort.bottom);
782 }
784 /**
785 * This function throws the visibility of the element error
786 */
787 function checkVisible(el) {
788 //check if the element is visible
789 let visible = utils.isElementDisplayed(el);
790 if (!visible) {
791 return false;
792 }
793 if (el.tagName.toLowerCase() === 'body') {
794 return true;
795 }
796 if (!elementInViewport(el)) {
797 //check if scroll function exist. If so, call it.
798 if (el.scrollIntoView) {
799 el.scrollIntoView(false);
800 if (!elementInViewport(el)) {
801 return false;
802 }
803 }
804 else {
805 return false;
806 }
807 }
808 return true;
809 }
811 //x and y are coordinates relative to the viewport
812 function generateEvents(type, x, y, touchId, target) {
813 lastCoordinates = [x, y];
814 let doc = curFrame.document;
815 switch (type) {
816 case 'tap':
817 if (mouseEventsOnly) {
818 mousetap(target.ownerDocument, x, y);
819 }
820 else {
821 let touchId = nextTouchId++;
822 let touch = createATouch(target, x, y, touchId);
823 emitTouchEvent('touchstart', touch);
824 emitTouchEvent('touchend', touch);
825 mousetap(target.ownerDocument, x, y);
826 }
827 lastCoordinates = null;
828 break;
829 case 'press':
830 isTap = true;
831 if (mouseEventsOnly) {
832 emitMouseEvent(doc, 'mousemove', x, y);
833 emitMouseEvent(doc, 'mousedown', x, y);
834 }
835 else {
836 let touchId = nextTouchId++;
837 let touch = createATouch(target, x, y, touchId);
838 emitTouchEvent('touchstart', touch);
839 touchIds[touchId] = touch;
840 return touchId;
841 }
842 break;
843 case 'release':
844 if (mouseEventsOnly) {
845 emitMouseEvent(doc, 'mouseup', lastCoordinates[0], lastCoordinates[1]);
846 }
847 else {
848 let touch = touchIds[touchId];
849 touch = createATouch(touch.target, lastCoordinates[0], lastCoordinates[1], touchId);
850 emitTouchEvent('touchend', touch);
851 if (isTap) {
852 mousetap(touch.target.ownerDocument, touch.clientX, touch.clientY);
853 }
854 delete touchIds[touchId];
855 }
856 isTap = false;
857 lastCoordinates = null;
858 break;
859 case 'cancel':
860 isTap = false;
861 if (mouseEventsOnly) {
862 emitMouseEvent(doc, 'mouseup', lastCoordinates[0], lastCoordinates[1]);
863 }
864 else {
865 emitTouchEvent('touchcancel', touchIds[touchId]);
866 delete touchIds[touchId];
867 }
868 lastCoordinates = null;
869 break;
870 case 'move':
871 isTap = false;
872 if (mouseEventsOnly) {
873 emitMouseEvent(doc, 'mousemove', x, y);
874 }
875 else {
876 touch = createATouch(touchIds[touchId].target, x, y, touchId);
877 touchIds[touchId] = touch;
878 emitTouchEvent('touchmove', touch);
879 }
880 break;
881 case 'contextmenu':
882 isTap = false;
883 let event = curFrame.document.createEvent('HTMLEvents');
884 event.initEvent('contextmenu', true, true);
885 if (mouseEventsOnly) {
886 target = doc.elementFromPoint(lastCoordinates[0], lastCoordinates[1]);
887 }
888 else {
889 target = touchIds[touchId].target;
890 }
891 target.dispatchEvent(event);
892 break;
893 default:
894 throw {message:"Unknown event type: " + type, code: 500, stack:null};
895 }
896 if (wasInterrupted()) {
897 if (previousFrame) {
898 //if previousFrame is set, then we're in a single process environment
899 curFrame = previousFrame;
900 previousFrame = null;
901 sandbox = null;
902 }
903 else {
904 //else we're in OOP environment, so we'll switch to the original OOP frame
905 sendSyncMessage("Marionette:switchToModalOrigin");
906 }
907 sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
908 }
909 }
911 /**
912 * Function that perform a single tap
913 */
914 function singleTap(msg) {
915 let command_id = msg.json.command_id;
916 try {
917 let el = elementManager.getKnownElement(msg.json.id, curFrame);
918 // after this block, the element will be scrolled into view
919 if (!checkVisible(el)) {
920 sendError("Element is not currently visible and may not be manipulated", 11, null, command_id);
921 return;
922 }
923 if (!curFrame.document.createTouch) {
924 mouseEventsOnly = true;
925 }
926 let c = coordinates(el, msg.json.corx, msg.json.cory);
927 generateEvents('tap', c.x, c.y, null, el);
928 sendOk(msg.json.command_id);
929 }
930 catch (e) {
931 sendError(e.message, e.code, e.stack, msg.json.command_id);
932 }
933 }
935 /**
936 * Function to create a touch based on the element
937 * corx and cory are relative to the viewport, id is the touchId
938 */
939 function createATouch(el, corx, cory, touchId) {
940 let doc = el.ownerDocument;
941 let win = doc.defaultView;
942 let clientX = corx;
943 let clientY = cory;
944 let pageX = clientX + win.pageXOffset,
945 pageY = clientY + win.pageYOffset;
946 let screenX = clientX + win.mozInnerScreenX,
947 screenY = clientY + win.mozInnerScreenY;
948 let atouch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
949 return atouch;
950 }
952 /**
953 * Function to emit touch events for each finger. e.g. finger=[['press', id], ['wait', 5], ['release']]
954 * touchId represents the finger id, i keeps track of the current action of the chain
955 */
956 function actions(chain, touchId, command_id, i) {
957 if (typeof i === "undefined") {
958 i = 0;
959 }
960 if (i == chain.length) {
961 sendResponse({value: touchId}, command_id);
962 return;
963 }
964 let pack = chain[i];
965 let command = pack[0];
966 let el;
967 let c;
968 i++;
969 if (command != 'press') {
970 //if mouseEventsOnly, then touchIds isn't used
971 if (!(touchId in touchIds) && !mouseEventsOnly) {
972 sendError("Element has not been pressed", 500, null, command_id);
973 return;
974 }
975 }
976 switch(command) {
977 case 'press':
978 if (lastCoordinates) {
979 generateEvents('cancel', lastCoordinates[0], lastCoordinates[1], touchId);
980 sendError("Invalid Command: press cannot follow an active touch event", 500, null, command_id);
981 return;
982 }
983 // look ahead to check if we're scrolling. Needed for APZ touch dispatching.
984 if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
985 scrolling = true;
986 }
987 el = elementManager.getKnownElement(pack[1], curFrame);
988 c = coordinates(el, pack[2], pack[3]);
989 touchId = generateEvents('press', c.x, c.y, null, el);
990 actions(chain, touchId, command_id, i);
991 break;
992 case 'release':
993 generateEvents('release', lastCoordinates[0], lastCoordinates[1], touchId);
994 actions(chain, null, command_id, i);
995 scrolling = false;
996 break;
997 case 'move':
998 el = elementManager.getKnownElement(pack[1], curFrame);
999 c = coordinates(el);
1000 generateEvents('move', c.x, c.y, touchId);
1001 actions(chain, touchId, command_id, i);
1002 break;
1003 case 'moveByOffset':
1004 generateEvents('move', lastCoordinates[0] + pack[1], lastCoordinates[1] + pack[2], touchId);
1005 actions(chain, touchId, command_id, i);
1006 break;
1007 case 'wait':
1008 if (pack[1] != null ) {
1009 let time = pack[1]*1000;
1010 // standard waiting time to fire contextmenu
1011 let standard = 750;
1012 try {
1013 standard = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
1014 }
1015 catch (e){}
1016 if (time >= standard && isTap) {
1017 chain.splice(i, 0, ['longPress'], ['wait', (time-standard)/1000]);
1018 time = standard;
1019 }
1020 checkTimer.initWithCallback(function(){actions(chain, touchId, command_id, i);}, time, Ci.nsITimer.TYPE_ONE_SHOT);
1021 }
1022 else {
1023 actions(chain, touchId, command_id, i);
1024 }
1025 break;
1026 case 'cancel':
1027 generateEvents('cancel', lastCoordinates[0], lastCoordinates[1], touchId);
1028 actions(chain, touchId, command_id, i);
1029 scrolling = false;
1030 break;
1031 case 'longPress':
1032 generateEvents('contextmenu', lastCoordinates[0], lastCoordinates[1], touchId);
1033 actions(chain, touchId, command_id, i);
1034 break;
1035 }
1036 }
1038 /**
1039 * Function to start action chain on one finger
1040 */
1041 function actionChain(msg) {
1042 let command_id = msg.json.command_id;
1043 let args = msg.json.chain;
1044 let touchId = msg.json.nextId;
1045 try {
1046 let commandArray = elementManager.convertWrappedArguments(args, curFrame);
1047 // loop the action array [ ['press', id], ['move', id], ['release', id] ]
1048 if (touchId == null) {
1049 touchId = nextTouchId++;
1050 }
1051 if (!curFrame.document.createTouch) {
1052 mouseEventsOnly = true;
1053 }
1054 actions(commandArray, touchId, command_id);
1055 }
1056 catch (e) {
1057 sendError(e.message, e.code, e.stack, msg.json.command_id);
1058 }
1059 }
1061 /**
1062 * Function to emit touch events which allow multi touch on the screen
1063 * @param type represents the type of event, touch represents the current touch,touches are all pending touches
1064 */
1065 function emitMultiEvents(type, touch, touches) {
1066 let target = touch.target;
1067 let doc = target.ownerDocument;
1068 let win = doc.defaultView;
1069 // touches that are in the same document
1070 let documentTouches = doc.createTouchList(touches.filter(function(t) {
1071 return ((t.target.ownerDocument === doc) && (type != 'touchcancel'));
1072 }));
1073 // touches on the same target
1074 let targetTouches = doc.createTouchList(touches.filter(function(t) {
1075 return ((t.target === target) && ((type != 'touchcancel') || (type != 'touchend')));
1076 }));
1077 // Create changed touches
1078 let changedTouches = doc.createTouchList(touch);
1079 // Create the event object
1080 let event = doc.createEvent('TouchEvent');
1081 event.initTouchEvent(type,
1082 true,
1083 true,
1084 win,
1085 0,
1086 false, false, false, false,
1087 documentTouches,
1088 targetTouches,
1089 changedTouches);
1090 target.dispatchEvent(event);
1091 }
1093 /**
1094 * Function to dispatch one set of actions
1095 * @param touches represents all pending touches, batchIndex represents the batch we are dispatching right now
1096 */
1097 function setDispatch(batches, touches, command_id, batchIndex) {
1098 if (typeof batchIndex === "undefined") {
1099 batchIndex = 0;
1100 }
1101 // check if all the sets have been fired
1102 if (batchIndex >= batches.length) {
1103 multiLast = {};
1104 sendOk(command_id);
1105 return;
1106 }
1107 // a set of actions need to be done
1108 let batch = batches[batchIndex];
1109 // each action for some finger
1110 let pack;
1111 // the touch id for the finger (pack)
1112 let touchId;
1113 // command for the finger
1114 let command;
1115 // touch that will be created for the finger
1116 let el;
1117 let corx;
1118 let cory;
1119 let touch;
1120 let lastTouch;
1121 let touchIndex;
1122 let waitTime = 0;
1123 let maxTime = 0;
1124 let c;
1125 batchIndex++;
1126 // loop through the batch
1127 for (let i = 0; i < batch.length; i++) {
1128 pack = batch[i];
1129 touchId = pack[0];
1130 command = pack[1];
1131 switch (command) {
1132 case 'press':
1133 el = elementManager.getKnownElement(pack[2], curFrame);
1134 c = coordinates(el, pack[3], pack[4]);
1135 touch = createATouch(el, c.x, c.y, touchId);
1136 multiLast[touchId] = touch;
1137 touches.push(touch);
1138 emitMultiEvents('touchstart', touch, touches);
1139 break;
1140 case 'release':
1141 touch = multiLast[touchId];
1142 // the index of the previous touch for the finger may change in the touches array
1143 touchIndex = touches.indexOf(touch);
1144 touches.splice(touchIndex, 1);
1145 emitMultiEvents('touchend', touch, touches);
1146 break;
1147 case 'move':
1148 el = elementManager.getKnownElement(pack[2], curFrame);
1149 c = coordinates(el);
1150 touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
1151 touchIndex = touches.indexOf(lastTouch);
1152 touches[touchIndex] = touch;
1153 multiLast[touchId] = touch;
1154 emitMultiEvents('touchmove', touch, touches);
1155 break;
1156 case 'moveByOffset':
1157 el = multiLast[touchId].target;
1158 lastTouch = multiLast[touchId];
1159 touchIndex = touches.indexOf(lastTouch);
1160 let doc = el.ownerDocument;
1161 let win = doc.defaultView;
1162 // since x and y are relative to the last touch, therefore, it's relative to the position of the last touch
1163 let clientX = lastTouch.clientX + pack[2],
1164 clientY = lastTouch.clientY + pack[3];
1165 let pageX = clientX + win.pageXOffset,
1166 pageY = clientY + win.pageYOffset;
1167 let screenX = clientX + win.mozInnerScreenX,
1168 screenY = clientY + win.mozInnerScreenY;
1169 touch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
1170 touches[touchIndex] = touch;
1171 multiLast[touchId] = touch;
1172 emitMultiEvents('touchmove', touch, touches);
1173 break;
1174 case 'wait':
1175 if (pack[2] != undefined ) {
1176 waitTime = pack[2]*1000;
1177 if (waitTime > maxTime) {
1178 maxTime = waitTime;
1179 }
1180 }
1181 break;
1182 }//end of switch block
1183 }//end of for loop
1184 if (maxTime != 0) {
1185 checkTimer.initWithCallback(function(){setDispatch(batches, touches, command_id, batchIndex);}, maxTime, Ci.nsITimer.TYPE_ONE_SHOT);
1186 }
1187 else {
1188 setDispatch(batches, touches, command_id, batchIndex);
1189 }
1190 }
1192 /**
1193 * Function to start multi-action
1194 */
1195 function multiAction(msg) {
1196 let command_id = msg.json.command_id;
1197 let args = msg.json.value;
1198 // maxlen is the longest action chain for one finger
1199 let maxlen = msg.json.maxlen;
1200 try {
1201 // unwrap the original nested array
1202 let commandArray = elementManager.convertWrappedArguments(args, curFrame);
1203 let concurrentEvent = [];
1204 let temp;
1205 for (let i = 0; i < maxlen; i++) {
1206 let row = [];
1207 for (let j = 0; j < commandArray.length; j++) {
1208 if (commandArray[j][i] != undefined) {
1209 // add finger id to the front of each action, i.e. [finger_id, action, element]
1210 temp = commandArray[j][i];
1211 temp.unshift(j);
1212 row.push(temp);
1213 }
1214 }
1215 concurrentEvent.push(row);
1216 }
1217 // now concurrent event is made of sets where each set contain a list of actions that need to be fired.
1218 // note: each action belongs to a different finger
1219 // pendingTouches keeps track of current touches that's on the screen
1220 let pendingTouches = [];
1221 setDispatch(concurrentEvent, pendingTouches, command_id);
1222 }
1223 catch (e) {
1224 sendError(e.message, e.code, e.stack, msg.json.command_id);
1225 }
1226 }
1228 /**
1229 * Navigate to the given URL. The operation will be performed on the
1230 * current browser context, and handles the case where we navigate
1231 * within an iframe. All other navigation is handled by the server
1232 * (in chrome space).
1233 */
1234 function get(msg) {
1235 let command_id = msg.json.command_id;
1237 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1238 let start = new Date().getTime();
1239 let end = null;
1240 function checkLoad() {
1241 checkTimer.cancel();
1242 end = new Date().getTime();
1243 let errorRegex = /about:.+(error)|(blocked)\?/;
1244 let elapse = end - start;
1245 if (msg.json.pageTimeout == null || elapse <= msg.json.pageTimeout) {
1246 if (curFrame.document.readyState == "complete") {
1247 removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
1248 sendOk(command_id);
1249 }
1250 else if (curFrame.document.readyState == "interactive" &&
1251 errorRegex.exec(curFrame.document.baseURI)) {
1252 removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
1253 sendError("Error loading page", 13, null, command_id);
1254 }
1255 else {
1256 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1257 }
1258 }
1259 else {
1260 removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
1261 sendError("Error loading page, timed out (checkLoad)", 21, null,
1262 command_id);
1263 }
1264 }
1265 // Prevent DOMContentLoaded events from frames from invoking this
1266 // code, unless the event is coming from the frame associated with
1267 // the current window (i.e. someone has used switch_to_frame).
1268 let onDOMContentLoaded = function onDOMContentLoaded(event) {
1269 if (!event.originalTarget.defaultView.frameElement ||
1270 event.originalTarget.defaultView.frameElement == curFrame.frameElement) {
1271 checkLoad();
1272 }
1273 };
1275 function timerFunc() {
1276 removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
1277 sendError("Error loading page, timed out (onDOMContentLoaded)", 21,
1278 null, command_id);
1279 }
1280 if (msg.json.pageTimeout != null) {
1281 checkTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
1282 }
1283 addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
1284 curFrame.location = msg.json.url;
1285 }
1287 /**
1288 * Get URL of the top level browsing context.
1289 */
1290 function getCurrentUrl(msg) {
1291 sendResponse({value: curFrame.location.href}, msg.json.command_id);
1292 }
1294 /**
1295 * Get the current Title of the window
1296 */
1297 function getTitle(msg) {
1298 sendResponse({value: curFrame.top.document.title}, msg.json.command_id);
1299 }
1301 /**
1302 * Get the current page source
1303 */
1304 function getPageSource(msg) {
1305 var XMLSerializer = curFrame.XMLSerializer;
1306 var pageSource = new XMLSerializer().serializeToString(curFrame.document);
1307 sendResponse({value: pageSource}, msg.json.command_id);
1308 }
1310 /**
1311 * Go back in history
1312 */
1313 function goBack(msg) {
1314 curFrame.history.back();
1315 sendOk(msg.json.command_id);
1316 }
1318 /**
1319 * Go forward in history
1320 */
1321 function goForward(msg) {
1322 curFrame.history.forward();
1323 sendOk(msg.json.command_id);
1324 }
1326 /**
1327 * Refresh the page
1328 */
1329 function refresh(msg) {
1330 let command_id = msg.json.command_id;
1331 curFrame.location.reload(true);
1332 let listen = function() {
1333 removeEventListener("DOMContentLoaded", arguments.callee, false);
1334 sendOk(command_id);
1335 };
1336 addEventListener("DOMContentLoaded", listen, false);
1337 }
1339 /**
1340 * Find an element in the document using requested search strategy
1341 */
1342 function findElementContent(msg) {
1343 let command_id = msg.json.command_id;
1344 try {
1345 let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
1346 let on_error = sendError;
1347 elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
1348 on_success, on_error, false, command_id);
1349 }
1350 catch (e) {
1351 sendError(e.message, e.code, e.stack, command_id);
1352 }
1353 }
1355 /**
1356 * Find elements in the document using requested search strategy
1357 */
1358 function findElementsContent(msg) {
1359 let command_id = msg.json.command_id;
1360 try {
1361 let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
1362 let on_error = sendError;
1363 elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
1364 on_success, on_error, true, command_id);
1365 }
1366 catch (e) {
1367 sendError(e.message, e.code, e.stack, command_id);
1368 }
1369 }
1371 /**
1372 * Find and return the active element on the page
1373 */
1374 function getActiveElement(msg) {
1375 let command_id = msg.json.command_id;
1376 var element = curFrame.document.activeElement;
1377 var id = elementManager.addToKnownElements(element);
1378 sendResponse({value: id}, command_id);
1379 }
1381 /**
1382 * Send click event to element
1383 */
1384 function clickElement(msg) {
1385 let command_id = msg.json.command_id;
1386 let el;
1387 try {
1388 el = elementManager.getKnownElement(msg.json.id, curFrame);
1389 if (checkVisible(el)) {
1390 if (utils.isElementEnabled(el)) {
1391 utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView)
1392 }
1393 else {
1394 sendError("Element is not Enabled", 12, null, command_id)
1395 }
1396 }
1397 else {
1398 sendError("Element is not visible", 11, null, command_id)
1399 }
1400 sendOk(command_id);
1401 }
1402 catch (e) {
1403 sendError(e.message, e.code, e.stack, command_id);
1404 }
1405 }
1407 /**
1408 * Get a given attribute of an element
1409 */
1410 function getElementAttribute(msg) {
1411 let command_id = msg.json.command_id;
1412 try {
1413 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1414 sendResponse({value: utils.getElementAttribute(el, msg.json.name)},
1415 command_id);
1416 }
1417 catch (e) {
1418 sendError(e.message, e.code, e.stack, command_id);
1419 }
1420 }
1422 /**
1423 * Get the text of this element. This includes text from child elements.
1424 */
1425 function getElementText(msg) {
1426 let command_id = msg.json.command_id;
1427 try {
1428 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1429 sendResponse({value: utils.getElementText(el)}, command_id);
1430 }
1431 catch (e) {
1432 sendError(e.message, e.code, e.stack, command_id);
1433 }
1434 }
1436 /**
1437 * Get the tag name of an element.
1438 */
1439 function getElementTagName(msg) {
1440 let command_id = msg.json.command_id;
1441 try {
1442 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1443 sendResponse({value: el.tagName.toLowerCase()}, command_id);
1444 }
1445 catch (e) {
1446 sendError(e.message, e.code, e.stack, command_id);
1447 }
1448 }
1450 /**
1451 * Check if element is displayed
1452 */
1453 function isElementDisplayed(msg) {
1454 let command_id = msg.json.command_id;
1455 try {
1456 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1457 sendResponse({value: utils.isElementDisplayed(el)}, command_id);
1458 }
1459 catch (e) {
1460 sendError(e.message, e.code, e.stack, command_id);
1461 }
1462 }
1464 /**
1465 * Return the property of the computed style of an element
1466 *
1467 * @param object aRequest
1468 * 'element' member holds the reference id to
1469 * the element that will be checked
1470 * 'propertyName' is the CSS rule that is being requested
1471 */
1472 function getElementValueOfCssProperty(msg){
1473 let command_id = msg.json.command_id;
1474 let propertyName = msg.json.propertyName;
1475 try {
1476 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1477 sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)},
1478 command_id);
1479 }
1480 catch (e) {
1481 sendError(e.message, e.code, e.stack, command_id);
1482 }
1483 }
1485 /**
1486 * Submit a form on a content page by either using form or element in a form
1487 * @param object msg
1488 * 'json' JSON object containing 'id' member of the element
1489 */
1490 function submitElement (msg) {
1491 let command_id = msg.json.command_id;
1492 try {
1493 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1494 while (el.parentNode != null && el.tagName.toLowerCase() != 'form') {
1495 el = el.parentNode;
1496 }
1497 if (el.tagName && el.tagName.toLowerCase() == 'form') {
1498 el.submit();
1499 sendOk(command_id);
1500 }
1501 else {
1502 sendError("Element is not a form element or in a form", 7, null, command_id);
1503 }
1505 }
1506 catch (e) {
1507 sendError(e.message, e.code, e.stack, command_id);
1508 }
1509 }
1511 /**
1512 * Get the size of the element and return it
1513 */
1514 function getElementSize(msg){
1515 let command_id = msg.json.command_id;
1516 try {
1517 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1518 let clientRect = el.getBoundingClientRect();
1519 sendResponse({value: {width: clientRect.width, height: clientRect.height}},
1520 command_id);
1521 }
1522 catch (e) {
1523 sendError(e.message, e.code, e.stack, command_id);
1524 }
1525 }
1527 /**
1528 * Check if element is enabled
1529 */
1530 function isElementEnabled(msg) {
1531 let command_id = msg.json.command_id;
1532 try {
1533 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1534 sendResponse({value: utils.isElementEnabled(el)}, command_id);
1535 }
1536 catch (e) {
1537 sendError(e.message, e.code, e.stack, command_id);
1538 }
1539 }
1541 /**
1542 * Check if element is selected
1543 */
1544 function isElementSelected(msg) {
1545 let command_id = msg.json.command_id;
1546 try {
1547 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1548 sendResponse({value: utils.isElementSelected(el)}, command_id);
1549 }
1550 catch (e) {
1551 sendError(e.message, e.code, e.stack, command_id);
1552 }
1553 }
1555 /**
1556 * Send keys to element
1557 */
1558 function sendKeysToElement(msg) {
1559 let command_id = msg.json.command_id;
1561 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1562 if (checkVisible(el)) {
1563 if (el.mozIsTextField && el.mozIsTextField(false)) {
1564 var currentTextLength = el.value ? el.value.length : 0;
1565 el.selectionStart = currentTextLength;
1566 el.selectionEnd = currentTextLength;
1567 }
1568 el.focus();
1569 var value = msg.json.value.join("");
1570 let hasShift = null;
1571 let hasCtrl = null;
1572 let hasAlt = null;
1573 let hasMeta = null;
1574 for (var i = 0; i < value.length; i++) {
1575 let upper = value.charAt(i).toUpperCase();
1576 var keyCode = null;
1577 var c = value.charAt(i);
1578 switch (c) {
1579 case '\uE001':
1580 keyCode = "VK_CANCEL";
1581 break;
1582 case '\uE002':
1583 keyCode = "VK_HELP";
1584 break;
1585 case '\uE003':
1586 keyCode = "VK_BACK_SPACE";
1587 break;
1588 case '\uE004':
1589 keyCode = "VK_TAB";
1590 break;
1591 case '\uE005':
1592 keyCode = "VK_CLEAR";
1593 break;
1594 case '\uE006':
1595 case '\uE007':
1596 keyCode = "VK_RETURN";
1597 break;
1598 case '\uE008':
1599 keyCode = "VK_SHIFT";
1600 hasShift = !hasShift;
1601 break;
1602 case '\uE009':
1603 keyCode = "VK_CONTROL";
1604 controlKey = !controlKey;
1605 break;
1606 case '\uE00A':
1607 keyCode = "VK_ALT";
1608 altKey = !altKey;
1609 break;
1610 case '\uE03D':
1611 keyCode = "VK_META";
1612 metaKey = !metaKey;
1613 break;
1614 case '\uE00B':
1615 keyCode = "VK_PAUSE";
1616 break;
1617 case '\uE00C':
1618 keyCode = "VK_ESCAPE";
1619 break;
1620 case '\uE00D':
1621 keyCode = "VK_Space"; // printable
1622 break;
1623 case '\uE00E':
1624 keyCode = "VK_PAGE_UP";
1625 break;
1626 case '\uE00F':
1627 keyCode = "VK_PAGE_DOWN";
1628 break;
1629 case '\uE010':
1630 keyCode = "VK_END";
1631 break;
1632 case '\uE011':
1633 keyCode = "VK_HOME";
1634 break;
1635 case '\uE012':
1636 keyCode = "VK_LEFT";
1637 break;
1638 case '\uE013':
1639 keyCode = "VK_UP";
1640 break;
1641 case '\uE014':
1642 keyCode = "VK_RIGHT";
1643 break;
1644 case '\uE015':
1645 keyCode = "VK_DOWN";
1646 break;
1647 case '\uE016':
1648 keyCode = "VK_INSERT";
1649 break;
1650 case '\uE017':
1651 keyCode = "VK_DELETE";
1652 break;
1653 case '\uE018':
1654 keyCode = "VK_SEMICOLON";
1655 break;
1656 case '\uE019':
1657 keyCode = "VK_EQUALS";
1658 break;
1659 case '\uE01A':
1660 keyCode = "VK_NUMPAD0";
1661 break;
1662 case '\uE01B':
1663 keyCode = "VK_NUMPAD1";
1664 break;
1665 case '\uE01C':
1666 keyCode = "VK_NUMPAD2";
1667 break;
1668 case '\uE01D':
1669 keyCode = "VK_NUMPAD3";
1670 break;
1671 case '\uE01E':
1672 keyCode = "VK_NUMPAD4";
1673 break;
1674 case '\uE01F':
1675 keyCode = "VK_NUMPAD5";
1676 break;
1677 case '\uE020':
1678 keyCode = "VK_NUMPAD6";
1679 break;
1680 case '\uE021':
1681 keyCode = "VK_NUMPAD7";
1682 break;
1683 case '\uE022':
1684 keyCode = "VK_NUMPAD8";
1685 break;
1686 case '\uE023':
1687 keyCode = "VK_NUMPAD9";
1688 break;
1689 case '\uE024':
1690 keyCode = "VK_MULTIPLY";
1691 break;
1692 case '\uE025':
1693 keyCode = "VK_ADD";
1694 break;
1695 case '\uE026':
1696 keyCode = "VK_SEPARATOR";
1697 break;
1698 case '\uE027':
1699 keyCode = "VK_SUBTRACT";
1700 break;
1701 case '\uE028':
1702 keyCode = "VK_DECIMAL";
1703 break;
1704 case '\uE029':
1705 keyCode = "VK_DIVIDE";
1706 break;
1707 case '\uE031':
1708 keyCode = "VK_F1";
1709 break;
1710 case '\uE032':
1711 keyCode = "VK_F2";
1712 break;
1713 case '\uE033':
1714 keyCode = "VK_F3";
1715 break;
1716 case '\uE034':
1717 keyCode = "VK_F4";
1718 break;
1719 case '\uE035':
1720 keyCode = "VK_F5";
1721 break;
1722 case '\uE036':
1723 keyCode = "VK_F6";
1724 break;
1725 case '\uE037':
1726 keyCode = "VK_F7";
1727 break;
1728 case '\uE038':
1729 keyCode = "VK_F8";
1730 break;
1731 case '\uE039':
1732 keyCode = "VK_F9";
1733 break;
1734 case '\uE03A':
1735 keyCode = "VK_F10";
1736 break;
1737 case '\uE03B':
1738 keyCode = "VK_F11";
1739 break;
1740 case '\uE03C':
1741 keyCode = "VK_F12";
1742 break;
1743 }
1744 hasShift = value.charAt(i) == upper;
1745 utils.synthesizeKey(keyCode || value[i],
1746 { shiftKey: hasShift, ctrlKey: hasCtrl, altKey: hasAlt, metaKey: hasMeta },
1747 curFrame);
1748 };
1749 sendOk(command_id);
1750 }
1751 else {
1752 sendError("Element is not visible", 11, null, command_id)
1753 }
1754 }
1756 /**
1757 * Get the element's top left-hand corner point.
1758 */
1759 function getElementLocation(msg) {
1760 let command_id = msg.json.command_id;
1761 try {
1762 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1763 let rect = el.getBoundingClientRect();
1765 let location = {};
1766 location.x = rect.left;
1767 location.y = rect.top;
1769 sendResponse({value: location}, command_id);
1770 }
1771 catch (e) {
1772 sendError(e.message, e.code, e.stack, command_id);
1773 }
1774 }
1776 /**
1777 * Clear the text of an element
1778 */
1779 function clearElement(msg) {
1780 let command_id = msg.json.command_id;
1781 try {
1782 let el = elementManager.getKnownElement(msg.json.id, curFrame);
1783 utils.clearElement(el);
1784 sendOk(command_id);
1785 }
1786 catch (e) {
1787 sendError(e.message, e.code, e.stack, command_id);
1788 }
1789 }
1791 /**
1792 * Switch to frame given either the server-assigned element id,
1793 * its index in window.frames, or the iframe's name or id.
1794 */
1795 function switchToFrame(msg) {
1796 let command_id = msg.json.command_id;
1797 function checkLoad() {
1798 let errorRegex = /about:.+(error)|(blocked)\?/;
1799 if (curFrame.document.readyState == "complete") {
1800 sendOk(command_id);
1801 return;
1802 }
1803 else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) {
1804 sendError("Error loading page", 13, null, command_id);
1805 return;
1806 }
1807 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1808 }
1809 let foundFrame = null;
1810 let frames = []; //curFrame.document.getElementsByTagName("iframe");
1811 let parWindow = null; //curFrame.QueryInterface(Ci.nsIInterfaceRequestor)
1812 // Check of the curFrame reference is dead
1813 try {
1814 frames = curFrame.document.getElementsByTagName("iframe");
1815 //Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one.
1816 //parWindow will refer to the iframe above the nested OOP frame.
1817 parWindow = curFrame.QueryInterface(Ci.nsIInterfaceRequestor)
1818 .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
1819 } catch (e) {
1820 // We probably have a dead compartment so accessing it is going to make Firefox
1821 // very upset. Let's now try redirect everything to the top frame even if the
1822 // user has given us a frame since search doesnt look up.
1823 msg.json.id = null;
1824 msg.json.element = null;
1825 }
1826 if ((msg.json.id == null) && (msg.json.element == null)) {
1827 // returning to root frame
1828 sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
1830 curFrame = content;
1831 if(msg.json.focus == true) {
1832 curFrame.focus();
1833 }
1834 sandbox = null;
1835 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1836 return;
1837 }
1838 if (msg.json.element != undefined) {
1839 if (elementManager.seenItems[msg.json.element] != undefined) {
1840 let wantedFrame;
1841 try {
1842 wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //HTMLIFrameElement
1843 }
1844 catch(e) {
1845 sendError(e.message, e.code, e.stack, command_id);
1846 }
1847 for (let i = 0; i < frames.length; i++) {
1848 // use XPCNativeWrapper to compare elements; see bug 834266
1849 if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) {
1850 curFrame = frames[i];
1851 foundFrame = i;
1852 }
1853 }
1854 }
1855 }
1856 if (foundFrame == null) {
1857 switch(typeof(msg.json.id)) {
1858 case "string" :
1859 let foundById = null;
1860 for (let i = 0; i < frames.length; i++) {
1861 //give precedence to name
1862 let frame = frames[i];
1863 let name = utils.getElementAttribute(frame, 'name');
1864 let id = utils.getElementAttribute(frame, 'id');
1865 if (name == msg.json.id) {
1866 foundFrame = i;
1867 break;
1868 } else if ((foundById == null) && (id == msg.json.id)) {
1869 foundById = i;
1870 }
1871 }
1872 if ((foundFrame == null) && (foundById != null)) {
1873 foundFrame = foundById;
1874 curFrame = frames[foundFrame];
1875 }
1876 break;
1877 case "number":
1878 if (frames[msg.json.id] != undefined) {
1879 foundFrame = msg.json.id;
1880 curFrame = frames[foundFrame];
1881 }
1882 break;
1883 }
1884 }
1885 if (foundFrame == null) {
1886 sendError("Unable to locate frame: " + msg.json.id, 8, null, command_id);
1887 return;
1888 }
1890 sandbox = null;
1892 // send a synchronous message to let the server update the currently active
1893 // frame element (for getActiveFrame)
1894 let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
1895 sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue });
1897 if (curFrame.contentWindow == null) {
1898 // The frame we want to switch to is a remote (out-of-process) frame;
1899 // notify our parent to handle the switch.
1900 curFrame = content;
1901 sendToServer('Marionette:switchToFrame', {frame: foundFrame,
1902 win: parWindow,
1903 command_id: command_id});
1904 }
1905 else {
1906 curFrame = curFrame.contentWindow;
1907 if(msg.json.focus == true) {
1908 curFrame.focus();
1909 }
1910 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1911 }
1912 }
1913 /**
1914 * Add a cookie to the document
1915 */
1916 function addCookie(msg) {
1917 cookie = msg.json.cookie;
1919 if (!cookie.expiry) {
1920 var date = new Date();
1921 var thePresent = new Date(Date.now());
1922 date.setYear(thePresent.getFullYear() + 20);
1923 cookie.expiry = date.getTime() / 1000; // Stored in seconds.
1924 }
1926 if (!cookie.domain) {
1927 var location = curFrame.document.location;
1928 cookie.domain = location.hostname;
1929 }
1930 else {
1931 var currLocation = curFrame.location;
1932 var currDomain = currLocation.host;
1933 if (currDomain.indexOf(cookie.domain) == -1) {
1934 sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id);
1935 }
1936 }
1938 // The cookie's domain may include a port. Which is bad. Remove it
1939 // We'll catch ip6 addresses by mistake. Since no-one uses those
1940 // this will be okay for now. See Bug 814416
1941 if (cookie.domain.match(/:\d+$/)) {
1942 cookie.domain = cookie.domain.replace(/:\d+$/, '');
1943 }
1945 var document = curFrame.document;
1946 if (!document || !document.contentType.match(/html/i)) {
1947 sendError('You may only set cookies on html documents', 25, null, msg.json.command_id);
1948 }
1949 var cookieManager = Cc['@mozilla.org/cookiemanager;1'].
1950 getService(Ci.nsICookieManager2);
1951 cookieManager.add(cookie.domain, cookie.path, cookie.name, cookie.value,
1952 cookie.secure, false, false, cookie.expiry);
1953 sendOk(msg.json.command_id);
1954 }
1956 /**
1957 * Get all cookies for the current domain.
1958 */
1959 function getCookies(msg) {
1960 var toReturn = [];
1961 var cookies = getVisibleCookies(curFrame.location);
1962 for (var i = 0; i < cookies.length; i++) {
1963 var cookie = cookies[i];
1964 var expires = cookie.expires;
1965 if (expires == 0) { // Session cookie, don't return an expiry.
1966 expires = null;
1967 } else if (expires == 1) { // Date before epoch time, cap to epoch.
1968 expires = 0;
1969 }
1970 toReturn.push({
1971 'name': cookie.name,
1972 'value': cookie.value,
1973 'path': cookie.path,
1974 'domain': cookie.host,
1975 'secure': cookie.isSecure,
1976 'expiry': expires
1977 });
1978 }
1980 sendResponse({value: toReturn}, msg.json.command_id);
1981 }
1983 /**
1984 * Delete a cookie by name
1985 */
1986 function deleteCookie(msg) {
1987 var toDelete = msg.json.name;
1988 var cookieManager = Cc['@mozilla.org/cookiemanager;1'].
1989 getService(Ci.nsICookieManager);
1991 var cookies = getVisibleCookies(curFrame.location);
1992 for (var i = 0; i < cookies.length; i++) {
1993 var cookie = cookies[i];
1994 if (cookie.name == toDelete) {
1995 cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
1996 }
1997 }
1999 sendOk(msg.json.command_id);
2000 }
2002 /**
2003 * Delete all the visibile cookies on a page
2004 */
2005 function deleteAllCookies(msg) {
2006 let cookieManager = Cc['@mozilla.org/cookiemanager;1'].
2007 getService(Ci.nsICookieManager);
2008 let cookies = getVisibleCookies(curFrame.location);
2009 for (let i = 0; i < cookies.length; i++) {
2010 let cookie = cookies[i];
2011 cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
2012 }
2013 sendOk(msg.json.command_id);
2014 }
2016 /**
2017 * Get all the visible cookies from a location
2018 */
2019 function getVisibleCookies(location) {
2020 let results = [];
2021 let currentPath = location.pathname;
2022 if (!currentPath) currentPath = '/';
2023 let isForCurrentPath = function(aPath) {
2024 return currentPath.indexOf(aPath) != -1;
2025 }
2027 let cookieManager = Cc['@mozilla.org/cookiemanager;1'].
2028 getService(Ci.nsICookieManager);
2029 let enumerator = cookieManager.enumerator;
2030 while (enumerator.hasMoreElements()) {
2031 let cookie = enumerator.getNext().QueryInterface(Ci['nsICookie']);
2033 // Take the hostname and progressively shorten
2034 let hostname = location.hostname;
2035 do {
2036 if ((cookie.host == '.' + hostname || cookie.host == hostname)
2037 && isForCurrentPath(cookie.path)) {
2038 results.push(cookie);
2039 break;
2040 }
2041 hostname = hostname.replace(/^.*?\./, '');
2042 } while (hostname.indexOf('.') != -1);
2043 }
2045 return results;
2046 }
2048 function getAppCacheStatus(msg) {
2049 sendResponse({ value: curFrame.applicationCache.status },
2050 msg.json.command_id);
2051 }
2053 // emulator callbacks
2054 let _emu_cb_id = 0;
2055 let _emu_cbs = {};
2057 function runEmulatorCmd(cmd, callback) {
2058 if (callback) {
2059 _emu_cbs[_emu_cb_id] = callback;
2060 }
2061 sendAsyncMessage("Marionette:runEmulatorCmd", {emulator_cmd: cmd, id: _emu_cb_id});
2062 _emu_cb_id += 1;
2063 }
2065 function runEmulatorShell(args, callback) {
2066 if (callback) {
2067 _emu_cbs[_emu_cb_id] = callback;
2068 }
2069 sendAsyncMessage("Marionette:runEmulatorShell", {emulator_shell: args, id: _emu_cb_id});
2070 _emu_cb_id += 1;
2071 }
2073 function emulatorCmdResult(msg) {
2074 let message = msg.json;
2075 if (!sandbox) {
2076 return;
2077 }
2078 let cb = _emu_cbs[message.id];
2079 delete _emu_cbs[message.id];
2080 if (!cb) {
2081 return;
2082 }
2083 try {
2084 cb(message.result);
2085 }
2086 catch(e) {
2087 sendError(e.message, e.code, e.stack, -1);
2088 return;
2089 }
2090 }
2092 function importScript(msg) {
2093 let command_id = msg.json.command_id;
2094 let file;
2095 if (importedScripts.exists()) {
2096 file = FileUtils.openFileOutputStream(importedScripts,
2097 FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY);
2098 }
2099 else {
2100 //Note: The permission bits here don't actually get set (bug 804563)
2101 importedScripts.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE,
2102 parseInt("0666", 8));
2103 file = FileUtils.openFileOutputStream(importedScripts,
2104 FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
2105 importedScripts.permissions = parseInt("0666", 8); //actually set permissions
2106 }
2107 file.write(msg.json.script, msg.json.script.length);
2108 file.close();
2109 sendOk(command_id);
2110 }
2112 /**
2113 * Takes a screen capture of the given web element if <code>id</code>
2114 * property exists in the message's JSON object, or if null captures
2115 * the bounding box of the current frame.
2116 *
2117 * If given an array of web element references in
2118 * <code>msg.json.highlights</code>, a red box will be painted around
2119 * them to highlight their position.
2120 */
2121 function takeScreenshot(msg) {
2122 let node = null;
2123 if (msg.json.id) {
2124 try {
2125 node = elementManager.getKnownElement(msg.json.id, curFrame)
2126 }
2127 catch (e) {
2128 sendResponse(e.message, e.code, e.stack, msg.json.command_id);
2129 return;
2130 }
2131 }
2132 else {
2133 node = curFrame;
2134 }
2135 let highlights = msg.json.highlights;
2137 var document = curFrame.document;
2138 var rect, win, width, height, left, top;
2139 // node can be either a window or an arbitrary DOM node
2140 if (node == curFrame) {
2141 // node is a window
2142 win = node;
2143 width = document.body.scrollWidth;
2144 height = document.body.scrollHeight;
2145 top = 0;
2146 left = 0;
2147 }
2148 else {
2149 // node is an arbitrary DOM node
2150 win = node.ownerDocument.defaultView;
2151 rect = node.getBoundingClientRect();
2152 width = rect.width;
2153 height = rect.height;
2154 top = rect.top;
2155 left = rect.left;
2156 }
2158 var canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
2159 "canvas");
2160 canvas.width = width;
2161 canvas.height = height;
2162 var ctx = canvas.getContext("2d");
2163 // Draws the DOM contents of the window to the canvas
2164 ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
2166 // This section is for drawing a red rectangle around each element
2167 // passed in via the highlights array
2168 if (highlights) {
2169 ctx.lineWidth = "2";
2170 ctx.strokeStyle = "red";
2171 ctx.save();
2173 for (var i = 0; i < highlights.length; ++i) {
2174 var elem = elementManager.getKnownElement(highlights[i], curFrame);
2175 rect = elem.getBoundingClientRect();
2177 var offsetY = -top;
2178 var offsetX = -left;
2180 // Draw the rectangle
2181 ctx.strokeRect(rect.left + offsetX,
2182 rect.top + offsetY,
2183 rect.width,
2184 rect.height);
2185 }
2186 }
2188 // Return the Base64 encoded string back to the client so that it
2189 // can save the file to disk if it is required
2190 var dataUrl = canvas.toDataURL("image/png", "");
2191 var data = dataUrl.substring(dataUrl.indexOf(",") + 1);
2192 sendResponse({value: data}, msg.json.command_id);
2193 }
2195 // Call register self when we get loaded
2196 registerSelf();