|
12 | 12 | # See the License for the specific language governing permissions and |
13 | 13 | # limitations under the License. |
14 | 14 |
|
15 | | -import base64 |
16 | | -import collections.abc |
17 | | -import datetime |
18 | | -import math |
19 | | -import re |
20 | | -import struct |
21 | | -import traceback |
| 15 | +# Upstream: packages/playwright-core/src/client/jsHandle.ts. |
| 16 | + |
22 | 17 | from pathlib import Path |
23 | | -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Pattern, Union |
24 | | -from urllib.parse import ParseResult, urlparse, urlunparse |
| 18 | +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union |
25 | 19 |
|
26 | 20 | from playwright._impl._connection import Channel, ChannelOwner, from_channel |
27 | | -from playwright._impl._errors import Error, is_target_closed_error |
28 | | -from playwright._impl._map import Map |
29 | | -from playwright._impl._str_utils import escape_regex_flags, parse_regex_flags |
| 21 | +from playwright._impl._errors import is_target_closed_error |
| 22 | +from playwright._impl._serializers import parse_value, serialize_value |
30 | 23 |
|
31 | 24 | if TYPE_CHECKING: # pragma: no cover |
32 | 25 | from playwright._impl._element_handle import ElementHandle |
|
35 | 28 | Serializable = Any |
36 | 29 |
|
37 | 30 |
|
38 | | -class VisitorInfo: |
39 | | - visited: Map[Any, int] |
40 | | - last_id: int |
41 | | - |
42 | | - def __init__(self) -> None: |
43 | | - self.visited = Map() |
44 | | - self.last_id = 0 |
45 | | - |
46 | | - def visit(self, obj: Any) -> int: |
47 | | - assert obj not in self.visited |
48 | | - self.last_id += 1 |
49 | | - self.visited[obj] = self.last_id |
50 | | - return self.last_id |
51 | | - |
52 | | - |
53 | 31 | class JSHandle(ChannelOwner): |
54 | 32 | def __init__( |
55 | 33 | self, parent: ChannelOwner, type: str, guid: str, initializer: Dict |
@@ -131,214 +109,17 @@ async def json_value(self) -> Any: |
131 | 109 | ) |
132 | 110 |
|
133 | 111 |
|
134 | | -def serialize_value( |
135 | | - value: Any, handles: List[Channel], visitor_info: Optional[VisitorInfo] = None |
136 | | -) -> Any: |
137 | | - if visitor_info is None: |
138 | | - visitor_info = VisitorInfo() |
139 | | - if isinstance(value, JSHandle): |
140 | | - h = len(handles) |
141 | | - handles.append(value._channel) |
142 | | - return dict(h=h) |
143 | | - if value is None: |
144 | | - return dict(v="null") |
145 | | - if isinstance(value, float): |
146 | | - if value == float("inf"): |
147 | | - return dict(v="Infinity") |
148 | | - if value == float("-inf"): |
149 | | - return dict(v="-Infinity") |
150 | | - if value == 0 and math.copysign(1.0, value) == -1.0: |
151 | | - return dict(v="-0") |
152 | | - if math.isnan(value): |
153 | | - return dict(v="NaN") |
154 | | - if isinstance(value, datetime.datetime): |
155 | | - # Node.js Date objects are always in UTC. |
156 | | - return { |
157 | | - "d": datetime.datetime.strftime( |
158 | | - value.astimezone(datetime.timezone.utc), "%Y-%m-%dT%H:%M:%S.%fZ" |
159 | | - ) |
160 | | - } |
161 | | - if isinstance(value, Exception): |
162 | | - return { |
163 | | - "e": { |
164 | | - "m": str(value), |
165 | | - "n": ( |
166 | | - (value.name or "") |
167 | | - if isinstance(value, Error) |
168 | | - else value.__class__.__name__ |
169 | | - ), |
170 | | - "s": ( |
171 | | - (value.stack or "") |
172 | | - if isinstance(value, Error) |
173 | | - else "".join( |
174 | | - traceback.format_exception(type(value), value=value, tb=None) |
175 | | - ) |
176 | | - ), |
177 | | - } |
178 | | - } |
179 | | - if isinstance(value, bool): |
180 | | - return {"b": value} |
181 | | - if isinstance(value, (int, float)): |
182 | | - return {"n": value} |
183 | | - if isinstance(value, str): |
184 | | - return {"s": value} |
185 | | - if isinstance(value, ParseResult): |
186 | | - return {"u": urlunparse(value)} |
187 | | - if isinstance(value, Pattern): |
188 | | - if not isinstance(value.pattern, str): |
189 | | - raise Error("Only str patterns are supported.") |
190 | | - return {"r": {"p": value.pattern, "f": escape_regex_flags(value)}} |
191 | | - |
192 | | - if value in visitor_info.visited: |
193 | | - return dict(ref=visitor_info.visited[value]) |
194 | | - |
195 | | - if isinstance(value, collections.abc.Sequence) and not isinstance(value, str): |
196 | | - id = visitor_info.visit(value) |
197 | | - a = [] |
198 | | - for e in value: |
199 | | - a.append(serialize_value(e, handles, visitor_info)) |
200 | | - return dict(a=a, id=id) |
201 | | - |
202 | | - if isinstance(value, dict): |
203 | | - id = visitor_info.visit(value) |
204 | | - o = [] |
205 | | - for name in value: |
206 | | - o.append( |
207 | | - {"k": name, "v": serialize_value(value[name], handles, visitor_info)} |
208 | | - ) |
209 | | - return dict(o=o, id=id) |
210 | | - return dict(v="undefined") |
211 | | - |
212 | | - |
213 | 112 | def serialize_argument(arg: Serializable = None) -> Any: |
214 | 113 | handles: List[Channel] = [] |
215 | | - value = serialize_value(arg, handles) |
216 | | - return dict(value=value, handles=handles) |
217 | | - |
218 | 114 |
|
219 | | -def parse_value(value: Any, refs: Optional[Dict[int, Any]] = None) -> Any: |
220 | | - if refs is None: |
221 | | - refs = {} |
222 | | - if value is None: |
| 115 | + def serialize_handle(value: Any) -> Optional[Dict[str, Any]]: |
| 116 | + if isinstance(value, JSHandle): |
| 117 | + handles.append(value._channel) |
| 118 | + return {"h": len(handles) - 1} |
223 | 119 | return None |
224 | | - if isinstance(value, dict): |
225 | | - if "ref" in value: |
226 | | - return refs[value["ref"]] |
227 | | - |
228 | | - if "v" in value: |
229 | | - v = value["v"] |
230 | | - if v == "Infinity": |
231 | | - return float("inf") |
232 | | - if v == "-Infinity": |
233 | | - return float("-inf") |
234 | | - if v == "-0": |
235 | | - return float("-0") |
236 | | - if v == "NaN": |
237 | | - return float("nan") |
238 | | - if v == "undefined": |
239 | | - return None |
240 | | - if v == "null": |
241 | | - return None |
242 | | - return v |
243 | | - |
244 | | - if "u" in value: |
245 | | - return urlparse(value["u"]) |
246 | | - |
247 | | - if "bi" in value: |
248 | | - return int(value["bi"]) |
249 | | - |
250 | | - if "e" in value: |
251 | | - error = Error(value["e"]["m"]) |
252 | | - error._name = value["e"]["n"] |
253 | | - error._stack = value["e"]["s"] |
254 | | - return error |
255 | | - |
256 | | - if "r" in value: |
257 | | - try: |
258 | | - return re.compile(value["r"]["p"], parse_regex_flags(value["r"]["f"])) |
259 | | - except re.error: |
260 | | - # JavaScript-only syntax, e.g. /(?<name>x)/, has no re equivalent. |
261 | | - return value |
262 | | - |
263 | | - if "a" in value: |
264 | | - a: List = [] |
265 | | - refs[value["id"]] = a |
266 | | - for e in value["a"]: |
267 | | - a.append(parse_value(e, refs)) |
268 | | - return a |
269 | 120 |
|
270 | | - if "d" in value: |
271 | | - # Node.js Date objects are always in UTC. |
272 | | - return datetime.datetime.strptime( |
273 | | - value["d"], "%Y-%m-%dT%H:%M:%S.%fZ" |
274 | | - ).replace(tzinfo=datetime.timezone.utc) |
275 | | - |
276 | | - if "o" in value: |
277 | | - o: Dict = {} |
278 | | - refs[value["id"]] = o |
279 | | - for e in value["o"]: |
280 | | - o[e["k"]] = parse_value(e["v"], refs) |
281 | | - return o |
282 | | - |
283 | | - if "n" in value: |
284 | | - return value["n"] |
285 | | - |
286 | | - if "s" in value: |
287 | | - return value["s"] |
288 | | - |
289 | | - if "b" in value: |
290 | | - return value["b"] |
291 | | - |
292 | | - if "ta" in value: |
293 | | - encoded_bytes = value["ta"]["b"] |
294 | | - decoded_bytes = base64.b64decode(encoded_bytes) |
295 | | - array_type = value["ta"]["k"] |
296 | | - if array_type == "i8": |
297 | | - word_size = 1 |
298 | | - fmt = "b" |
299 | | - elif array_type == "ui8" or array_type == "ui8c": |
300 | | - word_size = 1 |
301 | | - fmt = "B" |
302 | | - elif array_type == "i16": |
303 | | - word_size = 2 |
304 | | - fmt = "h" |
305 | | - elif array_type == "ui16": |
306 | | - word_size = 2 |
307 | | - fmt = "H" |
308 | | - elif array_type == "i32": |
309 | | - word_size = 4 |
310 | | - fmt = "i" |
311 | | - elif array_type == "ui32": |
312 | | - word_size = 4 |
313 | | - fmt = "I" |
314 | | - elif array_type == "f32": |
315 | | - word_size = 4 |
316 | | - fmt = "f" |
317 | | - elif array_type == "f64": |
318 | | - word_size = 8 |
319 | | - fmt = "d" |
320 | | - elif array_type == "bi64": |
321 | | - word_size = 8 |
322 | | - fmt = "q" |
323 | | - elif array_type == "bui64": |
324 | | - word_size = 8 |
325 | | - fmt = "Q" |
326 | | - else: |
327 | | - raise ValueError(f"Unsupported array type: {array_type}") |
328 | | - |
329 | | - byte_len = len(decoded_bytes) |
330 | | - if byte_len % word_size != 0: |
331 | | - raise ValueError( |
332 | | - f"Decoded bytes length {byte_len} is not a multiple of word size {word_size}" |
333 | | - ) |
334 | | - |
335 | | - if byte_len == 0: |
336 | | - return [] |
337 | | - array_len = byte_len // word_size |
338 | | - # "<" denotes little-endian |
339 | | - format_string = f"<{array_len}{fmt}" |
340 | | - return list(struct.unpack(format_string, decoded_bytes)) |
341 | | - return value |
| 121 | + value = serialize_value(arg, serialize_handle) |
| 122 | + return dict(value=value, handles=handles) |
342 | 123 |
|
343 | 124 |
|
344 | 125 | def parse_result(result: Any) -> Any: |
|
0 commit comments