1424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# found in the LICENSE file.
45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
53551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)import glob
63551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)import heapq
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import logging
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import os
946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)import os.path
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import shutil
115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liuimport subprocess as subprocess
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import sys
132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import tempfile
143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)import time
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from telemetry.core import exceptions
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)from telemetry.core import util
18a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)from telemetry.core.backends import browser_backend
19a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)from telemetry.core.backends.chrome import chrome_browser_backend
205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from telemetry.util import path
210529e5d033099cbfc42635f6f6183833b09dff6eBen Murdochfrom telemetry.util import support_binaries
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
24a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)class DesktopBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """The backend for controlling a locally-executed browser instance, on Linux,
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  Mac or Windows.
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """
2858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  def __init__(self, browser_options, executable, flash_path, is_content_shell,
2958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)               browser_directory, output_profile_path, extensions_to_load):
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    super(DesktopBrowserBackend, self).__init__(
31116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        supports_tab_control=not is_content_shell,
3290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        supports_extensions=not is_content_shell,
3358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        browser_options=browser_options,
3458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        output_profile_path=output_profile_path,
3558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)        extensions_to_load=extensions_to_load)
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Initialize fields so that an explosion during init doesn't break in Close.
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._proc = None
393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    self._tmp_profile_dir = None
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._tmp_output_file = None
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._executable = executable
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not self._executable:
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      raise Exception('Cannot create browser, no executable found!')
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    assert not flash_path or os.path.exists(flash_path)
47eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    self._flash_path = flash_path
48eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
49116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self._is_content_shell = is_content_shell
50116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
5158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    if len(extensions_to_load) > 0 and is_content_shell:
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      raise browser_backend.ExtensionsNotSupportedException(
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'Content shell does not support extensions.')
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
55bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch    self._browser_directory = browser_directory
56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._port = None
573551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    self._tmp_minidump_dir = tempfile.mkdtemp()
585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self._crash_service = None
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
60a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    self._SetupProfile()
61a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
62a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  def _SetupProfile(self):
6358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    if not self.browser_options.dont_override_profile:
64424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if self._output_profile_path:
65424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        # If both |_output_profile_path| and |profile_dir| are specified then
66424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        # the calling code will throw an exception, so we don't need to worry
67424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        # about that case here.
68424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        self._tmp_profile_dir = self._output_profile_path
69424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      else:
70424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        self._tmp_profile_dir = tempfile.mkdtemp()
711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      profile_dir = self.browser_options.profile_dir
72a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      if profile_dir:
73116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        if self._is_content_shell:
74a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          logging.critical('Profiles cannot be used with content shell')
75a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          sys.exit(1)
76424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        logging.info("Using profile directory:'%s'." % profile_dir)
773551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        shutil.rmtree(self._tmp_profile_dir)
783551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        shutil.copytree(profile_dir, self._tmp_profile_dir)
7946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if self.browser_options.use_devtools_active_port:
8046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      # No matter whether we're using an existing profile directory or
8146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      # creating a new one, always delete the well-known file containing
8246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      # the active DevTools port number.
8346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      port_file = self._GetDevToolsActivePortPath()
8446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if os.path.isfile(port_file):
8546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        try:
8646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          os.remove(port_file)
8746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        except Exception as e:
8846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          logging.critical('Unable to remove DevToolsActivePort file: %s' % e)
8946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          sys.exit(1)
9046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
9146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  def _GetDevToolsActivePortPath(self):
9246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return os.path.join(self.profile_directory, 'DevToolsActivePort')
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def _GetCrashServicePipeName(self):
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    # Ensure a unique pipe name by using the name of the temp dir.
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return r'\\.\pipe\%s_service' % os.path.basename(self._tmp_minidump_dir)
975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def _StartCrashService(self):
995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    os_name = self._browser.platform.GetOSName()
1005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if os_name != 'win':
1015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return None
1025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return subprocess.Popen([
1035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        support_binaries.FindPath('crash_service', os_name),
1045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        '--no-window',
1055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        '--dumps-dir=%s' % self._tmp_minidump_dir,
1065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        '--pipe-name=%s' % self._GetCrashServicePipeName()])
1075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def _GetCdbPath(self):
1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    possible_paths = (
1105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        'Debugging Tools For Windows',
1115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        'Debugging Tools For Windows (x86)',
1125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        'Debugging Tools For Windows (x64)',
1135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'),
1145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'),
1155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers',
1165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                     'x86'),
1175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers',
1185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                     'x64'),
1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    )
1205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    for possible_path in possible_paths:
1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      app_path = os.path.join(possible_path, 'cdb.exe')
1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      app_path = path.FindInstalledWindowsApplication(app_path)
1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if app_path:
1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return app_path
1255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return None
1265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def HasBrowserFinishedLaunching(self):
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # In addition to the functional check performed by the base class, quickly
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # check if the browser process is still alive.
130c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    if not self.IsBrowserRunning():
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      raise exceptions.ProcessGoneException(
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          "Return code: %d" % self._proc.returncode)
13346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if self.browser_options.use_devtools_active_port:
13446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      # The Telemetry user selected the new code path to start DevTools on
13546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      # an ephemeral port. Wait for the well-known file containing the port
13646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      # number to exist.
13746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      port_file = self._GetDevToolsActivePortPath()
13846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if not os.path.isfile(port_file):
13946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        # File isn't ready yet. Return false. Will retry.
14046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        return False
141f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      # Attempt to avoid reading the file until it's populated.
142f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      got_port = False
143f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      try:
144f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        if os.stat(port_file).st_size > 0:
145f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)          with open(port_file) as f:
146f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)            port_string = f.read()
147f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)            self._port = int(port_string)
148f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)            logging.info('Discovered ephemeral port %s' % self._port)
149f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)            got_port = True
150f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      except Exception:
151f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        # Both stat and open can throw exceptions.
152f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        pass
153f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      if not got_port:
154f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        # File isn't ready yet. Return false. Will retry.
155f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        return False
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return super(DesktopBrowserBackend, self).HasBrowserFinishedLaunching()
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def GetBrowserStartupArgs(self):
1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    args = super(DesktopBrowserBackend, self).GetBrowserStartupArgs()
16046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if self.browser_options.use_devtools_active_port:
16146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      self._port = 0
16246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    else:
16346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      self._port = util.GetUnreservedAvailableLocalPort()
16446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    logging.info('Requested remote debugging port: %d' % self._port)
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    args.append('--remote-debugging-port=%i' % self._port)
1663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    args.append('--enable-crash-reporter-for-testing')
167a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    args.append('--use-mock-keychain')
168116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    if not self._is_content_shell:
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      args.append('--window-size=1280,1024')
170eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if self._flash_path:
171eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        args.append('--ppapi-flash-path=%s' % self._flash_path)
17258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)      if not self.browser_options.dont_override_profile:
1733551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        args.append('--user-data-dir=%s' % self._tmp_profile_dir)
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return args
1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
176a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  def Start(self):
177c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    assert not self._proc, 'Must call Close() before Start()'
178c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
179c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    args = [self._executable]
180c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    args.extend(self.GetBrowserStartupArgs())
181c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    if self.browser_options.startup_url:
182c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      args.append(self.browser_options.startup_url)
183c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    env = os.environ.copy()
184c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    env['CHROME_HEADLESS'] = '1'  # Don't upload minidumps.
185c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir
1865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    env['CHROME_BREAKPAD_PIPE_NAME'] = self._GetCrashServicePipeName()
1875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self._crash_service = self._StartCrashService()
188c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    logging.debug('Starting Chrome %s', args)
189c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    if not self.browser_options.show_stdout:
190c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
191c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      self._proc = subprocess.Popen(
192c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch          args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env)
193c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    else:
194c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      self._proc = subprocess.Popen(args, env=env)
195c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
196c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    try:
197c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      self._WaitForBrowserToComeUp()
198c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    except:
199c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      self.Close()
200c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      raise
201a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  @property
2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def pid(self):
2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if self._proc:
2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return self._proc.pid
2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return None
2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
20890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  @property
209bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  def browser_directory(self):
210bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch    return self._browser_directory
211bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch
212bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  @property
21390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  def profile_directory(self):
2143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    return self._tmp_profile_dir
21590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
2162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def IsBrowserRunning(self):
217c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    return self._proc and self._proc.poll() == None
2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def GetStandardOutput(self):
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if not self._tmp_output_file:
2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if self.browser_options.show_stdout:
2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # This can happen in the case that loading the Chrome binary fails.
2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # We print rather than using logging here, because that makes a
2245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # recursive call to this function.
225a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        print >> sys.stderr, "Can't get standard output with --show-stdout"
2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      return ''
2272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._tmp_output_file.flush()
2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
2292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      with open(self._tmp_output_file.name) as f:
2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return f.read()
2312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except IOError:
2322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return ''
2332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def _GetMostRecentMinidump(self):
2353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp'))
2363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    if not dumps:
2375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return None
2383551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0]
2393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    if os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60)):
240f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      logging.warning('Crash dump is older than 5 minutes. May not be correct.')
2415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return most_recent_dump
2425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def _GetStackFromMinidump(self, minidump):
2445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    os_name = self._browser.platform.GetOSName()
2455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if os_name == 'win':
2465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      cdb = self._GetCdbPath()
2475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if not cdb:
2485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        logging.warning('cdb.exe not found.')
2495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        return None
2505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      output = subprocess.check_output([cdb, '-y', self._browser_directory,
2515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                                        '-c', '.ecxr;k30;q', '-z', minidump])
2525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      stack_start = output.find('ChildEBP')
2535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      stack_end = output.find('quit:')
2545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return output[stack_start:stack_end]
2555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    stackwalk = support_binaries.FindPath('minidump_stackwalk', os_name)
2575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if not stackwalk:
2585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      logging.warning('minidump_stackwalk binary not found.')
2595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return None
260f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
2615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    with open(minidump, 'rb') as infile:
2625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      minidump += '.stripped'
2633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      with open(minidump, 'wb') as outfile:
2643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        outfile.write(''.join(infile.read().partition('MDMP')[1:]))
2653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
2663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    symbols_path = os.path.join(self._tmp_minidump_dir, 'symbols')
2671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    symbols = glob.glob(os.path.join(self._browser_directory, '*.breakpad*'))
2691320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if symbols:
2701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      for symbol in sorted(symbols, key=os.path.getmtime, reverse=True):
2711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        if not os.path.isfile(symbol):
2721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          continue
2731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        with open(symbol, 'r') as f:
2741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          fields = f.readline().split()
2751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          if not fields:
2761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            continue
2771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          sha = fields[3]
2781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          binary = ' '.join(fields[4:])
2791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        symbol_path = os.path.join(symbols_path, binary, sha)
2801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        if os.path.exists(symbol_path):
2813551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)          continue
2821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        os.makedirs(symbol_path)
2831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        shutil.copyfile(symbol, os.path.join(symbol_path, binary + '.sym'))
2841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    else:
2851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      logging.info('Dumping breakpad symbols')
2861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      generate_breakpad_symbols_path = os.path.join(
2871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          util.GetChromiumSrcDir(), "components", "breakpad",
2881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          "tools", "generate_breakpad_symbols.py")
2891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      cmd = [
2901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          sys.executable,
2911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          generate_breakpad_symbols_path,
2921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          '--binary=%s' % self._executable,
2931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          '--symbols-dir=%s' % symbols_path,
2941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          '--build-dir=%s' % self._browser_directory,
2951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          ]
2961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      try:
2981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        subprocess.check_output(cmd, stderr=open(os.devnull, 'w'))
2991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      except subprocess.CalledProcessError:
3001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        logging.warning('Failed to execute "%s"' % ' '.join(cmd))
3011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return None
3023551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
3035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return subprocess.check_output([stackwalk, minidump, symbols_path],
3045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                                   stderr=open(os.devnull, 'w'))
3055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def GetStackTrace(self):
3075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    most_recent_dump = self._GetMostRecentMinidump()
3085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if not most_recent_dump:
3095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      logging.warning('No crash dump found. Returning browser stdout.')
3105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return self.GetStandardOutput()
3115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    stack = self._GetStackFromMinidump(most_recent_dump)
3135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if not stack:
3145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      logging.warning('Failed to symbolize minidump. Returning browser stdout.')
3155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return self.GetStandardOutput()
3165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return stack
3187dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
3192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def __del__(self):
3202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.Close()
3212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def Close(self):
3232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    super(DesktopBrowserBackend, self).Close()
3242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    # Shutdown politely if the profile may be used again.
3266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if self._output_profile_path and self.IsBrowserRunning():
327c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      self._proc.terminate()
328c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      try:
329c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        util.WaitFor(lambda: not self.IsBrowserRunning(), timeout=5)
330c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        self._proc = None
331c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      except util.TimeoutException:
332c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        logging.warning('Failed to gracefully shutdown. Proceeding to kill.')
3332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    # Shutdown aggressively if the above failed or if the profile is temporary.
335c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    if self.IsBrowserRunning():
336c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      self._proc.kill()
33703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    self._proc = None
3382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if self._crash_service:
3405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      self._crash_service.kill()
3415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      self._crash_service = None
3425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
343424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if self._output_profile_path:
344424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # If we need the output then double check that it exists.
345424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if not (self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir)):
346424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        raise Exception("No profile directory generated by Chrome: '%s'." %
347424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)            self._tmp_profile_dir)
348424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    else:
349424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # If we don't need the profile after the run then cleanup.
350424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir):
351424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        shutil.rmtree(self._tmp_profile_dir, ignore_errors=True)
352424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        self._tmp_profile_dir = None
3532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if self._tmp_output_file:
3552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self._tmp_output_file.close()
3562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self._tmp_output_file = None
357