Title: [253030] trunk
Revision
253030
Author
carlo...@webkit.org
Date
2019-12-03 02:03:32 -0800 (Tue, 03 Dec 2019)

Log Message

WebDriver: handle elements of type file in send keys command
https://bugs.webkit.org/show_bug.cgi?id=188514

Reviewed by Brian Burg.

Source/WebCore:

Export symbols now used by WebKit::WebAutomationSessionProxy.

* html/HTMLInputElement.h:

Source/WebDriver:

Handle the case of inpout element being a file upload in send keys command.

* Capabilities.h: Add strictFileInteractability.
* Session.cpp:
(WebDriver::Session::elementIsFileUpload): Helper to check if the given element is a file upload and whether
it's multiple or not.
(WebDriver::Session::parseElementIsFileUploadResult): Parse the result of elementIsFileUpload().
(WebDriver::Session::elementClick): If the element is a file upload, fail with invalid argument error.
(WebDriver::Session::setInputFileUploadFiles): Send setFilesForInputFileUpload message to the browser with the
selected files.
(WebDriver::Session::elementSendKeys): If the element is a file upload, call setInputFileUploadFiles()
instead. Also handle the strictFileInteractability capability to decide whether to focus and check
interactability on the input element or not.
* Session.h:
* WebDriverService.cpp:
(WebDriver::WebDriverService::parseCapabilities const): Handle strictFileInteractability.
(WebDriver::WebDriverService::validatedCapabilities const): Ditto.
(WebDriver::WebDriverService::matchCapabilities const): Ditto.
(WebDriver::WebDriverService::createSession): Ditto.

Source/WebKit:

Add setFilesForInputFileUpload method to Automation. It's like setFilesToSelectForFileUpload, but it works
differently, so I'm keeping both to not break safaridriver. The new one simply sends the file list to the web
process to be set on the input element, instead of saving the file list, wait for the driver to trigger the open
panel, intercept and complete the open panel request and send a dismiss open panel event to the driver.

* UIProcess/Automation/Automation.json: Add setFilesForInputFileUpload.
* UIProcess/Automation/WebAutomationSession.cpp:
(WebKit::WebAutomationSession::setFilesForInputFileUpload): Send SetFilesForInputFileUpload message to the web process.
* UIProcess/Automation/WebAutomationSession.h:
* WebProcess/Automation/WebAutomationSessionProxy.cpp:
(WebKit::WebAutomationSessionProxy::setFilesForInputFileUpload): Create a FileList with the received paths and
call HTMLInputElement::setFiles() on the given element.
* WebProcess/Automation/WebAutomationSessionProxy.h:
* WebProcess/Automation/WebAutomationSessionProxy.messages.in: Add SetFilesForInputFileUpload message.

WebDriverTests:

Remove expectations for tests that are now passing.

* TestExpectations.json:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (253029 => 253030)


--- trunk/Source/WebCore/ChangeLog	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebCore/ChangeLog	2019-12-03 10:03:32 UTC (rev 253030)
@@ -1,5 +1,16 @@
 2019-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
 
+        WebDriver: handle elements of type file in send keys command
+        https://bugs.webkit.org/show_bug.cgi?id=188514
+
+        Reviewed by Brian Burg.
+
+        Export symbols now used by WebKit::WebAutomationSessionProxy.
+
+        * html/HTMLInputElement.h:
+
+2019-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
+
         [PSON] Tooltips from previous page shown on new page
         https://bugs.webkit.org/show_bug.cgi?id=204735
 

Modified: trunk/Source/WebCore/html/HTMLInputElement.h (253029 => 253030)


--- trunk/Source/WebCore/html/HTMLInputElement.h	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebCore/html/HTMLInputElement.h	2019-12-03 10:03:32 UTC (rev 253030)
@@ -118,7 +118,7 @@
     WEBCORE_EXPORT bool isText() const;
 
     WEBCORE_EXPORT bool isEmailField() const;
-    bool isFileUpload() const;
+    WEBCORE_EXPORT bool isFileUpload() const;
     bool isImageButton() const;
     WEBCORE_EXPORT bool isNumberField() const;
     bool isSubmitButton() const;
@@ -235,7 +235,7 @@
 
     unsigned effectiveMaxLength() const;
 
-    bool multiple() const;
+    WEBCORE_EXPORT bool multiple() const;
 
     bool isAutoFilled() const { return m_isAutoFilled; }
     WEBCORE_EXPORT void setAutoFilled(bool = true);

Modified: trunk/Source/WebDriver/Capabilities.h (253029 => 253030)


--- trunk/Source/WebDriver/Capabilities.h	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebDriver/Capabilities.h	2019-12-03 10:03:32 UTC (rev 253030)
@@ -70,6 +70,7 @@
     Optional<String> browserVersion;
     Optional<String> platformName;
     Optional<bool> acceptInsecureCerts;
