Bug 1348872 - Check for existent modal dialogs for new Marionette sessions.
To ensure that Marionette can also detect modal dialogs opened right after
the application starts, and before Marionette has been initialized, the modal
dialog detection code has to be delayed until a new session actually gets
started. Then it's not enough to only register the observer notification, but
it should also be checked for open modal or tab modal dialogs.
MozReview-Commit-ID: ChYcR3I59DW
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -508,17 +508,17 @@ class MultiActions(object):
class Alert(object):
"""A class for interacting with alerts.
::
Alert(marionette).accept()
- Alert(merionette).dismiss()
+ Alert(marionette).dismiss()
"""
def __init__(self, marionette):
self.marionette = marionette
def accept(self):
"""Accept a currently displayed modal dialog."""
self.marionette._send_message("acceptDialog")
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -133,26 +133,19 @@ this.GeckoDriver = function (appName, se
this.marionetteLog = new logging.ContentLogger();
this.testName = null;
this.capabilities = new session.Capabilities();
this.mm = globalMessageManager;
this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
- // always keep weak reference to current dialogue
+ // points to an alert instance if a modal dialog is present
this.dialog = null;
- let handleDialog = (subject, topic) => {
- let winr;
- if (topic == modal.COMMON_DIALOG_LOADED) {
- winr = Cu.getWeakReference(subject);
- }
- this.dialog = new modal.Dialog(() => this.curBrowser, winr);
- };
- modal.addHandler(handleDialog);
+ this.dialogHandler = this.globalModalDialogHandler.bind(this);
};
Object.defineProperty(GeckoDriver.prototype, "a11yChecks", {
get: function () {
return this.capabilities.get("moz:accessibilityChecks");
}
});
@@ -219,16 +212,29 @@ Object.defineProperty(GeckoDriver.protot
GeckoDriver.prototype.QueryInterface = XPCOMUtils.generateQI([
Ci.nsIMessageListener,
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
]);
/**
+ * Callback used to observe the creation of new modal or tab modal dialogs
+ * during the session's lifetime.
+ */
+GeckoDriver.prototype.globalModalDialogHandler = function (subject, topic) {
+ let winr;
+ if (topic === modal.COMMON_DIALOG_LOADED) {
+ // Always keep a weak reference to the current dialog
+ winr = Cu.getWeakReference(subject);
+ }
+ this.dialog = new modal.Dialog(() => this.curBrowser, winr);
+};
+
+/**
* Switches to the global ChromeMessageBroadcaster, potentially replacing
* a frame-specific ChromeMessageSender. Has no effect if the global
* ChromeMessageBroadcaster is already in use. If this replaces a
* frame-specific ChromeMessageSender, it removes the message listeners
* from that sender, and then puts the corresponding frame script "to
* sleep", which removes most of the message listeners from it as well.
*/
GeckoDriver.prototype.switchToGlobalMessageManager = function() {
@@ -657,16 +663,21 @@ GeckoDriver.prototype.newSession = funct
yield registerBrowsers;
yield browserListening;
if (this.curBrowser.tab) {
this.curBrowser.contentBrowser.focus();
}
+ // Setup global listener for modal dialogs, and check if there is already
+ // one open for the currently selected browser window.
+ modal.addHandler(this.dialogHandler);
+ this.dialog = modal.findModalDialogs(this.curBrowser);
+
return {
sessionId: this.sessionId,
capabilities: this.capabilities,
};
};
/**
* Send the current session's capabilities to the client.
@@ -2368,16 +2379,18 @@ GeckoDriver.prototype.deleteSession = fu
if (this.observing !== null) {
for (let topic in this.observing) {
Services.obs.removeObserver(this.observing[topic], topic);
}
this.observing = null;
}
+ modal.removeHandler(this.dialogHandler);
+
this.sandboxes.clear();
cert.uninstallOverride();
this.sessionId = null;
this.capabilities = new session.Capabilities();
};
/** Returns the current status of the Application Cache. */
@@ -2675,20 +2688,19 @@ GeckoDriver.prototype.sendKeysToDialog =
event.sendKeysToElement(
cmd.parameters.value,
loginTextbox,
{ignoreVisibility: true},
this.dialog.window ? this.dialog.window : win);
};
-GeckoDriver.prototype._checkIfAlertIsPresent = function() {
+GeckoDriver.prototype._checkIfAlertIsPresent = function () {
if (!this.dialog || !this.dialog.ui) {
- throw new NoAlertOpenError(
- "No tab modal was open when attempting to get the dialog text");
+ throw new NoAlertOpenError("No modal dialog is currently open");
}
};
/**
* Enables or disables accepting new socket connections.
*
* By calling this method with `false` the server will not accept any further
* connections, but existing connections will not be forcible closed. Use `true`
--- a/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py
+++ b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py
@@ -16,20 +16,20 @@ class WindowManagerMixin(object):
self.start_window = self.marionette.current_chrome_window_handle
self.start_windows = self.marionette.chrome_window_handles
self.start_tab = self.marionette.current_window_handle
self.start_tabs = self.marionette.window_handles
def tearDown(self):
- if len(self.marionette.chrome_window_handles) != len(self.start_windows):
+ if len(self.marionette.chrome_window_handles) > len(self.start_windows):
raise Exception("Not all windows as opened by the test have been closed")
- if len(self.marionette.window_handles) != len(self.start_tabs):
+ if len(self.marionette.window_handles) > len(self.start_tabs):
raise Exception("Not all tabs as opened by the test have been closed")
super(WindowManagerMixin, self).tearDown()
def close_all_tabs(self):
current_window_handles = self.marionette.window_handles
# If the start tab is not present anymore, use the next one of the list
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py
@@ -1,31 +1,33 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_driver.by import By
from marionette_driver.errors import NoAlertPresentException, ElementNotInteractableException
+from marionette_driver.expected import element_present
from marionette_driver.marionette import Alert
from marionette_driver.wait import Wait
from marionette_harness import MarionetteTestCase, skip_if_e10s, WindowManagerMixin
class BaseAlertTestCase(WindowManagerMixin, MarionetteTestCase):
def alert_present(self):
try:
Alert(self.marionette).text
return True
except NoAlertPresentException:
return False
- def wait_for_alert(self):
- Wait(self.marionette).until(lambda _: self.alert_present())
+ def wait_for_alert(self, timeout=None):
+ Wait(self.marionette, timeout=timeout).until(
+ lambda _: self.alert_present())
def wait_for_alert_closed(self, timeout=None):
Wait(self.marionette, timeout=timeout).until(
lambda _: not self.alert_present())
class TestTabModalAlerts(BaseAlertTestCase):
@@ -93,16 +95,27 @@ class TestTabModalAlerts(BaseAlertTestCa
def test_prompt_dismiss(self):
self.marionette.find_element(By.ID, "tab-modal-prompt").click()
self.wait_for_alert()
alert = self.marionette.switch_to_alert()
alert.dismiss()
self.wait_for_condition(
lambda mn: mn.find_element(By.ID, "prompt-result").text == "null")
+ def test_alert_opened_before_session_starts(self):
+ self.marionette.find_element(By.ID, "tab-modal-alert").click()
+ self.wait_for_alert()
+
+ # Restart the session to ensure we still find the formerly left-open dialog.
+ self.marionette.delete_session()
+ self.marionette.start_session()
+
+ alert = self.marionette.switch_to_alert()
+ alert.dismiss()
+
def test_alert_text(self):
with self.assertRaises(NoAlertPresentException):
alert = self.marionette.switch_to_alert()
alert.text
self.marionette.find_element(By.ID, "tab-modal-alert").click()
self.wait_for_alert()
alert = self.marionette.switch_to_alert()
self.assertEqual(alert.text, "Marionette alert")
@@ -227,8 +240,21 @@ class TestModalAlerts(BaseAlertTestCase)
alert.dismiss()
self.wait_for_alert_closed()
status = Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
element_present(By.ID, "status")
)
self.assertEqual(status.text, "restricted")
+
+ def test_alert_opened_before_session_starts(self):
+ self.marionette.navigate(self.marionette.absolute_url("http_auth"))
+ self.wait_for_alert(timeout=self.marionette.timeout.page_load)
+
+ # Restart the session to ensure we still find the formerly left-open dialog.
+ self.marionette.delete_session()
+ self.marionette.start_session()
+
+ alert = self.marionette.switch_to_alert()
+ alert.dismiss()
+
+ self.wait_for_alert_closed()
--- a/testing/marionette/modal.js
+++ b/testing/marionette/modal.js
@@ -44,16 +44,55 @@ modal.addHandler = function (handler) {
Object.keys(this.handlers).map(topic => {
this.handlers[topic].add(handler);
Services.obs.addObserver(handler, topic, false);
});
};
/**
+ * Check for already existing modal or tab modal dialogs
+ *
+ * @param {browser.Context} context
+ * Reference to the browser context to check for existent dialogs.
+ *
+ * @return {modal.Dialog}
+ * Returns instance of the Dialog class, or `null` if no modal dialog is present.
+ */
+modal.findModalDialogs = function (context) {
+ // First check if there is a modal dialog already present for the current browser window.
+ let winEn = Services.wm.getEnumerator(null);
+ while (winEn.hasMoreElements()) {
+ let win = winEn.getNext();
+
+ // Modal dialogs which do not have an opener set, we cannot detect as long
+ // as GetZOrderDOMWindowEnumerator doesn't work on Linux (Bug 156333).
+ if (win.document.documentURI === "chrome://global/content/commonDialog.xul" &&
+ win.opener && win.opener === context.window) {
+ return new modal.Dialog(() => context, Cu.getWeakReference(win));
+ }
+ }
+
+ // If no modal dialog has been found, also check if there is an open tab modal
+ // dialog present for the current tab.
+ // TODO: Find an adequate implementation for Fennec.
+ if (context.tab && context.tabBrowser.getTabModalPromptBox) {
+ let contentBrowser = context.contentBrowser;
+ let promptManager = context.tabBrowser.getTabModalPromptBox(contentBrowser);
+ let prompts = promptManager.listPrompts();
+
+ if (prompts.length) {
+ return new modal.Dialog(() => context, null);
+ }
+ }
+
+ return null;
+};
+
+/**
* Remove modal dialogue handler by function reference.
*
* This function is a no-op if called on any other product than Firefox.
*
* @param {function} toRemove
* The handler previously passed to modal.addHandler which will now
* be removed.
*/