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. 4 5 import os 6 7 from telemetry import decorators 8 from telemetry.core import browser_credentials 9 from telemetry.core import exceptions 10 from telemetry.core import extension_dict 11 from telemetry.core import local_server 12 from telemetry.core import memory_cache_http_server 13 from telemetry.core import tab_list 14 from telemetry.core import wpr_modes 15 from telemetry.core import wpr_server 16 from telemetry.core.backends import browser_backend 17 18 19 class Browser(object): 20 """A running browser instance that can be controlled in a limited way. 21 22 To create a browser instance, use browser_finder.FindBrowser. 23 24 Be sure to clean up after yourself by calling Close() when you are done with 25 the browser. Or better yet: 26 browser_to_create = FindBrowser(options) 27 with browser_to_create.Create() as browser: 28 ... do all your operations on browser here 29 """ 30 def __init__(self, backend, platform_backend, archive_path, 31 append_to_existing_wpr, make_javascript_deterministic, 32 credentials_path): 33 assert platform_backend.platform != None 34 35 self._browser_backend = backend 36 self._platform_backend = platform_backend 37 self._wpr_server = None 38 self._local_server_controller = local_server.LocalServerController(backend) 39 self._tabs = tab_list.TabList(backend.tab_list_backend) 40 self.credentials = browser_credentials.BrowserCredentials() 41 self.credentials.credentials_path = credentials_path 42 self._platform_backend.DidCreateBrowser(self, self._browser_backend) 43 44 self.SetReplayArchivePath(archive_path, 45 append_to_existing_wpr, 46 make_javascript_deterministic) 47 48 browser_options = self._browser_backend.browser_options 49 self.platform.FlushDnsCache() 50 if browser_options.clear_sytem_cache_for_browser_and_profile_on_start: 51 if self.platform.CanFlushIndividualFilesFromSystemCache(): 52 self.platform.FlushSystemCacheForDirectory( 53 self._browser_backend.profile_directory) 54 self.platform.FlushSystemCacheForDirectory( 55 self._browser_backend.browser_directory) 56 else: 57 self.platform.FlushEntireSystemCache() 58 59 self._browser_backend.SetBrowser(self) 60 self._browser_backend.Start() 61 self._platform_backend.DidStartBrowser(self, self._browser_backend) 62 63 def __enter__(self): 64 return self 65 66 def __exit__(self, *args): 67 self.Close() 68 69 @property 70 def platform(self): 71 return self._platform_backend.platform 72 73 @property 74 def browser_type(self): 75 return self._browser_backend.browser_type 76 77 @property 78 def supports_extensions(self): 79 return self._browser_backend.supports_extensions 80 81 @property 82 def supports_tab_control(self): 83 return self._browser_backend.supports_tab_control 84 85 @property 86 def synthetic_gesture_source_type(self): 87 return self._browser_backend.browser_options.synthetic_gesture_source_type 88 89 @property 90 def tabs(self): 91 return self._tabs 92 93 @property 94 def foreground_tab(self): 95 for i in xrange(len(self._tabs)): 96 # The foreground tab is the first (only) one that isn't hidden. 97 # This only works through luck on Android, due to crbug.com/322544 98 # which means that tabs that have never been in the foreground return 99 # document.hidden as false; however in current code the Android foreground 100 # tab is always tab 0, which will be the first one that isn't hidden 101 if self._tabs[i].EvaluateJavaScript('!document.hidden'): 102 return self._tabs[i] 103 raise Exception("No foreground tab found") 104 105 @property 106 @decorators.Cache 107 def extensions(self): 108 if not self.supports_extensions: 109 raise browser_backend.ExtensionsNotSupportedException( 110 'Extensions not supported') 111 return extension_dict.ExtensionDict(self._browser_backend.extension_backend) 112 113 def _GetStatsCommon(self, pid_stats_function): 114 browser_pid = self._browser_backend.pid 115 result = { 116 'Browser': dict(pid_stats_function(browser_pid), **{'ProcessCount': 1}), 117 'Renderer': {'ProcessCount': 0}, 118 'Gpu': {'ProcessCount': 0}, 119 'Other': {'ProcessCount': 0} 120 } 121 process_count = 1 122 for child_pid in self._platform_backend.GetChildPids(browser_pid): 123 try: 124 child_cmd_line = self._platform_backend.GetCommandLine(child_pid) 125 child_stats = pid_stats_function(child_pid) 126 except exceptions.ProcessGoneException: 127 # It is perfectly fine for a process to have gone away between calling 128 # GetChildPids() and then further examining it. 129 continue 130 child_process_name = self._browser_backend.GetProcessName(child_cmd_line) 131 process_name_type_key_map = {'gpu-process': 'Gpu', 'renderer': 'Renderer'} 132 if child_process_name in process_name_type_key_map: 133 child_process_type_key = process_name_type_key_map[child_process_name] 134 else: 135 # TODO: identify other process types (zygote, plugin, etc), instead of 136 # lumping them in a single category. 137 child_process_type_key = 'Other' 138 result[child_process_type_key]['ProcessCount'] += 1 139 for k, v in child_stats.iteritems(): 140 if k in result[child_process_type_key]: 141 result[child_process_type_key][k] += v 142 else: 143 result[child_process_type_key][k] = v 144 process_count += 1 145 for v in result.itervalues(): 146 if v['ProcessCount'] > 1: 147 for k in v.keys(): 148 if k.endswith('Peak'): 149 del v[k] 150 del v['ProcessCount'] 151 result['ProcessCount'] = process_count 152 return result 153 154 @property 155 def memory_stats(self): 156 """Returns a dict of memory statistics for the browser: 157 { 'Browser': { 158 'VM': R, 159 'VMPeak': S, 160 'WorkingSetSize': T, 161 'WorkingSetSizePeak': U, 162 'ProportionalSetSize': V, 163 'PrivateDirty': W 164 }, 165 'Gpu': { 166 'VM': R, 167 'VMPeak': S, 168 'WorkingSetSize': T, 169 'WorkingSetSizePeak': U, 170 'ProportionalSetSize': V, 171 'PrivateDirty': W 172 }, 173 'Renderer': { 174 'VM': R, 175 'VMPeak': S, 176 'WorkingSetSize': T, 177 'WorkingSetSizePeak': U, 178 'ProportionalSetSize': V, 179 'PrivateDirty': W 180 }, 181 'SystemCommitCharge': X, 182 'SystemTotalPhysicalMemory': Y, 183 'ProcessCount': Z, 184 } 185 Any of the above keys may be missing on a per-platform basis. 186 """ 187 self._platform_backend.PurgeUnpinnedMemory() 188 result = self._GetStatsCommon(self._platform_backend.GetMemoryStats) 189 commit_charge = self._platform_backend.GetSystemCommitCharge() 190 if commit_charge: 191 result['SystemCommitCharge'] = commit_charge 192 total = self._platform_backend.GetSystemTotalPhysicalMemory() 193 if total: 194 result['SystemTotalPhysicalMemory'] = total 195 return result 196 197 @property 198 def cpu_stats(self): 199 """Returns a dict of cpu statistics for the system. 200 { 'Browser': { 201 'CpuProcessTime': S, 202 'TotalTime': T 203 }, 204 'Gpu': { 205 'CpuProcessTime': S, 206 'TotalTime': T 207 }, 208 'Renderer': { 209 'CpuProcessTime': S, 210 'TotalTime': T 211 } 212 } 213 Any of the above keys may be missing on a per-platform basis. 214 """ 215 result = self._GetStatsCommon(self._platform_backend.GetCpuStats) 216 del result['ProcessCount'] 217 218 # We want a single time value, not the sum for all processes. 219 cpu_timestamp = self._platform_backend.GetCpuTimestamp() 220 for process_type in result: 221 # Skip any process_types that are empty 222 if not len(result[process_type]): 223 continue 224 result[process_type].update(cpu_timestamp) 225 return result 226 227 @property 228 def io_stats(self): 229 """Returns a dict of IO statistics for the browser: 230 { 'Browser': { 231 'ReadOperationCount': W, 232 'WriteOperationCount': X, 233 'ReadTransferCount': Y, 234 'WriteTransferCount': Z 235 }, 236 'Gpu': { 237 'ReadOperationCount': W, 238 'WriteOperationCount': X, 239 'ReadTransferCount': Y, 240 'WriteTransferCount': Z 241 }, 242 'Renderer': { 243 'ReadOperationCount': W, 244 'WriteOperationCount': X, 245 'ReadTransferCount': Y, 246 'WriteTransferCount': Z 247 } 248 } 249 """ 250 result = self._GetStatsCommon(self._platform_backend.GetIOStats) 251 del result['ProcessCount'] 252 return result 253 254 def Close(self): 255 """Closes this browser.""" 256 if self._browser_backend.IsBrowserRunning(): 257 self._platform_backend.WillCloseBrowser(self, self._browser_backend) 258 259 if self._wpr_server: 260 self._wpr_server.Close() 261 self._wpr_server = None 262 263 self._local_server_controller.Close() 264 self._browser_backend.Close() 265 self.credentials = None 266 267 @property 268 def http_server(self): 269 return self._local_server_controller.GetRunningServer( 270 memory_cache_http_server.MemoryCacheHTTPServer, None) 271 272 def SetHTTPServerDirectories(self, paths): 273 """Returns True if the HTTP server was started, False otherwise.""" 274 if isinstance(paths, basestring): 275 paths = set([paths]) 276 paths = set(os.path.realpath(p) for p in paths) 277 278 # If any path is in a subdirectory of another, remove the subdirectory. 279 duplicates = set() 280 for parent_path in paths: 281 for sub_path in paths: 282 if parent_path == sub_path: 283 continue 284 if os.path.commonprefix((parent_path, sub_path)) == parent_path: 285 duplicates.add(sub_path) 286 paths -= duplicates 287 288 if self.http_server: 289 if paths and self.http_server.paths == paths: 290 return False 291 292 self.http_server.Close() 293 294 if not paths: 295 return False 296 297 server = memory_cache_http_server.MemoryCacheHTTPServer(paths) 298 self.StartLocalServer(server) 299 return True 300 301 def StartLocalServer(self, server): 302 """Starts a LocalServer and associates it with this browser. 303 304 It will be closed when the browser closes. 305 """ 306 self._local_server_controller.StartServer(server) 307 308 @property 309 def local_servers(self): 310 """Returns the currently running local servers.""" 311 return self._local_server_controller.local_servers 312 313 def SetReplayArchivePath(self, archive_path, append_to_existing_wpr=False, 314 make_javascript_deterministic=True): 315 if self._wpr_server: 316 self._wpr_server.Close() 317 self._wpr_server = None 318 319 if not archive_path: 320 return None 321 322 if self._browser_backend.wpr_mode == wpr_modes.WPR_OFF: 323 return 324 325 use_record_mode = self._browser_backend.wpr_mode == wpr_modes.WPR_RECORD 326 if not use_record_mode: 327 assert os.path.isfile(archive_path) 328 329 self._wpr_server = wpr_server.ReplayServer( 330 self._browser_backend, 331 archive_path, 332 use_record_mode, 333 append_to_existing_wpr, 334 make_javascript_deterministic) 335 336 def GetStandardOutput(self): 337 return self._browser_backend.GetStandardOutput() 338 339 def GetStackTrace(self): 340 return self._browser_backend.GetStackTrace() 341 342 @property 343 def supports_system_info(self): 344 return self._browser_backend.supports_system_info 345 346 def GetSystemInfo(self): 347 """Returns low-level information about the system, if available. 348 349 See the documentation of the SystemInfo class for more details.""" 350 return self._browser_backend.GetSystemInfo() 351 352 # TODO: Remove after call to Start() has been removed from 353 # related authotest files. 354 def Start(self): 355 pass 356