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.
8 import subprocess as subprocess
14 from telemetry.core import util
15 from telemetry.core.backends import browser_backend
16 from telemetry.core.backends.chrome import chrome_browser_backend
18 class DesktopBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
19 """The backend for controlling a locally-executed browser instance, on Linux,
22 def __init__(self, browser_options, executable, flash_path, is_content_shell,
23 browser_directory, output_profile_path, extensions_to_load):
24 super(DesktopBrowserBackend, self).__init__(
25 is_content_shell=is_content_shell,
26 supports_extensions=not is_content_shell,
27 browser_options=browser_options,
28 output_profile_path=output_profile_path,
29 extensions_to_load=extensions_to_load)
31 # Initialize fields so that an explosion during init doesn't break in Close.
33 self._tmp_profile_dir = None
34 self._tmp_output_file = None
36 self._executable = executable
37 if not self._executable:
38 raise Exception('Cannot create browser, no executable found!')
40 self._flash_path = flash_path
41 if self._flash_path and not os.path.exists(self._flash_path):
42 logging.warning(('Could not find flash at %s. Running without flash.\n\n'
43 'To fix this see http://go/read-src-internal') %
45 self._flash_path = None
47 if len(extensions_to_load) > 0 and is_content_shell:
48 raise browser_backend.ExtensionsNotSupportedException(
49 'Content shell does not support extensions.')
51 self._browser_directory = browser_directory
52 self._port = util.GetAvailableLocalPort()
53 self._profile_dir = None
54 self._supports_net_benchmarking = True
55 self._tmp_minidump_dir = tempfile.mkdtemp()
59 def _SetupProfile(self):
60 if not self.browser_options.dont_override_profile:
61 if self._output_profile_path:
62 # If both |_output_profile_path| and |profile_dir| are specified then
63 # the calling code will throw an exception, so we don't need to worry
64 # about that case here.
65 self._tmp_profile_dir = self._output_profile_path
67 self._tmp_profile_dir = tempfile.mkdtemp()
68 profile_dir = self._profile_dir or self.browser_options.profile_dir
70 if self.is_content_shell:
71 logging.critical('Profiles cannot be used with content shell')
73 logging.info("Using profile directory:'%s'." % profile_dir)
74 shutil.rmtree(self._tmp_profile_dir)
75 shutil.copytree(profile_dir, self._tmp_profile_dir)
77 def _LaunchBrowser(self):
78 args = [self._executable]
79 args.extend(self.GetBrowserStartupArgs())
80 if self.browser_options.startup_url:
81 args.append(self.browser_options.startup_url)
82 env = os.environ.copy()
83 env['CHROME_HEADLESS'] = '1' # Don't upload minidumps.
84 env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir
85 if not self.browser_options.show_stdout:
86 self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
87 self._proc = subprocess.Popen(
88 args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env)
90 self._proc = subprocess.Popen(args, env=env)
93 self._WaitForBrowserToComeUp()
94 self._PostBrowserStartupInitialization()
99 def GetBrowserStartupArgs(self):
100 args = super(DesktopBrowserBackend, self).GetBrowserStartupArgs()
101 args.append('--remote-debugging-port=%i' % self._port)
102 args.append('--enable-crash-reporter-for-testing')
103 if not self.is_content_shell:
104 args.append('--window-size=1280,1024')
106 args.append('--ppapi-flash-path=%s' % self._flash_path)
107 if self._supports_net_benchmarking:
108 args.append('--enable-net-benchmarking')
110 args.append('--enable-benchmarking')
111 if not self.browser_options.dont_override_profile:
112 args.append('--user-data-dir=%s' % self._tmp_profile_dir)
115 def SetProfileDirectory(self, profile_dir):
116 # Make sure _profile_dir hasn't already been set.
117 assert self._profile_dir is None
119 if self.is_content_shell:
120 logging.critical('Profile creation cannot be used with content shell')
123 self._profile_dir = profile_dir
126 self._LaunchBrowser()
128 # For old chrome versions, might have to relaunch to have the
129 # correct net_benchmarking switch.
130 if self.chrome_branch_number < 1418:
132 self._supports_net_benchmarking = False
133 self._LaunchBrowser()
138 return self._proc.pid
142 def browser_directory(self):
143 return self._browser_directory
146 def profile_directory(self):
147 return self._tmp_profile_dir
149 def IsBrowserRunning(self):
150 return self._proc.poll() == None
152 def GetStandardOutput(self):
153 assert self._tmp_output_file, "Can't get standard output with show_stdout"
154 self._tmp_output_file.flush()
156 with open(self._tmp_output_file.name) as f:
161 def GetStackTrace(self):
162 stackwalk = util.FindSupportBinary('minidump_stackwalk')
164 logging.warning('minidump_stackwalk binary not found. Must build it to '
165 'symbolize crash dumps. Returning browser stdout.')
166 return self.GetStandardOutput()
168 dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp'))
170 logging.warning('No crash dump found. Returning browser stdout.')
171 return self.GetStandardOutput()
172 most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0]
173 if os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60)):
174 logging.warn('Crash dump is older than 5 minutes. May not be correct.')
176 minidump = most_recent_dump + '.stripped'
177 with open(most_recent_dump, 'rb') as infile:
178 with open(minidump, 'wb') as outfile:
179 outfile.write(''.join(infile.read().partition('MDMP')[1:]))
181 symbols = glob.glob(os.path.join(os.path.dirname(stackwalk), '*.breakpad*'))
183 logging.warning('No breakpad symbols found. Returning browser stdout.')
184 return self.GetStandardOutput()
186 symbols_path = os.path.join(self._tmp_minidump_dir, 'symbols')
187 for symbol in sorted(symbols, key=os.path.getmtime, reverse=True):
188 if not os.path.isfile(symbol):
190 with open(symbol, 'r') as f:
191 fields = f.readline().split()
195 binary = ' '.join(fields[4:])
196 symbol_path = os.path.join(symbols_path, binary, sha)
197 if os.path.exists(symbol_path):
199 os.makedirs(symbol_path)
200 shutil.copyfile(symbol, os.path.join(symbol_path, binary + '.sym'))
202 error = tempfile.NamedTemporaryFile('w', 0)
203 return subprocess.Popen(
204 [stackwalk, minidump, symbols_path],
205 stdout=subprocess.PIPE, stderr=error).communicate()[0]
211 super(DesktopBrowserBackend, self).Close()
218 return self._proc.poll() != None
220 # Try to politely shutdown, first.
221 self._proc.terminate()
223 util.WaitFor(IsClosed, timeout=1)
225 except util.TimeoutException:
232 util.WaitFor(IsClosed, timeout=5)
234 except util.TimeoutException:
236 raise Exception('Could not shutdown the browser.')
238 if self._output_profile_path:
239 # If we need the output then double check that it exists.
240 if not (self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir)):
241 raise Exception("No profile directory generated by Chrome: '%s'." %
242 self._tmp_profile_dir)
244 # If we don't need the profile after the run then cleanup.
245 if self._tmp_profile_dir and os.path.exists(self._tmp_profile_dir):
246 shutil.rmtree(self._tmp_profile_dir, ignore_errors=True)
247 self._tmp_profile_dir = None
249 if self._tmp_output_file:
250 self._tmp_output_file.close()
251 self._tmp_output_file = None
253 def CreateForwarder(self, *port_pairs):
254 return browser_backend.DoNothingForwarder(*port_pairs)