1# Copyright 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4import inspect
5import os
6import re
7import urlparse
8
9_next_page_id = 0
10
11class Page(object):
12
13  def __init__(self, url, page_set=None, base_dir=None, name=''):
14    self._url = url
15    self._page_set = page_set
16    # Default value of base_dir is the directory of the file that defines the
17    # class of this page instance.
18    if base_dir is None:
19      base_dir = os.path.dirname(inspect.getfile(self.__class__))
20    self._base_dir = base_dir
21    self._name = name
22
23    global _next_page_id
24    self._id = _next_page_id
25    _next_page_id += 1
26
27    # These attributes can be set dynamically by the page.
28    self.synthetic_delays = dict()
29    self.startup_url = page_set.startup_url if page_set else ''
30    self.credentials = None
31    self.disabled = False
32    self.skip_waits = False
33    self.script_to_evaluate_on_commit = None
34    self._SchemeErrorCheck()
35
36  def _SchemeErrorCheck(self):
37    if not self._scheme:
38      raise ValueError('Must prepend the URL with scheme (e.g. file://)')
39
40    if self.startup_url:
41      startup_url_scheme = urlparse.urlparse(self.startup_url).scheme
42      if not startup_url_scheme:
43        raise ValueError('Must prepend the URL with scheme (e.g. http://)')
44      if startup_url_scheme == 'file':
45        raise ValueError('startup_url with local file scheme is not supported')
46
47  def TransferToPageSet(self, another_page_set):
48    """ Transfer this page to another page set.
49    Args:
50      another_page_set: an instance of telemetry.page.PageSet to transfer this
51          page to.
52    Note:
53      This method removes this page instance from the pages list of its current
54      page_set, so one should be careful not to iterate through the list of
55      pages of a page_set and calling this method.
56      For example, the below loop is erroneous:
57        for p in page_set_A.pages:
58          p.TransferToPageSet(page_set_B.pages)
59    """
60    assert self._page_set
61    if another_page_set is self._page_set:
62      return
63    self._page_set.pages.remove(self)
64    self._page_set = another_page_set
65    self._page_set.AddPage(self)
66
67  def RunNavigateSteps(self, action_runner):
68    action_runner.NavigateToPage(self)
69
70  def CanRunOnBrowser(self, browser_info):
71    """Override this to returns whether this page can be run on specific
72    browser.
73
74    Args:
75      browser_info: an instance of telemetry.core.browser_info.BrowserInfo
76    """
77    assert browser_info
78    return True
79
80  def AsDict(self):
81    """Converts a page object to a dict suitable for JSON output."""
82    d = {
83      'id': self._id,
84      'url': self._url,
85    }
86    if self._name:
87      d['name'] = self._name
88    return d
89
90  @property
91  def page_set(self):
92    return self._page_set
93
94  @property
95  def name(self):
96    return self._name
97
98  @property
99  def url(self):
100    return self._url
101
102  @property
103  def id(self):
104    return self._id
105
106  def GetSyntheticDelayCategories(self):
107    result = []
108    for delay, options in self.synthetic_delays.items():
109      options = '%f;%s' % (options.get('target_duration', 0),
110                           options.get('mode', 'static'))
111      result.append('DELAY(%s;%s)' % (delay, options))
112    return result
113
114  def __lt__(self, other):
115    return self.url < other.url
116
117  def __cmp__(self, other):
118    x = cmp(self.name, other.name)
119    if x != 0:
120      return x
121    return cmp(self.url, other.url)
122
123  def __str__(self):
124    return self.url
125
126  def AddCustomizeBrowserOptions(self, options):
127    """ Inherit page overrides this to add customized browser options."""
128    pass
129
130  @property
131  def _scheme(self):
132    return urlparse.urlparse(self.url).scheme
133
134  @property
135  def is_file(self):
136    """Returns True iff this URL points to a file."""
137    return self._scheme == 'file'
138
139  @property
140  def is_local(self):
141    """Returns True iff this URL is local. This includes chrome:// URLs."""
142    return self._scheme in ['file', 'chrome', 'about']
143
144  @property
145  def file_path(self):
146    """Returns the path of the file, stripping the scheme and query string."""
147    assert self.is_file
148    # Because ? is a valid character in a filename,
149    # we have to treat the url as a non-file by removing the scheme.
150    parsed_url = urlparse.urlparse(self.url[7:])
151    return os.path.normpath(os.path.join(
152        self._base_dir, parsed_url.netloc + parsed_url.path))
153
154  @property
155  def file_path_url(self):
156    """Returns the file path, including the params, query, and fragment."""
157    assert self.is_file
158    file_path_url = os.path.normpath(os.path.join(self._base_dir, self.url[7:]))
159    # Preserve trailing slash or backslash.
160    # It doesn't matter in a file path, but it does matter in a URL.
161    if self.url.endswith('/'):
162      file_path_url += os.sep
163    return file_path_url
164
165  @property
166  def serving_dir(self):
167    file_path = os.path.realpath(self.file_path)
168    if os.path.isdir(file_path):
169      return file_path
170    else:
171      return os.path.dirname(file_path)
172
173  @property
174  def file_safe_name(self):
175    """A version of display_name that's safe to use as a filename."""
176    # Just replace all special characters in the url with underscore.
177    return re.sub('[^a-zA-Z0-9]', '_', self.display_name)
178
179  @property
180  def display_name(self):
181    if self.name:
182      return self.name
183    if not self.is_file:
184      return self.url
185    all_urls = [p.url.rstrip('/') for p in self.page_set if p.is_file]
186    common_prefix = os.path.dirname(os.path.commonprefix(all_urls))
187    return self.url[len(common_prefix):].strip('/')
188
189  @property
190  def archive_path(self):
191    return self.page_set.WprFilePathForPage(self)
192