15
15
# specific language governing permissions and limitations
16
16
# under the License.
17
17
18
- from typing import Optional , Union
18
+ from typing import Any , Callable , Optional , Union
19
19
20
20
from selenium .webdriver .common .bidi .common import command_builder
21
21
@@ -66,12 +66,23 @@ def from_json(cls, json: dict) -> "NavigationInfo":
66
66
-------
67
67
NavigationInfo: A new instance of NavigationInfo.
68
68
"""
69
- return cls (
70
- context = json .get ("context" ),
71
- navigation = json .get ("navigation" ),
72
- timestamp = json .get ("timestamp" ),
73
- url = json .get ("url" ),
74
- )
69
+ context = json .get ("context" )
70
+ if context is None or not isinstance (context , str ):
71
+ raise ValueError ("context is required and must be a string" )
72
+
73
+ navigation = json .get ("navigation" )
74
+ if navigation is not None and not isinstance (navigation , str ):
75
+ raise ValueError ("navigation must be a string" )
76
+
77
+ timestamp = json .get ("timestamp" )
78
+ if timestamp is None or not isinstance (timestamp , int ) or timestamp < 0 :
79
+ raise ValueError ("timestamp is required and must be a non-negative integer" )
80
+
81
+ url = json .get ("url" )
82
+ if url is None or not isinstance (url , str ):
83
+ raise ValueError ("url is required and must be a string" )
84
+
85
+ return cls (context , navigation , timestamp , url )
75
86
76
87
77
88
class BrowsingContextInfo :
@@ -82,10 +93,10 @@ def __init__(
82
93
context : str ,
83
94
url : str ,
84
95
children : Optional [list ["BrowsingContextInfo" ]],
96
+ client_window : str ,
97
+ user_context : str ,
85
98
parent : Optional [str ] = None ,
86
- user_context : Optional [str ] = None ,
87
99
original_opener : Optional [str ] = None ,
88
- client_window : Optional [str ] = None ,
89
100
):
90
101
self .context = context
91
102
self .url = url
@@ -108,17 +119,49 @@ def from_json(cls, json: dict) -> "BrowsingContextInfo":
108
119
BrowsingContextInfo: A new instance of BrowsingContextInfo.
109
120
"""
110
121
children = None
111
- if json .get ("children" ) is not None :
112
- children = [BrowsingContextInfo .from_json (child ) for child in json .get ("children" )]
122
+ raw_children = json .get ("children" )
123
+ if raw_children is not None :
124
+ if not isinstance (raw_children , list ):
125
+ raise ValueError ("children must be a list if provided" )
126
+
127
+ children = []
128
+ for child in raw_children :
129
+ if not isinstance (child , dict ):
130
+ raise ValueError (f"Each child must be a dictionary, got { type (child )} " )
131
+ children .append (BrowsingContextInfo .from_json (child ))
132
+
133
+ context = json .get ("context" )
134
+ if context is None or not isinstance (context , str ):
135
+ raise ValueError ("context is required and must be a string" )
136
+
137
+ url = json .get ("url" )
138
+ if url is None or not isinstance (url , str ):
139
+ raise ValueError ("url is required and must be a string" )
140
+
141
+ parent = json .get ("parent" )
142
+ if parent is not None and not isinstance (parent , str ):
143
+ raise ValueError ("parent must be a string if provided" )
144
+
145
+ user_context = json .get ("userContext" )
146
+ if user_context is None or not isinstance (user_context , str ):
147
+ raise ValueError ("userContext is required and must be a string" )
148
+
149
+ original_opener = json .get ("originalOpener" )
150
+ if original_opener is not None and not isinstance (original_opener , str ):
151
+ raise ValueError ("originalOpener must be a string if provided" )
152
+
153
+ client_window = json .get ("clientWindow" )
154
+ if client_window is None or not isinstance (client_window , str ):
155
+ raise ValueError ("clientWindow is required and must be a string" )
113
156
114
157
return cls (
115
- context = json . get ( " context" ) ,
116
- url = json . get ( " url" ) ,
158
+ context = context ,
159
+ url = url ,
117
160
children = children ,
118
- parent = json . get ( "parent" ) ,
119
- user_context = json . get ( "userContext" ) ,
120
- original_opener = json . get ( "originalOpener" ) ,
121
- client_window = json . get ( "clientWindow" ) ,
161
+ client_window = client_window ,
162
+ user_context = user_context ,
163
+ parent = parent ,
164
+ original_opener = original_opener ,
122
165
)
123
166
124
167
@@ -148,12 +191,32 @@ def from_json(cls, json: dict) -> "DownloadWillBeginParams":
148
191
-------
149
192
DownloadWillBeginParams: A new instance of DownloadWillBeginParams.
150
193
"""
194
+ context = json .get ("context" )
195
+ if context is None or not isinstance (context , str ):
196
+ raise ValueError ("context is required and must be a string" )
197
+
198
+ navigation = json .get ("navigation" )
199
+ if navigation is not None and not isinstance (navigation , str ):
200
+ raise ValueError ("navigation must be a string" )
201
+
202
+ timestamp = json .get ("timestamp" )
203
+ if timestamp is None or not isinstance (timestamp , int ) or timestamp < 0 :
204
+ raise ValueError ("timestamp is required and must be a non-negative integer" )
205
+
206
+ url = json .get ("url" )
207
+ if url is None or not isinstance (url , str ):
208
+ raise ValueError ("url is required and must be a string" )
209
+
210
+ suggested_filename = json .get ("suggestedFilename" )
211
+ if suggested_filename is None or not isinstance (suggested_filename , str ):
212
+ raise ValueError ("suggestedFilename is required and must be a string" )
213
+
151
214
return cls (
152
- context = json . get ( " context" ) ,
153
- navigation = json . get ( " navigation" ) ,
154
- timestamp = json . get ( " timestamp" ) ,
155
- url = json . get ( " url" ) ,
156
- suggested_filename = json . get ( "suggestedFilename" ) ,
215
+ context = context ,
216
+ navigation = navigation ,
217
+ timestamp = timestamp ,
218
+ url = url ,
219
+ suggested_filename = suggested_filename ,
157
220
)
158
221
159
222
@@ -186,12 +249,32 @@ def from_json(cls, json: dict) -> "UserPromptOpenedParams":
186
249
-------
187
250
UserPromptOpenedParams: A new instance of UserPromptOpenedParams.
188
251
"""
252
+ context = json .get ("context" )
253
+ if context is None or not isinstance (context , str ):
254
+ raise ValueError ("context is required and must be a string" )
255
+
256
+ handler = json .get ("handler" )
257
+ if handler is None or not isinstance (handler , str ):
258
+ raise ValueError ("handler is required and must be a string" )
259
+
260
+ message = json .get ("message" )
261
+ if message is None or not isinstance (message , str ):
262
+ raise ValueError ("message is required and must be a string" )
263
+
264
+ type_value = json .get ("type" )
265
+ if type_value is None or not isinstance (type_value , str ):
266
+ raise ValueError ("type is required and must be a string" )
267
+
268
+ default_value = json .get ("defaultValue" )
269
+ if default_value is not None and not isinstance (default_value , str ):
270
+ raise ValueError ("defaultValue must be a string if provided" )
271
+
189
272
return cls (
190
- context = json . get ( " context" ) ,
191
- handler = json . get ( " handler" ) ,
192
- message = json . get ( " message" ) ,
193
- type = json . get ( "type" ) ,
194
- default_value = json . get ( "defaultValue" ) ,
273
+ context = context ,
274
+ handler = handler ,
275
+ message = message ,
276
+ type = type_value ,
277
+ default_value = default_value ,
195
278
)
196
279
197
280
@@ -222,11 +305,27 @@ def from_json(cls, json: dict) -> "UserPromptClosedParams":
222
305
-------
223
306
UserPromptClosedParams: A new instance of UserPromptClosedParams.
224
307
"""
308
+ context = json .get ("context" )
309
+ if context is None or not isinstance (context , str ):
310
+ raise ValueError ("context is required and must be a string" )
311
+
312
+ accepted = json .get ("accepted" )
313
+ if accepted is None or not isinstance (accepted , bool ):
314
+ raise ValueError ("accepted is required and must be a boolean" )
315
+
316
+ type_value = json .get ("type" )
317
+ if type_value is None or not isinstance (type_value , str ):
318
+ raise ValueError ("type is required and must be a string" )
319
+
320
+ user_text = json .get ("userText" )
321
+ if user_text is not None and not isinstance (user_text , str ):
322
+ raise ValueError ("userText must be a string if provided" )
323
+
225
324
return cls (
226
- context = json . get ( " context" ) ,
227
- accepted = json . get ( " accepted" ) ,
228
- type = json . get ( "type" ) ,
229
- user_text = json . get ( "userText" ) ,
325
+ context = context ,
326
+ accepted = accepted ,
327
+ type = type_value ,
328
+ user_text = user_text ,
230
329
)
231
330
232
331
@@ -253,9 +352,17 @@ def from_json(cls, json: dict) -> "HistoryUpdatedParams":
253
352
-------
254
353
HistoryUpdatedParams: A new instance of HistoryUpdatedParams.
255
354
"""
355
+ context = json .get ("context" )
356
+ if context is None or not isinstance (context , str ):
357
+ raise ValueError ("context is required and must be a string" )
358
+
359
+ url = json .get ("url" )
360
+ if url is None or not isinstance (url , str ):
361
+ raise ValueError ("url is required and must be a string" )
362
+
256
363
return cls (
257
- context = json . get ( " context" ) ,
258
- url = json . get ( " url" ) ,
364
+ context = context ,
365
+ url = url ,
259
366
)
260
367
261
368
@@ -278,7 +385,11 @@ def from_json(cls, json: dict) -> "BrowsingContextEvent":
278
385
-------
279
386
BrowsingContextEvent: A new instance of BrowsingContextEvent.
280
387
"""
281
- return cls (event_class = json .get ("event_class" ), ** json )
388
+ event_class = json .get ("event_class" )
389
+ if event_class is None or not isinstance (event_class , str ):
390
+ raise ValueError ("event_class is required and must be a string" )
391
+
392
+ return cls (event_class = event_class , ** json )
282
393
283
394
284
395
class BrowsingContext :
@@ -339,7 +450,7 @@ def capture_screenshot(
339
450
-------
340
451
str: The Base64-encoded screenshot.
341
452
"""
342
- params = {"context" : context , "origin" : origin }
453
+ params : dict [ str , Any ] = {"context" : context , "origin" : origin }
343
454
if format is not None :
344
455
params ["format" ] = format
345
456
if clip is not None :
@@ -383,7 +494,7 @@ def create(
383
494
-------
384
495
str: The browsing context ID of the created navigable.
385
496
"""
386
- params = {"type" : type }
497
+ params : dict [ str , Any ] = {"type" : type }
387
498
if reference_context is not None :
388
499
params ["referenceContext" ] = reference_context
389
500
if background is not None :
@@ -411,7 +522,7 @@ def get_tree(
411
522
-------
412
523
List[BrowsingContextInfo]: A list of browsing context information.
413
524
"""
414
- params = {}
525
+ params : dict [ str , Any ] = {}
415
526
if max_depth is not None :
416
527
params ["maxDepth" ] = max_depth
417
528
if root is not None :
@@ -434,7 +545,7 @@ def handle_user_prompt(
434
545
accept: Whether to accept the prompt.
435
546
user_text: The text to enter in the prompt.
436
547
"""
437
- params = {"context" : context }
548
+ params : dict [ str , Any ] = {"context" : context }
438
549
if accept is not None :
439
550
params ["accept" ] = accept
440
551
if user_text is not None :
@@ -464,7 +575,7 @@ def locate_nodes(
464
575
-------
465
576
List[Dict]: A list of nodes.
466
577
"""
467
- params = {"context" : context , "locator" : locator }
578
+ params : dict [ str , Any ] = {"context" : context , "locator" : locator }
468
579
if max_node_count is not None :
469
580
params ["maxNodeCount" ] = max_node_count
470
581
if serialization_options is not None :
@@ -564,7 +675,7 @@ def reload(
564
675
-------
565
676
Dict: A dictionary containing the navigation result.
566
677
"""
567
- params = {"context" : context }
678
+ params : dict [ str , Any ] = {"context" : context }
568
679
if ignore_cache is not None :
569
680
params ["ignoreCache" ] = ignore_cache
570
681
if wait is not None :
@@ -593,7 +704,7 @@ def set_viewport(
593
704
------
594
705
Exception: If the browsing context is not a top-level traversable.
595
706
"""
596
- params = {}
707
+ params : dict [ str , Any ] = {}
597
708
if context is not None :
598
709
params ["context" ] = context
599
710
if viewport is not None :
@@ -621,7 +732,7 @@ def traverse_history(self, context: str, delta: int) -> dict:
621
732
result = self .conn .execute (command_builder ("browsingContext.traverseHistory" , params ))
622
733
return result
623
734
624
- def _on_event (self , event_name : str , callback : callable ) -> int :
735
+ def _on_event (self , event_name : str , callback : Callable ) -> int :
625
736
"""Set a callback function to subscribe to a browsing context event.
626
737
627
738
Parameters:
@@ -665,7 +776,7 @@ def _callback(event_data):
665
776
666
777
return callback_id
667
778
668
- def add_event_handler (self , event : str , callback : callable , contexts : Optional [list [str ]] = None ) -> int :
779
+ def add_event_handler (self , event : str , callback : Callable , contexts : Optional [list [str ]] = None ) -> int :
669
780
"""Add an event handler to the browsing context.
670
781
671
782
Parameters:
@@ -710,15 +821,18 @@ def remove_event_handler(self, event: str, callback_id: int) -> None:
710
821
except KeyError :
711
822
raise Exception (f"Event { event } not found" )
712
823
713
- event = BrowsingContextEvent (event_name )
824
+ event_obj = BrowsingContextEvent (event_name )
714
825
715
- self .conn .remove_callback (event , callback_id )
716
- self .subscriptions [event_name ].remove (callback_id )
717
- if len (self .subscriptions [event_name ]) == 0 :
718
- params = {"events" : [event_name ]}
719
- session = Session (self .conn )
720
- self .conn .execute (session .unsubscribe (** params ))
721
- del self .subscriptions [event_name ]
826
+ self .conn .remove_callback (event_obj , callback_id )
827
+ if event_name in self .subscriptions :
828
+ callbacks = self .subscriptions [event_name ]
829
+ if callback_id in callbacks :
830
+ callbacks .remove (callback_id )
831
+ if not callbacks :
832
+ params = {"events" : [event_name ]}
833
+ session = Session (self .conn )
834
+ self .conn .execute (session .unsubscribe (** params ))
835
+ del self .subscriptions [event_name ]
722
836
723
837
def clear_event_handlers (self ) -> None :
724
838
"""Clear all event handlers from the browsing context."""
0 commit comments