Title: [220315] trunk/Source/WebDriver
Revision
220315
Author
carlo...@webkit.org
Date
2017-08-05 02:27:15 -0700 (Sat, 05 Aug 2017)

Log Message

WebDriver: properly handle capabilities and process firstMatch too
https://bugs.webkit.org/show_bug.cgi?id=174618

Reviewed by Brian Burg.

Implement processing of capabilities following the spec. This patch adds validation, merging and matching of
capabilities.

7.2 Processing Capabilities.
https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities

* Capabilities.h: Make all capabilities optional and move Timeouts struct here.
* Session.h:
* WebDriverService.cpp:
(WebDriver::deserializeTimeouts): Helper to extract timeouts from JSON object.
(WebDriver::WebDriverService::parseCapabilities const): At this point capabilities have already been validated,
so we just need to get them without checking the value type.
(WebDriver::WebDriverService::validatedCapabilities const): Validate the given capabilities, ensuring types of
values are the expected one.
(WebDriver::WebDriverService::mergeCapabilities const): Merge the alwaysMatch and firstMatch capabilities into a
single object ensuring that the same capability is not in both.
(WebDriver::WebDriverService::matchCapabilities const): Try to match the merged capabilities againt the platform
expected capabilities.
(WebDriver::WebDriverService::processCapabilities const): Validate, merge and match the capabilities.
(WebDriver::WebDriverService::newSession): Use processCapabilities(). Also initialize the timeouts from
capabilities and add all capabilities to the command result.
(WebDriver::WebDriverService::setTimeouts): Use deserializeTimeouts().
* WebDriverService.h:
* glib/SessionHostGlib.cpp:
(WebDriver::SessionHost::launchBrowser): Updated to properly access the capabilities that are now optional.
(WebDriver::SessionHost::startAutomationSession): Add FIXME.
* gtk/WebDriverServiceGtk.cpp:
(WebDriver::WebDriverService::platformCapabilities): Return the Capabilities with the known required ones filled.
(WebDriver::WebDriverService::platformValidateCapability const): Validate webkitgtk:browserOptions.
(WebDriver::WebDriverService::platformMatchCapability const): This does nothing for now.
(WebDriver::WebDriverService::platformCompareBrowserVersions): Compare the given browser versions.
(WebDriver::WebDriverService::platformParseCapabilities const): Updated now that capabilites have already been
validated before.

Modified Paths

Diff

Modified: trunk/Source/WebDriver/Capabilities.h (220314 => 220315)


--- trunk/Source/WebDriver/Capabilities.h	2017-08-05 08:11:11 UTC (rev 220314)
+++ trunk/Source/WebDriver/Capabilities.h	2017-08-05 09:27:15 UTC (rev 220315)
@@ -26,19 +26,28 @@
 #pragma once
 
 #include <wtf/Forward.h>