+    Optional<bool> strictFileInteractability;
     Optional<bool> setWindowRect;
     Optional<Timeouts> timeouts;
     Optional<PageLoadStrategy> pageLoadStrategy;

Modified: trunk/Source/WebDriver/ChangeLog (253029 => 253030)


--- trunk/Source/WebDriver/ChangeLog	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebDriver/ChangeLog	2019-12-03 10:03:32 UTC (rev 253030)
@@ -1,5 +1,32 @@
 2019-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
 
+        WebDriver: handle elements of type file in send keys command
+        https://bugs.webkit.org/show_bug.cgi?id=188514
+
+        Reviewed by Brian Burg.
+
+        Handle the case of inpout element being a file upload in send keys command.
+
+        * Capabilities.h: Add strictFileInteractability.
+        * Session.cpp:
+        (WebDriver::Session::elementIsFileUpload): Helper to check if the given element is a file upload and whether
+        it's multiple or not.
+        (WebDriver::Session::parseElementIsFileUploadResult): Parse the result of elementIsFileUpload().
+        (WebDriver::Session::elementClick): If the element is a file upload, fail with invalid argument error.
+        (WebDriver::Session::setInputFileUploadFiles): Send setFilesForInputFileUpload message to the browser with the
+        selected files.
+        (WebDriver::Session::elementSendKeys): If the element is a file upload, call setInputFileUploadFiles()
+        instead. Also handle the strictFileInteractability capability to decide whether to focus and check
+        interactability on the input element or not.
+        * Session.h:
+        * WebDriverService.cpp:
+        (WebDriver::WebDriverService::parseCapabilities const): Handle strictFileInteractability.
+        (WebDriver::WebDriverService::validatedCapabilities const): Ditto.
+        (WebDriver::WebDriverService::matchCapabilities const): Ditto.
+        (WebDriver::WebDriverService::createSession): Ditto.
+
+2019-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
+
         WebDriver: most of the clear tests are failing
         https://bugs.webkit.org/show_bug.cgi?id=180404
 

Modified: trunk/Source/WebDriver/Session.cpp (253029 => 253030)


--- trunk/Source/WebDriver/Session.cpp	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebDriver/Session.cpp	2019-12-03 10:03:32 UTC (rev 253030)
@@ -30,6 +30,7 @@
 #include "SessionHost.h"
 #include "WebDriverAtoms.h"
 #include <wtf/CryptographicallyRandomNumber.h>
+#include <wtf/FileSystem.h>
 #include <wtf/HashSet.h>
 #include <wtf/HexNumber.h>
 #include <wtf/NeverDestroyed.h>
@@ -1528,6 +1529,60 @@
     });
 }
 
