diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-22 22:58:22 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-22 22:58:22 +0000 |
commit | df8e899b92196a772511a165130f1fe08e199cb8 (patch) | |
tree | 893ca8821adc6165823f3c9a10dd0edfeb2e49e1 /content | |
parent | 5b77de94051020ca0aef549dee0cb33f7a737d88 (diff) | |
download | chromium_src-df8e899b92196a772511a165130f1fe08e199cb8.zip chromium_src-df8e899b92196a772511a165130f1fe08e199cb8.tar.gz chromium_src-df8e899b92196a772511a165130f1fe08e199cb8.tar.bz2 |
Move core pieces of chrome\browser. I've only gone up to "g", will do the rest in another cl.
TBR=avi
Review URL: http://codereview.chromium.org/6538100
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75652 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
30 files changed, 4383 insertions, 0 deletions
diff --git a/content/browser/browser_child_process_host.cc b/content/browser/browser_child_process_host.cc new file mode 100644 index 0000000..23ed85d --- /dev/null +++ b/content/browser/browser_child_process_host.cc @@ -0,0 +1,249 @@ +// Copyright (c) 2010 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 "content/browser/browser_child_process_host.h" + +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "chrome/app/breakpad_mac.h" +#include "chrome/common/child_process_logging.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths_internal.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/plugin_messages.h" +#include "chrome/common/process_watcher.h" +#include "chrome/common/result_codes.h" +#include "chrome/installer/util/google_update_settings.h" +#include "content/browser/browser_thread.h" + +#if defined(OS_LINUX) +#include "base/linux_util.h" +#endif // OS_LINUX + +namespace { + +typedef std::list<BrowserChildProcessHost*> ChildProcessList; +static base::LazyInstance<ChildProcessList> g_child_process_list( + base::LINKER_INITIALIZED); + +// The NotificationTask is used to notify about plugin process connection/ +// disconnection. It is needed because the notifications in the +// NotificationService must happen in the main thread. +class ChildNotificationTask : public Task { + public: + ChildNotificationTask( + NotificationType notification_type, ChildProcessInfo* info) + : notification_type_(notification_type), info_(*info) { } + + virtual void Run() { + NotificationService::current()-> + Notify(notification_type_, NotificationService::AllSources(), + Details<ChildProcessInfo>(&info_)); + } + + private: + NotificationType notification_type_; + ChildProcessInfo info_; +}; + +} // namespace + + +BrowserChildProcessHost::BrowserChildProcessHost( + ChildProcessInfo::ProcessType type, + ResourceDispatcherHost* resource_dispatcher_host, + ResourceMessageFilter::URLRequestContextOverride* + url_request_context_override) + : ChildProcessInfo(type, -1), + ALLOW_THIS_IN_INITIALIZER_LIST(client_(this)), + resource_dispatcher_host_(resource_dispatcher_host) { + Initialize(url_request_context_override); +} + +BrowserChildProcessHost::BrowserChildProcessHost( + ChildProcessInfo::ProcessType type, + ResourceDispatcherHost* resource_dispatcher_host) + : ChildProcessInfo(type, -1), + ALLOW_THIS_IN_INITIALIZER_LIST(client_(this)), + resource_dispatcher_host_(resource_dispatcher_host) { + Initialize(NULL); +} + +void BrowserChildProcessHost::Initialize( + ResourceMessageFilter::URLRequestContextOverride* + url_request_context_override) { + if (resource_dispatcher_host_) { + ResourceMessageFilter* resource_message_filter = new ResourceMessageFilter( + id(), type(), resource_dispatcher_host_); + if (url_request_context_override) { + resource_message_filter->set_url_request_context_override( + url_request_context_override); + } + AddFilter(resource_message_filter); + } + + g_child_process_list.Get().push_back(this); +} + +BrowserChildProcessHost::~BrowserChildProcessHost() { + g_child_process_list.Get().remove(this); +} + +// static +void BrowserChildProcessHost::SetCrashReporterCommandLine( + CommandLine* command_line) { +#if defined(USE_LINUX_BREAKPAD) + if (IsCrashReporterEnabled()) { + command_line->AppendSwitchASCII(switches::kEnableCrashReporter, + child_process_logging::GetClientId() + "," + base::GetLinuxDistro()); + } +#elif defined(OS_MACOSX) + if (IsCrashReporterEnabled()) { + command_line->AppendSwitchASCII(switches::kEnableCrashReporter, + child_process_logging::GetClientId()); + } +#endif // OS_MACOSX +} + +// static +void BrowserChildProcessHost::TerminateAll() { + // Make a copy since the ChildProcessHost dtor mutates the original list. + ChildProcessList copy = g_child_process_list.Get(); + STLDeleteElements(©); +} + +void BrowserChildProcessHost::Launch( +#if defined(OS_WIN) + const FilePath& exposed_dir, +#elif defined(OS_POSIX) + bool use_zygote, + const base::environment_vector& environ, +#endif + CommandLine* cmd_line) { + child_process_.reset(new ChildProcessLauncher( +#if defined(OS_WIN) + exposed_dir, +#elif defined(OS_POSIX) + use_zygote, + environ, + channel()->GetClientFileDescriptor(), +#endif + cmd_line, + &client_)); +} + +base::ProcessHandle BrowserChildProcessHost::GetChildProcessHandle() const { + DCHECK(child_process_.get()) + << "Requesting a child process handle before launching."; + DCHECK(child_process_->GetHandle()) + << "Requesting a child process handle before launch has completed OK."; + return child_process_->GetHandle(); +} + +void BrowserChildProcessHost::ForceShutdown() { + g_child_process_list.Get().remove(this); + ChildProcessHost::ForceShutdown(); +} + +void BrowserChildProcessHost::Notify(NotificationType type) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, new ChildNotificationTask(type, this)); +} + +base::TerminationStatus BrowserChildProcessHost::GetChildTerminationStatus( + int* exit_code) { + return child_process_->GetChildTerminationStatus(exit_code); +} + +void BrowserChildProcessHost::OnChildDied() { + if (handle() != base::kNullProcessHandle) { + int exit_code; + base::TerminationStatus status = GetChildTerminationStatus(&exit_code); + switch (status) { + case base::TERMINATION_STATUS_PROCESS_CRASHED: { + OnProcessCrashed(exit_code); + + // Report that this child process crashed. + Notify(NotificationType::CHILD_PROCESS_CRASHED); + UMA_HISTOGRAM_COUNTS("ChildProcess.Crashes", this->type()); + break; + } + case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: { + OnProcessWasKilled(exit_code); + + // Report that this child process was killed. + Notify(NotificationType::CHILD_PROCESS_WAS_KILLED); + UMA_HISTOGRAM_COUNTS("ChildProcess.Kills", this->type()); + break; + } + default: + break; + } + // Notify in the main loop of the disconnection. + Notify(NotificationType::CHILD_PROCESS_HOST_DISCONNECTED); + } + ChildProcessHost::OnChildDied(); +} + +void BrowserChildProcessHost::ShutdownStarted() { + // Must remove the process from the list now, in case it gets used for a + // new instance before our watcher tells us that the process terminated. + g_child_process_list.Get().remove(this); +} + +BrowserChildProcessHost::ClientHook::ClientHook(BrowserChildProcessHost* host) + : host_(host) { +} + +void BrowserChildProcessHost::ClientHook::OnProcessLaunched() { + if (!host_->child_process_->GetHandle()) { + host_->OnChildDied(); + return; + } + host_->set_handle(host_->child_process_->GetHandle()); + host_->OnProcessLaunched(); +} + +BrowserChildProcessHost::Iterator::Iterator() + : all_(true), type_(UNKNOWN_PROCESS) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) << + "ChildProcessInfo::Iterator must be used on the IO thread."; + iterator_ = g_child_process_list.Get().begin(); +} + +BrowserChildProcessHost::Iterator::Iterator(ChildProcessInfo::ProcessType type) + : all_(false), type_(type) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) << + "ChildProcessInfo::Iterator must be used on the IO thread."; + iterator_ = g_child_process_list.Get().begin(); + if (!Done() && (*iterator_)->type() != type_) + ++(*this); +} + +BrowserChildProcessHost* BrowserChildProcessHost::Iterator::operator++() { + do { + ++iterator_; + if (Done()) + break; + + if (!all_ && (*iterator_)->type() != type_) + continue; + + return *iterator_; + } while (true); + + return NULL; +} + +bool BrowserChildProcessHost::Iterator::Done() { + return iterator_ == g_child_process_list.Get().end(); +} diff --git a/content/browser/browser_child_process_host.h b/content/browser/browser_child_process_host.h new file mode 100644 index 0000000..f57ec5f --- /dev/null +++ b/content/browser/browser_child_process_host.h @@ -0,0 +1,143 @@ +// Copyright (c) 2011 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. + +#ifndef CONTENT_BROWSER_BROWSER_CHILD_PROCESS_HOST_H_ +#define CONTENT_BROWSER_BROWSER_CHILD_PROCESS_HOST_H_ +#pragma once + +#include <list> + +#include "chrome/common/child_process_host.h" +#include "chrome/common/child_process_info.h" +#include "content/browser/child_process_launcher.h" +#include "content/browser/renderer_host/resource_message_filter.h" + +class ResourceDispatcherHost; + +// Plugins/workers and other child processes that live on the IO thread should +// derive from this class. +// +// [Browser]RenderProcessHost is the main exception that doesn't derive from +// this class. That project lives on the UI thread. +class BrowserChildProcessHost : public ChildProcessHost, + public ChildProcessInfo, + public ChildProcessLauncher::Client { + public: + virtual ~BrowserChildProcessHost(); + + // Prepares command_line for crash reporting as appropriate. On Linux and + // Mac, a command-line flag to enable crash reporting in the child process + // will be appended if needed, because the child process may not have access + // to the data that determines the status of crash reporting in the + // currently-executing process. This function is a no-op on Windows. + static void SetCrashReporterCommandLine(CommandLine* command_line); + + // Terminates all child processes and deletes each ChildProcessHost instance. + static void TerminateAll(); + + // The Iterator class allows iteration through either all child processes, or + // ones of a specific type, depending on which constructor is used. Note that + // this should be done from the IO thread and that the iterator should not be + // kept around as it may be invalidated on subsequent event processing in the + // event loop. + class Iterator { + public: + Iterator(); + explicit Iterator(ChildProcessInfo::ProcessType type); + BrowserChildProcessHost* operator->() { return *iterator_; } + BrowserChildProcessHost* operator*() { return *iterator_; } + BrowserChildProcessHost* operator++(); + bool Done(); + + private: + bool all_; + ChildProcessInfo::ProcessType type_; + std::list<BrowserChildProcessHost*>::iterator iterator_; + }; + + protected: + // |resource_dispatcher_host| may be NULL to indicate none is needed for + // this process type. + // |url_request_context_getter| allows derived classes to override the + // net::URLRequestContext. + BrowserChildProcessHost( + ChildProcessInfo::ProcessType type, + ResourceDispatcherHost* resource_dispatcher_host, + ResourceMessageFilter::URLRequestContextOverride* + url_request_context_override); + + // A convenient constructor for those classes that want to use the default + // net::URLRequestContext. + BrowserChildProcessHost( + ChildProcessInfo::ProcessType type, + ResourceDispatcherHost* resource_dispatcher_host); + + // Derived classes call this to launch the child process asynchronously. + void Launch( +#if defined(OS_WIN) + const FilePath& exposed_dir, +#elif defined(OS_POSIX) + bool use_zygote, + const base::environment_vector& environ, +#endif + CommandLine* cmd_line); + + // Returns the handle of the child process. This can be called only after + // OnProcessLaunched is called or it will be invalid and may crash. + base::ProcessHandle GetChildProcessHandle() const; + + // ChildProcessLauncher::Client implementation. + virtual void OnProcessLaunched() {} + + // Derived classes can override this to know if the process crashed. + // |exit_code| is the status returned when the process crashed (for + // posix, as returned from waitpid(), for Windows, as returned from + // GetExitCodeProcess()). + virtual void OnProcessCrashed(int exit_code) {} + + // Derived classes can override this to know if the process was + // killed. |exit_code| is the status returned when the process + // was killed (for posix, as returned from waitpid(), for Windows, + // as returned from GetExitCodeProcess()). + virtual void OnProcessWasKilled(int exit_code) {} + + // Returns the termination status of a child. |exit_code| is the + // status returned when the process exited (for posix, as returned + // from waitpid(), for Windows, as returned from + // GetExitCodeProcess()). |exit_code| may be NULL. + virtual base::TerminationStatus GetChildTerminationStatus(int* exit_code); + + // Overrides from ChildProcessHost + virtual void OnChildDied(); + virtual void ShutdownStarted(); + virtual void Notify(NotificationType type); + // Extends the base class implementation and removes this host from + // the host list. Calls ChildProcessHost::ForceShutdown + virtual void ForceShutdown(); + + ResourceDispatcherHost* resource_dispatcher_host() { + return resource_dispatcher_host_; + } + + private: + void Initialize(ResourceMessageFilter::URLRequestContextOverride* + url_request_context_override); + + // By using an internal class as the ChildProcessLauncher::Client, we can + // intercept OnProcessLaunched and do our own processing before + // calling the subclass' implementation. + class ClientHook : public ChildProcessLauncher::Client { + public: + explicit ClientHook(BrowserChildProcessHost* host); + virtual void OnProcessLaunched(); + private: + BrowserChildProcessHost* host_; + }; + ClientHook client_; + // May be NULL if this current process has no resource dispatcher host. + ResourceDispatcherHost* resource_dispatcher_host_; + scoped_ptr<ChildProcessLauncher> child_process_; +}; + +#endif // CONTENT_BROWSER_BROWSER_CHILD_PROCESS_HOST_H_ diff --git a/content/browser/browser_message_filter.cc b/content/browser/browser_message_filter.cc new file mode 100644 index 0000000..e116153 --- /dev/null +++ b/content/browser/browser_message_filter.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2010 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 "content/browser/browser_message_filter.h" + +#include "base/logging.h" +#include "base/process.h" +#include "base/process_util.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/common/result_codes.h" + +BrowserMessageFilter::BrowserMessageFilter() + : channel_(NULL), peer_handle_(base::kNullProcessHandle) { +} + +BrowserMessageFilter::~BrowserMessageFilter() { + base::CloseProcessHandle(peer_handle_); +} + +void BrowserMessageFilter::OnFilterAdded(IPC::Channel* channel) { + channel_ = channel; +} + +void BrowserMessageFilter::OnChannelClosing() { + channel_ = NULL; +} + +void BrowserMessageFilter::OnChannelConnected(int32 peer_pid) { + if (!base::OpenProcessHandle(peer_pid, &peer_handle_)) { + NOTREACHED(); + } +} + +bool BrowserMessageFilter::Send(IPC::Message* message) { + if (message->is_sync()) { + // We don't support sending synchronous messages from the browser. If we + // really needed it, we can make this class derive from SyncMessageFilter + // but it seems better to not allow sending synchronous messages from the + // browser, since it might allow a corrupt/malicious renderer to hang us. + NOTREACHED() << "Can't send sync message through BrowserMessageFilter!"; + return false; + } + + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod(this, &BrowserMessageFilter::Send, message)); + return true; + } + + if (channel_) + return channel_->Send(message); + + delete message; + return false; +} + +void BrowserMessageFilter::OverrideThreadForMessage(const IPC::Message& message, + BrowserThread::ID* thread) { +} + +bool BrowserMessageFilter::OnMessageReceived(const IPC::Message& message) { + BrowserThread::ID thread = BrowserThread::IO; + OverrideThreadForMessage(message, &thread); + if (thread == BrowserThread::IO) + return DispatchMessage(message); + + BrowserThread::PostTask( + thread, FROM_HERE, + NewRunnableMethod( + this, &BrowserMessageFilter::DispatchMessage, message)); + return true; +} + +bool BrowserMessageFilter::DispatchMessage(const IPC::Message& message) { + bool message_was_ok = true; + bool rv = OnMessageReceived(message, &message_was_ok); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO) || rv) << + "Must handle messages that were dispatched to another thread!"; + if (!message_was_ok) { + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_BMF")); + BadMessageReceived(); + } + + return rv; +} + +void BrowserMessageFilter::BadMessageReceived() { + base::KillProcess(peer_handle(), ResultCodes::KILLED_BAD_MESSAGE, false); +} diff --git a/content/browser/browser_message_filter.h b/content/browser/browser_message_filter.h new file mode 100644 index 0000000..e557dfd --- /dev/null +++ b/content/browser/browser_message_filter.h @@ -0,0 +1,64 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_BROWSER_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_BROWSER_MESSAGE_FILTER_H_ +#pragma once + +#include "base/process.h" +#include "content/browser/browser_thread.h" +#include "ipc/ipc_channel_proxy.h" + +// Base class for message filters in the browser process. You can receive and +// send messages on any thread. +class BrowserMessageFilter : public IPC::ChannelProxy::MessageFilter, + public IPC::Message::Sender { + public: + BrowserMessageFilter(); + virtual ~BrowserMessageFilter(); + + // IPC::ChannelProxy::MessageFilter methods. If you override them, make sure + // to call them as well. These are always called on the IO thread. + virtual void OnFilterAdded(IPC::Channel* channel); + virtual void OnChannelClosing(); + virtual void OnChannelConnected(int32 peer_pid); + // DON'T OVERRIDE THIS! Override the other version below. + virtual bool OnMessageReceived(const IPC::Message& message); + + // IPC::Message::Sender implementation. Can be called on any thread. Can't + // send sync messages (since we don't want to block the browser on any other + // process). + virtual bool Send(IPC::Message* message); + + // If you want the given message to be dispatched to your OnMessageReceived on + // a different thread, change |thread| to the id of the target thread. + // If you don't handle this message, or want to keep it on the IO thread, do + // nothing. + virtual void OverrideThreadForMessage(const IPC::Message& message, + BrowserThread::ID* thread); + + // Override this to receive messages. + // Your function will normally be called on the IO thread. However, if your + // OverrideThreadForMessage modifies the thread used to dispatch the message, + // your function will be called on the requested thread. + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) = 0; + + // Can be called on any thread, after OnChannelConnected is called. + base::ProcessHandle peer_handle() { return peer_handle_; } + + protected: + // Call this if a message couldn't be deserialized. This kills the renderer. + // Can be called on any thread. + virtual void BadMessageReceived(); + + private: + // Dispatches a message to the derived class. + bool DispatchMessage(const IPC::Message& message); + + IPC::Channel* channel_; + base::ProcessHandle peer_handle_; +}; + +#endif // CONTENT_BROWSER_BROWSER_MESSAGE_FILTER_H_ diff --git a/content/browser/browser_thread.cc b/content/browser/browser_thread.cc new file mode 100644 index 0000000..c0e4355 --- /dev/null +++ b/content/browser/browser_thread.cc @@ -0,0 +1,233 @@ +// Copyright (c) 2010 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 "content/browser/browser_thread.h" + +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/threading/thread_restrictions.h" + +// Friendly names for the well-known threads. +static const char* browser_thread_names[BrowserThread::ID_COUNT] = { + "", // UI (name assembled in browser_main.cc). + "Chrome_DBThread", // DB + "Chrome_WebKitThread", // WEBKIT + "Chrome_FileThread", // FILE + "Chrome_ProcessLauncherThread", // PROCESS_LAUNCHER + "Chrome_CacheThread", // CACHE + "Chrome_IOThread", // IO +#if defined(USE_X11) + "Chrome_Background_X11Thread", // BACKGROUND_X11 +#endif +}; + +// An implementation of MessageLoopProxy to be used in conjunction +// with BrowserThread. +class BrowserThreadMessageLoopProxy : public base::MessageLoopProxy { + public: + explicit BrowserThreadMessageLoopProxy(BrowserThread::ID identifier) + : id_(identifier) { + } + + // MessageLoopProxy implementation. + virtual bool PostTask(const tracked_objects::Location& from_here, + Task* task) { + return BrowserThread::PostTask(id_, from_here, task); + } + + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + Task* task, int64 delay_ms) { + return BrowserThread::PostDelayedTask(id_, from_here, task, delay_ms); + } + + virtual bool PostNonNestableTask(const tracked_objects::Location& from_here, + Task* task) { + return BrowserThread::PostNonNestableTask(id_, from_here, task); + } + + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms) { + return BrowserThread::PostNonNestableDelayedTask(id_, from_here, task, + delay_ms); + } + virtual bool BelongsToCurrentThread() { + return BrowserThread::CurrentlyOn(id_); + } + + private: + BrowserThread::ID id_; + DISALLOW_COPY_AND_ASSIGN(BrowserThreadMessageLoopProxy); +}; + + +base::Lock BrowserThread::lock_; + +BrowserThread* BrowserThread::browser_threads_[ID_COUNT]; + +BrowserThread::BrowserThread(BrowserThread::ID identifier) + : Thread(browser_thread_names[identifier]), + identifier_(identifier) { + Initialize(); +} + +BrowserThread::BrowserThread(ID identifier, MessageLoop* message_loop) + : Thread(message_loop->thread_name().c_str()), + identifier_(identifier) { + set_message_loop(message_loop); + Initialize(); +} + +void BrowserThread::Initialize() { + base::AutoLock lock(lock_); + DCHECK(identifier_ >= 0 && identifier_ < ID_COUNT); + DCHECK(browser_threads_[identifier_] == NULL); + browser_threads_[identifier_] = this; +} + +BrowserThread::~BrowserThread() { + // Stop the thread here, instead of the parent's class destructor. This is so + // that if there are pending tasks that run, code that checks that it's on the + // correct BrowserThread succeeds. + Stop(); + + base::AutoLock lock(lock_); + browser_threads_[identifier_] = NULL; +#ifndef NDEBUG + // Double check that the threads are ordered correctly in the enumeration. + for (int i = identifier_ + 1; i < ID_COUNT; ++i) { + DCHECK(!browser_threads_[i]) << + "Threads must be listed in the reverse order that they die"; + } +#endif +} + +// static +bool BrowserThread::IsWellKnownThread(ID identifier) { + base::AutoLock lock(lock_); + return (identifier >= 0 && identifier < ID_COUNT && + browser_threads_[identifier]); +} + +// static +bool BrowserThread::CurrentlyOn(ID identifier) { + // We shouldn't use MessageLoop::current() since it uses LazyInstance which + // may be deleted by ~AtExitManager when a WorkerPool thread calls this + // function. + // http://crbug.com/63678 + base::ThreadRestrictions::ScopedAllowSingleton allow_singleton; + base::AutoLock lock(lock_); + DCHECK(identifier >= 0 && identifier < ID_COUNT); + return browser_threads_[identifier] && + browser_threads_[identifier]->message_loop() == MessageLoop::current(); +} + +// static +bool BrowserThread::IsMessageLoopValid(ID identifier) { + base::AutoLock lock(lock_); + DCHECK(identifier >= 0 && identifier < ID_COUNT); + return browser_threads_[identifier] && + browser_threads_[identifier]->message_loop(); +} + +// static +bool BrowserThread::PostTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task) { + return PostTaskHelper(identifier, from_here, task, 0, true); +} + +// static +bool BrowserThread::PostDelayedTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms) { + return PostTaskHelper(identifier, from_here, task, delay_ms, true); +} + +// static +bool BrowserThread::PostNonNestableTask( + ID identifier, + const tracked_objects::Location& from_here, + Task* task) { + return PostTaskHelper(identifier, from_here, task, 0, false); +} + +// static +bool BrowserThread::PostNonNestableDelayedTask( + ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms) { + return PostTaskHelper(identifier, from_here, task, delay_ms, false); +} + +// static +bool BrowserThread::GetCurrentThreadIdentifier(ID* identifier) { + // We shouldn't use MessageLoop::current() since it uses LazyInstance which + // may be deleted by ~AtExitManager when a WorkerPool thread calls this + // function. + // http://crbug.com/63678 + base::ThreadRestrictions::ScopedAllowSingleton allow_singleton; + MessageLoop* cur_message_loop = MessageLoop::current(); + for (int i = 0; i < ID_COUNT; ++i) { + if (browser_threads_[i] && + browser_threads_[i]->message_loop() == cur_message_loop) { + *identifier = browser_threads_[i]->identifier_; + return true; + } + } + + return false; +} + +// static +scoped_refptr<base::MessageLoopProxy> +BrowserThread::GetMessageLoopProxyForThread( + ID identifier) { + scoped_refptr<base::MessageLoopProxy> proxy( + new BrowserThreadMessageLoopProxy(identifier)); + return proxy; +} + +// static +bool BrowserThread::PostTaskHelper( + ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms, + bool nestable) { + DCHECK(identifier >= 0 && identifier < ID_COUNT); + // Optimization: to avoid unnecessary locks, we listed the ID enumeration in + // order of lifetime. So no need to lock if we know that the other thread + // outlives this one. + // Note: since the array is so small, ok to loop instead of creating a map, + // which would require a lock because std::map isn't thread safe, defeating + // the whole purpose of this optimization. + ID current_thread; + bool guaranteed_to_outlive_target_thread = + GetCurrentThreadIdentifier(¤t_thread) && + current_thread >= identifier; + + if (!guaranteed_to_outlive_target_thread) + lock_.Acquire(); + + MessageLoop* message_loop = browser_threads_[identifier] ? + browser_threads_[identifier]->message_loop() : NULL; + if (message_loop) { + if (nestable) { + message_loop->PostDelayedTask(from_here, task, delay_ms); + } else { + message_loop->PostNonNestableDelayedTask(from_here, task, delay_ms); + } + } else { + delete task; + } + + if (!guaranteed_to_outlive_target_thread) + lock_.Release(); + + return !!message_loop; +} diff --git a/content/browser/browser_thread.h b/content/browser/browser_thread.h new file mode 100644 index 0000000..d5e4473 --- /dev/null +++ b/content/browser/browser_thread.h @@ -0,0 +1,209 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_BROWSER_THREAD_H_ +#define CONTENT_BROWSER_BROWSER_THREAD_H_ +#pragma once + +#include "base/synchronization/lock.h" +#include "base/task.h" +#include "base/threading/thread.h" + +namespace base { +class MessageLoopProxy; +} + +/////////////////////////////////////////////////////////////////////////////// +// BrowserThread +// +// This class represents a thread that is known by a browser-wide name. For +// example, there is one IO thread for the entire browser process, and various +// pieces of code find it useful to retrieve a pointer to the IO thread's +// Invoke a task by thread ID: +// +// BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task); +// +// The return value is false if the task couldn't be posted because the target +// thread doesn't exist. If this could lead to data loss, you need to check the +// result and restructure the code to ensure it doesn't occur. +// +// This class automatically handles the lifetime of different threads. +// It's always safe to call PostTask on any thread. If it's not yet created, +// the task is deleted. There are no race conditions. If the thread that the +// task is posted to is guaranteed to outlive the current thread, then no locks +// are used. You should never need to cache pointers to MessageLoops, since +// they're not thread safe. +class BrowserThread : public base::Thread { + public: + // An enumeration of the well-known threads. + // NOTE: threads must be listed in the order of their life-time, with each + // thread outliving every other thread below it. + enum ID { + // The main thread in the browser. + UI, + + // This is the thread that interacts with the database. + DB, + + // This is the "main" thread for WebKit within the browser process when + // NOT in --single-process mode. + WEBKIT, + + // This is the thread that interacts with the file system. + FILE, + + // Used to launch and terminate processes. + PROCESS_LAUNCHER, + + // This is the thread to handle slow HTTP cache operations. + CACHE, + + // This is the thread that processes IPC and network messages. + IO, + +#if defined(USE_X11) + // This thread has a second connection to the X server and is used to + // process UI requests when routing the request to the UI thread would risk + // deadlock. + BACKGROUND_X11, +#endif + + // This identifier does not represent a thread. Instead it counts the + // number of well-known threads. Insert new well-known threads before this + // identifier. + ID_COUNT + }; + + // Construct a BrowserThread with the supplied identifier. It is an error + // to construct a BrowserThread that already exists. + explicit BrowserThread(ID identifier); + + // Special constructor for the main (UI) thread and unittests. We use a dummy + // thread here since the main thread already exists. + BrowserThread(ID identifier, MessageLoop* message_loop); + + virtual ~BrowserThread(); + + // These are the same methods in message_loop.h, but are guaranteed to either + // get posted to the MessageLoop if it's still alive, or be deleted otherwise. + // They return true iff the thread existed and the task was posted. Note that + // even if the task is posted, there's no guarantee that it will run, since + // the target thread may already have a Quit message in its queue. + static bool PostTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task); + static bool PostDelayedTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms); + static bool PostNonNestableTask(ID identifier, + const tracked_objects::Location& from_here, + Task* task); + static bool PostNonNestableDelayedTask( + ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms); + + template <class T> + static bool DeleteSoon(ID identifier, + const tracked_objects::Location& from_here, + const T* object) { + return PostNonNestableTask( + identifier, from_here, new DeleteTask<T>(object)); + } + + template <class T> + static bool ReleaseSoon(ID identifier, + const tracked_objects::Location& from_here, + const T* object) { + return PostNonNestableTask( + identifier, from_here, new ReleaseTask<T>(object)); + } + + // Callable on any thread. Returns whether the given ID corresponds to a well + // known thread. + static bool IsWellKnownThread(ID identifier); + + // Callable on any thread. Returns whether you're currently on a particular + // thread. + static bool CurrentlyOn(ID identifier); + + // Callable on any thread. Returns whether the threads message loop is valid. + // If this returns false it means the thread is in the process of shutting + // down. + static bool IsMessageLoopValid(ID identifier); + + // If the current message loop is one of the known threads, returns true and + // sets identifier to its ID. Otherwise returns false. + static bool GetCurrentThreadIdentifier(ID* identifier); + + // Callers can hold on to a refcounted MessageLoopProxy beyond the lifetime + // of the thread. + static scoped_refptr<base::MessageLoopProxy> GetMessageLoopProxyForThread( + ID identifier); + + // Use these templates in conjuction with RefCountedThreadSafe when you want + // to ensure that an object is deleted on a specific thread. This is needed + // when an object can hop between threads (i.e. IO -> FILE -> IO), and thread + // switching delays can mean that the final IO tasks executes before the FILE + // task's stack unwinds. This would lead to the object destructing on the + // FILE thread, which often is not what you want (i.e. to unregister from + // NotificationService, to notify other objects on the creating thread etc). + template<ID thread> + struct DeleteOnThread { + template<typename T> + static void Destruct(const T* x) { + if (CurrentlyOn(thread)) { + delete x; + } else { + DeleteSoon(thread, FROM_HERE, x); + } + } + }; + + // Sample usage: + // class Foo + // : public base::RefCountedThreadSafe< + // Foo, BrowserThread::DeleteOnIOThread> { + // + // ... + // private: + // friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>; + // friend class DeleteTask<Foo>; + // + // ~Foo(); + struct DeleteOnUIThread : public DeleteOnThread<UI> { }; + struct DeleteOnIOThread : public DeleteOnThread<IO> { }; + struct DeleteOnFileThread : public DeleteOnThread<FILE> { }; + struct DeleteOnDBThread : public DeleteOnThread<DB> { }; + struct DeleteOnWebKitThread : public DeleteOnThread<WEBKIT> { }; + + private: + // Common initialization code for the constructors. + void Initialize(); + + static bool PostTaskHelper( + ID identifier, + const tracked_objects::Location& from_here, + Task* task, + int64 delay_ms, + bool nestable); + + // The identifier of this thread. Only one thread can exist with a given + // identifier at a given time. + ID identifier_; + + // This lock protects |browser_threads_|. Do not read or modify that array + // without holding this lock. Do not block while holding this lock. + static base::Lock lock_; + + // An array of the BrowserThread objects. This array is protected by |lock_|. + // The threads are not owned by this array. Typically, the threads are owned + // on the UI thread by the g_browser_process object. BrowserThreads remove + // themselves from this array upon destruction. + static BrowserThread* browser_threads_[ID_COUNT]; +}; + +#endif // CONTENT_BROWSER_BROWSER_THREAD_H_ diff --git a/content/browser/browser_thread_unittest.cc b/content/browser/browser_thread_unittest.cc new file mode 100644 index 0000000..af2a869 --- /dev/null +++ b/content/browser/browser_thread_unittest.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2010 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 "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/scoped_ptr.h" +#include "content/browser/browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +class BrowserThreadTest : public testing::Test { + public: + void Release() const { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + loop_.PostTask(FROM_HERE, new MessageLoop::QuitTask); + } + + protected: + virtual void SetUp() { + ui_thread_.reset(new BrowserThread(BrowserThread::UI)); + file_thread_.reset(new BrowserThread(BrowserThread::FILE)); + ui_thread_->Start(); + file_thread_->Start(); + } + + virtual void TearDown() { + ui_thread_->Stop(); + file_thread_->Stop(); + } + + static void BasicFunction(MessageLoop* message_loop) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask); + } + + class DummyTask : public Task { + public: + explicit DummyTask(bool* deleted) : deleted_(deleted) { } + ~DummyTask() { + *deleted_ = true; + } + + void Run() { + CHECK(false); + } + + private: + bool* deleted_; + }; + + class DeletedOnFile + : public base::RefCountedThreadSafe< + DeletedOnFile, BrowserThread::DeleteOnFileThread> { + public: + explicit DeletedOnFile(MessageLoop* message_loop) + : message_loop_(message_loop) { } + + ~DeletedOnFile() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + } + + private: + MessageLoop* message_loop_; + }; + + class NeverDeleted + : public base::RefCountedThreadSafe< + NeverDeleted, BrowserThread::DeleteOnWebKitThread> { + public: + ~NeverDeleted() { + CHECK(false); + } + }; + + private: + scoped_ptr<BrowserThread> ui_thread_; + scoped_ptr<BrowserThread> file_thread_; + // It's kind of ugly to make this mutable - solely so we can post the Quit + // Task from Release(). This should be fixed. + mutable MessageLoop loop_; +}; + +TEST_F(BrowserThreadTest, PostTask) { + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableFunction(&BasicFunction, MessageLoop::current())); + MessageLoop::current()->Run(); +} + +TEST_F(BrowserThreadTest, Release) { + BrowserThread::ReleaseSoon(BrowserThread::UI, FROM_HERE, this); + MessageLoop::current()->Run(); +} + +TEST_F(BrowserThreadTest, TaskToNonExistentThreadIsDeleted) { + bool deleted = false; + BrowserThread::PostTask( + BrowserThread::WEBKIT, FROM_HERE, + new DummyTask(&deleted)); + EXPECT_TRUE(deleted); +} + +TEST_F(BrowserThreadTest, ReleasedOnCorrectThread) { + { + scoped_refptr<DeletedOnFile> test( + new DeletedOnFile(MessageLoop::current())); + } + MessageLoop::current()->Run(); +} + +TEST_F(BrowserThreadTest, NotReleasedIfTargetThreadNonExistent) { + scoped_refptr<NeverDeleted> test(new NeverDeleted()); +} + +TEST_F(BrowserThreadTest, PostTaskViaMessageLoopProxy) { + scoped_refptr<base::MessageLoopProxy> message_loop_proxy = + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE); + message_loop_proxy->PostTask(FROM_HERE, + NewRunnableFunction(&BasicFunction, + MessageLoop::current())); + MessageLoop::current()->Run(); +} + +TEST_F(BrowserThreadTest, ReleaseViaMessageLoopProxy) { + scoped_refptr<base::MessageLoopProxy> message_loop_proxy = + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); + message_loop_proxy->ReleaseSoon(FROM_HERE, this); + MessageLoop::current()->Run(); +} + +TEST_F(BrowserThreadTest, TaskToNonExistentThreadIsDeletedViaMessageLoopProxy) { + bool deleted = false; + scoped_refptr<base::MessageLoopProxy> message_loop_proxy = + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::WEBKIT); + message_loop_proxy->PostTask(FROM_HERE, new DummyTask(&deleted)); + EXPECT_TRUE(deleted); +} + +TEST_F(BrowserThreadTest, PostTaskViaMessageLoopProxyAfterThreadExits) { + scoped_ptr<BrowserThread> io_thread(new BrowserThread(BrowserThread::IO)); + io_thread->Start(); + io_thread->Stop(); + + bool deleted = false; + scoped_refptr<base::MessageLoopProxy> message_loop_proxy = + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO); + bool ret = message_loop_proxy->PostTask(FROM_HERE, new DummyTask(&deleted)); + EXPECT_FALSE(ret); + EXPECT_TRUE(deleted); +} + +TEST_F(BrowserThreadTest, PostTaskViaMessageLoopProxyAfterThreadIsDeleted) { + { + scoped_ptr<BrowserThread> io_thread(new BrowserThread(BrowserThread::IO)); + io_thread->Start(); + } + bool deleted = false; + scoped_refptr<base::MessageLoopProxy> message_loop_proxy = + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO); + bool ret = message_loop_proxy->PostTask(FROM_HERE, new DummyTask(&deleted)); + EXPECT_FALSE(ret); + EXPECT_TRUE(deleted); +} + diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc new file mode 100644 index 0000000..b2c514c --- /dev/null +++ b/content/browser/browsing_instance.cc @@ -0,0 +1,149 @@ +// Copyright (c) 2011 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 "content/browser/browsing_instance.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/webui/web_ui_factory.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/url_constants.h" +#include "content/browser/site_instance.h" + +// static +BrowsingInstance::ProfileSiteInstanceMap + BrowsingInstance::profile_site_instance_map_; + +BrowsingInstance::BrowsingInstance(Profile* profile) + : profile_(profile) { +} + +bool BrowsingInstance::ShouldUseProcessPerSite(const GURL& url) { + // Returns true if we should use the process-per-site model. This will be + // the case if the --process-per-site switch is specified, or in + // process-per-site-instance for particular sites (e.g., the new tab page). + + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + if (command_line.HasSwitch(switches::kProcessPerSite)) + return true; + + // We want to consolidate particular sites like extensions and WebUI whether + // it is in process-per-tab or process-per-site-instance. + // Note that --single-process may have been specified, but that affects the + // process creation logic in RenderProcessHost, so we do not need to worry + // about it here. + + if (url.SchemeIs(chrome::kExtensionScheme)) + return true; + + // DevTools pages have WebUI type but should not reuse the same host. + if (WebUIFactory::UseWebUIForURL(profile_, url) && + !url.SchemeIs(chrome::kChromeDevToolsScheme)) + return true; + + // In all other cases, don't use process-per-site logic. + return false; +} + +BrowsingInstance::SiteInstanceMap* BrowsingInstance::GetSiteInstanceMap( + Profile* profile, const GURL& url) { + if (!ShouldUseProcessPerSite(SiteInstance::GetEffectiveURL(profile, url))) { + // Not using process-per-site, so use a map specific to this instance. + return &site_instance_map_; + } + + // Otherwise, process-per-site is in use, at least for this URL. Look up the + // global map for this profile, creating an entry if necessary. + ProfileId runtime_id = profile ? profile->GetRuntimeId() + : Profile::InvalidProfileId; + return &profile_site_instance_map_[runtime_id]; +} + +bool BrowsingInstance::HasSiteInstance(const GURL& url) { + std::string site = + SiteInstance::GetSiteForURL(profile_, url).possibly_invalid_spec(); + + SiteInstanceMap* map = GetSiteInstanceMap(profile_, url); + SiteInstanceMap::iterator i = map->find(site); + return (i != map->end()); +} + +SiteInstance* BrowsingInstance::GetSiteInstanceForURL(const GURL& url) { + std::string site = + SiteInstance::GetSiteForURL(profile_, url).possibly_invalid_spec(); + + SiteInstanceMap* map = GetSiteInstanceMap(profile_, url); + SiteInstanceMap::iterator i = map->find(site); + if (i != map->end()) { + return i->second; + } + + // No current SiteInstance for this site, so let's create one. + SiteInstance* instance = new SiteInstance(this); + + // Set the site of this new SiteInstance, which will register it with us. + instance->SetSite(url); + return instance; +} + +void BrowsingInstance::RegisterSiteInstance(SiteInstance* site_instance) { + DCHECK(site_instance->browsing_instance() == this); + DCHECK(site_instance->has_site()); + std::string site = site_instance->site().possibly_invalid_spec(); + + // Only register if we don't have a SiteInstance for this site already. + // It's possible to have two SiteInstances point to the same site if two + // tabs are navigated there at the same time. (We don't call SetSite or + // register them until DidNavigate.) If there is a previously existing + // SiteInstance for this site, we just won't register the new one. + SiteInstanceMap* map = GetSiteInstanceMap(profile_, site_instance->site()); + SiteInstanceMap::iterator i = map->find(site); + if (i == map->end()) { + // Not previously registered, so register it. + (*map)[site] = site_instance; + } +} + +void BrowsingInstance::UnregisterSiteInstance(SiteInstance* site_instance) { + DCHECK(site_instance->browsing_instance() == this); + DCHECK(site_instance->has_site()); + std::string site = site_instance->site().possibly_invalid_spec(); + + // Only unregister the SiteInstance if it is the same one that is registered + // for the site. (It might have been an unregistered SiteInstance. See the + // comments in RegisterSiteInstance.) + + // We look for the site instance in both the local site_instance_map_ and also + // the static profile_site_instance_map_ - this is because the logic in + // ShouldUseProcessPerSite() can produce different results over the lifetime + // of Chrome (e.g. installation of apps with web extents can change our + // process-per-site policy for a given domain), so we don't know which map + // the site was put into when it was originally registered. + if (!RemoveSiteInstanceFromMap(&site_instance_map_, site, site_instance)) { + // Wasn't in our local map, so look in the static per-profile map. + ProfileId runtime_id = profile_ ? profile_->GetRuntimeId() + : Profile::InvalidProfileId; + RemoveSiteInstanceFromMap( + &profile_site_instance_map_[runtime_id], site, site_instance); + } +} + +bool BrowsingInstance::RemoveSiteInstanceFromMap(SiteInstanceMap* map, + const std::string& site, + SiteInstance* site_instance) { + SiteInstanceMap::iterator i = map->find(site); + if (i != map->end() && i->second == site_instance) { + // Matches, so erase it. + map->erase(i); + return true; + } + return false; +} + +BrowsingInstance::~BrowsingInstance() { + // We should only be deleted when all of the SiteInstances that refer to + // us are gone. + DCHECK(site_instance_map_.empty()); +} diff --git a/content/browser/browsing_instance.h b/content/browser/browsing_instance.h new file mode 100644 index 0000000..0c4bb2e --- /dev/null +++ b/content/browser/browsing_instance.h @@ -0,0 +1,135 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_BROWSING_INSTANCE_H_ +#define CONTENT_BROWSER_BROWSING_INSTANCE_H_ +#pragma once + +#include "base/hash_tables.h" +#include "base/ref_counted.h" +#include "chrome/browser/profiles/profile.h" + +class GURL; +class SiteInstance; + +/////////////////////////////////////////////////////////////////////////////// +// +// BrowsingInstance class +// +// A browsing instance corresponds to the notion of a "unit of related browsing +// contexts" in the HTML 5 spec. Intuitively, it represents a collection of +// tabs and frames that can have script connections to each other. In that +// sense, it reflects the user interface, and not the contents of the tabs and +// frames. +// +// We further subdivide a BrowsingInstance into SiteInstances, which represent +// the documents within each BrowsingInstance that are from the same site and +// thus can have script access to each other. Different SiteInstances can +// safely run in different processes, because their documents cannot access +// each other's contents (due to the same origin policy). +// +// It is important to only have one SiteInstance per site within a given +// BrowsingInstance. This is because any two documents from the same site +// might be able to script each other if they are in the same BrowsingInstance. +// Thus, they must be rendered in the same process. +// +// If the process-per-site model is in use, then we ensure that there is only +// one SiteInstance per site for the entire profile, not just for each +// BrowsingInstance. This reduces the number of renderer processes we create. +// (This is currently only true if --process-per-site is specified at the +// command line.) +// +// A BrowsingInstance is live as long as any SiteInstance has a reference to +// it. A SiteInstance is live as long as any NavigationEntry or RenderViewHost +// have references to it. Because both classes are RefCounted, they do not +// need to be manually deleted. +// +// Currently, the BrowsingInstance class is not visible outside of the +// SiteInstance class. To get a new SiteInstance that is part of the same +// BrowsingInstance, use SiteInstance::GetRelatedSiteInstance. Because of +// this, BrowsingInstances and SiteInstances are tested together in +// site_instance_unittest.cc. +// +/////////////////////////////////////////////////////////////////////////////// +class BrowsingInstance : public base::RefCounted<BrowsingInstance> { + public: + // Create a new BrowsingInstance. + explicit BrowsingInstance(Profile* profile); + + // Returns whether the process-per-site model is in use (globally or just for + // the given url), in which case we should ensure there is only one + // SiteInstance per site for the entire profile, not just for this + // BrowsingInstance. + virtual bool ShouldUseProcessPerSite(const GURL& url); + + // Get the profile to which this BrowsingInstance belongs. + Profile* profile() { return profile_; } + + // Returns whether this BrowsingInstance has registered a SiteInstance for + // the site of the given URL. + bool HasSiteInstance(const GURL& url); + + // Get the SiteInstance responsible for rendering the given URL. Should + // create a new one if necessary, but should not create more than one + // SiteInstance per site. + SiteInstance* GetSiteInstanceForURL(const GURL& url); + + // Adds the given SiteInstance to our map, to ensure that we do not create + // another SiteInstance for the same site. + void RegisterSiteInstance(SiteInstance* site_instance); + + // Removes the given SiteInstance from our map, after all references to it + // have been deleted. This means it is safe to create a new SiteInstance + // if the user later visits a page from this site, within this + // BrowsingInstance. + void UnregisterSiteInstance(SiteInstance* site_instance); + + protected: + friend class base::RefCounted<BrowsingInstance>; + + // Virtual to allow tests to extend it. + virtual ~BrowsingInstance(); + + private: + // Map of site to SiteInstance, to ensure we only have one SiteInstance per + // site. The site string should be the possibly_invalid_spec() of a GURL + // obtained with SiteInstance::GetSiteForURL. + typedef base::hash_map<std::string, SiteInstance*> SiteInstanceMap; + + // Map of Profile runtime Id to SiteInstanceMap, for use in the + // process-per-site model. + typedef base::hash_map<ProfileId, SiteInstanceMap> ProfileSiteInstanceMap; + + // Returns a pointer to the relevant SiteInstanceMap for this object. If the + // process-per-site model is in use, or if process-per-site-instance is in + // use and |url| matches a site for which we always use one process (e.g., + // the new tab page), then this returns the SiteInstanceMap for the entire + // profile. If not, this returns the BrowsingInstance's own private + // SiteInstanceMap. + SiteInstanceMap* GetSiteInstanceMap(Profile* profile, const GURL& url); + + // Utility routine which removes the passed SiteInstance from the passed + // SiteInstanceMap. + bool RemoveSiteInstanceFromMap(SiteInstanceMap* map, const std::string& site, + SiteInstance* site_instance); + + // Common profile to which all SiteInstances in this BrowsingInstance + // must belong. + Profile* const profile_; + + // Map of site to SiteInstance, to ensure we only have one SiteInstance per + // site. The site string should be the possibly_invalid_spec() of a GURL + // obtained with SiteInstance::GetSiteForURL. Note that this map may not + // contain every active SiteInstance, because a race exists where two + // SiteInstances can be assigned to the same site. This is ok in rare cases. + // This field is only used if we are not using process-per-site. + SiteInstanceMap site_instance_map_; + + // Global map of Profile to SiteInstanceMap, for process-per-site. + static ProfileSiteInstanceMap profile_site_instance_map_; + + DISALLOW_COPY_AND_ASSIGN(BrowsingInstance); +}; + +#endif // CONTENT_BROWSER_BROWSING_INSTANCE_H_ diff --git a/content/browser/cancelable_request.cc b/content/browser/cancelable_request.cc new file mode 100644 index 0000000..f1826fd --- /dev/null +++ b/content/browser/cancelable_request.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2011 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 "content/browser/cancelable_request.h" + +CancelableRequestProvider::CancelableRequestProvider() : next_handle_(1) { +} + +CancelableRequestProvider::~CancelableRequestProvider() { + // There may be requests whose result callback has not been run yet. We need + // to cancel them otherwise they may try and call us back after we've been + // deleted, or do other bad things. This can occur on shutdown (or profile + // destruction) when a request is scheduled, completed (but not dispatched), + // then the Profile is deleted. + base::AutoLock lock(pending_request_lock_); + while (!pending_requests_.empty()) + CancelRequestLocked(pending_requests_.begin()); +} + +CancelableRequestProvider::Handle CancelableRequestProvider::AddRequest( + CancelableRequestBase* request, + CancelableRequestConsumerBase* consumer) { + Handle handle; + { + base::AutoLock lock(pending_request_lock_); + + handle = next_handle_; + pending_requests_[next_handle_] = request; + ++next_handle_; + } + + consumer->OnRequestAdded(this, handle); + + request->Init(this, handle, consumer); + return handle; +} + +void CancelableRequestProvider::CancelRequest(Handle handle) { + base::AutoLock lock(pending_request_lock_); + CancelRequestLocked(pending_requests_.find(handle)); +} + +void CancelableRequestProvider::CancelRequestLocked( + const CancelableRequestMap::iterator& item) { + pending_request_lock_.AssertAcquired(); + if (item == pending_requests_.end()) { + NOTREACHED() << "Trying to cancel an unknown request"; + return; + } + + item->second->consumer()->OnRequestRemoved(this, item->first); + item->second->set_canceled(); + pending_requests_.erase(item); +} + +void CancelableRequestProvider::RequestCompleted(Handle handle) { + CancelableRequestConsumerBase* consumer = NULL; + { + base::AutoLock lock(pending_request_lock_); + + CancelableRequestMap::iterator i = pending_requests_.find(handle); + if (i == pending_requests_.end()) { + NOTREACHED() << "Trying to cancel an unknown request"; + return; + } + consumer = i->second->consumer(); + + // This message should only get sent if the class is not cancelled, or + // else the consumer might be gone). + DCHECK(!i->second->canceled()); + + pending_requests_.erase(i); + } + + // Notify the consumer that the request is gone + consumer->OnRequestRemoved(this, handle); +} + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +// Emit our most common CancelableRequestConsumer. +template class CancelableRequestConsumerTSimple<int>; + +// And the most common subclass of it. +template class CancelableRequestConsumerT<int, 0>; +#endif + +CancelableRequestBase::CancelableRequestBase() + : provider_(NULL), + consumer_(NULL), + handle_(0) { + callback_thread_ = MessageLoop::current(); +} + +CancelableRequestBase::~CancelableRequestBase() { +} + +void CancelableRequestBase::Init(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle, + CancelableRequestConsumerBase* consumer) { + DCHECK(handle_ == 0 && provider_ == NULL && consumer_ == NULL); + provider_ = provider; + consumer_ = consumer; + handle_ = handle; +} diff --git a/content/browser/cancelable_request.h b/content/browser/cancelable_request.h new file mode 100644 index 0000000..9dc86d3 --- /dev/null +++ b/content/browser/cancelable_request.h @@ -0,0 +1,704 @@ +// Copyright (c) 2010 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. + +// CancelableRequestProviders and Consumers work together to make requests that +// execute on a background thread in the provider and return data to the +// consumer. These class collaborate to keep a list of open requests and to +// make sure that requests to not outlive either of the objects involved in the +// transaction. +// +// If you do not need to return data to the consumer, do not use this system, +// just use the regular Task/RunnableMethod stuff. +// +// The CancelableRequest object is used internally to each provider to track +// request data and callback information. +// +// Example consumer calling |StartRequest| on a frontend service: +// +// class MyClass { +// void MakeRequest() { +// frontend_service->StartRequest(some_input1, some_input2, +// &callback_consumer_, +// NewCallback(this, &MyClass:RequestComplete)); +// // StartRequest() returns a Handle which may be retained for use with +// // CancelRequest() if required, e.g. in MyClass's destructor. +// } +// +// void RequestComplete(int status) { +// ... +// } +// +// private: +// CancelableRequestConsumer callback_consumer_; +// }; +// +// +// Example frontend provider. It receives requests and forwards them to the +// backend on another thread: +// +// class Frontend : public CancelableRequestProvider { +// typedef Callback1<int>::Type RequestCallbackType; +// +// Handle StartRequest(int some_input1, int some_input2, +// CancelableRequestConsumerBase* consumer, +// RequestCallbackType* callback) { +// scoped_refptr<CancelableRequest<RequestCallbackType> > request( +// new CancelableRequest<RequestCallbackType>(callback)); +// AddRequest(request, consumer); +// +// // Send the parameters and the request to the backend thread. +// backend_thread_->PostTask(FROM_HERE, +// NewRunnableMethod(backend_, &Backend::DoRequest, request, +// some_input1, some_input2)); +// +// // The handle will have been set by AddRequest. +// return request->handle(); +// } +// }; +// +// +// Example backend provider that does work and dispatches the callback back +// to the original thread. Note that we need to pass it as a scoped_refptr so +// that the object will be kept alive if the request is canceled (releasing +// the provider's reference to it). +// +// class Backend { +// void DoRequest( +// scoped_refptr< CancelableRequest<Frontend::RequestCallbackType> > +// request, +// int some_input1, int some_input2) { +// if (request->canceled()) +// return; +// +// ... do your processing ... +// +// // Depending on your typedefs, one of these two forms will be more +// // convenient: +// request->ForwardResult(Tuple1<int>(return_value)); +// +// // -- or -- (inferior in this case) +// request->ForwardResult(Frontend::RequestCallbackType::TupleType( +// return_value)); +// } +// }; + +#ifndef CONTENT_BROWSER_CANCELABLE_REQUEST_H_ +#define CONTENT_BROWSER_CANCELABLE_REQUEST_H_ +#pragma once + +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/synchronization/cancellation_flag.h" +#include "base/synchronization/lock.h" +#include "base/task.h" +#include "build/build_config.h" + +class CancelableRequestBase; +class CancelableRequestConsumerBase; + +// CancelableRequestProvider -------------------------------------------------- +// +// This class is threadsafe. Requests may be added or canceled from any thread, +// but a task must only be canceled from the same thread it was initially run +// on. +// +// It is intended that providers inherit from this class to provide the +// necessary functionality. + +class CancelableRequestProvider { + public: + // Identifies a specific request from this provider. + typedef int Handle; + + CancelableRequestProvider(); + virtual ~CancelableRequestProvider(); + + // Called by the enduser of the request to cancel it. This MUST be called on + // the same thread that originally issued the request (which is also the same + // thread that would have received the callback if it was not canceled). + // handle must be for a valid pending (not yet complete or cancelled) request. + void CancelRequest(Handle handle); + + protected: + // Adds a new request and initializes it. This is called by a derived class + // to add a new request. The request's Init() will be called (which is why + // the consumer is required. The handle to the new request is returned. + Handle AddRequest(CancelableRequestBase* request, + CancelableRequestConsumerBase* consumer); + + // Called by the CancelableRequest when the request has executed. It will + // be removed from the list of pending requests (as opposed to canceling, + // which will also set some state on the request). + void RequestCompleted(Handle handle); + + private: + typedef std::map<Handle, scoped_refptr<CancelableRequestBase> > + CancelableRequestMap; + + // Only call this when you already have acquired pending_request_lock_. + void CancelRequestLocked(const CancelableRequestMap::iterator& item); + + friend class CancelableRequestBase; + + base::Lock pending_request_lock_; + + // Lists all outstanding requests. Protected by the |lock_|. + CancelableRequestMap pending_requests_; + + // The next handle value we will return. Protected by the |lock_|. + int next_handle_; + + DISALLOW_COPY_AND_ASSIGN(CancelableRequestProvider); +}; + +// CancelableRequestConsumer -------------------------------------------------- +// +// Classes wishing to make requests on a provider should have an instance of +// this class. Callers will need to pass a pointer to this consumer object +// when they make the request. It will automatically track any pending +// requests, and will automatically cancel them on destruction to prevent the +// accidental calling of freed memory. +// +// It is recommended to just have this class as a member variable since there +// is nothing to be gained by inheriting from it other than polluting your +// namespace. +// +// THIS CLASS IS NOT THREADSAFE (unlike the provider). You must make requests +// and get callbacks all from the same thread. + +// Base class used to notify of new requests. +class CancelableRequestConsumerBase { + protected: + friend class CancelableRequestBase; + friend class CancelableRequestProvider; + + virtual ~CancelableRequestConsumerBase() { + } + + // Adds a new request to the list of requests that are being tracked. This + // is called by the provider when a new request is created. + virtual void OnRequestAdded(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) = 0; + + // Removes the given request from the list of pending requests. Called + // by the CancelableRequest immediately after the callback has executed for a + // given request, and by the provider when a request is canceled. + virtual void OnRequestRemoved(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) = 0; + + // Sent to provider before executing a callback. + virtual void WillExecute(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) = 0; + + // Sent after executing a callback. + virtual void DidExecute(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) = 0; +}; + +// Template for clients to use. It allows them to associate random "client +// data" with a specific request. The default value for this type is 0. +// The type T should be small and easily copyable (like a pointer +// or an integer). +template<class T> +class CancelableRequestConsumerTSimple : public CancelableRequestConsumerBase { + public: + CancelableRequestConsumerTSimple(); + + // Cancel any outstanding requests so that we do not get called back after we + // are destroyed. As these requests are removed, the providers will call us + // back on OnRequestRemoved, which will then update the list. To iterate + // successfully while the list is changing out from under us, we make a copy. + virtual ~CancelableRequestConsumerTSimple(); + + // Associates some random data with a specified request. The request MUST be + // outstanding, or it will assert. This is intended to be called immediately + // after a request is issued. + void SetClientData(CancelableRequestProvider* p, + CancelableRequestProvider::Handle h, + T client_data); + + // Retrieves previously associated data for a specified request. The request + // MUST be outstanding, or it will assert. This is intended to be called + // during processing of a callback to retrieve extra data. + T GetClientData(CancelableRequestProvider* p, + CancelableRequestProvider::Handle h); + + // Returns the data associated with the current request being processed. This + // is only valid during the time a callback is being processed. + T GetClientDataForCurrentRequest(); + + // Returns true if there are any pending requests. + bool HasPendingRequests() const; + + // Returns the number of pending requests. + size_t PendingRequestCount() const; + + // Cancels all requests outstanding. + void CancelAllRequests(); + + // Returns the handle for the first request that has the specified client data + // (in |handle|). Returns true if there is a request for the specified client + // data, false otherwise. + bool GetFirstHandleForClientData(T client_data, + CancelableRequestProvider::Handle* handle); + + // Gets the client data for all pending requests. + void GetAllClientData(std::vector<T>* data); + + protected: + struct PendingRequest { + PendingRequest(CancelableRequestProvider* p, + CancelableRequestProvider::Handle h) + : provider(p), handle(h) { + } + + PendingRequest() : provider(NULL), handle(0) {} + + // Comparison operator for stl. + bool operator<(const PendingRequest& other) const { + if (provider != other.provider) + return provider < other.provider; + return handle < other.handle; + } + + bool is_valid() const { return provider != NULL; } + + CancelableRequestProvider* provider; + CancelableRequestProvider::Handle handle; + }; + typedef std::map<PendingRequest, T> PendingRequestList; + + virtual T get_initial_t() const; + + virtual void OnRequestAdded(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle); + + virtual void OnRequestRemoved(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle); + + virtual void WillExecute(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle); + + virtual void DidExecute(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle); + + // Lists all outstanding requests. + PendingRequestList pending_requests_; + + // This is valid while processing a request and is used to identify the + // provider/handle of request. + PendingRequest current_request_; +}; + +template<class T> +CancelableRequestConsumerTSimple<T>::CancelableRequestConsumerTSimple() { +} + +template<class T> +CancelableRequestConsumerTSimple<T>::~CancelableRequestConsumerTSimple() { + CancelAllRequests(); +} + +template<class T> +void CancelableRequestConsumerTSimple<T>::SetClientData( + CancelableRequestProvider* p, + CancelableRequestProvider::Handle h, + T client_data) { + PendingRequest request(p, h); + DCHECK(pending_requests_.find(request) != pending_requests_.end()); + pending_requests_[request] = client_data; +} + +template<class T> +T CancelableRequestConsumerTSimple<T>::GetClientData( + CancelableRequestProvider* p, + CancelableRequestProvider::Handle h) { + PendingRequest request(p, h); + DCHECK(pending_requests_.find(request) != pending_requests_.end()); + return pending_requests_[request]; +} + +template<class T> +T CancelableRequestConsumerTSimple<T>::GetClientDataForCurrentRequest() { + DCHECK(current_request_.is_valid()); + return GetClientData(current_request_.provider, current_request_.handle); +} + +template<class T> +bool CancelableRequestConsumerTSimple<T>::HasPendingRequests() const { + return !pending_requests_.empty(); +} + +template<class T> +size_t CancelableRequestConsumerTSimple<T>::PendingRequestCount() const { + return pending_requests_.size(); +} + +template<class T> +void CancelableRequestConsumerTSimple<T>::CancelAllRequests() { + PendingRequestList copied_requests(pending_requests_); + for (typename PendingRequestList::iterator i = copied_requests.begin(); + i != copied_requests.end(); ++i) + i->first.provider->CancelRequest(i->first.handle); + copied_requests.clear(); + + // That should have cleared all the pending items. + DCHECK(pending_requests_.empty()); +} + +template<class T> +bool CancelableRequestConsumerTSimple<T>::GetFirstHandleForClientData( + T client_data, + CancelableRequestProvider::Handle* handle) { + for (typename PendingRequestList::const_iterator i = + pending_requests_.begin(); i != pending_requests_.end(); ++i) { + if (i->second == client_data) { + *handle = i->first.handle; + return true; + } + } + *handle = 0; + return false; +} + +template<class T> +void CancelableRequestConsumerTSimple<T>::GetAllClientData( + std::vector<T>* data) { + DCHECK(data); + for (typename PendingRequestList::iterator i = pending_requests_.begin(); + i != pending_requests_.end(); ++i) + data->push_back(i->second); +} + +template<class T> +T CancelableRequestConsumerTSimple<T>::get_initial_t() const { + return 0; +} + +template<class T> +void CancelableRequestConsumerTSimple<T>::OnRequestAdded( + CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) { + DCHECK(pending_requests_.find(PendingRequest(provider, handle)) == + pending_requests_.end()); + pending_requests_[PendingRequest(provider, handle)] = get_initial_t(); +} + +template<class T> +void CancelableRequestConsumerTSimple<T>::OnRequestRemoved( + CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) { + typename PendingRequestList::iterator i = + pending_requests_.find(PendingRequest(provider, handle)); + if (i == pending_requests_.end()) { + NOTREACHED() << "Got a complete notification for a nonexistent request"; + return; + } + + pending_requests_.erase(i); +} + +template<class T> +void CancelableRequestConsumerTSimple<T>::WillExecute( + CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) { + current_request_ = PendingRequest(provider, handle); +} + +template<class T> +void CancelableRequestConsumerTSimple<T>::DidExecute( + CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) { + current_request_ = PendingRequest(); +} + +// See CancelableRequestConsumerTSimple. The default value for T +// is given in |initial_t|. +template<class T, T initial_t> +class CancelableRequestConsumerT : public CancelableRequestConsumerTSimple<T> { + public: + CancelableRequestConsumerT(); + virtual ~CancelableRequestConsumerT(); + + protected: + virtual T get_initial_t() const; +}; + +template<class T, T initial_t> +CancelableRequestConsumerT<T, initial_t>::CancelableRequestConsumerT() { +} + +template<class T, T initial_t> +CancelableRequestConsumerT<T, initial_t>::~CancelableRequestConsumerT() { +} + +template<class T, T initial_t> +T CancelableRequestConsumerT<T, initial_t>::get_initial_t() const { + return initial_t; +} + +// Some clients may not want to store data. Rather than do some complicated +// thing with virtual functions to allow some consumers to store extra data and +// some not to, we just define a default one that stores some dummy data. +typedef CancelableRequestConsumerT<int, 0> CancelableRequestConsumer; + +// MSVC doesn't like complex extern templates and DLLs. +#if !defined(COMPILER_MSVC) +// The vast majority of CancelableRequestConsumers are instantiated on <int>, +// so prevent that template from being expanded in normal code. +extern template class CancelableRequestConsumerTSimple<int>; + +// We'll also want to extern-template the most common, typedef-ed +// CancelableRequestConsumerT. +extern template class CancelableRequestConsumerT<int, 0>; +#endif + +// CancelableRequest ---------------------------------------------------------- +// +// The request object that is used by a CancelableRequestProvider to send +// results to a CancelableRequestConsumer. This request handles the returning +// of results from a thread where the request is being executed to the thread +// and callback where the results are used. IT SHOULD BE PASSED AS A +// scoped_refptr TO KEEP IT ALIVE. +// +// It does not handle input parameters to the request. The caller must either +// transfer those separately or derive from this class to add the desired +// parameters. +// +// When the processing is complete on this message, the caller MUST call +// ForwardResult() with the return arguments that will be passed to the +// callback. If the request has been canceled, Return is optional (it will not +// do anything). If you do not have to return to the caller, the cancelable +// request system should not be used! (just use regular fire-and-forget tasks). +// +// Callback parameters are passed by value. In some cases, the request will +// want to return a large amount of data (for example, an image). One good +// approach is to derive from the CancelableRequest and make the data object +// (for example, a std::vector) owned by the CancelableRequest. The pointer +// to this data would be passed for the callback parameter. Since the +// CancelableRequest outlives the callback call, the data will be valid on the +// other thread for the callback, but will still be destroyed properly. + +// Non-templatized base class that provides cancellation +class CancelableRequestBase + : public base::RefCountedThreadSafe<CancelableRequestBase> { + public: + friend class CancelableRequestProvider; + + // Initializes most things to empty, Init() must be called to complete + // initialization of the object. This will be done by the provider when + // the request is dispatched. + // + // This must be called on the same thread the callback will be executed on, + // it will save that thread for later. + // + // This two-phase init is done so that the constructor can have no + // parameters, which makes it much more convenient for derived classes, + // which can be common. The derived classes need only declare the variables + // they provide in the constructor rather than many lines of internal + // tracking data that are passed to the base class (us). + // + // In addition, not all of the information (for example, the handle) is known + // at construction time. + CancelableRequestBase(); + + CancelableRequestConsumerBase* consumer() const { + return consumer_; + } + + CancelableRequestProvider::Handle handle() const { + return handle_; + } + + // The canceled flag indicates that the request should not be executed. + // A request can never be uncanceled, so only a setter for true is provided. + // This can be called multiple times, but only from one thread. + void set_canceled() { + canceled_.Set(); + } + bool canceled() { + return canceled_.IsSet(); + } + + protected: + friend class base::RefCountedThreadSafe<CancelableRequestBase>; + virtual ~CancelableRequestBase(); + + // Initializes the object with the particulars from the provider. It may only + // be called once (it is called by the provider, which is a friend). + void Init(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle, + CancelableRequestConsumerBase* consumer); + + // Tells the provider that the request is complete, which then tells the + // consumer. + void NotifyCompleted() const { + provider_->RequestCompleted(handle()); + consumer_->DidExecute(provider_, handle_); + } + + // Cover method for CancelableRequestConsumerBase::WillExecute. + void WillExecute() { + consumer_->WillExecute(provider_, handle_); + } + + // The message loop that this request was created on. The callback will + // happen on the same thread. + MessageLoop* callback_thread_; + + // The provider for this request. When we execute, we will notify this that + // request is complete to it can remove us from the requests it tracks. + CancelableRequestProvider* provider_; + + // Notified after we execute that the request is complete. This should only + // be accessed if !canceled_.IsSet(), otherwise the pointer is invalid. + CancelableRequestConsumerBase* consumer_; + + // The handle to this request inside the provider. This will be initialized + // to 0 when the request is created, and the provider will set it once the + // request has been dispatched. + CancelableRequestProvider::Handle handle_; + + // Set if the caller cancels this request. No callbacks should be made when + // this is set. + base::CancellationFlag canceled_; + + private: + DISALLOW_COPY_AND_ASSIGN(CancelableRequestBase); +}; + +// Templatized class. This is the one you should use directly or inherit from. +// The callback can be invoked by calling the ForwardResult() method. For this, +// you must either pack the parameters into a tuple, or use DispatchToMethod +// (in tuple.h). +// +// If you inherit to add additional input parameters or to do more complex +// memory management (see the bigger comment about this above), you can put +// those on a subclass of this. +// +// We have decided to allow users to treat derived classes of this as structs, +// so you can add members without getters and setters (which just makes the +// code harder to read). Don't use underscores after these vars. For example: +// +// typedef Callback1<int>::Type DoodieCallback; +// +// class DoodieRequest : public CancelableRequest<DoodieCallback> { +// public: +// DoodieRequest(CallbackType* callback) : CancelableRequest(callback) { +// } +// +// private: +// ~DoodieRequest() {} +// +// int input_arg1; +// std::wstring input_arg2; +// }; +template<typename CB> +class CancelableRequest : public CancelableRequestBase { + public: + typedef CB CallbackType; // CallbackRunner<...> + typedef typename CB::TupleType TupleType; // Tuple of the callback args. + + // The provider MUST call Init() (on the base class) before this is valid. + // This class will take ownership of the callback object and destroy it when + // appropriate. + explicit CancelableRequest(CallbackType* callback) + : CancelableRequestBase(), + callback_(callback) { + DCHECK(callback) << "We should always have a callback"; + } + + // Dispatches the parameters to the correct thread so the callback can be + // executed there. The caller does not need to check for cancel before + // calling this. It is optional in the cancelled case. In the non-cancelled + // case, this MUST be called. + // + // If there are any pointers in the parameters, they must live at least as + // long as the request so that it can be forwarded to the other thread. + // For complex objects, this would typically be done by having a derived + // request own the data itself. + void ForwardResult(const TupleType& param) { + DCHECK(callback_.get()); + if (!canceled()) { + if (callback_thread_ == MessageLoop::current()) { + // We can do synchronous callbacks when we're on the same thread. + ExecuteCallback(param); + } else { + callback_thread_->PostTask(FROM_HERE, NewRunnableMethod(this, + &CancelableRequest<CB>::ExecuteCallback, param)); + } + } + } + + // Like |ForwardResult| but this never does a synchronous callback. + void ForwardResultAsync(const TupleType& param) { + DCHECK(callback_.get()); + if (!canceled()) { + callback_thread_->PostTask(FROM_HERE, NewRunnableMethod(this, + &CancelableRequest<CB>::ExecuteCallback, param)); + } + } + + protected: + virtual ~CancelableRequest() {} + + private: + // Executes the callback and notifies the provider and the consumer that this + // request has been completed. This must be called on the callback_thread_. + void ExecuteCallback(const TupleType& param) { + if (!canceled_.IsSet()) { + WillExecute(); + + // Execute the callback. + callback_->RunWithParams(param); + + // Notify the provider that the request is complete. The provider will + // notify the consumer for us. + NotifyCompleted(); + } + } + + // This should only be executed if !canceled_.IsSet(), + // otherwise the pointers may be invalid. + scoped_ptr<CallbackType> callback_; +}; + +// A CancelableRequest with a single value. This is intended for use when +// the provider provides a single value. The provider fills the result into +// the value, and notifies the request with a pointer to the value. For example, +// HistoryService has many methods that callback with a vector. Use the +// following pattern for this: +// 1. Define the callback: +// typedef Callback2<Handle, std::vector<Foo>*>::Type FooCallback; +// 2. Define the CancelableRequest1 type. +// typedef CancelableRequest1<FooCallback, std::vector<Foo>> FooRequest; +// 3. The provider method should then fillin the contents of the vector, +// forwarding the result like so: +// request->ForwardResult(FooRequest::TupleType(request->handle(), +// &request->value)); +// +// Tip: for passing more than one value, use a Tuple for the value. +template<typename CB, typename Type> +class CancelableRequest1 : public CancelableRequest<CB> { + public: + explicit CancelableRequest1( + typename CancelableRequest<CB>::CallbackType* callback) + : CancelableRequest<CB>(callback) { + } + + // The value. + Type value; + + protected: + virtual ~CancelableRequest1() {} +}; + +#endif // CONTENT_BROWSER_CANCELABLE_REQUEST_H_ diff --git a/content/browser/cert_store.cc b/content/browser/cert_store.cc new file mode 100644 index 0000000..e43e570 --- /dev/null +++ b/content/browser/cert_store.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2010 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 "content/browser/cert_store.h" + +#include <algorithm> +#include <functional> + +#include "base/stl_util-inl.h" +#include "chrome/common/notification_service.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/renderer_host/render_view_host.h" + +template <typename T> +struct MatchSecond { + explicit MatchSecond(const T& t) : value(t) {} + + template<typename Pair> + bool operator()(const Pair& p) const { + return (value == p.second); + } + T value; +}; + +// static +CertStore* CertStore::GetInstance() { + return Singleton<CertStore>::get(); +} + +CertStore::CertStore() : next_cert_id_(1) { + // We watch for RenderProcess termination, as this is how we clear + // certificates for now. + // TODO(jcampan): we should be listening to events such as resource cached/ + // removed from cache, and remove the cert when we know it + // is not used anymore. + + registrar_.Add(this, NotificationType::RENDERER_PROCESS_TERMINATED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::RENDERER_PROCESS_CLOSED, + NotificationService::AllSources()); +} + +CertStore::~CertStore() { +} + +int CertStore::StoreCert(net::X509Certificate* cert, int process_id) { + DCHECK(cert); + base::AutoLock autoLock(cert_lock_); + + int cert_id; + + // Do we already know this cert? + ReverseCertMap::iterator cert_iter = cert_to_id_.find(cert); + if (cert_iter == cert_to_id_.end()) { + cert_id = next_cert_id_++; + // We use 0 as an invalid cert_id value. In the unlikely event that + // next_cert_id_ wraps around, we reset it to 1. + if (next_cert_id_ == 0) + next_cert_id_ = 1; + cert->AddRef(); + id_to_cert_[cert_id] = cert; + cert_to_id_[cert] = cert_id; + } else { + cert_id = cert_iter->second; + } + + // Let's update process_id_to_cert_id_. + if (std::find_if(process_id_to_cert_id_.lower_bound(process_id), + process_id_to_cert_id_.upper_bound(process_id), + MatchSecond<int>(cert_id)) == + process_id_to_cert_id_.upper_bound(process_id)) { + process_id_to_cert_id_.insert(std::make_pair(process_id, cert_id)); + } + + // And cert_id_to_process_id_. + if (std::find_if(cert_id_to_process_id_.lower_bound(cert_id), + cert_id_to_process_id_.upper_bound(cert_id), + MatchSecond<int>(process_id)) == + cert_id_to_process_id_.upper_bound(cert_id)) { + cert_id_to_process_id_.insert(std::make_pair(cert_id, process_id)); + } + + return cert_id; +} + +bool CertStore::RetrieveCert(int cert_id, + scoped_refptr<net::X509Certificate>* cert) { + base::AutoLock autoLock(cert_lock_); + + CertMap::iterator iter = id_to_cert_.find(cert_id); + if (iter == id_to_cert_.end()) + return false; + if (cert) + *cert = iter->second; + return true; +} + +void CertStore::RemoveCertInternal(int cert_id) { + CertMap::iterator cert_iter = id_to_cert_.find(cert_id); + DCHECK(cert_iter != id_to_cert_.end()); + + ReverseCertMap::iterator id_iter = cert_to_id_.find(cert_iter->second); + DCHECK(id_iter != cert_to_id_.end()); + cert_to_id_.erase(id_iter); + + cert_iter->second->Release(); + id_to_cert_.erase(cert_iter); +} + +void CertStore::RemoveCertsForRenderProcesHost(int process_id) { + base::AutoLock autoLock(cert_lock_); + + // We iterate through all the cert ids for that process. + IDMap::iterator ids_iter; + for (ids_iter = process_id_to_cert_id_.lower_bound(process_id); + ids_iter != process_id_to_cert_id_.upper_bound(process_id);) { + int cert_id = ids_iter->second; + // Remove this process from cert_id_to_process_id_. + IDMap::iterator proc_iter = + std::find_if(cert_id_to_process_id_.lower_bound(cert_id), + cert_id_to_process_id_.upper_bound(cert_id), + MatchSecond<int>(process_id)); + DCHECK(proc_iter != cert_id_to_process_id_.upper_bound(cert_id)); + cert_id_to_process_id_.erase(proc_iter); + + if (cert_id_to_process_id_.count(cert_id) == 0) { + // This cert is not referenced by any process, remove it from id_to_cert_ + // and cert_to_id_. + RemoveCertInternal(cert_id); + } + + // Erase the current item but keep the iterator valid. + process_id_to_cert_id_.erase(ids_iter++); + } +} + +void CertStore::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::RENDERER_PROCESS_TERMINATED || + type == NotificationType::RENDERER_PROCESS_CLOSED); + RenderProcessHost* rph = Source<RenderProcessHost>(source).ptr(); + DCHECK(rph); + RemoveCertsForRenderProcesHost(rph->id()); +} diff --git a/content/browser/cert_store.h b/content/browser/cert_store.h new file mode 100644 index 0000000..8022c9d --- /dev/null +++ b/content/browser/cert_store.h @@ -0,0 +1,84 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_CERT_STORE_H_ +#define CONTENT_BROWSER_CERT_STORE_H_ +#pragma once + +#include <map> + +#include "base/singleton.h" +#include "base/synchronization/lock.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "net/base/x509_certificate.h" + +// The purpose of the cert store is to provide an easy way to store/retrieve +// X509Certificate objects. When stored, an X509Certificate object is +// associated with a RenderProcessHost. If all the RenderProcessHosts +// associated with the cert have exited, the cert is removed from the store. +// This class is used by the SSLManager to keep track of the certs associated +// to loaded resources. +// It can be accessed from the UI and IO threads (it is thread-safe). +// Note that the cert ids will overflow if we register more than 2^32 - 1 certs +// in 1 browsing session (which is highly unlikely to happen). + +class CertStore : public NotificationObserver { + public: + // Returns the singleton instance of the CertStore. + static CertStore* GetInstance(); + + // Stores the specified cert and returns the id associated with it. The cert + // is associated to the specified RenderProcessHost. + // When all the RenderProcessHosts associated with a cert have exited, the + // cert is removed from the store. + // Note: ids starts at 1. + int StoreCert(net::X509Certificate* cert, int render_process_host_id); + + // Tries to retrieve the previously stored cert associated with the specified + // |cert_id|. Returns whether the cert could be found, and, if |cert| is + // non-NULL, copies it in. + bool RetrieveCert(int cert_id, scoped_refptr<net::X509Certificate>* cert); + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + friend struct DefaultSingletonTraits<CertStore>; + + CertStore(); + ~CertStore(); + + // Remove the specified cert from id_to_cert_ and cert_to_id_. + // NOTE: the caller (RemoveCertsForRenderProcesHost) must hold cert_lock_. + void RemoveCertInternal(int cert_id); + + // Removes all the certs associated with the specified process from the store. + void RemoveCertsForRenderProcesHost(int render_process_host_id); + + typedef std::multimap<int, int> IDMap; + typedef std::map<int, scoped_refptr<net::X509Certificate> > CertMap; + typedef std::map<net::X509Certificate*, int, net::X509Certificate::LessThan> + ReverseCertMap; + + NotificationRegistrar registrar_; + + IDMap process_id_to_cert_id_; + IDMap cert_id_to_process_id_; + + CertMap id_to_cert_; + ReverseCertMap cert_to_id_; + + int next_cert_id_; + + // This lock protects: process_to_ids_, id_to_processes_, id_to_cert_ and + // cert_to_id_. + base::Lock cert_lock_; + + DISALLOW_COPY_AND_ASSIGN(CertStore); +}; + +#endif // CONTENT_BROWSER_CERT_STORE_H_ diff --git a/content/browser/certificate_manager_model.cc b/content/browser/certificate_manager_model.cc new file mode 100644 index 0000000..c1660b6 --- /dev/null +++ b/content/browser/certificate_manager_model.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2010 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 "content/browser/certificate_manager_model.h" + +#include "base/i18n/time_formatting.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/net/x509_certificate_model.h" +#include "net/base/net_errors.h" +#include "net/base/x509_certificate.h" + +CertificateManagerModel::CertificateManagerModel(Observer* observer) + : observer_(observer) { +} + +CertificateManagerModel::~CertificateManagerModel() { +} + +void CertificateManagerModel::Refresh() { + VLOG(1) << "refresh started"; + cert_db_.ListCerts(&cert_list_); + observer_->CertificatesRefreshed(); + VLOG(1) << "refresh finished"; +} + +void CertificateManagerModel::FilterAndBuildOrgGroupingMap( + net::CertType filter_type, + CertificateManagerModel::OrgGroupingMap* map) const { + for (net::CertificateList::const_iterator i = cert_list_.begin(); + i != cert_list_.end(); ++i) { + net::X509Certificate* cert = i->get(); + net::CertType type = + x509_certificate_model::GetType(cert->os_cert_handle()); + if (type != filter_type) + continue; + + std::string org; + if (!cert->subject().organization_names.empty()) + org = cert->subject().organization_names[0]; + if (org.empty()) + org = cert->subject().GetDisplayName(); + + (*map)[org].push_back(cert); + } +} + +string16 CertificateManagerModel::GetColumnText( + const net::X509Certificate& cert, + Column column) const { + string16 rv; + switch (column) { + case COL_SUBJECT_NAME: + rv = UTF8ToUTF16( + x509_certificate_model::GetCertNameOrNickname(cert.os_cert_handle())); + break; + case COL_CERTIFICATE_STORE: + rv = UTF8ToUTF16( + x509_certificate_model::GetTokenName(cert.os_cert_handle())); + break; + case COL_SERIAL_NUMBER: + rv = ASCIIToUTF16( + x509_certificate_model::GetSerialNumberHexified( + cert.os_cert_handle(), "")); + break; + case COL_EXPIRES_ON: + if (!cert.valid_expiry().is_null()) + rv = base::TimeFormatShortDateNumeric(cert.valid_expiry()); + break; + default: + NOTREACHED(); + } + return rv; +} + +int CertificateManagerModel::ImportFromPKCS12(net::CryptoModule* module, + const std::string& data, + const string16& password) { + int result = cert_db_.ImportFromPKCS12(module, data, password); + if (result == net::OK) + Refresh(); + return result; +} + +bool CertificateManagerModel::ImportCACerts( + const net::CertificateList& certificates, + unsigned int trust_bits, + net::CertDatabase::ImportCertFailureList* not_imported) { + bool result = cert_db_.ImportCACerts(certificates, trust_bits, not_imported); + if (result && not_imported->size() != certificates.size()) + Refresh(); + return result; +} + +bool CertificateManagerModel::ImportServerCert( + const net::CertificateList& certificates, + net::CertDatabase::ImportCertFailureList* not_imported) { + bool result = cert_db_.ImportServerCert(certificates, not_imported); + if (result && not_imported->size() != certificates.size()) + Refresh(); + return result; +} + +bool CertificateManagerModel::SetCertTrust(const net::X509Certificate* cert, + net::CertType type, + unsigned int trust_bits) { + return cert_db_.SetCertTrust(cert, type, trust_bits); +} + +bool CertificateManagerModel::Delete(net::X509Certificate* cert) { + bool result = cert_db_.DeleteCertAndKey(cert); + if (result) + Refresh(); + return result; +} diff --git a/content/browser/certificate_manager_model.h b/content/browser/certificate_manager_model.h new file mode 100644 index 0000000..f134a68 --- /dev/null +++ b/content/browser/certificate_manager_model.h @@ -0,0 +1,110 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_CERTIFICATE_MANAGER_MODEL_H_ +#define CONTENT_BROWSER_CERTIFICATE_MANAGER_MODEL_H_ + +#include <map> +#include <string> + +#include "base/ref_counted.h" +#include "base/string16.h" +#include "net/base/cert_database.h" + +// CertificateManagerModel provides the data to be displayed in the certificate +// manager dialog, and processes changes from the view. +class CertificateManagerModel { + public: + // Map from the subject organization name to the list of certs from that + // organization. If a cert does not have an organization name, the + // subject's CertPrincipal::GetDisplayName() value is used instead. + typedef std::map<std::string, net::CertificateList> OrgGroupingMap; + + // Enumeration of the possible columns in the certificate manager tree view. + enum Column { + COL_SUBJECT_NAME, + COL_CERTIFICATE_STORE, + COL_SERIAL_NUMBER, + COL_EXPIRES_ON, + }; + + class Observer { + public: + // Called to notify the view that the certificate list has been refreshed. + // TODO(mattm): do a more granular updating strategy? Maybe retrieve new + // list of certs, diff against past list, and then notify of the changes? + virtual void CertificatesRefreshed() = 0; + }; + + explicit CertificateManagerModel(Observer* observer); + ~CertificateManagerModel(); + + // Accessor for read-only access to the underlying CertDatabase. + const net::CertDatabase& cert_db() const { return cert_db_; } + + // Refresh the list of certs. Following this call, the observer + // CertificatesRefreshed method will be called so the view can call + // FilterAndBuildOrgGroupingMap as necessary to refresh its tree views. + void Refresh(); + + // Fill |map| with the certificates matching |filter_type|. + void FilterAndBuildOrgGroupingMap(net::CertType filter_type, + OrgGroupingMap* map) const; + + // Get the data to be displayed in |column| for the given |cert|. + string16 GetColumnText(const net::X509Certificate& cert, Column column) const; + + // Import certificates from PKCS #12 encoded |data|, using the given + // |password|. Returns a net error code on failure. + int ImportFromPKCS12(net::CryptoModule* module, const std::string& data, + const string16& password); + + // Import CA certificates. + // Tries to import all the certificates given. The root will be trusted + // according to |trust_bits|. Any certificates that could not be imported + // will be listed in |not_imported|. + // |trust_bits| should be a bit field of TRUST_* values from CertDatabase, or + // UNTRUSTED. + // Returns false if there is an internal error, otherwise true is returned and + // |not_imported| should be checked for any certificates that were not + // imported. + bool ImportCACerts(const net::CertificateList& certificates, + unsigned int trust_bits, + net::CertDatabase::ImportCertFailureList* not_imported); + + // Import server certificate. The first cert should be the server cert. Any + // additional certs should be intermediate/CA certs and will be imported but + // not given any trust. + // Any certificates that could not be imported will be listed in + // |not_imported|. + // Returns false if there is an internal error, otherwise true is returned and + // |not_imported| should be checked for any certificates that were not + // imported. + bool ImportServerCert( + const net::CertificateList& certificates, + net::CertDatabase::ImportCertFailureList* not_imported); + + // Set trust values for certificate. + // |trust_bits| should be a bit field of TRUST_* values from CertDatabase, or + // UNTRUSTED. + // Returns true on success or false on failure. + bool SetCertTrust(const net::X509Certificate* cert, + net::CertType type, + unsigned int trust_bits); + + // Delete the cert. Returns true on success. |cert| is still valid when this + // function returns. + bool Delete(net::X509Certificate* cert); + + private: + net::CertDatabase cert_db_; + net::CertificateList cert_list_; + + // The observer to notify when certificate list is refreshed. + Observer* observer_; + + DISALLOW_COPY_AND_ASSIGN(CertificateManagerModel); +}; + +#endif // CONTENT_BROWSER_CERTIFICATE_MANAGER_MODEL_H_ diff --git a/content/browser/certificate_viewer.cc b/content/browser/certificate_viewer.cc new file mode 100644 index 0000000..3d92e7a --- /dev/null +++ b/content/browser/certificate_viewer.cc @@ -0,0 +1,18 @@ +// Copyright (c) 2010 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 "content/browser/certificate_viewer.h" + +#include "content/browser/cert_store.h" + +void ShowCertificateViewerByID(gfx::NativeWindow parent, int cert_id) { + scoped_refptr<net::X509Certificate> cert; + CertStore::GetInstance()->RetrieveCert(cert_id, &cert); + if (!cert.get()) { + // The certificate was not found. Could be that the renderer crashed before + // we displayed the page info. + return; + } + ShowCertificateViewer(parent, cert); +} diff --git a/content/browser/certificate_viewer.h b/content/browser/certificate_viewer.h new file mode 100644 index 0000000..8fa8befe --- /dev/null +++ b/content/browser/certificate_viewer.h @@ -0,0 +1,25 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_CERTIFICATE_VIEWER_H_ +#define CONTENT_BROWSER_CERTIFICATE_VIEWER_H_ +#pragma once + +#include "ui/gfx/native_widget_types.h" + +namespace net { + +class X509Certificate; + +} // namespace net + +// Opens a certificate viewer under |parent| to display the certificate from +// the |CertStore| with id |cert_id|. +void ShowCertificateViewerByID(gfx::NativeWindow parent, int cert_id); + +// Opens a certificate viewer under |parent| to display |cert|. +void ShowCertificateViewer(gfx::NativeWindow parent, + net::X509Certificate* cert); + +#endif // CONTENT_BROWSER_CERTIFICATE_VIEWER_H_ diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc new file mode 100644 index 0000000..ded41be --- /dev/null +++ b/content/browser/child_process_launcher.cc @@ -0,0 +1,348 @@ +// Copyright (c) 2010 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 "content/browser/child_process_launcher.h" + +#include <utility> // For std::pair. + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread.h" +#include "chrome/common/chrome_descriptors.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/process_watcher.h" +#include "chrome/common/result_codes.h" +#include "content/browser/browser_thread.h" + +#if defined(OS_WIN) +#include "base/file_path.h" +#include "chrome/common/sandbox_policy.h" +#elif defined(OS_LINUX) +#include "base/singleton.h" +#include "chrome/browser/crash_handler_host_linux.h" +#include "chrome/browser/zygote_host_linux.h" +#include "chrome/browser/renderer_host/render_sandbox_host_linux.h" +#endif + +#if defined(OS_MACOSX) +#include "chrome/browser/mach_broker_mac.h" +#endif + +#if defined(OS_POSIX) +#include "base/global_descriptors_posix.h" +#endif + +// Having the functionality of ChildProcessLauncher be in an internal +// ref counted object allows us to automatically terminate the process when the +// parent class destructs, while still holding on to state that we need. +class ChildProcessLauncher::Context + : public base::RefCountedThreadSafe<ChildProcessLauncher::Context> { + public: + Context() + : client_(NULL), + client_thread_id_(BrowserThread::UI), + starting_(true) +#if defined(OS_LINUX) + , zygote_(false) +#endif + { + } + + void Launch( +#if defined(OS_WIN) + const FilePath& exposed_dir, +#elif defined(OS_POSIX) + bool use_zygote, + const base::environment_vector& environ, + int ipcfd, +#endif + CommandLine* cmd_line, + Client* client) { + client_ = client; + + CHECK(BrowserThread::GetCurrentThreadIdentifier(&client_thread_id_)); + + BrowserThread::PostTask( + BrowserThread::PROCESS_LAUNCHER, FROM_HERE, + NewRunnableMethod( + this, + &Context::LaunchInternal, +#if defined(OS_WIN) + exposed_dir, +#elif defined(OS_POSIX) + use_zygote, + environ, + ipcfd, +#endif + cmd_line)); + } + + void ResetClient() { + // No need for locking as this function gets called on the same thread that + // client_ would be used. + CHECK(BrowserThread::CurrentlyOn(client_thread_id_)); + client_ = NULL; + } + + private: + friend class base::RefCountedThreadSafe<ChildProcessLauncher::Context>; + friend class ChildProcessLauncher; + + ~Context() { + Terminate(); + } + + void LaunchInternal( +#if defined(OS_WIN) + const FilePath& exposed_dir, +#elif defined(OS_POSIX) + bool use_zygote, + const base::environment_vector& env, + int ipcfd, +#endif + CommandLine* cmd_line) { + scoped_ptr<CommandLine> cmd_line_deleter(cmd_line); + base::ProcessHandle handle = base::kNullProcessHandle; +#if defined(OS_WIN) + handle = sandbox::StartProcessWithAccess(cmd_line, exposed_dir); +#elif defined(OS_POSIX) + +#if defined(OS_LINUX) + if (use_zygote) { + base::GlobalDescriptors::Mapping mapping; + mapping.push_back(std::pair<uint32_t, int>(kPrimaryIPCChannel, ipcfd)); + const int crash_signal_fd = + RendererCrashHandlerHostLinux::GetInstance()->GetDeathSignalSocket(); + if (crash_signal_fd >= 0) { + mapping.push_back(std::pair<uint32_t, int>(kCrashDumpSignal, + crash_signal_fd)); + } + handle = ZygoteHost::GetInstance()->ForkRenderer(cmd_line->argv(), + mapping); + } else + // Fall through to the normal posix case below when we're not zygoting. +#endif + { + base::file_handle_mapping_vector fds_to_map; + fds_to_map.push_back(std::make_pair( + ipcfd, + kPrimaryIPCChannel + base::GlobalDescriptors::kBaseDescriptor)); + +#if defined(OS_LINUX) + // On Linux, we need to add some extra file descriptors for crash handling + // and the sandbox. + bool is_renderer = + cmd_line->GetSwitchValueASCII(switches::kProcessType) == + switches::kRendererProcess; + bool is_plugin = + cmd_line->GetSwitchValueASCII(switches::kProcessType) == + switches::kPluginProcess; + bool is_gpu = + cmd_line->GetSwitchValueASCII(switches::kProcessType) == + switches::kGpuProcess; + + if (is_renderer || is_plugin || is_gpu) { + int crash_signal_fd; + if (is_renderer) { + crash_signal_fd = RendererCrashHandlerHostLinux::GetInstance()-> + GetDeathSignalSocket(); + } else if (is_plugin) { + crash_signal_fd = PluginCrashHandlerHostLinux::GetInstance()-> + GetDeathSignalSocket(); + } else { + crash_signal_fd = GpuCrashHandlerHostLinux::GetInstance()-> + GetDeathSignalSocket(); + } + if (crash_signal_fd >= 0) { + fds_to_map.push_back(std::make_pair( + crash_signal_fd, + kCrashDumpSignal + base::GlobalDescriptors::kBaseDescriptor)); + } + } + if (is_renderer) { + const int sandbox_fd = + RenderSandboxHostLinux::GetInstance()->GetRendererSocket(); + fds_to_map.push_back(std::make_pair( + sandbox_fd, + kSandboxIPCChannel + base::GlobalDescriptors::kBaseDescriptor)); + } +#endif // defined(OS_LINUX) + + bool launched = false; +#if defined(OS_MACOSX) + // It is possible for the child process to die immediately after + // launching. To prevent leaking MachBroker map entries in this case, + // lock around all of LaunchApp(). If the child dies, the death + // notification will be processed by the MachBroker after the call to + // AddPlaceholderForPid(), enabling proper cleanup. + { // begin scope for AutoLock + MachBroker* broker = MachBroker::GetInstance(); + base::AutoLock lock(broker->GetLock()); + + // This call to |PrepareForFork()| will start the MachBroker listener + // thread, if it is not already running. Therefore the browser process + // will be listening for Mach IPC before LaunchApp() is called. + broker->PrepareForFork(); +#endif + // Actually launch the app. + launched = base::LaunchApp(cmd_line->argv(), env, fds_to_map, + /* wait= */false, &handle); +#if defined(OS_MACOSX) + if (launched) + broker->AddPlaceholderForPid(handle); + } // end scope for AutoLock +#endif + if (!launched) + handle = base::kNullProcessHandle; + } +#endif // else defined(OS_POSIX) + + BrowserThread::PostTask( + client_thread_id_, FROM_HERE, + NewRunnableMethod( + this, + &ChildProcessLauncher::Context::Notify, +#if defined(OS_LINUX) + use_zygote, +#endif + handle)); + } + + void Notify( +#if defined(OS_LINUX) + bool zygote, +#endif + base::ProcessHandle handle) { + starting_ = false; + process_.set_handle(handle); +#if defined(OS_LINUX) + zygote_ = zygote; +#endif + if (client_) { + client_->OnProcessLaunched(); + } else { + Terminate(); + } + } + + void Terminate() { + if (!process_.handle()) + return; + + // On Posix, EnsureProcessTerminated can lead to 2 seconds of sleep! So + // don't this on the UI/IO threads. + BrowserThread::PostTask( + BrowserThread::PROCESS_LAUNCHER, FROM_HERE, + NewRunnableFunction( + &ChildProcessLauncher::Context::TerminateInternal, +#if defined(OS_LINUX) + zygote_, +#endif + process_.handle())); + process_.set_handle(base::kNullProcessHandle); + } + + static void TerminateInternal( +#if defined(OS_LINUX) + bool zygote, +#endif + base::ProcessHandle handle) { + base::Process process(handle); + // Client has gone away, so just kill the process. Using exit code 0 + // means that UMA won't treat this as a crash. + process.Terminate(ResultCodes::NORMAL_EXIT); + // On POSIX, we must additionally reap the child. +#if defined(OS_POSIX) +#if defined(OS_LINUX) + if (zygote) { + // If the renderer was created via a zygote, we have to proxy the reaping + // through the zygote process. + ZygoteHost::GetInstance()->EnsureProcessTerminated(handle); + } else +#endif // OS_LINUX + { + ProcessWatcher::EnsureProcessTerminated(handle); + } +#endif // OS_POSIX + process.Close(); + } + + Client* client_; + BrowserThread::ID client_thread_id_; + base::Process process_; + bool starting_; + +#if defined(OS_LINUX) + bool zygote_; +#endif +}; + + +ChildProcessLauncher::ChildProcessLauncher( +#if defined(OS_WIN) + const FilePath& exposed_dir, +#elif defined(OS_POSIX) + bool use_zygote, + const base::environment_vector& environ, + int ipcfd, +#endif + CommandLine* cmd_line, + Client* client) { + context_ = new Context(); + context_->Launch( +#if defined(OS_WIN) + exposed_dir, +#elif defined(OS_POSIX) + use_zygote, + environ, + ipcfd, +#endif + cmd_line, + client); +} + +ChildProcessLauncher::~ChildProcessLauncher() { + context_->ResetClient(); +} + +bool ChildProcessLauncher::IsStarting() { + return context_->starting_; +} + +base::ProcessHandle ChildProcessLauncher::GetHandle() { + DCHECK(!context_->starting_); + return context_->process_.handle(); +} + +base::TerminationStatus ChildProcessLauncher::GetChildTerminationStatus( + int* exit_code) { + base::TerminationStatus status; + base::ProcessHandle handle = context_->process_.handle(); +#if defined(OS_LINUX) + if (context_->zygote_) { + status = ZygoteHost::GetInstance()->GetTerminationStatus(handle, exit_code); + } else +#endif + { + status = base::GetTerminationStatus(handle, exit_code); + } + + // POSIX: If the process crashed, then the kernel closed the socket + // for it and so the child has already died by the time we get + // here. Since GetTerminationStatus called waitpid with WNOHANG, + // it'll reap the process. However, if GetTerminationStatus didn't + // reap the child (because it was still running), we'll need to + // Terminate via ProcessWatcher. So we can't close the handle here. + if (status != base::TERMINATION_STATUS_STILL_RUNNING) + context_->process_.Close(); + + return status; +} + +void ChildProcessLauncher::SetProcessBackgrounded(bool background) { + DCHECK(!context_->starting_); + context_->process_.SetProcessBackgrounded(background); +} diff --git a/content/browser/child_process_launcher.h b/content/browser/child_process_launcher.h new file mode 100644 index 0000000..0174f15 --- /dev/null +++ b/content/browser/child_process_launcher.h @@ -0,0 +1,71 @@ +// Copyright (c) 2009 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. + +#ifndef CONTENT_BROWSER_CHILD_PROCESS_LAUNCHER_H_ +#define CONTENT_BROWSER_CHILD_PROCESS_LAUNCHER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/process_util.h" +#include "base/ref_counted.h" + +class CommandLine; + +// Launches a process asynchronously and notifies the client of the process +// handle when it's available. It's used to avoid blocking the calling thread +// on the OS since often it can take > 100 ms to create the process. +class ChildProcessLauncher { + public: + class Client { + public: + // Will be called on the thread that the ChildProcessLauncher was + // constructed on. + virtual void OnProcessLaunched() = 0; + + protected: + virtual ~Client() {} + }; + + // Launches the process asynchronously, calling the client when the result is + // ready. Deleting this object before the process is created is safe, since + // the callback won't be called. If the process is still running by the time + // this object destructs, it will be terminated. + // Takes ownership of cmd_line. + ChildProcessLauncher( +#if defined(OS_WIN) + const FilePath& exposed_dir, +#elif defined(OS_POSIX) + bool use_zygote, + const base::environment_vector& environ, + int ipcfd, +#endif + CommandLine* cmd_line, + Client* client); + ~ChildProcessLauncher(); + + // True if the process is being launched and so the handle isn't available. + bool IsStarting(); + + // Getter for the process handle. Only call after the process has started. + base::ProcessHandle GetHandle(); + + // Call this when the child process exits to know what happened to + // it. |exit_code| is the exit code of the process if it exited + // (e.g. status from waitpid if on posix, from GetExitCodeProcess on + // Windows). |exit_code| may be NULL. + base::TerminationStatus GetChildTerminationStatus(int* exit_code); + + // Changes whether the process runs in the background or not. Only call + // this after the process has started. + void SetProcessBackgrounded(bool background); + + private: + class Context; + + scoped_refptr<Context> context_; + + DISALLOW_COPY_AND_ASSIGN(ChildProcessLauncher); +}; + +#endif // CONTENT_BROWSER_CHILD_PROCESS_LAUNCHER_H_ diff --git a/content/browser/child_process_security_policy.cc b/content/browser/child_process_security_policy.cc new file mode 100644 index 0000000..b57f7d5 --- /dev/null +++ b/content/browser/child_process_security_policy.cc @@ -0,0 +1,413 @@ +// Copyright (c) 2011 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 "content/browser/child_process_security_policy.h" + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/platform_file.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "chrome/common/bindings_policy.h" +#include "chrome/common/url_constants.h" +#include "googleurl/src/gurl.h" +#include "net/url_request/url_request.h" + +static const int kReadFilePermissions = + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_EXCLUSIVE_READ | + base::PLATFORM_FILE_ASYNC; + +// The SecurityState class is used to maintain per-child process security state +// information. +class ChildProcessSecurityPolicy::SecurityState { + public: + SecurityState() + : enabled_bindings_(0), + can_read_raw_cookies_(false) { } + ~SecurityState() { + scheme_policy_.clear(); + } + + // Grant permission to request URLs with the specified scheme. + void GrantScheme(const std::string& scheme) { + scheme_policy_[scheme] = true; + } + + // Revoke permission to request URLs with the specified scheme. + void RevokeScheme(const std::string& scheme) { + scheme_policy_[scheme] = false; + } + + // Grant certain permissions to a file. + void GrantPermissionsForFile(const FilePath& file, int permissions) { + file_permissions_[file.StripTrailingSeparators()] |= permissions; + } + + // Revokes all permissions granted to a file. + void RevokeAllPermissionsForFile(const FilePath& file) { + file_permissions_.erase(file.StripTrailingSeparators()); + } + + void GrantBindings(int bindings) { + enabled_bindings_ |= bindings; + } + + void GrantReadRawCookies() { + can_read_raw_cookies_ = true; + } + + void RevokeReadRawCookies() { + can_read_raw_cookies_ = false; + } + + // Determine whether permission has been granted to request url. + // Schemes that have not been granted default to being denied. + bool CanRequestURL(const GURL& url) { + SchemeMap::const_iterator judgment(scheme_policy_.find(url.scheme())); + + if (judgment == scheme_policy_.end()) + return false; // Unmentioned schemes are disallowed. + + return judgment->second; + } + + // Determine if the certain permissions have been granted to a file. + bool HasPermissionsForFile(const FilePath& file, int permissions) { + FilePath current_path = file.StripTrailingSeparators(); + FilePath last_path; + while (current_path != last_path) { + if (file_permissions_.find(current_path) != file_permissions_.end()) + return (file_permissions_[current_path] & permissions) == permissions; + last_path = current_path; + current_path = current_path.DirName(); + } + + return false; + } + + bool has_web_ui_bindings() const { + return BindingsPolicy::is_web_ui_enabled(enabled_bindings_); + } + + bool has_extension_bindings() const { + return BindingsPolicy::is_extension_enabled(enabled_bindings_); + } + + bool can_read_raw_cookies() const { + return can_read_raw_cookies_; + } + + private: + typedef std::map<std::string, bool> SchemeMap; + typedef std::map<FilePath, int> FileMap; // bit-set of PlatformFileFlags + + // Maps URL schemes to whether permission has been granted or revoked: + // |true| means the scheme has been granted. + // |false| means the scheme has been revoked. + // If a scheme is not present in the map, then it has never been granted + // or revoked. + SchemeMap scheme_policy_; + + // The set of files the child process is permited to upload to the web. + FileMap file_permissions_; + + int enabled_bindings_; + + bool can_read_raw_cookies_; + + DISALLOW_COPY_AND_ASSIGN(SecurityState); +}; + +ChildProcessSecurityPolicy::ChildProcessSecurityPolicy() { + // We know about these schemes and believe them to be safe. + RegisterWebSafeScheme(chrome::kHttpScheme); + RegisterWebSafeScheme(chrome::kHttpsScheme); + RegisterWebSafeScheme(chrome::kFtpScheme); + RegisterWebSafeScheme(chrome::kDataScheme); + RegisterWebSafeScheme("feed"); + RegisterWebSafeScheme(chrome::kExtensionScheme); + RegisterWebSafeScheme(chrome::kBlobScheme); + + // We know about the following psuedo schemes and treat them specially. + RegisterPseudoScheme(chrome::kAboutScheme); + RegisterPseudoScheme(chrome::kJavaScriptScheme); + RegisterPseudoScheme(chrome::kViewSourceScheme); +} + +ChildProcessSecurityPolicy::~ChildProcessSecurityPolicy() { + web_safe_schemes_.clear(); + pseudo_schemes_.clear(); + STLDeleteContainerPairSecondPointers(security_state_.begin(), + security_state_.end()); + security_state_.clear(); +} + +// static +ChildProcessSecurityPolicy* ChildProcessSecurityPolicy::GetInstance() { + return Singleton<ChildProcessSecurityPolicy>::get(); +} + +void ChildProcessSecurityPolicy::Add(int child_id) { + base::AutoLock lock(lock_); + if (security_state_.count(child_id) != 0) { + NOTREACHED() << "Add child process at most once."; + return; + } + + security_state_[child_id] = new SecurityState(); +} + +void ChildProcessSecurityPolicy::Remove(int child_id) { + base::AutoLock lock(lock_); + if (!security_state_.count(child_id)) + return; // May be called multiple times. + + delete security_state_[child_id]; + security_state_.erase(child_id); +} + +void ChildProcessSecurityPolicy::RegisterWebSafeScheme( + const std::string& scheme) { + base::AutoLock lock(lock_); + DCHECK(web_safe_schemes_.count(scheme) == 0) << "Add schemes at most once."; + DCHECK(pseudo_schemes_.count(scheme) == 0) << "Web-safe implies not psuedo."; + + web_safe_schemes_.insert(scheme); +} + +bool ChildProcessSecurityPolicy::IsWebSafeScheme(const std::string& scheme) { + base::AutoLock lock(lock_); + + return (web_safe_schemes_.find(scheme) != web_safe_schemes_.end()); +} + +void ChildProcessSecurityPolicy::RegisterPseudoScheme( + const std::string& scheme) { + base::AutoLock lock(lock_); + DCHECK(pseudo_schemes_.count(scheme) == 0) << "Add schemes at most once."; + DCHECK(web_safe_schemes_.count(scheme) == 0) << + "Psuedo implies not web-safe."; + + pseudo_schemes_.insert(scheme); +} + +bool ChildProcessSecurityPolicy::IsPseudoScheme(const std::string& scheme) { + base::AutoLock lock(lock_); + + return (pseudo_schemes_.find(scheme) != pseudo_schemes_.end()); +} + +void ChildProcessSecurityPolicy::GrantRequestURL( + int child_id, const GURL& url) { + + if (!url.is_valid()) + return; // Can't grant the capability to request invalid URLs. + + if (IsWebSafeScheme(url.scheme())) + return; // The scheme has already been whitelisted for every child process. + + if (IsPseudoScheme(url.scheme())) { + // The view-source scheme is a special case of a pseudo-URL that eventually + // results in requesting its embedded URL. + if (url.SchemeIs(chrome::kViewSourceScheme)) { + // URLs with the view-source scheme typically look like: + // view-source:http://www.google.com/a + // In order to request these URLs, the child_id needs to be able to + // request the embedded URL. + GrantRequestURL(child_id, GURL(url.path())); + } + + return; // Can't grant the capability to request pseudo schemes. + } + + { + base::AutoLock lock(lock_); + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return; + + // If the child process has been commanded to request a scheme, then we + // grant it the capability to request URLs of that scheme. + state->second->GrantScheme(url.scheme()); + } +} + +void ChildProcessSecurityPolicy::GrantReadFile(int child_id, + const FilePath& file) { + GrantPermissionsForFile(child_id, file, kReadFilePermissions); +} + +void ChildProcessSecurityPolicy::GrantPermissionsForFile( + int child_id, const FilePath& file, int permissions) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return; + + state->second->GrantPermissionsForFile(file, permissions); +} + +void ChildProcessSecurityPolicy::RevokeAllPermissionsForFile( + int child_id, const FilePath& file) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return; + + state->second->RevokeAllPermissionsForFile(file); +} + +void ChildProcessSecurityPolicy::GrantScheme(int child_id, + const std::string& scheme) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return; + + state->second->GrantScheme(scheme); +} + +void ChildProcessSecurityPolicy::GrantWebUIBindings(int child_id) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return; + + state->second->GrantBindings(BindingsPolicy::WEB_UI); + + // Web UI bindings need the ability to request chrome: URLs. + state->second->GrantScheme(chrome::kChromeUIScheme); + + // Web UI pages can contain links to file:// URLs. + state->second->GrantScheme(chrome::kFileScheme); +} + +void ChildProcessSecurityPolicy::GrantExtensionBindings(int child_id) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return; + + state->second->GrantBindings(BindingsPolicy::EXTENSION); +} + +void ChildProcessSecurityPolicy::GrantReadRawCookies(int child_id) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return; + + state->second->GrantReadRawCookies(); +} + +void ChildProcessSecurityPolicy::RevokeReadRawCookies(int child_id) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return; + + state->second->RevokeReadRawCookies(); +} + +bool ChildProcessSecurityPolicy::CanRequestURL( + int child_id, const GURL& url) { + if (!url.is_valid()) + return false; // Can't request invalid URLs. + + if (IsWebSafeScheme(url.scheme())) + return true; // The scheme has been white-listed for every child process. + + if (IsPseudoScheme(url.scheme())) { + // There are a number of special cases for pseudo schemes. + + if (url.SchemeIs(chrome::kViewSourceScheme)) { + // A view-source URL is allowed if the child process is permitted to + // request the embedded URL. Careful to avoid pointless recursion. + GURL child_url(url.path()); + if (child_url.SchemeIs(chrome::kViewSourceScheme) && + url.SchemeIs(chrome::kViewSourceScheme)) + return false; + + return CanRequestURL(child_id, child_url); + } + + if (LowerCaseEqualsASCII(url.spec(), chrome::kAboutBlankURL)) + return true; // Every child process can request <about:blank>. + + // URLs like <about:memory> and <about:crash> shouldn't be requestable by + // any child process. Also, this case covers <javascript:...>, which should + // be handled internally by the process and not kicked up to the browser. + return false; + } + + if (!net::URLRequest::IsHandledURL(url)) + return true; // This URL request is destined for ShellExecute. + + { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return false; + + // Otherwise, we consult the child process's security state to see if it is + // allowed to request the URL. + return state->second->CanRequestURL(url); + } +} + +bool ChildProcessSecurityPolicy::CanReadFile(int child_id, + const FilePath& file) { + return HasPermissionsForFile(child_id, file, kReadFilePermissions); +} + +bool ChildProcessSecurityPolicy::HasPermissionsForFile( + int child_id, const FilePath& file, int permissions) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return false; + + return state->second->HasPermissionsForFile(file, permissions); +} + +bool ChildProcessSecurityPolicy::HasWebUIBindings(int child_id) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return false; + + return state->second->has_web_ui_bindings(); +} + +bool ChildProcessSecurityPolicy::HasExtensionBindings(int child_id) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return false; + + return state->second->has_extension_bindings(); +} + +bool ChildProcessSecurityPolicy::CanReadRawCookies(int child_id) { + base::AutoLock lock(lock_); + + SecurityStateMap::iterator state = security_state_.find(child_id); + if (state == security_state_.end()) + return false; + + return state->second->can_read_raw_cookies(); +} diff --git a/content/browser/child_process_security_policy.h b/content/browser/child_process_security_policy.h new file mode 100644 index 0000000..2f2df5e --- /dev/null +++ b/content/browser/child_process_security_policy.h @@ -0,0 +1,164 @@ +// Copyright (c) 2011 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. + +#ifndef CONTENT_BROWSER_CHILD_PROCESS_SECURITY_POLICY_H_ +#define CONTENT_BROWSER_CHILD_PROCESS_SECURITY_POLICY_H_ + +#pragma once + +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/singleton.h" +#include "base/synchronization/lock.h" + +class FilePath; +class GURL; + +// The ChildProcessSecurityPolicy class is used to grant and revoke security +// capabilities for child porcesses. For example, it restricts whether a child +// process is permmitted to loaded file:// URLs based on whether the process +// has ever been commanded to load file:// URLs by the browser. +// +// ChildProcessSecurityPolicy is a singleton that may be used on any thread. +// +class ChildProcessSecurityPolicy { + public: + // Object can only be created through GetInstance() so the constructor is + // private. + ~ChildProcessSecurityPolicy(); + + // There is one global ChildProcessSecurityPolicy object for the entire + // browser process. The object returned by this method may be accessed on + // any thread. + static ChildProcessSecurityPolicy* GetInstance(); + + // Web-safe schemes can be requested by any child process. Once a web-safe + // scheme has been registered, any child process can request URLs with + // that scheme. There is no mechanism for revoking web-safe schemes. + void RegisterWebSafeScheme(const std::string& scheme); + + // Returns true iff |scheme| has been registered as a web-safe scheme. + bool IsWebSafeScheme(const std::string& scheme); + + // Pseudo schemes are treated differently than other schemes because they + // cannot be requested like normal URLs. There is no mechanism for revoking + // pseudo schemes. + void RegisterPseudoScheme(const std::string& scheme); + + // Returns true iff |scheme| has been registered as pseudo scheme. + bool IsPseudoScheme(const std::string& scheme); + + // Upon creation, child processes should register themselves by calling this + // this method exactly once. + void Add(int child_id); + + // Upon destruction, child processess should unregister themselves by caling + // this method exactly once. + void Remove(int child_id); + + // Whenever the browser processes commands the child process to request a URL, + // it should call this method to grant the child process the capability to + // request the URL. + void GrantRequestURL(int child_id, const GURL& url); + + // Whenever the user picks a file from a <input type="file"> element, the + // browser should call this function to grant the child process the capability + // to upload the file to the web. + void GrantReadFile(int child_id, const FilePath& file); + + // Grants certain permissions to a file. |permissions| must be a bit-set of + // base::PlatformFileFlags. + void GrantPermissionsForFile(int child_id, + const FilePath& file, + int permissions); + + // Revokes all permissions granted to the given file. + void RevokeAllPermissionsForFile(int child_id, const FilePath& file); + + // Grants the child process the capability to access URLs of the provided + // scheme. + void GrantScheme(int child_id, const std::string& scheme); + + // Grant the child process the ability to use Web UI Bindings. + void GrantWebUIBindings(int child_id); + + // Grant the child process the ability to use extension Bindings. + void GrantExtensionBindings(int child_id); + + // Grant the child process the ability to read raw cookies. + void GrantReadRawCookies(int child_id); + + // Revoke read raw cookies permission. + void RevokeReadRawCookies(int child_id); + + // Before servicing a child process's request for a URL, the browser should + // call this method to determine whether the process has the capability to + // request the URL. + bool CanRequestURL(int child_id, const GURL& url); + + // Before servicing a child process's request to upload a file to the web, the + // browser should call this method to determine whether the process has the + // capability to upload the requested file. + bool CanReadFile(int child_id, const FilePath& file); + + // Determines if certain permissions were granted for a file. |permissions| + // must be a bit-set of base::PlatformFileFlags. + bool HasPermissionsForFile(int child_id, + const FilePath& file, + int permissions); + + // Returns true if the specified child_id has been granted WebUIBindings. + // The browser should check this property before assuming the child process is + // allowed to use WebUIBindings. + bool HasWebUIBindings(int child_id); + + // Returns true if the specified child_id has been granted WebUIBindings. + // The browser should check this property before assuming the child process is + // allowed to use extension bindings. + bool HasExtensionBindings(int child_id); + + // Returns true if the specified child_id has been granted ReadRawCookies. + bool CanReadRawCookies(int child_id); + + private: + friend class ChildProcessSecurityPolicyInProcessBrowserTest; + FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyInProcessBrowserTest, + NoLeak); + + class SecurityState; + + typedef std::set<std::string> SchemeSet; + typedef std::map<int, SecurityState*> SecurityStateMap; + + // Obtain an instance of ChildProcessSecurityPolicy via GetInstance(). + ChildProcessSecurityPolicy(); + friend struct DefaultSingletonTraits<ChildProcessSecurityPolicy>; + + // You must acquire this lock before reading or writing any members of this + // class. You must not block while holding this lock. + base::Lock lock_; + + // These schemes are white-listed for all child processes. This set is + // protected by |lock_|. + SchemeSet web_safe_schemes_; + + // These schemes do not actually represent retrievable URLs. For example, + // the the URLs in the "about" scheme are aliases to other URLs. This set is + // protected by |lock_|. + SchemeSet pseudo_schemes_; + + // This map holds a SecurityState for each child process. The key for the + // map is the ID of the ChildProcessHost. The SecurityState objects are + // owned by this object and are protected by |lock_|. References to them must + // not escape this class. + SecurityStateMap security_state_; + + DISALLOW_COPY_AND_ASSIGN(ChildProcessSecurityPolicy); +}; + +#endif // CONTENT_BROWSER_CHILD_PROCESS_SECURITY_POLICY_H_ diff --git a/content/browser/child_process_security_policy_browsertest.cc b/content/browser/child_process_security_policy_browsertest.cc new file mode 100644 index 0000000..f28001d --- /dev/null +++ b/content/browser/child_process_security_policy_browsertest.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2009 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 <string> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/process_util.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/result_codes.h" +#include "chrome/test/in_process_browser_test.h" +#include "chrome/test/ui_test_utils.h" +#include "content/browser/child_process_security_policy.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "testing/gtest/include/gtest/gtest.h" + +class ChildProcessSecurityPolicyInProcessBrowserTest + : public InProcessBrowserTest { + public: + virtual void SetUp() { + EXPECT_EQ( + ChildProcessSecurityPolicy::GetInstance()->security_state_.size(), 0U); + InProcessBrowserTest::SetUp(); + } + + virtual void TearDown() { + EXPECT_EQ( + ChildProcessSecurityPolicy::GetInstance()->security_state_.size(), 0U); + InProcessBrowserTest::TearDown(); + } +}; + +IN_PROC_BROWSER_TEST_F(ChildProcessSecurityPolicyInProcessBrowserTest, NoLeak) { + const FilePath kTestDir(FILE_PATH_LITERAL("google")); + const FilePath kTestFile(FILE_PATH_LITERAL("google.html")); + GURL url(ui_test_utils::GetTestUrl(kTestDir, kTestFile)); + + ui_test_utils::NavigateToURL(browser(), url); + EXPECT_EQ( + ChildProcessSecurityPolicy::GetInstance()->security_state_.size(), 1U); + + TabContents* tab = browser()->GetTabContentsAt(0); + ASSERT_TRUE(tab != NULL); + base::KillProcess(tab->GetRenderProcessHost()->GetHandle(), + ResultCodes::KILLED, true); + + tab->controller().Reload(true); + EXPECT_EQ( + ChildProcessSecurityPolicy::GetInstance()->security_state_.size(), 1U); +} diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc new file mode 100644 index 0000000..92ba8d2 --- /dev/null +++ b/content/browser/child_process_security_policy_unittest.cc @@ -0,0 +1,333 @@ +// Copyright (c) 2011 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 <string> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/platform_file.h" +#include "chrome/common/url_constants.h" +#include "content/browser/child_process_security_policy.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_test_job.h" +#include "testing/gtest/include/gtest/gtest.h" + +class ChildProcessSecurityPolicyTest : public testing::Test { + protected: + // testing::Test + virtual void SetUp() { + // In the real world, "chrome:" is a handled scheme. + net::URLRequest::RegisterProtocolFactory(chrome::kChromeUIScheme, + &net::URLRequestTestJob::Factory); + } + virtual void TearDown() { + net::URLRequest::RegisterProtocolFactory(chrome::kChromeUIScheme, NULL); + } +}; + +static int kRendererID = 42; + +TEST_F(ChildProcessSecurityPolicyTest, IsWebSafeSchemeTest) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + EXPECT_TRUE(p->IsWebSafeScheme(chrome::kHttpScheme)); + EXPECT_TRUE(p->IsWebSafeScheme(chrome::kHttpsScheme)); + EXPECT_TRUE(p->IsWebSafeScheme(chrome::kFtpScheme)); + EXPECT_TRUE(p->IsWebSafeScheme(chrome::kDataScheme)); + EXPECT_TRUE(p->IsWebSafeScheme("feed")); + EXPECT_TRUE(p->IsWebSafeScheme(chrome::kExtensionScheme)); + + EXPECT_FALSE(p->IsWebSafeScheme("registered-web-safe-scheme")); + p->RegisterWebSafeScheme("registered-web-safe-scheme"); + EXPECT_TRUE(p->IsWebSafeScheme("registered-web-safe-scheme")); +} + +TEST_F(ChildProcessSecurityPolicyTest, IsPseudoSchemeTest) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + EXPECT_TRUE(p->IsPseudoScheme(chrome::kAboutScheme)); + EXPECT_TRUE(p->IsPseudoScheme(chrome::kJavaScriptScheme)); + EXPECT_TRUE(p->IsPseudoScheme(chrome::kViewSourceScheme)); + + EXPECT_FALSE(p->IsPseudoScheme("registered-psuedo-scheme")); + p->RegisterPseudoScheme("registered-psuedo-scheme"); + EXPECT_TRUE(p->IsPseudoScheme("registered-psuedo-scheme")); +} + +TEST_F(ChildProcessSecurityPolicyTest, StandardSchemesTest) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + p->Add(kRendererID); + + // Safe + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("http://www.google.com/"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("https://www.paypal.com/"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("ftp://ftp.gnu.org/"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("data:text/html,<b>Hi</b>"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, + GURL("view-source:http://www.google.com/"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("chrome-extension://xy/z"))); + + // Dangerous + EXPECT_FALSE(p->CanRequestURL(kRendererID, + GURL("file:///etc/passwd"))); + EXPECT_FALSE(p->CanRequestURL(kRendererID, + GURL("chrome://foo/bar"))); + + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, AboutTest) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + p->Add(kRendererID); + + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("about:blank"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("about:BlAnK"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("aBouT:BlAnK"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("aBouT:blank"))); + + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:memory"))); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:crash"))); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:cache"))); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:hang"))); + + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("aBoUt:memory"))); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:CrASh"))); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("abOuT:cAChe"))); + + p->GrantRequestURL(kRendererID, GURL(chrome::kAboutMemoryURL)); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL(chrome::kAboutMemoryURL))); + + p->GrantRequestURL(kRendererID, GURL(chrome::kAboutCrashURL)); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL(chrome::kAboutCrashURL))); + + p->GrantRequestURL(kRendererID, GURL(chrome::kAboutCacheURL)); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL(chrome::kAboutCacheURL))); + + p->GrantRequestURL(kRendererID, GURL(chrome::kAboutHangURL)); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL(chrome::kAboutHangURL))); + + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, JavaScriptTest) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + p->Add(kRendererID); + + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("javascript:alert('xss')"))); + p->GrantRequestURL(kRendererID, GURL("javascript:alert('xss')")); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("javascript:alert('xss')"))); + + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, RegisterWebSafeSchemeTest) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + p->Add(kRendererID); + + // Currently, "asdf" is destined for ShellExecute, so it is allowed. + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers"))); + + // Once we register a ProtocolFactory for "asdf", we default to deny. + net::URLRequest::RegisterProtocolFactory("asdf", + &net::URLRequestTestJob::Factory); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("asdf:rockers"))); + + // We can allow new schemes by adding them to the whitelist. + p->RegisterWebSafeScheme("asdf"); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers"))); + + // Cleanup. + net::URLRequest::RegisterProtocolFactory("asdf", NULL); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers"))); + + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, CanServiceCommandsTest) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + p->Add(kRendererID); + + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd"))); + p->GrantRequestURL(kRendererID, GURL("file:///etc/passwd")); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd"))); + + // We should forget our state if we repeat a renderer id. + p->Remove(kRendererID); + p->Add(kRendererID); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd"))); + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, ViewSource) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + p->Add(kRendererID); + + // View source is determined by the embedded scheme. + EXPECT_TRUE(p->CanRequestURL(kRendererID, + GURL("view-source:http://www.google.com/"))); + EXPECT_FALSE(p->CanRequestURL(kRendererID, + GURL("view-source:file:///etc/passwd"))); + EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd"))); + EXPECT_FALSE(p->CanRequestURL( + kRendererID, GURL("view-source:view-source:http://www.google.com/"))); + + p->GrantRequestURL(kRendererID, GURL("view-source:file:///etc/passwd")); + // View source needs to be able to request the embedded scheme. + EXPECT_TRUE(p->CanRequestURL(kRendererID, + GURL("view-source:file:///etc/passwd"))); + EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd"))); + + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, CanReadFiles) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + p->Add(kRendererID); + + EXPECT_FALSE(p->CanReadFile(kRendererID, + FilePath(FILE_PATH_LITERAL("/etc/passwd")))); + p->GrantReadFile(kRendererID, FilePath(FILE_PATH_LITERAL("/etc/passwd"))); + EXPECT_TRUE(p->CanReadFile(kRendererID, + FilePath(FILE_PATH_LITERAL("/etc/passwd")))); + EXPECT_FALSE(p->CanReadFile(kRendererID, + FilePath(FILE_PATH_LITERAL("/etc/shadow")))); + + p->Remove(kRendererID); + p->Add(kRendererID); + + EXPECT_FALSE(p->CanReadFile(kRendererID, + FilePath(FILE_PATH_LITERAL("/etc/passwd")))); + EXPECT_FALSE(p->CanReadFile(kRendererID, + FilePath(FILE_PATH_LITERAL("/etc/shadow")))); + + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, FilePermissions) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + // Grant permissions for a file. + p->Add(kRendererID); + FilePath file = FilePath(FILE_PATH_LITERAL("/etc/passwd")); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN)); + + p->GrantPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_TRUNCATE); + EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_TRUNCATE)); + EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ)); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_CREATE)); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_TRUNCATE)); + p->Remove(kRendererID); + + // Grant permissions for the directory the file is in. + p->Add(kRendererID); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN)); + p->GrantPermissionsForFile(kRendererID, FilePath(FILE_PATH_LITERAL("/etc")), + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ); + EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN)); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE)); + p->Remove(kRendererID); + + // Grant permissions for the directory the file is in (with trailing '/'). + p->Add(kRendererID); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN)); + p->GrantPermissionsForFile(kRendererID, FilePath(FILE_PATH_LITERAL("/etc/")), + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ); + EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN)); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE)); + + // Grant permissions for the file (should overwrite the permissions granted + // for the directory). + p->GrantPermissionsForFile(kRendererID, file, base::PLATFORM_FILE_TEMPORARY); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN)); + EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_TEMPORARY)); + + // Revoke all permissions for the file (it should inherit its permissions + // from the directory again). + p->RevokeAllPermissionsForFile(kRendererID, file); + EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ)); + EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, file, + base::PLATFORM_FILE_TEMPORARY)); + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, CanServiceWebUIBindings) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + GURL url("chrome://thumb/http://www.google.com/"); + + p->Add(kRendererID); + + EXPECT_FALSE(p->HasWebUIBindings(kRendererID)); + EXPECT_FALSE(p->CanRequestURL(kRendererID, url)); + p->GrantWebUIBindings(kRendererID); + EXPECT_TRUE(p->HasWebUIBindings(kRendererID)); + EXPECT_TRUE(p->CanRequestURL(kRendererID, url)); + + p->Remove(kRendererID); +} + +TEST_F(ChildProcessSecurityPolicyTest, RemoveRace) { + ChildProcessSecurityPolicy* p = ChildProcessSecurityPolicy::GetInstance(); + + GURL url("file:///etc/passwd"); + FilePath file(FILE_PATH_LITERAL("/etc/passwd")); + + p->Add(kRendererID); + + p->GrantRequestURL(kRendererID, url); + p->GrantReadFile(kRendererID, file); + p->GrantWebUIBindings(kRendererID); + + EXPECT_TRUE(p->CanRequestURL(kRendererID, url)); + EXPECT_TRUE(p->CanReadFile(kRendererID, file)); + EXPECT_TRUE(p->HasWebUIBindings(kRendererID)); + + p->Remove(kRendererID); + + // Renderers are added and removed on the UI thread, but the policy can be + // queried on the IO thread. The ChildProcessSecurityPolicy needs to be + // prepared to answer policy questions about renderers who no longer exist. + + // In this case, we default to secure behavior. + EXPECT_FALSE(p->CanRequestURL(kRendererID, url)); + EXPECT_FALSE(p->CanReadFile(kRendererID, file)); + EXPECT_FALSE(p->HasWebUIBindings(kRendererID)); +} diff --git a/content/browser/chrome_blob_storage_context.cc b/content/browser/chrome_blob_storage_context.cc new file mode 100644 index 0000000..245e860 --- /dev/null +++ b/content/browser/chrome_blob_storage_context.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2010 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 "content/browser/chrome_blob_storage_context.h" + +#include "chrome/browser/net/chrome_url_request_context.h" +#include "webkit/blob/blob_storage_controller.h" + +using webkit_blob::BlobStorageController; + +ChromeBlobStorageContext::ChromeBlobStorageContext() { +} + +void ChromeBlobStorageContext::InitializeOnIOThread() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + controller_.reset(new BlobStorageController()); +} + +ChromeBlobStorageContext::~ChromeBlobStorageContext() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); +} diff --git a/content/browser/chrome_blob_storage_context.h b/content/browser/chrome_blob_storage_context.h new file mode 100644 index 0000000..1c5fe55 --- /dev/null +++ b/content/browser/chrome_blob_storage_context.h @@ -0,0 +1,46 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_CHROME_BLOB_STORAGE_CONTEXT_H_ +#define CONTENT_BROWSER_CHROME_BLOB_STORAGE_CONTEXT_H_ +#pragma once + +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/browser_thread.h" + +class GURL; + +namespace webkit_blob { +class BlobStorageController; +} + +// A context class that keeps track of BlobStorageController used by the chrome. +// There is an instance associated with each Profile. There could be multiple +// URLRequestContexts in the same profile that refers to the same instance. +// +// All methods, except the ctor, are expected to be called on +// the IO thread (unless specifically called out in doc comments). +class ChromeBlobStorageContext + : public base::RefCountedThreadSafe<ChromeBlobStorageContext, + BrowserThread::DeleteOnIOThread> { + public: + ChromeBlobStorageContext(); + + void InitializeOnIOThread(); + + webkit_blob::BlobStorageController* controller() const { + return controller_.get(); + } + + private: + friend class BrowserThread; + friend class DeleteTask<ChromeBlobStorageContext>; + + virtual ~ChromeBlobStorageContext(); + + scoped_ptr<webkit_blob::BlobStorageController> controller_; +}; + +#endif // CONTENT_BROWSER_CHROME_BLOB_STORAGE_CONTEXT_H_ diff --git a/content/browser/cross_site_request_manager.cc b/content/browser/cross_site_request_manager.cc new file mode 100644 index 0000000..38762bd --- /dev/null +++ b/content/browser/cross_site_request_manager.cc @@ -0,0 +1,38 @@ +// 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 "content/browser/cross_site_request_manager.h" + +#include "base/singleton.h" + +bool CrossSiteRequestManager::HasPendingCrossSiteRequest(int renderer_id, + int render_view_id) { + base::AutoLock lock(lock_); + + std::pair<int, int> key(renderer_id, render_view_id); + return pending_cross_site_views_.find(key) != + pending_cross_site_views_.end(); +} + +void CrossSiteRequestManager::SetHasPendingCrossSiteRequest(int renderer_id, + int render_view_id, + bool has_pending) { + base::AutoLock lock(lock_); + + std::pair<int, int> key(renderer_id, render_view_id); + if (has_pending) { + pending_cross_site_views_.insert(key); + } else { + pending_cross_site_views_.erase(key); + } +} + +CrossSiteRequestManager::CrossSiteRequestManager() {} + +CrossSiteRequestManager::~CrossSiteRequestManager() {} + +// static +CrossSiteRequestManager* CrossSiteRequestManager::GetInstance() { + return Singleton<CrossSiteRequestManager>::get(); +} diff --git a/content/browser/cross_site_request_manager.h b/content/browser/cross_site_request_manager.h new file mode 100644 index 0000000..1274264 --- /dev/null +++ b/content/browser/cross_site_request_manager.h @@ -0,0 +1,60 @@ +// Copyright (c) 2010 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. + +#ifndef CONTENT_BROWSER_CROSS_SITE_REQUEST_MANAGER_H_ +#define CONTENT_BROWSER_CROSS_SITE_REQUEST_MANAGER_H_ +#pragma once + +#include <set> +#include <utility> + +#include "base/basictypes.h" +#include "base/synchronization/lock.h" + +template <typename T> struct DefaultSingletonTraits; + +// CrossSiteRequestManager is used to handle bookkeeping for cross-site +// requests and responses between the UI and IO threads. Such requests involve +// a transition from one RenderViewHost to another within TabContents, and +// involve coordination with ResourceDispatcherHost. +// +// CrossSiteRequestManager is a singleton that may be used on any thread. +// +class CrossSiteRequestManager { + public: + // Returns the singleton instance. + static CrossSiteRequestManager* GetInstance(); + + // Returns whether the RenderViewHost specified by the given IDs currently + // has a pending cross-site request. If so, we will have to delay the + // response until the previous RenderViewHost runs its onunload handler. + // Called by ResourceDispatcherHost on the IO thread. + bool HasPendingCrossSiteRequest(int renderer_id, int render_view_id); + + // Sets whether the RenderViewHost specified by the given IDs currently has a + // pending cross-site request. Called by RenderViewHost on the UI thread. + void SetHasPendingCrossSiteRequest(int renderer_id, + int render_view_id, + bool has_pending); + + private: + friend struct DefaultSingletonTraits<CrossSiteRequestManager>; + typedef std::set<std::pair<int, int> > RenderViewSet; + + CrossSiteRequestManager(); + ~CrossSiteRequestManager(); + + // You must acquire this lock before reading or writing any members of this + // class. You must not block while holding this lock. + base::Lock lock_; + + // Set of (render_process_host_id, render_view_id) pairs of all + // RenderViewHosts that have pending cross-site requests. Used to pass + // information about the RenderViewHosts between the UI and IO threads. + RenderViewSet pending_cross_site_views_; + + DISALLOW_COPY_AND_ASSIGN(CrossSiteRequestManager); +}; + +#endif // CONTENT_BROWSER_CROSS_SITE_REQUEST_MANAGER_H_ diff --git a/content/browser/disposition_utils.cc b/content/browser/disposition_utils.cc new file mode 100644 index 0000000..fba7479 --- /dev/null +++ b/content/browser/disposition_utils.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2011 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 "content/browser/disposition_utils.h" + +#include "build/build_config.h" + +namespace disposition_utils { + +WindowOpenDisposition DispositionFromClick(bool middle_button, + bool alt_key, + bool ctrl_key, + bool meta_key, + bool shift_key) { + // MacOS uses meta key (Command key) to spawn new tabs. +#if defined(OS_MACOSX) + if (middle_button || meta_key) +#else + if (middle_button || ctrl_key) +#endif + return shift_key ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB; + if (shift_key) + return NEW_WINDOW; + if (alt_key) + return SAVE_TO_DISK; + return CURRENT_TAB; +} + +} diff --git a/content/browser/disposition_utils.h b/content/browser/disposition_utils.h new file mode 100644 index 0000000..ba8c9e5 --- /dev/null +++ b/content/browser/disposition_utils.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011 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. + +#ifndef CONTENT_BROWSER_DISPOSITION_UTILS_H_ +#define CONTENT_BROWSER_DISPOSITION_UTILS_H_ +#pragma once + +#include "webkit/glue/window_open_disposition.h" + +namespace disposition_utils { + +// Translates event flags from a click on a link into the user's desired +// window disposition. For example, a middle click would mean to open +// a background tab. +WindowOpenDisposition DispositionFromClick(bool middle_button, + bool alt_key, + bool ctrl_key, + bool meta_key, + bool shift_key); + +} + +#endif // CONTENT_BROWSER_DISPOSITION_UTILS_H_ diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 1629d8b..4a5a09a 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -20,6 +20,32 @@ '..', ], 'sources': [ + 'browser/browser_child_process_host.cc', + 'browser/browser_child_process_host.h', + 'browser/browser_message_filter.cc', + 'browser/browser_message_filter.h', + 'browser/browser_thread.cc', + 'browser/browser_thread.h', + 'browser/browsing_instance.cc', + 'browser/browsing_instance.h', + 'browser/cancelable_request.cc', + 'browser/cancelable_request.h', + 'browser/cert_store.cc', + 'browser/cert_store.h', + 'browser/certificate_manager_model.cc', + 'browser/certificate_manager_model.h', + 'browser/certificate_viewer.cc', + 'browser/certificate_viewer.h', + 'browser/child_process_launcher.cc', + 'browser/child_process_launcher.h', + 'browser/child_process_security_policy.cc', + 'browser/child_process_security_policy.h', + 'browser/chrome_blob_storage_context.cc', + 'browser/chrome_blob_storage_context.h', + 'browser/cross_site_request_manager.cc', + 'browser/cross_site_request_manager.h', + 'browser/disposition_utils.cc', + 'browser/disposition_utils.h', 'browser/renderer_host/accelerated_surface_container_mac.cc', 'browser/renderer_host/accelerated_surface_container_mac.h', 'browser/renderer_host/accelerated_surface_container_manager_mac.cc', @@ -144,6 +170,13 @@ '../build/linux/system.gyp:x11', ], }], + ['OS!="linux"', { + 'sources!': [ + # TODO(mattm): Cert manager stuff is really !USE_NSS. + 'browser/certificate_manager_model.cc', + 'browser/certificate_manager_model.h', + ], + }], ], }, ], |