+#include <wtf/Seconds.h>
 #include <wtf/Vector.h>
 #include <wtf/text/WTFString.h>
 
 namespace WebDriver {
 
+struct Timeouts {
+    std::optional<Seconds> script;
+    std::optional<Seconds> pageLoad;
+    std::optional<Seconds> implicit;
+};
+
 struct Capabilities {
-    String browserName;
-    String browserVersion;
-    String platform;
+    std::optional<String> browserName;
+    std::optional<String> browserVersion;
+    std::optional<String> platformName;
+    std::optional<bool> acceptInsecureCerts;
+    std::optional<Timeouts> timeouts;
 #if PLATFORM(GTK)
-    String browserBinary;
-    Vector<String> browserArguments;
-    bool useOverlayScrollbars { true };
+    std::optional<String> browserBinary;
+    std::optional<Vector<String>> browserArguments;
+    std::optional<bool> useOverlayScrollbars;
 #endif
 };
 

Modified: trunk/Source/WebDriver/ChangeLog (220314 => 220315)


--- trunk/Source/WebDriver/ChangeLog	2017-08-05 08:11:11 UTC (rev 220314)
+++ trunk/Source/WebDriver/ChangeLog	2017-08-05 09:27:15 UTC (rev 220315)
@@ -1,5 +1,46 @@
 2017-08-05  Carlos Garcia Campos  <cgar...@igalia.com>
 
+        WebDriver: properly handle capabilities and process firstMatch too
+        https://bugs.webkit.org/show_bug.cgi?id=174618
+
+        Reviewed by Brian Burg.
+
+        Implement processing of capabilities following the spec. This patch adds validation, merging and matching of
+        capabilities.
+
+        7.2 Processing Capabilities.
+        https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities
+
+        * Capabilities.h: Make all capabilities optional and move Timeouts struct here.
+        * Session.h:
+        * WebDriverService.cpp:
+        (WebDriver::deserializeTimeouts): Helper to extract timeouts from JSON object.
+        (WebDriver::WebDriverService::parseCapabilities const): At this point capabilities have already been validated,
+        so we just need to get them without checking the value type.
+        (WebDriver::WebDriverService::validatedCapabilities const): Validate the given capabilities, ensuring types of
+        values are the expected one.
+        (WebDriver::WebDriverService::mergeCapabilities const): Merge the alwaysMatch and firstMatch capabilities into a
+        single object ensuring that the same capability is not in both.
+        (WebDriver::WebDriverService::matchCapabilities const): Try to match the merged capabilities againt the platform
+        expected capabilities.
+        (WebDriver::WebDriverService::processCapabilities const): Validate, merge and match the capabilities.
+        (WebDriver::WebDriverService::newSession): Use processCapabilities(). Also initialize the timeouts from
+        capabilities and add all capabilities to the command result.
+        (WebDriver::WebDriverService::setTimeouts): Use deserializeTimeouts().
+        * WebDriverService.h:
+        * glib/SessionHostGlib.cpp:
+        (WebDriver::SessionHost::launchBrowser): Updated to properly access the capabilities that are now optional.
+        (WebDriver::SessionHost::startAutomationSession): Add FIXME.
+        * gtk/WebDriverServiceGtk.cpp:
+        (WebDriver::WebDriverService::platformCapabilities): Return the Capabilities with the known required ones filled.
+        (WebDriver::WebDriverService::platformValidateCapability const): Validate webkitgtk:browserOptions.
+        (WebDriver::WebDriverService::platformMatchCapability const): This does nothing for now.
+        (WebDriver::WebDriverService::platformCompareBrowserVersions): Compare the given browser versions.
+        (WebDriver::WebDriverService::platformParseCapabilities const): Updated now that capabilites have already been
+        validated before.
+
+2017-08-05  Carlos Garcia Campos  <cgar...@igalia.com>
+
         WebDriver: use in-view center point for clicks instead of bounding box center point
         https://bugs.webkit.org/show_bug.cgi?id=174863
 

Modified: trunk/Source/WebDriver/Session.h (220314 => 220315)


--- trunk/Source/WebDriver/Session.h	2017-08-05 08:11:11 UTC (rev 220314)
+++ trunk/Source/WebDriver/Session.h	2017-08-05 09:27:15 UTC (rev 220315)
@@ -25,11 +25,11 @@
 
 #pragma once
 
+#include "Capabilities.h"
 #include <wtf/Forward.h>
 #include <wtf/Function.h>
 #include <wtf/OptionSet.h>
 #include <wtf/RefCounted.h>
-#include <wtf/Seconds.h>
 #include <wtf/Vector.h>
 #include <wtf/text/WTFString.h>
 
@@ -41,7 +41,6 @@
 
 namespace WebDriver {
 
-class Capabilities;
 class CommandResult;
 class SessionHost;
 
@@ -60,12 +59,6 @@
     enum class ExecuteScriptMode { Sync, Async };
     enum class Timeout { Script, PageLoad, Implicit };
 
-    struct Timeouts {
-        std::optional<Seconds> script;
-        std::optional<Seconds> pageLoad;
-        std::optional<Seconds> implicit;
-    };
-
     void waitForNavigationToComplete(Function<void (CommandResult&&)>&&);
     void createTopLevelBrowsingContext(Function<void (CommandResult&&)>&&);
     void close(Function<void (CommandResult&&)>&&);

Modified: trunk/Source/WebDriver/WebDriverService.cpp (220314 => 220315)


--- trunk/Source/WebDriver/WebDriverService.cpp	2017-08-05 08:11:11 UTC (rev 220314)
+++ trunk/Source/WebDriver/WebDriverService.cpp	2017-08-05 09:27:15 UTC (rev 220315)
@@ -251,25 +251,53 @@
     replyHandler({ result.httpStatusCode(), responseObject->toJSONString().utf8(), ASCIILiteral("application/json; charset=utf-8") });
 }
 
-bool WebDriverService::parseCapabilities(InspectorObject& desiredCapabilities, Capabilities& capabilities, Function<void (CommandResult&&)>& completionHandler)
+static std::optional<Timeouts> deserializeTimeouts(InspectorObject& timeoutsObject)
 {
-    RefPtr<InspectorValue> value;
-    if (desiredCapabilities.getValue(ASCIILiteral("browserName"), value) && !value->asString(capabilities.browserName)) {
-        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("browserName parameter is invalid in capabilities")));
-        return false;
+    // §8.5 Set Timeouts.
+    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-deserialize-as-a-timeout
+    Timeouts timeouts;
+    auto end = timeoutsObject.end();
+    for (auto it = timeoutsObject.begin(); it != end; ++it) {
+        if (it->key == "sessionId")
+            continue;
+
+        int timeoutMS;
+        if (!it->value->asInteger(timeoutMS) || timeoutMS < 0 || timeoutMS > INT_MAX)
+            return std::nullopt;
+
+        if (it->key == "script")
+            timeouts.script = Seconds::fromMilliseconds(timeoutMS);
+        else if (it->key == "pageLoad")
+            timeouts.pageLoad = Seconds::fromMilliseconds(timeoutMS);
+        else if (it->key == "implicit")
+            timeouts.implicit = Seconds::fromMilliseconds(timeoutMS);
+        else
+            return std::nullopt;
     }
-    if (desiredCapabilities.getValue(ASCIILiteral("version"), value) && !value->asString(capabilities.browserVersion)) {
-        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("version parameter is invalid in capabilities")));
-        return false;
-    }
-    if (desiredCapabilities.getValue(ASCIILiteral("platform"), value) && !value->asString(capabilities.platform)) {
-        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("platform parameter is invalid in capabilities")));
-        return false;
-    }
-    // FIXME: parse all other well-known capabilities: acceptInsecureCerts, pageLoadStrategy, proxy, setWindowRect, timeouts, unhandledPromptBehavior.
-    return platformParseCapabilities(desiredCapabilities, capabilities, completionHandler);
+    return timeouts;
 }
 
