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