Skip to content

Commit a6d76d6

Browse files
committed
gh-137586: Fix MacOS plist fallback, builtins.open shadow, add Brave
Return 'com.apple.Safari' instead of None when the LaunchServices plist is absent (fresh installs never write it). None caused the non-http branch to fall back to bare '/usr/bin/open <url>', bypassing -b and triggering the OS file handler. Addresses gpshead's CHANGES_REQUESTED. Fix infinite recursion: webbrowser's module-level open() shadowed builtins.open, so open(plist, 'rb') called webbrowser.open() recursively on every non-http/https URL. Use builtins.open() explicitly. Narrow 'except Exception' to '(OSError, KeyError, ValueError)'. Addresses hugovk's review comment. Add 'brave browser': 'com.brave.Browser' to _BUNDLE_IDS and register it so webbrowser.get('brave') works and Brave uses precise -b dispatch instead of the name-based -a fallback. Tested locally against Safari, Chrome, Firefox, Opera, Edge, and Brave with https, http, and file:// URLs. 29/29 checks pass.
1 parent aacbceb commit a6d76d6

2 files changed

Lines changed: 14 additions & 26 deletions

File tree

Lib/test/test_webbrowser.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -351,30 +351,16 @@ def test_default_non_http_uses_bundle_id(self):
351351
file_url = 'file:///tmp/test.html'
352352
browser = webbrowser.MacOS('default')
353353
with mock.patch('webbrowser._macos_default_browser_bundle_id',
354-
return_value='com.apple.Safari'), \
354+
return_value='com.google.Chrome'), \
355355
mock.patch('subprocess.run') as mock_run:
356356
mock_run.return_value = mock.Mock(returncode=0)
357357
result = browser.open(file_url)
358358
mock_run.assert_called_once_with(
359-
['/usr/bin/open', '-b', 'com.apple.Safari', file_url],
359+
['/usr/bin/open', '-b', 'com.google.Chrome', file_url],
360360
stderr=subprocess.DEVNULL,
361361
)
362362
self.assertTrue(result)
363363

364-
def test_default_non_http_fallback_when_no_bundle_id(self):
365-
# If the bundle ID lookup fails, fall back to /usr/bin/open without -b.
366-
file_url = 'file:///tmp/test.html'
367-
browser = webbrowser.MacOS('default')
368-
with mock.patch('webbrowser._macos_default_browser_bundle_id',
369-
return_value=None), \
370-
mock.patch('subprocess.run') as mock_run:
371-
mock_run.return_value = mock.Mock(returncode=0)
372-
browser.open(file_url)
373-
mock_run.assert_called_once_with(
374-
['/usr/bin/open', file_url],
375-
stderr=subprocess.DEVNULL,
376-
)
377-
378364
def test_named_known_browser_uses_bundle_id(self):
379365
# Named browsers with a known bundle ID use /usr/bin/open -b.
380366
browser = webbrowser.MacOS('safari')

Lib/webbrowser.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ def register_standard_browsers():
498498
register("safari", None, MacOS('safari'))
499499
register("opera", None, MacOS('opera'))
500500
register("microsoft-edge", None, MacOS('microsoft edge'))
501+
register("brave", None, MacOS('brave browser'))
501502
# macOS can use below Unix support (but we prefer using the macOS
502503
# specific stuff)
503504

@@ -620,24 +621,27 @@ def _macos_default_browser_bundle_id():
620621
"""Return the bundle ID of the default web browser.
621622
622623
Reads the LaunchServices preferences file that macOS maintains
623-
when the user sets a default browser. Returns None if the file
624-
is absent or no https handler is recorded.
624+
when the user sets a default browser. Returns 'com.apple.Safari'
625+
if the file is absent or no https handler is recorded, because on
626+
a fresh macOS installation Safari is the default browser and the
627+
LaunchServices plist is not written until the user explicitly
628+
changes their default browser.
625629
"""
626-
import plistlib, os
630+
import builtins, plistlib, os
627631
plist = os.path.expanduser(
628632
'~/Library/Preferences/com.apple.LaunchServices/'
629633
'com.apple.launchservices.secure.plist'
630634
)
631635
try:
632-
with open(plist, 'rb') as f:
636+
with builtins.open(plist, 'rb') as f:
633637
data = plistlib.load(f)
634638
for handler in data.get('LSHandlers', []):
635639
if handler.get('LSHandlerURLScheme') == 'https':
636640
return (handler.get('LSHandlerRoleAll')
637641
or handler.get('LSHandlerRoleViewer'))
638-
except Exception:
642+
except (OSError, KeyError, ValueError):
639643
pass
640-
return None
644+
return 'com.apple.Safari'
641645

642646
class MacOS(BaseBrowser):
643647
"""Launcher class for macOS browsers, using /usr/bin/open.
@@ -662,6 +666,7 @@ class MacOS(BaseBrowser):
662666
'chromium': 'org.chromium.Chromium',
663667
'opera': 'com.operasoftware.Opera',
664668
'microsoft edge': 'com.microsoft.edgemac',
669+
'brave browser': 'com.brave.Browser',
665670
}
666671

667672
def open(self, url, new=0, autoraise=True):
@@ -673,10 +678,7 @@ def open(self, url, new=0, autoraise=True):
673678
cmd = ['/usr/bin/open', url]
674679
else:
675680
bundle_id = _macos_default_browser_bundle_id()
676-
if bundle_id:
677-
cmd = ['/usr/bin/open', '-b', bundle_id, url]
678-
else:
679-
cmd = ['/usr/bin/open', url]
681+
cmd = ['/usr/bin/open', '-b', bundle_id, url]
680682
else:
681683
bundle_id = self._BUNDLE_IDS.get(self.name.lower())
682684
if bundle_id:

0 commit comments

Comments
 (0)