Skip to content

Commit

Permalink
Merge r220740 - WebDriver: handle click events on option elements
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=174710
<rdar://problem/33459305>

Reviewed by Brian Burg.

Source/WebCore:

Export WebCore symbols required by WebKit layer.

* html/HTMLOptGroupElement.h:
* html/HTMLOptionElement.h:

Source/WebDriver:

Option elements are considered as a special case by the specification. When clicking an option element, we
should get its container and use it when scrolling into view and calculating in-view center point instead of the
option element itself. Then, we should not emulate a click, but change the selected status of the option element
like if it were done by a user action, firing the corresponding events. Now we check whether the element is an
option to call selectOptionElement() or performMouseInteraction().

This fixes more than 20 selenium tests.

* CommandResult.cpp:
(WebDriver::CommandResult::CommandResult): Handle ElementNotSelectable protocol error.
(WebDriver::CommandResult::httpStatusCode const): Add ElementNotSelectable.
(WebDriver::CommandResult::errorString const): Ditto.
* CommandResult.h:
* Session.cpp:
(WebDriver::Session::selectOptionElement): Ask automation to select the given option element.
(WebDriver::Session::elementClick): Call selectOptionElement() or performMouseInteraction() depending on whether
the element is an option or not.
* Session.h:

Source/WebKit:

Add selectOptionElement method to automation to select an option element according to the WebDriver
specification.

14.1 Element Click.
https://w3c.github.io/webdriver/webdriver-spec.html#element-click

* UIProcess/Automation/Automation.json: Add selectOptionElement method and ElementNotSelectable error.
* UIProcess/Automation/WebAutomationSession.cpp:
(WebKit::WebAutomationSession::selectOptionElement):Send SelectOptionElement message to the web process.
(WebKit::WebAutomationSession::didSelectOptionElement): Notify the driver.
* UIProcess/Automation/WebAutomationSession.h:
* UIProcess/Automation/WebAutomationSession.messages.in: Add DidSelectOptionElement message.
* WebProcess/Automation/WebAutomationSessionProxy.cpp:
(WebKit::elementContainer): Helper to get the container of an element according to the spec.
(WebKit::WebAutomationSessionProxy::computeElementLayout): Use the container element to scroll the view and
compute the in-view center point.
(WebKit::WebAutomationSessionProxy::selectOptionElement): Use HTMLSelectElement::optionSelectedByUser().
* WebProcess/Automation/WebAutomationSessionProxy.h:
* WebProcess/Automation/WebAutomationSessionProxy.messages.in: Add SelectOptionElement message.
  • Loading branch information
carlosgcampos committed Aug 17, 2017
1 parent 2e5393d commit 6d450d1
Show file tree
Hide file tree
Showing 16 changed files with 257 additions and 14 deletions.
13 changes: 13 additions & 0 deletions Source/WebCore/ChangeLog
@@ -1,3 +1,16 @@
2017-08-14 Carlos Garcia Campos <cgarcia@igalia.com>

WebDriver: handle click events on option elements
https://bugs.webkit.org/show_bug.cgi?id=174710
<rdar://problem/33459305>

Reviewed by Brian Burg.

Export WebCore symbols required by WebKit layer.

* html/HTMLOptGroupElement.h:
* html/HTMLOptionElement.h:

2017-08-14 Chris Dumez <cdumez@apple.com>

