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