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
8
9from py_trace_event import trace_event
10
11DEFAULT_WEB_CONTENTS_TIMEOUT = 90
12
13# TODO(achuith, dtu, nduca): Add unit tests specifically for WebContents,
14# independent of Tab.
15class WebContents(object):
16
17  __metaclass__ = trace_event.TracedMetaClass
18
19  """Represents web contents in the browser"""
20  def __init__(self, inspector_backend):
21    self._inspector_backend = inspector_backend
22
23    with open(os.path.join(os.path.dirname(__file__),
24        'network_quiescence.js')) as f:
25      self._quiescence_js = f.read()
26
27    with open(os.path.join(os.path.dirname(__file__),
28        'wait_for_frame.js')) as f:
29      self._wait_for_frame_js = f.read()
30
31    # An incrementing ID used to query frame timing javascript. Using a new id
32    # with each request ensures that previously timed-out wait for frame
33    # requests don't impact new requests.
34    self._wait_for_frame_id = 0
35
36  @property
37  def id(self):
38    """Return the unique id string for this tab object."""
39    return self._inspector_backend.id
40
41  def GetUrl(self):
42    """Returns the URL to which the WebContents is connected.
43
44    Raises:
45      exceptions.Error: If there is an error in inspector backend connection.
46    """
47    return self._inspector_backend.url
48
49  def GetWebviewContexts(self):
50    """Returns a list of webview contexts within the current inspector backend.
51
52    Returns:
53      A list of WebContents objects representing the webview contexts.
54
55    Raises:
56      exceptions.Error: If there is an error in inspector backend connection.
57    """
58    webviews = []
59    inspector_backends = self._inspector_backend.GetWebviewInspectorBackends()
60    for inspector_backend in inspector_backends:
61      webviews.append(WebContents(inspector_backend))
62    return webviews
63
64  def WaitForDocumentReadyStateToBeComplete(self,
65      timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
66    """Waits for the document to finish loading.
67
68    Raises:
69      exceptions.Error: See WaitForJavaScriptCondition() for a detailed list
70      of possible exceptions.
71    """
72
73    self.WaitForJavaScriptCondition(
74        'document.readyState == "complete"', timeout=timeout)
75
76  def WaitForDocumentReadyStateToBeInteractiveOrBetter(self,
77      timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
78    """Waits for the document to be interactive.
79
80    Raises:
81      exceptions.Error: See WaitForJavaScriptCondition() for a detailed list
82      of possible exceptions.
83    """
84    self.WaitForJavaScriptCondition(
85        'document.readyState == "interactive" || '
86        'document.readyState == "complete"', timeout=timeout)
87
88  def WaitForFrameToBeDisplayed(self,
89          timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
90    """Waits for a frame to be displayed before returning.
91
92    Raises:
93      exceptions.Error: See WaitForJavaScriptCondition() for a detailed list
94      of possible exceptions.
95    """
96    # Generate a new id for each call of this function to ensure that we track
97    # each request to wait seperately.
98    self._wait_for_frame_id += 1
99    self.WaitForJavaScriptCondition(
100        '{{ @script }}; window.__telemetry_testHasFramePassed({{ frame_id }})',
101        script=self._wait_for_frame_js,
102        frame_id=str(self._wait_for_frame_id),  # Place id as a str.
103        timeout=timeout)
104
105  def HasReachedQuiescence(self):
106    """Determine whether the page has reached quiescence after loading.
107
108    Returns:
109      True if 2 seconds have passed since last resource received, false
110      otherwise.
111    Raises:
112      exceptions.Error: See EvaluateJavaScript() for a detailed list of
113      possible exceptions.
114    """
115    # Inclusion of the script that provides
116    # window.__telemetry_testHasReachedNetworkQuiescence()
117    # is idempotent, it's run on every call because WebContents doesn't track
118    # page loads and we need to execute anew for every newly loaded page.
119    return self.EvaluateJavaScript(
120        '{{ @script }}; window.__telemetry_testHasReachedNetworkQuiescence()',
121        script=self._quiescence_js)
122
123  def ExecuteJavaScript(self, *args, **kwargs):
124    """Executes a given JavaScript statement. Does not return the result.
125
126    Example: runner.ExecuteJavaScript('var foo = {{ value }};', value='hi');
127
128    Args:
129      statement: The statement to execute (provided as a string).
130
131    Optional keyword args:
132      timeout: The number of seconds to wait for the statement to execute.
133      context_id: The id of an iframe where to execute the code; the main page
134          has context_id=1, the first iframe context_id=2, etc.
135      Additional keyword arguments provide values to be interpolated within
136          the statement. See telemetry.util.js_template for details.
137
138    Raises:
139      py_utils.TimeoutException
140      exceptions.EvaluationException
141      exceptions.WebSocketException
142      exceptions.DevtoolsTargetCrashException
143    """
144    return self._inspector_backend.ExecuteJavaScript(*args, **kwargs)
145
146  def EvaluateJavaScript(self, *args, **kwargs):
147    """Returns the result of evaluating a given JavaScript expression.
148
149    Example: runner.ExecuteJavaScript('document.location.href');
150
151    Args:
152      expression: The expression to execute (provided as a string).
153
154    Optional keyword args:
155      timeout: The number of seconds to wait for the expression to evaluate.
156      context_id: The id of an iframe where to execute the code; the main page
157          has context_id=1, the first iframe context_id=2, etc.
158      Additional keyword arguments provide values to be interpolated within
159          the expression. See telemetry.util.js_template for details.
160
161    Raises:
162      py_utils.TimeoutException
163      exceptions.EvaluationException
164      exceptions.WebSocketException
165      exceptions.DevtoolsTargetCrashException
166    """
167    return self._inspector_backend.EvaluateJavaScript(*args, **kwargs)
168
169  def WaitForJavaScriptCondition(self, *args, **kwargs):
170    """Wait for a JavaScript condition to become true.
171
172    Example: runner.WaitForJavaScriptCondition('window.foo == 10');
173
174    Args:
175      condition: The JavaScript condition (provided as string).
176
177    Optional keyword args:
178      timeout: The number in seconds to wait for the condition to become
179          True (default to 60).
180      context_id: The id of an iframe where to execute the code; the main page
181          has context_id=1, the first iframe context_id=2, etc.
182      Additional keyword arguments provide values to be interpolated within
183          the expression. See telemetry.util.js_template for details.
184
185    Raises:
186      py_utils.TimeoutException
187      exceptions.EvaluationException
188      exceptions.WebSocketException
189      exceptions.DevtoolsTargetCrashException
190    """
191    return self._inspector_backend.WaitForJavaScriptCondition(*args, **kwargs)
192
193  def EnableAllContexts(self):
194    """Enable all contexts in a page. Returns the number of available contexts.
195
196    Raises:
197      exceptions.WebSocketDisconnected
198      py_utils.TimeoutException
199      exceptions.DevtoolsTargetCrashException
200    """
201    return self._inspector_backend.EnableAllContexts()
202
203  def WaitForNavigate(self, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
204    """Waits for the navigation to complete.
205
206    The current page is expect to be in a navigation.
207    This function returns when the navigation is complete or when
208    the timeout has been exceeded.
209
210    Raises:
211      py_utils.TimeoutException
212      exceptions.DevtoolsTargetCrashException
213    """
214    self._inspector_backend.WaitForNavigate(timeout)
215
216  def Navigate(self, url, script_to_evaluate_on_commit=None,
217               timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
218    """Navigates to url.
219
220    If |script_to_evaluate_on_commit| is given, the script source string will be
221    evaluated when the navigation is committed. This is after the context of
222    the page exists, but before any script on the page itself has executed.
223
224    Raises:
225      py_utils.TimeoutException
226      exceptions.DevtoolsTargetCrashException
227    """
228    self._inspector_backend.Navigate(url, script_to_evaluate_on_commit, timeout)
229
230  def IsAlive(self):
231    """Whether the WebContents is still operating normally.
232
233    Since WebContents function asynchronously, this method does not guarantee
234    that the WebContents will still be alive at any point in the future.
235
236    Returns:
237      A boolean indicating whether the WebContents is opearting normally.
238    """
239    return self._inspector_backend.IsInspectable()
240
241  def CloseConnections(self):
242    """Closes all TCP sockets held open by the browser.
243
244    Raises:
245      exceptions.DevtoolsTargetCrashException if the tab is not alive.
246    """
247    if not self.IsAlive():
248      raise exceptions.DevtoolsTargetCrashException
249    self.ExecuteJavaScript('window.chrome && chrome.benchmarking &&'
250                           'chrome.benchmarking.closeConnections()')
251
252  def SynthesizeScrollGesture(self, x=100, y=800, xDistance=0, yDistance=-500,
253                              xOverscroll=None, yOverscroll=None,
254                              preventFling=None, speed=None,
255                              gestureSourceType=None, repeatCount=None,
256                              repeatDelayMs=None, interactionMarkerName=None,
257                              timeout=60):
258    """Runs an inspector command that causes a repeatable browser driven scroll.
259
260    Args:
261      x: X coordinate of the start of the gesture in CSS pixels.
262      y: Y coordinate of the start of the gesture in CSS pixels.
263      xDistance: Distance to scroll along the X axis (positive to scroll left).
264      yDistance: Ddistance to scroll along the Y axis (positive to scroll up).
265      xOverscroll: Number of additional pixels to scroll back along the X axis.
266      xOverscroll: Number of additional pixels to scroll back along the Y axis.
267      preventFling: Prevents a fling gesture.
268      speed: Swipe speed in pixels per second.
269      gestureSourceType: Which type of input events to be generated.
270      repeatCount: Number of additional repeats beyond the first scroll.
271      repeatDelayMs: Number of milliseconds delay between each repeat.
272      interactionMarkerName: The name of the interaction markers to generate.
273
274    Raises:
275      py_utils.TimeoutException
276      exceptions.DevtoolsTargetCrashException
277    """
278    return self._inspector_backend.SynthesizeScrollGesture(
279        x=x, y=y, xDistance=xDistance, yDistance=yDistance,
280        xOverscroll=xOverscroll, yOverscroll=yOverscroll,
281        preventFling=preventFling, speed=speed,
282        gestureSourceType=gestureSourceType, repeatCount=repeatCount,
283        repeatDelayMs=repeatDelayMs,
284        interactionMarkerName=interactionMarkerName,
285        timeout=timeout)
286
287  def DispatchKeyEvent(self, keyEventType='char', modifiers=None,
288                       timestamp=None, text=None, unmodifiedText=None,
289                       keyIdentifier=None, domCode=None, domKey=None,
290                       windowsVirtualKeyCode=None, nativeVirtualKeyCode=None,
291                       autoRepeat=None, isKeypad=None, isSystemKey=None,
292                       timeout=60):
293    """Dispatches a key event to the page.
294
295    Args:
296      type: Type of the key event. Allowed values: 'keyDown', 'keyUp',
297          'rawKeyDown', 'char'.
298      modifiers: Bit field representing pressed modifier keys. Alt=1, Ctrl=2,
299          Meta/Command=4, Shift=8 (default: 0).
300      timestamp: Time at which the event occurred. Measured in UTC time in
301          seconds since January 1, 1970 (default: current time).
302      text: Text as generated by processing a virtual key code with a keyboard
303          layout. Not needed for for keyUp and rawKeyDown events (default: '').
304      unmodifiedText: Text that would have been generated by the keyboard if no
305          modifiers were pressed (except for shift). Useful for shortcut
306          (accelerator) key handling (default: "").
307      keyIdentifier: Unique key identifier (e.g., 'U+0041') (default: '').
308      windowsVirtualKeyCode: Windows virtual key code (default: 0).
309      nativeVirtualKeyCode: Native virtual key code (default: 0).
310      autoRepeat: Whether the event was generated from auto repeat (default:
311          False).
312      isKeypad: Whether the event was generated from the keypad (default:
313          False).
314      isSystemKey: Whether the event was a system key event (default: False).
315
316    Raises:
317      py_utils.TimeoutException
318      exceptions.DevtoolsTargetCrashException
319    """
320    return self._inspector_backend.DispatchKeyEvent(
321        keyEventType=keyEventType, modifiers=modifiers, timestamp=timestamp,
322        text=text, unmodifiedText=unmodifiedText, keyIdentifier=keyIdentifier,
323        domCode=domCode, domKey=domKey,
324        windowsVirtualKeyCode=windowsVirtualKeyCode,
325        nativeVirtualKeyCode=nativeVirtualKeyCode, autoRepeat=autoRepeat,
326        isKeypad=isKeypad, isSystemKey=isSystemKey, timeout=timeout)
327