WebDriver: handle click events on option elements
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Aug 2017 07:03:13 +0000 (07:03 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Aug 2017 07:03:13 +0000 (07:03 +0000)
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.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@220740 268f45cc-cd09-0410-ab3c-d52691b4dbfc

16 files changed:
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLOptGroupElement.h
Source/WebCore/html/HTMLOptionElement.h
Source/WebDriver/ChangeLog
Source/WebDriver/CommandResult.cpp
Source/WebDriver/CommandResult.h
Source/WebDriver/Session.cpp
Source/WebDriver/Session.h
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/Automation/Automation.json
Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp
Source/WebKit/UIProcess/Automation/WebAutomationSession.h
Source/WebKit/UIProcess/Automation/WebAutomationSession.messages.in
Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp
Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h
Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in

index 4c1e448..e3fa61e 100644 (file)
@@ -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  Simon Fraser  <simon.fraser@apple.com>
 
         Remove Proximity Events and related code
index 6aa19c9..d42dfeb 100644 (file)
@@ -34,7 +34,7 @@ public:
     static Ref<HTMLOptGroupElement> create(const QualifiedName&, Document&);
 
     bool isDisabledFormControl() const final;
-    HTMLSelectElement* ownerSelectElement() const;
+    WEBCORE_EXPORT HTMLSelectElement* ownerSelectElement() const;
     
     WEBCORE_EXPORT String groupLabelText() const;
 
index 712a313..25bdc8e 100644 (file)
@@ -49,9 +49,9 @@ public:
     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;
@@ -59,7 +59,7 @@ public:
 
     bool ownElementDisabled() const { return m_disabled; }
 
-    bool isDisabledFormControl() const final;
+    WEBCORE_EXPORT bool isDisabledFormControl() const final;
 
     String textIndentedToRespectGroupLabel() const;
 
index 74854d4..a2f704f 100644 (file)
@@ -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
index be8c817..a53bac9 100644 (file)
@@ -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;
     }
@@ -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:
@@ -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:
index 8c3fb89..7f789e5 100644 (file)
@@ -41,6 +41,7 @@ public:
     // https://www.w3.org/TR/webdriver/#handling-errors
     enum class ErrorCode {
         ElementClickIntercepted,
+        ElementNotSelectable,
         ElementNotInteractable,
         InvalidArgument,
         InvalidElementState,
index 7ed5696..f74718a 100644 (file)
@@ -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) {
@@ -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;
@@ -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));
+        });
     });
 }
 
index 75b4ab3..c3255e2 100644 (file)
@@ -137,6 +137,8 @@ private:
     };
     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&&)>&&);
index 8ce2406..6ce7a82 100644 (file)
@@ -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
index 21366c2..d2f9294 100644 (file)
@@ -59,7 +59,8 @@
                 "MissingParameter",
                 "InvalidParameter",
                 "InvalidSelector",
-                "ElementNotInteractable"
+                "ElementNotInteractable",
+                "ElementNotSelectable"
             ]
         },
         {
             "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.",
             "parameters": [
index c8ad5a1..87f004e 100644 (file)
@@ -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);
index 95275c6..096d525 100644 (file)
@@ -133,6 +133,7 @@ public:
     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;
@@ -176,6 +177,7 @@ private:
     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);
@@ -241,6 +243,9 @@ private:
     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;
 
index f38ac95..bc473b6 100644 (file)
@@ -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)
index 52d347d..07e4ffe 100644 (file)
@@ -48,6 +48,9 @@
 #include <WebCore/FrameTree.h>
 #include <WebCore/FrameView.h>
 #include <WebCore/HTMLFrameElementBase.h>
+#include <WebCore/HTMLOptGroupElement.h>
+#include <WebCore/HTMLOptionElement.h>
+#include <WebCore/HTMLSelectElement.h>
 #include <WebCore/JSElement.h>
 #include <WebCore/MainFrame.h>
 #include <wtf/UUID.h>
@@ -503,6 +506,32 @@ static std::optional<WebCore::FloatPoint> elementInViewClientCenterPoint(WebCore
     return clientCenterPoint;
 }
 
