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 + '\n\nJavaScript |this|:\n' + 57 self.EvaluateJavaScript(""" 58 (function() { 59 var error = ''; 60 for (name in this) { 61 try { 62 error += '\\t' + name + ': ' + this[name] + '\\n'; 63 } catch (e) { 64 error += '\\t' + name + ': ???\\n'; 65 } 66 } 67 return error; 68 })(); 69 """)) 70 71 def HasReachedQuiescence(self): 72 """Determine whether the page has reached quiescence after loading. 73 74 Returns: 75 True if 2 seconds have passed since last resource received, false 76 otherwise.""" 77 78 # Inclusion of the script that provides 79 # window.__telemetry_testHasReachedNetworkQuiescence() 80 # is idempotent, it's run on every call because WebContents doesn't track 81 # page loads and we need to execute anew for every newly loaded page. 82 has_reached_quiescence = ( 83 self.EvaluateJavaScript(self._quiescence_js + 84 "window.__telemetry_testHasReachedNetworkQuiescence()")) 85 return has_reached_quiescence 86 87 def ExecuteJavaScript(self, statement, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 88 """Executes statement in JavaScript. Does not return the result. 89 90 If the statement failed to evaluate, EvaluateException will be raised. 91 """ 92 return self.ExecuteJavaScriptInContext( 93 statement, context_id=None, timeout=timeout) 94 95 def EvaluateJavaScript(self, expr, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 96 """Evalutes expr in JavaScript and returns the JSONized result. 97 98 Consider using ExecuteJavaScript for cases where the result of the 99 expression is not needed. 100 101 If evaluation throws in JavaScript, a Python EvaluateException will 102 be raised. 103 104 If the result of the evaluation cannot be JSONized, then an 105 EvaluationException will be raised. 106 """ 107 return self.EvaluateJavaScriptInContext( 108 expr, context_id=None, timeout=timeout) 109 110 def ExecuteJavaScriptInContext(self, expr, context_id, 111 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 112 """Similar to ExecuteJavaScript, except context_id can refer to an iframe. 113 The main page has context_id=1, the first iframe context_id=2, etc. 114 """ 115 return self._inspector_backend.ExecuteJavaScript( 116 expr, context_id=context_id, timeout=timeout) 117 118 def EvaluateJavaScriptInContext(self, expr, context_id, 119 timeout=DEFAULT_WEB_CONTENTS_TIMEOUT): 120 """Similar to ExecuteJavaScript, except context_id can refer to an iframe. 121 The main page has context_id=1, the first iframe context_id=2, etc. 122 """ 123 return self._inspector_backend.EvaluateJavaScript( 124 expr, context_id=context_id, timeout=timeout) 125 126 def EnableAllContexts(self): 127 """Enable all contexts in a page. Returns the number of available contexts. 128 """ 129 return self._inspector_backend.EnableAllContexts() 130 131 @property 132 def message_output_stream(self): 133 return self._inspector_backend.message_output_stream 134 135 @message_output_stream.setter 136 def message_output_stream(self, stream): 137 self._inspector_backend.message_output_stream = stream 138 139 @property 140 def timeline_model(self): 141 return self._inspector_backend.timeline_model 142 143 def StartTimelineRecording(self, options=None): 144 self._inspector_backend.StartTimelineRecording(options) 145 146 @property 147 def is_timeline_recording_running(self): 148 return self._inspector_backend.is_timeline_recording_running 149 150 def StopTimelineRecording(self): 151 self._inspector_backend.StopTimelineRecording() 152 153 def TakeJSHeapSnapshot(self, timeout=120): 154 return self._inspector_backend.TakeJSHeapSnapshot(timeout) 155