ยปCore Development>Code coverage>Lib/webbrowser.py

Python code coverage for Lib/webbrowser.py

#countcontent
1n/a#! /usr/bin/env python3
2n/a"""Interfaces for launching and remotely controlling Web browsers."""
3n/a# Maintained by Georg Brandl.
4n/a
5n/aimport os
6n/aimport shlex
7n/aimport shutil
8n/aimport sys
9n/aimport subprocess
10n/a
11n/a__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
12n/a
13n/aclass Error(Exception):
14n/a pass
15n/a
16n/a_browsers = {} # Dictionary of available browser controllers
17n/a_tryorder = [] # Preference order of available browsers
18n/a
19n/adef register(name, klass, instance=None, update_tryorder=1):
20n/a """Register a browser connector and, optionally, connection."""
21n/a _browsers[name.lower()] = [klass, instance]
22n/a if update_tryorder > 0:
23n/a _tryorder.append(name)
24n/a elif update_tryorder < 0:
25n/a _tryorder.insert(0, name)
26n/a
27n/adef get(using=None):
28n/a """Return a browser launcher instance appropriate for the environment."""
29n/a if using is not None:
30n/a alternatives = [using]
31n/a else:
32n/a alternatives = _tryorder
33n/a for browser in alternatives:
34n/a if '%s' in browser:
35n/a # User gave us a command line, split it into name and args
36n/a browser = shlex.split(browser)
37n/a if browser[-1] == '&':
38n/a return BackgroundBrowser(browser[:-1])
39n/a else:
40n/a return GenericBrowser(browser)
41n/a else:
42n/a # User gave us a browser name or path.
43n/a try:
44n/a command = _browsers[browser.lower()]
45n/a except KeyError:
46n/a command = _synthesize(browser)
47n/a if command[1] is not None:
48n/a return command[1]
49n/a elif command[0] is not None:
50n/a return command[0]()
51n/a raise Error("could not locate runnable browser")
52n/a
53n/a# Please note: the following definition hides a builtin function.
54n/a# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
55n/a# instead of "from webbrowser import *".
56n/a
57n/adef open(url, new=0, autoraise=True):
58n/a for name in _tryorder:
59n/a browser = get(name)
60n/a if browser.open(url, new, autoraise):
61n/a return True
62n/a return False
63n/a
64n/adef open_new(url):
65n/a return open(url, 1)
66n/a
67n/adef open_new_tab(url):
68n/a return open(url, 2)
69n/a
70n/a
71n/adef _synthesize(browser, update_tryorder=1):
72n/a """Attempt to synthesize a controller base on existing controllers.
73n/a
74n/a This is useful to create a controller when a user specifies a path to
75n/a an entry in the BROWSER environment variable -- we can copy a general
76n/a controller to operate using a specific installation of the desired
77n/a browser in this way.
78n/a
79n/a If we can't create a controller in this way, or if there is no
80n/a executable for the requested browser, return [None, None].
81n/a
82n/a """
83n/a cmd = browser.split()[0]
84n/a if not shutil.which(cmd):
85n/a return [None, None]
86n/a name = os.path.basename(cmd)
87n/a try:
88n/a command = _browsers[name.lower()]
89n/a except KeyError:
90n/a return [None, None]
91n/a # now attempt to clone to fit the new name:
92n/a controller = command[1]
93n/a if controller and name.lower() == controller.basename:
94n/a import copy
95n/a controller = copy.copy(controller)
96n/a controller.name = browser
97n/a controller.basename = os.path.basename(browser)
98n/a register(browser, None, controller, update_tryorder)
99n/a return [None, controller]
100n/a return [None, None]
101n/a
102n/a
103n/a# General parent classes
104n/a
105n/aclass BaseBrowser(object):
106n/a """Parent class for all browsers. Do not use directly."""
107n/a
108n/a args = ['%s']
109n/a
110n/a def __init__(self, name=""):
111n/a self.name = name
112n/a self.basename = name
113n/a
114n/a def open(self, url, new=0, autoraise=True):
115n/a raise NotImplementedError
116n/a
117n/a def open_new(self, url):
118n/a return self.open(url, 1)
119n/a
120n/a def open_new_tab(self, url):
121n/a return self.open(url, 2)
122n/a
123n/a
124n/aclass GenericBrowser(BaseBrowser):
125n/a """Class for all browsers started with a command
126n/a and without remote functionality."""
127n/a
128n/a def __init__(self, name):
129n/a if isinstance(name, str):
130n/a self.name = name
131n/a self.args = ["%s"]
132n/a else:
133n/a # name should be a list with arguments
134n/a self.name = name[0]
135n/a self.args = name[1:]
136n/a self.basename = os.path.basename(self.name)
137n/a
138n/a def open(self, url, new=0, autoraise=True):
139n/a cmdline = [self.name] + [arg.replace("%s", url)
140n/a for arg in self.args]
141n/a try:
142n/a if sys.platform[:3] == 'win':
143n/a p = subprocess.Popen(cmdline)
144n/a else:
145n/a p = subprocess.Popen(cmdline, close_fds=True)
146n/a return not p.wait()
147n/a except OSError:
148n/a return False
149n/a
150n/a
151n/aclass BackgroundBrowser(GenericBrowser):
152n/a """Class for all browsers which are to be started in the
153n/a background."""
154n/a
155n/a def open(self, url, new=0, autoraise=True):
156n/a cmdline = [self.name] + [arg.replace("%s", url)
157n/a for arg in self.args]
158n/a try:
159n/a if sys.platform[:3] == 'win':
160n/a p = subprocess.Popen(cmdline)
161n/a else:
162n/a p = subprocess.Popen(cmdline, close_fds=True,
163n/a start_new_session=True)
164n/a return (p.poll() is None)
165n/a except OSError:
166n/a return False
167n/a
168n/a
169n/aclass UnixBrowser(BaseBrowser):
170n/a """Parent class for all Unix browsers with remote functionality."""
171n/a
172n/a raise_opts = None
173n/a background = False
174n/a redirect_stdout = True
175n/a # In remote_args, %s will be replaced with the requested URL. %action will
176n/a # be replaced depending on the value of 'new' passed to open.
177n/a # remote_action is used for new=0 (open). If newwin is not None, it is
178n/a # used for new=1 (open_new). If newtab is not None, it is used for
179n/a # new=3 (open_new_tab). After both substitutions are made, any empty
180n/a # strings in the transformed remote_args list will be removed.
181n/a remote_args = ['%action', '%s']
182n/a remote_action = None
183n/a remote_action_newwin = None
184n/a remote_action_newtab = None
185n/a
186n/a def _invoke(self, args, remote, autoraise):
187n/a raise_opt = []
188n/a if remote and self.raise_opts:
189n/a # use autoraise argument only for remote invocation
190n/a autoraise = int(autoraise)
191n/a opt = self.raise_opts[autoraise]
192n/a if opt: raise_opt = [opt]
193n/a
194n/a cmdline = [self.name] + raise_opt + args
195n/a
196n/a if remote or self.background:
197n/a inout = subprocess.DEVNULL
198n/a else:
199n/a # for TTY browsers, we need stdin/out
200n/a inout = None
201n/a p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
202n/a stdout=(self.redirect_stdout and inout or None),
203n/a stderr=inout, start_new_session=True)
204n/a if remote:
205n/a # wait at most five seconds. If the subprocess is not finished, the
206n/a # remote invocation has (hopefully) started a new instance.
207n/a try:
208n/a rc = p.wait(5)
209n/a # if remote call failed, open() will try direct invocation
210n/a return not rc
211n/a except subprocess.TimeoutExpired:
212n/a return True
213n/a elif self.background:
214n/a if p.poll() is None:
215n/a return True
216n/a else:
217n/a return False
218n/a else:
219n/a return not p.wait()
220n/a
221n/a def open(self, url, new=0, autoraise=True):
222n/a if new == 0:
223n/a action = self.remote_action
224n/a elif new == 1:
225n/a action = self.remote_action_newwin
226n/a elif new == 2:
227n/a if self.remote_action_newtab is None:
228n/a action = self.remote_action_newwin
229n/a else:
230n/a action = self.remote_action_newtab
231n/a else:
232n/a raise Error("Bad 'new' parameter to open(); " +
233n/a "expected 0, 1, or 2, got %s" % new)
234n/a
235n/a args = [arg.replace("%s", url).replace("%action", action)
236n/a for arg in self.remote_args]
237n/a args = [arg for arg in args if arg]
238n/a success = self._invoke(args, True, autoraise)
239n/a if not success:
240n/a # remote invocation failed, try straight way
241n/a args = [arg.replace("%s", url) for arg in self.args]
242n/a return self._invoke(args, False, False)
243n/a else:
244n/a return True
245n/a
246n/a
247n/aclass Mozilla(UnixBrowser):
248n/a """Launcher class for Mozilla browsers."""
249n/a
250n/a remote_args = ['%action', '%s']
251n/a remote_action = ""
252n/a remote_action_newwin = "-new-window"
253n/a remote_action_newtab = "-new-tab"
254n/a background = True
255n/a
256n/a
257n/aclass Netscape(UnixBrowser):
258n/a """Launcher class for Netscape browser."""
259n/a
260n/a raise_opts = ["-noraise", "-raise"]
261n/a remote_args = ['-remote', 'openURL(%s%action)']
262n/a remote_action = ""
263n/a remote_action_newwin = ",new-window"
264n/a remote_action_newtab = ",new-tab"
265n/a background = True
266n/a
267n/a
268n/aclass Galeon(UnixBrowser):
269n/a """Launcher class for Galeon/Epiphany browsers."""
270n/a
271n/a raise_opts = ["-noraise", ""]
272n/a remote_args = ['%action', '%s']
273n/a remote_action = "-n"
274n/a remote_action_newwin = "-w"
275n/a background = True
276n/a
277n/a
278n/aclass Chrome(UnixBrowser):
279n/a "Launcher class for Google Chrome browser."
280n/a
281n/a remote_args = ['%action', '%s']
282n/a remote_action = ""
283n/a remote_action_newwin = "--new-window"
284n/a remote_action_newtab = ""
285n/a background = True
286n/a
287n/aChromium = Chrome
288n/a
289n/a
290n/aclass Opera(UnixBrowser):
291n/a "Launcher class for Opera browser."
292n/a
293n/a raise_opts = ["-noraise", ""]
294n/a remote_args = ['-remote', 'openURL(%s%action)']
295n/a remote_action = ""
296n/a remote_action_newwin = ",new-window"
297n/a remote_action_newtab = ",new-page"
298n/a background = True
299n/a
300n/a
301n/aclass Elinks(UnixBrowser):
302n/a "Launcher class for Elinks browsers."
303n/a
304n/a remote_args = ['-remote', 'openURL(%s%action)']
305n/a remote_action = ""
306n/a remote_action_newwin = ",new-window"
307n/a remote_action_newtab = ",new-tab"
308n/a background = False
309n/a
310n/a # elinks doesn't like its stdout to be redirected -
311n/a # it uses redirected stdout as a signal to do -dump
312n/a redirect_stdout = False
313n/a
314n/a
315n/aclass Konqueror(BaseBrowser):
316n/a """Controller for the KDE File Manager (kfm, or Konqueror).
317n/a
318n/a See the output of ``kfmclient --commands``
319n/a for more information on the Konqueror remote-control interface.
320n/a """
321n/a
322n/a def open(self, url, new=0, autoraise=True):
323n/a # XXX Currently I know no way to prevent KFM from opening a new win.
324n/a if new == 2:
325n/a action = "newTab"
326n/a else:
327n/a action = "openURL"
328n/a
329n/a devnull = subprocess.DEVNULL
330n/a
331n/a try:
332n/a p = subprocess.Popen(["kfmclient", action, url],
333n/a close_fds=True, stdin=devnull,
334n/a stdout=devnull, stderr=devnull)
335n/a except OSError:
336n/a # fall through to next variant
337n/a pass
338n/a else:
339n/a p.wait()
340n/a # kfmclient's return code unfortunately has no meaning as it seems
341n/a return True
342n/a
343n/a try:
344n/a p = subprocess.Popen(["konqueror", "--silent", url],
345n/a close_fds=True, stdin=devnull,
346n/a stdout=devnull, stderr=devnull,
347n/a start_new_session=True)
348n/a except OSError:
349n/a # fall through to next variant
350n/a pass
351n/a else:
352n/a if p.poll() is None:
353n/a # Should be running now.
354n/a return True
355n/a
356n/a try:
357n/a p = subprocess.Popen(["kfm", "-d", url],
358n/a close_fds=True, stdin=devnull,
359n/a stdout=devnull, stderr=devnull,
360n/a start_new_session=True)
361n/a except OSError:
362n/a return False
363n/a else:
364n/a return (p.poll() is None)
365n/a
366n/a
367n/aclass Grail(BaseBrowser):
368n/a # There should be a way to maintain a connection to Grail, but the
369n/a # Grail remote control protocol doesn't really allow that at this
370n/a # point. It probably never will!
371n/a def _find_grail_rc(self):
372n/a import glob
373n/a import pwd
374n/a import socket
375n/a import tempfile
376n/a tempdir = os.path.join(tempfile.gettempdir(),
377n/a ".grail-unix")
378n/a user = pwd.getpwuid(os.getuid())[0]
379n/a filename = os.path.join(tempdir, user + "-*")
380n/a maybes = glob.glob(filename)
381n/a if not maybes:
382n/a return None
383n/a s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
384n/a for fn in maybes:
385n/a # need to PING each one until we find one that's live
386n/a try:
387n/a s.connect(fn)
388n/a except OSError:
389n/a # no good; attempt to clean it out, but don't fail:
390n/a try:
391n/a os.unlink(fn)
392n/a except OSError:
393n/a pass
394n/a else:
395n/a return s
396n/a
397n/a def _remote(self, action):
398n/a s = self._find_grail_rc()
399n/a if not s:
400n/a return 0
401n/a s.send(action)
402n/a s.close()
403n/a return 1
404n/a
405n/a def open(self, url, new=0, autoraise=True):
406n/a if new:
407n/a ok = self._remote("LOADNEW " + url)
408n/a else:
409n/a ok = self._remote("LOAD " + url)
410n/a return ok
411n/a
412n/a
413n/a#
414n/a# Platform support for Unix
415n/a#
416n/a
417n/a# These are the right tests because all these Unix browsers require either
418n/a# a console terminal or an X display to run.
419n/a
420n/adef register_X_browsers():
421n/a
422n/a # use xdg-open if around
423n/a if shutil.which("xdg-open"):
424n/a register("xdg-open", None, BackgroundBrowser("xdg-open"))
425n/a
426n/a # The default GNOME3 browser
427n/a if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
428n/a register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
429n/a
430n/a # The default GNOME browser
431n/a if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
432n/a register("gnome-open", None, BackgroundBrowser("gnome-open"))
433n/a
434n/a # The default KDE browser
435n/a if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
436n/a register("kfmclient", Konqueror, Konqueror("kfmclient"))
437n/a
438n/a if shutil.which("x-www-browser"):
439n/a register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
440n/a
441n/a # The Mozilla browsers
442n/a for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
443n/a if shutil.which(browser):
444n/a register(browser, None, Mozilla(browser))
445n/a
446n/a # The Netscape and old Mozilla browsers
447n/a for browser in ("mozilla-firefox",
448n/a "mozilla-firebird", "firebird",
449n/a "mozilla", "netscape"):
450n/a if shutil.which(browser):
451n/a register(browser, None, Netscape(browser))
452n/a
453n/a # Konqueror/kfm, the KDE browser.
454n/a if shutil.which("kfm"):
455n/a register("kfm", Konqueror, Konqueror("kfm"))
456n/a elif shutil.which("konqueror"):
457n/a register("konqueror", Konqueror, Konqueror("konqueror"))
458n/a
459n/a # Gnome's Galeon and Epiphany
460n/a for browser in ("galeon", "epiphany"):
461n/a if shutil.which(browser):
462n/a register(browser, None, Galeon(browser))
463n/a
464n/a # Skipstone, another Gtk/Mozilla based browser
465n/a if shutil.which("skipstone"):
466n/a register("skipstone", None, BackgroundBrowser("skipstone"))
467n/a
468n/a # Google Chrome/Chromium browsers
469n/a for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
470n/a if shutil.which(browser):
471n/a register(browser, None, Chrome(browser))
472n/a
473n/a # Opera, quite popular
474n/a if shutil.which("opera"):
475n/a register("opera", None, Opera("opera"))
476n/a
477n/a # Next, Mosaic -- old but still in use.
478n/a if shutil.which("mosaic"):
479n/a register("mosaic", None, BackgroundBrowser("mosaic"))
480n/a
481n/a # Grail, the Python browser. Does anybody still use it?
482n/a if shutil.which("grail"):
483n/a register("grail", Grail, None)
484n/a
485n/a# Prefer X browsers if present
486n/aif os.environ.get("DISPLAY"):
487n/a register_X_browsers()
488n/a
489n/a# Also try console browsers
490n/aif os.environ.get("TERM"):
491n/a if shutil.which("www-browser"):
492n/a register("www-browser", None, GenericBrowser("www-browser"))
493n/a # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
494n/a if shutil.which("links"):
495n/a register("links", None, GenericBrowser("links"))
496n/a if shutil.which("elinks"):
497n/a register("elinks", None, Elinks("elinks"))
498n/a # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
499n/a if shutil.which("lynx"):
500n/a register("lynx", None, GenericBrowser("lynx"))
501n/a # The w3m browser <http://w3m.sourceforge.net/>
502n/a if shutil.which("w3m"):
503n/a register("w3m", None, GenericBrowser("w3m"))
504n/a
505n/a#
506n/a# Platform support for Windows
507n/a#
508n/a
509n/aif sys.platform[:3] == "win":
510n/a class WindowsDefault(BaseBrowser):
511n/a def open(self, url, new=0, autoraise=True):
512n/a try:
513n/a os.startfile(url)
514n/a except OSError:
515n/a # [Error 22] No application is associated with the specified
516n/a # file for this operation: '<URL>'
517n/a return False
518n/a else:
519n/a return True
520n/a
521n/a _tryorder = []
522n/a _browsers = {}
523n/a
524n/a # First try to use the default Windows browser
525n/a register("windows-default", WindowsDefault)
526n/a
527n/a # Detect some common Windows browsers, fallback to IE
528n/a iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
529n/a "Internet Explorer\\IEXPLORE.EXE")
530n/a for browser in ("firefox", "firebird", "seamonkey", "mozilla",
531n/a "netscape", "opera", iexplore):
532n/a if shutil.which(browser):
533n/a register(browser, None, BackgroundBrowser(browser))
534n/a
535n/a#
536n/a# Platform support for MacOS
537n/a#
538n/a
539n/aif sys.platform == 'darwin':
540n/a # Adapted from patch submitted to SourceForge by Steven J. Burr
541n/a class MacOSX(BaseBrowser):
542n/a """Launcher class for Aqua browsers on Mac OS X
543n/a
544n/a Optionally specify a browser name on instantiation. Note that this
545n/a will not work for Aqua browsers if the user has moved the application
546n/a package after installation.
547n/a
548n/a If no browser is specified, the default browser, as specified in the
549n/a Internet System Preferences panel, will be used.
550n/a """
551n/a def __init__(self, name):
552n/a self.name = name
553n/a
554n/a def open(self, url, new=0, autoraise=True):
555n/a assert "'" not in url
556n/a # hack for local urls
557n/a if not ':' in url:
558n/a url = 'file:'+url
559n/a
560n/a # new must be 0 or 1
561n/a new = int(bool(new))
562n/a if self.name == "default":
563n/a # User called open, open_new or get without a browser parameter
564n/a script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
565n/a else:
566n/a # User called get and chose a browser
567n/a if self.name == "OmniWeb":
568n/a toWindow = ""
569n/a else:
570n/a # Include toWindow parameter of OpenURL command for browsers
571n/a # that support it. 0 == new window; -1 == existing
572n/a toWindow = "toWindow %d" % (new - 1)
573n/a cmd = 'OpenURL "%s"' % url.replace('"', '%22')
574n/a script = '''tell application "%s"
575n/a activate
576n/a %s %s
577n/a end tell''' % (self.name, cmd, toWindow)
578n/a # Open pipe to AppleScript through osascript command
579n/a osapipe = os.popen("osascript", "w")
580n/a if osapipe is None:
581n/a return False
582n/a # Write script to osascript's stdin
583n/a osapipe.write(script)
584n/a rc = osapipe.close()
585n/a return not rc
586n/a
587n/a class MacOSXOSAScript(BaseBrowser):
588n/a def __init__(self, name):
589n/a self._name = name
590n/a
591n/a def open(self, url, new=0, autoraise=True):
592n/a if self._name == 'default':
593n/a script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
594n/a else:
595n/a script = '''
596n/a tell application "%s"
597n/a activate
598n/a open location "%s"
599n/a end
600n/a '''%(self._name, url.replace('"', '%22'))
601n/a
602n/a osapipe = os.popen("osascript", "w")
603n/a if osapipe is None:
604n/a return False
605n/a
606n/a osapipe.write(script)
607n/a rc = osapipe.close()
608n/a return not rc
609n/a
610n/a
611n/a # Don't clear _tryorder or _browsers since OS X can use above Unix support
612n/a # (but we prefer using the OS X specific stuff)
613n/a register("safari", None, MacOSXOSAScript('safari'), -1)
614n/a register("firefox", None, MacOSXOSAScript('firefox'), -1)
615n/a register("chrome", None, MacOSXOSAScript('chrome'), -1)
616n/a register("MacOSX", None, MacOSXOSAScript('default'), -1)
617n/a
618n/a
619n/a# OK, now that we know what the default preference orders for each
620n/a# platform are, allow user to override them with the BROWSER variable.
621n/aif "BROWSER" in os.environ:
622n/a _userchoices = os.environ["BROWSER"].split(os.pathsep)
623n/a _userchoices.reverse()
624n/a
625n/a # Treat choices in same way as if passed into get() but do register
626n/a # and prepend to _tryorder
627n/a for cmdline in _userchoices:
628n/a if cmdline != '':
629n/a cmd = _synthesize(cmdline, -1)
630n/a if cmd[1] is None:
631n/a register(cmdline, None, GenericBrowser(cmdline), -1)
632n/a cmdline = None # to make del work if _userchoices was empty
633n/a del cmdline
634n/a del _userchoices
635n/a
636n/a# what to do if _tryorder is now empty?
637n/a
638n/a
639n/adef main():
640n/a import getopt
641n/a usage = """Usage: %s [-n | -t] url
642n/a -n: open new window
643n/a -t: open new tab""" % sys.argv[0]
644n/a try:
645n/a opts, args = getopt.getopt(sys.argv[1:], 'ntd')
646n/a except getopt.error as msg:
647n/a print(msg, file=sys.stderr)
648n/a print(usage, file=sys.stderr)
649n/a sys.exit(1)
650n/a new_win = 0
651n/a for o, a in opts:
652n/a if o == '-n': new_win = 1
653n/a elif o == '-t': new_win = 2
654n/a if len(args) != 1:
655n/a print(usage, file=sys.stderr)
656n/a sys.exit(1)
657n/a
658n/a url = args[0]
659n/a open(url, new_win)
660n/a
661n/a print("\a")
662n/a
663n/aif __name__ == "__main__":
664n/a main()