diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2017-03-08 15:15:54 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-03-08 15:15:54 (GMT) |
commit | a7cba27aea138311117e2ab1d91584efcfeac4ec (patch) | |
tree | c2e6b9e0802057f71b43e17fe27a53e50c12a283 | |
parent | 8606e9524a7a4065042f7f228dc57eb74f88e4d3 (diff) | |
download | cpython-a7cba27aea138311117e2ab1d91584efcfeac4ec.zip cpython-a7cba27aea138311117e2ab1d91584efcfeac4ec.tar.gz cpython-a7cba27aea138311117e2ab1d91584efcfeac4ec.tar.bz2 |
bpo-29645: Speed up importing the webbrowser module. (#484)
-rw-r--r-- | Lib/test/test_webbrowser.py | 25 | ||||
-rwxr-xr-x | Lib/webbrowser.py | 171 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
3 files changed, 120 insertions, 79 deletions
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 @@ class BrowserRegistrationTest(unittest.TestCase): 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 shlex 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] - - # 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) + 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) 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 @@ def get(using=None): # 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 @@ def register_X_browsers(): 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 - - 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")) + # 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() + + # 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 @@ if sys.platform[:3] == "win": 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 @@ if sys.platform == 'darwin': 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 @@ -270,6 +270,9 @@ Extension Modules Library ------- +- bpo-29645: Speed up importing the webbrowser module. webbrowser.register() + is now thread-safe. + - bpo-28231: The zipfile module now accepts path-like objects for external paths. |