|
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/. */ |
|
5 |
|
6 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; |
|
7 |
|
8 let uuidGen = Cc["@mozilla.org/uuid-generator;1"] |
|
9 .getService(Ci.nsIUUIDGenerator); |
|
10 |
|
11 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] |
|
12 .getService(Ci.mozIJSSubScriptLoader); |
|
13 |
|
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); |
|
27 |
|
28 loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js"); |
|
29 loader.loadSubScript("chrome://specialpowers/content/specialpowers.js"); |
|
30 |
|
31 let marionetteLogObj = new MarionetteLogObj(); |
|
32 |
|
33 let isB2G = false; |
|
34 |
|
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; |
|
44 |
|
45 // The sandbox we execute test scripts in. Gets lazily created in |
|
46 // createExecuteContentSandbox(). |
|
47 let sandbox; |
|
48 |
|
49 // the unload handler |
|
50 let onunload; |
|
51 |
|
52 // Flag to indicate whether an async script is currently running or not. |
|
53 let asyncTestRunning = false; |
|
54 let asyncTestCommandId; |
|
55 let asyncTestTimeoutId; |
|
56 |
|
57 let inactivityTimeoutId = null; |
|
58 let heartbeatCallback = function () {}; // Called by the simpletest methods. |
|
59 |
|
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; |
|
78 |
|
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 }; |
|
92 |
|
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); |
|
102 |
|
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 } |
|
114 |
|
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 } |
|
127 |
|
128 /** |
|
129 * Add a message listener that's tied to our listenerId. |
|
130 */ |
|
131 function addMessageListenerId(messageName, handler) { |
|
132 addMessageListener(messageName + listenerId, handler); |
|
133 } |
|
134 |
|
135 /** |
|
136 * Remove a message listener that's tied to our listenerId. |
|
137 */ |
|
138 function removeMessageListenerId(messageName, handler) { |
|
139 removeMessageListener(messageName + listenerId, handler); |
|
140 } |
|
141 |
|
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 } |
|
189 |
|
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 } |
|
203 |
|
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 } |
|
221 |
|
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 } |
|
231 |
|
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 } |
|
242 |
|
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 } |
|
297 |
|
298 /* |
|
299 * Helper methods |
|
300 */ |
|
301 |
|
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 } |
|
311 |
|
312 /** |
|
313 * Send response back to server |
|
314 */ |
|
315 function sendResponse(value, command_id) { |
|
316 sendToServer("Marionette:done", value, command_id); |
|
317 } |
|
318 |
|
319 /** |
|
320 * Send ack back to server |
|
321 */ |
|
322 function sendOk(command_id) { |
|
323 sendToServer("Marionette:ok", {}, command_id); |
|
324 } |
|
325 |
|
326 /** |
|
327 * Send log message to server |
|
328 */ |
|
329 function sendLog(msg) { |
|
330 sendToServer("Marionette:log", { message: msg }); |
|
331 } |
|
332 |
|
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 } |
|
340 |
|
341 /** |
|
342 * Clear test values after completion of test |
|
343 */ |
|
344 function resetValues() { |
|
345 sandbox = null; |
|
346 curFrame = content; |
|
347 mouseEventsOnly = false; |
|
348 } |
|
349 |
|
350 /** |
|
351 * Dump a logline to stdout. Prepends logline with a timestamp. |
|
352 */ |
|
353 function dumpLog(logline) { |
|
354 dump(Date.now() + " Marionette: " + logline); |
|
355 } |
|
356 |
|
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 } |
|
372 |
|
373 /* |
|
374 * Marionette Methods |
|
375 */ |
|
376 |
|
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; |
|
388 |
|
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 }); |
|
402 |
|
403 XPCOMUtils.defineLazyGetter(sandbox, 'SpecialPowers', function() { |
|
404 return new SpecialPowers(aWindow); |
|
405 }); |
|
406 |
|
407 sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) { |
|
408 if (commandId == asyncTestCommandId) { |
|
409 curFrame.removeEventListener("unload", onunload, false); |
|
410 curFrame.clearTimeout(asyncTestTimeoutId); |
|
411 |
|
412 if (inactivityTimeoutId != null) { |
|
413 curFrame.clearTimeout(inactivityTimeoutId); |
|
414 } |
|
415 |
|
416 |
|
417 sendSyncMessage("Marionette:shareData", |
|
418 {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); |
|
419 marionetteLogObj.clearLogs(); |
|
420 |
|
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 } |
|
435 |
|
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 }; |
|
452 |
|
453 return sandbox; |
|
454 } |
|
455 |
|
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 }; |
|
468 |
|
469 setTimer(); |
|
470 heartbeatCallback = function resetInactivityTimeout() { |
|
471 curFrame.clearTimeout(inactivityTimeoutId); |
|
472 setTimer(); |
|
473 }; |
|
474 } |
|
475 |
|
476 asyncTestCommandId = msg.json.command_id; |
|
477 let script = msg.json.script; |
|
478 |
|
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 } |
|
490 |
|
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(); |
|
504 |
|
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 } |
|
521 |
|
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 } |
|
548 |
|
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 } |
|
556 |
|
557 /** |
|
558 * Execute async script |
|
559 */ |
|
560 function executeAsyncScript(msg) { |
|
561 executeWithCallback(msg); |
|
562 } |
|
563 |
|
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 } |
|
575 |
|
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 }; |
|
592 |
|
593 setTimer(); |
|
594 heartbeatCallback = function resetInactivityTimeout() { |
|
595 curFrame.clearTimeout(inactivityTimeoutId); |
|
596 setTimer(); |
|
597 }; |
|
598 } |
|
599 |
|
600 let script = msg.json.script; |
|
601 asyncTestCommandId = msg.json.command_id; |
|
602 |
|
603 onunload = function() { |
|
604 sendError("unload was called", 17, null, asyncTestCommandId); |
|
605 }; |
|
606 curFrame.addEventListener("unload", onunload, false); |
|
607 |
|
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; |
|
620 |
|
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); |
|
629 |
|
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 }; |
|
635 |
|
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 } |
|
652 |
|
653 scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" + |
|
654 "let __marionetteFunc = function() { " + script + "};" + |
|
655 "__marionetteFunc.apply(null, __marionetteParams); "; |
|
656 } |
|
657 |
|
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 } |
|
678 |
|
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 } |
|
714 |
|
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 } |
|
738 |
|
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 } |
|
747 |
|
748 |
|
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 } |
|
768 |
|
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 } |
|
783 |
|
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 } |
|
810 |
|
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 } |
|
910 |
|
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 } |
|
934 |
|
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 } |
|
951 |
|
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 } |
|
1037 |
|
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 } |
|
1060 |
|
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 } |
|
1092 |
|
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 } |
|
1191 |
|
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 } |
|
1227 |
|
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; |
|
1236 |
|
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 }; |
|
1274 |
|
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 } |
|
1286 |
|
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 } |
|
1293 |
|
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 } |
|
1300 |
|
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 } |
|
1309 |
|
1310 /** |
|
1311 * Go back in history |
|
1312 */ |
|
1313 function goBack(msg) { |
|
1314 curFrame.history.back(); |
|
1315 sendOk(msg.json.command_id); |
|
1316 } |
|
1317 |
|
1318 /** |
|
1319 * Go forward in history |
|
1320 */ |
|
1321 function goForward(msg) { |
|
1322 curFrame.history.forward(); |
|
1323 sendOk(msg.json.command_id); |
|
1324 } |
|
1325 |
|
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 } |
|
1338 |
|
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 } |
|
1354 |
|
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 } |
|
1370 |
|
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 } |
|
1380 |
|
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 } |
|
1406 |
|
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 } |
|
1421 |
|
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 } |
|
1435 |
|
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 } |
|
1449 |
|
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 } |
|
1463 |
|
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 } |
|
1484 |
|
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 } |
|
1504 |
|
1505 } |
|
1506 catch (e) { |
|
1507 sendError(e.message, e.code, e.stack, command_id); |
|
1508 } |
|
1509 } |
|
1510 |
|
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 } |
|
1526 |
|
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 } |
|
1540 |
|
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 } |
|
1554 |
|
1555 /** |
|
1556 * Send keys to element |
|
1557 */ |
|
1558 function sendKeysToElement(msg) { |
|
1559 let command_id = msg.json.command_id; |
|
1560 |
|
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 } |
|
1755 |
|
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(); |
|
1764 |
|
1765 let location = {}; |
|
1766 location.x = rect.left; |
|
1767 location.y = rect.top; |
|
1768 |
|
1769 sendResponse({value: location}, command_id); |
|
1770 } |
|
1771 catch (e) { |
|
1772 sendError(e.message, e.code, e.stack, command_id); |
|
1773 } |
|
1774 } |
|
1775 |
|
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 } |
|
1790 |
|
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 }); |
|
1829 |
|
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 } |
|
1889 |
|
1890 sandbox = null; |
|
1891 |
|
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 }); |
|
1896 |
|
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; |
|
1918 |
|
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 } |
|
1925 |
|
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 } |
|
1937 |
|
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 } |
|
1944 |
|
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 } |
|
1955 |
|
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 } |
|
1979 |
|
1980 sendResponse({value: toReturn}, msg.json.command_id); |
|
1981 } |
|
1982 |
|
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); |
|
1990 |
|
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 } |
|
1998 |
|
1999 sendOk(msg.json.command_id); |
|
2000 } |
|
2001 |
|
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 } |
|
2015 |
|
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 } |
|
2026 |
|
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']); |
|
2032 |
|
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 } |
|
2044 |
|
2045 return results; |
|
2046 } |
|
2047 |
|
2048 function getAppCacheStatus(msg) { |
|
2049 sendResponse({ value: curFrame.applicationCache.status }, |
|
2050 msg.json.command_id); |
|
2051 } |
|
2052 |
|
2053 // emulator callbacks |
|
2054 let _emu_cb_id = 0; |
|
2055 let _emu_cbs = {}; |
|
2056 |
|
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 } |
|
2064 |
|
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 } |
|
2072 |
|
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 } |
|
2091 |
|
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 } |
|
2111 |
|
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; |
|
2136 |
|
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 } |
|
2157 |
|
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)"); |
|
2165 |
|
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(); |
|
2172 |
|
2173 for (var i = 0; i < highlights.length; ++i) { |
|
2174 var elem = elementManager.getKnownElement(highlights[i], curFrame); |
|
2175 rect = elem.getBoundingClientRect(); |
|
2176 |
|
2177 var offsetY = -top; |
|
2178 var offsetX = -left; |
|
2179 |
|
2180 // Draw the rectangle |
|
2181 ctx.strokeRect(rect.left + offsetX, |
|
2182 rect.top + offsetY, |
|
2183 rect.width, |
|
2184 rect.height); |
|
2185 } |
|
2186 } |
|
2187 |
|
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 } |
|
2194 |
|
2195 // Call register self when we get loaded |
|
2196 registerSelf(); |