+void WebDriverService::parseCapabilities(const InspectorObject& matchedCapabilities, Capabilities& capabilities) const
+{
+    // Matched capabilities have already been validated.
+    bool acceptInsecureCerts;
+    if (matchedCapabilities.getBoolean(ASCIILiteral("acceptInsecureCerts"), acceptInsecureCerts))
+        capabilities.acceptInsecureCerts = acceptInsecureCerts;
+    String browserName;
+    if (matchedCapabilities.getString(ASCIILiteral("browserName"), browserName))
+        capabilities.browserName = browserName;
+    String browserVersion;
+    if (matchedCapabilities.getString(ASCIILiteral("browserVersion"), browserVersion))
+        capabilities.browserVersion = browserVersion;
+    String platformName;
+    if (matchedCapabilities.getString(ASCIILiteral("platformName"), platformName))
+        capabilities.platformName = platformName;
+    RefPtr<InspectorObject> timeouts;
+    if (matchedCapabilities.getObject(ASCIILiteral("timeouts"), timeouts))
+        capabilities.timeouts = deserializeTimeouts(*timeouts);
+    platformParseCapabilities(matchedCapabilities, capabilities);
+}
+
 RefPtr<Session> WebDriverService::findSessionOrCompleteWithError(InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler)
 {
     String sessionID;
@@ -287,29 +315,208 @@
     return session;
 }
 
