Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* Any copyright is dedicated to the Public Domain.
4 http://creativecommons.org/publicdomain/zero/1.0/ */
6 /*=============================================================================
7 Globals
8 =============================================================================*/
9 XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
10 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
12 /*=============================================================================
13 Useful constants
14 =============================================================================*/
15 const serverRoot = "http://example.com/browser/metro/";
16 const baseURI = "http://mochi.test:8888/browser/metro/";
17 const chromeRoot = getRootDirectory(gTestPath);
18 const kDefaultWait = 2000;
19 const kDefaultInterval = 50;
21 /*=============================================================================
22 Load Helpers
23 =============================================================================*/
25 let splitPath = chromeRoot.split('/');
26 if (!splitPath[splitPath.length-1]) {
27 splitPath.pop();
28 }
29 // ../mochitest to make sure we're looking for the libs on the right path
30 // even for mochiperf tests.
31 splitPath.pop();
32 splitPath.push('mochitest');
34 const mochitestPath = splitPath.join('/') + '/';
36 [
37 "helpers/BookmarksHelper.js",
38 "helpers/HistoryHelper.js",
39 "helpers/ViewStateHelper.js"
40 ].forEach(function(lib) {
41 Services.scriptloader.loadSubScript(mochitestPath + lib, this);
42 }, this);
44 /*=============================================================================
45 Metro ui helpers
46 =============================================================================*/
48 function isLandscapeMode()
49 {
50 return Elements.windowState.getAttribute("viewstate") == "landscape";
51 }
53 function setDevPixelEqualToPx()
54 {
55 todo(false, "test depends on devPixelsPerPx set to 1.0 - see bugs 886624 and 859742");
56 SpecialPowers.setCharPref("layout.css.devPixelsPerPx", "1.0");
57 registerCleanupFunction(function () {
58 SpecialPowers.clearUserPref("layout.css.devPixelsPerPx");
59 });
60 }
62 function checkContextUIMenuItemCount(aCount)
63 {
64 let visibleCount = 0;
65 for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
66 if (!ContextMenuUI.commands.childNodes[idx].hidden)
67 visibleCount++;
68 }
69 is(visibleCount, aCount, "command list count");
70 }
72 function checkContextUIMenuItemVisibility(aVisibleList)
73 {
74 let errors = 0;
75 for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
76 let item = ContextMenuUI.commands.childNodes[idx];
77 if (aVisibleList.indexOf(item.id) != -1 && item.hidden) {
78 // item should be visible
79 errors++;
80 info("should be visible:" + item.id);
81 } else if (aVisibleList.indexOf(item.id) == -1 && !item.hidden) {
82 // item should be hidden
83 errors++;
84 info("should be hidden:" + item.id);
85 }
86 }
87 is(errors, 0, "context menu item list visibility");
88 }
90 function checkMonoclePositionRange(aMonocle, aMinX, aMaxX, aMinY, aMaxY)
91 {
92 let monocle = null;
93 if (aMonocle == "start")
94 monocle = SelectionHelperUI._startMark;
95 else if (aMonocle == "end")
96 monocle = SelectionHelperUI._endMark;
97 else if (aMonocle == "caret")
98 monocle = SelectionHelperUI._caretMark;
99 else
100 ok(false, "bad monocle id");
102 ok(monocle.xPos > aMinX && monocle.xPos < aMaxX,
103 "X position is " + monocle.xPos + ", expected between " + aMinX + " and " + aMaxX);
104 ok(monocle.yPos > aMinY && monocle.yPos < aMaxY,
105 "Y position is " + monocle.yPos + ", expected between " + aMinY + " and " + aMaxY);
106 }
108 /*
109 * showNotification - displays a test notification with the current
110 * browser and waits for the noticiation to be fully displayed.
111 *
112 * Usage: yield showNotification();
113 */
114 function showNotification()
115 {
116 return Task.spawn(function() {
117 let strings = Strings.browser;
118 var buttons = [
119 {
120 isDefault: false,
121 label: strings.GetStringFromName("popupButtonAllowOnce2"),
122 accessKey: "",
123 callback: function() { }
124 },
125 {
126 label: strings.GetStringFromName("popupButtonAlwaysAllow3"),
127 accessKey: "",
128 callback: function() { }
129 },
130 {
131 label: strings.GetStringFromName("popupButtonNeverWarn3"),
132 accessKey: "",
133 callback: function() { }
134 }
135 ];
136 let notificationBox = Browser.getNotificationBox();
137 const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
138 let note = notificationBox.appendNotification("test notification", "popup-blocked",
139 "chrome://browser/skin/images/infobar-popup.png",
140 priority, buttons);
141 yield waitForEvent(notificationBox, "transitionend");
142 throw new Task.Result(note);
143 });
144 }
146 function removeNotifications() {
147 Browser.getNotificationBox().removeAllNotifications(true);
148 }
150 function getSelection(aElement) {
151 if (!aElement)
152 return null;
154 // chrome text edit
155 if (aElement instanceof Ci.nsIDOMXULTextBoxElement) {
156 return aElement.QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
157 .editor.selection;
158 }
160 // editable content element
161 if (aElement instanceof Ci.nsIDOMNSEditableElement) {
162 return aElement.QueryInterface(Ci.nsIDOMNSEditableElement)
163 .editor.selection;
164 }
166 // document or window
167 if (aElement instanceof HTMLDocument || aElement instanceof Window) {
168 return aElement.getSelection();
169 }
171 // browser
172 return aElement.contentWindow.getSelection();
173 }
175 function getTrimmedSelection(aElement) {
176 let sel = getSelection(aElement);
177 if (!sel)
178 return "";
179 return sel.toString().trim();
180 }
182 /*
183 * clearSelection(aTarget) - clears the current selection in
184 * aTarget, shuts down the selection manager and purges all
185 * message manager events to insure a reset state for the ui.
186 */
187 function clearSelection(aTarget) {
188 SelectionHelperUI.closeEditSession(true);
189 getSelection(aTarget).removeAllRanges();
190 purgeEventQueue();
191 }
193 // Hides the tab and context app bar if they are visible
194 function hideContextUI()
195 {
196 purgeEventQueue();
198 return Task.spawn(function() {
199 if (ContextUI.tabbarVisible) {
200 let promise = waitForEvent(Elements.tray, "transitionend", null, Elements.tray);
201 if (ContextUI.dismiss()) {
202 yield promise;
203 }
204 }
206 if (ContextUI.contextAppbarVisible) {
207 let promise = waitForEvent(Elements.contextappbar, "transitionend", null, Elements.contextappbar);
208 ContextUI.dismissContextAppbar();
209 yield promise;
210 }
211 });
212 }
214 function showNavBar()
215 {
216 if (!ContextUI.navbarVisible) {
217 let promise = waitForEvent(Elements.navbar, "transitionend");
218 ContextUI.displayNavbar();
219 return promise;
220 }
221 return Promise.resolve(null);
222 }
224 function hideNavBar()
225 {
226 if (ContextUI.navbarVisible) {
227 let promise = waitForEvent(Elements.navbar, "transitionend");
228 ContextUI.dismissNavbar();
229 return promise;
230 }
231 return Promise.resolve(null);
232 }
234 function fireAppBarDisplayEvent()
235 {
236 let promise = waitForEvent(Elements.tray, "transitionend");
237 let event = document.createEvent("Events");
238 event.initEvent("MozEdgeUICompleted", true, false);
239 gWindow.dispatchEvent(event);
240 purgeEventQueue();
241 return promise;
242 }
244 /*=============================================================================
245 General test helpers
246 =============================================================================*/
247 let gOpenedTabs = [];
249 function loadUriInActiveTab(aUri)
250 {
251 return Task.spawn(function() {
252 let promise = waitForEvent(getBrowser(), "pageshow");
253 BrowserUI.goToURI(aUri);
254 yield waitForCondition(function () {
255 return getBrowser().currentURI.spec == aUri
256 }, "getBrowser().currentURI.spec == " + aUri);
257 yield promise;
258 });
259 }
261 function navForward() {
262 return Task.spawn(function() {
263 let promise = waitForEvent(getBrowser(), "pageshow");
264 EventUtils.synthesizeKey("VK_RIGHT", { altKey: true }, window);
265 yield promise;
266 });
267 }
269 function navBackViaNavButton() {
270 return Task.spawn(function() {
271 let promise = waitForEvent(getBrowser(), "pageshow");
272 let backButton = document.getElementById("overlay-back");
273 sendElementTap(window, backButton);
274 yield promise;
275 });
276 }
278 /**
279 * Loads a URL in a new tab asynchronously.
280 *
281 * Usage:
282 * Task.spawn(function() {
283 * let tab = yield addTab("http://example.com/");
284 * ok(Browser.selectedTab == tab, "the new tab is selected");
285 * });
286 *
287 * @param aUrl the URL to load
288 * @returns a task that resolves to the new tab object after the URL is loaded.
289 */
290 function addTab(aUrl) {
291 return Task.spawn(function() {
292 info("Opening "+aUrl+" in a new tab");
293 let tab = Browser.addTab(aUrl, true);
294 yield tab.pageShowPromise;
296 is(tab.browser.currentURI.spec, aUrl, aUrl + " is loaded");
298 yield hideContextUI();
300 gOpenedTabs.push(tab);
302 throw new Task.Result(tab);
303 });
304 }
306 /**
307 * Cleans up tabs left open by addTab().
308 * This is being called at runTests() after the test loop.
309 */
310 function cleanUpOpenedTabs() {
311 let tab;
312 while(tab = gOpenedTabs.shift()) {
313 cleanupNotificationsForBrowser(tab.browser);
314 Browser.closeTab(Browser.getTabFromChrome(tab.chromeTab), { forceClose: true })
315 }
316 }
318 function cleanupNotificationsForBrowser(aBrowser) {
319 let notificationBox = Browser.getNotificationBox(aBrowser);
320 notificationBox && notificationBox.removeAllNotifications(true);
321 }
323 /**
324 * Waits a specified number of miliseconds for a specified event to be
325 * fired on a specified element.
326 *
327 * Usage:
328 * let receivedEvent = waitForEvent(element, "eventName");
329 * // Do some processing here that will cause the event to be fired
330 * // ...
331 * // Now yield until the Promise is fulfilled
332 * yield receivedEvent;
333 * if (receivedEvent && !(receivedEvent instanceof Error)) {
334 * receivedEvent.msg == "eventName";
335 * // ...
336 * }
337 *
338 * @param aSubject the element that should receive the event
339 * @param aEventName the event to wait for
340 * @param aTimeoutMs the number of miliseconds to wait before giving up
341 * @returns a Promise that resolves to the received event, or to an Error
342 */
343 function waitForEvent(aSubject, aEventName, aTimeoutMs, aTarget) {
344 let eventDeferred = Promise.defer();
345 let timeoutMs = aTimeoutMs || kDefaultWait;
346 let stack = new Error().stack;
347 let timerID = setTimeout(function wfe_canceller() {
348 aSubject.removeEventListener(aEventName, listener);
349 eventDeferred.reject( new Error(aEventName+" event timeout at " + stack) );
350 }, timeoutMs);
352 var listener = function (aEvent) {
353 if (aTarget && aTarget !== aEvent.target)
354 return;
356 // stop the timeout clock and resume
357 clearTimeout(timerID);
358 eventDeferred.resolve(aEvent);
359 }
361 function cleanup(aEventOrError) {
362 // unhook listener in case of success or failure
363 aSubject.removeEventListener(aEventName, listener);
364 return aEventOrError;
365 }
366 aSubject.addEventListener(aEventName, listener, false);
367 return eventDeferred.promise.then(cleanup, cleanup);
368 }
370 /**
371 * Wait for an nsIMessageManager IPC message.
372 */
373 function waitForMessage(aName, aMessageManager) {
374 let deferred = Promise.defer();
375 let manager = aMessageManager || messageManager;
376 function listener(aMessage) {
377 deferred.resolve(aMessage);
378 }
379 manager.addMessageListener(aName, listener);
380 function cleanup(aEventOrError) {
381 manager.removeMessageListener(aName, listener);
382 }
383 return deferred.promise.then(cleanup, cleanup);
384 }
386 /**
387 * Waits a specified number of miliseconds.
388 *
389 * Usage:
390 * let wait = yield waitForMs(2000);
391 * ok(wait, "2 seconds should now have elapsed");
392 *
393 * @param aMs the number of miliseconds to wait for
394 * @returns a Promise that resolves to true after the time has elapsed
395 */
396 function waitForMs(aMs) {
397 info("Wating for " + aMs + "ms");
398 let deferred = Promise.defer();
399 let startTime = Date.now();
400 setTimeout(done, aMs);
402 function done() {
403 deferred.resolve(true);
404 info("waitForMs finished waiting, waited for "
405 + (Date.now() - startTime)
406 + "ms");
407 }
409 return deferred.promise;
410 }
412 /**
413 * Waits a specified number of miliseconds for a supplied callback to
414 * return a truthy value.
415 *
416 * Usage:
417 * let success = yield waitForCondition(myTestFunction);
418 * if (success && !(success instanceof Error)) {
419 * // ...
420 * }
421 *
422 * @param aCondition the callback that must return a truthy value
423 * @param aTimeoutMs the number of miliseconds to wait before giving up
424 * @param aIntervalMs the number of miliseconds between calls to aCondition
425 * @returns a Promise that resolves to true, or to an Error
426 */
427 function waitForCondition(aCondition, aTimeoutMs, aIntervalMs) {
428 let deferred = Promise.defer();
429 let timeoutMs = aTimeoutMs || kDefaultWait;
430 let intervalMs = aIntervalMs || kDefaultInterval;
431 let startTime = Date.now();
432 let stack = new Error().stack;
434 function testCondition() {
435 let now = Date.now();
436 if((now - startTime) > timeoutMs) {
437 deferred.reject( new Error("Timed out waiting for condition to be true at " + stack) );
438 return;
439 }
441 let condition;
442 try {
443 condition = aCondition();
444 } catch (e) {
445 deferred.reject( new Error("Got exception while attempting to test condition: " + e) );
446 return;
447 }
449 if (condition) {
450 deferred.resolve(true);
451 } else {
452 setTimeout(testCondition, intervalMs);
453 }
454 }
456 setTimeout(testCondition, 0);
457 return deferred.promise;
458 }
460 /**
461 * same as waitForCondition but with better test output.
462 *
463 * @param aCondition the callback that must return a truthy value
464 * @param aTestMsg test condition message printed when the test succeeds or
465 * fails. Defaults to the stringified version of aCondition.
466 * @param aTimeoutMs the number of miliseconds to wait before giving up
467 * @param aIntervalMs the number of miliseconds between calls to aCondition
468 * @returns a Promise that resolves to true, or to an Error
469 */
470 function waitForCondition2(aCondition, aTestMsg, aTimeoutMs, aIntervalMs) {
471 let deferred = Promise.defer();
472 let msg = aTestMsg || aCondition;
473 let timeoutMs = aTimeoutMs || kDefaultWait;
474 let intervalMs = aIntervalMs || kDefaultInterval;
475 let startTime = Date.now();
477 function testCondition() {
478 let now = Date.now();
479 if((now - startTime) > timeoutMs) {
480 deferred.reject( new Error("Timed out waiting for " + msg) );
481 return;
482 }
484 let condition;
485 try {
486 condition = aCondition();
487 } catch (e) {
488 deferred.reject( new Error("Got exception while attempting to test '" + msg + "': " + e) );
489 return;
490 }
492 if (condition) {
493 ok(true, msg);
494 deferred.resolve(true);
495 } else {
496 setTimeout(testCondition, intervalMs);
497 }
498 }
500 setTimeout(testCondition, 0);
501 return deferred.promise;
502 }
504 /*
505 * Waits for an image in a page to load. Wrapper around waitForCondition.
506 *
507 * @param aWindow the tab or window that contains the image.
508 * @param aImageId the id of the image in the page.
509 * @returns a Promise that resolves to true, or to an Error
510 */
511 function waitForImageLoad(aWindow, aImageId) {
512 let elem = aWindow.document.getElementById(aImageId);
513 return waitForCondition(function () {
514 let request = elem.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
515 if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
516 return true;
517 return false;
518 }, 5000, 100);
519 }
521 /**
522 * Waits a specified number of miliseconds for an observer event.
523 *
524 * @param aObsEvent the observer event to wait for
525 * @param aTimeoutMs the number of miliseconds to wait before giving up
526 * @returns a Promise that resolves to true, or to an Error
527 */
528 function waitForObserver(aObsEvent, aTimeoutMs, aObsData) {
529 try {
531 let deferred = Promise.defer();
532 let timeoutMs = aTimeoutMs || kDefaultWait;
533 let timerID = 0;
535 var observeWatcher = {
536 onEvent: function () {
537 clearTimeout(timerID);
538 Services.obs.removeObserver(this, aObsEvent);
539 deferred.resolve();
540 },
542 onError: function () {
543 clearTimeout(timerID);
544 Services.obs.removeObserver(this, aObsEvent);
545 deferred.reject(new Error(aObsEvent + " event timeout"));
546 },
548 observe: function (aSubject, aTopic, aData) {
549 if (aTopic == aObsEvent &&
550 (!aObsData || (aObsData == aData))) {
551 this.onEvent();
552 }
553 },
555 QueryInterface: function (aIID) {
556 if (!aIID.equals(Ci.nsIObserver) &&
557 !aIID.equals(Ci.nsISupportsWeakReference) &&
558 !aIID.equals(Ci.nsISupports)) {
559 throw Components.results.NS_ERROR_NO_INTERFACE;
560 }
561 return this;
562 },
563 }
565 timerID = setTimeout(function wfo_canceller() {
566 observeWatcher.onError();
567 }, timeoutMs);
569 Services.obs.addObserver(observeWatcher, aObsEvent, true);
570 return deferred.promise;
572 } catch (ex) {
573 info(ex.message);
574 }
575 }
578 /*=============================================================================
579 * Input mode helpers - these helpers notify observers to metro_precise_input
580 * and metro_imprecise_input respectively, triggering the same behaviour as user touch or mouse input
581 *
582 * Usage: let promise = waitForObservers("metro_imprecise_input");
583 * notifyImprecise();
584 * yield promise; // you are now in imprecise mode
585 *===========================================================================*/
586 function notifyPrecise()
587 {
588 Services.obs.notifyObservers(null, "metro_precise_input", null);
589 }
591 function notifyImprecise()
592 {
593 Services.obs.notifyObservers(null, "metro_imprecise_input", null);
594 }
596 /*=============================================================================
597 * Native input helpers - these helpers send input directly to the os
598 * generating os level input events that get processed by widget and
599 * apzc logic.
600 *===========================================================================*/
601 function synthesizeNativeMouse(aElement, aOffsetX, aOffsetY, aMsg) {
602 let x = aOffsetX;
603 let y = aOffsetY;
604 if (aElement) {
605 if (aElement.getBoundingClientRect) {
606 let rect = aElement.getBoundingClientRect();
607 x += rect.left;
608 y += rect.top;
609 } else if(aElement.left && aElement.top) {
610 x += aElement.left;
611 y += aElement.top;
612 }
613 }
614 Browser.windowUtils.sendNativeMouseEvent(x, y, aMsg, 0, null);
615 }
617 function synthesizeNativeMouseMove(aElement, aOffsetX, aOffsetY) {
618 synthesizeNativeMouse(aElement,
619 aOffsetX,
620 aOffsetY,
621 0x0001); // MOUSEEVENTF_MOVE
622 }
624 function synthesizeNativeMouseLDown(aElement, aOffsetX, aOffsetY) {
625 synthesizeNativeMouse(aElement,
626 aOffsetX,
627 aOffsetY,
628 0x0002); // MOUSEEVENTF_LEFTDOWN
629 }
631 function synthesizeNativeMouseLUp(aElement, aOffsetX, aOffsetY) {
632 synthesizeNativeMouse(aElement,
633 aOffsetX,
634 aOffsetY,
635 0x0004); // MOUSEEVENTF_LEFTUP
636 }
638 function synthesizeNativeMouseRDown(aElement, aOffsetX, aOffsetY) {
639 synthesizeNativeMouse(aElement,
640 aOffsetX,
641 aOffsetY,
642 0x0008); // MOUSEEVENTF_RIGHTDOWN
643 }
645 function synthesizeNativeMouseRUp(aElement, aOffsetX, aOffsetY) {
646 synthesizeNativeMouse(aElement,
647 aOffsetX,
648 aOffsetY,
649 0x0010); // MOUSEEVENTF_RIGHTUP
650 }
652 function synthesizeNativeMouseMDown(aElement, aOffsetX, aOffsetY) {
653 synthesizeNativeMouse(aElement,
654 aOffsetX,
655 aOffsetY,
656 0x0020); // MOUSEEVENTF_MIDDLEDOWN
657 }
659 function synthesizeNativeMouseMUp(aElement, aOffsetX, aOffsetY) {
660 synthesizeNativeMouse(aElement,
661 aOffsetX,
662 aOffsetY,
663 0x0040); // MOUSEEVENTF_MIDDLEUP
664 }
666 // WARNING: these calls can trigger the soft keyboard on tablets, but not
667 // on test slaves (bug 947428).
668 // WARNING: When testing the apzc, be careful of bug 933990. Events sent
669 // shortly after loading a page may get ignored.
671 function sendNativeLongTap(aElement, aX, aY) {
672 let coords = logicalCoordsForElement(aElement, aX, aY);
673 Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, true);
674 }
676 function sendNativeTap(aElement, aX, aY) {
677 let coords = logicalCoordsForElement(aElement, aX, aY);
678 Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
679 }
681 function sendNativeDoubleTap(aElement, aX, aY) {
682 let coords = logicalCoordsForElement(aElement, aX, aY);
683 Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
684 Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
685 }
687 function clearNativeTouchSequence() {
688 Browser.windowUtils.clearNativeTouchSequence();
689 }
691 /*=============================================================================
692 * Synthesized event helpers - these helpers synthesize input events that get
693 * dispatched directly to the dom. As such widget and apzc logic is bypassed.
694 *===========================================================================*/
696 /*
697 * logicalCoordsForElement - given coordinates relative to top-left of
698 * given element, returns logical coordinates for window. If a non-numeric
699 * X or Y value is given, a value for the center of the element in that
700 * dimension is used.
701 *
702 * @param aElement element coordinates are relative to.
703 * @param aX, aY relative coordinates.
704 */
705 function logicalCoordsForElement (aElement, aX, aY) {
706 let coords = { x: null, y: null };
707 let rect = aElement.getBoundingClientRect();
709 coords.x = isNaN(aX) ? rect.left + (rect.width / 2) : rect.left + aX;
710 coords.y = isNaN(aY) ? rect.top + (rect.height / 2) : rect.top + aY;
712 return coords;
713 }
715 function sendContextMenuMouseClickToElement(aWindow, aElement, aX, aY) {
716 let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
717 .getInterface(Ci.nsIDOMWindowUtils);
718 let coords = logicalCoordsForElement(aElement, aX, aY);
720 utils.sendMouseEventToWindow("mousedown", coords.x, coords.y, 2, 1, 0);
721 utils.sendMouseEventToWindow("mouseup", coords.x, coords.y, 2, 1, 0);
722 utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0);
723 }
725 function sendMouseClick(aWindow, aX, aY) {
726 EventUtils.synthesizeMouseAtPoint(aX, aY, {}, aWindow);
727 }
729 /*
730 * sendContextMenuClick - simulates a press-hold touch input event. Event
731 * is delivered to the main window of the application through the top-level
732 * widget.
733 *
734 * @param aX, aY logical coordinates of the event.
735 */
736 function sendContextMenuClick(aX, aY) {
737 let mediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
738 .getService(Components.interfaces.nsIWindowMediator);
739 let mainwin = mediator.getMostRecentWindow("navigator:browser");
740 let utils = mainwin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
741 .getInterface(Components.interfaces.nsIDOMWindowUtils);
742 utils.sendMouseEvent("contextmenu", aX, aY, 2, 1, 0, true,
743 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
744 }
746 /*
747 * sendContextMenuClickToSelection - simulates a press-hold touch input event
748 * selected text in a window.
749 */
750 function sendContextMenuClickToSelection(aWindow) {
751 let selection = aWindow.getSelection();
752 if (!selection || !selection.rangeCount) {
753 ok(false, "no selection to tap!");
754 return;
755 }
756 let range = selection.getRangeAt(0);
757 let rect = range.getBoundingClientRect();
758 let x = rect.left + (rect.width / 2);
759 let y = rect.top + (rect.height / 2);
760 let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
761 .getInterface(Components.interfaces.nsIDOMWindowUtils);
762 utils.sendMouseEventToWindow("contextmenu", x, y, 2, 1, 0, true,
763 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
764 }
766 /*
767 * sendContextMenuClickToWindow - simulates a press-hold touch input event.
768 *
769 * @param aWindow window used to retrieve dom window utils, and the
770 * target window for the event.
771 * @param aX, aY logical coordinates of the event relative to aWindow.
772 */
773 function sendContextMenuClickToWindow(aWindow, aX, aY) {
774 let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
775 .getInterface(Components.interfaces.nsIDOMWindowUtils);
777 utils.sendMouseEventToWindow("contextmenu", aX, aY, 2, 1, 0, true,
778 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
779 }
781 function sendContextMenuClickToElement(aWindow, aElement, aX, aY) {
782 let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
783 .getInterface(Components.interfaces.nsIDOMWindowUtils);
784 let coords = logicalCoordsForElement(aElement, aX, aY);
785 utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0, true,
786 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
787 }
789 /*
790 * sendDoubleTap - simulates a double click or double tap.
791 */
792 function sendDoubleTap(aWindow, aX, aY) {
793 EventUtils.synthesizeMouseAtPoint(aX, aY, {
794 clickCount: 1,
795 inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
796 }, aWindow);
798 EventUtils.synthesizeMouseAtPoint(aX, aY, {
799 clickCount: 2,
800 inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
801 }, aWindow);
802 }
804 function sendTap(aWindow, aX, aY) {
805 EventUtils.synthesizeMouseAtPoint(aX, aY, {
806 clickCount: 1,
807 inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
808 }, aWindow);
809 }
811 function sendElementTap(aWindow, aElement, aX, aY) {
812 let coords = logicalCoordsForElement(aElement, aX, aY);
813 EventUtils.synthesizeMouseAtPoint(coords.x, coords.y, {
814 clickCount: 1,
815 inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
816 }, aWindow);
817 }
819 /*
820 * sendTouchDrag - sends a touch series composed of a touchstart,
821 * touchmove, and touchend w3c event.
822 */
823 function sendTouchDrag(aWindow, aStartX, aStartY, aEndX, aEndY) {
824 EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow);
825 EventUtils.synthesizeTouchAtPoint(aEndX, aEndY, { type: "touchmove" }, aWindow);
826 EventUtils.synthesizeTouchAtPoint(aEndX, aEndY, { type: "touchend" }, aWindow);
827 }
829 /*
830 * TouchDragAndHold - simulates a drag and hold sequence of events.
831 */
832 function TouchDragAndHold() {
833 }
835 TouchDragAndHold.prototype = {
836 _timeoutStep: 2,
837 _numSteps: 50,
838 _debug: false,
839 _win: null,
840 _native: false,
841 _pointerId: 1,
842 _dui: Components.interfaces.nsIDOMWindowUtils,
844 set useNativeEvents(aValue) {
845 this._native = aValue;
846 },
848 set stepTimeout(aValue) {
849 this._timeoutStep = aValue;
850 },
852 set numSteps(aValue) {
853 this._numSteps = aValue;
854 },
856 set nativePointerId(aValue) {
857 this._pointerId = aValue;
858 },
860 callback: function callback() {
861 if (this._win == null)
862 return;
864 if (this._debug) {
865 SelectionHelperUI.debugDisplayDebugPoint(this._currentPoint.xPos,
866 this._currentPoint.yPos, 5, "#FF0000", true);
867 }
869 if (++this._step.steps >= this._numSteps) {
870 if (this._native) {
871 this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
872 this._endPoint.xPos, this._endPoint.yPos,
873 1, 90);
874 } else {
875 EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
876 { type: "touchmove" }, this._win);
877 }
878 this._defer.resolve();
879 return;
880 }
881 this._currentPoint.xPos += this._step.x;
882 this._currentPoint.yPos += this._step.y;
883 if (this._debug) {
884 info("[" + this._step.steps + "] touchmove " + this._currentPoint.xPos + " x " + this._currentPoint.yPos);
885 }
887 if (this._native) {
888 this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
889 this._currentPoint.xPos, this._currentPoint.yPos,
890 1, 90);
891 } else {
892 EventUtils.synthesizeTouchAtPoint(this._currentPoint.xPos, this._currentPoint.yPos,
893 { type: "touchmove" }, this._win);
894 }
896 let self = this;
897 setTimeout(function () { self.callback(); }, this._timeoutStep);
898 },
900 start: function start(aWindow, aStartX, aStartY, aEndX, aEndY) {
901 this._defer = Promise.defer();
902 this._win = aWindow;
903 this._utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
904 .getInterface(Ci.nsIDOMWindowUtils);
905 this._endPoint = { xPos: aEndX, yPos: aEndY };
906 this._currentPoint = { xPos: aStartX, yPos: aStartY };
907 this._step = { steps: 0, x: (aEndX - aStartX) / this._numSteps, y: (aEndY - aStartY) / this._numSteps };
908 if (this._debug) {
909 info("[0] touchstart " + aStartX + " x " + aStartY);
910 }
911 // flush layout, bug 914847
912 this._utils.elementFromPoint(aStartX, aStartY, false, true);
913 if (this._native) {
914 this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
915 aStartX, aStartY, 1, 90);
916 } else {
917 EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow);
918 }
919 let self = this;
920 setTimeout(function () { self.callback(); }, this._timeoutStep);
921 return this._defer.promise;
922 },
924 move: function move(aEndX, aEndY) {
925 if (this._win == null)
926 return;
927 if (this._debug) {
928 info("[0] continuation to " + aEndX + " x " + aEndY);
929 }
930 this._defer = Promise.defer();
931 this._step = { steps: 0,
932 x: (aEndX - this._endPoint.xPos) / this._numSteps,
933 y: (aEndY - this._endPoint.yPos) / this._numSteps };
934 this._endPoint = { xPos: aEndX, yPos: aEndY };
935 let self = this;
936 setTimeout(function () { self.callback(); }, this._timeoutStep);
937 return this._defer.promise;
938 },
940 end: function end() {
941 if (this._debug) {
942 info("[" + this._step.steps + "] touchend " + this._endPoint.xPos + " x " + this._endPoint.yPos);
943 SelectionHelperUI.debugClearDebugPoints();
944 }
945 if (this._native) {
946 this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_REMOVE,
947 this._endPoint.xPos, this._endPoint.yPos,
948 1, 90);
949 } else {
950 EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
951 { type: "touchend" }, this._win);
952 }
953 this._win = null;
954 },
955 };
957 /*=============================================================================
958 System utilities
959 =============================================================================*/
961 /*
962 * emptyClipboard - clear the windows clipboard.
963 */
964 function emptyClipboard() {
965 Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard)
966 .emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
967 }
969 /*
970 * purgeEventQueue - purges the event queue on the calling thread.
971 * Pumps latent in-process message manager events awaiting delivery.
972 */
973 function purgeEventQueue() {
974 let thread = Services.tm.currentThread;
975 while (thread.hasPendingEvents()) {
976 if (!thread.processNextEvent(true))
977 break;
978 }
979 }
981 /*=============================================================================
982 Test-running helpers
983 =============================================================================*/
984 let gCurrentTest = null;
985 let gTests = [];
987 function runTests() {
988 waitForExplicitFinish();
990 Task.spawn(function() {
991 while((gCurrentTest = gTests.shift())){
992 try {
993 if ('function' == typeof gCurrentTest.setUp) {
994 info("SETUP " + gCurrentTest.desc);
995 yield Task.spawn(gCurrentTest.setUp.bind(gCurrentTest));
996 }
997 try {
998 info("RUN " + gCurrentTest.desc);
999 yield Task.spawn(gCurrentTest.run.bind(gCurrentTest));
1000 } finally {
1001 if ('function' == typeof gCurrentTest.tearDown) {
1002 info("TEARDOWN " + gCurrentTest.desc);
1003 yield Task.spawn(gCurrentTest.tearDown.bind(gCurrentTest));
1004 }
1005 }
1006 } catch (ex) {
1007 ok(false, "runTests: Task failed - " + ex + ' at ' + ex.stack);
1008 } finally {
1009 info("END " + gCurrentTest.desc);
1010 }
1011 }
1013 try {
1014 cleanUpOpenedTabs();
1016 let badTabs = [];
1017 Browser.tabs.forEach(function(item, index, array) {
1018 let location = item.browser.currentURI.spec;
1019 if (index == 0 && location == "about:blank" || location == "about:start") {
1020 return;
1021 }
1022 ok(false, "Left over tab after test: '" + location + "'");
1023 badTabs.push(item);
1024 });
1026 badTabs.forEach(function(item, index, array) {
1027 Browser.closeTab(item, { forceClose: true });
1028 });
1029 } catch (ex) {
1030 ok(false, "Cleanup tabs failed - " + ex);
1031 }
1033 finish();
1034 });
1035 }
1037 // wrap a method with a spy that records how and how many times it gets called
1038 // the spy is returned; use spy.restore() to put the original back
1039 function spyOnMethod(aObj, aMethod) {
1040 let origFunc = aObj[aMethod];
1041 let spy = function() {
1042 let callArguments = Array.slice(arguments);
1043 spy.callCount++;
1044 spy.calledWith = callArguments;
1045 spy.argsForCall.push(callArguments);
1046 return (spy.returnValue = origFunc.apply(aObj, arguments));
1047 };
1048 spy.callCount = 0;
1049 spy.argsForCall = [];
1050 spy.restore = function() {
1051 return (aObj[aMethod] = origFunc);
1052 };
1053 return (aObj[aMethod] = spy);
1054 }
1056 // replace a method with a stub that records how and how many times it gets called
1057 // the stub is returned; use stub.restore() to put the original back
1058 function stubMethod(aObj, aMethod) {
1059 let origFunc = aObj[aMethod];
1060 let func = function() {
1061 func.calledWith = Array.slice(arguments);
1062 func.callCount++;
1063 };
1064 func.callCount = 0;
1065 func.restore = function() {
1066 return (aObj[aMethod] = origFunc);
1067 };
1068 aObj[aMethod] = func;
1069 return func;
1070 }