bpo-29645: Speed up importing the webbrowser module. (#484)

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