-void WebDriverService::newSession(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
+RefPtr<InspectorObject> WebDriverService::validatedCapabilities(const InspectorObject& capabilities) const
 {
-    // §8.1 New Session.
-    // https://www.w3.org/TR/webdriver/#new-session
+    // §7.2 Processing Capabilities.
+    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-validate-capabilities
+    auto result = InspectorObject::create();
+    auto end = capabilities.end();
+    for (auto it = capabilities.begin(); it != end; ++it) {
+        if (it->value->isNull())
+            result->setValue(it->key, RefPtr<InspectorValue>(it->value));
+        else if (it->key == "acceptInsecureCerts") {
+            bool acceptInsecureCerts;
+            if (!it->value->asBoolean(acceptInsecureCerts))
+                return nullptr;
+            result->setBoolean(it->key, acceptInsecureCerts);
+        } else if (it->key == "browserName" || it->key == "browserVersion" || it->key == "platformName") {
+            String stringValue;
+            if (!it->value->asString(stringValue))
+                return nullptr;
+            result->setString(it->key, stringValue);
+        } else if (it->key == "pageLoadStrategy") {
+            String pageLoadStrategy;
+            if (!it->value->asString(pageLoadStrategy))
+                return nullptr;
+            // FIXME: implement pageLoadStrategy.
+            result->setString(it->key, pageLoadStrategy);
+        } else if (it->key == "proxy") {
+            // FIXME: implement proxy support.
+        } else if (it->key == "timeouts") {
+            RefPtr<InspectorObject> timeouts;
+            if (!it->value->asObject(timeouts) || !deserializeTimeouts(*timeouts))
+                return nullptr;
+            result->setValue(it->key, RefPtr<InspectorValue>(it->value));
+        } else if (it->key == "unhandledPromptBehavior") {
+            String unhandledPromptBehavior;
+            if (!it->value->asString(unhandledPromptBehavior))
+                return nullptr;
+            // FIXME: implement prompts support.
+            result->setString(it->key, unhandledPromptBehavior);
+        } else if (it->key.find(":") != notFound) {
+            if (!platformValidateCapability(it->key, it->value))
+                return nullptr;
+            result->setValue(it->key, RefPtr<InspectorValue>(it->value));
+        }
+    }
+    return result;
+}
+
+RefPtr<InspectorObject> WebDriverService::mergeCapabilities(const InspectorObject& requiredCapabilities, const InspectorObject& firstMatchCapabilities) const
+{
+    // §7.2 Processing Capabilities.
+    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-merging-capabilities
+    auto result = InspectorObject::create();
+    auto requiredEnd = requiredCapabilities.end();
+    for (auto it = requiredCapabilities.begin(); it != requiredEnd; ++it)
+        result->setValue(it->key, RefPtr<InspectorValue>(it->value));
+
+    auto firstMatchEnd = firstMatchCapabilities.end();
+    for (auto it = firstMatchCapabilities.begin(); it != firstMatchEnd; ++it) {
+        if (requiredCapabilities.find(it->key) != requiredEnd)
+            return nullptr;
+
+        result->setValue(it->key, RefPtr<InspectorValue>(it->value));
+    }
+
+    return result;
+}
+
+std::optional<String> WebDriverService::matchCapabilities(const InspectorObject& mergedCapabilities) const
+{
+    // §7.2 Processing Capabilities.
+    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-matching-capabilities
+    Capabilities matchedCapabilities = platformCapabilities();
+
+    // Some capabilities like browser name and version might need to launch the browser,
+    // so we only reject the known capabilities that don't match.
+    auto end = mergedCapabilities.end();
+    for (auto it = mergedCapabilities.begin(); it != end; ++it) {
+        if (it->key == "browserName" && matchedCapabilities.browserName) {
+            String browserName;
+            it->value->asString(browserName);
+            if (!equalIgnoringASCIICase(matchedCapabilities.browserName.value(), browserName))
+                return makeString("expected browserName ", matchedCapabilities.browserName.value(), " but got ", browserName);
+        } else if (it->key == "browserVersion" && matchedCapabilities.browserVersion) {
+            String browserVersion;
+            it->value->asString(browserVersion);
+            if (!platformCompareBrowserVersions(browserVersion, matchedCapabilities.browserVersion.value()))
+                return makeString("requested browserVersion is ", browserVersion, " but actual version is ", matchedCapabilities.browserVersion.value());
+        } else if (it->key == "platformName" && matchedCapabilities.platformName) {
+            String platformName;
+            it->value->asString(platformName);
+            if (!equalLettersIgnoringASCIICase(platformName, "any") && !equalIgnoringASCIICase(matchedCapabilities.platformName.value(), platformName))
+                return makeString("expected platformName ", matchedCapabilities.platformName.value(), " but got ", platformName);
+        } else if (it->key == "acceptInsecureCerts" && matchedCapabilities.acceptInsecureCerts) {
+            bool acceptInsecureCerts;
+            it->value->asBoolean(acceptInsecureCerts);
+            if (acceptInsecureCerts && !matchedCapabilities.acceptInsecureCerts.value())
+                return String("browser doesn't accept insecure TLS certificates");
+        } else if (it->key == "proxy") {
+            // FIXME: implement proxy support.
+        } else if (auto errorString = platformMatchCapability(it->key, it->value))
+            return errorString;
+    }
+
+    return std::nullopt;
+}
+
+RefPtr<InspectorObject> WebDriverService::processCapabilities(const InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler) const
+{
+    // §7.2 Processing Capabilities.
+    // https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities
+
+    // 1. Let capabilities request be the result of getting the property "capabilities" from parameters.
     RefPtr<InspectorObject> capabilitiesObject;
-    if (!parameters->getObject(ASCIILiteral("capabilities"), capabilitiesObject)) {
+    if (!parameters.getObject(ASCIILiteral("capabilities"), capabilitiesObject)) {
         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated));
-        return;
+        return nullptr;
     }
+
+    // 2. Let required capabilities be the result of getting the property "alwaysMatch" from capabilities request.
     RefPtr<InspectorValue> requiredCapabilitiesValue;
     RefPtr<InspectorObject> requiredCapabilities;
     if (!capabilitiesObject->getValue(ASCIILiteral("alwaysMatch"), requiredCapabilitiesValue))
+        // 2.1. If required capabilities is undefined, set the value to an empty JSON Object.
         requiredCapabilities = InspectorObject::create();
     else if (!requiredCapabilitiesValue->asObject(requiredCapabilities)) {
         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("alwaysMatch is invalid in capabilities")));
-        return;
+        return nullptr;
     }
-    // FIXME: process firstMatch capabilities.
 
