bpo-29645: Speed up importing the webbrowser module. (#484)
diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
index 622c1cb..0820b91 100644
--- a/Lib/test/test_webbrowser.py
+++ b/Lib/test/test_webbrowser.py
@@ -266,5 +266,30 @@
self._check_registration(preferred=True)
+class ImportTest(unittest.TestCase):
+ def test_register(self):
+ webbrowser = support.import_fresh_module('webbrowser')
+ self.assertIsNone(webbrowser._tryorder)
+ self.assertFalse(webbrowser._browsers)
+
+ class ExampleBrowser:
+ pass
+ webbrowser.register('Example1', ExampleBrowser)
+ self.assertTrue(webbrowser._tryorder)
+ self.assertEqual(webbrowser._tryorder[-1], 'Example1')
+ self.assertTrue(webbrowser._browsers)
+ self.assertIn('example1', webbrowser._browsers)
+ self.assertEqual(webbrowser._browsers['example1'], [ExampleBrowser, None])
+
+ def test_get(self):
+ webbrowser = support.import_fresh_module('webbrowser')
+ self.assertIsNone(webbrowser._tryorder)
+ self.assertFalse(webbrowser._browsers)
+
+ with self.assertRaises(webbrowser.Error):
+ webbrowser.get('fakebrowser')
+ self.assertIsNotNone(webbrowser._tryorder)
+
+
if __name__=='__main__':
unittest.main()
diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
index fb6c83b..2a5729b 100755
--- a/Lib/webbrowser.py
+++ b/Lib/webbrowser.py
@@ -7,30 +7,39 @@
import shutil
import sys
import subprocess
+import threading
__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
class Error(Exception):
pass
+_lock = threading.RLock()
_browsers = {} # Dictionary of available browser controllers
-_tryorder = [] # Preference order of available browsers
+_tryorder = None # Preference order of available browsers
_os_preferred_browser = None # The preferred browser
def register(name, klass, instance=None, *, preferred=False):
"""Register a browser connector."""
- _browsers[name.lower()] = [klass, instance]
+ with _lock:
+ if _tryorder is None:
+ register_standard_browsers()
+ _browsers[name.lower()] = [klass, instance]
- # Preferred browsers go to the front of the list.
- # Need to match to the default browser returned by xdg-settings, which
- # may be of the form e.g. "firefox.desktop".
- if preferred or (_os_preferred_browser and name in _os_preferred_browser):
- _tryorder.insert(0, name)
- else:
- _tryorder.append(name)
+ # Preferred browsers go to the front of the list.
+ # Need to match to the default browser returned by xdg-settings, which
+ # may be of the form e.g. "firefox.desktop".
+ if preferred or (_os_preferred_browser and name in _os_preferred_browser):
+ _tryorder.insert(0, name)
+ else:
+ _tryorder.append(name)
def get(using=None):
"""Return a browser launcher instance appropriate for the environment."""
+ if _tryorder is None:
+ with _lock:
+ if _tryorder is None:
+ register_standard_browsers()
if using is not None:
alternatives = [using]
else:
@@ -60,6 +69,10 @@
# instead of "from webbrowser import *".
def open(url, new=0, autoraise=True):
+ if _tryorder is None:
+ with _lock:
+ if _tryorder is None:
+ register_standard_browsers()
for name in _tryorder:
browser = get(name)
if browser.open(url, new, autoraise):
@@ -487,34 +500,76 @@
if shutil.which("grail"):
register("grail", Grail, None)
-# Prefer X browsers if present
-if os.environ.get("DISPLAY"):
- try:
- cmd = "xdg-settings get default-web-browser".split()
- raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
- result = raw_result.decode().strip()
- except (FileNotFoundError, subprocess.CalledProcessError):
- pass
+def register_standard_browsers():
+ global _tryorder
+ _tryorder = []
+
+ if sys.platform == 'darwin':
+ register("MacOSX", None, MacOSXOSAScript('default'))
+ register("chrome", None, MacOSXOSAScript('chrome'))
+ register("firefox", None, MacOSXOSAScript('firefox'))
+ register("safari", None, MacOSXOSAScript('safari'))
+ # OS X can use below Unix support (but we prefer using the OS X
+ # specific stuff)
+
+ if sys.platform[:3] == "win":
+ # First try to use the default Windows browser
+ register("windows-default", WindowsDefault)
+
+ # Detect some common Windows browsers, fallback to IE
+ iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
+ "Internet Explorer\\IEXPLORE.EXE")
+ for browser in ("firefox", "firebird", "seamonkey", "mozilla",
+ "netscape", "opera", iexplore):
+ if shutil.which(browser):
+ register(browser, None, BackgroundBrowser(browser))
else:
- _os_preferred_browser = result
+ # Prefer X browsers if present
+ if os.environ.get("DISPLAY"):
+ try:
+ cmd = "xdg-settings get default-web-browser".split()
+ raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
+ result = raw_result.decode().strip()
+ except (FileNotFoundError, subprocess.CalledProcessError):
+ pass
+ else:
+ global _os_preferred_browser
+ _os_preferred_browser = result
- register_X_browsers()
+ register_X_browsers()
-# Also try console browsers
-if os.environ.get("TERM"):
- if shutil.which("www-browser"):
- register("www-browser", None, GenericBrowser("www-browser"))
- # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
- if shutil.which("links"):
- register("links", None, GenericBrowser("links"))
- if shutil.which("elinks"):
- register("elinks", None, Elinks("elinks"))
- # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
- if shutil.which("lynx"):
- register("lynx", None, GenericBrowser("lynx"))
- # The w3m browser <http://w3m.sourceforge.net/>
- if shutil.which("w3m"):
- register("w3m", None, GenericBrowser("w3m"))
+ # Also try console browsers
+ if os.environ.get("TERM"):
+ if shutil.which("www-browser"):
+ register("www-browser", None, GenericBrowser("www-browser"))
+ # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
+ if shutil.which("links"):
+ register("links", None, GenericBrowser("links"))
+ if shutil.which("elinks"):
+ register("elinks", None, Elinks("elinks"))
+ # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
+ if shutil.which("lynx"):
+ register("lynx", None, GenericBrowser("lynx"))
+ # The w3m browser <http://w3m.sourceforge.net/>
+ if shutil.which("w3m"):
+ register("w3m", None, GenericBrowser("w3m"))
+
+ # OK, now that we know what the default preference orders for each
+ # platform are, allow user to override them with the BROWSER variable.
+ if "BROWSER" in os.environ:
+ userchoices = os.environ["BROWSER"].split(os.pathsep)
+ userchoices.reverse()
+
+ # Treat choices in same way as if passed into get() but do register
+ # and prepend to _tryorder
+ for cmdline in userchoices:
+ if cmdline != '':
+ cmd = _synthesize(cmdline, -1)
+ if cmd[1] is None:
+ register(cmdline, None, GenericBrowser(cmdline), preferred=True)
+
+ # what to do if _tryorder is now empty?
+
#
# Platform support for Windows
@@ -532,20 +587,6 @@
else:
return True
- _tryorder = []
- _browsers = {}
-
- # First try to use the default Windows browser
- register("windows-default", WindowsDefault)
-
- # Detect some common Windows browsers, fallback to IE
- iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
- "Internet Explorer\\IEXPLORE.EXE")
- for browser in ("firefox", "firebird", "seamonkey", "mozilla",
- "netscape", "opera", iexplore):
- if shutil.which(browser):
- register(browser, None, BackgroundBrowser(browser))
-
#
# Platform support for MacOS
#
@@ -622,34 +663,6 @@
return not rc
- # Don't clear _tryorder or _browsers since OS X can use above Unix support
- # (but we prefer using the OS X specific stuff)
- register("safari", None, MacOSXOSAScript('safari'), preferred=True)
- register("firefox", None, MacOSXOSAScript('firefox'), preferred=True)
- register("chrome", None, MacOSXOSAScript('chrome'), preferred=True)
- register("MacOSX", None, MacOSXOSAScript('default'), preferred=True)
-
-
-# OK, now that we know what the default preference orders for each
-# platform are, allow user to override them with the BROWSER variable.
-if "BROWSER" in os.environ:
- _userchoices = os.environ["BROWSER"].split(os.pathsep)
- _userchoices.reverse()
-
- # Treat choices in same way as if passed into get() but do register
- # and prepend to _tryorder
- for cmdline in _userchoices:
- if cmdline != '':
- cmd = _synthesize(cmdline, -1)
- if cmd[1] is None:
- register(cmdline, None, GenericBrowser(cmdline), preferred=True)
- cmdline = None # to make del work if _userchoices was empty
- del cmdline
- del _userchoices
-
-# what to do if _tryorder is now empty?
-
-
def main():
import getopt
usage = """Usage: %s [-n | -t] url