1# Copyright 2013 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 time 5 6from telemetry.util import image_util 7 8 9class InspectorPage(object): 10 """Class that controls a page connected by an inspector_websocket. 11 12 This class provides utility methods for controlling a page connected by an 13 inspector_websocket. It does not perform any exception handling. All 14 inspector_websocket exceptions must be handled by the caller. 15 """ 16 def __init__(self, inspector_websocket, timeout=60): 17 self._inspector_websocket = inspector_websocket 18 self._inspector_websocket.RegisterDomain('Page', self._OnNotification) 19 20 self._navigation_pending = False 21 self._navigation_url = '' # Support for legacy backends. 22 self._navigation_frame_id = '' 23 self._navigated_frame_ids = None # Holds frame ids while navigating. 24 self._script_to_evaluate_on_commit = None 25 # Turn on notifications. We need them to get the Page.frameNavigated event. 26 self._EnablePageNotifications(timeout=timeout) 27 28 def _OnNotification(self, msg): 29 if msg['method'] == 'Page.frameNavigated': 30 url = msg['params']['frame']['url'] 31 if not self._navigated_frame_ids == None: 32 frame_id = msg['params']['frame']['id'] 33 if self._navigation_frame_id == frame_id: 34 self._navigation_frame_id = '' 35 self._navigated_frame_ids = None 36 self._navigation_pending = False 37 else: 38 self._navigated_frame_ids.add(frame_id) 39 elif self._navigation_url == url: 40 # TODO(tonyg): Remove this when Chrome 38 goes stable. 41 self._navigation_url = '' 42 self._navigation_pending = False 43 elif (not url == 'chrome://newtab/' and not url == 'about:blank' 44 and not 'parentId' in msg['params']['frame']): 45 # Marks the navigation as complete and unblocks the 46 # WaitForNavigate call. 47 self._navigation_pending = False 48 49 def _SetScriptToEvaluateOnCommit(self, source): 50 existing_source = (self._script_to_evaluate_on_commit and 51 self._script_to_evaluate_on_commit['source']) 52 if source == existing_source: 53 return 54 if existing_source: 55 request = { 56 'method': 'Page.removeScriptToEvaluateOnLoad', 57 'params': { 58 'identifier': self._script_to_evaluate_on_commit['id'], 59 } 60 } 61 self._inspector_websocket.SyncRequest(request) 62 self._script_to_evaluate_on_commit = None 63 if source: 64 request = { 65 'method': 'Page.addScriptToEvaluateOnLoad', 66 'params': { 67 'scriptSource': source, 68 } 69 } 70 res = self._inspector_websocket.SyncRequest(request) 71 self._script_to_evaluate_on_commit = { 72 'id': res['result']['identifier'], 73 'source': source 74 } 75 76 def _EnablePageNotifications(self, timeout=60): 77 request = { 78 'method': 'Page.enable' 79 } 80 res = self._inspector_websocket.SyncRequest(request, timeout) 81 assert len(res['result'].keys()) == 0 82 83 def WaitForNavigate(self, timeout=60): 84 """Waits for the navigation to complete. 85 86 The current page is expect to be in a navigation. This function returns 87 when the navigation is complete or when the timeout has been exceeded. 88 """ 89 start_time = time.time() 90 remaining_time = timeout 91 self._navigation_pending = True 92 while self._navigation_pending and remaining_time > 0: 93 remaining_time = max(timeout - (time.time() - start_time), 0.0) 94 self._inspector_websocket.DispatchNotifications(remaining_time) 95 96 def Navigate(self, url, script_to_evaluate_on_commit=None, timeout=60): 97 """Navigates to |url|. 98 99 If |script_to_evaluate_on_commit| is given, the script source string will be 100 evaluated when the navigation is committed. This is after the context of 101 the page exists, but before any script on the page itself has executed. 102 """ 103 104 self._SetScriptToEvaluateOnCommit(script_to_evaluate_on_commit) 105 request = { 106 'method': 'Page.navigate', 107 'params': { 108 'url': url, 109 } 110 } 111 self._navigated_frame_ids = set() 112 res = self._inspector_websocket.SyncRequest(request, timeout) 113 if 'frameId' in res['result']: 114 # Modern backends are returning frameId from Page.navigate. 115 # Use it here to unblock upon precise navigation. 116 frame_id = res['result']['frameId'] 117 if self._navigated_frame_ids and frame_id in self._navigated_frame_ids: 118 self._navigated_frame_ids = None 119 return 120 self._navigation_frame_id = frame_id 121 else: 122 # TODO(tonyg): Remove this when Chrome 38 goes stable. 123 self._navigated_frame_ids = None 124 self._navigation_url = url 125 self.WaitForNavigate(timeout) 126 127 def GetCookieByName(self, name, timeout=60): 128 """Returns the value of the cookie by the given |name|.""" 129 request = { 130 'method': 'Page.getCookies' 131 } 132 res = self._inspector_websocket.SyncRequest(request, timeout) 133 cookies = res['result']['cookies'] 134 for cookie in cookies: 135 if cookie['name'] == name: 136 return cookie['value'] 137 return None 138 139 def CaptureScreenshot(self, timeout=60): 140 request = { 141 'method': 'Page.captureScreenshot' 142 } 143 # "Google API are missing..." infobar might cause a viewport resize 144 # which invalidates screenshot request. See crbug.com/459820. 145 for _ in range(2): 146 res = self._inspector_websocket.SyncRequest(request, timeout) 147 if res and ('result' in res) and ('data' in res['result']): 148 return image_util.FromBase64Png(res['result']['data']) 149 return None 150 151 def CollectGarbage(self, timeout=60): 152 request = { 153 'method': 'HeapProfiler.collectGarbage' 154 } 155 res = self._inspector_websocket.SyncRequest(request, timeout) 156 assert 'result' in res 157