blob: aadbd390cf871915a9bafc8294c4cda5849a80b3 [file] [log] [blame]
// Copyright (c) 2012 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.
// This file contains the definition for LayoutTestController.
#include <vector>
#include "webkit/tools/test_shell/layout_test_controller.h"
#include "base/base64.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "net/base/net_util.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebAnimationController.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebConsoleMessage.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDeviceOrientation.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDeviceOrientationClientMock.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebGeolocationClientMock.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebScriptSource.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityPolicy.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSettings.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebSize.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURL.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
#include "webkit/glue/dom_operations.h"
#include "webkit/glue/webkit_glue.h"
#include "webkit/glue/webpreferences.h"
#include "webkit/support/simple_database_system.h"
#include "webkit/tools/test_shell/notification_presenter.h"
#include "webkit/tools/test_shell/simple_resource_loader_bridge.h"
#include "webkit/tools/test_shell/test_navigation_controller.h"
#include "webkit/tools/test_shell/test_shell.h"
#include "webkit/tools/test_shell/test_shell_devtools_agent.h"
#include "webkit/tools/test_shell/test_webview_delegate.h"
using std::string;
using WebKit::WebBindings;
using WebKit::WebConsoleMessage;
using WebKit::WebElement;
using WebKit::WebScriptSource;
using WebKit::WebSecurityPolicy;
using WebKit::WebSize;
using WebKit::WebString;
using WebKit::WebURL;
TestShell* LayoutTestController::shell_ = NULL;
// Most of these flags need to be cleared in Reset() so that they get turned
// off between each test run.
bool LayoutTestController::accepts_editing_ = true;
bool LayoutTestController::wait_until_done_ = false;
bool LayoutTestController::can_open_windows_ = false;
bool LayoutTestController::close_remaining_windows_ = true;
bool LayoutTestController::stop_provisional_frame_loads_ = false;
LayoutTestController::WorkQueue LayoutTestController::work_queue_;
LayoutTestController::LayoutTestController(TestShell* shell) :
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
// Set static shell_ variable since we can't do it in an initializer list.
// We also need to be careful not to assign shell_ to new windows which are
// temporary.
if (NULL == shell_)
shell_ = shell;
// Initialize the map that associates methods of this class with the names
// they will use when called by JavaScript. The actual binding of those
// names to their methods will be done by calling BindToJavaScript() (defined
// by CppBoundClass, the parent to LayoutTestController).
BindCallback("waitUntilDone",
base::Bind(&LayoutTestController::waitUntilDone,
base::Unretained(this)));
BindCallback("notifyDone",
base::Bind(&LayoutTestController::notifyDone,
base::Unretained(this)));
// The fallback method is called when an unknown method is invoked.
BindFallbackCallback(base::Bind(&LayoutTestController::fallbackMethod,
base::Unretained(this)));
}
LayoutTestController::~LayoutTestController() {
}
LayoutTestController::WorkQueue::WorkQueue() {
}
LayoutTestController::WorkQueue::~WorkQueue() {
Reset();
}
void LayoutTestController::WorkQueue::ProcessWorkSoon() {
if (shell_->delegate()->top_loading_frame())
return;
if (!queue_.empty()) {
// We delay processing queued work to avoid recursion problems.
timer_.Start(FROM_HERE, base::TimeDelta(), this, &WorkQueue::ProcessWork);
} else if (!wait_until_done_) {
shell_->TestFinished();
}
}
void LayoutTestController::WorkQueue::ProcessWork() {
// Quit doing work once a load is in progress.
while (!queue_.empty()) {
bool started_load = queue_.front()->Run(shell_);
delete queue_.front();
queue_.pop();
if (started_load)
return;
}
if (!wait_until_done_ && !shell_->delegate()->top_loading_frame())
shell_->TestFinished();
}
void LayoutTestController::WorkQueue::Reset() {
frozen_ = false;
while (!queue_.empty()) {
delete queue_.front();
queue_.pop();
}
}
void LayoutTestController::WorkQueue::AddWork(WorkItem* work) {
if (frozen_) {
delete work;
return;
}
queue_.push(work);
}
void LayoutTestController::waitUntilDone(
const CppArgumentList& args, CppVariant* result) {
bool is_debugger_present = false;
#if defined(OS_WIN)
// TODO(ojan): Make cross-platform.
is_debugger_present = ::IsDebuggerPresent();
#endif
if (!is_debugger_present) {
// TODO(ojan): Use base::OneShotTimer. For some reason, using OneShotTimer
// seems to cause layout test failures on the try bots.
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&LayoutTestController::notifyDoneTimedOut,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(shell_->GetLayoutTestTimeout()));
}
wait_until_done_ = true;
result->SetNull();
}
void LayoutTestController::notifyDone(
const CppArgumentList& args, CppVariant* result) {
// Test didn't timeout. Kill the timeout timer.
weak_factory_.InvalidateWeakPtrs();
completeNotifyDone(false);
result->SetNull();
}
void LayoutTestController::notifyDoneTimedOut() {
completeNotifyDone(true);
}
void LayoutTestController::completeNotifyDone(bool is_timeout) {
if (shell_->layout_test_mode() && wait_until_done_ &&
!shell_->delegate()->top_loading_frame() && work_queue_.empty()) {
if (is_timeout)
shell_->TestTimedOut();
else
shell_->TestFinished();
}
wait_until_done_ = false;
}
void LayoutTestController::Reset() {
if (shell_) {
shell_->webView()->setZoomLevel(false, 0);
shell_->webView()->setTabKeyCyclesThroughElements(true);
#if defined(TOOLKIT_GTK)
// (Constants copied because we can't depend on the header that defined
// them from this file.)
shell_->webView()->setSelectionColors(
0xff1e90ff, 0xff000000, 0xffc8c8c8, 0xff323232);
#endif // defined(TOOLKIT_GTK)
shell_->webView()->removeAllUserContent();
// Reset editingBehavior to a reasonable default between tests
// see http://trac.webkit.org/changeset/60158
#if defined(OS_MACOSX)
shell_->webView()->settings()->setEditingBehavior(
WebKit::WebSettings::EditingBehaviorMac);
#else
shell_->webView()->settings()->setEditingBehavior(
WebKit::WebSettings::EditingBehaviorWin);
#endif
}
accepts_editing_ = true;
wait_until_done_ = false;
can_open_windows_ = false;
stop_provisional_frame_loads_ = false;
SimpleResourceLoaderBridge::SetAcceptAllCookies(false);
WebSecurityPolicy::resetOriginAccessWhitelists();
// Reset the default quota for each origin to 5MB
SimpleDatabaseSystem::GetInstance()->SetDatabaseQuota(5 * 1024 * 1024);
setlocale(LC_ALL, "");
if (close_remaining_windows_) {
// Iterate through the window list and close everything except the original
// shell. We don't want to delete elements as we're iterating, so we copy
// to a temp vector first.
WindowList* windows = TestShell::windowList();
std::vector<gfx::NativeWindow> windows_to_delete;
for (WindowList::iterator i = windows->begin(); i != windows->end(); ++i) {
if (*i != shell_->mainWnd())
windows_to_delete.push_back(*i);
}
DCHECK(windows_to_delete.size() + 1 == windows->size());
for (size_t i = 0; i < windows_to_delete.size(); ++i) {
TestShell::DestroyWindow(windows_to_delete[i]);
}
DCHECK(windows->size() == 1);
} else {
// Reset the value
close_remaining_windows_ = true;
}
work_queue_.Reset();
}
void LayoutTestController::LocationChangeDone() {
// no more new work after the first complete load.
work_queue_.set_frozen(true);
if (!wait_until_done_)
work_queue_.ProcessWorkSoon();
}
void LayoutTestController::PolicyDelegateDone() {
if (!shell_->layout_test_mode())
return;
DCHECK(wait_until_done_);
shell_->TestFinished();
wait_until_done_ = false;
}
void LayoutTestController::fallbackMethod(
const CppArgumentList& args, CppVariant* result) {
std::string message(
"JavaScript ERROR: unknown method called on LayoutTestController");
if (!shell_->layout_test_mode()) {
logging::LogMessage("CONSOLE:", 0).stream() << message;
} else {
printf("CONSOLE MESSAGE: %s\n", message.c_str());
}
result->SetNull();
}
void LayoutTestController::LogErrorToConsole(const std::string& text) {
shell_->delegate()->didAddMessageToConsole(
WebConsoleMessage(WebConsoleMessage::LevelError,
WebString::fromUTF8(text)),
WebString(), 0);
}