@@ -16,12 +16,33 @@ class Browser(object):
16
16
17
17
def __init__ (self , url = "http://127.0.0.1:9222" ):
18
18
self .dev_url = url
19
+ self .context_ids = {}
20
+ self ._ws_api = None
19
21
20
22
if self .dev_url not in self ._all_tabs :
21
23
self ._tabs = self ._all_tabs [self .dev_url ] = {}
22
24
else :
23
25
self ._tabs = self ._all_tabs [self .dev_url ]
24
26
27
+ def _get_websocket_url (self ):
28
+ r = requests .get ("%s/json/version" % self .dev_url )
29
+ r .raise_for_status ()
30
+ version_data = r .json ()
31
+ return version_data ['webSocketDebuggerUrl' ]
32
+
33
+ @property
34
+ def ws_api (self ):
35
+ """The main Websocket API connection."""
36
+ if not self ._ws_api :
37
+ #
38
+ # This object is just a websocket with event handling, not a new Tab.
39
+ #
40
+ self ._ws_api = Tab (
41
+ id = 'browser' , type = 'browser' , webSocketDebuggerUrl = self ._get_websocket_url ()
42
+ )
43
+ self ._ws_api .start ()
44
+ return self ._ws_api
45
+
25
46
def new_tab (self , url = None , timeout = None ):
26
47
url = url or ''
27
48
rp = requests .get ("%s/json/new?%s" % (self .dev_url , url ), json = True , timeout = timeout )
@@ -36,7 +57,8 @@ def list_tab(self, timeout=None):
36
57
if tab_json ['type' ] != 'page' : # pragma: no cover
37
58
continue
38
59
39
- if tab_json ['id' ] in self ._tabs and self ._tabs [tab_json ['id' ]].status != Tab .status_stopped :
60
+ active_tab = tab_json ['id' ] in self ._tabs
61
+ if active_tab and self ._tabs [tab_json ['id' ]].status != Tab .status_stopped :
40
62
tabs_map [tab_json ['id' ]] = self ._tabs [tab_json ['id' ]]
41
63
else :
42
64
tabs_map [tab_json ['id' ]] = Tab (** tab_json )
@@ -56,6 +78,13 @@ def close_tab(self, tab_id, timeout=None):
56
78
tab_id = tab_id .id
57
79
58
80
tab = self ._tabs .pop (tab_id , None )
81
+
82
+ if tab and tab_id in self .context_ids :
83
+ self .ws_api .call_method (
84
+ 'Target.disposeBrowserContext' ,
85
+ browserContextId = self .context_ids [tab_id ]
86
+ )
87
+
59
88
if tab and tab .status == Tab .status_started : # pragma: no cover
60
89
tab .stop ()
61
90
@@ -69,4 +98,37 @@ def version(self, timeout=None):
69
98
def __str__ (self ):
70
99
return '<Browser %s>' % self .dev_url
71
100
101
+ def new_private_tab (self , timeout = None ):
102
+ """Create a new tab in a new browser context.
103
+
104
+ This tab will be isolated from other tabs.
105
+
106
+ https://chromedevtools.github.io/devtools-protocol/tot/Target/#method-createBrowserContext
107
+
108
+ :param timeout: The timeout for API calls.
109
+ :rtype: Tab
110
+ """
111
+ context = self .ws_api .Target .createBrowserContext (_timeout = timeout )
112
+ try :
113
+ context_id = context ['browserContextId' ]
114
+ except KeyError :
115
+ raise RuntimeError ("Can't create a new private context." )
116
+
117
+ target = self .ws_api .Target .createTarget (
118
+ url = 'about:blank' , browserContextId = context_id , _timeout = timeout
119
+ )
120
+
121
+ self .list_tab (timeout = timeout )
122
+
123
+ if target ['targetId' ] in self ._tabs :
124
+ tab = self ._tabs [target ['targetId' ]]
125
+ tab .context_id = context_id
126
+ self .context_ids [tab .id ] = context_id
127
+ else :
128
+ raise RuntimeError ("Failed to create a new private tab: %s" % target ['targetId' ])
129
+ return tab
130
+
131
+ def __del__ (self ):
132
+ self .ws_api .stop ()
133
+
72
134
__repr__ = __str__
0 commit comments