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.core import util 8 9 DEFAULT_WEB_CONTENTS_TIMEOUT = 90 10 11 # TODO(achuith, dtu, nduca): Add unit tests specifically for WebContents, 12 # independent of Tab. 13 class WebContents(object): 14 """Represents web contents in the browser""" 15 def __init__(self, inspector_backend, backend_list): 16 self._inspector_backend = inspector_backend 17 self._backend_list = backend_list 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 WaitForDocumentReadyStateToBeComplete(self, 29 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 30 self.WaitForJavaScriptExpression( 31 'document.readyState == "complete"', timeout) 32 33 def WaitForDocumentReadyStateToBeInteractiveOrBetter(self, 34 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 35 self.WaitForJavaScriptExpression( 36 'document.readyState == "interactive" || ' 37 'document.readyState == "complete"', timeout) 38 39 def WaitForJavaScriptExpression(self, expr, timeout): 40 """Waits for the given JavaScript expression to be True. 41 42 This method is robust against any given Evaluation timing out. 43 """ 44 def IsJavaScriptExpressionTrue(): 45 try: 46 return bool(self.EvaluateJavaScript(expr)) 47 except util.TimeoutException: 48 # If the main thread is busy for longer than Evaluate's timeout, we 49 # may time out here early. Instead, we want to wait for the full 50 # timeout of this method. 51 return False 52 try: 53 util.WaitFor(IsJavaScriptExpressionTrue, timeout) 54 except util.TimeoutException as e: 55 # Try to make timeouts a little more actionable by dumping |this|. 56 raise util.TimeoutException(e.message + self.EvaluateJavaScript(""" 57 (function() { 58 var error = '\\n\\nJavaScript |this|:\\n'; 59 for (name in this) { 60 try { 61 error += '\\t' + name + ': ' + this[name] + '\\n'; 62 } catch (e) { 63 error += '\\t' + name + ': ???\\n'; 64 } 65 } 66 if (window && window.document) { 67 error += '\\n\\nJavaScript window.document:\\n'; 68 for (name in window.document) { 69 try { 70 error += '\\t' + name + ': ' + window.document[name] + '\\n'; 71 } catch (e) { 72 error += '\\t' + name + ': ???\\n'; 73 } 74 } 75 } 76 return error; 77 })(); 78 """)) 79 80 def HasReachedQuiescence(self): 81 """Determine whether the page has reached quiescence after loading. 82 83 Returns: 84 True if 2 seconds have passed since last resource received, false 85 otherwise.""" 86 87 # Inclusion of the script that provides 88 # window.__telemetry_testHasReachedNetworkQuiescence() 89 # is idempotent, it's run on every call because WebContents doesn't track 90 # page loads and we need to execute anew for every newly loaded page. 91 has_reached_quiescence = ( 92 self.EvaluateJavaScript(self._quiescence_js + 93 "window.__telemetry_testHasReachedNetworkQuiescence()")) 94 return has_reached_quiescence 95 96 def ExecuteJavaScript(self, statement, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 97 """Executes statement in JavaScript. Does not return the result. 98 99 If the statement failed to evaluate, EvaluateException will be raised. 100 """ 101 return self.ExecuteJavaScriptInContext( 102 statement, context_id=None, timeout=timeout) 103 104 def EvaluateJavaScript(self, expr, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 105 """Evalutes expr in JavaScript and returns the JSONized result. 106 107 Consider using ExecuteJavaScript for cases where the result of the 108 expression is not needed. 109 110 If evaluation throws in JavaScript, a Python EvaluateException will 111 be raised. 112 113 If the result of the evaluation cannot be JSONized, then an 114 EvaluationException will be raised. 115 """ 116 return self.EvaluateJavaScriptInContext( 117 expr, context_id=None, timeout=timeout) 118 119 def ExecuteJavaScriptInContext(self, expr, context_id, 120 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 121 """Similar to ExecuteJavaScript, except context_id can refer to an iframe. 122 The main page has context_id=1, the first iframe context_id=2, etc. 123 """ 124 return self._inspector_backend.ExecuteJavaScript( 125 expr, context_id=context_id, timeout=timeout) 126 127 def EvaluateJavaScriptInContext(self, expr, context_id, 128 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 129 """Similar to ExecuteJavaScript, except context_id can refer to an iframe. 130 The main page has context_id=1, the first iframe context_id=2, etc. 131 """ 132 return self._inspector_backend.EvaluateJavaScript( 133 expr, context_id=context_id, timeout=timeout) 134 135 def EnableAllContexts(self): 136 """Enable all contexts in a page. Returns the number of available contexts. 137 """ 138 return self._inspector_backend.EnableAllContexts() 139 140 @property 141 def message_output_stream(self): 142 return self._inspector_backend.message_output_stream 143 144 @message_output_stream.setter 145 def message_output_stream(self, stream): 146 self._inspector_backend.message_output_stream = stream 147 148 @property 149 def timeline_model(self): 150 return self._inspector_backend.timeline_model 151 152 def StartTimelineRecording(self, options=None): 153 self._inspector_backend.StartTimelineRecording(options) 154 155 @property 156 def is_timeline_recording_running(self): 157 return self._inspector_backend.is_timeline_recording_running 158 159 def StopTimelineRecording(self): 160 self._inspector_backend.StopTimelineRecording() 161 162 def TakeJSHeapSnapshot(self, timeout=120): 163 return self._inspector_backend.TakeJSHeapSnapshot(timeout) 164