Changeset 235408 in webkit


Ignore:
Timestamp:
Aug 27, 2018 4:31:15 PM (6 years ago)
Author:
Simon Fraser
Message:

Teach WebKitTestRunner and DumpRenderTree about detecting world leaks
https://bugs.webkit.org/show_bug.cgi?id=188994

Reviewed by Tim Horton.
Source/WebCore:

Export Document::postTask() for use by WTR's injected bundle.

  • dom/Document.h:

Source/WebKit:

This patch adds the notion of a "control command" in the protocol between webkitpy and
WebKitTestRunner/DumpRenderTree. A command is simply an input string starting with a #
that is checked for before trying to parse the input as test URL. For now, just one
commmand is supported, which is "#CHECK FOR WORLD LEAKS".

In response to the command, the tool dumps an output block in the usual pseudo-MIME-style,
with a trailing "#EOF". Future patches will add support to webkitpy to parse this output.

DumpRenderTree stubs out the command, returning an empty block.

WebKitTestRunner responds to the command by dumping the list of live documents, if it was
run with the --check-for-world-leaks option.

When run with --check-for-world-leaks, WebKitTestRunner gets the list of live documents via
WKBundleGetLiveDocumentURLs() after every test (this allows it to detect the first test
that leaked a document), and keeps them in a map of document identifier to test and live document URL.
Then when it receives the "#CHECK FOR WORLD LEAKS" command, it calls into the bundle to
clear the page and memory caches, runs a GC, then posts a task (in the Document::postTaks() sense)
after which it requests the list of live documents for a final time, excluding any that are loaded
in live Frames (thus omitting the about:blank that will be loaded at this point). Documents in this
list are therefore leaked (or abandoned).

Future patches will hook up webkitpy reporting for leaked documents.

  • WebProcess/InjectedBundle/API/c/WKBundle.cpp:

(WKBundleGetLiveDocumentURLs):
(WKBundleClearPageCache):
(WKBundleClearMemoryCache):

  • WebProcess/InjectedBundle/API/c/WKBundlePage.cpp:

(WKBundlePagePostTask):

  • WebProcess/InjectedBundle/API/c/WKBundlePage.h:
  • WebProcess/InjectedBundle/API/c/WKBundlePrivate.h:
  • WebProcess/InjectedBundle/InjectedBundle.cpp:

(WebKit::InjectedBundle::liveDocumentURLs):

  • WebProcess/InjectedBundle/InjectedBundle.h:

Tools:

This patch adds the notion of a "control command" in the protocol between webkitpy and
WebKitTestRunner/DumpRenderTree. A command is simply an input string starting with a #
that is checked for before trying to parse the input as test URL. For now, just one
commmand is supported, which is "#CHECK FOR WORLD LEAKS".

In response to the command, the tool dumps an output block in the usual pseudo-MIME-style,
with a trailing "#EOF". Future patches will add support to webkitpy to parse this output.

DumpRenderTree stubs out the command, returning an empty block.

WebKitTestRunner responds to the command by dumping the list of live documents, if it was
run with the --check-for-world-leaks option.

When run with --check-for-world-leaks, WebKitTestRunner gets the list of live documents via
WKBundleGetLiveDocumentURLs() after every test (this allows it to detect the first test
that leaked a document), and keeps them in a map of document identifier to test and live document URL.
Then when it receives the "#CHECK FOR WORLD LEAKS" command, it calls into the bundle to
clear the page and memory caches, runs a GC, then posts a task (in the Document::postTaks() sense)
after which it requests the list of live documents for a final time, excluding any that are loaded
in live Frames (thus omitting the about:blank that will be loaded at this point). Documents in this
list are therefore leaked (or abandoned).

Future patches will hook up webkitpy reporting for leaked documents.

  • DumpRenderTree/mac/DumpRenderTree.mm:

(initializeGlobalsFromCommandLineOptions):
(handleControlCommand):
(runTestingServerLoop):

  • DumpRenderTree/win/DumpRenderTree.cpp:

(handleControlCommand):
(main):

  • WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:

(WTR::postGCTask):
(WTR::InjectedBundle::reportLiveDocuments):
(WTR::InjectedBundle::didReceiveMessageToPage):

  • WebKitTestRunner/InjectedBundle/InjectedBundle.h:
  • WebKitTestRunner/Options.cpp:

(WTR::handleOptionCheckForWorldLeaks):
(WTR::OptionsHandler::OptionsHandler):

  • WebKitTestRunner/Options.h:
  • WebKitTestRunner/TestController.cpp:

(WTR::AsyncTask::run):
(WTR::AsyncTask::currentTask):
(WTR::TestController::initialize):
(WTR::TestController::ensureViewSupportsOptionsForTest):
(WTR::TestController::resetStateToConsistentValues):
(WTR::TestController::updateLiveDocumentsAfterTest):
(WTR::TestController::checkForWorldLeaks):
(WTR::TestController::findAndDumpWorldLeaks):
(WTR::TestController::willDestroyWebView):
(WTR::parseInputLine):
(WTR::TestController::waitForCompletion):
(WTR::TestController::handleControlCommand):
(WTR::TestController::runTestingServerLoop):
(WTR::TestController::run):
(WTR::TestController::didReceiveLiveDocumentsList):
(WTR::TestController::didReceiveMessageFromInjectedBundle):

  • WebKitTestRunner/TestController.h:

(WTR::AsyncTask::AsyncTask):
(WTR::AsyncTask::taskComplete):
(WTR::TestController::AbandonedDocumentInfo::AbandonedDocumentInfo):

  • WebKitTestRunner/TestInvocation.cpp:

(WTR::TestInvocation::invoke):

  • WebKitTestRunner/TestOptions.h:

(WTR::TestOptions::hasSameInitializationOptions const):

Location:
trunk
Files:
20 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r235403 r235408  
     12018-08-27  Simon Fraser  <simon.fraser@apple.com>
     2
     3        Teach WebKitTestRunner and DumpRenderTree about detecting world leaks
     4        https://bugs.webkit.org/show_bug.cgi?id=188994
     5
     6        Reviewed by Tim Horton.
     7
     8        Export Document::postTask() for use by WTR's injected bundle.
     9
     10        * dom/Document.h:
     11
    1122018-08-27  Aditya Keerthi  <akeerthi@apple.com>
    213
  • trunk/Source/WebCore/dom/Document.h

    r235014 r235408  
    10311031    void parseDNSPrefetchControlHeader(const String&);
    10321032
    1033     void postTask(Task&&) final; // Executes the task on context's thread asynchronously.
     1033    WEBCORE_EXPORT void postTask(Task&&) final; // Executes the task on context's thread asynchronously.
    10341034
    10351035    ScriptedAnimationController* scriptedAnimationController() { return m_scriptedAnimationController.get(); }
  • trunk/Source/WebKit/ChangeLog

    r235407 r235408  
     12018-08-27  Simon Fraser  <simon.fraser@apple.com>
     2
     3        Teach WebKitTestRunner and DumpRenderTree about detecting world leaks
     4        https://bugs.webkit.org/show_bug.cgi?id=188994
     5
     6        Reviewed by Tim Horton.
     7
     8        This patch adds the notion of a "control command" in the protocol between webkitpy and
     9        WebKitTestRunner/DumpRenderTree. A command is simply an input string starting with a #
     10        that is checked for before trying to parse the input as test URL. For now, just one
     11        commmand is supported, which is "#CHECK FOR WORLD LEAKS".
     12       
     13        In response to the command, the tool dumps an output block in the usual pseudo-MIME-style,
     14        with a trailing "#EOF". Future patches will add support to webkitpy to parse this output.
     15       
     16        DumpRenderTree stubs out the command, returning an empty block.
     17       
     18        WebKitTestRunner responds to the command by dumping the list of live documents, if it was
     19        run with the --check-for-world-leaks option.
     20       
     21        When run with --check-for-world-leaks, WebKitTestRunner gets the list of live documents via
     22        WKBundleGetLiveDocumentURLs() after every test (this allows it to detect the first test
     23        that leaked a document), and keeps them in a map of document identifier to test and live document URL.
     24        Then when it receives the "#CHECK FOR WORLD LEAKS" command, it calls into the bundle to
     25        clear the page and memory caches, runs a GC, then posts a task (in the Document::postTaks() sense)
     26        after which it requests the list of live documents for a final time, excluding any that are loaded
     27        in live Frames (thus omitting the about:blank that will be loaded at this point). Documents in this
     28        list are therefore leaked (or abandoned).
     29       
     30        Future patches will hook up webkitpy reporting for leaked documents.
     31
     32        * WebProcess/InjectedBundle/API/c/WKBundle.cpp:
     33        (WKBundleGetLiveDocumentURLs):
     34        (WKBundleClearPageCache):
     35        (WKBundleClearMemoryCache):
     36        * WebProcess/InjectedBundle/API/c/WKBundlePage.cpp:
     37        (WKBundlePagePostTask):
     38        * WebProcess/InjectedBundle/API/c/WKBundlePage.h:
     39        * WebProcess/InjectedBundle/API/c/WKBundlePrivate.h:
     40        * WebProcess/InjectedBundle/InjectedBundle.cpp:
     41        (WebKit::InjectedBundle::liveDocumentURLs):
     42        * WebProcess/InjectedBundle/InjectedBundle.h:
     43
    1442018-08-27  Alex Christensen  <achristensen@webkit.org>
    245
  • trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundle.cpp

    r233400 r235408  
    3535#include "WKBundleAPICast.h"
    3636#include "WKBundlePrivate.h"
     37#include "WKMutableArray.h"
     38#include "WKMutableDictionary.h"
     39#include "WKNumber.h"
     40#include "WKRetainPtr.h"
     41#include "WKString.h"
    3742#include "WebConnection.h"
    3843#include "WebFrame.h"
     
    4045#include "WebPageGroupProxy.h"
    4146#include <WebCore/DatabaseTracker.h>
     47#include <WebCore/MemoryCache.h>
     48#include <WebCore/PageCache.h>
    4249#include <WebCore/ResourceLoadObserver.h>
    4350#include <WebCore/ServiceWorkerThreadProxy.h>
     
    214221}
    215222
     223WKArrayRef WKBundleGetLiveDocumentURLs(WKBundleRef bundleRef, WKBundlePageGroupRef pageGroupRef, bool excludeDocumentsInPageGroupPages)
     224{
     225    auto liveDocuments = toImpl(bundleRef)->liveDocumentURLs(toImpl(pageGroupRef), excludeDocumentsInPageGroupPages);
     226
     227    auto liveURLs = adoptWK(WKMutableArrayCreate());
     228
     229    for (const auto& it : liveDocuments) {
     230        auto urlInfo = adoptWK(WKMutableDictionaryCreate());
     231
     232        auto documentIDKey = adoptWK(WKStringCreateWithUTF8CString("id"));
     233        auto documentURLKey = adoptWK(WKStringCreateWithUTF8CString("url"));
     234
     235        auto documentIDValue = adoptWK(WKUInt64Create(it.key));
     236        auto documentURLValue = adoptWK(toCopiedAPI(it.value));
     237
     238        WKDictionarySetItem(urlInfo.get(), documentIDKey.get(), documentIDValue.get());
     239        WKDictionarySetItem(urlInfo.get(), documentURLKey.get(), documentURLValue.get());
     240
     241        WKArrayAppendItem(liveURLs.get(), urlInfo.get());
     242    }
     243   
     244    return liveURLs.leakRef();
     245}
     246
    216247void WKBundleReportException(JSContextRef context, JSValueRef exception)
    217248{
     
    228259    // Historically, we've used the following (somewhat nonsensical) string for the databaseIdentifier of local files.
    229260    DatabaseTracker::singleton().setQuota(*SecurityOriginData::fromDatabaseIdentifier("file__0"), quota);
     261}
     262
     263void WKBundleClearPageCache(WKBundleRef bundle)
     264{
     265    PageCache::singleton().pruneToSizeNow(0, PruningReason::MemoryPressure);
     266}
     267
     268void WKBundleClearMemoryCache(WKBundleRef bundle)
     269{
     270    MemoryCache::singleton().evictResources();
    230271}
    231272
  • trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp

    r234330 r235408  
    6565#include <WebCore/PageOverlayController.h>
    6666#include <WebCore/RenderLayerCompositor.h>
     67#include <WebCore/ScriptExecutionContext.h>
    6768#include <WebCore/SecurityOriginData.h>
    6869#include <WebCore/URL.h>
     
    616617   
    617618    page->ensureTestTrigger().setTestCallbackAndStartNotificationTimer([=]() {
     619        callback(context);
     620    });
     621}
     622
     623void WKBundlePagePostTask(WKBundlePageRef pageRef, WKBundlePageTestNotificationCallback callback, void* context)
     624{
     625    if (!callback)
     626        return;
     627   
     628    WebKit::WebPage* webPage = toImpl(pageRef);
     629    WebCore::Page* page = webPage ? webPage->corePage() : nullptr;
     630    if (!page)
     631        return;
     632
     633    WebCore::Document* document = page->mainFrame().document();
     634    if (!document)
     635        return;
     636
     637    document->postTask([=] (WebCore::ScriptExecutionContext&) {
    618638        callback(context);
    619639    });
  • trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h

    r226471 r235408  
    118118WK_EXPORT void WKBundlePageRegisterScrollOperationCompletionCallback(WKBundlePageRef, WKBundlePageTestNotificationCallback, void* context);
    119119
     120// Posts a task in the ScriptExecutionContext of the main frame. Used to do work after other tasks have completed.
     121WK_EXPORT void WKBundlePagePostTask(WKBundlePageRef, WKBundlePageTestNotificationCallback, void* context);
     122
    120123WK_EXPORT void WKBundlePagePostMessage(WKBundlePageRef page, WKStringRef messageName, WKTypeRef messageBody);
    121124
  • trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePrivate.h

    r233207 r235408  
    6969WK_EXPORT WKDataRef WKBundleCreateWKDataFromUInt8Array(WKBundleRef bundle, JSContextRef context, JSValueRef data);
    7070WK_EXPORT void WKBundleSetAsynchronousSpellCheckingEnabled(WKBundleRef bundleRef, WKBundlePageGroupRef pageGroupRef, bool enabled);
     71// Returns array of dictionaries. Dictionary keys are document identifiers, values are document URLs.
     72WK_EXPORT WKArrayRef WKBundleGetLiveDocumentURLs(WKBundleRef bundle, WKBundlePageGroupRef pageGroup, bool excludeDocumentsInPageGroupPages);
    7173
    7274// UserContent API
     
    9799WK_EXPORT void WKBundleExtendClassesForParameterCoder(WKBundleRef bundle, WKArrayRef classes);
    98100
     101WK_EXPORT void WKBundleClearPageCache(WKBundleRef bundle);
     102WK_EXPORT void WKBundleClearMemoryCache(WKBundleRef bundle);
     103
    99104#ifdef __cplusplus
    100105}
  • trunk/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.cpp

    r235205 r235408  
    5656#include <WebCore/ApplicationCacheStorage.h>
    5757#include <WebCore/CommonVM.h>
     58#include <WebCore/Document.h>
    5859#include <WebCore/Frame.h>
    5960#include <WebCore/FrameLoader.h>
     
    605606}
    606607
     608InjectedBundle::DocumentIDToURLMap InjectedBundle::liveDocumentURLs(WebPageGroupProxy* pageGroup, bool excludeDocumentsInPageGroupPages)
     609{
     610    DocumentIDToURLMap result;
     611
     612    for (const auto* document : Document::allDocuments())
     613        result.add(document->identifier().toUInt64(), document->url().string());
     614
     615    if (excludeDocumentsInPageGroupPages) {
     616        for (const auto* page : PageGroup::pageGroup(pageGroup->identifier())->pages()) {
     617            for (const auto* frame = &page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
     618                if (!frame->document())
     619                    continue;
     620                result.remove(frame->document()->identifier().toUInt64());
     621            }
     622        }
     623    }
     624
     625    return result;
     626}
     627
    607628void InjectedBundle::setTabKeyCyclesThroughElements(WebPage* page, bool enabled)
    608629{
  • trunk/Source/WebKit/WebProcess/InjectedBundle/InjectedBundle.h

    r233207 r235408  
    123123    uint64_t webNotificationID(JSContextRef, JSValueRef);
    124124    Ref<API::Data> createWebDataFromUint8Array(JSContextRef, JSValueRef);
     125   
     126    typedef HashMap<uint64_t, String> DocumentIDToURLMap;
     127    DocumentIDToURLMap liveDocumentURLs(WebPageGroupProxy*, bool excludeDocumentsInPageGroupPages);
    125128
    126129    // UserContent API
  • trunk/Tools/ChangeLog

    r235405 r235408  
     12018-08-27  Simon Fraser  <simon.fraser@apple.com>
     2
     3        Teach WebKitTestRunner and DumpRenderTree about detecting world leaks
     4        https://bugs.webkit.org/show_bug.cgi?id=188994
     5
     6        Reviewed by Tim Horton.
     7       
     8        This patch adds the notion of a "control command" in the protocol between webkitpy and
     9        WebKitTestRunner/DumpRenderTree. A command is simply an input string starting with a #
     10        that is checked for before trying to parse the input as test URL. For now, just one
     11        commmand is supported, which is "#CHECK FOR WORLD LEAKS".
     12       
     13        In response to the command, the tool dumps an output block in the usual pseudo-MIME-style,
     14        with a trailing "#EOF". Future patches will add support to webkitpy to parse this output.
     15       
     16        DumpRenderTree stubs out the command, returning an empty block.
     17       
     18        WebKitTestRunner responds to the command by dumping the list of live documents, if it was
     19        run with the --check-for-world-leaks option.
     20       
     21        When run with --check-for-world-leaks, WebKitTestRunner gets the list of live documents via
     22        WKBundleGetLiveDocumentURLs() after every test (this allows it to detect the first test
     23        that leaked a document), and keeps them in a map of document identifier to test and live document URL.
     24        Then when it receives the "#CHECK FOR WORLD LEAKS" command, it calls into the bundle to
     25        clear the page and memory caches, runs a GC, then posts a task (in the Document::postTaks() sense)
     26        after which it requests the list of live documents for a final time, excluding any that are loaded
     27        in live Frames (thus omitting the about:blank that will be loaded at this point). Documents in this
     28        list are therefore leaked (or abandoned).
     29       
     30        Future patches will hook up webkitpy reporting for leaked documents.
     31
     32        * DumpRenderTree/mac/DumpRenderTree.mm:
     33        (initializeGlobalsFromCommandLineOptions):
     34        (handleControlCommand):
     35        (runTestingServerLoop):
     36        * DumpRenderTree/win/DumpRenderTree.cpp:
     37        (handleControlCommand):
     38        (main):
     39        * WebKitTestRunner/InjectedBundle/InjectedBundle.cpp:
     40        (WTR::postGCTask):
     41        (WTR::InjectedBundle::reportLiveDocuments):
     42        (WTR::InjectedBundle::didReceiveMessageToPage):
     43        * WebKitTestRunner/InjectedBundle/InjectedBundle.h:
     44        * WebKitTestRunner/Options.cpp:
     45        (WTR::handleOptionCheckForWorldLeaks):
     46        (WTR::OptionsHandler::OptionsHandler):
     47        * WebKitTestRunner/Options.h:
     48        * WebKitTestRunner/TestController.cpp:
     49        (WTR::AsyncTask::run):
     50        (WTR::AsyncTask::currentTask):
     51        (WTR::TestController::initialize):
     52        (WTR::TestController::ensureViewSupportsOptionsForTest):
     53        (WTR::TestController::resetStateToConsistentValues):
     54        (WTR::TestController::updateLiveDocumentsAfterTest):
     55        (WTR::TestController::checkForWorldLeaks):
     56        (WTR::TestController::findAndDumpWorldLeaks):
     57        (WTR::TestController::willDestroyWebView):
     58        (WTR::parseInputLine):
     59        (WTR::TestController::waitForCompletion):
     60        (WTR::TestController::handleControlCommand):
     61        (WTR::TestController::runTestingServerLoop):
     62        (WTR::TestController::run):
     63        (WTR::TestController::didReceiveLiveDocumentsList):
     64        (WTR::TestController::didReceiveMessageFromInjectedBundle):
     65        * WebKitTestRunner/TestController.h:
     66        (WTR::AsyncTask::AsyncTask):
     67        (WTR::AsyncTask::taskComplete):
     68        (WTR::TestController::AbandonedDocumentInfo::AbandonedDocumentInfo):
     69        * WebKitTestRunner/TestInvocation.cpp:
     70        (WTR::TestInvocation::invoke):
     71        * WebKitTestRunner/TestOptions.h:
     72        (WTR::TestOptions::hasSameInitializationOptions const):
     73
    1742018-08-27  Alex Christensen  <achristensen@webkit.org>
    275
  • trunk/Tools/DumpRenderTree/mac/DumpRenderTree.mm

    r234940 r235408  
    224224static int showWebView;
    225225static int printTestCount;
     226static int checkForWorldLeaks;
    226227static BOOL printSeparators;
    227228static RetainPtr<CFStringRef> persistentUserStyleSheetLocation;
     
    11211122        {"show-webview", no_argument, &showWebView, YES},
    11221123        {"print-test-count", no_argument, &printTestCount, YES},
     1124        {"check-for-world-leaks", no_argument, &checkForWorldLeaks, NO},
    11231125        {nullptr, 0, nullptr, 0}
    11241126    };
     
    11531155}
    11541156
     1157static bool handleControlCommand(const char* command)
     1158{
     1159    if (!strcmp("#CHECK FOR WORLD LEAKS", command)) {
     1160        // DumpRenderTree does not support checking for world leaks.
     1161        WTF::String result("\n");
     1162        printf("Content-Type: text/plain\n");
     1163        printf("Content-Length: %u\n", result.length());
     1164        fwrite(result.utf8().data(), 1, result.length(), stdout);
     1165        printf("#EOF\n");
     1166        fprintf(stderr, "#EOF\n");
     1167        fflush(stdout);
     1168        fflush(stderr);
     1169        return true;
     1170    }
     1171    return false;
     1172}
     1173
    11551174static void runTestingServerLoop()
    11561175{
     
    11651184
    11661185        if (strlen(filenameBuffer) == 0)
     1186            continue;
     1187
     1188        if (handleControlCommand(filenameBuffer))
    11671189            continue;
    11681190
  • trunk/Tools/DumpRenderTree/win/DumpRenderTree.cpp

    r234948 r235408  
    11131113}
    11141114
     1115static bool handleControlCommand(const char* command)
     1116{
     1117    if (!strcmp("#CHECK FOR ABANDONED DOCUMENTS", command)) {
     1118        // DumpRenderTree does not support checking for abandonded documents.
     1119        String result("\n");
     1120        printf("Content-Type: text/plain\n");
     1121        printf("Content-Length: %u\n", result.length());
     1122        fwrite(result.utf8().data(), 1, result.length(), stdout);
     1123        printf("#EOF\n");
     1124        fprintf(stderr, "#EOF\n");
     1125        fflush(stdout);
     1126        fflush(stderr);
     1127        return true;
     1128    }
     1129    return false;
     1130}
    11151131
    11161132static void runTest(const string& inputLine)
     
    16171633                continue;
    16181634
     1635            if (handleControlCommand(filenameBuffer))
     1636                continue;
     1637
    16191638            runTest(filenameBuffer);
    16201639        }
  • trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.cpp

    r235399 r235408  
    181181}
    182182
     183static void postGCTask(void* context)
     184{
     185    WKBundlePageRef page = reinterpret_cast<WKBundlePageRef>(context);
     186    InjectedBundle::singleton().reportLiveDocuments(page);
     187    WKRelease(page);
     188}
     189
     190void InjectedBundle::reportLiveDocuments(WKBundlePageRef page)
     191{
     192    const bool excludeDocumentsInPageGroup = true;
     193    auto documentURLs = adoptWK(WKBundleGetLiveDocumentURLs(m_bundle, m_pageGroup, excludeDocumentsInPageGroup));
     194    auto ackMessageName = adoptWK(WKStringCreateWithUTF8CString("LiveDocuments"));
     195    WKBundlePagePostMessage(page, ackMessageName.get(), documentURLs.get());
     196}
     197
    183198void InjectedBundle::didReceiveMessageToPage(WKBundlePageRef page, WKStringRef messageName, WKTypeRef messageBody)
    184199{
     
    245260
    246261        InjectedBundle::page()->resetAfterTest();
    247 
     262        return;
     263    }
     264
     265    if (WKStringIsEqualToUTF8CString(messageName, "GetLiveDocuments")) {
     266        const bool excludeDocumentsInPageGroup = false;
     267        auto documentURLs = adoptWK(WKBundleGetLiveDocumentURLs(m_bundle, m_pageGroup, excludeDocumentsInPageGroup));
     268        auto ackMessageName = adoptWK(WKStringCreateWithUTF8CString("LiveDocuments"));
     269        WKBundlePagePostMessage(page, ackMessageName.get(), documentURLs.get());
     270        return;
     271    }
     272
     273    if (WKStringIsEqualToUTF8CString(messageName, "CheckForWorldLeaks")) {
     274        WKBundleClearPageCache(m_bundle);
     275        WKBundleClearMemoryCache(m_bundle);
     276        WKBundleGarbageCollectJavaScriptObjects(m_bundle);
     277
     278        WKRetain(page); // Balanced by the release in postGCTask.
     279        WKBundlePagePostTask(page, postGCTask, (void*)page);
    248280        return;
    249281    }
  • trunk/Tools/WebKitTestRunner/InjectedBundle/InjectedBundle.h

    r235399 r235408  
    141141    void textFieldDidEndEditing();
    142142
     143    void reportLiveDocuments(WKBundlePageRef);
     144
    143145    void resetUserScriptInjectedCount() { m_userScriptInjectedCount = 0; }
    144146    void increaseUserScriptInjectedCount() { ++m_userScriptInjectedCount; }
  • trunk/Tools/WebKitTestRunner/Options.cpp

    r230192 r235408  
    9494}
    9595
     96static bool handleOptionCheckForWorldLeaks(Options& options, const char*, const char*)
     97{
     98    options.checkForWorldLeaks = true;
     99    return true;
     100}
     101
    96102static bool handleOptionAllowAnyHTTPSCertificateForAllowedHosts(Options& options, const char*, const char*)
    97103{
     
    130136    optionList.append(Option("--show-webview", "Show the WebView during test runs (for debugging)", handleOptionShowWebView));
    131137    optionList.append(Option("--show-touches", "Show the touches during test runs (for debugging)", handleOptionShowTouches));
     138    optionList.append(Option("--check-for-world-leaks", "Check for leaks of world objects (currently, documents)", handleOptionCheckForWorldLeaks));
    132139
    133140    optionList.append(Option(0, 0, handleOptionUnmatched));
  • trunk/Tools/WebKitTestRunner/Options.h

    r230192 r235408  
    4949    bool shouldShowWebView { false };
    5050    bool shouldShowTouches { false };
     51    bool checkForWorldLeaks { false };
    5152    bool allowAnyHTTPSCertificateForAllowedHosts { false };
    5253    std::vector<std::string> paths;
  • trunk/Tools/WebKitTestRunner/TestController.cpp

    r235399 r235408  
    114114    // Any fake response would do, all we need for testing is to implement the callback.
    115115    return WKStringCreateWithUTF8CString("MIHFMHEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAnX0TILJrOMUue%2BPtwBRE6XfV%0AWtKQbsshxk5ZhcUwcwyvcnIq9b82QhJdoACdD34rqfCAIND46fXKQUnb0mvKzQID%0AAQABFhFNb3ppbGxhSXNNeUZyaWVuZDANBgkqhkiG9w0BAQQFAANBAAKv2Eex2n%2FS%0Ar%2F7iJNroWlSzSMtTiQTEB%2BADWHGj9u1xrUrOilq%2Fo2cuQxIfZcNZkYAkWP4DubqW%0Ai0%2F%2FrgBvmco%3D");
     116}
     117
     118AsyncTask* AsyncTask::m_currentTask;
     119
     120bool AsyncTask::run()
     121{
     122    m_currentTask = this;
     123    m_task();
     124    TestController::singleton().runUntil(m_taskDone, m_timeout);
     125    m_currentTask = nullptr;
     126    return m_taskDone;
     127}
     128
     129AsyncTask* AsyncTask::currentTask()
     130{
     131    return m_currentTask;
    116132}
    117133
     
    385401    m_shouldShowWebView = options.shouldShowWebView;
    386402    m_shouldShowTouches = options.shouldShowTouches;
     403    m_checkForWorldLeaks = options.checkForWorldLeaks;
    387404    m_allowAnyHTTPSCertificateForAllowedHosts = options.allowAnyHTTPSCertificateForAllowedHosts;
    388405
     
    661678            return;
    662679
     680        willDestroyWebView();
     681
    663682        WKPageSetPageUIClient(m_mainWebView->page(), nullptr);
    664683        WKPageSetPageNavigationClient(m_mainWebView->page(), nullptr);
     
    670689    createWebViewWithOptions(options);
    671690
    672     if (!resetStateToConsistentValues(options))
     691    if (!resetStateToConsistentValues(options, ResetStage::BeforeTest))
    673692        TestInvocation::dumpWebProcessUnresponsiveness("<unknown> - TestController::run - Failed to reset state to consistent values\n");
    674693}
     
    784803}
    785804
    786 bool TestController::resetStateToConsistentValues(const TestOptions& options)
     805bool TestController::resetStateToConsistentValues(const TestOptions& options, ResetStage resetStage)
    787806{
    788807    SetForScope<State> changeState(m_state, Resetting);
     
    912931    WKPageLoadURL(m_mainWebView->page(), blankURL());
    913932    runUntil(m_doneResetting, m_currentInvocation->shortTimeout());
     933    if (!m_doneResetting)
     934        return false;
     935   
     936    if (resetStage == ResetStage::AfterTest && m_checkForWorldLeaks)
     937        updateLiveDocumentsAfterTest();
     938
    914939    return m_doneResetting;
     940}
     941
     942void TestController::updateLiveDocumentsAfterTest()
     943{
     944    AsyncTask([]() {
     945        // After each test, we update the list of live documents so that we can detect when an abandoned document first showed up.
     946        WKRetainPtr<WKStringRef> messageName = adoptWK(WKStringCreateWithUTF8CString("GetLiveDocuments"));
     947        WKPagePostMessageToInjectedBundle(TestController::singleton().mainWebView()->page(), messageName.get(), nullptr);
     948    }, 5_s).run();
     949}
     950
     951void TestController::checkForWorldLeaks()
     952{
     953    AsyncTask([]() {
     954        // This runs at the end of a series of tests. It clears caches, runs a GC and then fetches the list of documents.
     955        WKRetainPtr<WKStringRef> messageName = adoptWK(WKStringCreateWithUTF8CString("CheckForWorldLeaks"));
     956        WKPagePostMessageToInjectedBundle(TestController::singleton().mainWebView()->page(), messageName.get(), nullptr);
     957    }, 20_s).run();
     958}
     959
     960void TestController::findAndDumpWorldLeaks()
     961{
     962    checkForWorldLeaks();
     963
     964    StringBuilder builder;
     965   
     966    if (m_abandonedDocumentInfo.size()) {
     967        for (const auto& it : m_abandonedDocumentInfo) {
     968            auto documentURL = it.value.abandonedDocumentURL;
     969            if (documentURL.isEmpty())
     970                documentURL = "(no url)";
     971            builder.append("TEST: ");
     972            builder.append(it.value.testURL);
     973            builder.append('\n');
     974            builder.append("ABANDONED DOCUMENT: ");
     975            builder.append(documentURL);
     976            builder.append('\n');
     977        }
     978    } else
     979        builder.append("no abandoned documents");
     980
     981    String result = builder.toString();
     982    printf("Content-Type: text/plain\n");
     983    printf("Content-Length: %u\n", result.length());
     984    fwrite(result.utf8().data(), 1, result.length(), stdout);
     985    printf("#EOF\n");
     986    fprintf(stderr, "#EOF\n");
     987    fflush(stdout);
     988    fflush(stderr);
     989}
     990
     991void TestController::willDestroyWebView()
     992{
     993    // Before we kill the web view, look for abandoned documents before that web process goes away.
     994    checkForWorldLeaks();
    915995}
    916996
     
    12391319}
    12401320
    1241 TestCommand parseInputLine(const std::string& inputLine)
     1321static TestCommand parseInputLine(const std::string& inputLine)
    12421322{
    12431323    TestCommand result;
     
    12971377}
    12981378
     1379bool TestController::waitForCompletion(const WTF::Function<void ()>& function, WTF::Seconds timeout)
     1380{
     1381    m_doneResetting = false;
     1382    function();
     1383    runUntil(m_doneResetting, timeout);
     1384    return !m_doneResetting;
     1385}
     1386
     1387bool TestController::handleControlCommand(const char* command)
     1388{
     1389    if (!strcmp("#CHECK FOR WORLD LEAKS", command)) {
     1390        findAndDumpWorldLeaks();
     1391        return true;
     1392    }
     1393    return false;
     1394}
     1395
    12991396void TestController::runTestingServerLoop()
    13001397{
     
    13061403
    13071404        if (strlen(filenameBuffer) == 0)
     1405            continue;
     1406
     1407        if (handleControlCommand(filenameBuffer))
    13081408            continue;
    13091409
     
    13221422                break;
    13231423        }
     1424        findAndDumpWorldLeaks();
    13241425    }
    13251426}
     
    13861487}
    13871488
     1489void TestController::didReceiveLiveDocumentsList(WKArrayRef liveDocumentList)
     1490{
     1491    auto numDocuments = WKArrayGetSize(liveDocumentList);
     1492
     1493    HashMap<uint64_t, String> documentInfo;
     1494    for (size_t i = 0; i < numDocuments; ++i) {
     1495        WKTypeRef item = WKArrayGetItemAtIndex(liveDocumentList, i);
     1496        if (item && WKGetTypeID(item) == WKDictionaryGetTypeID()) {
     1497            WKDictionaryRef liveDocumentItem = static_cast<WKDictionaryRef>(item);
     1498
     1499            WKRetainPtr<WKStringRef> idKey(AdoptWK, WKStringCreateWithUTF8CString("id"));
     1500            WKUInt64Ref documentID = static_cast<WKUInt64Ref>(WKDictionaryGetItemForKey(liveDocumentItem, idKey.get()));
     1501
     1502            WKRetainPtr<WKStringRef> urlKey(AdoptWK, WKStringCreateWithUTF8CString("url"));
     1503            WKStringRef documentURL = static_cast<WKStringRef>(WKDictionaryGetItemForKey(liveDocumentItem, urlKey.get()));
     1504
     1505            documentInfo.add(WKUInt64GetValue(documentID), toWTFString(documentURL));
     1506        }
     1507    }
     1508
     1509    if (!documentInfo.size()) {
     1510        m_abandonedDocumentInfo.clear();
     1511        return;
     1512    }
     1513
     1514    // Remove any documents which are no longer live.
     1515    m_abandonedDocumentInfo.removeIf([&](auto& keyAndValue) {
     1516        return !documentInfo.contains(keyAndValue.key);
     1517    });
     1518   
     1519    // Add newly abandoned documents.
     1520    String currentTestURL = m_currentInvocation ? toWTFString(adoptWK(WKURLCopyString(m_currentInvocation->url()))) : "no test";
     1521    for (const auto& it : documentInfo)
     1522        m_abandonedDocumentInfo.add(it.key, AbandonedDocumentInfo(currentTestURL, it.value));
     1523}
     1524
    13881525void TestController::didReceiveMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody)
    13891526{
     1527    if (WKStringIsEqualToUTF8CString(messageName, "LiveDocuments")) {
     1528        ASSERT(WKGetTypeID(messageBody) == WKArrayGetTypeID());
     1529        didReceiveLiveDocumentsList(static_cast<WKArrayRef>(messageBody));
     1530        AsyncTask::currentTask()->taskComplete();
     1531        return;
     1532    }
     1533
    13901534    if (WKStringIsEqualToUTF8CString(messageName, "EventSender")) {
    13911535        if (m_state != RunningTest)
  • trunk/Tools/WebKitTestRunner/TestController.h

    r235399 r235408  
    4949struct TestOptions;
    5050
     51class AsyncTask {
     52public:
     53    AsyncTask(WTF::Function<void ()>&& task, WTF::Seconds timeout)
     54        : m_task(WTFMove(task))
     55        , m_timeout(timeout)
     56    {
     57        ASSERT(!currentTask());
     58    }
     59
     60    // Returns false on timeout.
     61    bool run();
     62
     63    void taskComplete()
     64    {
     65        m_taskDone = true;
     66    }
     67
     68    static AsyncTask* currentTask();
     69
     70private:
     71    static AsyncTask* m_currentTask;
     72
     73    WTF::Function<void ()> m_task;
     74    WTF::Seconds m_timeout;
     75    bool m_taskDone { false };
     76};
     77
    5178// FIXME: Rename this TestRunner?
    5279class TestController {
     
    122149    unsigned imageCountInGeneralPasteboard() const;
    123150
    124     bool resetStateToConsistentValues(const TestOptions&);
     151    enum class ResetStage { BeforeTest, AfterTest };
     152    bool resetStateToConsistentValues(const TestOptions&, ResetStage);
    125153    void resetPreferencesToConsistentValues(const TestOptions&);
     154
     155    void willDestroyWebView();
    126156
    127157    void terminateWebContentProcess();
     
    232262    void runTestingServerLoop();
    233263    bool runTest(const char* pathOrURL);
     264   
     265    // Returns false if timed out.
     266    bool waitForCompletion(const WTF::Function<void ()>&, WTF::Seconds timeout);
     267
     268    bool handleControlCommand(const char* command);
    234269
    235270    void platformInitialize();
     
    262297    void updateWindowScaleForTest(PlatformWebView*, const TestInvocation&);
    263298
     299    void updateLiveDocumentsAfterTest();
     300    void checkForWorldLeaks();
     301
     302    void didReceiveLiveDocumentsList(WKArrayRef);
     303    void findAndDumpWorldLeaks();
     304
    264305    void decidePolicyForGeolocationPermissionRequestIfPossible();
    265306    void decidePolicyForUserMediaPermissionRequestIfPossible();
     
    432473   
    433474    bool m_shouldShowTouches { false };
     475    bool m_checkForWorldLeaks { false };
    434476
    435477    bool m_allowAnyHTTPSCertificateForAllowedHosts { false };
     
    445487
    446488    WorkQueueManager m_workQueueManager;
     489
     490    struct AbandonedDocumentInfo {
     491        String testURL;
     492        String abandonedDocumentURL;
     493
     494        AbandonedDocumentInfo() = default;
     495        AbandonedDocumentInfo(String inTestURL, String inAbandonedDocumentURL)
     496            : testURL(inTestURL)
     497            , abandonedDocumentURL(inAbandonedDocumentURL)
     498        { }
     499    };
     500    HashMap<uint64_t, AbandonedDocumentInfo> m_abandonedDocumentInfo;
    447501};
    448502
  • trunk/Tools/WebKitTestRunner/TestInvocation.cpp

    r235399 r235408  
    188188#endif // !PLATFORM(IOS)
    189189
    190     if (TestController::singleton().resetStateToConsistentValues(m_options))
     190    if (TestController::singleton().resetStateToConsistentValues(m_options, TestController::ResetStage::AfterTest))
    191191        return;
    192192
  • trunk/Tools/WebKitTestRunner/TestOptions.h

    r235399 r235408  
    6161    bool punchOutWhiteBackgroundsInDarkMode { false };
    6262    bool runSingly { false };
     63    bool checkForWorldLeaks { false };
    6364
    6465    float deviceScaleFactor { 1 };
     
    9899            || punchOutWhiteBackgroundsInDarkMode != options.punchOutWhiteBackgroundsInDarkMode
    99100            || jscOptions != options.jscOptions
    100             || runSingly != options.runSingly)
     101            || runSingly != options.runSingly
     102            || checkForWorldLeaks != options.checkForWorldLeaks)
    101103            return false;
    102104
Note: See TracChangeset for help on using the changeset viewer.