Changeset 220315 in webkit


Ignore:
Timestamp:
Aug 5, 2017 2:27:15 AM (7 years ago)
Author:
Carlos Garcia Campos
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.

Location:
trunk/Source/WebDriver
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebDriver/Capabilities.h

    r219605 r220315  
    2727
    2828#include <wtf/Forward.h>
     29#include <wtf/Seconds.h>
    2930#include <wtf/Vector.h>
    3031#include <wtf/text/WTFString.h>
     
    3233namespace WebDriver {
    3334
     35struct Timeouts {
     36    std::optional<Seconds> script;
     37    std::optional<Seconds> pageLoad;
     38    std::optional<Seconds> implicit;
     39};
     40
    3441struct Capabilities {
    35     String browserName;
    36     String browserVersion;
    37     String platform;
     42    std::optional<String> browserName;
     43    std::optional<String> browserVersion;
     44    std::optional<String> platformName;
     45    std::optional<bool> acceptInsecureCerts;
     46    std::optional<Timeouts> timeouts;
    3847#if PLATFORM(GTK)
    39     String browserBinary;
    40     Vector<String> browserArguments;
    41     bool useOverlayScrollbars { true };
     48    std::optional<String> browserBinary;
     49    std::optional<Vector<String>> browserArguments;
     50    std::optional<bool> useOverlayScrollbars;
    4251#endif
    4352};
  • trunk/Source/WebDriver/ChangeLog

    r220314 r220315  
     12017-08-05  Carlos Garcia Campos  <cgarcia@igalia.com>
     2
     3        WebDriver: properly handle capabilities and process firstMatch too
     4        https://bugs.webkit.org/show_bug.cgi?id=174618
     5
     6        Reviewed by Brian Burg.
     7
     8        Implement processing of capabilities following the spec. This patch adds validation, merging and matching of
     9        capabilities.
     10
     11        7.2 Processing Capabilities.
     12        https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities
     13
     14        * Capabilities.h: Make all capabilities optional and move Timeouts struct here.
     15        * Session.h:
     16        * WebDriverService.cpp:
     17        (WebDriver::deserializeTimeouts): Helper to extract timeouts from JSON object.
     18        (WebDriver::WebDriverService::parseCapabilities const): At this point capabilities have already been validated,
     19        so we just need to get them without checking the value type.
     20        (WebDriver::WebDriverService::validatedCapabilities const): Validate the given capabilities, ensuring types of
     21        values are the expected one.
     22        (WebDriver::WebDriverService::mergeCapabilities const): Merge the alwaysMatch and firstMatch capabilities into a
     23        single object ensuring that the same capability is not in both.
     24        (WebDriver::WebDriverService::matchCapabilities const): Try to match the merged capabilities againt the platform
     25        expected capabilities.
     26        (WebDriver::WebDriverService::processCapabilities const): Validate, merge and match the capabilities.
     27        (WebDriver::WebDriverService::newSession): Use processCapabilities(). Also initialize the timeouts from
     28        capabilities and add all capabilities to the command result.
     29        (WebDriver::WebDriverService::setTimeouts): Use deserializeTimeouts().
     30        * WebDriverService.h:
     31        * glib/SessionHostGlib.cpp:
     32        (WebDriver::SessionHost::launchBrowser): Updated to properly access the capabilities that are now optional.
     33        (WebDriver::SessionHost::startAutomationSession): Add FIXME.
     34        * gtk/WebDriverServiceGtk.cpp:
     35        (WebDriver::WebDriverService::platformCapabilities): Return the Capabilities with the known required ones filled.
     36        (WebDriver::WebDriverService::platformValidateCapability const): Validate webkitgtk:browserOptions.
     37        (WebDriver::WebDriverService::platformMatchCapability const): This does nothing for now.
     38        (WebDriver::WebDriverService::platformCompareBrowserVersions): Compare the given browser versions.
     39        (WebDriver::WebDriverService::platformParseCapabilities const): Updated now that capabilites have already been
     40        validated before.
     41
    1422017-08-05  Carlos Garcia Campos  <cgarcia@igalia.com>
    243
  • trunk/Source/WebDriver/Session.h

    r220314 r220315  
    2626#pragma once
    2727
     28#include "Capabilities.h"
    2829#include <wtf/Forward.h>
    2930#include <wtf/Function.h>
    3031#include <wtf/OptionSet.h>
    3132#include <wtf/RefCounted.h>
    32 #include <wtf/Seconds.h>
    3333#include <wtf/Vector.h>
    3434#include <wtf/text/WTFString.h>
     
    4242namespace WebDriver {
    4343
    44 class Capabilities;
    4544class CommandResult;
    4645class SessionHost;
     
    6059    enum class ExecuteScriptMode { Sync, Async };
    6160    enum class Timeout { Script, PageLoad, Implicit };
    62 
    63     struct Timeouts {
    64         std::optional<Seconds> script;
    65         std::optional<Seconds> pageLoad;
    66         std::optional<Seconds> implicit;
    67     };
    6861
    6962    void waitForNavigationToComplete(Function<void (CommandResult&&)>&&);
  • trunk/Source/WebDriver/WebDriverService.cpp

    r219794 r220315  
    252252}
    253253
    254 bool WebDriverService::parseCapabilities(InspectorObject& desiredCapabilities, Capabilities& capabilities, Function<void (CommandResult&&)>& completionHandler)
    255 {
    256     RefPtr<InspectorValue> value;
    257     if (desiredCapabilities.getValue(ASCIILiteral("browserName"), value) && !value->asString(capabilities.browserName)) {
    258         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("browserName parameter is invalid in capabilities")));
    259         return false;
    260     }
    261     if (desiredCapabilities.getValue(ASCIILiteral("version"), value) && !value->asString(capabilities.browserVersion)) {
    262         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("version parameter is invalid in capabilities")));
    263         return false;
    264     }
    265     if (desiredCapabilities.getValue(ASCIILiteral("platform"), value) && !value->asString(capabilities.platform)) {
    266         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("platform parameter is invalid in capabilities")));
    267         return false;
    268     }
    269     // FIXME: parse all other well-known capabilities: acceptInsecureCerts, pageLoadStrategy, proxy, setWindowRect, timeouts, unhandledPromptBehavior.
    270     return platformParseCapabilities(desiredCapabilities, capabilities, completionHandler);
    271 }
    272 
    273 RefPtr<Session> WebDriverService::findSessionOrCompleteWithError(InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler)
    274 {
    275     String sessionID;
    276     if (!parameters.getString(ASCIILiteral("sessionId"), sessionID)) {
    277         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
    278         return nullptr;
    279     }
    280 
    281     auto session = m_sessions.get(sessionID);
    282     if (!session) {
    283         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
    284         return nullptr;
    285     }
    286 
    287     return session;
    288 }
    289 
    290 void WebDriverService::newSession(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
    291 {
    292     // §8.1 New Session.
    293     // https://www.w3.org/TR/webdriver/#new-session
    294     RefPtr<InspectorObject> capabilitiesObject;
    295     if (!parameters->getObject(ASCIILiteral("capabilities"), capabilitiesObject)) {
    296         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated));
    297         return;
    298     }
    299     RefPtr<InspectorValue> requiredCapabilitiesValue;
    300     RefPtr<InspectorObject> requiredCapabilities;
    301     if (!capabilitiesObject->getValue(ASCIILiteral("alwaysMatch"), requiredCapabilitiesValue))
    302         requiredCapabilities = InspectorObject::create();
    303     else if (!requiredCapabilitiesValue->asObject(requiredCapabilities)) {
    304         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("alwaysMatch is invalid in capabilities")));
    305         return;
    306     }
    307     // FIXME: process firstMatch capabilities.
    308 
    309     Capabilities capabilities;
    310     if (!parseCapabilities(*requiredCapabilities, capabilities, completionHandler))
    311         return;
    312 
    313     auto sessionHost = std::make_unique<SessionHost>(WTFMove(capabilities));
    314     auto* sessionHostPtr = sessionHost.get();
    315     sessionHostPtr->connectToBrowser([this, sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](SessionHost::Succeeded succeeded) mutable {
    316         if (succeeded == SessionHost::Succeeded::No) {
    317             completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to connect to browser")));
    318             return;
    319         }
    320 
    321         RefPtr<Session> session = Session::create(WTFMove(sessionHost));
    322         session->createTopLevelBrowsingContext([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
    323             if (result.isError()) {
    324                 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorString()));
    325                 return;
    326             }
    327 
    328             m_activeSession = session.get();
    329             m_sessions.add(session->id(), session);
    330             RefPtr<InspectorObject> resultObject = InspectorObject::create();
    331             resultObject->setString(ASCIILiteral("sessionId"), session->id());
    332             RefPtr<InspectorObject> capabilities = InspectorObject::create();
    333             capabilities->setString(ASCIILiteral("browserName"), session->capabilities().browserName);
    334             capabilities->setString(ASCIILiteral("version"), session->capabilities().browserVersion);
    335             capabilities->setString(ASCIILiteral("platform"), session->capabilities().platform);
    336             resultObject->setObject(ASCIILiteral("value"), WTFMove(capabilities));
    337             completionHandler(CommandResult::success(WTFMove(resultObject)));
    338         });
    339     });
    340 }
    341 
    342 void WebDriverService::deleteSession(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
    343 {
    344     // §8.2 Delete Session.
    345     // https://www.w3.org/TR/webdriver/#delete-session
    346     String sessionID;
    347     if (!parameters->getString(ASCIILiteral("sessionId"), sessionID)) {
    348         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
    349         return;
    350     }
    351 
    352     auto session = m_sessions.take(sessionID);
    353     if (!session) {
    354         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
    355         return;
    356     }
    357 
    358     if (m_activeSession == session.get())
    359         m_activeSession = nullptr;
    360 
    361     session->close(WTFMove(completionHandler));
    362 }
    363 
    364 void WebDriverService::setTimeouts(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
     254static std::optional<Timeouts> deserializeTimeouts(InspectorObject& timeoutsObject)
    365255{
    366256    // §8.5 Set Timeouts.
    367     // https://www.w3.org/TR/webdriver/#set-timeouts
    368     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
    369     if (!session)
    370         return;
    371 
    372     Session::Timeouts timeouts;
    373     auto end = parameters->end();
    374     for (auto it = parameters->begin(); it != end; ++it) {
     257    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-deserialize-as-a-timeout
     258    Timeouts timeouts;
     259    auto end = timeoutsObject.end();
     260    for (auto it = timeoutsObject.begin(); it != end; ++it) {
    375261        if (it->key == "sessionId")
    376262            continue;
    377263
    378264        int timeoutMS;
    379         if (!it->value->asInteger(timeoutMS) || timeoutMS < 0 || timeoutMS > INT_MAX) {
    380             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
    381             return;
    382         }
     265        if (!it->value->asInteger(timeoutMS) || timeoutMS < 0 || timeoutMS > INT_MAX)
     266            return std::nullopt;
    383267
    384268        if (it->key == "script")
     
    388272        else if (it->key == "implicit")
    389273            timeouts.implicit = Seconds::fromMilliseconds(timeoutMS);
    390         else {
    391             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
    392             return;
    393         }
    394     }
    395 
    396     session->setTimeouts(timeouts, WTFMove(completionHandler));
     274        else
     275            return std::nullopt;
     276    }
     277    return timeouts;
     278}
     279
     280void WebDriverService::parseCapabilities(const InspectorObject& matchedCapabilities, Capabilities& capabilities) const
     281{
     282    // Matched capabilities have already been validated.
     283    bool acceptInsecureCerts;
     284    if (matchedCapabilities.getBoolean(ASCIILiteral("acceptInsecureCerts"), acceptInsecureCerts))
     285        capabilities.acceptInsecureCerts = acceptInsecureCerts;
     286    String browserName;
     287    if (matchedCapabilities.getString(ASCIILiteral("browserName"), browserName))
     288        capabilities.browserName = browserName;
     289    String browserVersion;
     290    if (matchedCapabilities.getString(ASCIILiteral("browserVersion"), browserVersion))
     291        capabilities.browserVersion = browserVersion;
     292    String platformName;
     293    if (matchedCapabilities.getString(ASCIILiteral("platformName"), platformName))
     294        capabilities.platformName = platformName;
     295    RefPtr<InspectorObject> timeouts;
     296    if (matchedCapabilities.getObject(ASCIILiteral("timeouts"), timeouts))
     297        capabilities.timeouts = deserializeTimeouts(*timeouts);
     298    platformParseCapabilities(matchedCapabilities, capabilities);
     299}
     300
     301RefPtr<Session> WebDriverService::findSessionOrCompleteWithError(InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler)
     302{
     303    String sessionID;
     304    if (!parameters.getString(ASCIILiteral("sessionId"), sessionID)) {
     305        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
     306        return nullptr;
     307    }
     308
     309    auto session = m_sessions.get(sessionID);
     310    if (!session) {
     311        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
     312        return nullptr;
     313    }
     314
     315    return session;
     316}
     317
     318RefPtr<InspectorObject> WebDriverService::validatedCapabilities(const InspectorObject& capabilities) const
     319{
     320    // §7.2 Processing Capabilities.
     321    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-validate-capabilities
     322    auto result = InspectorObject::create();
     323    auto end = capabilities.end();
     324    for (auto it = capabilities.begin(); it != end; ++it) {
     325        if (it->value->isNull())
     326            result->setValue(it->key, RefPtr<InspectorValue>(it->value));
     327        else if (it->key == "acceptInsecureCerts") {
     328            bool acceptInsecureCerts;
     329            if (!it->value->asBoolean(acceptInsecureCerts))
     330                return nullptr;
     331            result->setBoolean(it->key, acceptInsecureCerts);
     332        } else if (it->key == "browserName" || it->key == "browserVersion" || it->key == "platformName") {
     333            String stringValue;
     334            if (!it->value->asString(stringValue))
     335                return nullptr;
     336            result->setString(it->key, stringValue);
     337        } else if (it->key == "pageLoadStrategy") {
     338            String pageLoadStrategy;
     339            if (!it->value->asString(pageLoadStrategy))
     340                return nullptr;
     341            // FIXME: implement pageLoadStrategy.
     342            result->setString(it->key, pageLoadStrategy);
     343        } else if (it->key == "proxy") {
     344            // FIXME: implement proxy support.
     345        } else if (it->key == "timeouts") {
     346            RefPtr<InspectorObject> timeouts;
     347            if (!it->value->asObject(timeouts) || !deserializeTimeouts(*timeouts))
     348                return nullptr;
     349            result->setValue(it->key, RefPtr<InspectorValue>(it->value));
     350        } else if (it->key == "unhandledPromptBehavior") {
     351            String unhandledPromptBehavior;
     352            if (!it->value->asString(unhandledPromptBehavior))
     353                return nullptr;
     354            // FIXME: implement prompts support.
     355            result->setString(it->key, unhandledPromptBehavior);
     356        } else if (it->key.find(":") != notFound) {
     357            if (!platformValidateCapability(it->key, it->value))
     358                return nullptr;
     359            result->setValue(it->key, RefPtr<InspectorValue>(it->value));
     360        }
     361    }
     362    return result;
     363}
     364
     365RefPtr<InspectorObject> WebDriverService::mergeCapabilities(const InspectorObject& requiredCapabilities, const InspectorObject& firstMatchCapabilities) const
     366{
     367    // §7.2 Processing Capabilities.
     368    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-merging-capabilities
     369    auto result = InspectorObject::create();
     370    auto requiredEnd = requiredCapabilities.end();
     371    for (auto it = requiredCapabilities.begin(); it != requiredEnd; ++it)
     372        result->setValue(it->key, RefPtr<InspectorValue>(it->value));
     373
     374    auto firstMatchEnd = firstMatchCapabilities.end();
     375    for (auto it = firstMatchCapabilities.begin(); it != firstMatchEnd; ++it) {
     376        if (requiredCapabilities.find(it->key) != requiredEnd)
     377            return nullptr;
     378
     379        result->setValue(it->key, RefPtr<InspectorValue>(it->value));
     380    }
     381
     382    return result;
     383}
     384
     385std::optional<String> WebDriverService::matchCapabilities(const InspectorObject& mergedCapabilities) const
     386{
     387    // §7.2 Processing Capabilities.
     388    // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-matching-capabilities
     389    Capabilities matchedCapabilities = platformCapabilities();
     390
     391    // Some capabilities like browser name and version might need to launch the browser,
     392    // so we only reject the known capabilities that don't match.
     393    auto end = mergedCapabilities.end();
     394    for (auto it = mergedCapabilities.begin(); it != end; ++it) {
     395        if (it->key == "browserName" && matchedCapabilities.browserName) {
     396            String browserName;
     397            it->value->asString(browserName);
     398            if (!equalIgnoringASCIICase(matchedCapabilities.browserName.value(), browserName))
     399                return makeString("expected browserName ", matchedCapabilities.browserName.value(), " but got ", browserName);
     400        } else if (it->key == "browserVersion" && matchedCapabilities.browserVersion) {
     401            String browserVersion;
     402            it->value->asString(browserVersion);
     403            if (!platformCompareBrowserVersions(browserVersion, matchedCapabilities.browserVersion.value()))
     404                return makeString("requested browserVersion is ", browserVersion, " but actual version is ", matchedCapabilities.browserVersion.value());
     405        } else if (it->key == "platformName" && matchedCapabilities.platformName) {
     406            String platformName;
     407            it->value->asString(platformName);
     408            if (!equalLettersIgnoringASCIICase(platformName, "any") && !equalIgnoringASCIICase(matchedCapabilities.platformName.value(), platformName))
     409                return makeString("expected platformName ", matchedCapabilities.platformName.value(), " but got ", platformName);
     410        } else if (it->key == "acceptInsecureCerts" && matchedCapabilities.acceptInsecureCerts) {
     411            bool acceptInsecureCerts;
     412            it->value->asBoolean(acceptInsecureCerts);
     413            if (acceptInsecureCerts && !matchedCapabilities.acceptInsecureCerts.value())
     414                return String("browser doesn't accept insecure TLS certificates");
     415        } else if (it->key == "proxy") {
     416            // FIXME: implement proxy support.
     417        } else if (auto errorString = platformMatchCapability(it->key, it->value))
     418            return errorString;
     419    }
     420
     421    return std::nullopt;
     422}
     423
     424RefPtr<InspectorObject> WebDriverService::processCapabilities(const InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler) const
     425{
     426    // §7.2 Processing Capabilities.
     427    // https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities
     428
     429    // 1. Let capabilities request be the result of getting the property "capabilities" from parameters.
     430    RefPtr<InspectorObject> capabilitiesObject;
     431    if (!parameters.getObject(ASCIILiteral("capabilities"), capabilitiesObject)) {
     432        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated));
     433        return nullptr;
     434    }
     435
     436    // 2. Let required capabilities be the result of getting the property "alwaysMatch" from capabilities request.
     437    RefPtr<InspectorValue> requiredCapabilitiesValue;
     438    RefPtr<InspectorObject> requiredCapabilities;
     439    if (!capabilitiesObject->getValue(ASCIILiteral("alwaysMatch"), requiredCapabilitiesValue))
     440        // 2.1. If required capabilities is undefined, set the value to an empty JSON Object.
     441        requiredCapabilities = InspectorObject::create();
     442    else if (!requiredCapabilitiesValue->asObject(requiredCapabilities)) {
     443        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("alwaysMatch is invalid in capabilities")));
     444        return nullptr;
     445    }
     446
     447    // 2.2. Let required capabilities be the result of trying to validate capabilities with argument required capabilities.
     448    requiredCapabilities = validatedCapabilities(*requiredCapabilities);
     449    if (!requiredCapabilities) {
     450        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Invalid alwaysMatch capabilities")));
     451        return nullptr;
     452    }
     453
     454    // 3. Let all first match capabilities be the result of getting the property "firstMatch" from capabilities request.
     455    RefPtr<InspectorValue> firstMatchCapabilitiesValue;
     456    RefPtr<InspectorArray> firstMatchCapabilitiesList;
     457    if (!capabilitiesObject->getValue(ASCIILiteral("firstMatch"), firstMatchCapabilitiesValue)) {
     458        // 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.
     459        firstMatchCapabilitiesList = InspectorArray::create();
     460        firstMatchCapabilitiesList->pushObject(InspectorObject::create());
     461    } else if (!firstMatchCapabilitiesValue->asArray(firstMatchCapabilitiesList)) {
     462        // 3.2. If all first match capabilities is not a JSON List, return error with error code invalid argument.
     463        completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("firstMatch is invalid in capabilities")));
     464        return nullptr;
     465    }
     466
     467    // 4. Let validated first match capabilities be an empty JSON List.
     468    Vector<RefPtr<InspectorObject>> validatedFirstMatchCapabilitiesList;
     469    auto firstMatchCapabilitiesListLength = firstMatchCapabilitiesList->length();
     470    validatedFirstMatchCapabilitiesList.reserveInitialCapacity(firstMatchCapabilitiesListLength);
     471    // 5. For each first match capabilities corresponding to an indexed property in all first match capabilities.
     472    for (unsigned i = 0; i < firstMatchCapabilitiesListLength; ++i) {
     473        RefPtr<InspectorValue> firstMatchCapabilitiesValue = firstMatchCapabilitiesList->get(i);
     474        RefPtr<InspectorObject> firstMatchCapabilities;
     475        if (!firstMatchCapabilitiesValue->asObject(firstMatchCapabilities)) {
     476            completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Invalid capabilities found in firstMatch")));
     477            return nullptr;
     478        }
     479        // 5.1. Let validated capabilities be the result of trying to validate capabilities with argument first match capabilities.
     480        firstMatchCapabilities = validatedCapabilities(*firstMatchCapabilities);
     481        if (!firstMatchCapabilities) {
     482            completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Invalid firstMatch capabilities")));
     483            return nullptr;
     484        }
     485        // 5.2. Append validated capabilities to validated first match capabilities.
     486        validatedFirstMatchCapabilitiesList.uncheckedAppend(WTFMove(firstMatchCapabilities));
     487    }
     488
     489    // 6. For each first match capabilities corresponding to an indexed property in validated first match capabilities.
     490    std::optional<String> errorString;
     491    for (auto& validatedFirstMatchCapabilies : validatedFirstMatchCapabilitiesList) {
     492        // 6.1. Let merged capabilities be the result of trying to merge capabilities with required capabilities and first match capabilities as arguments.
     493        auto mergedCapabilities = mergeCapabilities(*requiredCapabilities, *validatedFirstMatchCapabilies);
     494        if (!mergedCapabilities) {
     495            completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Same capability found in firstMatch and alwaysMatch")));
     496            return nullptr;
     497        }
     498        // 6.2. Let matched capabilities be the result of trying to match capabilities with merged capabilities as an argument.
     499        errorString = matchCapabilities(*mergedCapabilities);
     500        if (!errorString) {
     501            // 6.3. If matched capabilities is not null return matched capabilities.
     502            return mergedCapabilities;
     503        }
     504    }
     505
     506    completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, errorString ? errorString.value() : String("Invalid capabilities")));
     507    return nullptr;
     508}
     509
     510void WebDriverService::newSession(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
     511{
     512    // §8.1 New Session.
     513    // https://www.w3.org/TR/webdriver/#new-session
     514    auto matchedCapabilities = processCapabilities(*parameters, completionHandler);
     515    if (!matchedCapabilities)
     516        return;
     517
     518    Capabilities capabilities;
     519    parseCapabilities(*matchedCapabilities, capabilities);
     520    auto sessionHost = std::make_unique<SessionHost>(WTFMove(capabilities));
     521    auto* sessionHostPtr = sessionHost.get();
     522    sessionHostPtr->connectToBrowser([this, sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](SessionHost::Succeeded succeeded) mutable {
     523        if (succeeded == SessionHost::Succeeded::No) {
     524            completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to connect to browser")));
     525            return;
     526        }
     527
     528        RefPtr<Session> session = Session::create(WTFMove(sessionHost));
     529        session->createTopLevelBrowsingContext([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
     530            if (result.isError()) {
     531                completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorMessage()));
     532                return;
     533            }
     534
     535            m_activeSession = session.get();
     536            m_sessions.add(session->id(), session);
     537
     538            const auto& capabilities = session->capabilities();
     539            if (capabilities.timeouts)
     540                session->setTimeouts(capabilities.timeouts.value(), [](CommandResult&&) { });
     541
     542            RefPtr<InspectorObject> resultObject = InspectorObject::create();
     543            resultObject->setString(ASCIILiteral("sessionId"), session->id());
     544            RefPtr<InspectorObject> capabilitiesObject = InspectorObject::create();
     545            if (capabilities.browserName)
     546                capabilitiesObject->setString(ASCIILiteral("browserName"), capabilities.browserName.value());
     547            if (capabilities.browserVersion)
     548                capabilitiesObject->setString(ASCIILiteral("browserVersion"), capabilities.browserVersion.value());
     549            if (capabilities.platformName)
     550                capabilitiesObject->setString(ASCIILiteral("platformName"), capabilities.platformName.value());
     551            if (capabilities.acceptInsecureCerts)
     552                capabilitiesObject->setBoolean(ASCIILiteral("acceptInsecureCerts"), capabilities.acceptInsecureCerts.value());
     553            if (capabilities.timeouts) {
     554                RefPtr<InspectorObject> timeoutsObject = InspectorObject::create();
     555                if (capabilities.timeouts.value().script)
     556                    timeoutsObject->setInteger(ASCIILiteral("script"), capabilities.timeouts.value().script.value().millisecondsAs<int>());
     557                if (capabilities.timeouts.value().pageLoad)
     558                    timeoutsObject->setInteger(ASCIILiteral("pageLoad"), capabilities.timeouts.value().pageLoad.value().millisecondsAs<int>());
     559                if (capabilities.timeouts.value().implicit)
     560                    timeoutsObject->setInteger(ASCIILiteral("implicit"), capabilities.timeouts.value().implicit.value().millisecondsAs<int>());
     561                capabilitiesObject->setObject(ASCIILiteral("timeouts"), WTFMove(timeoutsObject));
     562            }
     563            resultObject->setObject(ASCIILiteral("value"), WTFMove(capabilitiesObject));
     564            completionHandler(CommandResult::success(WTFMove(resultObject)));
     565        });
     566    });
     567}
     568
     569void WebDriverService::deleteSession(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
     570{
     571    // §8.2 Delete Session.
     572    // https://www.w3.org/TR/webdriver/#delete-session
     573    String sessionID;
     574    if (!parameters->getString(ASCIILiteral("sessionId"), sessionID)) {
     575        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
     576        return;
     577    }
     578
     579    auto session = m_sessions.take(sessionID);
     580    if (!session) {
     581        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
     582        return;
     583    }
     584
     585    if (m_activeSession == session.get())
     586        m_activeSession = nullptr;
     587
     588    session->close(WTFMove(completionHandler));
     589}
     590
     591void WebDriverService::setTimeouts(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
     592{
     593    // §8.5 Set Timeouts.
     594    // https://www.w3.org/TR/webdriver/#set-timeouts
     595    auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
     596    if (!session)
     597        return;
     598
     599    auto timeouts = deserializeTimeouts(*parameters);
     600    if (!timeouts) {
     601        completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
     602        return;
     603    }
     604
     605    session->setTimeouts(timeouts.value(), WTFMove(completionHandler));
    397606}
    398607
  • trunk/Source/WebDriver/WebDriverService.h

    r219605 r220315  
    5151    void quit();
    5252
     53    static bool platformCompareBrowserVersions(const String&, const String&);
     54
    5355private:
    5456    enum class HTTPMethod { Get, Post, Delete };
     
    101103    void executeAsyncScript(RefPtr<Inspector::InspectorObject>&&, Function<void (CommandResult&&)>&&);
    102104
    103     bool parseCapabilities(Inspector::InspectorObject& desiredCapabilities, Capabilities&, Function<void (CommandResult&&)>&);
    104     bool platformParseCapabilities(Inspector::InspectorObject& desiredCapabilities, Capabilities&, Function<void (CommandResult&&)>&);
     105    static Capabilities platformCapabilities();
     106    RefPtr<Inspector::InspectorObject> processCapabilities(const Inspector::InspectorObject&, Function<void (CommandResult&&)>&) const;
     107    RefPtr<Inspector::InspectorObject> validatedCapabilities(const Inspector::InspectorObject&) const;
     108    RefPtr<Inspector::InspectorObject> mergeCapabilities(const Inspector::InspectorObject&, const Inspector::InspectorObject&) const;
     109    std::optional<String> matchCapabilities(const Inspector::InspectorObject&) const;
     110    bool platformValidateCapability(const String&, const RefPtr<Inspector::InspectorValue>&) const;
     111    std::optional<String> platformMatchCapability(const String&, const RefPtr<Inspector::InspectorValue>&) const;
     112    void parseCapabilities(const Inspector::InspectorObject& desiredCapabilities, Capabilities&) const;
     113    void platformParseCapabilities(const Inspector::InspectorObject& desiredCapabilities, Capabilities&) const;
    105114    RefPtr<Session> findSessionOrCompleteWithError(Inspector::InspectorObject&, Function<void (CommandResult&&)>&);
    106115
  • trunk/Source/WebDriver/glib/SessionHostGlib.cpp

    r219608 r220315  
    136136    g_subprocess_launcher_setenv(launcher.get(), "WEBKIT_INSPECTOR_SERVER", inspectorAddress.get(), TRUE);
    137137#if PLATFORM(GTK)
    138     g_subprocess_launcher_setenv(launcher.get(), "GTK_OVERLAY_SCROLLING", m_capabilities.useOverlayScrollbars ? "1" : "0", TRUE);
     138    g_subprocess_launcher_setenv(launcher.get(), "GTK_OVERLAY_SCROLLING", m_capabilities.useOverlayScrollbars.value() ? "1" : "0", TRUE);
    139139#endif
    140140
    141     GUniquePtr<char*> args(g_new0(char*, m_capabilities.browserArguments.size() + 2));
    142     args.get()[0] = g_strdup(m_capabilities.browserBinary.utf8().data());
    143     for (unsigned i = 0; i < m_capabilities.browserArguments.size(); ++i)
    144         args.get()[i + 1] = g_strdup(m_capabilities.browserArguments[i].utf8().data());
     141    const auto& browserArguments = m_capabilities.browserArguments.value();
     142    GUniquePtr<char*> args(g_new0(char*, browserArguments.size() + 2));
     143    args.get()[0] = g_strdup(m_capabilities.browserBinary.value().utf8().data());
     144    for (unsigned i = 0; i < browserArguments.size(); ++i)
     145        args.get()[i + 1] = g_strdup(browserArguments[i].utf8().data());
    145146
    146147    m_browser = adoptGRef(g_subprocess_launcher_spawnv(launcher.get(), args.get(), nullptr));
     
    224225    ASSERT(m_dbusConnection);
    225226    ASSERT(!m_startSessionCompletionHandler);
     227    // FIXME: Make StartAutomationSession return browser information and we use it to match capabilities.
    226228    m_startSessionCompletionHandler = WTFMove(completionHandler);
    227229    g_dbus_connection_call(m_dbusConnection.get(), nullptr,
  • trunk/Source/WebDriver/gtk/WebDriverServiceGtk.cpp

    r219605 r220315  
    3535namespace WebDriver {
    3636
    37 bool WebDriverService::platformParseCapabilities(InspectorObject& desiredCapabilities, Capabilities& capabilities, Function<void (CommandResult&&)>& completionHandler)
     37Capabilities WebDriverService::platformCapabilities()
    3838{
    39     RefPtr<InspectorValue> value;
     39    Capabilities capabilities;
     40    capabilities.platformName = String("linux");
     41    capabilities.acceptInsecureCerts = false;
     42    return capabilities;
     43}
     44
     45bool WebDriverService::platformValidateCapability(const String& name, const RefPtr<InspectorValue>& value) const
     46{
     47    if (name != "webkitgtk:browserOptions")
     48        return true;
     49
    4050    RefPtr<InspectorObject> browserOptions;
    41     if (desiredCapabilities.getValue(ASCIILiteral("webkitgtk:browserOptions"), value) && !value->asObject(browserOptions)) {
    42         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("webkitgtk:browserOptions is invalid in capabilities")));
     51    if (!value->asObject(browserOptions))
    4352        return false;
    44     }
    45     if (browserOptions->isNull()) {
    46         capabilities.browserBinary = LIBEXECDIR "/webkit2gtk-" WEBKITGTK_API_VERSION_STRING "/MiniBrowser";
    47         capabilities.browserArguments = { ASCIILiteral("--automation") };
     53
     54    if (browserOptions->isNull())
    4855        return true;
    49     }
    5056
    51     if (!browserOptions->getString(ASCIILiteral("binary"), capabilities.browserBinary)) {
    52         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("binary parameter is invalid or missing in webkitgtk:browserOptions")));
     57    // If browser options are provided, binary is required.
     58    String binary;
     59    if (!browserOptions->getString(ASCIILiteral("binary"), binary))
    5360        return false;
    54     }
    5561
     62    RefPtr<InspectorValue> useOverlayScrollbarsValue;
     63    bool useOverlayScrollbars;
     64    if (browserOptions->getValue(ASCIILiteral("useOverlayScrollbars"), useOverlayScrollbarsValue) && !useOverlayScrollbarsValue->asBoolean(useOverlayScrollbars))
     65        return false;
     66
     67    RefPtr<InspectorValue> browserArgumentsValue;
    5668    RefPtr<InspectorArray> browserArguments;
    57     if (browserOptions->getValue(ASCIILiteral("args"), value) && !value->asArray(browserArguments)) {
    58         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("args parameter is invalid in webkitgtk:browserOptions")));
     69    if (browserOptions->getValue(ASCIILiteral("args"), browserArgumentsValue) && !browserArgumentsValue->asArray(browserArguments))
    5970        return false;
    60     }
     71
    6172    unsigned browserArgumentsLength = browserArguments->length();
    62     if (!browserArgumentsLength)
    63         return true;
    64     capabilities.browserArguments.reserveInitialCapacity(browserArgumentsLength);
    6573    for (unsigned i = 0; i < browserArgumentsLength; ++i) {
    6674        RefPtr<InspectorValue> value = browserArguments->get(i);
    6775        String argument;
    68         if (!value->asString(argument)) {
    69             capabilities.browserArguments.clear();
    70             completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to extract arguments from webkitgtk:browserOptions::args")));
     76        if (!value->asString(argument))
    7177            return false;
    72         }
    73         capabilities.browserArguments.uncheckedAppend(WTFMove(argument));
    7478    }
    7579
    76     if (browserOptions->getValue(ASCIILiteral("useOverlayScrollbars"), value) && !value->asBoolean(capabilities.useOverlayScrollbars)) {
    77         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("useOverlayScrollbars parameter is invalid in webkitgtk:browserOptions")));
     80    return true;
     81}
     82
     83std::optional<String> WebDriverService::platformMatchCapability(const String&, const RefPtr<InspectorValue>&) const
     84{
     85    return std::nullopt;
     86}
     87
     88static bool parseVersion(const String& version, uint64_t& major, uint64_t& minor, uint64_t& micro)
     89{
     90    major = minor = micro = 0;
     91
     92    Vector<String> tokens;
     93    version.split(".", false, tokens);
     94    bool ok;
     95    switch (tokens.size()) {
     96    case 3:
     97        micro = tokens[2].toInt64(&ok);
     98        if (!ok)
     99            return false;
     100        FALLTHROUGH;
     101    case 2:
     102        minor = tokens[1].toInt64(&ok);
     103        if (!ok)
     104            return false;
     105        FALLTHROUGH;
     106    case 1:
     107        major = tokens[0].toInt64(&ok);
     108        if (!ok)
     109            return false;
     110        break;
     111    default:
    78112        return false;
    79113    }
     
    82116}
    83117
     118bool WebDriverService::platformCompareBrowserVersions(const String& requiredVersion, const String& proposedVersion)
     119{
     120    // We require clients to use format major.micro.minor as version string.
     121    uint64_t requiredMajor, requiredMinor, requiredMicro;
     122    if (!parseVersion(requiredVersion, requiredMajor, requiredMinor, requiredMicro))
     123        return false;
     124
     125    uint64_t proposedMajor, proposedMinor, proposedMicro;
     126    if (!parseVersion(proposedVersion, proposedMajor, proposedMinor, proposedMicro))
     127        return false;
     128
     129    return proposedMajor > requiredMajor
     130        || (proposedMajor == requiredMajor && proposedMinor > requiredMinor)
     131        || (proposedMajor == requiredMajor && proposedMinor == requiredMinor && proposedMicro >= requiredMicro);
     132}
     133
     134void WebDriverService::platformParseCapabilities(const InspectorObject& matchedCapabilities, Capabilities& capabilities) const
     135{
     136    RefPtr<InspectorObject> browserOptions;
     137    if (!matchedCapabilities.getObject(ASCIILiteral("webkitgtk:browserOptions"), browserOptions)) {
     138        capabilities.browserBinary = String(LIBEXECDIR "/webkit2gtk-" WEBKITGTK_API_VERSION_STRING "/MiniBrowser");
     139        capabilities.browserArguments = Vector<String> { ASCIILiteral("--automation") };
     140        capabilities.useOverlayScrollbars = true;
     141        return;
     142    }
     143
     144    String browserBinary;
     145    browserOptions->getString(ASCIILiteral("binary"), browserBinary);
     146    ASSERT(!browserBinary.isNull());
     147    capabilities.browserBinary = browserBinary;
     148
     149    capabilities.browserArguments = Vector<String>();
     150    RefPtr<InspectorArray> browserArguments;
     151    if (browserOptions->getArray(ASCIILiteral("args"), browserArguments)) {
     152        unsigned browserArgumentsLength = browserArguments->length();
     153        capabilities.browserArguments->reserveInitialCapacity(browserArgumentsLength);
     154        for (unsigned i = 0; i < browserArgumentsLength; ++i) {
     155            RefPtr<InspectorValue> value = browserArguments->get(i);
     156            String argument;
     157            value->asString(argument);
     158            ASSERT(!argument.isNull());
     159            capabilities.browserArguments->uncheckedAppend(WTFMove(argument));
     160        }
     161    }
     162
     163    bool useOverlayScrollbars;
     164    if (browserOptions->getBoolean(ASCIILiteral("useOverlayScrollbars"), useOverlayScrollbars))
     165        capabilities.useOverlayScrollbars = useOverlayScrollbars;
     166    else
     167        capabilities.useOverlayScrollbars = true;
     168}
     169
    84170} // namespace WebDriver
Note: See TracChangeset for help on using the changeset viewer.