1ecf29663eb65d86f83982db4a215639274d9d9c
[WebKit-https.git] / Tools / DumpRenderTree / chromium / TestShell.cpp
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "TestShell.h"
33
34 #include "DRTDevToolsAgent.h"
35 #include "DRTDevToolsClient.h"
36 #include "LayoutTestController.h"
37 #include "MockWebPrerenderingSupport.h"
38 #include "platform/WebArrayBufferView.h"
39 #include "WebCompositor.h"
40 #include "WebDataSource.h"
41 #include "WebDocument.h"
42 #include "WebElement.h"
43 #include "WebFrame.h"
44 #include "WebHistoryItem.h"
45 #include "WebIDBFactory.h"
46 #include "WebTestingSupport.h"
47 #include "platform/WebThread.h"
48 #include "WebKit.h"
49 #include "platform/WebKitPlatformSupport.h"
50 #include "WebPermissions.h"
51 #include "platform/WebPoint.h"
52 #include "WebRuntimeFeatures.h"
53 #include "WebScriptController.h"
54 #include "WebSettings.h"
55 #include "platform/WebSize.h"
56 #include "platform/WebString.h"
57 #include "platform/WebURLRequest.h"
58 #include "platform/WebURLResponse.h"
59 #include "WebView.h"
60 #include "WebViewHost.h"
61 #include "skia/ext/platform_canvas.h"
62 #include "webkit/support/webkit_support.h"
63 #include "webkit/support/webkit_support_gfx.h"
64 #include <algorithm>
65 #include <cctype>
66 #include <vector>
67 #include <wtf/MD5.h>
68 #include <wtf/OwnArrayPtr.h>
69
70
71 using namespace WebKit;
72 using namespace std;
73
74 // Content area size for newly created windows.
75 static const int testWindowWidth = 800;
76 static const int testWindowHeight = 600;
77
78 // The W3C SVG layout tests use a different size than the other layout tests.
79 static const int SVGTestWindowWidth = 480;
80 static const int SVGTestWindowHeight = 360;
81
82 static const char layoutTestsPattern[] = "/LayoutTests/";
83 static const string::size_type layoutTestsPatternSize = sizeof(layoutTestsPattern) - 1;
84 static const char fileUrlPattern[] = "file:/";
85 static const char fileTestPrefix[] = "(file test):";
86 static const char dataUrlPattern[] = "data:";
87 static const string::size_type dataUrlPatternSize = sizeof(dataUrlPattern) - 1;
88
89 // FIXME: Move this to a common place so that it can be shared with
90 // WebCore::TransparencyWin::makeLayerOpaque().
91 static void makeCanvasOpaque(SkCanvas* canvas)
92 {
93     const SkBitmap& bitmap = canvas->getTopDevice()->accessBitmap(true);
94     ASSERT(bitmap.config() == SkBitmap::kARGB_8888_Config);
95
96     SkAutoLockPixels lock(bitmap);
97     for (int y = 0; y < bitmap.height(); y++) {
98         uint32_t* row = bitmap.getAddr32(0, y);
99         for (int x = 0; x < bitmap.width(); x++)
100             row[x] |= 0xFF000000; // Set alpha bits to 1.
101     }
102 }
103
104 TestShell::TestShell()
105     : m_testIsPending(false)
106     , m_testIsPreparing(false)
107     , m_focusedWidget(0)
108     , m_testShellMode(false)
109     , m_devTools(0)
110     , m_allowExternalPages(false)
111     , m_acceleratedCompositingForVideoEnabled(false)
112     , m_threadedCompositingEnabled(false)
113     , m_forceCompositingMode(false)
114     , m_accelerated2dCanvasEnabled(false)
115     , m_deferred2dCanvasEnabled(false)
116     , m_acceleratedPaintingEnabled(false)
117     , m_stressOpt(false)
118     , m_stressDeopt(false)
119     , m_dumpWhenFinished(true)
120     , m_isDisplayingModalDialog(false)
121 {
122     WebRuntimeFeatures::enableDataTransferItems(true);
123     WebRuntimeFeatures::enableGeolocation(true);
124     WebRuntimeFeatures::enablePointerLock(true);
125     WebRuntimeFeatures::enableIndexedDatabase(true);
126     WebRuntimeFeatures::enableFileSystem(true);
127     WebRuntimeFeatures::enableJavaScriptI18NAPI(true);
128     WebRuntimeFeatures::enableMediaSource(true);
129     WebRuntimeFeatures::enableEncryptedMedia(true);
130     WebRuntimeFeatures::enableMediaStream(true);
131     WebRuntimeFeatures::enablePeerConnection(true);
132     WebRuntimeFeatures::enableWebAudio(true);
133     WebRuntimeFeatures::enableVideoTrack(true);
134     WebRuntimeFeatures::enableGamepad(true);
135     WebRuntimeFeatures::enableShadowDOM(true);
136     WebRuntimeFeatures::enableStyleScoped(true);
137     WebRuntimeFeatures::enableScriptedSpeech(true);
138
139     // 30 second is the same as the value in Mac DRT.
140     // If we use a value smaller than the timeout value of
141     // (new-)run-webkit-tests, (new-)run-webkit-tests misunderstands that a
142     // timed-out DRT process was crashed.
143     m_timeout = 30 * 1000;
144 }
145
146 void TestShell::initialize()
147 {
148     m_webPermissions = adoptPtr(new WebPermissions(this));
149     m_accessibilityController = adoptPtr(new AccessibilityController(this));
150     m_gamepadController = adoptPtr(new GamepadController());
151
152     m_layoutTestController = adoptPtr(new LayoutTestController(this));
153     m_eventSender = adoptPtr(new EventSender(this));
154     m_textInputController = adoptPtr(new TextInputController(this));
155 #if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
156     m_notificationPresenter = adoptPtr(new NotificationPresenter(this));
157 #endif
158     m_printer = m_testShellMode ? TestEventPrinter::createTestShellPrinter() : TestEventPrinter::createDRTPrinter();
159 #if ENABLE(LINK_PRERENDER)
160     m_prerenderingSupport = adoptPtr(new MockWebPrerenderingSupport());
161 #endif
162
163     WTF::initializeThreading();
164
165     if (m_threadedCompositingEnabled) {
166         m_webCompositorThread = adoptPtr(WebKit::webKitPlatformSupport()->createThread("Compositor"));
167         WebCompositor::initialize(m_webCompositorThread.get());
168     } else
169         WebCompositor::initialize(0);
170
171     createMainWindow();
172 }
173
174 void TestShell::createMainWindow()
175 {
176     m_drtDevToolsAgent = adoptPtr(new DRTDevToolsAgent);
177     m_webViewHost = adoptPtr(createNewWindow(WebURL(), m_drtDevToolsAgent.get()));
178     m_webView = m_webViewHost->webView();
179     m_drtDevToolsAgent->setWebView(m_webView);
180 }
181
182 TestShell::~TestShell()
183 {
184     // Note: DevTools are closed together with all the other windows in the
185     // windows list.
186
187     // Destroy the WebView before its WebViewHost.
188     m_drtDevToolsAgent->setWebView(0);
189 }
190
191 void TestShell::createDRTDevToolsClient(DRTDevToolsAgent* agent)
192 {
193     m_drtDevToolsClient = adoptPtr(new DRTDevToolsClient(agent, m_devTools->webView()));
194 }
195
196 void TestShell::showDevTools()
197 {
198     if (!m_devTools) {
199         WebURL url = webkit_support::GetDevToolsPathAsURL();
200         if (!url.isValid()) {
201             ASSERT(false);
202             return;
203         }
204         m_devTools = createNewWindow(url);
205         m_devTools->webView()->settings()->setMemoryInfoEnabled(true);
206         m_devTools->setLogConsoleOutput(false);
207         ASSERT(m_devTools);
208         createDRTDevToolsClient(m_drtDevToolsAgent.get());
209     }
210     m_devTools->show(WebKit::WebNavigationPolicyNewWindow);
211 }
212
213 void TestShell::closeDevTools()
214 {
215     if (m_devTools) {
216         m_devTools->webView()->settings()->setMemoryInfoEnabled(false);
217         m_drtDevToolsAgent->reset();
218         m_drtDevToolsClient.clear();
219         closeWindow(m_devTools);
220         m_devTools = 0;
221     }
222 }
223
224 void TestShell::resetWebSettings(WebView& webView)
225 {
226     m_prefs.reset();
227     m_prefs.acceleratedCompositingEnabled = true;
228     m_prefs.acceleratedCompositingForVideoEnabled = m_acceleratedCompositingForVideoEnabled;
229     m_prefs.forceCompositingMode = m_forceCompositingMode;
230     m_prefs.accelerated2dCanvasEnabled = m_accelerated2dCanvasEnabled;
231     m_prefs.deferred2dCanvasEnabled = m_deferred2dCanvasEnabled;
232     m_prefs.acceleratedPaintingEnabled = m_acceleratedPaintingEnabled;
233     m_prefs.applyTo(&webView);
234 }
235
236 void TestShell::runFileTest(const TestParams& params)
237 {
238     ASSERT(params.testUrl.isValid());
239     m_testIsPreparing = true;
240     m_params = params;
241     string testUrl = m_params.testUrl.spec();
242
243     if (testUrl.find("loading/") != string::npos
244         || testUrl.find("loading\\") != string::npos)
245         m_layoutTestController->setShouldDumpFrameLoadCallbacks(true);
246
247     if (testUrl.find("compositing/") != string::npos || testUrl.find("compositing\\") != string::npos) {
248         m_prefs.acceleratedCompositingForVideoEnabled = true;
249         m_prefs.accelerated2dCanvasEnabled = true;
250         m_prefs.deferred2dCanvasEnabled = true;
251         m_prefs.mockScrollbarsEnabled = true;
252         m_prefs.applyTo(m_webView);
253     }
254
255     if (testUrl.find("/dumpAsText/") != string::npos
256         || testUrl.find("\\dumpAsText\\") != string::npos) {
257         m_layoutTestController->setShouldDumpAsText(true);
258         m_layoutTestController->setShouldGeneratePixelResults(false);
259     }
260
261     if (testUrl.find("/inspector/") != string::npos
262         || testUrl.find("\\inspector\\") != string::npos)
263         showDevTools();
264
265     if (m_params.debugLayerTree)
266         m_layoutTestController->setShowDebugLayerTree(true);
267
268     if (m_dumpWhenFinished)
269         m_printer->handleTestHeader(testUrl.c_str());
270     loadURL(m_params.testUrl);
271
272     m_testIsPreparing = false;
273     waitTestFinished();
274 }
275
276 static inline bool isSVGTestURL(const WebURL& url)
277 {
278     return url.isValid() && string(url.spec()).find("W3C-SVG-1.1") != string::npos;
279 }
280
281 void TestShell::resizeWindowForTest(WebViewHost* window, const WebURL& url)
282 {
283     int width, height;
284     if (isSVGTestURL(url)) {
285         width = SVGTestWindowWidth;
286         height = SVGTestWindowHeight;
287     } else {
288         width = testWindowWidth;
289         height = testWindowHeight;
290     }
291     window->setWindowRect(WebRect(0, 0, width + virtualWindowBorder * 2, height + virtualWindowBorder * 2));
292 }
293
294 void TestShell::resetTestController()
295 {
296     resetWebSettings(*webView());
297     m_webPermissions->reset();
298     m_accessibilityController->reset();
299     m_gamepadController->reset();
300     m_layoutTestController->reset();
301     m_eventSender->reset();
302     m_webViewHost->reset();
303 #if ENABLE(NOTIFICATIONS) || ENABLE(LEGACY_NOTIFICATIONS)
304     m_notificationPresenter->reset();
305 #endif
306 #if OS(ANDROID)
307     webkit_support::ReleaseMediaResources();
308 #endif
309     m_drtDevToolsAgent->reset();
310     if (m_drtDevToolsClient)
311         m_drtDevToolsClient->reset();
312     webView()->setPageScaleFactor(1, WebPoint(0, 0));
313     webView()->enableFixedLayoutMode(false);
314     webView()->setFixedLayoutSize(WebSize(0, 0));
315     webView()->mainFrame()->clearOpener();
316     WebTestingSupport::resetInternalsObject(webView()->mainFrame());
317 }
318
319 void TestShell::loadURL(const WebURL& url)
320 {
321     m_webViewHost->loadURLForFrame(url, WebString());
322 }
323
324 void TestShell::reload()
325 {
326     m_webViewHost->navigationController()->reload();
327 }
328
329 void TestShell::goToOffset(int offset)
330 {
331      m_webViewHost->navigationController()->goToOffset(offset);
332 }
333
334 int TestShell::navigationEntryCount() const
335 {
336     return m_webViewHost->navigationController()->entryCount();
337 }
338
339 void TestShell::callJSGC()
340 {
341     m_webView->mainFrame()->collectGarbage();
342 }
343
344 void TestShell::setFocus(WebWidget* widget, bool enable)
345 {
346     // Simulate the effects of InteractiveSetFocus(), which includes calling
347     // both setFocus() and setIsActive().
348     if (enable) {
349         if (m_focusedWidget != widget) {
350             if (m_focusedWidget)
351                 m_focusedWidget->setFocus(false);
352             webView()->setIsActive(enable);
353             widget->setFocus(enable);
354             m_focusedWidget = widget;
355         }
356     } else {
357         if (m_focusedWidget == widget) {
358             widget->setFocus(enable);
359             webView()->setIsActive(enable);
360             m_focusedWidget = 0;
361         }
362     }
363 }
364
365 void TestShell::testFinished()
366 {
367     if (!m_testIsPending)
368         return;
369     m_testIsPending = false;
370     if (m_dumpWhenFinished)
371         dump();
372     webkit_support::QuitMessageLoop();
373 }
374
375 void TestShell::testTimedOut()
376 {
377     m_printer->handleTimedOut();
378     testFinished();
379 }
380
381 void TestShell::setPerTilePaintingEnabled(bool enabled)
382 {
383     WebCompositor::setPerTilePaintingEnabled(enabled);
384 }
385
386 static string dumpDocumentText(WebFrame* frame)
387 {
388     // We use the document element's text instead of the body text here because
389     // not all documents have a body, such as XML documents.
390     WebElement documentElement = frame->document().documentElement();
391     if (documentElement.isNull())
392         return string();
393     return documentElement.innerText().utf8();
394 }
395
396 static string dumpFramesAsText(WebFrame* frame, bool recursive)
397 {
398     string result;
399
400     // Add header for all but the main frame. Skip empty frames.
401     if (frame->parent() && !frame->document().documentElement().isNull()) {
402         result.append("\n--------\nFrame: '");
403         result.append(frame->name().utf8().data());
404         result.append("'\n--------\n");
405     }
406
407     result.append(dumpDocumentText(frame));
408     result.append("\n");
409
410     if (recursive) {
411         for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling())
412             result.append(dumpFramesAsText(child, recursive));
413     }
414
415     return result;
416 }
417
418 static string dumpFramesAsPrintedText(WebFrame* frame, bool recursive)
419 {
420     string result;
421
422     // Cannot do printed format for anything other than HTML
423     if (!frame->document().isHTMLDocument())
424         return string();
425
426     // Add header for all but the main frame. Skip empty frames.
427     if (frame->parent() && !frame->document().documentElement().isNull()) {
428         result.append("\n--------\nFrame: '");
429         result.append(frame->name().utf8().data());
430         result.append("'\n--------\n");
431     }
432
433     result.append(frame->renderTreeAsText(WebFrame::RenderAsTextPrinting).utf8());
434     result.append("\n");
435
436     if (recursive) {
437         for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling())
438             result.append(dumpFramesAsPrintedText(child, recursive));
439     }
440
441     return result;
442 }
443
444 static void dumpFrameScrollPosition(WebFrame* frame, bool recursive)
445 {
446     WebSize offset = frame->scrollOffset();
447     if (offset.width > 0 || offset.height > 0) {
448         if (frame->parent())
449             printf("frame '%s' ", frame->name().utf8().data());
450         printf("scrolled to %d,%d\n", offset.width, offset.height);
451     }
452
453     if (!recursive)
454         return;
455     for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling())
456         dumpFrameScrollPosition(child, recursive);
457 }
458
459 struct ToLower {
460     char16 operator()(char16 c) { return tolower(c); }
461 };
462
463 // FIXME: Eliminate std::transform(), std::vector, and std::sort().
464
465 // Returns True if item1 < item2.
466 static bool HistoryItemCompareLess(const WebHistoryItem& item1, const WebHistoryItem& item2)
467 {
468     string16 target1 = item1.target();
469     string16 target2 = item2.target();
470     std::transform(target1.begin(), target1.end(), target1.begin(), ToLower());
471     std::transform(target2.begin(), target2.end(), target2.begin(), ToLower());
472     return target1 < target2;
473 }
474
475 static string normalizeLayoutTestURLInternal(const string& url)
476 {
477     string result = url;
478     size_t pos;
479     if (!url.find(fileUrlPattern) && ((pos = url.find(layoutTestsPattern)) != string::npos)) {
480         // adjust file URLs to match upstream results.
481         result.replace(0, pos + layoutTestsPatternSize, fileTestPrefix);
482     } else if (!url.find(dataUrlPattern)) {
483         // URL-escape data URLs to match results upstream.
484         string path = url.substr(dataUrlPatternSize);
485         result.replace(dataUrlPatternSize, url.length(), path);
486     }
487     return result;
488 }
489
490 static string dumpHistoryItem(const WebHistoryItem& item, int indent, bool isCurrent)
491 {
492     string result;
493
494     if (isCurrent) {
495         result.append("curr->");
496         result.append(indent - 6, ' '); // 6 == "curr->".length()
497     } else
498         result.append(indent, ' ');
499
500     string url = normalizeLayoutTestURLInternal(item.urlString().utf8());
501     result.append(url);
502     if (!item.target().isEmpty()) {
503         result.append(" (in frame \"");
504         result.append(item.target().utf8());
505         result.append("\")");
506     }
507     if (item.isTargetItem())
508         result.append("  **nav target**");
509     result.append("\n");
510
511     const WebVector<WebHistoryItem>& children = item.children();
512     if (!children.isEmpty()) {
513         // Must sort to eliminate arbitrary result ordering which defeats
514         // reproducible testing.
515         // FIXME: WebVector should probably just be a std::vector!!
516         std::vector<WebHistoryItem> sortedChildren;
517         for (size_t i = 0; i < children.size(); ++i)
518             sortedChildren.push_back(children[i]);
519         std::sort(sortedChildren.begin(), sortedChildren.end(), HistoryItemCompareLess);
520         for (size_t i = 0; i < sortedChildren.size(); ++i)
521             result += dumpHistoryItem(sortedChildren[i], indent + 4, false);
522     }
523
524     return result;
525 }
526
527 static void dumpBackForwardList(const TestNavigationController& navigationController, string& result)
528 {
529     result.append("\n============== Back Forward List ==============\n");
530     for (int index = 0; index < navigationController.entryCount(); ++index) {
531         int currentIndex = navigationController.lastCommittedEntryIndex();
532         WebHistoryItem historyItem = navigationController.entryAtIndex(index)->contentState();
533         if (historyItem.isNull()) {
534             historyItem.initialize();
535             historyItem.setURLString(navigationController.entryAtIndex(index)->URL().spec().utf16());
536         }
537         result.append(dumpHistoryItem(historyItem, 8, index == currentIndex));
538     }
539     result.append("===============================================\n");
540 }
541
542 string TestShell::dumpAllBackForwardLists()
543 {
544     string result;
545     for (unsigned i = 0; i < m_windowList.size(); ++i)
546         dumpBackForwardList(*m_windowList[i]->navigationController(), result);
547     return result;
548 }
549
550 void TestShell::dump()
551 {
552     WebScriptController::flushConsoleMessages();
553
554     // Dump the requested representation.
555     WebFrame* frame = m_webView->mainFrame();
556     if (!frame)
557         return;
558     bool shouldDumpAsText = m_layoutTestController->shouldDumpAsText();
559     bool shouldDumpAsAudio = m_layoutTestController->shouldDumpAsAudio();
560     bool shouldGeneratePixelResults = m_layoutTestController->shouldGeneratePixelResults();
561     bool shouldDumpAsPrinted = m_layoutTestController->isPrinting();
562     bool dumpedAnything = false;
563
564     if (shouldDumpAsAudio) {
565         m_printer->handleAudioHeader();
566
567         const WebKit::WebArrayBufferView& webArrayBufferView = m_layoutTestController->audioData();
568         printf("Content-Length: %d\n", webArrayBufferView.byteLength());
569
570         if (fwrite(webArrayBufferView.baseAddress(), 1, webArrayBufferView.byteLength(), stdout) != webArrayBufferView.byteLength())
571             FATAL("Short write to stdout, disk full?\n");
572         m_printer->handleAudioFooter();
573         m_printer->handleTestFooter(true);
574
575         fflush(stdout);
576         fflush(stderr);
577         return;
578     }
579
580     if (m_params.dumpTree) {
581         dumpedAnything = true;
582         m_printer->handleTextHeader();
583         // Text output: the test page can request different types of output
584         // which we handle here.
585         if (!shouldDumpAsText) {
586             // Plain text pages should be dumped as text
587             string mimeType = frame->dataSource()->response().mimeType().utf8();
588             if (mimeType == "text/plain") {
589                 shouldDumpAsText = true;
590                 shouldGeneratePixelResults = false;
591             }
592         }
593         if (shouldDumpAsText) {
594             bool recursive = m_layoutTestController->shouldDumpChildFramesAsText();
595             string dataUtf8 = shouldDumpAsPrinted ? dumpFramesAsPrintedText(frame, recursive) : dumpFramesAsText(frame, recursive);
596             if (fwrite(dataUtf8.c_str(), 1, dataUtf8.size(), stdout) != dataUtf8.size())
597                 FATAL("Short write to stdout, disk full?\n");
598         } else {
599           WebFrame::RenderAsTextControls renderTextBehavior = WebFrame::RenderAsTextNormal;
600             if (shouldDumpAsPrinted)
601                 renderTextBehavior |= WebFrame::RenderAsTextPrinting;
602             if (m_params.debugRenderTree)
603                 renderTextBehavior |= WebFrame::RenderAsTextDebug;
604             printf("%s", frame->renderTreeAsText(renderTextBehavior).utf8().data());
605             bool recursive = m_layoutTestController->shouldDumpChildFrameScrollPositions();
606             dumpFrameScrollPosition(frame, recursive);
607         }
608         if (m_layoutTestController->shouldDumpBackForwardList())
609             printf("%s", dumpAllBackForwardLists().c_str());
610     }
611     if (dumpedAnything && m_params.printSeparators)
612         m_printer->handleTextFooter();
613
614     if (m_params.dumpPixels && shouldGeneratePixelResults) {
615         // Image output: we write the image data to the file given on the
616         // command line (for the dump pixels argument), and the MD5 sum to
617         // stdout.
618         dumpedAnything = true;
619         m_webView->layout();
620         if (m_layoutTestController->testRepaint()) {
621             WebSize viewSize = m_webView->size();
622             int width = viewSize.width;
623             int height = viewSize.height;
624             if (m_layoutTestController->sweepHorizontally()) {
625                 for (WebRect column(0, 0, 1, height); column.x < width; column.x++)
626                     m_webViewHost->paintRect(column);
627             } else {
628                 for (WebRect line(0, 0, width, 1); line.y < height; line.y++)
629                     m_webViewHost->paintRect(line);
630             }
631         } else if (m_layoutTestController->isPrinting())
632             m_webViewHost->paintPagesWithBoundaries();
633         else
634             m_webViewHost->paintInvalidatedRegion();
635
636         // See if we need to draw the selection bounds rect. Selection bounds
637         // rect is the rect enclosing the (possibly transformed) selection.
638         // The rect should be drawn after everything is laid out and painted.
639         if (m_layoutTestController->shouldDumpSelectionRect()) {
640             // If there is a selection rect - draw a red 1px border enclosing rect
641             WebRect wr = frame->selectionBoundsRect();
642             if (!wr.isEmpty()) {
643                 // Render a red rectangle bounding selection rect
644                 SkPaint paint;
645                 paint.setColor(0xFFFF0000); // Fully opaque red
646                 paint.setStyle(SkPaint::kStroke_Style);
647                 paint.setFlags(SkPaint::kAntiAlias_Flag);
648                 paint.setStrokeWidth(1.0f);
649                 SkIRect rect; // Bounding rect
650                 rect.set(wr.x, wr.y, wr.x + wr.width, wr.y + wr.height);
651                 m_webViewHost->canvas()->drawIRect(rect, paint);
652             }
653         }
654
655         dumpImage(m_webViewHost->canvas());
656     }
657     m_printer->handleTestFooter(dumpedAnything);
658     fflush(stdout);
659     fflush(stderr);
660 }
661
662 void TestShell::dumpImage(SkCanvas* canvas) const
663 {
664     // Fix the alpha. The expected PNGs on Mac have an alpha channel, so we want
665     // to keep it. On Windows, the alpha channel is wrong since text/form control
666     // drawing may have erased it in a few places. So on Windows we force it to
667     // opaque and also don't write the alpha channel for the reference. Linux
668     // doesn't have the wrong alpha like Windows, but we match Windows.
669 #if OS(MAC_OS_X)
670     bool discardTransparency = false;
671 #else
672     bool discardTransparency = true;
673     makeCanvasOpaque(canvas);
674 #endif
675
676     const SkBitmap& sourceBitmap = canvas->getTopDevice()->accessBitmap(false);
677     SkAutoLockPixels sourceBitmapLock(sourceBitmap);
678
679     // Compute MD5 sum.
680     MD5 digester;
681     Vector<uint8_t, 16> digestValue;
682 #if OS(ANDROID)
683     // On Android, pixel layout is RGBA (see third_party/skia/include/core/SkColorPriv.h);
684     // however, other Chrome platforms use BGRA (see skia/config/SkUserConfig.h).
685     // To match the checksum of other Chrome platforms, we need to reorder the layout of pixels.
686     // NOTE: The following code assumes we use SkBitmap::kARGB_8888_Config,
687     // which has been checked in device.makeOpaque() (see above).
688     const uint8_t* rawPixels = reinterpret_cast<const uint8_t*>(sourceBitmap.getPixels());
689     size_t bitmapSize = sourceBitmap.getSize();
690     OwnArrayPtr<uint8_t> reorderedPixels = adoptArrayPtr(new uint8_t[bitmapSize]);
691     for (size_t i = 0; i < bitmapSize; i += 4) {
692         reorderedPixels[i] = rawPixels[i + 2]; // R
693         reorderedPixels[i + 1] = rawPixels[i + 1]; // G
694         reorderedPixels[i + 2] = rawPixels[i]; // B
695         reorderedPixels[i + 3] = rawPixels[i + 3]; // A
696     }
697     digester.addBytes(reorderedPixels.get(), bitmapSize);
698     reorderedPixels.clear();
699 #else
700     digester.addBytes(reinterpret_cast<const uint8_t*>(sourceBitmap.getPixels()), sourceBitmap.getSize());
701 #endif
702     digester.checksum(digestValue);
703     string md5hash;
704     md5hash.reserve(16 * 2);
705     for (unsigned i = 0; i < 16; ++i) {
706         char hex[3];
707         // Use "x", not "X". The string must be lowercased.
708         sprintf(hex, "%02x", digestValue[i]);
709         md5hash.append(hex);
710     }
711
712     // Only encode and dump the png if the hashes don't match. Encoding the
713     // image is really expensive.
714     if (md5hash.compare(m_params.pixelHash)) {
715         std::vector<unsigned char> png;
716 #if OS(ANDROID)
717         webkit_support::EncodeRGBAPNGWithChecksum(reinterpret_cast<const unsigned char*>(sourceBitmap.getPixels()), sourceBitmap.width(),
718             sourceBitmap.height(), static_cast<int>(sourceBitmap.rowBytes()), discardTransparency, md5hash, &png);
719 #else
720         webkit_support::EncodeBGRAPNGWithChecksum(reinterpret_cast<const unsigned char*>(sourceBitmap.getPixels()), sourceBitmap.width(),
721             sourceBitmap.height(), static_cast<int>(sourceBitmap.rowBytes()), discardTransparency, md5hash, &png);
722 #endif
723
724         m_printer->handleImage(md5hash.c_str(), m_params.pixelHash.c_str(), &png[0], png.size(), m_params.pixelFileName.c_str());
725     } else
726         m_printer->handleImage(md5hash.c_str(), m_params.pixelHash.c_str(), 0, 0, m_params.pixelFileName.c_str());
727 }
728
729 void TestShell::bindJSObjectsToWindow(WebFrame* frame)
730 {
731     WebTestingSupport::injectInternalsObject(frame);
732     m_accessibilityController->bindToJavascript(frame, WebString::fromUTF8("accessibilityController"));
733     m_gamepadController->bindToJavascript(frame, WebString::fromUTF8("gamepadController"));
734     m_layoutTestController->bindToJavascript(frame, WebString::fromUTF8("layoutTestController"));
735     m_layoutTestController->bindToJavascript(frame, WebString::fromUTF8("testRunner"));
736     m_eventSender->bindToJavascript(frame, WebString::fromUTF8("eventSender"));
737     m_textInputController->bindToJavascript(frame, WebString::fromUTF8("textInputController"));
738 }
739
740 WebViewHost* TestShell::createNewWindow(const WebKit::WebURL& url)
741 {
742     return createNewWindow(url, 0);
743 }
744
745 WebViewHost* TestShell::createNewWindow(const WebKit::WebURL& url, DRTDevToolsAgent* devToolsAgent)
746 {
747     WebViewHost* host = new WebViewHost(this);
748     WebView* view = WebView::create(host);
749     view->setPermissionClient(webPermissions());
750     view->setDevToolsAgentClient(devToolsAgent);
751     host->setWebWidget(view);
752     m_prefs.applyTo(view);
753     view->initializeMainFrame(host);
754     m_windowList.append(host);
755     host->loadURLForFrame(url, WebString());
756     return host;
757 }
758
759 void TestShell::closeWindow(WebViewHost* window)
760 {
761     size_t i = m_windowList.find(window);
762     if (i == notFound) {
763         ASSERT_NOT_REACHED();
764         return;
765     }
766     m_windowList.remove(i);
767     WebWidget* focusedWidget = m_focusedWidget;
768     if (window->webWidget() == m_focusedWidget)
769         focusedWidget = 0;
770
771     delete window;
772     // We set the focused widget after deleting the web view host because it
773     // can change the focus.
774     m_focusedWidget = focusedWidget;
775     if (m_focusedWidget) {
776         webView()->setIsActive(true);
777         m_focusedWidget->setFocus(true);
778     }
779 }
780
781 void TestShell::closeRemainingWindows()
782 {
783     // Just close devTools window manually because we have custom deinitialization code for it.
784     closeDevTools();
785
786     // Iterate through the window list and close everything except the main
787     // window. We don't want to delete elements as we're iterating, so we copy
788     // to a temp vector first.
789     Vector<WebViewHost*> windowsToDelete;
790     for (unsigned i = 0; i < m_windowList.size(); ++i) {
791         if (m_windowList[i] != webViewHost())
792             windowsToDelete.append(m_windowList[i]);
793     }
794     ASSERT(windowsToDelete.size() + 1 == m_windowList.size());
795     for (unsigned i = 0; i < windowsToDelete.size(); ++i)
796         closeWindow(windowsToDelete[i]);
797     ASSERT(m_windowList.size() == 1);
798 }
799
800 int TestShell::windowCount()
801 {
802     return m_windowList.size();
803 }
804
805 string TestShell::normalizeLayoutTestURL(const string& url)
806 {
807     return normalizeLayoutTestURLInternal(url);
808 }