Wed, 31 Dec 2014 07:53:36 +0100
Correct small whitespace inconsistency, lost while renaming variables.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
5 var EXPORTED_SYMBOLS = ["MozMillController", "globalEventRegistry",
6 "sleep", "windowMap"];
8 const Cc = Components.classes;
9 const Ci = Components.interfaces;
10 const Cu = Components.utils;
12 var EventUtils = {}; Cu.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
14 var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
15 var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
16 var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
17 var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
18 var mozelement = {}; Cu.import('resource://mozmill/driver/mozelement.js', mozelement);
19 var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
20 var windows = {}; Cu.import('resource://mozmill/modules/windows.js', windows);
22 // Declare most used utils functions in the controller namespace
23 var assert = new assertions.Assert();
24 var waitFor = assert.waitFor;
26 var sleep = utils.sleep;
28 // For Mozmill 1.5 backward compatibility
29 var windowMap = windows.map;
31 waitForEvents = function () {
32 }
34 waitForEvents.prototype = {
35 /**
36 * Initialize list of events for given node
37 */
38 init: function waitForEvents_init(node, events) {
39 if (node.getNode != undefined)
40 node = node.getNode();
42 this.events = events;
43 this.node = node;
44 node.firedEvents = {};
45 this.registry = {};
47 for each (var e in events) {
48 var listener = function (event) {
49 this.firedEvents[event.type] = true;
50 }
52 this.registry[e] = listener;
53 this.registry[e].result = false;
54 this.node.addEventListener(e, this.registry[e], true);
55 }
56 },
58 /**
59 * Wait until all assigned events have been fired
60 */
61 wait: function waitForEvents_wait(timeout, interval) {
62 for (var e in this.registry) {
63 assert.waitFor(function () {
64 return this.node.firedEvents[e] == true;
65 }, "waitForEvents.wait(): Event '" + ex + "' has been fired.", timeout, interval);
67 this.node.removeEventListener(e, this.registry[e], true);
68 }
69 }
70 }
72 /**
73 * Class to handle menus and context menus
74 *
75 * @constructor
76 * @param {MozMillController} controller
77 * Mozmill controller of the window under test
78 * @param {string} menuSelector
79 * jQuery like selector string of the element
80 * @param {object} document
81 * Document to use for finding the menu
82 * [optional - default: aController.window.document]
83 */
84 var Menu = function (controller, menuSelector, document) {
85 this._controller = controller;
86 this._menu = null;
88 document = document || controller.window.document;
89 var node = document.querySelector(menuSelector);
90 if (node) {
91 // We don't unwrap nodes automatically yet (Bug 573185)
92 node = node.wrappedJSObject || node;
93 this._menu = new mozelement.Elem(node);
94 } else {
95 throw new Error("Menu element '" + menuSelector + "' not found.");
96 }
97 }
99 Menu.prototype = {
101 /**
102 * Open and populate the menu
103 *
104 * @param {ElemBase} contextElement
105 * Element whose context menu has to be opened
106 * @returns {Menu} The Menu instance
107 */
108 open: function Menu_open(contextElement) {
109 // We have to open the context menu
110 var menu = this._menu.getNode();
111 if ((menu.localName == "popup" || menu.localName == "menupopup") &&
112 contextElement && contextElement.exists()) {
113 this._controller.rightClick(contextElement);
114 assert.waitFor(function () {
115 return menu.state == "open";
116 }, "Context menu has been opened.");
117 }
119 // Run through the entire menu and populate with dynamic entries
120 this._buildMenu(menu);
122 return this;
123 },
125 /**
126 * Close the menu
127 *
128 * @returns {Menu} The Menu instance
129 */
130 close: function Menu_close() {
131 var menu = this._menu.getNode();
133 this._controller.keypress(this._menu, "VK_ESCAPE", {});
134 assert.waitFor(function () {
135 return menu.state == "closed";
136 }, "Context menu has been closed.");
138 return this;
139 },
141 /**
142 * Retrieve the specified menu entry
143 *
144 * @param {string} itemSelector
145 * jQuery like selector string of the menu item
146 * @returns {ElemBase} Menu element
147 * @throws Error If menu element has not been found
148 */
149 getItem: function Menu_getItem(itemSelector) {
150 // Run through the entire menu and populate with dynamic entries
151 this._buildMenu(this._menu.getNode());
153 var node = this._menu.getNode().querySelector(itemSelector);
155 if (!node) {
156 throw new Error("Menu entry '" + itemSelector + "' not found.");
157 }
159 return new mozelement.Elem(node);
160 },
162 /**
163 * Click the specified menu entry
164 *
165 * @param {string} itemSelector
166 * jQuery like selector string of the menu item
167 *
168 * @returns {Menu} The Menu instance
169 */
170 click: function Menu_click(itemSelector) {
171 this._controller.click(this.getItem(itemSelector));
173 return this;
174 },
176 /**
177 * Synthesize a keypress against the menu
178 *
179 * @param {string} key
180 * Key to press
181 * @param {object} modifier
182 * Key modifiers
183 * @see MozMillController#keypress
184 *
185 * @returns {Menu} The Menu instance
186 */
187 keypress: function Menu_keypress(key, modifier) {
188 this._controller.keypress(this._menu, key, modifier);
190 return this;
191 },
193 /**
194 * Opens the context menu, click the specified entry and
195 * make sure that the menu has been closed.
196 *
197 * @param {string} itemSelector
198 * jQuery like selector string of the element
199 * @param {ElemBase} contextElement
200 * Element whose context menu has to be opened
201 *
202 * @returns {Menu} The Menu instance
203 */
204 select: function Menu_select(itemSelector, contextElement) {
205 this.open(contextElement);
206 this.click(itemSelector);
207 this.close();
208 },
210 /**
211 * Recursive function which iterates through all menu elements and
212 * populates the menus with dynamic menu entries.
213 *
214 * @param {node} menu
215 * Top menu node whose elements have to be populated
216 */
217 _buildMenu: function Menu__buildMenu(menu) {
218 var items = menu ? menu.childNodes : null;
220 Array.forEach(items, function (item) {
221 // When we have a menu node, fake a click onto it to populate
222 // the sub menu with dynamic entries
223 if (item.tagName == "menu") {
224 var popup = item.querySelector("menupopup");
226 if (popup) {
227 var popupEvent = this._controller.window.document.createEvent("MouseEvent");
228 popupEvent.initMouseEvent("popupshowing", true, true,
229 this._controller.window, 0, 0, 0, 0, 0,
230 false, false, false, false, 0, null);
231 popup.dispatchEvent(popupEvent);
233 this._buildMenu(popup);
234 }
235 }
236 }, this);
237 }
238 };
240 var MozMillController = function (window) {
241 this.window = window;
243 this.mozmillModule = {};
244 Cu.import('resource://mozmill/driver/mozmill.js', this.mozmillModule);
246 var self = this;
247 assert.waitFor(function () {
248 return window != null && self.isLoaded();
249 }, "controller(): Window has been initialized.");
251 // Ensure to focus the window which will move it virtually into the foreground
252 // when focusmanager.testmode is set enabled.
253 this.window.focus();
255 var windowType = window.document.documentElement.getAttribute('windowtype');
256 if (controllerAdditions[windowType] != undefined ) {
257 this.prototype = new utils.Copy(this.prototype);
258 controllerAdditions[windowType](this);
259 this.windowtype = windowType;
260 }
261 }
263 /**
264 * Returns the global browser object of the window
265 *
266 * @returns {Object} The browser object
267 */
268 MozMillController.prototype.__defineGetter__("browserObject", function () {
269 return utils.getBrowserObject(this.window);
270 });
272 // constructs a MozMillElement from the controller's window
273 MozMillController.prototype.__defineGetter__("rootElement", function () {
274 if (this._rootElement == undefined) {
275 let docElement = this.window.document.documentElement;
276 this._rootElement = new mozelement.MozMillElement("Elem", docElement);
277 }
279 return this._rootElement;
280 });
282 MozMillController.prototype.sleep = utils.sleep;
283 MozMillController.prototype.waitFor = assert.waitFor;
285 // Open the specified url in the current tab
286 MozMillController.prototype.open = function (url) {
287 switch (this.mozmillModule.Application) {
288 case "Firefox":
289 case "MetroFirefox":
290 // Stop a running page load to not overlap requests
291 if (this.browserObject.selectedBrowser) {
292 this.browserObject.selectedBrowser.stop();
293 }
295 this.browserObject.loadURI(url);
296 break;
298 default:
299 throw new Error("MozMillController.open not supported.");
300 }
302 broker.pass({'function':'Controller.open()'});
303 }
305 /**
306 * Take a screenshot of specified node
307 *
308 * @param {Element} node
309 * The window or DOM element to capture
310 * @param {String} name
311 * The name of the screenshot used in reporting and as filename
312 * @param {Boolean} save
313 * If true saves the screenshot as 'name.jpg' in tempdir,
314 * otherwise returns a dataURL
315 * @param {Element[]} highlights
316 * A list of DOM elements to highlight by drawing a red rectangle around them
317 *
318 * @returns {Object} Object which contains properties like filename, dataURL,
319 * name and timestamp of the screenshot
320 */
321 MozMillController.prototype.screenshot = function (node, name, save, highlights) {
322 if (!node) {
323 throw new Error("node is undefined");
324 }
326 // Unwrap the node and highlights
327 if ("getNode" in node) {
328 node = node.getNode();
329 }
331 if (highlights) {
332 for (var i = 0; i < highlights.length; ++i) {
333 if ("getNode" in highlights[i]) {
334 highlights[i] = highlights[i].getNode();
335 }
336 }
337 }
339 // If save is false, a dataURL is used
340 // Include both in the report anyway to avoid confusion and make the report easier to parse
341 var screenshot = {"filename": undefined,
342 "dataURL": utils.takeScreenshot(node, highlights),
343 "name": name,
344 "timestamp": new Date().toLocaleString()};
346 if (!save) {
347 return screenshot;
348 }
350 // Save the screenshot to disk
352 let {filename, failure} = utils.saveDataURL(screenshot.dataURL, name);
353 screenshot.filename = filename;
354 screenshot.failure = failure;
356 if (failure) {
357 broker.log({'function': 'controller.screenshot()',
358 'message': 'Error writing to file: ' + screenshot.filename});
359 } else {
360 // Send the screenshot object to python over jsbridge
361 broker.sendMessage("screenshot", screenshot);
362 broker.pass({'function': 'controller.screenshot()'});
363 }
365 return screenshot;
366 }
368 /**
369 * Checks if the specified window has been loaded
370 *
371 * @param {DOMWindow} [aWindow=this.window] Window object to check for loaded state
372 */
373 MozMillController.prototype.isLoaded = function (aWindow) {
374 var win = aWindow || this.window;
376 return windows.map.getValue(utils.getWindowId(win), "loaded") || false;
377 };
379 MozMillController.prototype.__defineGetter__("waitForEvents", function () {
380 if (this._waitForEvents == undefined) {
381 this._waitForEvents = new waitForEvents();
382 }
384 return this._waitForEvents;
385 });
387 /**
388 * Wrapper function to create a new instance of a menu
389 * @see Menu
390 */
391 MozMillController.prototype.getMenu = function (menuSelector, document) {
392 return new Menu(this, menuSelector, document);
393 };
395 MozMillController.prototype.__defineGetter__("mainMenu", function () {
396 return this.getMenu("menubar");
397 });
399 MozMillController.prototype.__defineGetter__("menus", function () {
400 logDeprecated('controller.menus', 'Use controller.mainMenu instead');
401 });
403 MozMillController.prototype.waitForImage = function (aElement, timeout, interval) {
404 this.waitFor(function () {
405 return aElement.getNode().complete == true;
406 }, "timeout exceeded for waitForImage " + aElement.getInfo(), timeout, interval);
408 broker.pass({'function':'Controller.waitForImage()'});
409 }
411 MozMillController.prototype.startUserShutdown = function (timeout, restart, next, resetProfile) {
412 if (restart && resetProfile) {
413 throw new Error("You can't have a user-restart and reset the profile; there is a race condition");
414 }
416 let shutdownObj = {
417 'user': true,
418 'restart': Boolean(restart),
419 'next': next,
420 'resetProfile': Boolean(resetProfile),
421 'timeout': timeout
422 };
424 broker.sendMessage('shutdown', shutdownObj);
425 }
427 /**
428 * Restart the application
429 *
430 * @param {string} aNext
431 * Name of the next test function to run after restart
432 * @param {boolean} [aFlags=undefined]
433 * Additional flags how to handle the shutdown or restart. The attributes
434 * eRestarti386 (0x20) and eRestartx86_64 (0x30) have not been documented yet.
435 * @see https://developer.mozilla.org/nsIAppStartup#Attributes
436 */
437 MozMillController.prototype.restartApplication = function (aNext, aFlags) {
438 var flags = Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart;
440 if (aFlags) {
441 flags |= aFlags;
442 }
444 broker.sendMessage('shutdown', {'user': false,
445 'restart': true,
446 'flags': flags,
447 'next': aNext,
448 'timeout': 0 });
450 // We have to ensure to stop the test from continuing until the application is
451 // shutting down. The only way to do that is by throwing an exception.
452 throw new errors.ApplicationQuitError();
453 }
455 /**
456 * Stop the application
457 *
458 * @param {boolean} [aResetProfile=false]
459 * Whether to reset the profile during restart
460 * @param {boolean} [aFlags=undefined]
461 * Additional flags how to handle the shutdown or restart. The attributes
462 * eRestarti386 and eRestartx86_64 have not been documented yet.
463 * @see https://developer.mozilla.org/nsIAppStartup#Attributes
464 */
465 MozMillController.prototype.stopApplication = function (aResetProfile, aFlags) {
466 var flags = Ci.nsIAppStartup.eAttemptQuit;
468 if (aFlags) {
469 flags |= aFlags;
470 }
472 broker.sendMessage('shutdown', {'user': false,
473 'restart': false,
474 'flags': flags,
475 'resetProfile': aResetProfile,
476 'timeout': 0 });
478 // We have to ensure to stop the test from continuing until the application is
479 // shutting down. The only way to do that is by throwing an exception.
480 throw new errors.ApplicationQuitError();
481 }
483 //Browser navigation functions
484 MozMillController.prototype.goBack = function () {
485 this.window.content.history.back();
486 broker.pass({'function':'Controller.goBack()'});
488 return true;
489 }
491 MozMillController.prototype.goForward = function () {
492 this.window.content.history.forward();
493 broker.pass({'function':'Controller.goForward()'});
495 return true;
496 }
498 MozMillController.prototype.refresh = function () {
499 this.window.content.location.reload(true);
500 broker.pass({'function':'Controller.refresh()'});
502 return true;
503 }
505 function logDeprecated(funcName, message) {
506 broker.log({'function': funcName + '() - DEPRECATED',
507 'message': funcName + '() is deprecated. ' + message});
508 }
510 function logDeprecatedAssert(funcName) {
511 logDeprecated('controller.' + funcName,
512 '. Use the generic `assertion` module instead.');
513 }
515 MozMillController.prototype.assertText = function (el, text) {
516 logDeprecatedAssert("assertText");
518 var n = el.getNode();
520 if (n && n.innerHTML == text) {
521 broker.pass({'function': 'Controller.assertText()'});
522 } else {
523 throw new Error("could not validate element " + el.getInfo() +
524 " with text "+ text);
525 }
527 return true;
528 };
530 /**
531 * Assert that a specified node exists
532 */
533 MozMillController.prototype.assertNode = function (el) {
534 logDeprecatedAssert("assertNode");
536 //this.window.focus();
537 var element = el.getNode();
538 if (!element) {
539 throw new Error("could not find element " + el.getInfo());
540 }
542 broker.pass({'function': 'Controller.assertNode()'});
543 return true;
544 };
546 /**
547 * Assert that a specified node doesn't exist
548 */
549 MozMillController.prototype.assertNodeNotExist = function (el) {
550 logDeprecatedAssert("assertNodeNotExist");
552 try {
553 var element = el.getNode();
554 } catch (e) {
555 broker.pass({'function': 'Controller.assertNodeNotExist()'});
556 }
558 if (element) {
559 throw new Error("Unexpectedly found element " + el.getInfo());
560 } else {
561 broker.pass({'function':'Controller.assertNodeNotExist()'});
562 }
564 return true;
565 };
567 /**
568 * Assert that a form element contains the expected value
569 */
570 MozMillController.prototype.assertValue = function (el, value) {
571 logDeprecatedAssert("assertValue");
573 var n = el.getNode();
575 if (n && n.value == value) {
576 broker.pass({'function': 'Controller.assertValue()'});
577 } else {
578 throw new Error("could not validate element " + el.getInfo() +
579 " with value " + value);
580 }
582 return false;
583 };
585 /**
586 * Check if the callback function evaluates to true
587 */
588 MozMillController.prototype.assert = function (callback, message, thisObject) {
589 logDeprecatedAssert("assert");
591 utils.assert(callback, message, thisObject);
592 broker.pass({'function': ": controller.assert('" + callback + "')"});
594 return true;
595 }
597 /**
598 * Assert that a provided value is selected in a select element
599 */
600 MozMillController.prototype.assertSelected = function (el, value) {
601 logDeprecatedAssert("assertSelected");
603 var n = el.getNode();
604 var validator = value;
606 if (n && n.options[n.selectedIndex].value == validator) {
607 broker.pass({'function':'Controller.assertSelected()'});
608 } else {
609 throw new Error("could not assert value for element " + el.getInfo() +
610 " with value " + value);
611 }
613 return true;
614 };
616 /**
617 * Assert that a provided checkbox is checked
618 */
619 MozMillController.prototype.assertChecked = function (el) {
620 logDeprecatedAssert("assertChecked");
622 var element = el.getNode();
624 if (element && element.checked == true) {
625 broker.pass({'function':'Controller.assertChecked()'});
626 } else {
627 throw new Error("assert failed for checked element " + el.getInfo());
628 }
630 return true;
631 };
633 /**
634 * Assert that a provided checkbox is not checked
635 */
636 MozMillController.prototype.assertNotChecked = function (el) {
637 logDeprecatedAssert("assertNotChecked");
639 var element = el.getNode();
641 if (!element) {
642 throw new Error("Could not find element" + el.getInfo());
643 }
645 if (!element.hasAttribute("checked") || element.checked != true) {
646 broker.pass({'function': 'Controller.assertNotChecked()'});
647 } else {
648 throw new Error("assert failed for not checked element " + el.getInfo());
649 }
651 return true;
652 };
654 /**
655 * Assert that an element's javascript property exists or has a particular value
656 *
657 * if val is undefined, will return true if the property exists.
658 * if val is specified, will return true if the property exists and has the correct value
659 */
660 MozMillController.prototype.assertJSProperty = function (el, attrib, val) {
661 logDeprecatedAssert("assertJSProperty");
663 var element = el.getNode();
665 if (!element){
666 throw new Error("could not find element " + el.getInfo());
667 }
669 var value = element[attrib];
670 var res = (value !== undefined && (val === undefined ? true :
671 String(value) == String(val)));
672 if (res) {
673 broker.pass({'function':'Controller.assertJSProperty("' + el.getInfo() + '") : ' + val});
674 } else {
675 throw new Error("Controller.assertJSProperty(" + el.getInfo() + ") : " +
676 (val === undefined ? "property '" + attrib +
677 "' doesn't exist" : val + " == " + value));
678 }
680 return true;
681 };
683 /**
684 * Assert that an element's javascript property doesn't exist or doesn't have a particular value
685 *
686 * if val is undefined, will return true if the property doesn't exist.
687 * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
688 */
689 MozMillController.prototype.assertNotJSProperty = function (el, attrib, val) {
690 logDeprecatedAssert("assertNotJSProperty");
692 var element = el.getNode();
694 if (!element){
695 throw new Error("could not find element " + el.getInfo());
696 }
698 var value = element[attrib];
699 var res = (val === undefined ? value === undefined : String(value) != String(val));
700 if (res) {
701 broker.pass({'function':'Controller.assertNotProperty("' + el.getInfo() + '") : ' + val});
702 } else {
703 throw new Error("Controller.assertNotJSProperty(" + el.getInfo() + ") : " +
704 (val === undefined ? "property '" + attrib +
705 "' exists" : val + " != " + value));
706 }
708 return true;
709 };
711 /**
712 * Assert that an element's dom property exists or has a particular value
713 *
714 * if val is undefined, will return true if the property exists.
715 * if val is specified, will return true if the property exists and has the correct value
716 */
717 MozMillController.prototype.assertDOMProperty = function (el, attrib, val) {
718 logDeprecatedAssert("assertDOMProperty");
720 var element = el.getNode();
722 if (!element){
723 throw new Error("could not find element " + el.getInfo());
724 }
726 var value, res = element.hasAttribute(attrib);
727 if (res && val !== undefined) {
728 value = element.getAttribute(attrib);
729 res = (String(value) == String(val));
730 }
732 if (res) {
733 broker.pass({'function':'Controller.assertDOMProperty("' + el.getInfo() + '") : ' + val});
734 } else {
735 throw new Error("Controller.assertDOMProperty(" + el.getInfo() + ") : " +
736 (val === undefined ? "property '" + attrib +
737 "' doesn't exist" : val + " == " + value));
738 }
740 return true;
741 };
743 /**
744 * Assert that an element's dom property doesn't exist or doesn't have a particular value
745 *
746 * if val is undefined, will return true if the property doesn't exist.
747 * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
748 */
749 MozMillController.prototype.assertNotDOMProperty = function (el, attrib, val) {
750 logDeprecatedAssert("assertNotDOMProperty");
752 var element = el.getNode();
754 if (!element) {
755 throw new Error("could not find element " + el.getInfo());
756 }
758 var value, res = element.hasAttribute(attrib);
759 if (res && val !== undefined) {
760 value = element.getAttribute(attrib);
761 res = (String(value) == String(val));
762 }
764 if (!res) {
765 broker.pass({'function':'Controller.assertNotDOMProperty("' + el.getInfo() + '") : ' + val});
766 } else {
767 throw new Error("Controller.assertNotDOMProperty(" + el.getInfo() + ") : " +
768 (val == undefined ? "property '" + attrib +
769 "' exists" : val + " == " + value));
770 }
772 return true;
773 };
775 /**
776 * Assert that a specified image has actually loaded. The Safari workaround results
777 * in additional requests for broken images (in Safari only) but works reliably
778 */
779 MozMillController.prototype.assertImageLoaded = function (el) {
780 logDeprecatedAssert("assertImageLoaded");
782 var img = el.getNode();
784 if (!img || img.tagName != 'IMG') {
785 throw new Error('Controller.assertImageLoaded() failed.')
786 return false;
787 }
789 var comp = img.complete;
790 var ret = null; // Return value
792 // Workaround for Safari -- it only supports the
793 // complete attrib on script-created images
794 if (typeof comp == 'undefined') {
795 test = new Image();
796 // If the original image was successfully loaded,
797 // src for new one should be pulled from cache
798 test.src = img.src;
799 comp = test.complete;
800 }
802 // Check the complete attrib. Note the strict
803 // equality check -- we don't want undefined, null, etc.
804 // --------------------------
805 if (comp === false) {
806 // False -- Img failed to load in IE/Safari, or is
807 // still trying to load in FF
808 ret = false;
809 } else if (comp === true && img.naturalWidth == 0) {
810 // True, but image has no size -- image failed to
811 // load in FF
812 ret = false;
813 } else {
814 // Otherwise all we can do is assume everything's
815 // hunky-dory
816 ret = true;
817 }
819 if (ret) {
820 broker.pass({'function':'Controller.assertImageLoaded'});
821 } else {
822 throw new Error('Controller.assertImageLoaded() failed.')
823 }
825 return true;
826 };
828 /**
829 * Drag one element to the top x,y coords of another specified element
830 */
831 MozMillController.prototype.mouseMove = function (doc, start, dest) {
832 // if one of these elements couldn't be looked up
833 if (typeof start != 'object'){
834 throw new Error("received bad coordinates");
835 }
837 if (typeof dest != 'object'){
838 throw new Error("received bad coordinates");
839 }
841 var triggerMouseEvent = function (element, clientX, clientY) {
842 clientX = clientX ? clientX: 0;
843 clientY = clientY ? clientY: 0;
845 // make the mouse understand where it is on the screen
846 var screenX = element.boxObject.screenX ? element.boxObject.screenX : 0;
847 var screenY = element.boxObject.screenY ? element.boxObject.screenY : 0;
849 var evt = element.ownerDocument.createEvent('MouseEvents');
850 if (evt.initMouseEvent) {
851 evt.initMouseEvent('mousemove', true, true, element.ownerDocument.defaultView,
852 1, screenX, screenY, clientX, clientY);
853 } else {
854 evt.initEvent('mousemove', true, true);
855 }
857 element.dispatchEvent(evt);
858 };
860 // Do the initial move to the drag element position
861 triggerMouseEvent(doc.body, start[0], start[1]);
862 triggerMouseEvent(doc.body, dest[0], dest[1]);
864 broker.pass({'function':'Controller.mouseMove()'});
865 return true;
866 }
868 /**
869 * Drag an element to the specified offset on another element, firing mouse and
870 * drag events. Adapted from ChromeUtils.js synthesizeDrop()
871 *
872 * @deprecated Use the MozMillElement object
873 *
874 * @param {MozElement} aSrc
875 * Source element to be dragged
876 * @param {MozElement} aDest
877 * Destination element over which the drop occurs
878 * @param {Number} [aOffsetX=element.width/2]
879 * Relative x offset for dropping on the aDest element
880 * @param {Number} [aOffsetY=element.height/2]
881 * Relative y offset for dropping on the aDest element
882 * @param {DOMWindow} [aSourceWindow=this.element.ownerDocument.defaultView]
883 * Custom source Window to be used.
884 * @param {String} [aDropEffect="move"]
885 * Effect used for the drop event
886 * @param {Object[]} [aDragData]
887 * An array holding custom drag data to be used during the drag event
888 * Format: [{ type: "text/plain", "Text to drag"}, ...]
889 *
890 * @returns {String} the captured dropEffect
891 */
892 MozMillController.prototype.dragToElement = function (aSrc, aDest, aOffsetX,
893 aOffsetY, aSourceWindow,
894 aDropEffect, aDragData) {
895 logDeprecated("controller.dragToElement", "Use the MozMillElement object.");
896 return aSrc.dragToElement(aDest, aOffsetX, aOffsetY, aSourceWindow, null,
897 aDropEffect, aDragData);
898 };
900 function Tabs(controller) {
901 this.controller = controller;
902 }
904 Tabs.prototype.getTab = function (index) {
905 return this.controller.browserObject.browsers[index].contentDocument;
906 }
908 Tabs.prototype.__defineGetter__("activeTab", function () {
909 return this.controller.browserObject.selectedBrowser.contentDocument;
910 });
912 Tabs.prototype.selectTab = function (index) {
913 // GO in to tab manager and grab the tab by index and call focus.
914 }
916 Tabs.prototype.findWindow = function (doc) {
917 for (var i = 0; i <= (this.controller.window.frames.length - 1); i++) {
918 if (this.controller.window.frames[i].document == doc) {
919 return this.controller.window.frames[i];
920 }
921 }
923 throw new Error("Cannot find window for document. Doc title == " + doc.title);
924 }
926 Tabs.prototype.getTabWindow = function (index) {
927 return this.findWindow(this.getTab(index));
928 }
930 Tabs.prototype.__defineGetter__("activeTabWindow", function () {
931 return this.findWindow(this.activeTab);
932 });
934 Tabs.prototype.__defineGetter__("length", function () {
935 return this.controller.browserObject.browsers.length;
936 });
938 Tabs.prototype.__defineGetter__("activeTabIndex", function () {
939 var browser = this.controller.browserObject;
941 switch(this.controller.mozmillModule.Application) {
942 case "MetroFirefox":
943 return browser.tabs.indexOf(browser.selectedTab);
944 case "Firefox":
945 default:
946 return browser.tabContainer.selectedIndex;
947 }
948 });
950 Tabs.prototype.selectTabIndex = function (aIndex) {
951 var browser = this.controller.browserObject;
953 switch(this.controller.mozmillModule.Application) {
954 case "MetroFirefox":
955 browser.selectedTab = browser.tabs[aIndex];
956 break;
957 case "Firefox":
958 default:
959 browser.selectTabAtIndex(aIndex);
960 }
961 }
963 function browserAdditions (controller) {
964 controller.tabs = new Tabs(controller);
966 controller.waitForPageLoad = function (aDocument, aTimeout, aInterval) {
967 var timeout = aTimeout || 30000;
968 var win = null;
969 var timed_out = false;
971 // If a user tries to do waitForPageLoad(2000), this will assign the
972 // interval the first arg which is most likely what they were expecting
973 if (typeof(aDocument) == "number"){
974 timeout = aDocument;
975 }
977 // If we have a real document use its default view
978 if (aDocument && (typeof(aDocument) === "object") &&
979 "defaultView" in aDocument)
980 win = aDocument.defaultView;
982 // If no document has been specified, fallback to the default view of the
983 // currently selected tab browser
984 win = win || this.browserObject.selectedBrowser.contentWindow;
986 // Wait until the content in the tab has been loaded
987 try {
988 this.waitFor(function () {
989 return windows.map.hasPageLoaded(utils.getWindowId(win));
990 }, "Timeout", timeout, aInterval);
991 }
992 catch (ex if ex instanceof errors.TimeoutError) {
993 timed_out = true;
994 }
995 finally {
996 state = 'URI=' + win.document.location.href +
997 ', readyState=' + win.document.readyState;
998 message = "controller.waitForPageLoad(" + state + ")";
1000 if (timed_out) {
1001 throw new errors.AssertionError(message);
1002 }
1004 broker.pass({'function': message});
1005 }
1006 }
1007 }
1009 var controllerAdditions = {
1010 'navigator:browser' :browserAdditions
1011 };
1013 /**
1014 * DEPRECATION WARNING
1015 *
1016 * The following methods have all been DEPRECATED as of Mozmill 2.0
1017 */
1018 MozMillController.prototype.assertProperty = function (el, attrib, val) {
1019 logDeprecatedAssert("assertProperty");
1021 return this.assertJSProperty(el, attrib, val);
1022 };
1024 MozMillController.prototype.assertPropertyNotExist = function (el, attrib) {
1025 logDeprecatedAssert("assertPropertyNotExist");
1026 return this.assertNotJSProperty(el, attrib);
1027 };
1029 /**
1030 * DEPRECATION WARNING
1031 *
1032 * The following methods have all been DEPRECATED as of Mozmill 2.0
1033 * Use the MozMillElement object instead (https://developer.mozilla.org/en/Mozmill/Mozmill_Element_Object)
1034 */
1035 MozMillController.prototype.select = function (aElement, index, option, value) {
1036 logDeprecated("controller.select", "Use the MozMillElement object.");
1038 return aElement.select(index, option, value);
1039 };
1041 MozMillController.prototype.keypress = function (aElement, aKey, aModifiers, aExpectedEvent) {
1042 logDeprecated("controller.keypress", "Use the MozMillElement object.");
1044 if (!aElement) {
1045 aElement = new mozelement.MozMillElement("Elem", this.window);
1046 }
1048 return aElement.keypress(aKey, aModifiers, aExpectedEvent);
1049 }
1051 MozMillController.prototype.type = function (aElement, aText, aExpectedEvent) {
1052 logDeprecated("controller.type", "Use the MozMillElement object.");
1054 if (!aElement) {
1055 aElement = new mozelement.MozMillElement("Elem", this.window);
1056 }
1058 var that = this;
1059 var retval = true;
1060 Array.forEach(aText, function (letter) {
1061 if (!that.keypress(aElement, letter, {}, aExpectedEvent)) {
1062 retval = false; }
1063 });
1065 return retval;
1066 }
1068 MozMillController.prototype.mouseEvent = function (aElement, aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
1069 logDeprecated("controller.mouseEvent", "Use the MozMillElement object.");
1071 return aElement.mouseEvent(aOffsetX, aOffsetY, aEvent, aExpectedEvent);
1072 }
1074 MozMillController.prototype.click = function (aElement, left, top, expectedEvent) {
1075 logDeprecated("controller.click", "Use the MozMillElement object.");
1077 return aElement.click(left, top, expectedEvent);
1078 }
1080 MozMillController.prototype.doubleClick = function (aElement, left, top, expectedEvent) {
1081 logDeprecated("controller.doubleClick", "Use the MozMillElement object.");
1083 return aElement.doubleClick(left, top, expectedEvent);
1084 }
1086 MozMillController.prototype.mouseDown = function (aElement, button, left, top, expectedEvent) {
1087 logDeprecated("controller.mouseDown", "Use the MozMillElement object.");
1089 return aElement.mouseDown(button, left, top, expectedEvent);
1090 };
1092 MozMillController.prototype.mouseOut = function (aElement, button, left, top, expectedEvent) {
1093 logDeprecated("controller.mouseOut", "Use the MozMillElement object.");
1095 return aElement.mouseOut(button, left, top, expectedEvent);
1096 };
1098 MozMillController.prototype.mouseOver = function (aElement, button, left, top, expectedEvent) {
1099 logDeprecated("controller.mouseOver", "Use the MozMillElement object.");
1101 return aElement.mouseOver(button, left, top, expectedEvent);
1102 };
1104 MozMillController.prototype.mouseUp = function (aElement, button, left, top, expectedEvent) {
1105 logDeprecated("controller.mouseUp", "Use the MozMillElement object.");
1107 return aElement.mouseUp(button, left, top, expectedEvent);
1108 };
1110 MozMillController.prototype.middleClick = function (aElement, left, top, expectedEvent) {
1111 logDeprecated("controller.middleClick", "Use the MozMillElement object.");
1113 return aElement.middleClick(aElement, left, top, expectedEvent);
1114 }
1116 MozMillController.prototype.rightClick = function (aElement, left, top, expectedEvent) {
1117 logDeprecated("controller.rightClick", "Use the MozMillElement object.");
1119 return aElement.rightClick(left, top, expectedEvent);
1120 }
1122 MozMillController.prototype.check = function (aElement, state) {
1123 logDeprecated("controller.check", "Use the MozMillElement object.");
1125 return aElement.check(state);
1126 }
1128 MozMillController.prototype.radio = function (aElement) {
1129 logDeprecated("controller.radio", "Use the MozMillElement object.");
1131 return aElement.select();
1132 }
1134 MozMillController.prototype.waitThenClick = function (aElement, timeout, interval) {
1135 logDeprecated("controller.waitThenClick", "Use the MozMillElement object.");
1137 return aElement.waitThenClick(timeout, interval);
1138 }
1140 MozMillController.prototype.waitForElement = function (aElement, timeout, interval) {
1141 logDeprecated("controller.waitForElement", "Use the MozMillElement object.");
1143 return aElement.waitForElement(timeout, interval);
1144 }
1146 MozMillController.prototype.waitForElementNotPresent = function (aElement, timeout, interval) {
1147 logDeprecated("controller.waitForElementNotPresent", "Use the MozMillElement object.");
1149 return aElement.waitForElementNotPresent(timeout, interval);
1150 }