146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)# Copyright 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
41320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport inspect
51320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport os
61320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport re
71320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport urlparse
81320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
91320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci_next_page_id = 0
101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciclass Page(object):
121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def __init__(self, url, page_set=None, base_dir=None, name=''):
141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._url = url
151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._page_set = page_set
161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # Default value of base_dir is the directory of the file that defines the
171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # class of this page instance.
181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if base_dir is None:
191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      base_dir = os.path.dirname(inspect.getfile(self.__class__))
201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._base_dir = base_dir
211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._name = name
221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    global _next_page_id
241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._id = _next_page_id
251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    _next_page_id += 1
261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # These attributes can be set dynamically by the page.
281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.synthetic_delays = dict()
291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.startup_url = page_set.startup_url if page_set else ''
301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.credentials = None
311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.disabled = False
321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.skip_waits = False
331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.script_to_evaluate_on_commit = None
341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._SchemeErrorCheck()
351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def _SchemeErrorCheck(self):
371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if not self._scheme:
381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      raise ValueError('Must prepend the URL with scheme (e.g. file://)')
391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if self.startup_url:
411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      startup_url_scheme = urlparse.urlparse(self.startup_url).scheme
421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if not startup_url_scheme:
431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        raise ValueError('Must prepend the URL with scheme (e.g. http://)')
441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if startup_url_scheme == 'file':
451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        raise ValueError('startup_url with local file scheme is not supported')
461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def TransferToPageSet(self, another_page_set):
481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """ Transfer this page to another page set.
491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    Args:
501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      another_page_set: an instance of telemetry.page.PageSet to transfer this
511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          page to.
521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    Note:
531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      This method removes this page instance from the pages list of its current
541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      page_set, so one should be careful not to iterate through the list of
551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      pages of a page_set and calling this method.
561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      For example, the below loop is erroneous:
571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        for p in page_set_A.pages:
581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          p.TransferToPageSet(page_set_B.pages)
591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """
601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    assert self._page_set
611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if another_page_set is self._page_set:
621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return
631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._page_set.pages.remove(self)
641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._page_set = another_page_set
651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._page_set.AddPage(self)
661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def RunNavigateSteps(self, action_runner):
681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    action_runner.NavigateToPage(self)
691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def CanRunOnBrowser(self, browser_info):
711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """Override this to returns whether this page can be run on specific
721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    browser.
731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    Args:
751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      browser_info: an instance of telemetry.core.browser_info.BrowserInfo
761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """
771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    assert browser_info
781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return True
791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def AsDict(self):
811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """Converts a page object to a dict suitable for JSON output."""
821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    d = {
831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      'id': self._id,
841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      'url': self._url,
851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }
861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if self._name:
871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      d['name'] = self._name
881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return d
891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def page_set(self):
921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self._page_set
931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def name(self):
961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self._name
971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def url(self):
1001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self._url
1011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def id(self):
1041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self._id
1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def GetSyntheticDelayCategories(self):
1071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    result = []
1081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    for delay, options in self.synthetic_delays.items():
1091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      options = '%f;%s' % (options.get('target_duration', 0),
1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                           options.get('mode', 'static'))
1111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      result.append('DELAY(%s;%s)' % (delay, options))
1121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return result
1131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def __lt__(self, other):
1151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self.url < other.url
1161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def __cmp__(self, other):
1181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    x = cmp(self.name, other.name)
1191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if x != 0:
1201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return x
1211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return cmp(self.url, other.url)
1221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def __str__(self):
1241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self.url
1251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def AddCustomizeBrowserOptions(self, options):
1271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """ Inherit page overrides this to add customized browser options."""
1281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    pass
1291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def _scheme(self):
1321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return urlparse.urlparse(self.url).scheme
1331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def is_file(self):
1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """Returns True iff this URL points to a file."""
1371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self._scheme == 'file'
1381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def is_local(self):
1411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """Returns True iff this URL is local. This includes chrome:// URLs."""
1421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self._scheme in ['file', 'chrome', 'about']
1431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def file_path(self):
1461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """Returns the path of the file, stripping the scheme and query string."""
1471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    assert self.is_file
1481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # Because ? is a valid character in a filename,
1491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # we have to treat the url as a non-file by removing the scheme.
1501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    parsed_url = urlparse.urlparse(self.url[7:])
1511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return os.path.normpath(os.path.join(
1521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        self._base_dir, parsed_url.netloc + parsed_url.path))
1531320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def file_path_url(self):
1561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """Returns the file path, including the params, query, and fragment."""
1571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    assert self.is_file
1581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    file_path_url = os.path.normpath(os.path.join(self._base_dir, self.url[7:]))
1591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # Preserve trailing slash or backslash.
1601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # It doesn't matter in a file path, but it does matter in a URL.
1611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if self.url.endswith('/'):
1621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      file_path_url += os.sep
1631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return file_path_url
1641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1661320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def serving_dir(self):
1671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    file_path = os.path.realpath(self.file_path)
1681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if os.path.isdir(file_path):
1691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return file_path
1701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    else:
1711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return os.path.dirname(file_path)
1721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def file_safe_name(self):
1751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    """A version of display_name that's safe to use as a filename."""
1761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # Just replace all special characters in the url with underscore.
1771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return re.sub('[^a-zA-Z0-9]', '_', self.display_name)
1781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def display_name(self):
1811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if self.name:
1821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return self.name
1831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if not self.is_file:
1841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return self.url
1851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    all_urls = [p.url.rstrip('/') for p in self.page_set if p.is_file]
1861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    common_prefix = os.path.dirname(os.path.commonprefix(all_urls))
1871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self.url[len(common_prefix):].strip('/')
1881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @property
1901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def archive_path(self):
1911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return self.page_set.WprFilePathForPage(self)
192