+void Session::elementIsFileUpload(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
+{
+    RefPtr<JSON::Array> arguments = JSON::Array::create();
+    arguments->pushString(createElement(elementID)->toJSONString());
+
+    static const char isFileUploadScript[] =
+        "function(element) {"
+        "    if (element.tagName.toLowerCase() === 'input' && element.type === 'file')"
+        "        return { 'fileUpload': true, 'multiple': element.hasAttribute('multiple') };"
+        "    return { 'fileUpload': false };"
+        "}";
+
+    RefPtr<JSON::Object> parameters = JSON::Object::create();
+    parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
+    if (m_currentBrowsingContext)
+        parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
+    parameters->setString("function"_s, isFileUploadScript);
+    parameters->setArray("arguments"_s, WTFMove(arguments));
+    m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
+        if (response.isError || !response.responseObject) {
+            completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
+            return;
+        }
+        String valueString;
+        if (!response.responseObject->getString("result"_s, valueString)) {
+            completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
+            return;
+        }
+        RefPtr<JSON::Value> resultValue;
+        if (!JSON::Value::parseJSON(valueString, resultValue)) {
+            completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
+            return;
+        }
+        completionHandler(CommandResult::success(WTFMove(resultValue)));
+    });
+}
+
+Optional<Session::FileUploadType> Session::parseElementIsFileUploadResult(const RefPtr<JSON::Value>& resultValue)
+{
+    RefPtr<JSON::Object> result;
+    if (!resultValue->asObject(result))
+        return WTF::nullopt;
+
+    bool isFileUpload;
+    if (!result->getBoolean("fileUpload"_s, isFileUpload) || !isFileUpload)
+        return WTF::nullopt;
+
+    bool multiple;
+    if (!result->getBoolean("multiple"_s, multiple) || !multiple)
+        return FileUploadType::Single;
+
+    return FileUploadType::Multiple;
+}
+
 void Session::selectOptionElement(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
 {
     RefPtr<JSON::Object> parameters = JSON::Object::create();
@@ -1555,41 +1610,52 @@
             completionHandler(WTFMove(result));
             return;
         }
-        OptionSet<ElementLayoutOption> options = { ElementLayoutOption::ScrollIntoViewIfNeeded, ElementLayoutOption::UseViewportCoordinates };
-        computeElementLayout(elementID, options, [this, protectedThis = protectedThis.copyRef(), elementID, completionHandler = WTFMove(completionHandler)](Optional<Rect>&& rect, Optional<Point>&& inViewCenter, bool isObscured, RefPtr<JSON::Object>&& error) mutable {
-            if (!rect || error) {
-                completionHandler(CommandResult::fail(WTFMove(error)));
+        elementIsFileUpload(elementID, [this, protectedThis = protectedThis.copyRef(), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
+            if (result.isError()) {
+                completionHandler(WTFMove(result));
                 return;
             }
-            if (isObscured) {
-                completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementClickIntercepted));
+
+            if (parseElementIsFileUploadResult(result.result())) {
+                completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
                 return;
             }
-            if (!inViewCenter) {
-                completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementNotInteractable));
-                return;
-            }
-
-            getElementTagName(elementID, [this, elementID, inViewCenter = WTFMove(inViewCenter), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
-                bool isOptionElement = false;
-                if (!result.isError()) {
-                    String tagName;
-                    if (result.result()->asString(tagName))
-                        isOptionElement = tagName == "option";
+            OptionSet<ElementLayoutOption> options = { ElementLayoutOption::ScrollIntoViewIfNeeded, ElementLayoutOption::UseViewportCoordinates };
+            computeElementLayout(elementID, options, [this, protectedThis = protectedThis.copyRef(), elementID, completionHandler = WTFMove(completionHandler)](Optional<Rect>&& rect, Optional<Point>&& inViewCenter, bool isObscured, RefPtr<JSON::Object>&& error) mutable {
+                if (!rect || error) {
+                    completionHandler(CommandResult::fail(WTFMove(error)));
+                    return;
                 }
+                if (isObscured) {
+                    completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementClickIntercepted));
+                    return;
+                }
+                if (!inViewCenter) {
+                    completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementNotInteractable));
+                    return;
+                }
 
-                Function<void (CommandResult&&)> continueAfterClickFunction = [this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
-                    if (result.isError()) {
-                        completionHandler(WTFMove(result));
-                        return;
+                getElementTagName(elementID, [this, elementID, inViewCenter = WTFMove(inViewCenter), 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));
-                };
-                if (isOptionElement)
-                    selectOptionElement(elementID, WTFMove(continueAfterClickFunction));
-                else
-                    performMouseInteraction(inViewCenter.value().x, inViewCenter.value().y, MouseButton::Left, MouseInteraction::SingleClick, WTFMove(continueAfterClickFunction));
+                    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));
+                });
             });
         });
     });
@@ -1698,6 +1764,43 @@
     });
 }
 
+void Session::setInputFileUploadFiles(const String& elementID, const String& text, bool multiple, Function<void (CommandResult&&)>&& completionHandler)
+{
+    Vector<String> files = text.split('\n');
+    if (files.isEmpty()) {
+        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
+        return;
+    }
+
+    if (!multiple && files.size() != 1) {
+        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
+        return;
+    }
+
+    RefPtr<JSON::Array> filenames = JSON::Array::create();
+    for (const auto& file : files) {
+        if (!FileSystem::fileExists(file)) {
+            completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
+            return;
+        }
+        filenames->pushString(file);
+    }
+
+    RefPtr<JSON::Object> parameters = JSON::Object::create();
+    parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
+    parameters->setString("frameHandle"_s, m_currentBrowsingContext.valueOr(emptyString()));
+    parameters->setString("nodeHandle"_s, elementID);
+    parameters->setArray("filenames"_s, WTFMove(filenames));
+    m_host->sendCommandToBackend("setFilesForInputFileUpload"_s, WTFMove(parameters), [this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
+        if (response.isError) {
+            completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
+            return;
+        }
+
+        completionHandler(CommandResult::success());
+    });
+}
+
 String Session::virtualKeyForKey(UChar key, KeyModifier& modifier)
 {
     // ยง17.4.2 Keyboard Actions.
@@ -1848,74 +1951,92 @@
             completionHandler(WTFMove(result));
             return;
         }
