| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/autofill_assistant/browser/web_controller.h" |
| |
| #include <math.h> |
| #include <algorithm> |
| #include <ctime> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/i18n/char_iterator.h" |
| #include "base/logging.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/post_task.h" |
| #include "build/build_config.h" |
| #include "components/autofill/content/browser/content_autofill_driver.h" |
| #include "components/autofill/core/browser/autofill_manager.h" |
| #include "components/autofill/core/browser/credit_card.h" |
| #include "components/autofill/core/common/autofill_constants.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "components/autofill_assistant/browser/rectf.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace autofill_assistant { |
| using autofill::ContentAutofillDriver; |
| |
| namespace { |
| // Time between two periodic box model and document ready state checks. |
| static constexpr base::TimeDelta kPeriodicCheckInterval = |
| base::TimeDelta::FromMilliseconds(200); |
| |
| // Timeout after roughly 10 seconds (50*200ms). |
| constexpr int kPeriodicCheckRounds = 50; |
| |
| // Expiration time for the Autofill Assistant cookie. |
| constexpr int kCookieExpiresSeconds = 600; |
| |
| // Name and value used for the static cookie. |
| const char* const kAutofillAssistantCookieName = "autofill_assistant_cookie"; |
| const char* const kAutofillAssistantCookieValue = "true"; |
| |
| const char* const kGetBoundingClientRectAsList = |
| R"(function(node) { |
| const r = node.getBoundingClientRect(); |
| const v = window.visualViewport; |
| return [r.left, r.top, r.right, r.bottom, |
| v.offsetLeft, v.offsetTop, v.width, v.height]; |
| })"; |
| |
| const char* const kScrollIntoViewScript = |
| R"(function(node) { |
| const rect = node.getBoundingClientRect(); |
| if (rect.height < window.innerHeight) { |
| window.scrollBy({top: rect.top - window.innerHeight * 0.25, |
| behavior: 'smooth'}); |
| } else { |
| window.scrollBy({top: rect.top, behavior: 'smooth'}); |
| } |
| node.scrollIntoViewIfNeeded(); |
| })"; |
| |
| const char* const kScrollIntoViewIfNeededScript = |
| R"(function(node) { |
| node.scrollIntoViewIfNeeded(); |
| })"; |
| |
| // Javascript to select a value from a select box. Also fires a "change" event |
| // to trigger any listeners. Changing the index directly does not trigger this. |
| const char* const kSelectOptionScript = |
| R"(function(value) { |
| const uppercaseValue = value.toUpperCase(); |
| var found = false; |
| for (var i = 0; i < this.options.length; ++i) { |
| const label = this.options[i].label.toUpperCase(); |
| if (label.length > 0 && label.startsWith(uppercaseValue)) { |
| this.options.selectedIndex = i; |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| return false; |
| } |
| const e = document.createEvent('HTMLEvents'); |
| e.initEvent('change', true, true); |
| this.dispatchEvent(e); |
| return true; |
| })"; |
| |
| // Javascript to highlight an element. |
| const char* const kHighlightElementScript = |
| R"(function() { |
| this.style.boxShadow = '0px 0px 0px 3px white, ' + |
| '0px 0px 0px 6px rgb(66, 133, 244)'; |
| return true; |
| })"; |
| |
| // Javascript code to retrieve the 'value' attribute of a node. |
| const char* const kGetValueAttributeScript = |
| "function () { return this.value; }"; |
| |
| // Javascript code to set the 'value' attribute of a node and then fire a |
| // "change" event to trigger any listeners. |
| const char* const kSetValueAttributeScript = |
| R"(function (value) { |
| this.value = value; |
| const e = document.createEvent('HTMLEvents'); |
| e.initEvent('change', true, true); |
| this.dispatchEvent(e); |
| })"; |
| |
| // Javascript code to set an attribute of a node to a given value. |
| const char* const kSetAttributeScript = |
| R"(function (attribute, value) { |
| let receiver = this; |
| for (let i = 0; i < attribute.length - 1; i++) { |
| receiver = receiver[attribute[i]]; |
| } |
| receiver[attribute[attribute.length - 1]] = value; |
| })"; |
| |
| // Javascript code to get the outerHTML of a node. |
| // TODO(crbug.com/806868): Investigate if using DOM.GetOuterHtml would be a |
| // better solution than injecting Javascript code. |
| const char* const kGetOuterHtmlScript = |
| "function () { return this.outerHTML; }"; |
| |
| // Javascript code to get document root element. |
| const char* const kGetDocumentElement = |
| R"( |
| (function() { |
| return document.documentElement; |
| }()) |
| )"; |
| |
| // Javascript code to query all elements for a selector. |
| const char* const kQuerySelectorAll = |
| R"(function (selector, strictMode) { |
| var found = this.querySelectorAll(selector); |
| if(found.length == 1) |
| return found[0]; |
| if(found.length > 1 && !strictMode) |
| return found[0]; |
| return undefined; |
| })"; |
| |
| // Javascript code to query whether the document is ready for interact. |
| const char* const kIsDocumentReadyForInteract = |
| R"(function () { |
| return document.readyState == 'interactive' |
| || document.readyState == 'complete'; |
| })"; |
| |
| bool ConvertPseudoType(const PseudoType pseudo_type, |
| dom::PseudoType* pseudo_type_output) { |
| switch (pseudo_type) { |
| case PseudoType::UNDEFINED: |
| break; |
| case PseudoType::FIRST_LINE: |
| *pseudo_type_output = dom::PseudoType::FIRST_LINE; |
| return true; |
| case PseudoType::FIRST_LETTER: |
| *pseudo_type_output = dom::PseudoType::FIRST_LETTER; |
| return true; |
| case PseudoType::BEFORE: |
| *pseudo_type_output = dom::PseudoType::BEFORE; |
| return true; |
| case PseudoType::AFTER: |
| *pseudo_type_output = dom::PseudoType::AFTER; |
| return true; |
| case PseudoType::BACKDROP: |
| *pseudo_type_output = dom::PseudoType::BACKDROP; |
| return true; |
| case PseudoType::SELECTION: |
| *pseudo_type_output = dom::PseudoType::SELECTION; |
| return true; |
| case PseudoType::FIRST_LINE_INHERITED: |
| *pseudo_type_output = dom::PseudoType::FIRST_LINE_INHERITED; |
| return true; |
| case PseudoType::SCROLLBAR: |
| *pseudo_type_output = dom::PseudoType::SCROLLBAR; |
| return true; |
| case PseudoType::SCROLLBAR_THUMB: |
| *pseudo_type_output = dom::PseudoType::SCROLLBAR_THUMB; |
| return true; |
| case PseudoType::SCROLLBAR_BUTTON: |
| *pseudo_type_output = dom::PseudoType::SCROLLBAR_BUTTON; |
| return true; |
| case PseudoType::SCROLLBAR_TRACK: |
| *pseudo_type_output = dom::PseudoType::SCROLLBAR_TRACK; |
| return true; |
| case PseudoType::SCROLLBAR_TRACK_PIECE: |
| *pseudo_type_output = dom::PseudoType::SCROLLBAR_TRACK_PIECE; |
| return true; |
| case PseudoType::SCROLLBAR_CORNER: |
| *pseudo_type_output = dom::PseudoType::SCROLLBAR_CORNER; |
| return true; |
| case PseudoType::RESIZER: |
| *pseudo_type_output = dom::PseudoType::RESIZER; |
| return true; |
| case PseudoType::INPUT_LIST_BUTTON: |
| *pseudo_type_output = dom::PseudoType::INPUT_LIST_BUTTON; |
| return true; |
| } |
| return false; |
| } |
| } // namespace |
| |
| WebController::ElementPositionGetter::ElementPositionGetter() |
| : weak_ptr_factory_(this) {} |
| WebController::ElementPositionGetter::~ElementPositionGetter() = default; |
| |
| void WebController::ElementPositionGetter::Start( |
| content::RenderFrameHost* frame_host, |
| DevtoolsClient* devtools_client, |
| std::string element_object_id, |
| ElementPositionCallback callback) { |
| devtools_client_ = devtools_client; |
| object_id_ = element_object_id; |
| callback_ = std::move(callback); |
| remaining_rounds_ = kPeriodicCheckRounds; |
| |
| // Wait for a roundtrips through the renderer and compositor pipeline, |
| // otherwise touch event may be dropped because of missing handler. |
| // Note that mouse left button will always be send to the renderer, but it |
| // is slightly better to wait for the changes, like scroll, to be visualized |
| // in compositor as real interaction. |
| frame_host->InsertVisualStateCallback(base::BindOnce( |
| &WebController::ElementPositionGetter::OnVisualStateUpdatedCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| GetAndWaitBoxModelStable(); |
| } |
| |
| void WebController::ElementPositionGetter::OnVisualStateUpdatedCallback( |
| bool success) { |
| if (success) { |
| visual_state_updated_ = true; |
| return; |
| } |
| |
| OnError(); |
| } |
| |
| void WebController::ElementPositionGetter::GetAndWaitBoxModelStable() { |
| devtools_client_->GetDOM()->GetBoxModel( |
| dom::GetBoxModelParams::Builder().SetObjectId(object_id_).Build(), |
| base::BindOnce( |
| &WebController::ElementPositionGetter::OnGetBoxModelForStableCheck, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void WebController::ElementPositionGetter::OnGetBoxModelForStableCheck( |
| std::unique_ptr<dom::GetBoxModelResult> result) { |
| if (!result || !result->GetModel() || !result->GetModel()->GetContent()) { |
| DVLOG(1) << __func__ << " Failed to get box model."; |
| OnError(); |
| return; |
| } |
| |
| // Return the center of the element. |
| const std::vector<double>* content_box = result->GetModel()->GetContent(); |
| DCHECK_EQ(content_box->size(), 8u); |
| int new_point_x = |
| round((round((*content_box)[0]) + round((*content_box)[2])) * 0.5); |
| int new_point_y = |
| round((round((*content_box)[3]) + round((*content_box)[5])) * 0.5); |
| |
| // Wait for at least three rounds (~600ms = |
| // 3*kPeriodicCheckInterval) for visual state update callback since |
| // it might take longer time to return or never return if no updates. |
| DCHECK(kPeriodicCheckRounds > 2 && kPeriodicCheckRounds >= remaining_rounds_); |
| if (has_point_ && new_point_x == point_x_ && new_point_y == point_y_ && |
| (visual_state_updated_ || remaining_rounds_ + 2 < kPeriodicCheckRounds)) { |
| // Note that there is still a chance that the element's position has been |
| // changed after the last call of GetBoxModel, however, it might be safe |
| // to assume the element's position will not be changed before issuing |
| // click or tap event after stable for kPeriodicCheckInterval. In |
| // addition, checking again after issuing click or tap event doesn't help |
| // since the change may be expected. |
| OnResult(new_point_x, new_point_y); |
| return; |
| } |
| |
| if (remaining_rounds_ <= 0) { |
| OnError(); |
| return; |
| } |
| |
| bool is_first_round = !has_point_; |
| has_point_ = true; |
| point_x_ = new_point_x; |
| point_y_ = new_point_y; |
| |
| // Scroll the element into view again if it was moved out of view, starting |
| // from the second round. |
| if (!is_first_round) { |
| std::vector<std::unique_ptr<runtime::CallArgument>> argument; |
| argument.emplace_back( |
| runtime::CallArgument::Builder().SetObjectId(object_id_).Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id_) |
| .SetArguments(std::move(argument)) |
| .SetFunctionDeclaration(std::string(kScrollIntoViewIfNeededScript)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::ElementPositionGetter::OnScrollIntoView, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| --remaining_rounds_; |
| base::PostDelayedTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce( |
| &WebController::ElementPositionGetter::GetAndWaitBoxModelStable, |
| weak_ptr_factory_.GetWeakPtr()), |
| kPeriodicCheckInterval); |
| } |
| |
| void WebController::ElementPositionGetter::OnScrollIntoView( |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || result->HasExceptionDetails()) { |
| DVLOG(1) << __func__ << " Failed to scroll the element."; |
| OnError(); |
| return; |
| } |
| |
| --remaining_rounds_; |
| base::PostDelayedTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce( |
| &WebController::ElementPositionGetter::GetAndWaitBoxModelStable, |
| weak_ptr_factory_.GetWeakPtr()), |
| kPeriodicCheckInterval); |
| } |
| |
| void WebController::ElementPositionGetter::OnResult(int x, int y) { |
| if (callback_) { |
| std::move(callback_).Run(/* success= */ true, x, y); |
| } |
| } |
| |
| void WebController::ElementPositionGetter::OnError() { |
| if (callback_) { |
| std::move(callback_).Run(/* success= */ false, /* x= */ 0, /* y= */ 0); |
| } |
| } |
| |
| // static |
| std::unique_ptr<WebController> WebController::CreateForWebContents( |
| content::WebContents* web_contents) { |
| return std::make_unique<WebController>( |
| web_contents, |
| std::make_unique<DevtoolsClient>( |
| content::DevToolsAgentHost::GetOrCreateFor(web_contents))); |
| } |
| |
| WebController::WebController(content::WebContents* web_contents, |
| std::unique_ptr<DevtoolsClient> devtools_client) |
| : web_contents_(web_contents), |
| devtools_client_(std::move(devtools_client)), |
| weak_ptr_factory_(this) {} |
| |
| WebController::~WebController() {} |
| |
| WebController::FillFormInputData::FillFormInputData() {} |
| |
| WebController::FillFormInputData::~FillFormInputData() {} |
| |
| void WebController::LoadURL(const GURL& url) { |
| DVLOG(3) << __func__ << " " << url; |
| web_contents_->GetController().LoadURLWithParams( |
| content::NavigationController::LoadURLParams(url)); |
| } |
| |
| void WebController::ClickOrTapElement(const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " " << selector; |
| #if defined(OS_ANDROID) |
| TapElement(selector, std::move(callback)); |
| #else |
| // TODO(crbug.com/806868): Remove 'ClickElement' since this feature is only |
| // available on Android. |
| ClickElement(selector, std::move(callback)); |
| #endif |
| } |
| |
| void WebController::ClickElement(const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(!selector.empty()); |
| FindElement(selector, /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForClickOrTap, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback), /* is_a_click= */ true)); |
| } |
| |
| void WebController::TapElement(const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(!selector.empty()); |
| FindElement(selector, /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForClickOrTap, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback), /* is_a_click= */ false)); |
| } |
| |
| void WebController::OnFindElementForClickOrTap( |
| base::OnceCallback<void(bool)> callback, |
| bool is_a_click, |
| std::unique_ptr<FindElementResult> result) { |
| // Found element must belong to a frame. |
| if (!result->container_frame_host || result->object_id.empty()) { |
| DVLOG(1) << __func__ << " Failed to find the element to click or tap."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| std::string element_object_id = result->object_id; |
| WaitForDocumentToBecomeInteractive( |
| kPeriodicCheckRounds, element_object_id, |
| base::BindOnce( |
| &WebController::OnWaitDocumentToBecomeInteractiveForClickOrTap, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), is_a_click, |
| std::move(result))); |
| } |
| |
| void WebController::OnWaitDocumentToBecomeInteractiveForClickOrTap( |
| base::OnceCallback<void(bool)> callback, |
| bool is_a_click, |
| std::unique_ptr<FindElementResult> target_element, |
| bool result) { |
| if (!result) { |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| ClickOrTapElement(std::move(target_element), is_a_click, std::move(callback)); |
| } |
| |
| void WebController::ClickOrTapElement( |
| std::unique_ptr<FindElementResult> target_element, |
| bool is_a_click, |
| base::OnceCallback<void(bool)> callback) { |
| std::string element_object_id = target_element->object_id; |
| std::vector<std::unique_ptr<runtime::CallArgument>> argument; |
| argument.emplace_back( |
| runtime::CallArgument::Builder().SetObjectId(element_object_id).Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(element_object_id) |
| .SetArguments(std::move(argument)) |
| .SetFunctionDeclaration(std::string(kScrollIntoViewScript)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::OnScrollIntoView, |
| weak_ptr_factory_.GetWeakPtr(), std::move(target_element), |
| std::move(callback), is_a_click)); |
| } |
| |
| void WebController::OnScrollIntoView( |
| std::unique_ptr<FindElementResult> target_element, |
| base::OnceCallback<void(bool)> callback, |
| bool is_a_click, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || result->HasExceptionDetails()) { |
| DVLOG(1) << __func__ << " Failed to scroll the element."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| ElementPositionGetter* element_position_checker = new ElementPositionGetter(); |
| element_position_checker->Start( |
| target_element->container_frame_host, devtools_client_.get(), |
| target_element->object_id, |
| base::BindOnce(&WebController::TapOrClickOnCoordinates, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::WrapUnique(element_position_checker), |
| std::move(callback), is_a_click)); |
| } |
| |
| void WebController::TapOrClickOnCoordinates( |
| std::unique_ptr<ElementPositionGetter> element_position_getter, |
| base::OnceCallback<void(bool)> callback, |
| bool is_a_click, |
| bool has_coordinates, |
| int x, |
| int y) { |
| if (!has_coordinates) { |
| DVLOG(1) << __func__ << " Failed to get element position."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| if (is_a_click) { |
| devtools_client_->GetInput()->DispatchMouseEvent( |
| input::DispatchMouseEventParams::Builder() |
| .SetX(x) |
| .SetY(y) |
| .SetClickCount(1) |
| .SetButton(input::DispatchMouseEventButton::LEFT) |
| .SetType(input::DispatchMouseEventType::MOUSE_PRESSED) |
| .Build(), |
| base::BindOnce(&WebController::OnDispatchPressMouseEvent, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), x, |
| y)); |
| return; |
| } |
| |
| std::vector<std::unique_ptr<::autofill_assistant::input::TouchPoint>> |
| touch_points; |
| touch_points.emplace_back( |
| input::TouchPoint::Builder().SetX(x).SetY(y).Build()); |
| devtools_client_->GetInput()->DispatchTouchEvent( |
| input::DispatchTouchEventParams::Builder() |
| .SetType(input::DispatchTouchEventType::TOUCH_START) |
| .SetTouchPoints(std::move(touch_points)) |
| .Build(), |
| base::BindOnce(&WebController::OnDispatchTouchEventStart, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnDispatchPressMouseEvent( |
| base::OnceCallback<void(bool)> callback, |
| int x, |
| int y, |
| std::unique_ptr<input::DispatchMouseEventResult> result) { |
| if (!result) { |
| DVLOG(1) << __func__ |
| << " Failed to dispatch mouse left button pressed event."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| devtools_client_->GetInput()->DispatchMouseEvent( |
| input::DispatchMouseEventParams::Builder() |
| .SetX(x) |
| .SetY(y) |
| .SetClickCount(1) |
| .SetButton(input::DispatchMouseEventButton::LEFT) |
| .SetType(input::DispatchMouseEventType::MOUSE_RELEASED) |
| .Build(), |
| base::BindOnce(&WebController::OnDispatchReleaseMouseEvent, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnDispatchReleaseMouseEvent( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<input::DispatchMouseEventResult> result) { |
| OnResult(true, std::move(callback)); |
| } |
| |
| void WebController::OnDispatchTouchEventStart( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<input::DispatchTouchEventResult> result) { |
| if (!result) { |
| DVLOG(1) << __func__ << " Failed to dispatch touch start event."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| std::vector<std::unique_ptr<::autofill_assistant::input::TouchPoint>> |
| touch_points; |
| devtools_client_->GetInput()->DispatchTouchEvent( |
| input::DispatchTouchEventParams::Builder() |
| .SetType(input::DispatchTouchEventType::TOUCH_END) |
| .SetTouchPoints(std::move(touch_points)) |
| .Build(), |
| base::BindOnce(&WebController::OnDispatchTouchEventEnd, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnDispatchTouchEventEnd( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<input::DispatchTouchEventResult> result) { |
| DCHECK(result); |
| OnResult(true, std::move(callback)); |
| } |
| |
| void WebController::ElementCheck(ElementCheckType check_type, |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(!selector.empty()); |
| // We don't use strict_mode because we only check for the existence of at |
| // least one such element and we don't act on it. |
| FindElement(selector, /* strict_mode= */ false, |
| base::BindOnce(&WebController::OnFindElementForCheck, |
| weak_ptr_factory_.GetWeakPtr(), check_type, |
| std::move(callback))); |
| } |
| |
| void WebController::OnFindElementForCheck( |
| ElementCheckType check_type, |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<FindElementResult> result) { |
| if (result->object_id.empty()) { |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| if (check_type == kExistenceCheck) { |
| OnResult(true, std::move(callback)); |
| return; |
| } |
| DCHECK_EQ(check_type, kVisibilityCheck); |
| |
| devtools_client_->GetDOM()->GetBoxModel( |
| dom::GetBoxModelParams::Builder().SetObjectId(result->object_id).Build(), |
| base::BindOnce(&WebController::OnGetBoxModelForVisible, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnGetBoxModelForVisible( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<dom::GetBoxModelResult> result) { |
| OnResult(result && result->GetModel() && result->GetModel()->GetContent(), |
| std::move(callback)); |
| } |
| |
| void WebController::FindElement(const Selector& selector, |
| bool strict_mode, |
| FindElementCallback callback) { |
| devtools_client_->GetRuntime()->Evaluate( |
| std::string(kGetDocumentElement), |
| base::BindOnce(&WebController::OnGetDocumentElement, |
| weak_ptr_factory_.GetWeakPtr(), selector, strict_mode, |
| std::move(callback))); |
| } |
| |
| void WebController::OnGetDocumentElement( |
| const Selector& selector, |
| bool strict_mode, |
| FindElementCallback callback, |
| std::unique_ptr<runtime::EvaluateResult> result) { |
| std::unique_ptr<FindElementResult> element_result = |
| std::make_unique<FindElementResult>(); |
| element_result->container_frame_host = web_contents_->GetMainFrame(); |
| element_result->container_frame_selector_index = 0; |
| element_result->object_id = ""; |
| if (!result || !result->GetResult() || !result->GetResult()->HasObjectId()) { |
| DVLOG(1) << __func__ << " Failed to get document root element."; |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| |
| RecursiveFindElement(result->GetResult()->GetObjectId(), 0, selector, |
| strict_mode, std::move(element_result), |
| std::move(callback)); |
| } |
| |
| void WebController::RecursiveFindElement( |
| const std::string& object_id, |
| size_t index, |
| const Selector& selector, |
| bool strict_mode, |
| std::unique_ptr<FindElementResult> element_result, |
| FindElementCallback callback) { |
| std::vector<std::unique_ptr<runtime::CallArgument>> argument; |
| argument.emplace_back(runtime::CallArgument::Builder() |
| .SetValue(base::Value::ToUniquePtrValue( |
| base::Value(selector.selectors[index]))) |
| .Build()); |
| argument.emplace_back( |
| runtime::CallArgument::Builder() |
| .SetValue(base::Value::ToUniquePtrValue(base::Value(strict_mode))) |
| .Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id) |
| .SetArguments(std::move(argument)) |
| .SetFunctionDeclaration(std::string(kQuerySelectorAll)) |
| .Build(), |
| base::BindOnce(&WebController::OnQuerySelectorAll, |
| weak_ptr_factory_.GetWeakPtr(), index, selector, |
| strict_mode, std::move(element_result), |
| std::move(callback))); |
| } |
| |
| void WebController::OnQuerySelectorAll( |
| size_t index, |
| const Selector& selector, |
| bool strict_mode, |
| std::unique_ptr<FindElementResult> element_result, |
| FindElementCallback callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || !result->GetResult() || !result->GetResult()->HasObjectId()) { |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| |
| if (selector.selectors.size() == index + 1) { |
| // The pseudo type is associated to the final element matched by |
| // |selector|, which means that we currently don't handle matching an |
| // element inside a pseudo element. |
| if (selector.pseudo_type == PseudoType::UNDEFINED) { |
| // Return object id of the element. |
| element_result->object_id = result->GetResult()->GetObjectId(); |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| |
| // We are looking for a pseudo element associated with this element. |
| dom::PseudoType pseudo_type; |
| if (!ConvertPseudoType(selector.pseudo_type, &pseudo_type)) { |
| // Return empty result. |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| |
| devtools_client_->GetDOM()->DescribeNode( |
| dom::DescribeNodeParams::Builder() |
| .SetObjectId(result->GetResult()->GetObjectId()) |
| .Build(), |
| base::BindOnce(&WebController::OnDescribeNodeForPseudoElement, |
| weak_ptr_factory_.GetWeakPtr(), pseudo_type, |
| std::move(element_result), std::move(callback))); |
| return; |
| } |
| |
| devtools_client_->GetDOM()->DescribeNode( |
| dom::DescribeNodeParams::Builder() |
| .SetObjectId(result->GetResult()->GetObjectId()) |
| .Build(), |
| base::BindOnce( |
| &WebController::OnDescribeNode, weak_ptr_factory_.GetWeakPtr(), |
| result->GetResult()->GetObjectId(), index, selector, strict_mode, |
| std::move(element_result), std::move(callback))); |
| } |
| |
| void WebController::OnDescribeNodeForPseudoElement( |
| dom::PseudoType pseudo_type, |
| std::unique_ptr<FindElementResult> element_result, |
| FindElementCallback callback, |
| std::unique_ptr<dom::DescribeNodeResult> result) { |
| if (!result || !result->GetNode()) { |
| DVLOG(1) << __func__ << " Failed to describe the node for pseudo element."; |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| |
| auto* node = result->GetNode(); |
| if (node->HasPseudoElements()) { |
| for (const auto& pseudo_element : *(node->GetPseudoElements())) { |
| if (pseudo_element->HasPseudoType() && |
| pseudo_element->GetPseudoType() == pseudo_type) { |
| devtools_client_->GetDOM()->ResolveNode( |
| dom::ResolveNodeParams::Builder() |
| .SetBackendNodeId(pseudo_element->GetBackendNodeId()) |
| .Build(), |
| base::BindOnce(&WebController::OnResolveNodeForPseudoElement, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(element_result), std::move(callback))); |
| return; |
| } |
| } |
| } |
| |
| // Failed to find the pseudo element: run the callback with empty result. |
| std::move(callback).Run(std::move(element_result)); |
| } |
| |
| void WebController::OnResolveNodeForPseudoElement( |
| std::unique_ptr<FindElementResult> element_result, |
| FindElementCallback callback, |
| std::unique_ptr<dom::ResolveNodeResult> result) { |
| if (result && result->GetObject() && result->GetObject()->HasObjectId()) { |
| element_result->object_id = result->GetObject()->GetObjectId(); |
| } |
| |
| std::move(callback).Run(std::move(element_result)); |
| } |
| |
| void WebController::OnDescribeNode( |
| const std::string& object_id, |
| size_t index, |
| const Selector& selector, |
| bool strict_mode, |
| std::unique_ptr<FindElementResult> element_result, |
| FindElementCallback callback, |
| std::unique_ptr<dom::DescribeNodeResult> result) { |
| if (!result || !result->GetNode()) { |
| DVLOG(1) << __func__ << " Failed to describe the node."; |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| |
| auto* node = result->GetNode(); |
| std::vector<int> backend_ids; |
| if (node->HasContentDocument()) { |
| backend_ids.emplace_back(node->GetContentDocument()->GetBackendNodeId()); |
| |
| element_result->container_frame_selector_index = index; |
| |
| // Find out the corresponding render frame host through document url and |
| // name. |
| // TODO(crbug.com/806868): Use more attributes to find out the render frame |
| // host if name and document url are not enough to uniquely identify it. |
| std::string frame_name; |
| if (node->HasAttributes()) { |
| const std::vector<std::string>* attributes = node->GetAttributes(); |
| for (size_t i = 0; i < attributes->size();) { |
| if ((*attributes)[i] == "name") { |
| frame_name = (*attributes)[i + 1]; |
| break; |
| } |
| // Jump two positions since attribute name and value are always paired. |
| i = i + 2; |
| } |
| } |
| element_result->container_frame_host = FindCorrespondingRenderFrameHost( |
| frame_name, node->GetContentDocument()->GetDocumentURL()); |
| if (!element_result->container_frame_host) { |
| DVLOG(1) << __func__ << " Failed to find corresponding owner frame."; |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| } else if (node->HasFrameId()) { |
| // TODO(crbug.com/806868): Support out-of-process iframe. |
| DVLOG(3) << "Warning (unsupported): the element is inside an OOPIF."; |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| |
| if (node->HasShadowRoots()) { |
| // TODO(crbug.com/806868): Support multiple shadow roots. |
| backend_ids.emplace_back( |
| node->GetShadowRoots()->front()->GetBackendNodeId()); |
| } |
| |
| if (!backend_ids.empty()) { |
| devtools_client_->GetDOM()->ResolveNode( |
| dom::ResolveNodeParams::Builder() |
| .SetBackendNodeId(backend_ids[0]) |
| .Build(), |
| base::BindOnce(&WebController::OnResolveNode, |
| weak_ptr_factory_.GetWeakPtr(), index, selector, |
| strict_mode, std::move(element_result), |
| std::move(callback))); |
| return; |
| } |
| |
| RecursiveFindElement(object_id, ++index, selector, strict_mode, |
| std::move(element_result), std::move(callback)); |
| } |
| |
| void WebController::OnResolveNode( |
| size_t index, |
| const Selector& selector, |
| bool strict_mode, |
| std::unique_ptr<FindElementResult> element_result, |
| FindElementCallback callback, |
| std::unique_ptr<dom::ResolveNodeResult> result) { |
| if (!result || !result->GetObject() || !result->GetObject()->HasObjectId()) { |
| DVLOG(1) << __func__ << " Failed to resolve object id from backend id."; |
| std::move(callback).Run(std::move(element_result)); |
| return; |
| } |
| |
| RecursiveFindElement(result->GetObject()->GetObjectId(), ++index, selector, |
| strict_mode, std::move(element_result), |
| std::move(callback)); |
| } |
| |
| content::RenderFrameHost* WebController::FindCorrespondingRenderFrameHost( |
| std::string name, |
| std::string document_url) { |
| content::RenderFrameHost* ret_frame = nullptr; |
| for (auto* frame : web_contents_->GetAllFrames()) { |
| if (frame->GetFrameName() == name && |
| frame->GetLastCommittedURL().spec() == document_url) { |
| DCHECK(!ret_frame); |
| ret_frame = frame; |
| } |
| } |
| |
| return ret_frame; |
| } |
| |
| void WebController::OnResult(bool result, |
| base::OnceCallback<void(bool)> callback) { |
| std::move(callback).Run(result); |
| } |
| |
| void WebController::OnResult( |
| bool exists, |
| const std::string& value, |
| base::OnceCallback<void(bool, const std::string&)> callback) { |
| std::move(callback).Run(exists, value); |
| } |
| |
| void WebController::OnFindElementForFocusElement( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| if (element_result->object_id.empty()) { |
| DVLOG(1) << __func__ << " Failed to find the element to focus on."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| std::vector<std::unique_ptr<runtime::CallArgument>> argument; |
| argument.emplace_back(runtime::CallArgument::Builder() |
| .SetObjectId(element_result->object_id) |
| .Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(element_result->object_id) |
| .SetArguments(std::move(argument)) |
| .SetFunctionDeclaration(std::string(kScrollIntoViewScript)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::OnFocusElement, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnFocusElement( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || result->HasExceptionDetails()) { |
| DVLOG(1) << __func__ << " Failed to focus on element."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| OnResult(true, std::move(callback)); |
| } |
| |
| void WebController::FillAddressForm(const autofill::AutofillProfile* profile, |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << selector; |
| auto data_to_autofill = std::make_unique<FillFormInputData>(); |
| data_to_autofill->profile = |
| std::make_unique<autofill::AutofillProfile>(*profile); |
| FindElement(selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForFillingForm, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(data_to_autofill), selector, |
| std::move(callback))); |
| } |
| |
| void WebController::OnFindElementForFillingForm( |
| std::unique_ptr<FillFormInputData> data_to_autofill, |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| if (element_result->object_id.empty()) { |
| DVLOG(1) << __func__ << " Failed to find the element for filling the form."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| ContentAutofillDriver* driver = ContentAutofillDriver::GetForRenderFrameHost( |
| element_result->container_frame_host); |
| DCHECK(!selector.empty()); |
| // TODO(crbug.com/806868): Figure out whether there are cases where we need |
| // more than one selector, and come up with a solution that can figure out the |
| // right number of selectors to include. |
| driver->GetAutofillAgent()->GetElementFormAndFieldData( |
| std::vector<std::string>(1, selector.selectors.back()), |
| base::BindOnce(&WebController::OnGetFormAndFieldDataForFillingForm, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(data_to_autofill), std::move(callback), |
| element_result->container_frame_host)); |
| } |
| |
| void WebController::OnGetFormAndFieldDataForFillingForm( |
| std::unique_ptr<FillFormInputData> data_to_autofill, |
| base::OnceCallback<void(bool)> callback, |
| content::RenderFrameHost* container_frame_host, |
| const autofill::FormData& form_data, |
| const autofill::FormFieldData& form_field) { |
| if (form_data.fields.empty()) { |
| DVLOG(1) << __func__ << " Failed to get form data to fill form."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| ContentAutofillDriver* driver = |
| ContentAutofillDriver::GetForRenderFrameHost(container_frame_host); |
| if (!driver) { |
| DVLOG(1) << __func__ << " Failed to get the autofill driver."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| if (data_to_autofill->card) { |
| driver->autofill_manager()->FillCreditCardForm( |
| autofill::kNoQueryId, form_data, form_field, *data_to_autofill->card, |
| data_to_autofill->cvc); |
| } else { |
| driver->autofill_manager()->FillProfileForm(*data_to_autofill->profile, |
| form_data, form_field); |
| } |
| |
| OnResult(true, std::move(callback)); |
| } |
| |
| void WebController::FillCardForm(std::unique_ptr<autofill::CreditCard> card, |
| const base::string16& cvc, |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " " << selector; |
| auto data_to_autofill = std::make_unique<FillFormInputData>(); |
| data_to_autofill->card = std::move(card); |
| data_to_autofill->cvc = cvc; |
| FindElement(selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForFillingForm, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(data_to_autofill), selector, |
| std::move(callback))); |
| } |
| |
| void WebController::SelectOption(const Selector& selector, |
| const std::string& selected_option, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " " << selector << ", option=" << selected_option; |
| FindElement(selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForSelectOption, |
| weak_ptr_factory_.GetWeakPtr(), selected_option, |
| std::move(callback))); |
| } |
| |
| void WebController::OnFindElementForSelectOption( |
| const std::string& selected_option, |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| const std::string object_id = element_result->object_id; |
| if (object_id.empty()) { |
| DVLOG(1) << __func__ << " Failed to find the element to select an option."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| std::vector<std::unique_ptr<runtime::CallArgument>> argument; |
| argument.emplace_back( |
| runtime::CallArgument::Builder() |
| .SetValue(base::Value::ToUniquePtrValue(base::Value(selected_option))) |
| .Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id) |
| .SetArguments(std::move(argument)) |
| .SetFunctionDeclaration(std::string(kSelectOptionScript)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::OnSelectOption, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnSelectOption( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || result->HasExceptionDetails()) { |
| DVLOG(1) << __func__ << " Failed to select option."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| // Read the result returned from Javascript code. |
| DCHECK(result->GetResult()->GetValue()->is_bool()); |
| OnResult(result->GetResult()->GetValue()->GetBool(), std::move(callback)); |
| } |
| |
| void WebController::HighlightElement(const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " " << selector; |
| FindElement( |
| selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForHighlightElement, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnFindElementForHighlightElement( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| const std::string object_id = element_result->object_id; |
| if (object_id.empty()) { |
| DVLOG(1) << __func__ << " Failed to find the element to highlight."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| std::vector<std::unique_ptr<runtime::CallArgument>> argument; |
| argument.emplace_back( |
| runtime::CallArgument::Builder().SetObjectId(object_id).Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id) |
| .SetArguments(std::move(argument)) |
| .SetFunctionDeclaration(std::string(kHighlightElementScript)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::OnHighlightElement, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnHighlightElement( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || result->HasExceptionDetails()) { |
| DVLOG(1) << __func__ << " Failed to highlight element."; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| // Read the result returned from Javascript code. |
| DCHECK(result->GetResult()->GetValue()->is_bool()); |
| OnResult(result->GetResult()->GetValue()->GetBool(), std::move(callback)); |
| } |
| |
| void WebController::FocusElement(const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " " << selector; |
| DCHECK(!selector.empty()); |
| FindElement( |
| selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForFocusElement, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::GetFieldValue( |
| const Selector& selector, |
| base::OnceCallback<void(bool, const std::string&)> callback) { |
| FindElement( |
| selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForGetFieldValue, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnFindElementForGetFieldValue( |
| base::OnceCallback<void(bool, const std::string&)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| const std::string object_id = element_result->object_id; |
| if (object_id.empty()) { |
| OnResult(/* exists= */ false, "", std::move(callback)); |
| return; |
| } |
| |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id) |
| .SetFunctionDeclaration(std::string(kGetValueAttributeScript)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::OnGetValueAttribute, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnGetValueAttribute( |
| base::OnceCallback<void(bool, const std::string&)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || result->HasExceptionDetails()) { |
| OnResult(/* exists= */ true, "", std::move(callback)); |
| return; |
| } |
| |
| // Read the result returned from Javascript code. |
| DCHECK(result->GetResult()->GetValue()->is_string()); |
| OnResult(/* exists= */ true, result->GetResult()->GetValue()->GetString(), |
| std::move(callback)); |
| } |
| |
| void WebController::SetFieldValue(const Selector& selector, |
| const std::string& value, |
| bool simulate_key_presses, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " " << selector << ", value=" << value |
| << ", simulate_key_presses=" << simulate_key_presses; |
| if (simulate_key_presses) { |
| std::vector<std::string> utf8_chars; |
| base::i18n::UTF8CharIterator iter(&value); |
| while (!iter.end()) { |
| wchar_t wide_char = iter.get(); |
| std::string utf8_char; |
| if (!base::WideToUTF8(&wide_char, 1, &utf8_char)) { |
| DVLOG(1) << __func__ |
| << " Failed to convert character to UTF-8: " << wide_char; |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| utf8_chars.push_back(utf8_char); |
| iter.Advance(); |
| } |
| |
| // We first clear the field value, and then simulate the key presses. |
| // TODO(crbug.com/806868): Disable keyboard during this action and then |
| // reset to previous state. |
| InternalSetFieldValue( |
| selector, "", |
| base::BindOnce(&WebController::OnClearFieldForSendKeyboardInput, |
| weak_ptr_factory_.GetWeakPtr(), selector, utf8_chars, |
| std::move(callback))); |
| return; |
| } |
| InternalSetFieldValue(selector, value, std::move(callback)); |
| } |
| |
| void WebController::InternalSetFieldValue( |
| const Selector& selector, |
| const std::string& value, |
| base::OnceCallback<void(bool)> callback) { |
| FindElement(selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForSetFieldValue, |
| weak_ptr_factory_.GetWeakPtr(), value, |
| std::move(callback))); |
| } |
| |
| void WebController::OnClearFieldForSendKeyboardInput( |
| const Selector& selector, |
| const std::vector<std::string>& utf8_chars, |
| base::OnceCallback<void(bool)> callback, |
| bool clear_status) { |
| if (!clear_status) { |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| SendKeyboardInput(selector, utf8_chars, std::move(callback)); |
| } |
| |
| void WebController::OnClickElementForSendKeyboardInput( |
| const std::vector<std::string>& utf8_chars, |
| base::OnceCallback<void(bool)> callback, |
| bool click_status) { |
| if (!click_status) { |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| DispatchKeyboardTextDownEvent(utf8_chars, 0, std::move(callback)); |
| } |
| |
| void WebController::DispatchKeyboardTextDownEvent( |
| const std::vector<std::string>& utf8_chars, |
| size_t index, |
| base::OnceCallback<void(bool)> callback) { |
| if (index >= utf8_chars.size()) { |
| OnResult(true, std::move(callback)); |
| return; |
| } |
| |
| devtools_client_->GetInput()->DispatchKeyEvent( |
| CreateKeyEventParamsFromText( |
| autofill_assistant::input::DispatchKeyEventType::KEY_DOWN, |
| utf8_chars[index]), |
| base::BindOnce(&WebController::DispatchKeyboardTextUpEvent, |
| weak_ptr_factory_.GetWeakPtr(), utf8_chars, index, |
| std::move(callback))); |
| } |
| |
| void WebController::DispatchKeyboardTextUpEvent( |
| const std::vector<std::string>& utf8_chars, |
| size_t index, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK_LT(index, utf8_chars.size()); |
| devtools_client_->GetInput()->DispatchKeyEvent( |
| CreateKeyEventParamsFromText( |
| autofill_assistant::input::DispatchKeyEventType::KEY_UP, |
| utf8_chars[index]), |
| base::BindOnce(&WebController::DispatchKeyboardTextDownEvent, |
| weak_ptr_factory_.GetWeakPtr(), utf8_chars, index + 1, |
| std::move(callback))); |
| } |
| |
| auto WebController::CreateKeyEventParamsFromText( |
| autofill_assistant::input::DispatchKeyEventType type, |
| const std::string& text) -> DispatchKeyEventParamsPtr { |
| auto params = input::DispatchKeyEventParams::Builder().SetType(type).Build(); |
| params->SetText(text); |
| return params; |
| } |
| |
| void WebController::OnPressKeyboard( |
| int key_code, |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| OnResult(result && !result->HasExceptionDetails(), std::move(callback)); |
| } |
| |
| void WebController::OnFindElementForSetFieldValue( |
| const std::string& value, |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| const std::string object_id = element_result->object_id; |
| if (object_id.empty()) { |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| std::vector<std::unique_ptr<runtime::CallArgument>> argument; |
| argument.emplace_back( |
| runtime::CallArgument::Builder() |
| .SetValue(base::Value::ToUniquePtrValue(base::Value(value))) |
| .Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id) |
| .SetArguments(std::move(argument)) |
| .SetFunctionDeclaration(std::string(kSetValueAttributeScript)) |
| .Build(), |
| base::BindOnce(&WebController::OnSetValueAttribute, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnSetValueAttribute( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| OnResult(result && !result->HasExceptionDetails(), std::move(callback)); |
| } |
| |
| void WebController::SetAttribute(const Selector& selector, |
| const std::vector<std::string>& attribute, |
| const std::string& value, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " " << selector << ", attribute=[" |
| << base::JoinString(attribute, ",") << "], value=" << value; |
| |
| DCHECK(!selector.empty()); |
| DCHECK_GT(attribute.size(), 0u); |
| FindElement(selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForSetAttribute, |
| weak_ptr_factory_.GetWeakPtr(), attribute, value, |
| std::move(callback))); |
| } |
| |
| void WebController::OnFindElementForSetAttribute( |
| const std::vector<std::string>& attribute, |
| const std::string& value, |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| const std::string object_id = element_result->object_id; |
| if (object_id.empty()) { |
| OnResult(false, std::move(callback)); |
| return; |
| } |
| |
| base::Value::ListStorage attribute_values; |
| for (const std::string& string : attribute) { |
| attribute_values.emplace_back(base::Value(string)); |
| } |
| |
| std::vector<std::unique_ptr<runtime::CallArgument>> arguments; |
| arguments.emplace_back(runtime::CallArgument::Builder() |
| .SetValue(base::Value::ToUniquePtrValue( |
| base::Value(attribute_values))) |
| .Build()); |
| arguments.emplace_back( |
| runtime::CallArgument::Builder() |
| .SetValue(base::Value::ToUniquePtrValue(base::Value(value))) |
| .Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id) |
| .SetArguments(std::move(arguments)) |
| .SetFunctionDeclaration(std::string(kSetAttributeScript)) |
| .Build(), |
| base::BindOnce(&WebController::OnSetAttribute, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnSetAttribute( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| OnResult(result && !result->HasExceptionDetails(), std::move(callback)); |
| } |
| |
| void WebController::SendKeyboardInput( |
| const Selector& selector, |
| const std::vector<std::string>& utf8_chars, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " " << selector |
| << ", input=" << base::JoinString(utf8_chars, ""); |
| DCHECK(!selector.empty()); |
| FindElement(selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForSendKeyboardInput, |
| weak_ptr_factory_.GetWeakPtr(), selector, |
| utf8_chars, std::move(callback))); |
| } |
| |
| void WebController::OnFindElementForSendKeyboardInput( |
| const Selector& selector, |
| const std::vector<std::string>& utf8_chars, |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| ClickElement(selector, base::BindOnce( |
| &WebController::OnClickElementForSendKeyboardInput, |
| weak_ptr_factory_.GetWeakPtr(), utf8_chars, |
| std::move(callback))); |
| } |
| |
| void WebController::GetOuterHtml( |
| const Selector& selector, |
| base::OnceCallback<void(bool, const std::string&)> callback) { |
| DVLOG(3) << __func__ << " " << selector; |
| FindElement( |
| selector, |
| /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForGetOuterHtml, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| std::unique_ptr<BatchElementChecker> |
| WebController::CreateBatchElementChecker() { |
| return std::make_unique<BatchElementChecker>(this); |
| } |
| |
| void WebController::GetElementPosition( |
| const Selector& selector, |
| base::OnceCallback<void(bool, const RectF&)> callback) { |
| FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce(&WebController::OnFindElementForPosition, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnFindElementForPosition( |
| base::OnceCallback<void(bool, const RectF&)> callback, |
| std::unique_ptr<FindElementResult> result) { |
| if (result->object_id.empty()) { |
| RectF empty; |
| std::move(callback).Run(false, empty); |
| return; |
| } |
| |
| std::vector<std::unique_ptr<runtime::CallArgument>> argument; |
| argument.emplace_back( |
| runtime::CallArgument::Builder().SetObjectId(result->object_id).Build()); |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(result->object_id) |
| .SetArguments(std::move(argument)) |
| .SetFunctionDeclaration(std::string(kGetBoundingClientRectAsList)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::OnGetElementPositionResult, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnGetElementPositionResult( |
| base::OnceCallback<void(bool, const RectF&)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || result->HasExceptionDetails()) { |
| RectF empty; |
| std::move(callback).Run(false, empty); |
| return; |
| } |
| const auto* value = result->GetResult()->GetValue(); |
| DCHECK(value); |
| DCHECK(value->is_list()); |
| const auto& list = value->GetList(); |
| DCHECK_EQ(list.size(), 8u); |
| |
| // getBoundingClientRect returns coordinates in the layout viewport. They need |
| // to be transformed into coordinates in the visual viewport, between 0 and 1. |
| float left_layout = static_cast<float>(list[0].GetDouble()); |
| float top_layout = static_cast<float>(list[1].GetDouble()); |
| float right_layout = static_cast<float>(list[2].GetDouble()); |
| float bottom_layout = static_cast<float>(list[3].GetDouble()); |
| float visual_left_offset = static_cast<float>(list[4].GetDouble()); |
| float visual_top_offset = static_cast<float>(list[5].GetDouble()); |
| float visual_w = static_cast<float>(list[6].GetDouble()); |
| float visual_h = static_cast<float>(list[7].GetDouble()); |
| |
| RectF rect; |
| rect.left = (left_layout - visual_left_offset) / visual_w; |
| rect.top = (top_layout - visual_top_offset) / visual_h; |
| rect.right = (right_layout - visual_left_offset) / visual_w; |
| rect.bottom = (bottom_layout - visual_top_offset) / visual_h; |
| |
| std::move(callback).Run(true, rect); |
| } |
| |
| void WebController::OnFindElementForGetOuterHtml( |
| base::OnceCallback<void(bool, const std::string&)> callback, |
| std::unique_ptr<FindElementResult> element_result) { |
| const std::string object_id = element_result->object_id; |
| if (object_id.empty()) { |
| DVLOG(2) << __func__ << " Failed to find element for GetOuterHtml"; |
| OnResult(false, "", std::move(callback)); |
| return; |
| } |
| |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id) |
| .SetFunctionDeclaration(std::string(kGetOuterHtmlScript)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::OnGetOuterHtml, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnGetOuterHtml( |
| base::OnceCallback<void(bool, const std::string&)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || result->HasExceptionDetails()) { |
| DVLOG(2) << __func__ << " Failed to find element for GetOuterHtml"; |
| OnResult(false, "", std::move(callback)); |
| return; |
| } |
| |
| // Read the result returned from Javascript code. |
| DCHECK(result->GetResult()->GetValue()->is_string()); |
| OnResult(true, result->GetResult()->GetValue()->GetString(), |
| std::move(callback)); |
| } |
| |
| void WebController::SetCookie(const std::string& domain, |
| base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__ << " domain=" << domain; |
| DCHECK(!domain.empty()); |
| auto expires_seconds = |
| std::chrono::seconds(std::time(nullptr)).count() + kCookieExpiresSeconds; |
| devtools_client_->GetNetwork()->SetCookie( |
| network::SetCookieParams::Builder() |
| .SetName(kAutofillAssistantCookieName) |
| .SetValue(kAutofillAssistantCookieValue) |
| .SetDomain(domain) |
| .SetExpires(expires_seconds) |
| .Build(), |
| base::BindOnce(&WebController::OnSetCookie, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnSetCookie( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<network::SetCookieResult> result) { |
| std::move(callback).Run(result && result->GetSuccess()); |
| } |
| |
| void WebController::HasCookie(base::OnceCallback<void(bool)> callback) { |
| DVLOG(3) << __func__; |
| devtools_client_->GetNetwork()->GetCookies( |
| base::BindOnce(&WebController::OnHasCookie, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebController::OnHasCookie( |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<network::GetCookiesResult> result) { |
| if (!result) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| const auto& cookies = *result->GetCookies(); |
| for (const auto& cookie : cookies) { |
| if (cookie->GetName() == kAutofillAssistantCookieName && |
| cookie->GetValue() == kAutofillAssistantCookieValue) { |
| std::move(callback).Run(true); |
| return; |
| } |
| } |
| std::move(callback).Run(false); |
| } |
| |
| void WebController::ClearCookie() { |
| DVLOG(3) << __func__; |
| devtools_client_->GetNetwork()->DeleteCookies(kAutofillAssistantCookieName, |
| base::DoNothing()); |
| } |
| |
| void WebController::WaitForDocumentToBecomeInteractive( |
| int remaining_rounds, |
| std::string object_id, |
| base::OnceCallback<void(bool)> callback) { |
| devtools_client_->GetRuntime()->CallFunctionOn( |
| runtime::CallFunctionOnParams::Builder() |
| .SetObjectId(object_id) |
| .SetFunctionDeclaration(std::string(kIsDocumentReadyForInteract)) |
| .SetReturnByValue(true) |
| .Build(), |
| base::BindOnce(&WebController::OnWaitForDocumentToBecomeInteractive, |
| weak_ptr_factory_.GetWeakPtr(), remaining_rounds, |
| object_id, std::move(callback))); |
| } |
| |
| void WebController::OnWaitForDocumentToBecomeInteractive( |
| int remaining_rounds, |
| std::string object_id, |
| base::OnceCallback<void(bool)> callback, |
| std::unique_ptr<runtime::CallFunctionOnResult> result) { |
| if (!result || !result->GetResult() || result->HasExceptionDetails() || |
| remaining_rounds <= 0) { |
| DVLOG(1) << __func__ |
| << " Failed to wait for the document to become interactive with " |
| "remaining_rounds: " |
| << remaining_rounds; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| DCHECK(result->GetResult()->GetValue()->is_bool()); |
| if (result->GetResult()->GetValue()->GetBool()) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| base::PostDelayedTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce(&WebController::WaitForDocumentToBecomeInteractive, |
| weak_ptr_factory_.GetWeakPtr(), --remaining_rounds, |
| object_id, std::move(callback)), |
| kPeriodicCheckInterval); |
| } |
| |
| } // namespace autofill_assistant |