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