diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py')
-rw-r--r-- | testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py b/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py new file mode 100644 index 000000000..4fec98d99 --- /dev/null +++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py @@ -0,0 +1,388 @@ +# 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 import ( + By, Wait +) + +from marionette_driver.errors import NoSuchElementException + +import firefox_puppeteer.errors as errors + +from firefox_puppeteer.api.security import Security +from firefox_puppeteer.ui.base import UIBaseLib, DOMElement + + +class TabBar(UIBaseLib): + """Wraps the tabs toolbar DOM element inside a browser window.""" + + # Properties for visual elements of the tabs toolbar # + + @property + def menupanel(self): + """A :class:`MenuPanel` instance which represents the menu panel + at the far right side of the tabs toolbar. + + :returns: :class:`MenuPanel` instance. + """ + return MenuPanel(self.marionette, self.window) + + @property + def newtab_button(self): + """The DOM element which represents the new tab button. + + :returns: Reference to the new tab button. + """ + return self.toolbar.find_element(By.ANON_ATTRIBUTE, {'anonid': 'tabs-newtab-button'}) + + @property + def tabs(self): + """List of all the :class:`Tab` instances of the current browser window. + + :returns: List of :class:`Tab` instances. + """ + tabs = self.toolbar.find_elements(By.TAG_NAME, 'tab') + + return [Tab(self.marionette, self.window, tab) for tab in tabs] + + @property + def toolbar(self): + """The DOM element which represents the tab toolbar. + + :returns: Reference to the tabs toolbar. + """ + return self.element + + # Properties for helpers when working with the tabs toolbar # + + @property + def selected_index(self): + """The index of the currently selected tab. + + :return: Index of the selected tab. + """ + return int(self.toolbar.get_property('selectedIndex')) + + @property + def selected_tab(self): + """A :class:`Tab` instance of the currently selected tab. + + :returns: :class:`Tab` instance. + """ + return self.tabs[self.selected_index] + + # Methods for helpers when working with the tabs toolbar # + + def close_all_tabs(self, exceptions=None): + """Forces closing of all open tabs. + + There is an optional `exceptions` list, which can be used to exclude + specific tabs from being closed. + + :param exceptions: Optional, list of :class:`Tab` instances not to close. + """ + # Get handles from tab exceptions, and find those which can be closed + for tab in self.tabs: + if tab not in exceptions: + tab.close(force=True) + + def close_tab(self, tab=None, trigger='menu', force=False): + """Closes the tab by using the specified trigger. + + By default the currently selected tab will be closed. If another :class:`Tab` + is specified, that one will be closed instead. Also when the tab is closed, a + :func:`switch_to` call is automatically performed, so that the new selected + tab becomes active. + + :param tab: Optional, the :class:`Tab` instance to close. Defaults to + the currently selected tab. + + :param trigger: Optional, method to close the current tab. This can + be a string with one of `menu` or `shortcut`, or a callback which gets triggered + with the :class:`Tab` as parameter. Defaults to `menu`. + + :param force: Optional, forces the closing of the window by using the Gecko API. + Defaults to `False`. + """ + tab = tab or self.selected_tab + tab.close(trigger, force) + + def open_tab(self, trigger='menu'): + """Opens a new tab in the current browser window. + + If the tab opens in the foreground, a call to :func:`switch_to` will + automatically be performed. But if it opens in the background, the current + tab will keep its focus. + + :param trigger: Optional, method to open the new tab. This can + be a string with one of `menu`, `button` or `shortcut`, or a callback + which gets triggered with the current :class:`Tab` as parameter. + Defaults to `menu`. + + :returns: :class:`Tab` instance for the opened tab. + """ + start_handles = self.marionette.window_handles + + # Prepare action which triggers the opening of the browser window + if callable(trigger): + trigger(self.selected_tab) + elif trigger == 'button': + self.window.tabbar.newtab_button.click() + elif trigger == 'menu': + self.window.menubar.select_by_id('file-menu', + 'menu_newNavigatorTab') + elif trigger == 'shortcut': + self.window.send_shortcut(self.window.localize_entity('tabCmd.commandkey'), + accel=True) + # elif - need to add other cases + else: + raise ValueError('Unknown opening method: "%s"' % trigger) + + # TODO: Needs to be replaced with event handling code (bug 1121705) + Wait(self.marionette).until( + lambda mn: len(mn.window_handles) == len(start_handles) + 1, + message='No new tab has been opened.') + + handles = self.marionette.window_handles + [new_handle] = list(set(handles) - set(start_handles)) + [new_tab] = [tab for tab in self.tabs if tab.handle == new_handle] + + # if the new tab is the currently selected tab, switch to it + if new_tab == self.selected_tab: + new_tab.switch_to() + + return new_tab + + def switch_to(self, target): + """Switches the context to the specified tab. + + :param target: The tab to switch to. `target` can be an index, a :class:`Tab` + instance, or a callback that returns True in the context of the desired tab. + + :returns: Instance of the selected :class:`Tab`. + """ + start_handle = self.marionette.current_window_handle + + if isinstance(target, int): + return self.tabs[target].switch_to() + elif isinstance(target, Tab): + return target.switch_to() + if callable(target): + for tab in self.tabs: + tab.switch_to() + if target(tab): + return tab + + self.marionette.switch_to_window(start_handle) + raise errors.UnknownTabError("No tab found for '{}'".format(target)) + + raise ValueError("The 'target' parameter must either be an index or a callable") + + @staticmethod + def get_handle_for_tab(marionette, tab_element): + """Retrieves the marionette handle for the given :class:`Tab` instance. + + :param marionette: An instance of the Marionette client. + + :param tab_element: The DOM element corresponding to a tab inside the tabs toolbar. + + :returns: `handle` of the tab. + """ + # TODO: This introduces coupling with marionette's window handles + # implementation. To avoid this, the capacity to get the XUL + # element corresponding to the active window according to + # marionette or a similar ability should be added to marionette. + handle = marionette.execute_script(""" + let win = arguments[0].linkedBrowser; + if (!win) { + return null; + } + return win.outerWindowID.toString(); + """, script_args=[tab_element]) + + return handle + + +class Tab(UIBaseLib): + """Wraps a tab DOM element.""" + + def __init__(self, marionette, window, element): + super(Tab, self).__init__(marionette, window, element) + + self._security = Security(self.marionette) + self._handle = None + + # Properties for visual elements of tabs # + + @property + def close_button(self): + """The DOM element which represents the tab close button. + + :returns: Reference to the tab close button. + """ + return self.tab_element.find_element(By.ANON_ATTRIBUTE, {'anonid': 'close-button'}) + + @property + def tab_element(self): + """The inner tab DOM element. + + :returns: Tab DOM element. + """ + return self.element + + # Properties for backend values + + @property + def location(self): + """Returns the current URL + + :returns: Current URL + """ + self.switch_to() + + return self.marionette.execute_script(""" + return arguments[0].linkedBrowser.currentURI.spec; + """, script_args=[self.tab_element]) + + @property + def certificate(self): + """The SSL certificate assiciated with the loaded web page. + + :returns: Certificate details as JSON blob. + """ + self.switch_to() + + return self._security.get_certificate_for_page(self.tab_element) + + # Properties for helpers when working with tabs # + + @property + def handle(self): + """The `handle` of the content window. + + :returns: content window `handle`. + """ + # If no handle has been set yet, wait until it is available + if not self._handle: + self._handle = Wait(self.marionette).until( + lambda mn: TabBar.get_handle_for_tab(mn, self.element), + message='Tab handle could not be found.') + + return self._handle + + @property + def selected(self): + """Checks if the tab is selected. + + :return: `True` if the tab is selected. + """ + return self.marionette.execute_script(""" + return arguments[0].hasAttribute('selected'); + """, script_args=[self.tab_element]) + + # Methods for helpers when working with tabs # + + def __eq__(self, other): + return self.handle == other.handle + + def close(self, trigger='menu', force=False): + """Closes the tab by using the specified trigger. + + When the tab is closed a :func:`switch_to` call is automatically performed, so that + the new selected tab becomes active. + + :param trigger: Optional, method in how to close the tab. This can + be a string with one of `button`, `menu` or `shortcut`, or a callback which + gets triggered with the current :class:`Tab` as parameter. Defaults to `menu`. + + :param force: Optional, forces the closing of the window by using the Gecko API. + Defaults to `False`. + """ + handle = self.handle + start_handles = self.marionette.window_handles + + self.switch_to() + + if force: + self.marionette.close() + elif callable(trigger): + trigger(self) + elif trigger == 'button': + self.close_button.click() + elif trigger == 'menu': + self.window.menubar.select_by_id('file-menu', 'menu_close') + elif trigger == 'shortcut': + self.window.send_shortcut(self.window.localize_entity('closeCmd.key'), + accel=True) + else: + raise ValueError('Unknown closing method: "%s"' % trigger) + + Wait(self.marionette).until( + lambda _: len(self.window.tabbar.tabs) == len(start_handles) - 1, + message='Tab with handle "%s" has not been closed.' % handle) + + # Ensure to switch to the window handle which represents the new selected tab + self.window.tabbar.selected_tab.switch_to() + + def select(self): + """Selects the tab and sets the focus to it.""" + self.tab_element.click() + self.switch_to() + + # Bug 1121705: Maybe we have to wait for TabSelect event + Wait(self.marionette).until( + lambda _: self.selected, + message='Tab with handle "%s" could not be selected.' % self.handle) + + def switch_to(self): + """Switches the context of Marionette to this tab. + + Please keep in mind that calling this method will not select the tab. + Use the :func:`~Tab.select` method instead. + """ + self.marionette.switch_to_window(self.handle) + + +class MenuPanel(UIBaseLib): + + @property + def popup(self): + """ + :returns: The :class:`MenuPanelElement`. + """ + popup = self.marionette.find_element(By.ID, 'PanelUI-popup') + return self.MenuPanelElement(popup) + + class MenuPanelElement(DOMElement): + """Wraps the menu panel.""" + _buttons = None + + @property + def buttons(self): + """ + :returns: A list of all the clickable buttons in the menu panel. + """ + if not self._buttons: + self._buttons = (self.find_element(By.ID, 'PanelUI-multiView') + .find_element(By.ANON_ATTRIBUTE, + {'anonid': 'viewContainer'}) + .find_elements(By.TAG_NAME, + 'toolbarbutton')) + return self._buttons + + def click(self, target=None): + """ + Overrides HTMLElement.click to provide a target to click. + + :param target: The label associated with the button to click on, + e.g., `New Private Window`. + """ + if not target: + return DOMElement.click(self) + + for button in self.buttons: + if button.get_attribute('label') == target: + return button.click() + raise NoSuchElementException('Could not find "{}"" in the ' + 'menu panel UI'.format(target)) |