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 util
8
9DEFAULT_WEB_CONTENTS_TIMEOUT = 90
10
11# TODO(achuith, dtu, nduca): Add unit tests specifically for WebContents,
12# independent of Tab.
13class 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