Rename WebKitTools to Tools
[WebKit-https.git] / Tools / DumpRenderTree / chromium / TestShell.cpp
1 /*
2  * Copyright (C) 2010 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 "WebDataSource.h"
38 #include "WebDocument.h"
39 #include "WebElement.h"
40 #include "WebFrame.h"
41 #include "WebHistoryItem.h"
42 #include "WebKit.h"
43 #include "WebRuntimeFeatures.h"
44 #include "WebScriptController.h"
45 #include "WebSettings.h"
46 #include "WebSize.h"
47 #include "WebSpeechInputControllerMock.h"
48 #include "WebString.h"
49 #include "WebURLRequest.h"
50 #include "WebURLResponse.h"
51 #include "WebView.h"
52 #include "WebViewHost.h"
53 #include "skia/ext/bitmap_platform_device.h"
54 #include "skia/ext/platform_canvas.h"
55 #include "webkit/support/webkit_support.h"
56 #include "webkit/support/webkit_support_gfx.h"
57 #include <algorithm>
58 #include <cctype>
59 #include <vector>
60 #include <wtf/MD5.h>
61
62 using namespace WebKit;
63 using namespace std;
64
65 // Content area size for newly created windows.
66 static const int testWindowWidth = 800;
67 static const int testWindowHeight = 600;
68
69 // The W3C SVG layout tests use a different size than the other layout tests.
70 static const int SVGTestWindowWidth = 480;
71 static const int SVGTestWindowHeight = 360;
72
73 static const char layoutTestsPattern[] = "/LayoutTests/";
74 static const string::size_type layoutTestsPatternSize = sizeof(layoutTestsPattern) - 1;
75 static const char fileUrlPattern[] = "file:/";
76 static const char fileTestPrefix[] = "(file test):";
77 static const char dataUrlPattern[] = "data:";
78 static const string::size_type dataUrlPatternSize = sizeof(dataUrlPattern) - 1;
79
80 TestShell::TestShell(bool testShellMode)
81     : m_testIsPending(false)
82     , m_testIsPreparing(false)
83     , m_focusedWidget(0)
84     , m_testShellMode(testShellMode)
85     , m_devTools(0)
86     , m_allowExternalPages(false)
87     , m_acceleratedCompositingEnabled(false)
88     , m_accelerated2dCanvasEnabled(false)
89     , m_loadCount(1)
90     , m_dumpWhenFinished(true)
91 {
92     WebRuntimeFeatures::enableGeolocation(true);
93     WebRuntimeFeatures::enableIndexedDatabase(true);
94     WebRuntimeFeatures::enableFileSystem(true);
95     m_accessibilityController.set(new AccessibilityController(this));
96     m_layoutTestController.set(new LayoutTestController(this));
97     m_eventSender.set(new EventSender(this));
98     m_plainTextController.set(new PlainTextController());
99     m_textInputController.set(new TextInputController(this));
100     m_notificationPresenter.set(new NotificationPresenter(this));
101     m_printer.set(m_testShellMode ? TestEventPrinter::createTestShellPrinter() : TestEventPrinter::createDRTPrinter());
102
103     // 30 second is the same as the value in Mac DRT.
104     // If we use a value smaller than the timeout value of
105     // (new-)run-webkit-tests, (new-)run-webkit-tests misunderstands that a
106     // timed-out DRT process was crashed.
107     m_timeout = 30 * 1000;
108
109     m_drtDevToolsAgent.set(new DRTDevToolsAgent);
110     m_webViewHost = createWebView();
111     m_webView = m_webViewHost->webView();
112     m_drtDevToolsAgent->setWebView(m_webView);
113 }
114
115 TestShell::~TestShell()
116 {
117     // Note: DevTools are closed together with all the other windows in the
118     // windows list.
119
120     // Destroy the WebView before its WebViewHost.
121     m_drtDevToolsAgent->setWebView(0);
122 }
123
124 void TestShell::createDRTDevToolsClient(DRTDevToolsAgent* agent)
125 {
126     m_drtDevToolsClient.set(new DRTDevToolsClient(agent, m_devTools->webView()));
127 }
128
129 void TestShell::showDevTools()
130 {
131     if (!m_devTools) {
132         WebURL url = webkit_support::GetDevToolsPathAsURL();
133         if (!url.isValid()) {
134             ASSERT(false);
135             return;
136         }
137         m_devTools = createNewWindow(url);
138         ASSERT(m_devTools);
139         createDRTDevToolsClient(m_drtDevToolsAgent.get());
140     }
141     m_devTools->show(WebKit::WebNavigationPolicyNewWindow);
142 }
143
144 void TestShell::closeDevTools()
145 {
146     if (m_devTools) {
147         m_drtDevToolsAgent->reset();
148         m_drtDevToolsClient.clear();
149         closeWindow(m_devTools);
150         m_devTools = 0;
151     }
152 }
153
154 void TestShell::resetWebSettings(WebView& webView)
155 {
156     m_prefs.reset();
157     m_prefs.acceleratedCompositingEnabled = m_acceleratedCompositingEnabled;
158     m_prefs.accelerated2dCanvasEnabled = m_accelerated2dCanvasEnabled;
159     m_prefs.applyTo(&webView);
160 }
161
162 void TestShell::runFileTest(const TestParams& params)
163 {
164     ASSERT(params.testUrl.isValid());
165     m_testIsPreparing = true;
166     m_params = params;
167     string testUrl = m_params.testUrl.spec();
168
169     bool inspectorTestMode = testUrl.find("/inspector/") != string::npos
170         || testUrl.find("\\inspector\\") != string::npos;
171     m_prefs.developerExtrasEnabled = inspectorTestMode;
172     applyPreferences();
173
174     if (testUrl.find("loading/") != string::npos
175         || testUrl.find("loading\\") != string::npos)
176         m_layoutTestController->setShouldDumpFrameLoadCallbacks(true);
177
178     if (inspectorTestMode)
179         showDevTools();
180
181     if (m_dumpWhenFinished)
182         m_printer->handleTestHeader(testUrl.c_str());
183     loadURL(m_params.testUrl);
184
185     m_testIsPreparing = false;
186     waitTestFinished();
187 }
188
189 static inline bool isSVGTestURL(const WebURL& url)
190 {
191     return url.isValid() && string(url.spec()).find("W3C-SVG-1.1") != string::npos;
192 }
193
194 void TestShell::resizeWindowForTest(WebViewHost* window, const WebURL& url)
195 {
196     int width, height;
197     if (isSVGTestURL(url)) {
198         width = SVGTestWindowWidth;
199         height = SVGTestWindowHeight;
200     } else {
201         width = testWindowWidth;
202         height = testWindowHeight;
203     }
204     window->setWindowRect(WebRect(0, 0, width + virtualWindowBorder * 2, height + virtualWindowBorder * 2));
205 }
206
207 void TestShell::resetTestController()
208 {
209     resetWebSettings(*webView());
210     m_accessibilityController->reset();
211     m_layoutTestController->reset();
212     m_eventSender->reset();
213     m_webViewHost->reset();
214     m_notificationPresenter->reset();
215     m_drtDevToolsAgent->reset();
216     if (m_drtDevToolsClient)
217         m_drtDevToolsClient->reset();
218 }
219
220 void TestShell::loadURL(const WebURL& url)
221 {
222     m_webViewHost->loadURLForFrame(url, WebString());
223 }
224
225 void TestShell::reload()
226 {
227     m_webViewHost->navigationController()->reload();
228 }
229
230 void TestShell::goToOffset(int offset)
231 {
232      m_webViewHost->navigationController()->goToOffset(offset);
233 }
234
235 int TestShell::navigationEntryCount() const
236 {
237     return m_webViewHost->navigationController()->entryCount();
238 }
239
240 void TestShell::callJSGC()
241 {
242     m_webView->mainFrame()->collectGarbage();
243 }
244
245 void TestShell::setFocus(WebWidget* widget, bool enable)
246 {
247     // Simulate the effects of InteractiveSetFocus(), which includes calling
248     // both setFocus() and setIsActive().
249     if (enable) {
250         if (m_focusedWidget != widget) {
251             if (m_focusedWidget)
252                 m_focusedWidget->setFocus(false);
253             webView()->setIsActive(enable);
254             widget->setFocus(enable);
255             m_focusedWidget = widget;
256         }
257     } else {
258         if (m_focusedWidget == widget) {
259             widget->setFocus(enable);
260             webView()->setIsActive(enable);
261             m_focusedWidget = 0;
262         }
263     }
264 }
265
266 void TestShell::testFinished()
267 {
268     if (!m_testIsPending)
269         return;
270     m_testIsPending = false;
271     if (m_dumpWhenFinished)
272         dump();
273     webkit_support::QuitMessageLoop();
274 }
275
276 void TestShell::testTimedOut()
277 {
278     m_printer->handleTimedOut();
279     testFinished();
280 }
281
282 static string dumpDocumentText(WebFrame* frame)
283 {
284     // We use the document element's text instead of the body text here because
285     // not all documents have a body, such as XML documents.
286     WebElement documentElement = frame->document().documentElement();
287     if (documentElement.isNull())
288         return string();
289     return documentElement.innerText().utf8();
290 }
291
292 static string dumpFramesAsText(WebFrame* frame, bool recursive)
293 {
294     string result;
295
296     // Add header for all but the main frame. Skip empty frames.
297     if (frame->parent() && !frame->document().documentElement().isNull()) {
298         result.append("\n--------\nFrame: '");
299         result.append(frame->name().utf8().data());
300         result.append("'\n--------\n");
301     }
302
303     result.append(dumpDocumentText(frame));
304     result.append("\n");
305
306     if (recursive) {
307         for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling())
308             result.append(dumpFramesAsText(child, recursive));
309     }
310
311     return result;
312 }
313
314 static void dumpFrameScrollPosition(WebFrame* frame, bool recursive)
315 {
316     WebSize offset = frame->scrollOffset();
317     if (offset.width > 0 || offset.height > 0) {
318         if (frame->parent())
319             printf("frame '%s' ", frame->name().utf8().data());
320         printf("scrolled to %d,%d\n", offset.width, offset.height);
321     }
322
323     if (!recursive)
324         return;
325     for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling())
326         dumpFrameScrollPosition(child, recursive);
327 }
328
329 struct ToLower {
330     char16 operator()(char16 c) { return tolower(c); }
331 };
332
333 // FIXME: Eliminate std::transform(), std::vector, and std::sort().
334
335 // Returns True if item1 < item2.
336 static bool HistoryItemCompareLess(const WebHistoryItem& item1, const WebHistoryItem& item2)
337 {
338     string16 target1 = item1.target();
339     string16 target2 = item2.target();
340     std::transform(target1.begin(), target1.end(), target1.begin(), ToLower());
341     std::transform(target2.begin(), target2.end(), target2.begin(), ToLower());
342     return target1 < target2;
343 }
344
345 static string dumpHistoryItem(const WebHistoryItem& item, int indent, bool isCurrent)
346 {
347     string result;
348
349     if (isCurrent) {
350         result.append("curr->");
351         result.append(indent - 6, ' '); // 6 == "curr->".length()
352     } else {
353         result.append(indent, ' ');
354     }
355
356     string url = item.urlString().utf8();
357     size_t pos;
358     if (!url.find(fileUrlPattern) && ((pos = url.find(layoutTestsPattern)) != string::npos)) {
359         // adjust file URLs to match upstream results.
360         url.replace(0, pos + layoutTestsPatternSize, fileTestPrefix);
361     } else if (!url.find(dataUrlPattern)) {
362         // URL-escape data URLs to match results upstream.
363         string path = webkit_support::EscapePath(url.substr(dataUrlPatternSize));
364         url.replace(dataUrlPatternSize, url.length(), path);
365     }
366
367     result.append(url);
368     if (!item.target().isEmpty()) {
369         result.append(" (in frame \"");
370         result.append(item.target().utf8());
371         result.append("\")");
372     }
373     if (item.isTargetItem())
374         result.append("  **nav target**");
375     result.append("\n");
376
377     const WebVector<WebHistoryItem>& children = item.children();
378     if (!children.isEmpty()) {
379         // Must sort to eliminate arbitrary result ordering which defeats
380         // reproducible testing.
381         // FIXME: WebVector should probably just be a std::vector!!
382         std::vector<WebHistoryItem> sortedChildren;
383         for (size_t i = 0; i < children.size(); ++i)
384             sortedChildren.push_back(children[i]);
385         std::sort(sortedChildren.begin(), sortedChildren.end(), HistoryItemCompareLess);
386         for (size_t i = 0; i < sortedChildren.size(); ++i)
387             result += dumpHistoryItem(sortedChildren[i], indent + 4, false);
388     }
389
390     return result;
391 }
392
393 static void dumpBackForwardList(const TestNavigationController& navigationController, string& result)
394 {
395     result.append("\n============== Back Forward List ==============\n");
396     for (int index = 0; index < navigationController.entryCount(); ++index) {
397         int currentIndex = navigationController.lastCommittedEntryIndex();
398         WebHistoryItem historyItem = navigationController.entryAtIndex(index)->contentState();
399         if (historyItem.isNull()) {
400             historyItem.initialize();
401             historyItem.setURLString(navigationController.entryAtIndex(index)->URL().spec().utf16());
402         }
403         result.append(dumpHistoryItem(historyItem, 8, index == currentIndex));
404     }
405     result.append("===============================================\n");
406 }
407
408 string TestShell::dumpAllBackForwardLists()
409 {
410     string result;
411     for (unsigned i = 0; i < m_windowList.size(); ++i)
412         dumpBackForwardList(*m_windowList[i]->navigationController(), result);
413     return result;
414 }
415
416 void TestShell::dump()
417 {
418     WebScriptController::flushConsoleMessages();
419
420     // Dump the requested representation.
421     WebFrame* frame = m_webView->mainFrame();
422     if (!frame)
423         return;
424     bool shouldDumpAsText = m_layoutTestController->shouldDumpAsText();
425     bool shouldGeneratePixelResults = m_layoutTestController->shouldGeneratePixelResults();
426     bool dumpedAnything = false;
427     if (m_params.dumpTree) {
428         dumpedAnything = true;
429         m_printer->handleTextHeader();
430         // Text output: the test page can request different types of output
431         // which we handle here.
432         if (!shouldDumpAsText) {
433             // Plain text pages should be dumped as text
434             string mimeType = frame->dataSource()->response().mimeType().utf8();
435             if (mimeType == "text/plain") {
436                 shouldDumpAsText = true;
437                 shouldGeneratePixelResults = false;
438             }
439         }
440         if (shouldDumpAsText) {
441             bool recursive = m_layoutTestController->shouldDumpChildFramesAsText();
442             string dataUtf8 = dumpFramesAsText(frame, recursive);
443             if (fwrite(dataUtf8.c_str(), 1, dataUtf8.size(), stdout) != dataUtf8.size())
444                 FATAL("Short write to stdout, disk full?\n");
445         } else {
446             printf("%s", frame->renderTreeAsText().utf8().data());
447             bool recursive = m_layoutTestController->shouldDumpChildFrameScrollPositions();
448             dumpFrameScrollPosition(frame, recursive);
449         }
450         if (m_layoutTestController->shouldDumpBackForwardList())
451             printf("%s", dumpAllBackForwardLists().c_str());
452     }
453     if (dumpedAnything && m_params.printSeparators)
454         m_printer->handleTextFooter();
455
456     if (m_params.dumpPixels && shouldGeneratePixelResults) {
457         // Image output: we write the image data to the file given on the
458         // command line (for the dump pixels argument), and the MD5 sum to
459         // stdout.
460         dumpedAnything = true;
461         m_webView->layout();
462         if (m_layoutTestController->testRepaint()) {
463             WebSize viewSize = m_webView->size();
464             int width = viewSize.width;
465             int height = viewSize.height;
466             if (m_layoutTestController->sweepHorizontally()) {
467                 for (WebRect column(0, 0, 1, height); column.x < width; column.x++)
468                     m_webViewHost->paintRect(column);
469             } else {
470                 for (WebRect line(0, 0, width, 1); line.y < height; line.y++)
471                     m_webViewHost->paintRect(line);
472             }
473         } else
474             m_webViewHost->paintInvalidatedRegion();
475
476         // See if we need to draw the selection bounds rect. Selection bounds
477         // rect is the rect enclosing the (possibly transformed) selection.
478         // The rect should be drawn after everything is laid out and painted.
479         if (m_layoutTestController->shouldDumpSelectionRect()) {
480             // If there is a selection rect - draw a red 1px border enclosing rect
481             WebRect wr = frame->selectionBoundsRect();
482             if (!wr.isEmpty()) {
483                 // Render a red rectangle bounding selection rect
484                 SkPaint paint;
485                 paint.setColor(0xFFFF0000); // Fully opaque red
486                 paint.setStyle(SkPaint::kStroke_Style);
487                 paint.setFlags(SkPaint::kAntiAlias_Flag);
488                 paint.setStrokeWidth(1.0f);
489                 SkIRect rect; // Bounding rect
490                 rect.set(wr.x, wr.y, wr.x + wr.width, wr.y + wr.height);
491                 m_webViewHost->canvas()->drawIRect(rect, paint);
492             }
493         }
494
495         dumpImage(m_webViewHost->canvas());
496     }
497     m_printer->handleImageFooter();
498     m_printer->handleTestFooter(dumpedAnything);
499     fflush(stdout);
500     fflush(stderr);
501 }
502
503 void TestShell::dumpImage(skia::PlatformCanvas* canvas) const
504 {
505     skia::BitmapPlatformDevice& device =
506         static_cast<skia::BitmapPlatformDevice&>(canvas->getTopPlatformDevice());
507     const SkBitmap& sourceBitmap = device.accessBitmap(false);
508
509     SkAutoLockPixels sourceBitmapLock(sourceBitmap);
510
511     // Fix the alpha. The expected PNGs on Mac have an alpha channel, so we want
512     // to keep it. On Windows, the alpha channel is wrong since text/form control
513     // drawing may have erased it in a few places. So on Windows we force it to
514     // opaque and also don't write the alpha channel for the reference. Linux
515     // doesn't have the wrong alpha like Windows, but we match Windows.
516 #if OS(MAC_OS_X)
517     bool discardTransparency = false;
518 #else
519     bool discardTransparency = true;
520     device.makeOpaque(0, 0, sourceBitmap.width(), sourceBitmap.height());
521 #endif
522
523     // Compute MD5 sum.
524     MD5 digester;
525     Vector<uint8_t, 16> digestValue;
526     digester.addBytes(reinterpret_cast<const uint8_t*>(sourceBitmap.getPixels()), sourceBitmap.getSize());
527     digester.checksum(digestValue);
528     string md5hash;
529     md5hash.reserve(16 * 2);
530     for (unsigned i = 0; i < 16; ++i) {
531         char hex[3];
532         // Use "x", not "X". The string must be lowercased.
533         sprintf(hex, "%02x", digestValue[i]);
534         md5hash.append(hex);
535     }
536
537     // Only encode and dump the png if the hashes don't match. Encoding the image
538     // is really expensive.
539     if (md5hash.compare(m_params.pixelHash)) {
540         std::vector<unsigned char> png;
541         webkit_support::EncodeBGRAPNG(
542             reinterpret_cast<const unsigned char*>(sourceBitmap.getPixels()),
543             sourceBitmap.width(), sourceBitmap.height(),
544             static_cast<int>(sourceBitmap.rowBytes()), discardTransparency, &png);
545
546         m_printer->handleImage(md5hash.c_str(), m_params.pixelHash.c_str(), &png[0], png.size(), m_params.pixelFileName.c_str());
547     } else
548         m_printer->handleImage(md5hash.c_str(), m_params.pixelHash.c_str(), 0, 0, m_params.pixelFileName.c_str());
549 }
550
551 void TestShell::bindJSObjectsToWindow(WebFrame* frame)
552 {
553     m_accessibilityController->bindToJavascript(frame, WebString::fromUTF8("accessibilityController"));
554     m_layoutTestController->bindToJavascript(frame, WebString::fromUTF8("layoutTestController"));
555     m_eventSender->bindToJavascript(frame, WebString::fromUTF8("eventSender"));
556     m_plainTextController->bindToJavascript(frame, WebString::fromUTF8("plainText"));
557     m_textInputController->bindToJavascript(frame, WebString::fromUTF8("textInputController"));
558 }
559
560 WebViewHost* TestShell::createWebView()
561 {
562     return createNewWindow(WebURL());
563 }
564
565 WebViewHost* TestShell::createNewWindow(const WebURL& url)
566 {
567     WebViewHost* host = new WebViewHost(this);
568     WebView* view = WebView::create(host, m_drtDevToolsAgent.get());
569     host->setWebWidget(view);
570     m_prefs.applyTo(view);
571     view->initializeMainFrame(host);
572     m_windowList.append(host);
573     host->loadURLForFrame(url, WebString());
574     return host;
575 }
576
577 void TestShell::closeWindow(WebViewHost* window)
578 {
579     size_t i = m_windowList.find(window);
580     if (i == notFound) {
581         ASSERT_NOT_REACHED();
582         return;
583     }
584     m_windowList.remove(i);
585     WebWidget* focusedWidget = m_focusedWidget;
586     if (window->webWidget() == m_focusedWidget)
587         focusedWidget = 0;
588
589     delete window;
590     // We set the focused widget after deleting the web view host because it
591     // can change the focus.
592     m_focusedWidget = focusedWidget;
593     if (m_focusedWidget) {
594         webView()->setIsActive(true);
595         m_focusedWidget->setFocus(true);
596     }
597 }
598
599 void TestShell::closeRemainingWindows()
600 {
601     // Iterate through the window list and close everything except the main
602     // window. We don't want to delete elements as we're iterating, so we copy
603     // to a temp vector first.
604     Vector<WebViewHost*> windowsToDelete;
605     for (unsigned i = 0; i < m_windowList.size(); ++i) {
606         if (m_windowList[i] != webViewHost())
607             windowsToDelete.append(m_windowList[i]);
608     }
609     ASSERT(windowsToDelete.size() + 1 == m_windowList.size());
610     for (unsigned i = 0; i < windowsToDelete.size(); ++i)
611         closeWindow(windowsToDelete[i]);
612     ASSERT(m_windowList.size() == 1);
613 }
614
615 int TestShell::windowCount()
616 {
617     return m_windowList.size();
618 }