#include "extensions/browser/api/offscreen/offscreen_document_manager.h"
#include "base/test/bind.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_destroyer.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/api/offscreen/lifetime_enforcer_factories.h"
#include "extensions/browser/api/offscreen/offscreen_document_lifetime_enforcer.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_host_test_helper.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/offscreen_document_host.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/api/offscreen.h"
#include "extensions/common/mojom/view_type.mojom.h"
#include "extensions/common/switches.h"
#include "extensions/test/test_extension_dir.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/ui/browser.h"
#endif
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
namespace extensions {
namespace {
class TestLifetimeEnforcer : public OffscreenDocumentLifetimeEnforcer {
public:
TestLifetimeEnforcer(OffscreenDocumentHost* offscreen_document,
TerminationCallback termination_callback,
NotifyInactiveCallback notify_inactive_callback)
: OffscreenDocumentLifetimeEnforcer(offscreen_document,
std::move(termination_callback),
std::move(notify_inactive_callback)) {
}
~TestLifetimeEnforcer() override = default;
void CallTerminate() { TerminateDocument(); }
void CallNotifyInactive() {
DCHECK(!is_active_);
NotifyInactive();
}
void SetActive(bool is_active) { is_active_ = is_active; }
private:
bool IsActive() override { return is_active_; }
bool is_active_ = true;
};
std::unique_ptr<OffscreenDocumentLifetimeEnforcer> CreateTestLifetimeEnforcer(
TestLifetimeEnforcer** lifetime_enforcer_out,
OffscreenDocumentHost* offscreen_document,
OffscreenDocumentLifetimeEnforcer::TerminationCallback termination_callback,
OffscreenDocumentLifetimeEnforcer::NotifyInactiveCallback
notify_inactive_callback) {
auto enforcer = std::make_unique<TestLifetimeEnforcer>(
offscreen_document, std::move(termination_callback),
std::move(notify_inactive_callback));
*lifetime_enforcer_out = enforcer.get();
return enforcer;
}
}
class OffscreenDocumentManagerBrowserTest : public ExtensionApiTest {
public:
OffscreenDocumentManagerBrowserTest() = default;
~OffscreenDocumentManagerBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionApiTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kOffscreenDocumentTesting);
}
OffscreenDocumentHost* CreateDocumentAndWaitForLoad(
const Extension& extension,
const GURL& url,
std::set<api::offscreen::Reason> reasons,
Profile& profile) {
ExtensionHostTestHelper host_waiter(&profile);
host_waiter.RestrictToType(mojom::ViewType::kOffscreenDocument);
OffscreenDocumentHost* offscreen_document =
OffscreenDocumentManager::Get(&profile)->CreateOffscreenDocument(
extension, url, reasons);
host_waiter.WaitForHostCompletedFirstLoad();
return offscreen_document;
}
OffscreenDocumentHost* CreateDocumentAndWaitForLoad(
const Extension& extension,
const GURL& url,
Profile& profile) {
return CreateDocumentAndWaitForLoad(
extension, url, {api::offscreen::Reason::kTesting}, profile);
}
OffscreenDocumentHost* CreateDocumentAndWaitForLoad(
const Extension& extension,
const GURL& url) {
return CreateDocumentAndWaitForLoad(extension, url, *profile());
}
OffscreenDocumentManager* offscreen_document_manager() {
return OffscreenDocumentManager::Get(profile());
}
};
IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest,
CreateOffscreenDocument) {
static constexpr char kManifest[] =
R"({
"name": "Offscreen Document Test",
"manifest_version": 3,
"version": "0.1"
})";
static constexpr char kOffscreenDocumentHtml[] =
R"(<html>
<body>
<div id="signal">Hello, World</div>
</body>
</html>)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
kOffscreenDocumentHtml);
scoped_refptr<const Extension> extension =
LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
EXPECT_EQ(nullptr,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
OffscreenDocumentHost* offscreen_document = CreateDocumentAndWaitForLoad(
*extension, extension->GetResourceURL("offscreen.html"));
{
static constexpr char kScript[] =
R"({
let div = document.getElementById('signal');
div ? div.innerText : '<no div>';
})";
EXPECT_EQ("Hello, World",
content::EvalJs(offscreen_document->host_contents(), kScript));
}
EXPECT_EQ(offscreen_document,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
{
ExtensionHostTestHelper host_waiter(profile());
host_waiter.RestrictToHost(offscreen_document);
DisableExtension(extension->id(), {disable_reason::DISABLE_USER_ACTION});
host_waiter.WaitForHostDestroyed();
}
EXPECT_EQ(nullptr,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
}
IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest,
ClosingDocumentThroughTheManager) {
static constexpr char kManifest[] =
R"({
"name": "Offscreen Document Test",
"manifest_version": 3,
"version": "0.1"
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
"<html>offscreen</html>");
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
const GURL offscreen_url = extension->GetResourceURL("offscreen.html");
OffscreenDocumentHost* offscreen_document =
CreateDocumentAndWaitForLoad(*extension, offscreen_url);
ASSERT_TRUE(offscreen_document);
{
ExtensionHostTestHelper host_waiter(profile());
host_waiter.RestrictToHost(offscreen_document);
offscreen_document_manager()->CloseOffscreenDocumentForExtension(
*extension);
}
EXPECT_EQ(nullptr,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
}
IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest,
CallingWindowCloseInAnOffscreenDocumentClosesIt) {
static constexpr char kManifest[] =
R"({
"name": "Offscreen Document Test",
"manifest_version": 3,
"version": "0.1"
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
"<html>offscreen</html>");
scoped_refptr<const Extension> extension =
LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
OffscreenDocumentHost* offscreen_document = CreateDocumentAndWaitForLoad(
*extension, extension->GetResourceURL("offscreen.html"));
ASSERT_TRUE(offscreen_document);
EXPECT_EQ(offscreen_document,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
{
ExtensionHostTestHelper host_waiter(profile());
host_waiter.RestrictToHost(offscreen_document);
ASSERT_TRUE(content::ExecJs(offscreen_document->host_contents(),
"window.close();"));
host_waiter.WaitForHostDestroyed();
}
EXPECT_EQ(nullptr,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
}
IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest,
LifetimeEnforcement_Terminate) {
TestLifetimeEnforcer* lifetime_enforcer = nullptr;
LifetimeEnforcerFactories::TestingOverride factory_override;
factory_override.map().emplace(
api::offscreen::Reason::kTesting,
base::BindRepeating(&CreateTestLifetimeEnforcer, &lifetime_enforcer));
static constexpr char kManifest[] =
R"({
"name": "Offscreen Document Test",
"manifest_version": 3,
"version": "0.1"
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
"<html>offscreen</html>");
scoped_refptr<const Extension> extension =
LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
OffscreenDocumentHost* offscreen_document = CreateDocumentAndWaitForLoad(
*extension, extension->GetResourceURL("offscreen.html"));
ASSERT_TRUE(offscreen_document);
EXPECT_EQ(offscreen_document,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
ASSERT_TRUE(lifetime_enforcer);
lifetime_enforcer->CallTerminate();
EXPECT_EQ(nullptr,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
}
IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest,
LifetimeEnforcement_NotifyInactive) {
TestLifetimeEnforcer* lifetime_enforcer = nullptr;
LifetimeEnforcerFactories::TestingOverride factory_override;
factory_override.map().emplace(
api::offscreen::Reason::kTesting,
base::BindRepeating(&CreateTestLifetimeEnforcer, &lifetime_enforcer));
static constexpr char kManifest[] =
R"({
"name": "Offscreen Document Test",
"manifest_version": 3,
"version": "0.1"
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
"<html>offscreen</html>");
scoped_refptr<const Extension> extension =
LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
OffscreenDocumentHost* offscreen_document = CreateDocumentAndWaitForLoad(
*extension, extension->GetResourceURL("offscreen.html"));
ASSERT_TRUE(offscreen_document);
EXPECT_EQ(offscreen_document,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
ASSERT_TRUE(lifetime_enforcer);
lifetime_enforcer->SetActive(false);
lifetime_enforcer->CallNotifyInactive();
EXPECT_EQ(nullptr,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
}
IN_PROC_BROWSER_TEST_F(
OffscreenDocumentManagerBrowserTest,
LifetimeEnforcement_DocumentIsNotTerminatedUntilAllInactive) {
TestLifetimeEnforcer* dom_parser_enforcer = nullptr;
LifetimeEnforcerFactories::TestingOverride factory_override;
factory_override.map().emplace(
api::offscreen::Reason::kDomParser,
base::BindRepeating(&CreateTestLifetimeEnforcer, &dom_parser_enforcer));
TestLifetimeEnforcer* blobs_enforcer = nullptr;
factory_override.map().emplace(
api::offscreen::Reason::kBlobs,
base::BindRepeating(&CreateTestLifetimeEnforcer, &blobs_enforcer));
static constexpr char kManifest[] =
R"({
"name": "Offscreen Document Test",
"manifest_version": 3,
"version": "0.1"
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
"<html>offscreen</html>");
scoped_refptr<const Extension> extension =
LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
OffscreenDocumentHost* offscreen_document = CreateDocumentAndWaitForLoad(
*extension, extension->GetResourceURL("offscreen.html"),
{api::offscreen::Reason::kBlobs, api::offscreen::Reason::kDomParser},
*profile());
ASSERT_TRUE(offscreen_document);
EXPECT_EQ(offscreen_document,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
ASSERT_TRUE(dom_parser_enforcer);
ASSERT_TRUE(blobs_enforcer);
dom_parser_enforcer->SetActive(false);
dom_parser_enforcer->CallNotifyInactive();
EXPECT_EQ(offscreen_document,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
dom_parser_enforcer->SetActive(true);
dom_parser_enforcer->SetActive(false);
dom_parser_enforcer->CallNotifyInactive();
EXPECT_EQ(offscreen_document,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
dom_parser_enforcer->SetActive(true);
blobs_enforcer->SetActive(false);
blobs_enforcer->CallNotifyInactive();
EXPECT_EQ(offscreen_document,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
dom_parser_enforcer->SetActive(false);
dom_parser_enforcer->CallNotifyInactive();
EXPECT_EQ(nullptr,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
}
IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest,
IncognitoOffscreenDocuments) {
static constexpr char kManifest[] =
R"({
"name": "Offscreen Document Test",
"manifest_version": 3,
"version": "0.1",
"incognito": "split"
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
"<html>offscreen</html>");
scoped_refptr<const Extension> extension =
LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
{
TestExtensionRegistryObserver registry_observer(
ExtensionRegistry::Get(profile()), extension->id());
util::SetIsIncognitoEnabled(extension->id(), profile(),
true);
extension = registry_observer.WaitForExtensionLoaded();
}
ASSERT_TRUE(extension);
ASSERT_TRUE(util::IsIncognitoEnabled(extension->id(), profile()));
const GURL offscreen_url = extension->GetResourceURL("offscreen.html");
OffscreenDocumentHost* on_the_record_host =
CreateDocumentAndWaitForLoad(*extension, offscreen_url);
ASSERT_TRUE(on_the_record_host);
EXPECT_FALSE(on_the_record_host->host_contents()
->GetBrowserContext()
->IsOffTheRecord());
#if BUILDFLAG(ENABLE_EXTENSIONS)
Browser* incognito_browser = CreateIncognitoBrowser();
ASSERT_TRUE(incognito_browser);
Profile* incognito_profile = incognito_browser->profile();
#else
Profile* incognito_profile =
profile()->GetPrimaryOTRProfile(true);
#endif
OffscreenDocumentHost* incognito_host = CreateDocumentAndWaitForLoad(
*extension, offscreen_url, *incognito_profile);
ASSERT_TRUE(incognito_host);
EXPECT_TRUE(
incognito_host->host_contents()->GetBrowserContext()->IsOffTheRecord());
EXPECT_NE(incognito_host, on_the_record_host);
EXPECT_EQ(Profile::FromBrowserContext(
on_the_record_host->host_contents()->GetBrowserContext()),
Profile::FromBrowserContext(
incognito_host->host_contents()->GetBrowserContext())
->GetOriginalProfile());
EXPECT_EQ(on_the_record_host,
OffscreenDocumentManager::Get(profile())
->GetOffscreenDocumentForExtension(*extension));
EXPECT_EQ(incognito_host, OffscreenDocumentManager::Get(incognito_profile)
->GetOffscreenDocumentForExtension(*extension));
{
ExtensionHostTestHelper host_waiter(incognito_profile);
host_waiter.RestrictToHost(incognito_host);
#if BUILDFLAG(IS_ANDROID)
ProfileDestroyer::DestroyOTRProfileWhenAppropriate(incognito_profile);
#else
CloseBrowserSynchronously(incognito_browser);
#endif
host_waiter.WaitForHostDestroyed();
}
EXPECT_EQ(on_the_record_host,
offscreen_document_manager()->GetOffscreenDocumentForExtension(
*extension));
}
}