-    Capabilities capabilities;
-    if (!parseCapabilities(*requiredCapabilities, capabilities, completionHandler))
+    // 2.2. Let required capabilities be the result of trying to validate capabilities with argument required capabilities.
+    requiredCapabilities = validatedCapabilities(*requiredCapabilities);
+    if (!requiredCapabilities) {
+        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Invalid alwaysMatch capabilities")));
+        return nullptr;
+    }
+
+    // 3. Let all first match capabilities be the result of getting the property "firstMatch" from capabilities request.
+    RefPtr<InspectorValue> firstMatchCapabilitiesValue;
+    RefPtr<InspectorArray> firstMatchCapabilitiesList;
+    if (!capabilitiesObject->getValue(ASCIILiteral("firstMatch"), firstMatchCapabilitiesValue)) {
+        // 3.1. If all first match capabilities is undefined, set the value to a JSON List with a single entry of an empty JSON Object.
+        firstMatchCapabilitiesList = InspectorArray::create();
+        firstMatchCapabilitiesList->pushObject(InspectorObject::create());
+    } else if (!firstMatchCapabilitiesValue->asArray(firstMatchCapabilitiesList)) {
+        // 3.2. If all first match capabilities is not a JSON List, return error with error code invalid argument.
+        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("firstMatch is invalid in capabilities")));
+        return nullptr;
+    }
+
+    // 4. Let validated first match capabilities be an empty JSON List.
+    Vector<RefPtr<InspectorObject>> validatedFirstMatchCapabilitiesList;
+    auto firstMatchCapabilitiesListLength = firstMatchCapabilitiesList->length();
+    validatedFirstMatchCapabilitiesList.reserveInitialCapacity(firstMatchCapabilitiesListLength);
+    // 5. For each first match capabilities corresponding to an indexed property in all first match capabilities.
+    for (unsigned i = 0; i < firstMatchCapabilitiesListLength; ++i) {
+        RefPtr<InspectorValue> firstMatchCapabilitiesValue = firstMatchCapabilitiesList->get(i);
+        RefPtr<InspectorObject> firstMatchCapabilities;
+        if (!firstMatchCapabilitiesValue->asObject(firstMatchCapabilities)) {
+            completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Invalid capabilities found in firstMatch")));
+            return nullptr;
+        }
+        // 5.1. Let validated capabilities be the result of trying to validate capabilities with argument first match capabilities.
+        firstMatchCapabilities = validatedCapabilities(*firstMatchCapabilities);
+        if (!firstMatchCapabilities) {
+            completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Invalid firstMatch capabilities")));
+            return nullptr;
+        }
+        // 5.2. Append validated capabilities to validated first match capabilities.
+        validatedFirstMatchCapabilitiesList.uncheckedAppend(WTFMove(firstMatchCapabilities));
+    }
+
+    // 6. For each first match capabilities corresponding to an indexed property in validated first match capabilities.
+    std::optional<String> errorString;
+    for (auto& validatedFirstMatchCapabilies : validatedFirstMatchCapabilitiesList) {
+        // 6.1. Let merged capabilities be the result of trying to merge capabilities with required capabilities and first match capabilities as arguments.
+        auto mergedCapabilities = mergeCapabilities(*requiredCapabilities, *validatedFirstMatchCapabilies);
+        if (!mergedCapabilities) {
+            completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Same capability found in firstMatch and alwaysMatch")));
+            return nullptr;
+        }
+        // 6.2. Let matched capabilities be the result of trying to match capabilities with merged capabilities as an argument.
+        errorString = matchCapabilities(*mergedCapabilities);
+        if (!errorString) {
+            // 6.3. If matched capabilities is not null return matched capabilities.
+            return mergedCapabilities;
+        }
+    }
+
+    completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, errorString ? errorString.value() : String("Invalid capabilities")));
+    return nullptr;
+}
+
+void WebDriverService::newSession(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
+{
+    // §8.1 New Session.
+    // https://www.w3.org/TR/webdriver/#new-session
+    auto matchedCapabilities = processCapabilities(*parameters, completionHandler);
+    if (!matchedCapabilities)
         return;
 
+    Capabilities capabilities;
+    parseCapabilities(*matchedCapabilities, capabilities);
     auto sessionHost = std::make_unique<SessionHost>(WTFMove(capabilities));
     auto* sessionHostPtr = sessionHost.get();
     sessionHostPtr->connectToBrowser([this, sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](SessionHost::Succeeded succeeded) mutable {
@@ -321,19 +528,39 @@
         RefPtr<Session> session = Session::create(WTFMove(sessionHost));
         session->createTopLevelBrowsingContext([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
             if (result.isError()) {
-                completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorString()));
+                completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorMessage()));
                 return;
             }
 
             m_activeSession = session.get();
             m_sessions.add(session->id(), session);
+
+            const auto& capabilities = session->capabilities();
+            if (capabilities.timeouts)
+                session->setTimeouts(capabilities.timeouts.value(), [](CommandResult&&) { });
+
             RefPtr<InspectorObject> resultObject = InspectorObject::create();
             resultObject->setString(ASCIILiteral("sessionId"), session->id());
-            RefPtr<InspectorObject> capabilities = InspectorObject::create();
-            capabilities->setString(ASCIILiteral("browserName"), session->capabilities().browserName);
-            capabilities->setString(ASCIILiteral("version"), session->capabilities().browserVersion);
-            capabilities->setString(ASCIILiteral("platform"), session->capabilities().platform);
-            resultObject->setObject(ASCIILiteral("value"), WTFMove(capabilities));
+            RefPtr<InspectorObject> capabilitiesObject = InspectorObject::create();
+            if (capabilities.browserName)
+                capabilitiesObject->setString(ASCIILiteral("browserName"), capabilities.browserName.value());
+            if (capabilities.browserVersion)
+                capabilitiesObject->setString(ASCIILiteral("browserVersion"), capabilities.browserVersion.value());
+            if (capabilities.platformName)
+                capabilitiesObject->setString(ASCIILiteral("platformName"), capabilities.platformName.value());
+            if (capabilities.acceptInsecureCerts)
+                capabilitiesObject->setBoolean(ASCIILiteral("acceptInsecureCerts"), capabilities.acceptInsecureCerts.value());
+            if (capabilities.timeouts) {
+                RefPtr<InspectorObject> timeoutsObject = InspectorObject::create();
+                if (capabilities.timeouts.value().script)
+                    timeoutsObject->setInteger(ASCIILiteral("script"), capabilities.timeouts.value().script.value().millisecondsAs<int>());
+                if (capabilities.timeouts.value().pageLoad)
+                    timeoutsObject->setInteger(ASCIILiteral("pageLoad"), capabilities.timeouts.value().pageLoad.value().millisecondsAs<int>());
+                if (capabilities.timeouts.value().implicit)
+                    timeoutsObject->setInteger(ASCIILiteral("implicit"), capabilities.timeouts.value().implicit.value().millisecondsAs<int>());
+                capabilitiesObject->setObject(ASCIILiteral("timeouts"), WTFMove(timeoutsObject));
+            }
+            resultObject->setObject(ASCIILiteral("value"), WTFMove(capabilitiesObject));
             completionHandler(CommandResult::success(WTFMove(resultObject)));
         });
     });