-        // FIXME: move this to an atom.
-        static const char focusScript[] =
-            "function focus(element) {"
-            "    let doc = element.ownerDocument || element;"
-            "    let prevActiveElement = doc.activeElement;"
-            "    if (element != prevActiveElement && prevActiveElement)"
-            "        prevActiveElement.blur();"
-            "    element.focus();"
-            "    let tagName = element.tagName.toUpperCase();"
-            "    if (tagName === 'BODY' || element === document.documentElement)"
-            "        return;"
-            "    let isTextElement = tagName === 'TEXTAREA' || (tagName === 'INPUT' && element.type === 'text');"
-            "    if (isTextElement && element.selectionEnd == 0)"
-            "        element.setSelectionRange(element.value.length, element.value.length);"
-            "    if (element != doc.activeElement)"
-            "        throw {name: 'ElementNotInteractable', message: 'Element is not focusable.'};"
-            "}";
-
-        RefPtr<JSON::Array> arguments = JSON::Array::create();
-        arguments->pushString(createElement(elementID)->toJSONString());
-        RefPtr<JSON::Object> parameters = JSON::Object::create();
-        parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
-        if (m_currentBrowsingContext)
-            parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
-        parameters->setString("function"_s, focusScript);
-        parameters->setArray("arguments"_s, WTFMove(arguments));
-        m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), text, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
-            if (response.isError || !response.responseObject) {
-                completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
+        elementIsFileUpload(elementID, [this, protectedThis = protectedThis.copyRef(), elementID, text, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
+            if (result.isError()) {
+                completionHandler(WTFMove(result));
                 return;
             }
 
-            unsigned stickyModifiers = 0;
-            auto textLength = text.length();
-            Vector<KeyboardInteraction> interactions;
-            interactions.reserveInitialCapacity(textLength);
-            for (unsigned i = 0; i < textLength; ++i) {
-                auto key = text[i];
-                KeyboardInteraction interaction;
-                KeyModifier modifier;
-                auto virtualKey = virtualKeyForKey(key, modifier);
-                if (!virtualKey.isNull()) {
-                    interaction.key = virtualKey;
-                    if (modifier != KeyModifier::None) {
-                        stickyModifiers ^= modifier;
-                        if (stickyModifiers & modifier)
-                            interaction.type = KeyboardInteractionType::KeyPress;
-                        else
-                            interaction.type = KeyboardInteractionType::KeyRelease;
+            auto fileUploadType = parseElementIsFileUploadResult(result.result());
+            if (!fileUploadType || capabilities().strictFileInteractability.valueOr(false)) {
+                // FIXME: move this to an atom.
+                static const char focusScript[] =
+                    "function focus(element) {"
+                    "    let doc = element.ownerDocument || element;"
+                    "    let prevActiveElement = doc.activeElement;"
+                    "    if (element != prevActiveElement && prevActiveElement)"
+                    "        prevActiveElement.blur();"
+                    "    element.focus();"
+                    "    let tagName = element.tagName.toUpperCase();"
+                    "    if (tagName === 'BODY' || element === document.documentElement)"
+                    "        return;"
+                    "    let isTextElement = tagName === 'TEXTAREA' || (tagName === 'INPUT' && element.type === 'text');"
+                    "    if (isTextElement && element.selectionEnd == 0)"
+                    "        element.setSelectionRange(element.value.length, element.value.length);"
+                    "    if (element != doc.activeElement)"
+                    "        throw {name: 'ElementNotInteractable', message: 'Element is not focusable.'};"
+                    "}";
+
+                RefPtr<JSON::Array> arguments = JSON::Array::create();
+                arguments->pushString(createElement(elementID)->toJSONString());
+                RefPtr<JSON::Object> parameters = JSON::Object::create();
+                parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
+                if (m_currentBrowsingContext)
+                    parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
+                parameters->setString("function"_s, focusScript);
+                parameters->setArray("arguments"_s, WTFMove(arguments));
+                m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), fileUploadType, elementID, text, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
+                    if (response.isError || !response.responseObject) {
+                        completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
+                        return;
                     }
-                } else
-                    interaction.text = String(&key, 1);
-                interactions.uncheckedAppend(WTFMove(interaction));
-            }
 
-            // Reset sticky modifiers if needed.
-            if (stickyModifiers) {
-                if (stickyModifiers & KeyModifier::Shift)
-                    interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Shift"_s) });
-                if (stickyModifiers & KeyModifier::Control)
-                    interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Control"_s) });
-                if (stickyModifiers & KeyModifier::Alternate)
-                    interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Alternate"_s) });
-                if (stickyModifiers & KeyModifier::Meta)
-                    interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Meta"_s) });
+                    if (fileUploadType) {
+                        setInputFileUploadFiles(elementID, text, fileUploadType.value() == FileUploadType::Multiple, WTFMove(completionHandler));
+                        return;
+                    }
+
+                    unsigned stickyModifiers = 0;
+                    auto textLength = text.length();
+                    Vector<KeyboardInteraction> interactions;
+                    interactions.reserveInitialCapacity(textLength);
+                    for (unsigned i = 0; i < textLength; ++i) {
+                        auto key = text[i];
+                        KeyboardInteraction interaction;
+                        KeyModifier modifier;
+                        auto virtualKey = virtualKeyForKey(key, modifier);
+                        if (!virtualKey.isNull()) {
+                            interaction.key = virtualKey;
+                            if (modifier != KeyModifier::None) {
+                                stickyModifiers ^= modifier;
+                                if (stickyModifiers & modifier)
+                                    interaction.type = KeyboardInteractionType::KeyPress;
+                                else
+                                    interaction.type = KeyboardInteractionType::KeyRelease;
+                            }
+                        } else
+                            interaction.text = String(&key, 1);
+                        interactions.uncheckedAppend(WTFMove(interaction));
+                    }
+
+                    // Reset sticky modifiers if needed.
+                    if (stickyModifiers) {
+                        if (stickyModifiers & KeyModifier::Shift)
+                            interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Shift"_s) });
+                        if (stickyModifiers & KeyModifier::Control)
+                            interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Control"_s) });
+                        if (stickyModifiers & KeyModifier::Alternate)
+                            interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Alternate"_s) });
+                        if (stickyModifiers & KeyModifier::Meta)
+                            interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Meta"_s) });
+                    }
+
+                    performKeyboardInteractions(WTFMove(interactions), WTFMove(completionHandler));
+                });
+            } else {
+                setInputFileUploadFiles(elementID, text, fileUploadType.value() == FileUploadType::Multiple, WTFMove(completionHandler));
+                return;
             }
-
-            performKeyboardInteractions(WTFMove(interactions), WTFMove(completionHandler));
         });
     });
 }

