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