geckoinstance.py (29685B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/ 4 5 # ALL CHANGES TO THIS FILE MUST HAVE REVIEW FROM A MARIONETTE PEER! 6 # 7 # Please refer to INSTRUCTIONS TO ADD A NEW PREFERENCE in 8 # remote/shared/RecommendedPreferences.sys.mjs 9 # 10 # The Marionette Python client is used out-of-tree with various builds of 11 # Firefox. Removing a preference from this file will cause regressions, 12 # so please be careful and get review from a Testing :: Marionette peer 13 # before you make any changes to this file. 14 15 import codecs 16 import io 17 import json 18 import os 19 import sys 20 import tempfile 21 import time 22 import traceback 23 from copy import deepcopy 24 25 import mozversion 26 from mozprofile import Profile 27 from mozrunner import FennecEmulatorRunner, Runner 28 29 from . import errors 30 31 if sys.platform.startswith("darwin"): 32 # Marionette's own processhandler is only used on MacOS for now 33 from .processhandler import UNKNOWN_RETURNCODE, ProcessHandler 34 35 36 class GeckoInstance: 37 required_prefs = { 38 # Make sure Shield doesn't hit the network. 39 "app.normandy.api_url": "", 40 # Increase the APZ content response timeout in tests to 1 minute. 41 # This is to accommodate the fact that test environments tends to be slower 42 # than production environments (with the b2g emulator being the slowest of them 43 # all), resulting in the production timeout value sometimes being exceeded 44 # and causing false-positive test failures. See bug 1176798, bug 1177018, 45 # bug 1210465. 46 "apz.content_response_timeout": 60000, 47 # Disable extension discovery 48 "browser.discovery.enabled": False, 49 # Make sure error page is not shown for blank pages with 4xx or 5xx response code 50 "browser.http.blank_page_with_error_response.enabled": True, 51 # Disable CFR features for automated tests. 52 "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features": False, 53 # Don't pull sponsored Top Sites content from the network 54 "browser.newtabpage.activity-stream.showSponsoredTopSites": False, 55 # Disable geolocation ping (#1) 56 "browser.region.network.url": "", 57 # Don't pull Top Sites content from the network 58 "browser.topsites.contile.enabled": False, 59 # Disable translations 60 "browser.translations.enable": False, 61 # Disable UI tour 62 "browser.uitour.enabled": False, 63 # Disable captive portal 64 "captivedetect.canonicalURL": "", 65 # Defensively disable data reporting systems 66 "datareporting.healthreport.documentServerURI": "http://%(server)s/dummy/healthreport/", 67 "datareporting.healthreport.logging.consoleEnabled": False, 68 "datareporting.healthreport.service.enabled": False, 69 "datareporting.healthreport.service.firstRun": False, 70 "datareporting.healthreport.uploadEnabled": False, 71 "datareporting.usage.uploadEnabled": False, 72 "telemetry.fog.test.localhost_port": -1, 73 # Do not show datareporting policy notifications which can interfere with tests 74 "datareporting.policy.dataSubmissionEnabled": False, 75 "datareporting.policy.dataSubmissionPolicyBypassNotification": True, 76 # Disable popup-blocker 77 "dom.disable_open_during_load": False, 78 # Enabling the support for File object creation in the content process. 79 "dom.file.createInChild": True, 80 # Disable delayed user input event handling 81 "dom.input_events.security.minNumTicks": 0, 82 # Disable delayed user input event handling 83 "dom.input_events.security.minTimeElapsedInMS": 0, 84 # Disable the ProcessHangMonitor 85 "dom.ipc.reportProcessHangs": False, 86 # No slow script dialogs 87 "dom.max_chrome_script_run_time": 0, 88 "dom.max_script_run_time": 0, 89 # Disable navigation change rate limitation 90 "dom.navigation.navigationRateLimit.count": 0, 91 # DOM Push 92 "dom.push.connection.enabled": False, 93 # Screen Orientation API 94 "dom.screenorientation.allow-lock": True, 95 # Disable dialog abuse if alerts are triggered too quickly 96 "dom.successive_dialog_time_limit": 0, 97 # Only load extensions from the application and user profile 98 # AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION 99 "extensions.autoDisableScopes": 0, 100 "extensions.enabledScopes": 5, 101 # Disable form autofill for extensions and credit cards 102 "extensions.formautofill.addresses.enabled": False, 103 "extensions.formautofill.creditCards.enabled": False, 104 # Disable metadata caching for installed add-ons by default 105 "extensions.getAddons.cache.enabled": False, 106 # Disable intalling any distribution add-ons 107 "extensions.installDistroAddons": False, 108 # Turn off extension updates so they don't bother tests 109 "extensions.update.enabled": False, 110 "extensions.update.notifyUser": False, 111 # Redirect various extension update URLs 112 "extensions.blocklist.detailsURL": ( 113 "http://%(server)s/extensions-dummy/blocklistDetailsURL" 114 ), 115 "extensions.blocklist.itemURL": "http://%(server)s/extensions-dummy/blocklistItemURL", 116 "extensions.hotfix.url": "http://%(server)s/extensions-dummy/hotfixURL", 117 "extensions.systemAddon.update.enabled": False, 118 "extensions.update.background.url": ( 119 "http://%(server)s/extensions-dummy/updateBackgroundURL" 120 ), 121 "extensions.update.url": "http://%(server)s/extensions-dummy/updateURL", 122 # Make sure opening about:addons won"t hit the network 123 "extensions.getAddons.discovery.api_url": "data:, ", 124 "extensions.getAddons.get.url": "http://%(server)s/extensions-dummy/repositoryGetURL", 125 "extensions.getAddons.search.browseURL": ( 126 "http://%(server)s/extensions-dummy/repositoryBrowseURL" 127 ), 128 # Allow the application to have focus even it runs in the background 129 "focusmanager.testmode": True, 130 # Disable useragent updates 131 "general.useragent.updates.enabled": False, 132 # Disable geolocation ping (#2) 133 "geo.provider.network.url": "", 134 # Always use network provider for geolocation tests 135 # so we bypass the OSX dialog raised by the corelocation provider 136 "geo.provider.testing": True, 137 # Do not scan Wifi 138 "geo.wifi.scan": False, 139 # Ensure webrender is on, no need for environment variables 140 "gfx.webrender.all": True, 141 # Disable idle-daily notifications to avoid expensive operations 142 # that may cause unexpected test timeouts. 143 "idle.lastDailyNotification": -1, 144 # Disable Firefox accounts ping 145 "identity.fxaccounts.auth.uri": "https://{server}/dummy/fxa", 146 # Disable download and usage of OpenH264, and Widevine plugins 147 "media.gmp-manager.updateEnabled": False, 148 # Disable the GFX sanity window 149 "media.sanity-test.disabled": True, 150 "media.volume_scale": "0.01", 151 # Disable connectivity service pings 152 "network.connectivity-service.enabled": False, 153 # Do not prompt for temporary redirects 154 "network.http.prompt-temp-redirect": False, 155 # Do not automatically switch between offline and online 156 "network.manage-offline-status": False, 157 # Make sure SNTP requests don't hit the network 158 "network.sntp.pools": "%(server)s", 159 # Disabled for causing marionette crashes on OSX. See bug 1882856 160 "network.dns.native_https_query": False, 161 # Privacy and Tracking Protection 162 "privacy.trackingprotection.enabled": False, 163 "privacy.trackingprotection.pbmode.enabled": False, 164 # Disable recommended automation prefs in CI 165 "remote.prefs.recommended": False, 166 # Don't do network connections for mitm priming 167 "security.certerrors.mitm.priming.enabled": False, 168 # Tests don't wait for the notification button security delay 169 "security.notification_enable_delay": 0, 170 # Do not download intermediate certificates 171 "security.remote_settings.intermediates.enabled": False, 172 # Disable logging for remote settings 173 "services.settings.loglevel": "off", 174 # Ensure blocklist updates don't hit the network 175 "services.settings.server": "data:,#remote-settings-dummy/v1", 176 # Disable password capture, so that tests that include forms aren"t 177 # influenced by the presence of the persistent doorhanger notification 178 "signon.rememberSignons": False, 179 # Disable alerts for credential issues 180 "signon.management.page.breach-alerts.enabled": False, 181 "signon.management.page.vulnerable-passwords.enabled": False, 182 # Prevent starting into safe mode after application crashes 183 # Do not show TOU new user modal which can interfere with tests 184 "termsofuse.bypassNotification": True, 185 "toolkit.startup.max_resumed_crashes": -1, 186 # Disable most telemetry pings 187 "toolkit.telemetry.server": "https://%(server)s/telemetry-dummy/", 188 # Disable window occlusion on Windows, see Bug 1802473. 189 "widget.windows.window_occlusion_tracking.enabled": False, 190 } 191 192 def __init__( 193 self, 194 host=None, 195 port=None, 196 bin=None, 197 profile=None, 198 addons=None, 199 app_args=None, 200 debugger_info=None, 201 symbols_path=None, 202 gecko_log=None, 203 prefs=None, 204 workspace=None, 205 verbose=0, 206 headless=False, 207 ): 208 self.runner_class = Runner 209 self.app_args = app_args or [] 210 self.debugger_info = debugger_info 211 self.runner = None 212 self.symbols_path = symbols_path 213 self.binary = bin 214 215 self.marionette_host = host 216 self.marionette_port = port 217 self.addons = addons 218 self.prefs = prefs 219 self.required_prefs = deepcopy(self.required_prefs) 220 if prefs: 221 self.required_prefs.update(prefs) 222 223 self._gecko_log_option = gecko_log 224 self._gecko_log = None 225 self.verbose = verbose 226 self.headless = headless 227 228 # keep track of errors to decide whether instance is unresponsive 229 self.unresponsive_count = 0 230 231 # Alternative to default temporary directory 232 self.workspace = workspace 233 234 # Don't use the 'profile' property here, because sub-classes could add 235 # further preferences and data, which would not be included in the new 236 # profile 237 self._profile = profile 238 239 @property 240 def gecko_log(self): 241 if self._gecko_log: 242 return self._gecko_log 243 244 path = self._gecko_log_option 245 if path != "-": 246 if path is None: 247 path = "gecko.log" 248 elif os.path.isdir(path): 249 fname = f"gecko-{time.time()}.log" 250 path = os.path.join(path, fname) 251 252 path = os.path.realpath(path) 253 if os.access(path, os.F_OK): 254 os.remove(path) 255 256 self._gecko_log = path 257 return self._gecko_log 258 259 @property 260 def profile(self): 261 return self._profile 262 263 @profile.setter 264 def profile(self, value): 265 self._update_profile(value) 266 267 def _update_profile(self, profile=None, profile_name=None): 268 """Check if the profile has to be created, or replaced. 269 270 :param profile: A Profile instance to be used. 271 :param name: Profile name to be used in the path. 272 """ 273 if self.runner and self.runner.is_running(): 274 raise errors.MarionetteException( 275 "The current profile can only be updated " 276 "when the instance is not running" 277 ) 278 279 if isinstance(profile, Profile): 280 # Only replace the profile if it is not the current one 281 if hasattr(self, "_profile") and profile is self._profile: 282 return 283 284 else: 285 profile_args = self.profile_args 286 profile_path = profile 287 288 # If a path to a profile is given then clone it 289 if isinstance(profile_path, str): 290 profile_args["path_from"] = profile_path 291 profile_args["path_to"] = tempfile.mkdtemp( 292 suffix=f".{profile_name or os.path.basename(profile_path)}", 293 dir=self.workspace, 294 ) 295 # The target must not exist yet 296 os.rmdir(profile_args["path_to"]) 297 298 profile = Profile.clone(**profile_args) 299 300 # Otherwise create a new profile 301 else: 302 profile_args["profile"] = tempfile.mkdtemp( 303 suffix=".{}".format(profile_name or "mozrunner"), 304 dir=self.workspace, 305 ) 306 profile = Profile(**profile_args) 307 profile.create_new = True 308 309 if isinstance(self.profile, Profile): 310 self.profile.cleanup() 311 312 self._profile = profile 313 314 def switch_profile(self, profile_name=None, clone_from=None): 315 """Switch the profile by using the given name, and optionally clone it. 316 317 Compared to :attr:`profile` this method allows to switch the profile 318 by giving control over the profile name as used for the new profile. It 319 also always creates a new blank profile, or as clone of an existent one. 320 321 :param profile_name: Optional, name of the profile, which will be used 322 as part of the profile path (folder name containing the profile). 323 :clone_from: Optional, if specified the new profile will be cloned 324 based on the given profile. This argument can be an instance of 325 ``mozprofile.Profile``, or the path of the profile. 326 """ 327 if isinstance(clone_from, Profile): 328 clone_from = clone_from.profile 329 330 self._update_profile(clone_from, profile_name=profile_name) 331 332 @property 333 def profile_args(self): 334 args = {"preferences": deepcopy(self.required_prefs)} 335 args["preferences"]["marionette.port"] = self.marionette_port 336 args["preferences"]["marionette.defaultPrefs.port"] = self.marionette_port 337 338 if self.prefs: 339 args["preferences"].update(self.prefs) 340 341 if self.verbose: 342 level = "Trace" if self.verbose >= 2 else "Debug" 343 args["preferences"]["remote.log.level"] = level 344 345 if "-jsdebugger" in self.app_args: 346 args["preferences"].update({ 347 "devtools.browsertoolbox.panel": "jsdebugger", 348 "devtools.chrome.enabled": True, 349 "devtools.debugger.prompt-connection": False, 350 "devtools.debugger.remote-enabled": True, 351 "devtools.testing": True, 352 }) 353 354 if self.addons: 355 args["addons"] = self.addons 356 357 return args 358 359 @classmethod 360 def create(cls, app=None, *args, **kwargs): 361 try: 362 if not app and kwargs["bin"] is not None: 363 app_id = mozversion.get_version(binary=kwargs["bin"])["application_id"] 364 app = app_ids[app_id] 365 366 instance_class = apps[app] 367 except (OSError, KeyError): 368 exc, val, tb = sys.exc_info() 369 msg = f'Application "{app}" unknown (should be one of {list(apps.keys())})' 370 raise NotImplementedError(msg).with_traceback(tb) 371 372 return instance_class(*args, **kwargs) 373 374 def start(self): 375 self._update_profile(self.profile) 376 self.runner = self.runner_class(**self._get_runner_args()) 377 378 # debugger information 379 debug_args = None 380 interactive = False 381 382 if self.debugger_info: 383 debug_args = [self.debugger_info.path] + self.debugger_info.args 384 interactive = self.debugger_info.interactive 385 386 self.runner.start(debug_args, interactive) 387 388 def _get_runner_args(self): 389 process_args = { 390 "processOutputLine": [NullOutput()], 391 "universal_newlines": True, 392 } 393 394 if self.gecko_log == "-": 395 if getattr(sys.stdout, "encoding") == "utf-8": 396 process_args["stream"] = sys.stdout 397 elif hasattr(sys.stdout, "buffer"): 398 process_args["stream"] = codecs.getwriter("utf-8")(sys.stdout.buffer) 399 elif isinstance(sys.stdout, io.TextIOBase): 400 # If sys.stdout expects unicode strings, we can't wrap it because the 401 # wrapper will write byte strings. This can happen when e.g. tests 402 # replace sys.stdout with a io.StringIO(). 403 process_args["stream"] = sys.stdout 404 else: 405 process_args["stream"] = codecs.getwriter("utf-8")(sys.stdout) 406 else: 407 process_args["logfile"] = self.gecko_log 408 409 env = os.environ.copy() 410 411 # Store all required preferences for tests which need to create clean profiles. 412 required_prefs_keys = list(self.required_prefs.keys()) 413 env["MOZ_MARIONETTE_REQUIRED_PREFS"] = json.dumps(required_prefs_keys) 414 415 if self.headless: 416 env["MOZ_HEADLESS"] = "1" 417 env["DISPLAY"] = "77" # Set a fake display. 418 419 # environment variables needed for crashreporting 420 # https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting 421 env.update({ 422 "MOZ_CRASHREPORTER": "1", 423 "MOZ_CRASHREPORTER_NO_REPORT": "1", 424 "MOZ_CRASHREPORTER_SHUTDOWN": "1", 425 }) 426 427 extra_args = ["-marionette", "-remote-allow-system-access"] 428 args = { 429 "binary": self.binary, 430 "profile": self.profile, 431 "cmdargs": extra_args + self.app_args, 432 "env": env, 433 "symbols_path": self.symbols_path, 434 "process_args": process_args, 435 } 436 437 if sys.platform.startswith("darwin"): 438 # Bug 1887666: The custom process handler class for Marionette is 439 # only supported on MacOS at the moment. 440 args["process_class"] = ProcessHandler 441 442 return args 443 444 def close(self, clean=False): 445 """ 446 Close the managed Gecko process. 447 448 Depending on self.runner_class, setting `clean` to True may also kill 449 the emulator process in which this instance is running. 450 451 :param clean: If True, also perform runner cleanup. 452 """ 453 if self.runner: 454 self.runner.stop() 455 if clean: 456 self.runner.cleanup() 457 458 if clean: 459 if isinstance(self.profile, Profile): 460 self.profile.cleanup() 461 self.profile = None 462 463 def restart(self, prefs=None, clean=True): 464 """ 465 Close then start the managed Gecko process. 466 467 :param prefs: Dictionary of preference names and values. 468 :param clean: If True, reset the profile before starting. 469 """ 470 if prefs: 471 self.prefs = prefs 472 else: 473 self.prefs = None 474 475 self.close(clean=clean) 476 self.start() 477 478 def update_process(self, pid, timeout=None): 479 """Update the process to track when the application re-launched itself""" 480 if sys.platform.startswith("darwin"): 481 # The new process handler is only supported on MacOS yet 482 returncode = self.runner.process_handler.update_process(pid, timeout) 483 if returncode not in [0, UNKNOWN_RETURNCODE]: 484 raise OSError( 485 f"Old process inappropriately quit with exit code: {returncode}" 486 ) 487 488 else: 489 returncode = self.runner.process_handler.check_for_detached(pid) 490 491 492 class FennecInstance(GeckoInstance): 493 fennec_prefs = { 494 # Enable output for dump() and chrome console API 495 "browser.dom.window.dump.enabled": True, 496 "devtools.console.stdout.chrome": True, 497 # Disable safe browsing / tracking protection updates 498 "browser.safebrowsing.update.enabled": False, 499 # Do not restore the last open set of tabs if the browser has crashed 500 "browser.sessionstore.resume_from_crash": False, 501 } 502 503 def __init__( 504 self, 505 emulator_binary=None, 506 avd_home=None, 507 avd=None, 508 adb_path=None, 509 serial=None, 510 connect_to_running_emulator=False, 511 package_name=None, 512 env=None, 513 *args, 514 **kwargs, 515 ): 516 required_prefs = deepcopy(FennecInstance.fennec_prefs) 517 required_prefs.update(kwargs.get("prefs", {})) 518 519 super().__init__(*args, **kwargs) 520 self.required_prefs.update(required_prefs) 521 522 self.runner_class = FennecEmulatorRunner 523 # runner args 524 self._package_name = package_name 525 self.emulator_binary = emulator_binary 526 self.avd_home = avd_home 527 self.adb_path = adb_path 528 self.avd = avd 529 self.env = env 530 self.serial = serial 531 self.connect_to_running_emulator = connect_to_running_emulator 532 533 @property 534 def package_name(self): 535 """ 536 Name of app to run on emulator. 537 538 Note that FennecInstance does not use self.binary 539 """ 540 if self._package_name is None: 541 self._package_name = "org.mozilla.fennec" 542 user = os.getenv("USER") 543 if user: 544 self._package_name += "_" + user 545 return self._package_name 546 547 def start(self): 548 self._update_profile(self.profile) 549 self.runner = self.runner_class(**self._get_runner_args()) 550 try: 551 if self.connect_to_running_emulator: 552 self.runner.device.connect() 553 self.runner.start() 554 except Exception: 555 exc_cls, exc, tb = sys.exc_info() 556 raise exc_cls( 557 f"Error possibly due to runner or device args: {exc}" 558 ).with_traceback(tb) 559 560 # forward marionette port 561 self.runner.device.device.forward( 562 local=f"tcp:{self.marionette_port}", 563 remote=f"tcp:{self.marionette_port}", 564 ) 565 566 def _get_runner_args(self): 567 process_args = { 568 "processOutputLine": [NullOutput()], 569 "universal_newlines": True, 570 } 571 572 env = {} if self.env is None else self.env.copy() 573 574 runner_args = { 575 "app": self.package_name, 576 "avd_home": self.avd_home, 577 "adb_path": self.adb_path, 578 "binary": self.emulator_binary, 579 "env": env, 580 "profile": self.profile, 581 "cmdargs": ["-marionette"] + self.app_args, 582 "symbols_path": self.symbols_path, 583 "process_args": process_args, 584 "logdir": self.workspace or os.getcwd(), 585 "serial": self.serial, 586 } 587 if self.avd: 588 runner_args["avd"] = self.avd 589 590 return runner_args 591 592 def close(self, clean=False): 593 """ 594 Close the managed Gecko process. 595 596 If `clean` is True and the Fennec instance is running in an 597 emulator managed by mozrunner, this will stop the emulator. 598 599 :param clean: If True, also perform runner cleanup. 600 """ 601 super().close(clean) 602 if clean and self.runner and self.runner.device.connected: 603 try: 604 self.runner.device.device.remove_forwards(f"tcp:{self.marionette_port}") 605 self.unresponsive_count = 0 606 except Exception: 607 self.unresponsive_count += 1 608 traceback.print_exception(*sys.exc_info()) 609 610 611 class DesktopInstance(GeckoInstance): 612 desktop_prefs = { 613 # Disable Firefox old build background check 614 "app.update.checkInstallTime": False, 615 # Disable automatically upgrading Firefox 616 # 617 # Note: Possible update tests could reset or flip the value to allow 618 # updates to be downloaded and applied. 619 "app.update.disabledForTesting": True, 620 # !!! For backward compatibility up to Firefox 64. Only remove 621 # when this Firefox version is no longer supported by the client !!! 622 "app.update.auto": False, 623 # Disable the profile backup service. 624 "browser.backup.enabled": False, 625 # Don't show the content blocking introduction panel 626 # We use a larger number than the default 22 to have some buffer 627 # This can be removed once Firefox 69 and 68 ESR and are no longer supported. 628 "browser.contentblocking.introCount": 99, 629 # Enable output for dump() and chrome console API 630 "browser.dom.window.dump.enabled": True, 631 "devtools.console.stdout.chrome": True, 632 # Indicate that the download panel has been shown once so that whichever 633 # download test runs first doesn"t show the popup inconsistently 634 "browser.download.panel.shown": True, 635 # Do not show the EULA notification which can interfer with tests 636 "browser.EULA.override": True, 637 # Disable all machine learning features by default 638 "browser.ml.enable": False, 639 # Do not initialize any activitystream features 640 "browser.newtabpage.activity-stream.testing.shouldInitializeFeeds": False, 641 # Background thumbnails in particular cause grief, and disabling thumbnails 642 # in general can"t hurt - we re-enable them when tests need them 643 "browser.pagethumbnails.capturing_disabled": True, 644 # Disable safe browsing / tracking protection updates 645 "browser.safebrowsing.update.enabled": False, 646 # Disable updates to search engines 647 "browser.search.update": False, 648 # Do not restore the last open set of tabs if the browser has crashed 649 "browser.sessionstore.resume_from_crash": False, 650 # Don't check for the default web browser during startup 651 "browser.shell.checkDefaultBrowser": False, 652 # Disable session restore infobar 653 "browser.startup.couldRestoreSession.count": -1, 654 # Needed for branded builds to prevent opening a second tab on startup 655 "browser.startup.homepage_override.mstone": "ignore", 656 # Start with a blank page by default 657 "browser.startup.page": 0, 658 # Unload the previously selected tab immediately 659 "browser.tabs.remote.unloadDelayMs": 0, 660 # Don't unload tabs when available memory is running low 661 "browser.tabs.unloadOnLowMemory": False, 662 # Do not warn when closing all open tabs 663 "browser.tabs.warnOnClose": False, 664 # Do not warn when closing all other open tabs 665 "browser.tabs.warnOnCloseOtherTabs": False, 666 # Do not warn when multiple tabs will be opened 667 "browser.tabs.warnOnOpen": False, 668 # Don't show the Bookmarks Toolbar on any tab (the above pref that 669 # disables the New Tab Page ends up showing the toolbar on about:blank). 670 "browser.toolbars.bookmarks.visibility": "never", 671 # Disable the UI tour 672 "browser.uitour.enabled": False, 673 # Turn off Merino suggestions in the location bar so as not to trigger network 674 # connections. 675 "browser.urlbar.merino.endpointURL": "", 676 # Turn off search suggestions in the location bar so as not to trigger network 677 # connections. 678 "browser.urlbar.suggest.searches": False, 679 # Don't warn when exiting the browser 680 "browser.warnOnQuit": False, 681 # Disable the QoS manager on MacOS and the priority manager on all other 682 # platforms to not cause stalled processes in background tabs when the 683 # overall CPU load on the machine is high. 684 # 685 # TODO: Should be considered to get removed once bug 1960741 is fixed. 686 "threads.lower_mainthread_priority_in_background.enabled": False, 687 "dom.ipc.processPriorityManager.enabled": False, 688 # Turn off semantic history search as it triggers network connections to 689 # download ML models. 690 "places.semanticHistory.featureGate": False, 691 # Disable first-run welcome page 692 "startup.homepage_welcome_url": "about:blank", 693 "startup.homepage_welcome_url.additional": "", 694 } 695 696 def __init__(self, *args, **kwargs): 697 required_prefs = deepcopy(DesktopInstance.desktop_prefs) 698 required_prefs.update(kwargs.get("prefs", {})) 699 700 super().__init__(*args, **kwargs) 701 self.required_prefs.update(required_prefs) 702 703 704 class ThunderbirdInstance(GeckoInstance): 705 def __init__(self, *args, **kwargs): 706 super().__init__(*args, **kwargs) 707 try: 708 # Copied alongside in the test archive 709 from .thunderbirdinstance import thunderbird_prefs 710 except ImportError: 711 try: 712 # Directly from the source tree 713 here = os.path.dirname(__file__) 714 sys.path.append( 715 os.path.join(here, "../../../../comm/testing/marionette") 716 ) 717 from thunderbirdinstance import thunderbird_prefs 718 except ImportError: 719 thunderbird_prefs = {} 720 self.required_prefs.update(thunderbird_prefs) 721 722 723 class NullOutput: 724 def __call__(self, line): 725 pass 726 727 728 apps = { 729 "fennec": FennecInstance, 730 "fxdesktop": DesktopInstance, 731 "thunderbird": ThunderbirdInstance, 732 } 733 734 app_ids = { 735 "{aa3c5121-dab2-40e2-81ca-7ea25febc110}": "fennec", 736 "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "fxdesktop", 737 "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "thunderbird", 738 }