_js_handle.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. # Copyright (c) Microsoft Corporation.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import base64
  15. import collections.abc
  16. import datetime
  17. import math
  18. import struct
  19. import traceback
  20. from pathlib import Path
  21. from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
  22. from urllib.parse import ParseResult, urlparse, urlunparse
  23. from playwright._impl._connection import Channel, ChannelOwner, from_channel
  24. from playwright._impl._errors import Error, is_target_closed_error
  25. from playwright._impl._map import Map
  26. if TYPE_CHECKING: # pragma: no cover
  27. from playwright._impl._element_handle import ElementHandle
  28. Serializable = Any
  29. class VisitorInfo:
  30. visited: Map[Any, int]
  31. last_id: int
  32. def __init__(self) -> None:
  33. self.visited = Map()
  34. self.last_id = 0
  35. def visit(self, obj: Any) -> int:
  36. assert obj not in self.visited
  37. self.last_id += 1
  38. self.visited[obj] = self.last_id
  39. return self.last_id
  40. class JSHandle(ChannelOwner):
  41. def __init__(
  42. self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
  43. ) -> None:
  44. super().__init__(parent, type, guid, initializer)
  45. self._preview = self._initializer["preview"]
  46. self._channel.on(
  47. "previewUpdated", lambda params: self._on_preview_updated(params["preview"])
  48. )
  49. def __repr__(self) -> str:
  50. return f"<JSHandle preview={self._preview}>"
  51. def __str__(self) -> str:
  52. return self._preview
  53. def _on_preview_updated(self, preview: str) -> None:
  54. self._preview = preview
  55. async def evaluate(self, expression: str, arg: Serializable = None) -> Any:
  56. return parse_result(
  57. await self._channel.send(
  58. "evaluateExpression",
  59. None,
  60. dict(
  61. expression=expression,
  62. arg=serialize_argument(arg),
  63. ),
  64. )
  65. )
  66. async def evaluate_handle(
  67. self, expression: str, arg: Serializable = None
  68. ) -> "JSHandle":
  69. return from_channel(
  70. await self._channel.send(
  71. "evaluateExpressionHandle",
  72. None,
  73. dict(
  74. expression=expression,
  75. arg=serialize_argument(arg),
  76. ),
  77. )
  78. )
  79. async def get_property(self, propertyName: str) -> "JSHandle":
  80. return from_channel(
  81. await self._channel.send("getProperty", None, dict(name=propertyName))
  82. )
  83. async def get_properties(self) -> Dict[str, "JSHandle"]:
  84. return {
  85. prop["name"]: from_channel(prop["value"])
  86. for prop in await self._channel.send(
  87. "getPropertyList",
  88. None,
  89. )
  90. }
  91. def as_element(self) -> Optional["ElementHandle"]:
  92. return None
  93. async def dispose(self) -> None:
  94. try:
  95. await self._channel.send(
  96. "dispose",
  97. None,
  98. )
  99. except Exception as e:
  100. if not is_target_closed_error(e):
  101. raise e
  102. async def json_value(self) -> Any:
  103. return parse_result(
  104. await self._channel.send(
  105. "jsonValue",
  106. None,
  107. )
  108. )
  109. def serialize_value(
  110. value: Any, handles: List[Channel], visitor_info: Optional[VisitorInfo] = None
  111. ) -> Any:
  112. if visitor_info is None:
  113. visitor_info = VisitorInfo()
  114. if isinstance(value, JSHandle):
  115. h = len(handles)
  116. handles.append(value._channel)
  117. return dict(h=h)
  118. if value is None:
  119. return dict(v="null")
  120. if isinstance(value, float):
  121. if value == float("inf"):
  122. return dict(v="Infinity")
  123. if value == float("-inf"):
  124. return dict(v="-Infinity")
  125. if value == float("-0"):
  126. return dict(v="-0")
  127. if math.isnan(value):
  128. return dict(v="NaN")
  129. if isinstance(value, datetime.datetime):
  130. # Node.js Date objects are always in UTC.
  131. return {
  132. "d": datetime.datetime.strftime(
  133. value.astimezone(datetime.timezone.utc), "%Y-%m-%dT%H:%M:%S.%fZ"
  134. )
  135. }
  136. if isinstance(value, Exception):
  137. return {
  138. "e": {
  139. "m": str(value),
  140. "n": (
  141. (value.name or "")
  142. if isinstance(value, Error)
  143. else value.__class__.__name__
  144. ),
  145. "s": (
  146. (value.stack or "")
  147. if isinstance(value, Error)
  148. else "".join(
  149. traceback.format_exception(type(value), value=value, tb=None)
  150. )
  151. ),
  152. }
  153. }
  154. if isinstance(value, bool):
  155. return {"b": value}
  156. if isinstance(value, (int, float)):
  157. return {"n": value}
  158. if isinstance(value, str):
  159. return {"s": value}
  160. if isinstance(value, ParseResult):
  161. return {"u": urlunparse(value)}
  162. if value in visitor_info.visited:
  163. return dict(ref=visitor_info.visited[value])
  164. if isinstance(value, collections.abc.Sequence) and not isinstance(value, str):
  165. id = visitor_info.visit(value)
  166. a = []
  167. for e in value:
  168. a.append(serialize_value(e, handles, visitor_info))
  169. return dict(a=a, id=id)
  170. if isinstance(value, dict):
  171. id = visitor_info.visit(value)
  172. o = []
  173. for name in value:
  174. o.append(
  175. {"k": name, "v": serialize_value(value[name], handles, visitor_info)}
  176. )
  177. return dict(o=o, id=id)
  178. return dict(v="undefined")
  179. def serialize_argument(arg: Serializable = None) -> Any:
  180. handles: List[Channel] = []
  181. value = serialize_value(arg, handles)
  182. return dict(value=value, handles=handles)
  183. def parse_value(value: Any, refs: Optional[Dict[int, Any]] = None) -> Any:
  184. if refs is None:
  185. refs = {}
  186. if value is None:
  187. return None
  188. if isinstance(value, dict):
  189. if "ref" in value:
  190. return refs[value["ref"]]
  191. if "v" in value:
  192. v = value["v"]
  193. if v == "Infinity":
  194. return float("inf")
  195. if v == "-Infinity":
  196. return float("-inf")
  197. if v == "-0":
  198. return float("-0")
  199. if v == "NaN":
  200. return float("nan")
  201. if v == "undefined":
  202. return None
  203. if v == "null":
  204. return None
  205. return v
  206. if "u" in value:
  207. return urlparse(value["u"])
  208. if "bi" in value:
  209. return int(value["bi"])
  210. if "e" in value:
  211. error = Error(value["e"]["m"])
  212. error._name = value["e"]["n"]
  213. error._stack = value["e"]["s"]
  214. return error
  215. if "a" in value:
  216. a: List = []
  217. refs[value["id"]] = a
  218. for e in value["a"]:
  219. a.append(parse_value(e, refs))
  220. return a
  221. if "d" in value:
  222. # Node.js Date objects are always in UTC.
  223. return datetime.datetime.strptime(
  224. value["d"], "%Y-%m-%dT%H:%M:%S.%fZ"
  225. ).replace(tzinfo=datetime.timezone.utc)
  226. if "o" in value:
  227. o: Dict = {}
  228. refs[value["id"]] = o
  229. for e in value["o"]:
  230. o[e["k"]] = parse_value(e["v"], refs)
  231. return o
  232. if "n" in value:
  233. return value["n"]
  234. if "s" in value:
  235. return value["s"]
  236. if "b" in value:
  237. return value["b"]
  238. if "ta" in value:
  239. encoded_bytes = value["ta"]["b"]
  240. decoded_bytes = base64.b64decode(encoded_bytes)
  241. array_type = value["ta"]["k"]
  242. if array_type == "i8":
  243. word_size = 1
  244. fmt = "b"
  245. elif array_type == "ui8" or array_type == "ui8c":
  246. word_size = 1
  247. fmt = "B"
  248. elif array_type == "i16":
  249. word_size = 2
  250. fmt = "h"
  251. elif array_type == "ui16":
  252. word_size = 2
  253. fmt = "H"
  254. elif array_type == "i32":
  255. word_size = 4
  256. fmt = "i"
  257. elif array_type == "ui32":
  258. word_size = 4
  259. fmt = "I"
  260. elif array_type == "f32":
  261. word_size = 4
  262. fmt = "f"
  263. elif array_type == "f64":
  264. word_size = 8
  265. fmt = "d"
  266. elif array_type == "bi64":
  267. word_size = 8
  268. fmt = "q"
  269. elif array_type == "bui64":
  270. word_size = 8
  271. fmt = "Q"
  272. else:
  273. raise ValueError(f"Unsupported array type: {array_type}")
  274. byte_len = len(decoded_bytes)
  275. if byte_len % word_size != 0:
  276. raise ValueError(
  277. f"Decoded bytes length {byte_len} is not a multiple of word size {word_size}"
  278. )
  279. if byte_len == 0:
  280. return []
  281. array_len = byte_len // word_size
  282. # "<" denotes little-endian
  283. format_string = f"<{array_len}{fmt}"
  284. return list(struct.unpack(format_string, decoded_bytes))
  285. return value
  286. def parse_result(result: Any) -> Any:
  287. return parse_value(result)
  288. def add_source_url_to_script(source: str, path: Union[str, Path]) -> str:
  289. return source + "\n//# sourceURL=" + str(path).replace("\n", "")