update webbrowser and test_webbrowser to 3.13.3

Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
This commit is contained in:
Ashwin Naren
2025-04-20 21:15:29 -07:00
committed by Jeong, YunWon
parent 82a62382d0
commit 320d74527f
2 changed files with 385 additions and 220 deletions

View File

@@ -1,15 +1,22 @@
import webbrowser
import unittest
import os
import sys
import re
import shlex
import subprocess
from unittest import mock
import sys
import unittest
import webbrowser
from test import support
from test.support import import_helper
from test.support import is_apple_mobile
from test.support import os_helper
from test.support import requires_subprocess
from test.support import threading_helper
from unittest import mock
# The webbrowser module uses threading locks
threading_helper.requires_working_threading(module=True)
URL = 'http://www.example.com'
URL = 'https://www.example.com'
CMD_NAME = 'test'
@@ -22,6 +29,7 @@ class PopenMock(mock.MagicMock):
return 0
@requires_subprocess()
class CommandTestMixin:
def _test(self, meth, *, args=[URL], kw={}, options, arguments):
@@ -92,6 +100,40 @@ class ChromeCommandTest(CommandTestMixin, unittest.TestCase):
options=[],
arguments=[URL])
def test_open_bad_new_parameter(self):
with self.assertRaisesRegex(webbrowser.Error,
re.escape("Bad 'new' parameter to open(); "
"expected 0, 1, or 2, got 999")):
self._test('open',
options=[],
arguments=[URL],
kw=dict(new=999))
class EdgeCommandTest(CommandTestMixin, unittest.TestCase):
browser_class = webbrowser.Edge
def test_open(self):
self._test('open',
options=[],
arguments=[URL])
def test_open_with_autoraise_false(self):
self._test('open', kw=dict(autoraise=False),
options=[],
arguments=[URL])
def test_open_new(self):
self._test('open_new',
options=['--new-window'],
arguments=[URL])
def test_open_new_tab(self):
self._test('open_new_tab',
options=[],
arguments=[URL])
class MozillaCommandTest(CommandTestMixin, unittest.TestCase):
@@ -118,34 +160,9 @@ class MozillaCommandTest(CommandTestMixin, unittest.TestCase):
arguments=['-new-tab', URL])
class NetscapeCommandTest(CommandTestMixin, unittest.TestCase):
class EpiphanyCommandTest(CommandTestMixin, unittest.TestCase):
browser_class = webbrowser.Netscape
def test_open(self):
self._test('open',
options=['-raise', '-remote'],
arguments=['openURL({})'.format(URL)])
def test_open_with_autoraise_false(self):
self._test('open', kw=dict(autoraise=False),
options=['-noraise', '-remote'],
arguments=['openURL({})'.format(URL)])
def test_open_new(self):
self._test('open_new',
options=['-raise', '-remote'],
arguments=['openURL({},new-window)'.format(URL)])
def test_open_new_tab(self):
self._test('open_new_tab',
options=['-raise', '-remote'],
arguments=['openURL({},new-tab)'.format(URL)])
class GaleonCommandTest(CommandTestMixin, unittest.TestCase):
browser_class = webbrowser.Galeon
browser_class = webbrowser.Epiphany
def test_open(self):
self._test('open',
@@ -199,22 +216,89 @@ class ELinksCommandTest(CommandTestMixin, unittest.TestCase):
def test_open(self):
self._test('open', options=['-remote'],
arguments=['openURL({})'.format(URL)])
arguments=[f'openURL({URL})'])
def test_open_with_autoraise_false(self):
self._test('open',
options=['-remote'],
arguments=['openURL({})'.format(URL)])
arguments=[f'openURL({URL})'])
def test_open_new(self):
self._test('open_new',
options=['-remote'],
arguments=['openURL({},new-window)'.format(URL)])
arguments=[f'openURL({URL},new-window)'])
def test_open_new_tab(self):
self._test('open_new_tab',
options=['-remote'],
arguments=['openURL({},new-tab)'.format(URL)])
arguments=[f'openURL({URL},new-tab)'])
@unittest.skipUnless(sys.platform == "ios", "Test only applicable to iOS")
class IOSBrowserTest(unittest.TestCase):
def _obj_ref(self, *args):
# Construct a string representation of the arguments that can be used
# as a proxy for object instance references
return "|".join(str(a) for a in args)
@unittest.skipIf(getattr(webbrowser, "objc", None) is None,
"iOS Webbrowser tests require ctypes")
def setUp(self):
# Intercept the objc library. Wrap the calls to get the
# references to classes and selectors to return strings, and
# wrap msgSend to return stringified object references
self.orig_objc = webbrowser.objc
webbrowser.objc = mock.Mock()
webbrowser.objc.objc_getClass = lambda cls: f"C#{cls.decode()}"
webbrowser.objc.sel_registerName = lambda sel: f"S#{sel.decode()}"
webbrowser.objc.objc_msgSend.side_effect = self._obj_ref
def tearDown(self):
webbrowser.objc = self.orig_objc
def _test(self, meth, **kwargs):
# The browser always gets focus, there's no concept of separate browser
# windows, and there's no API-level control over creating a new tab.
# Therefore, all calls to webbrowser are effectively the same.
getattr(webbrowser, meth)(URL, **kwargs)
# The ObjC String version of the URL is created with UTF-8 encoding
url_string_args = [
"C#NSString",
"S#stringWithCString:encoding:",
b'https://www.example.com',
4,
]
# The NSURL version of the URL is created from that string
url_obj_args = [
"C#NSURL",
"S#URLWithString:",
self._obj_ref(*url_string_args),
]
# The openURL call is invoked on the shared application
shared_app_args = ["C#UIApplication", "S#sharedApplication"]
# Verify that the last call is the one that opens the URL.
webbrowser.objc.objc_msgSend.assert_called_with(
self._obj_ref(*shared_app_args),
"S#openURL:options:completionHandler:",
self._obj_ref(*url_obj_args),
None,
None
)
def test_open(self):
self._test('open')
def test_open_with_autoraise_false(self):
self._test('open', autoraise=False)
def test_open_new(self):
self._test('open_new')
def test_open_new_tab(self):
self._test('open_new_tab')
class BrowserRegistrationTest(unittest.TestCase):
@@ -269,6 +353,16 @@ class BrowserRegistrationTest(unittest.TestCase):
def test_register_preferred(self):
self._check_registration(preferred=True)
@unittest.skipUnless(sys.platform == "darwin", "macOS specific test")
def test_no_xdg_settings_on_macOS(self):
# On macOS webbrowser should not use xdg-settings to
# look for X11 based browsers (for those users with
# XQuartz installed)
with mock.patch("subprocess.check_output") as ck_o:
webbrowser.register_standard_browsers()
ck_o.assert_not_called()
class ImportTest(unittest.TestCase):
def test_register(self):
@@ -294,29 +388,38 @@ class ImportTest(unittest.TestCase):
webbrowser.get('fakebrowser')
self.assertIsNotNone(webbrowser._tryorder)
@unittest.skipIf(" " in sys.executable, "test assumes no space in path (GH-114452)")
def test_synthesize(self):
webbrowser = import_helper.import_fresh_module('webbrowser')
name = os.path.basename(sys.executable).lower()
webbrowser.register(name, None, webbrowser.GenericBrowser(name))
webbrowser.get(sys.executable)
@unittest.skipIf(
is_apple_mobile,
"Apple mobile doesn't allow modifying browser with environment"
)
def test_environment(self):
webbrowser = import_helper.import_fresh_module('webbrowser')
try:
browser = webbrowser.get().name
except (webbrowser.Error, AttributeError) as err:
except webbrowser.Error as err:
self.skipTest(str(err))
with os_helper.EnvironmentVarGuard() as env:
env["BROWSER"] = browser
webbrowser = import_helper.import_fresh_module('webbrowser')
webbrowser.get()
@unittest.skipIf(
is_apple_mobile,
"Apple mobile doesn't allow modifying browser with environment"
)
def test_environment_preferred(self):
webbrowser = import_helper.import_fresh_module('webbrowser')
try:
webbrowser.get()
least_preferred_browser = webbrowser.get(webbrowser._tryorder[-1]).name
except (webbrowser.Error, AttributeError, IndexError) as err:
except (webbrowser.Error, IndexError) as err:
self.skipTest(str(err))
with os_helper.EnvironmentVarGuard() as env:
@@ -330,5 +433,74 @@ class ImportTest(unittest.TestCase):
self.assertEqual(webbrowser.get().name, sys.executable)
if __name__=='__main__':
class CliTest(unittest.TestCase):
def test_parse_args(self):
for command, url, new_win in [
# No optional arguments
("https://example.com", "https://example.com", 0),
# Each optional argument
("https://example.com -n", "https://example.com", 1),
("-n https://example.com", "https://example.com", 1),
("https://example.com -t", "https://example.com", 2),
("-t https://example.com", "https://example.com", 2),
# Long form
("https://example.com --new-window", "https://example.com", 1),
("--new-window https://example.com", "https://example.com", 1),
("https://example.com --new-tab", "https://example.com", 2),
("--new-tab https://example.com", "https://example.com", 2),
]:
args = webbrowser.parse_args(shlex.split(command))
self.assertEqual(args.url, url)
self.assertEqual(args.new_win, new_win)
def test_parse_args_error(self):
for command in [
# Arguments must not both be given
"https://example.com -n -t",
"https://example.com --new-window --new-tab",
"https://example.com -n --new-tab",
"https://example.com --new-window -t",
]:
with support.captured_stderr() as stderr:
with self.assertRaises(SystemExit):
webbrowser.parse_args(shlex.split(command))
self.assertIn(
'error: argument -t/--new-tab: not allowed with argument -n/--new-window',
stderr.getvalue(),
)
# Ensure ambiguous shortening fails
with support.captured_stderr() as stderr:
with self.assertRaises(SystemExit):
webbrowser.parse_args(shlex.split("https://example.com --new"))
self.assertIn(
'error: ambiguous option: --new could match --new-window, --new-tab',
stderr.getvalue()
)
def test_main(self):
for command, expected_url, expected_new_win in [
# No optional arguments
("https://example.com", "https://example.com", 0),
# Each optional argument
("https://example.com -n", "https://example.com", 1),
("-n https://example.com", "https://example.com", 1),
("https://example.com -t", "https://example.com", 2),
("-t https://example.com", "https://example.com", 2),
# Long form
("https://example.com --new-window", "https://example.com", 1),
("--new-window https://example.com", "https://example.com", 1),
("https://example.com --new-tab", "https://example.com", 2),
("--new-tab https://example.com", "https://example.com", 2),
]:
with (
mock.patch("webbrowser.open", return_value=None) as mock_open,
mock.patch("builtins.print", return_value=None),
):
webbrowser.main(shlex.split(command))
mock_open.assert_called_once_with(expected_url, expected_new_win)
if __name__ == '__main__':
unittest.main()

