Skip to content

Commit 66a3549

Browse files
committed
windows: webbrowser.open opens default browser on all URLs
lookup default browsers via `UrlAssociations\https\UserChoice` if protocol is not http/https ensures browser is opened for e.g. file:// URLs
1 parent 44213bc commit 66a3549

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

Lib/test/test_webbrowser.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,44 @@ def test_open_new_tab(self):
301301
self._test('open_new_tab')
302302

303303

304+
@unittest.skipUnless(sys.platform[:3] == "win", "Windows test")
305+
class WindowsDefaultTest(unittest.TestCase):
306+
def setUp(self):
307+
support.patch(self, os, "startfile", mock.Mock())
308+
self.browser = webbrowser.WindowsDefault()
309+
support.patch(self, self.browser, "_open_default_browser", mock.Mock())
310+
311+
def test_default(self):
312+
browser = webbrowser.get()
313+
self.assertIsInstance(browser, webbrowser.WindowsDefault)
314+
315+
def test_open_startfile(self):
316+
url = "https://python.org"
317+
self.browser.open(url)
318+
self.browser._open_default_browser.assert_not_called()
319+
os.startfile.assert_called_with(url)
320+
321+
def test_open_browser_lookup(self):
322+
url = "file://python.org"
323+
self.browser.open(url)
324+
self.browser._open_default_browser.assert_called_with(url)
325+
os.startfile.assert_not_called()
326+
327+
def test_open_browser_lookup_fails(self):
328+
url = "file://python.org"
329+
self.browser._open_default_browser.return_value = False
330+
self.browser.open(url)
331+
self.browser._open_default_browser.assert_called_with(url)
332+
os.startfile.assert_called_with(url)
333+
334+
def test_open_browser_lookup_error(self):
335+
url = "file://python.org"
336+
self.browser._open_default_browser.side_effect = OSError('registry failed...')
337+
self.browser.open(url)
338+
self.browser._open_default_browser.assert_called_with(url)
339+
os.startfile.assert_called_with(url)
340+
341+
304342
class BrowserRegistrationTest(unittest.TestCase):
305343

306344
def setUp(self):

Lib/webbrowser.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,61 @@ def register_standard_browsers():
573573

574574
if sys.platform[:3] == "win":
575575
class WindowsDefault(BaseBrowser):
576+
def _open_default_browser(self, url):
577+
"""Open a URL with the default browser
578+
579+
launches the web browser no matter what `url` is, unlike startfile.
580+
581+
Raises OSError if registry lookups fail.
582+
Returns False if URL not opened.
583+
"""
584+
try:
585+
import winreg
586+
except ImportError:
587+
return False
588+
# lookup progId for https URLs
589+
# e.g. 'FirefoxURL-abc123'
590+
with winreg.OpenKey(
591+
winreg.HKEY_CURRENT_USER,
592+
r"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice",
593+
) as key:
594+
browser_id = winreg.QueryValueEx(key, "ProgId")[0]
595+
# lookup launch command-line
596+
# e.g. '"C:\\Program Files\\Mozilla Firefox\\firefox.exe" -osint -url "%1"'
597+
with winreg.OpenKey(
598+
winreg.HKEY_CLASSES_ROOT,
599+
rf"{browser_id}\shell\open\command",
600+
) as key:
601+
browser_cmd = winreg.QueryValueEx(key, "")[0]
602+
603+
# build command-line
604+
if "%1" not in browser_cmd:
605+
# Command is missing '%1' placeholder,
606+
# so we don't know how to build the command to open a file
607+
# would append be safe in this case?
608+
return False
609+
610+
# the rest copied from BackgroundBrowser
611+
cmdline = [arg.replace("%1", url) for arg in shlex.split(browser_cmd)]
612+
try:
613+
p = subprocess.Popen(cmdline)
614+
return p.poll() is None
615+
except OSError:
616+
return False
617+
576618
def open(self, url, new=0, autoraise=True):
577619
sys.audit("webbrowser.open", url)
620+
proto, _sep, _rest = url.partition(":")
621+
if _sep and proto.lower() not in {"http", "https"}:
622+
# need to lookup browser if it's not a web URL
623+
try:
624+
opened = self._open_default_browser(url)
625+
except OSError:
626+
# failed to lookup registry items
627+
opened = False
628+
if opened:
629+
return opened
630+
578631
try:
579632
os.startfile(url)
580633
except OSError:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Ensure web browser is launched by :func:`webbrowser.open` on Windows, even
2+
for ``file://`` URLs.

0 commit comments

Comments
 (0)