diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-16 18:31:26 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-16 18:31:26 +0000 |
commit | 8c8657d61fb8792985888c88730b2eb2f4fc4019 (patch) | |
tree | 73f4290e22afe39a19d64a0792a5a2ff2740a32c /chrome/browser/renderer_host | |
parent | 52ef74f432e2fb08be4f7ea323244884894fc656 (diff) | |
download | chromium_src-8c8657d61fb8792985888c88730b2eb2f4fc4019.zip chromium_src-8c8657d61fb8792985888c88730b2eb2f4fc4019.tar.gz chromium_src-8c8657d61fb8792985888c88730b2eb2f4fc4019.tar.bz2 |
Move functions required by the rest of the browser from RenderProcessHost to an interface and move the implementation to BrowserRenderProcessHost. This will allow me to write render view unit tests without using the actual renderer, but there are no tests yet.
Review URL: http://codereview.chromium.org/18132
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8188 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/renderer_host')
-rw-r--r-- | chrome/browser/renderer_host/browser_render_process_host.cc | 730 | ||||
-rw-r--r-- | chrome/browser/renderer_host/browser_render_process_host.h | 160 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_process_host.cc | 159 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_process_host.h | 203 |
4 files changed, 1252 insertions, 0 deletions
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc new file mode 100644 index 0000000..198d886 --- /dev/null +++ b/chrome/browser/renderer_host/browser_render_process_host.cc @@ -0,0 +1,730 @@ +// 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 "chrome/browser/renderer_host/browser_render_process_host.h" + +#include <algorithm> +#include <sstream> +#include <vector> + +#include "base/command_line.h" +#include "base/debug_util.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/shared_memory.h" +#include "base/singleton.h" +#include "base/string_util.h" +#include "base/thread.h" +#include "base/win_util.h" +#include "chrome/app/result_codes.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/cache_manager_host.h" +#include "chrome/browser/extensions/user_script_master.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/plugin_service.h" +#include "chrome/browser/render_widget_helper.h" +#include "chrome/browser/render_view_host.h" +#include "chrome/browser/renderer_security_policy.h" +#include "chrome/browser/resource_message_filter.h" +#include "chrome/browser/sandbox_policy.h" +#include "chrome/browser/spellchecker.h" +#include "chrome/browser/visitedlink_master.h" +#include "chrome/browser/tab_contents/web_contents.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/debug_flags.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/logging_chrome.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/process_watcher.h" +#include "chrome/common/win_util.h" +#include "chrome/renderer/render_process.h" +#include "net/base/cookie_monster.h" +#include "net/base/net_util.h" +#include "sandbox/src/sandbox.h" + +#include "SkBitmap.h" + +#include "generated_resources.h" + +namespace { + +// ---------------------------------------------------------------------------- + +class RendererMainThread : public base::Thread { + public: + explicit RendererMainThread(const std::wstring& channel_id) + : base::Thread("Chrome_InProcRendererThread"), + channel_id_(channel_id) { + } + + protected: + virtual void Init() { + CoInitialize(NULL); + + bool rv = RenderProcess::GlobalInit(channel_id_); + DCHECK(rv); + // It's a little lame to manually set this flag. But the single process + // RendererThread will receive the WM_QUIT. We don't need to assert on + // this thread, so just force the flag manually. + // If we want to avoid this, we could create the InProcRendererThread + // directly with _beginthreadex() rather than using the Thread class. + base::Thread::SetThreadWasQuitProperly(true); + } + + virtual void CleanUp() { + RenderProcess::GlobalCleanup(); + + CoUninitialize(); + } + + private: + std::wstring channel_id_; +}; + +// Used for a View_ID where the renderer has not been attached yet +const int32 kInvalidViewID = -1; + +// Get the path to the renderer executable, which is the same as the +// current executable. +bool GetRendererPath(std::wstring* cmd_line) { + return PathService::Get(base::FILE_EXE, cmd_line); +} + +const wchar_t* const kDesktopName = L"ChromeRendererDesktop"; + +} // namespace + +//------------------------------------------------------------------------------ + +// static +void BrowserRenderProcessHost::RegisterPrefs(PrefService* prefs) { + prefs->RegisterBooleanPref(prefs::kStartRenderersManually, false); +} + +BrowserRenderProcessHost::BrowserRenderProcessHost(Profile* profile) + : RenderProcessHost(profile), + visible_widgets_(0), + backgrounded_(true) { + DCHECK(host_id() >= 0); // We use a negative host_id_ in destruction. + widget_helper_ = new RenderWidgetHelper(host_id()); + + CacheManagerHost::GetInstance()->Add(host_id()); + RendererSecurityPolicy::GetInstance()->Add(host_id()); + + PrefService* prefs = profile->GetPrefs(); + prefs->AddPrefObserver(prefs::kBlockPopups, this); + widget_helper_->set_block_popups( + profile->GetPrefs()->GetBoolean(prefs::kBlockPopups)); + + NotificationService::current()->AddObserver(this, + NOTIFY_USER_SCRIPTS_LOADED, NotificationService::AllSources()); + + // Note: When we create the BrowserRenderProcessHost, it's technically backgrounded, + // because it has no visible listeners. But the process doesn't + // actually exist yet, so we'll Background it later, after creation. +} + +BrowserRenderProcessHost::~BrowserRenderProcessHost() { + // Some tests hold BrowserRenderProcessHost in a scoped_ptr, so we must call + // Unregister here as well as in response to Release(). + Unregister(); + + // We may have some unsent messages at this point, but that's OK. + channel_.reset(); + + if (process_.handle() && !run_renderer_in_process()) { + watcher_.StopWatching(); + ProcessWatcher::EnsureProcessTerminated(process_.handle()); + } + + profile()->GetPrefs()->RemovePrefObserver(prefs::kBlockPopups, this); + + NotificationService::current()->RemoveObserver(this, + NOTIFY_USER_SCRIPTS_LOADED, NotificationService::AllSources()); +} + +bool BrowserRenderProcessHost::Init() { + // calling Init() more than once does nothing, this makes it more convenient + // for the view host which may not be sure in some cases + if (channel_.get()) + return true; + + // run the IPC channel on the shared IO thread. + base::Thread* io_thread = g_browser_process->io_thread(); + + scoped_refptr<ResourceMessageFilter> resource_message_filter = + new ResourceMessageFilter(g_browser_process->resource_dispatcher_host(), + PluginService::GetInstance(), + g_browser_process->print_job_manager(), + host_id(), + profile(), + widget_helper_, + profile()->GetSpellChecker()); + + CommandLine browser_command_line; + + // setup IPC channel + std::wstring channel_id = GenerateRandomChannelID(this); + channel_.reset( + new IPC::SyncChannel(channel_id, IPC::Channel::MODE_SERVER, this, + resource_message_filter, + io_thread->message_loop(), true, + g_browser_process->shutdown_event())); + // As a preventive mesure, we DCHECK if someone sends a synchronous message + // with no time-out, which in the context of the browser process we should not + // be doing. + channel_->set_sync_messages_with_no_timeout_allowed(false); + + // build command line for renderer, we have to quote the executable name to + // deal with spaces + std::wstring renderer_path = + browser_command_line.GetSwitchValue(switches::kRendererPath); + if (renderer_path.empty()) + if (!GetRendererPath(&renderer_path)) + return false; + std::wstring cmd_line; + cmd_line = L"\"" + renderer_path + L"\""; + if (logging::DialogsAreSuppressed()) + CommandLine::AppendSwitch(&cmd_line, switches::kNoErrorDialogs); + + // propagate the following switches to the renderer command line + // (along with any associated values) if present in the browser command line + static const wchar_t* const switch_names[] = { + switches::kRendererAssertTest, + switches::kRendererCrashTest, + switches::kRendererStartupDialog, + switches::kNoSandbox, + switches::kTestSandbox, + switches::kInProcessPlugins, + switches::kDomAutomationController, + switches::kUserAgent, + switches::kJavaScriptFlags, + switches::kRecordMode, + switches::kPlaybackMode, + switches::kDisableBreakpad, + switches::kFullMemoryCrashReport, + switches::kEnableLogging, + switches::kDumpHistogramsOnExit, + switches::kDisableLogging, + switches::kLoggingLevel, + switches::kDebugPrint, + switches::kAllowAllActiveX, + switches::kMemoryProfiling, + switches::kEnableWatchdog, + switches::kMessageLoopHistogrammer, + switches::kEnableDCHECK, + switches::kSilentDumpOnDCHECK, + switches::kDisablePopupBlocking, + switches::kUseLowFragHeapCrt, + switches::kGearsInRenderer, + switches::kEnableUserScripts, + switches::kEnableVideo, + }; + + for (int i = 0; i < arraysize(switch_names); ++i) { + if (browser_command_line.HasSwitch(switch_names[i])) { + CommandLine::AppendSwitchWithValue( + &cmd_line, switch_names[i], + browser_command_line.GetSwitchValue(switch_names[i])); + } + } + + // Pass on the browser locale. + const std::wstring locale = g_browser_process->GetApplicationLocale(); + CommandLine::AppendSwitchWithValue(&cmd_line, switches::kLang, locale); + + bool in_sandbox = !browser_command_line.HasSwitch(switches::kNoSandbox); + if (browser_command_line.HasSwitch(switches::kInProcessPlugins)) { + // In process plugins won't work if the sandbox is enabled. + in_sandbox = false; + } + + bool child_needs_help = + DebugFlags::ProcessDebugFlags(&cmd_line, + DebugFlags::RENDERER, + in_sandbox); + CommandLine::AppendSwitchWithValue(&cmd_line, + switches::kProcessType, + switches::kRendererProcess); + + CommandLine::AppendSwitchWithValue(&cmd_line, + switches::kProcessChannelID, + channel_id); + + const std::wstring& profile_path = + browser_command_line.GetSwitchValue(switches::kUserDataDir); + if (!profile_path.empty()) + CommandLine::AppendSwitchWithValue(&cmd_line, switches::kUserDataDir, + profile_path); + + bool run_in_process = run_renderer_in_process(); + if (run_in_process) { + // Crank up a thread and run the initialization there. With the way that + // messages flow between the browser and renderer, this thread is required + // to prevent a deadlock in single-process mode. When using multiple + // processes, the primordial thread in the renderer process has a message + // loop which is used for sending messages asynchronously to the io thread + // in the browser process. If we don't create this thread, then the + // RenderThread is both responsible for rendering and also for + // communicating IO. This can lead to deadlocks where the RenderThread is + // waiting for the IO to complete, while the browsermain is trying to pass + // an event to the RenderThread. + // + // TODO: We should consider how to better cleanup threads on exit. + base::Thread *render_thread = new RendererMainThread(channel_id); + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + render_thread->StartWithOptions(options); + } else { + if (g_browser_process->local_state() && + g_browser_process->local_state()->GetBoolean( + prefs::kStartRenderersManually)) { + std::wstring message = + L"Please start a renderer process using:\n" + cmd_line; + + // We don't know the owner window for BrowserRenderProcessHost and therefore we + // pass a NULL HWND argument. + win_util::MessageBox(NULL, + message, + switches::kBrowserStartRenderersManually, + MB_OK); + } else { + if (in_sandbox) { + // spawn the child process in the sandbox + sandbox::BrokerServices* broker_service = + g_browser_process->broker_services(); + + sandbox::ResultCode result; + PROCESS_INFORMATION target = {0}; + sandbox::TargetPolicy* policy = broker_service->CreatePolicy(); + policy->SetJobLevel(sandbox::JOB_LOCKDOWN, 0); + + sandbox::TokenLevel initial_token = sandbox::USER_UNPROTECTED; + if (win_util::GetWinVersion() > win_util::WINVERSION_XP) { + // On 2003/Vista the initial token has to be restricted if the main + // token is restricted. + initial_token = sandbox::USER_RESTRICTED_SAME_ACCESS; + } + + policy->SetTokenLevel(initial_token, sandbox::USER_LOCKDOWN); + policy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); + + HDESK desktop = CreateDesktop(kDesktopName, NULL, NULL, 0, + DESKTOP_CREATEWINDOW, NULL); + if (desktop) { + policy->SetDesktop(kDesktopName); + } else { + DLOG(WARNING) << "Failed to apply desktop security to the renderer"; + } + + if (!AddGenericPolicy(policy)) { + NOTREACHED(); + return false; + } + + CommandLine command_line; + if (command_line.HasSwitch(switches::kGearsInRenderer)) { + if (!AddPolicyForGearsInRenderer(policy)) { + NOTREACHED(); + return false; + } + } + + if (!AddDllEvictionPolicy(policy)) { + NOTREACHED(); + return false; + } + + result = broker_service->SpawnTarget(renderer_path.c_str(), + cmd_line.c_str(), + policy, &target); + policy->Release(); + + if (desktop) + CloseDesktop(desktop); + + if (sandbox::SBOX_ALL_OK != result) + return false; + + bool on_sandbox_desktop = (desktop != NULL); + NotificationService::current()->Notify( + NOTIFY_RENDERER_PROCESS_IN_SBOX, Source<BrowserRenderProcessHost>(this), + Details<bool>(&on_sandbox_desktop)); + + ResumeThread(target.hThread); + CloseHandle(target.hThread); + process_.set_handle(target.hProcess); + + // Help the process a little. It can't start the debugger by itself if + // the process is in a sandbox. + if (child_needs_help) + DebugUtil::SpawnDebuggerOnProcess(target.dwProcessId); + } else { + // spawn child process + HANDLE process; + if (!base::LaunchApp(cmd_line, false, false, &process)) + return false; + process_.set_handle(process); + } + + watcher_.StartWatching(process_.handle(), this); + } + } + + // Now that the process is created, set it's backgrounding accordingly. + SetBackgrounded(backgrounded_); + + InitVisitedLinks(); + InitUserScripts(); + + if (max_page_id_ != -1) + channel_->Send(new ViewMsg_SetNextPageID(max_page_id_ + 1)); + + return true; +} + +int BrowserRenderProcessHost::GetNextRoutingID() { + return widget_helper_->GetNextRoutingID(); +} + +void BrowserRenderProcessHost::CancelResourceRequests(int render_widget_id) { + widget_helper_->CancelResourceRequests(render_widget_id); +} + +void BrowserRenderProcessHost::CrossSiteClosePageACK( + int new_render_process_host_id, + int new_request_id) { + widget_helper_->CrossSiteClosePageACK(new_render_process_host_id, + new_request_id); +} + +bool BrowserRenderProcessHost::WaitForPaintMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg) { + return widget_helper_->WaitForPaintMsg(render_widget_id, max_delay, msg); +} + +void BrowserRenderProcessHost::ReceivedBadMessage(uint16 msg_type) { + BadMessageTerminateProcess(msg_type, process_.handle()); +} + +void BrowserRenderProcessHost::WidgetRestored() { + // Verify we were properly backgrounded. + DCHECK(backgrounded_ == (visible_widgets_ == 0)); + visible_widgets_++; + SetBackgrounded(false); +} + +void BrowserRenderProcessHost::WidgetHidden() { + // On startup, the browser will call Hide + if (backgrounded_) + return; + + DCHECK(backgrounded_ == (visible_widgets_ == 0)); + visible_widgets_--; + DCHECK(visible_widgets_ >= 0); + if (visible_widgets_ == 0) { + DCHECK(!backgrounded_); + SetBackgrounded(true); + } +} + +void BrowserRenderProcessHost::AddWord(const std::wstring& word) { + base::Thread* io_thread = g_browser_process->io_thread(); + if (profile()->GetSpellChecker()) { + io_thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + profile()->GetSpellChecker(), &SpellChecker::AddWord, word)); + } +} + +base::ProcessHandle BrowserRenderProcessHost::GetRendererProcessHandle() { + if (run_renderer_in_process()) + return base::Process::Current().handle(); + return process_.handle(); +} + +void BrowserRenderProcessHost::InitVisitedLinks() { + VisitedLinkMaster* visitedlink_master = profile()->GetVisitedLinkMaster(); + if (!visitedlink_master) { + return; + } + + base::SharedMemoryHandle handle_for_process = NULL; + visitedlink_master->ShareToProcess(GetRendererProcessHandle(), + &handle_for_process); + DCHECK(handle_for_process); + if (handle_for_process) { + channel_->Send(new ViewMsg_VisitedLink_NewTable(handle_for_process)); + } +} + +void BrowserRenderProcessHost::InitUserScripts() { + CommandLine command_line; + if (!command_line.HasSwitch(switches::kEnableUserScripts)) { + return; + } + + // TODO(aa): Figure out lifetime and ownership of this object + // - VisitedLinkMaster is owned by Profile, but there has been talk of + // having scripts live elsewhere besides the profile. + // - File IO should be asynchronous (see VisitedLinkMaster), but how do we + // get scripts to the first renderer without blocking startup? Should we + // cache some information across restarts? + UserScriptMaster* user_script_master = profile()->GetUserScriptMaster(); + if (!user_script_master) { + return; + } + + if (!user_script_master->ScriptsReady()) { + // No scripts ready. :( + return; + } + + // Update the renderer process with the current scripts. + SendUserScriptsUpdate(user_script_master->GetSharedMemory()); +} + +void BrowserRenderProcessHost::SendUserScriptsUpdate( + base::SharedMemory *shared_memory) { + base::SharedMemoryHandle handle_for_process = NULL; + shared_memory->ShareToProcess(GetRendererProcessHandle(), + &handle_for_process); + DCHECK(handle_for_process); + if (handle_for_process) { + channel_->Send(new ViewMsg_UserScripts_NewScripts(handle_for_process)); + } +} + +bool BrowserRenderProcessHost::FastShutdownIfPossible() { + if (!process_.handle()) + return false; // Render process is probably crashed. + if (BrowserRenderProcessHost::run_renderer_in_process()) + return false; // Since process mode can't do fast shutdown. + + // Test if there's an unload listener + BrowserRenderProcessHost::listeners_iterator iter; + // NOTE: This is a bit dangerous. We know that for now, listeners are + // always RenderWidgetHosts. But in theory, they don't have to be. + for (iter = listeners_begin(); iter != listeners_end(); ++iter) { + RenderWidgetHost* widget = static_cast<RenderWidgetHost*>(iter->second); + DCHECK(widget); + if (!widget || !widget->IsRenderView()) + continue; + RenderViewHost* rvh = static_cast<RenderViewHost*>(widget); + if (!rvh->CanTerminate()) { + // NOTE: It's possible that an onunload listener may be installed + // while we're shutting down, so there's a small race here. Given that + // the window is small, it's unlikely that the web page has much + // state that will be lost by not calling its unload handlers properly. + return false; + } + } + + // Otherwise, we're allowed to just terminate the process. Using exit code 0 + // means that UMA won't treat this as a renderer crash. + process_.Terminate(ResultCodes::NORMAL_EXIT); + return true; +} + +bool BrowserRenderProcessHost::Send(IPC::Message* msg) { + if (!channel_.get()) { + delete msg; + return false; + } + return channel_->Send(msg); +} + +void BrowserRenderProcessHost::OnMessageReceived(const IPC::Message& msg) { + if (msg.routing_id() == MSG_ROUTING_CONTROL) { + // dispatch control messages + bool msg_is_ok = true; + IPC_BEGIN_MESSAGE_MAP_EX(BrowserRenderProcessHost, msg, msg_is_ok) + IPC_MESSAGE_HANDLER(ViewHostMsg_PageContents, OnPageContents) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdatedCacheStats, + OnUpdatedCacheStats) + IPC_MESSAGE_UNHANDLED_ERROR() + IPC_END_MESSAGE_MAP_EX() + + if (!msg_is_ok) { + // The message had a handler, but its de-serialization failed. + // We consider this a capital crime. Kill the renderer if we have one. + ReceivedBadMessage(msg.type()); + } + return; + } + + // dispatch incoming messages to the appropriate TabContents + IPC::Channel::Listener* listener = GetListenerByID(msg.routing_id()); + if (!listener) { + if (msg.is_sync()) { + // The listener has gone away, so we must respond or else the caller will + // hang waiting for a reply. + IPC::Message* reply = IPC::SyncMessage::GenerateReply(&msg); + reply->set_reply_error(); + Send(reply); + } + return; + } + listener->OnMessageReceived(msg); +} + +void BrowserRenderProcessHost::OnChannelConnected(int32 peer_pid) { + // process_ is not NULL if we created the renderer process + if (!process_.handle()) { + if (GetCurrentProcessId() == peer_pid) { + // We are in single-process mode. In theory we should have access to + // ourself but it may happen that we don't. + process_.set_handle(GetCurrentProcess()); + } else { + // Request MAXIMUM_ALLOWED to match the access a handle + // returned by CreateProcess() has to the process object. + process_.set_handle(OpenProcess(MAXIMUM_ALLOWED, FALSE, peer_pid)); + DCHECK(process_.handle()); + watcher_.StartWatching(process_.handle(), this); + } + } else { + // Need to verify that the peer_pid is actually the process we know, if + // it is not, we need to panic now. See bug 1002150. + CHECK(peer_pid == process_.pid()); + } +} + +// Static. This function can be called from the IO Thread or from the UI thread. +void BrowserRenderProcessHost::BadMessageTerminateProcess(uint16 msg_type, + HANDLE process) { + LOG(ERROR) << "bad message " << msg_type << " terminating renderer."; + if (BrowserRenderProcessHost::run_renderer_in_process()) { + // In single process mode it is better if we don't suicide but just crash. + CHECK(false); + } + NOTREACHED(); + ::TerminateProcess(process, ResultCodes::KILLED_BAD_MESSAGE); +} + +// indicates the renderer process has exited +void BrowserRenderProcessHost::OnObjectSignaled(HANDLE object) { + DCHECK(process_.handle()); + DCHECK(channel_.get()); + DCHECK_EQ(object, process_.handle()); + + bool clean_shutdown = !base::DidProcessCrash(object); + + process_.Close(); + + channel_.reset(); + + if (!notified_termination_) { + // If |close_expected| is false, it means the renderer process went away + // before the web views expected it; count it as a crash. + NotificationService::current()->Notify(NOTIFY_RENDERER_PROCESS_TERMINATED, + Source<RenderProcessHost>(this), + Details<bool>(&clean_shutdown)); + notified_termination_ = true; + } + + // This process should detach all the listeners, causing the object to be + // deleted. We therefore need a stack copy of the web view list to avoid + // crashing when checking for the termination condition the last time. + IDMap<IPC::Channel::Listener> local_listeners(listeners_); + for (IDMap<IPC::Channel::Listener>::const_iterator i = local_listeners.begin(); + i != local_listeners.end(); ++i) { + i->second->OnMessageReceived(ViewHostMsg_RendererGone(i->first)); + } + // at this point, this object should be deleted +} + +void BrowserRenderProcessHost::Unregister() { + // RenderProcessHost::Unregister will clean up the host_id_, so we must + // do our cleanup that uses that variable before we call it. + if (host_id() >= 0) { + CacheManagerHost::GetInstance()->Remove(host_id()); + RendererSecurityPolicy::GetInstance()->Remove(host_id()); + } + + RenderProcessHost::Unregister(); +} + +void BrowserRenderProcessHost::OnPageContents(const GURL& url, + int32 page_id, + const std::wstring& contents) { + Profile* p = profile(); + if (!p || p->IsOffTheRecord()) + return; + + HistoryService* hs = p->GetHistoryService(Profile::IMPLICIT_ACCESS); + if (hs) + hs->SetPageContents(url, contents); +} + +void BrowserRenderProcessHost::OnUpdatedCacheStats( + const CacheManager::UsageStats& stats) { + CacheManagerHost::GetInstance()->ObserveStats(host_id(), stats); +} + +void BrowserRenderProcessHost::SetBackgrounded(bool backgrounded) { + // If the process_ is NULL, the process hasn't been created yet. + if (process_.handle()) { + bool rv = process_.SetProcessBackgrounded(backgrounded); + if (!rv) { + return; + } + + // Now tune the memory footprint of the renderer. + // If the OS needs to page, we'd rather it page idle renderers. + BrowserProcess::MemoryModel model = g_browser_process->memory_model(); + if (model < BrowserProcess::HIGH_MEMORY_MODEL) { + if (backgrounded) { + if (model == BrowserProcess::LOW_MEMORY_MODEL) + process_.EmptyWorkingSet(); + else if (model == BrowserProcess::MEDIUM_MEMORY_MODEL) + process_.ReduceWorkingSet(); + } else { + if (model == BrowserProcess::MEDIUM_MEMORY_MODEL) + process_.UnReduceWorkingSet(); + } + } + } + + // Note: we always set the backgrounded_ value. If the process is NULL + // (and hence hasn't been created yet), we will set the process priority + // later when we create the process. + backgrounded_ = backgrounded; +} + +// NotificationObserver implementation. +void BrowserRenderProcessHost::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type) { + case NOTIFY_PREF_CHANGED: { + std::wstring* pref_name_in = Details<std::wstring>(details).ptr(); + DCHECK(Source<PrefService>(source).ptr() == profile()->GetPrefs()); + if (*pref_name_in == prefs::kBlockPopups) { + widget_helper_->set_block_popups( + profile()->GetPrefs()->GetBoolean(prefs::kBlockPopups)); + } else { + NOTREACHED() << "unexpected pref change notification" << *pref_name_in; + } + break; + } + case NOTIFY_USER_SCRIPTS_LOADED: { + base::SharedMemory* shared_memory = + Details<base::SharedMemory>(details).ptr(); + DCHECK(shared_memory); + if (shared_memory) { + SendUserScriptsUpdate(shared_memory); + } + break; + } + default: { + NOTREACHED(); + break; + } + } +} diff --git a/chrome/browser/renderer_host/browser_render_process_host.h b/chrome/browser/renderer_host/browser_render_process_host.h new file mode 100644 index 0000000..2f766df --- /dev/null +++ b/chrome/browser/renderer_host/browser_render_process_host.h @@ -0,0 +1,160 @@ +// 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. + +#ifndef CHROME_BROWSER_RENDERER_HOST_BROWSER_RENDER_PROCESS_HOST_H_ +#define CHROME_BROWSER_RENDERER_HOST_BROWSER_RENDER_PROCESS_HOST_H_ + +#include <limits> +#include <set> +#include <vector> +#include <windows.h> + +#include "base/id_map.h" +#include "base/object_watcher.h" +#include "base/rand_util.h" +#include "base/ref_counted.h" +#include "base/scoped_handle.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages.h" + +class PrefService; +class RenderWidgetHelper; +class WebContents; + +namespace base { +class Thread; +} + +// Implements a concrete RenderProcessHost for the browser process for talking +// to actual renderer processes (as opposed to mocks). +// +// Represents the browser side of the browser <--> renderer communication +// channel. There will be one RenderProcessHost per renderer process. +// +// This object is refcounted so that it can release its resources when all +// hosts using it go away. +// +// This object communicates back and forth with the RenderProcess object +// running in the renderer process. Each RenderProcessHost and RenderProcess +// keeps a list of RenderView (renderer) and WebContents (browser) which +// are correlated with IDs. This way, the Views and the corresponding ViewHosts +// communicate through the two process objects. +class BrowserRenderProcessHost : public RenderProcessHost, + public base::ObjectWatcher::Delegate, + public NotificationObserver { + public: + explicit BrowserRenderProcessHost(Profile* profile); + ~BrowserRenderProcessHost(); + + // RenderProcessHost implementation (public portion). + virtual bool Init(); + virtual int GetNextRoutingID(); + virtual void CancelResourceRequests(int render_widget_id); + virtual void CrossSiteClosePageACK(int new_render_process_host_id, + int new_request_id); + virtual bool WaitForPaintMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg); + virtual void ReceivedBadMessage(uint16 msg_type); + virtual void WidgetRestored(); + virtual void WidgetHidden(); + virtual void AddWord(const std::wstring& word); + virtual bool FastShutdownIfPossible(); + + // IPC::Channel::Sender via RenderProcessHost. + virtual bool Send(IPC::Message* msg); + + // IPC::Channel::Listener via RenderProcessHost. + virtual void OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelConnected(int32 peer_pid); + + static void RegisterPrefs(PrefService* prefs); + + // If the a process has sent a message that cannot be decoded, it is deemed + // corrupted and thus needs to be terminated using this call. This function + // can be safely called from any thread. + static void BadMessageTerminateProcess(uint16 msg_type, HANDLE renderer); + + // ObjectWatcher::Delegate + virtual void OnObjectSignaled(HANDLE object); + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + // RenderProcessHost implementation (protected portion). + virtual void Unregister(); + + // Control message handlers. + void OnPageContents(const GURL& url, int32 page_id, + const std::wstring& contents); + // Clipboard messages + void OnClipboardWriteHTML(const std::wstring& markup, const GURL& src_url); + void OnClipboardWriteBookmark(const std::wstring& title, const GURL& url); + void OnClipboardWriteBitmap(base::SharedMemoryHandle bitmap, gfx::Size size); + void OnClipboardIsFormatAvailable(unsigned int format, bool* result); + void OnClipboardReadText(std::wstring* result); + void OnClipboardReadAsciiText(std::string* result); + void OnClipboardReadHTML(std::wstring* markup, GURL* src_url); + void OnUpdatedCacheStats(const CacheManager::UsageStats& stats); + + // Initialize support for visited links. Send the renderer process its initial + // set of visited links. + void InitVisitedLinks(); + + // Initialize support for user scripts. Send the renderer process its initial + // set of scripts and listen for updates to scripts. + void InitUserScripts(); + + // Sends the renderer process a new set of user scripts. + void SendUserScriptsUpdate(base::SharedMemory* shared_memory); + + // Gets a handle to the renderer process, normalizing the case where we were + // started with --single-process. + base::ProcessHandle GetRendererProcessHandle(); + + // Callers can reduce the RenderProcess' priority. + // Returns true if the priority is backgrounded; false otherwise. + void SetBackgrounded(bool boost); + + // Used to watch the renderer process handle. + base::ObjectWatcher watcher_; + + // The count of currently visible widgets. Since the host can be a container + // for multiple widgets, it uses this count to determine when it should be + // backgrounded. + int32 visible_widgets_; + + // Does this process have backgrounded priority. + bool backgrounded_; + + // Used to allow a RenderWidgetHost to intercept various messages on the + // IO thread. + scoped_refptr<RenderWidgetHelper> widget_helper_; + + DISALLOW_COPY_AND_ASSIGN(BrowserRenderProcessHost); +}; + +// Generates a unique channel name for a child renderer/plugin process. +// The "instance" pointer value is baked into the channel id. +inline std::wstring GenerateRandomChannelID(void* instance) { + // Note: the string must start with the current process id, this is how + // child processes determine the pid of the parent. + // Build the channel ID. This is composed of a unique identifier for the + // parent browser process, an identifier for the renderer/plugin instance, + // and a random component. We use a random component so that a hacked child + // process can't cause denial of service by causing future named pipe creation + // to fail. + return StringPrintf(L"%d.%x.%d", + GetCurrentProcessId(), instance, + base::RandInt(0, std::numeric_limits<int>::max())); +} + + +#endif // CHROME_BROWSER_RENDERER_HOST_BROWSER_RENDER_PROCESS_HOST_H_ + diff --git a/chrome/browser/renderer_host/render_process_host.cc b/chrome/browser/renderer_host/render_process_host.cc new file mode 100644 index 0000000..18323f4 --- /dev/null +++ b/chrome/browser/renderer_host/render_process_host.cc @@ -0,0 +1,159 @@ +// 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 "chrome/browser/renderer_host/render_process_host.h" + +#include "base/rand_util.h" +#include "base/sys_info.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/notification_service.h" + +namespace { + +unsigned int GetMaxRendererProcessCount() { + // Defines the maximum number of renderer processes according to the amount + // of installed memory as reported by the OS. The table values are calculated + // by assuming that you want the renderers to use half of the installed ram + // and assuming that each tab uses ~25MB. + static const int kMaxRenderersByRamTier[] = { + 4, // less than 256MB + 8, // 256MB + 12, // 512MB + 16, // 768MB + }; + + static unsigned int max_count = 0; + if (!max_count) { + int memory_tier = base::SysInfo::AmountOfPhysicalMemoryMB() / 256; + if (memory_tier >= arraysize(kMaxRenderersByRamTier)) + max_count = chrome::kMaxRendererProcessCount; + else + max_count = kMaxRenderersByRamTier[memory_tier]; + } + return max_count; +} + +// Returns true if the given host is suitable for launching a new view +// associated with the given profile. +// TODO(jabdelmalek): do we want to avoid processes with hung renderers +// or with a large memory consumption? +static bool IsSuitableHost(Profile* profile, RenderProcessHost* host) { + return host->profile() == profile; +} + +// the global list of all renderer processes +IDMap<RenderProcessHost> all_hosts; + +} // namespace + +bool RenderProcessHost::run_renderer_in_process_ = false; + +RenderProcessHost::RenderProcessHost(Profile* profile) + : max_page_id_(-1), + notified_termination_(false), + profile_(profile) { + host_id_ = all_hosts.Add(this); +} + +RenderProcessHost::~RenderProcessHost() { +} + +void RenderProcessHost::Attach(IPC::Channel::Listener* listener, + int routing_id) { + listeners_.AddWithID(listener, routing_id); +} + +void RenderProcessHost::Release(int listener_id) { + DCHECK(listeners_.Lookup(listener_id) != NULL); + listeners_.Remove(listener_id); + + // Make sure that all associated resource requests are stopped. + CancelResourceRequests(listener_id); + + // When no other owners of this object, we can delete ourselves + if (listeners_.IsEmpty()) { + if (!notified_termination_) { + bool close_expected = true; + NotificationService::current()->Notify( + NOTIFY_RENDERER_PROCESS_TERMINATED, + Source<RenderProcessHost>(this), + Details<bool>(&close_expected)); + notified_termination_ = true; + } + Unregister(); + MessageLoop::current()->DeleteSoon(FROM_HERE, this); + } +} + +void RenderProcessHost::ReportExpectingClose(int32 listener_id) { + listeners_expecting_close_.insert(listener_id); +} + +void RenderProcessHost::UpdateMaxPageID(int32 page_id) { + if (page_id > max_page_id_) + max_page_id_ = page_id; +} + +// static +RenderProcessHost::iterator RenderProcessHost::begin() { + return all_hosts.begin(); +} + +// static +RenderProcessHost::iterator RenderProcessHost::end() { + return all_hosts.end(); +} + +// static +size_t RenderProcessHost::size() { + return all_hosts.size(); +} + +// static +RenderProcessHost* RenderProcessHost::FromID(int render_process_id) { + return all_hosts.Lookup(render_process_id); +} + +// static +bool RenderProcessHost::ShouldTryToUseExistingProcessHost() { + unsigned int renderer_process_count = + static_cast<unsigned int>(all_hosts.size()); + + // NOTE: Sometimes it's necessary to create more render processes than + // GetMaxRendererProcessCount(), for instance when we want to create + // a renderer process for a profile that has no existing renderers. + // This is OK in moderation, since the GetMaxRendererProcessCount() + // is conservative. + + return run_renderer_in_process() || + (renderer_process_count >= GetMaxRendererProcessCount()); +} + +// static +RenderProcessHost* RenderProcessHost::GetExistingProcessHost(Profile* profile) { + // First figure out which existing renderers we can use. + std::vector<RenderProcessHost*> suitable_renderers; + suitable_renderers.reserve(size()); + + for (iterator iter = begin(); iter != end(); ++iter) { + if (IsSuitableHost(profile, iter->second)) + suitable_renderers.push_back(iter->second); + } + + // Now pick a random suitable renderer, if we have any. + if (!suitable_renderers.empty()) { + int suitable_count = static_cast<int>(suitable_renderers.size()); + int random_index = base::RandInt(0, suitable_count - 1); + return suitable_renderers[random_index]; + } + + return NULL; +} + +void RenderProcessHost::Unregister() { + if (host_id_ >= 0) { + all_hosts.Remove(host_id_); + host_id_ = -1; + } +} diff --git a/chrome/browser/renderer_host/render_process_host.h b/chrome/browser/renderer_host/render_process_host.h new file mode 100644 index 0000000..46e1353 --- /dev/null +++ b/chrome/browser/renderer_host/render_process_host.h @@ -0,0 +1,203 @@ +// 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 CHROME_BROWSER_RENDERER_HOST_RENDER_PROCESS_HOST_H_ +#define CHROME_BROWSER_RENDERER_HOST_RENDER_PROCESS_HOST_H_ + +#include "base/basictypes.h" +#include "base/id_map.h" +#include "base/process.h" +#include "base/scoped_ptr.h" +#include "chrome/common/ipc_sync_channel.h" + +class Profile; + +// Virtual interface that represents the browser side of the browser <-> +// renderer communication channel. There will generally be one +// RenderProcessHost per renderer process. +// +// The concrete implementation of this class for normal use is the +// BrowserRenderProcessHost. It may also be implemented by a testing interface +// for mocking purposes. +class RenderProcessHost : public IPC::Channel::Sender, + public IPC::Channel::Listener { + public: + typedef IDMap<RenderProcessHost>::const_iterator iterator; + + RenderProcessHost(Profile* profile); + virtual ~RenderProcessHost(); + + // Returns the user profile associated with this renderer process. + Profile* profile() const { return profile_; } + + // Returns the unique identifier for this host. This can be used later in + // a call to FromID() to get back to this object (this is used to avoid + // sending non-threadsafe pointers to other threads). + int host_id() const { return host_id_; } + + // Returns the process object associated with the child process. In certain + // tests or single-process mode, this will actually represent the current + // process. + const base::Process& process() const { return process_; } + + // May return NULL if there is no connection. + IPC::SyncChannel* channel() { return channel_.get(); } + + // Used for refcounting, each holder of this object must Attach and Release + // just like it would for a COM object. This object should be allocated on + // the heap; when no listeners own it any more, it will delete itself. + void Attach(IPC::Channel::Listener* listener, int routing_id); + + // See Attach() + void Release(int listener_id); + + // Listeners should call this when they've sent a "Close" message and + // they're waiting for a "Close_ACK", so that if the renderer process + // goes away we'll know that it was intentional rather than a crash. + void ReportExpectingClose(int32 listener_id); + + // Allows iteration over this RenderProcessHost's RenderViewHost listeners. + // Use from UI thread only. + typedef IDMap<IPC::Channel::Listener>::const_iterator listeners_iterator; + listeners_iterator listeners_begin() { + return listeners_.begin(); + } + listeners_iterator listeners_end() { + return listeners_.end(); + } + + IPC::Channel::Listener* GetListenerByID(int routing_id) { + return listeners_.Lookup(routing_id); + } + + // Called to inform the render process host of a new "max page id" for a + // render view host. The render process host computes the largest page id + // across all render view hosts and uses the value when it needs to + // initialize a new renderer in place of the current one. + void UpdateMaxPageID(int32 page_id); + + // Virtual interface --------------------------------------------------------- + + // Initialize the new renderer process, returning true on success. This must + // be called once before the object can be used, but can be called after + // that with no effect. Therefore, if the caller isn't sure about whether + // the process has been created, it should just call Init(). + virtual bool Init() = 0; + + // Gets the next available routing id. + virtual int GetNextRoutingID() = 0; + + // Called on the UI thread to cancel any outstanding resource requests for + // the specified render widget. + virtual void CancelResourceRequests(int render_widget_id) = 0; + + // Called on the UI thread to simulate a ClosePage_ACK message to the + // ResourceDispatcherHost. Necessary for a cross-site request, in the case + // that the original RenderViewHost is not live and thus cannot run an + // onunload handler. + virtual void CrossSiteClosePageACK(int new_render_process_host_id, + int new_request_id) = 0; + + // Called on the UI thread to wait for the next PaintRect message for the + // specified render widget. Returns true if successful, and the msg out- + // param will contain a copy of the received PaintRect message. + virtual bool WaitForPaintMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg) = 0; + + // Called when a received message cannot be decoded. + virtual void ReceivedBadMessage(uint16 msg_type) = 0; + + // Track the count of visible widgets. Called by listeners to register and + // unregister visibility. + virtual void WidgetRestored() = 0; + virtual void WidgetHidden() = 0; + + // Add a word in the spellchecker. + virtual void AddWord(const std::wstring& word) = 0; + + // Try to shutdown the associated renderer process as fast as possible. + // If this renderer has any RenderViews with unload handlers, then this + // function does nothing. The current implementation uses TerminateProcess. + // Returns True if it was able to do fast shutdown. + virtual bool FastShutdownIfPossible() = 0; + + // Static management functions ----------------------------------------------- + + // Flag to run the renderer in process. This is primarily + // for debugging purposes. When running "in process", the + // browser maintains a single RenderProcessHost which communicates + // to a RenderProcess which is instantiated in the same process + // with the Browser. All IPC between the Browser and the + // Renderer is the same, it's just not crossing a process boundary. + static bool run_renderer_in_process() { + return run_renderer_in_process_; + } + static void set_run_renderer_in_process(bool value) { + run_renderer_in_process_ = value; + } + + // Allows iteration over all the RenderProcessHosts in the browser. Note + // that each host may not be active, and therefore may have NULL channels. + // This is just a standard STL iterator, so it is not valid if the list + // of RenderProcessHosts changes between iterations. + static iterator begin(); + static iterator end(); + static size_t size(); // TODO(brettw) rename this, it's very unclear. + + // Returns the RenderProcessHost given its ID. Returns NULL if the ID does + // not correspond to a live RenderProcessHost. + static RenderProcessHost* FromID(int render_process_id); + + // Returns true if the caller should attempt to use an existing + // RenderProcessHost rather than creating a new one. + static bool ShouldTryToUseExistingProcessHost(); + + // Get an existing RenderProcessHost associated with the given profile, if + // possible. The renderer process is chosen randomly from the + // processes associated with the given profile. + // Returns NULL if no suitable renderer process is available. + static RenderProcessHost* GetExistingProcessHost(Profile* profile); + + protected: + // Unregister this object from all globals that reference it. + // This would naturally be part of the destructor, but we destruct + // asynchronously. + // + // This can be overridden by derived classes to add their own unregister code, + // but you should be sure to always call this one AT THE END of your own. + virtual void Unregister(); + + base::Process process_; + + // A proxy for our IPC::Channel that lives on the IO thread (see + // browser_process.h) + scoped_ptr<IPC::SyncChannel> channel_; + + // the registered listeners. When this list is empty or all NULL, we should + // delete ourselves + IDMap<IPC::Channel::Listener> listeners_; + + // The maximum page ID we've ever seen from the renderer process. + int32 max_page_id_; + + // Indicates whether we have notified that the process has terminated. We + // only want to send this out once. + bool notified_termination_; + + private: + int host_id_; + + Profile* profile_; + + // set of listeners that expect the renderer process to close + std::set<int> listeners_expecting_close_; + + // See getter above. + static bool run_renderer_in_process_; + + DISALLOW_COPY_AND_ASSIGN(RenderProcessHost); +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_RENDER_PROCESS_HOST_H_ |