1# Copyright 2014 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 contextlib
6import json
7import logging
8import re
9import urllib2
10
11from telemetry.internal.backends.chrome import chrome_browser_backend
12from telemetry.internal.backends.chrome import system_info_backend
13
14import py_utils
15
16
17class IosBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
18  _DEBUGGER_URL_BUILDER = 'ws://localhost:%i/devtools/page/%i'
19  _DEBUGGER_URL_REGEX = r'ws://localhost:(\d+)/devtools/page/(\d+)'
20  _DEVICE_LIST_URL = 'http://localhost:9221/json'
21
22  def __init__(self, ios_platform_backend, browser_options):
23    browser_options.output_profile_path = '.'
24    super(IosBrowserBackend, self).__init__(
25        ios_platform_backend,
26        supports_tab_control=False,
27        supports_extensions=False,
28        browser_options=browser_options)
29    self._webviews = []
30    self._port = None
31    self._page = None
32    self._system_info_backend = None
33    self.UpdateRunningBrowsersInfo()
34
35  def UpdateRunningBrowsersInfo(self):
36    """ Refresh to match current state of the running browser.
37    """
38    device_urls = self.GetDeviceUrls()
39    urls = self.GetWebSocketDebuggerUrls(device_urls)
40    for url in urls:
41      m = re.match(self._DEBUGGER_URL_REGEX, url)
42      if m:
43        self._webviews.append([int(m.group(1)), int(m.group(2))])
44      else:
45        logging.error('Unexpected url format: %s' % url)
46
47    # TODO(baxley): For now, grab first item from |_webviews|. Ideally, we'd
48    # prefer to have the currently displayed tab, or something similar.
49    if self._webviews:
50      self._port = self._webviews[0][0]
51      self._page = self._webviews[0][1]
52
53  def GetDeviceUrls(self):
54    device_urls = []
55    try:
56      with contextlib.closing(
57          urllib2.urlopen(self._DEVICE_LIST_URL)) as device_list:
58        json_urls = device_list.read()
59        device_urls = json.loads(json_urls)
60        if not device_urls:
61          logging.debug('No iOS devices found. Will not try searching for iOS '
62                        'browsers.')
63          return []
64    except urllib2.URLError as e:
65      logging.debug('Error communicating with iOS device.')
66      logging.debug(str(e))
67      return []
68    return device_urls
69
70  def GetWebSocketDebuggerUrls(self, device_urls):
71    """ Get a list of the websocket debugger URLs to communicate with
72        all running UIWebViews.
73    """
74    data = []
75    # Loop through all devices.
76    for d in device_urls:
77      def GetData():
78        try:
79          with contextlib.closing(
80              # pylint: disable=cell-var-from-loop
81              urllib2.urlopen('http://%s/json' % d['url'])) as f:
82            json_result = f.read()
83            data = json.loads(json_result)
84            return data
85        except urllib2.URLError as e:
86          logging.debug('Error communicating with iOS device.')
87          logging.debug(e)
88          return False
89      try:
90        # Retry a few times since it can take a few seconds for this API to be
91        # ready, if ios_webkit_debug_proxy is just launched.
92        data = py_utils.WaitFor(GetData, 5)
93      except py_utils.TimeoutException as e:
94        logging.debug('Timeout retrieving data from iOS device')
95        logging.debug(e)
96        return []
97
98    # Find all running UIWebViews.
99    debug_urls = []
100    for j in data:
101      debug_urls.append(j['webSocketDebuggerUrl'])
102
103    return debug_urls
104
105  def GetSystemInfo(self):
106    if self._system_info_backend is None:
107      self._system_info_backend = system_info_backend.SystemInfoBackend(
108          self._port, self._page)
109    return self._system_info_backend.GetSystemInfo()
110
111  def IsBrowserRunning(self):
112    return bool(self._webviews)
113
114  #TODO(baxley): The following were stubbed out to get the sunspider benchmark
115  # running. These should be implemented.
116  @property
117  def browser_directory(self):
118    logging.warn('Not implemented')
119    return None
120
121  @property
122  def profile_directory(self):
123    logging.warn('Not implemented')
124    return None
125
126  def Start(self):
127    logging.warn('Not implemented')
128
129  def extension_backend(self):
130    logging.warn('Not implemented')
131    return None
132
133  def GetBrowserStartupArgs(self):
134    logging.warn('Not implemented')
135    return None
136
137  def HasBrowserFinishedLaunching(self):
138    logging.warn('Not implemented')
139    return False
140
141  def GetStandardOutput(self):
142    raise NotImplementedError()
143
144  def GetStackTrace(self):
145    raise NotImplementedError()
146
147  def GetMostRecentMinidumpPath(self):
148    return None
149
150  def GetAllMinidumpPaths(self):
151    return None
152
153  def GetAllUnsymbolizedMinidumpPaths(self):
154    return None
155
156  def SymbolizeMinidump(self, minidump_path):
157    return None
158