1 // Copyright 2018 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 
5 #include "components/autofill_assistant/browser/web/web_controller.h"
6 
7 #include <math.h>
8 #include <algorithm>
9 #include <ctime>
10 #include <utility>
11 
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/callback.h"
15 #include "base/logging.h"
16 #include "base/stl_util.h"
17 #include "base/strings/strcat.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/task/post_task.h"
21 #include "build/build_config.h"
22 #include "components/autofill/content/browser/content_autofill_driver.h"
23 #include "components/autofill/core/browser/autofill_manager.h"
24 #include "components/autofill/core/browser/data_model/credit_card.h"
25 #include "components/autofill/core/common/autofill_constants.h"
26 #include "components/autofill/core/common/form_data.h"
27 #include "components/autofill_assistant/browser/client_settings.h"
28 #include "components/autofill_assistant/browser/client_status.h"
29 #include "components/autofill_assistant/browser/rectf.h"
30 #include "components/autofill_assistant/browser/string_conversions_util.h"
31 #include "components/autofill_assistant/browser/web/web_controller_util.h"
32 #include "content/public/browser/browser_task_traits.h"
33 #include "content/public/browser/browser_thread.h"
34 #include "content/public/browser/render_frame_host.h"
35 #include "content/public/browser/web_contents.h"
36 #include "ui/events/keycodes/dom/dom_key.h"
37 #include "ui/events/keycodes/dom/keycode_converter.h"
38 
39 namespace autofill_assistant {
40 using autofill::ContentAutofillDriver;
41 
42 namespace {
43 
44 const char* const kGetVisualViewport =
45     R"({ const v = window.visualViewport;
46          [v.pageLeft,
47           v.pageTop,
48           v.width,
49           v.height] })";
50 
51 // Scrolls to the specified node with top padding. The top padding can
52 // be specified through pixels or ratio. Pixels take precedence.
53 const char* const kScrollIntoViewWithPaddingScript =
54     R"(function(node, topPaddingPixels, topPaddingRatio) {
55     node.scrollIntoViewIfNeeded();
56     const rect = node.getBoundingClientRect();
57     let topPadding = topPaddingPixels;
58     if (!topPadding){
59       topPadding = window.innerHeight * topPaddingRatio;
60     }
61     window.scrollBy({top: rect.top - topPadding});
62   })";
63 
64 // Scroll the window or any scrollable container as needed for the element to
65 // appear centered. This is in preparation of a click, to improve the chances
66 // for the element to click to be visible.
67 const char* const kScrollIntoViewCenterScript =
68     R"(function(node) {
69     node.scrollIntoView({block: "center", inline: "center"});
70   })";
71 
72 // Javascript to select a value from a select box. Also fires a "change" event
73 // to trigger any listeners. Changing the index directly does not trigger this.
74 // TODO(b/148656337): Remove the need to encode the ENUM values in JS.
75 const char* const kSelectOptionScript =
76     R"(function(value, compareStrategy) {
77       const VALUE_MATCH = 1;
78       const LABEL_MATCH = 2;
79       const LABEL_STARTSWITH = 3;
80       const uppercaseValue = value.toUpperCase();
81       let found = false;
82       for (let i = 0; i < this.options.length; ++i) {
83         const optionValue = this.options[i].value.toUpperCase();
84         const optionLabel = this.options[i].label.toUpperCase();
85         if ((compareStrategy === VALUE_MATCH && optionValue === uppercaseValue)
86               || (compareStrategy === LABEL_MATCH
87                     && optionLabel === uppercaseValue)
88               || (compareStrategy === LABEL_STARTSWITH
89                     && optionLabel.startsWith(uppercaseValue))) {
90           this.options.selectedIndex = i;
91           found = true;
92           break;
93         }
94       }
95       if (!found) {
96         return false;
97       }
98       const e = document.createEvent('HTMLEvents');
99       e.initEvent('change', true, true);
100       this.dispatchEvent(e);
101       return true;
102     })";
103 
104 // Javascript to highlight an element.
105 const char* const kHighlightElementScript =
106     R"(function() {
107       this.style.boxShadow = '0px 0px 0px 3px white, ' +
108           '0px 0px 0px 6px rgb(66, 133, 244)';
109       return true;
110     })";
111 
112 // Javascript code to retrieve the 'value' attribute of a node.
113 const char* const kGetValueAttributeScript =
114     "function () { return this.value; }";
115 
116 // Javascript code to select the current value.
117 const char* const kSelectFieldValue = "function() { this.select(); }";
118 
119 // Javascript code to set the 'value' attribute of a node and then fire a
120 // "change" event to trigger any listeners.
121 const char* const kSetValueAttributeScript =
122     R"(function (value) {
123          this.value = value;
124          const e = document.createEvent('HTMLEvents');
125          e.initEvent('change', true, true);
126          this.dispatchEvent(e);
127        })";
128 
129 // Javascript code to set an attribute of a node to a given value.
130 const char* const kSetAttributeScript =
131     R"(function (attribute, value) {
132          let receiver = this;
133          for (let i = 0; i < attribute.length - 1; i++) {
134            receiver = receiver[attribute[i]];
135          }
136          receiver[attribute[attribute.length - 1]] = value;
137        })";
138 
139 // Javascript code to get the outerHTML of a node.
140 // TODO(crbug.com/806868): Investigate if using DOM.GetOuterHtml would be a
141 // better solution than injecting Javascript code.
142 const char* const kGetOuterHtmlScript =
143     "function () { return this.outerHTML; }";
144 
145 const char* const kGetElementTagScript = "function () { return this.tagName; }";
146 
147 // Javascript code to query whether the document is ready for interact.
148 const char* const kIsDocumentReadyForInteract =
149     R"(function () {
150       return document.readyState == 'interactive'
151           || document.readyState == 'complete';
152     })";
153 
154 // Javascript code to click on an element.
155 const char* const kClickElement =
156     R"(function (selector) {
157       selector.click();
158     })";
159 
160 // Javascript code that returns a promise that will succeed once the main
161 // document window has changed height.
162 //
163 // This ignores width changes, to filter out resizes caused by changes to the
164 // screen orientation.
165 const char* const kWaitForWindowHeightChange = R"(
166 new Promise((fulfill, reject) => {
167   var lastWidth = window.innerWidth;
168   var handler = function(event) {
169     if (window.innerWidth != lastWidth) {
170       lastWidth = window.innerWidth;
171       return
172     }
173     window.removeEventListener('resize', handler)
174     fulfill(true)
175   }
176   window.addEventListener('resize', handler)
177 })
178 )";
179 
180 // Converts a int that correspond to the DocumentReadyState enum into an
181 // equivalent quoted Javascript string.
DocumentReadyStateToQuotedJsString(int state)182 std::string DocumentReadyStateToQuotedJsString(int state) {
183   switch (static_cast<DocumentReadyState>(state)) {
184     case DOCUMENT_UNKNOWN_READY_STATE:
185       return "''";
186     case DOCUMENT_UNINITIALIZED:
187       return "'uninitialized'";
188     case DOCUMENT_LOADING:
189       return "'loading'";
190     case DOCUMENT_LOADED:
191       return "'loaded'";
192     case DOCUMENT_INTERACTIVE:
193       return "'interactive'";
194     case DOCUMENT_COMPLETE:
195       return "'complete'";
196 
197       // No default, to get a compilation error if a new enum value is left
198       // unsupported.
199   }
200 
201   // If the enum values aren't sequential, just add empty strings to fill in the
202   // blanks.
203   return "''";
204 }
205 
206 // Appends to |out| the definition of a function that'll wait for a
207 // ready state, expressed as a DocumentReadyState enum value.
AppendWaitForDocumentReadyStateFunction(std::string * out)208 void AppendWaitForDocumentReadyStateFunction(std::string* out) {
209   // quoted_names covers all possible DocumentReadyState values.
210   std::vector<std::string> quoted_names(DOCUMENT_MAX_READY_STATE + 1);
211   for (int i = 0; i <= DOCUMENT_MAX_READY_STATE; i++) {
212     quoted_names[i] = DocumentReadyStateToQuotedJsString(i);
213   }
214   base::StrAppend(out, {R"(function (minReadyStateNum) {
215   return new Promise((fulfill, reject) => {
216     let handler = function(event) {
217       let readyState = document.readyState;
218       let readyStates = [)",
219                         base::JoinString(quoted_names, ", "), R"(];
220       let readyStateNum = readyStates.indexOf(readyState);
221       if (readyStateNum == -1) readyStateNum = 0;
222       if (readyStateNum >= minReadyStateNum) {
223         document.removeEventListener('readystatechange', handler);
224         fulfill(readyStateNum);
225       }
226     }
227     document.addEventListener('readystatechange', handler)
228     handler();
229   })
230 })"});
231 }
232 
233 // Forward the result of WaitForDocumentReadyState to the callback. The same
234 // code work on both EvaluateResult and CallFunctionOnResult.
235 template <typename T>
OnWaitForDocumentReadyState(base::OnceCallback<void (const ClientStatus &,DocumentReadyState)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<T> result)236 void OnWaitForDocumentReadyState(
237     base::OnceCallback<void(const ClientStatus&, DocumentReadyState)> callback,
238     const DevtoolsClient::ReplyStatus& reply_status,
239     std::unique_ptr<T> result) {
240   ClientStatus status =
241       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
242   VLOG_IF(1, !status.ok()) << __func__
243                            << " Failed to get document ready state.";
244   int ready_state;
245   SafeGetIntValue(result->GetResult(), &ready_state);
246   std::move(callback).Run(status, static_cast<DocumentReadyState>(ready_state));
247 }
248 
249 }  // namespace
250 
251 // static
CreateForWebContents(content::WebContents * web_contents,const ClientSettings * settings)252 std::unique_ptr<WebController> WebController::CreateForWebContents(
253     content::WebContents* web_contents,
254     const ClientSettings* settings) {
255   return std::make_unique<WebController>(
256       web_contents,
257       std::make_unique<DevtoolsClient>(
258           content::DevToolsAgentHost::GetOrCreateFor(web_contents)),
259       settings);
260 }
261 
WebController(content::WebContents * web_contents,std::unique_ptr<DevtoolsClient> devtools_client,const ClientSettings * settings)262 WebController::WebController(content::WebContents* web_contents,
263                              std::unique_ptr<DevtoolsClient> devtools_client,
264                              const ClientSettings* settings)
265     : web_contents_(web_contents),
266       devtools_client_(std::move(devtools_client)),
267       settings_(settings) {}
268 
~WebController()269 WebController::~WebController() {}
270 
FillFormInputData()271 WebController::FillFormInputData::FillFormInputData() {}
272 
~FillFormInputData()273 WebController::FillFormInputData::~FillFormInputData() {}
274 
LoadURL(const GURL & url)275 void WebController::LoadURL(const GURL& url) {
276 #ifdef NDEBUG
277   VLOG(3) << __func__ << " <redacted>";
278 #else
279   DVLOG(3) << __func__ << " " << url;
280 #endif
281   web_contents_->GetController().LoadURLWithParams(
282       content::NavigationController::LoadURLParams(url));
283 }
284 
ClickOrTapElement(const Selector & selector,ClickAction::ClickType click_type,base::OnceCallback<void (const ClientStatus &)> callback)285 void WebController::ClickOrTapElement(
286     const Selector& selector,
287     ClickAction::ClickType click_type,
288     base::OnceCallback<void(const ClientStatus&)> callback) {
289   VLOG(3) << __func__ << " " << selector;
290   DCHECK(!selector.empty());
291   FindElement(selector,
292               /* strict_mode= */ true,
293               base::BindOnce(&WebController::OnFindElementForClickOrTap,
294                              weak_ptr_factory_.GetWeakPtr(),
295                              std::move(callback), click_type));
296 }
297 
OnFindElementForClickOrTap(base::OnceCallback<void (const ClientStatus &)> callback,ClickAction::ClickType click_type,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> result)298 void WebController::OnFindElementForClickOrTap(
299     base::OnceCallback<void(const ClientStatus&)> callback,
300     ClickAction::ClickType click_type,
301     const ClientStatus& status,
302     std::unique_ptr<ElementFinder::Result> result) {
303   // Found element must belong to a frame.
304   if (!status.ok()) {
305     VLOG(1) << __func__ << " Failed to find the element to click or tap.";
306     std::move(callback).Run(status);
307     return;
308   }
309 
310   std::string element_object_id = result->object_id;
311   WaitForDocumentToBecomeInteractive(
312       settings_->document_ready_check_count, element_object_id,
313       result->node_frame_id,
314       base::BindOnce(
315           &WebController::OnWaitDocumentToBecomeInteractiveForClickOrTap,
316           weak_ptr_factory_.GetWeakPtr(), std::move(callback), click_type,
317           std::move(result)));
318 }
319 
OnWaitDocumentToBecomeInteractiveForClickOrTap(base::OnceCallback<void (const ClientStatus &)> callback,ClickAction::ClickType click_type,std::unique_ptr<ElementFinder::Result> target_element,bool result)320 void WebController::OnWaitDocumentToBecomeInteractiveForClickOrTap(
321     base::OnceCallback<void(const ClientStatus&)> callback,
322     ClickAction::ClickType click_type,
323     std::unique_ptr<ElementFinder::Result> target_element,
324     bool result) {
325   if (!result) {
326     std::move(callback).Run(ClientStatus(TIMED_OUT));
327     return;
328   }
329 
330   ClickOrTapElement(std::move(target_element), click_type, std::move(callback));
331 }
332 
ClickOrTapElement(std::unique_ptr<ElementFinder::Result> target_element,ClickAction::ClickType click_type,base::OnceCallback<void (const ClientStatus &)> callback)333 void WebController::ClickOrTapElement(
334     std::unique_ptr<ElementFinder::Result> target_element,
335     ClickAction::ClickType click_type,
336     base::OnceCallback<void(const ClientStatus&)> callback) {
337   std::string element_object_id = target_element->object_id;
338   std::vector<std::unique_ptr<runtime::CallArgument>> argument;
339   AddRuntimeCallArgumentObjectId(element_object_id, &argument);
340   devtools_client_->GetRuntime()->CallFunctionOn(
341       runtime::CallFunctionOnParams::Builder()
342           .SetObjectId(element_object_id)
343           .SetArguments(std::move(argument))
344           .SetFunctionDeclaration(std::string(kScrollIntoViewCenterScript))
345           .SetReturnByValue(true)
346           .Build(),
347       target_element->node_frame_id,
348       base::BindOnce(&WebController::OnScrollIntoView,
349                      weak_ptr_factory_.GetWeakPtr(), std::move(target_element),
350                      std::move(callback), click_type));
351 }
352 
OnScrollIntoView(std::unique_ptr<ElementFinder::Result> target_element,base::OnceCallback<void (const ClientStatus &)> callback,ClickAction::ClickType click_type,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)353 void WebController::OnScrollIntoView(
354     std::unique_ptr<ElementFinder::Result> target_element,
355     base::OnceCallback<void(const ClientStatus&)> callback,
356     ClickAction::ClickType click_type,
357     const DevtoolsClient::ReplyStatus& reply_status,
358     std::unique_ptr<runtime::CallFunctionOnResult> result) {
359   ClientStatus status =
360       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
361   if (!status.ok()) {
362     VLOG(1) << __func__ << " Failed to scroll the element.";
363     std::move(callback).Run(status);
364     return;
365   }
366 
367   if (click_type == ClickAction::JAVASCRIPT) {
368     std::string element_object_id = target_element->object_id;
369     std::vector<std::unique_ptr<runtime::CallArgument>> argument;
370     AddRuntimeCallArgumentObjectId(element_object_id, &argument);
371     devtools_client_->GetRuntime()->CallFunctionOn(
372         runtime::CallFunctionOnParams::Builder()
373             .SetObjectId(element_object_id)
374             .SetArguments(std::move(argument))
375             .SetFunctionDeclaration(kClickElement)
376             .Build(),
377         target_element->node_frame_id,
378         base::BindOnce(&WebController::OnClickJS,
379                        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
380     return;
381   }
382 
383   std::unique_ptr<ElementPositionGetter> getter =
384       std::make_unique<ElementPositionGetter>(
385           devtools_client_.get(), *settings_, target_element->node_frame_id);
386   auto* ptr = getter.get();
387   pending_workers_.emplace_back(std::move(getter));
388   ptr->Start(
389       target_element->container_frame_host, target_element->object_id,
390       base::BindOnce(&WebController::TapOrClickOnCoordinates,
391                      weak_ptr_factory_.GetWeakPtr(), ptr, std::move(callback),
392                      target_element->node_frame_id, click_type));
393 }
394 
OnClickJS(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)395 void WebController::OnClickJS(
396     base::OnceCallback<void(const ClientStatus&)> callback,
397     const DevtoolsClient::ReplyStatus& reply_status,
398     std::unique_ptr<runtime::CallFunctionOnResult> result) {
399   ClientStatus status =
400       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
401   if (!status.ok()) {
402     VLOG(1) << __func__ << " Failed to click (javascript) the element.";
403   }
404   std::move(callback).Run(status);
405 }
406 
TapOrClickOnCoordinates(ElementPositionGetter * getter_to_release,base::OnceCallback<void (const ClientStatus &)> callback,const std::string & node_frame_id,ClickAction::ClickType click_type,bool has_coordinates,int x,int y)407 void WebController::TapOrClickOnCoordinates(
408     ElementPositionGetter* getter_to_release,
409     base::OnceCallback<void(const ClientStatus&)> callback,
410     const std::string& node_frame_id,
411     ClickAction::ClickType click_type,
412     bool has_coordinates,
413     int x,
414     int y) {
415   base::EraseIf(pending_workers_, [getter_to_release](const auto& worker) {
416     return worker.get() == getter_to_release;
417   });
418 
419   if (!has_coordinates) {
420     VLOG(1) << __func__ << " Failed to get element position.";
421     std::move(callback).Run(ClientStatus(ELEMENT_UNSTABLE));
422     return;
423   }
424 
425   DCHECK(click_type == ClickAction::TAP || click_type == ClickAction::CLICK);
426   if (click_type == ClickAction::CLICK) {
427     devtools_client_->GetInput()->DispatchMouseEvent(
428         input::DispatchMouseEventParams::Builder()
429             .SetX(x)
430             .SetY(y)
431             .SetClickCount(1)
432             .SetButton(input::MouseButton::LEFT)
433             .SetType(input::DispatchMouseEventType::MOUSE_PRESSED)
434             .Build(),
435         node_frame_id,
436         base::BindOnce(&WebController::OnDispatchPressMouseEvent,
437                        weak_ptr_factory_.GetWeakPtr(), std::move(callback),
438                        node_frame_id, x, y));
439     return;
440   }
441 
442   std::vector<std::unique_ptr<::autofill_assistant::input::TouchPoint>>
443       touch_points;
444   touch_points.emplace_back(
445       input::TouchPoint::Builder().SetX(x).SetY(y).Build());
446   devtools_client_->GetInput()->DispatchTouchEvent(
447       input::DispatchTouchEventParams::Builder()
448           .SetType(input::DispatchTouchEventType::TOUCH_START)
449           .SetTouchPoints(std::move(touch_points))
450           .Build(),
451       node_frame_id,
452       base::BindOnce(&WebController::OnDispatchTouchEventStart,
453                      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
454                      node_frame_id));
455 }
456 
OnDispatchPressMouseEvent(base::OnceCallback<void (const ClientStatus &)> callback,const std::string & node_frame_id,int x,int y,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<input::DispatchMouseEventResult> result)457 void WebController::OnDispatchPressMouseEvent(
458     base::OnceCallback<void(const ClientStatus&)> callback,
459     const std::string& node_frame_id,
460     int x,
461     int y,
462     const DevtoolsClient::ReplyStatus& reply_status,
463     std::unique_ptr<input::DispatchMouseEventResult> result) {
464   if (!result) {
465     VLOG(1) << __func__
466             << " Failed to dispatch mouse left button pressed event.";
467     std::move(callback).Run(
468         UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__));
469     return;
470   }
471 
472   devtools_client_->GetInput()->DispatchMouseEvent(
473       input::DispatchMouseEventParams::Builder()
474           .SetX(x)
475           .SetY(y)
476           .SetClickCount(1)
477           .SetButton(input::MouseButton::LEFT)
478           .SetType(input::DispatchMouseEventType::MOUSE_RELEASED)
479           .Build(),
480       node_frame_id,
481       base::BindOnce(&WebController::OnDispatchReleaseMouseEvent,
482                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
483 }
484 
OnDispatchReleaseMouseEvent(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<input::DispatchMouseEventResult> result)485 void WebController::OnDispatchReleaseMouseEvent(
486     base::OnceCallback<void(const ClientStatus&)> callback,
487     const DevtoolsClient::ReplyStatus& reply_status,
488     std::unique_ptr<input::DispatchMouseEventResult> result) {
489   if (!result) {
490     VLOG(1) << __func__ << " Failed to dispatch release mouse event.";
491     std::move(callback).Run(
492         UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__));
493     return;
494   }
495   std::move(callback).Run(OkClientStatus());
496 }
497 
OnDispatchTouchEventStart(base::OnceCallback<void (const ClientStatus &)> callback,const std::string & node_frame_id,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<input::DispatchTouchEventResult> result)498 void WebController::OnDispatchTouchEventStart(
499     base::OnceCallback<void(const ClientStatus&)> callback,
500     const std::string& node_frame_id,
501     const DevtoolsClient::ReplyStatus& reply_status,
502     std::unique_ptr<input::DispatchTouchEventResult> result) {
503   if (!result) {
504     VLOG(1) << __func__ << " Failed to dispatch touch start event.";
505     std::move(callback).Run(
506         UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__));
507     return;
508   }
509 
510   std::vector<std::unique_ptr<::autofill_assistant::input::TouchPoint>>
511       touch_points;
512   devtools_client_->GetInput()->DispatchTouchEvent(
513       input::DispatchTouchEventParams::Builder()
514           .SetType(input::DispatchTouchEventType::TOUCH_END)
515           .SetTouchPoints(std::move(touch_points))
516           .Build(),
517       node_frame_id,
518       base::BindOnce(&WebController::OnDispatchTouchEventEnd,
519                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
520 }
521 
OnDispatchTouchEventEnd(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<input::DispatchTouchEventResult> result)522 void WebController::OnDispatchTouchEventEnd(
523     base::OnceCallback<void(const ClientStatus&)> callback,
524     const DevtoolsClient::ReplyStatus& reply_status,
525     std::unique_ptr<input::DispatchTouchEventResult> result) {
526   if (!result) {
527     VLOG(1) << __func__ << " Failed to dispatch touch end event.";
528     std::move(callback).Run(
529         UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__));
530     return;
531   }
532   std::move(callback).Run(OkClientStatus());
533 }
534 
ElementCheck(const Selector & selector,bool strict,base::OnceCallback<void (const ClientStatus &)> callback)535 void WebController::ElementCheck(
536     const Selector& selector,
537     bool strict,
538     base::OnceCallback<void(const ClientStatus&)> callback) {
539   DCHECK(!selector.empty());
540   FindElement(
541       selector, strict,
542       base::BindOnce(&WebController::OnFindElementForCheck,
543                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
544 }
545 
OnFindElementForCheck(base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> result)546 void WebController::OnFindElementForCheck(
547     base::OnceCallback<void(const ClientStatus&)> callback,
548     const ClientStatus& status,
549     std::unique_ptr<ElementFinder::Result> result) {
550   VLOG_IF(1, !status.ok() && status.proto_status() != ELEMENT_RESOLUTION_FAILED)
551       << __func__ << ": " << status;
552   std::move(callback).Run(status);
553 }
554 
WaitForWindowHeightChange(base::OnceCallback<void (const ClientStatus &)> callback)555 void WebController::WaitForWindowHeightChange(
556     base::OnceCallback<void(const ClientStatus&)> callback) {
557   devtools_client_->GetRuntime()->Evaluate(
558       runtime::EvaluateParams::Builder()
559           .SetExpression(kWaitForWindowHeightChange)
560           .SetAwaitPromise(true)
561           .Build(),
562       /* node_frame_id= */ std::string(),
563       base::BindOnce(&WebController::OnWaitForWindowHeightChange,
564                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
565 }
566 
OnWaitForWindowHeightChange(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::EvaluateResult> result)567 void WebController::OnWaitForWindowHeightChange(
568     base::OnceCallback<void(const ClientStatus&)> callback,
569     const DevtoolsClient::ReplyStatus& reply_status,
570     std::unique_ptr<runtime::EvaluateResult> result) {
571   std::move(callback).Run(
572       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__));
573 }
574 
GetDocumentReadyState(const Selector & optional_frame,base::OnceCallback<void (const ClientStatus &,DocumentReadyState)> callback)575 void WebController::GetDocumentReadyState(
576     const Selector& optional_frame,
577     base::OnceCallback<void(const ClientStatus&, DocumentReadyState)>
578         callback) {
579   WaitForDocumentReadyState(optional_frame, DOCUMENT_UNKNOWN_READY_STATE,
580                             std::move(callback));
581 }
582 
WaitForDocumentReadyState(const Selector & optional_frame,DocumentReadyState min_ready_state,base::OnceCallback<void (const ClientStatus &,DocumentReadyState)> callback)583 void WebController::WaitForDocumentReadyState(
584     const Selector& optional_frame,
585     DocumentReadyState min_ready_state,
586     base::OnceCallback<void(const ClientStatus&, DocumentReadyState)>
587         callback) {
588   if (optional_frame.empty()) {
589     std::string expression;
590     expression.append("(");
591     AppendWaitForDocumentReadyStateFunction(&expression);
592     base::StringAppendF(&expression, ")(%d)",
593                         static_cast<int>(min_ready_state));
594     devtools_client_->GetRuntime()->Evaluate(
595         runtime::EvaluateParams::Builder()
596             .SetExpression(expression)
597             .SetReturnByValue(true)
598             .SetAwaitPromise(true)
599             .Build(),
600         /* node_frame_id= */ std::string(),
601         base::BindOnce(&OnWaitForDocumentReadyState<runtime::EvaluateResult>,
602                        std::move(callback)));
603     return;
604   }
605   FindElement(
606       optional_frame, /* strict= */ false,
607       base::BindOnce(&WebController::OnFindElementForWaitForDocumentReadyState,
608                      weak_ptr_factory_.GetWeakPtr(), min_ready_state,
609                      std::move(callback)));
610 }
611 
OnFindElementForWaitForDocumentReadyState(DocumentReadyState min_ready_state,base::OnceCallback<void (const ClientStatus &,DocumentReadyState)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element)612 void WebController::OnFindElementForWaitForDocumentReadyState(
613     DocumentReadyState min_ready_state,
614     base::OnceCallback<void(const ClientStatus&, DocumentReadyState)> callback,
615     const ClientStatus& status,
616     std::unique_ptr<ElementFinder::Result> element) {
617   if (!status.ok()) {
618     std::move(callback).Run(status, DOCUMENT_UNKNOWN_READY_STATE);
619     return;
620   }
621 
622   std::string function_declaration;
623   AppendWaitForDocumentReadyStateFunction(&function_declaration);
624 
625   std::vector<std::unique_ptr<runtime::CallArgument>> arguments;
626   AddRuntimeCallArgument(static_cast<int>(min_ready_state), &arguments);
627   devtools_client_->GetRuntime()->CallFunctionOn(
628       runtime::CallFunctionOnParams::Builder()
629           .SetObjectId(element ? element->object_id : "")
630           .SetFunctionDeclaration(function_declaration)
631           .SetArguments(std::move(arguments))
632           .SetReturnByValue(true)
633           .SetAwaitPromise(true)
634           .Build(),
635       element->node_frame_id,
636       base::BindOnce(
637           &OnWaitForDocumentReadyState<runtime::CallFunctionOnResult>,
638           std::move(callback)));
639 }
640 
FindElement(const Selector & selector,bool strict_mode,ElementFinder::Callback callback)641 void WebController::FindElement(const Selector& selector,
642                                 bool strict_mode,
643                                 ElementFinder::Callback callback) {
644   auto finder = std::make_unique<ElementFinder>(
645       web_contents_, devtools_client_.get(), selector, strict_mode);
646   auto* ptr = finder.get();
647   pending_workers_.emplace_back(std::move(finder));
648   ptr->Start(base::BindOnce(&WebController::OnFindElementResult,
649                             weak_ptr_factory_.GetWeakPtr(), ptr,
650                             std::move(callback)));
651 }
652 
OnFindElementResult(ElementFinder * finder_to_release,ElementFinder::Callback callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> result)653 void WebController::OnFindElementResult(
654     ElementFinder* finder_to_release,
655     ElementFinder::Callback callback,
656     const ClientStatus& status,
657     std::unique_ptr<ElementFinder::Result> result) {
658   base::EraseIf(pending_workers_, [finder_to_release](const auto& worker) {
659     return worker.get() == finder_to_release;
660   });
661   std::move(callback).Run(status, std::move(result));
662 }
663 
OnFindElementForFocusElement(const TopPadding & top_padding,base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)664 void WebController::OnFindElementForFocusElement(
665     const TopPadding& top_padding,
666     base::OnceCallback<void(const ClientStatus&)> callback,
667     const ClientStatus& status,
668     std::unique_ptr<ElementFinder::Result> element_result) {
669   if (!status.ok()) {
670     VLOG(1) << __func__ << " Failed to find the element to focus on.";
671     std::move(callback).Run(status);
672     return;
673   }
674 
675   std::string element_object_id = element_result->object_id;
676   WaitForDocumentToBecomeInteractive(
677       settings_->document_ready_check_count, element_object_id,
678       element_result->node_frame_id,
679       base::BindOnce(
680           &WebController::OnWaitDocumentToBecomeInteractiveForFocusElement,
681           weak_ptr_factory_.GetWeakPtr(), top_padding, std::move(callback),
682           std::move(element_result)));
683 }
684 
OnWaitDocumentToBecomeInteractiveForFocusElement(const TopPadding & top_padding,base::OnceCallback<void (const ClientStatus &)> callback,std::unique_ptr<ElementFinder::Result> target_element,bool result)685 void WebController::OnWaitDocumentToBecomeInteractiveForFocusElement(
686     const TopPadding& top_padding,
687     base::OnceCallback<void(const ClientStatus&)> callback,
688     std::unique_ptr<ElementFinder::Result> target_element,
689     bool result) {
690   if (!result) {
691     std::move(callback).Run(ClientStatus(ELEMENT_UNSTABLE));
692     return;
693   }
694 
695   std::vector<std::unique_ptr<runtime::CallArgument>> arguments;
696   AddRuntimeCallArgumentObjectId(target_element->object_id, &arguments);
697   AddRuntimeCallArgument(top_padding.pixels(), &arguments);
698   AddRuntimeCallArgument(top_padding.ratio(), &arguments);
699   devtools_client_->GetRuntime()->CallFunctionOn(
700       runtime::CallFunctionOnParams::Builder()
701           .SetObjectId(target_element->object_id)
702           .SetArguments(std::move(arguments))
703           .SetFunctionDeclaration(std::string(kScrollIntoViewWithPaddingScript))
704           .SetReturnByValue(true)
705           .Build(),
706       target_element->node_frame_id,
707       base::BindOnce(&WebController::OnFocusElement,
708                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
709 }
710 
OnFocusElement(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)711 void WebController::OnFocusElement(
712     base::OnceCallback<void(const ClientStatus&)> callback,
713     const DevtoolsClient::ReplyStatus& reply_status,
714     std::unique_ptr<runtime::CallFunctionOnResult> result) {
715   ClientStatus status =
716       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
717   VLOG_IF(1, !status.ok()) << __func__ << " Failed to focus on element.";
718   std::move(callback).Run(status);
719 }
720 
FillAddressForm(const autofill::AutofillProfile * profile,const Selector & selector,base::OnceCallback<void (const ClientStatus &)> callback)721 void WebController::FillAddressForm(
722     const autofill::AutofillProfile* profile,
723     const Selector& selector,
724     base::OnceCallback<void(const ClientStatus&)> callback) {
725   VLOG(3) << __func__ << selector;
726   auto data_to_autofill = std::make_unique<FillFormInputData>();
727   data_to_autofill->profile =
728       std::make_unique<autofill::AutofillProfile>(*profile);
729   FindElement(selector,
730               /* strict_mode= */ true,
731               base::BindOnce(&WebController::OnFindElementForFillingForm,
732                              weak_ptr_factory_.GetWeakPtr(),
733                              std::move(data_to_autofill), selector,
734                              std::move(callback)));
735 }
736 
FillCardForm(std::unique_ptr<autofill::CreditCard> card,const base::string16 & cvc,const Selector & selector,base::OnceCallback<void (const ClientStatus &)> callback)737 void WebController::FillCardForm(
738     std::unique_ptr<autofill::CreditCard> card,
739     const base::string16& cvc,
740     const Selector& selector,
741     base::OnceCallback<void(const ClientStatus&)> callback) {
742   VLOG(3) << __func__ << " " << selector;
743   auto data_to_autofill = std::make_unique<FillFormInputData>();
744   data_to_autofill->card = std::move(card);
745   data_to_autofill->cvc = cvc;
746   FindElement(selector,
747               /* strict_mode= */ true,
748               base::BindOnce(&WebController::OnFindElementForFillingForm,
749                              weak_ptr_factory_.GetWeakPtr(),
750                              std::move(data_to_autofill), selector,
751                              std::move(callback)));
752 }
753 
OnFindElementForFillingForm(std::unique_ptr<FillFormInputData> data_to_autofill,const Selector & selector,base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)754 void WebController::OnFindElementForFillingForm(
755     std::unique_ptr<FillFormInputData> data_to_autofill,
756     const Selector& selector,
757     base::OnceCallback<void(const ClientStatus&)> callback,
758     const ClientStatus& status,
759     std::unique_ptr<ElementFinder::Result> element_result) {
760   if (!status.ok()) {
761     VLOG(1) << __func__ << " Failed to find the element for filling the form.";
762     std::move(callback).Run(FillAutofillErrorStatus(status));
763     return;
764   }
765 
766   ContentAutofillDriver* driver = ContentAutofillDriver::GetForRenderFrameHost(
767       element_result->container_frame_host);
768   if (driver == nullptr) {
769     VLOG(1) << __func__ << " Failed to get the autofill driver.";
770     std::move(callback).Run(
771         FillAutofillErrorStatus(UnexpectedErrorStatus(__FILE__, __LINE__)));
772     return;
773   }
774   DCHECK(!selector.empty());
775   // TODO(crbug.com/806868): Figure out whether there are cases where we need
776   // more than one selector, and come up with a solution that can figure out the
777   // right number of selectors to include.
778   driver->GetAutofillAgent()->GetElementFormAndFieldData(
779       std::vector<std::string>(1, selector.selectors.back()),
780       base::BindOnce(&WebController::OnGetFormAndFieldDataForFillingForm,
781                      weak_ptr_factory_.GetWeakPtr(),
782                      std::move(data_to_autofill), std::move(callback),
783                      element_result->container_frame_host));
784 }
785 
OnGetFormAndFieldDataForFillingForm(std::unique_ptr<FillFormInputData> data_to_autofill,base::OnceCallback<void (const ClientStatus &)> callback,content::RenderFrameHost * container_frame_host,const autofill::FormData & form_data,const autofill::FormFieldData & form_field)786 void WebController::OnGetFormAndFieldDataForFillingForm(
787     std::unique_ptr<FillFormInputData> data_to_autofill,
788     base::OnceCallback<void(const ClientStatus&)> callback,
789     content::RenderFrameHost* container_frame_host,
790     const autofill::FormData& form_data,
791     const autofill::FormFieldData& form_field) {
792   if (form_data.fields.empty()) {
793     VLOG(1) << __func__ << " Failed to get form data to fill form.";
794     std::move(callback).Run(
795         FillAutofillErrorStatus(UnexpectedErrorStatus(__FILE__, __LINE__)));
796     return;
797   }
798 
799   ContentAutofillDriver* driver =
800       ContentAutofillDriver::GetForRenderFrameHost(container_frame_host);
801   if (driver == nullptr) {
802     VLOG(1) << __func__ << " Failed to get the autofill driver.";
803     std::move(callback).Run(
804         FillAutofillErrorStatus(UnexpectedErrorStatus(__FILE__, __LINE__)));
805     return;
806   }
807 
808   if (data_to_autofill->card) {
809     driver->autofill_manager()->FillCreditCardForm(
810         autofill::kNoQueryId, form_data, form_field, *data_to_autofill->card,
811         data_to_autofill->cvc);
812   } else {
813     driver->autofill_manager()->FillProfileForm(*data_to_autofill->profile,
814                                                 form_data, form_field);
815   }
816 
817   std::move(callback).Run(OkClientStatus());
818 }
819 
RetrieveElementFormAndFieldData(const Selector & selector,base::OnceCallback<void (const ClientStatus &,const autofill::FormData & form_data,const autofill::FormFieldData & field_data)> callback)820 void WebController::RetrieveElementFormAndFieldData(
821     const Selector& selector,
822     base::OnceCallback<void(const ClientStatus&,
823                             const autofill::FormData& form_data,
824                             const autofill::FormFieldData& field_data)>
825         callback) {
826   DVLOG(3) << __func__ << " " << selector;
827   FindElement(
828       selector, /* strict_mode= */ true,
829       base::BindOnce(&WebController::OnFindElementToRetrieveFormAndFieldData,
830                      weak_ptr_factory_.GetWeakPtr(), selector,
831                      std::move(callback)));
832 }
833 
OnFindElementToRetrieveFormAndFieldData(const Selector & selector,base::OnceCallback<void (const ClientStatus &,const autofill::FormData & form_data,const autofill::FormFieldData & field_data)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)834 void WebController::OnFindElementToRetrieveFormAndFieldData(
835     const Selector& selector,
836     base::OnceCallback<void(const ClientStatus&,
837                             const autofill::FormData& form_data,
838                             const autofill::FormFieldData& field_data)>
839         callback,
840     const ClientStatus& status,
841     std::unique_ptr<ElementFinder::Result> element_result) {
842   if (!status.ok()) {
843     DVLOG(1) << __func__
844              << " Failed to find the element to retrieve form and field data.";
845     std::move(callback).Run(status, autofill::FormData(),
846                             autofill::FormFieldData());
847     return;
848   }
849   ContentAutofillDriver* driver = ContentAutofillDriver::GetForRenderFrameHost(
850       element_result->container_frame_host);
851   if (driver == nullptr) {
852     DVLOG(1) << __func__ << " Failed to get the autofill driver.";
853     std::move(callback).Run(
854         FillAutofillErrorStatus(UnexpectedErrorStatus(__FILE__, __LINE__)),
855         autofill::FormData(), autofill::FormFieldData());
856     return;
857   }
858   DCHECK(!selector.empty());
859   driver->GetAutofillAgent()->GetElementFormAndFieldData(
860       std::vector<std::string>(1, selector.selectors.back()),
861       base::BindOnce(&WebController::OnGetFormAndFieldDataForRetrieving,
862                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
863 }
864 
OnGetFormAndFieldDataForRetrieving(base::OnceCallback<void (const ClientStatus &,const autofill::FormData & form_data,const autofill::FormFieldData & field_data)> callback,const autofill::FormData & form_data,const autofill::FormFieldData & field_data)865 void WebController::OnGetFormAndFieldDataForRetrieving(
866     base::OnceCallback<void(const ClientStatus&,
867                             const autofill::FormData& form_data,
868                             const autofill::FormFieldData& field_data)>
869         callback,
870     const autofill::FormData& form_data,
871     const autofill::FormFieldData& field_data) {
872   if (form_data.fields.empty()) {
873     DVLOG(1) << __func__
874              << " Failed to get form and field data for retrieving.";
875     std::move(callback).Run(UnexpectedErrorStatus(__FILE__, __LINE__),
876                             autofill::FormData(), autofill::FormFieldData());
877     return;
878   }
879   std::move(callback).Run(OkClientStatus(), form_data, field_data);
880 }
881 
SelectOption(const Selector & selector,const std::string & value,DropdownSelectStrategy select_strategy,base::OnceCallback<void (const ClientStatus &)> callback)882 void WebController::SelectOption(
883     const Selector& selector,
884     const std::string& value,
885     DropdownSelectStrategy select_strategy,
886     base::OnceCallback<void(const ClientStatus&)> callback) {
887 #ifdef NDEBUG
888   VLOG(3) << __func__ << " " << selector << ", value=(redacted)"
889           << ", strategy=" << select_strategy;
890 #else
891   DVLOG(3) << __func__ << " " << selector << ", value=" << value
892            << ", strategy=" << select_strategy;
893 #endif
894 
895   FindElement(selector,
896               /* strict_mode= */ true,
897               base::BindOnce(&WebController::OnFindElementForSelectOption,
898                              weak_ptr_factory_.GetWeakPtr(), value,
899                              select_strategy, std::move(callback)));
900 }
901 
OnFindElementForSelectOption(const std::string & value,DropdownSelectStrategy select_strategy,base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)902 void WebController::OnFindElementForSelectOption(
903     const std::string& value,
904     DropdownSelectStrategy select_strategy,
905     base::OnceCallback<void(const ClientStatus&)> callback,
906     const ClientStatus& status,
907     std::unique_ptr<ElementFinder::Result> element_result) {
908   if (!status.ok()) {
909     VLOG(1) << __func__ << " Failed to find the element to select an option.";
910     std::move(callback).Run(status);
911     return;
912   }
913 
914   std::vector<std::unique_ptr<runtime::CallArgument>> arguments;
915   AddRuntimeCallArgument(value, &arguments);
916   AddRuntimeCallArgument(static_cast<int>(select_strategy), &arguments);
917   devtools_client_->GetRuntime()->CallFunctionOn(
918       runtime::CallFunctionOnParams::Builder()
919           .SetObjectId(element_result->object_id)
920           .SetArguments(std::move(arguments))
921           .SetFunctionDeclaration(std::string(kSelectOptionScript))
922           .SetReturnByValue(true)
923           .Build(),
924       element_result->node_frame_id,
925       base::BindOnce(&WebController::OnSelectOption,
926                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
927 }
928 
OnSelectOption(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)929 void WebController::OnSelectOption(
930     base::OnceCallback<void(const ClientStatus&)> callback,
931     const DevtoolsClient::ReplyStatus& reply_status,
932     std::unique_ptr<runtime::CallFunctionOnResult> result) {
933   ClientStatus status =
934       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
935   if (!status.ok()) {
936     VLOG(1) << __func__ << " Failed to select option.";
937     std::move(callback).Run(status);
938     return;
939   }
940   bool found;
941   if (!SafeGetBool(result->GetResult(), &found)) {
942     std::move(callback).Run(
943         UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__));
944     return;
945   }
946   if (!found) {
947     VLOG(1) << __func__ << " Failed to find option.";
948     std::move(callback).Run(ClientStatus(OPTION_VALUE_NOT_FOUND));
949     return;
950   }
951   std::move(callback).Run(OkClientStatus());
952 }
953 
HighlightElement(const Selector & selector,base::OnceCallback<void (const ClientStatus &)> callback)954 void WebController::HighlightElement(
955     const Selector& selector,
956     base::OnceCallback<void(const ClientStatus&)> callback) {
957   VLOG(3) << __func__ << " " << selector;
958   FindElement(
959       selector,
960       /* strict_mode= */ true,
961       base::BindOnce(&WebController::OnFindElementForHighlightElement,
962                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
963 }
964 
OnFindElementForHighlightElement(base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)965 void WebController::OnFindElementForHighlightElement(
966     base::OnceCallback<void(const ClientStatus&)> callback,
967     const ClientStatus& status,
968     std::unique_ptr<ElementFinder::Result> element_result) {
969   if (!status.ok()) {
970     VLOG(1) << __func__ << " Failed to find the element to highlight.";
971     std::move(callback).Run(status);
972     return;
973   }
974 
975   const std::string& object_id = element_result->object_id;
976   std::vector<std::unique_ptr<runtime::CallArgument>> argument;
977   AddRuntimeCallArgumentObjectId(object_id, &argument);
978   devtools_client_->GetRuntime()->CallFunctionOn(
979       runtime::CallFunctionOnParams::Builder()
980           .SetObjectId(object_id)
981           .SetArguments(std::move(argument))
982           .SetFunctionDeclaration(std::string(kHighlightElementScript))
983           .SetReturnByValue(true)
984           .Build(),
985       element_result->node_frame_id,
986       base::BindOnce(&WebController::OnHighlightElement,
987                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
988 }
989 
OnHighlightElement(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)990 void WebController::OnHighlightElement(
991     base::OnceCallback<void(const ClientStatus&)> callback,
992     const DevtoolsClient::ReplyStatus& reply_status,
993     std::unique_ptr<runtime::CallFunctionOnResult> result) {
994   ClientStatus status =
995       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
996   VLOG_IF(1, !status.ok()) << __func__ << " Failed to highlight element.";
997   std::move(callback).Run(status);
998 }
999 
FocusElement(const Selector & selector,const TopPadding & top_padding,base::OnceCallback<void (const ClientStatus &)> callback)1000 void WebController::FocusElement(
1001     const Selector& selector,
1002     const TopPadding& top_padding,
1003     base::OnceCallback<void(const ClientStatus&)> callback) {
1004   VLOG(3) << __func__ << " " << selector;
1005   DCHECK(!selector.empty());
1006   FindElement(selector,
1007               /* strict_mode= */ false,
1008               base::BindOnce(&WebController::OnFindElementForFocusElement,
1009                              weak_ptr_factory_.GetWeakPtr(), top_padding,
1010                              std::move(callback)));
1011 }
1012 
GetFieldValue(const Selector & selector,base::OnceCallback<void (const ClientStatus &,const std::string &)> callback)1013 void WebController::GetFieldValue(
1014     const Selector& selector,
1015     base::OnceCallback<void(const ClientStatus&, const std::string&)>
1016         callback) {
1017   FindElement(
1018       selector,
1019       /* strict_mode= */ true,
1020       base::BindOnce(&WebController::OnFindElementForGetFieldValue,
1021                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1022 }
1023 
OnFindElementForGetFieldValue(base::OnceCallback<void (const ClientStatus &,const std::string &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)1024 void WebController::OnFindElementForGetFieldValue(
1025     base::OnceCallback<void(const ClientStatus&, const std::string&)> callback,
1026     const ClientStatus& status,
1027     std::unique_ptr<ElementFinder::Result> element_result) {
1028   const std::string object_id = element_result->object_id;
1029   if (!status.ok()) {
1030     std::move(callback).Run(status, "");
1031     return;
1032   }
1033 
1034   devtools_client_->GetRuntime()->CallFunctionOn(
1035       runtime::CallFunctionOnParams::Builder()
1036           .SetObjectId(object_id)
1037           .SetFunctionDeclaration(std::string(kGetValueAttributeScript))
1038           .SetReturnByValue(true)
1039           .Build(),
1040       element_result->node_frame_id,
1041       base::BindOnce(&WebController::OnGetValueAttribute,
1042                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1043 }
1044 
OnGetValueAttribute(base::OnceCallback<void (const ClientStatus & element_status,const std::string &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)1045 void WebController::OnGetValueAttribute(
1046     base::OnceCallback<void(const ClientStatus& element_status,
1047                             const std::string&)> callback,
1048     const DevtoolsClient::ReplyStatus& reply_status,
1049     std::unique_ptr<runtime::CallFunctionOnResult> result) {
1050   std::string value;
1051   ClientStatus status =
1052       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
1053   // Read the result returned from Javascript code.
1054   VLOG_IF(1, !status.ok()) << __func__
1055                            << "Failed to get attribute value: " << status;
1056   SafeGetStringValue(result->GetResult(), &value);
1057   std::move(callback).Run(status, value);
1058 }
1059 
SetFieldValue(const Selector & selector,const std::string & value,KeyboardValueFillStrategy fill_strategy,int key_press_delay_in_millisecond,base::OnceCallback<void (const ClientStatus &)> callback)1060 void WebController::SetFieldValue(
1061     const Selector& selector,
1062     const std::string& value,
1063     KeyboardValueFillStrategy fill_strategy,
1064     int key_press_delay_in_millisecond,
1065     base::OnceCallback<void(const ClientStatus&)> callback) {
1066 #ifdef NDEBUG
1067   VLOG(3) << __func__ << " " << selector << ", value=(redacted)"
1068           << ", strategy=" << fill_strategy;
1069 #else
1070   DVLOG(3) << __func__ << " " << selector << ", value=" << value
1071            << ", strategy=" << fill_strategy;
1072 #endif
1073 
1074   if ((fill_strategy == SIMULATE_KEY_PRESSES ||
1075        fill_strategy == SIMULATE_KEY_PRESSES_SELECT_VALUE) &&
1076       !value.empty()) {
1077     // We first select the field value, and then simulate the key presses. This
1078     // will clear / overwrite the previous value.
1079     // TODO(crbug.com/806868): Disable keyboard during this action and then
1080     // reset to previous state.
1081     if (fill_strategy == SIMULATE_KEY_PRESSES_SELECT_VALUE) {
1082       // TODO(b/149004036): In case of empty, send a backspace (i.e. code 8),
1083       // instead of falling back to InternalSetFieldValue(""). This currently
1084       // fails in WebControllerBrowserTest.GetAndSetFieldValue. Fixing this
1085       // might fix b/148001624 as well.
1086       SelectFieldValueForReplace(
1087           selector,
1088           base::BindOnce(&WebController::OnFieldValueSelectedForDispatchKeys,
1089                          weak_ptr_factory_.GetWeakPtr(), UTF8ToUnicode(value),
1090                          key_press_delay_in_millisecond, std::move(callback)));
1091     } else {
1092       InternalSetFieldValue(
1093           selector, "",
1094           base::BindOnce(&WebController::OnClearFieldForSendKeyboardInput,
1095                          weak_ptr_factory_.GetWeakPtr(), selector,
1096                          UTF8ToUnicode(value), key_press_delay_in_millisecond,
1097                          std::move(callback)));
1098     }
1099     return;
1100   }
1101   InternalSetFieldValue(selector, value, std::move(callback));
1102 }
1103 
OnClearFieldForSendKeyboardInput(const Selector & selector,const std::vector<UChar32> & codepoints,int key_press_delay_in_millisecond,base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & clear_status)1104 void WebController::OnClearFieldForSendKeyboardInput(
1105     const Selector& selector,
1106     const std::vector<UChar32>& codepoints,
1107     int key_press_delay_in_millisecond,
1108     base::OnceCallback<void(const ClientStatus&)> callback,
1109     const ClientStatus& clear_status) {
1110   if (!clear_status.ok()) {
1111     std::move(callback).Run(clear_status);
1112     return;
1113   }
1114   SendKeyboardInput(selector, codepoints, key_press_delay_in_millisecond,
1115                     std::move(callback));
1116 }
1117 
SelectFieldValueForReplace(const Selector & selector,base::OnceCallback<void (std::unique_ptr<ElementFinder::Result>,const ClientStatus &)> callback)1118 void WebController::SelectFieldValueForReplace(
1119     const Selector& selector,
1120     base::OnceCallback<void(std::unique_ptr<ElementFinder::Result>,
1121                             const ClientStatus&)> callback) {
1122   VLOG(2) << __func__ << " " << selector;
1123   FindElement(
1124       selector, /* strict_mode= */ true,
1125       base::BindOnce(&WebController::OnFindElementForSelectValue,
1126                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1127 }
1128 
OnFindElementForSelectValue(base::OnceCallback<void (std::unique_ptr<ElementFinder::Result>,const ClientStatus &)> callback,const ClientStatus & element_status,std::unique_ptr<ElementFinder::Result> element_result)1129 void WebController::OnFindElementForSelectValue(
1130     base::OnceCallback<void(std::unique_ptr<ElementFinder::Result>,
1131                             const ClientStatus&)> callback,
1132     const ClientStatus& element_status,
1133     std::unique_ptr<ElementFinder::Result> element_result) {
1134   if (!element_status.ok()) {
1135     std::move(callback).Run(std::move(element_result), element_status);
1136     return;
1137   }
1138 
1139   devtools_client_->GetRuntime()->CallFunctionOn(
1140       runtime::CallFunctionOnParams::Builder()
1141           .SetObjectId(element_result->object_id)
1142           .SetFunctionDeclaration(std::string(kSelectFieldValue))
1143           .Build(),
1144       element_result->node_frame_id,
1145       base::BindOnce(&WebController::OnSelectFieldValueForDispatchKeys,
1146                      weak_ptr_factory_.GetWeakPtr(), std::move(element_result),
1147                      std::move(callback)));
1148 }
1149 
OnSelectFieldValueForDispatchKeys(std::unique_ptr<ElementFinder::Result> element_result,base::OnceCallback<void (std::unique_ptr<ElementFinder::Result>,const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)1150 void WebController::OnSelectFieldValueForDispatchKeys(
1151     std::unique_ptr<ElementFinder::Result> element_result,
1152     base::OnceCallback<void(std::unique_ptr<ElementFinder::Result>,
1153                             const ClientStatus&)> callback,
1154     const DevtoolsClient::ReplyStatus& reply_status,
1155     std::unique_ptr<runtime::CallFunctionOnResult> result) {
1156   std::move(callback).Run(
1157       std::move(element_result),
1158       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__));
1159 }
1160 
OnFieldValueSelectedForDispatchKeys(const std::vector<UChar32> & codepoints,int key_press_delay_in_millisecond,base::OnceCallback<void (const ClientStatus &)> callback,std::unique_ptr<ElementFinder::Result> element_result,const ClientStatus & select_status)1161 void WebController::OnFieldValueSelectedForDispatchKeys(
1162     const std::vector<UChar32>& codepoints,
1163     int key_press_delay_in_millisecond,
1164     base::OnceCallback<void(const ClientStatus&)> callback,
1165     std::unique_ptr<ElementFinder::Result> element_result,
1166     const ClientStatus& select_status) {
1167   if (!select_status.ok()) {
1168     std::move(callback).Run(select_status);
1169     return;
1170   }
1171   DispatchKeyboardTextDownEvent(
1172       element_result->node_frame_id, codepoints, 0,
1173       /* delay= */ false, key_press_delay_in_millisecond, std::move(callback));
1174 }
1175 
DispatchKeyboardTextDownEvent(const std::string & node_frame_id,const std::vector<UChar32> & codepoints,size_t index,bool delay,int delay_in_millisecond,base::OnceCallback<void (const ClientStatus &)> callback)1176 void WebController::DispatchKeyboardTextDownEvent(
1177     const std::string& node_frame_id,
1178     const std::vector<UChar32>& codepoints,
1179     size_t index,
1180     bool delay,
1181     int delay_in_millisecond,
1182     base::OnceCallback<void(const ClientStatus&)> callback) {
1183   if (index >= codepoints.size()) {
1184     std::move(callback).Run(OkClientStatus());
1185     return;
1186   }
1187 
1188   if (delay && delay_in_millisecond > 0) {
1189     base::PostDelayedTask(
1190         FROM_HERE, {content::BrowserThread::UI},
1191         base::BindOnce(
1192             &WebController::DispatchKeyboardTextDownEvent,
1193             weak_ptr_factory_.GetWeakPtr(), node_frame_id, codepoints, index,
1194             /* delay= */ false, delay_in_millisecond, std::move(callback)),
1195         base::TimeDelta::FromMilliseconds(delay_in_millisecond));
1196     return;
1197   }
1198 
1199   devtools_client_->GetInput()->DispatchKeyEvent(
1200       CreateKeyEventParamsForCharacter(
1201           autofill_assistant::input::DispatchKeyEventType::KEY_DOWN,
1202           codepoints[index]),
1203       node_frame_id,
1204       base::BindOnce(&WebController::DispatchKeyboardTextUpEvent,
1205                      weak_ptr_factory_.GetWeakPtr(), node_frame_id, codepoints,
1206                      index, delay_in_millisecond, std::move(callback)));
1207 }
1208 
DispatchKeyboardTextUpEvent(const std::string & node_frame_id,const std::vector<UChar32> & codepoints,size_t index,int delay_in_millisecond,base::OnceCallback<void (const ClientStatus &)> callback)1209 void WebController::DispatchKeyboardTextUpEvent(
1210     const std::string& node_frame_id,
1211     const std::vector<UChar32>& codepoints,
1212     size_t index,
1213     int delay_in_millisecond,
1214     base::OnceCallback<void(const ClientStatus&)> callback) {
1215   DCHECK_LT(index, codepoints.size());
1216   devtools_client_->GetInput()->DispatchKeyEvent(
1217       CreateKeyEventParamsForCharacter(
1218           autofill_assistant::input::DispatchKeyEventType::KEY_UP,
1219           codepoints[index]),
1220       node_frame_id,
1221       base::BindOnce(
1222           &WebController::DispatchKeyboardTextDownEvent,
1223           weak_ptr_factory_.GetWeakPtr(), node_frame_id, codepoints, index + 1,
1224           /* delay= */ true, delay_in_millisecond, std::move(callback)));
1225 }
1226 
CreateKeyEventParamsForCharacter(autofill_assistant::input::DispatchKeyEventType type,UChar32 codepoint)1227 auto WebController::CreateKeyEventParamsForCharacter(
1228     autofill_assistant::input::DispatchKeyEventType type,
1229     UChar32 codepoint) -> DispatchKeyEventParamsPtr {
1230   auto params = input::DispatchKeyEventParams::Builder().SetType(type).Build();
1231 
1232   std::string text;
1233   if (AppendUnicodeToUTF8(codepoint, &text)) {
1234     params->SetText(text);
1235   } else {
1236 #ifdef NDEBUG
1237     VLOG(1) << __func__ << ": Failed to convert codepoint to UTF-8";
1238 #else
1239     DVLOG(1) << __func__
1240              << ": Failed to convert codepoint to UTF-8: " << codepoint;
1241 #endif
1242   }
1243 
1244   auto dom_key = ui::DomKey::FromCharacter(codepoint);
1245   if (dom_key.IsValid()) {
1246     params->SetKey(ui::KeycodeConverter::DomKeyToKeyString(dom_key));
1247   } else {
1248 #ifdef NDEBUG
1249     VLOG(1) << __func__ << ": Failed to set DomKey for codepoint";
1250 #else
1251     DVLOG(1) << __func__
1252              << ": Failed to set DomKey for codepoint: " << codepoint;
1253 #endif
1254   }
1255 
1256   return params;
1257 }
1258 
InternalSetFieldValue(const Selector & selector,const std::string & value,base::OnceCallback<void (const ClientStatus &)> callback)1259 void WebController::InternalSetFieldValue(
1260     const Selector& selector,
1261     const std::string& value,
1262     base::OnceCallback<void(const ClientStatus&)> callback) {
1263   FindElement(selector,
1264               /* strict_mode= */ true,
1265               base::BindOnce(&WebController::OnFindElementForSetFieldValue,
1266                              weak_ptr_factory_.GetWeakPtr(), value,
1267                              std::move(callback)));
1268 }
1269 
OnFindElementForSetFieldValue(const std::string & value,base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)1270 void WebController::OnFindElementForSetFieldValue(
1271     const std::string& value,
1272     base::OnceCallback<void(const ClientStatus&)> callback,
1273     const ClientStatus& status,
1274     std::unique_ptr<ElementFinder::Result> element_result) {
1275   if (!status.ok()) {
1276     std::move(callback).Run(status);
1277     return;
1278   }
1279 
1280   std::vector<std::unique_ptr<runtime::CallArgument>> argument;
1281   AddRuntimeCallArgument(value, &argument);
1282   devtools_client_->GetRuntime()->CallFunctionOn(
1283       runtime::CallFunctionOnParams::Builder()
1284           .SetObjectId(element_result->object_id)
1285           .SetArguments(std::move(argument))
1286           .SetFunctionDeclaration(std::string(kSetValueAttributeScript))
1287           .Build(),
1288       element_result->node_frame_id,
1289       base::BindOnce(&WebController::OnSetValueAttribute,
1290                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1291 }
1292 
OnSetValueAttribute(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)1293 void WebController::OnSetValueAttribute(
1294     base::OnceCallback<void(const ClientStatus&)> callback,
1295     const DevtoolsClient::ReplyStatus& reply_status,
1296     std::unique_ptr<runtime::CallFunctionOnResult> result) {
1297   std::move(callback).Run(
1298       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__));
1299 }
1300 
SetAttribute(const Selector & selector,const std::vector<std::string> & attribute,const std::string & value,base::OnceCallback<void (const ClientStatus &)> callback)1301 void WebController::SetAttribute(
1302     const Selector& selector,
1303     const std::vector<std::string>& attribute,
1304     const std::string& value,
1305     base::OnceCallback<void(const ClientStatus&)> callback) {
1306 #ifdef NDEBUG
1307   VLOG(3) << __func__ << " " << selector
1308           << ", attribute=(redacted), value=(redacted)";
1309 #else
1310   DVLOG(3) << __func__ << " " << selector << ", attribute=["
1311            << base::JoinString(attribute, ",") << "], value=" << value;
1312 #endif
1313 
1314   DCHECK(!selector.empty());
1315   DCHECK_GT(attribute.size(), 0u);
1316   FindElement(selector,
1317               /* strict_mode= */ true,
1318               base::BindOnce(&WebController::OnFindElementForSetAttribute,
1319                              weak_ptr_factory_.GetWeakPtr(), attribute, value,
1320                              std::move(callback)));
1321 }
1322 
OnFindElementForSetAttribute(const std::vector<std::string> & attribute,const std::string & value,base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)1323 void WebController::OnFindElementForSetAttribute(
1324     const std::vector<std::string>& attribute,
1325     const std::string& value,
1326     base::OnceCallback<void(const ClientStatus&)> callback,
1327     const ClientStatus& status,
1328     std::unique_ptr<ElementFinder::Result> element_result) {
1329   if (!status.ok()) {
1330     std::move(callback).Run(status);
1331     return;
1332   }
1333 
1334   base::Value::ListStorage attribute_values;
1335   for (const std::string& string : attribute) {
1336     attribute_values.emplace_back(base::Value(string));
1337   }
1338 
1339   std::vector<std::unique_ptr<runtime::CallArgument>> arguments;
1340   AddRuntimeCallArgument(attribute_values, &arguments);
1341   AddRuntimeCallArgument(value, &arguments);
1342   devtools_client_->GetRuntime()->CallFunctionOn(
1343       runtime::CallFunctionOnParams::Builder()
1344           .SetObjectId(element_result->object_id)
1345           .SetArguments(std::move(arguments))
1346           .SetFunctionDeclaration(std::string(kSetAttributeScript))
1347           .Build(),
1348       element_result->node_frame_id,
1349       base::BindOnce(&WebController::OnSetAttribute,
1350                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1351 }
1352 
OnSetAttribute(base::OnceCallback<void (const ClientStatus &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)1353 void WebController::OnSetAttribute(
1354     base::OnceCallback<void(const ClientStatus&)> callback,
1355     const DevtoolsClient::ReplyStatus& reply_status,
1356     std::unique_ptr<runtime::CallFunctionOnResult> result) {
1357   std::move(callback).Run(
1358       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__));
1359 }
1360 
SendKeyboardInput(const Selector & selector,const std::vector<UChar32> & codepoints,const int delay_in_millisecond,base::OnceCallback<void (const ClientStatus &)> callback)1361 void WebController::SendKeyboardInput(
1362     const Selector& selector,
1363     const std::vector<UChar32>& codepoints,
1364     const int delay_in_millisecond,
1365     base::OnceCallback<void(const ClientStatus&)> callback) {
1366   if (VLOG_IS_ON(3)) {
1367     std::string input_str;
1368     if (!UnicodeToUTF8(codepoints, &input_str)) {
1369       input_str.assign("<invalid input>");
1370     }
1371 #ifdef NDEBUG
1372     VLOG(3) << __func__ << " " << selector << ", input=(redacted)";
1373 #else
1374     DVLOG(3) << __func__ << " " << selector << ", input=" << input_str;
1375 #endif
1376   }
1377 
1378   DCHECK(!selector.empty());
1379   FindElement(
1380       selector, /* strict_mode= */ true,
1381       base::BindOnce(&WebController::OnFindElementForSendKeyboardInput,
1382                      weak_ptr_factory_.GetWeakPtr(), selector, codepoints,
1383                      delay_in_millisecond, std::move(callback)));
1384 }
1385 
OnFindElementForSendKeyboardInput(const Selector & selector,const std::vector<UChar32> & codepoints,const int delay_in_millisecond,base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)1386 void WebController::OnFindElementForSendKeyboardInput(
1387     const Selector& selector,
1388     const std::vector<UChar32>& codepoints,
1389     const int delay_in_millisecond,
1390     base::OnceCallback<void(const ClientStatus&)> callback,
1391     const ClientStatus& status,
1392     std::unique_ptr<ElementFinder::Result> element_result) {
1393   if (!status.ok()) {
1394     std::move(callback).Run(status);
1395     return;
1396   }
1397   ClickOrTapElement(
1398       selector, ClickAction::CLICK,
1399       base::BindOnce(&WebController::OnClickElementForSendKeyboardInput,
1400                      weak_ptr_factory_.GetWeakPtr(),
1401                      element_result->node_frame_id, codepoints,
1402                      delay_in_millisecond, std::move(callback)));
1403 }
1404 
OnClickElementForSendKeyboardInput(const std::string & node_frame_id,const std::vector<UChar32> & codepoints,int delay_in_millisecond,base::OnceCallback<void (const ClientStatus &)> callback,const ClientStatus & click_status)1405 void WebController::OnClickElementForSendKeyboardInput(
1406     const std::string& node_frame_id,
1407     const std::vector<UChar32>& codepoints,
1408     int delay_in_millisecond,
1409     base::OnceCallback<void(const ClientStatus&)> callback,
1410     const ClientStatus& click_status) {
1411   if (!click_status.ok()) {
1412     std::move(callback).Run(click_status);
1413     return;
1414   }
1415   DispatchKeyboardTextDownEvent(node_frame_id, codepoints, 0,
1416                                 /* delay= */ false, delay_in_millisecond,
1417                                 std::move(callback));
1418 }
1419 
GetVisualViewport(base::OnceCallback<void (bool,const RectF &)> callback)1420 void WebController::GetVisualViewport(
1421     base::OnceCallback<void(bool, const RectF&)> callback) {
1422   devtools_client_->GetRuntime()->Evaluate(
1423       runtime::EvaluateParams::Builder()
1424           .SetExpression(std::string(kGetVisualViewport))
1425           .SetReturnByValue(true)
1426           .Build(),
1427       /* node_frame_id= */ std::string(),
1428       base::BindOnce(&WebController::OnGetVisualViewport,
1429                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1430 }
1431 
OnGetVisualViewport(base::OnceCallback<void (bool,const RectF &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::EvaluateResult> result)1432 void WebController::OnGetVisualViewport(
1433     base::OnceCallback<void(bool, const RectF&)> callback,
1434     const DevtoolsClient::ReplyStatus& reply_status,
1435     std::unique_ptr<runtime::EvaluateResult> result) {
1436   ClientStatus status =
1437       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
1438   if (!status.ok() || !result->GetResult()->HasValue() ||
1439       !result->GetResult()->GetValue()->is_list() ||
1440       result->GetResult()->GetValue()->GetList().size() != 4u) {
1441     VLOG(1) << __func__ << " Failed to get visual viewport: " << status;
1442     RectF empty;
1443     std::move(callback).Run(false, empty);
1444     return;
1445   }
1446   const auto& list = result->GetResult()->GetValue()->GetList();
1447   // Value::GetDouble() is safe to call without checking the value type; it'll
1448   // return 0.0 if the value has the wrong type.
1449 
1450   float left = static_cast<float>(list[0].GetDouble());
1451   float top = static_cast<float>(list[1].GetDouble());
1452   float width = static_cast<float>(list[2].GetDouble());
1453   float height = static_cast<float>(list[3].GetDouble());
1454 
1455   RectF rect;
1456   rect.left = left;
1457   rect.top = top;
1458   rect.right = left + width;
1459   rect.bottom = top + height;
1460 
1461   std::move(callback).Run(true, rect);
1462 }
1463 
GetElementPosition(const Selector & selector,base::OnceCallback<void (bool,const RectF &)> callback)1464 void WebController::GetElementPosition(
1465     const Selector& selector,
1466     base::OnceCallback<void(bool, const RectF&)> callback) {
1467   FindElement(
1468       selector, /* strict_mode= */ true,
1469       base::BindOnce(&WebController::OnFindElementForPosition,
1470                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1471 }
1472 
OnFindElementForPosition(base::OnceCallback<void (bool,const RectF &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> result)1473 void WebController::OnFindElementForPosition(
1474     base::OnceCallback<void(bool, const RectF&)> callback,
1475     const ClientStatus& status,
1476     std::unique_ptr<ElementFinder::Result> result) {
1477   if (!status.ok()) {
1478     RectF empty;
1479     std::move(callback).Run(false, empty);
1480     return;
1481   }
1482   std::unique_ptr<ElementRectGetter> getter =
1483       std::make_unique<ElementRectGetter>(devtools_client_.get());
1484   auto* ptr = getter.get();
1485   pending_workers_.emplace_back(std::move(getter));
1486   ptr->Start(
1487       std::move(result),
1488       base::BindOnce(&WebController::OnGetElementRectResult,
1489                      weak_ptr_factory_.GetWeakPtr(), ptr, std::move(callback)));
1490 }
1491 
OnGetElementRectResult(ElementRectGetter * getter_to_release,base::OnceCallback<void (bool,const RectF &)> callback,bool has_rect,const RectF & element_rect)1492 void WebController::OnGetElementRectResult(
1493     ElementRectGetter* getter_to_release,
1494     base::OnceCallback<void(bool, const RectF&)> callback,
1495     bool has_rect,
1496     const RectF& element_rect) {
1497   base::EraseIf(pending_workers_, [getter_to_release](const auto& worker) {
1498     return worker.get() == getter_to_release;
1499   });
1500   std::move(callback).Run(has_rect, element_rect);
1501 }
1502 
GetOuterHtml(const Selector & selector,base::OnceCallback<void (const ClientStatus &,const std::string &)> callback)1503 void WebController::GetOuterHtml(
1504     const Selector& selector,
1505     base::OnceCallback<void(const ClientStatus&, const std::string&)>
1506         callback) {
1507   VLOG(3) << __func__ << " " << selector;
1508   FindElement(
1509       selector,
1510       /* strict_mode= */ true,
1511       base::BindOnce(&WebController::OnFindElementForGetOuterHtml,
1512                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1513 }
1514 
OnFindElementForGetOuterHtml(base::OnceCallback<void (const ClientStatus &,const std::string &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)1515 void WebController::OnFindElementForGetOuterHtml(
1516     base::OnceCallback<void(const ClientStatus&, const std::string&)> callback,
1517     const ClientStatus& status,
1518     std::unique_ptr<ElementFinder::Result> element_result) {
1519   if (!status.ok()) {
1520     VLOG(2) << __func__ << " Failed to find element for GetOuterHtml";
1521     std::move(callback).Run(status, "");
1522     return;
1523   }
1524 
1525   devtools_client_->GetRuntime()->CallFunctionOn(
1526       runtime::CallFunctionOnParams::Builder()
1527           .SetObjectId(element_result->object_id)
1528           .SetFunctionDeclaration(std::string(kGetOuterHtmlScript))
1529           .SetReturnByValue(true)
1530           .Build(),
1531       element_result->node_frame_id,
1532       base::BindOnce(&WebController::OnGetOuterHtml,
1533                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1534 }
1535 
OnGetOuterHtml(base::OnceCallback<void (const ClientStatus &,const std::string &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)1536 void WebController::OnGetOuterHtml(
1537     base::OnceCallback<void(const ClientStatus&, const std::string&)> callback,
1538     const DevtoolsClient::ReplyStatus& reply_status,
1539     std::unique_ptr<runtime::CallFunctionOnResult> result) {
1540   ClientStatus status =
1541       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
1542   if (!status.ok()) {
1543     VLOG(2) << __func__ << " Failed to get HTML content for GetOuterHtml";
1544     std::move(callback).Run(status, "");
1545     return;
1546   }
1547   std::string value;
1548   SafeGetStringValue(result->GetResult(), &value);
1549   std::move(callback).Run(OkClientStatus(), value);
1550 }
1551 
GetElementTag(const Selector & selector,base::OnceCallback<void (const ClientStatus &,const std::string &)> callback)1552 void WebController::GetElementTag(
1553     const Selector& selector,
1554     base::OnceCallback<void(const ClientStatus&, const std::string&)>
1555         callback) {
1556   VLOG(3) << __func__ << " " << selector;
1557   FindElement(
1558       selector,
1559       /* strict_mode= */ true,
1560       base::BindOnce(&WebController::OnFindElementForGetElementTag,
1561                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1562 }
1563 
OnFindElementForGetElementTag(base::OnceCallback<void (const ClientStatus &,const std::string &)> callback,const ClientStatus & status,std::unique_ptr<ElementFinder::Result> element_result)1564 void WebController::OnFindElementForGetElementTag(
1565     base::OnceCallback<void(const ClientStatus&, const std::string&)> callback,
1566     const ClientStatus& status,
1567     std::unique_ptr<ElementFinder::Result> element_result) {
1568   if (!status.ok()) {
1569     VLOG(2) << __func__ << " Failed to find element for GetElementTag";
1570     std::move(callback).Run(status, "");
1571     return;
1572   }
1573 
1574   devtools_client_->GetRuntime()->CallFunctionOn(
1575       runtime::CallFunctionOnParams::Builder()
1576           .SetObjectId(element_result->object_id)
1577           .SetFunctionDeclaration(std::string(kGetElementTagScript))
1578           .SetReturnByValue(true)
1579           .Build(),
1580       element_result->node_frame_id,
1581       base::BindOnce(&WebController::OnGetElementTag,
1582                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1583 }
1584 
OnGetElementTag(base::OnceCallback<void (const ClientStatus &,const std::string &)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)1585 void WebController::OnGetElementTag(
1586     base::OnceCallback<void(const ClientStatus&, const std::string&)> callback,
1587     const DevtoolsClient::ReplyStatus& reply_status,
1588     std::unique_ptr<runtime::CallFunctionOnResult> result) {
1589   ClientStatus status =
1590       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
1591   if (!status.ok()) {
1592     VLOG(2) << __func__ << " Failed to get element tag for GetElementTag";
1593     std::move(callback).Run(status, "");
1594     return;
1595   }
1596   std::string value;
1597   SafeGetStringValue(result->GetResult(), &value);
1598   std::move(callback).Run(OkClientStatus(), value);
1599 }
1600 
WaitForDocumentToBecomeInteractive(int remaining_rounds,const std::string & object_id,const std::string & node_frame_id,base::OnceCallback<void (bool)> callback)1601 void WebController::WaitForDocumentToBecomeInteractive(
1602     int remaining_rounds,
1603     const std::string& object_id,
1604     const std::string& node_frame_id,
1605     base::OnceCallback<void(bool)> callback) {
1606   devtools_client_->GetRuntime()->CallFunctionOn(
1607       runtime::CallFunctionOnParams::Builder()
1608           .SetObjectId(object_id)
1609           .SetFunctionDeclaration(std::string(kIsDocumentReadyForInteract))
1610           .SetReturnByValue(true)
1611           .Build(),
1612       node_frame_id,
1613       base::BindOnce(&WebController::OnWaitForDocumentToBecomeInteractive,
1614                      weak_ptr_factory_.GetWeakPtr(), remaining_rounds,
1615                      object_id, node_frame_id, std::move(callback)));
1616 }
1617 
OnWaitForDocumentToBecomeInteractive(int remaining_rounds,const std::string & object_id,const std::string & node_frame_id,base::OnceCallback<void (bool)> callback,const DevtoolsClient::ReplyStatus & reply_status,std::unique_ptr<runtime::CallFunctionOnResult> result)1618 void WebController::OnWaitForDocumentToBecomeInteractive(
1619     int remaining_rounds,
1620     const std::string& object_id,
1621     const std::string& node_frame_id,
1622     base::OnceCallback<void(bool)> callback,
1623     const DevtoolsClient::ReplyStatus& reply_status,
1624     std::unique_ptr<runtime::CallFunctionOnResult> result) {
1625   ClientStatus status =
1626       CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
1627   if (!status.ok() || remaining_rounds <= 0) {
1628     VLOG(1) << __func__
1629             << " Failed to wait for the document to become interactive with "
1630                "remaining_rounds: "
1631             << remaining_rounds;
1632     std::move(callback).Run(false);
1633     return;
1634   }
1635 
1636   bool ready;
1637   if (SafeGetBool(result->GetResult(), &ready) && ready) {
1638     std::move(callback).Run(true);
1639     return;
1640   }
1641 
1642   base::PostDelayedTask(
1643       FROM_HERE, {content::BrowserThread::UI},
1644       base::BindOnce(&WebController::WaitForDocumentToBecomeInteractive,
1645                      weak_ptr_factory_.GetWeakPtr(), --remaining_rounds,
1646                      object_id, node_frame_id, std::move(callback)),
1647       settings_->document_ready_check_interval);
1648 }
1649 
1650 }  // namespace autofill_assistant
1651