355
Lib/webbrowser.py vendored
View File

@@ -11,14 +11,17 @@ 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 = 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."""
with _lock:
@@ -29,11 +32,12 @@ def register(name, klass, instance=None, *, preferred=False):
# 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):
if preferred or (_os_preferred_browser and f'{name}.desktop' == _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:
@@ -64,6 +68,7 @@ def get(using=None):
return command[0]()
raise Error("could not locate runnable browser")
# Please note: the following definition hides a builtin function.
# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
# instead of "from webbrowser import *".
@@ -76,6 +81,9 @@ def open(url, new=0, autoraise=True):
- 1: a new browser window.
- 2: a new browser page ("tab").
If possible, autoraise raises the window (the default) or not.
If opening the browser succeeds, return True.
If there is a problem, return False.
"""
if _tryorder is None:
with _lock:
@@ -87,6 +95,7 @@ def open(url, new=0, autoraise=True):
return True
return False
def open_new(url):
"""Open url in a new window of the default browser.
@@ -94,6 +103,7 @@ def open_new(url):
"""
return open(url, 1)
def open_new_tab(url):
"""Open url in a new page ("tab") of the default browser.
@@ -136,7 +146,7 @@ def _synthesize(browser, *, preferred=False):
# General parent classes
class BaseBrowser(object):
class BaseBrowser:
"""Parent class for all browsers. Do not use directly."""
args = ['%s']
@@ -197,7 +207,7 @@ class BackgroundBrowser(GenericBrowser):
else:
p = subprocess.Popen(cmdline, close_fds=True,
start_new_session=True)
return (p.poll() is None)
return p.poll() is None
except OSError:
return False
@@ -225,7 +235,8 @@ class UnixBrowser(BaseBrowser):
# use autoraise argument only for remote invocation
autoraise = int(autoraise)
opt = self.raise_opts[autoraise]
if opt: raise_opt = [opt]
if opt:
raise_opt = [opt]
cmdline = [self.name] + raise_opt + args
@@ -266,8 +277,8 @@ class UnixBrowser(BaseBrowser):
else:
action = self.remote_action_newtab
else:
raise Error("Bad 'new' parameter to open(); " +
"expected 0, 1, or 2, got %s" % new)
raise Error("Bad 'new' parameter to open(); "
f"expected 0, 1, or 2, got {new}")
args = [arg.replace("%s", url).replace("%action", action)
for arg in self.remote_args]
@@ -291,19 +302,8 @@ class Mozilla(UnixBrowser):
background = True
class Netscape(UnixBrowser):
"""Launcher class for Netscape browser."""
raise_opts = ["-noraise", "-raise"]
remote_args = ['-remote', 'openURL(%s%action)']
remote_action = ""
remote_action_newwin = ",new-window"
remote_action_newtab = ",new-tab"
background = True
class Galeon(UnixBrowser):
"""Launcher class for Galeon/Epiphany browsers."""
class Epiphany(UnixBrowser):
"""Launcher class for Epiphany browser."""
raise_opts = ["-noraise", ""]
remote_args = ['%action', '%s']
@@ -313,7 +313,7 @@ class Galeon(UnixBrowser):
class Chrome(UnixBrowser):
"Launcher class for Google Chrome browser."
"""Launcher class for Google Chrome browser."""
remote_args = ['%action', '%s']
remote_action = ""
@@ -321,11 +321,12 @@ class Chrome(UnixBrowser):
remote_action_newtab = ""
background = True
Chromium = Chrome
class Opera(UnixBrowser):
"Launcher class for Opera browser."
"""Launcher class for Opera browser."""
remote_args = ['%action', '%s']
remote_action = ""
@@ -335,7 +336,7 @@ class Opera(UnixBrowser):
class Elinks(UnixBrowser):
"Launcher class for Elinks browsers."
"""Launcher class for Elinks browsers."""
remote_args = ['-remote', 'openURL(%s%action)']
remote_action = ""
@@ -398,54 +399,17 @@ class Konqueror(BaseBrowser):
except OSError:
return False
else:
return (p.poll() is None)
return p.poll() is None
class Grail(BaseBrowser):
# There should be a way to maintain a connection to Grail, but the
# Grail remote control protocol doesn't really allow that at this
# point. It probably never will!
def _find_grail_rc(self):
import glob
import pwd
import socket
import tempfile
tempdir = os.path.join(tempfile.gettempdir(),
".grail-unix")
user = pwd.getpwuid(os.getuid())[0]
filename = os.path.join(glob.escape(tempdir), glob.escape(user) + "-*")
maybes = glob.glob(filename)
if not maybes:
return None
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
for fn in maybes:
# need to PING each one until we find one that's live
try:
s.connect(fn)
except OSError:
# no good; attempt to clean it out, but don't fail:
try:
os.unlink(fn)
except OSError:
pass
else:
return s
class Edge(UnixBrowser):
"""Launcher class for Microsoft Edge browser."""
def _remote(self, action):
s = self._find_grail_rc()
if not s:
return 0
s.send(action)
s.close()
return 1
def open(self, url, new=0, autoraise=True):
sys.audit("webbrowser.open", url)
if new:
ok = self._remote("LOADNEW " + url)
else:
ok = self._remote("LOAD " + url)
return ok
remote_args = ['%action', '%s']
remote_action = ""
remote_action_newwin = "--new-window"
remote_action_newtab = ""
background = True
#
@@ -461,47 +425,44 @@ def register_X_browsers():
if shutil.which("xdg-open"):
register("xdg-open", None, BackgroundBrowser("xdg-open"))
# Opens an appropriate browser for the URL scheme according to
# freedesktop.org settings (GNOME, KDE, XFCE, etc.)
if shutil.which("gio"):
register("gio", None, BackgroundBrowser(["gio", "open", "--", "%s"]))
xdg_desktop = os.getenv("XDG_CURRENT_DESKTOP", "").split(":")
# The default GNOME3 browser
if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
if (("GNOME" in xdg_desktop or
"GNOME_DESKTOP_SESSION_ID" in os.environ) and
shutil.which("gvfs-open")):
register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
# The default GNOME browser
if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
register("gnome-open", None, BackgroundBrowser("gnome-open"))
# The default KDE browser
if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
if (("KDE" in xdg_desktop or
"KDE_FULL_SESSION" in os.environ) and
shutil.which("kfmclient")):
register("kfmclient", Konqueror, Konqueror("kfmclient"))
# Common symbolic link for the default X11 browser
if shutil.which("x-www-browser"):
register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
# The Mozilla browsers
for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
for browser in ("firefox", "iceweasel", "seamonkey", "mozilla-firefox",
"mozilla"):
if shutil.which(browser):
register(browser, None, Mozilla(browser))
# The Netscape and old Mozilla browsers
for browser in ("mozilla-firefox",
"mozilla-firebird", "firebird",
"mozilla", "netscape"):
if shutil.which(browser):
register(browser, None, Netscape(browser))
# Konqueror/kfm, the KDE browser.
if shutil.which("kfm"):
register("kfm", Konqueror, Konqueror("kfm"))
elif shutil.which("konqueror"):
register("konqueror", Konqueror, Konqueror("konqueror"))
# Gnome's Galeon and Epiphany
for browser in ("galeon", "epiphany"):
if shutil.which(browser):
register(browser, None, Galeon(browser))
# Skipstone, another Gtk/Mozilla based browser
if shutil.which("skipstone"):
register("skipstone", None, BackgroundBrowser("skipstone"))
# Gnome's Epiphany
if shutil.which("epiphany"):
register("epiphany", None, Epiphany("epiphany"))
# Google Chrome/Chromium browsers
for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
@@ -512,13 +473,9 @@ def register_X_browsers():
if shutil.which("opera"):
register("opera", None, Opera("opera"))
# Next, Mosaic -- old but still in use.
if shutil.which("mosaic"):
register("mosaic", None, BackgroundBrowser("mosaic"))
if shutil.which("microsoft-edge"):
register("microsoft-edge", None, Edge("microsoft-edge"))
# Grail, the Python browser. Does anybody still use it?
if shutil.which("grail"):
register("grail", Grail, None)
def register_standard_browsers():
global _tryorder
@@ -532,6 +489,9 @@ def register_standard_browsers():
# OS X can use below Unix support (but we prefer using the OS X
# specific stuff)
if sys.platform == "ios":
register("iosbrowser", None, IOSBrowser(), preferred=True)
if sys.platform == "serenityos":
# SerenityOS webbrowser, simply called "Browser".
register("Browser", None, BackgroundBrowser("Browser"))
@@ -540,21 +500,33 @@ def register_standard_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):
# Detect some common Windows browsers, fallback to Microsoft Edge
# location in 64-bit Windows
edge64 = os.path.join(os.environ.get("PROGRAMFILES(x86)", "C:\\Program Files (x86)"),
"Microsoft\\Edge\\Application\\msedge.exe")
# location in 32-bit Windows
edge32 = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
"Microsoft\\Edge\\Application\\msedge.exe")
for browser in ("firefox", "seamonkey", "mozilla", "chrome",
"opera", edge64, edge32):
if shutil.which(browser):
register(browser, None, BackgroundBrowser(browser))
if shutil.which("MicrosoftEdge.exe"):
register("microsoft-edge", None, Edge("MicrosoftEdge.exe"))
else:
# Prefer X browsers if present
if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"):
#
# NOTE: Do not check for X11 browser on macOS,
# XQuartz installation sets a DISPLAY environment variable and will
# autostart when someone tries to access the display. Mac users in
# general don't need an X11 browser.
if sys.platform != "darwin" and (os.environ.get("DISPLAY") or os.environ.get("WAYLAND_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, PermissionError, NotADirectoryError) :
except (FileNotFoundError, subprocess.CalledProcessError,
PermissionError, NotADirectoryError):
pass
else:
global _os_preferred_browser
@@ -564,14 +536,15 @@ def register_standard_browsers():
# Also try console browsers
if os.environ.get("TERM"):
# Common symbolic link for the default text-based browser
if shutil.which("www-browser"):
register("www-browser", None, GenericBrowser("www-browser"))
# The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
# The Links/elinks browsers <http://links.twibright.com/>
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/>
# The Lynx browser <https://lynx.invisible-island.net/>, <http://lynx.browser.org/>
if shutil.which("lynx"):
register("lynx", None, GenericBrowser("lynx"))
# The w3m browser <http://w3m.sourceforge.net/>
@@ -613,72 +586,26 @@ if sys.platform[:3] == "win":
return True
#
# Platform support for MacOS
# Platform support for macOS
#
if sys.platform == 'darwin':
# Adapted from patch submitted to SourceForge by Steven J. Burr
class MacOSX(BaseBrowser):
"""Launcher class for Aqua browsers on Mac OS X
Optionally specify a browser name on instantiation. Note that this
will not work for Aqua browsers if the user has moved the application
package after installation.
If no browser is specified, the default browser, as specified in the
Internet System Preferences panel, will be used.
"""
def __init__(self, name):
self.name = name
class MacOSXOSAScript(BaseBrowser):
def __init__(self, name='default'):
super().__init__(name)
def open(self, url, new=0, autoraise=True):
sys.audit("webbrowser.open", url)
assert "'" not in url
# hack for local urls
if not ':' in url:
url = 'file:'+url
# new must be 0 or 1
new = int(bool(new))
if self.name == "default":
# User called open, open_new or get without a browser parameter
script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
url = url.replace('"', '%22')
if self.name == 'default':
script = f'open location "{url}"' # opens in default browser
else:
# User called get and chose a browser
if self.name == "OmniWeb":
toWindow = ""
else:
# Include toWindow parameter of OpenURL command for browsers
# that support it. 0 == new window; -1 == existing
toWindow = "toWindow %d" % (new - 1)
cmd = 'OpenURL "%s"' % url.replace('"', '%22')
script = '''tell application "%s"
activate
%s %s
end tell''' % (self.name, cmd, toWindow)
# Open pipe to AppleScript through osascript command
osapipe = os.popen("osascript", "w")
if osapipe is None:
return False
# Write script to osascript's stdin
osapipe.write(script)
rc = osapipe.close()
return not rc
class MacOSXOSAScript(BaseBrowser):
def __init__(self, name):
self._name = name
def open(self, url, new=0, autoraise=True):
if self._name == 'default':
script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
else:
script = '''
tell application "%s"
script = f'''
tell application "{self.name}"
activate
open location "%s"
open location "{url}"
end
'''%(self._name, url.replace('"', '%22'))
'''
osapipe = os.popen("osascript", "w")
if osapipe is None:
@@ -688,30 +615,96 @@ if sys.platform == 'darwin':
rc = osapipe.close()
return not rc
#
# Platform support for iOS
#
if sys.platform == "ios":
from _ios_support import objc
if objc:
# If objc exists, we know ctypes is also importable.
from ctypes import c_void_p, c_char_p, c_ulong
def main():
import getopt
usage = """Usage: %s [-n | -t] url
-n: open new window
-t: open new tab""" % sys.argv[0]
try:
opts, args = getopt.getopt(sys.argv[1:], 'ntd')
except getopt.error as msg:
print(msg, file=sys.stderr)
print(usage, file=sys.stderr)
sys.exit(1)
new_win = 0
for o, a in opts:
if o == '-n': new_win = 1
elif o == '-t': new_win = 2
if len(args) != 1:
print(usage, file=sys.stderr)
sys.exit(1)
class IOSBrowser(BaseBrowser):
def open(self, url, new=0, autoraise=True):
sys.audit("webbrowser.open", url)
# If ctypes isn't available, we can't open a browser
if objc is None:
return False
url = args[0]
open(url, new_win)
# All the messages in this call return object references.
objc.objc_msgSend.restype = c_void_p
# This is the equivalent of:
# NSString url_string =
# [NSString stringWithCString:url.encode("utf-8")
# encoding:NSUTF8StringEncoding];
NSString = objc.objc_getClass(b"NSString")
constructor = objc.sel_registerName(b"stringWithCString:encoding:")
objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_char_p, c_ulong]
url_string = objc.objc_msgSend(
NSString,
constructor,
url.encode("utf-8"),
4, # NSUTF8StringEncoding = 4
)
# Create an NSURL object representing the URL
# This is the equivalent of:
# NSURL *nsurl = [NSURL URLWithString:url];
NSURL = objc.objc_getClass(b"NSURL")
urlWithString_ = objc.sel_registerName(b"URLWithString:")
objc.objc_msgSend.argtypes = [c_void_p, c_void_p, c_void_p]
ns_url = objc.objc_msgSend(NSURL, urlWithString_, url_string)
# Get the shared UIApplication instance
# This code is the equivalent of:
# UIApplication shared_app = [UIApplication sharedApplication]
UIApplication = objc.objc_getClass(b"UIApplication")
sharedApplication = objc.sel_registerName(b"sharedApplication")
objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
shared_app = objc.objc_msgSend(UIApplication, sharedApplication)
# Open the URL on the shared application
# This code is the equivalent of:
# [shared_app openURL:ns_url
# options:NIL
# completionHandler:NIL];
openURL_ = objc.sel_registerName(b"openURL:options:completionHandler:")
objc.objc_msgSend.argtypes = [
c_void_p, c_void_p, c_void_p, c_void_p, c_void_p
]
# Method returns void
objc.objc_msgSend.restype = None
objc.objc_msgSend(shared_app, openURL_, ns_url, None, None)
return True
def parse_args(arg_list: list[str] | None):
import argparse
parser = argparse.ArgumentParser(description="Open URL in a web browser.")
parser.add_argument("url", help="URL to open")
group = parser.add_mutually_exclusive_group()
group.add_argument("-n", "--new-window", action="store_const",
const=1, default=0, dest="new_win",
help="open new window")
group.add_argument("-t", "--new-tab", action="store_const",
const=2, default=0, dest="new_win",
help="open new tab")
args = parser.parse_args(arg_list)
return args
def main(arg_list: list[str] | None = None):
args = parse_args(arg_list)
open(args.url, args.new_win)
print("\a")
if __name__ == "__main__":
main()