1515# specific language governing permissions and limitations
1616# under the License.
1717
18- from typing import Optional , Union
18+ from typing import Any , Callable , Optional , Union
1919
2020from selenium .webdriver .common .bidi .common import command_builder
2121
@@ -66,12 +66,23 @@ def from_json(cls, json: dict) -> "NavigationInfo":
6666 -------
6767 NavigationInfo: A new instance of NavigationInfo.
6868 """
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 )
7586
7687
7788class BrowsingContextInfo :
@@ -82,10 +93,10 @@ def __init__(
8293 context : str ,
8394 url : str ,
8495 children : Optional [list ["BrowsingContextInfo" ]],
96+ client_window : str ,
97+ user_context : str ,
8598 parent : Optional [str ] = None ,
86- user_context : Optional [str ] = None ,
8799 original_opener : Optional [str ] = None ,
88- client_window : Optional [str ] = None ,
89100 ):
90101 self .context = context
91102 self .url = url
@@ -108,17 +119,49 @@ def from_json(cls, json: dict) -> "BrowsingContextInfo":
108119 BrowsingContextInfo: A new instance of BrowsingContextInfo.
109120 """
110121 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" )
113156
114157 return cls (
115- context = json . get ( " context" ) ,
116- url = json . get ( " url" ) ,
158+ context = context ,
159+ url = url ,
117160 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 ,
122165 )
123166
124167
@@ -148,12 +191,32 @@ def from_json(cls, json: dict) -> "DownloadWillBeginParams":
148191 -------
149192 DownloadWillBeginParams: A new instance of DownloadWillBeginParams.
150193 """
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+
151214 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 ,
157220 )
158221
159222
@@ -186,12 +249,32 @@ def from_json(cls, json: dict) -> "UserPromptOpenedParams":
186249 -------
187250 UserPromptOpenedParams: A new instance of UserPromptOpenedParams.
188251 """
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+
189272 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 ,
195278 )
196279
197280
@@ -222,11 +305,27 @@ def from_json(cls, json: dict) -> "UserPromptClosedParams":
222305 -------
223306 UserPromptClosedParams: A new instance of UserPromptClosedParams.
224307 """
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+
225324 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 ,
230329 )
231330
232331
@@ -253,9 +352,17 @@ def from_json(cls, json: dict) -> "HistoryUpdatedParams":
253352 -------
254353 HistoryUpdatedParams: A new instance of HistoryUpdatedParams.
255354 """
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+
256363 return cls (
257- context = json . get ( " context" ) ,
258- url = json . get ( " url" ) ,
364+ context = context ,
365+ url = url ,
259366 )
260367
261368
@@ -278,7 +385,11 @@ def from_json(cls, json: dict) -> "BrowsingContextEvent":
278385 -------
279386 BrowsingContextEvent: A new instance of BrowsingContextEvent.
280387 """
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 )
282393
283394
284395class BrowsingContext :
@@ -339,7 +450,7 @@ def capture_screenshot(
339450 -------
340451 str: The Base64-encoded screenshot.
341452 """
342- params = {"context" : context , "origin" : origin }
453+ params : dict [ str , Any ] = {"context" : context , "origin" : origin }
343454 if format is not None :
344455 params ["format" ] = format
345456 if clip is not None :
@@ -383,7 +494,7 @@ def create(
383494 -------
384495 str: The browsing context ID of the created navigable.
385496 """
386- params = {"type" : type }
497+ params : dict [ str , Any ] = {"type" : type }
387498 if reference_context is not None :
388499 params ["referenceContext" ] = reference_context
389500 if background is not None :
@@ -411,7 +522,7 @@ def get_tree(
411522 -------
412523 List[BrowsingContextInfo]: A list of browsing context information.
413524 """
414- params = {}
525+ params : dict [ str , Any ] = {}
415526 if max_depth is not None :
416527 params ["maxDepth" ] = max_depth
417528 if root is not None :
@@ -434,7 +545,7 @@ def handle_user_prompt(
434545 accept: Whether to accept the prompt.
435546 user_text: The text to enter in the prompt.
436547 """
437- params = {"context" : context }
548+ params : dict [ str , Any ] = {"context" : context }
438549 if accept is not None :
439550 params ["accept" ] = accept
440551 if user_text is not None :
@@ -464,7 +575,7 @@ def locate_nodes(
464575 -------
465576 List[Dict]: A list of nodes.
466577 """
467- params = {"context" : context , "locator" : locator }
578+ params : dict [ str , Any ] = {"context" : context , "locator" : locator }
468579 if max_node_count is not None :
469580 params ["maxNodeCount" ] = max_node_count
470581 if serialization_options is not None :
@@ -564,7 +675,7 @@ def reload(
564675 -------
565676 Dict: A dictionary containing the navigation result.
566677 """
567- params = {"context" : context }
678+ params : dict [ str , Any ] = {"context" : context }
568679 if ignore_cache is not None :
569680 params ["ignoreCache" ] = ignore_cache
570681 if wait is not None :
@@ -593,7 +704,7 @@ def set_viewport(
593704 ------
594705 Exception: If the browsing context is not a top-level traversable.
595706 """
596- params = {}
707+ params : dict [ str , Any ] = {}
597708 if context is not None :
598709 params ["context" ] = context
599710 if viewport is not None :
@@ -621,7 +732,7 @@ def traverse_history(self, context: str, delta: int) -> dict:
621732 result = self .conn .execute (command_builder ("browsingContext.traverseHistory" , params ))
622733 return result
623734
624- def _on_event (self , event_name : str , callback : callable ) -> int :
735+ def _on_event (self , event_name : str , callback : Callable ) -> int :
625736 """Set a callback function to subscribe to a browsing context event.
626737
627738 Parameters:
@@ -665,7 +776,7 @@ def _callback(event_data):
665776
666777 return callback_id
667778
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 :
669780 """Add an event handler to the browsing context.
670781
671782 Parameters:
@@ -710,15 +821,18 @@ def remove_event_handler(self, event: str, callback_id: int) -> None:
710821 except KeyError :
711822 raise Exception (f"Event { event } not found" )
712823
713- event = BrowsingContextEvent (event_name )
824+ event_obj = BrowsingContextEvent (event_name )
714825
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 ]
722836
723837 def clear_event_handlers (self ) -> None :
724838 """Clear all event handlers from the browsing context."""
0 commit comments