Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-105983: Add private mode support to webbrowser #105984

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
814309a
browser incognito mode
kr-g Jun 22, 2023
2a67e33
📜🤖 Added by blurb_it.
blurb-it[bot] Jun 22, 2023
9d2fcfe
Merge branch 'main' into kr-g-browser-incognito
kr-g Jun 22, 2023
46d06d4
browser incognito mode
kr-g Jun 22, 2023
7baebe5
removed unused flag
kr-g Jun 22, 2023
b9079f4
reverted changes cause by formatting with black
kr-g Jun 22, 2023
f04f255
Update Misc/NEWS.d/next/Security/2023-06-22-09-14-45.gh-issue-105983.…
kr-g Jun 22, 2023
7eaa455
doc text
kr-g Jun 22, 2023
9e4ee59
Update Lib/webbrowser.py
kr-g Jun 22, 2023
333c0e7
typo
kr-g Jun 22, 2023
4872893
Merge branch 'python:main' into kr-g-browser-incognito
kr-g Jun 23, 2023
308e060
removed outdated browsers
kr-g Jun 23, 2023
fd0e6a3
removed var assignment
kr-g Jun 23, 2023
e952efe
moved from security to library folder
kr-g Jun 23, 2023
ade902e
added tests
kr-g Jun 23, 2023
7c28c35
renamed to test_open_incognito
kr-g Jun 23, 2023
6597494
Update Lib/test/test_webbrowser.py
kr-g Jun 23, 2023
8975499
Update Lib/webbrowser.py
kr-g Jun 23, 2023
2b9a280
gh-105983: added incognito / private mode to webbrowser.py
AmirNakhaeii Jun 24, 2023
545784d
gh-105983: Fixed some logical errors
AmirNakhaeii Jun 24, 2023
f1a20fb
📜🤖 Added by blurb_it.
blurb-it[bot] Jun 24, 2023
ae0602b
Merge branch 'python:main' into fix-issue-105983
AmirNakhaeii Jun 24, 2023
9312844
Merge branch 'main' into fix-issue-105983
AmirNakhaeii Jun 25, 2023
9e9811b
gh-105983: Fix incognito flag for edge browser
AmirNakhaeii Jun 25, 2023
a4657c7
added incognito mode documentation
kr-g Jun 27, 2023
0f4b11f
Merge branch 'main' into kr-g-browser-incognito
kr-g Jun 27, 2023
9580689
Merge branch 'kr-g-browser-incognito' of https://github.com/kr-g/cpyt…
kr-g Jun 27, 2023
3684723
merged branch from amir
kr-g Jun 30, 2023
2d7a9c5
removed
kr-g Jun 30, 2023
993f387
Merge pull request #1 from kr-g/amir-contrib
kr-g Jun 30, 2023
9bb7124
corrected incognito var
kr-g Jun 30, 2023
2130631
added missing help text
kr-g Jun 30, 2023
f8364db
added microsoft edge to the browser list
kr-g Jun 30, 2023
d1de67c
reworked browser list option
kr-g Jun 30, 2023
994fe23
Merge branch 'python:main' into kr-g-browser-incognito
kr-g Jun 30, 2023
a43bd63
Merge branch 'main' into kr-g-browser-incognito
kr-g Jun 30, 2023
3cd8d82
Merge branch 'python:main' into kr-g-browser-incognito
kr-g Jul 18, 2023
5f0afcb
Merge branch 'python:main' into kr-g-browser-incognito
kr-g Sep 11, 2023
9408b3a
Merge branch 'python:main' into kr-g-browser-incognito
kr-g Sep 19, 2023
394265c
Merge branch 'python:main' into kr-g-browser-incognito
kr-g Oct 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Doc/library/webbrowser.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ The following functions are defined:
equivalent to :func:`open_new`.


.. function:: open_incognito(url)

Open *url* in incognito, or private, mode of the default browser, if possible, otherwise
equivalent to :func:`open_new`.


.. function:: get(using=None)

Return a controller object for the browser type *using*. If *using* is
Expand Down Expand Up @@ -221,6 +227,12 @@ module-level convenience functions:
possible, otherwise equivalent to :func:`open_new`.


.. method:: controller.open_incognito(url)

Open *url* in a incognito, or private, mode of the browser handled by this controller, if
possible, otherwise equivalent to :func:`open_new`.


