1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/base/tests/mochitest/head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1070 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* Any copyright is dedicated to the Public Domain. 1.7 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.8 + 1.9 +/*============================================================================= 1.10 + Globals 1.11 +=============================================================================*/ 1.12 +XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm"); 1.13 +XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); 1.14 + 1.15 +/*============================================================================= 1.16 + Useful constants 1.17 +=============================================================================*/ 1.18 +const serverRoot = "http://example.com/browser/metro/"; 1.19 +const baseURI = "http://mochi.test:8888/browser/metro/"; 1.20 +const chromeRoot = getRootDirectory(gTestPath); 1.21 +const kDefaultWait = 2000; 1.22 +const kDefaultInterval = 50; 1.23 + 1.24 +/*============================================================================= 1.25 + Load Helpers 1.26 +=============================================================================*/ 1.27 + 1.28 +let splitPath = chromeRoot.split('/'); 1.29 +if (!splitPath[splitPath.length-1]) { 1.30 + splitPath.pop(); 1.31 +} 1.32 +// ../mochitest to make sure we're looking for the libs on the right path 1.33 +// even for mochiperf tests. 1.34 +splitPath.pop(); 1.35 +splitPath.push('mochitest'); 1.36 + 1.37 +const mochitestPath = splitPath.join('/') + '/'; 1.38 + 1.39 +[ 1.40 + "helpers/BookmarksHelper.js", 1.41 + "helpers/HistoryHelper.js", 1.42 + "helpers/ViewStateHelper.js" 1.43 +].forEach(function(lib) { 1.44 + Services.scriptloader.loadSubScript(mochitestPath + lib, this); 1.45 +}, this); 1.46 + 1.47 +/*============================================================================= 1.48 + Metro ui helpers 1.49 +=============================================================================*/ 1.50 + 1.51 +function isLandscapeMode() 1.52 +{ 1.53 + return Elements.windowState.getAttribute("viewstate") == "landscape"; 1.54 +} 1.55 + 1.56 +function setDevPixelEqualToPx() 1.57 +{ 1.58 + todo(false, "test depends on devPixelsPerPx set to 1.0 - see bugs 886624 and 859742"); 1.59 + SpecialPowers.setCharPref("layout.css.devPixelsPerPx", "1.0"); 1.60 + registerCleanupFunction(function () { 1.61 + SpecialPowers.clearUserPref("layout.css.devPixelsPerPx"); 1.62 + }); 1.63 +} 1.64 + 1.65 +function checkContextUIMenuItemCount(aCount) 1.66 +{ 1.67 + let visibleCount = 0; 1.68 + for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) { 1.69 + if (!ContextMenuUI.commands.childNodes[idx].hidden) 1.70 + visibleCount++; 1.71 + } 1.72 + is(visibleCount, aCount, "command list count"); 1.73 +} 1.74 + 1.75 +function checkContextUIMenuItemVisibility(aVisibleList) 1.76 +{ 1.77 + let errors = 0; 1.78 + for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) { 1.79 + let item = ContextMenuUI.commands.childNodes[idx]; 1.80 + if (aVisibleList.indexOf(item.id) != -1 && item.hidden) { 1.81 + // item should be visible 1.82 + errors++; 1.83 + info("should be visible:" + item.id); 1.84 + } else if (aVisibleList.indexOf(item.id) == -1 && !item.hidden) { 1.85 + // item should be hidden 1.86 + errors++; 1.87 + info("should be hidden:" + item.id); 1.88 + } 1.89 + } 1.90 + is(errors, 0, "context menu item list visibility"); 1.91 +} 1.92 + 1.93 +function checkMonoclePositionRange(aMonocle, aMinX, aMaxX, aMinY, aMaxY) 1.94 +{ 1.95 + let monocle = null; 1.96 + if (aMonocle == "start") 1.97 + monocle = SelectionHelperUI._startMark; 1.98 + else if (aMonocle == "end") 1.99 + monocle = SelectionHelperUI._endMark; 1.100 + else if (aMonocle == "caret") 1.101 + monocle = SelectionHelperUI._caretMark; 1.102 + else 1.103 + ok(false, "bad monocle id"); 1.104 + 1.105 + ok(monocle.xPos > aMinX && monocle.xPos < aMaxX, 1.106 + "X position is " + monocle.xPos + ", expected between " + aMinX + " and " + aMaxX); 1.107 + ok(monocle.yPos > aMinY && monocle.yPos < aMaxY, 1.108 + "Y position is " + monocle.yPos + ", expected between " + aMinY + " and " + aMaxY); 1.109 +} 1.110 + 1.111 +/* 1.112 + * showNotification - displays a test notification with the current 1.113 + * browser and waits for the noticiation to be fully displayed. 1.114 + * 1.115 + * Usage: yield showNotification(); 1.116 + */ 1.117 +function showNotification() 1.118 +{ 1.119 + return Task.spawn(function() { 1.120 + let strings = Strings.browser; 1.121 + var buttons = [ 1.122 + { 1.123 + isDefault: false, 1.124 + label: strings.GetStringFromName("popupButtonAllowOnce2"), 1.125 + accessKey: "", 1.126 + callback: function() { } 1.127 + }, 1.128 + { 1.129 + label: strings.GetStringFromName("popupButtonAlwaysAllow3"), 1.130 + accessKey: "", 1.131 + callback: function() { } 1.132 + }, 1.133 + { 1.134 + label: strings.GetStringFromName("popupButtonNeverWarn3"), 1.135 + accessKey: "", 1.136 + callback: function() { } 1.137 + } 1.138 + ]; 1.139 + let notificationBox = Browser.getNotificationBox(); 1.140 + const priority = notificationBox.PRIORITY_WARNING_MEDIUM; 1.141 + let note = notificationBox.appendNotification("test notification", "popup-blocked", 1.142 + "chrome://browser/skin/images/infobar-popup.png", 1.143 + priority, buttons); 1.144 + yield waitForEvent(notificationBox, "transitionend"); 1.145 + throw new Task.Result(note); 1.146 + }); 1.147 +} 1.148 + 1.149 +function removeNotifications() { 1.150 + Browser.getNotificationBox().removeAllNotifications(true); 1.151 +} 1.152 + 1.153 +function getSelection(aElement) { 1.154 + if (!aElement) 1.155 + return null; 1.156 + 1.157 + // chrome text edit 1.158 + if (aElement instanceof Ci.nsIDOMXULTextBoxElement) { 1.159 + return aElement.QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement) 1.160 + .editor.selection; 1.161 + } 1.162 + 1.163 + // editable content element 1.164 + if (aElement instanceof Ci.nsIDOMNSEditableElement) { 1.165 + return aElement.QueryInterface(Ci.nsIDOMNSEditableElement) 1.166 + .editor.selection; 1.167 + } 1.168 + 1.169 + // document or window 1.170 + if (aElement instanceof HTMLDocument || aElement instanceof Window) { 1.171 + return aElement.getSelection(); 1.172 + } 1.173 + 1.174 + // browser 1.175 + return aElement.contentWindow.getSelection(); 1.176 +} 1.177 + 1.178 +function getTrimmedSelection(aElement) { 1.179 + let sel = getSelection(aElement); 1.180 + if (!sel) 1.181 + return ""; 1.182 + return sel.toString().trim(); 1.183 +} 1.184 + 1.185 +/* 1.186 + * clearSelection(aTarget) - clears the current selection in 1.187 + * aTarget, shuts down the selection manager and purges all 1.188 + * message manager events to insure a reset state for the ui. 1.189 + */ 1.190 +function clearSelection(aTarget) { 1.191 + SelectionHelperUI.closeEditSession(true); 1.192 + getSelection(aTarget).removeAllRanges(); 1.193 + purgeEventQueue(); 1.194 +} 1.195 + 1.196 +// Hides the tab and context app bar if they are visible 1.197 +function hideContextUI() 1.198 +{ 1.199 + purgeEventQueue(); 1.200 + 1.201 + return Task.spawn(function() { 1.202 + if (ContextUI.tabbarVisible) { 1.203 + let promise = waitForEvent(Elements.tray, "transitionend", null, Elements.tray); 1.204 + if (ContextUI.dismiss()) { 1.205 + yield promise; 1.206 + } 1.207 + } 1.208 + 1.209 + if (ContextUI.contextAppbarVisible) { 1.210 + let promise = waitForEvent(Elements.contextappbar, "transitionend", null, Elements.contextappbar); 1.211 + ContextUI.dismissContextAppbar(); 1.212 + yield promise; 1.213 + } 1.214 + }); 1.215 +} 1.216 + 1.217 +function showNavBar() 1.218 +{ 1.219 + if (!ContextUI.navbarVisible) { 1.220 + let promise = waitForEvent(Elements.navbar, "transitionend"); 1.221 + ContextUI.displayNavbar(); 1.222 + return promise; 1.223 + } 1.224 + return Promise.resolve(null); 1.225 +} 1.226 + 1.227 +function hideNavBar() 1.228 +{ 1.229 + if (ContextUI.navbarVisible) { 1.230 + let promise = waitForEvent(Elements.navbar, "transitionend"); 1.231 + ContextUI.dismissNavbar(); 1.232 + return promise; 1.233 + } 1.234 + return Promise.resolve(null); 1.235 +} 1.236 + 1.237 +function fireAppBarDisplayEvent() 1.238 +{ 1.239 + let promise = waitForEvent(Elements.tray, "transitionend"); 1.240 + let event = document.createEvent("Events"); 1.241 + event.initEvent("MozEdgeUICompleted", true, false); 1.242 + gWindow.dispatchEvent(event); 1.243 + purgeEventQueue(); 1.244 + return promise; 1.245 +} 1.246 + 1.247 +/*============================================================================= 1.248 + General test helpers 1.249 +=============================================================================*/ 1.250 +let gOpenedTabs = []; 1.251 + 1.252 +function loadUriInActiveTab(aUri) 1.253 +{ 1.254 + return Task.spawn(function() { 1.255 + let promise = waitForEvent(getBrowser(), "pageshow"); 1.256 + BrowserUI.goToURI(aUri); 1.257 + yield waitForCondition(function () { 1.258 + return getBrowser().currentURI.spec == aUri 1.259 + }, "getBrowser().currentURI.spec == " + aUri); 1.260 + yield promise; 1.261 + }); 1.262 +} 1.263 + 1.264 +function navForward() { 1.265 + return Task.spawn(function() { 1.266 + let promise = waitForEvent(getBrowser(), "pageshow"); 1.267 + EventUtils.synthesizeKey("VK_RIGHT", { altKey: true }, window); 1.268 + yield promise; 1.269 + }); 1.270 +} 1.271 + 1.272 +function navBackViaNavButton() { 1.273 + return Task.spawn(function() { 1.274 + let promise = waitForEvent(getBrowser(), "pageshow"); 1.275 + let backButton = document.getElementById("overlay-back"); 1.276 + sendElementTap(window, backButton); 1.277 + yield promise; 1.278 + }); 1.279 +} 1.280 + 1.281 +/** 1.282 + * Loads a URL in a new tab asynchronously. 1.283 + * 1.284 + * Usage: 1.285 + * Task.spawn(function() { 1.286 + * let tab = yield addTab("http://example.com/"); 1.287 + * ok(Browser.selectedTab == tab, "the new tab is selected"); 1.288 + * }); 1.289 + * 1.290 + * @param aUrl the URL to load 1.291 + * @returns a task that resolves to the new tab object after the URL is loaded. 1.292 + */ 1.293 +function addTab(aUrl) { 1.294 + return Task.spawn(function() { 1.295 + info("Opening "+aUrl+" in a new tab"); 1.296 + let tab = Browser.addTab(aUrl, true); 1.297 + yield tab.pageShowPromise; 1.298 + 1.299 + is(tab.browser.currentURI.spec, aUrl, aUrl + " is loaded"); 1.300 + 1.301 + yield hideContextUI(); 1.302 + 1.303 + gOpenedTabs.push(tab); 1.304 + 1.305 + throw new Task.Result(tab); 1.306 + }); 1.307 +} 1.308 + 1.309 +/** 1.310 + * Cleans up tabs left open by addTab(). 1.311 + * This is being called at runTests() after the test loop. 1.312 + */ 1.313 +function cleanUpOpenedTabs() { 1.314 + let tab; 1.315 + while(tab = gOpenedTabs.shift()) { 1.316 + cleanupNotificationsForBrowser(tab.browser); 1.317 + Browser.closeTab(Browser.getTabFromChrome(tab.chromeTab), { forceClose: true }) 1.318 + } 1.319 +} 1.320 + 1.321 +function cleanupNotificationsForBrowser(aBrowser) { 1.322 + let notificationBox = Browser.getNotificationBox(aBrowser); 1.323 + notificationBox && notificationBox.removeAllNotifications(true); 1.324 +} 1.325 + 1.326 +/** 1.327 + * Waits a specified number of miliseconds for a specified event to be 1.328 + * fired on a specified element. 1.329 + * 1.330 + * Usage: 1.331 + * let receivedEvent = waitForEvent(element, "eventName"); 1.332 + * // Do some processing here that will cause the event to be fired 1.333 + * // ... 1.334 + * // Now yield until the Promise is fulfilled 1.335 + * yield receivedEvent; 1.336 + * if (receivedEvent && !(receivedEvent instanceof Error)) { 1.337 + * receivedEvent.msg == "eventName"; 1.338 + * // ... 1.339 + * } 1.340 + * 1.341 + * @param aSubject the element that should receive the event 1.342 + * @param aEventName the event to wait for 1.343 + * @param aTimeoutMs the number of miliseconds to wait before giving up 1.344 + * @returns a Promise that resolves to the received event, or to an Error 1.345 + */ 1.346 +function waitForEvent(aSubject, aEventName, aTimeoutMs, aTarget) { 1.347 + let eventDeferred = Promise.defer(); 1.348 + let timeoutMs = aTimeoutMs || kDefaultWait; 1.349 + let stack = new Error().stack; 1.350 + let timerID = setTimeout(function wfe_canceller() { 1.351 + aSubject.removeEventListener(aEventName, listener); 1.352 + eventDeferred.reject( new Error(aEventName+" event timeout at " + stack) ); 1.353 + }, timeoutMs); 1.354 + 1.355 + var listener = function (aEvent) { 1.356 + if (aTarget && aTarget !== aEvent.target) 1.357 + return; 1.358 + 1.359 + // stop the timeout clock and resume 1.360 + clearTimeout(timerID); 1.361 + eventDeferred.resolve(aEvent); 1.362 + } 1.363 + 1.364 + function cleanup(aEventOrError) { 1.365 + // unhook listener in case of success or failure 1.366 + aSubject.removeEventListener(aEventName, listener); 1.367 + return aEventOrError; 1.368 + } 1.369 + aSubject.addEventListener(aEventName, listener, false); 1.370 + return eventDeferred.promise.then(cleanup, cleanup); 1.371 +} 1.372 + 1.373 +/** 1.374 + * Wait for an nsIMessageManager IPC message. 1.375 + */ 1.376 +function waitForMessage(aName, aMessageManager) { 1.377 + let deferred = Promise.defer(); 1.378 + let manager = aMessageManager || messageManager; 1.379 + function listener(aMessage) { 1.380 + deferred.resolve(aMessage); 1.381 + } 1.382 + manager.addMessageListener(aName, listener); 1.383 + function cleanup(aEventOrError) { 1.384 + manager.removeMessageListener(aName, listener); 1.385 + } 1.386 + return deferred.promise.then(cleanup, cleanup); 1.387 +} 1.388 + 1.389 +/** 1.390 + * Waits a specified number of miliseconds. 1.391 + * 1.392 + * Usage: 1.393 + * let wait = yield waitForMs(2000); 1.394 + * ok(wait, "2 seconds should now have elapsed"); 1.395 + * 1.396 + * @param aMs the number of miliseconds to wait for 1.397 + * @returns a Promise that resolves to true after the time has elapsed 1.398 + */ 1.399 +function waitForMs(aMs) { 1.400 + info("Wating for " + aMs + "ms"); 1.401 + let deferred = Promise.defer(); 1.402 + let startTime = Date.now(); 1.403 + setTimeout(done, aMs); 1.404 + 1.405 + function done() { 1.406 + deferred.resolve(true); 1.407 + info("waitForMs finished waiting, waited for " 1.408 + + (Date.now() - startTime) 1.409 + + "ms"); 1.410 + } 1.411 + 1.412 + return deferred.promise; 1.413 +} 1.414 + 1.415 +/** 1.416 + * Waits a specified number of miliseconds for a supplied callback to 1.417 + * return a truthy value. 1.418 + * 1.419 + * Usage: 1.420 + * let success = yield waitForCondition(myTestFunction); 1.421 + * if (success && !(success instanceof Error)) { 1.422 + * // ... 1.423 + * } 1.424 + * 1.425 + * @param aCondition the callback that must return a truthy value 1.426 + * @param aTimeoutMs the number of miliseconds to wait before giving up 1.427 + * @param aIntervalMs the number of miliseconds between calls to aCondition 1.428 + * @returns a Promise that resolves to true, or to an Error 1.429 + */ 1.430 +function waitForCondition(aCondition, aTimeoutMs, aIntervalMs) { 1.431 + let deferred = Promise.defer(); 1.432 + let timeoutMs = aTimeoutMs || kDefaultWait; 1.433 + let intervalMs = aIntervalMs || kDefaultInterval; 1.434 + let startTime = Date.now(); 1.435 + let stack = new Error().stack; 1.436 + 1.437 + function testCondition() { 1.438 + let now = Date.now(); 1.439 + if((now - startTime) > timeoutMs) { 1.440 + deferred.reject( new Error("Timed out waiting for condition to be true at " + stack) ); 1.441 + return; 1.442 + } 1.443 + 1.444 + let condition; 1.445 + try { 1.446 + condition = aCondition(); 1.447 + } catch (e) { 1.448 + deferred.reject( new Error("Got exception while attempting to test condition: " + e) ); 1.449 + return; 1.450 + } 1.451 + 1.452 + if (condition) { 1.453 + deferred.resolve(true); 1.454 + } else { 1.455 + setTimeout(testCondition, intervalMs); 1.456 + } 1.457 + } 1.458 + 1.459 + setTimeout(testCondition, 0); 1.460 + return deferred.promise; 1.461 +} 1.462 + 1.463 +/** 1.464 + * same as waitForCondition but with better test output. 1.465 + * 1.466 + * @param aCondition the callback that must return a truthy value 1.467 + * @param aTestMsg test condition message printed when the test succeeds or 1.468 + * fails. Defaults to the stringified version of aCondition. 1.469 + * @param aTimeoutMs the number of miliseconds to wait before giving up 1.470 + * @param aIntervalMs the number of miliseconds between calls to aCondition 1.471 + * @returns a Promise that resolves to true, or to an Error 1.472 + */ 1.473 +function waitForCondition2(aCondition, aTestMsg, aTimeoutMs, aIntervalMs) { 1.474 + let deferred = Promise.defer(); 1.475 + let msg = aTestMsg || aCondition; 1.476 + let timeoutMs = aTimeoutMs || kDefaultWait; 1.477 + let intervalMs = aIntervalMs || kDefaultInterval; 1.478 + let startTime = Date.now(); 1.479 + 1.480 + function testCondition() { 1.481 + let now = Date.now(); 1.482 + if((now - startTime) > timeoutMs) { 1.483 + deferred.reject( new Error("Timed out waiting for " + msg) ); 1.484 + return; 1.485 + } 1.486 + 1.487 + let condition; 1.488 + try { 1.489 + condition = aCondition(); 1.490 + } catch (e) { 1.491 + deferred.reject( new Error("Got exception while attempting to test '" + msg + "': " + e) ); 1.492 + return; 1.493 + } 1.494 + 1.495 + if (condition) { 1.496 + ok(true, msg); 1.497 + deferred.resolve(true); 1.498 + } else { 1.499 + setTimeout(testCondition, intervalMs); 1.500 + } 1.501 + } 1.502 + 1.503 + setTimeout(testCondition, 0); 1.504 + return deferred.promise; 1.505 +} 1.506 + 1.507 +/* 1.508 + * Waits for an image in a page to load. Wrapper around waitForCondition. 1.509 + * 1.510 + * @param aWindow the tab or window that contains the image. 1.511 + * @param aImageId the id of the image in the page. 1.512 + * @returns a Promise that resolves to true, or to an Error 1.513 + */ 1.514 +function waitForImageLoad(aWindow, aImageId) { 1.515 + let elem = aWindow.document.getElementById(aImageId); 1.516 + return waitForCondition(function () { 1.517 + let request = elem.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); 1.518 + if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE)) 1.519 + return true; 1.520 + return false; 1.521 + }, 5000, 100); 1.522 +} 1.523 + 1.524 +/** 1.525 + * Waits a specified number of miliseconds for an observer event. 1.526 + * 1.527 + * @param aObsEvent the observer event to wait for 1.528 + * @param aTimeoutMs the number of miliseconds to wait before giving up 1.529 + * @returns a Promise that resolves to true, or to an Error 1.530 + */ 1.531 +function waitForObserver(aObsEvent, aTimeoutMs, aObsData) { 1.532 + try { 1.533 + 1.534 + let deferred = Promise.defer(); 1.535 + let timeoutMs = aTimeoutMs || kDefaultWait; 1.536 + let timerID = 0; 1.537 + 1.538 + var observeWatcher = { 1.539 + onEvent: function () { 1.540 + clearTimeout(timerID); 1.541 + Services.obs.removeObserver(this, aObsEvent); 1.542 + deferred.resolve(); 1.543 + }, 1.544 + 1.545 + onError: function () { 1.546 + clearTimeout(timerID); 1.547 + Services.obs.removeObserver(this, aObsEvent); 1.548 + deferred.reject(new Error(aObsEvent + " event timeout")); 1.549 + }, 1.550 + 1.551 + observe: function (aSubject, aTopic, aData) { 1.552 + if (aTopic == aObsEvent && 1.553 + (!aObsData || (aObsData == aData))) { 1.554 + this.onEvent(); 1.555 + } 1.556 + }, 1.557 + 1.558 + QueryInterface: function (aIID) { 1.559 + if (!aIID.equals(Ci.nsIObserver) && 1.560 + !aIID.equals(Ci.nsISupportsWeakReference) && 1.561 + !aIID.equals(Ci.nsISupports)) { 1.562 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.563 + } 1.564 + return this; 1.565 + }, 1.566 + } 1.567 + 1.568 + timerID = setTimeout(function wfo_canceller() { 1.569 + observeWatcher.onError(); 1.570 + }, timeoutMs); 1.571 + 1.572 + Services.obs.addObserver(observeWatcher, aObsEvent, true); 1.573 + return deferred.promise; 1.574 + 1.575 + } catch (ex) { 1.576 + info(ex.message); 1.577 + } 1.578 +} 1.579 + 1.580 + 1.581 +/*============================================================================= 1.582 + * Input mode helpers - these helpers notify observers to metro_precise_input 1.583 + * and metro_imprecise_input respectively, triggering the same behaviour as user touch or mouse input 1.584 + * 1.585 + * Usage: let promise = waitForObservers("metro_imprecise_input"); 1.586 + * notifyImprecise(); 1.587 + * yield promise; // you are now in imprecise mode 1.588 + *===========================================================================*/ 1.589 +function notifyPrecise() 1.590 +{ 1.591 + Services.obs.notifyObservers(null, "metro_precise_input", null); 1.592 +} 1.593 + 1.594 +function notifyImprecise() 1.595 +{ 1.596 + Services.obs.notifyObservers(null, "metro_imprecise_input", null); 1.597 +} 1.598 + 1.599 +/*============================================================================= 1.600 + * Native input helpers - these helpers send input directly to the os 1.601 + * generating os level input events that get processed by widget and 1.602 + * apzc logic. 1.603 + *===========================================================================*/ 1.604 +function synthesizeNativeMouse(aElement, aOffsetX, aOffsetY, aMsg) { 1.605 + let x = aOffsetX; 1.606 + let y = aOffsetY; 1.607 + if (aElement) { 1.608 + if (aElement.getBoundingClientRect) { 1.609 + let rect = aElement.getBoundingClientRect(); 1.610 + x += rect.left; 1.611 + y += rect.top; 1.612 + } else if(aElement.left && aElement.top) { 1.613 + x += aElement.left; 1.614 + y += aElement.top; 1.615 + } 1.616 + } 1.617 + Browser.windowUtils.sendNativeMouseEvent(x, y, aMsg, 0, null); 1.618 +} 1.619 + 1.620 +function synthesizeNativeMouseMove(aElement, aOffsetX, aOffsetY) { 1.621 + synthesizeNativeMouse(aElement, 1.622 + aOffsetX, 1.623 + aOffsetY, 1.624 + 0x0001); // MOUSEEVENTF_MOVE 1.625 +} 1.626 + 1.627 +function synthesizeNativeMouseLDown(aElement, aOffsetX, aOffsetY) { 1.628 + synthesizeNativeMouse(aElement, 1.629 + aOffsetX, 1.630 + aOffsetY, 1.631 + 0x0002); // MOUSEEVENTF_LEFTDOWN 1.632 +} 1.633 + 1.634 +function synthesizeNativeMouseLUp(aElement, aOffsetX, aOffsetY) { 1.635 + synthesizeNativeMouse(aElement, 1.636 + aOffsetX, 1.637 + aOffsetY, 1.638 + 0x0004); // MOUSEEVENTF_LEFTUP 1.639 +} 1.640 + 1.641 +function synthesizeNativeMouseRDown(aElement, aOffsetX, aOffsetY) { 1.642 + synthesizeNativeMouse(aElement, 1.643 + aOffsetX, 1.644 + aOffsetY, 1.645 + 0x0008); // MOUSEEVENTF_RIGHTDOWN 1.646 +} 1.647 + 1.648 +function synthesizeNativeMouseRUp(aElement, aOffsetX, aOffsetY) { 1.649 + synthesizeNativeMouse(aElement, 1.650 + aOffsetX, 1.651 + aOffsetY, 1.652 + 0x0010); // MOUSEEVENTF_RIGHTUP 1.653 +} 1.654 + 1.655 +function synthesizeNativeMouseMDown(aElement, aOffsetX, aOffsetY) { 1.656 + synthesizeNativeMouse(aElement, 1.657 + aOffsetX, 1.658 + aOffsetY, 1.659 + 0x0020); // MOUSEEVENTF_MIDDLEDOWN 1.660 +} 1.661 + 1.662 +function synthesizeNativeMouseMUp(aElement, aOffsetX, aOffsetY) { 1.663 + synthesizeNativeMouse(aElement, 1.664 + aOffsetX, 1.665 + aOffsetY, 1.666 + 0x0040); // MOUSEEVENTF_MIDDLEUP 1.667 +} 1.668 + 1.669 +// WARNING: these calls can trigger the soft keyboard on tablets, but not 1.670 +// on test slaves (bug 947428). 1.671 +// WARNING: When testing the apzc, be careful of bug 933990. Events sent 1.672 +// shortly after loading a page may get ignored. 1.673 + 1.674 +function sendNativeLongTap(aElement, aX, aY) { 1.675 + let coords = logicalCoordsForElement(aElement, aX, aY); 1.676 + Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, true); 1.677 +} 1.678 + 1.679 +function sendNativeTap(aElement, aX, aY) { 1.680 + let coords = logicalCoordsForElement(aElement, aX, aY); 1.681 + Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false); 1.682 +} 1.683 + 1.684 +function sendNativeDoubleTap(aElement, aX, aY) { 1.685 + let coords = logicalCoordsForElement(aElement, aX, aY); 1.686 + Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false); 1.687 + Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false); 1.688 +} 1.689 + 1.690 +function clearNativeTouchSequence() { 1.691 + Browser.windowUtils.clearNativeTouchSequence(); 1.692 +} 1.693 + 1.694 +/*============================================================================= 1.695 + * Synthesized event helpers - these helpers synthesize input events that get 1.696 + * dispatched directly to the dom. As such widget and apzc logic is bypassed. 1.697 + *===========================================================================*/ 1.698 + 1.699 +/* 1.700 + * logicalCoordsForElement - given coordinates relative to top-left of 1.701 + * given element, returns logical coordinates for window. If a non-numeric 1.702 + * X or Y value is given, a value for the center of the element in that 1.703 + * dimension is used. 1.704 + * 1.705 + * @param aElement element coordinates are relative to. 1.706 + * @param aX, aY relative coordinates. 1.707 + */ 1.708 +function logicalCoordsForElement (aElement, aX, aY) { 1.709 + let coords = { x: null, y: null }; 1.710 + let rect = aElement.getBoundingClientRect(); 1.711 + 1.712 + coords.x = isNaN(aX) ? rect.left + (rect.width / 2) : rect.left + aX; 1.713 + coords.y = isNaN(aY) ? rect.top + (rect.height / 2) : rect.top + aY; 1.714 + 1.715 + return coords; 1.716 +} 1.717 + 1.718 +function sendContextMenuMouseClickToElement(aWindow, aElement, aX, aY) { 1.719 + let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.720 + .getInterface(Ci.nsIDOMWindowUtils); 1.721 + let coords = logicalCoordsForElement(aElement, aX, aY); 1.722 + 1.723 + utils.sendMouseEventToWindow("mousedown", coords.x, coords.y, 2, 1, 0); 1.724 + utils.sendMouseEventToWindow("mouseup", coords.x, coords.y, 2, 1, 0); 1.725 + utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0); 1.726 +} 1.727 + 1.728 +function sendMouseClick(aWindow, aX, aY) { 1.729 + EventUtils.synthesizeMouseAtPoint(aX, aY, {}, aWindow); 1.730 +} 1.731 + 1.732 +/* 1.733 + * sendContextMenuClick - simulates a press-hold touch input event. Event 1.734 + * is delivered to the main window of the application through the top-level 1.735 + * widget. 1.736 + * 1.737 + * @param aX, aY logical coordinates of the event. 1.738 + */ 1.739 +function sendContextMenuClick(aX, aY) { 1.740 + let mediator = Components.classes["@mozilla.org/appshell/window-mediator;1"] 1.741 + .getService(Components.interfaces.nsIWindowMediator); 1.742 + let mainwin = mediator.getMostRecentWindow("navigator:browser"); 1.743 + let utils = mainwin.QueryInterface(Components.interfaces.nsIInterfaceRequestor) 1.744 + .getInterface(Components.interfaces.nsIDOMWindowUtils); 1.745 + utils.sendMouseEvent("contextmenu", aX, aY, 2, 1, 0, true, 1.746 + 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH); 1.747 +} 1.748 + 1.749 +/* 1.750 + * sendContextMenuClickToSelection - simulates a press-hold touch input event 1.751 + * selected text in a window. 1.752 + */ 1.753 +function sendContextMenuClickToSelection(aWindow) { 1.754 + let selection = aWindow.getSelection(); 1.755 + if (!selection || !selection.rangeCount) { 1.756 + ok(false, "no selection to tap!"); 1.757 + return; 1.758 + } 1.759 + let range = selection.getRangeAt(0); 1.760 + let rect = range.getBoundingClientRect(); 1.761 + let x = rect.left + (rect.width / 2); 1.762 + let y = rect.top + (rect.height / 2); 1.763 + let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) 1.764 + .getInterface(Components.interfaces.nsIDOMWindowUtils); 1.765 + utils.sendMouseEventToWindow("contextmenu", x, y, 2, 1, 0, true, 1.766 + 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH); 1.767 +} 1.768 + 1.769 +/* 1.770 + * sendContextMenuClickToWindow - simulates a press-hold touch input event. 1.771 + * 1.772 + * @param aWindow window used to retrieve dom window utils, and the 1.773 + * target window for the event. 1.774 + * @param aX, aY logical coordinates of the event relative to aWindow. 1.775 + */ 1.776 +function sendContextMenuClickToWindow(aWindow, aX, aY) { 1.777 + let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) 1.778 + .getInterface(Components.interfaces.nsIDOMWindowUtils); 1.779 + 1.780 + utils.sendMouseEventToWindow("contextmenu", aX, aY, 2, 1, 0, true, 1.781 + 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH); 1.782 +} 1.783 + 1.784 +function sendContextMenuClickToElement(aWindow, aElement, aX, aY) { 1.785 + let utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) 1.786 + .getInterface(Components.interfaces.nsIDOMWindowUtils); 1.787 + let coords = logicalCoordsForElement(aElement, aX, aY); 1.788 + utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0, true, 1.789 + 1, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH); 1.790 +} 1.791 + 1.792 +/* 1.793 + * sendDoubleTap - simulates a double click or double tap. 1.794 + */ 1.795 +function sendDoubleTap(aWindow, aX, aY) { 1.796 + EventUtils.synthesizeMouseAtPoint(aX, aY, { 1.797 + clickCount: 1, 1.798 + inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH 1.799 + }, aWindow); 1.800 + 1.801 + EventUtils.synthesizeMouseAtPoint(aX, aY, { 1.802 + clickCount: 2, 1.803 + inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH 1.804 + }, aWindow); 1.805 +} 1.806 + 1.807 +function sendTap(aWindow, aX, aY) { 1.808 + EventUtils.synthesizeMouseAtPoint(aX, aY, { 1.809 + clickCount: 1, 1.810 + inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH 1.811 + }, aWindow); 1.812 +} 1.813 + 1.814 +function sendElementTap(aWindow, aElement, aX, aY) { 1.815 + let coords = logicalCoordsForElement(aElement, aX, aY); 1.816 + EventUtils.synthesizeMouseAtPoint(coords.x, coords.y, { 1.817 + clickCount: 1, 1.818 + inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH 1.819 + }, aWindow); 1.820 +} 1.821 + 1.822 +/* 1.823 + * sendTouchDrag - sends a touch series composed of a touchstart, 1.824 + * touchmove, and touchend w3c event. 1.825 + */ 1.826 +function sendTouchDrag(aWindow, aStartX, aStartY, aEndX, aEndY) { 1.827 + EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow); 1.828 + EventUtils.synthesizeTouchAtPoint(aEndX, aEndY, { type: "touchmove" }, aWindow); 1.829 + EventUtils.synthesizeTouchAtPoint(aEndX, aEndY, { type: "touchend" }, aWindow); 1.830 +} 1.831 + 1.832 +/* 1.833 + * TouchDragAndHold - simulates a drag and hold sequence of events. 1.834 + */ 1.835 +function TouchDragAndHold() { 1.836 +} 1.837 + 1.838 +TouchDragAndHold.prototype = { 1.839 + _timeoutStep: 2, 1.840 + _numSteps: 50, 1.841 + _debug: false, 1.842 + _win: null, 1.843 + _native: false, 1.844 + _pointerId: 1, 1.845 + _dui: Components.interfaces.nsIDOMWindowUtils, 1.846 + 1.847 + set useNativeEvents(aValue) { 1.848 + this._native = aValue; 1.849 + }, 1.850 + 1.851 + set stepTimeout(aValue) { 1.852 + this._timeoutStep = aValue; 1.853 + }, 1.854 + 1.855 + set numSteps(aValue) { 1.856 + this._numSteps = aValue; 1.857 + }, 1.858 + 1.859 + set nativePointerId(aValue) { 1.860 + this._pointerId = aValue; 1.861 + }, 1.862 + 1.863 + callback: function callback() { 1.864 + if (this._win == null) 1.865 + return; 1.866 + 1.867 + if (this._debug) { 1.868 + SelectionHelperUI.debugDisplayDebugPoint(this._currentPoint.xPos, 1.869 + this._currentPoint.yPos, 5, "#FF0000", true); 1.870 + } 1.871 + 1.872 + if (++this._step.steps >= this._numSteps) { 1.873 + if (this._native) { 1.874 + this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT, 1.875 + this._endPoint.xPos, this._endPoint.yPos, 1.876 + 1, 90); 1.877 + } else { 1.878 + EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos, 1.879 + { type: "touchmove" }, this._win); 1.880 + } 1.881 + this._defer.resolve(); 1.882 + return; 1.883 + } 1.884 + this._currentPoint.xPos += this._step.x; 1.885 + this._currentPoint.yPos += this._step.y; 1.886 + if (this._debug) { 1.887 + info("[" + this._step.steps + "] touchmove " + this._currentPoint.xPos + " x " + this._currentPoint.yPos); 1.888 + } 1.889 + 1.890 + if (this._native) { 1.891 + this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT, 1.892 + this._currentPoint.xPos, this._currentPoint.yPos, 1.893 + 1, 90); 1.894 + } else { 1.895 + EventUtils.synthesizeTouchAtPoint(this._currentPoint.xPos, this._currentPoint.yPos, 1.896 + { type: "touchmove" }, this._win); 1.897 + } 1.898 + 1.899 + let self = this; 1.900 + setTimeout(function () { self.callback(); }, this._timeoutStep); 1.901 + }, 1.902 + 1.903 + start: function start(aWindow, aStartX, aStartY, aEndX, aEndY) { 1.904 + this._defer = Promise.defer(); 1.905 + this._win = aWindow; 1.906 + this._utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.907 + .getInterface(Ci.nsIDOMWindowUtils); 1.908 + this._endPoint = { xPos: aEndX, yPos: aEndY }; 1.909 + this._currentPoint = { xPos: aStartX, yPos: aStartY }; 1.910 + this._step = { steps: 0, x: (aEndX - aStartX) / this._numSteps, y: (aEndY - aStartY) / this._numSteps }; 1.911 + if (this._debug) { 1.912 + info("[0] touchstart " + aStartX + " x " + aStartY); 1.913 + } 1.914 + // flush layout, bug 914847 1.915 + this._utils.elementFromPoint(aStartX, aStartY, false, true); 1.916 + if (this._native) { 1.917 + this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT, 1.918 + aStartX, aStartY, 1, 90); 1.919 + } else { 1.920 + EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow); 1.921 + } 1.922 + let self = this; 1.923 + setTimeout(function () { self.callback(); }, this._timeoutStep); 1.924 + return this._defer.promise; 1.925 + }, 1.926 + 1.927 + move: function move(aEndX, aEndY) { 1.928 + if (this._win == null) 1.929 + return; 1.930 + if (this._debug) { 1.931 + info("[0] continuation to " + aEndX + " x " + aEndY); 1.932 + } 1.933 + this._defer = Promise.defer(); 1.934 + this._step = { steps: 0, 1.935 + x: (aEndX - this._endPoint.xPos) / this._numSteps, 1.936 + y: (aEndY - this._endPoint.yPos) / this._numSteps }; 1.937 + this._endPoint = { xPos: aEndX, yPos: aEndY }; 1.938 + let self = this; 1.939 + setTimeout(function () { self.callback(); }, this._timeoutStep); 1.940 + return this._defer.promise; 1.941 + }, 1.942 + 1.943 + end: function end() { 1.944 + if (this._debug) { 1.945 + info("[" + this._step.steps + "] touchend " + this._endPoint.xPos + " x " + this._endPoint.yPos); 1.946 + SelectionHelperUI.debugClearDebugPoints(); 1.947 + } 1.948 + if (this._native) { 1.949 + this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_REMOVE, 1.950 + this._endPoint.xPos, this._endPoint.yPos, 1.951 + 1, 90); 1.952 + } else { 1.953 + EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos, 1.954 + { type: "touchend" }, this._win); 1.955 + } 1.956 + this._win = null; 1.957 + }, 1.958 +}; 1.959 + 1.960 +/*============================================================================= 1.961 + System utilities 1.962 +=============================================================================*/ 1.963 + 1.964 +/* 1.965 + * emptyClipboard - clear the windows clipboard. 1.966 + */ 1.967 +function emptyClipboard() { 1.968 + Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard) 1.969 + .emptyClipboard(Ci.nsIClipboard.kGlobalClipboard); 1.970 +} 1.971 + 1.972 +/* 1.973 + * purgeEventQueue - purges the event queue on the calling thread. 1.974 + * Pumps latent in-process message manager events awaiting delivery. 1.975 + */ 1.976 +function purgeEventQueue() { 1.977 + let thread = Services.tm.currentThread; 1.978 + while (thread.hasPendingEvents()) { 1.979 + if (!thread.processNextEvent(true)) 1.980 + break; 1.981 + } 1.982 +} 1.983 + 1.984 +/*============================================================================= 1.985 + Test-running helpers 1.986 +=============================================================================*/ 1.987 +let gCurrentTest = null; 1.988 +let gTests = []; 1.989 + 1.990 +function runTests() { 1.991 + waitForExplicitFinish(); 1.992 + 1.993 + Task.spawn(function() { 1.994 + while((gCurrentTest = gTests.shift())){ 1.995 + try { 1.996 + if ('function' == typeof gCurrentTest.setUp) { 1.997 + info("SETUP " + gCurrentTest.desc); 1.998 + yield Task.spawn(gCurrentTest.setUp.bind(gCurrentTest)); 1.999 + } 1.1000 + try { 1.1001 + info("RUN " + gCurrentTest.desc); 1.1002 + yield Task.spawn(gCurrentTest.run.bind(gCurrentTest)); 1.1003 + } finally { 1.1004 + if ('function' == typeof gCurrentTest.tearDown) { 1.1005 + info("TEARDOWN " + gCurrentTest.desc); 1.1006 + yield Task.spawn(gCurrentTest.tearDown.bind(gCurrentTest)); 1.1007 + } 1.1008 + } 1.1009 + } catch (ex) { 1.1010 + ok(false, "runTests: Task failed - " + ex + ' at ' + ex.stack); 1.1011 + } finally { 1.1012 + info("END " + gCurrentTest.desc); 1.1013 + } 1.1014 + } 1.1015 + 1.1016 + try { 1.1017 + cleanUpOpenedTabs(); 1.1018 + 1.1019 + let badTabs = []; 1.1020 + Browser.tabs.forEach(function(item, index, array) { 1.1021 + let location = item.browser.currentURI.spec; 1.1022 + if (index == 0 && location == "about:blank" || location == "about:start") { 1.1023 + return; 1.1024 + } 1.1025 + ok(false, "Left over tab after test: '" + location + "'"); 1.1026 + badTabs.push(item); 1.1027 + }); 1.1028 + 1.1029 + badTabs.forEach(function(item, index, array) { 1.1030 + Browser.closeTab(item, { forceClose: true }); 1.1031 + }); 1.1032 + } catch (ex) { 1.1033 + ok(false, "Cleanup tabs failed - " + ex); 1.1034 + } 1.1035 + 1.1036 + finish(); 1.1037 + }); 1.1038 +} 1.1039 + 1.1040 +// wrap a method with a spy that records how and how many times it gets called 1.1041 +// the spy is returned; use spy.restore() to put the original back 1.1042 +function spyOnMethod(aObj, aMethod) { 1.1043 + let origFunc = aObj[aMethod]; 1.1044 + let spy = function() { 1.1045 + let callArguments = Array.slice(arguments); 1.1046 + spy.callCount++; 1.1047 + spy.calledWith = callArguments; 1.1048 + spy.argsForCall.push(callArguments); 1.1049 + return (spy.returnValue = origFunc.apply(aObj, arguments)); 1.1050 + }; 1.1051 + spy.callCount = 0; 1.1052 + spy.argsForCall = []; 1.1053 + spy.restore = function() { 1.1054 + return (aObj[aMethod] = origFunc); 1.1055 + }; 1.1056 + return (aObj[aMethod] = spy); 1.1057 +} 1.1058 + 1.1059 +// replace a method with a stub that records how and how many times it gets called 1.1060 +// the stub is returned; use stub.restore() to put the original back 1.1061 +function stubMethod(aObj, aMethod) { 1.1062 + let origFunc = aObj[aMethod]; 1.1063 + let func = function() { 1.1064 + func.calledWith = Array.slice(arguments); 1.1065 + func.callCount++; 1.1066 + }; 1.1067 + func.callCount = 0; 1.1068 + func.restore = function() { 1.1069 + return (aObj[aMethod] = origFunc); 1.1070 + }; 1.1071 + aObj[aMethod] = func; 1.1072 + return func; 1.1073 +}