1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/shell/webkit_test_controller.h" 6 7 #include <iostream> 8 9 #include "base/base64.h" 10 #include "base/command_line.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/run_loop.h" 13 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/stringprintf.h" 15 #include "content/public/browser/devtools_manager.h" 16 #include "content/public/browser/gpu_data_manager.h" 17 #include "content/public/browser/navigation_controller.h" 18 #include "content/public/browser/navigation_entry.h" 19 #include "content/public/browser/notification_service.h" 20 #include "content/public/browser/notification_types.h" 21 #include "content/public/browser/render_process_host.h" 22 #include "content/public/browser/render_view_host.h" 23 #include "content/public/browser/render_widget_host_view.h" 24 #include "content/public/browser/web_contents.h" 25 #include "content/public/browser/web_contents_view.h" 26 #include "content/public/common/content_switches.h" 27 #include "content/shell/common/shell_messages.h" 28 #include "content/shell/common/shell_switches.h" 29 #include "content/shell/common/webkit_test_helpers.h" 30 #include "content/shell/shell.h" 31 #include "content/shell/shell_browser_context.h" 32 #include "content/shell/shell_content_browser_client.h" 33 #include "ui/gfx/codec/png_codec.h" 34 35 namespace content { 36 37 const int kTestSVGWindowWidthDip = 480; 38 const int kTestSVGWindowHeightDip = 360; 39 40 // WebKitTestResultPrinter ---------------------------------------------------- 41 42 WebKitTestResultPrinter::WebKitTestResultPrinter( 43 std::ostream* output, std::ostream* error) 44 : state_(DURING_TEST), 45 capture_text_only_(false), 46 encode_binary_data_(false), 47 output_(output), 48 error_(error) { 49 } 50 51 WebKitTestResultPrinter::~WebKitTestResultPrinter() { 52 } 53 54 void WebKitTestResultPrinter::PrintTextHeader() { 55 if (state_ != DURING_TEST) 56 return; 57 if (!capture_text_only_) 58 *output_ << "Content-Type: text/plain\n"; 59 state_ = IN_TEXT_BLOCK; 60 } 61 62 void WebKitTestResultPrinter::PrintTextBlock(const std::string& block) { 63 if (state_ != IN_TEXT_BLOCK) 64 return; 65 *output_ << block; 66 } 67 68 void WebKitTestResultPrinter::PrintTextFooter() { 69 if (state_ != IN_TEXT_BLOCK) 70 return; 71 if (!capture_text_only_) { 72 *output_ << "#EOF\n"; 73 output_->flush(); 74 } 75 state_ = IN_IMAGE_BLOCK; 76 } 77 78 void WebKitTestResultPrinter::PrintImageHeader( 79 const std::string& actual_hash, 80 const std::string& expected_hash) { 81 if (state_ != IN_IMAGE_BLOCK || capture_text_only_) 82 return; 83 *output_ << "\nActualHash: " << actual_hash << "\n"; 84 if (!expected_hash.empty()) 85 *output_ << "\nExpectedHash: " << expected_hash << "\n"; 86 } 87 88 void WebKitTestResultPrinter::PrintImageBlock( 89 const std::vector<unsigned char>& png_image) { 90 if (state_ != IN_IMAGE_BLOCK || capture_text_only_) 91 return; 92 *output_ << "Content-Type: image/png\n"; 93 if (encode_binary_data_) { 94 PrintEncodedBinaryData(png_image); 95 return; 96 } 97 98 *output_ << "Content-Length: " << png_image.size() << "\n"; 99 output_->write( 100 reinterpret_cast<const char*>(&png_image[0]), png_image.size()); 101 } 102 103 void WebKitTestResultPrinter::PrintImageFooter() { 104 if (state_ != IN_IMAGE_BLOCK) 105 return; 106 if (!capture_text_only_) { 107 *output_ << "#EOF\n"; 108 *error_ << "#EOF\n"; 109 output_->flush(); 110 error_->flush(); 111 } 112 state_ = AFTER_TEST; 113 } 114 115 void WebKitTestResultPrinter::PrintAudioHeader() { 116 DCHECK_EQ(state_, DURING_TEST); 117 if (!capture_text_only_) 118 *output_ << "Content-Type: audio/wav\n"; 119 state_ = IN_AUDIO_BLOCK; 120 } 121 122 void WebKitTestResultPrinter::PrintAudioBlock( 123 const std::vector<unsigned char>& audio_data) { 124 if (state_ != IN_AUDIO_BLOCK || capture_text_only_) 125 return; 126 if (encode_binary_data_) { 127 PrintEncodedBinaryData(audio_data); 128 return; 129 } 130 131 *output_ << "Content-Length: " << audio_data.size() << "\n"; 132 output_->write( 133 reinterpret_cast<const char*>(&audio_data[0]), audio_data.size()); 134 } 135 136 void WebKitTestResultPrinter::PrintAudioFooter() { 137 if (state_ != IN_AUDIO_BLOCK) 138 return; 139 if (!capture_text_only_) { 140 *output_ << "#EOF\n"; 141 *error_ << "#EOF\n"; 142 output_->flush(); 143 error_->flush(); 144 } 145 state_ = IN_IMAGE_BLOCK; 146 } 147 148 void WebKitTestResultPrinter::AddMessage(const std::string& message) { 149 AddMessageRaw(message + "\n"); 150 } 151 152 void WebKitTestResultPrinter::AddMessageRaw(const std::string& message) { 153 if (state_ != DURING_TEST) 154 return; 155 *output_ << message; 156 } 157 158 void WebKitTestResultPrinter::AddErrorMessage(const std::string& message) { 159 if (!capture_text_only_) 160 *error_ << message << "\n"; 161 if (state_ != DURING_TEST) 162 return; 163 PrintTextHeader(); 164 *output_ << message << "\n"; 165 PrintTextFooter(); 166 PrintImageFooter(); 167 } 168 169 void WebKitTestResultPrinter::PrintEncodedBinaryData( 170 const std::vector<unsigned char>& data) { 171 *output_ << "Content-Transfer-Encoding: base64\n"; 172 173 std::string data_base64; 174 const bool success = base::Base64Encode( 175 base::StringPiece(reinterpret_cast<const char*>(&data[0]), data.size()), 176 &data_base64); 177 DCHECK(success); 178 179 *output_ << "Content-Length: " << data_base64.length() << "\n"; 180 output_->write(data_base64.c_str(), data_base64.length()); 181 } 182 183 184 // WebKitTestController ------------------------------------------------------- 185 186 WebKitTestController* WebKitTestController::instance_ = NULL; 187 188 // static 189 WebKitTestController* WebKitTestController::Get() { 190 DCHECK(instance_); 191 return instance_; 192 } 193 194 WebKitTestController::WebKitTestController() 195 : main_window_(NULL), 196 test_phase_(BETWEEN_TESTS) { 197 CHECK(!instance_); 198 instance_ = this; 199 printer_.reset(new WebKitTestResultPrinter(&std::cout, &std::cerr)); 200 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEncodeBinary)) 201 printer_->set_encode_binary_data(true); 202 registrar_.Add(this, 203 NOTIFICATION_RENDERER_PROCESS_CREATED, 204 NotificationService::AllSources()); 205 GpuDataManager::GetInstance()->AddObserver(this); 206 ResetAfterLayoutTest(); 207 } 208 209 WebKitTestController::~WebKitTestController() { 210 DCHECK(CalledOnValidThread()); 211 CHECK(instance_ == this); 212 CHECK(test_phase_ == BETWEEN_TESTS); 213 GpuDataManager::GetInstance()->RemoveObserver(this); 214 DiscardMainWindow(); 215 instance_ = NULL; 216 } 217 218 bool WebKitTestController::PrepareForLayoutTest( 219 const GURL& test_url, 220 const base::FilePath& current_working_directory, 221 bool enable_pixel_dumping, 222 const std::string& expected_pixel_hash) { 223 DCHECK(CalledOnValidThread()); 224 test_phase_ = DURING_TEST; 225 current_working_directory_ = current_working_directory; 226 enable_pixel_dumping_ = enable_pixel_dumping; 227 expected_pixel_hash_ = expected_pixel_hash; 228 test_url_ = test_url; 229 printer_->reset(); 230 ShellBrowserContext* browser_context = 231 ShellContentBrowserClient::Get()->browser_context(); 232 if (test_url.spec().find("compositing/") != std::string::npos) 233 is_compositing_test_ = true; 234 initial_size_ = gfx::Size( 235 Shell::kDefaultTestWindowWidthDip, Shell::kDefaultTestWindowHeightDip); 236 // The W3C SVG layout tests use a different size than the other layout tests. 237 if (test_url.spec().find("W3C-SVG-1.1") != std::string::npos) 238 initial_size_ = gfx::Size(kTestSVGWindowWidthDip, kTestSVGWindowHeightDip); 239 if (!main_window_) { 240 main_window_ = content::Shell::CreateNewWindow( 241 browser_context, 242 GURL(), 243 NULL, 244 MSG_ROUTING_NONE, 245 initial_size_); 246 WebContentsObserver::Observe(main_window_->web_contents()); 247 send_configuration_to_next_host_ = true; 248 current_pid_ = base::kNullProcessId; 249 main_window_->LoadURL(test_url); 250 } else { 251 #if (defined(OS_WIN) && !defined(USE_AURA)) || \ 252 defined(TOOLKIT_GTK) || defined(OS_MACOSX) 253 // Shell::SizeTo is not implemented on all platforms. 254 main_window_->SizeTo(initial_size_.width(), initial_size_.height()); 255 #endif 256 main_window_->web_contents()->GetRenderViewHost()->GetView() 257 ->SetSize(initial_size_); 258 main_window_->web_contents()->GetRenderViewHost()->WasResized(); 259 RenderViewHost* render_view_host = 260 main_window_->web_contents()->GetRenderViewHost(); 261 WebPreferences prefs = render_view_host->GetWebkitPreferences(); 262 OverrideWebkitPrefs(&prefs); 263 render_view_host->UpdateWebkitPreferences(prefs); 264 SendTestConfiguration(); 265 266 NavigationController::LoadURLParams params(test_url); 267 params.transition_type = PageTransitionFromInt( 268 PAGE_TRANSITION_TYPED | PAGE_TRANSITION_FROM_ADDRESS_BAR); 269 params.should_clear_history_list = true; 270 main_window_->web_contents()->GetController().LoadURLWithParams(params); 271 main_window_->web_contents()->GetView()->Focus(); 272 } 273 main_window_->web_contents()->GetRenderViewHost()->SetActive(true); 274 main_window_->web_contents()->GetRenderViewHost()->Focus(); 275 return true; 276 } 277 278 bool WebKitTestController::ResetAfterLayoutTest() { 279 DCHECK(CalledOnValidThread()); 280 printer_->PrintTextFooter(); 281 printer_->PrintImageFooter(); 282 send_configuration_to_next_host_ = false; 283 test_phase_ = BETWEEN_TESTS; 284 is_compositing_test_ = false; 285 enable_pixel_dumping_ = false; 286 expected_pixel_hash_.clear(); 287 test_url_ = GURL(); 288 prefs_ = WebPreferences(); 289 should_override_prefs_ = false; 290 return true; 291 } 292 293 void WebKitTestController::SetTempPath(const base::FilePath& temp_path) { 294 temp_path_ = temp_path; 295 } 296 297 void WebKitTestController::RendererUnresponsive() { 298 DCHECK(CalledOnValidThread()); 299 LOG(WARNING) << "renderer unresponsive"; 300 } 301 302 void WebKitTestController::WorkerCrashed() { 303 DCHECK(CalledOnValidThread()); 304 printer_->AddErrorMessage("#CRASHED - worker"); 305 DiscardMainWindow(); 306 } 307 308 void WebKitTestController::OverrideWebkitPrefs(WebPreferences* prefs) { 309 if (should_override_prefs_) { 310 *prefs = prefs_; 311 } else { 312 ApplyLayoutTestDefaultPreferences(prefs); 313 if (is_compositing_test_) { 314 CommandLine& command_line = *CommandLine::ForCurrentProcess(); 315 if (!command_line.HasSwitch(switches::kEnableSoftwareCompositing)) 316 prefs->accelerated_2d_canvas_enabled = true; 317 prefs->accelerated_compositing_for_video_enabled = true; 318 prefs->mock_scrollbars_enabled = true; 319 } 320 } 321 } 322 323 void WebKitTestController::OpenURL(const GURL& url) { 324 if (test_phase_ != DURING_TEST) 325 return; 326 327 Shell::CreateNewWindow(main_window_->web_contents()->GetBrowserContext(), 328 url, 329 main_window_->web_contents()->GetSiteInstance(), 330 MSG_ROUTING_NONE, 331 gfx::Size()); 332 } 333 334 void WebKitTestController::TestFinishedInSecondaryWindow() { 335 RenderViewHost* render_view_host = 336 main_window_->web_contents()->GetRenderViewHost(); 337 render_view_host->Send( 338 new ShellViewMsg_NotifyDone(render_view_host->GetRoutingID())); 339 } 340 341 bool WebKitTestController::IsMainWindow(WebContents* web_contents) const { 342 return main_window_ && web_contents == main_window_->web_contents(); 343 } 344 345 bool WebKitTestController::OnMessageReceived(const IPC::Message& message) { 346 DCHECK(CalledOnValidThread()); 347 bool handled = true; 348 IPC_BEGIN_MESSAGE_MAP(WebKitTestController, message) 349 IPC_MESSAGE_HANDLER(ShellViewHostMsg_PrintMessage, OnPrintMessage) 350 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TextDump, OnTextDump) 351 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ImageDump, OnImageDump) 352 IPC_MESSAGE_HANDLER(ShellViewHostMsg_AudioDump, OnAudioDump) 353 IPC_MESSAGE_HANDLER(ShellViewHostMsg_OverridePreferences, 354 OnOverridePreferences) 355 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TestFinished, OnTestFinished) 356 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ShowDevTools, OnShowDevTools) 357 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CloseDevTools, OnCloseDevTools) 358 IPC_MESSAGE_HANDLER(ShellViewHostMsg_GoToOffset, OnGoToOffset) 359 IPC_MESSAGE_HANDLER(ShellViewHostMsg_Reload, OnReload) 360 IPC_MESSAGE_HANDLER(ShellViewHostMsg_LoadURLForFrame, OnLoadURLForFrame) 361 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CaptureSessionHistory, 362 OnCaptureSessionHistory) 363 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CloseRemainingWindows, 364 OnCloseRemainingWindows) 365 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ResetDone, OnResetDone) 366 IPC_MESSAGE_UNHANDLED(handled = false) 367 IPC_END_MESSAGE_MAP() 368 369 return handled; 370 } 371 372 void WebKitTestController::PluginCrashed(const base::FilePath& plugin_path, 373 base::ProcessId plugin_pid) { 374 DCHECK(CalledOnValidThread()); 375 printer_->AddErrorMessage( 376 base::StringPrintf("#CRASHED - plugin (pid %d)", plugin_pid)); 377 base::MessageLoop::current()->PostTask( 378 FROM_HERE, 379 base::Bind(base::IgnoreResult(&WebKitTestController::DiscardMainWindow), 380 base::Unretained(this))); 381 } 382 383 void WebKitTestController::RenderViewCreated(RenderViewHost* render_view_host) { 384 DCHECK(CalledOnValidThread()); 385 // Might be kNullProcessHandle, in which case we will receive a notification 386 // later when the RenderProcessHost was created. 387 if (render_view_host->GetProcess()->GetHandle() != base::kNullProcessHandle) 388 current_pid_ = base::GetProcId(render_view_host->GetProcess()->GetHandle()); 389 if (!send_configuration_to_next_host_) 390 return; 391 send_configuration_to_next_host_ = false; 392 SendTestConfiguration(); 393 } 394 395 void WebKitTestController::RenderProcessGone(base::TerminationStatus status) { 396 DCHECK(CalledOnValidThread()); 397 if (current_pid_ != base::kNullProcessId) { 398 printer_->AddErrorMessage(std::string("#CRASHED - renderer (pid ") + 399 base::IntToString(current_pid_) + ")"); 400 } else { 401 printer_->AddErrorMessage("#CRASHED - renderer"); 402 } 403 DiscardMainWindow(); 404 } 405 406 void WebKitTestController::WebContentsDestroyed(WebContents* web_contents) { 407 DCHECK(CalledOnValidThread()); 408 printer_->AddErrorMessage("FAIL: main window was destroyed"); 409 DiscardMainWindow(); 410 } 411 412 void WebKitTestController::Observe(int type, 413 const NotificationSource& source, 414 const NotificationDetails& details) { 415 DCHECK(CalledOnValidThread()); 416 switch (type) { 417 case NOTIFICATION_RENDERER_PROCESS_CREATED: { 418 if (!main_window_) 419 return; 420 RenderViewHost* render_view_host = 421 main_window_->web_contents()->GetRenderViewHost(); 422 if (!render_view_host) 423 return; 424 RenderProcessHost* render_process_host = 425 Source<RenderProcessHost>(source).ptr(); 426 if (render_process_host != render_view_host->GetProcess()) 427 return; 428 current_pid_ = base::GetProcId(render_process_host->GetHandle()); 429 break; 430 } 431 default: 432 NOTREACHED(); 433 } 434 } 435 436 void WebKitTestController::OnGpuProcessCrashed( 437 base::TerminationStatus exit_code) { 438 DCHECK(CalledOnValidThread()); 439 printer_->AddErrorMessage("#CRASHED - gpu"); 440 DiscardMainWindow(); 441 } 442 443 void WebKitTestController::TimeoutHandler() { 444 DCHECK(CalledOnValidThread()); 445 printer_->AddErrorMessage( 446 "FAIL: Timed out waiting for notifyDone to be called"); 447 DiscardMainWindow(); 448 } 449 450 void WebKitTestController::DiscardMainWindow() { 451 // If we're running a test, we need to close all windows and exit the message 452 // loop. Otherwise, we're already outside of the message loop, and we just 453 // discard the main window. 454 WebContentsObserver::Observe(NULL); 455 if (test_phase_ != BETWEEN_TESTS) { 456 Shell::CloseAllWindows(); 457 base::MessageLoop::current()->PostTask(FROM_HERE, 458 base::MessageLoop::QuitClosure()); 459 test_phase_ = CLEAN_UP; 460 } else if (main_window_) { 461 main_window_->Close(); 462 } 463 main_window_ = NULL; 464 current_pid_ = base::kNullProcessId; 465 } 466 467 void WebKitTestController::SendTestConfiguration() { 468 RenderViewHost* render_view_host = 469 main_window_->web_contents()->GetRenderViewHost(); 470 ShellTestConfiguration params; 471 params.current_working_directory = current_working_directory_; 472 params.temp_path = temp_path_; 473 params.test_url = test_url_; 474 params.enable_pixel_dumping = enable_pixel_dumping_; 475 params.allow_external_pages = CommandLine::ForCurrentProcess()->HasSwitch( 476 switches::kAllowExternalPages); 477 params.expected_pixel_hash = expected_pixel_hash_; 478 params.initial_size = initial_size_; 479 render_view_host->Send(new ShellViewMsg_SetTestConfiguration( 480 render_view_host->GetRoutingID(), params)); 481 } 482 483 void WebKitTestController::OnTestFinished() { 484 test_phase_ = CLEAN_UP; 485 if (!printer_->output_finished()) 486 printer_->PrintImageFooter(); 487 RenderViewHost* render_view_host = 488 main_window_->web_contents()->GetRenderViewHost(); 489 base::MessageLoop::current()->PostTask( 490 FROM_HERE, 491 base::Bind(base::IgnoreResult(&WebKitTestController::Send), 492 base::Unretained(this), 493 new ShellViewMsg_Reset(render_view_host->GetRoutingID()))); 494 } 495 496 void WebKitTestController::OnImageDump( 497 const std::string& actual_pixel_hash, 498 const SkBitmap& image) { 499 SkAutoLockPixels image_lock(image); 500 501 printer_->PrintImageHeader(actual_pixel_hash, expected_pixel_hash_); 502 503 // Only encode and dump the png if the hashes don't match. Encoding the 504 // image is really expensive. 505 if (actual_pixel_hash != expected_pixel_hash_) { 506 std::vector<unsigned char> png; 507 508 // Only the expected PNGs for Mac have a valid alpha channel. 509 #if defined(OS_MACOSX) 510 bool discard_transparency = false; 511 #else 512 bool discard_transparency = true; 513 #endif 514 515 std::vector<gfx::PNGCodec::Comment> comments; 516 comments.push_back(gfx::PNGCodec::Comment("checksum", actual_pixel_hash)); 517 bool success = gfx::PNGCodec::Encode( 518 static_cast<const unsigned char*>(image.getPixels()), 519 gfx::PNGCodec::FORMAT_BGRA, 520 gfx::Size(image.width(), image.height()), 521 static_cast<int>(image.rowBytes()), 522 discard_transparency, 523 comments, 524 &png); 525 if (success) 526 printer_->PrintImageBlock(png); 527 } 528 printer_->PrintImageFooter(); 529 } 530 531 void WebKitTestController::OnAudioDump(const std::vector<unsigned char>& dump) { 532 printer_->PrintAudioHeader(); 533 printer_->PrintAudioBlock(dump); 534 printer_->PrintAudioFooter(); 535 } 536 537 void WebKitTestController::OnTextDump(const std::string& dump) { 538 printer_->PrintTextHeader(); 539 printer_->PrintTextBlock(dump); 540 printer_->PrintTextFooter(); 541 } 542 543 void WebKitTestController::OnPrintMessage(const std::string& message) { 544 printer_->AddMessageRaw(message); 545 } 546 547 void WebKitTestController::OnOverridePreferences(const WebPreferences& prefs) { 548 should_override_prefs_ = true; 549 prefs_ = prefs; 550 } 551 552 void WebKitTestController::OnShowDevTools() { 553 main_window_->ShowDevTools(); 554 } 555 556 void WebKitTestController::OnCloseDevTools() { 557 main_window_->CloseDevTools(); 558 } 559 560 void WebKitTestController::OnGoToOffset(int offset) { 561 main_window_->GoBackOrForward(offset); 562 } 563 564 void WebKitTestController::OnReload() { 565 main_window_->Reload(); 566 } 567 568 void WebKitTestController::OnLoadURLForFrame(const GURL& url, 569 const std::string& frame_name) { 570 main_window_->LoadURLForFrame(url, frame_name); 571 } 572 573 void WebKitTestController::OnCaptureSessionHistory() { 574 std::vector<int> routing_ids; 575 std::vector<std::vector<PageState> > session_histories; 576 std::vector<unsigned> current_entry_indexes; 577 578 RenderViewHost* render_view_host = 579 main_window_->web_contents()->GetRenderViewHost(); 580 581 for (std::vector<Shell*>::iterator window = Shell::windows().begin(); 582 window != Shell::windows().end(); 583 ++window) { 584 WebContents* web_contents = (*window)->web_contents(); 585 // Only capture the history from windows in the same process as the main 586 // window. During layout tests, we only use two processes when an 587 // devtools window is open. This should not happen during history navigation 588 // tests. 589 if (render_view_host->GetProcess() != 590 web_contents->GetRenderViewHost()->GetProcess()) { 591 NOTREACHED(); 592 continue; 593 } 594 routing_ids.push_back(web_contents->GetRenderViewHost()->GetRoutingID()); 595 current_entry_indexes.push_back( 596 web_contents->GetController().GetCurrentEntryIndex()); 597 std::vector<PageState> history; 598 for (int entry = 0; entry < web_contents->GetController().GetEntryCount(); 599 ++entry) { 600 PageState state = web_contents->GetController().GetEntryAtIndex(entry)-> 601 GetPageState(); 602 if (!state.IsValid()) { 603 state = PageState::CreateFromURL( 604 web_contents->GetController().GetEntryAtIndex(entry)->GetURL()); 605 } 606 history.push_back(state); 607 } 608 session_histories.push_back(history); 609 } 610 611 Send(new ShellViewMsg_SessionHistory(render_view_host->GetRoutingID(), 612 routing_ids, 613 session_histories, 614 current_entry_indexes)); 615 } 616 617 void WebKitTestController::OnCloseRemainingWindows() { 618 DevToolsManager::GetInstance()->CloseAllClientHosts(); 619 std::vector<Shell*> open_windows(Shell::windows()); 620 for (size_t i = 0; i < open_windows.size(); ++i) { 621 if (open_windows[i] != main_window_) 622 open_windows[i]->Close(); 623 } 624 base::MessageLoop::current()->RunUntilIdle(); 625 } 626 627 void WebKitTestController::OnResetDone() { 628 base::MessageLoop::current()->PostTask(FROM_HERE, 629 base::MessageLoop::QuitClosure()); 630 } 631 632 } // namespace content 633