@@ -369,31 +596,13 @@
     if (!session)
         return;
 
-    Session::Timeouts timeouts;
-    auto end = parameters->end();
-    for (auto it = parameters->begin(); it != end; ++it) {
-        if (it->key == "sessionId")
-            continue;
-
-        int timeoutMS;
-        if (!it->value->asInteger(timeoutMS) || timeoutMS < 0 || timeoutMS > INT_MAX) {
-            completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
-            return;
-        }
-
-        if (it->key == "script")
-            timeouts.script = Seconds::fromMilliseconds(timeoutMS);
-        else if (it->key == "pageLoad")
-            timeouts.pageLoad = Seconds::fromMilliseconds(timeoutMS);
-        else if (it->key == "implicit")
-            timeouts.implicit = Seconds::fromMilliseconds(timeoutMS);
-        else {
-            completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
-            return;
-        }
+    auto timeouts = deserializeTimeouts(*parameters);
+    if (!timeouts) {
+        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
+        return;
     }
 
-    session->setTimeouts(timeouts, WTFMove(completionHandler));
+    session->setTimeouts(timeouts.value(), WTFMove(completionHandler));
 }
 
 void WebDriverService::go(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)

Modified: trunk/Source/WebDriver/WebDriverService.h (220314 => 220315)


--- trunk/Source/WebDriver/WebDriverService.h	2017-08-05 08:11:11 UTC (rev 220314)
+++ trunk/Source/WebDriver/WebDriverService.h	2017-08-05 09:27:15 UTC (rev 220315)
@@ -50,6 +50,8 @@
     int run(int argc, char** argv);
     void quit();
 
+    static bool platformCompareBrowserVersions(const String&, const String&);
+
 private:
     enum class HTTPMethod { Get, Post, Delete };
     typedef void (WebDriverService::*CommandHandler)(RefPtr<Inspector::InspectorObject>&&, Function<void (CommandResult&&)>&&);
@@ -100,8 +102,15 @@
     void executeScript(RefPtr<Inspector::InspectorObject>&&, Function<void (CommandResult&&)>&&);
     void executeAsyncScript(RefPtr<Inspector::InspectorObject>&&, Function<void (CommandResult&&)>&&);
 
-    bool parseCapabilities(Inspector::InspectorObject& desiredCapabilities, Capabilities&, Function<void (CommandResult&&)>&);
-    bool platformParseCapabilities(Inspector::InspectorObject& desiredCapabilities, Capabilities&, Function<void (CommandResult&&)>&);
+    static Capabilities platformCapabilities();
+    RefPtr<Inspector::InspectorObject> processCapabilities(const Inspector::InspectorObject&, Function<void (CommandResult&&)>&) const;
+    RefPtr<Inspector::InspectorObject> validatedCapabilities(const Inspector::InspectorObject&) const;
+    RefPtr<Inspector::InspectorObject> mergeCapabilities(const Inspector::InspectorObject&, const Inspector::InspectorObject&) const;
+    std::optional<String> matchCapabilities(const Inspector::InspectorObject&) const;
+    bool platformValidateCapability(const String&, const RefPtr<Inspector::InspectorValue>&) const;
+    std::optional<String> platformMatchCapability(const String&, const RefPtr<Inspector::InspectorValue>&) const;
+    void parseCapabilities(const Inspector::InspectorObject& desiredCapabilities, Capabilities&) const;
+    void platformParseCapabilities(const Inspector::InspectorObject& desiredCapabilities, Capabilities&) const;
     RefPtr<Session> findSessionOrCompleteWithError(Inspector::InspectorObject&, Function<void (CommandResult&&)>&);
 
     void handleRequest(HTTPRequestHandler::Request&&, Function<void (HTTPRequestHandler::Response&&)>&& replyHandler) override;

Modified: trunk/Source/WebDriver/glib/SessionHostGlib.cpp (220314 => 220315)


