TestShell.cpp 26.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "TestShell.h"

34 35
#include "DRTDevToolsAgent.h"
#include "DRTDevToolsClient.h"
36
#include "LayoutTestController.h"
37
#include "WebArrayBufferView.h"
38 39 40 41 42
#include "WebDataSource.h"
#include "WebDocument.h"
#include "WebElement.h"
#include "WebFrame.h"
#include "WebHistoryItem.h"
43
#include "WebIDBFactory.h"
44
#include "WebTestingSupport.h"
45
#include "WebKit.h"
46
#include "WebPermissions.h"
47
#include "WebPoint.h"
48 49 50 51 52 53 54 55 56
#include "WebRuntimeFeatures.h"
#include "WebScriptController.h"
#include "WebSettings.h"
#include "WebSize.h"
#include "WebSpeechInputControllerMock.h"
#include "WebString.h"
#include "WebURLRequest.h"
#include "WebURLResponse.h"
#include "WebView.h"
57 58 59
#include "WebViewHost.h"
#include "skia/ext/platform_canvas.h"
#include "webkit/support/webkit_support.h"
60
#include "webkit/support/webkit_support_gfx.h"
61 62 63
#include <algorithm>
#include <cctype>
#include <vector>
64
#include <wtf/MD5.h>
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

using namespace WebKit;
using namespace std;

// Content area size for newly created windows.
static const int testWindowWidth = 800;
static const int testWindowHeight = 600;

// The W3C SVG layout tests use a different size than the other layout tests.
static const int SVGTestWindowWidth = 480;
static const int SVGTestWindowHeight = 360;

static const char layoutTestsPattern[] = "/LayoutTests/";
static const string::size_type layoutTestsPatternSize = sizeof(layoutTestsPattern) - 1;
static const char fileUrlPattern[] = "file:/";
static const char fileTestPrefix[] = "(file test):";
static const char dataUrlPattern[] = "data:";
static const string::size_type dataUrlPatternSize = sizeof(dataUrlPattern) - 1;

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
// FIXME: Move this to a common place so that it can be shared with
// WebCore::TransparencyWin::makeLayerOpaque().
static void makeCanvasOpaque(SkCanvas* canvas)
{
    const SkBitmap& bitmap = canvas->getTopDevice()->accessBitmap(true);
    ASSERT(bitmap.config() == SkBitmap::kARGB_8888_Config);

    SkAutoLockPixels lock(bitmap);
    for (int y = 0; y < bitmap.height(); y++) {
        uint32_t* row = bitmap.getAddr32(0, y);
        for (int x = 0; x < bitmap.width(); x++)
            row[x] |= 0xFF000000; // Set alpha bits to 1.
    }
}

99
TestShell::TestShell(bool testShellMode)
100 101 102
    : m_testIsPending(false)
    , m_testIsPreparing(false)
    , m_focusedWidget(0)
103
    , m_testShellMode(testShellMode)
104
    , m_devTools(0)
105
    , m_allowExternalPages(false)
106
    , m_acceleratedCompositingForVideoEnabled(false)
107
    , m_threadedCompositingEnabled(false)
108
    , m_compositeToTexture(false)
109
    , m_forceCompositingMode(false)
110
    , m_accelerated2dCanvasEnabled(false)
111 112
    , m_legacyAccelerated2dCanvasEnabled(false)
    , m_acceleratedDrawingEnabled(false)
113 114
    , m_stressOpt(false)
    , m_stressDeopt(false)
115
    , m_dumpWhenFinished(true)
