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