--- trunk/Source/WebDriver/glib/SessionHostGlib.cpp	2017-08-05 08:11:11 UTC (rev 220314)
+++ trunk/Source/WebDriver/glib/SessionHostGlib.cpp	2017-08-05 09:27:15 UTC (rev 220315)
@@ -135,13 +135,14 @@
     GUniquePtr<char> inspectorAddress(g_strdup_printf("127.0.0.1:%u", port));
     g_subprocess_launcher_setenv(launcher.get(), "WEBKIT_INSPECTOR_SERVER", inspectorAddress.get(), TRUE);
 #if PLATFORM(GTK)
-    g_subprocess_launcher_setenv(launcher.get(), "GTK_OVERLAY_SCROLLING", m_capabilities.useOverlayScrollbars ? "1" : "0", TRUE);
+    g_subprocess_launcher_setenv(launcher.get(), "GTK_OVERLAY_SCROLLING", m_capabilities.useOverlayScrollbars.value() ? "1" : "0", TRUE);
 #endif
 
-    GUniquePtr<char*> args(g_new0(char*, m_capabilities.browserArguments.size() + 2));
-    args.get()[0] = g_strdup(m_capabilities.browserBinary.utf8().data());
-    for (unsigned i = 0; i < m_capabilities.browserArguments.size(); ++i)
-        args.get()[i + 1] = g_strdup(m_capabilities.browserArguments[i].utf8().data());
+    const auto& browserArguments = m_capabilities.browserArguments.value();
+    GUniquePtr<char*> args(g_new0(char*, browserArguments.size() + 2));
+    args.get()[0] = g_strdup(m_capabilities.browserBinary.value().utf8().data());
+    for (unsigned i = 0; i < browserArguments.size(); ++i)
+        args.get()[i + 1] = g_strdup(browserArguments[i].utf8().data());
 
     m_browser = adoptGRef(g_subprocess_launcher_spawnv(launcher.get(), args.get(), nullptr));
     g_subprocess_wait_async(m_browser.get(), m_cancellable.get(), [](GObject* browser, GAsyncResult* result, gpointer userData) {
@@ -223,6 +224,7 @@
 {
     ASSERT(m_dbusConnection);
     ASSERT(!m_startSessionCompletionHandler);
+    // FIXME: Make StartAutomationSession return browser information and we use it to match capabilities.
     m_startSessionCompletionHandler = WTFMove(completionHandler);
     g_dbus_connection_call(m_dbusConnection.get(), nullptr,
         INSPECTOR_DBUS_OBJECT_PATH,

Modified: trunk/Source/WebDriver/gtk/WebDriverServiceGtk.cpp (220314 => 220315)


--- trunk/Source/WebDriver/gtk/WebDriverServiceGtk.cpp	2017-08-05 08:11:11 UTC (rev 220314)
+++ trunk/Source/WebDriver/gtk/WebDriverServiceGtk.cpp	2017-08-05 09:27:15 UTC (rev 220315)
@@ -34,47 +34,81 @@
 
 namespace WebDriver {
 
-bool WebDriverService::platformParseCapabilities(InspectorObject& desiredCapabilities, Capabilities& capabilities, Function<void (CommandResult&&)>& completionHandler)
+Capabilities WebDriverService::platformCapabilities()
 {
-    RefPtr<InspectorValue> value;
+    Capabilities capabilities;
+    capabilities.platformName = String("linux");
+    capabilities.acceptInsecureCerts = false;
+    return capabilities;
+}
+
+bool WebDriverService::platformValidateCapability(const String& name, const RefPtr<InspectorValue>& value) const
+{
+    if (name != "webkitgtk:browserOptions")
+        return true;
+
     RefPtr<InspectorObject> browserOptions;
-    if (desiredCapabilities.getValue(ASCIILiteral("webkitgtk:browserOptions"), value) && !value->asObject(browserOptions)) {
-        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("webkitgtk:browserOptions is invalid in capabilities")));
+    if (!value->asObject(browserOptions))
         return false;
-    }
-    if (browserOptions->isNull()) {
-        capabilities.browserBinary = LIBEXECDIR "/webkit2gtk-" WEBKITGTK_API_VERSION_STRING "/MiniBrowser";
-        capabilities.browserArguments = { ASCIILiteral("--automation") };
+
+    if (browserOptions->isNull())
         return true;
-    }
 
-    if (!browserOptions->getString(ASCIILiteral("binary"), capabilities.browserBinary)) {
-        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("binary parameter is invalid or missing in webkitgtk:browserOptions")));
+    // If browser options are provided, binary is required.
+    String binary;
+    if (!browserOptions->getString(ASCIILiteral("binary"), binary))
         return false;
-    }
 
+    RefPtr<InspectorValue> useOverlayScrollbarsValue;
+    bool useOverlayScrollbars;
+    if (browserOptions->getValue(ASCIILiteral("useOverlayScrollbars"), useOverlayScrollbarsValue) && !useOverlayScrollbarsValue->asBoolean(useOverlayScrollbars))
+        return false;
+
+    RefPtr<InspectorValue> browserArgumentsValue;
     RefPtr<InspectorArray> browserArguments;
-    if (browserOptions->getValue(ASCIILiteral("args"), value) && !value->asArray(browserArguments)) {
-        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("args parameter is invalid in webkitgtk:browserOptions")));
+    if (browserOptions->getValue(ASCIILiteral("args"), browserArgumentsValue) && !browserArgumentsValue->asArray(browserArguments))
         return false;
-    }
+
     unsigned browserArgumentsLength = browserArguments->length();