116
{
117
    WebRuntimeFeatures::enableDataTransferItems(true);
118
    WebRuntimeFeatures::enableGeolocation(true);
119
    WebRuntimeFeatures::enablePointerLock(true);
120
    WebRuntimeFeatures::enableIndexedDatabase(true);
121
    WebRuntimeFeatures::enableFileSystem(true);
122
    WebRuntimeFeatures::enableJavaScriptI18NAPI(true);
123
    WebRuntimeFeatures::enableMediaStream(true);
124
    WebRuntimeFeatures::enableWebAudio(true); 
125
    WebRuntimeFeatures::enableVideoTrack(true);
126
    WebRuntimeFeatures::enableGamepad(true);
127

128
    m_webPermissions = adoptPtr(new WebPermissions(this));
129 130 131 132 133
    m_accessibilityController = adoptPtr(new AccessibilityController(this));
    m_layoutTestController = adoptPtr(new LayoutTestController(this));
    m_eventSender = adoptPtr(new EventSender(this));
    m_plainTextController = adoptPtr(new PlainTextController());
    m_textInputController = adoptPtr(new TextInputController(this));
134
#if ENABLE(NOTIFICATIONS)
135
    m_notificationPresenter = adoptPtr(new NotificationPresenter(this));
136
#endif
137
    m_printer = m_testShellMode ? TestEventPrinter::createTestShellPrinter() : TestEventPrinter::createDRTPrinter();
138 139 140 141 142 143

    // 30 second is the same as the value in Mac DRT.
    // If we use a value smaller than the timeout value of
    // (new-)run-webkit-tests, (new-)run-webkit-tests misunderstands that a
    // timed-out DRT process was crashed.
    m_timeout = 30 * 1000;
144

145 146 147 148 149
    createMainWindow();
}

void TestShell::createMainWindow()
{
150
    m_drtDevToolsAgent = adoptPtr(new DRTDevToolsAgent);
151
    m_webViewHost = adoptPtr(createNewWindow(WebURL(), m_drtDevToolsAgent.get()));
152
    m_webView = m_webViewHost->webView();
153
    m_drtDevToolsAgent->setWebView(m_webView);
154 155 156 157
}

TestShell::~TestShell()
{
158 159 160
    // Note: DevTools are closed together with all the other windows in the
    // windows list.

161
    // Destroy the WebView before its WebViewHost.
162
    m_drtDevToolsAgent->setWebView(0);
163 164
}

165 166
void TestShell::createDRTDevToolsClient(DRTDevToolsAgent* agent)
{
167
    m_drtDevToolsClient = adoptPtr(new DRTDevToolsClient(agent, m_devTools->webView()));
168 169 170 171 172 173 174 175 176 177 178
}

void TestShell::showDevTools()
{
    if (!m_devTools) {
        WebURL url = webkit_support::GetDevToolsPathAsURL();
        if (!url.isValid()) {
            ASSERT(false);
            return;
        }
        m_devTools = createNewWindow(url);
179
        m_devTools->webView()->settings()->setMemoryInfoEnabled(true);
180
        m_devTools->setLogConsoleOutput(false);
181 182 183 184 185 186 187 188 189
        ASSERT(m_devTools);
        createDRTDevToolsClient(m_drtDevToolsAgent.get());
    }
    m_devTools->show(WebKit::WebNavigationPolicyNewWindow);
}

void TestShell::closeDevTools()
{
    if (m_devTools) {
190
        m_devTools->webView()->settings()->setMemoryInfoEnabled(false);
191
        m_drtDevToolsAgent->reset();
192
        m_drtDevToolsClient.clear();
193 194 195 196 197
        closeWindow(m_devTools);
        m_devTools = 0;
    }
}

198 199
void TestShell::resetWebSettings(WebView& webView)
{
200
    m_prefs.reset();
201 202
    m_prefs.acceleratedCompositingEnabled = true;
    m_prefs.acceleratedCompositingForVideoEnabled = m_acceleratedCompositingForVideoEnabled;
203
    m_prefs.threadedCompositingEnabled = m_threadedCompositingEnabled;
204
    m_prefs.compositeToTexture = m_compositeToTexture;
205
    m_prefs.forceCompositingMode = m_forceCompositingMode;
206
    m_prefs.accelerated2dCanvasEnabled = m_accelerated2dCanvasEnabled;
207 208
    m_prefs.legacyAccelerated2dCanvasEnabled = m_legacyAccelerated2dCanvasEnabled;
    m_prefs.acceleratedDrawingEnabled = m_acceleratedDrawingEnabled;
209
    m_prefs.applyTo(&webView);
210 211 212 213
}

