PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/components/autofill_assistant/browser/web/selector_observer.h

https://github.com/chromium/chromium
C Header | 342 lines | 229 code | 44 blank | 69 comment | 2 complexity | 6eada551b850b460a6dd6f522255caea MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_SELECTOR_OBSERVER_H_
  5. #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_SELECTOR_OBSERVER_H_
  6. #include <memory>
  7. #include <vector>
  8. #include "base/callback.h"
  9. #include "base/containers/flat_map.h"
  10. #include "base/memory/raw_ptr.h"
  11. #include "base/strings/strcat.h"
  12. #include "base/time/time.h"
  13. #include "base/timer/timer.h"
  14. #include "base/types/strong_alias.h"
  15. #include "components/autofill_assistant/browser/client_status.h"
  16. #include "components/autofill_assistant/browser/devtools/devtools/domains/types_runtime.h"
  17. #include "components/autofill_assistant/browser/devtools/devtools_client.h"
  18. #include "components/autofill_assistant/browser/devtools/message_dispatcher.h"
  19. #include "components/autofill_assistant/browser/service.pb.h"
  20. #include "components/autofill_assistant/browser/user_data.h"
  21. #include "components/autofill_assistant/browser/web/element.h"
  22. #include "components/autofill_assistant/browser/web/web_controller_worker.h"
  23. #include "content/public/browser/web_contents.h"
  24. #include "js_snippets.h"
  25. #include "third_party/abseil-cpp/absl/types/optional.h"
  26. namespace autofill_assistant {
  27. // Class to observe selectors. It received a list of selectors and a callback
  28. // that gets notified every time the match status of a selector changes. If a
  29. // selector matches, the callback also receives an identifier that can be
  30. // used to retrieve the matching DOM nodes the caller is interested in (Using
  31. // GetElementsAndStop).
  32. //
  33. // It works by splitting the Selectors into subsequences of filters separated
  34. // by EnterFrame filters and injecting a JavaScript script into the frames to
  35. // observe (using MutationObserver) the status of this subsequences. When the
  36. // match status or the matching element of the subsequences change, they send an
  37. // to OnHasChanges(). OnHasChanges() then sends the updates to the callback if
  38. // necessary.
  39. //
  40. // TODO(b/205676462): Implement pseudo elements.
  41. class SelectorObserver : public WebControllerWorker {
  42. public:
  43. using SelectorId = base::StrongAlias<class SelectorIdTag, size_t>;
  44. // Used to request selectors to be observed.
  45. struct ObservableSelector {
  46. ObservableSelector(SelectorId selector_id,
  47. const SelectorProto& proto,
  48. bool strict);
  49. ~ObservableSelector();
  50. ObservableSelector(const ObservableSelector&);
  51. // Id to reference this selector in the updates. It's chosen by the callers
  52. // and can be any string as long as it's unique.
  53. SelectorId selector_id;
  54. SelectorProto proto;
  55. // If true, match will fail if more that one element matches the selector.
  56. bool strict;
  57. };
  58. // An update to the match status of a selector.
  59. struct Update {
  60. Update();
  61. ~Update();
  62. Update(const Update&);
  63. // Selector identifier, same as the one in |ObservableSelector|.
  64. SelectorId selector_id;
  65. bool match;
  66. // Identifier for the specific DOM node that matched. This can be used to
  67. // fetch the element later.
  68. int element_id;
  69. };
  70. struct RequestedElement {
  71. RequestedElement(const SelectorId& selector_id, int element_id);
  72. ~RequestedElement();
  73. RequestedElement(const RequestedElement&);
  74. // The id of the selector passed to |ObservableSelector|.
  75. SelectorId selector_id;
  76. // An identifier for a matching DOM node. Callers get it from
  77. // an |Update| and copy it here if they want to fetch the element at the
  78. // end.
  79. int element_id;
  80. };
  81. // Settings to configure the selector observer.
  82. struct Settings {
  83. Settings(const base::TimeDelta& max_wait_time,
  84. const base::TimeDelta& min_check_interval,
  85. const base::TimeDelta& extra_timeout,
  86. const base::TimeDelta& debounce_interval);
  87. ~Settings();
  88. Settings(const Settings&);
  89. // Maximum amount of time it will wait for an element.
  90. const base::TimeDelta max_wait_time;
  91. // Selector checks will run at least this often, even if no DOM changes are
  92. // detected.
  93. const base::TimeDelta min_check_interval;
  94. // Extra wait time before assuming something has failed and giving up.
  95. const base::TimeDelta extra_timeout;
  96. // Wait until no DOM changes are received for this amount of time to check
  97. // the selectors. An interval of 0 effectively disables debouncing.
  98. const base::TimeDelta debounce_interval;
  99. };
  100. using Callback = base::RepeatingCallback<
  101. void(const ClientStatus&, const std::vector<Update>&, SelectorObserver*)>;
  102. // |content::WebContents| and |DevtoolsClient| need to outlive this instance.
  103. // |UserData| needs to exist until Start() is called.
  104. explicit SelectorObserver(const std::vector<ObservableSelector>& selectors,
  105. const Settings& settings,
  106. content::WebContents* web_contents,
  107. DevtoolsClient* devtools_client,
  108. const UserData* user_data,
  109. Callback update_callback);
  110. ~SelectorObserver() override;
  111. // Calls the callbacks when the conditions (that can be tested using
  112. // javascript) match, or after |timeout_ms|. The DevtoolsClient needs to
  113. // outlive this instance.
  114. ClientStatus Start(base::OnceClosure finished_callback);
  115. // Continue watching for changes. Callbacks should call either Continue() or
  116. // GetElementsAndStop().
  117. void Continue();
  118. // Convert |element_ids| to DomObjectFrameStacks.
  119. void GetElementsAndStop(
  120. const std::vector<RequestedElement>& element_ids,
  121. base::OnceCallback<void(
  122. const ClientStatus&,
  123. const base::flat_map<SelectorId, DomObjectFrameStack>&)> callback);
  124. private:
  125. // A DomRoot is a root element of a document or a shadow DOM that we should
  126. // observe. frame_id is the devtools frame id (empty string to use the main
  127. // frame), root_backend_node_id is the backend_node_id of the root element to
  128. // observe. In the case of the main frame or a OOP frame, root_backend_node_id
  129. // can be set to kDomRootUseMainDoc, indicating to observe the document's root
  130. // element.
  131. struct DomRoot : public std::pair<std::string, int> {
  132. constexpr static int kUseMainDoc = -1;
  133. using std::pair<std::string, int>::pair;
  134. const std::string& frame_id() const { return first; }
  135. bool should_use_main_doc() const { return second == kUseMainDoc; }
  136. int root_backend_node_id() const {
  137. DCHECK(second != kUseMainDoc);
  138. return second;
  139. }
  140. };
  141. enum class State {
  142. INITIALIZED = 0,
  143. RUNNING = 1,
  144. FETCHING_ELEMENTS = 2,
  145. TERMINATED = 3,
  146. ERROR_STATE = 4,
  147. };
  148. State state_ = State::INITIALIZED;
  149. const Settings settings_;
  150. base::TimeTicks started_;
  151. std::unique_ptr<base::OneShotTimer> timeout_timer_;
  152. base::flat_map<SelectorId, ObservableSelector> selectors_;
  153. raw_ptr<DevtoolsClient> devtools_client_;
  154. raw_ptr<content::WebContents> web_contents_;
  155. raw_ptr<const UserData> user_data_;
  156. Callback update_callback_;
  157. base::OnceClosure finished_callback_;
  158. base::OnceCallback<void(
  159. const ClientStatus&,
  160. const base::flat_map<SelectorId, DomObjectFrameStack>&)>
  161. get_elements_callback_;
  162. int pending_frame_injects_ = 0;
  163. int pending_get_elements_responses_;
  164. base::flat_map<SelectorId, DomObjectFrameStack> get_elements_response_;
  165. // Selector observer script api object id for each DomRoot
  166. base::flat_map<DomRoot, std::string> script_api_object_ids_;
  167. // Object id's of containing iframes
  168. base::flat_map<DomRoot, std::string> iframe_object_ids_;
  169. // Dom root for each selector's stretch: {selector_id, frame_depth} -> DomRoot
  170. base::flat_map<std::pair<SelectorId, size_t>, DomRoot> dom_roots_;
  171. // How deep is a frame (root = 0). frame_id -> depth
  172. base::flat_map<DomRoot, size_t> frame_depth_;
  173. base::flat_map<DomRoot, int> wait_time_remaining_ms_;
  174. // Stop watching and free held resources.
  175. void Stop();
  176. void OnGetElementsResponse(
  177. const DomRoot&,
  178. const std::vector<RequestedElement>& elements,
  179. const base::flat_map<int, std::string>& object_ids);
  180. void MaybeFinishedGettingElements();
  181. void FailWithError(const ClientStatus&);
  182. template <typename T>
  183. bool FailIfError(const DevtoolsClient::ReplyStatus& status,
  184. const T* result,
  185. const char* file,
  186. int line);
  187. bool GetObjectId(const runtime::RemoteObject* result,
  188. std::string* out,
  189. const char* file,
  190. int line);
  191. void EnterState(State status);
  192. ClientStatus CallSelectorObserverScriptApi(
  193. const DomRoot&,
  194. const std::string& function,
  195. runtime::CallFunctionOnParams::CallFunctionOnParamsBuilder<0>&&
  196. param_builder,
  197. base::OnceCallback<void(const MessageDispatcher::ReplyStatus&,
  198. std::unique_ptr<runtime::CallFunctionOnResult>)>
  199. callback);
  200. // Remove observers from this DomRoot
  201. void TerminateDomRoot(const DomRoot&);
  202. void OnTerminateDone(const MessageDispatcher::ReplyStatus& status,
  203. std::unique_ptr<runtime::CallFunctionOnResult> result);
  204. void InjectFrame(const DomRoot&, const std::string& object_id);
  205. void OnInjectFrame(const DomRoot&,
  206. const MessageDispatcher::ReplyStatus&,
  207. std::unique_ptr<runtime::CallFunctionOnResult>);
  208. void InjectOrAddSelectorsByParent(
  209. const DomRoot& parent,
  210. const std::string& node_object_id,
  211. const std::vector<SelectorId>& selector_ids);
  212. void OnDescribeNodeDone(const DomRoot& parent,
  213. const std::string& parent_object_id,
  214. const std::vector<SelectorId>& selector_ids,
  215. const MessageDispatcher::ReplyStatus&,
  216. std::unique_ptr<dom::DescribeNodeResult> result);
  217. void InjectOrAddSelectorsToDomRoot(
  218. const DomRoot&,
  219. size_t frame_depth,
  220. const std::vector<SelectorId>& selector_ids);
  221. void ResolveObjectIdAndInjectFrame(const DomRoot&, size_t frame_depth);
  222. void InjectOrAddSelectorsToBackendId(
  223. const DomRoot& parent,
  224. int backend_id,
  225. const std::vector<SelectorId>& selector_ids);
  226. void OnGetDocumentElement(const DomRoot&,
  227. const DevtoolsClient::ReplyStatus& status,
  228. std::unique_ptr<runtime::EvaluateResult> result);
  229. void OnResolveNode(const DomRoot&,
  230. const DevtoolsClient::ReplyStatus& status,
  231. std::unique_ptr<dom::ResolveNodeResult> result);
  232. void OnResolveBackendId(const DevtoolsClient::ReplyStatus& reply_status,
  233. std::unique_ptr<dom::ResolveNodeResult> result);
  234. void AddSelectorsToDomRoot(const DomRoot&,
  235. const std::vector<SelectorId>& selector_ids);
  236. void AwaitChanges(const DomRoot&);
  237. // Receives and processes changes form JavacScript. Updates have the format
  238. // [{
  239. // selectorId: string,
  240. // isLeafFrame: bool,
  241. // elementId: int
  242. // }]
  243. // |elementId| is unique within this DomRoot.
  244. // |isLeafFrame| is true if this is the last frame of the selector, i.e., if
  245. // true the referenced element is the element the selector is looking for,
  246. // otherwise it's a frame we need to inject into in order to find the final
  247. // element.
  248. void OnHasChanges(const DomRoot&,
  249. const MessageDispatcher::ReplyStatus&,
  250. std::unique_ptr<runtime::CallFunctionOnResult>);
  251. void OnGetFramesObjectIds(
  252. const DomRoot&,
  253. const base::flat_map<int, std::vector<SelectorId>>& frames_to_inject,
  254. const base::flat_map<int, std::string>& element_object_ids);
  255. void OnGetElements(
  256. const DomRoot&,
  257. const std::map<int, std::vector<std::string>> frames_to_inject,
  258. const MessageDispatcher::ReplyStatus&,
  259. std::unique_ptr<runtime::CallFunctionOnResult>);
  260. void GetElementsByElementId(
  261. const DomRoot&,
  262. const std::vector<int>& element_ids,
  263. base::OnceCallback<void(const base::flat_map<int, std::string>&)>
  264. callback);
  265. void OnGetElementsByElementIdResult(
  266. const DomRoot&,
  267. base::OnceCallback<void(const base::flat_map<int, std::string>&)>
  268. callback,
  269. const MessageDispatcher::ReplyStatus& status,
  270. std::unique_ptr<runtime::CallFunctionOnResult> result);
  271. void CallGetElementsByIdCallback(
  272. base::OnceCallback<void(const base::flat_map<int, std::string>&)>
  273. callback,
  274. const MessageDispatcher::ReplyStatus& status,
  275. std::unique_ptr<runtime::GetPropertiesResult> result);
  276. void OnFrameUnloaded(const DomRoot&);
  277. // Invalidates frames at `frame_depth` and deeper.
  278. void InvalidateDeeperFrames(const SelectorId& selector_id,
  279. size_t frame_depth);
  280. // Invalidates frames after DomRoot, not including DomRoot.
  281. void InvalidateDeeperFrames(const SelectorId& selector_id, const DomRoot&);
  282. void TerminateUnneededDomRoots();
  283. void OnHardTimeout();
  284. void CheckTimeout();
  285. base::TimeDelta MaxTimeRemaining() const;
  286. std::string BuildExpression(const DomRoot&) const;
  287. std::string BuildUpdateExpression(
  288. const DomRoot&,
  289. const std::vector<SelectorId>& selector_ids) const;
  290. static void SerializeSelector(const SelectorProto& selector,
  291. const SelectorId&,
  292. bool strict,
  293. size_t frame_depth,
  294. JsSnippet& snippet);
  295. base::WeakPtrFactory<SelectorObserver> weak_ptr_factory_{this};
  296. };
  297. } // namespace autofill_assistant
  298. #endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_SELECTOR_OBSERVER_H_