| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import abc |
| import logging |
| import os |
| import plistlib |
| import subprocess |
| import time |
| import typing |
| |
| import utils |
| |
| |
| class BrowserDriver(abc.ABC): |
| """Abstract Base Class encapsulating browser setup and tear down. |
| """ |
| |
| def __init__(self, browser_name: str, process_name: str): |
| self.name = browser_name |
| self.process_name = process_name |
| self.browser_process = None |
| |
| # LaunchServices can get confused when an application is launched from |
| # more than one location and break AppleScript commands. Always launch |
| # browsers from /Applications to avoid such problems. |
| self.executable_path = (os.path.join("/Applications", |
| f"{self.process_name}.app")) |
| |
| if not os.path.exists(self.executable_path): |
| raise ValueError(f"Application doesn't exist for {browser_name}.") |
| |
| @abc.abstractmethod |
| def Launch(self): |
| """Starts the browser and ensures it is started before returning. |
| """ |
| pass |
| |
| def TearDown(self): |
| """Terminates the browser and ensures it's cleaned up before returning. |
| """ |
| logging.debug(f"Tearing down {self.process_name}") |
| if self.browser_process: |
| utils.TerminateProcess(self.browser_process) |
| |
| def GetApplicationInfo(self) -> typing.Dict: |
| """ Returns the Info.plist data in the application folder. """ |
| plist_path = os.path.join(self.executable_path, "Contents", "Info.plist") |
| with open(plist_path, 'rb') as plist_file: |
| return plistlib.load(plist_file) |
| |
| def Summary(self): |
| """Returns a dictionary describing the browser. |
| """ |
| info = self.GetApplicationInfo() |
| return { |
| 'name': self.name, |
| 'version': info['CFBundleShortVersionString'], |
| 'identifier': info['CFBundleIdentifier'] |
| } |
| |
| def _EnsureStarted(self): |
| """Waits until a browser with the given `process_name` is running. |
| """ |
| while not self.browser_process: |
| self.browser_process = utils.FindProcess(self.process_name) |
| time.sleep(0.100) |
| logging.debug(f"Waiting for {self.process_name} to start") |
| logging.debug(f"{self.process_name} started") |
| |
| |
| class SafariDriver(BrowserDriver): |
| def __init__(self, extra_args=[]): |
| super().__init__("safari", "Safari") |
| self.extra_args = extra_args |
| |
| def Launch(self): |
| subprocess.call(["open", "-a", "Safari"]) |
| # Call prep_safari.scpt to make sure the run starts clean. See file |
| # comment for details. |
| subprocess.call([ |
| "osascript", |
| os.path.join(os.path.dirname(__file__), "driver_scripts_templates", |
| "prep_safari.scpt") |
| ]) |
| subprocess.call(["open", "-a", "Safari", "--args"] + self.extra_args) |
| |
| self._EnsureStarted() |
| |
| |
| class ChromiumDriver(BrowserDriver): |
| def __init__(self, |
| browser_name: str, |
| variation: str, |
| process_name: str, |
| extra_args=[]): |
| if variation != "": |
| browser_name += f"_{variation}" |
| super().__init__(browser_name, process_name) |
| self.extra_args = extra_args |
| |
| |
| def Launch(self): |
| open_args = ["-a", self.process_name] |
| subprocess.call(["open"] + open_args + ["--args"] + [ |
| "--enable-benchmarking", "--disable-stack-profiler", "--no-first-run", |
| "--no-default-browser-check" |
| ] + self.extra_args) |
| |
| self._EnsureStarted() |
| |
| def Summary(self): |
| """Returns a dictionary describing the browser. |
| """ |
| info = self.GetApplicationInfo() |
| return { |
| 'name': self.name, |
| 'identifier': info['CFBundleIdentifier'], |
| 'version': info['CFBundleShortVersionString'], |
| 'commit': info['SCMRevision'], |
| 'extra_args': self.extra_args |
| } |
| |
| |
| # Helper functions to get default browser configurations. |
| |
| |
| def Safari(): |
| return SafariDriver() |
| |
| |
| def Chrome(variation, extra_args=[]): |
| return ChromiumDriver("chrome", |
| variation, |
| "Google Chrome", |
| extra_args=extra_args) |
| |
| |
| def Canary(variation, extra_args=[]): |
| return ChromiumDriver("canary", |
| variation, |
| "Google Chrome Canary", |
| extra_args=extra_args) |
| |
| |
| def Chromium(variation, extra_args=[]): |
| return ChromiumDriver("chromium", |
| variation, |
| "Chromium", |
| extra_args=extra_args) |
| |
| |
| def Edge(variation, extra_args=[]): |
| return ChromiumDriver("edge", |
| variation, |
| "Microsoft Edge", |
| extra_args=extra_args) |
| |
| |
| PROCESS_NAMES = [ |
| "Safari", "Google Chrome", "Google Chrome Canary", "Chromium", |
| "Microsoft Edge" |
| ] |
| |
| |
| def MakeBrowserDriver(browser_name: str, |
| variation: str, |
| chrome_user_dir=None, |
| output_dir=None, |
| tracing_mode=False, |
| extra_command_line=None) -> BrowserDriver: |
| """Creates browser driver by name. |
| |
| Args: |
| browser_name: Identifier for the browser to create. Supported browsers |
| are: safari, chrome and chromium. |
| chrome_user_dir: Optional user directory path to use for chrome. |
| """ |
| |
| if "safari" == browser_name: |
| return Safari() |
| if browser_name in ["chrome", "chromium", "canary", "edge"]: |
| if chrome_user_dir: |
| chrome_extra_arg = [f"--user-data-dir={chrome_user_dir}"] |
| else: |
| chrome_extra_arg = ["--guest"] |
| if variation == 'AlignWakeUps': |
| chrome_extra_arg += ['--enable-features=AlignWakeUps'] |
| |
| if tracing_mode: |
| chrome_extra_arg += [ |
| '--enable-tracing=toplevel,toplevel.flow,mojom,navigation' |
| ] |
| trace_path = os.path.join(output_dir, "chrometrace.log") |
| chrome_extra_arg += [ |
| f'--trace-startup-file={os.path.abspath(trace_path)}' |
| ] |
| |
| if extra_command_line: |
| for command in extra_command_line: |
| # Quotes are needed to avoid to avoid cli replacement. |
| command = command.replace('"', '') |
| chrome_extra_arg += [command] |
| |
| if browser_name == "chrome": |
| return Chrome(variation, extra_args=chrome_extra_arg) |
| if browser_name == "canary": |
| return Canary(variation, extra_args=chrome_extra_arg) |
| elif browser_name == "chromium": |
| return Chromium(variation, extra_args=chrome_extra_arg) |
| elif browser_name == "edge": |
| return Edge(variation, extra_args=chrome_extra_arg) |
| return None |