void TestShell::runFileTest(const TestParams& params)
{
214
    ASSERT(params.testUrl.isValid());
215 216 217 218
    m_testIsPreparing = true;
    m_params = params;
    string testUrl = m_params.testUrl.spec();

219 220 221 222
    if (testUrl.find("loading/") != string::npos
        || testUrl.find("loading\\") != string::npos)
        m_layoutTestController->setShouldDumpFrameLoadCallbacks(true);

223 224 225 226 227 228
    if (testUrl.find("compositing/") != string::npos || testUrl.find("compositing\\") != string::npos) {
        m_prefs.acceleratedCompositingForVideoEnabled = true;
        m_prefs.accelerated2dCanvasEnabled = true;
        m_prefs.applyTo(m_webView);
    }

229 230 231 232 233 234 235 236
    if (testUrl.find("/dumpAsText/") != string::npos
        || testUrl.find("\\dumpAsText\\") != string::npos) {
        m_layoutTestController->setShouldDumpAsText(true);
        m_layoutTestController->setShouldGeneratePixelResults(false);
    }

    if (testUrl.find("/inspector/") != string::npos
        || testUrl.find("\\inspector\\") != string::npos)
237 238
        showDevTools();

239 240 241
    if (m_params.debugLayerTree)
        m_layoutTestController->setShowDebugLayerTree(true);

242 243
    if (m_dumpWhenFinished)
        m_printer->handleTestHeader(testUrl.c_str());
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
    loadURL(m_params.testUrl);

    m_testIsPreparing = false;
    waitTestFinished();
}

static inline bool isSVGTestURL(const WebURL& url)
{
    return url.isValid() && string(url.spec()).find("W3C-SVG-1.1") != string::npos;
}

void TestShell::resizeWindowForTest(WebViewHost* window, const WebURL& url)
{
    int width, height;
    if (isSVGTestURL(url)) {
        width = SVGTestWindowWidth;
        height = SVGTestWindowHeight;
    } else {
        width = testWindowWidth;
        height = testWindowHeight;
    }
    window->setWindowRect(WebRect(0, 0, width + virtualWindowBorder * 2, height + virtualWindowBorder * 2));
}

void TestShell::resetTestController()
{
270
    resetWebSettings(*webView());
271
    m_webPermissions->reset();
272 273 274 275
    m_accessibilityController->reset();
    m_layoutTestController->reset();
    m_eventSender->reset();
    m_webViewHost->reset();
276
#if ENABLE(NOTIFICATIONS)
277
    m_notificationPresenter->reset();
278
#endif
279 280 281
    m_drtDevToolsAgent->reset();
    if (m_drtDevToolsClient)
        m_drtDevToolsClient->reset();
282
    webView()->setPageScaleFactor(1, WebPoint(0, 0));
283 284
    webView()->enableFixedLayoutMode(false);
    webView()->setFixedLayoutSize(WebSize(0, 0));
285
    webView()->mainFrame()->clearOpener();
286
    WebTestingSupport::resetInternalsObject(webView()->mainFrame());
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
}

void TestShell::loadURL(const WebURL& url)
{
    m_webViewHost->loadURLForFrame(url, WebString());
}

void TestShell::reload()
{
    m_webViewHost->navigationController()->reload();
}

void TestShell::goToOffset(int offset)
{
     m_webViewHost->navigationController()->goToOffset(offset);
}

int TestShell::navigationEntryCount() const
{
    return m_webViewHost->navigationController()->entryCount();
}

void TestShell::callJSGC()
{
    m_webView->mainFrame()->collectGarbage();
}

void TestShell::setFocus(WebWidget* widget, bool enable)
{
    // Simulate the effects of InteractiveSetFocus(), which includes calling
    // both setFocus() and setIsActive().
    if (enable) {
        if (m_focusedWidget != widget) {
            if (m_focusedWidget)
                m_focusedWidget->setFocus(false);
            webView()->setIsActive(enable);
            widget->setFocus(enable);
            m_focusedWidget = widget;
        }
    } else {
        if (m_focusedWidget == widget) {
            widget->setFocus(enable);
            webView()->setIsActive(enable);
            m_focusedWidget = 0;
        }
    }
}

void TestShell::testFinished()
{
    if (!m_testIsPending)
        return;
    m_testIsPending = false;
340 341
    if (m_dumpWhenFinished)
        dump();
342 343 344 345 346
    webkit_support::QuitMessageLoop();
}