Modified: trunk/Source/WebDriver/Session.h (253029 => 253030)


--- trunk/Source/WebDriver/Session.h	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebDriver/Session.h	2019-12-03 10:03:32 UTC (rev 253030)
@@ -169,7 +169,12 @@
     };
     void computeElementLayout(const String& elementID, OptionSet<ElementLayoutOption>, Function<void (Optional<Rect>&&, Optional<Point>&&, bool, RefPtr<JSON::Object>&&)>&&);
 
+    void elementIsFileUpload(const String& elementID, Function<void (CommandResult&&)>&&);
+    enum class FileUploadType { Single, Multiple };
+    Optional<FileUploadType> parseElementIsFileUploadResult(const RefPtr<JSON::Value>&);
     void selectOptionElement(const String& elementID, Function<void (CommandResult&&)>&&);
+    void setInputFileUploadFiles(const String& elementID, const String& text, bool multiple, Function<void (CommandResult&&)>&&);
+    void didSetInputFileUploadFiles(bool wasCancelled);
 
     enum class MouseInteraction { Move, Down, Up, SingleClick, DoubleClick };
     void performMouseInteraction(int x, int y, MouseButton, MouseInteraction, Function<void (CommandResult&&)>&&);

Modified: trunk/Source/WebDriver/WebDriverService.cpp (253029 => 253030)


--- trunk/Source/WebDriver/WebDriverService.cpp	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebDriver/WebDriverService.cpp	2019-12-03 10:03:32 UTC (rev 253030)
@@ -482,6 +482,9 @@
     RefPtr<JSON::Object> proxy;
     if (matchedCapabilities.getObject("proxy"_s, proxy))
         capabilities.proxy = deserializeProxy(*proxy);
+    bool strictFileInteractability;
+    if (matchedCapabilities.getBoolean("strictFileInteractability"_s, strictFileInteractability))
+        capabilities.strictFileInteractability = strictFileInteractability;
     RefPtr<JSON::Object> timeouts;
     if (matchedCapabilities.getObject("timeouts"_s, timeouts))
         capabilities.timeouts = deserializeTimeouts(*timeouts);
@@ -539,6 +542,11 @@
             if (!it->value->asObject(proxy) || !deserializeProxy(*proxy))
                 return nullptr;
             result->setValue(it->key, RefPtr<JSON::Value>(it->value));