XHR should only fire an abort event if the cancellation was requested by the client
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/html/HTMLOptGroupElement.h
Expand Up @@ -34,7 +34,7 @@ class HTMLOptGroupElement final : public HTMLElement {
static Ref<HTMLOptGroupElement> create(const QualifiedName&, Document&);

bool isDisabledFormControl() const final;
HTMLSelectElement* ownerSelectElement() const;
WEBCORE_EXPORT HTMLSelectElement* ownerSelectElement() const;

WEBCORE_EXPORT String groupLabelText() const;

Expand Down
6 changes: 3 additions & 3 deletions Source/WebCore/html/HTMLOptionElement.h
Expand Up @@ -49,17 +49,17 @@ class HTMLOptionElement final : public HTMLElement {
WEBCORE_EXPORT void setSelected(bool);

#if ENABLE(DATALIST_ELEMENT)
HTMLDataListElement* ownerDataListElement() const;
WEBCORE_EXPORT HTMLDataListElement* ownerDataListElement() const;
#endif
HTMLSelectElement* ownerSelectElement() const;
WEBCORE_EXPORT HTMLSelectElement* ownerSelectElement() const;

WEBCORE_EXPORT String label() const;
String displayLabel() const;
WEBCORE_EXPORT void setLabel(const String&);

bool ownElementDisabled() const { return m_disabled; }

bool isDisabledFormControl() const final;
WEBCORE_EXPORT bool isDisabledFormControl() const final;

String textIndentedToRespectGroupLabel() const;

Expand Down
27 changes: 27 additions & 0 deletions Source/WebDriver/ChangeLog
@@ -1,3 +1,30 @@
2017-08-14 Carlos Garcia Campos <cgarcia@igalia.com>

WebDriver: handle click events on option elements
https://bugs.webkit.org/show_bug.cgi?id=174710
<rdar://problem/33459305>

Reviewed by Brian Burg.

Option elements are considered as a special case by the specification. When clicking an option element, we
should get its container and use it when scrolling into view and calculating in-view center point instead of the
option element itself. Then, we should not emulate a click, but change the selected status of the option element
like if it were done by a user action, firing the corresponding events. Now we check whether the element is an
option to call selectOptionElement() or performMouseInteraction().

This fixes more than 20 selenium tests.

* CommandResult.cpp:
(WebDriver::CommandResult::CommandResult): Handle ElementNotSelectable protocol error.
(WebDriver::CommandResult::httpStatusCode const): Add ElementNotSelectable.
(WebDriver::CommandResult::errorString const): Ditto.
* CommandResult.h:
* Session.cpp:
(WebDriver::Session::selectOptionElement): Ask automation to select the given option element.
(WebDriver::Session::elementClick): Call selectOptionElement() or performMouseInteraction() depending on whether
the element is an option or not.
* Session.h:

2017-08-11 Carlos Alberto Lopez Perez <clopez@igalia.com>

Fix build warning in WebDriverService.h
Expand Down
5 changes: 5 additions & 0 deletions Source/WebDriver/CommandResult.cpp
Expand Up @@ -106,6 +106,8 @@ CommandResult::CommandResult(RefPtr<InspectorValue>&& result, std::optional<Erro
m_errorCode = ErrorCode::Timeout;
else if (errorName == "NoJavaScriptDialog")
m_errorCode = ErrorCode::NoSuchAlert;
else if (errorName == "ElementNotSelectable")
m_errorCode = ErrorCode::ElementNotSelectable;

break;
}
Expand All @@ -127,6 +129,7 @@ unsigned CommandResult::httpStatusCode() const
// https://www.w3.org/TR/webdriver/#handling-errors
switch (m_errorCode.value()) {
case ErrorCode::ElementClickIntercepted:
case ErrorCode::ElementNotSelectable:
case ErrorCode::ElementNotInteractable:
case ErrorCode::InvalidArgument:
case ErrorCode::InvalidElementState:
Expand Down Expand Up @@ -162,6 +165,8 @@ String CommandResult::errorString() const
switch (m_errorCode.value()) {
case ErrorCode::ElementClickIntercepted:
return ASCIILiteral("element click intercepted");
case ErrorCode::ElementNotSelectable:
return ASCIILiteral("element not selectable");
case ErrorCode::ElementNotInteractable:
return ASCIILiteral("element not interactable");
case ErrorCode::InvalidArgument:
Expand Down
1 change: 1 addition & 0 deletions Source/WebDriver/CommandResult.h
Expand Up @@ -41,6 +41,7 @@ class CommandResult {
// https://www.w3.org/TR/webdriver/#handling-errors
enum class ErrorCode {
ElementClickIntercepted,
ElementNotSelectable,
ElementNotInteractable,
InvalidArgument,
InvalidElementState,
Expand Down
39 changes: 36 additions & 3 deletions Source/WebDriver/Session.cpp
Expand Up @@ -1191,6 +1191,21 @@ void Session::waitForNavigationToComplete(Function<void (CommandResult&&)>&& com
});
}

void Session::selectOptionElement(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
{
RefPtr<InspectorObject> parameters = InspectorObject::create();
parameters->setString(ASCIILiteral("browsingContextHandle"), m_toplevelBrowsingContext.value());
parameters->setString(ASCIILiteral("frameHandle"), m_currentBrowsingContext.value());
parameters->setString(ASCIILiteral("nodeHandle"), elementID);
m_host->sendCommandToBackend(ASCIILiteral("selectOptionElement"), WTFMove(parameters), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
if (response.isError) {
completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
return;
}
completionHandler(CommandResult::success());
});
}

void Session::elementClick(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
{
if (!m_toplevelBrowsingContext) {
Expand All @@ -1200,7 +1215,7 @@ void Session::elementClick(const String& elementID, Function<void (CommandResult

OptionSet<ElementLayoutOption> options = ElementLayoutOption::ScrollIntoViewIfNeeded;
options |= ElementLayoutOption::UseViewportCoordinates;
computeElementLayout(elementID, options, [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](std::optional<Rect>&& rect, std::optional<Point>&& inViewCenter, bool isObscured, RefPtr<InspectorObject>&& error) mutable {
computeElementLayout(elementID, options, [this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](std::optional<Rect>&& rect, std::optional<Point>&& inViewCenter, bool isObscured, RefPtr<InspectorObject>&& error) mutable {
if (!rect || error) {
completionHandler(CommandResult::fail(WTFMove(error)));
return;
Expand All @@ -1214,9 +1229,27 @@ void Session::elementClick(const String& elementID, Function<void (CommandResult
return;
}

performMouseInteraction(inViewCenter.value().x, inViewCenter.value().y, MouseButton::Left, MouseInteraction::SingleClick, WTFMove(completionHandler));
getElementTagName(elementID, [this, elementID, inViewCenter = WTFMove(inViewCenter), isObscured, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
bool isOptionElement = false;
if (!result.isError()) {
String tagName;
if (result.result()->asString(tagName))
isOptionElement = tagName == "option";
}

waitForNavigationToComplete(WTFMove(completionHandler));
Function<void (CommandResult&&)> continueAfterClickFunction = [this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
if (result.isError()) {
completionHandler(WTFMove(result));
return;
}

waitForNavigationToComplete(WTFMove(completionHandler));
};
if (isOptionElement)
selectOptionElement(elementID, WTFMove(continueAfterClickFunction));
else
performMouseInteraction(inViewCenter.value().x, inViewCenter.value().y, MouseButton::Left, MouseInteraction::SingleClick, WTFMove(continueAfterClickFunction));
});
});
}

Expand Down
2 changes: 2 additions & 0 deletions Source/WebDriver/Session.h
Expand Up @@ -137,6 +137,8 @@ class Session : public RefCounted<Session> {
};
void computeElementLayout(const String& elementID, OptionSet<ElementLayoutOption>, Function<void (std::optional<Rect>&&, std::optional<Point>&&, bool, RefPtr<Inspector::InspectorObject>&&)>&&);

void selectOptionElement(const String& elementID, Function<void (CommandResult&&)>&&);

enum class MouseButton { None, Left, Middle, Right };
enum class MouseInteraction { Move, Down, Up, SingleClick, DoubleClick };
void performMouseInteraction(int x, int y, MouseButton, MouseInteraction, Function<void (CommandResult&&)>&&);
Expand Down
28 changes: 28 additions & 0 deletions Source/WebKit/ChangeLog
@@ -1,3 +1,31 @@
2017-08-14 Carlos Garcia Campos <cgarcia@igalia.com>

WebDriver: handle click events on option elements
https://bugs.webkit.org/show_bug.cgi?id=174710
<rdar://problem/33459305>

Reviewed by Brian Burg.

Add selectOptionElement method to automation to select an option element according to the WebDriver
specification.

14.1 Element Click.
https://w3c.github.io/webdriver/webdriver-spec.html#element-click

* UIProcess/Automation/Automation.json: Add selectOptionElement method and ElementNotSelectable error.
* UIProcess/Automation/WebAutomationSession.cpp:
(WebKit::WebAutomationSession::selectOptionElement):Send SelectOptionElement message to the web process.
(WebKit::WebAutomationSession::didSelectOptionElement): Notify the driver.
* UIProcess/Automation/WebAutomationSession.h:
* UIProcess/Automation/WebAutomationSession.messages.in: Add DidSelectOptionElement message.
* WebProcess/Automation/WebAutomationSessionProxy.cpp:
(WebKit::elementContainer): Helper to get the container of an element according to the spec.
(WebKit::WebAutomationSessionProxy::computeElementLayout): Use the container element to scroll the view and
compute the in-view center point.
(WebKit::WebAutomationSessionProxy::selectOptionElement): Use HTMLSelectElement::optionSelectedByUser().
* WebProcess/Automation/WebAutomationSessionProxy.h:
* WebProcess/Automation/WebAutomationSessionProxy.messages.in: Add SelectOptionElement message.

2017-08-14 Simon Fraser <simon.fraser@apple.com>

Remove Proximity Events and related code
Expand Down
13 changes: 12 additions & 1 deletion Source/WebKit/UIProcess/Automation/Automation.json
Expand Up @@ -59,7 +59,8 @@
"MissingParameter",
"InvalidParameter",
"InvalidSelector",
"ElementNotInteractable"
"ElementNotInteractable",
"ElementNotSelectable"
]
},
{
Expand Down Expand Up @@ -429,6 +430,16 @@
],
"async": true
},
{
"name": "selectOptionElement",
"description": "Selects the given option element. In case of container with multiple options enabled, the element selectedness is toggled.",
"parameters": [
{ "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." },
{ "name": "frameHandle", "$ref": "FrameHandle", "description": "The handle for the frame that contains the element." },
{ "name": "nodeHandle", "$ref": "NodeHandle", "description": "The handle of the element to use." }
],
"async": true
},
{
"name": "isShowingJavaScriptDialog",
"description": "Checks if a browsing context is showing a JavaScript alert, confirm, or prompt dialog.",
Expand Down
31 changes: 31 additions & 0 deletions Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp
Expand Up @@ -845,6 +845,37 @@ void WebAutomationSession::didComputeElementLayout(uint64_t callbackID, WebCore:
callback->sendSuccess(WTFMove(rectObject), WTFMove(inViewCenterPointObject), isObscured);
}

void WebAutomationSession::selectOptionElement(Inspector::ErrorString& errorString, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, Ref<SelectOptionElementCallback>&& callback)
{
WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
if (!page)
FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);

std::optional<uint64_t> frameID = webFrameIDForHandle(frameHandle);
if (!frameID)
FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);

uint64_t callbackID = m_nextSelectOptionElementCallbackID++;
m_selectOptionElementCallbacks.set(callbackID, WTFMove(callback));

page->process().send(Messages::WebAutomationSessionProxy::SelectOptionElement(page->pageID(), frameID.value(), nodeHandle, callbackID), 0);
}

void WebAutomationSession::didSelectOptionElement(uint64_t callbackID, const String& errorType)
{
auto callback = m_selectOptionElementCallbacks.take(callbackID);
if (!callback)
return;

if (!errorType.isEmpty()) {
callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(errorType));
return;
}

callback->sendSuccess();
}


void WebAutomationSession::isShowingJavaScriptDialog(Inspector::ErrorString& errorString, const String& browsingContextHandle, bool* result)
{
ASSERT(m_client);
Expand Down
5 changes: 5 additions & 0 deletions Source/WebKit/UIProcess/Automation/WebAutomationSession.h
Expand Up @@ -133,6 +133,7 @@ class WebAutomationSession final : public API::ObjectImpl<API::Object::Type::Aut
void resolveChildFrameHandle(Inspector::ErrorString&, const String& browsingContextHandle, const String* optionalFrameHandle, const int* optionalOrdinal, const String* optionalName, const String* optionalNodeHandle, Ref<ResolveChildFrameHandleCallback>&&) override;
void resolveParentFrameHandle(Inspector::ErrorString&, const String& browsingContextHandle, const String& frameHandle, Ref<ResolveParentFrameHandleCallback>&&) override;
void computeElementLayout(Inspector::ErrorString&, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const bool* optionalScrollIntoViewIfNeeded, const bool* useViewportCoordinates, Ref<Inspector::AutomationBackendDispatcherHandler::ComputeElementLayoutCallback>&&) override;
void selectOptionElement(Inspector::ErrorString&, const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, Ref<Inspector::AutomationBackendDispatcherHandler::SelectOptionElementCallback>&&) override;
void isShowingJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle, bool* result) override;
void dismissCurrentJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle) override;
void acceptCurrentJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle) override;
Expand Down Expand Up @@ -176,6 +177,7 @@ class WebAutomationSession final : public API::ObjectImpl<API::Object::Type::Aut
void didResolveChildFrame(uint64_t callbackID, uint64_t frameID, const String& errorType);
void didResolveParentFrame(uint64_t callbackID, uint64_t frameID, const String& errorType);
void didComputeElementLayout(uint64_t callbackID, WebCore::IntRect, std::optional<WebCore::IntPoint>, bool isObscured, const String& errorType);
void didSelectOptionElement(uint64_t callbackID, const String& errorType);
void didTakeScreenshot(uint64_t callbackID, const ShareableBitmap::Handle&, const String& errorType);
void didGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie>, const String& errorType);
void didDeleteCookie(uint64_t callbackID, const String& errorType);
Expand Down Expand Up @@ -241,6 +243,9 @@ class WebAutomationSession final : public API::ObjectImpl<API::Object::Type::Aut
uint64_t m_nextDeleteCookieCallbackID { 1 };
HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::DeleteSingleCookieCallback>> m_deleteCookieCallbacks;

uint64_t m_nextSelectOptionElementCallbackID { 1 };
HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::SelectOptionElementCallback>> m_selectOptionElementCallbacks;

RunLoop::Timer<WebAutomationSession> m_loadTimer;
Vector<String> m_filesToSelectForFileUpload;

Expand Down
Expand Up @@ -28,6 +28,8 @@ messages -> WebAutomationSession {

DidComputeElementLayout(uint64_t callbackID, WebCore::IntRect rect, std::optional<WebCore::IntPoint> inViewCenterPoint, bool isObscured, String errorType)

DidSelectOptionElement(uint64_t callbackID, String errorType)

DidTakeScreenshot(uint64_t callbackID, WebKit::ShareableBitmap::Handle imageDataHandle, String errorType)

DidGetCookiesForFrame(uint64_t callbackID, Vector<WebCore::Cookie> cookies, String errorType)
Expand Down

0 comments on commit 6d450d1

Please sign in to comment.