summaryrefslogtreecommitdiffstats
path: root/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py
blob: 85729cc58516c26d9e9500bd46800cc1dc9c5e56 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# 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/.

import sys

from marionette_driver import Wait
from six import reraise


class WindowManagerMixin(object):
    def setUp(self):
        super(WindowManagerMixin, self).setUp()

        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):
            raise Exception("Not all windows as opened by the test have been closed")

        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
        if self.start_tab not in current_window_handles:
            self.start_tab = current_window_handles[0]

        current_window_handles.remove(self.start_tab)
        for handle in current_window_handles:
            self.marionette.switch_to_window(handle)
            self.marionette.close()

        self.marionette.switch_to_window(self.start_tab)

    def close_all_windows(self):
        current_chrome_window_handles = self.marionette.chrome_window_handles

        # If the start window is not present anymore, use the next one of the list
        if self.start_window not in current_chrome_window_handles:
            self.start_window = current_chrome_window_handles[0]
        current_chrome_window_handles.remove(self.start_window)

        for handle in current_chrome_window_handles:
            self.marionette.switch_to_window(handle)
            self.marionette.close_chrome_window()

        self.marionette.switch_to_window(self.start_window)

    def open_tab(self, callback=None, focus=False):
        current_tabs = self.marionette.window_handles

        try:
            if callable(callback):
                callback()
            else:
                result = self.marionette.open(type="tab", focus=focus)
                if result["type"] != "tab":
                    raise Exception(
                        "Newly opened browsing context is of type {} and not tab.".format(
                            result["type"]
                        )
                    )
        except Exception:
            exc_cls, exc, tb = sys.exc_info()
            reraise(
                exc_cls,
                exc_cls("Failed to trigger opening a new tab: {}".format(exc)),
                tb,
            )
        else:
            Wait(self.marionette).until(
                lambda mn: len(mn.window_handles) == len(current_tabs) + 1,
                message="No new tab has been opened",
            )

            [new_tab] = list(set(self.marionette.window_handles) - set(current_tabs))

            return new_tab

    def open_window(self, callback=None, focus=False, private=False):
        current_windows = self.marionette.chrome_window_handles
        current_tabs = self.marionette.window_handles

        def loaded(handle):
            with self.marionette.using_context("chrome"):
                return self.marionette.execute_script(
                    """
                  const { windowManager } = ChromeUtils.importESModule(
                    "chrome://remote/content/shared/WindowManager.sys.mjs"
                  );
                  const win = windowManager.findWindowByHandle(arguments[0]).win;
                  return win.document.readyState == "complete";
                """,
                    script_args=[handle],
                )

        try:
            if callable(callback):
                callback(focus)
            else:
                result = self.marionette.open(
                    type="window", focus=focus, private=private
                )
                if result["type"] != "window":
                    raise Exception(
                        "Newly opened browsing context is of type {} and not window.".format(
                            result["type"]
                        )
                    )
        except Exception:
            exc_cls, exc, tb = sys.exc_info()
            reraise(
                exc_cls,
                exc_cls("Failed to trigger opening a new window: {}".format(exc)),
                tb,
            )
        else:
            Wait(self.marionette).until(
                lambda mn: len(mn.chrome_window_handles) == len(current_windows) + 1,
                message="No new window has been opened",
            )

            [new_window] = list(
                set(self.marionette.chrome_window_handles) - set(current_windows)
            )

            # Before continuing ensure the window has been completed loading
            Wait(self.marionette).until(
                lambda _: loaded(new_window),
                message="Window with handle '{}'' did not finish loading".format(
                    new_window
                ),
            )

            # Bug 1507771 - Return the correct handle based on the currently selected context
            # as long as "WebDriver:NewWindow" is not handled separtely in chrome context
            context = self.marionette._send_message(
                "Marionette:GetContext", key="value"
            )
            if context == "chrome":
                return new_window
            elif context == "content":
                [new_tab] = list(
                    set(self.marionette.window_handles) - set(current_tabs)
                )
                return new_tab

    def open_chrome_window(self, url, focus=False):
        """Open a new chrome window with the specified chrome URL.

        Can be replaced with "WebDriver:NewWindow" once the command
        supports opening generic chrome windows beside browsers (bug 1507771).
        """

        def open_with_js(focus):
            with self.marionette.using_context("chrome"):
                self.marionette.execute_async_script(
                    """
                  let [url, focus, resolve] = arguments;

                  function waitForEvent(target, type, args) {
                    return new Promise(resolve => {
                      let params = Object.assign({once: true}, args);
                      target.addEventListener(type, event => {
                        dump(`** Received DOM event ${event.type} for ${event.target}\n`);
                        resolve();
                      }, params);
                    });
                  }

                  function waitForFocus(win) {
                    return Promise.all([
                      waitForEvent(win, "activate"),
                      waitForEvent(win, "focus", {capture: true}),
                    ]);
                  }

                  (async function() {
                    // Open a window, wait for it to receive focus
                    let win = window.openDialog(url, null, "chrome,centerscreen");
                    let focused = waitForFocus(win);

                    win.focus();
                    await focused;

                    // The new window shouldn't get focused. As such set the
                    // focus back to the opening window.
                    if (!focus && Services.focus.activeWindow != window) {
                      let focused = waitForFocus(window);
                      window.focus();
                      await focused;
                    }

                    resolve(win.docShell.browsingContext.id);
                  })();
                """,
                    script_args=(url, focus),
                )

        with self.marionette.using_context("chrome"):
            return self.open_window(callback=open_with_js, focus=focus)