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 5import os 6 7from telemetry.core import exceptions 8from telemetry.core import util 9 10DEFAULT_WEB_CONTENTS_TIMEOUT = 90 11 12# TODO(achuith, dtu, nduca): Add unit tests specifically for WebContents, 13# independent of Tab. 14class WebContents(object): 15 """Represents web contents in the browser""" 16 def __init__(self, inspector_backend): 17 self._inspector_backend = inspector_backend 18 19 with open(os.path.join(os.path.dirname(__file__), 20 'network_quiescence.js')) as f: 21 self._quiescence_js = f.read() 22 23 @property 24 def id(self): 25 """Return the unique id string for this tab object.""" 26 return self._inspector_backend.id 27 28 def GetUrl(self): 29 """Returns the URL to which the WebContents is connected. 30 31 Raises: 32 exceptions.Error: If there is an error in inspector backend connection. 33 """ 34 return self._inspector_backend.url 35 36 def GetWebviewContexts(self): 37 """Returns a list of webview contexts within the current inspector backend. 38 39 Returns: 40 A list of WebContents objects representing the webview contexts. 41 42 Raises: 43 exceptions.Error: If there is an error in inspector backend connection. 44 """ 45 webviews = [] 46 inspector_backends = self._inspector_backend.GetWebviewInspectorBackends() 47 for inspector_backend in inspector_backends: 48 webviews.append(WebContents(inspector_backend)) 49 return webviews 50 51 def WaitForDocumentReadyStateToBeComplete(self, 52 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 53 """Waits for the document to finish loading. 54 55 Raises: 56 exceptions.Error: See WaitForJavaScriptExpression() for a detailed list 57 of possible exceptions. 58 """ 59 60 self.WaitForJavaScriptExpression( 61 'document.readyState == "complete"', timeout) 62 63 def WaitForDocumentReadyStateToBeInteractiveOrBetter(self, 64 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 65 """Waits for the document to be interactive. 66 67 Raises: 68 exceptions.Error: See WaitForJavaScriptExpression() for a detailed list 69 of possible exceptions. 70 """ 71 self.WaitForJavaScriptExpression( 72 'document.readyState == "interactive" || ' 73 'document.readyState == "complete"', timeout) 74 75 def WaitForJavaScriptExpression(self, expr, timeout): 76 """Waits for the given JavaScript expression to be True. 77 78 This method is robust against any given Evaluation timing out. 79 80 Args: 81 expr: The expression to evaluate. 82 timeout: The number of seconds to wait for the expression to be True. 83 84 Raises: 85 exceptions.TimeoutException: On a timeout. 86 exceptions.Error: See EvaluateJavaScript() for a detailed list of 87 possible exceptions. 88 """ 89 def IsJavaScriptExpressionTrue(): 90 try: 91 return bool(self.EvaluateJavaScript(expr)) 92 except exceptions.TimeoutException: 93 # If the main thread is busy for longer than Evaluate's timeout, we 94 # may time out here early. Instead, we want to wait for the full 95 # timeout of this method. 96 return False 97 try: 98 util.WaitFor(IsJavaScriptExpressionTrue, timeout) 99 except exceptions.TimeoutException as e: 100 # Try to make timeouts a little more actionable by dumping console output. 101 debug_message = None 102 try: 103 debug_message = ( 104 'Console output:\n%s' % 105 self._inspector_backend.GetCurrentConsoleOutputBuffer()) 106 except Exception as e: 107 debug_message = ( 108 'Exception thrown when trying to capture console output: %s' % 109 repr(e)) 110 raise exceptions.TimeoutException( 111 e.message + '\n' + debug_message) 112 113 def HasReachedQuiescence(self): 114 """Determine whether the page has reached quiescence after loading. 115 116 Returns: 117 True if 2 seconds have passed since last resource received, false 118 otherwise. 119 Raises: 120 exceptions.Error: See EvaluateJavaScript() for a detailed list of 121 possible exceptions. 122 """ 123 124 # Inclusion of the script that provides 125 # window.__telemetry_testHasReachedNetworkQuiescence() 126 # is idempotent, it's run on every call because WebContents doesn't track 127 # page loads and we need to execute anew for every newly loaded page. 128 has_reached_quiescence = ( 129 self.EvaluateJavaScript(self._quiescence_js + 130 "window.__telemetry_testHasReachedNetworkQuiescence()")) 131 return has_reached_quiescence 132 133 def ExecuteJavaScript(self, statement, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 134 """Executes statement in JavaScript. Does not return the result. 135 136 If the statement failed to evaluate, EvaluateException will be raised. 137 138 Raises: 139 exceptions.Error: See ExecuteJavaScriptInContext() for a detailed list of 140 possible exceptions. 141 """ 142 return self.ExecuteJavaScriptInContext( 143 statement, context_id=None, timeout=timeout) 144 145 def EvaluateJavaScript(self, expr, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 146 """Evalutes expr in JavaScript and returns the JSONized result. 147 148 Consider using ExecuteJavaScript for cases where the result of the 149 expression is not needed. 150 151 If evaluation throws in JavaScript, a Python EvaluateException will 152 be raised. 153 154 If the result of the evaluation cannot be JSONized, then an 155 EvaluationException will be raised. 156 157 Raises: 158 exceptions.Error: See EvaluateJavaScriptInContext() for a detailed list 159 of possible exceptions. 160 """ 161 return self.EvaluateJavaScriptInContext( 162 expr, context_id=None, timeout=timeout) 163 164 def ExecuteJavaScriptInContext(self, expr, context_id, 165 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 166 """Similar to ExecuteJavaScript, except context_id can refer to an iframe. 167 The main page has context_id=1, the first iframe context_id=2, etc. 168 169 Raises: 170 exceptions.EvaluateException 171 exceptions.WebSocketDisconnected 172 exceptions.TimeoutException 173 exceptions.DevtoolsTargetCrashException 174 """ 175 return self._inspector_backend.ExecuteJavaScript( 176 expr, context_id=context_id, timeout=timeout) 177 178 def EvaluateJavaScriptInContext(self, expr, context_id, 179 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 180 """Similar to ExecuteJavaScript, except context_id can refer to an iframe. 181 The main page has context_id=1, the first iframe context_id=2, etc. 182 183 Raises: 184 exceptions.EvaluateException 185 exceptions.WebSocketDisconnected 186 exceptions.TimeoutException 187 exceptions.DevtoolsTargetCrashException 188 """ 189 return self._inspector_backend.EvaluateJavaScript( 190 expr, context_id=context_id, timeout=timeout) 191 192 def EnableAllContexts(self): 193 """Enable all contexts in a page. Returns the number of available contexts. 194 195 Raises: 196 exceptions.WebSocketDisconnected 197 exceptions.TimeoutException 198 exceptions.DevtoolsTargetCrashException 199 """ 200 return self._inspector_backend.EnableAllContexts() 201 202 def WaitForNavigate(self, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 203 """Waits for the navigation to complete. 204 205 The current page is expect to be in a navigation. 206 This function returns when the navigation is complete or when 207 the timeout has been exceeded. 208 209 Raises: 210 exceptions.TimeoutException 211 exceptions.DevtoolsTargetCrashException 212 """ 213 self._inspector_backend.WaitForNavigate(timeout) 214 215 def Navigate(self, url, script_to_evaluate_on_commit=None, 216 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 217 """Navigates to url. 218 219 If |script_to_evaluate_on_commit| is given, the script source string will be 220 evaluated when the navigation is committed. This is after the context of 221 the page exists, but before any script on the page itself has executed. 222 223 Raises: 224 exceptions.TimeoutException 225 exceptions.DevtoolsTargetCrashException 226 """ 227 self._inspector_backend.Navigate(url, script_to_evaluate_on_commit, timeout) 228 229 def IsAlive(self): 230 """Whether the WebContents is still operating normally. 231 232 Since WebContents function asynchronously, this method does not guarantee 233 that the WebContents will still be alive at any point in the future. 234 235 Returns: 236 A boolean indicating whether the WebContents is opearting normally. 237 """ 238 return self._inspector_backend.IsInspectable() 239 240 def CloseConnections(self): 241 """Closes all TCP sockets held open by the browser. 242 243 Raises: 244 exceptions.DevtoolsTargetCrashException if the tab is not alive. 245 """ 246 if not self.IsAlive(): 247 raise exceptions.DevtoolsTargetCrashException 248 self.ExecuteJavaScript('window.chrome && chrome.benchmarking &&' 249 'chrome.benchmarking.closeConnections()') 250 251 def SynthesizeScrollGesture(self, x=100, y=800, xDistance=0, yDistance=-500, 252 xOverscroll=None, yOverscroll=None, 253 preventFling=True, speed=None, 254 gestureSourceType=None, repeatCount=None, 255 repeatDelayMs=None, interactionMarkerName=None, 256 timeout=60): 257 """Runs an inspector command that causes a repeatable browser driven scroll. 258 259 Args: 260 x: X coordinate of the start of the gesture in CSS pixels. 261 y: Y coordinate of the start of the gesture in CSS pixels. 262 xDistance: Distance to scroll along the X axis (positive to scroll left). 263 yDistance: Ddistance to scroll along the Y axis (positive to scroll up). 264 xOverscroll: Number of additional pixels to scroll back along the X axis. 265 xOverscroll: Number of additional pixels to scroll back along the Y axis. 266 preventFling: Prevents a fling gesture. 267 speed: Swipe speed in pixels per second. 268 gestureSourceType: Which type of input events to be generated. 269 repeatCount: Number of additional repeats beyond the first scroll. 270 repeatDelayMs: Number of milliseconds delay between each repeat. 271 interactionMarkerName: The name of the interaction markers to generate. 272 273 Raises: 274 exceptions.TimeoutException 275 exceptions.DevtoolsTargetCrashException 276 """ 277 return self._inspector_backend.SynthesizeScrollGesture( 278 x=x, y=y, xDistance=xDistance, yDistance=yDistance, 279 xOverscroll=xOverscroll, yOverscroll=yOverscroll, 280 preventFling=preventFling, speed=speed, 281 gestureSourceType=gestureSourceType, repeatCount=repeatCount, 282 repeatDelayMs=repeatDelayMs, 283 interactionMarkerName=interactionMarkerName, 284 timeout=timeout) 285