.. rubric:: Footnotes

.. [1] Executables named here without a full path will be searched in the
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_webbrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ def test_open_new_tab(self):
options=[],
arguments=[URL])

def test_open_incognito(self):
self._test('open_incognito',
options=["--incognito"],
arguments=[URL])


class EdgeCommandTest(CommandTestMixin, unittest.TestCase):

Expand All @@ -119,6 +124,11 @@ def test_open_new_tab(self):
options=[],
arguments=[URL])

def test_open_incognito(self):
self._test('open_incognito',
options=["--inprivate"],
arguments=[URL])


class MozillaCommandTest(CommandTestMixin, unittest.TestCase):

Expand All @@ -144,6 +154,10 @@ def test_open_new_tab(self):
options=[],
arguments=['-new-tab', URL])

def test_open_incognito(self):
self._test('open_incognito',
options=["--private-window"],
arguments=[URL])

class EpiphanyCommandTest(CommandTestMixin, unittest.TestCase):

Expand All @@ -169,6 +183,10 @@ def test_open_new_tab(self):
options=['-w'],
arguments=[URL])

def test_open_incognito(self):
self._test('open_incognito',
options=["--incognito-mode"],
arguments=[URL])

class OperaCommandTest(CommandTestMixin, unittest.TestCase):

Expand Down
51 changes: 47 additions & 4 deletions Lib/webbrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Error(Exception):
_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:
Expand Down Expand Up @@ -75,6 +76,7 @@ def open(url, new=0, autoraise=True):
- 0: the same browser window (the default).
- 1: a new browser window.
- 2: a new browser page ("tab").
- 3: an incognito/ private browser.
If possible, autoraise raises the window (the default) or not.
"""
if _tryorder is None:
Expand All @@ -101,6 +103,12 @@ def open_new_tab(url):
"""
return open(url, 2)

def open_incognito(url):
"""Open url in incognito mode of the default browser.

If not possible, then the behavior becomes equivalent to open_new().
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Falling back to "open_new" feels like "errors passing silently" to me. Why was this choice made?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a user of the API I'd be disappointed if an URL is opened in regular mode when I explicitly asked to open it in incognito mode. I'd therefore prefer to raise an exception when incognito mode isn't available.

For use cases where incognito mode is preferred but optional the user can code this themselves, whereas you cannot build my preferred semantics on top of the current implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two API use cases are different: open_new_tab vs open_new is "just" a different look and feel. "open_incognito" has semantic differences in the browser state (such as not storing the page in long term history and separate cookie stores). Opening a page in a regular window when an incognito window was requested can result in unwanted side effects.

I wouldn't mind a keyword argument to enable a fallback to open_new, but it shouldn't be the default. Just like exists_ok defaults to false in os.makedirs. Something like def open_incognito(url, *, regular_ok=False).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @ronaldoussoren: if a user is asking to keep their private URL secret, we must not "leak" that to the open internet, which could have extremely serious consequences for some people. Let's raise an exception instead.

Regarding Opera: if the user asks for a regular tab and it opens in private tab, that's fine, nothing is leaked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've plenty of time: 3.13 will be released in October 2024 with a May 2024 deadline to get new features in.

For documentation, something like:

webbrowser.open_private(url)
Open url in a private/incognito window of the default browser, if possible, otherwise raise RuntimeError.

@ronaldoussoren has already made the proposal, @kr-g please could you update this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no preference on whether it should be an exception of a separate method or a permissive argument.

I can agree with others about the issue in general only because a user who is supposed to get the incognito mode most likely will be unaware of it. So we have a choice between "always show user a warning in advance" vs "let the application gracefully treat a failure by showing an absolutely relevant warning with instructions (or an error if the very fact of page visitation must not be leaked)". Personally, I prefer the second option.

The exact interface choice is out of my competence.

"""
return open(url, 3)

def _synthesize(browser, *, preferred=False):
"""Attempt to synthesize a controller based on existing controllers.
Expand Down Expand Up @@ -154,6 +162,9 @@ def open_new(self, url):
def open_new_tab(self, url):
return self.open(url, 2)

def open_incognito(self, url):
return self.open(url, 3)


class GenericBrowser(BaseBrowser):
"""Class for all browsers started with a command
Expand Down Expand Up @@ -218,6 +229,7 @@ class UnixBrowser(BaseBrowser):
remote_action = None
remote_action_newwin = None
remote_action_newtab = None
remote_action_incognito = ""

