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