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