1515# specific language governing permissions and limitations
1616# under the License.
1717
18+ from dataclasses import dataclass
19+ from typing import List , Optional
20+
21+ from selenium .webdriver .common .bidi .common import command_builder
22+
1823from .log import LogEntryAdded
1924from .session import Session
2025
2126
27+ class ResultOwnership :
28+ """Represents the possible result ownership types."""
29+
30+ NONE = "none"
31+ ROOT = "root"
32+
33+
34+ class RealmType :
35+ """Represents the possible realm types."""
36+
37+ WINDOW = "window"
38+ DEDICATED_WORKER = "dedicated-worker"
39+ SHARED_WORKER = "shared-worker"
40+ SERVICE_WORKER = "service-worker"
41+ WORKER = "worker"
42+ PAINT_WORKLET = "paint-worklet"
43+ AUDIO_WORKLET = "audio-worklet"
44+ WORKLET = "worklet"
45+
46+
47+ @dataclass
48+ class RealmInfo :
49+ """Represents information about a realm."""
50+
51+ realm : str
52+ origin : str
53+ type : str
54+ context : Optional [str ] = None
55+ sandbox : Optional [str ] = None
56+
57+ @classmethod
58+ def from_json (cls , json : dict ) -> "RealmInfo" :
59+ """Creates a RealmInfo instance from a dictionary.
60+
61+ Parameters:
62+ -----------
63+ json: A dictionary containing the realm information.
64+
65+ Returns:
66+ -------
67+ RealmInfo: A new instance of RealmInfo.
68+ """
69+ return cls (
70+ realm = json .get ("realm" ),
71+ origin = json .get ("origin" ),
72+ type = json .get ("type" ),
73+ context = json .get ("context" ),
74+ sandbox = json .get ("sandbox" ),
75+ )
76+
77+
78+ @dataclass
79+ class Source :
80+ """Represents the source of a script message."""
81+
82+ realm : str
83+ context : Optional [str ] = None
84+
85+ @classmethod
86+ def from_json (cls , json : dict ) -> "Source" :
87+ """Creates a Source instance from a dictionary.
88+
89+ Parameters:
90+ -----------
91+ json: A dictionary containing the source information.
92+
93+ Returns:
94+ -------
95+ Source: A new instance of Source.
96+ """
97+ return cls (
98+ realm = json .get ("realm" ),
99+ context = json .get ("context" ),
100+ )
101+
102+
103+ @dataclass
104+ class EvaluateResult :
105+ """Represents the result of script evaluation."""
106+
107+ realm : str
108+ result : Optional [dict ] = None
109+ exception_details : Optional [dict ] = None
110+
111+ @classmethod
112+ def from_json (cls , json : dict ) -> "EvaluateResult" :
113+ """Creates an EvaluateResult instance from a dictionary.
114+
115+ Parameters:
116+ -----------
117+ json: A dictionary containing the evaluation result.
118+
119+ Returns:
120+ -------
121+ EvaluateResult: A new instance of EvaluateResult.
122+ """
123+ return cls (
124+ realm = json .get ("realm" ),
125+ result = json .get ("result" ),
126+ exception_details = json .get ("exceptionDetails" ),
127+ )
128+
129+
130+ class ScriptMessage :
131+ """Represents a script message event."""
132+
133+ event_class = "script.message"
134+
135+ def __init__ (self , channel : str , data : dict , source : Source ):
136+ self .channel = channel
137+ self .data = data
138+ self .source = source
139+
140+ @classmethod
141+ def from_json (cls , json : dict ) -> "ScriptMessage" :
142+ """Creates a ScriptMessage instance from a dictionary.
143+
144+ Parameters:
145+ -----------
146+ json: A dictionary containing the script message.
147+
148+ Returns:
149+ -------
150+ ScriptMessage: A new instance of ScriptMessage.
151+ """
152+ return cls (
153+ channel = json .get ("channel" ),
154+ data = json .get ("data" ),
155+ source = Source .from_json (json .get ("source" , {})),
156+ )
157+
158+
159+ class RealmCreated :
160+ """Represents a realm created event."""
161+
162+ event_class = "script.realmCreated"
163+
164+ def __init__ (self , realm_info : RealmInfo ):
165+ self .realm_info = realm_info
166+
167+ @classmethod
168+ def from_json (cls , json : dict ) -> "RealmCreated" :
169+ """Creates a RealmCreated instance from a dictionary.
170+
171+ Parameters:
172+ -----------
173+ json: A dictionary containing the realm created event.
174+
175+ Returns:
176+ -------
177+ RealmCreated: A new instance of RealmCreated.
178+ """
179+ return cls (realm_info = RealmInfo .from_json (json ))
180+
181+
182+ class RealmDestroyed :
183+ """Represents a realm destroyed event."""
184+
185+ event_class = "script.realmDestroyed"
186+
187+ def __init__ (self , realm : str ):
188+ self .realm = realm
189+
190+ @classmethod
191+ def from_json (cls , json : dict ) -> "RealmDestroyed" :
192+ """Creates a RealmDestroyed instance from a dictionary.
193+
194+ Parameters:
195+ -----------
196+ json: A dictionary containing the realm destroyed event.
197+
198+ Returns:
199+ -------
200+ RealmDestroyed: A new instance of RealmDestroyed.
201+ """
202+ return cls (realm = json .get ("realm" ))
203+
204+
22205class Script :
206+ """BiDi implementation of the script module."""
207+
208+ EVENTS = {
209+ "message" : "script.message" ,
210+ "realm_created" : "script.realmCreated" ,
211+ "realm_destroyed" : "script.realmDestroyed" ,
212+ }
213+
23214 def __init__ (self , conn ):
24215 self .conn = conn
25216 self .log_entry_subscribed = False
217+ self .subscriptions = {}
218+ self .callbacks = {}
26219
27220 def add_console_message_handler (self , handler ):
28221 self ._subscribe_to_log_entries ()
@@ -38,6 +231,185 @@ def remove_console_message_handler(self, id):
38231
39232 remove_javascript_error_handler = remove_console_message_handler
40233
234+ def add_preload_script (
235+ self ,
236+ function_declaration : str ,
237+ arguments : Optional [List [dict ]] = None ,
238+ contexts : Optional [List [str ]] = None ,
239+ user_contexts : Optional [List [str ]] = None ,
240+ sandbox : Optional [str ] = None ,
241+ ) -> str :
242+ """Adds a preload script.
243+
244+ Parameters:
245+ -----------
246+ function_declaration: The function declaration to preload.
247+ arguments: The arguments to pass to the function.
248+ contexts: The browsing context IDs to apply the script to.
249+ user_contexts: The user context IDs to apply the script to.
250+ sandbox: The sandbox name to apply the script to.
251+
252+ Returns:
253+ -------
254+ str: The preload script ID.
255+
256+ Raises:
257+ ------
258+ ValueError: If both contexts and user_contexts are provided.
259+ """
260+ if contexts is not None and user_contexts is not None :
261+ raise ValueError ("Cannot specify both contexts and user_contexts" )
262+
263+ params = {"functionDeclaration" : function_declaration }
264+
265+ if arguments is not None :
266+ params ["arguments" ] = arguments
267+ if contexts is not None :
268+ params ["contexts" ] = contexts
269+ if user_contexts is not None :
270+ params ["userContexts" ] = user_contexts
271+ if sandbox is not None :
272+ params ["sandbox" ] = sandbox
273+
274+ result = self .conn .execute (command_builder ("script.addPreloadScript" , params ))
275+ return result ["script" ]
276+
277+ def remove_preload_script (self , script_id : str ) -> None :
278+ """Removes a preload script.
279+
280+ Parameters:
281+ -----------
282+ script_id: The preload script ID to remove.
283+ """
284+ params = {"script" : script_id }
285+ self .conn .execute (command_builder ("script.removePreloadScript" , params ))
286+
287+ def disown (self , handles : List [str ], target : dict ) -> None :
288+ """Disowns the given handles.
289+
290+ Parameters:
291+ -----------
292+ handles: The handles to disown.
293+ target: The target realm or context.
294+ """
295+ params = {
296+ "handles" : handles ,
297+ "target" : target ,
298+ }
299+ self .conn .execute (command_builder ("script.disown" , params ))
300+
301+ def call_function (
302+ self ,
303+ function_declaration : str ,
304+ await_promise : bool ,
305+ target : dict ,
306+ arguments : Optional [List [dict ]] = None ,
307+ result_ownership : Optional [str ] = None ,
308+ serialization_options : Optional [dict ] = None ,
309+ this : Optional [dict ] = None ,
310+ user_activation : bool = False ,
311+ ) -> EvaluateResult :
312+ """Calls a provided function with given arguments in a given realm.
313+
314+ Parameters:
315+ -----------
316+ function_declaration: The function declaration to call.
317+ await_promise: Whether to await promise resolution.
318+ target: The target realm or context.
319+ arguments: The arguments to pass to the function.
320+ result_ownership: The result ownership type.
321+ serialization_options: The serialization options.
322+ this: The 'this' value for the function call.
323+ user_activation: Whether to trigger user activation.
324+
325+ Returns:
326+ -------
327+ EvaluateResult: The result of the function call.
328+ """
329+ params = {
330+ "functionDeclaration" : function_declaration ,
331+ "awaitPromise" : await_promise ,
332+ "target" : target ,
333+ "userActivation" : user_activation ,
334+ }
335+
336+ if arguments is not None :
337+ params ["arguments" ] = arguments
338+ if result_ownership is not None :
339+ params ["resultOwnership" ] = result_ownership
340+ if serialization_options is not None :
341+ params ["serializationOptions" ] = serialization_options
342+ if this is not None :
343+ params ["this" ] = this
344+
345+ result = self .conn .execute (command_builder ("script.callFunction" , params ))
346+ return EvaluateResult .from_json (result )
347+
348+ def evaluate (
349+ self ,
350+ expression : str ,
351+ target : dict ,
352+ await_promise : bool ,
353+ result_ownership : Optional [str ] = None ,
354+ serialization_options : Optional [dict ] = None ,
355+ user_activation : bool = False ,
356+ ) -> EvaluateResult :
357+ """Evaluates a provided script in a given realm.
358+
359+ Parameters:
360+ -----------
361+ expression: The script expression to evaluate.
362+ target: The target realm or context.
363+ await_promise: Whether to await promise resolution.
364+ result_ownership: The result ownership type.
365+ serialization_options: The serialization options.
366+ user_activation: Whether to trigger user activation.
367+
368+ Returns:
369+ -------
370+ EvaluateResult: The result of the script evaluation.
371+ """
372+ params = {
373+ "expression" : expression ,
374+ "target" : target ,
375+ "awaitPromise" : await_promise ,
376+ "userActivation" : user_activation ,
377+ }
378+
379+ if result_ownership is not None :
380+ params ["resultOwnership" ] = result_ownership
381+ if serialization_options is not None :
382+ params ["serializationOptions" ] = serialization_options
383+
384+ result = self .conn .execute (command_builder ("script.evaluate" , params ))
385+ return EvaluateResult .from_json (result )
386+
387+ def get_realms (
388+ self ,
389+ context : Optional [str ] = None ,
390+ type : Optional [str ] = None ,
391+ ) -> List [RealmInfo ]:
392+ """Returns a list of all realms, optionally filtered.
393+
394+ Parameters:
395+ -----------
396+ context: The browsing context ID to filter by.
397+ type: The realm type to filter by.
398+
399+ Returns:
400+ -------
401+ List[RealmInfo]: A list of realm information.
402+ """
403+ params = {}
404+
405+ if context is not None :
406+ params ["context" ] = context
407+ if type is not None :
408+ params ["type" ] = type
409+
410+ result = self .conn .execute (command_builder ("script.getRealms" , params ))
411+ return [RealmInfo .from_json (realm ) for realm in result ["realms" ]]
412+
41413 def _subscribe_to_log_entries (self ):
42414 if not self .log_entry_subscribed :
43415 session = Session (self .conn )
0 commit comments