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