[go: nahoru, domu]

blob: df299077c209dbce425c4c27858944b3a6762afd [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/singleton.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_handle.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 "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
namespace extensions {
namespace {
class MimeHandlerStreamManagerFactory
: public BrowserContextKeyedServiceFactory {
public:
MimeHandlerStreamManagerFactory();
static MimeHandlerStreamManagerFactory* GetInstance();
MimeHandlerStreamManager* Get(content::BrowserContext* context);
private:
// BrowserContextKeyedServiceFactory overrides.
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* profile) const override;
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override;
};
MimeHandlerStreamManagerFactory::MimeHandlerStreamManagerFactory()
: BrowserContextKeyedServiceFactory(
"MimeHandlerStreamManager",
BrowserContextDependencyManager::GetInstance()) {
}
// static
MimeHandlerStreamManagerFactory*
MimeHandlerStreamManagerFactory::GetInstance() {
return base::Singleton<MimeHandlerStreamManagerFactory>::get();
}
MimeHandlerStreamManager* MimeHandlerStreamManagerFactory::Get(
content::BrowserContext* context) {
return static_cast<MimeHandlerStreamManager*>(
GetServiceForBrowserContext(context, true));
}
KeyedService* MimeHandlerStreamManagerFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
return new MimeHandlerStreamManager();
}
content::BrowserContext*
MimeHandlerStreamManagerFactory::GetBrowserContextToUse(
content::BrowserContext* context) const {
return extensions::ExtensionsBrowserClient::Get()
->GetContextRedirectedToOriginal(context, /*force_guest_profile=*/true);
}
} // namespace
// A WebContentsObserver that observes for a particular RenderFrameHost either
// navigating or closing (including by crashing). This is necessary to ensure
// that streams that aren't claimed by a MimeHandlerViewGuest are not leaked, by
// aborting the stream if any of those events occurs.
class MimeHandlerStreamManager::EmbedderObserver
: public content::WebContentsObserver {
public:
EmbedderObserver(MimeHandlerStreamManager* stream_manager,
const std::string& stream_id,
int frame_tree_node_id);
private:
// WebContentsObserver overrides.
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
void PrimaryMainFrameRenderProcessGone(
base::TerminationStatus status) override;
void WebContentsDestroyed() override;
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
void RenderFrameHostChanged(content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) override;
void AbortStream();
bool IsTrackedRenderFrameHost(content::RenderFrameHost* render_frame_host);
const raw_ptr<MimeHandlerStreamManager> stream_manager_;
const std::string stream_id_;
int frame_tree_node_id_;
content::GlobalRenderFrameHostId render_frame_host_id_;
// We get an initial load notification for the URL the mime handler is
// serving. We don't want to clean up the stream here. This field helps us
// track the first load notification. Defaults to true.
bool initial_load_for_frame_;
// If a RFH is swapped with another RFH, this is set to the new RFH. This
// ensures that we don't inadvarently clean up the stream when the old RFH
// dies.
raw_ptr<content::RenderFrameHost> new_host_;
};
MimeHandlerStreamManager::MimeHandlerStreamManager() = default;
MimeHandlerStreamManager::~MimeHandlerStreamManager() = default;
// static
MimeHandlerStreamManager* MimeHandlerStreamManager::Get(
content::BrowserContext* context) {
return MimeHandlerStreamManagerFactory::GetInstance()->Get(context);
}
void MimeHandlerStreamManager::AddStream(
const std::string& stream_id,
std::unique_ptr<StreamContainer> stream,
int frame_tree_node_id) {
streams_by_extension_id_[stream->extension_id()].insert(stream_id);
auto result = streams_.insert(std::make_pair(stream_id, std::move(stream)));
DCHECK(result.second);
embedder_observers_[stream_id] =
std::make_unique<EmbedderObserver>(this, stream_id, frame_tree_node_id);
}
std::unique_ptr<StreamContainer> MimeHandlerStreamManager::ReleaseStream(
const std::string& stream_id) {
auto stream = streams_.find(stream_id);
if (stream == streams_.end())
return nullptr;
std::unique_ptr<StreamContainer> result =
base::WrapUnique(stream->second.release());
streams_by_extension_id_[result->extension_id()].erase(stream_id);
streams_.erase(stream);
embedder_observers_.erase(stream_id);
return result;
}
void MimeHandlerStreamManager::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
auto streams = streams_by_extension_id_.find(extension->id());
if (streams == streams_by_extension_id_.end())
return;
for (const auto& stream_id : streams->second) {
streams_.erase(stream_id);
embedder_observers_.erase(stream_id);
}
streams_by_extension_id_.erase(streams);
}
MimeHandlerStreamManager::EmbedderObserver::EmbedderObserver(
MimeHandlerStreamManager* stream_manager,
const std::string& stream_id,
int frame_tree_node_id)
: content::WebContentsObserver(
content::WebContents::FromFrameTreeNodeId(frame_tree_node_id)),
stream_manager_(stream_manager),
stream_id_(stream_id),
frame_tree_node_id_(frame_tree_node_id),
initial_load_for_frame_(true),
new_host_(nullptr) {}
void MimeHandlerStreamManager::EmbedderObserver::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (!IsTrackedRenderFrameHost(render_frame_host))
return;
// The MimeHandlerStreamManager::EmbedderObserver is initialized before the
// final RenderFrameHost for the navigation has been chosen. When it is later
// picked, a specualtive RenderFrameHost might be deleted. Do not abort the
// stream in that case.
if (frame_tree_node_id_ != content::RenderFrameHost::kNoFrameTreeNodeId &&
!render_frame_host->IsActive())
return;
AbortStream();
}
void MimeHandlerStreamManager::EmbedderObserver::
PrimaryMainFrameRenderProcessGone(base::TerminationStatus status) {
AbortStream();
}
void MimeHandlerStreamManager::EmbedderObserver::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) {
if (navigation_handle->IsSameDocument() ||
!IsTrackedRenderFrameHost(navigation_handle->GetRenderFrameHost())) {
return;
}
// We get an initial load notification for the URL we are serving. We don't
// want to clean up the stream here. Update the RenderFrameHost tracking.
if (initial_load_for_frame_) {
initial_load_for_frame_ = false;
frame_tree_node_id_ = content::RenderFrameHost::kNoFrameTreeNodeId;
render_frame_host_id_ =
navigation_handle->GetRenderFrameHost()->GetGlobalId();
return;
}
AbortStream();
}
void MimeHandlerStreamManager::EmbedderObserver::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
// If the top level frame is navigating away, clean up the stream.
// TODO(mcnee): It's incorrect to assume DidStartNavigation will lead to the
// document changing. This could cause the stream to be destroyed prematurely.
if (navigation_handle->IsInPrimaryMainFrame() &&
!navigation_handle->IsSameDocument()) {
AbortStream();
}
}
void MimeHandlerStreamManager::EmbedderObserver::RenderFrameHostChanged(
content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) {
// If the old_host is null, then it means that a subframe is being created.
// Don't treat this like a host change.
if (!old_host)
return;
// If this is an unrelated host, ignore.
if ((frame_tree_node_id_ != content::RenderFrameHost::kNoFrameTreeNodeId &&
old_host->GetFrameTreeNodeId() != frame_tree_node_id_) ||
(render_frame_host_id_ &&
(old_host->GetGlobalId() != render_frame_host_id_))) {
return;
}
new_host_ = new_host;
// Update the RFH id to that of the new RFH. This ensures
// that if the new RFH gets deleted before loading the stream, we will
// abort it.
DCHECK(
(frame_tree_node_id_ == content::RenderFrameHost::kNoFrameTreeNodeId) ||
(frame_tree_node_id_ == new_host_->GetFrameTreeNodeId()));
render_frame_host_id_ = new_host_->GetGlobalId();
// No need to keep this around anymore since we have valid render frame IDs
// now.
frame_tree_node_id_ = content::RenderFrameHost::kNoFrameTreeNodeId;
}
void MimeHandlerStreamManager::EmbedderObserver::WebContentsDestroyed() {
AbortStream();
}
void MimeHandlerStreamManager::EmbedderObserver::AbortStream() {
Observe(nullptr);
// This will cause the stream to be destroyed.
stream_manager_->ReleaseStream(stream_id_);
}
bool MimeHandlerStreamManager::EmbedderObserver::IsTrackedRenderFrameHost(
content::RenderFrameHost* render_frame_host) {
// We don't want to abort the stream if the frame we were tracking changed to
// new_host_.
if (new_host_ && (render_frame_host != new_host_))
return false;
if (frame_tree_node_id_ != content::RenderFrameHost::kNoFrameTreeNodeId) {
return render_frame_host->GetFrameTreeNodeId() == frame_tree_node_id_;
} else {
DCHECK(render_frame_host_id_);
return render_frame_host->GetGlobalId() == render_frame_host_id_;
}
}
// static
void MimeHandlerStreamManager::EnsureFactoryBuilt() {
MimeHandlerStreamManagerFactory::GetInstance();
}
} // namespace extensions