+        } else if (it->key == "strictFileInteractability") {
+            bool strictFileInteractability;
+            if (!it->value->asBoolean(strictFileInteractability))
+                return nullptr;
+            result->setBoolean(it->key, strictFileInteractability);
         } else if (it->key == "timeouts") {
             RefPtr<JSON::Object> timeouts;
             if (!it->value->asObject(timeouts) || !deserializeTimeouts(*timeouts))
@@ -592,6 +600,8 @@
         matchedCapabilities->setString("platformName"_s, platformCapabilities.platformName.value());
     if (platformCapabilities.acceptInsecureCerts)
         matchedCapabilities->setBoolean("acceptInsecureCerts"_s, platformCapabilities.acceptInsecureCerts.value());
+    if (platformCapabilities.strictFileInteractability)
+        matchedCapabilities->setBoolean("strictFileInteractability"_s, platformCapabilities.strictFileInteractability.value());
     if (platformCapabilities.setWindowRect)
         matchedCapabilities->setBoolean("setWindowRect"_s, platformCapabilities.setWindowRect.value());
 
@@ -800,6 +810,7 @@
             capabilitiesObject->setString("browserVersion"_s, capabilities.browserVersion.valueOr(emptyString()));
             capabilitiesObject->setString("platformName"_s, capabilities.platformName.valueOr(emptyString()));
             capabilitiesObject->setBoolean("acceptInsecureCerts"_s, capabilities.acceptInsecureCerts.valueOr(false));
+            capabilitiesObject->setBoolean("strictFileInteractability"_s, capabilities.strictFileInteractability.valueOr(false));
             capabilitiesObject->setBoolean("setWindowRect"_s, capabilities.setWindowRect.valueOr(true));
             switch (capabilities.unhandledPromptBehavior.valueOr(UnhandledPromptBehavior::DismissAndNotify)) {
             case UnhandledPromptBehavior::Dismiss:

Modified: trunk/Source/WebKit/ChangeLog (253029 => 253030)


--- trunk/Source/WebKit/ChangeLog	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebKit/ChangeLog	2019-12-03 10:03:32 UTC (rev 253030)
@@ -1,5 +1,27 @@
 2019-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
 
+        WebDriver: handle elements of type file in send keys command
+        https://bugs.webkit.org/show_bug.cgi?id=188514
+
+        Reviewed by Brian Burg.
+
+        Add setFilesForInputFileUpload method to Automation. It's like setFilesToSelectForFileUpload, but it works
+        differently, so I'm keeping both to not break safaridriver. The new one simply sends the file list to the web
+        process to be set on the input element, instead of saving the file list, wait for the driver to trigger the open
+        panel, intercept and complete the open panel request and send a dismiss open panel event to the driver.
+
+        * UIProcess/Automation/Automation.json: Add setFilesForInputFileUpload.
+        * UIProcess/Automation/WebAutomationSession.cpp:
+        (WebKit::WebAutomationSession::setFilesForInputFileUpload): Send SetFilesForInputFileUpload message to the web process.
+        * UIProcess/Automation/WebAutomationSession.h:
+        * WebProcess/Automation/WebAutomationSessionProxy.cpp:
+        (WebKit::WebAutomationSessionProxy::setFilesForInputFileUpload): Create a FileList with the received paths and
+        call HTMLInputElement::setFiles() on the given element.
+        * WebProcess/Automation/WebAutomationSessionProxy.h:
+        * WebProcess/Automation/WebAutomationSessionProxy.messages.in: Add SetFilesForInputFileUpload message.
+
+2019-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
+
         WebDriver: most of the clear tests are failing
         https://bugs.webkit.org/show_bug.cgi?id=180404
 

Modified: trunk/Source/WebKit/UIProcess/Automation/Automation.json (253029 => 253030)


--- trunk/Source/WebKit/UIProcess/Automation/Automation.json	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebKit/UIProcess/Automation/Automation.json	2019-12-03 10:03:32 UTC (rev 253030)
@@ -624,6 +624,17 @@
             ]
         },
         {
+            "name": "setFilesForInputFileUpload",
+            "description": "Sets the choosen files for the given input file upload element.",
+            "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 input element." },
+                { "name": "nodeHandle", "$ref": "NodeHandle", "description": "The handle of the input element." },
+                { "name": "filenames", "type": "array", "items": { "type": "string" }, "description": "Absolute paths to the choosen files." }
+            ],
+            "async": true
+        },
+        {
             "name": "getAllCookies",
             "description": "Returns all cookies visible to the specified browsing context.",
             "parameters": [

Modified: trunk/Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp (253029 => 253030)


--- trunk/Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp	2019-12-03 10:03:32 UTC (rev 253030)
@@ -1239,6 +1239,39 @@
     m_filesToSelectForFileUpload.swap(newFileList);
 }
 
+void WebAutomationSession::setFilesForInputFileUpload(const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const JSON::Array& filenames, Ref<SetFilesForInputFileUploadCallback>&& callback)
+{
+    WebPageProxy* page = webPageProxyForHandle(browsingContextHandle);
+    if (!page)
+        ASYNC_FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
+
+    bool frameNotFound = false;
+    auto frameID = webFrameIDForHandle(frameHandle, frameNotFound);
+    if (frameNotFound)
+        ASYNC_FAIL_WITH_PREDEFINED_ERROR(FrameNotFound);
+
+    Vector<String> newFileList;
+    newFileList.reserveInitialCapacity(filenames.length());
+    for (size_t i = 0; i < filenames.length(); ++i) {
+        String filename;
+        if (!filenames.get(i)->asString(filename))
+            ASYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The parameter 'filenames' contains a non-string value.");
+
+        newFileList.append(filename);
+    }
+
+    CompletionHandler<void(Optional<String>)> completionHandler = [callback = callback.copyRef()](Optional<String> errorType) mutable {
+        if (errorType) {
+            callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_MESSAGE(*errorType));
+            return;
+        }
+
+        callback->sendSuccess();
+    };
+
+    page->process().sendWithAsyncReply(Messages::WebAutomationSessionProxy::SetFilesForInputFileUpload(page->webPageID(), frameID, nodeHandle, WTFMove(newFileList)), WTFMove(completionHandler));
+}
+
 static Ref<Inspector::Protocol::Automation::Cookie> buildObjectForCookie(const WebCore::Cookie& cookie)
 {
     return Inspector::Protocol::Automation::Cookie::create()

Modified: trunk/Source/WebKit/UIProcess/Automation/WebAutomationSession.h (253029 => 253030)


--- trunk/Source/WebKit/UIProcess/Automation/WebAutomationSession.h	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebKit/UIProcess/Automation/WebAutomationSession.h	2019-12-03 10:03:32 UTC (rev 253030)
@@ -191,6 +191,7 @@
     void messageOfCurrentJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle, String* text) override;
     void setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString&, const String& browsingContextHandle, const String& text) override;
     void setFilesToSelectForFileUpload(Inspector::ErrorString&, const String& browsingContextHandle, const JSON::Array& filenames, const JSON::Array* optionalFileContents) override;
