Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Chrome remote inspector utility for pyauto tests. | 6 """Chrome remote inspector utility for pyauto tests. |
| 7 | 7 |
| 8 This script provides a python interface that acts as a front-end for Chrome's | 8 This script provides a python interface that acts as a front-end for Chrome's |
| 9 remote inspector module, communicating via sockets to interact with Chrome in | 9 remote inspector module, communicating via sockets to interact with Chrome in |
| 10 the same way that the Developer Tools does. This -- in theory -- should allow | 10 the same way that the Developer Tools does. This -- in theory -- should allow |
| (...skipping 263 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 274 class _RemoteInspectorThread(threading.Thread): | 274 class _RemoteInspectorThread(threading.Thread): |
| 275 """Manages communication using Chrome's remote inspector protocol. | 275 """Manages communication using Chrome's remote inspector protocol. |
| 276 | 276 |
| 277 This class works in conjunction with the _DevToolsSocketClient class to | 277 This class works in conjunction with the _DevToolsSocketClient class to |
| 278 communicate with a remote Chrome instance following the remote inspector | 278 communicate with a remote Chrome instance following the remote inspector |
| 279 communication protocol in WebKit. This class performs the higher-level work | 279 communication protocol in WebKit. This class performs the higher-level work |
| 280 of managing request and reply messages, whereas _DevToolsSocketClient handles | 280 of managing request and reply messages, whereas _DevToolsSocketClient handles |
| 281 the lower-level work of socket communication. | 281 the lower-level work of socket communication. |
| 282 """ | 282 """ |
| 283 | 283 |
| 284 def __init__(self, url, tab_index, tab_filter, verbose, show_socket_messages): | 284 def __init__(self, url, tab_index, tab_filter, verbose, show_socket_messages, |
| 285 agent_name): | |
| 285 """Initialize. | 286 """Initialize. |
| 286 | 287 |
| 287 Args: | 288 Args: |
| 288 url: The base URL to connent to. | 289 url: The base URL to connent to. |
| 289 tab_index: The integer index of the tab in the remote Chrome instance to | 290 tab_index: The integer index of the tab in the remote Chrome instance to |
| 290 use for snapshotting. | 291 use for snapshotting. |
| 291 tab_filter: When specified, is run over tabs of the remote Chrome | 292 tab_filter: When specified, is run over tabs of the remote Chrome |
| 292 instances to choose which one to connect to. | 293 instances to choose which one to connect to. |
| 293 verbose: A boolean indicating whether or not to use verbose logging. | 294 verbose: A boolean indicating whether or not to use verbose logging. |
| 294 show_socket_messages: A boolean indicating whether or not to show the | 295 show_socket_messages: A boolean indicating whether or not to show the |
| 295 socket messages sent/received when communicating with the remote | 296 socket messages sent/received when communicating with the remote |
| 296 Chrome instance. | 297 Chrome instance. |
| 297 """ | 298 """ |
| 298 threading.Thread.__init__(self) | 299 threading.Thread.__init__(self) |
| 299 self._logger = logging.getLogger('_RemoteInspectorThread') | 300 self._logger = logging.getLogger('_RemoteInspectorThread') |
| 300 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose]) | 301 self._logger.setLevel([logging.WARNING, logging.DEBUG][verbose]) |
| 301 | 302 |
| 302 self._killed = False | 303 self._killed = False |
| 303 self._requests = [] | 304 self._requests = [] |
| 304 self._action_queue = [] | 305 self._action_queue = [] |
| 305 self._action_queue_condition = threading.Condition() | 306 self._action_queue_condition = threading.Condition() |
| 306 self._action_specific_callback = None # Callback only for current action. | 307 self._action_specific_callback = None # Callback only for current action. |
| 307 self._action_specific_callback_lock = threading.Lock() | 308 self._action_specific_callback_lock = threading.Lock() |
| 308 self._general_callbacks = [] # General callbacks that can be long-lived. | 309 self._general_callbacks = [] # General callbacks that can be long-lived. |
| 309 self._general_callbacks_lock = threading.Lock() | 310 self._general_callbacks_lock = threading.Lock() |
| 310 self._condition_to_wait = None | 311 self._condition_to_wait = None |
| 312 self._agent_name = agent_name | |
| 311 | 313 |
| 312 # Create a DevToolsSocket client and wait for it to complete the remote | 314 # Create a DevToolsSocket client and wait for it to complete the remote |
| 313 # debugging protocol handshake with the remote Chrome instance. | 315 # debugging protocol handshake with the remote Chrome instance. |
| 314 result = self._IdentifyDevToolsSocketConnectionInfo( | 316 result = self._IdentifyDevToolsSocketConnectionInfo( |
| 315 url, tab_index, tab_filter) | 317 url, tab_index, tab_filter) |
| 316 self._client = _DevToolsSocketClient( | 318 self._client = _DevToolsSocketClient( |
| 317 verbose, show_socket_messages, result['host'], result['port'], | 319 verbose, show_socket_messages, result['host'], result['port'], |
| 318 result['path']) | 320 result['path']) |
| 319 self._client.inspector_thread = self | 321 self._client.inspector_thread = self |
| 320 while asyncore.socket_map: | 322 while asyncore.socket_map: |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 504 return request | 506 return request |
| 505 return None | 507 return None |
| 506 | 508 |
| 507 def _FillInParams(self, request): | 509 def _FillInParams(self, request): |
| 508 """Fills in parameters for requests as necessary before the request is sent. | 510 """Fills in parameters for requests as necessary before the request is sent. |
| 509 | 511 |
| 510 Args: | 512 Args: |
| 511 request: The _DevToolsSocketRequest object associated with a request | 513 request: The _DevToolsSocketRequest object associated with a request |
| 512 message that is about to be sent. | 514 message that is about to be sent. |
| 513 """ | 515 """ |
| 514 if request.method == 'Profiler.takeHeapSnapshot': | 516 if request.method == self._agent_name +'.takeHeapSnapshot': |
| 515 # We always want detailed v8 heap snapshot information. | 517 # We always want detailed v8 heap snapshot information. |
| 516 request.params = {'detailed': True} | 518 request.params = {'detailed': True} |
| 517 elif request.method == 'Profiler.getHeapSnapshot': | 519 elif request.method == self._agent_name + '.getHeapSnapshot': |
| 518 # To actually request the snapshot data from a previously-taken snapshot, | 520 # To actually request the snapshot data from a previously-taken snapshot, |
| 519 # we need to specify the unique uid of the snapshot we want. | 521 # we need to specify the unique uid of the snapshot we want. |
| 520 # The relevant uid should be contained in the last | 522 # The relevant uid should be contained in the last |
| 521 # 'Profiler.takeHeapSnapshot' request object. | 523 # 'Profiler.takeHeapSnapshot' request object. |
| 522 last_req = self._GetLatestRequestOfType(request, | 524 last_req = self._GetLatestRequestOfType(request, |
| 523 'Profiler.takeHeapSnapshot') | 525 self._agent_name + '.takeHeapSnapshot') |
| 524 if last_req and 'uid' in last_req.results: | 526 if last_req and 'uid' in last_req.results: |
| 525 request.params = {'uid': last_req.results['uid']} | 527 request.params = {'uid': last_req.results['uid']} |
| 526 elif request.method == 'Profiler.getProfile': | 528 elif request.method == self._agent_name + '.getProfile': |
| 527 # TODO(eustas): Remove this case after M27 is released. | 529 # TODO(eustas): Remove this case after M27 is released. |
| 528 last_req = self._GetLatestRequestOfType(request, | 530 last_req = self._GetLatestRequestOfType(request, |
| 529 'Profiler.takeHeapSnapshot') | 531 self._agent_name + '.takeHeapSnapshot') |
| 530 if last_req and 'uid' in last_req.results: | 532 if last_req and 'uid' in last_req.results: |
| 531 request.params = {'type': 'HEAP', 'uid': last_req.results['uid']} | 533 request.params = {'type': 'HEAP', 'uid': last_req.results['uid']} |
| 532 | 534 |
| 533 @staticmethod | 535 @staticmethod |
| 534 def _IdentifyDevToolsSocketConnectionInfo(url, tab_index, tab_filter): | 536 def _IdentifyDevToolsSocketConnectionInfo(url, tab_index, tab_filter): |
| 535 """Identifies DevToolsSocket connection info from a remote Chrome instance. | 537 """Identifies DevToolsSocket connection info from a remote Chrome instance. |
| 536 | 538 |
| 537 Args: | 539 Args: |
| 538 url: The base URL to connent to. | 540 url: The base URL to connent to. |
| 539 tab_index: The integer index of the tab in the remote Chrome instance to | 541 tab_index: The integer index of the tab in the remote Chrome instance to |
| (...skipping 309 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 849 | 851 |
| 850 # Creating _RemoteInspectorThread might raise an exception. This prevents an | 852 # Creating _RemoteInspectorThread might raise an exception. This prevents an |
| 851 # AttributeError in the destructor. | 853 # AttributeError in the destructor. |
| 852 self._remote_inspector_thread = None | 854 self._remote_inspector_thread = None |
| 853 self._remote_inspector_driver_thread = None | 855 self._remote_inspector_driver_thread = None |
| 854 | 856 |
| 855 # TODO(dennisjeffrey): Do not assume port 9222. The port should be passed | 857 # TODO(dennisjeffrey): Do not assume port 9222. The port should be passed |
| 856 # as input to this function. | 858 # as input to this function. |
| 857 url = 'http://localhost:9222' | 859 url = 'http://localhost:9222' |
| 858 | 860 |
| 859 self._webkit_version = self._GetWebkitVersion(url) | 861 self._version = self._GetVersion(url) |
| 862 | |
| 863 # TODO(loislo): Remove this hack after M28 is released. | |
| 864 self._agent_name = 'Profiler' | |
| 865 if self._IsBrowserDayNumberMoreThan(1470): | |
| 866 self._agent_name = 'HeapProfiler' | |
| 860 | 867 |
| 861 # Start up a thread for long-term communication with the remote inspector. | 868 # Start up a thread for long-term communication with the remote inspector. |
| 862 self._remote_inspector_thread = _RemoteInspectorThread( | 869 self._remote_inspector_thread = _RemoteInspectorThread( |
| 863 url, tab_index, tab_filter, verbose, show_socket_messages) | 870 url, tab_index, tab_filter, verbose, show_socket_messages, |
| 871 self._agent_name) | |
| 864 self._remote_inspector_thread.start() | 872 self._remote_inspector_thread.start() |
| 865 # At this point, a connection has already been made to the remote inspector. | 873 # At this point, a connection has already been made to the remote inspector. |
| 866 | 874 |
| 867 # This thread calls asyncore.loop, which activates the channel service. | 875 # This thread calls asyncore.loop, which activates the channel service. |
| 868 self._remote_inspector_driver_thread = _RemoteInspectorDriverThread() | 876 self._remote_inspector_driver_thread = _RemoteInspectorDriverThread() |
| 869 self._remote_inspector_driver_thread.start() | 877 self._remote_inspector_driver_thread.start() |
| 870 | 878 |
| 871 def __del__(self): | 879 def __del__(self): |
| 872 """Called on destruction of this object.""" | 880 """Called on destruction of this object.""" |
| 873 self.Stop() | 881 self.Stop() |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 891 { | 899 { |
| 892 'url': string, # URL of the webpage that was snapshotted. | 900 'url': string, # URL of the webpage that was snapshotted. |
| 893 'raw_data': string, # The raw data as JSON string. | 901 'raw_data': string, # The raw data as JSON string. |
| 894 'total_v8_node_count': integer, # Total number of nodes in the v8 heap. | 902 'total_v8_node_count': integer, # Total number of nodes in the v8 heap. |
| 895 # Only if |include_summary| is True. | 903 # Only if |include_summary| is True. |
| 896 'total_heap_size': integer, # Total v8 heap size (number of bytes). | 904 'total_heap_size': integer, # Total v8 heap size (number of bytes). |
| 897 # Only if |include_summary| is True. | 905 # Only if |include_summary| is True. |
| 898 } | 906 } |
| 899 """ | 907 """ |
| 900 # TODO(eustas): Remove this hack after M27 is released. | 908 # TODO(eustas): Remove this hack after M27 is released. |
| 901 if self._IsWebkitVersionNotOlderThan(537, 27): | 909 if self._IsPlatformVersionNotOlderThan(537, 27): |
| 902 get_heap_snapshot_method = 'Profiler.getHeapSnapshot' | 910 get_heap_snapshot_method = '.getHeapSnapshot' |
| 903 else: | 911 else: |
| 904 get_heap_snapshot_method = 'Profiler.getProfile' | 912 get_heap_snapshot_method = '.getProfile' |
| 905 | 913 |
| 906 HEAP_SNAPSHOT_MESSAGES = [ | 914 HEAP_SNAPSHOT_MESSAGES = [ |
| 907 ('Page.getResourceTree', {}), | 915 ('Page.getResourceTree', {}), |
| 908 ('Debugger.enable', {}), | 916 ('Debugger.enable', {}), |
| 909 ('Profiler.clearProfiles', {}), | 917 (self._agent_name + '.clearProfiles', {}), |
| 910 ('Profiler.takeHeapSnapshot', {}), | 918 (self._agent_name + '.takeHeapSnapshot', {}), |
| 911 (get_heap_snapshot_method, {}), | 919 (self._agent_name + get_heap_snapshot_method, {}), |
| 912 ] | 920 ] |
| 913 | 921 |
| 914 self._current_heap_snapshot = [] | 922 self._current_heap_snapshot = [] |
| 915 self._url = '' | 923 self._url = '' |
| 916 self._collected_heap_snapshot_data = {} | 924 self._collected_heap_snapshot_data = {} |
| 917 | 925 |
| 918 done_condition = threading.Condition() | 926 done_condition = threading.Condition() |
| 919 | 927 |
| 920 def HandleReply(reply_dict): | 928 def HandleReply(reply_dict): |
| 921 """Processes a reply message received from the remote Chrome instance. | 929 """Processes a reply message received from the remote Chrome instance. |
| 922 | 930 |
| 923 Args: | 931 Args: |
| 924 reply_dict: A dictionary object representing the reply message received | 932 reply_dict: A dictionary object representing the reply message received |
| 925 from the remote inspector. | 933 from the remote inspector. |
| 926 """ | 934 """ |
| 927 if 'result' in reply_dict: | 935 if 'result' in reply_dict: |
| 928 # This is the result message associated with a previously-sent request. | 936 # This is the result message associated with a previously-sent request. |
| 929 request = self._remote_inspector_thread.GetRequestWithId( | 937 request = self._remote_inspector_thread.GetRequestWithId( |
| 930 reply_dict['id']) | 938 reply_dict['id']) |
| 931 if 'frameTree' in reply_dict['result']: | 939 if 'frameTree' in reply_dict['result']: |
| 932 self._url = reply_dict['result']['frameTree']['frame']['url'] | 940 self._url = reply_dict['result']['frameTree']['frame']['url'] |
| 933 elif 'method' in reply_dict: | 941 elif 'method' in reply_dict: |
| 934 # This is an auxiliary message sent from the remote Chrome instance. | 942 # This is an auxiliary message sent from the remote Chrome instance. |
| 935 if reply_dict['method'] == 'Profiler.addProfileHeader': | 943 if reply_dict['method'] == self._agent_name + '.addProfileHeader': |
| 936 snapshot_req = ( | 944 snapshot_req = ( |
| 937 self._remote_inspector_thread.GetFirstUnfulfilledRequest( | 945 self._remote_inspector_thread.GetFirstUnfulfilledRequest( |
| 938 'Profiler.takeHeapSnapshot')) | 946 self._agent_name + '.takeHeapSnapshot')) |
| 939 if snapshot_req: | 947 if snapshot_req: |
| 940 snapshot_req.results['uid'] = reply_dict['params']['header']['uid'] | 948 snapshot_req.results['uid'] = reply_dict['params']['header']['uid'] |
| 941 elif reply_dict['method'] == 'Profiler.addHeapSnapshotChunk': | 949 elif reply_dict['method'] == self._agent_name + '.addHeapSnapshotChunk': |
| 942 self._current_heap_snapshot.append(reply_dict['params']['chunk']) | 950 self._current_heap_snapshot.append(reply_dict['params']['chunk']) |
| 943 elif reply_dict['method'] == 'Profiler.finishHeapSnapshot': | 951 elif reply_dict['method'] == self._agent_name + '.finishHeapSnapshot': |
| 944 # A heap snapshot has been completed. Analyze and output the data. | 952 # A heap snapshot has been completed. Analyze and output the data. |
| 945 self._logger.debug('Heap snapshot taken: %s', self._url) | 953 self._logger.debug('Heap snapshot taken: %s', self._url) |
| 946 # TODO(dennisjeffrey): Parse the heap snapshot on-the-fly as the data | 954 # TODO(dennisjeffrey): Parse the heap snapshot on-the-fly as the data |
| 947 # is coming in over the wire, so we can avoid storing the entire | 955 # is coming in over the wire, so we can avoid storing the entire |
| 948 # snapshot string in memory. | 956 # snapshot string in memory. |
| 949 raw_snapshot_data = ''.join(self._current_heap_snapshot) | 957 raw_snapshot_data = ''.join(self._current_heap_snapshot) |
| 950 self._collected_heap_snapshot_data = { | 958 self._collected_heap_snapshot_data = { |
| 951 'url': self._url, | 959 'url': self._url, |
| 952 'raw_data': raw_snapshot_data} | 960 'raw_data': raw_snapshot_data} |
| 953 if include_summary: | 961 if include_summary: |
| (...skipping 320 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1274 A human-readable string representation of the given number of bytes. | 1282 A human-readable string representation of the given number of bytes. |
| 1275 """ | 1283 """ |
| 1276 if num_bytes < 1024: | 1284 if num_bytes < 1024: |
| 1277 return '%d B' % num_bytes | 1285 return '%d B' % num_bytes |
| 1278 elif num_bytes < 1048576: | 1286 elif num_bytes < 1048576: |
| 1279 return '%.2f KB' % (num_bytes / 1024.0) | 1287 return '%.2f KB' % (num_bytes / 1024.0) |
| 1280 else: | 1288 else: |
| 1281 return '%.2f MB' % (num_bytes / 1048576.0) | 1289 return '%.2f MB' % (num_bytes / 1048576.0) |
| 1282 | 1290 |
| 1283 @staticmethod | 1291 @staticmethod |
| 1284 def _GetWebkitVersion(endpoint): | 1292 def _GetVersion(endpoint): |
| 1285 """Fetches Webkit version information from a remote Chrome instance. | 1293 """Fetches version information from a remote Chrome instance. |
| 1286 | 1294 |
| 1287 Args: | 1295 Args: |
| 1288 endpoint: The base URL to connent to. | 1296 endpoint: The base URL to connent to. |
| 1289 | 1297 |
| 1290 Returns: | 1298 Returns: |
| 1291 A dictionary containing Webkit version information: | 1299 A dictionary containing Brownser and Content version information: |
|
dennis_jeffrey
2013/04/10 17:21:34
Brownser --> Browser
| |
| 1292 { | 1300 { |
| 1293 'major': integer, | 1301 'Browser': { |
| 1294 'minor': integer, | 1302 'major': integer, |
| 1303 'minor': integer, | |
| 1304 'fix': integer, | |
| 1305 'day': integer | |
| 1306 }, | |
| 1307 'Content': { | |
| 1308 'name': string, | |
| 1309 'major': integer, | |
| 1310 'minor': integer | |
| 1311 } | |
| 1295 } | 1312 } |
| 1296 | 1313 |
| 1297 Raises: | 1314 Raises: |
| 1298 RuntimeError: When Webkit version info can't be fetched or parsed. | 1315 RuntimeError: When Browser version info can't be fetched or parsed. |
| 1299 """ | 1316 """ |
| 1300 try: | 1317 try: |
| 1301 f = urllib2.urlopen(endpoint + '/json/version') | 1318 f = urllib2.urlopen(endpoint + '/json/version') |
| 1302 result = f.read(); | 1319 result = f.read(); |
| 1303 result = simplejson.loads(result) | 1320 result = simplejson.loads(result) |
| 1304 except urllib2.URLError, e: | 1321 except urllib2.URLError, e: |
| 1305 raise RuntimeError( | 1322 raise RuntimeError( |
| 1306 'Error accessing Chrome instance debugging port: ' + str(e)) | 1323 'Error accessing Chrome instance debugging port: ' + str(e)) |
| 1307 | 1324 |
| 1325 if 'Browser' not in result: | |
| 1326 raise RuntimeError('Browser version is not specified.') | |
| 1327 | |
| 1328 parsed = re.search('^Chrome\/(\d+).(\d+).(\d+).(\d+)', result['Browser']) | |
| 1329 if parsed is None: | |
| 1330 raise RuntimeError('Browser-Version cannot be parsed.') | |
| 1331 try: | |
| 1332 day = int(parsed.group(3)) | |
| 1333 browser_info = { | |
| 1334 'major': int(parsed.group(1)), | |
| 1335 'minor': int(parsed.group(2)), | |
| 1336 'day': day, | |
| 1337 'fix': int(parsed.group(4)), | |
| 1338 } | |
| 1339 except ValueError: | |
| 1340 raise RuntimeError('Browser-Version cannot be parsed.') | |
| 1341 | |
| 1308 if 'WebKit-Version' not in result: | 1342 if 'WebKit-Version' not in result: |
| 1309 raise RuntimeError('WebKit-Version is not specified.') | 1343 raise RuntimeError('Content-Version is not specified.') |
| 1310 | 1344 |
| 1311 parsed = re.search('^(\d+)\.(\d+)', result['WebKit-Version']) | 1345 parsed = re.search('^(\d+)\.(\d+)', result['WebKit-Version']) |
| 1312 if parsed is None: | 1346 if parsed is None: |
| 1313 raise RuntimeError('WebKit-Version cannot be parsed.') | 1347 raise RuntimeError('Content-Version cannot be parsed.') |
| 1314 | 1348 |
| 1315 try: | 1349 try: |
| 1316 info = { | 1350 platform_info = { |
| 1351 'name': 'Blink' if day > 1464 else 'WebKit', | |
| 1317 'major': int(parsed.group(1)), | 1352 'major': int(parsed.group(1)), |
| 1318 'minor': int(parsed.group(2)), | 1353 'minor': int(parsed.group(2)), |
| 1319 } | 1354 } |
| 1320 except ValueError: | 1355 except ValueError: |
| 1321 raise RuntimeError('WebKit-Version cannot be parsed.') | 1356 raise RuntimeError('WebKit-Version cannot be parsed.') |
| 1322 | 1357 |
| 1323 return info | 1358 return { |
| 1359 'browser': browser_info, | |
| 1360 'platform': platform_info | |
| 1361 } | |
| 1324 | 1362 |
| 1325 def _IsWebkitVersionNotOlderThan(self, major, minor): | 1363 def _IsContentVersionNotOlderThan(self, major, minor): |
| 1326 """Compares remote Webkit version with specified one. | 1364 """Compares remote Browser Content version with specified one. |
| 1327 | 1365 |
| 1328 Args: | 1366 Args: |
| 1329 major: Major Webkit version. | 1367 major: Major Webkit version. |
| 1330 minor: Minor Webkit version. | 1368 minor: Minor Webkit version. |
| 1331 | 1369 |
| 1332 Returns: | 1370 Returns: |
| 1333 True if remote Webkit version is same or newer than specified, | 1371 True if remote Content version is same or newer than specified, |
| 1334 False otherwise. | 1372 False otherwise. |
| 1335 | 1373 |
| 1336 Raises: | 1374 Raises: |
| 1337 RuntimeError: If remote Webkit version hasn't been fetched yet. | 1375 RuntimeError: If remote Content version hasn't been fetched yet. |
| 1338 """ | 1376 """ |
| 1339 if not hasattr(self, '_webkit_version'): | 1377 if not hasattr(self, '_version'): |
| 1340 raise RuntimeError('WebKit version has not been fetched yet.') | 1378 raise RuntimeError('Browser version has not been fetched yet.') |
| 1341 version = self._webkit_version | 1379 version = self._version['platform'] |
| 1342 | 1380 |
| 1343 if version['major'] < major: | 1381 if version['major'] < major: |
| 1344 return False | 1382 return False |
| 1345 elif version['major'] == major and version['minor'] < minor: | 1383 elif version['major'] == major and version['minor'] < minor: |
| 1346 return False | 1384 return False |
| 1347 else: | 1385 else: |
| 1348 return True | 1386 return True |
| 1387 | |
| 1388 def _IsBrowserDayNumberMoreThan(self, day_number): | |
| 1389 """Compares remote Chromium day number with specified one. | |
| 1390 | |
| 1391 Args: | |
| 1392 day_number: Forth part of the chromium version. | |
| 1393 | |
| 1394 Returns: | |
| 1395 True if remote Chromium day number is same or newer than specified, | |
| 1396 False otherwise. | |
| 1397 | |
| 1398 Raises: | |
| 1399 RuntimeError: If remote Chromium version hasn't been fetched yet. | |
| 1400 """ | |
| 1401 if not hasattr(self, '_version'): | |
| 1402 raise RuntimeError('Browser revision has not been fetched yet.') | |
| 1403 version = self._version['browser'] | |
| 1404 | |
| 1405 return version['day'] > day_number | |
| OLD | NEW |