1# Copyright 2013 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"""Finds desktop browsers that can be controlled by telemetry."""
5
6import logging
7import os
8import sys
9
10import dependency_manager  # pylint: disable=import-error
11
12from telemetry.core import exceptions
13from telemetry.core import platform as platform_module
14from telemetry.internal.backends.chrome import desktop_browser_backend
15from telemetry.internal.browser import browser
16from telemetry.internal.browser import possible_browser
17from telemetry.internal.platform import desktop_device
18from telemetry.internal.util import binary_manager
19# This is a workaround for https://goo.gl/1tGNgd
20from telemetry.internal.util import path as path_module
21
22
23class PossibleDesktopBrowser(possible_browser.PossibleBrowser):
24  """A desktop browser that can be controlled."""
25
26  def __init__(self, browser_type, finder_options, executable, flash_path,
27               is_content_shell, browser_directory, is_local_build=False):
28    target_os = sys.platform.lower()
29    super(PossibleDesktopBrowser, self).__init__(
30        browser_type, target_os, not is_content_shell)
31    assert browser_type in FindAllBrowserTypes(finder_options), (
32        'Please add %s to desktop_browser_finder.FindAllBrowserTypes' %
33        browser_type)
34    self._local_executable = executable
35    self._flash_path = flash_path
36    self._is_content_shell = is_content_shell
37    self._browser_directory = browser_directory
38    self.is_local_build = is_local_build
39
40  def __repr__(self):
41    return 'PossibleDesktopBrowser(type=%s, executable=%s, flash=%s)' % (
42        self.browser_type, self._local_executable, self._flash_path)
43
44  def _InitPlatformIfNeeded(self):
45    if self._platform:
46      return
47
48    self._platform = platform_module.GetHostPlatform()
49
50    # pylint: disable=protected-access
51    self._platform_backend = self._platform._platform_backend
52
53  def Create(self, finder_options):
54    if self._flash_path and not os.path.exists(self._flash_path):
55      logging.warning(
56          'Could not find Flash at %s. Continuing without Flash.\n'
57          'To run with Flash, check it out via http://go/read-src-internal',
58          self._flash_path)
59      self._flash_path = None
60
61    self._InitPlatformIfNeeded()
62
63    browser_backend = desktop_browser_backend.DesktopBrowserBackend(
64        self._platform_backend,
65        finder_options.browser_options, self._local_executable,
66        self._flash_path, self._is_content_shell, self._browser_directory,
67        output_profile_path=finder_options.output_profile_path,
68        extensions_to_load=finder_options.extensions_to_load)
69    return browser.Browser(
70        browser_backend, self._platform_backend, self._credentials_path)
71
72  def SupportsOptions(self, finder_options):
73    if (len(finder_options.extensions_to_load) != 0) and self._is_content_shell:
74      return False
75    return True
76
77  def UpdateExecutableIfNeeded(self):
78    pass
79
80  def last_modification_time(self):
81    if os.path.exists(self._local_executable):
82      return os.path.getmtime(self._local_executable)
83    return -1
84
85def SelectDefaultBrowser(possible_browsers):
86  local_builds_by_date = [
87      b for b in sorted(possible_browsers,
88                        key=lambda b: b.last_modification_time())
89      if b.is_local_build]
90  if local_builds_by_date:
91    return local_builds_by_date[-1]
92  return None
93
94def CanFindAvailableBrowsers():
95  return not platform_module.GetHostPlatform().GetOSName() == 'chromeos'
96
97def CanPossiblyHandlePath(target_path):
98  _, extension = os.path.splitext(target_path.lower())
99  if sys.platform == 'darwin' or sys.platform.startswith('linux'):
100    return not extension
101  elif sys.platform.startswith('win'):
102    return extension == '.exe'
103  return False
104
105def FindAllBrowserTypes(_):
106  return [
107      'exact',
108      'reference',
109      'release',
110      'release_x64',
111      'debug',
112      'debug_x64',
113      'default',
114      'stable',
115      'beta',
116      'dev',
117      'canary',
118      'content-shell-debug',
119      'content-shell-debug_x64',
120      'content-shell-release',
121      'content-shell-release_x64',
122      'content-shell-default',
123      'system']
124
125def FindAllAvailableBrowsers(finder_options, device):
126  """Finds all the desktop browsers available on this machine."""
127  if not isinstance(device, desktop_device.DesktopDevice):
128    return []
129
130  browsers = []
131
132  if not CanFindAvailableBrowsers():
133    return []
134
135  has_x11_display = True
136  if (sys.platform.startswith('linux') and
137      os.getenv('DISPLAY') == None):
138    has_x11_display = False
139
140  os_name = platform_module.GetHostPlatform().GetOSName()
141  arch_name = platform_module.GetHostPlatform().GetArchName()
142  try:
143    flash_path = binary_manager.LocalPath('flash', arch_name, os_name)
144  except dependency_manager.NoPathFoundError:
145    flash_path = None
146    logging.warning(
147        'Chrome build location for %s_%s not found. Browser will be run '
148        'without Flash.', os_name, arch_name)
149
150  chromium_app_names = []
151  if sys.platform == 'darwin':
152    chromium_app_names.append('Chromium.app/Contents/MacOS/Chromium')
153    chromium_app_names.append('Google Chrome.app/Contents/MacOS/Google Chrome')
154    content_shell_app_name = 'Content Shell.app/Contents/MacOS/Content Shell'
155  elif sys.platform.startswith('linux'):
156    chromium_app_names.append('chrome')
157    content_shell_app_name = 'content_shell'
158  elif sys.platform.startswith('win'):
159    chromium_app_names.append('chrome.exe')
160    content_shell_app_name = 'content_shell.exe'
161  else:
162    raise Exception('Platform not recognized')
163
164  # Add the explicit browser executable if given and we can handle it.
165  if (finder_options.browser_executable and
166      CanPossiblyHandlePath(finder_options.browser_executable)):
167    is_content_shell = finder_options.browser_executable.endswith(
168        content_shell_app_name)
169    is_chrome_or_chromium = len([x for x in chromium_app_names if
170        finder_options.browser_executable.endswith(x)]) != 0
171
172    # It is okay if the executable name doesn't match any of known chrome
173    # browser executables, since it may be of a different browser.
174    if is_chrome_or_chromium or is_content_shell:
175      normalized_executable = os.path.expanduser(
176          finder_options.browser_executable)
177      if path_module.IsExecutable(normalized_executable):
178        browser_directory = os.path.dirname(finder_options.browser_executable)
179        browsers.append(PossibleDesktopBrowser(
180            'exact', finder_options, normalized_executable, flash_path,
181            is_content_shell,
182            browser_directory))
183      else:
184        raise exceptions.PathMissingError(
185            '%s specified by --browser-executable does not exist or is not '
186            'executable' %
187            normalized_executable)
188
189  def AddIfFound(browser_type, build_path, app_name, content_shell):
190    app = os.path.join(build_path, app_name)
191    if path_module.IsExecutable(app):
192      browsers.append(PossibleDesktopBrowser(
193          browser_type, finder_options, app, flash_path,
194          content_shell, build_path, is_local_build=True))
195      return True
196    return False
197
198  # Add local builds
199  for build_path in path_module.GetBuildDirectories(finder_options.chrome_root):
200    # TODO(agrieve): Extract browser_type from args.gn's is_debug.
201    browser_type = os.path.basename(build_path).lower()
202    for chromium_app_name in chromium_app_names:
203      AddIfFound(browser_type, build_path, chromium_app_name, False)
204    AddIfFound('content-shell-' + browser_type, build_path,
205               content_shell_app_name, True)
206
207  reference_build = None
208  if finder_options.browser_type == 'reference':
209    # Reference builds are only available in a Chromium checkout. We should not
210    # raise an error just because they don't exist.
211    os_name = platform_module.GetHostPlatform().GetOSName()
212    arch_name = platform_module.GetHostPlatform().GetArchName()
213    reference_build = binary_manager.FetchPath(
214        'reference_build', arch_name, os_name)
215
216  # Mac-specific options.
217  if sys.platform == 'darwin':
218    mac_canary_root = '/Applications/Google Chrome Canary.app/'
219    mac_canary = mac_canary_root + 'Contents/MacOS/Google Chrome Canary'
220    mac_system_root = '/Applications/Google Chrome.app'
221    mac_system = mac_system_root + '/Contents/MacOS/Google Chrome'
222    if path_module.IsExecutable(mac_canary):
223      browsers.append(PossibleDesktopBrowser('canary', finder_options,
224                                             mac_canary, None, False,
225                                             mac_canary_root))
226
227    if path_module.IsExecutable(mac_system):
228      browsers.append(PossibleDesktopBrowser('system', finder_options,
229                                             mac_system, None, False,
230                                             mac_system_root))
231
232    if reference_build and path_module.IsExecutable(reference_build):
233      reference_root = os.path.dirname(os.path.dirname(os.path.dirname(
234          reference_build)))
235      browsers.append(PossibleDesktopBrowser('reference', finder_options,
236                                             reference_build, None, False,
237                                             reference_root))
238
239  # Linux specific options.
240  if sys.platform.startswith('linux'):
241    versions = {
242        'system': os.path.split(os.path.realpath('/usr/bin/google-chrome'))[0],
243        'stable': '/opt/google/chrome',
244        'beta': '/opt/google/chrome-beta',
245        'dev': '/opt/google/chrome-unstable'
246    }
247
248    for version, root in versions.iteritems():
249      browser_path = os.path.join(root, 'chrome')
250      if path_module.IsExecutable(browser_path):
251        browsers.append(PossibleDesktopBrowser(version, finder_options,
252                                               browser_path, None, False, root))
253    if reference_build and path_module.IsExecutable(reference_build):
254      reference_root = os.path.dirname(reference_build)
255      browsers.append(PossibleDesktopBrowser('reference', finder_options,
256                                             reference_build, None, False,
257                                             reference_root))
258
259  # Win32-specific options.
260  if sys.platform.startswith('win'):
261    app_paths = [
262        ('system', os.path.join('Google', 'Chrome', 'Application')),
263        ('canary', os.path.join('Google', 'Chrome SxS', 'Application')),
264    ]
265    if reference_build:
266      app_paths.append(
267          ('reference', os.path.dirname(reference_build)))
268
269    for browser_name, app_path in app_paths:
270      for chromium_app_name in chromium_app_names:
271        full_path = path_module.FindInstalledWindowsApplication(
272            os.path.join(app_path, chromium_app_name))
273        if full_path:
274          browsers.append(PossibleDesktopBrowser(
275              browser_name, finder_options, full_path,
276              None, False, os.path.dirname(full_path)))
277
278  has_ozone_platform = False
279  for arg in finder_options.browser_options.extra_browser_args:
280    if "--ozone-platform" in arg:
281      has_ozone_platform = True
282
283  if len(browsers) and not has_x11_display and not has_ozone_platform:
284    logging.warning(
285      'Found (%s), but you do not have a DISPLAY environment set.' %
286      ','.join([b.browser_type for b in browsers]))
287    return []
288
289  return browsers
290