+    void setFilesForInputFileUpload(const String& browsingContextHandle, const String& frameHandle, const String& nodeHandle, const JSON::Array& filenames, Ref<SetFilesForInputFileUploadCallback>&&) override;
     void getAllCookies(const String& browsingContextHandle, Ref<GetAllCookiesCallback>&&) override;
     void deleteSingleCookie(const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&&) override;
     void addSingleCookie(const String& browsingContextHandle, const JSON::Object& cookie, Ref<AddSingleCookieCallback>&&) override;

Modified: trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp (253029 => 253030)


--- trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp	2019-12-03 10:03:32 UTC (rev 253030)
@@ -44,11 +44,14 @@
 #include <WebCore/DOMRect.h>
 #include <WebCore/DOMRectList.h>
 #include <WebCore/DOMWindow.h>
+#include <WebCore/File.h>
+#include <WebCore/FileList.h>
 #include <WebCore/Frame.h>
 #include <WebCore/FrameTree.h>
 #include <WebCore/FrameView.h>
 #include <WebCore/HTMLFrameElement.h>
 #include <WebCore/HTMLIFrameElement.h>
+#include <WebCore/HTMLInputElement.h>
 #include <WebCore/HTMLOptGroupElement.h>
 #include <WebCore/HTMLOptionElement.h>
 #include <WebCore/HTMLSelectElement.h>
@@ -682,6 +685,44 @@
     completionHandler(WTF::nullopt);
 }
 