void TestShell::testTimedOut()
{
347
    m_printer->handleTimedOut();
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
    testFinished();
}

static string dumpDocumentText(WebFrame* frame)
{
    // We use the document element's text instead of the body text here because
    // not all documents have a body, such as XML documents.
    WebElement documentElement = frame->document().documentElement();
    if (documentElement.isNull())
        return string();
    return documentElement.innerText().utf8();
}

static string dumpFramesAsText(WebFrame* frame, bool recursive)
{
    string result;

    // Add header for all but the main frame. Skip empty frames.
    if (frame->parent() && !frame->document().documentElement().isNull()) {
        result.append("\n--------\nFrame: '");
        result.append(frame->name().utf8().data());
        result.append("'\n--------\n");
    }

    result.append(dumpDocumentText(frame));
    result.append("\n");

    if (recursive) {
        for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling())
            result.append(dumpFramesAsText(child, recursive));
    }

    return result;
}

383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
static string dumpFramesAsPrintedText(WebFrame* frame, bool recursive)
{
    string result;

    // Cannot do printed format for anything other than HTML
    if (!frame->document().isHTMLDocument())
        return string();

    // Add header for all but the main frame. Skip empty frames.
    if (frame->parent() && !frame->document().documentElement().isNull()) {
        result.append("\n--------\nFrame: '");
        result.append(frame->name().utf8().data());
        result.append("'\n--------\n");
    }

    result.append(frame->renderTreeAsText(WebFrame::RenderAsTextPrinting).utf8());
    result.append("\n");

    if (recursive) {
        for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling())
            result.append(dumpFramesAsPrintedText(child, recursive));
    }

    return result;
}

409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
static void dumpFrameScrollPosition(WebFrame* frame, bool recursive)
{
    WebSize offset = frame->scrollOffset();
    if (offset.width > 0 || offset.height > 0) {
        if (frame->parent())
            printf("frame '%s' ", frame->name().utf8().data());
        printf("scrolled to %d,%d\n", offset.width, offset.height);
    }

    if (!recursive)
        return;
    for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling())
        dumpFrameScrollPosition(child, recursive);
}

struct ToLower {
    char16 operator()(char16 c) { return tolower(c); }
};

// FIXME: Eliminate std::transform(), std::vector, and std::sort().

// Returns True if item1 < item2.
static bool HistoryItemCompareLess(const WebHistoryItem& item1, const WebHistoryItem& item2)
{
    string16 target1 = item1.target();
    string16 target2 = item2.target();
    std::transform(target1.begin(), target1.end(), target1.begin(), ToLower());
    std::transform(target2.begin(), target2.end(), target2.begin(), ToLower());
    return target1 < target2;
}

440 441 442 443 444 445 446 447 448
static string normalizeLayoutTestURLInternal(const string& url)
{
    string result = url;
    size_t pos;
    if (!url.find(fileUrlPattern) && ((pos = url.find(layoutTestsPattern)) != string::npos)) {
        // adjust file URLs to match upstream results.
        result.replace(0, pos + layoutTestsPatternSize, fileTestPrefix);
    } else if (!url.find(dataUrlPattern)) {
        // URL-escape data URLs to match results upstream.
449
        string path = url.substr(dataUrlPatternSize);
450 451 452 453 454
        result.replace(dataUrlPatternSize, url.length(), path);
    }
    return result;
}

