/*
 * Copyright (C) 2009, 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "DumpRenderTree.h"

#include "AccessibilityController.h"
#include "BackForwardController.h"
#include "BackForwardListImpl.h"
#include "CString.h"
#include "Credential.h"
#include "DatabaseTracker.h"
#include "DocumentLoader.h"
#include "DumpRenderTree/GCController.h"
#include "DumpRenderTreeSupport.h"
#include "EditingBehaviorTypes.h"
#include "EditorClientBlackBerry.h"
#include "EditorInsertAction.h"
#include "Element.h"
#include "EventSender.h"
#include "Frame.h"
#include "FrameLoaderTypes.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "HistoryItem.h"
#include "IntSize.h"
#include "LayoutTestController.h"
#include "NotImplemented.h"
#include "OwnArrayPtr.h"
#include "Page.h"
#include "PageGroup.h"
#include "PixelDumpSupport.h"
#include "PixelDumpSupportBlackBerry.h"
#include "Range.h"
#include "RenderTreeAsText.h"
#include "ScriptController.h"
#include "SecurityOrigin.h"
#include "Settings.h"
#include "TextAffinity.h"
#include "Timer.h"
#include "Vector.h"
#include "WebCoreTestSupport.h"
#include "WebPage.h"
#include "WebPageClient.h"
#include "WorkQueue.h"
#include "WorkQueueItem.h"
#include <WebSettings.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wtf/NonCopyingSort.h>

#define SDCARD_PATH "/developer"

volatile bool testDone;

RefPtr<LayoutTestController> gLayoutTestController;

WebCore::Frame* mainFrame = 0;
WebCore::Frame* topLoadingFrame = 0;
bool waitForPolicy = false;

// FIXME: Assuming LayoutTests has been copied to /developer/LayoutTests/
static const char* const kSDCLayoutTestsURI = "file:///developer/LayoutTests/";
static const char* httpTestSyntax = "http/tests/";
static const char* httpPrefixURL = "http://127.0.0.1:8000/";

using namespace std;

static WTF::String drtAffinityDescription(WebCore::EAffinity affinity)
{
    if (affinity == WebCore::UPSTREAM)
        return WTF::String("NSSelectionAffinityUpstream");
    if (affinity == WebCore::DOWNSTREAM)
        return WTF::String("NSSelectionAffinityDownstream");
    return "";
}

static WTF::String drtDumpPath(WebCore::Node* node)
{
    WebCore::Node* parent = node->parentNode();
    WTF::String str = WTF::String::format("%s", node->nodeName().utf8().data());
    if (parent) {
        str.append(" > ");
        str.append(drtDumpPath(parent));
    }
    return str;
}

static WTF::String drtRangeDescription(WebCore::Range* range)
{
    if (!range)
        return "(null)";
    return WTF::String::format("range from %d of %s to %d of %s", range->startOffset(), drtDumpPath(range->startContainer()).utf8().data(), range->endOffset(), drtDumpPath(range->endContainer()).utf8().data());
}

static WTF::String drtFrameDescription(WebCore::Frame* frame)
{
    WTF::String name = frame->tree()->uniqueName().string();
    if (frame == mainFrame) {
        if (!name.isNull() && name.length())
            return WTF::String::format("main frame \"%s\"", name.utf8().data());
        return "main frame";
    }
    if (!name.isNull())
        return WTF::String::format("frame \"%s\"", name.utf8().data());
    return "frame (anonymous)";
}

static WTF::String drtCredentialDescription(WebCore::Credential&)
{
    return "<unknown>";
}

static bool shouldLogFrameLoadDelegates(const WTF::String& url)
{
    return url.contains("loading/");
}

namespace BlackBerry {
namespace WebKit {

DumpRenderTree* DumpRenderTree::s_currentInstance = 0;
bool DumpRenderTree::s_selectTrailingWhitespaceEnabled = false;

static void createFile(const WTF::String& fileName)
{
    FILE* fd = fopen(fileName.utf8().data(), "wb");
    fclose(fd);
}

DumpRenderTree::DumpRenderTree(BlackBerry::WebKit::WebPage* page)
    : m_gcController(0)
    , m_accessibilityController(0)
    , m_page(page)
    , m_dumpPixels(false)
    , m_waitToDumpWatchdogTimer(this, &DumpRenderTree::waitToDumpWatchdogTimerFired)
    , m_workTimer(this, &DumpRenderTree::processWork)
    , m_acceptsEditing(true)
{
    WTF::String sdcardPath = SDCARD_PATH;
    m_resultsDir = sdcardPath + "/results/";
    m_indexFile = sdcardPath + "/index.drt";
    m_doneFile = sdcardPath + "/done";
    m_currentTestFile = sdcardPath + "/current.drt";
    m_page->resetVirtualViewportOnCommitted(false);
    m_page->setVirtualViewportSize(800, 600);
    s_currentInstance = this;
}

DumpRenderTree::~DumpRenderTree()
{
    delete m_gcController;
    delete m_accessibilityController;
}

void DumpRenderTree::runTest(const WTF::String& url)
{
    createFile(m_resultsDir + *m_currentTest + ".dump.crash");

    mainFrame->loader()->stopForUserCancel();
    resetToConsistentStateBeforeTesting();
    if (shouldLogFrameLoadDelegates(url))
        gLayoutTestController->setDumpFrameLoadCallbacks(true);
    WTF::String stdoutFile = m_resultsDir + *m_currentTest + ".dump";
    WTF::String stderrFile = m_resultsDir + *m_currentTest + ".stderr";

    // FIXME: we should preserve the original stdout and stderr here but aren't doing
    // that yet due to issues with dup, etc.
    freopen(stdoutFile.utf8().data(), "wb", stdout);
    freopen(stderrFile.utf8().data(), "wb", stderr);

    FILE* current = fopen(m_currentTestFile.utf8().data(), "w");
    fwrite(m_currentTest->utf8().data(), 1, m_currentTest->utf8().length(), current);
    fclose(current);
    m_page->load(url.utf8().data(), 0, false);
}

void DumpRenderTree::doneDrt()
{
    fclose(stdout);
    fclose(stderr);

    // Notify the external world that we're done.
    createFile(m_doneFile);
    (m_page->client())->notifyRunLayoutTestsFinished();
}

void DumpRenderTree::runRemainingTests()
{
    // FIXME: fflush should not be necessary but is temporarily required due to a bug in stdio output.
    fflush(stdout);
    fflush(stderr);

    if (m_currentTest >= m_tests.end() - 1) {
        doneDrt();
        return;
    }

    m_currentTest++;
    if (isHTTPTest(m_currentTest->utf8().data())) {
        m_currentHttpTest = m_currentTest->utf8().data();
        m_currentHttpTest.remove(0, strlen(httpTestSyntax));
        runTest(httpPrefixURL + m_currentHttpTest);
    } else
        runTest(kSDCLayoutTestsURI + *m_currentTest);
}

void DumpRenderTree::resetToConsistentStateBeforeTesting()
{
    if (isHTTPTest(m_currentTest->utf8().data()))
        gLayoutTestController = LayoutTestController::create(String(httpPrefixURL + *m_currentTest).utf8().data(), "");
    else
        gLayoutTestController = LayoutTestController::create(String(kSDCLayoutTestsURI + *m_currentTest).utf8().data(), "");

    gLayoutTestController->setIconDatabaseEnabled(false);

    DumpRenderTreeSupport::resetGeolocationMock(m_page);

    topLoadingFrame = 0;
    m_loadFinished = false;
    s_selectTrailingWhitespaceEnabled = false;

    testDone = false;
    WorkQueue::shared()->clear();
    WorkQueue::shared()->setFrozen(false);

    WebSettings* settings = m_page->settings();

    settings->setTextReflowMode(WebSettings::TextReflowDisabled);
    settings->setJavaScriptEnabled(true);
    settings->setLoadsImagesAutomatically(true);
    settings->setJavaScriptOpenWindowsAutomatically(true);
    settings->setZoomToFitOnLoad(false);
    settings->setDefaultFontSize(16);
    settings->setDefaultFixedFontSize(13);
    settings->setMinimumFontSize(1);
    settings->setSerifFontFamily("Times");
    settings->setFixedFontFamily("Courier New");
    settings->setSansSerifFontFamily("Arial");
    settings->setStandardFontFamily("Times");
    settings->setXSSAuditorEnabled(false);
    settings->setFrameFlatteningEnabled(false);
    settings->setMaximumPagesInCache(0);
    settings->setPluginsEnabled(true);
    settings->setUserScalable(true);
    // Apply new settings to current page, see more in the destructor of WebSettingsTransaction.
    WebSettingsTransaction webSettingTransaction(settings);

    BlackBerry::WebKit::DumpRenderTree::currentInstance()->page()->clearBackForwardList(false);

    setAcceptsEditing(true);
    DumpRenderTreeSupport::setLinksIncludedInFocusChain(true);

    m_page->setVirtualViewportSize(800, 600);
    m_page->resetVirtualViewportOnCommitted(false);
    m_page->setJavaScriptCanAccessClipboard(true);

    if (WebCore::Page* page = DumpRenderTreeSupport::corePage(m_page)) {
        page->setTabKeyCyclesThroughElements(true);
        page->settings()->setEditingBehaviorType(WebCore::EditingUnixBehavior);
        page->settings()->setDOMPasteAllowed(true);
        page->settings()->setValidationMessageTimerMagnification(-1);
        page->settings()->setInteractiveFormValidationEnabled(true);
        page->settings()->setAllowFileAccessFromFileURLs(true);
        page->settings()->setAllowUniversalAccessFromFileURLs(true);
        page->settings()->setAuthorAndUserStylesEnabled(true);
        page->settings()->setUsePreHTML5ParserQuirks(false);
        // FIXME: Other ports also clear history/backForwardList allong with visited links.
        page->group().removeVisitedLinks();
        if (mainFrame = page->mainFrame()) {
            mainFrame->tree()->clearName();
            mainFrame->loader()->setOpener(0);
        }
    }

    // For now we manually garbage collect between each test to make sure the device won't run out of memory due to lazy collection.
    DumpRenderTreeSupport::garbageCollectorCollect();
}

void DumpRenderTree::runTests()
{
    m_gcController = new GCController();
    m_accessibilityController = new AccessibilityController();
    getTestsToRun();

    mainFrame = DumpRenderTreeSupport::corePage(m_page)->mainFrame();

    m_currentTest = m_tests.begin();

    if (m_currentTest == m_tests.end()) {
        doneDrt();
        return;
    }

    if (isHTTPTest(m_currentTest->utf8().data())) {
        m_currentHttpTest = m_currentTest->utf8().data();
        m_currentHttpTest.remove(0, strlen(httpTestSyntax));
        runTest(httpPrefixURL + m_currentHttpTest);
    } else
        runTest(kSDCLayoutTestsURI + *m_currentTest);
}


WTF::String DumpRenderTree::dumpFramesAsText(WebCore::Frame* frame)
{
    WTF::String s;
    WebCore::Element* documentElement = frame->document()->documentElement();
    if (!documentElement)
        return s.utf8().data();

    if (frame->tree()->parent())
        s = WTF::String::format("\n--------\nFrame: '%s'\n--------\n", frame->tree()->uniqueName().string().utf8().data());

    s += documentElement->innerText() + "\n";

    if (gLayoutTestController->dumpChildFramesAsText()) {
        WebCore::FrameTree* tree = frame->tree();
        for (WebCore::Frame* child = tree->firstChild(); child; child = child->tree()->nextSibling())
            s += dumpFramesAsText(child);
    }
    return s;
}

static void dumpToFile(const WTF::String& data)
{
    fwrite(data.utf8().data(), 1, data.utf8().length(), stdout);
}

bool DumpRenderTree::isHTTPTest(const WTF::String& test)
{
    if (test.length() < strlen(httpTestSyntax))
        return false;
    return test.lower().substring(0, strlen(httpTestSyntax)) == httpTestSyntax;
}

void DumpRenderTree::getTestsToRun()
{
    Vector<WTF::String> files;

    FILE* fd = fopen(m_indexFile.utf8().data(), "r");
    fseek(fd, 0, SEEK_END);
    int size = ftell(fd);
    fseek(fd, 0, SEEK_SET);
    OwnArrayPtr<char> buf = adoptArrayPtr(new char[size]);
    fread(buf.get(), 1, size, fd);
    fclose(fd);
    WTF::String s(buf.get(), size);
    s.split("\n", files);

    m_tests = files;
}

void DumpRenderTree::invalidateAnyPreviousWaitToDumpWatchdog()
{
    m_waitToDumpWatchdogTimer.stop();
    waitForPolicy = false;
}

WTF::String DumpRenderTree::renderTreeDump() const
{
    if (mainFrame) {
        if (mainFrame->view() && mainFrame->view()->layoutPending())
            mainFrame->view()->layout();

        return externalRepresentation(mainFrame);
    }
    return "";
}

static bool historyItemCompare(const RefPtr<WebCore::HistoryItem>& a, const RefPtr<WebCore::HistoryItem>& b)
{
    return codePointCompare(a->urlString(), b->urlString()) < 0;
}

static WTF::String dumpHistoryItem(PassRefPtr<WebCore::HistoryItem> item, int indent, bool current)
{
    WTF::String result;

    int start = 0;
    if (current) {
        result += "curr->";
        start = 6;
    }
    for (int i = start; i < indent; i++)
        result += " ";

    WTF::String url = item->urlString();
    if (url.contains("file://")) {
        static WTF::String layoutTestsString("/LayoutTests/");
        static WTF::String fileTestString("(file test):");

        WTF::String res = url.substring(url.find(layoutTestsString) + layoutTestsString.length());
        if (res.isEmpty())
            return result;

        result += fileTestString;
        result += res;
    } else
        result += url;

    WTF::String target = item->target();
    if (!target.isEmpty())
        result += " (in frame \"" + target + "\")";

    if (item->isTargetItem())
        result += "  **nav target**";
    result += "\n";

    WebCore::HistoryItemVector children = item->children();
    // Must sort to eliminate arbitrary result ordering which defeats reproducible testing.
    nonCopyingSort(children.begin(), children.end(), historyItemCompare);
    unsigned resultSize = children.size();
    for (unsigned i = 0; i < resultSize; ++i)
        result += dumpHistoryItem(children[i], indent + 4, false);

    return result;
}

static WTF::String dumpBackForwardListForWebView()
{
    WTF::String result = "\n============== Back Forward List ==============\n";
    // FORMAT:
    // "        (file test):fast/loader/resources/click-fragment-link.html  **nav target**"
    // "curr->  (file test):fast/loader/resources/click-fragment-link.html#testfragment  **nav target**"
    WebCore::BackForwardListImpl* bfList = static_cast<WebCore::BackForwardListImpl*>(mainFrame->page()->backForward()->client());
    int maxItems = bfList->capacity();
    WebCore::HistoryItemVector entries;
    bfList->backListWithLimit(maxItems, entries);
    unsigned resultSize = entries.size();
    for (unsigned i = 0; i < resultSize; ++i)
        result += dumpHistoryItem(entries[i], 8, false);

    result += dumpHistoryItem(bfList->currentItem(), 8, true);

    bfList->forwardListWithLimit(maxItems, entries);
    resultSize = entries.size();
    for (unsigned i = 0; i < resultSize; ++i)
        result += dumpHistoryItem(entries[i], 8, false);

    result += "===============================================\n";

    return result;
}

void DumpRenderTree::dump()
{
    invalidateAnyPreviousWaitToDumpWatchdog();

    WTF::String dumpFile = m_resultsDir + *m_currentTest + ".dump";

    WTF::String resultMimeType = "text/plain";
    WTF::String responseMimeType = mainFrame->loader()->documentLoader()->responseMIMEType();

    bool dumpAsText = gLayoutTestController->dumpAsText() || responseMimeType == "text/plain";
    WTF::String data = dumpAsText ? dumpFramesAsText(mainFrame) : renderTreeDump();

    if (gLayoutTestController->dumpBackForwardList())
        data += dumpBackForwardListForWebView();

    WTF::String result = "Content-Type: " + resultMimeType + "\n" + data;

    dumpToFile(result);
    if (m_dumpPixels && !dumpAsText && gLayoutTestController->generatePixelResults())
        dumpWebViewAsPixelsAndCompareWithExpected(gLayoutTestController->expectedPixelHash());

    WTF::String crashFile = dumpFile + ".crash";
    unlink(crashFile.utf8().data());

    testDone = true;
    runRemainingTests();
}

void DumpRenderTree::setWaitToDumpWatchdog(double interval)
{
    invalidateAnyPreviousWaitToDumpWatchdog();
    m_waitToDumpWatchdogTimer.startOneShot(interval);
}

void DumpRenderTree::waitToDumpWatchdogTimerFired(WebCore::Timer<DumpRenderTree>*)
{
    gLayoutTestController->waitToDumpWatchdogTimerFired();
}

void DumpRenderTree::processWork(WebCore::Timer<DumpRenderTree>*)
{
    if (topLoadingFrame)
        return;

    if (WorkQueue::shared()->processWork() && !gLayoutTestController->waitToDump())
        dump();
}

void DumpRenderTree::locationChangeForFrame(WebCore::Frame* frame)
{
    if (frame != topLoadingFrame)
        return;

    topLoadingFrame = 0;
    WorkQueue::shared()->setFrozen(true); // first complete load freezes the queue
    if (gLayoutTestController->waitToDump())
        return;

    if (WorkQueue::shared()->count())
        m_workTimer.startOneShot(0);
    else
        dump();
}

// FrameLoadClient delegates.
void DumpRenderTree::didStartProvisionalLoadForFrame(WebCore::Frame* frame)
{
    if (!testDone && gLayoutTestController->dumpFrameLoadCallbacks())
        printf("%s - didStartProvisionalLoadForFrame\n", drtFrameDescription(frame).utf8().data());

    if (!testDone && gLayoutTestController->dumpUserGestureInFrameLoadCallbacks())
        printf("Frame with user gesture \"%s\" - in didStartProvisionalLoadForFrame\n", WebCore::ScriptController::processingUserGesture() ? "true" : "false");

    if (!topLoadingFrame && !testDone)
        topLoadingFrame = frame;

    if (!testDone && gLayoutTestController->stopProvisionalFrameLoads()) {
        printf("%s - stopping load in didStartProvisionalLoadForFrame callback\n", drtFrameDescription(frame).utf8().data());
        frame->loader()->stopForUserCancel();
    }
}

void DumpRenderTree::didCommitLoadForFrame(WebCore::Frame* frame)
{
    if (!testDone && gLayoutTestController->dumpFrameLoadCallbacks())
        printf("%s - didCommitLoadForFrame\n", drtFrameDescription(frame).utf8().data());

    gLayoutTestController->setWindowIsKey(true);
}

void DumpRenderTree::didFailProvisionalLoadForFrame(WebCore::Frame* frame)
{
    if (!testDone && gLayoutTestController->dumpFrameLoadCallbacks())
        printf("%s - didFailProvisionalLoadWithError\n", drtFrameDescription(frame).utf8().data());

    locationChangeForFrame(frame);
}

void DumpRenderTree::didFailLoadForFrame(WebCore::Frame* frame)
{
    if (!testDone && gLayoutTestController->dumpFrameLoadCallbacks())
        printf("%s - didFailLoadWithError\n", drtFrameDescription(frame).utf8().data());

    locationChangeForFrame(frame);
}

void DumpRenderTree::didFinishLoadForFrame(WebCore::Frame* frame)
{
    if (!testDone && gLayoutTestController->dumpFrameLoadCallbacks())
        printf("%s - didFinishLoadForFrame\n", drtFrameDescription(frame).utf8().data());

    if (frame == topLoadingFrame)
        m_loadFinished = true;
    locationChangeForFrame(frame);
}

void DumpRenderTree::didFinishDocumentLoadForFrame(WebCore::Frame* frame)
{
    if (!testDone) {
        if (gLayoutTestController->dumpFrameLoadCallbacks())
            printf("%s - didFinishDocumentLoadForFrame\n", drtFrameDescription(frame).utf8().data());
        else {
            unsigned pendingFrameUnloadEvents = frame->domWindow()->pendingUnloadEventListeners();
            if (pendingFrameUnloadEvents)
                printf("%s - has %u onunload handler(s)\n", drtFrameDescription(frame).utf8().data(), pendingFrameUnloadEvents);
        }
    }
}

void DumpRenderTree::didClearWindowObjectInWorld(WebCore::DOMWrapperWorld*, JSGlobalContextRef context, JSObjectRef windowObject)
{
    JSValueRef exception = 0;

    gLayoutTestController->makeWindowObject(context, windowObject, &exception);
    ASSERT(!exception);

    m_gcController->makeWindowObject(context, windowObject, &exception);
    ASSERT(!exception);

    m_accessibilityController->makeWindowObject(context, windowObject, &exception);
    ASSERT(!exception);

    JSStringRef eventSenderStr = JSStringCreateWithUTF8CString("eventSender");
    JSValueRef eventSender = makeEventSender(context);
    JSObjectSetProperty(context, windowObject, eventSenderStr, eventSender, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, 0);
    JSStringRelease(eventSenderStr);
    WebCoreTestSupport::injectInternalsObject(context);
}

void DumpRenderTree::didReceiveTitleForFrame(const WTF::String& title, WebCore::Frame* frame)
{
    if (!testDone && gLayoutTestController->dumpFrameLoadCallbacks())
        printf("%s - didReceiveTitle: %s\n", drtFrameDescription(frame).utf8().data(), title.utf8().data());

    if (gLayoutTestController->dumpTitleChanges())
        printf("TITLE CHANGED: %s\n", title.utf8().data());
}

// ChromeClient delegates.
void DumpRenderTree::addMessageToConsole(const WTF::String& message, unsigned int lineNumber, const WTF::String& sourceID)
{
    printf("CONSOLE MESSAGE: line %d: %s\n", lineNumber, message.utf8().data());
}

void DumpRenderTree::runJavaScriptAlert(const WTF::String& message)
{
    if (!testDone)
        printf("ALERT: %s\n", message.utf8().data());
}

bool DumpRenderTree::runJavaScriptConfirm(const WTF::String& message)
{
    if (!testDone)
        printf("CONFIRM: %s\n", message.utf8().data());
    return true;
}

WTF::String DumpRenderTree::runJavaScriptPrompt(const WTF::String& message, const WTF::String& defaultValue)
{
    if (!testDone)
        printf("PROMPT: %s, default text: %s\n", message.utf8().data(), defaultValue.utf8().data());
    return defaultValue;
}

bool DumpRenderTree::runBeforeUnloadConfirmPanel(const WTF::String& message)
{
    if (!testDone)
        printf("CONFIRM NAVIGATION: %s\n", message.utf8().data());
    return true;
}

void DumpRenderTree::setStatusText(const WTF::String& status)
{
    if (gLayoutTestController->dumpStatusCallbacks())
        printf("UI DELEGATE STATUS CALLBACK: setStatusText:%s\n", status.utf8().data());
}

void DumpRenderTree::exceededDatabaseQuota(WebCore::SecurityOrigin* origin, const WTF::String& name)
{
    if (!testDone && gLayoutTestController->dumpDatabaseCallbacks())
        printf("UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{%s, %s, %i} database:%s\n", origin->protocol().utf8().data(), origin->host().utf8().data(), origin->port(), name.utf8().data());

    WebCore::DatabaseTracker::tracker().setQuota(mainFrame->document()->securityOrigin(), 5 * 1024 * 1024);
}

bool DumpRenderTree::allowsOpeningWindow()
{
    return gLayoutTestController->canOpenWindows();
}

void DumpRenderTree::windowCreated(BlackBerry::WebKit::WebPage* page)
{
    page->settings()->setJavaScriptOpenWindowsAutomatically(true);
}

// EditorClient delegates.
void DumpRenderTree::didBeginEditing()
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: webViewDidBeginEditing:%s\n", "WebViewDidBeginEditingNotification");
}

void DumpRenderTree::didEndEditing()
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: webViewDidEndEditing:%s\n", "WebViewDidEndEditingNotification");
}

void DumpRenderTree::didChange()
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: webViewDidChange:%s\n", "WebViewDidChangeNotification");
}

void DumpRenderTree::didChangeSelection()
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: webViewDidChangeSelection:%s\n", "WebViewDidChangeSelectionNotification");
}

bool DumpRenderTree::findString(const WTF::String& string, WebCore::FindOptions options)
{
    WebCore::Page* page = mainFrame ? mainFrame->page() : 0;
    return page && page->findString(string, options);
}

bool DumpRenderTree::shouldBeginEditingInDOMRange(WebCore::Range* range)
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n", drtRangeDescription(range).utf8().data());
    return m_acceptsEditing;
}

bool DumpRenderTree::shouldEndEditingInDOMRange(WebCore::Range* range)
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n", drtRangeDescription(range).utf8().data());
    return m_acceptsEditing;
}

bool DumpRenderTree::shouldDeleteDOMRange(WebCore::Range* range)
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: shouldDeleteDOMRange:%s\n", drtRangeDescription(range).utf8().data());
    return m_acceptsEditing;
}

bool DumpRenderTree::shouldChangeSelectedDOMRangeToDOMRangeAffinityStillSelecting(WebCore::Range* fromRange, WebCore::Range* toRange, int affinity, bool stillSelecting)
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: shouldChangeSelectedDOMRange:%s toDOMRange:%s affinity:%s stillSelecting:%s\n", drtRangeDescription(fromRange).utf8().data(), drtRangeDescription(toRange).utf8().data(), drtAffinityDescription(static_cast<WebCore::EAffinity>(affinity)).utf8().data(), stillSelecting ? "TRUE" : "FALSE");
    return m_acceptsEditing;
}

static const char* insertActionString(WebCore::EditorInsertAction action)
{
    switch (action) {
    case WebCore::EditorInsertActionTyped:
        return "WebViewInsertActionTyped";
    case WebCore::EditorInsertActionPasted:
        return "WebViewInsertActionPasted";
    case WebCore::EditorInsertActionDropped:
        return "WebViewInsertActionDropped";
    }
    ASSERT_NOT_REACHED();
    return "WebViewInsertActionTyped";
}

bool DumpRenderTree::shouldInsertNode(WebCore::Node* node, WebCore::Range* range, int action)
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n", drtDumpPath(node).utf8().data(), drtRangeDescription(range).utf8().data(), insertActionString((WebCore::EditorInsertAction)action));
    return m_acceptsEditing;
}

bool DumpRenderTree::shouldInsertText(const WTF::String& text, WebCore::Range* range, int action)
{
    if (!testDone && gLayoutTestController->dumpEditingCallbacks())
        printf("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n", text.utf8().data(), drtRangeDescription(range).utf8().data(), insertActionString((WebCore::EditorInsertAction)action));
    return m_acceptsEditing;
}

void DumpRenderTree::didDecidePolicyForNavigationAction(const WebCore::NavigationAction& action, const WebCore::ResourceRequest& request)
{
    if (!waitForPolicy)
        return;

    const char* typeDescription;
    switch (action.type()) {
    case WebCore::NavigationTypeLinkClicked:
        typeDescription = "link clicked";
        break;
    case WebCore::NavigationTypeFormSubmitted:
        typeDescription = "form submitted";
        break;
    case WebCore::NavigationTypeBackForward:
        typeDescription = "back/forward";
        break;
    case WebCore::NavigationTypeReload:
        typeDescription = "reload";
        break;
    case WebCore::NavigationTypeFormResubmitted:
        typeDescription = "form resubmitted";
        break;
    case WebCore::NavigationTypeOther:
        typeDescription = "other";
        break;
    default:
        typeDescription = "illegal value";
    }

    printf("Policy delegate: attempt to load %s with navigation type '%s'\n", request.url().string().utf8().data(), typeDescription);
    // FIXME: do originating part.

    gLayoutTestController->notifyDone();
}

void DumpRenderTree::didDispatchWillPerformClientRedirect()
{
    if (!testDone && gLayoutTestController->dumpUserGestureInFrameLoadCallbacks())
        printf("Frame with user gesture \"%s\" - in willPerformClientRedirect\n", WebCore::ScriptController::processingUserGesture() ? "true" : "false");
}

void DumpRenderTree::didHandleOnloadEventsForFrame(WebCore::Frame* frame)
{
    if (!testDone && gLayoutTestController->dumpFrameLoadCallbacks())
        printf("%s - didHandleOnloadEventsForFrame\n", drtFrameDescription(frame).utf8().data());
}

void DumpRenderTree::didReceiveResponseForFrame(WebCore::Frame* frame, const WebCore::ResourceResponse& response)
{
    if (!testDone && gLayoutTestController->dumpResourceResponseMIMETypes())
        printf("%s has MIME type %s\n", response.url().lastPathComponent().utf8().data(), response.mimeType().utf8().data());
}

bool DumpRenderTree::didReceiveAuthenticationChallenge(WebCore::Credential& credential)
{
    if (!gLayoutTestController->handlesAuthenticationChallenges()) {
        credential = WebCore::Credential();
        printf("%s - didReceiveAuthenticationChallenge - Simulating cancelled authentication\n", drtCredentialDescription(credential).utf8().data());
        return false;
    }
    const char* user = gLayoutTestController->authenticationUsername().c_str();
    const char* password = gLayoutTestController->authenticationPassword().c_str();
    credential = WebCore::Credential(user, password, WebCore::CredentialPersistenceForSession);
    printf("%s - didReceiveAuthenticationChallenge - Responding with %s:%s\n", drtCredentialDescription(credential).utf8().data(), user, password);
    return true;
}

}
}

// Static dump() function required by cross-platform DRT code.
void dump()
{
    BlackBerry::WebKit::DumpRenderTree* dumper = BlackBerry::WebKit::DumpRenderTree::currentInstance();
    if (!dumper)
        return;

    dumper->dump();
}