+void WebAutomationSessionProxy::setFilesForInputFileUpload(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID, String nodeHandle, Vector<String>&& filenames, CompletionHandler<void(Optional<String>)>&& completionHandler)
+{
+    WebPage* page = WebProcess::singleton().webPage(pageID);
+    if (!page) {
+        String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
+        completionHandler(windowNotFoundErrorType);
+        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);
+        completionHandler(frameNotFoundErrorType);
+        return;
+    }
+
+    WebCore::Element* coreElement = elementForNodeHandle(*frame, nodeHandle);
+    if (!coreElement || !is<WebCore::HTMLInputElement>(coreElement) || !downcast<WebCore::HTMLInputElement>(*coreElement).isFileUpload()) {
+        String nodeNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
+        completionHandler(nodeNotFoundErrorType);
+        return;
+    }
+
+    auto& inputElement = downcast<WebCore::HTMLInputElement>(*coreElement);
+    Vector<Ref<WebCore::File>> fileObjects;
+    if (inputElement.multiple()) {
+        if (auto* files = inputElement.files()) {
+            for (auto& file : files->files())
+                fileObjects.append(file.copyRef());
+        }
+    }
+    for (const auto& path : filenames)
+        fileObjects.append(File::create(path));
+    inputElement.setFiles(FileList::create(WTFMove(fileObjects)));
+
+    completionHandler(WTF::nullopt);
+}
+
 static WebCore::IntRect snapshotRectForScreenshot(WebPage& page, WebCore::Element* element, bool clipToViewport)
 {
     auto* frameView = page.mainFrameView();

Modified: trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h (253029 => 253030)


--- trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.h	2019-12-03 10:03:32 UTC (rev 253030)
@@ -71,6 +71,7 @@
     void focusFrame(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>);
     void computeElementLayout(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String nodeHandle, bool scrollIntoViewIfNeeded, CoordinateSystem, CompletionHandler<void(Optional<String>, WebCore::IntRect, Optional<WebCore::IntPoint>, bool)>&&);
     void selectOptionElement(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String nodeHandle, CompletionHandler<void(Optional<String>)>&&);
+    void setFilesForInputFileUpload(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String nodeHandle, Vector<String>&& filenames, CompletionHandler<void(Optional<String>)>&&);
     void takeScreenshot(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String nodeHandle, bool scrollIntoViewIfNeeded, bool clipToViewport, uint64_t callbackID);
     void getCookiesForFrame(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, CompletionHandler<void(Optional<String>, Vector<WebCore::Cookie>)>&&);
     void deleteCookie(WebCore::PageIdentifier, Optional<WebCore::FrameIdentifier>, String cookieName, CompletionHandler<void(Optional<String>)>&&);

Modified: trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in (253029 => 253030)


--- trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.messages.in	2019-12-03 10:03:32 UTC (rev 253030)
@@ -34,6 +34,8 @@
 
     SelectOptionElement(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID, String nodeHandle) -> (Optional<String> errorType) Async
 
+    SetFilesForInputFileUpload(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID, String nodeHandle, Vector<String> filenames) -> (Optional<String> errorType) Async
+
     TakeScreenshot(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool clipToViewport, uint64_t callbackID)
 
     GetCookiesForFrame(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> frameID) -> (Optional<String> errorType, Vector<WebCore::Cookie> cookies) Async

Modified: trunk/WebDriverTests/ChangeLog (253029 => 253030)


--- trunk/WebDriverTests/ChangeLog	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/WebDriverTests/ChangeLog	2019-12-03 10:03:32 UTC (rev 253030)
@@ -1,5 +1,16 @@
 2019-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
 
+        WebDriver: handle elements of type file in send keys command
+        https://bugs.webkit.org/show_bug.cgi?id=188514
+
+        Reviewed by Brian Burg.
+
+        Remove expectations for tests that are now passing.
+
+        * TestExpectations.json:
+
+2019-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
+
         WebDriver: most of the clear tests are failing
         https://bugs.webkit.org/show_bug.cgi?id=180404
 

Modified: trunk/WebDriverTests/TestExpectations.json (253029 => 253030)


--- trunk/WebDriverTests/TestExpectations.json	2019-12-03 09:32:14 UTC (rev 253029)
+++ trunk/WebDriverTests/TestExpectations.json	2019-12-03 10:03:32 UTC (rev 253030)
@@ -406,13 +406,6 @@
             }
         }
     },
-    "imported/w3c/webdriver/tests/element_click/file_upload.py": {
-        "subtests": {
-            "test_file_upload_state": {
-                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188513"}}
-            }
-        }
-    },
     "imported/w3c/webdriver/tests/element_click/interactability.py": {
         "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188545"}},
         "subtests": {
@@ -500,9 +493,6 @@
     },
     "imported/w3c/webdriver/tests/element_send_keys/events.py": {
         "subtests": {
-            "test_file_upload": {
-                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
-            },
             "test_not_blurred[input]": {
                 "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188118"}}
             },
@@ -511,14 +501,6 @@
             }
         }
     },
-    "imported/w3c/webdriver/tests/element_send_keys/file_upload.py": {
-        "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}},
-        "subtests": {
-            "test_empty_text": {
-                "expected": {"all": {"status": ["PASS"]}}
-            }
-        }
-    },
     "imported/w3c/webdriver/tests/element_send_keys/form_controls.py": {
         "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/182331"}},
         "subtests": {
@@ -611,36 +593,6 @@
             }
         }
     },
-    "imported/w3c/webdriver/tests/new_session/create_alwaysMatch.py": {
-        "subtests": {
-            "test_valid[strictFileInteractability-True]": {
-                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
-            },
-            "test_valid[strictFileInteractability-False]": {
-                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
-            }
-        }
-    },
-    "imported/w3c/webdriver/tests/new_session/create_firstMatch.py": {
-        "subtests": {
-            "test_valid[strictFileInteractability-True]": {
-                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
-            },
-            "test_valid[strictFileInteractability-False]": {
-                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
-            }
-        }
-    },
-    "imported/w3c/webdriver/tests/new_session/response.py": {
-        "subtests": {
-            "test_capability_type[strictFileInteractability-bool]": {
-                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
-            },
-            "test_capability_default_value[strictFileInteractability-False]": {
-                "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/188514"}}
-            }
-        }
-    },
     "imported/w3c/webdriver/tests/new_session/default_values.py": {
         "subtests": {
             "test_valid_but_unmatchable_key": {
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to