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 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
63 Public Attributes: | 63 Public Attributes: |
64 method: The string method name associated with this request. | 64 method: The string method name associated with this request. |
65 id: A unique integer id associated with this request. | 65 id: A unique integer id associated with this request. |
66 params: A dictionary of input parameters associated with this request. | 66 params: A dictionary of input parameters associated with this request. |
67 results: A dictionary of relevant results obtained from the remote Chrome | 67 results: A dictionary of relevant results obtained from the remote Chrome |
68 instance that are associated with this request. | 68 instance that are associated with this request. |
69 is_fulfilled: A boolean indicating whether or not this request has been sent | 69 is_fulfilled: A boolean indicating whether or not this request has been sent |
70 and all relevant results for it have been obtained (i.e., this value is | 70 and all relevant results for it have been obtained (i.e., this value is |
71 True only if all results for this request are known). | 71 True only if all results for this request are known). |
72 """ | 72 """ |
73 def __init__(self, method, message_id): | 73 def __init__(self, method, params, message_id): |
74 """Initialize. | 74 """Initialize. |
75 | 75 |
76 Args: | 76 Args: |
77 method: The string method name for this request. | 77 method: The string method name for this request. |
78 message_id: An integer id for this request, which is assumed to be unique | 78 message_id: An integer id for this request, which is assumed to be unique |
79 from among all requests. | 79 from among all requests. |
80 """ | 80 """ |
81 self.method = method | 81 self.method = method |
82 self.id = message_id | 82 self.id = message_id |
83 self.params = {} | 83 self.params = params |
84 self.results = {} | 84 self.results = {} |
85 self.is_fulfilled = False | 85 self.is_fulfilled = False |
86 | 86 |
87 def __repr__(self): | 87 def __repr__(self): |
88 json_dict = {} | 88 json_dict = {} |
89 json_dict['method'] = self.method | 89 json_dict['method'] = self.method |
90 json_dict['id'] = self.id | 90 json_dict['id'] = self.id |
91 if self.params: | 91 if self.params: |
92 json_dict['params'] = self.params | 92 json_dict['params'] = self.params |
93 return simplejson.dumps(json_dict, separators=(',', ':')) | 93 return simplejson.dumps(json_dict, separators=(',', ':')) |
(...skipping 232 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
326 """Start this thread; overridden from threading.Thread.""" | 326 """Start this thread; overridden from threading.Thread.""" |
327 while not self._killed: | 327 while not self._killed: |
328 if self._action_queue: | 328 if self._action_queue: |
329 # There's a request to the remote inspector that needs to be processed. | 329 # There's a request to the remote inspector that needs to be processed. |
330 messages, callback = self._action_queue.pop(0) | 330 messages, callback = self._action_queue.pop(0) |
331 self._action_specific_callback = callback | 331 self._action_specific_callback = callback |
332 | 332 |
333 # Prepare the request list. | 333 # Prepare the request list. |
334 for message_id, message in enumerate(messages): | 334 for message_id, message in enumerate(messages): |
335 self._requests.append( | 335 self._requests.append( |
336 _DevToolsSocketRequest(message, message_id)) | 336 _DevToolsSocketRequest(message[0], message[1], message_id)) |
337 | 337 |
338 # Send out each request. Wait until each request is complete before | 338 # Send out each request. Wait until each request is complete before |
339 # sending the next request. | 339 # sending the next request. |
340 for request in self._requests: | 340 for request in self._requests: |
341 self._FillInParams(request) | 341 self._FillInParams(request) |
342 self._client.SendMessage(str(request)) | 342 self._client.SendMessage(str(request)) |
343 while not request.is_fulfilled: | 343 while not request.is_fulfilled: |
344 if self._killed: | 344 if self._killed: |
345 self._client.close() | 345 self._client.close() |
346 return | 346 return |
(...skipping 377 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
724 { | 724 { |
725 'url': string, # URL of the webpage that was snapshotted. | 725 'url': string, # URL of the webpage that was snapshotted. |
726 'raw_data': string, # The raw data as JSON string. | 726 'raw_data': string, # The raw data as JSON string. |
727 'total_v8_node_count': integer, # Total number of nodes in the v8 heap. | 727 'total_v8_node_count': integer, # Total number of nodes in the v8 heap. |
728 # Only if |include_summary| is True. | 728 # Only if |include_summary| is True. |
729 'total_heap_size': integer, # Total v8 heap size (number of bytes). | 729 'total_heap_size': integer, # Total v8 heap size (number of bytes). |
730 # Only if |include_summary| is True. | 730 # Only if |include_summary| is True. |
731 } | 731 } |
732 """ | 732 """ |
733 HEAP_SNAPSHOT_MESSAGES = [ | 733 HEAP_SNAPSHOT_MESSAGES = [ |
734 'Page.getResourceTree', | 734 ('Page.getResourceTree', {}), |
735 'Debugger.enable', | 735 ('Debugger.enable', {}), |
736 'Profiler.clearProfiles', | 736 ('Profiler.clearProfiles', {}), |
737 'Profiler.takeHeapSnapshot', | 737 ('Profiler.takeHeapSnapshot', {}), |
738 'Profiler.getProfile', | 738 ('Profiler.getProfile', {}), |
739 ] | 739 ] |
740 | 740 |
741 self._current_heap_snapshot = [] | 741 self._current_heap_snapshot = [] |
742 self._url = '' | 742 self._url = '' |
743 self._collected_heap_snapshot_data = {} | 743 self._collected_heap_snapshot_data = {} |
744 | 744 |
745 def HandleReply(reply_dict): | 745 def HandleReply(reply_dict): |
746 """Processes a reply message received from the remote Chrome instance. | 746 """Processes a reply message received from the remote Chrome instance. |
747 | 747 |
748 Args: | 748 Args: |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
790 self._collected_heap_snapshot_data['total_heap_size'] = total_size | 790 self._collected_heap_snapshot_data['total_heap_size'] = total_size |
791 | 791 |
792 # Tell the remote inspector to take a v8 heap snapshot, then wait until | 792 # Tell the remote inspector to take a v8 heap snapshot, then wait until |
793 # the snapshot information is available to return. | 793 # the snapshot information is available to return. |
794 self._remote_inspector_thread.PerformAction(HEAP_SNAPSHOT_MESSAGES, | 794 self._remote_inspector_thread.PerformAction(HEAP_SNAPSHOT_MESSAGES, |
795 HandleReply) | 795 HandleReply) |
796 while not self._collected_heap_snapshot_data: | 796 while not self._collected_heap_snapshot_data: |
797 time.sleep(0.1) | 797 time.sleep(0.1) |
798 return self._collected_heap_snapshot_data | 798 return self._collected_heap_snapshot_data |
799 | 799 |
800 def EvaluateJavaScript(self, expression): | |
801 """Evaluates a JavaScript expression and returns the result. | |
802 | |
803 Sends a message containing the expression to the remote Chrome instance we | |
804 are connected to, and evaluates it in the context of the tab we are | |
805 connected to. Blocks until the result is available and returns it. | |
806 | |
807 Returns: | |
808 A dictionary representing the result. | |
dennis_jeffrey
2012/05/22 17:14:05
Is there a standard format for the result dictiona
jochen (gone - plz use gerrit)
2012/05/23 08:20:48
not really. It depends on what the expression eval
| |
809 """ | |
810 EVALUATE_MESSAGES = [ | |
811 ('Runtime.evaluate', { 'expression': expression, | |
812 'objectGroup': 'group', | |
813 'returnByValue': True }), | |
814 ('Runtime.releaseObjectGroup', { 'objectGroup': 'group' }) | |
815 ] | |
816 | |
817 self._result = None | |
818 self._got_result = False | |
819 | |
820 def HandleReply(reply_dict): | |
821 """Processes a reply message received from the remote Chrome instance. | |
822 | |
823 Args: | |
824 reply_dict: A dictionary object representing the reply message received | |
825 from the remote Chrome instance. | |
826 """ | |
827 if 'result' in reply_dict and 'result' in reply_dict['result']: | |
828 self._result = reply_dict['result']['result']['value'] | |
829 self._got_result = True | |
830 | |
831 # Tell the remote inspector to evaluate the given expression, then wait | |
832 # until that information is available to return. | |
833 self._remote_inspector_thread.PerformAction(EVALUATE_MESSAGES, | |
834 HandleReply) | |
835 while not self._got_result: | |
836 time.sleep(0.1) | |
837 return self._result | |
838 | |
800 def GetMemoryObjectCounts(self): | 839 def GetMemoryObjectCounts(self): |
801 """Retrieves memory object count information. | 840 """Retrieves memory object count information. |
802 | 841 |
803 Returns: | 842 Returns: |
804 A dictionary containing the memory object count information: | 843 A dictionary containing the memory object count information: |
805 { | 844 { |
806 'DOMNodeCount': integer, # Total number of DOM nodes. | 845 'DOMNodeCount': integer, # Total number of DOM nodes. |
807 'EventListenerCount': integer, # Total number of event listeners. | 846 'EventListenerCount': integer, # Total number of event listeners. |
808 } | 847 } |
809 """ | 848 """ |
810 MEMORY_COUNT_MESSAGES = [ | 849 MEMORY_COUNT_MESSAGES = [ |
811 'Memory.getDOMNodeCount', | 850 ('Memory.getDOMNodeCount', {}) |
812 ] | 851 ] |
813 | 852 |
814 self._event_listener_count = None | 853 self._event_listener_count = None |
815 self._dom_node_count = None | 854 self._dom_node_count = None |
816 | 855 |
817 def HandleReply(reply_dict): | 856 def HandleReply(reply_dict): |
818 """Processes a reply message received from the remote Chrome instance. | 857 """Processes a reply message received from the remote Chrome instance. |
819 | 858 |
820 Args: | 859 Args: |
821 reply_dict: A dictionary object representing the reply message received | 860 reply_dict: A dictionary object representing the reply message received |
(...skipping 20 matching lines...) Expand all Loading... | |
842 while not self._event_listener_count or not self._dom_node_count: | 881 while not self._event_listener_count or not self._dom_node_count: |
843 time.sleep(0.1) | 882 time.sleep(0.1) |
844 return { | 883 return { |
845 'DOMNodeCount': self._dom_node_count, | 884 'DOMNodeCount': self._dom_node_count, |
846 'EventListenerCount': self._event_listener_count, | 885 'EventListenerCount': self._event_listener_count, |
847 } | 886 } |
848 | 887 |
849 def CollectGarbage(self): | 888 def CollectGarbage(self): |
850 """Forces a garbage collection.""" | 889 """Forces a garbage collection.""" |
851 COLLECT_GARBAGE_MESSAGES = [ | 890 COLLECT_GARBAGE_MESSAGES = [ |
852 'Profiler.collectGarbage', | 891 ('Profiler.collectGarbage', {}) |
853 ] | 892 ] |
854 | 893 |
855 # Tell the remote inspector to do a garbage collect. We can return | 894 # Tell the remote inspector to do a garbage collect. We can return |
856 # immediately, since there is no result for which to wait. | 895 # immediately, since there is no result for which to wait. |
857 self._remote_inspector_thread.PerformAction(COLLECT_GARBAGE_MESSAGES, None) | 896 self._remote_inspector_thread.PerformAction(COLLECT_GARBAGE_MESSAGES, None) |
858 | 897 |
859 def StartTimelineEventMonitoring(self, event_callback): | 898 def StartTimelineEventMonitoring(self, event_callback): |
860 """Starts timeline event monitoring. | 899 """Starts timeline event monitoring. |
861 | 900 |
862 Args: | 901 Args: |
863 event_callback: A callable to invoke whenever a timeline event is observed | 902 event_callback: A callable to invoke whenever a timeline event is observed |
864 from the remote inspector. The callable should take a single input, | 903 from the remote inspector. The callable should take a single input, |
865 which is a dictionary containing the detailed information of a | 904 which is a dictionary containing the detailed information of a |
866 timeline event. | 905 timeline event. |
867 """ | 906 """ |
868 if self._timeline_started: | 907 if self._timeline_started: |
869 self._logger.warning('Timeline monitoring already started.') | 908 self._logger.warning('Timeline monitoring already started.') |
870 return | 909 return |
871 TIMELINE_MESSAGES = [ | 910 TIMELINE_MESSAGES = [ |
872 'Timeline.start', | 911 ('Timeline.start', {}) |
873 ] | 912 ] |
874 | 913 |
875 self._event_callback = event_callback | 914 self._event_callback = event_callback |
876 | 915 |
877 def HandleReply(reply_dict): | 916 def HandleReply(reply_dict): |
878 """Processes a reply message received from the remote Chrome instance. | 917 """Processes a reply message received from the remote Chrome instance. |
879 | 918 |
880 Args: | 919 Args: |
881 reply_dict: A dictionary object representing the reply message received | 920 reply_dict: A dictionary object representing the reply message received |
882 from the remote Chrome instance. | 921 from the remote Chrome instance. |
883 """ | 922 """ |
884 if reply_dict.get('method') == 'Timeline.eventRecorded': | 923 if reply_dict.get('method') == 'Timeline.eventRecorded': |
885 self._event_callback(reply_dict['params']['record']) | 924 self._event_callback(reply_dict['params']['record']) |
886 | 925 |
887 # Tell the remote inspector to start the timeline. We can return | 926 # Tell the remote inspector to start the timeline. We can return |
888 # immediately, since there is no result for which to wait. | 927 # immediately, since there is no result for which to wait. |
889 self._timeline_callback = HandleReply | 928 self._timeline_callback = HandleReply |
890 self._remote_inspector_thread.AddMessageCallback(self._timeline_callback) | 929 self._remote_inspector_thread.AddMessageCallback(self._timeline_callback) |
891 self._remote_inspector_thread.PerformAction(TIMELINE_MESSAGES, None) | 930 self._remote_inspector_thread.PerformAction(TIMELINE_MESSAGES, None) |
892 self._timeline_started = True | 931 self._timeline_started = True |
893 | 932 |
894 def StopTimelineEventMonitoring(self): | 933 def StopTimelineEventMonitoring(self): |
895 """Stops timeline event monitoring.""" | 934 """Stops timeline event monitoring.""" |
896 if not self._timeline_started: | 935 if not self._timeline_started: |
897 self._logger.warning('Timeline monitoring already stopped.') | 936 self._logger.warning('Timeline monitoring already stopped.') |
898 return | 937 return |
899 TIMELINE_MESSAGES = [ | 938 TIMELINE_MESSAGES = [ |
900 'Timeline.stop', | 939 ('Timeline.stop', {}) |
901 ] | 940 ] |
902 | 941 |
903 # Tell the remote inspector to stop the timeline. We can return | 942 # Tell the remote inspector to stop the timeline. We can return |
904 # immediately, since there is no result for which to wait. | 943 # immediately, since there is no result for which to wait. |
905 self._remote_inspector_thread.RemoveMessageCallback(self._timeline_callback) | 944 self._remote_inspector_thread.RemoveMessageCallback(self._timeline_callback) |
906 self._remote_inspector_thread.PerformAction(TIMELINE_MESSAGES, None) | 945 self._remote_inspector_thread.PerformAction(TIMELINE_MESSAGES, None) |
907 self._timeline_started = False | 946 self._timeline_started = False |
908 | 947 |
909 def _ConvertByteCountToHumanReadableString(self, num_bytes): | 948 def _ConvertByteCountToHumanReadableString(self, num_bytes): |
910 """Converts an integer number of bytes into a human-readable string. | 949 """Converts an integer number of bytes into a human-readable string. |
911 | 950 |
912 Args: | 951 Args: |
913 num_bytes: An integer number of bytes. | 952 num_bytes: An integer number of bytes. |
914 | 953 |
915 Returns: | 954 Returns: |
916 A human-readable string representation of the given number of bytes. | 955 A human-readable string representation of the given number of bytes. |
917 """ | 956 """ |
918 if num_bytes < 1024: | 957 if num_bytes < 1024: |
919 return '%d B' % num_bytes | 958 return '%d B' % num_bytes |
920 elif num_bytes < 1048576: | 959 elif num_bytes < 1048576: |
921 return '%.2f KB' % (num_bytes / 1024.0) | 960 return '%.2f KB' % (num_bytes / 1024.0) |
922 else: | 961 else: |
923 return '%.2f MB' % (num_bytes / 1048576.0) | 962 return '%.2f MB' % (num_bytes / 1048576.0) |
OLD | NEW |