blob: e7cb5d0552a555ed3d3b0a8220383dd838088b21 [file] [log] [blame]
// Copyright 2022 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 "extensions/browser/api/offscreen/offscreen_document_manager.h"
#include "base/check.h"
#include "base/dcheck_is_on.h"
#include "base/feature_list.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/browser_context.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/offscreen_document_host.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_factory.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace extensions {
namespace {
class OffscreenDocumentManagerFactory
: public BrowserContextKeyedServiceFactory {
public:
OffscreenDocumentManagerFactory();
OffscreenDocumentManagerFactory(const OffscreenDocumentManagerFactory&) =
delete;
OffscreenDocumentManagerFactory& operator=(
const OffscreenDocumentManagerFactory&) = delete;
~OffscreenDocumentManagerFactory() override = default;
OffscreenDocumentManager* GetForBrowserContext(
content::BrowserContext* context);
private:
// BrowserContextKeyedServiceFactory:
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override;
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
};
OffscreenDocumentManagerFactory::OffscreenDocumentManagerFactory()
: BrowserContextKeyedServiceFactory(
"OffscreenDocumentManager",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(ExtensionRegistryFactory::GetInstance());
DependsOn(ProcessManagerFactory::GetInstance());
}
OffscreenDocumentManager* OffscreenDocumentManagerFactory::GetForBrowserContext(
content::BrowserContext* browser_context) {
return static_cast<OffscreenDocumentManager*>(
GetServiceForBrowserContext(browser_context, /*create=*/true));
}
content::BrowserContext*
OffscreenDocumentManagerFactory::GetBrowserContextToUse(
content::BrowserContext* context) const {
// Use the `context` passed in; this service has separate instances in
// on-the-record and incognito.
return context;
}
KeyedService* OffscreenDocumentManagerFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
return new OffscreenDocumentManager(context);
}
} // namespace
// OffscreenDocumentManager::OffscreenDocumentData:
OffscreenDocumentManager::OffscreenDocumentData::OffscreenDocumentData() =
default;
OffscreenDocumentManager::OffscreenDocumentData::~OffscreenDocumentData() =
default;
OffscreenDocumentManager::OffscreenDocumentData::OffscreenDocumentData(
OffscreenDocumentData&&) = default;
// OffscreenDocumentManager:
OffscreenDocumentManager::OffscreenDocumentManager(
content::BrowserContext* browser_context)
: browser_context_(browser_context),
process_manager_(ProcessManager::Get(browser_context_)) {
registry_observation_.Observe(ExtensionRegistry::Get(browser_context_));
}
OffscreenDocumentManager::~OffscreenDocumentManager() = default;
// static
OffscreenDocumentManager* OffscreenDocumentManager::Get(
content::BrowserContext* browser_context) {
return static_cast<OffscreenDocumentManagerFactory*>(GetFactory())
->GetForBrowserContext(browser_context);
}
// static
BrowserContextKeyedServiceFactory* OffscreenDocumentManager::GetFactory() {
static base::NoDestructor<OffscreenDocumentManagerFactory> g_factory;
return g_factory.get();
}
OffscreenDocumentHost* OffscreenDocumentManager::CreateOffscreenDocument(
const Extension& extension,
const GURL& url) {
DCHECK(base::FeatureList::IsEnabled(
extensions_features::kExtensionsOffscreenDocuments));
DCHECK_EQ(url::Origin::Create(url), extension.origin());
// Currently only a single offscreen document is supported per extension.
DCHECK_EQ(nullptr, GetOffscreenDocumentForExtension(extension));
DCHECK(!base::Contains(offscreen_documents_, extension.id()));
#if DCHECK_IS_ON()
// This should only be for an off-the-record context if the extension is both
// enabled in incognito *and* runs in split mode. For spanning mode
// extensions, similar to the background context, offscreen documents only run
// in the on-the-record context.
if (browser_context_->IsOffTheRecord()) {
DCHECK(util::IsIncognitoEnabled(extension.id(), browser_context_));
DCHECK(IncognitoInfo::IsSplitMode(&extension));
}
#endif
OffscreenDocumentData& data = offscreen_documents_[extension.id()];
scoped_refptr<content::SiteInstance> site_instance =
process_manager_->GetSiteInstanceForURL(url);
data.host = std::make_unique<OffscreenDocumentHost>(extension,
site_instance.get(), url);
OffscreenDocumentHost* host = data.host.get();
// The following Unretained() is safe because this class owns the offscreen
// document host, so it can't possibly be called after the manager's
// destruction.
host->SetCloseHandler(
base::BindOnce(&OffscreenDocumentManager::CloseOffscreenDocument,
base::Unretained(this)));
host->CreateRendererSoon();
return host;
}
OffscreenDocumentHost*
OffscreenDocumentManager::GetOffscreenDocumentForExtension(
const Extension& extension) {
auto iter = offscreen_documents_.find(extension.id());
if (iter == offscreen_documents_.end())
return nullptr;
return iter->second.host.get();
}
void OffscreenDocumentManager::CloseOffscreenDocumentForExtension(
const Extension& extension) {
DCHECK_NE(nullptr, GetOffscreenDocumentForExtension(extension));
offscreen_documents_.erase(extension.id());
}
void OffscreenDocumentManager::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
// Close any offscreen document associated with the unloaded extension.
offscreen_documents_.erase(extension->id());
}
void OffscreenDocumentManager::Shutdown() {
// This would normally happen during destruction, but that isn't sufficient -
// we need to close all the corresponding ExtensionHosts
// (OffscreenDocumentHosts) prior to the browser context being marked as
// shut down.
offscreen_documents_.clear();
}
void OffscreenDocumentManager::CloseOffscreenDocument(
ExtensionHost* offscreen_document) {
auto iter = offscreen_documents_.find(offscreen_document->extension_id());
DCHECK(iter != offscreen_documents_.end());
DCHECK_EQ(iter->second.host.get(), offscreen_document);
offscreen_documents_.erase(iter);
}
} // namespace extensions