455 456 457 458 459 460 461
static string dumpHistoryItem(const WebHistoryItem& item, int indent, bool isCurrent)
{
    string result;

    if (isCurrent) {
        result.append("curr->");
        result.append(indent - 6, ' '); // 6 == "curr->".length()
462
    } else
463 464
        result.append(indent, ' ');

465
    string url = normalizeLayoutTestURLInternal(item.urlString().utf8());
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
    result.append(url);
    if (!item.target().isEmpty()) {
        result.append(" (in frame \"");
        result.append(item.target().utf8());
        result.append("\")");
    }
    if (item.isTargetItem())
        result.append("  **nav target**");
    result.append("\n");

    const WebVector<WebHistoryItem>& children = item.children();
    if (!children.isEmpty()) {
        // Must sort to eliminate arbitrary result ordering which defeats
        // reproducible testing.
        // FIXME: WebVector should probably just be a std::vector!!
        std::vector<WebHistoryItem> sortedChildren;
        for (size_t i = 0; i < children.size(); ++i)
            sortedChildren.push_back(children[i]);
        std::sort(sortedChildren.begin(), sortedChildren.end(), HistoryItemCompareLess);
        for (size_t i = 0; i < sortedChildren.size(); ++i)
            result += dumpHistoryItem(sortedChildren[i], indent + 4, false);
    }

    return result;
}

static void dumpBackForwardList(const TestNavigationController& navigationController, string& result)
{
    result.append("\n============== Back Forward List ==============\n");
    for (int index = 0; index < navigationController.entryCount(); ++index) {
        int currentIndex = navigationController.lastCommittedEntryIndex();
        WebHistoryItem historyItem = navigationController.entryAtIndex(index)->contentState();
        if (historyItem.isNull()) {
            historyItem.initialize();
            historyItem.setURLString(navigationController.entryAtIndex(index)->URL().spec().utf16());
        }
        result.append(dumpHistoryItem(historyItem, 8, index == currentIndex));
    }
    result.append("===============================================\n");
}

string TestShell::dumpAllBackForwardLists()
{
    string result;
    for (unsigned i = 0; i < m_windowList.size(); ++i)
        dumpBackForwardList(*m_windowList[i]->navigationController(), result);
    return result;
}