def _invoke(self, args, remote, autoraise, url=None):
raise_opt = []
Expand Down Expand Up @@ -265,9 +277,11 @@ def open(self, url, new=0, autoraise=True):
action = self.remote_action_newwin
else:
action = self.remote_action_newtab
elif new == 3:
action = self.remote_action_incognito
else:
raise Error("Bad 'new' parameter to open(); " +
"expected 0, 1, or 2, got %s" % new)
f"expected 0, 1, 2, or 3, got {new}")

args = [arg.replace("%s", url).replace("%action", action)
for arg in self.remote_args]
Expand All @@ -288,6 +302,7 @@ class Mozilla(UnixBrowser):
remote_action = ""
remote_action_newwin = "-new-window"
remote_action_newtab = "-new-tab"
remote_action_incognito = "--private-window"
background = True


Expand All @@ -298,6 +313,7 @@ class Epiphany(UnixBrowser):
remote_args = ['%action', '%s']
remote_action = "-n"
remote_action_newwin = "-w"
remote_action_incognito = "--incognito-mode"
background = True


Expand All @@ -308,6 +324,7 @@ class Chrome(UnixBrowser):
remote_action = ""
remote_action_newwin = "--new-window"
remote_action_newtab = ""
remote_action_incognito = "--incognito"
background = True

Chromium = Chrome
Expand Down Expand Up @@ -397,6 +414,7 @@ class Edge(UnixBrowser):
remote_action = ""
remote_action_newwin = "--new-window"
remote_action_newtab = ""
remote_action_incognito = "--inprivate"
background = True


Expand Down Expand Up @@ -588,14 +606,23 @@ def open(self, url, new=0, autoraise=True):
return not rc


def _get_supported_browsers():
if _tryorder is None:
register_standard_browsers()
return _browsers.keys()


def main():
import getopt
usage = """Usage: %s [-n | -t | -h] url
usage = """Usage: %s [-l] | [-b browser] [-i | -n | -t | -h] url
-n: open new window
-t: open new tab
-i: open with private mode
-l: list available browsers
-b <browser>: uses <browser> to open
-h, --help: show help""" % sys.argv[0]
try:
opts, args = getopt.getopt(sys.argv[1:], 'ntdh',['help'])
opts, args = getopt.getopt(sys.argv[1:], 'lintb:dh',['help'])
except getopt.error as msg:
print(msg, file=sys.stderr)
print(usage, file=sys.stderr)
Expand All @@ -604,6 +631,14 @@ def main():
for o, a in opts:
if o == '-n': new_win = 1
elif o == '-t': new_win = 2
elif o == "-i":
new_win = 3
elif o == "-b":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have -b and -l added? I am not opposed to this, but again: this feature is not related.

If you feel like adding this feature is a good idea (it looks like it might be), please open a new issue: explain the use-case and the "why".

But, let's keep this PR simple: only incognito mode.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is related to the cmdline interface when webbroser main() func is called.
they belong also to the newly added incognito method.
therefore it is not a new feature from my perspective.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not resolved at all. These are separate new additions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do appreciate your contribution here!
As the PR author, you are in the best place to address the review by removing the code added to support --browser.
That can be discussed in a separate issue. But if you don’t have the time, then someone else could complete the PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for me this is a migration topic. the experts do know better what to do here. myself i dont see here in the working loop since i dont have this detailed knowledge of what being best on master code baseline

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don’t understand «migration». There are clear requests to remove some code that was added, to keep this PR focused on one thing. If you don’t want to pursue it, then someone else will have to volunteer.

browser = a
elif o == "-l":
for nam in _get_supported_browsers():
print(nam)
sys.exit()
elif o == '-h' or o == '--help':
print(usage, file=sys.stderr)
sys.exit()
Expand All @@ -612,7 +647,15 @@ def main():
sys.exit(1)

url = args[0]
open(url, new_win)
if browser:
try:
br = get(browser)
except:
print(f"browser {browser} not found", file=sys.stderr)
sys.exit(1)
br.open(url, new_win)
else:
open(url, new_win)

print("\a")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for incognito / private browsing for :func:`webbrowser.open`.
Loading