#include "base/functional/callback_helpers.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/version_info/channel.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/api/offscreen/audio_lifetime_enforcer.h"
#include "extensions/browser/api/offscreen/offscreen_api.h"
#include "extensions/browser/api/offscreen/offscreen_document_manager.h"
#include "extensions/browser/background_script_executor.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/lazy_context_id.h"
#include "extensions/browser/lazy_context_task_queue.h"
#include "extensions/browser/offscreen_document_host.h"
#include "extensions/browser/script_executor.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/common/extension.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/switches.h"
#include "extensions/test/extension_background_page_waiter.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/test/gtest_tags.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "content/public/common/content_client.h"
#endif
namespace extensions {
namespace {
class AudioWaiter : public content::WebContentsObserver { … };
scoped_refptr<const Extension> SetExtensionIncognitoEnabled(
const Extension& extension,
Profile& profile) { … }
void WakeUpServiceWorker(const Extension& extension, Profile& profile) { … }
#if BUILDFLAG(IS_CHROMEOS_ASH)
class ContentBrowserClientMock : public ChromeContentBrowserClient {
public:
MOCK_METHOD(void,
CheckGetAllScreensMediaAllowed,
(content::RenderFrameHost * render_frame_host,
base::OnceCallback<void(bool)> callback),
(override));
};
#endif
}
class OffscreenApiTest : public ExtensionApiTest { … };
#if BUILDFLAG(IS_MAC)
#define MAYBE_BasicDocumentManagement …
#else
#define MAYBE_BasicDocumentManagement …
#endif
IN_PROC_BROWSER_TEST_F(OffscreenApiTest, MAYBE_BasicDocumentManagement) { … }
#if defined(ADDRESS_SANITIZER) || BUILDFLAG(IS_MAC)
#define MAYBE_IncognitoModeHandling_SplitMode …
#else
#define MAYBE_IncognitoModeHandling_SplitMode …
#endif
IN_PROC_BROWSER_TEST_F(OffscreenApiTest,
MAYBE_IncognitoModeHandling_SplitMode) { … }
#if defined(ADDRESS_SANITIZER) || BUILDFLAG(IS_MAC)
#define MAYBE_IncognitoModeHandling_SpanningMode …
#else
#define MAYBE_IncognitoModeHandling_SpanningMode …
#endif
IN_PROC_BROWSER_TEST_F(OffscreenApiTest,
MAYBE_IncognitoModeHandling_SpanningMode) { … }
IN_PROC_BROWSER_TEST_F(OffscreenApiTest, LifetimeEnforcement) { … }
IN_PROC_BROWSER_TEST_F(OffscreenApiTest, OpenAndImmediatelyCloseDocument) { … }
#if BUILDFLAG(IS_CHROMEOS_ASH)
class GetAllScreensMediaOffscreenApiTest : public OffscreenApiTest {
public:
GetAllScreensMediaOffscreenApiTest() = default;
~GetAllScreensMediaOffscreenApiTest() override = default;
void SetUpOnMainThread() override {
OffscreenApiTest::SetUpOnMainThread();
browser_client_ = std::make_unique<ContentBrowserClientMock>();
content::SetBrowserClientForTesting(browser_client_.get());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
OffscreenApiTest::SetUpCommandLine(command_line);
scoped_feature_list_.InitFromCommandLine(
"GetAllScreensMedia",
"");
}
protected:
ContentBrowserClientMock& content_browser_client() {
return *browser_client_;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<ContentBrowserClientMock> browser_client_;
};
IN_PROC_BROWSER_TEST_F(GetAllScreensMediaOffscreenApiTest,
GetAllScreensMediaAllowed) {
base::AddTagToTestResult("feature_id",
"screenplay-f3601ae4-bff7-495a-a51f-3c0997a46445");
EXPECT_CALL(content_browser_client(),
CheckGetAllScreensMediaAllowed(testing::_, testing::_))
.WillOnce(testing::Invoke([](content::RenderFrameHost* render_frame_host,
base::OnceCallback<void(bool)> callback) {
std::move(callback).Run(true);
}));
static constexpr char kManifest[] =
R"({
"name": "Offscreen Document Test",
"manifest_version": 3,
"version": "0.1",
"background": {"service_worker": "background.js"},
"permissions": ["offscreen"]
})";
static constexpr char kOffscreenJs[] =
R"(
'use strict';
let streams;
async function captureAllScreens() {
try {
streams = await navigator.mediaDevices.getAllScreensMedia();
if (streams === null || streams.length == 0) {
return false;
}
let allStreamsOk = true;
streams.forEach((stream) => {
const videoTracks = stream.getVideoTracks();
if (videoTracks.length == 0) {
allStreamsOk = false;
return;
}
const videoTrack = videoTracks[0];
if (typeof videoTrack.screenDetailed !== "function") {
allStreamsOk = false;
return;
}
});
chrome.test.sendScriptResult(allStreamsOk);
return;
} catch(e) {
console.error('Unexcpected exception: ' + e);
chrome.test.sendScriptResult(false);
}
}
function stopCapture() {
streams.forEach((stream) => {
stream.getVideoTracks()[0].stop();
});
}
chrome.runtime.onMessage.addListener(async (msg) => {
if (msg == 'capture') {
await captureAllScreens();
} else if (msg == 'stop') {
stopCapture();
} else {
console.error('Unexpected message: ' + msg);
}
})
R)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "// Blank.");
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
R"(
<html>
<script src="offscreen.js"></script>
<meta http-equiv="Content-Security-Policy"
content="object-src 'none'; base-uri 'none';
script-src 'strict-dynamic'
'sha256-Y55VppSZfjQ4A035BPDo9OMXignyoxRXv+KKCZpnWiM=';
require-trusted-types-for 'script';trusted-types a;">
</html>
)");
test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.js"), kOffscreenJs);
scoped_refptr<const Extension> extension =
LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
OffscreenDocumentManager* manager = OffscreenDocumentManager::Get(profile());
ProgrammaticallyCreateOffscreenDocument(*extension, *profile(),
"DISPLAY_MEDIA");
OffscreenDocumentHost* document =
manager->GetOffscreenDocumentForExtension(*extension);
ASSERT_TRUE(document);
content::WaitForLoadStop(document->host_contents());
{
base::Value result = BackgroundScriptExecutor::ExecuteScript(
profile(), extension->id(), "chrome.runtime.sendMessage('capture');",
BackgroundScriptExecutor::ResultCapture::kSendScriptResult);
ASSERT_TRUE(result.is_bool());
EXPECT_TRUE(result.GetBool());
}
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(manager->GetOffscreenDocumentForExtension(*extension));
{
BackgroundScriptExecutor::ExecuteScriptAsync(
profile(), extension->id(), "chrome.runtime.sendMessage('stop');");
}
}
#endif
#if BUILDFLAG(IS_WIN)
#define MAYBE_TabCaptureStreams …
#else
#define MAYBE_TabCaptureStreams …
#endif
IN_PROC_BROWSER_TEST_F(OffscreenApiTest, MAYBE_TabCaptureStreams) { … }
class OffscreenApiTestWithoutCommandLineFlag : public OffscreenApiTest { … };
IN_PROC_BROWSER_TEST_F(OffscreenApiTest, LongLoadOffscreenDocument) { … }
IN_PROC_BROWSER_TEST_F(OffscreenApiTest,
UserGesturesAreCurriedFromServiceWorkers) { … }
IN_PROC_BROWSER_TEST_F(OffscreenApiTestWithoutCommandLineFlag,
TestingReasonNotAllowed) { … }
}