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 "WebViewHost.h" |
|
38 #include "base/md5.h" // FIXME: Wrap by webkit_support. |
|
39 #include "base/string16.h" |
|
40 #include "gfx/codec/png_codec.h" // FIXME: Remove dependecy. WebCore/platform/image-encoder is better? |
|
41 #include "net/base/escape.h" // FIXME: Remove dependency. |
|
42 #include "public/WebDataSource.h" |
|
43 #include "public/WebDocument.h" |
|
44 #include "public/WebElement.h" |
|
45 #include "public/WebFrame.h" |
|
46 #include "public/WebHistoryItem.h" |
|
47 #include "public/WebRuntimeFeatures.h" |
|
48 #include "public/WebScriptController.h" |
|
49 #include "public/WebSettings.h" |
|
50 #include "public/WebSize.h" |
|
51 #include "public/WebString.h" |
|
52 #include "public/WebURLRequest.h" |
|
53 #include "public/WebURLResponse.h" |
|
54 #include "public/WebView.h" |
|
55 #include "skia/ext/bitmap_platform_device.h" |
|
56 #include "skia/ext/platform_canvas.h" |
|
57 #include "webkit/support/webkit_support.h" |
|
58 #include <algorithm> |
|
59 #include <cctype> |
|
60 #include <vector> |
|
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_allowExternalPages(false) |
|
86 , m_devTools(0) |
|
87 { |
|
88 WebRuntimeFeatures::enableGeolocation(true); |
|
89 m_accessibilityController.set(new AccessibilityController(this)); |
|
90 m_layoutTestController.set(new LayoutTestController(this)); |
|
91 m_eventSender.set(new EventSender(this)); |
|
92 m_plainTextController.set(new PlainTextController()); |
|
93 m_textInputController.set(new TextInputController(this)); |
|
94 m_notificationPresenter.set(new NotificationPresenter(this)); |
|
95 m_printer.set(m_testShellMode ? TestEventPrinter::createTestShellPrinter() : TestEventPrinter::createDRTPrinter()); |
|
96 |
|
97 // 30 second is the same as the value in Mac DRT. |
|
98 // If we use a value smaller than the timeout value of |
|
99 // (new-)run-webkit-tests, (new-)run-webkit-tests misunderstands that a |
|
100 // timed-out DRT process was crashed. |
|
101 m_timeout = 30 * 1000; |
|
102 |
|
103 m_drtDevToolsAgent.set(new DRTDevToolsAgent); |
|
104 m_webViewHost = createWebView(); |
|
105 m_webView = m_webViewHost->webView(); |
|
106 m_drtDevToolsAgent->setWebView(m_webView); |
|
107 } |
|
108 |
|
109 TestShell::~TestShell() |
|
110 { |
|
111 // Note: DevTools are closed together with all the other windows in the |
|
112 // windows list. |
|
113 |
|
114 loadURL(GURL("about:blank")); |
|
115 // Call GC twice to clean up garbage. |
|
116 callJSGC(); |
|
117 callJSGC(); |
|
118 |
|
119 // Destroy the WebView before its WebViewHost. |
|
120 m_webView->close(); |
|
121 } |
|
122 |
|
123 void TestShell::createDRTDevToolsClient(DRTDevToolsAgent* agent) |
|
124 { |
|
125 m_drtDevToolsClient.set(new DRTDevToolsClient(agent, m_devTools->webView())); |
|
126 } |
|
127 |
|
128 void TestShell::showDevTools() |
|
129 { |
|
130 if (!m_devTools) { |
|
131 WebURL url = webkit_support::GetDevToolsPathAsURL(); |
|
132 if (!url.isValid()) { |
|
133 ASSERT(false); |
|
134 return; |
|
135 } |
|
136 m_devTools = createNewWindow(url); |
|
137 ASSERT(m_devTools); |
|
138 createDRTDevToolsClient(m_drtDevToolsAgent.get()); |
|
139 } |
|
140 m_devTools->show(WebKit::WebNavigationPolicyNewWindow); |
|
141 } |
|
142 |
|
143 void TestShell::closeDevTools() |
|
144 { |
|
145 if (m_devTools) { |
|
146 closeWindow(m_devTools); |
|
147 m_devTools = 0; |
|
148 } |
|
149 } |
|
150 |
|
151 void TestShell::resetWebSettings(WebView& webView) |
|
152 { |
|
153 // Match the settings used by Mac DumpRenderTree, with the exception of |
|
154 // fonts. |
|
155 WebSettings* settings = webView.settings(); |
|
156 #if OS(MAC_OS_X) |
|
157 WebString serif = WebString::fromUTF8("Times"); |
|
158 settings->setCursiveFontFamily(WebString::fromUTF8("Apple Chancery")); |
|
159 settings->setFantasyFontFamily(WebString::fromUTF8("Papyrus")); |
|
160 #else |
|
161 // NOTE: case matters here, this must be 'times new roman', else |
|
162 // some layout tests fail. |
|
163 WebString serif = WebString::fromUTF8("times new roman"); |
|
164 |
|
165 // These two fonts are picked from the intersection of |
|
166 // Win XP font list and Vista font list : |
|
167 // http://www.microsoft.com/typography/fonts/winxp.htm |
|
168 // http://blogs.msdn.com/michkap/archive/2006/04/04/567881.aspx |
|
169 // Some of them are installed only with CJK and complex script |
|
170 // support enabled on Windows XP and are out of consideration here. |
|
171 // (although we enabled both on our buildbots.) |
|
172 // They (especially Impact for fantasy) are not typical cursive |
|
173 // and fantasy fonts, but it should not matter for layout tests |
|
174 // as long as they're available. |
|
175 settings->setCursiveFontFamily(WebString::fromUTF8("Comic Sans MS")); |
|
176 settings->setFantasyFontFamily(WebString::fromUTF8("Impact")); |
|
177 #endif |
|
178 settings->setSerifFontFamily(serif); |
|
179 settings->setStandardFontFamily(serif); |
|
180 settings->setFixedFontFamily(WebString::fromUTF8("Courier")); |
|
181 settings->setSansSerifFontFamily(WebString::fromUTF8("Helvetica")); |
|
182 |
|
183 settings->setDefaultTextEncodingName(WebString::fromUTF8("ISO-8859-1")); |
|
184 settings->setDefaultFontSize(16); |
|
185 settings->setDefaultFixedFontSize(13); |
|
186 settings->setMinimumFontSize(1); |
|
187 settings->setMinimumLogicalFontSize(9); |
|
188 settings->setJavaScriptCanOpenWindowsAutomatically(true); |
|
189 settings->setJavaScriptCanAccessClipboard(true); |
|
190 settings->setDOMPasteAllowed(true); |
|
191 settings->setDeveloperExtrasEnabled(false); |
|
192 settings->setNeedsSiteSpecificQuirks(true); |
|
193 settings->setShrinksStandaloneImagesToFit(false); |
|
194 settings->setUsesEncodingDetector(false); |
|
195 settings->setTextAreasAreResizable(false); |
|
196 settings->setJavaEnabled(false); |
|
197 settings->setAllowScriptsToCloseWindows(false); |
|
198 settings->setXSSAuditorEnabled(false); |
|
199 settings->setDownloadableBinaryFontsEnabled(true); |
|
200 settings->setLocalStorageEnabled(true); |
|
201 settings->setOfflineWebApplicationCacheEnabled(true); |
|
202 settings->setAllowFileAccessFromFileURLs(true); |
|
203 |
|
204 // LayoutTests were written with Safari Mac in mind which does not allow |
|
205 // tabbing to links by default. |
|
206 webView.setTabsToLinks(false); |
|
207 |
|
208 // Allow those layout tests running as local files, i.e. under |
|
209 // LayoutTests/http/tests/local, to access http server. |
|
210 settings->setAllowUniversalAccessFromFileURLs(true); |
|
211 |
|
212 settings->setJavaScriptEnabled(true); |
|
213 settings->setPluginsEnabled(true); |
|
214 settings->setWebSecurityEnabled(true); |
|
215 settings->setEditableLinkBehaviorNeverLive(); |
|
216 settings->setFontRenderingModeNormal(); |
|
217 settings->setShouldPaintCustomScrollbars(true); |
|
218 settings->setTextDirectionSubmenuInclusionBehaviorNeverIncluded(); |
|
219 |
|
220 settings->setLoadsImagesAutomatically(true); |
|
221 settings->setImagesEnabled(true); |
|
222 |
|
223 #if OS(DARWIN) |
|
224 settings->setEditingBehavior(WebSettings::EditingBehaviorMac); |
|
225 #else |
|
226 settings->setEditingBehavior(WebSettings::EditingBehaviorWin); |
|
227 #endif |
|
228 } |
|
229 |
|
230 void TestShell::runFileTest(const TestParams& params) |
|
231 { |
|
232 ASSERT(params.testUrl.isValid()); |
|
233 m_testIsPreparing = true; |
|
234 m_params = params; |
|
235 string testUrl = m_params.testUrl.spec(); |
|
236 |
|
237 bool inspectorTestMode = testUrl.find("/inspector/") != string::npos |
|
238 || testUrl.find("\\inspector\\") != string::npos; |
|
239 m_webView->settings()->setDeveloperExtrasEnabled(inspectorTestMode); |
|
240 |
|
241 if (testUrl.find("loading/") != string::npos |
|
242 || testUrl.find("loading\\") != string::npos) |
|
243 m_layoutTestController->setShouldDumpFrameLoadCallbacks(true); |
|
244 |
|
245 if (inspectorTestMode) |
|
246 showDevTools(); |
|
247 |
|
248 m_printer->handleTestHeader(testUrl.c_str()); |
|
249 loadURL(m_params.testUrl); |
|
250 |
|
251 m_testIsPreparing = false; |
|
252 waitTestFinished(); |
|
253 } |
|
254 |
|
255 static inline bool isSVGTestURL(const WebURL& url) |
|
256 { |
|
257 return url.isValid() && string(url.spec()).find("W3C-SVG-1.1") != string::npos; |
|
258 } |
|
259 |
|
260 void TestShell::resizeWindowForTest(WebViewHost* window, const WebURL& url) |
|
261 { |
|
262 int width, height; |
|
263 if (isSVGTestURL(url)) { |
|
264 width = SVGTestWindowWidth; |
|
265 height = SVGTestWindowHeight; |
|
266 } else { |
|
267 width = testWindowWidth; |
|
268 height = testWindowHeight; |
|
269 } |
|
270 window->setWindowRect(WebRect(0, 0, width + virtualWindowBorder * 2, height + virtualWindowBorder * 2)); |
|
271 } |
|
272 |
|
273 void TestShell::resetTestController() |
|
274 { |
|
275 resetWebSettings(*webView()); |
|
276 m_accessibilityController->reset(); |
|
277 m_layoutTestController->reset(); |
|
278 m_eventSender->reset(); |
|
279 m_webViewHost->reset(); |
|
280 m_notificationPresenter->reset(); |
|
281 } |
|
282 |
|
283 void TestShell::loadURL(const WebURL& url) |
|
284 { |
|
285 m_webViewHost->loadURLForFrame(url, WebString()); |
|
286 } |
|
287 |
|
288 void TestShell::reload() |
|
289 { |
|
290 m_webViewHost->navigationController()->reload(); |
|
291 } |
|
292 |
|
293 void TestShell::goToOffset(int offset) |
|
294 { |
|
295 m_webViewHost->navigationController()->goToOffset(offset); |
|
296 } |
|
297 |
|
298 int TestShell::navigationEntryCount() const |
|
299 { |
|
300 return m_webViewHost->navigationController()->entryCount(); |
|
301 } |
|
302 |
|
303 void TestShell::callJSGC() |
|
304 { |
|
305 m_webView->mainFrame()->collectGarbage(); |
|
306 } |
|
307 |
|
308 void TestShell::setFocus(WebWidget* widget, bool enable) |
|
309 { |
|
310 // Simulate the effects of InteractiveSetFocus(), which includes calling |
|
311 // both setFocus() and setIsActive(). |
|
312 if (enable) { |
|
313 if (m_focusedWidget != widget) { |
|
314 if (m_focusedWidget) |
|
315 m_focusedWidget->setFocus(false); |
|
316 webView()->setIsActive(enable); |
|
317 widget->setFocus(enable); |
|
318 m_focusedWidget = widget; |
|
319 } |
|
320 } else { |
|
321 if (m_focusedWidget == widget) { |
|
322 widget->setFocus(enable); |
|
323 webView()->setIsActive(enable); |
|
324 m_focusedWidget = 0; |
|
325 } |
|
326 } |
|
327 } |
|
328 |
|
329 void TestShell::testFinished() |
|
330 { |
|
331 if (!m_testIsPending) |
|
332 return; |
|
333 m_testIsPending = false; |
|
334 dump(); |
|
335 webkit_support::QuitMessageLoop(); |
|
336 } |
|
337 |
|
338 void TestShell::testTimedOut() |
|
339 { |
|
340 m_printer->handleTimedOut(); |
|
341 testFinished(); |
|
342 } |
|
343 |
|
344 static string dumpDocumentText(WebFrame* frame) |
|
345 { |
|
346 // We use the document element's text instead of the body text here because |
|
347 // not all documents have a body, such as XML documents. |
|
348 WebElement documentElement = frame->document().documentElement(); |
|
349 if (documentElement.isNull()) |
|
350 return string(); |
|
351 return documentElement.innerText().utf8(); |
|
352 } |
|
353 |
|
354 static string dumpFramesAsText(WebFrame* frame, bool recursive) |
|
355 { |
|
356 string result; |
|
357 |
|
358 // Add header for all but the main frame. Skip empty frames. |
|
359 if (frame->parent() && !frame->document().documentElement().isNull()) { |
|
360 result.append("\n--------\nFrame: '"); |
|
361 result.append(frame->name().utf8().data()); |
|
362 result.append("'\n--------\n"); |
|
363 } |
|
364 |
|
365 result.append(dumpDocumentText(frame)); |
|
366 result.append("\n"); |
|
367 |
|
368 if (recursive) { |
|
369 for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling()) |
|
370 result.append(dumpFramesAsText(child, recursive)); |
|
371 } |
|
372 |
|
373 return result; |
|
374 } |
|
375 |
|
376 static void dumpFrameScrollPosition(WebFrame* frame, bool recursive) |
|
377 { |
|
378 WebSize offset = frame->scrollOffset(); |
|
379 if (offset.width > 0 || offset.height > 0) { |
|
380 if (frame->parent()) |
|
381 printf("frame '%s' ", frame->name().utf8().data()); |
|
382 printf("scrolled to %d,%d\n", offset.width, offset.height); |
|
383 } |
|
384 |
|
385 if (!recursive) |
|
386 return; |
|
387 for (WebFrame* child = frame->firstChild(); child; child = child->nextSibling()) |
|
388 dumpFrameScrollPosition(child, recursive); |
|
389 } |
|
390 |
|
391 struct ToLower { |
|
392 char16 operator()(char16 c) { return tolower(c); } |
|
393 }; |
|
394 |
|
395 // FIXME: Eliminate std::transform(), std::vector, and std::sort(). |
|
396 |
|
397 // Returns True if item1 < item2. |
|
398 static bool HistoryItemCompareLess(const WebHistoryItem& item1, const WebHistoryItem& item2) |
|
399 { |
|
400 string16 target1 = item1.target(); |
|
401 string16 target2 = item2.target(); |
|
402 std::transform(target1.begin(), target1.end(), target1.begin(), ToLower()); |
|
403 std::transform(target2.begin(), target2.end(), target2.begin(), ToLower()); |
|
404 return target1 < target2; |
|
405 } |
|
406 |
|
407 static string dumpHistoryItem(const WebHistoryItem& item, int indent, bool isCurrent) |
|
408 { |
|
409 string result; |
|
410 |
|
411 if (isCurrent) { |
|
412 result.append("curr->"); |
|
413 result.append(indent - 6, ' '); // 6 == "curr->".length() |
|
414 } else { |
|
415 result.append(indent, ' '); |
|
416 } |
|
417 |
|
418 string url = item.urlString().utf8(); |
|
419 size_t pos; |
|
420 if (!url.find(fileUrlPattern) && ((pos = url.find(layoutTestsPattern)) != string::npos)) { |
|
421 // adjust file URLs to match upstream results. |
|
422 url.replace(0, pos + layoutTestsPatternSize, fileTestPrefix); |
|
423 } else if (!url.find(dataUrlPattern)) { |
|
424 // URL-escape data URLs to match results upstream. |
|
425 string path = EscapePath(url.substr(dataUrlPatternSize)); |
|
426 url.replace(dataUrlPatternSize, url.length(), path); |
|
427 } |
|
428 |
|
429 result.append(url); |
|
430 if (!item.target().isEmpty()) { |
|
431 result.append(" (in frame \""); |
|
432 result.append(item.target().utf8()); |
|
433 result.append("\")"); |
|
434 } |
|
435 if (item.isTargetItem()) |
|
436 result.append(" **nav target**"); |
|
437 result.append("\n"); |
|
438 |
|
439 const WebVector<WebHistoryItem>& children = item.children(); |
|
440 if (!children.isEmpty()) { |
|
441 // Must sort to eliminate arbitrary result ordering which defeats |
|
442 // reproducible testing. |
|
443 // FIXME: WebVector should probably just be a std::vector!! |
|
444 std::vector<WebHistoryItem> sortedChildren; |
|
445 for (size_t i = 0; i < children.size(); ++i) |
|
446 sortedChildren.push_back(children[i]); |
|
447 std::sort(sortedChildren.begin(), sortedChildren.end(), HistoryItemCompareLess); |
|
448 for (size_t i = 0; i < sortedChildren.size(); ++i) |
|
449 result += dumpHistoryItem(sortedChildren[i], indent + 4, false); |
|
450 } |
|
451 |
|
452 return result; |
|
453 } |
|
454 |
|
455 static void dumpBackForwardList(const TestNavigationController& navigationController, string& result) |
|
456 { |
|
457 result.append("\n============== Back Forward List ==============\n"); |
|
458 for (int index = 0; index < navigationController.entryCount(); ++index) { |
|
459 int currentIndex = navigationController.lastCommittedEntryIndex(); |
|
460 WebHistoryItem historyItem = navigationController.entryAtIndex(index)->contentState(); |
|
461 if (historyItem.isNull()) { |
|
462 historyItem.initialize(); |
|
463 historyItem.setURLString(navigationController.entryAtIndex(index)->URL().spec().utf16()); |
|
464 } |
|
465 result.append(dumpHistoryItem(historyItem, 8, index == currentIndex)); |
|
466 } |
|
467 result.append("===============================================\n"); |
|
468 } |
|
469 |
|
470 string TestShell::dumpAllBackForwardLists() |
|
471 { |
|
472 string result; |
|
473 for (unsigned i = 0; i < m_windowList.size(); ++i) |
|
474 dumpBackForwardList(*m_windowList[i]->navigationController(), result); |
|
475 return result; |
|
476 } |
|
477 |
|
478 void TestShell::dump() |
|
479 { |
|
480 WebScriptController::flushConsoleMessages(); |
|
481 |
|
482 // Dump the requested representation. |
|
483 WebFrame* frame = m_webView->mainFrame(); |
|
484 if (!frame) |
|
485 return; |
|
486 bool shouldDumpAsText = m_layoutTestController->shouldDumpAsText(); |
|
487 bool dumpedAnything = false; |
|
488 if (m_params.dumpTree) { |
|
489 dumpedAnything = true; |
|
490 m_printer->handleTextHeader(); |
|
491 // Text output: the test page can request different types of output |
|
492 // which we handle here. |
|
493 if (!shouldDumpAsText) { |
|
494 // Plain text pages should be dumped as text |
|
495 string mimeType = frame->dataSource()->response().mimeType().utf8(); |
|
496 shouldDumpAsText = mimeType == "text/plain"; |
|
497 } |
|
498 if (shouldDumpAsText) { |
|
499 bool recursive = m_layoutTestController->shouldDumpChildFramesAsText(); |
|
500 string dataUtf8 = dumpFramesAsText(frame, recursive); |
|
501 if (fwrite(dataUtf8.c_str(), 1, dataUtf8.size(), stdout) != dataUtf8.size()) |
|
502 FATAL("Short write to stdout, disk full?\n"); |
|
503 } else { |
|
504 printf("%s", frame->renderTreeAsText().utf8().data()); |
|
505 bool recursive = m_layoutTestController->shouldDumpChildFrameScrollPositions(); |
|
506 dumpFrameScrollPosition(frame, recursive); |
|
507 } |
|
508 if (m_layoutTestController->shouldDumpBackForwardList()) |
|
509 printf("%s", dumpAllBackForwardLists().c_str()); |
|
510 } |
|
511 if (dumpedAnything && m_params.printSeparators) |
|
512 m_printer->handleTextFooter(); |
|
513 |
|
514 if (m_params.dumpPixels && m_layoutTestController->shouldGeneratePixelResults()) { |
|
515 // Image output: we write the image data to the file given on the |
|
516 // command line (for the dump pixels argument), and the MD5 sum to |
|
517 // stdout. |
|
518 dumpedAnything = true; |
|
519 m_webView->layout(); |
|
520 if (m_layoutTestController->testRepaint()) { |
|
521 WebSize viewSize = m_webView->size(); |
|
522 int width = viewSize.width; |
|
523 int height = viewSize.height; |
|
524 if (m_layoutTestController->sweepHorizontally()) { |
|
525 for (WebRect column(0, 0, 1, height); column.x < width; column.x++) |
|
526 m_webViewHost->paintRect(column); |
|
527 } else { |
|
528 for (WebRect line(0, 0, width, 1); line.y < height; line.y++) |
|
529 m_webViewHost->paintRect(line); |
|
530 } |
|
531 } else |
|
532 m_webViewHost->paintInvalidatedRegion(); |
|
533 |
|
534 // See if we need to draw the selection bounds rect. Selection bounds |
|
535 // rect is the rect enclosing the (possibly transformed) selection. |
|
536 // The rect should be drawn after everything is laid out and painted. |
|
537 if (m_layoutTestController->shouldDumpSelectionRect()) { |
|
538 // If there is a selection rect - draw a red 1px border enclosing rect |
|
539 WebRect wr = frame->selectionBoundsRect(); |
|
540 if (!wr.isEmpty()) { |
|
541 // Render a red rectangle bounding selection rect |
|
542 SkPaint paint; |
|
543 paint.setColor(0xFFFF0000); // Fully opaque red |
|
544 paint.setStyle(SkPaint::kStroke_Style); |
|
545 paint.setFlags(SkPaint::kAntiAlias_Flag); |
|
546 paint.setStrokeWidth(1.0f); |
|
547 SkIRect rect; // Bounding rect |
|
548 rect.set(wr.x, wr.y, wr.x + wr.width, wr.y + wr.height); |
|
549 m_webViewHost->canvas()->drawIRect(rect, paint); |
|
550 } |
|
551 } |
|
552 |
|
553 dumpImage(m_webViewHost->canvas()); |
|
554 } |
|
555 m_printer->handleImageFooter(); |
|
556 m_printer->handleTestFooter(dumpedAnything); |
|
557 fflush(stdout); |
|
558 fflush(stderr); |
|
559 } |
|
560 |
|
561 void TestShell::dumpImage(skia::PlatformCanvas* canvas) const |
|
562 { |
|
563 skia::BitmapPlatformDevice& device = |
|
564 static_cast<skia::BitmapPlatformDevice&>(canvas->getTopPlatformDevice()); |
|
565 const SkBitmap& sourceBitmap = device.accessBitmap(false); |
|
566 |
|
567 SkAutoLockPixels sourceBitmapLock(sourceBitmap); |
|
568 |
|
569 // Fix the alpha. The expected PNGs on Mac have an alpha channel, so we want |
|
570 // to keep it. On Windows, the alpha channel is wrong since text/form control |
|
571 // drawing may have erased it in a few places. So on Windows we force it to |
|
572 // opaque and also don't write the alpha channel for the reference. Linux |
|
573 // doesn't have the wrong alpha like Windows, but we ignore it anyway. |
|
574 #if OS(WINDOWS) |
|
575 bool discardTransparency = true; |
|
576 device.makeOpaque(0, 0, sourceBitmap.width(), sourceBitmap.height()); |
|
577 #elif OS(MAC_OS_X) |
|
578 bool discardTransparency = false; |
|
579 #elif OS(UNIX) |
|
580 bool discardTransparency = true; |
|
581 #endif |
|
582 |
|
583 // Compute MD5 sum. We should have done this before calling |
|
584 // device.makeOpaque on Windows. Because we do it after the call, there are |
|
585 // some images that are the pixel identical on windows and other platforms |
|
586 // but have different MD5 sums. At this point, rebaselining all the windows |
|
587 // tests is too much of a pain, so we just check in different baselines. |
|
588 MD5Context ctx; |
|
589 MD5Init(&ctx); |
|
590 MD5Update(&ctx, sourceBitmap.getPixels(), sourceBitmap.getSize()); |
|
591 |
|
592 MD5Digest digest; |
|
593 MD5Final(&digest, &ctx); |
|
594 string md5hash = MD5DigestToBase16(digest); |
|
595 |
|
596 // Only encode and dump the png if the hashes don't match. Encoding the image |
|
597 // is really expensive. |
|
598 if (md5hash.compare(m_params.pixelHash)) { |
|
599 std::vector<unsigned char> png; |
|
600 gfx::PNGCodec::ColorFormat colorFormat = gfx::PNGCodec::FORMAT_BGRA; |
|
601 gfx::PNGCodec::Encode( |
|
602 reinterpret_cast<const unsigned char*>(sourceBitmap.getPixels()), |
|
603 colorFormat, sourceBitmap.width(), sourceBitmap.height(), |
|
604 static_cast<int>(sourceBitmap.rowBytes()), discardTransparency, &png); |
|
605 |
|
606 m_printer->handleImage(md5hash.c_str(), m_params.pixelHash.c_str(), &png[0], png.size(), m_params.pixelFileName.c_str()); |
|
607 } else |
|
608 m_printer->handleImage(md5hash.c_str(), m_params.pixelHash.c_str(), 0, 0, m_params.pixelFileName.c_str()); |
|
609 } |
|
610 |
|
611 void TestShell::bindJSObjectsToWindow(WebFrame* frame) |
|
612 { |
|
613 m_accessibilityController->bindToJavascript(frame, WebString::fromUTF8("accessibilityController")); |
|
614 m_layoutTestController->bindToJavascript(frame, WebString::fromUTF8("layoutTestController")); |
|
615 m_eventSender->bindToJavascript(frame, WebString::fromUTF8("eventSender")); |
|
616 m_plainTextController->bindToJavascript(frame, WebString::fromUTF8("plainText")); |
|
617 m_textInputController->bindToJavascript(frame, WebString::fromUTF8("textInputController")); |
|
618 } |
|
619 |
|
620 WebViewHost* TestShell::createWebView() |
|
621 { |
|
622 return createNewWindow(WebURL()); |
|
623 } |
|
624 |
|
625 WebViewHost* TestShell::createNewWindow(const WebURL& url) |
|
626 { |
|
627 WebViewHost* host = new WebViewHost(this); |
|
628 WebView* view = WebView::create(host, m_drtDevToolsAgent.get()); |
|
629 host->setWebWidget(view); |
|
630 resetWebSettings(*view); |
|
631 view->initializeMainFrame(host); |
|
632 m_windowList.append(host); |
|
633 host->loadURLForFrame(url, WebString()); |
|
634 return host; |
|
635 } |
|
636 |
|
637 void TestShell::closeWindow(WebViewHost* window) |
|
638 { |
|
639 size_t i = m_windowList.find(window); |
|
640 if (i == notFound) { |
|
641 ASSERT_NOT_REACHED(); |
|
642 return; |
|
643 } |
|
644 m_windowList.remove(i); |
|
645 if (window->webWidget() == m_focusedWidget) |
|
646 m_focusedWidget = 0; |
|
647 window->webWidget()->close(); |
|
648 delete window; |
|
649 } |
|
650 |
|
651 void TestShell::closeRemainingWindows() |
|
652 { |
|
653 // Iterate through the window list and close everything except the main |
|
654 // ihwindow. We don't want to delete elements as we're iterating, so we copy |
|
655 // to a temp vector first. |
|
656 Vector<WebViewHost*> windowsToDelete; |
|
657 for (unsigned i = 0; i < m_windowList.size(); ++i) { |
|
658 if (m_windowList[i] != webViewHost()) |
|
659 windowsToDelete.append(m_windowList[i]); |
|
660 } |
|
661 ASSERT(windowsToDelete.size() + 1 == m_windowList.size()); |
|
662 for (unsigned i = 0; i < windowsToDelete.size(); ++i) |
|
663 closeWindow(windowsToDelete[i]); |
|
664 ASSERT(m_windowList.size() == 1); |
|
665 } |
|
666 |
|
667 int TestShell::windowCount() |
|
668 { |
|
669 return m_windowList.size(); |
|
670 } |
|