Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/common/bidi/network.py
1864 views
1
# Licensed to the Software Freedom Conservancy (SFC) under one
2
# or more contributor license agreements. See the NOTICE file
3
# distributed with this work for additional information
4
# regarding copyright ownership. The SFC licenses this file
5
# to you under the Apache License, Version 2.0 (the
6
# "License"); you may not use this file except in compliance
7
# with the License. You may obtain a copy of the License at
8
#
9
# http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing,
12
# software distributed under the License is distributed on an
13
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
# KIND, either express or implied. See the License for the
15
# specific language governing permissions and limitations
16
# under the License.
17
18
from selenium.webdriver.common.bidi.common import command_builder
19
20
21
class NetworkEvent:
22
"""Represents a network event."""
23
24
def __init__(self, event_class, **kwargs):
25
self.event_class = event_class
26
self.params = kwargs
27
28
@classmethod
29
def from_json(cls, json):
30
return cls(event_class=json.get("event_class"), **json)
31
32
33
class Network:
34
EVENTS = {
35
"before_request": "network.beforeRequestSent",
36
"response_started": "network.responseStarted",
37
"response_completed": "network.responseCompleted",
38
"auth_required": "network.authRequired",
39
"fetch_error": "network.fetchError",
40
"continue_request": "network.continueRequest",
41
"continue_auth": "network.continueWithAuth",
42
}
43
44
PHASES = {
45
"before_request": "beforeRequestSent",
46
"response_started": "responseStarted",
47
"auth_required": "authRequired",
48
}
49
50
def __init__(self, conn):
51
self.conn = conn
52
self.intercepts = []
53
self.callbacks = {}
54
self.subscriptions = {}
55
56
def _add_intercept(self, phases=[], contexts=None, url_patterns=None):
57
"""Add an intercept to the network.
58
59
Parameters:
60
----------
61
phases (list, optional): A list of phases to intercept.
62
Default is empty list.
63
contexts (list, optional): A list of contexts to intercept.
64
Default is None.
65
url_patterns (list, optional): A list of URL patterns to intercept.
66
Default is None.
67
68
Returns:
69
-------
70
str : intercept id
71
"""
72
params = {}
73
if contexts is not None:
74
params["contexts"] = contexts
75
if url_patterns is not None:
76
params["urlPatterns"] = url_patterns
77
if len(phases) > 0:
78
params["phases"] = phases
79
else:
80
params["phases"] = ["beforeRequestSent"]
81
cmd = command_builder("network.addIntercept", params)
82
83
result = self.conn.execute(cmd)
84
self.intercepts.append(result["intercept"])
85
return result
86
87
def _remove_intercept(self, intercept=None):
88
"""Remove a specific intercept, or all intercepts.
89
90
Parameters:
91
----------
92
intercept (str, optional): The intercept to remove.
93
Default is None.
94
95
Raises:
96
------
97
Exception: If intercept is not found.
98
99
Notes:
100
-----
101
If intercept is None, all intercepts will be removed.
102
"""
103
if intercept is None:
104
intercepts_to_remove = self.intercepts.copy() # create a copy before iterating
105
for intercept_id in intercepts_to_remove: # remove all intercepts
106
self.conn.execute(command_builder("network.removeIntercept", {"intercept": intercept_id}))
107
self.intercepts.remove(intercept_id)
108
else:
109
try:
110
self.conn.execute(command_builder("network.removeIntercept", {"intercept": intercept}))
111
self.intercepts.remove(intercept)
112
except Exception as e:
113
raise Exception(f"Exception: {e}")
114
115
def _on_request(self, event_name, callback):
116
"""Set a callback function to subscribe to a network event.
117
118
Parameters:
119
----------
120
event_name (str): The event to subscribe to.
121
callback (function): The callback function to execute on event.
122
Takes Request object as argument.
123
124
Returns:
125
-------
126
int : callback id
127
"""
128
129
event = NetworkEvent(event_name)
130
131
def _callback(event_data):
132
request = Request(
133
network=self,
134
request_id=event_data.params["request"].get("request", None),
135
body_size=event_data.params["request"].get("bodySize", None),
136
cookies=event_data.params["request"].get("cookies", None),
137
resource_type=event_data.params["request"].get("goog:resourceType", None),
138
headers=event_data.params["request"].get("headers", None),
139
headers_size=event_data.params["request"].get("headersSize", None),
140
timings=event_data.params["request"].get("timings", None),
141
url=event_data.params["request"].get("url", None),
142
)
143
callback(request)
144
145
callback_id = self.conn.add_callback(event, _callback)
146
147
if event_name in self.callbacks:
148
self.callbacks[event_name].append(callback_id)
149
else:
150
self.callbacks[event_name] = [callback_id]
151
152
return callback_id
153
154
def add_request_handler(self, event, callback, url_patterns=None, contexts=None):
155
"""Add a request handler to the network.
156
157
Parameters:
158
----------
159
event (str): The event to subscribe to.
160
url_patterns (list, optional): A list of URL patterns to intercept.
161
Default is None.
162
contexts (list, optional): A list of contexts to intercept.
163
Default is None.
164
callback (function): The callback function to execute on request interception
165
Takes Request object as argument.
166
167
Returns:
168
-------
169
int : callback id
170
"""
171
172
try:
173
event_name = self.EVENTS[event]
174
phase_name = self.PHASES[event]
175
except KeyError:
176
raise Exception(f"Event {event} not found")
177
178
result = self._add_intercept(phases=[phase_name], url_patterns=url_patterns, contexts=contexts)
179
callback_id = self._on_request(event_name, callback)
180
181
if event_name in self.subscriptions:
182
self.subscriptions[event_name].append(callback_id)
183
else:
184
params = {}
185
params["events"] = [event_name]
186
self.conn.execute(command_builder("session.subscribe", params))
187
self.subscriptions[event_name] = [callback_id]
188
189
self.callbacks[callback_id] = result["intercept"]
190
return callback_id
191
192
def remove_request_handler(self, event, callback_id):
193
"""Remove a request handler from the network.
194
195
Parameters:
196
----------
197
event_name (str): The event to unsubscribe from.
198
callback_id (int): The callback id to remove.
199
"""
200
try:
201
event_name = self.EVENTS[event]
202
except KeyError:
203
raise Exception(f"Event {event} not found")
204
205
net_event = NetworkEvent(event_name)
206
207
self.conn.remove_callback(net_event, callback_id)
208
self._remove_intercept(self.callbacks[callback_id])
209
del self.callbacks[callback_id]
210
self.subscriptions[event_name].remove(callback_id)
211
if len(self.subscriptions[event_name]) == 0:
212
params = {}
213
params["events"] = [event_name]
214
self.conn.execute(command_builder("session.unsubscribe", params))
215
del self.subscriptions[event_name]
216
217
def clear_request_handlers(self):
218
"""Clear all request handlers from the network."""
219
220
for event_name in self.subscriptions:
221
net_event = NetworkEvent(event_name)
222
for callback_id in self.subscriptions[event_name]:
223
self.conn.remove_callback(net_event, callback_id)
224
self._remove_intercept(self.callbacks[callback_id])
225
del self.callbacks[callback_id]
226
params = {}
227
params["events"] = [event_name]
228
self.conn.execute(command_builder("session.unsubscribe", params))
229
self.subscriptions = {}
230
231
def add_auth_handler(self, username, password):
232
"""Add an authentication handler to the network.
233
234
Parameters:
235
----------
236
username (str): The username to authenticate with.
237
password (str): The password to authenticate with.
238
239
Returns:
240
-------
241
int : callback id
242
"""
243
event = "auth_required"
244
245
def _callback(request):
246
request._continue_with_auth(username, password)
247
248
return self.add_request_handler(event, _callback)
249
250
def remove_auth_handler(self, callback_id):
251
"""Remove an authentication handler from the network.
252
253
Parameters:
254
----------
255
callback_id (int): The callback id to remove.
256
"""
257
event = "auth_required"
258
self.remove_request_handler(event, callback_id)
259
260
261
class Request:
262
"""Represents an intercepted network request."""
263
264
def __init__(
265
self,
266
network: Network,
267
request_id,
268
body_size=None,
269
cookies=None,
270
resource_type=None,
271
headers=None,
272
headers_size=None,
273
method=None,
274
timings=None,
275
url=None,
276
):
277
self.network = network
278
self.request_id = request_id
279
self.body_size = body_size
280
self.cookies = cookies
281
self.resource_type = resource_type
282
self.headers = headers
283
self.headers_size = headers_size
284
self.method = method
285
self.timings = timings
286
self.url = url
287
288
def fail_request(self):
289
"""Fail this request."""
290
291
if not self.request_id:
292
raise ValueError("Request not found.")
293
294
params = {"request": self.request_id}
295
self.network.conn.execute(command_builder("network.failRequest", params))
296
297
def continue_request(self, body=None, method=None, headers=None, cookies=None, url=None):
298
"""Continue after intercepting this request."""
299
300
if not self.request_id:
301
raise ValueError("Request not found.")
302
303
params = {"request": self.request_id}
304
if body is not None:
305
params["body"] = body
306
if method is not None:
307
params["method"] = method
308
if headers is not None:
309
params["headers"] = headers
310
if cookies is not None:
311
params["cookies"] = cookies
312
if url is not None:
313
params["url"] = url
314
315
self.network.conn.execute(command_builder("network.continueRequest", params))
316
317
def _continue_with_auth(self, username=None, password=None):
318
"""Continue with authentication.
319
320
Parameters:
321
----------
322
request (Request): The request to continue with.
323
username (str): The username to authenticate with.
324
password (str): The password to authenticate with.
325
326
Notes:
327
-----
328
If username or password is None, it attempts auth with no credentials
329
"""
330
331
params = {}
332
params["request"] = self.request_id
333
334
if not username or not password: # no credentials is valid option
335
params["action"] = "default"
336
else:
337
params["action"] = "provideCredentials"
338
params["credentials"] = {"type": "password", "username": username, "password": password}
339
340
self.network.conn.execute(command_builder("network.continueWithAuth", params))
341
342