void TestShell::dump()
{
    WebScriptController::flushConsoleMessages();

    // Dump the requested representation.
    WebFrame* frame = m_webView->mainFrame();
    if (!frame)
        return;
    bool shouldDumpAsText = m_layoutTestController->shouldDumpAsText();
524
    bool shouldDumpAsAudio = m_layoutTestController->shouldDumpAsAudio();
525
    bool shouldGeneratePixelResults = m_layoutTestController->shouldGeneratePixelResults();
526
    bool shouldDumpAsPrinted = m_layoutTestController->isPrinting();
527
    bool dumpedAnything = false;
528 529 530

    if (shouldDumpAsAudio) {
        m_printer->handleAudioHeader();
531

532 533
        const WebKit::WebArrayBufferView& webArrayBufferView = m_layoutTestController->audioData();
        printf("Content-Length: %d\n", webArrayBufferView.byteLength());
534

535
        if (fwrite(webArrayBufferView.baseAddress(), 1, webArrayBufferView.byteLength(), stdout) != webArrayBufferView.byteLength())
536 537
            FATAL("Short write to stdout, disk full?\n");
        printf("\n");
538

539
        m_printer->handleTestFooter(true);
540

541 542 543 544 545
        fflush(stdout);
        fflush(stderr);
        return;
    }

546 547
    if (m_params.dumpTree) {
        dumpedAnything = true;
548
        m_printer->handleTextHeader();
549 550 551 552 553
        // Text output: the test page can request different types of output
        // which we handle here.
        if (!shouldDumpAsText) {
            // Plain text pages should be dumped as text
            string mimeType = frame->dataSource()->response().mimeType().utf8();
554 555 556 557
            if (mimeType == "text/plain") {
                shouldDumpAsText = true;
                shouldGeneratePixelResults = false;
            }
558 559 560
        }
        if (shouldDumpAsText) {
            bool recursive = m_layoutTestController->shouldDumpChildFramesAsText();
561
            string dataUtf8 = shouldDumpAsPrinted ? dumpFramesAsPrintedText(frame, recursive) : dumpFramesAsText(frame, recursive);
562 563 564
            if (fwrite(dataUtf8.c_str(), 1, dataUtf8.size(), stdout) != dataUtf8.size())
                FATAL("Short write to stdout, disk full?\n");
        } else {
565 566 567 568 569 570
          WebFrame::RenderAsTextControls renderTextBehavior = WebFrame::RenderAsTextNormal;
            if (shouldDumpAsPrinted)
                renderTextBehavior |= WebFrame::RenderAsTextPrinting;
            if (m_params.debugRenderTree)
                renderTextBehavior |= WebFrame::RenderAsTextDebug;
            printf("%s", frame->renderTreeAsText(renderTextBehavior).utf8().data());
571 572 573 574 575 576 577
            bool recursive = m_layoutTestController->shouldDumpChildFrameScrollPositions();
            dumpFrameScrollPosition(frame, recursive);
        }
        if (m_layoutTestController->shouldDumpBackForwardList())
            printf("%s", dumpAllBackForwardLists().c_str());
    }
    if (dumpedAnything && m_params.printSeparators)
578
        m_printer->handleTextFooter();
579

580
    if (m_params.dumpPixels && shouldGeneratePixelResults) {
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
        // Image output: we write the image data to the file given on the
        // command line (for the dump pixels argument), and the MD5 sum to
        // stdout.
        dumpedAnything = true;
        m_webView->layout();
        if (m_layoutTestController->testRepaint()) {
            WebSize viewSize = m_webView->size();
            int width = viewSize.width;
            int height = viewSize.height;
            if (m_layoutTestController->sweepHorizontally()) {
                for (WebRect column(0, 0, 1, height); column.x < width; column.x++)
                    m_webViewHost->paintRect(column);
            } else {
                for (WebRect line(0, 0, width, 1); line.y < height; line.y++)
                    m_webViewHost->paintRect(line);
            }
597 598 599
        } else if (m_layoutTestController->isPrinting())
            m_webViewHost->paintPagesWithBoundaries();
        else
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
            m_webViewHost->paintInvalidatedRegion();

        // See if we need to draw the selection bounds rect. Selection bounds
        // rect is the rect enclosing the (possibly transformed) selection.
        // The rect should be drawn after everything is laid out and painted.
        if (m_layoutTestController->shouldDumpSelectionRect()) {
            // If there is a selection rect - draw a red 1px border enclosing rect
            WebRect wr = frame->selectionBoundsRect();
            if (!wr.isEmpty()) {
                // Render a red rectangle bounding selection rect
                SkPaint paint;
                paint.setColor(0xFFFF0000); // Fully opaque red
                paint.setStyle(SkPaint::kStroke_Style);
                paint.setFlags(SkPaint::kAntiAlias_Flag);
                paint.setStrokeWidth(1.0f);
                SkIRect rect; // Bounding rect
                rect.set(wr.x, wr.y, wr.x + wr.width, wr.y + wr.height);
                m_webViewHost->canvas()->drawIRect(rect, paint);
            }
        }

621
        dumpImage(m_webViewHost->canvas());
622
    }
623 624
    m_printer->handleImageFooter();
    m_printer->handleTestFooter(dumpedAnything);
625 626 627 628
    fflush(stdout);
    fflush(stderr);
}

629
void TestShell::dumpImage(SkCanvas* canvas) const
630 631 632 633 634
{
    // Fix the alpha. The expected PNGs on Mac have an alpha channel, so we want
    // to keep it. On Windows, the alpha channel is wrong since text/form control
    // drawing may have erased it in a few places. So on Windows we force it to
    // opaque and also don't write the alpha channel for the reference. Linux
635 636
    // doesn't have the wrong alpha like Windows, but we match Windows.
#if OS(MAC_OS_X)
637
    bool discardTransparency = false;
638
#else
639
    bool discardTransparency = true;
640
    makeCanvasOpaque(canvas);
641 642
#endif

643 644 645
    const SkBitmap& sourceBitmap = canvas->getTopDevice()->accessBitmap(false);
    SkAutoLockPixels sourceBitmapLock(sourceBitmap);

646
    // Compute MD5 sum.
647 648 649 650 651 652 653 654 655 656 657 658
    MD5 digester;
    Vector<uint8_t, 16> digestValue;
    digester.addBytes(reinterpret_cast<const uint8_t*>(sourceBitmap.getPixels()), sourceBitmap.getSize());
    digester.checksum(digestValue);
    string md5hash;
    md5hash.reserve(16 * 2);
    for (unsigned i = 0; i < 16; ++i) {
        char hex[3];
        // Use "x", not "X". The string must be lowercased.
        sprintf(hex, "%02x", digestValue[i]);
        md5hash.append(hex);
    }
659

660 661
    // Only encode and dump the png if the hashes don't match. Encoding the
    // image is really expensive.
662
    if (md5hash.compare(m_params.pixelHash)) {
663
        std::vector<unsigned char> png;
664 665
        webkit_support::EncodeBGRAPNGWithChecksum(reinterpret_cast<const unsigned char*>(sourceBitmap.getPixels()), sourceBitmap.width(),
            sourceBitmap.height(), static_cast<int>(sourceBitmap.rowBytes()), discardTransparency, md5hash, &png);
666

667 668 669
        m_printer->handleImage(md5hash.c_str(), m_params.pixelHash.c_str(), &png[0], png.size(), m_params.pixelFileName.c_str());
    } else
        m_printer->handleImage(md5hash.c_str(), m_params.pixelHash.c_str(), 0, 0, m_params.pixelFileName.c_str());
670 671 672 673
}

