fune/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py
Dão Gottwald f8110c1b6a Bug 1451709 - Remove code for the "Restore Tabs From Last Time" button in the tab bar. r=mikedeboer
MozReview-Commit-ID: JMmkE6axBBK

--HG--
extra : rebase_source : 2449c9fd6fe8f59280dbdfe5a1a55bcd2867a665
2018-04-05 14:02:55 +02:00

390 lines
13 KiB
Python

# 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 __future__ import absolute_import
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))