// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "webkit/tools/test_shell/test_shell.h" #include "base/command_line.h" #include "base/debug_on_start.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/gfx/png_encoder.h" #include "base/gfx/size.h" #include "base/icu_util.h" #include "base/md5.h" #include "base/message_loop.h" #include "base/path_service.h" #include "base/stats_table.h" #include "base/string_util.h" #include "build/build_config.h" #include "googleurl/src/url_util.h" #include "net/base/mime_util.h" #include "net/url_request/url_request_file_job.h" #include "net/url_request/url_request_filter.h" #include "skia/ext/bitmap_platform_device.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/glue/webdatasource.h" #include "webkit/glue/webframe.h" #include "webkit/glue/webkit_glue.h" #include "webkit/glue/webkit_resources.h" #include "webkit/glue/webpreferences.h" #include "webkit/glue/weburlrequest.h" #include "webkit/glue/webview.h" #include "webkit/glue/webwidget.h" #include "webkit/tools/test_shell/simple_resource_loader_bridge.h" #include "webkit/tools/test_shell/test_navigation_controller.h" #if defined(OS_MACOSX) #include "webkit/glue/bogus_webkit_strings.h" #elif defined(OS_WIN) || defined(OS_LINUX) #include "webkit_strings.h" #endif #include "SkBitmap.h" namespace { // Default timeout in ms for file page loads when in layout test mode. const int kDefaultFileTestTimeoutMillisecs = 10 * 1000; // Content area size for newly created windows. const int kTestWindowWidth = 800; const int kTestWindowHeight = 600; // The W3C SVG layout tests use a different size than the other layout // tests. const int kSVGTestWindowWidth = 480; const int kSVGTestWindowHeight = 360; // Helper method for getting the path to the test shell resources directory. FilePath GetResourcesFilePath() { FilePath path; PathService::Get(base::DIR_SOURCE_ROOT, &path); path = path.Append(FILE_PATH_LITERAL("webkit")); path = path.Append(FILE_PATH_LITERAL("tools")); path = path.Append(FILE_PATH_LITERAL("test_shell")); return path.Append(FILE_PATH_LITERAL("resources")); } // URLRequestTestShellFileJob is used to serve the inspector class URLRequestTestShellFileJob : public URLRequestFileJob { public: virtual ~URLRequestTestShellFileJob() { } static URLRequestJob* InspectorFactory(URLRequest* request, const std::string& scheme) { std::wstring path; PathService::Get(base::DIR_EXE, &path); file_util::AppendToPath(&path, L"Resources"); file_util::AppendToPath(&path, L"Inspector"); file_util::AppendToPath(&path, UTF8ToWide(request->url().path())); return new URLRequestTestShellFileJob(request, path); } private: URLRequestTestShellFileJob(URLRequest* request, const std::wstring& path) : URLRequestFileJob(request) { this->file_path_ = FilePath::FromWStringHack(path); } DISALLOW_COPY_AND_ASSIGN(URLRequestTestShellFileJob); }; } // namespace // Initialize static member variable WindowList* TestShell::window_list_; WebPreferences* TestShell::web_prefs_ = NULL; bool TestShell::layout_test_mode_ = false; int TestShell::file_test_timeout_ms_ = kDefaultFileTestTimeoutMillisecs; TestShell::TestShell() : m_mainWnd(NULL), m_editWnd(NULL), m_webViewHost(NULL), m_popupHost(NULL), m_focusedWidgetHost(NULL), #if defined(OS_WIN) default_edit_wnd_proc_(0), #endif test_is_preparing_(false), test_is_pending_(false), is_modal_(false), dump_stats_table_on_exit_(false) { delegate_ = new TestWebViewDelegate(this); layout_test_controller_.reset(new LayoutTestController(this)); event_sending_controller_.reset(new EventSendingController(this)); text_input_controller_.reset(new TextInputController(this)); navigation_controller_.reset(new TestNavigationController(this)); URLRequestFilter* filter = URLRequestFilter::GetInstance(); filter->AddHostnameHandler("test-shell-resource", "inspector", &URLRequestTestShellFileJob::InspectorFactory); url_util::AddStandardScheme("test-shell-resource"); } TestShell::~TestShell() { // Navigate to an empty page to fire all the destruction logic for the // current page. LoadURL(L"about:blank"); // Call GC twice to clean up garbage. CallJSGC(); CallJSGC(); PlatformCleanUp(); StatsTable *table = StatsTable::current(); if (dump_stats_table_on_exit_) { // Dump the stats table. printf("\n"); if (table != NULL) { int counter_max = table->GetMaxCounters(); for (int index=0; index < counter_max; index++) { std::string name(table->GetRowName(index)); if (name.length() > 0) { int value = table->GetRowValue(index); printf("%s:\t%d\n", name.c_str(), value); } } } printf("\n"); } } void TestShell::ShutdownTestShell() { #if defined(OS_WIN) || defined(OS_MACOSX) PlatformShutdown(); #endif SimpleResourceLoaderBridge::Shutdown(); delete window_list_; delete TestShell::web_prefs_; } // All fatal log messages (e.g. DCHECK failures) imply unit test failures static void UnitTestAssertHandler(const std::string& str) { FAIL() << str; } // static std::string TestShell::DumpImage(WebFrame* web_frame, const std::wstring& file_name) { scoped_ptr device; if (!web_frame->CaptureImage(&device, true)) return std::string(); const SkBitmap& src_bmp = device->accessBitmap(false); // Encode image. std::vector png; SkAutoLockPixels src_bmp_lock(src_bmp); PNGEncoder::Encode( reinterpret_cast(src_bmp.getPixels()), PNGEncoder::FORMAT_BGRA, src_bmp.width(), src_bmp.height(), static_cast(src_bmp.rowBytes()), true, &png); // Write to disk. file_util::WriteFile(file_name, reinterpret_cast(&png[0]), png.size()); // Compute MD5 sum. MD5Context ctx; MD5Init(&ctx); MD5Update(&ctx, src_bmp.getPixels(), src_bmp.getSize()); MD5Digest digest; MD5Final(&digest, &ctx); return MD5DigestToBase16(digest); } // static void TestShell::InitLogging(bool suppress_error_dialogs, bool layout_test_mode, bool enable_gp_fault_error_box) { if (suppress_error_dialogs) logging::SetLogAssertHandler(UnitTestAssertHandler); #if defined(OS_WIN) if (!IsDebuggerPresent()) { UINT new_flags = SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX; if (!enable_gp_fault_error_box) new_flags |= SEM_NOGPFAULTERRORBOX; // Preserve existing error mode, as discussed at // http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx UINT existing_flags = SetErrorMode(new_flags); SetErrorMode(existing_flags | new_flags); } #endif // Only log to a file if we're running layout tests. This prevents debugging // output from disrupting whether or not we pass. logging::LoggingDestination destination = logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG; if (layout_test_mode) destination = logging::LOG_ONLY_TO_FILE; // We might have multiple test_shell processes going at once FilePath log_filename; PathService::Get(base::DIR_EXE, &log_filename); log_filename = log_filename.Append(FILE_PATH_LITERAL("test_shell.log")); logging::InitLogging(log_filename.value().c_str(), destination, logging::LOCK_LOG_FILE, logging::DELETE_OLD_LOG_FILE); // we want process and thread IDs because we may have multiple processes logging::SetLogItems(true, true, false, true); // Turn on logging of notImplemented()s inside WebKit, but only if we're // not running layout tests (because otherwise they'd corrupt the test // output). if (!layout_test_mode) webkit_glue::EnableWebCoreNotImplementedLogging(); } // static void TestShell::CleanupLogging() { logging::CloseLogFile(); } // static void TestShell::SetAllowScriptsToCloseWindows() { if (web_prefs_) web_prefs_->allow_scripts_to_close_windows = true; } // static void TestShell::ResetWebPreferences() { DCHECK(web_prefs_); // Match the settings used by Mac DumpRenderTree. if (web_prefs_) { *web_prefs_ = WebPreferences(); web_prefs_->standard_font_family = L"Times"; web_prefs_->fixed_font_family = L"Courier"; web_prefs_->serif_font_family = L"Times"; web_prefs_->sans_serif_font_family = L"Helvetica"; // These two fonts are picked from the intersection of // Win XP font list and Vista font list : // http://www.microsoft.com/typography/fonts/winxp.htm // http://blogs.msdn.com/michkap/archive/2006/04/04/567881.aspx // Some of them are installed only with CJK and complex script // support enabled on Windows XP and are out of consideration here. // (although we enabled both on our buildbots.) // They (especially Impact for fantasy) are not typical cursive // and fantasy fonts, but it should not matter for layout tests // as long as they're available. #if defined(OS_MACOSX) web_prefs_->cursive_font_family = L"Apple Chancery"; web_prefs_->fantasy_font_family = L"Papyrus"; #else web_prefs_->cursive_font_family = L"Comic Sans MS"; web_prefs_->fantasy_font_family = L"Impact"; #endif web_prefs_->default_encoding = L"ISO-8859-1"; web_prefs_->default_font_size = 16; web_prefs_->default_fixed_font_size = 13; web_prefs_->minimum_font_size = 1; web_prefs_->minimum_logical_font_size = 9; web_prefs_->javascript_can_open_windows_automatically = true; web_prefs_->dom_paste_enabled = true; web_prefs_->developer_extras_enabled = !layout_test_mode_; web_prefs_->shrinks_standalone_images_to_fit = false; web_prefs_->uses_universal_detector = false; web_prefs_->text_areas_are_resizable = false; web_prefs_->java_enabled = true; web_prefs_->allow_scripts_to_close_windows = false; } } // static bool TestShell::RemoveWindowFromList(gfx::WindowHandle window) { WindowList::iterator entry = std::find(TestShell::windowList()->begin(), TestShell::windowList()->end(), window); if (entry != TestShell::windowList()->end()) { TestShell::windowList()->erase(entry); return true; } return false; } void TestShell::Show(WebView* webview, WindowOpenDisposition disposition) { delegate_->Show(webview, disposition); } void TestShell::BindJSObjectsToWindow(WebFrame* frame) { // Only bind the test classes if we're running tests. if (layout_test_mode_) { layout_test_controller_->BindToJavascript(frame, L"layoutTestController"); event_sending_controller_->BindToJavascript(frame, L"eventSender"); text_input_controller_->BindToJavascript(frame, L"textInputController"); } } void TestShell::CallJSGC() { WebFrame* frame = webView()->GetMainFrame(); frame->CallJSGC(); } WebView* TestShell::CreateWebView(WebView* webview) { // If we're running layout tests, only open a new window if the test has // called layoutTestController.setCanOpenWindows() if (layout_test_mode_ && !layout_test_controller_->CanOpenWindows()) return NULL; TestShell* new_win; if (!CreateNewWindow(std::wstring(), &new_win)) return NULL; return new_win->webView(); } void TestShell::SizeToSVG() { SizeTo(kSVGTestWindowWidth, kSVGTestWindowHeight); } void TestShell::SizeToDefault() { SizeTo(kTestWindowWidth, kTestWindowHeight); } void TestShell::LoadURL(const wchar_t* url) { LoadURLForFrame(url, NULL); } bool TestShell::Navigate(const TestNavigationEntry& entry, bool reload) { WebRequestCachePolicy cache_policy; if (reload) { cache_policy = WebRequestReloadIgnoringCacheData; } else if (entry.GetPageID() != -1) { cache_policy = WebRequestReturnCacheDataElseLoad; } else { cache_policy = WebRequestUseProtocolCachePolicy; } scoped_ptr request(WebRequest::Create(entry.GetURL())); request->SetCachePolicy(cache_policy); // If we are reloading, then WebKit will use the state of the current page. // Otherwise, we give it the state to navigate to. if (!reload) request->SetHistoryState(entry.GetContentState()); request->SetExtraData( new TestShellExtraRequestData(entry.GetPageID())); // Get the right target frame for the entry. WebFrame* frame = webView()->GetMainFrame(); if (!entry.GetTargetFrame().empty()) frame = webView()->GetFrameWithName(entry.GetTargetFrame()); // TODO(mpcomplete): should we clear the target frame, or should // back/forward navigations maintain the target frame? frame->LoadRequest(request.get()); // Restore focus to the main frame prior to loading new request. // This makes sure that we don't have a focused iframe. Otherwise, that // iframe would keep focus when the SetFocus called immediately after // LoadRequest, thus making some tests fail (see http://b/issue?id=845337 // for more details). webView()->SetFocusedFrame(frame); SetFocus(webViewHost(), true); return true; } void TestShell::GoBackOrForward(int offset) { navigation_controller_->GoToOffset(offset); } std::wstring TestShell::GetDocumentText() { return webkit_glue::DumpDocumentText(webView()->GetMainFrame()); } void TestShell::Reload() { navigation_controller_->Reload(); } void TestShell::SetFocus(WebWidgetHost* host, bool enable) { if (!layout_test_mode_) { InteractiveSetFocus(host, enable); } else { if (enable) { if (m_focusedWidgetHost != host) { if (m_focusedWidgetHost) m_focusedWidgetHost->webwidget()->SetFocus(false); host->webwidget()->SetFocus(enable); m_focusedWidgetHost = host; } } else { if (m_focusedWidgetHost == host) { host->webwidget()->SetFocus(enable); m_focusedWidgetHost = NULL; } } } } //----------------------------------------------------------------------------- namespace webkit_glue { void PrefetchDns(const std::string& hostname) {} void PrecacheUrl(const char16* url, int url_length) {} void AppendToLog(const char* file, int line, const char* msg) { logging::LogMessage(file, line).stream() << msg; } bool GetMimeTypeFromExtension(const std::wstring &ext, std::string *mime_type) { return net::GetMimeTypeFromExtension(ext, mime_type); } bool GetMimeTypeFromFile(const std::wstring &file_path, std::string *mime_type) { return net::GetMimeTypeFromFile(file_path, mime_type); } bool GetPreferredExtensionForMimeType(const std::string& mime_type, std::wstring* ext) { return net::GetPreferredExtensionForMimeType(mime_type, ext); } std::string GetDataResource(int resource_id) { switch (resource_id) { case IDR_BROKENIMAGE: { // Use webkit's broken image icon (16x16) static std::string broken_image_data; if (broken_image_data.empty()) { FilePath path = GetResourcesFilePath(); path = path.Append(FILE_PATH_LITERAL("missingImage.gif")); bool success = file_util::ReadFileToString(path.ToWStringHack(), &broken_image_data); if (!success) { LOG(FATAL) << "Failed reading: " << path.value(); } } return broken_image_data; } case IDR_FEED_PREVIEW: // It is necessary to return a feed preview template that contains // a {{URL}} substring where the feed URL should go; see the code // that computes feed previews in feed_preview.cc:MakeFeedPreview. // This fixes issue #932714. return std::string("Feed preview for {{URL}}"); case IDR_EDITOR_DELETE_BUTTON: { // Use webkit's delete button image. static std::string delete_button_data; if (delete_button_data.empty()) { FilePath path = GetResourcesFilePath(); path = path.Append(FILE_PATH_LITERAL("deleteButton.png")); bool success = file_util::ReadFileToString(path.ToWStringHack(), &delete_button_data); if (!success) { LOG(FATAL) << "Failed reading: " << path.value(); } } return delete_button_data; } case IDR_TEXTAREA_RESIZER: { // Use webkit's text area resizer image. static std::string resize_corner_data; if (resize_corner_data.empty()) { FilePath path = GetResourcesFilePath(); path = path.Append(FILE_PATH_LITERAL("textAreaResizeCorner.png")); bool success = file_util::ReadFileToString(path.ToWStringHack(), &resize_corner_data); if (!success) { LOG(FATAL) << "Failed reading: " << path.value(); } } return resize_corner_data; } default: break; } return std::string(); } GlueBitmap GetBitmapResource(int resource_id) { return NULL; } bool GetApplicationDirectory(std::wstring *path) { return PathService::Get(base::DIR_EXE, path); } GURL GetInspectorURL() { return GURL("test-shell-resource://inspector/inspector.html"); } std::string GetUIResourceProtocol() { return "test-shell-resource"; } bool GetExeDirectory(std::wstring *path) { return PathService::Get(base::DIR_EXE, path); } bool SpellCheckWord(const wchar_t* word, int word_len, int* misspelling_start, int* misspelling_len) { // Report all words being correctly spelled. *misspelling_start = 0; *misspelling_len = 0; return true; } bool IsPluginRunningInRendererProcess() { return true; } bool GetPluginFinderURL(std::string* plugin_finder_url) { return false; } bool IsDefaultPluginEnabled() { return false; } std::wstring GetWebKitLocale() { return L"en-US"; } } // namespace webkit_glue