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