void TestShell::bindJSObjectsToWindow(WebFrame* frame)
{
674
    WebTestingSupport::injectInternalsObject(frame);
675 676 677 678 679 680 681
    m_accessibilityController->bindToJavascript(frame, WebString::fromUTF8("accessibilityController"));
    m_layoutTestController->bindToJavascript(frame, WebString::fromUTF8("layoutTestController"));
    m_eventSender->bindToJavascript(frame, WebString::fromUTF8("eventSender"));
    m_plainTextController->bindToJavascript(frame, WebString::fromUTF8("plainText"));
    m_textInputController->bindToJavascript(frame, WebString::fromUTF8("textInputController"));
}

682
WebViewHost* TestShell::createNewWindow(const WebKit::WebURL& url)
683
{
684
    return createNewWindow(url, 0);
685 686
}

687
WebViewHost* TestShell::createNewWindow(const WebKit::WebURL& url, DRTDevToolsAgent* devToolsAgent)
688 689
{
    WebViewHost* host = new WebViewHost(this);
690
    WebView* view = WebView::create(host);
691
    view->setPermissionClient(webPermissions());
692
    view->setDevToolsAgentClient(devToolsAgent);
693
    host->setWebWidget(view);
694
    m_prefs.applyTo(view);
695 696 697 698 699 700 701 702 703 704 705 706 707 708
    view->initializeMainFrame(host);
    m_windowList.append(host);
    host->loadURLForFrame(url, WebString());
    return host;
}

void TestShell::closeWindow(WebViewHost* window)
{
    size_t i = m_windowList.find(window);
    if (i == notFound) {
        ASSERT_NOT_REACHED();
        return;
    }
    m_windowList.remove(i);
709
    WebWidget* focusedWidget = m_focusedWidget;
710
    if (window->webWidget() == m_focusedWidget)
711 712
        focusedWidget = 0;

713
    delete window;
714 715 716 717 718 719 720
    // We set the focused widget after deleting the web view host because it
    // can change the focus.
    m_focusedWidget = focusedWidget;
    if (m_focusedWidget) {
        webView()->setIsActive(true);
        m_focusedWidget->setFocus(true);
    }
721 722 723 724
}

void TestShell::closeRemainingWindows()
{
725 726 727
    // Just close devTools window manually because we have custom deinitialization code for it.
    closeDevTools();

728
    // Iterate through the window list and close everything except the main
729
    // window. We don't want to delete elements as we're iterating, so we copy
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
    // to a temp vector first.
    Vector<WebViewHost*> windowsToDelete;
    for (unsigned i = 0; i < m_windowList.size(); ++i) {
        if (m_windowList[i] != webViewHost())
            windowsToDelete.append(m_windowList[i]);
    }
    ASSERT(windowsToDelete.size() + 1 == m_windowList.size());
    for (unsigned i = 0; i < windowsToDelete.size(); ++i)
        closeWindow(windowsToDelete[i]);
    ASSERT(m_windowList.size() == 1);
}

int TestShell::windowCount()
{
    return m_windowList.size();
}
746 747 748 749 750

string TestShell::normalizeLayoutTestURL(const string& url)
{
    return normalizeLayoutTestURLInternal(url);
}