Changeset 254329 in webkit


Ignore:
Timestamp:
Jan 10, 2020 12:44:37 AM (4 years ago)
Author:
Carlos Garcia Campos
Message:

Automation: evaluateJavaScriptFunction should use Promises
https://bugs.webkit.org/show_bug.cgi?id=204151

Reviewed by Brian Burg.

Source/WebDriver:

  • CommandResult.cpp:

(WebDriver::CommandResult::httpStatusCode const): Timeout errors should return 500 not 408.

  • Session.cpp:

(WebDriver::Session::executeScript): Ensure the script body goes between new lines to avoid problems with
trailing comments like in function() { return foo; Comment }.

Source/WebKit:

Make the function to run scripts async and handle the result as a promise. To implement the script timeout we
use another promise that starts the timer and then we run a Promise.race() with both promises. To simplify the
results reporting, all exceptions (including timeout errors that are now handled as exceptions) are now handled
as errors passed to the resultCallback. The boolean parameter has been removed, we can simply check the type of
the value received because results are always strings and errors are always exception objects.

  • WebProcess/Automation/WebAutomationSessionProxy.cpp:

(WebKit::evaluateJavaScriptCallback): Handle the script result, including all possible errors now (not only timeouts).
(WebKit::WebAutomationSessionProxy::evaluateJavaScriptFunction): Any exception running the script should be an
internal error now. The code to handle error has been moved to evaluateJavaScriptCallback().

  • WebProcess/Automation/WebAutomationSessionProxy.js:

(WebKitAutomation.AutomationSessionProxy.prototype.evaluateJavaScriptFunction): Call _execute and handle the
promise result to call resultCallback wityh either the result or the error.
(WebKitAutomation.AutomationSessionProxy.prototype._execute): Make the function to run the script async and
handle the result as a promise.

WebDriverTests:

Remove expectations for tests that are now passing.

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebDriver/ChangeLog

    r254118 r254329  
     12020-01-10  Carlos Garcia Campos  <cgarcia@igalia.com>
     2
     3        Automation: evaluateJavaScriptFunction should use Promises
     4        https://bugs.webkit.org/show_bug.cgi?id=204151
     5
     6        Reviewed by Brian Burg.
     7
     8        * CommandResult.cpp:
     9        (WebDriver::CommandResult::httpStatusCode const): Timeout errors should return 500 not 408.
     10        * Session.cpp:
     11        (WebDriver::Session::executeScript): Ensure the script body goes between new lines to avoid problems with
     12        trailing comments like in function() { return foo; // Comment }.
     13
    1142020-01-07  Carlos Garcia Campos  <cgarcia@igalia.com>
    215
  • trunk/Source/WebDriver/CommandResult.cpp

    r254118 r254329  
    149149    case ErrorCode::UnknownCommand:
    150150        return 404;
    151     case ErrorCode::ScriptTimeout:
    152     case ErrorCode::Timeout:
    153         return 408;
    154151    case ErrorCode::JavascriptError:
    155152    case ErrorCode::MoveTargetOutOfBounds:
     153    case ErrorCode::ScriptTimeout:
    156154    case ErrorCode::SessionNotCreated:
     155    case ErrorCode::Timeout:
    157156    case ErrorCode::UnableToCaptureScreen:
    158157    case ErrorCode::UnexpectedAlertOpen:
  • trunk/Source/WebDriver/Session.cpp

    r253883 r254329  
    21342134        if (m_currentBrowsingContext)
    21352135            parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
    2136         parameters->setString("function"_s, "function(){" + script + '}');
     2136        parameters->setString("function"_s, "function(){\n" + script + "\n}");
    21372137        parameters->setArray("arguments"_s, WTFMove(arguments));
    2138         if (mode == ExecuteScriptMode::Async) {
     2138        if (mode == ExecuteScriptMode::Async)
    21392139            parameters->setBoolean("expectsImplicitCallbackArgument"_s, true);
    2140             if (m_scriptTimeout != std::numeric_limits<double>::infinity())
    2141                 parameters->setDouble("callbackTimeout"_s, m_scriptTimeout);
    2142         }
     2140        if (m_scriptTimeout != std::numeric_limits<double>::infinity())
     2141            parameters->setDouble("callbackTimeout"_s, m_scriptTimeout);
    21432142        m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
    21442143            if (response.isError || !response.responseObject) {
  • trunk/Source/WebKit/ChangeLog

    r254328 r254329  
     12020-01-10  Carlos Garcia Campos  <cgarcia@igalia.com>
     2
     3        Automation: evaluateJavaScriptFunction should use Promises
     4        https://bugs.webkit.org/show_bug.cgi?id=204151
     5
     6        Reviewed by Brian Burg.
     7
     8        Make the function to run scripts async and handle the result as a promise. To implement the script timeout we
     9        use another promise that starts the timer and then we run a Promise.race() with both promises. To simplify the
     10        results reporting, all exceptions (including timeout errors that are now handled as exceptions) are now handled
     11        as errors passed to the resultCallback. The boolean parameter has been removed, we can simply check the type of
     12        the value received because results are always strings and errors are always exception objects.
     13
     14        * WebProcess/Automation/WebAutomationSessionProxy.cpp:
     15        (WebKit::evaluateJavaScriptCallback): Handle the script result, including all possible errors now (not only timeouts).
     16        (WebKit::WebAutomationSessionProxy::evaluateJavaScriptFunction): Any exception running the script should be an
     17        internal error now. The code to handle error has been moved to evaluateJavaScriptCallback().
     18        * WebProcess/Automation/WebAutomationSessionProxy.js:
     19        (WebKitAutomation.AutomationSessionProxy.prototype.evaluateJavaScriptFunction): Call _execute and handle the
     20        promise result to call resultCallback wityh either the result or the error.
     21        (WebKitAutomation.AutomationSessionProxy.prototype._execute): Make the function to run the script async and
     22        handle the result as a promise.
     23
    1242020-01-10  Carlos Garcia Campos  <cgarcia@igalia.com>
    225
  • trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.cpp

    r254328 r254329  
    182182static JSValueRef evaluateJavaScriptCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    183183{
    184     ASSERT_ARG(argumentCount, argumentCount == 4);
     184    ASSERT_ARG(argumentCount, argumentCount == 3);
    185185    ASSERT_ARG(arguments, JSValueIsNumber(context, arguments[0]));
    186186    ASSERT_ARG(arguments, JSValueIsNumber(context, arguments[1]));
    187     ASSERT_ARG(arguments, JSValueIsString(context, arguments[2]));
    188     ASSERT_ARG(arguments, JSValueIsBoolean(context, arguments[3]));
     187    ASSERT_ARG(arguments, JSValueIsObject(context, arguments[2]) || JSValueIsString(context, arguments[2]));
    189188
    190189    auto automationSessionProxy = WebProcess::singleton().automationSessionProxy();
     
    194193    WebCore::FrameIdentifier frameID = WebCore::frameIdentifierFromID(JSValueToNumber(context, arguments[0], exception));
    195194    uint64_t callbackID = JSValueToNumber(context, arguments[1], exception);
    196     auto result = adoptRef(JSValueToStringCopy(context, arguments[2], exception));
    197 
    198     bool resultIsErrorName = JSValueToBoolean(context, arguments[3]);
    199 
    200     if (resultIsErrorName) {
    201         if (result->string() == "JavaScriptTimeout") {
    202             String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptTimeout);
    203             automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, String(), errorType);
    204         } else {
    205             ASSERT_NOT_REACHED();
    206             String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InternalError);
    207             automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, String(), errorType);
    208         }
    209     } else
    210         automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, result->string(), String());
    211 
    212     return JSValueMakeUndefined(context);
    213 }
    214 
    215 JSObjectRef WebAutomationSessionProxy::scriptObject(JSGlobalContextRef context)
    216 {
    217     JSC::JSGlobalObject* globalObject = toJS(context);
    218     JSC::VM& vm = globalObject->vm();
    219     JSC::JSLockHolder locker(vm);
    220     auto scriptObjectID = JSC::Identifier::fromUid(m_scriptObjectIdentifier);
    221     if (!globalObject->hasProperty(globalObject, scriptObjectID))
    222         return nullptr;
    223 
    224     return const_cast<JSObjectRef>(toRef(globalObject, globalObject->get(globalObject, scriptObjectID)));
    225 }
    226 
    227 void WebAutomationSessionProxy::setScriptObject(JSGlobalContextRef context, JSObjectRef object)
    228 {
    229     JSC::JSGlobalObject* globalObject = toJS(context);
    230     JSC::VM& vm = globalObject->vm();
    231     JSC::JSLockHolder locker(vm);
    232     auto scriptObjectID = JSC::Identifier::fromUid(m_scriptObjectIdentifier);
    233     PutPropertySlot slot(globalObject);
    234     globalObject->methodTable(vm)->put(globalObject, globalObject, scriptObjectID, toJS(globalObject, object), slot);
    235 }
    236 
    237 JSObjectRef WebAutomationSessionProxy::scriptObjectForFrame(WebFrame& frame)
    238 {
    239     JSGlobalContextRef context = frame.jsContext();
    240     if (auto* scriptObject = this->scriptObject(context))
    241         return scriptObject;
    242 
    243     JSValueRef exception = nullptr;
    244     String script = StringImpl::createWithoutCopying(WebAutomationSessionProxyScriptSource, sizeof(WebAutomationSessionProxyScriptSource));
    245     JSObjectRef scriptObjectFunction = const_cast<JSObjectRef>(JSEvaluateScript(context, OpaqueJSString::tryCreate(script).get(), nullptr, nullptr, 0, &exception));
    246     ASSERT(JSValueIsObject(context, scriptObjectFunction));
    247 
    248     JSValueRef sessionIdentifier = toJSValue(context, m_sessionIdentifier);
    249     JSObjectRef evaluateFunction = JSObjectMakeFunctionWithCallback(context, nullptr, evaluate);
    250     JSObjectRef createUUIDFunction = JSObjectMakeFunctionWithCallback(context, nullptr, createUUID);
    251     JSObjectRef isValidNodeIdentifierFunction = JSObjectMakeFunctionWithCallback(context, nullptr, isValidNodeIdentifier);
    252     JSValueRef arguments[] = { sessionIdentifier, evaluateFunction, createUUIDFunction, isValidNodeIdentifierFunction };
    253     JSObjectRef scriptObject = const_cast<JSObjectRef>(JSObjectCallAsFunction(context, scriptObjectFunction, nullptr, WTF_ARRAY_LENGTH(arguments), arguments, &exception));
    254     ASSERT(JSValueIsObject(context, scriptObject));
    255 
    256     setScriptObject(context, scriptObject);
    257     return scriptObject;
    258 }
    259 
    260 WebCore::Element* WebAutomationSessionProxy::elementForNodeHandle(WebFrame& frame, const String& nodeHandle)
    261 {
    262     // Don't use scriptObjectForFrame() since we can assume if the script object
    263     // does not exist, there are no nodes mapped to handles. Using scriptObjectForFrame()
    264     // will make a new script object if it can't find one, preventing us from returning fast.
    265     JSGlobalContextRef context = frame.jsContext();
    266     auto* scriptObject = this->scriptObject(context);
    267     if (!scriptObject)
    268         return nullptr;
    269 
    270     JSValueRef functionArguments[] = {
    271         toJSValue(context, nodeHandle)
    272     };
    273 
    274     JSValueRef result = callPropertyFunction(context, scriptObject, "nodeForIdentifier"_s, WTF_ARRAY_LENGTH(functionArguments), functionArguments, nullptr);
    275     JSObjectRef element = JSValueToObject(context, result, nullptr);
    276     if (!element)
    277         return nullptr;
    278 
    279     auto elementWrapper = JSC::jsDynamicCast<WebCore::JSElement*>(toJS(context)->vm(), toJS(element));
    280     if (!elementWrapper)
    281         return nullptr;
    282 
    283     return &elementWrapper->wrapped();
    284 }
    285 
    286 void WebAutomationSessionProxy::didClearWindowObjectForFrame(WebFrame& frame)
    287 {
    288     String errorMessage = "Callback was not called before the unload event."_s;
    289     String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError);
    290 
    291     auto pendingFrameCallbacks = m_webFramePendingEvaluateJavaScriptCallbacksMap.take(frame.frameID());
    292     for (uint64_t callbackID : pendingFrameCallbacks)
    293         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, errorMessage, errorType), 0);
    294 }
    295 
    296 void WebAutomationSessionProxy::evaluateJavaScriptFunction(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> optionalFrameID, const String& function, Vector<String> arguments, bool expectsImplicitCallbackArgument, Optional<double> callbackTimeout, uint64_t callbackID)
    297 {
    298     WebPage* page = WebProcess::singleton().webPage(pageID);
    299     if (!page) {
    300         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, { },
    301             Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound)), 0);
    302         return;
    303     }
    304     WebFrame* frame = optionalFrameID ? WebProcess::singleton().webFrame(*optionalFrameID) : page->mainWebFrame();
    305     if (!frame) {
    306         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, { },
    307             Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound)), 0);
    308         return;
    309     }
    310 
    311     JSObjectRef scriptObject = scriptObjectForFrame(*frame);
    312     ASSERT(scriptObject);
    313 
    314     auto frameID = frame->frameID();
    315     JSValueRef exception = nullptr;
    316     JSGlobalContextRef context = frame->jsContext();
    317 
    318     if (expectsImplicitCallbackArgument) {
    319         auto result = m_webFramePendingEvaluateJavaScriptCallbacksMap.add(frameID, Vector<uint64_t>());
    320         result.iterator->value.append(callbackID);
    321     }
    322 
    323     JSValueRef functionArguments[] = {
    324         toJSValue(context, function),
    325         toJSArray(context, arguments, toJSValue, &exception),
    326         JSValueMakeBoolean(context, expectsImplicitCallbackArgument),
    327         JSValueMakeNumber(context, frameID.toUInt64()),
    328         JSValueMakeNumber(context, callbackID),
    329         JSObjectMakeFunctionWithCallback(context, nullptr, evaluateJavaScriptCallback),
    330         JSValueMakeNumber(context, callbackTimeout.valueOr(-1))
    331     };
    332 
    333     {
    334         WebCore::UserGestureIndicator gestureIndicator(WebCore::ProcessingUserGesture, frame->coreFrame()->document());
    335         callPropertyFunction(context, scriptObject, "evaluateJavaScriptFunction"_s, WTF_ARRAY_LENGTH(functionArguments), functionArguments, &exception);
    336     }
    337 
    338     if (!exception)
    339         return;
    340 
    341     String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError);
    342 
    343     String exceptionMessage;
    344     if (JSValueIsObject(context, exception)) {
    345         JSValueRef nameValue = JSObjectGetProperty(context, const_cast<JSObjectRef>(exception), OpaqueJSString::tryCreate("name"_s).get(), nullptr);
    346         auto exceptionName = adoptRef(JSValueToStringCopy(context, nameValue, nullptr))->string();
    347         if (exceptionName == "NodeNotFound")
     195    if (JSValueIsString(context, arguments[2])) {
     196        auto result = adoptRef(JSValueToStringCopy(context, arguments[2], exception));
     197        automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, result->string(), { });
     198    } else if (JSValueIsObject(context, arguments[2])) {
     199        JSObjectRef error = JSValueToObject(context, arguments[2], exception);
     200        JSValueRef nameValue = JSObjectGetProperty(context, error, OpaqueJSString::tryCreate("name"_s).get(), exception);
     201        String exceptionName = adoptRef(JSValueToStringCopy(context, nameValue, nullptr))->string();
     202        String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError);
     203        if (exceptionName == "JavaScriptTimeout")
     204            errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptTimeout);
     205        else if (exceptionName == "NodeNotFound")
    348206            errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
    349207        else if (exceptionName == "InvalidNodeIdentifier")
     
    358216            errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::ElementNotInteractable);
    359217
     218        JSValueRef messageValue = JSObjectGetProperty(context, error, OpaqueJSString::tryCreate("message"_s).get(), exception);
     219        auto exceptionMessage = adoptRef(JSValueToStringCopy(context, messageValue, exception))->string();
     220        automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, exceptionMessage, errorType);
     221    } else {
     222        String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InternalError);
     223        automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, { }, errorType);
     224    }
     225
     226    return JSValueMakeUndefined(context);
     227}
     228
     229JSObjectRef WebAutomationSessionProxy::scriptObject(JSGlobalContextRef context)
     230{
     231    JSC::JSGlobalObject* globalObject = toJS(context);
     232    JSC::VM& vm = globalObject->vm();
     233    JSC::JSLockHolder locker(vm);
     234    auto scriptObjectID = JSC::Identifier::fromUid(m_scriptObjectIdentifier);
     235    if (!globalObject->hasProperty(globalObject, scriptObjectID))
     236        return nullptr;
     237
     238    return const_cast<JSObjectRef>(toRef(globalObject, globalObject->get(globalObject, scriptObjectID)));
     239}
     240
     241void WebAutomationSessionProxy::setScriptObject(JSGlobalContextRef context, JSObjectRef object)
     242{
     243    JSC::JSGlobalObject* globalObject = toJS(context);
     244    JSC::VM& vm = globalObject->vm();
     245    JSC::JSLockHolder locker(vm);
     246    auto scriptObjectID = JSC::Identifier::fromUid(m_scriptObjectIdentifier);
     247    PutPropertySlot slot(globalObject);
     248    globalObject->methodTable(vm)->put(globalObject, globalObject, scriptObjectID, toJS(globalObject, object), slot);
     249}
     250
     251JSObjectRef WebAutomationSessionProxy::scriptObjectForFrame(WebFrame& frame)
     252{
     253    JSGlobalContextRef context = frame.jsContext();
     254    if (auto* scriptObject = this->scriptObject(context))
     255        return scriptObject;
     256
     257    JSValueRef exception = nullptr;
     258    String script = StringImpl::createWithoutCopying(WebAutomationSessionProxyScriptSource, sizeof(WebAutomationSessionProxyScriptSource));
     259    JSObjectRef scriptObjectFunction = const_cast<JSObjectRef>(JSEvaluateScript(context, OpaqueJSString::tryCreate(script).get(), nullptr, nullptr, 0, &exception));
     260    ASSERT(JSValueIsObject(context, scriptObjectFunction));
     261
     262    JSValueRef sessionIdentifier = toJSValue(context, m_sessionIdentifier);
     263    JSObjectRef evaluateFunction = JSObjectMakeFunctionWithCallback(context, nullptr, evaluate);
     264    JSObjectRef createUUIDFunction = JSObjectMakeFunctionWithCallback(context, nullptr, createUUID);
     265    JSObjectRef isValidNodeIdentifierFunction = JSObjectMakeFunctionWithCallback(context, nullptr, isValidNodeIdentifier);
     266    JSValueRef arguments[] = { sessionIdentifier, evaluateFunction, createUUIDFunction, isValidNodeIdentifierFunction };
     267    JSObjectRef scriptObject = const_cast<JSObjectRef>(JSObjectCallAsFunction(context, scriptObjectFunction, nullptr, WTF_ARRAY_LENGTH(arguments), arguments, &exception));
     268    ASSERT(JSValueIsObject(context, scriptObject));
     269
     270    setScriptObject(context, scriptObject);
     271    return scriptObject;
     272}
     273
     274WebCore::Element* WebAutomationSessionProxy::elementForNodeHandle(WebFrame& frame, const String& nodeHandle)
     275{
     276    // Don't use scriptObjectForFrame() since we can assume if the script object
     277    // does not exist, there are no nodes mapped to handles. Using scriptObjectForFrame()
     278    // will make a new script object if it can't find one, preventing us from returning fast.
     279    JSGlobalContextRef context = frame.jsContext();
     280    auto* scriptObject = this->scriptObject(context);
     281    if (!scriptObject)
     282        return nullptr;
     283
     284    JSValueRef functionArguments[] = {
     285        toJSValue(context, nodeHandle)
     286    };
     287
     288    JSValueRef result = callPropertyFunction(context, scriptObject, "nodeForIdentifier"_s, WTF_ARRAY_LENGTH(functionArguments), functionArguments, nullptr);
     289    JSObjectRef element = JSValueToObject(context, result, nullptr);
     290    if (!element)
     291        return nullptr;
     292
     293    auto elementWrapper = JSC::jsDynamicCast<WebCore::JSElement*>(toJS(context)->vm(), toJS(element));
     294    if (!elementWrapper)
     295        return nullptr;
     296
     297    return &elementWrapper->wrapped();
     298}
     299
     300void WebAutomationSessionProxy::didClearWindowObjectForFrame(WebFrame& frame)
     301{
     302    String errorMessage = "Callback was not called before the unload event."_s;
     303    String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError);
     304
     305    auto pendingFrameCallbacks = m_webFramePendingEvaluateJavaScriptCallbacksMap.take(frame.frameID());
     306    for (uint64_t callbackID : pendingFrameCallbacks)
     307        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, errorMessage, errorType), 0);
     308}
     309
     310void WebAutomationSessionProxy::evaluateJavaScriptFunction(WebCore::PageIdentifier pageID, Optional<WebCore::FrameIdentifier> optionalFrameID, const String& function, Vector<String> arguments, bool expectsImplicitCallbackArgument, Optional<double> callbackTimeout, uint64_t callbackID)
     311{
     312    WebPage* page = WebProcess::singleton().webPage(pageID);
     313    if (!page) {
     314        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, { },
     315            Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound)), 0);
     316        return;
     317    }
     318    WebFrame* frame = optionalFrameID ? WebProcess::singleton().webFrame(*optionalFrameID) : page->mainWebFrame();
     319    if (!frame) {
     320        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, { },
     321            Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound)), 0);
     322        return;
     323    }
     324
     325    JSObjectRef scriptObject = scriptObjectForFrame(*frame);
     326    ASSERT(scriptObject);
     327
     328    auto frameID = frame->frameID();
     329    JSValueRef exception = nullptr;
     330    JSGlobalContextRef context = frame->jsContext();
     331
     332    if (expectsImplicitCallbackArgument) {
     333        auto result = m_webFramePendingEvaluateJavaScriptCallbacksMap.add(frameID, Vector<uint64_t>());
     334        result.iterator->value.append(callbackID);
     335    }
     336
     337    JSValueRef functionArguments[] = {
     338        toJSValue(context, function),
     339        toJSArray(context, arguments, toJSValue, &exception),
     340        JSValueMakeBoolean(context, expectsImplicitCallbackArgument),
     341        JSValueMakeNumber(context, frameID.toUInt64()),
     342        JSValueMakeNumber(context, callbackID),
     343        JSObjectMakeFunctionWithCallback(context, nullptr, evaluateJavaScriptCallback),
     344        JSValueMakeNumber(context, callbackTimeout.valueOr(-1))
     345    };
     346
     347    {
     348        WebCore::UserGestureIndicator gestureIndicator(WebCore::ProcessingUserGesture, frame->coreFrame()->document());
     349        callPropertyFunction(context, scriptObject, "evaluateJavaScriptFunction"_s, WTF_ARRAY_LENGTH(functionArguments), functionArguments, &exception);
     350    }
     351
     352    if (!exception)
     353        return;
     354
     355    String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InternalError);
     356
     357    String exceptionMessage;
     358    if (JSValueIsObject(context, exception)) {
    360359        JSValueRef messageValue = JSObjectGetProperty(context, const_cast<JSObjectRef>(exception), OpaqueJSString::tryCreate("message"_s).get(), nullptr);
    361360        exceptionMessage = adoptRef(JSValueToStringCopy(context, messageValue, nullptr))->string();
  • trunk/Source/WebKit/WebProcess/Automation/WebAutomationSessionProxy.js

    r254120 r254329  
    4242    evaluateJavaScriptFunction(functionString, argumentStrings, expectsImplicitCallbackArgument, frameID, callbackID, resultCallback, callbackTimeout)
    4343    {
    44         // The script is expected to be a function declaration. Evaluate it inside parenthesis to get the function value.
    45         let functionValue = evaluate("(" + functionString + ")");
    46         if (typeof functionValue !== "function")
    47             throw new TypeError("Script did not evaluate to a function.");
    48 
    49         this._clearStaleNodes();
    50 
    51         let argumentValues = argumentStrings.map(this._jsonParse, this);
    52 
    53         let timeoutIdentifier = 0;
    54         let resultReported = false;
    55 
    56         let reportResult = (result) => {
    57             if (timeoutIdentifier)
    58                 clearTimeout(timeoutIdentifier);
    59             resultCallback(frameID, callbackID, this._jsonStringify(result), false);
    60             resultReported = true;
    61         };
    62         let reportTimeoutError = () => { resultCallback(frameID, callbackID, "JavaScriptTimeout", true); };
    63 
    64         if (expectsImplicitCallbackArgument) {
    65             argumentValues.push(reportResult);
    66             functionValue.apply(null, argumentValues);
    67             if (!resultReported && callbackTimeout >= 0)
    68                 timeoutIdentifier = setTimeout(reportTimeoutError, callbackTimeout);
    69         } else
    70             reportResult(functionValue.apply(null, argumentValues));
     44        this._execute(functionString, argumentStrings, expectsImplicitCallbackArgument, callbackTimeout)
     45            .then(result => { resultCallback(frameID, callbackID, this._jsonStringify(result)); })
     46            .catch(error => { resultCallback(frameID, callbackID, error); });
    7147    }
    7248
     
    8258
    8359    // Private
     60
     61    _execute(functionString, argumentStrings, expectsImplicitCallbackArgument, callbackTimeout)
     62    {
     63        let timeoutPromise;
     64        let timeoutIdentifier = 0;
     65        if (callbackTimeout >= 0) {
     66            timeoutPromise = new Promise((resolve, reject) => {
     67                timeoutIdentifier = setTimeout(() => {
     68                    reject({ name: "JavaScriptTimeout", message: "script timed out after " + callbackTimeout + "ms" });
     69                }, callbackTimeout);
     70            });
     71        }
     72
     73        let promise = new Promise((resolve, reject) => {
     74            // The script is expected to be a function declaration. Evaluate it inside parenthesis to get the function value.
     75            let functionValue = evaluate("(async " + functionString + ")");
     76            if (typeof functionValue !== "function")
     77                reject(new TypeError("Script did not evaluate to a function."));
     78
     79            this._clearStaleNodes();
     80
     81            let argumentValues = argumentStrings.map(this._jsonParse, this);
     82            if (expectsImplicitCallbackArgument)
     83                argumentValues.push(resolve);
     84            let resultPromise = functionValue.apply(null, argumentValues);
     85
     86            let promises = [resultPromise];
     87            if (timeoutPromise)
     88                promises.push(timeoutPromise);
     89            Promise.race(promises)
     90                .then(result => {
     91                    if (!expectsImplicitCallbackArgument) {
     92                        resolve(result);
     93                    }
     94                })
     95                .catch(error => {
     96                    reject(error);
     97                });
     98        });
     99
     100        // Async scripts can call Promise.resolve() in the function script, generating a new promise that is resolved in a
     101        // timer (see w3c test execute_async_script/promise.py::test_promise_resolve_timeout). In that case, the internal race
     102        // finishes resolved, so we need to start a new one here to wait for the second promise to be resolved or the timeout.
     103        let promises = [promise];
     104        if (timeoutPromise)
     105            promises.push(timeoutPromise);
     106        return Promise.race(promises)
     107            .finally(() => {
     108                if (timeoutIdentifier) {
     109                    clearTimeout(timeoutIdentifier);
     110                }
     111            });
     112    }
    84113
    85114    _jsonParse(string)
  • trunk/WebDriverTests/ChangeLog

    r254255 r254329  
     12020-01-10  Carlos Garcia Campos  <cgarcia@igalia.com>
     2
     3        Automation: evaluateJavaScriptFunction should use Promises
     4        https://bugs.webkit.org/show_bug.cgi?id=204151
     5
     6        Reviewed by Brian Burg.
     7
     8        Remove expectations for tests that are now passing.
     9
     10        * TestExpectations.json:
     11
    1122020-01-09  Carlos Garcia Campos  <cgarcia@igalia.com>
    213
  • trunk/WebDriverTests/TestExpectations.json

    r254255 r254329  
    536536        }
    537537    },
    538     "imported/w3c/webdriver/tests/execute_async_script/promise.py": {
    539         "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/204151"}},
    540         "subtests": {
    541             "test_await_promise_reject": {
    542                 "expected": {"all": {"status": ["PASS"]}}
    543             }
    544         }
    545     },
    546     "imported/w3c/webdriver/tests/execute_script/promise.py": {
    547         "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/204151"}},
    548         "subtests": {
    549             "test_await_promise_reject": {
    550                 "expected": {"all": {"status": ["PASS"]}}
    551             }
    552         }
    553     },
    554538    "imported/w3c/webdriver/tests/element_send_keys/content_editable.py": {
    555539        "expected": {"all": {"status": ["FAIL"], "bug": "webkit.org/b/180403"}}
Note: See TracChangeset for help on using the changeset viewer.