// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/messaging/extension_message_port.h" #include "base/scoped_observer.h" #include "chrome/browser/profiles/profile.h" #include "content/public/browser/interstitial_page.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" #include "extensions/browser/extension_host.h" #include "extensions/browser/process_manager.h" #include "extensions/browser/process_manager_observer.h" #include "extensions/common/extension_messages.h" #include "extensions/common/manifest_handlers/background_info.h" namespace extensions { const char kReceivingEndDoesntExistError[] = "Could not establish connection. Receiving end does not exist."; // Helper class to detect when frames are destroyed. class ExtensionMessagePort::FrameTracker : public content::WebContentsObserver, public ProcessManagerObserver { public: explicit FrameTracker(ExtensionMessagePort* port) : pm_observer_(this), port_(port), interstitial_frame_(nullptr) {} ~FrameTracker() override {} void TrackExtensionProcessFrames() { pm_observer_.Add(ProcessManager::Get(port_->browser_context_)); } void TrackTabFrames(content::WebContents* tab) { Observe(tab); } void TrackInterstitialFrame(content::WebContents* tab, content::RenderFrameHost* interstitial_frame) { // |tab| should never be nullptr, because an interstitial's lifetime is // tied to a tab. This is a CHECK, not a DCHECK because we really need an // observer subject to detect frame removal (via DidDetachInterstitialPage). CHECK(tab); DCHECK(interstitial_frame); interstitial_frame_ = interstitial_frame; Observe(tab); } private: // content::WebContentsObserver overrides: void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override { port_->UnregisterFrame(render_frame_host); } void DidNavigateAnyFrame(content::RenderFrameHost* render_frame_host, const content::LoadCommittedDetails& details, const content::FrameNavigateParams&) override { if (!details.is_in_page) port_->UnregisterFrame(render_frame_host); } void DidDetachInterstitialPage() override { if (interstitial_frame_) port_->UnregisterFrame(interstitial_frame_); } // extensions::ProcessManagerObserver overrides: void OnExtensionFrameUnregistered( const std::string& extension_id, content::RenderFrameHost* render_frame_host) override { if (extension_id == port_->extension_id_) port_->UnregisterFrame(render_frame_host); } ScopedObserver<ProcessManager, ProcessManagerObserver> pm_observer_; ExtensionMessagePort* port_; // Owns this FrameTracker. // Set to the main frame of an interstitial if we are tracking an interstitial // page, because RenderFrameDeleted is never triggered for frames in an // interstitial (and we only support tracking the interstitial's main frame). content::RenderFrameHost* interstitial_frame_; DISALLOW_COPY_AND_ASSIGN(FrameTracker); }; ExtensionMessagePort::ExtensionMessagePort( base::WeakPtr<MessageService> message_service, int port_id, const std::string& extension_id, content::RenderProcessHost* extension_process) : weak_message_service_(message_service), port_id_(port_id), extension_id_(extension_id), browser_context_(extension_process->GetBrowserContext()), extension_process_(extension_process), did_create_port_(false), background_host_ptr_(nullptr), frame_tracker_(new FrameTracker(this)) { auto all_hosts = ProcessManager::Get(browser_context_) ->GetRenderFrameHostsForExtension(extension_id); for (content::RenderFrameHost* rfh : all_hosts) RegisterFrame(rfh); frame_tracker_->TrackExtensionProcessFrames(); } ExtensionMessagePort::ExtensionMessagePort( base::WeakPtr<MessageService> message_service, int port_id, const std::string& extension_id, content::RenderFrameHost* rfh, bool include_child_frames) : weak_message_service_(message_service), port_id_(port_id), extension_id_(extension_id), browser_context_(rfh->GetProcess()->GetBrowserContext()), extension_process_(nullptr), did_create_port_(false), background_host_ptr_(nullptr), frame_tracker_(new FrameTracker(this)) { content::WebContents* tab = content::WebContents::FromRenderFrameHost(rfh); if (!tab) { content::InterstitialPage* interstitial = content::InterstitialPage::FromRenderFrameHost(rfh); // A RenderFrameHost must be hosted in a WebContents or InterstitialPage. CHECK(interstitial); // Only the main frame of an interstitial is supported, because frames in // the interstitial do not trigger RenderFrameCreated / RenderFrameDeleted // on WebContentObservers. Consequently, (1) we cannot detect removal of // RenderFrameHosts, and (2) even if the RenderFrameDeleted is propagated, // then WebContentsObserverSanityChecker triggers a CHECK when it detects // frame notifications without a corresponding RenderFrameCreated. if (!rfh->GetParent()) { // It is safe to pass the interstitial's WebContents here because we only // use it to observe DidDetachInterstitialPage. frame_tracker_->TrackInterstitialFrame(interstitial->GetWebContents(), rfh); RegisterFrame(rfh); } return; } frame_tracker_->TrackTabFrames(tab); if (include_child_frames) { tab->ForEachFrame(base::Bind(&ExtensionMessagePort::RegisterFrame, base::Unretained(this))); } else { RegisterFrame(rfh); } } ExtensionMessagePort::~ExtensionMessagePort() {} void ExtensionMessagePort::RemoveCommonFrames(const MessagePort& port) { // Avoid overlap in the set of frames to make sure that it does not matter // when UnregisterFrame is called. for (std::set<content::RenderFrameHost*>::iterator it = frames_.begin(); it != frames_.end(); ) { if (port.HasFrame(*it)) { frames_.erase(it++); } else { ++it; } } } bool ExtensionMessagePort::HasFrame(content::RenderFrameHost* rfh) const { return frames_.find(rfh) != frames_.end(); } bool ExtensionMessagePort::IsValidPort() { return !frames_.empty(); } void ExtensionMessagePort::DispatchOnConnect( const std::string& channel_name, scoped_ptr<base::DictionaryValue> source_tab, int source_frame_id, int guest_process_id, int guest_render_frame_routing_id, const std::string& source_extension_id, const std::string& target_extension_id, const GURL& source_url, const std::string& tls_channel_id) { ExtensionMsg_TabConnectionInfo source; if (source_tab) source.tab.Swap(source_tab.get()); source.frame_id = source_frame_id; ExtensionMsg_ExternalConnectionInfo info; info.target_id = target_extension_id; info.source_id = source_extension_id; info.source_url = source_url; info.guest_process_id = guest_process_id; info.guest_render_frame_routing_id = guest_render_frame_routing_id; SendToPort(make_scoped_ptr(new ExtensionMsg_DispatchOnConnect( MSG_ROUTING_NONE, port_id_, channel_name, source, info, tls_channel_id))); } void ExtensionMessagePort::DispatchOnDisconnect( const std::string& error_message) { SendToPort(make_scoped_ptr(new ExtensionMsg_DispatchOnDisconnect( MSG_ROUTING_NONE, port_id_, error_message))); } void ExtensionMessagePort::DispatchOnMessage(const Message& message) { SendToPort(make_scoped_ptr(new ExtensionMsg_DeliverMessage( MSG_ROUTING_NONE, port_id_, message))); } void ExtensionMessagePort::IncrementLazyKeepaliveCount() { ProcessManager* pm = ProcessManager::Get(browser_context_); ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id_); if (host && BackgroundInfo::HasLazyBackgroundPage(host->extension())) pm->IncrementLazyKeepaliveCount(host->extension()); // Keep track of the background host, so when we decrement, we only do so if // the host hasn't reloaded. background_host_ptr_ = host; } void ExtensionMessagePort::DecrementLazyKeepaliveCount() { ProcessManager* pm = ProcessManager::Get(browser_context_); ExtensionHost* host = pm->GetBackgroundHostForExtension(extension_id_); if (host && host == background_host_ptr_) pm->DecrementLazyKeepaliveCount(host->extension()); } void ExtensionMessagePort::OpenPort(int process_id, int routing_id) { DCHECK(routing_id != MSG_ROUTING_NONE || extension_process_); did_create_port_ = true; } void ExtensionMessagePort::ClosePort(int process_id, int routing_id) { if (routing_id == MSG_ROUTING_NONE) { // The only non-frame-specific message is the response to an unhandled // onConnect event in the extension process. DCHECK(extension_process_); frames_.clear(); CloseChannel(); return; } content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(process_id, routing_id); if (rfh) UnregisterFrame(rfh); } void ExtensionMessagePort::CloseChannel() { std::string error_message = did_create_port_ ? std::string() : kReceivingEndDoesntExistError; if (weak_message_service_) weak_message_service_->CloseChannel(port_id_, error_message); } void ExtensionMessagePort::RegisterFrame(content::RenderFrameHost* rfh) { // Only register a RenderFrameHost whose RenderFrame has been created, to // ensure that we are notified of frame destruction. Without this check, // |frames_| can eventually contain a stale pointer because RenderFrameDeleted // is not triggered for |rfh|. if (rfh->IsRenderFrameLive()) frames_.insert(rfh); } void ExtensionMessagePort::UnregisterFrame(content::RenderFrameHost* rfh) { if (frames_.erase(rfh) != 0 && frames_.empty()) CloseChannel(); } void ExtensionMessagePort::SendToPort(scoped_ptr<IPC::Message> msg) { DCHECK_GT(frames_.size(), 0UL); if (extension_process_) { // All extension frames reside in the same process, so we can just send a // single IPC message to the extension process as an optimization. // The frame tracking is then only used to make sure that the port gets // closed when all frames have closed / reloaded. msg->set_routing_id(MSG_ROUTING_CONTROL); extension_process_->Send(msg.release()); return; } for (content::RenderFrameHost* rfh : frames_) { IPC::Message* msg_copy = new IPC::Message(*msg.get()); msg_copy->set_routing_id(rfh->GetRoutingID()); rfh->Send(msg_copy); } } } // namespace extensions