-    if (!browserArgumentsLength)
-        return true;
-    capabilities.browserArguments.reserveInitialCapacity(browserArgumentsLength);
     for (unsigned i = 0; i < browserArgumentsLength; ++i) {
         RefPtr<InspectorValue> value = browserArguments->get(i);
         String argument;
-        if (!value->asString(argument)) {
-            capabilities.browserArguments.clear();
-            completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to extract arguments from webkitgtk:browserOptions::args")));
+        if (!value->asString(argument))
             return false;
-        }
-        capabilities.browserArguments.uncheckedAppend(WTFMove(argument));
     }
 
-    if (browserOptions->getValue(ASCIILiteral("useOverlayScrollbars"), value) && !value->asBoolean(capabilities.useOverlayScrollbars)) {
-        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("useOverlayScrollbars parameter is invalid in webkitgtk:browserOptions")));
+    return true;
+}
+
+std::optional<String> WebDriverService::platformMatchCapability(const String&, const RefPtr<InspectorValue>&) const
+{
+    return std::nullopt;
+}
+
+static bool parseVersion(const String& version, uint64_t& major, uint64_t& minor, uint64_t& micro)
+{
+    major = minor = micro = 0;
+
+    Vector<String> tokens;
+    version.split(".", false, tokens);
+    bool ok;
+    switch (tokens.size()) {
+    case 3:
+        micro = tokens[2].toInt64(&ok);
+        if (!ok)
+            return false;
+        FALLTHROUGH;
+    case 2:
+        minor = tokens[1].toInt64(&ok);
+        if (!ok)
+            return false;
+        FALLTHROUGH;
+    case 1:
+        major = tokens[0].toInt64(&ok);
+        if (!ok)
+            return false;
+        break;
+    default:
         return false;
     }
 
@@ -81,4 +115,56 @@
     return true;
 }
 
+bool WebDriverService::platformCompareBrowserVersions(const String& requiredVersion, const String& proposedVersion)
+{
+    // We require clients to use format major.micro.minor as version string.
+    uint64_t requiredMajor, requiredMinor, requiredMicro;
+    if (!parseVersion(requiredVersion, requiredMajor, requiredMinor, requiredMicro))
+        return false;
+
+    uint64_t proposedMajor, proposedMinor, proposedMicro;
+    if (!parseVersion(proposedVersion, proposedMajor, proposedMinor, proposedMicro))
+        return false;
+
+    return proposedMajor > requiredMajor
+        || (proposedMajor == requiredMajor && proposedMinor > requiredMinor)
+        || (proposedMajor == requiredMajor && proposedMinor == requiredMinor && proposedMicro >= requiredMicro);
+}
+
+void WebDriverService::platformParseCapabilities(const InspectorObject& matchedCapabilities, Capabilities& capabilities) const
+{
+    RefPtr<InspectorObject> browserOptions;
+    if (!matchedCapabilities.getObject(ASCIILiteral("webkitgtk:browserOptions"), browserOptions)) {
+        capabilities.browserBinary = String(LIBEXECDIR "/webkit2gtk-" WEBKITGTK_API_VERSION_STRING "/MiniBrowser");
+        capabilities.browserArguments = Vector<String> { ASCIILiteral("--automation") };
+        capabilities.useOverlayScrollbars = true;
+        return;
+    }
+
+    String browserBinary;
+    browserOptions->getString(ASCIILiteral("binary"), browserBinary);
+    ASSERT(!browserBinary.isNull());
+    capabilities.browserBinary = browserBinary;
+
+    capabilities.browserArguments = Vector<String>();
+    RefPtr<InspectorArray> browserArguments;
+    if (browserOptions->getArray(ASCIILiteral("args"), browserArguments)) {
+        unsigned browserArgumentsLength = browserArguments->length();
+        capabilities.browserArguments->reserveInitialCapacity(browserArgumentsLength);
+        for (unsigned i = 0; i < browserArgumentsLength; ++i) {
+            RefPtr<InspectorValue> value = browserArguments->get(i);
+            String argument;
+            value->asString(argument);
+            ASSERT(!argument.isNull());
+            capabilities.browserArguments->uncheckedAppend(WTFMove(argument));
+        }
+    }
+
+    bool useOverlayScrollbars;
+    if (browserOptions->getBoolean(ASCIILiteral("useOverlayScrollbars"), useOverlayScrollbars))
+        capabilities.useOverlayScrollbars = useOverlayScrollbars;
+    else
+        capabilities.useOverlayScrollbars = true;
+}
+
 } // namespace WebDriver
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to