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/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/browser/shell.h"
28 #include "content/shell/browser/shell_browser_context.h"
29 #include "content/shell/browser/shell_content_browser_client.h"
30 #include "content/shell/common/shell_messages.h"
31 #include "content/shell/common/shell_switches.h"
32 #include "content/shell/common/webkit_test_helpers.h"
33 #include "ui/gfx/codec/png_codec.h"
37 const int kTestSVGWindowWidthDip
= 480;
38 const int kTestSVGWindowHeightDip
= 360;
40 // WebKitTestResultPrinter ----------------------------------------------------
42 WebKitTestResultPrinter::WebKitTestResultPrinter(
43 std::ostream
* output
, std::ostream
* error
)
44 : state_(DURING_TEST
),
45 capture_text_only_(false),
46 encode_binary_data_(false),
51 WebKitTestResultPrinter::~WebKitTestResultPrinter() {
54 void WebKitTestResultPrinter::PrintTextHeader() {
55 if (state_
!= DURING_TEST
)
57 if (!capture_text_only_
)
58 *output_
<< "Content-Type: text/plain\n";
59 state_
= IN_TEXT_BLOCK
;
62 void WebKitTestResultPrinter::PrintTextBlock(const std::string
& block
) {
63 if (state_
!= IN_TEXT_BLOCK
)
68 void WebKitTestResultPrinter::PrintTextFooter() {
69 if (state_
!= IN_TEXT_BLOCK
)
71 if (!capture_text_only_
) {
75 state_
= IN_IMAGE_BLOCK
;
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_
)
83 *output_
<< "\nActualHash: " << actual_hash
<< "\n";
84 if (!expected_hash
.empty())
85 *output_
<< "\nExpectedHash: " << expected_hash
<< "\n";
88 void WebKitTestResultPrinter::PrintImageBlock(
89 const std::vector
<unsigned char>& png_image
) {
90 if (state_
!= IN_IMAGE_BLOCK
|| capture_text_only_
)
92 *output_
<< "Content-Type: image/png\n";
93 if (encode_binary_data_
) {
94 PrintEncodedBinaryData(png_image
);
98 *output_
<< "Content-Length: " << png_image
.size() << "\n";
100 reinterpret_cast<const char*>(&png_image
[0]), png_image
.size());
103 void WebKitTestResultPrinter::PrintImageFooter() {
104 if (state_
!= IN_IMAGE_BLOCK
)
106 if (!capture_text_only_
) {
107 *output_
<< "#EOF\n";
113 void WebKitTestResultPrinter::PrintAudioHeader() {
114 DCHECK_EQ(state_
, DURING_TEST
);
115 if (!capture_text_only_
)
116 *output_
<< "Content-Type: audio/wav\n";
117 state_
= IN_AUDIO_BLOCK
;
120 void WebKitTestResultPrinter::PrintAudioBlock(
121 const std::vector
<unsigned char>& audio_data
) {
122 if (state_
!= IN_AUDIO_BLOCK
|| capture_text_only_
)
124 if (encode_binary_data_
) {
125 PrintEncodedBinaryData(audio_data
);
129 *output_
<< "Content-Length: " << audio_data
.size() << "\n";
131 reinterpret_cast<const char*>(&audio_data
[0]), audio_data
.size());
134 void WebKitTestResultPrinter::PrintAudioFooter() {
135 if (state_
!= IN_AUDIO_BLOCK
)
137 if (!capture_text_only_
) {
138 *output_
<< "#EOF\n";
141 state_
= IN_IMAGE_BLOCK
;
144 void WebKitTestResultPrinter::AddMessage(const std::string
& message
) {
145 AddMessageRaw(message
+ "\n");
148 void WebKitTestResultPrinter::AddMessageRaw(const std::string
& message
) {
149 if (state_
!= DURING_TEST
)
154 void WebKitTestResultPrinter::AddErrorMessage(const std::string
& message
) {
155 if (!capture_text_only_
)
156 *error_
<< message
<< "\n";
157 if (state_
!= DURING_TEST
)
160 *output_
<< message
<< "\n";
165 void WebKitTestResultPrinter::PrintEncodedBinaryData(
166 const std::vector
<unsigned char>& data
) {
167 *output_
<< "Content-Transfer-Encoding: base64\n";
169 std::string data_base64
;
170 const bool success
= base::Base64Encode(
171 base::StringPiece(reinterpret_cast<const char*>(&data
[0]), data
.size()),
175 *output_
<< "Content-Length: " << data_base64
.length() << "\n";
176 output_
->write(data_base64
.c_str(), data_base64
.length());
179 void WebKitTestResultPrinter::CloseStderr() {
180 if (state_
!= AFTER_TEST
)
182 if (!capture_text_only_
) {
189 // WebKitTestController -------------------------------------------------------
191 WebKitTestController
* WebKitTestController::instance_
= NULL
;
194 WebKitTestController
* WebKitTestController::Get() {
199 WebKitTestController::WebKitTestController()
200 : main_window_(NULL
),
201 test_phase_(BETWEEN_TESTS
) {
204 printer_
.reset(new WebKitTestResultPrinter(&std::cout
, &std::cerr
));
205 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEncodeBinary
))
206 printer_
->set_encode_binary_data(true);
208 NOTIFICATION_RENDERER_PROCESS_CREATED
,
209 NotificationService::AllSources());
210 GpuDataManager::GetInstance()->AddObserver(this);
211 ResetAfterLayoutTest();
214 WebKitTestController::~WebKitTestController() {
215 DCHECK(CalledOnValidThread());
216 CHECK(instance_
== this);
217 CHECK(test_phase_
== BETWEEN_TESTS
);
218 GpuDataManager::GetInstance()->RemoveObserver(this);
223 bool WebKitTestController::PrepareForLayoutTest(
224 const GURL
& test_url
,
225 const base::FilePath
& current_working_directory
,
226 bool enable_pixel_dumping
,
227 const std::string
& expected_pixel_hash
) {
228 DCHECK(CalledOnValidThread());
229 test_phase_
= DURING_TEST
;
230 current_working_directory_
= current_working_directory
;
231 enable_pixel_dumping_
= enable_pixel_dumping
;
232 expected_pixel_hash_
= expected_pixel_hash
;
233 test_url_
= test_url
;
235 ShellBrowserContext
* browser_context
=
236 ShellContentBrowserClient::Get()->browser_context();
237 if (test_url
.spec().find("compositing/") != std::string::npos
)
238 is_compositing_test_
= true;
239 initial_size_
= gfx::Size(
240 Shell::kDefaultTestWindowWidthDip
, Shell::kDefaultTestWindowHeightDip
);
241 // The W3C SVG layout tests use a different size than the other layout tests.
242 if (test_url
.spec().find("W3C-SVG-1.1") != std::string::npos
)
243 initial_size_
= gfx::Size(kTestSVGWindowWidthDip
, kTestSVGWindowHeightDip
);
245 main_window_
= content::Shell::CreateNewWindow(
251 WebContentsObserver::Observe(main_window_
->web_contents());
252 send_configuration_to_next_host_
= true;
253 current_pid_
= base::kNullProcessId
;
254 main_window_
->LoadURL(test_url
);
256 #if (defined(OS_WIN) && !defined(USE_AURA)) || \
257 defined(TOOLKIT_GTK) || defined(OS_MACOSX)
258 // Shell::SizeTo is not implemented on all platforms.
259 main_window_
->SizeTo(initial_size_
.width(), initial_size_
.height());
261 main_window_
->web_contents()->GetRenderViewHost()->GetView()
262 ->SetSize(initial_size_
);
263 main_window_
->web_contents()->GetRenderViewHost()->WasResized();
264 RenderViewHost
* render_view_host
=
265 main_window_
->web_contents()->GetRenderViewHost();
266 WebPreferences prefs
= render_view_host
->GetWebkitPreferences();
267 OverrideWebkitPrefs(&prefs
);
268 render_view_host
->UpdateWebkitPreferences(prefs
);
269 SendTestConfiguration();
271 NavigationController::LoadURLParams
params(test_url
);
272 params
.transition_type
= PageTransitionFromInt(
273 PAGE_TRANSITION_TYPED
| PAGE_TRANSITION_FROM_ADDRESS_BAR
);
274 params
.should_clear_history_list
= true;
275 main_window_
->web_contents()->GetController().LoadURLWithParams(params
);
276 main_window_
->web_contents()->GetView()->Focus();
278 main_window_
->web_contents()->GetRenderViewHost()->SetActive(true);
279 main_window_
->web_contents()->GetRenderViewHost()->Focus();
283 bool WebKitTestController::ResetAfterLayoutTest() {
284 DCHECK(CalledOnValidThread());
285 printer_
->PrintTextFooter();
286 printer_
->PrintImageFooter();
287 printer_
->CloseStderr();
288 send_configuration_to_next_host_
= false;
289 test_phase_
= BETWEEN_TESTS
;
290 is_compositing_test_
= false;
291 enable_pixel_dumping_
= false;
292 expected_pixel_hash_
.clear();
294 prefs_
= WebPreferences();
295 should_override_prefs_
= false;
297 #if defined(OS_ANDROID)
298 // Re-using the shell's main window on Android causes issues with networking
299 // requests never succeeding. See http://crbug.com/277652.
305 void WebKitTestController::SetTempPath(const base::FilePath
& temp_path
) {
306 temp_path_
= temp_path
;
309 void WebKitTestController::RendererUnresponsive() {
310 DCHECK(CalledOnValidThread());
311 LOG(WARNING
) << "renderer unresponsive";
314 void WebKitTestController::WorkerCrashed() {
315 DCHECK(CalledOnValidThread());
316 printer_
->AddErrorMessage("#CRASHED - worker");
320 void WebKitTestController::OverrideWebkitPrefs(WebPreferences
* prefs
) {
321 if (should_override_prefs_
) {
324 ApplyLayoutTestDefaultPreferences(prefs
);
325 if (is_compositing_test_
) {
326 CommandLine
& command_line
= *CommandLine::ForCurrentProcess();
327 if (!command_line
.HasSwitch(switches::kEnableSoftwareCompositing
))
328 prefs
->accelerated_2d_canvas_enabled
= true;
329 prefs
->accelerated_compositing_for_video_enabled
= true;
330 prefs
->mock_scrollbars_enabled
= true;
335 void WebKitTestController::OpenURL(const GURL
& url
) {
336 if (test_phase_
!= DURING_TEST
)
339 Shell::CreateNewWindow(main_window_
->web_contents()->GetBrowserContext(),
341 main_window_
->web_contents()->GetSiteInstance(),
346 void WebKitTestController::TestFinishedInSecondaryWindow() {
347 RenderViewHost
* render_view_host
=
348 main_window_
->web_contents()->GetRenderViewHost();
349 render_view_host
->Send(
350 new ShellViewMsg_NotifyDone(render_view_host
->GetRoutingID()));
353 bool WebKitTestController::IsMainWindow(WebContents
* web_contents
) const {
354 return main_window_
&& web_contents
== main_window_
->web_contents();
357 bool WebKitTestController::OnMessageReceived(const IPC::Message
& message
) {
358 DCHECK(CalledOnValidThread());
360 IPC_BEGIN_MESSAGE_MAP(WebKitTestController
, message
)
361 IPC_MESSAGE_HANDLER(ShellViewHostMsg_PrintMessage
, OnPrintMessage
)
362 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TextDump
, OnTextDump
)
363 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ImageDump
, OnImageDump
)
364 IPC_MESSAGE_HANDLER(ShellViewHostMsg_AudioDump
, OnAudioDump
)
365 IPC_MESSAGE_HANDLER(ShellViewHostMsg_OverridePreferences
,
366 OnOverridePreferences
)
367 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TestFinished
, OnTestFinished
)
368 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ShowDevTools
, OnShowDevTools
)
369 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CloseDevTools
, OnCloseDevTools
)
370 IPC_MESSAGE_HANDLER(ShellViewHostMsg_GoToOffset
, OnGoToOffset
)
371 IPC_MESSAGE_HANDLER(ShellViewHostMsg_Reload
, OnReload
)
372 IPC_MESSAGE_HANDLER(ShellViewHostMsg_LoadURLForFrame
, OnLoadURLForFrame
)
373 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CaptureSessionHistory
,
374 OnCaptureSessionHistory
)
375 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CloseRemainingWindows
,
376 OnCloseRemainingWindows
)
377 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ResetDone
, OnResetDone
)
378 IPC_MESSAGE_UNHANDLED(handled
= false)
379 IPC_END_MESSAGE_MAP()
384 void WebKitTestController::PluginCrashed(const base::FilePath
& plugin_path
,
385 base::ProcessId plugin_pid
) {
386 DCHECK(CalledOnValidThread());
387 printer_
->AddErrorMessage(
388 base::StringPrintf("#CRASHED - plugin (pid %d)", plugin_pid
));
389 base::MessageLoop::current()->PostTask(
391 base::Bind(base::IgnoreResult(&WebKitTestController::DiscardMainWindow
),
392 base::Unretained(this)));
395 void WebKitTestController::RenderViewCreated(RenderViewHost
* render_view_host
) {
396 DCHECK(CalledOnValidThread());
397 // Might be kNullProcessHandle, in which case we will receive a notification
398 // later when the RenderProcessHost was created.
399 if (render_view_host
->GetProcess()->GetHandle() != base::kNullProcessHandle
)
400 current_pid_
= base::GetProcId(render_view_host
->GetProcess()->GetHandle());
401 if (!send_configuration_to_next_host_
)
403 send_configuration_to_next_host_
= false;
404 SendTestConfiguration();
407 void WebKitTestController::RenderProcessGone(base::TerminationStatus status
) {
408 DCHECK(CalledOnValidThread());
409 if (current_pid_
!= base::kNullProcessId
) {
410 printer_
->AddErrorMessage(std::string("#CRASHED - renderer (pid ") +
411 base::IntToString(current_pid_
) + ")");
413 printer_
->AddErrorMessage("#CRASHED - renderer");
418 void WebKitTestController::WebContentsDestroyed(WebContents
* web_contents
) {
419 DCHECK(CalledOnValidThread());
420 printer_
->AddErrorMessage("FAIL: main window was destroyed");
424 void WebKitTestController::Observe(int type
,
425 const NotificationSource
& source
,
426 const NotificationDetails
& details
) {
427 DCHECK(CalledOnValidThread());
429 case NOTIFICATION_RENDERER_PROCESS_CREATED
: {
432 RenderViewHost
* render_view_host
=
433 main_window_
->web_contents()->GetRenderViewHost();
434 if (!render_view_host
)
436 RenderProcessHost
* render_process_host
=
437 Source
<RenderProcessHost
>(source
).ptr();
438 if (render_process_host
!= render_view_host
->GetProcess())
440 current_pid_
= base::GetProcId(render_process_host
->GetHandle());
448 void WebKitTestController::OnGpuProcessCrashed(
449 base::TerminationStatus exit_code
) {
450 DCHECK(CalledOnValidThread());
451 printer_
->AddErrorMessage("#CRASHED - gpu");
455 void WebKitTestController::TimeoutHandler() {
456 DCHECK(CalledOnValidThread());
457 printer_
->AddErrorMessage(
458 "FAIL: Timed out waiting for notifyDone to be called");
462 void WebKitTestController::DiscardMainWindow() {
463 // If we're running a test, we need to close all windows and exit the message
464 // loop. Otherwise, we're already outside of the message loop, and we just
465 // discard the main window.
466 WebContentsObserver::Observe(NULL
);
467 if (test_phase_
!= BETWEEN_TESTS
) {
468 Shell::CloseAllWindows();
469 base::MessageLoop::current()->PostTask(FROM_HERE
,
470 base::MessageLoop::QuitClosure());
471 test_phase_
= CLEAN_UP
;
472 } else if (main_window_
) {
473 main_window_
->Close();
476 current_pid_
= base::kNullProcessId
;
479 void WebKitTestController::SendTestConfiguration() {
480 RenderViewHost
* render_view_host
=
481 main_window_
->web_contents()->GetRenderViewHost();
482 ShellTestConfiguration params
;
483 params
.current_working_directory
= current_working_directory_
;
484 params
.temp_path
= temp_path_
;
485 params
.test_url
= test_url_
;
486 params
.enable_pixel_dumping
= enable_pixel_dumping_
;
487 params
.allow_external_pages
= CommandLine::ForCurrentProcess()->HasSwitch(
488 switches::kAllowExternalPages
);
489 params
.expected_pixel_hash
= expected_pixel_hash_
;
490 params
.initial_size
= initial_size_
;
491 render_view_host
->Send(new ShellViewMsg_SetTestConfiguration(
492 render_view_host
->GetRoutingID(), params
));
495 void WebKitTestController::OnTestFinished() {
496 test_phase_
= CLEAN_UP
;
497 if (!printer_
->output_finished())
498 printer_
->PrintImageFooter();
499 RenderViewHost
* render_view_host
=
500 main_window_
->web_contents()->GetRenderViewHost();
501 base::MessageLoop::current()->PostTask(
503 base::Bind(base::IgnoreResult(&WebKitTestController::Send
),
504 base::Unretained(this),
505 new ShellViewMsg_Reset(render_view_host
->GetRoutingID())));
508 void WebKitTestController::OnImageDump(
509 const std::string
& actual_pixel_hash
,
510 const SkBitmap
& image
) {
511 SkAutoLockPixels
image_lock(image
);
513 printer_
->PrintImageHeader(actual_pixel_hash
, expected_pixel_hash_
);
515 // Only encode and dump the png if the hashes don't match. Encoding the
516 // image is really expensive.
517 if (actual_pixel_hash
!= expected_pixel_hash_
) {
518 std::vector
<unsigned char> png
;
520 // Only the expected PNGs for Mac have a valid alpha channel.
521 #if defined(OS_MACOSX)
522 bool discard_transparency
= false;
524 bool discard_transparency
= true;
526 if (CommandLine::ForCurrentProcess()->HasSwitch(
527 switches::kEnableOverlayFullscreenVideo
))
528 discard_transparency
= false;
530 std::vector
<gfx::PNGCodec::Comment
> comments
;
531 comments
.push_back(gfx::PNGCodec::Comment("checksum", actual_pixel_hash
));
532 bool success
= gfx::PNGCodec::Encode(
533 static_cast<const unsigned char*>(image
.getPixels()),
534 gfx::PNGCodec::FORMAT_BGRA
,
535 gfx::Size(image
.width(), image
.height()),
536 static_cast<int>(image
.rowBytes()),
537 discard_transparency
,
541 printer_
->PrintImageBlock(png
);
543 printer_
->PrintImageFooter();
546 void WebKitTestController::OnAudioDump(const std::vector
<unsigned char>& dump
) {
547 printer_
->PrintAudioHeader();
548 printer_
->PrintAudioBlock(dump
);
549 printer_
->PrintAudioFooter();
552 void WebKitTestController::OnTextDump(const std::string
& dump
) {
553 printer_
->PrintTextHeader();
554 printer_
->PrintTextBlock(dump
);
555 printer_
->PrintTextFooter();
558 void WebKitTestController::OnPrintMessage(const std::string
& message
) {
559 printer_
->AddMessageRaw(message
);
562 void WebKitTestController::OnOverridePreferences(const WebPreferences
& prefs
) {
563 should_override_prefs_
= true;
567 void WebKitTestController::OnShowDevTools() {
568 main_window_
->ShowDevTools();
571 void WebKitTestController::OnCloseDevTools() {
572 main_window_
->CloseDevTools();
575 void WebKitTestController::OnGoToOffset(int offset
) {
576 main_window_
->GoBackOrForward(offset
);
579 void WebKitTestController::OnReload() {
580 main_window_
->Reload();
583 void WebKitTestController::OnLoadURLForFrame(const GURL
& url
,
584 const std::string
& frame_name
) {
585 main_window_
->LoadURLForFrame(url
, frame_name
);
588 void WebKitTestController::OnCaptureSessionHistory() {
589 std::vector
<int> routing_ids
;
590 std::vector
<std::vector
<PageState
> > session_histories
;
591 std::vector
<unsigned> current_entry_indexes
;
593 RenderViewHost
* render_view_host
=
594 main_window_
->web_contents()->GetRenderViewHost();
596 for (std::vector
<Shell
*>::iterator window
= Shell::windows().begin();
597 window
!= Shell::windows().end();
599 WebContents
* web_contents
= (*window
)->web_contents();
600 // Only capture the history from windows in the same process as the main
601 // window. During layout tests, we only use two processes when an
602 // devtools window is open. This should not happen during history navigation
604 if (render_view_host
->GetProcess() !=
605 web_contents
->GetRenderViewHost()->GetProcess()) {
609 routing_ids
.push_back(web_contents
->GetRenderViewHost()->GetRoutingID());
610 current_entry_indexes
.push_back(
611 web_contents
->GetController().GetCurrentEntryIndex());
612 std::vector
<PageState
> history
;
613 for (int entry
= 0; entry
< web_contents
->GetController().GetEntryCount();
615 PageState state
= web_contents
->GetController().GetEntryAtIndex(entry
)->
617 if (!state
.IsValid()) {
618 state
= PageState::CreateFromURL(
619 web_contents
->GetController().GetEntryAtIndex(entry
)->GetURL());
621 history
.push_back(state
);
623 session_histories
.push_back(history
);
626 Send(new ShellViewMsg_SessionHistory(render_view_host
->GetRoutingID(),
629 current_entry_indexes
));
632 void WebKitTestController::OnCloseRemainingWindows() {
633 DevToolsManager::GetInstance()->CloseAllClientHosts();
634 std::vector
<Shell
*> open_windows(Shell::windows());
635 for (size_t i
= 0; i
< open_windows
.size(); ++i
) {
636 if (open_windows
[i
] != main_window_
)
637 open_windows
[i
]->Close();
639 base::MessageLoop::current()->RunUntilIdle();
642 void WebKitTestController::OnResetDone() {
643 base::MessageLoop::current()->PostTask(FROM_HERE
,
644 base::MessageLoop::QuitClosure());
647 } // namespace content