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/browser/web_contents_view.h"
28 #include "content/public/common/content_switches.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
) {
206 printer_
.reset(new WebKitTestResultPrinter(&std::cout
, &std::cerr
));
207 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEncodeBinary
))
208 printer_
->set_encode_binary_data(true);
210 NOTIFICATION_RENDERER_PROCESS_CREATED
,
211 NotificationService::AllSources());
212 GpuDataManager::GetInstance()->AddObserver(this);
213 ResetAfterLayoutTest();
216 WebKitTestController::~WebKitTestController() {
217 DCHECK(CalledOnValidThread());
218 CHECK(instance_
== this);
219 CHECK(test_phase_
== BETWEEN_TESTS
);
220 GpuDataManager::GetInstance()->RemoveObserver(this);
225 bool WebKitTestController::PrepareForLayoutTest(
226 const GURL
& test_url
,
227 const base::FilePath
& current_working_directory
,
228 bool enable_pixel_dumping
,
229 const std::string
& expected_pixel_hash
) {
230 DCHECK(CalledOnValidThread());
231 test_phase_
= DURING_TEST
;
232 current_working_directory_
= current_working_directory
;
233 enable_pixel_dumping_
= enable_pixel_dumping
;
234 expected_pixel_hash_
= expected_pixel_hash
;
235 test_url_
= test_url
;
237 ShellBrowserContext
* browser_context
=
238 ShellContentBrowserClient::Get()->browser_context();
239 if (test_url
.spec().find("compositing/") != std::string::npos
)
240 is_compositing_test_
= true;
241 initial_size_
= gfx::Size(
242 Shell::kDefaultTestWindowWidthDip
, Shell::kDefaultTestWindowHeightDip
);
243 // The W3C SVG layout tests use a different size than the other layout tests.
244 if (test_url
.spec().find("W3C-SVG-1.1") != std::string::npos
)
245 initial_size_
= gfx::Size(kTestSVGWindowWidthDip
, kTestSVGWindowHeightDip
);
247 main_window_
= content::Shell::CreateNewWindow(
253 WebContentsObserver::Observe(main_window_
->web_contents());
254 send_configuration_to_next_host_
= true;
255 current_pid_
= base::kNullProcessId
;
256 main_window_
->LoadURL(test_url
);
258 #if (defined(OS_WIN) && !defined(USE_AURA)) || \
259 defined(TOOLKIT_GTK) || defined(OS_MACOSX)
260 // Shell::SizeTo is not implemented on all platforms.
261 main_window_
->SizeTo(initial_size_
);
263 main_window_
->web_contents()->GetRenderViewHost()->GetView()
264 ->SetSize(initial_size_
);
265 main_window_
->web_contents()->GetRenderViewHost()->WasResized();
266 RenderViewHost
* render_view_host
=
267 main_window_
->web_contents()->GetRenderViewHost();
268 WebPreferences prefs
= render_view_host
->GetWebkitPreferences();
269 OverrideWebkitPrefs(&prefs
);
270 render_view_host
->UpdateWebkitPreferences(prefs
);
271 SendTestConfiguration();
273 NavigationController::LoadURLParams
params(test_url
);
274 params
.transition_type
= PageTransitionFromInt(
275 PAGE_TRANSITION_TYPED
| PAGE_TRANSITION_FROM_ADDRESS_BAR
);
276 params
.should_clear_history_list
= true;
277 main_window_
->web_contents()->GetController().LoadURLWithParams(params
);
278 main_window_
->web_contents()->GetView()->Focus();
280 main_window_
->web_contents()->GetRenderViewHost()->SetActive(true);
281 main_window_
->web_contents()->GetRenderViewHost()->Focus();
285 bool WebKitTestController::ResetAfterLayoutTest() {
286 DCHECK(CalledOnValidThread());
287 printer_
->PrintTextFooter();
288 printer_
->PrintImageFooter();
289 printer_
->CloseStderr();
290 send_configuration_to_next_host_
= false;
291 test_phase_
= BETWEEN_TESTS
;
292 is_compositing_test_
= false;
293 enable_pixel_dumping_
= false;
294 expected_pixel_hash_
.clear();
296 prefs_
= WebPreferences();
297 should_override_prefs_
= false;
299 #if defined(OS_ANDROID)
300 // Re-using the shell's main window on Android causes issues with networking
301 // requests never succeeding. See http://crbug.com/277652.
307 void WebKitTestController::SetTempPath(const base::FilePath
& temp_path
) {
308 temp_path_
= temp_path
;
311 void WebKitTestController::RendererUnresponsive() {
312 DCHECK(CalledOnValidThread());
313 LOG(WARNING
) << "renderer unresponsive";
316 void WebKitTestController::WorkerCrashed() {
317 DCHECK(CalledOnValidThread());
318 printer_
->AddErrorMessage("#CRASHED - worker");
322 void WebKitTestController::OverrideWebkitPrefs(WebPreferences
* prefs
) {
323 if (should_override_prefs_
) {
326 ApplyLayoutTestDefaultPreferences(prefs
);
327 if (is_compositing_test_
) {
328 CommandLine
& command_line
= *CommandLine::ForCurrentProcess();
329 if (!command_line
.HasSwitch(switches::kEnableSoftwareCompositing
))
330 prefs
->accelerated_2d_canvas_enabled
= true;
331 prefs
->accelerated_compositing_for_video_enabled
= true;
332 prefs
->mock_scrollbars_enabled
= true;
337 void WebKitTestController::OpenURL(const GURL
& url
) {
338 if (test_phase_
!= DURING_TEST
)
341 Shell::CreateNewWindow(main_window_
->web_contents()->GetBrowserContext(),
343 main_window_
->web_contents()->GetSiteInstance(),
348 void WebKitTestController::TestFinishedInSecondaryWindow() {
349 RenderViewHost
* render_view_host
=
350 main_window_
->web_contents()->GetRenderViewHost();
351 render_view_host
->Send(
352 new ShellViewMsg_NotifyDone(render_view_host
->GetRoutingID()));
355 bool WebKitTestController::IsMainWindow(WebContents
* web_contents
) const {
356 return main_window_
&& web_contents
== main_window_
->web_contents();
359 bool WebKitTestController::OnMessageReceived(const IPC::Message
& message
) {
360 DCHECK(CalledOnValidThread());
362 IPC_BEGIN_MESSAGE_MAP(WebKitTestController
, message
)
363 IPC_MESSAGE_HANDLER(ShellViewHostMsg_PrintMessage
, OnPrintMessage
)
364 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TextDump
, OnTextDump
)
365 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ImageDump
, OnImageDump
)
366 IPC_MESSAGE_HANDLER(ShellViewHostMsg_AudioDump
, OnAudioDump
)
367 IPC_MESSAGE_HANDLER(ShellViewHostMsg_OverridePreferences
,
368 OnOverridePreferences
)
369 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TestFinished
, OnTestFinished
)
370 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ShowDevTools
, OnShowDevTools
)
371 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CloseDevTools
, OnCloseDevTools
)
372 IPC_MESSAGE_HANDLER(ShellViewHostMsg_GoToOffset
, OnGoToOffset
)
373 IPC_MESSAGE_HANDLER(ShellViewHostMsg_Reload
, OnReload
)
374 IPC_MESSAGE_HANDLER(ShellViewHostMsg_LoadURLForFrame
, OnLoadURLForFrame
)
375 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CaptureSessionHistory
,
376 OnCaptureSessionHistory
)
377 IPC_MESSAGE_HANDLER(ShellViewHostMsg_CloseRemainingWindows
,
378 OnCloseRemainingWindows
)
379 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ResetDone
, OnResetDone
)
380 IPC_MESSAGE_UNHANDLED(handled
= false)
381 IPC_END_MESSAGE_MAP()
386 void WebKitTestController::PluginCrashed(const base::FilePath
& plugin_path
,
387 base::ProcessId plugin_pid
) {
388 DCHECK(CalledOnValidThread());
389 printer_
->AddErrorMessage(
390 base::StringPrintf("#CRASHED - plugin (pid %d)", plugin_pid
));
391 base::MessageLoop::current()->PostTask(
393 base::Bind(base::IgnoreResult(&WebKitTestController::DiscardMainWindow
),
394 base::Unretained(this)));
397 void WebKitTestController::RenderViewCreated(RenderViewHost
* render_view_host
) {
398 DCHECK(CalledOnValidThread());
399 // Might be kNullProcessHandle, in which case we will receive a notification
400 // later when the RenderProcessHost was created.
401 if (render_view_host
->GetProcess()->GetHandle() != base::kNullProcessHandle
)
402 current_pid_
= base::GetProcId(render_view_host
->GetProcess()->GetHandle());
403 if (!send_configuration_to_next_host_
)
405 send_configuration_to_next_host_
= false;
406 SendTestConfiguration();
409 void WebKitTestController::RenderProcessGone(base::TerminationStatus status
) {
410 DCHECK(CalledOnValidThread());
411 if (current_pid_
!= base::kNullProcessId
) {
412 printer_
->AddErrorMessage(std::string("#CRASHED - renderer (pid ") +
413 base::IntToString(current_pid_
) + ")");
415 printer_
->AddErrorMessage("#CRASHED - renderer");
420 void WebKitTestController::WebContentsDestroyed(WebContents
* web_contents
) {
421 DCHECK(CalledOnValidThread());
422 printer_
->AddErrorMessage("FAIL: main window was destroyed");
426 void WebKitTestController::Observe(int type
,
427 const NotificationSource
& source
,
428 const NotificationDetails
& details
) {
429 DCHECK(CalledOnValidThread());
431 case NOTIFICATION_RENDERER_PROCESS_CREATED
: {
434 RenderViewHost
* render_view_host
=
435 main_window_
->web_contents()->GetRenderViewHost();
436 if (!render_view_host
)
438 RenderProcessHost
* render_process_host
=
439 Source
<RenderProcessHost
>(source
).ptr();
440 if (render_process_host
!= render_view_host
->GetProcess())
442 current_pid_
= base::GetProcId(render_process_host
->GetHandle());
450 void WebKitTestController::OnGpuProcessCrashed(
451 base::TerminationStatus exit_code
) {
452 DCHECK(CalledOnValidThread());
453 printer_
->AddErrorMessage("#CRASHED - gpu");
457 void WebKitTestController::TimeoutHandler() {
458 DCHECK(CalledOnValidThread());
459 printer_
->AddErrorMessage(
460 "FAIL: Timed out waiting for notifyDone to be called");
464 void WebKitTestController::DiscardMainWindow() {
465 // If we're running a test, we need to close all windows and exit the message
466 // loop. Otherwise, we're already outside of the message loop, and we just
467 // discard the main window.
468 WebContentsObserver::Observe(NULL
);
469 if (test_phase_
!= BETWEEN_TESTS
) {
470 Shell::CloseAllWindows();
471 base::MessageLoop::current()->PostTask(FROM_HERE
,
472 base::MessageLoop::QuitClosure());
473 test_phase_
= CLEAN_UP
;
474 } else if (main_window_
) {
475 main_window_
->Close();
478 current_pid_
= base::kNullProcessId
;
481 void WebKitTestController::SendTestConfiguration() {
482 RenderViewHost
* render_view_host
=
483 main_window_
->web_contents()->GetRenderViewHost();
484 ShellTestConfiguration params
;
485 params
.current_working_directory
= current_working_directory_
;
486 params
.temp_path
= temp_path_
;
487 params
.test_url
= test_url_
;
488 params
.enable_pixel_dumping
= enable_pixel_dumping_
;
489 params
.allow_external_pages
= CommandLine::ForCurrentProcess()->HasSwitch(
490 switches::kAllowExternalPages
);
491 params
.expected_pixel_hash
= expected_pixel_hash_
;
492 params
.initial_size
= initial_size_
;
493 render_view_host
->Send(new ShellViewMsg_SetTestConfiguration(
494 render_view_host
->GetRoutingID(), params
));
497 void WebKitTestController::OnTestFinished() {
498 test_phase_
= CLEAN_UP
;
499 if (!printer_
->output_finished())
500 printer_
->PrintImageFooter();
501 RenderViewHost
* render_view_host
=
502 main_window_
->web_contents()->GetRenderViewHost();
503 base::MessageLoop::current()->PostTask(
505 base::Bind(base::IgnoreResult(&WebKitTestController::Send
),
506 base::Unretained(this),
507 new ShellViewMsg_Reset(render_view_host
->GetRoutingID())));
510 void WebKitTestController::OnImageDump(
511 const std::string
& actual_pixel_hash
,
512 const SkBitmap
& image
) {
513 SkAutoLockPixels
image_lock(image
);
515 printer_
->PrintImageHeader(actual_pixel_hash
, expected_pixel_hash_
);
517 // Only encode and dump the png if the hashes don't match. Encoding the
518 // image is really expensive.
519 if (actual_pixel_hash
!= expected_pixel_hash_
) {
520 std::vector
<unsigned char> png
;
522 // Only the expected PNGs for Mac have a valid alpha channel.
523 #if defined(OS_MACOSX)
524 bool discard_transparency
= false;
526 bool discard_transparency
= true;
528 if (CommandLine::ForCurrentProcess()->HasSwitch(
529 switches::kEnableOverlayFullscreenVideo
))
530 discard_transparency
= false;
532 std::vector
<gfx::PNGCodec::Comment
> comments
;
533 comments
.push_back(gfx::PNGCodec::Comment("checksum", actual_pixel_hash
));
534 bool success
= gfx::PNGCodec::Encode(
535 static_cast<const unsigned char*>(image
.getPixels()),
536 gfx::PNGCodec::FORMAT_BGRA
,
537 gfx::Size(image
.width(), image
.height()),
538 static_cast<int>(image
.rowBytes()),
539 discard_transparency
,
543 printer_
->PrintImageBlock(png
);
545 printer_
->PrintImageFooter();
548 void WebKitTestController::OnAudioDump(const std::vector
<unsigned char>& dump
) {
549 printer_
->PrintAudioHeader();
550 printer_
->PrintAudioBlock(dump
);
551 printer_
->PrintAudioFooter();
554 void WebKitTestController::OnTextDump(const std::string
& dump
) {
555 printer_
->PrintTextHeader();
556 printer_
->PrintTextBlock(dump
);
557 printer_
->PrintTextFooter();
560 void WebKitTestController::OnPrintMessage(const std::string
& message
) {
561 printer_
->AddMessageRaw(message
);
564 void WebKitTestController::OnOverridePreferences(const WebPreferences
& prefs
) {
565 should_override_prefs_
= true;
569 void WebKitTestController::OnShowDevTools() {
570 ShellBrowserContext
* browser_context
=
571 ShellContentBrowserClient::Get()->browser_context();
572 StoragePartition
* storage_partition
=
573 BrowserContext::GetStoragePartition(browser_context
, NULL
);
574 storage_partition
->GetDOMStorageContext()->DeleteLocalStorage(
575 content::GetDevToolsPathAsURL().GetOrigin());
576 main_window_
->ShowDevTools();
579 void WebKitTestController::OnCloseDevTools() {
580 main_window_
->CloseDevTools();
583 void WebKitTestController::OnGoToOffset(int offset
) {
584 main_window_
->GoBackOrForward(offset
);
587 void WebKitTestController::OnReload() {
588 main_window_
->Reload();
591 void WebKitTestController::OnLoadURLForFrame(const GURL
& url
,
592 const std::string
& frame_name
) {
593 main_window_
->LoadURLForFrame(url
, frame_name
);
596 void WebKitTestController::OnCaptureSessionHistory() {
597 std::vector
<int> routing_ids
;
598 std::vector
<std::vector
<PageState
> > session_histories
;
599 std::vector
<unsigned> current_entry_indexes
;
601 RenderViewHost
* render_view_host
=
602 main_window_
->web_contents()->GetRenderViewHost();
604 for (std::vector
<Shell
*>::iterator window
= Shell::windows().begin();
605 window
!= Shell::windows().end();
607 WebContents
* web_contents
= (*window
)->web_contents();
608 // Only capture the history from windows in the same process as the main
609 // window. During layout tests, we only use two processes when an
610 // devtools window is open. This should not happen during history navigation
612 if (render_view_host
->GetProcess() !=
613 web_contents
->GetRenderViewHost()->GetProcess()) {
617 routing_ids
.push_back(web_contents
->GetRenderViewHost()->GetRoutingID());
618 current_entry_indexes
.push_back(
619 web_contents
->GetController().GetCurrentEntryIndex());
620 std::vector
<PageState
> history
;
621 for (int entry
= 0; entry
< web_contents
->GetController().GetEntryCount();
623 PageState state
= web_contents
->GetController().GetEntryAtIndex(entry
)->
625 if (!state
.IsValid()) {
626 state
= PageState::CreateFromURL(
627 web_contents
->GetController().GetEntryAtIndex(entry
)->GetURL());
629 history
.push_back(state
);
631 session_histories
.push_back(history
);
634 Send(new ShellViewMsg_SessionHistory(render_view_host
->GetRoutingID(),
637 current_entry_indexes
));
640 void WebKitTestController::OnCloseRemainingWindows() {
641 DevToolsManager::GetInstance()->CloseAllClientHosts();
642 std::vector
<Shell
*> open_windows(Shell::windows());
643 for (size_t i
= 0; i
< open_windows
.size(); ++i
) {
644 if (open_windows
[i
] != main_window_
)
645 open_windows
[i
]->Close();
647 base::MessageLoop::current()->RunUntilIdle();
650 void WebKitTestController::OnResetDone() {
651 base::MessageLoop::current()->PostTask(FROM_HERE
,
652 base::MessageLoop::QuitClosure());
655 } // namespace content