diff options
author | Ben Murdoch <benm@google.com> | 2010-07-29 17:14:53 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-08-04 14:29:45 +0100 |
commit | c407dc5cd9bdc5668497f21b26b09d988ab439de (patch) | |
tree | 7eaf8707c0309516bdb042ad976feedaf72b0bb1 /chrome/browser/renderer_host/browser_render_process_host.cc | |
parent | 0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff) | |
download | external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2 |
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'chrome/browser/renderer_host/browser_render_process_host.cc')
-rw-r--r-- | chrome/browser/renderer_host/browser_render_process_host.cc | 1079 |
1 files changed, 1079 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..d6509e8 --- /dev/null +++ b/chrome/browser/renderer_host/browser_render_process_host.cc @@ -0,0 +1,1079 @@ +// 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. + +// Represents the browser side of the browser <--> renderer communication +// channel. There will be one RenderProcessHost per renderer process. + +#include "chrome/browser/renderer_host/browser_render_process_host.h" + +#include <algorithm> +#include <limits> +#include <vector> + +#if defined(OS_POSIX) +#include <utility> // for pair<> +#endif + +#include "app/app_switches.h" +#include "base/command_line.h" +#include "base/field_trial.h" +#include "base/logging.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "base/thread.h" +#include "chrome/browser/browser_child_process_host.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/browser/extensions/extension_function_dispatcher.h" +#include "chrome/browser/extensions/extension_message_service.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/extensions/user_script_master.h" +#include "chrome/browser/gpu_process_host.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/io_thread.h" +#include "chrome/browser/plugin_service.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/renderer_host/audio_renderer_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/browser/renderer_host/render_widget_helper.h" +#include "chrome/browser/renderer_host/render_widget_host.h" +#include "chrome/browser/renderer_host/resource_message_filter.h" +#include "chrome/browser/renderer_host/web_cache_manager.h" +#include "chrome/browser/spellcheck_host.h" +#include "chrome/browser/visitedlink_master.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/child_process_info.h" +#include "chrome/common/gpu_messages.h" +#include "chrome/common/logging_chrome.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/process_watcher.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/result_codes.h" +#include "chrome/renderer/render_process_impl.h" +#include "chrome/renderer/render_thread.h" +#include "grit/generated_resources.h" +#include "ipc/ipc_logging.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_platform_file.h" +#include "ipc/ipc_switches.h" +#include "media/base/media_switches.h" +#include "webkit/glue/plugins/plugin_switches.h" + +#if defined(OS_WIN) +#include "app/win_util.h" +#endif + +using WebKit::WebCache; + +#include "third_party/skia/include/core/SkBitmap.h" + + +// This class creates the IO thread for the renderer when running in +// single-process mode. It's not used in multi-process mode. +class RendererMainThread : public base::Thread { + public: + explicit RendererMainThread(const std::string& channel_id) + : base::Thread("Chrome_InProcRendererThread"), + channel_id_(channel_id), + render_process_(NULL) { + } + + ~RendererMainThread() { + Stop(); + } + + protected: + virtual void Init() { +#if defined(OS_WIN) + CoInitialize(NULL); +#endif + + render_process_ = new RenderProcessImpl(); + render_process_->set_main_thread(new RenderThread(channel_id_)); + // 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() { + delete render_process_; + +#if defined(OS_WIN) + CoUninitialize(); +#endif + } + + private: + std::string channel_id_; + // Deleted in CleanUp() on the renderer thread, so don't use a smart pointer. + RenderProcess* render_process_; +}; + + +// Size of the buffer after which individual link updates deemed not warranted +// and the overall update should be used instead. +static const unsigned kVisitedLinkBufferThreshold = 50; + +// This class manages buffering and sending visited link hashes (fingerprints) +// to renderer based on widget visibility. +// As opposed to the VisitedLinkEventListener in profile.cc, which coalesces to +// reduce the rate of messages being sent to render processes, this class +// ensures that the updates occur only when explicitly requested. This is +// used by BrowserRenderProcessHost to only send Add/Reset link events to the +// renderers when their tabs are visible and the corresponding RenderViews are +// created. +class VisitedLinkUpdater { + public: + VisitedLinkUpdater() : reset_needed_(false), has_receiver_(false) {} + + // Buffers |links| to update, but doesn't actually relay them. + void AddLinks(const VisitedLinkCommon::Fingerprints& links) { + if (reset_needed_) + return; + + if (pending_.size() + links.size() > kVisitedLinkBufferThreshold) { + // Once the threshold is reached, there's no need to store pending visited + // link updates -- we opt for resetting the state for all links. + AddReset(); + return; + } + + pending_.insert(pending_.end(), links.begin(), links.end()); + } + + // Tells the updater that sending individual link updates is no longer + // necessary and the visited state for all links should be reset. + void AddReset() { + reset_needed_ = true; + pending_.clear(); + } + + // Sends visited link update messages: a list of links whose visited state + // changed or reset of visited state for all links. + void Update(IPC::Channel::Sender* sender) { + DCHECK(sender); + + if (!has_receiver_) + return; + + if (reset_needed_) { + sender->Send(new ViewMsg_VisitedLink_Reset()); + reset_needed_ = false; + return; + } + + if (pending_.size() == 0) + return; + + sender->Send(new ViewMsg_VisitedLink_Add(pending_)); + + pending_.clear(); + } + + // Notifies the updater that it is now safe to send visited state updates. + void ReceiverReady(IPC::Channel::Sender* sender) { + has_receiver_ = true; + // Go ahead and send whatever we already have buffered up. + Update(sender); + } + + private: + bool reset_needed_; + bool has_receiver_; + VisitedLinkCommon::Fingerprints pending_; +}; + +BrowserRenderProcessHost::BrowserRenderProcessHost(Profile* profile) + : RenderProcessHost(profile), + visible_widgets_(0), + backgrounded_(true), + ALLOW_THIS_IN_INITIALIZER_LIST(cached_dibs_cleaner_( + base::TimeDelta::FromSeconds(5), + this, &BrowserRenderProcessHost::ClearTransportDIBCache)), + extension_process_(false) { + widget_helper_ = new RenderWidgetHelper(); + + registrar_.Add(this, NotificationType::USER_SCRIPTS_UPDATED, + Source<Profile>(profile->GetOriginalProfile())); + registrar_.Add(this, NotificationType::EXTENSION_LOADED, + Source<Profile>(profile->GetOriginalProfile())); + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, + Source<Profile>(profile->GetOriginalProfile())); + registrar_.Add(this, NotificationType::SPELLCHECK_HOST_REINITIALIZED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::SPELLCHECK_WORD_ADDED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::SPELLCHECK_AUTOSPELL_TOGGLED, + NotificationService::AllSources()); + + visited_link_updater_.reset(new VisitedLinkUpdater()); + + WebCacheManager::GetInstance()->Add(id()); + ChildProcessSecurityPolicy::GetInstance()->Add(id()); + + // 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() { + WebCacheManager::GetInstance()->Remove(id()); + ChildProcessSecurityPolicy::GetInstance()->Remove(id()); + + // We may have some unsent messages at this point, but that's OK. + channel_.reset(); + while (!queued_messages_.empty()) { + delete queued_messages_.front(); + queued_messages_.pop(); + } + + // Destroy the AudioRendererHost properly. + if (audio_renderer_host_.get()) + audio_renderer_host_->Destroy(); + + ClearTransportDIBCache(); + + NotificationService::current()->Notify( + NotificationType::EXTENSION_PORT_DELETED_DEBUG, + Source<IPC::Message::Sender>(this), + NotificationService::NoDetails()); +} + +bool BrowserRenderProcessHost::Init(bool is_extensions_process, + URLRequestContextGetter* request_context) { + // 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; + + // It is possible for an extension process to be reused for non-extension + // content, e.g. if an extension calls window.open. + extension_process_ = extension_process_ || is_extensions_process; + + // run the IPC channel on the shared IO thread. + base::Thread* io_thread = g_browser_process->io_thread(); + + // Construct the AudioRendererHost with the IO thread. + audio_renderer_host_ = new AudioRendererHost(); + + scoped_refptr<ResourceMessageFilter> resource_message_filter = + new ResourceMessageFilter(g_browser_process->resource_dispatcher_host(), + id(), + audio_renderer_host_.get(), + PluginService::GetInstance(), + g_browser_process->print_job_manager(), + profile(), + widget_helper_, + request_context); + + std::wstring renderer_prefix; +#if defined(OS_POSIX) + // A command prefix is something prepended to the command line of the spawned + // process. It is supported only on POSIX systems. + const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); + renderer_prefix = + browser_command_line.GetSwitchValue(switches::kRendererCmdPrefix); +#endif // defined(OS_POSIX) + + // Find the renderer before creating the channel so if this fails early we + // return without creating the channel. + FilePath renderer_path = ChildProcessHost::GetChildPath( + renderer_prefix.empty()); + if (renderer_path.empty()) + return false; + + // Setup the IPC channel. + const std::string channel_id = + ChildProcessInfo::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); + + if (run_renderer_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. Since the primordial + // thread in the renderer process runs the WebKit code and can sometimes + // make blocking calls to the UI thread (i.e. this thread), they need to run + // on separate threads. + in_process_renderer_.reset(new RendererMainThread(channel_id)); + + base::Thread::Options options; +#if !defined(OS_LINUX) + // In-process plugins require this to be a UI message loop. + options.message_loop_type = MessageLoop::TYPE_UI; +#else + // We can't have multiple UI loops on Linux, so we don't support + // in-process plugins. + options.message_loop_type = MessageLoop::TYPE_DEFAULT; +#endif + in_process_renderer_->StartWithOptions(options); + + OnProcessLaunched(); // Fake a callback that the process is ready. + } else { + // Build command line for renderer. We call AppendRendererCommandLine() + // first so the process type argument will appear first. + CommandLine* cmd_line = new CommandLine(renderer_path); + if (!renderer_prefix.empty()) + cmd_line->PrependWrapper(renderer_prefix); + AppendRendererCommandLine(cmd_line); + cmd_line->AppendSwitchWithValue(switches::kProcessChannelID, + ASCIIToWide(channel_id)); + + // Spawn the child process asynchronously to avoid blocking the UI thread. + // As long as there's no renderer prefix, we can use the zygote process + // at this stage. + child_process_.reset(new ChildProcessLauncher( +#if defined(OS_WIN) + FilePath(), +#elif defined(POSIX) + renderer_prefix.empty(), + base::environment_vector(), + channel_->GetClientFileDescriptor(), +#endif + cmd_line, + this)); + + fast_shutdown_started_ = false; + } + + 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( + const ViewMsg_ClosePage_Params& params) { + widget_helper_->CrossSiteClosePageACK(params); +} + +bool BrowserRenderProcessHost::WaitForUpdateMsg( + int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg) { + // The post task to this thread with the process id could be in queue, and we + // don't want to dispatch a message before then since it will need the handle. + if (child_process_.get() && child_process_->IsStarting()) + return false; + + return widget_helper_->WaitForUpdateMsg(render_widget_id, max_delay, msg); +} + +void BrowserRenderProcessHost::ReceivedBadMessage(uint32 msg_type) { + BadMessageTerminateProcess(msg_type, GetHandle()); +} + +void BrowserRenderProcessHost::ViewCreated() { + visited_link_updater_->ReceiverReady(this); +} + +void BrowserRenderProcessHost::WidgetRestored() { + // Verify we were properly backgrounded. + DCHECK_EQ(backgrounded_, (visible_widgets_ == 0)); + visible_widgets_++; + visited_link_updater_->Update(this); + SetBackgrounded(false); +} + +void BrowserRenderProcessHost::WidgetHidden() { + // On startup, the browser will call Hide + if (backgrounded_) + return; + + DCHECK_EQ(backgrounded_, (visible_widgets_ == 0)); + visible_widgets_--; + DCHECK_GE(visible_widgets_, 0); + if (visible_widgets_ == 0) { + DCHECK(!backgrounded_); + SetBackgrounded(true); + } +} + +void BrowserRenderProcessHost::SendVisitedLinkTable( + base::SharedMemory* table_memory) { + // Check if the process is still starting and we don't have a handle for it + // yet, in which case this will happen later when InitVisitedLinks is called. + if (!run_renderer_in_process() && + (!child_process_.get() || child_process_->IsStarting())) { + return; + } + + base::SharedMemoryHandle handle_for_process; + table_memory->ShareToProcess(GetHandle(), &handle_for_process); + if (base::SharedMemory::IsHandleValid(handle_for_process)) + Send(new ViewMsg_VisitedLink_NewTable(handle_for_process)); +} + +void BrowserRenderProcessHost::AddVisitedLinks( + const VisitedLinkCommon::Fingerprints& links) { + visited_link_updater_->AddLinks(links); + if (visible_widgets_ == 0) + return; + + visited_link_updater_->Update(this); +} + +void BrowserRenderProcessHost::ResetVisitedLinks() { + visited_link_updater_->AddReset(); + if (visible_widgets_ == 0) + return; + + visited_link_updater_->Update(this); +} + +void BrowserRenderProcessHost::AppendRendererCommandLine( + CommandLine* command_line) const { + // Pass the process type first, so it shows first in process listings. + // Extensions use a special pseudo-process type to make them distinguishable, + // even though they're just renderers. + command_line->AppendSwitchWithValue(switches::kProcessType, + extension_process_ ? switches::kExtensionProcess : + switches::kRendererProcess); + + if (logging::DialogsAreSuppressed()) + command_line->AppendSwitch(switches::kNoErrorDialogs); + + // Now send any options from our own command line we want to propogate. + const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); + PropagateBrowserCommandLineToRenderer(browser_command_line, command_line); + + // Pass on the browser locale. + const std::string locale = g_browser_process->GetApplicationLocale(); + command_line->AppendSwitchWithValue(switches::kLang, ASCIIToWide(locale)); + + // If we run FieldTrials, we want to pass to their state to the renderer so + // that it can act in accordance with each state, or record histograms + // relating to the FieldTrial states. + std::string field_trial_states; + FieldTrialList::StatesToString(&field_trial_states); + if (!field_trial_states.empty()) { + command_line->AppendSwitchWithValue(switches::kForceFieldTestNameAndValue, + field_trial_states); + } + + BrowserChildProcessHost::SetCrashReporterCommandLine(command_line); + + FilePath user_data_dir = + browser_command_line.GetSwitchValuePath(switches::kUserDataDir); + + if (!user_data_dir.empty()) + command_line->AppendSwitchWithValue(switches::kUserDataDir, + user_data_dir.value()); +#if defined(OS_CHROMEOS) + const std::string& profile = + browser_command_line.GetSwitchValueASCII(switches::kProfile); + if (!profile.empty()) + command_line->AppendSwitchWithValue(switches::kProfile, profile); +#endif +} + +void BrowserRenderProcessHost::PropagateBrowserCommandLineToRenderer( + const CommandLine& browser_cmd, + CommandLine* renderer_cmd) const { + // Propagate the following switches to the renderer command line (along + // with any associated values) if present in the browser command line. + static const char* const switch_names[] = { + switches::kRendererAssertTest, +#if !defined(OFFICIAL_BUILD) + switches::kRendererCheckFalseTest, +#endif // !defined(OFFICIAL_BUILD) + switches::kRendererCrashTest, + switches::kRendererStartupDialog, + switches::kNoSandbox, + switches::kTestSandbox, +#if defined(USE_SECCOMP_SANDBOX) + switches::kDisableSeccompSandbox, +#else + switches::kEnableSeccompSandbox, +#endif +#if !defined (GOOGLE_CHROME_BUILD) + // These are unsupported and not fully tested modes, so don't enable them + // for official Google Chrome builds. + switches::kInProcessPlugins, +#endif // GOOGLE_CHROME_BUILD + switches::kAllowScriptingGallery, + switches::kDomAutomationController, + switches::kUserAgent, + switches::kNoReferrers, + switches::kJavaScriptFlags, + switches::kRecordMode, + switches::kPlaybackMode, + switches::kNoJsRandomness, + switches::kDisableBreakpad, + switches::kFullMemoryCrashReport, + switches::kEnableLogging, + switches::kDumpHistogramsOnExit, + switches::kDisableLogging, + switches::kLoggingLevel, + switches::kDebugPrint, + switches::kMemoryProfiling, + switches::kEnableWatchdog, + switches::kMessageLoopHistogrammer, + switches::kEnableDCHECK, + switches::kSilentDumpOnDCHECK, + switches::kUseLowFragHeapCrt, + switches::kEnableSearchProviderApiV2, + switches::kEnableStatsTable, + switches::kExperimentalSpellcheckerFeatures, + switches::kDisableAudio, + switches::kSimpleDataSource, + switches::kEnableBenchmarking, + switches::kInternalNaCl, + switches::kInternalPepper, + switches::kRegisterPepperPlugins, + switches::kDisableByteRangeSupport, + switches::kDisableDatabases, + switches::kDisableDesktopNotifications, + switches::kDisableWebSockets, + switches::kDisableLocalStorage, + switches::kDisableSessionStorage, + switches::kDisableSharedWorkers, + switches::kDisableApplicationCache, + switches::kEnableIndexedDatabase, + switches::kDisableGeolocation, + switches::kShowPaintRects, + switches::kEnableOpenMax, + switches::kVideoThreads, + switches::kEnableVideoFullscreen, + switches::kEnableVideoLayering, + switches::kEnableVideoLogging, + switches::kEnableTouch, + // We propagate the Chrome Frame command line here as well in case the + // renderer is not run in the sandbox. + switches::kChromeFrame, + // We need to propagate this flag to determine whether to make the + // WebGLArray constructors on the DOMWindow visible. This + // information is needed very early during bringup. We prefer to + // use the WebPreferences to set this flag on a page-by-page basis. + switches::kEnableExperimentalWebGL, + switches::kEnableGLSLTranslator, + switches::kInProcessWebGL, +#if defined(OS_MACOSX) + // Allow this to be set when invoking the browser and relayed along. + switches::kEnableSandboxLogging, + switches::kDisableFlashCoreAnimation, +#endif + switches::kRemoteShellPort, + switches::kEnablePepperTesting, + switches::kEnableChromoting, + switches::kPrelaunchGpuProcess, + }; + + for (size_t i = 0; i < arraysize(switch_names); ++i) { + if (browser_cmd.HasSwitch(switch_names[i])) { + renderer_cmd->AppendSwitchWithValue(switch_names[i], + browser_cmd.GetSwitchValueASCII(switch_names[i])); + } + } + + // Disable databases in incognito mode. + if (profile()->IsOffTheRecord() && + !browser_cmd.HasSwitch(switches::kDisableDatabases)) { + renderer_cmd->AppendSwitch(switches::kDisableDatabases); + } +} + +base::ProcessHandle BrowserRenderProcessHost::GetHandle() { + // child_process_ is null either because we're in single process mode, we have + // done fast termination, or the process has crashed. + if (run_renderer_in_process() || !child_process_.get()) + return base::Process::Current().handle(); + + if (child_process_->IsStarting()) + return base::kNullProcessHandle; + + return child_process_->GetHandle(); +} + +void BrowserRenderProcessHost::InitVisitedLinks() { + VisitedLinkMaster* visitedlink_master = profile()->GetVisitedLinkMaster(); + if (!visitedlink_master) + return; + + SendVisitedLinkTable(visitedlink_master->shared_memory()); +} + +void BrowserRenderProcessHost::InitUserScripts() { + UserScriptMaster* user_script_master = profile()->GetUserScriptMaster(); + + // Incognito profiles won't have user scripts. + 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::InitExtensions() { + // TODO(aa): Should only bother sending these function names if this is an + // extension process. + std::vector<std::string> function_names; + ExtensionFunctionDispatcher::GetAllFunctionNames(&function_names); + Send(new ViewMsg_Extension_SetFunctionNames(function_names)); +} + +void BrowserRenderProcessHost::SendUserScriptsUpdate( + base::SharedMemory *shared_memory) { + // Process is being started asynchronously. We'll end up calling + // InitUserScripts when it's created which will call this again. + if (child_process_.get() && child_process_->IsStarting()) + return; + + base::SharedMemoryHandle handle_for_process; + if (!shared_memory->ShareToProcess(GetHandle(), &handle_for_process)) { + // This can legitimately fail if the renderer asserts at startup. + return; + } + + if (base::SharedMemory::IsHandleValid(handle_for_process)) { + Send(new ViewMsg_UserScripts_UpdatedScripts(handle_for_process)); + } +} + +void BrowserRenderProcessHost::SendExtensionExtentsUpdate() { + // Check if the process is still starting and we don't have a handle for it + // yet, in which case this will happen later when InitVisitedLinks is called. + if (!run_renderer_in_process() && + (!child_process_.get() || child_process_->IsStarting())) { + return; + } + + ExtensionsService* service = profile()->GetExtensionsService(); + if (!service) + return; + ViewMsg_ExtensionExtentsUpdated_Params params; + for (size_t i = 0; i < service->extensions()->size(); ++i) { + Extension* extension = service->extensions()->at(i); + if (!extension->web_extent().is_empty()) { + ViewMsg_ExtensionExtentInfo info; + info.extension_id = extension->id(); + info.web_extent = extension->web_extent(); + info.browse_extent = extension->browse_extent(); + params.extension_apps.push_back(info); + } + } + + Send(new ViewMsg_ExtensionExtentsUpdated(params)); +} + +bool BrowserRenderProcessHost::FastShutdownIfPossible() { + if (run_renderer_in_process()) + return false; // Single process mode can't do fast shutdown. + + if (!child_process_.get() || child_process_->IsStarting() || !GetHandle()) + return false; // Render process hasn't started or is probably crashed. + + // Test if there's an unload listener. + // 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. + if (!sudden_termination_allowed()) + return false; + + // Check for any external tab containers, since they may still be running even + // though this window closed. + listeners_iterator iter(ListenersIterator()); + while (!iter.IsAtEnd()) { + // NOTE: This is a bit dangerous. We know that for now, listeners are + // always RenderWidgetHosts. But in theory, they don't have to be. + const RenderWidgetHost* widget = + static_cast<const RenderWidgetHost*>(iter.GetCurrentValue()); + DCHECK(widget); + if (widget && widget->IsRenderView()) { + const RenderViewHost* rvh = static_cast<const RenderViewHost*>(widget); + if (rvh->delegate()->IsExternalTabContainer()) + return false; + } + + iter.Advance(); + } + + child_process_.reset(); + fast_shutdown_started_ = true; + return true; +} + +bool BrowserRenderProcessHost::SendWithTimeout(IPC::Message* msg, + int timeout_ms) { + if (!channel_.get()) { + delete msg; + return false; + } + return channel_->SendWithTimeout(msg, timeout_ms); +} + +// This is a platform specific function for mapping a transport DIB given its id +TransportDIB* BrowserRenderProcessHost::MapTransportDIB( + TransportDIB::Id dib_id) { +#if defined(OS_WIN) + // On Windows we need to duplicate the handle from the remote process + HANDLE section = win_util::GetSectionFromProcess( + dib_id.handle, GetHandle(), false /* read write */); + return TransportDIB::Map(section); +#elif defined(OS_MACOSX) + // On OSX, the browser allocates all DIBs and keeps a file descriptor around + // for each. + return widget_helper_->MapTransportDIB(dib_id); +#elif defined(OS_LINUX) + return TransportDIB::Map(dib_id); +#endif // defined(OS_LINUX) +} + +TransportDIB* BrowserRenderProcessHost::GetTransportDIB( + TransportDIB::Id dib_id) { + const std::map<TransportDIB::Id, TransportDIB*>::iterator + i = cached_dibs_.find(dib_id); + if (i != cached_dibs_.end()) { + cached_dibs_cleaner_.Reset(); + return i->second; + } + + TransportDIB* dib = MapTransportDIB(dib_id); + if (!dib) + return NULL; + + if (cached_dibs_.size() >= MAX_MAPPED_TRANSPORT_DIBS) { + // Clean a single entry from the cache + std::map<TransportDIB::Id, TransportDIB*>::iterator smallest_iterator; + size_t smallest_size = std::numeric_limits<size_t>::max(); + + for (std::map<TransportDIB::Id, TransportDIB*>::iterator + i = cached_dibs_.begin(); i != cached_dibs_.end(); ++i) { + if (i->second->size() <= smallest_size) { + smallest_iterator = i; + smallest_size = i->second->size(); + } + } + + delete smallest_iterator->second; + cached_dibs_.erase(smallest_iterator); + } + + cached_dibs_[dib_id] = dib; + cached_dibs_cleaner_.Reset(); + return dib; +} + +void BrowserRenderProcessHost::ClearTransportDIBCache() { + STLDeleteContainerPairSecondPointers( + cached_dibs_.begin(), cached_dibs_.end()); + cached_dibs_.clear(); +} + +bool BrowserRenderProcessHost::Send(IPC::Message* msg) { + if (!channel_.get()) { + delete msg; + return false; + } + + if (child_process_.get() && child_process_->IsStarting()) { + queued_messages_.push(msg); + return true; + } + + return channel_->Send(msg); +} + +void BrowserRenderProcessHost::OnMessageReceived(const IPC::Message& msg) { + mark_child_process_activity_time(); + 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_UpdatedCacheStats, + OnUpdatedCacheStats) + IPC_MESSAGE_HANDLER(ViewHostMsg_SuddenTerminationChanged, + SuddenTerminationChanged); + IPC_MESSAGE_HANDLER(ViewHostMsg_ExtensionAddListener, + OnExtensionAddListener) + IPC_MESSAGE_HANDLER(ViewHostMsg_ExtensionRemoveListener, + OnExtensionRemoveListener) + IPC_MESSAGE_HANDLER(ViewHostMsg_ExtensionCloseChannel, + OnExtensionCloseChannel) + IPC_MESSAGE_HANDLER(ViewHostMsg_SpellChecker_RequestDictionary, + OnSpellCheckerRequestDictionary) + 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 RenderView/WidgetHost. + 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) { +#if defined(IPC_MESSAGE_LOG_ENABLED) + Send(new ViewMsg_SetIPCLoggingEnabled(IPC::Logging::current()->Enabled())); +#endif +} + +// Static. This function can be called from any thread. +void BrowserRenderProcessHost::BadMessageTerminateProcess( + uint32 msg_type, base::ProcessHandle process) { + LOG(ERROR) << "bad message " << msg_type << " terminating renderer."; + if (run_renderer_in_process()) { + // In single process mode it is better if we don't suicide but just crash. + CHECK(false); + } + NOTREACHED(); + base::KillProcess(process, ResultCodes::KILLED_BAD_MESSAGE, false); +} + +void BrowserRenderProcessHost::OnChannelError() { + // Our child process has died. If we didn't expect it, it's a crash. + // In any case, we need to let everyone know it's gone. + // The OnChannelError notification can fire multiple times due to nested sync + // calls to a renderer. If we don't have a valid channel here it means we + // already handled the error. + if (!channel_.get()) + return; + + // NULL in single process mode or if fast termination happened. + bool did_crash = + child_process_.get() ? child_process_->DidProcessCrash() : false; + + if (did_crash) { + UMA_HISTOGRAM_PERCENTAGE("BrowserRenderProcessHost.ChildCrashes", + extension_process_ ? 2 : 1); + } + + RendererClosedDetails details(did_crash, extension_process_); + NotificationService::current()->Notify( + NotificationType::RENDERER_PROCESS_CLOSED, + Source<RenderProcessHost>(this), + Details<RendererClosedDetails>(&details)); + + WebCacheManager::GetInstance()->Remove(id()); + child_process_.reset(); + channel_.reset(); + + IDMap<IPC::Channel::Listener>::iterator iter(&listeners_); + while (!iter.IsAtEnd()) { + iter.GetCurrentValue()->OnMessageReceived( + ViewHostMsg_RenderViewGone(iter.GetCurrentKey())); + iter.Advance(); + } + + ClearTransportDIBCache(); + + // this object is not deleted at this point and may be reused later. + // TODO(darin): clean this up +} + +void BrowserRenderProcessHost::OnUpdatedCacheStats( + const WebCache::UsageStats& stats) { + WebCacheManager::GetInstance()->ObserveStats(id(), stats); +} + +void BrowserRenderProcessHost::SuddenTerminationChanged(bool enabled) { + set_sudden_termination_allowed(enabled); +} + +void BrowserRenderProcessHost::SetBackgrounded(bool backgrounded) { + // 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; + if (!child_process_.get() || child_process_->IsStarting()) + return; + +#if defined(OS_WIN) + // The cbstext.dll loads as a global GetMessage hook in the browser process + // and intercepts/unintercepts the kernel32 API SetPriorityClass in a + // background thread. If the UI thread invokes this API just when it is + // intercepted the stack is messed up on return from the interceptor + // which causes random crashes in the browser process. Our hack for now + // is to not invoke the SetPriorityClass API if the dll is loaded. + if (GetModuleHandle(L"cbstext.dll")) + return; +#endif // OS_WIN + + child_process_->SetProcessBackgrounded(backgrounded); +} + +void BrowserRenderProcessHost::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::USER_SCRIPTS_UPDATED: { + base::SharedMemory* shared_memory = + Details<base::SharedMemory>(details).ptr(); + if (shared_memory) { + SendUserScriptsUpdate(shared_memory); + } + break; + } + case NotificationType::EXTENSION_LOADED: + case NotificationType::EXTENSION_UNLOADED: { + Extension* extension = Details<Extension>(details).ptr(); + if (!extension->web_extent().is_empty()) + SendExtensionExtentsUpdate(); + break; + } + case NotificationType::SPELLCHECK_HOST_REINITIALIZED: { + InitSpellChecker(); + break; + } + case NotificationType::SPELLCHECK_WORD_ADDED: { + AddSpellCheckWord( + reinterpret_cast<const Source<SpellCheckHost>*>(&source)-> + ptr()->last_added_word()); + break; + } + case NotificationType::SPELLCHECK_AUTOSPELL_TOGGLED: { + PrefService* prefs = profile()->GetPrefs(); + EnableAutoSpellCorrect( + prefs->GetBoolean(prefs::kEnableAutoSpellCorrect)); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +void BrowserRenderProcessHost::OnProcessLaunched() { + // Now that the process is created, set its backgrounding accordingly. + SetBackgrounded(backgrounded_); + + Send(new ViewMsg_SetIsIncognitoProcess(profile()->IsOffTheRecord())); + + InitVisitedLinks(); + InitUserScripts(); + InitExtensions(); + SendExtensionExtentsUpdate(); + // We don't want to initialize the spellchecker unless SpellCheckHost has been + // created. In InitSpellChecker(), we know if GetSpellCheckHost() is NULL + // then the spellchecker has been turned off, but here, we don't know if + // it's been turned off or just not loaded yet. + if (profile()->GetSpellCheckHost()) + InitSpellChecker(); + + if (max_page_id_ != -1) + Send(new ViewMsg_SetNextPageID(max_page_id_ + 1)); + + while (!queued_messages_.empty()) { + Send(queued_messages_.front()); + queued_messages_.pop(); + } + + NotificationService::current()->Notify( + NotificationType::RENDERER_PROCESS_CREATED, + Source<RenderProcessHost>(this), NotificationService::NoDetails()); +} + +void BrowserRenderProcessHost::OnExtensionAddListener( + const std::string& event_name) { + if (profile()->GetExtensionMessageService()) { + profile()->GetExtensionMessageService()->AddEventListener( + event_name, id()); + } +} + +void BrowserRenderProcessHost::OnExtensionRemoveListener( + const std::string& event_name) { + if (profile()->GetExtensionMessageService()) { + profile()->GetExtensionMessageService()->RemoveEventListener( + event_name, id()); + } +} + +void BrowserRenderProcessHost::OnExtensionCloseChannel(int port_id) { + if (profile()->GetExtensionMessageService()) { + profile()->GetExtensionMessageService()->CloseChannel(port_id); + } +} + +void BrowserRenderProcessHost::OnSpellCheckerRequestDictionary() { + if (profile()->GetSpellCheckHost()) { + // The spellchecker initialization already started and finished; just send + // it to the renderer. + InitSpellChecker(); + } else { + // We may have gotten multiple requests from different renderers. We don't + // want to initialize multiple times in this case, so we set |force| to + // false. + profile()->ReinitializeSpellCheckHost(false); + } +} + +void BrowserRenderProcessHost::AddSpellCheckWord(const std::string& word) { + Send(new ViewMsg_SpellChecker_WordAdded(word)); +} + +void BrowserRenderProcessHost::InitSpellChecker() { + SpellCheckHost* spellcheck_host = profile()->GetSpellCheckHost(); + if (spellcheck_host) { + PrefService* prefs = profile()->GetPrefs(); + IPC::PlatformFileForTransit file; + + if (spellcheck_host->bdict_file() != base::kInvalidPlatformFileValue) { +#if defined(OS_POSIX) + file = base::FileDescriptor(spellcheck_host->bdict_file(), false); +#elif defined(OS_WIN) + ::DuplicateHandle(::GetCurrentProcess(), spellcheck_host->bdict_file(), + GetHandle(), &file, 0, false, DUPLICATE_SAME_ACCESS); +#endif + } + + Send(new ViewMsg_SpellChecker_Init( + file, + spellcheck_host->custom_words(), + spellcheck_host->language(), + prefs->GetBoolean(prefs::kEnableAutoSpellCorrect))); + } else { + Send(new ViewMsg_SpellChecker_Init( + IPC::InvalidPlatformFileForTransit(), + std::vector<std::string>(), + std::string(), + false)); + } +} + +void BrowserRenderProcessHost::EnableAutoSpellCorrect(bool enable) { + Send(new ViewMsg_SpellChecker_EnableAutoSpellCorrect(enable)); +} |