+static WebCore::Element* containerElementForElement(WebCore::Element& element)
+{
+    // §13. Element State.
+    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-container.
+    if (is<WebCore::HTMLOptionElement>(element)) {
+        auto& optionElement = downcast<WebCore::HTMLOptionElement>(element);
+#if ENABLE(DATALIST_ELEMENT)
+        if (auto* parentElement = optionElement.ownerDataListElement())
+            return parentElement;
+#endif
+        if (auto* parentElement = optionElement.ownerSelectElement())
+            return parentElement;
+
+        return nullptr;
+    }
+
+    if (is<WebCore::HTMLOptGroupElement>(element)) {
+        if (auto* parentElement = downcast<WebCore::HTMLOptGroupElement>(element).ownerSelectElement())
+            return parentElement;
+
+        return nullptr;
+    }
+
+    return &element;
+}
+
 void WebAutomationSessionProxy::computeElementLayout(uint64_t pageID, uint64_t frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool useViewportCoordinates, uint64_t callbackID)
 {
     WebPage* page = WebProcess::singleton().webPage(pageID);
@@ -527,9 +556,12 @@ void WebAutomationSessionProxy::computeElementLayout(uint64_t pageID, uint64_t f
         return;
     }
 
-    if (scrollIntoViewIfNeeded) {
+    auto* containerElement = containerElementForElement(*coreElement);
+    if (scrollIntoViewIfNeeded && containerElement) {
+        // §14.1 Element Click. Step 4. Scroll into view the element’s container.
+        // https://w3c.github.io/webdriver/webdriver-spec.html#element-click
+        containerElement->scrollIntoViewIfNeeded(false);
         // FIXME: Wait in an implementation-specific way up to the session implicit wait timeout for the element to become in view.
-        coreElement->scrollIntoViewIfNeeded(false);
     }
 
     WebCore::IntRect rect = coreElement->clientRect();
@@ -542,15 +574,65 @@ void WebAutomationSessionProxy::computeElementLayout(uint64_t pageID, uint64_t f
 
     bool isObscured = false;
     std::optional<WebCore::IntPoint> inViewCenter;
-    if (auto clientCenterPoint = elementInViewClientCenterPoint(*coreElement, isObscured)) {
-        inViewCenter = WebCore::IntPoint(coreFrameView->clientToDocumentPoint(clientCenterPoint.value()));
-        if (useViewportCoordinates)
-            inViewCenter = coreFrameView->contentsToRootView(inViewCenter.value());
+    if (containerElement) {
+        if (auto clientCenterPoint = elementInViewClientCenterPoint(*containerElement, isObscured)) {
+            inViewCenter = WebCore::IntPoint(coreFrameView->clientToDocumentPoint(clientCenterPoint.value()));
+            if (useViewportCoordinates)
+                inViewCenter = coreFrameView->contentsToRootView(inViewCenter.value());
+        }
     }
 
     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, rect, inViewCenter, isObscured, String()), 0);
 }
 
+void WebAutomationSessionProxy::selectOptionElement(uint64_t pageID, uint64_t frameID, String nodeHandle, uint64_t callbackID)
+{
+    WebPage* page = WebProcess::singleton().webPage(pageID);
+    if (!page) {
+        String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
+        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, windowNotFoundErrorType), 0);
+        return;
+    }
+
+    WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
+    if (!frame || !frame->coreFrame() || !frame->coreFrame()->view()) {
+        String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
+        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, frameNotFoundErrorType), 0);
+        return;
+    }
+
+    WebCore::Element* coreElement = elementForNodeHandle(*frame, nodeHandle);
+    if (!coreElement || (!is<WebCore::HTMLOptionElement>(coreElement) && !is<WebCore::HTMLOptGroupElement>(coreElement))) {
+        String nodeNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
+        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, nodeNotFoundErrorType), 0);
+        return;
+    }
+
+    String elementNotInteractableErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::ElementNotInteractable);
+    if (is<WebCore::HTMLOptGroupElement>(coreElement)) {
+        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, elementNotInteractableErrorType), 0);
+        return;
+    }
+
+    auto& optionElement = downcast<WebCore::HTMLOptionElement>(*coreElement);
+    auto* selectElement = optionElement.ownerSelectElement();
+    if (!selectElement) {
+        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, elementNotInteractableErrorType), 0);
+        return;
+    }
+
+    if (selectElement->isDisabledFormControl() || optionElement.isDisabledFormControl()) {
+        String elementNotSelectableErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::ElementNotSelectable);
+        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, elementNotSelectableErrorType), 0);
+        return;
+    }
+
+    // FIXME: According to the spec we should fire mouse over, move and down events, then input and change, and finally mouse up and click.
+    // optionSelectedByUser() will fire input and change events if needed, but all other events should be fired manually here.
+    selectElement->optionSelectedByUser(optionElement.index(), true, selectElement->multiple());
+    WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, { }), 0);
+}
+
 void WebAutomationSessionProxy::takeScreenshot(uint64_t pageID, uint64_t callbackID)
 {
     ShareableBitmap::Handle handle;
index 4b97944..845674b 100644 (file)
@@ -65,6 +65,7 @@ private:
     void resolveParentFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID);
     void focusFrame(uint64_t pageID, uint64_t frameID);
     void computeElementLayout(uint64_t pageID, uint64_t frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool useViewportCoordinates, uint64_t callbackID);
+    void selectOptionElement(uint64_t pageID, uint64_t frameID, String nodeHandle, uint64_t callbackID);
     void takeScreenshot(uint64_t pageID, uint64_t callbackID);
     void getCookiesForFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID);
     void deleteCookie(uint64_t pageID, uint64_t frameID, String cookieName, uint64_t callbackID);
index 2daf2a5..9d4131c 100644 (file)
@@ -32,6 +32,8 @@ messages -> WebAutomationSessionProxy {
 
     ComputeElementLayout(uint64_t pageID, uint64_t frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool useViewportCoordinates, uint64_t callbackID)
 
+    SelectOptionElement(uint64_t pageID, uint64_t frameID, String nodeHandle, uint64_t callbackID)
+
     TakeScreenshot(uint64_t pageID, uint64_t callbackID)
 
     GetCookiesForFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID)