1 // Copyright 2013 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.
5 #include "content/shell/browser/webkit_test_controller.h"
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/dom_storage_context.h"
17 #include "content/public/browser/gpu_data_manager.h"
18 #include "content/public/browser/navigation_controller.h"
19 #include "content/public/browser/navigation_entry.h"
20 #include "content/public/browser/notification_service.h"
21 #include "content/public/browser/notification_types.h"
22 #include "content/public/browser/render_process_host.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/render_widget_host_view.h"
25 #include "content/public/browser/storage_partition.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/common/content_switches.h"
28 #include "content/public/common/url_constants.h"
29 #include "content/shell/browser/shell.h"
30 #include "content/shell/browser/shell_browser_context.h"
31 #include "content/shell/browser/shell_content_browser_client.h"
32 #include "content/shell/browser/shell_devtools_frontend.h"
33 #include "content/shell/common/shell_messages.h"
34 #include "content/shell/common/shell_switches.h"
35 #include "content/shell/common/webkit_test_helpers.h"
36 #include "ui/gfx/codec/png_codec.h"
40 const int kTestSVGWindowWidthDip = 480;
41 const int kTestSVGWindowHeightDip = 360;
43 // WebKitTestResultPrinter ----------------------------------------------------
45 WebKitTestResultPrinter::WebKitTestResultPrinter(
46 std::ostream* output, std::ostream* error)
47 : state_(DURING_TEST),
48 capture_text_only_(false),
49 encode_binary_data_(false),
54 WebKitTestResultPrinter::~WebKitTestResultPrinter() {
57 void WebKitTestResultPrinter::PrintTextHeader() {
58 if (state_ != DURING_TEST)
60 if (!capture_text_only_)
61 *output_ << "Content-Type: text/plain\n";
62 state_ = IN_TEXT_BLOCK;
65 void WebKitTestResultPrinter::PrintTextBlock(const std::string& block) {
66 if (state_ != IN_TEXT_BLOCK)
71 void WebKitTestResultPrinter::PrintTextFooter() {
72 if (state_ != IN_TEXT_BLOCK)
74 if (!capture_text_only_) {
78 state_ = IN_IMAGE_BLOCK;
81 void WebKitTestResultPrinter::PrintImageHeader(
82 const std::string& actual_hash,
83 const std::string& expected_hash) {
84 if (state_ != IN_IMAGE_BLOCK || capture_text_only_)
86 *output_ << "\nActualHash: " << actual_hash << "\n";
87 if (!expected_hash.empty())
88 *output_ << "\nExpectedHash: " << expected_hash << "\n";
91 void WebKitTestResultPrinter::PrintImageBlock(
92 const std::vector<unsigned char>& png_image) {
93 if (state_ != IN_IMAGE_BLOCK || capture_text_only_)
95 *output_ << "Content-Type: image/png\n";
96 if (encode_binary_data_) {
97 PrintEncodedBinaryData(png_image);
101 *output_ << "Content-Length: " << png_image.size() << "\n";
103 reinterpret_cast<const char*>(&png_image[0]), png_image.size());
106 void WebKitTestResultPrinter::PrintImageFooter() {
107 if (state_ != IN_IMAGE_BLOCK)
109 if (!capture_text_only_) {
110 *output_ << "#EOF\n";
116 void WebKitTestResultPrinter::PrintAudioHeader() {
117 DCHECK_EQ(state_, DURING_TEST);
118 if (!capture_text_only_)
119 *output_ << "Content-Type: audio/wav\n";
120 state_ = IN_AUDIO_BLOCK;
123 void WebKitTestResultPrinter::PrintAudioBlock(
124 const std::vector<unsigned char>& audio_data) {
125 if (state_ != IN_AUDIO_BLOCK || capture_text_only_)
127 if (encode_binary_data_) {
128 PrintEncodedBinaryData(audio_data);
132 *output_ << "Content-Length: " << audio_data.size() << "\n";
134 reinterpret_cast<const char*>(&audio_data[0]), audio_data.size());
137 void WebKitTestResultPrinter::PrintAudioFooter() {
138 if (state_ != IN_AUDIO_BLOCK)
140 if (!capture_text_only_) {
141 *output_ << "#EOF\n";
144 state_ = IN_IMAGE_BLOCK;
147 void WebKitTestResultPrinter::AddMessage(const std::string& message) {
148 AddMessageRaw(message + "\n");
151 void WebKitTestResultPrinter::AddMessageRaw(const std::string& message) {
152 if (state_ != DURING_TEST)
157 void WebKitTestResultPrinter::AddErrorMessage(const std::string& message) {
158 if (!capture_text_only_)
159 *error_ << message << "\n";
160 if (state_ != DURING_TEST)
163 *output_ << message << "\n";
168 void WebKitTestResultPrinter::PrintEncodedBinaryData(
169 const std::vector<unsigned char>& data) {
170 *output_ << "Content-Transfer-Encoding: base64\n";
172 std::string data_base64;
174 base::StringPiece(reinterpret_cast<const char*>(&data[0]), data.size()),
177 *output_ << "Content-Length: " << data_base64.length() << "\n";
178 output_->write(data_base64.c_str(), data_base64.length());
181 void WebKitTestResultPrinter::CloseStderr() {
182 if (state_ != AFTER_TEST)
184 if (!capture_text_only_) {
191 // WebKitTestController -------------------------------------------------------
193 WebKitTestController* WebKitTestController::instance_ = NULL;
196 WebKitTestController* WebKitTestController::Get() {
201 WebKitTestController::WebKitTestController()
202 : main_window_(NULL),
203 test_phase_(BETWEEN_TESTS),
204 is_leak_detection_enabled_(CommandLine::ForCurrentProcess()->HasSwitch(
205 switches::kEnableLeakDetection)) {
208 printer_.reset(new WebKitTestResultPrinter(&std::cout, &std::cerr));
209 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEncodeBinary))
210 printer_->set_encode_binary_data(true);
212 NOTIFICATION_RENDERER_PROCESS_CREATED,
213 NotificationService::AllSources());
214 GpuDataManager::GetInstance()->AddObserver(this);
215 ResetAfterLayoutTest();
218 WebKitTestController::~WebKitTestController() {
219 DCHECK(CalledOnValidThread());
220 CHECK(instance_ == this);
221 CHECK(test_phase_ == BETWEEN_TESTS);
222 GpuDataManager::GetInstance()->RemoveObserver(this);
227 bool WebKitTestController::PrepareForLayoutTest(
228 const GURL& test_url,
229 const base::FilePath& current_working_directory,
230 bool enable_pixel_dumping,
231 const std::string& expected_pixel_hash) {
232 DCHECK(CalledOnValidThread());
233 test_phase_ = DURING_TEST;
234 current_working_directory_ = current_working_directory;
235 enable_pixel_dumping_ = enable_pixel_dumping;
236 expected_pixel_hash_ = expected_pixel_hash;
237 test_url_ = test_url;
239 ShellBrowserContext* browser_context =
240 ShellContentBrowserClient::Get()->browser_context();
241 if (test_url.spec().find("compositing/") != std::string::npos)
242 is_compositing_test_ = true;
243 initial_size_ = gfx::Size(
244 Shell::kDefaultTestWindowWidthDip, Shell::kDefaultTestWindowHeightDip);
245 // The W3C SVG layout tests use a different size than the other layout tests.
246 if (test_url.spec().find("W3C-SVG-1.1") != std::string::npos)
247 initial_size_ = gfx::Size(kTestSVGWindowWidthDip, kTestSVGWindowHeightDip);
249 main_window_ = content::Shell::CreateNewWindow(
255 WebContentsObserver::Observe(main_window_->web_contents());
256 send_configuration_to_next_host_ = true;
257 current_pid_ = base::kNullProcessId;
258 main_window_->LoadURL(test_url);
260 #if defined(OS_MACOSX)
261 // Shell::SizeTo is not implemented on all platforms.
262 main_window_->SizeTo(initial_size_);
264 main_window_->web_contents()->GetRenderViewHost()->GetView()
265 ->SetSize(initial_size_);
266 main_window_->web_contents()->GetRenderViewHost()->WasResized();
267 RenderViewHost* render_view_host =
268 main_window_->web_contents()->GetRenderViewHost();
269 WebPreferences prefs = render_view_host->GetWebkitPreferences();
270 OverrideWebkitPrefs(&prefs);
271 render_view_host->UpdateWebkitPreferences(prefs);
272 SendTestConfiguration();
274 NavigationController::LoadURLParams params(test_url);
275 params.transition_type = PageTransitionFromInt(
276 PAGE_TRANSITION_TYPED | PAGE_TRANSITION_FROM_ADDRESS_BAR);
277 params.should_clear_history_list = true;
278 main_window_->web_contents()->GetController().LoadURLWithParams(params);
279 main_window_->web_contents()->Focus();
281 main_window_->web_contents()->GetRenderViewHost()->SetActive(true);
282 main_window_->web_contents()->GetRenderViewHost()->Focus();
286 bool WebKitTestController::ResetAfterLayoutTest() {
287 DCHECK(CalledOnValidThread());
288 printer_->PrintTextFooter();
289 printer_->PrintImageFooter();
290 printer_->CloseStderr();
291 send_configuration_to_next_host_ = false;
292 test_phase_ = BETWEEN_TESTS;
293 is_compositing_test_ = false;
294 enable_pixel_dumping_ = false;
295 expected_pixel_hash_.clear();
297 prefs_ = WebPreferences();
298 should_override_prefs_ = false;
300 #if defined(OS_ANDROID)
301 // Re-using the shell's main window on Android causes issues with networking
302 // requests never succeeding. See http://crbug.com/277652.
308 void WebKitTestController::SetTempPath(const base::FilePath& temp_path) {
309 temp_path_ = temp_path;
312 void WebKitTestController::RendererUnresponsive() {
313 DCHECK(CalledOnValidThread());
314 LOG(WARNING) << "renderer unresponsive";
317 void WebKitTestController::WorkerCrashed() {
318 DCHECK(CalledOnValidThread());
319 printer_->AddErrorMessage("#CRASHED - worker");
323 void WebKitTestController::OverrideWebkitPrefs(WebPreferences* prefs) {
324 if (should_override_prefs_) {
327 ApplyLayoutTestDefaultPreferences(prefs);
328 if (is_compositing_test_) {
329 CommandLine& command_line = *CommandLine::ForCurrentProcess();
330 if (!command_line.HasSwitch(switches::kDisableGpu))
331 prefs->accelerated_2d_canvas_enabled = true;
332 prefs->accelerated_compositing_for_video_enabled = true;
333 prefs->mock_scrollbars_enabled = true;
338 void WebKitTestController::OpenURL(const GURL& url) {
339 if (test_phase_ != DURING_TEST)
342 Shell::CreateNewWindow(main_window_->web_contents()->GetBrowserContext(),
344 main_window_->web_contents()->GetSiteInstance(),
349 void WebKitTestController::TestFinishedInSecondaryWindow() {
350 RenderViewHost* render_view_host =
351 main_window_->web_contents()->GetRenderViewHost();
352 render_view_host->Send(
353 new ShellViewMsg_NotifyDone(render_view_host->GetRoutingID()));
356 bool WebKitTestController::IsMainWindow(WebContents* web_contents) const {
357 return main_window_ && web_contents == main_window_->web_contents();
360 bool WebKitTestController::OnMessageReceived(const IPC::Message& message) {
361 DCHECK(CalledOnValidThread());
363 IPC_BEGIN_MESSAGE_MAP(WebKitTestController, message)
364 IPC_MESSAGE_HANDLER(ShellViewHostMsg_PrintMessage, OnPrintMessage)
365 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TextDump, OnTextDump)
366 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ImageDump, OnImageDump)
367 IPC_MESSAGE_HANDLER(ShellViewHostMsg_AudioDump, OnAudioDump)
368 IPC_MESSAGE_HANDLER(ShellViewHostMsg_OverridePreferences,
369 OnOverridePreferences)
370 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TestFinished, OnTestFinished)
371 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ClearDevToolsLocalStorage,
372 OnClearDevToolsLocalStorage)
373 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ShowDevTools, OnShowDevTools)
374 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CloseDevTools, OnCloseDevTools)
375 IPC_MESSAGE_HANDLER(ShellViewHostMsg_GoToOffset, OnGoToOffset)
376 IPC_MESSAGE_HANDLER(ShellViewHostMsg_Reload, OnReload)
377 IPC_MESSAGE_HANDLER(ShellViewHostMsg_LoadURLForFrame, OnLoadURLForFrame)
378 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CaptureSessionHistory,
379 OnCaptureSessionHistory)
380 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CloseRemainingWindows,
381 OnCloseRemainingWindows)
382 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ResetDone, OnResetDone)
383 IPC_MESSAGE_HANDLER(ShellViewHostMsg_LeakDetectionDone, OnLeakDetectionDone)
384 IPC_MESSAGE_UNHANDLED(handled = false)
385 IPC_END_MESSAGE_MAP()
390 void WebKitTestController::PluginCrashed(const base::FilePath& plugin_path,
391 base::ProcessId plugin_pid) {
392 DCHECK(CalledOnValidThread());
393 printer_->AddErrorMessage(
394 base::StringPrintf("#CRASHED - plugin (pid %d)", plugin_pid));
395 base::MessageLoop::current()->PostTask(
397 base::Bind(base::IgnoreResult(&WebKitTestController::DiscardMainWindow),
398 base::Unretained(this)));
401 void WebKitTestController::RenderViewCreated(RenderViewHost* render_view_host) {
402 DCHECK(CalledOnValidThread());
403 // Might be kNullProcessHandle, in which case we will receive a notification
404 // later when the RenderProcessHost was created.
405 if (render_view_host->GetProcess()->GetHandle() != base::kNullProcessHandle)
406 current_pid_ = base::GetProcId(render_view_host->GetProcess()->GetHandle());
407 if (!send_configuration_to_next_host_)
409 send_configuration_to_next_host_ = false;
410 SendTestConfiguration();
413 void WebKitTestController::RenderProcessGone(base::TerminationStatus status) {
414 DCHECK(CalledOnValidThread());
415 if (current_pid_ != base::kNullProcessId) {
416 printer_->AddErrorMessage(std::string("#CRASHED - renderer (pid ") +
417 base::IntToString(current_pid_) + ")");
419 printer_->AddErrorMessage("#CRASHED - renderer");
424 void WebKitTestController::WebContentsDestroyed() {
425 DCHECK(CalledOnValidThread());
426 printer_->AddErrorMessage("FAIL: main window was destroyed");
430 void WebKitTestController::Observe(int type,
431 const NotificationSource& source,
432 const NotificationDetails& details) {
433 DCHECK(CalledOnValidThread());
435 case NOTIFICATION_RENDERER_PROCESS_CREATED: {
438 RenderViewHost* render_view_host =
439 main_window_->web_contents()->GetRenderViewHost();
440 if (!render_view_host)
442 RenderProcessHost* render_process_host =
443 Source<RenderProcessHost>(source).ptr();
444 if (render_process_host != render_view_host->GetProcess())
446 current_pid_ = base::GetProcId(render_process_host->GetHandle());
454 void WebKitTestController::OnGpuProcessCrashed(
455 base::TerminationStatus exit_code) {
456 DCHECK(CalledOnValidThread());
457 printer_->AddErrorMessage("#CRASHED - gpu");
461 void WebKitTestController::DiscardMainWindow() {
462 // If we're running a test, we need to close all windows and exit the message
463 // loop. Otherwise, we're already outside of the message loop, and we just
464 // discard the main window.
465 WebContentsObserver::Observe(NULL);
466 if (test_phase_ != BETWEEN_TESTS) {
467 Shell::CloseAllWindows();
468 base::MessageLoop::current()->PostTask(FROM_HERE,
469 base::MessageLoop::QuitClosure());
470 test_phase_ = CLEAN_UP;
471 } else if (main_window_) {
472 main_window_->Close();
475 current_pid_ = base::kNullProcessId;
478 void WebKitTestController::SendTestConfiguration() {
479 RenderViewHost* render_view_host =
480 main_window_->web_contents()->GetRenderViewHost();
481 ShellTestConfiguration params;
482 params.current_working_directory = current_working_directory_;
483 params.temp_path = temp_path_;
484 params.test_url = test_url_;
485 params.enable_pixel_dumping = enable_pixel_dumping_;
486 params.allow_external_pages = CommandLine::ForCurrentProcess()->HasSwitch(
487 switches::kAllowExternalPages);
488 params.expected_pixel_hash = expected_pixel_hash_;
489 params.initial_size = initial_size_;
490 render_view_host->Send(new ShellViewMsg_SetTestConfiguration(
491 render_view_host->GetRoutingID(), params));
494 void WebKitTestController::OnTestFinished() {
495 test_phase_ = CLEAN_UP;
496 if (!printer_->output_finished())
497 printer_->PrintImageFooter();
498 RenderViewHost* render_view_host =
499 main_window_->web_contents()->GetRenderViewHost();
500 base::MessageLoop::current()->PostTask(
502 base::Bind(base::IgnoreResult(&WebKitTestController::Send),
503 base::Unretained(this),
504 new ShellViewMsg_Reset(render_view_host->GetRoutingID())));
507 void WebKitTestController::OnImageDump(
508 const std::string& actual_pixel_hash,
509 const SkBitmap& image) {
510 SkAutoLockPixels image_lock(image);
512 printer_->PrintImageHeader(actual_pixel_hash, expected_pixel_hash_);
514 // Only encode and dump the png if the hashes don't match. Encoding the
515 // image is really expensive.
516 if (actual_pixel_hash != expected_pixel_hash_) {
517 std::vector<unsigned char> png;
519 // Only the expected PNGs for Mac have a valid alpha channel.
520 #if defined(OS_MACOSX)
521 bool discard_transparency = false;
523 bool discard_transparency = true;
525 if (CommandLine::ForCurrentProcess()->HasSwitch(
526 switches::kEnableOverlayFullscreenVideo))
527 discard_transparency = false;
529 std::vector<gfx::PNGCodec::Comment> comments;
530 comments.push_back(gfx::PNGCodec::Comment("checksum", actual_pixel_hash));
531 bool success = gfx::PNGCodec::Encode(
532 static_cast<const unsigned char*>(image.getPixels()),
533 gfx::PNGCodec::FORMAT_BGRA,
534 gfx::Size(image.width(), image.height()),
535 static_cast<int>(image.rowBytes()),
536 discard_transparency,
540 printer_->PrintImageBlock(png);
542 printer_->PrintImageFooter();
545 void WebKitTestController::OnAudioDump(const std::vector<unsigned char>& dump) {
546 printer_->PrintAudioHeader();
547 printer_->PrintAudioBlock(dump);
548 printer_->PrintAudioFooter();
551 void WebKitTestController::OnTextDump(const std::string& dump) {
552 printer_->PrintTextHeader();
553 printer_->PrintTextBlock(dump);
554 printer_->PrintTextFooter();
557 void WebKitTestController::OnPrintMessage(const std::string& message) {
558 printer_->AddMessageRaw(message);
561 void WebKitTestController::OnOverridePreferences(const WebPreferences& prefs) {
562 should_override_prefs_ = true;
566 void WebKitTestController::OnClearDevToolsLocalStorage() {
567 ShellBrowserContext* browser_context =
568 ShellContentBrowserClient::Get()->browser_context();
569 StoragePartition* storage_partition =
570 BrowserContext::GetStoragePartition(browser_context, NULL);
571 storage_partition->GetDOMStorageContext()->DeleteLocalStorage(
572 content::GetDevToolsPathAsURL("", "").GetOrigin());
575 void WebKitTestController::OnShowDevTools(const std::string& settings,
576 const std::string& frontend_url) {
577 main_window_->ShowDevToolsForTest(settings, frontend_url);
580 void WebKitTestController::OnCloseDevTools() {
581 main_window_->CloseDevTools();
584 void WebKitTestController::OnGoToOffset(int offset) {
585 main_window_->GoBackOrForward(offset);
588 void WebKitTestController::OnReload() {
589 main_window_->Reload();
592 void WebKitTestController::OnLoadURLForFrame(const GURL& url,
593 const std::string& frame_name) {
594 main_window_->LoadURLForFrame(url, frame_name);
597 void WebKitTestController::OnCaptureSessionHistory() {
598 std::vector<int> routing_ids;
599 std::vector<std::vector<PageState> > session_histories;
600 std::vector<unsigned> current_entry_indexes;
602 RenderViewHost* render_view_host =
603 main_window_->web_contents()->GetRenderViewHost();
605 for (std::vector<Shell*>::iterator window = Shell::windows().begin();
606 window != Shell::windows().end();
608 WebContents* web_contents = (*window)->web_contents();
609 // Only capture the history from windows in the same process as the main
610 // window. During layout tests, we only use two processes when an
611 // devtools window is open. This should not happen during history navigation
613 if (render_view_host->GetProcess() !=
614 web_contents->GetRenderViewHost()->GetProcess()) {
618 routing_ids.push_back(web_contents->GetRenderViewHost()->GetRoutingID());
619 current_entry_indexes.push_back(
620 web_contents->GetController().GetCurrentEntryIndex());
621 std::vector<PageState> history;
622 for (int entry = 0; entry < web_contents->GetController().GetEntryCount();
624 PageState state = web_contents->GetController().GetEntryAtIndex(entry)->
626 if (!state.IsValid()) {
627 state = PageState::CreateFromURL(
628 web_contents->GetController().GetEntryAtIndex(entry)->GetURL());
630 history.push_back(state);
632 session_histories.push_back(history);
635 Send(new ShellViewMsg_SessionHistory(render_view_host->GetRoutingID(),
638 current_entry_indexes));
641 void WebKitTestController::OnCloseRemainingWindows() {
642 DevToolsManager::GetInstance()->CloseAllClientHosts();
643 std::vector<Shell*> open_windows(Shell::windows());
644 for (size_t i = 0; i < open_windows.size(); ++i) {
645 if (open_windows[i] != main_window_)
646 open_windows[i]->Close();
648 base::MessageLoop::current()->RunUntilIdle();
651 void WebKitTestController::OnResetDone() {
652 if (is_leak_detection_enabled_) {
653 if (main_window_ && main_window_->web_contents()) {
654 RenderViewHost* render_view_host =
655 main_window_->web_contents()->GetRenderViewHost();
656 render_view_host->Send(
657 new ShellViewMsg_TryLeakDetection(render_view_host->GetRoutingID()));
662 base::MessageLoop::current()->PostTask(FROM_HERE,
663 base::MessageLoop::QuitClosure());
666 void WebKitTestController::OnLeakDetectionDone(
667 const LeakDetectionResult& result) {
668 if (!result.leaked) {
669 base::MessageLoop::current()->PostTask(FROM_HERE,
670 base::MessageLoop::QuitClosure());
674 printer_->AddErrorMessage(
675 base::StringPrintf("#LEAK - renderer pid %d (%s)", current_pid_,
676 result.detail.c_str()));
680 } // namespace content