blob: 731679e4bf9439162177f6e6d7fb93ab297f66df [file]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.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/process_map.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/file_util.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "third_party/blink/public/common/features.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
namespace extensions {
enum class ManifestVersion { TWO, THREE };
class SandboxedPagesTest
: public ExtensionApiTest,
public ::testing::WithParamInterface<ManifestVersion> {
public:
SandboxedPagesTest() = default;
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
[[nodiscard]] bool RunTest(const char* extension_name,
const char* manifest,
const RunOptions& run_options,
const LoadOptions& load_options) {
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
// Load the extension with the given `manifest`.
if (!temp_dir_.CreateUniqueTempDir()) {
ADD_FAILURE() << "Could not create temporary dir for test";
return false;
}
base::FilePath source_extension_path =
test_data_dir_.AppendASCII(extension_name);
base::FilePath destination_extension_path =
temp_dir_.GetPath().AppendASCII(extension_name);
if (!base::CopyDirectory(source_extension_path, destination_extension_path,
true /* recursive */)) {
ADD_FAILURE() << source_extension_path.value()
<< " could not be copied to "
<< destination_extension_path.value();
return false;
}
test_data_dir_ = temp_dir_.GetPath();
base::FilePath manifest_path =
destination_extension_path.Append(kManifestFilename);
if (!base::WriteFile(manifest_path, manifest)) {
ADD_FAILURE() << "Could not write manifest file to "
<< manifest_path.value();
return false;
}
return RunExtensionTest(extension_name, run_options, load_options);
}
private:
base::ScopedTempDir temp_dir_;
};
// A test class to verify operation of metrics to record use of extension API
// functions in extensions pages that are sandboxed, but not listed as sandboxed
// in the extension's manifest. This class is parameterized on
// kIsolateSandboxedIframes so that it tests both in-process and
// process-isolated sandboxed frames.
class SandboxAPIMetricsTest : public ExtensionApiTest,
public ::testing::WithParamInterface<bool> {
public:
SandboxAPIMetricsTest() {
if (GetParam()) {
feature_list_.InitAndEnableFeature(
blink::features::kIsolateSandboxedIframes);
} else {
feature_list_.InitAndDisableFeature(
blink::features::kIsolateSandboxedIframes);
}
}
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
private:
base::ScopedTempDir temp_dir_;
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(,
SandboxedPagesTest,
#if BUILDFLAG(IS_ANDROID)
// Android only supports manifest V3.
::testing::Values(ManifestVersion::THREE));
#else
::testing::Values(ManifestVersion::TWO,
ManifestVersion::THREE));
#endif
IN_PROC_BROWSER_TEST_P(SandboxedPagesTest, SandboxedPages) {
const char* kManifestV2 = R"(
{
"name": "Extension with sandboxed pages",
"manifest_version": 2,
"version": "0.1",
"sandbox": {
"pages": ["sandboxed.html"]
}
}
)";
const char* kManifestV3 = R"(
{
"name": "Extension with sandboxed pages",
"manifest_version": 3,
"version": "0.1",
"sandbox": {
"pages": ["sandboxed.html"]
}
}
)";
const char* kManifest =
GetParam() == ManifestVersion::TWO ? kManifestV2 : kManifestV3;
EXPECT_TRUE(
RunTest("sandboxed_pages", kManifest, {.extension_url = "main.html"}, {}))
<< message_;
}
#if !BUILDFLAG(IS_ANDROID)
// Verifies the behavior of sandboxed pages in Manifest V2. Remote frames
// should be disallowed. Android only supports Manifest V3, so this test is
// skipped on Android.
IN_PROC_BROWSER_TEST_F(SandboxedPagesTest, ManifestV2DisallowsWebContent) {
ASSERT_TRUE(StartEmbeddedTestServer());
const char* kManifest = R"(
{
"name": "Tests that loading web content fails inside sandboxed pages",
"manifest_version": 2,
"version": "0.1",
"web_accessible_resources": ["local_frame.html", "remote_frame.html"],
"sandbox": {
"pages": ["sandboxed.html"],
"content_security_policy": "sandbox allow-scripts; child-src *;"
}
}
)";
// This extension attempts to load remote web content inside a sandboxed page.
// Loading web content will fail because of CSP. In addition to that we will
// show manifest warnings, hence ignore_manifest_warnings is set to true.
ASSERT_TRUE(RunTest("sandboxed_pages_csp", kManifest,
{.extension_url = "main.html"},
{.ignore_manifest_warnings = true}))
<< message_;
}
#endif // !BUILDFLAG(IS_ANDROID)
// Verifies the behavior of sandboxed pages in Manifest V3. Remote frames
// should be allowed.
IN_PROC_BROWSER_TEST_F(SandboxedPagesTest, ManifestV3AllowsWebContent) {
ASSERT_TRUE(StartEmbeddedTestServer());
static constexpr char kManifest[] =
R"({
"name": "test extension",
"version": "0.1",
"manifest_version": 3,
"content_security_policy": {
"sandbox": "sandbox allow-scripts; child-src *;"
},
"sandbox": { "pages": ["sandboxed.html"] }
})";
static constexpr char kSandboxedHtml[] =
R"(<html>
<body>Sandboxed Page</body>
<script>
var iframe = document.createElement('iframe');
iframe.src = 'http://example.com:%d/extensions/echo_message.html';
// Check that we can post-message the frame.
addEventListener('message', (e) => {
// Note: We use domAutomationController here (and
// DOMMessageQueue below) because since this is a sandboxed page,
// it doesn't have access to any chrome.* APIs, including
// chrome.test.
domAutomationController.send(e.data);
});
iframe.onload = () => {
iframe.contentWindow.postMessage('hello', '*');
};
document.body.appendChild(iframe);
</script>
</html>)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(
FILE_PATH_LITERAL("sandboxed.html"),
base::StringPrintf(kSandboxedHtml, embedded_test_server()->port()));
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
content::DOMMessageQueue message_queue;
content::WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(content::NavigateToURL(
web_contents, extension->GetResourceURL("sandboxed.html")));
content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
ASSERT_TRUE(frame_host);
// The frame should be sandboxed, so the origin should be "null" (as opposed
// to `extension->origin()`).
EXPECT_EQ("null", frame_host->GetLastCommittedOrigin().Serialize());
std::string message;
ASSERT_TRUE(message_queue.WaitForMessage(&message));
EXPECT_EQ(R"("echo hello")", message);
}
// This test has an API function access inside a frame sandboxed via HTML
// attributes (rather than the manifest specification); it should trigger a
// histogram count.
IN_PROC_BROWSER_TEST_P(SandboxAPIMetricsTest,
SandboxedApiAccessTriggersHistogramCounts) {
ASSERT_TRUE(StartEmbeddedTestServer());
static constexpr char kManifest[] =
R"({
"name": "test extension",
"version": "0.1",
"manifest_version": 3
})";
static constexpr char kPageWithSandboxedFrame[] =
R"(<html>
<body>
<h1>Page with Sandboxed Frame</h1>
<iframe sandbox="allow-scripts" src="sandboxed_page.html"></iframe>
</body>
</html>)";
static constexpr char kSandboxedScriptSrc[] =
R"((async function hasAccessToExtensionAPIs() {
try {
// Use chrome.extension because it is available on Android.
let allowed = await chrome.extension.isAllowedIncognitoAccess();
// Intentionally check the type and the false value.
return allowed === false;
} catch(err) {
return false;
}
})().then(result => domAutomationController.send(result));
)";
static constexpr char kSandboxedPage[] =
R"(<html>
<body>
<h1>Sandboxed Page</h1>
<script src="sandboxed.js"></script>
</body>
</html>)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("main.html"), kPageWithSandboxedFrame);
test_dir.WriteFile(FILE_PATH_LITERAL("sandboxed.js"), kSandboxedScriptSrc);
test_dir.WriteFile(FILE_PATH_LITERAL("sandboxed_page.html"), kSandboxedPage);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
// Prepare histogram.
base::HistogramTester histograms;
const char* kHistogramName =
"Extensions.Functions.DidSandboxedExtensionAPICall";
// Use message queue to verify that loading of the sandboxed child completed
// successfully.
content::DOMMessageQueue message_queue;
content::WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(content::NavigateToURL(web_contents,
extension->GetResourceURL("main.html")));
content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
ASSERT_TRUE(frame_host);
// Verify the sandboxed frame loaded and has api access.
std::string message;
ASSERT_TRUE(message_queue.WaitForMessage(&message));
EXPECT_EQ("true", message);
// Verify histogram count captured exactly one API call from the sandboxed
// frame.
histograms.ExpectBucketCount(kHistogramName, true, 1u);
}
// This test is nearly identical to ApiAccessTriggersHistogramCounts, except
// that the API access is in the (non-sandboxed) main frame, and shouldn't
// trigger a count.
IN_PROC_BROWSER_TEST_P(SandboxAPIMetricsTest,
NonSandboxedApiAccessDoesntTriggerHistogramCounts) {
ASSERT_TRUE(StartEmbeddedTestServer());
static constexpr char kManifest[] =
R"({
"name": "test extension",
"version": "0.1",
"manifest_version": 3
})";
static constexpr char kMainScriptSrc[] =
R"(window.onload = async () => {
let hasApiAccess = true;
try {
// Use chrome.extension because it is available on Android.
let allowed = await chrome.extension.isAllowedIncognitoAccess();
// Intentionally check the type and the false value.
hasApiAccess = allowed === false;
} catch(err) {
hasApiAccess = false;
}
domAutomationController.send(hasApiAccess);
};)";
static constexpr char kPageWithSandboxedFrame[] =
R"(<html>
<head>
<script src="main.js"></script>
</head>
<body>
<h1>Page with Sandboxed Frame</h1>
<iframe sandbox="allow-scripts" src="sandboxed_page.html"></iframe>
</body>
</html>)";
static constexpr char kSandboxedPage[] =
R"(<html>
<body>
<h1>Sandboxed Page</h1>
</body>
</html>)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("main.js"), kMainScriptSrc);
test_dir.WriteFile(FILE_PATH_LITERAL("main.html"), kPageWithSandboxedFrame);
test_dir.WriteFile(FILE_PATH_LITERAL("sandboxed_page.html"), kSandboxedPage);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
// Prepare histogram.
base::HistogramTester histograms;
const char* kHistogramName =
"Extensions.Functions.DidSandboxedExtensionAPICall";
// Use message queue to verify that loading of the sandboxed child completed
// successfully.
content::DOMMessageQueue message_queue;
content::WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(content::NavigateToURL(web_contents,
extension->GetResourceURL("main.html")));
content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
ASSERT_TRUE(frame_host);
// Verify the sandboxed frame loaded.
std::string message;
ASSERT_TRUE(message_queue.WaitForMessage(&message));
EXPECT_EQ("true", message);
// Verify histogram count captured no API calls from the non-sandboxed frame.
histograms.ExpectBucketCount(kHistogramName, true, 0u);
}
INSTANTIATE_TEST_SUITE_P(All,
SandboxAPIMetricsTest,
::testing::Bool(),
[](const testing::TestParamInfo<bool>& info) {
return info.param
? "kIsolateSandboxedIframesEnabled"
: "kIsolateSandboxedIframesDisabled";
});
// Verify sandbox behavior.
IN_PROC_BROWSER_TEST_P(SandboxedPagesTest, WebAccessibleResourcesTest) {
ASSERT_TRUE(embedded_test_server()->Start());
// Install extension.
TestExtensionDir extension_dir;
static constexpr char kManifestV2[] = R"({
"name": "Extension sandbox text",
"version": "1.0",
"manifest_version": 2,
"sandbox": {
"pages": ["sandboxed_page.html"]
},
"web_accessible_resources": [
"web_accessible_resource.html"
]
})";
static constexpr char kManifestV3[] =
R"({
"name": "Extension sandbox text",
"version": "1.0",
"manifest_version": 3,
"sandbox": {
"pages": ["sandboxed_page.html"]
},
"web_accessible_resources": [{
"resources": ["web_accessible_resource.html"],
"matches": ["<all_urls>"]
}]
})";
const char* manifest =
GetParam() == ManifestVersion::TWO ? kManifestV2 : kManifestV3;
extension_dir.WriteManifest(manifest);
extension_dir.WriteFile(FILE_PATH_LITERAL("sandboxed_page.html"), "");
extension_dir.WriteFile(FILE_PATH_LITERAL("page.html"), "");
extension_dir.WriteFile(FILE_PATH_LITERAL("resource.html"), "resource.html");
extension_dir.WriteFile(FILE_PATH_LITERAL("web_accessible_resource.html"),
"web_accessible_resource.html");
const Extension* extension = LoadExtension(extension_dir.UnpackedPath());
ASSERT_TRUE(extension);
// Fetch url from frame to verify histograms match expectations.
auto test_frame_with_fetch = [&](const char* frame_url, const char* fetch_url,
bool is_web_accessible_resource, int count,
std::string expected_frame_origin) {
// Fetch and test resource.
content::WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(content::NavigateToURL(web_contents,
extension->GetResourceURL(frame_url)));
constexpr char kFetchScriptTemplate[] =
R"(
fetch($1).then(result => {
return result.text();
}).catch(err => {
return String(err);
});)";
EXPECT_EQ(content::EvalJs(
web_contents,
content::JsReplace(kFetchScriptTemplate,
extension->GetResourceURL(fetch_url))),
fetch_url);
EXPECT_EQ(expected_frame_origin, web_contents->GetPrimaryMainFrame()
->GetLastCommittedOrigin()
.Serialize());
};
// Extension page fetching an extension file.
test_frame_with_fetch("page.html", "resource.html", false, 0,
extension->origin().Serialize());
// Extension page fetching a web accessible resource.
test_frame_with_fetch("page.html", "web_accessible_resource.html", true, 0,
extension->origin().Serialize());
// Sandboxed extension page fetching an extension file.
test_frame_with_fetch("sandboxed_page.html", "resource.html", false, 1,
"null");
// Sandboxed extension page fetching a web accessible resource.
test_frame_with_fetch("sandboxed_page.html", "web_accessible_resource.html",
true, 1, "null");
}
// Verifies that MV3 sandboxed pages don't have access to extension messaging
// APIs.
IN_PROC_BROWSER_TEST_F(SandboxedPagesTest,
ManifestV3MessagingBindingsWithheld) {
static constexpr char kManifest[] =
R"({
"name": "Sandboxed API exposure test",
"version": "0.1",
"manifest_version": 3,
"sandbox": { "pages": ["sandboxed.html"] }
})";
static constexpr char kSandboxedHtml[] =
R"(<html><body>Sandboxed Page</body></html>)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("sandboxed.html"), kSandboxedHtml);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
content::WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(content::NavigateToURL(
web_contents, extension->GetResourceURL("sandboxed.html")));
EXPECT_EQ("undefined",
content::EvalJs(web_contents, "typeof chrome.runtime"));
// Sandboxed pages are hosted in a process that isn't tracked in the
// process map.
EXPECT_FALSE(ProcessMap::Get(profile())->Contains(
extension->id(),
web_contents->GetPrimaryMainFrame()->GetProcess()->GetID()));
}
// Verifies that requesting a sandboxed page using percent-encoding in the path
// (e.g. "sandboxed%2Ehtml") is still recognized as a sandboxed page.
// Regression test for https://crbug.com/538969297.
IN_PROC_BROWSER_TEST_F(SandboxedPagesTest, PercentEncodedSandboxedPagePath) {
static constexpr char kManifest[] =
R"({
"name": "Percent-encoded sandboxed page test",
"version": "0.1",
"manifest_version": 3,
"sandbox": { "pages": ["sandboxed.html"] }
})";
static constexpr char kSandboxedHtml[] =
R"(<html><body>Sandboxed Page</body></html>)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("sandboxed.html"), kSandboxedHtml);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
content::WebContents* web_contents = GetActiveWebContents();
GURL percent_encoded_url(extension->url().spec() + "sandboxed%2Ehtml");
ASSERT_TRUE(content::NavigateToURL(web_contents, percent_encoded_url));
content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
ASSERT_TRUE(frame_host);
// The frame should be sandboxed, so the origin should be "null".
EXPECT_EQ("null", frame_host->GetLastCommittedOrigin().Serialize());
// Extension APIs like `chrome.runtime` should be withheld.
EXPECT_EQ("undefined",
content::EvalJs(web_contents, "typeof chrome.runtime"));
// Sandboxed pages are hosted in a process that isn't tracked in the
// process map.
EXPECT_FALSE(ProcessMap::Get(profile())->Contains(
extension->id(), frame_host->GetProcess()->GetID()));
}
// Verifies that requesting a sandboxed page using case-variant path (e.g.
// "Sandboxed.html") is still recognized as a sandboxed page.
// Regression test for https://crbug.com/542355360.
IN_PROC_BROWSER_TEST_F(SandboxedPagesTest, CaseInsensitiveSandboxedPagePath) {
static constexpr char kManifest[] =
R"({
"name": "Case-insensitive sandboxed page test",
"version": "0.1",
"manifest_version": 3,
"sandbox": { "pages": ["sandboxed.html", "café.html"] }
})";
static constexpr char kSandboxedHtml[] =
R"(<html><body>Sandboxed Page</body></html>)";
static constexpr char kCafeHtml[] =
R"(<html>
<head><meta charset="utf-8"></head><body>Café Page</body>
</html>)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
// Write the case-variant files first so that on case-sensitive filesystems
// (like Linux) the files exist to be loaded, but on case-insensitive
// filesystems (macOS, Windows) the canonical lowercase files are written
// second.
test_dir.WriteFile(FILE_PATH_LITERAL("Sandboxed.html"), kSandboxedHtml);
test_dir.WriteFile(FILE_PATH_LITERAL("sandboxed.html"), kSandboxedHtml);
test_dir.WriteFile(FILE_PATH_LITERAL("CAFÉ.html"), kCafeHtml);
test_dir.WriteFile(FILE_PATH_LITERAL("café.html"), kCafeHtml);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
content::WebContents* web_contents = GetActiveWebContents();
// Test ASCII case mismatch.
{
// Note: Deliberately don't use Extension::GetResourceURL(), which goes
// through additional sanitization checks.
GURL uppercase_url = extension->url().Resolve("Sandboxed.html");
ASSERT_TRUE(NavigateToURL(web_contents, uppercase_url));
// The page should load, but should be properly sandboxed: it shouldn't
// have extension APIs or be hosted in a trusted process.
content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
ASSERT_TRUE(frame_host);
EXPECT_FALSE(frame_host->IsErrorDocument());
EXPECT_EQ("Sandboxed Page",
content::EvalJs(web_contents, "document.body.innerText"));
EXPECT_EQ("null", frame_host->GetLastCommittedOrigin().Serialize());
EXPECT_EQ("undefined",
content::EvalJs(web_contents, "typeof chrome.runtime"));
EXPECT_FALSE(ProcessMap::Get(profile())->Contains(
extension->id(), frame_host->GetProcess()->GetID()));
}
// Test non-ASCII / Unicode UTF-8 case mismatch.
{
// Note: Deliberately don't use Extension::GetResourceURL(), which goes
// through additional sanitization checks.
GURL uppercase_url = extension->url().Resolve("CAFÉ.html");
ASSERT_TRUE(NavigateToURL(web_contents, uppercase_url));
// The page should load, but should be properly sandboxed: it shouldn't
// have extension APIs or be hosted in a trusted process.
content::RenderFrameHost* frame_host = web_contents->GetPrimaryMainFrame();
ASSERT_TRUE(frame_host);
EXPECT_FALSE(frame_host->IsErrorDocument());
EXPECT_EQ("Café Page",
content::EvalJs(web_contents, "document.body.innerText"));
EXPECT_EQ("null", frame_host->GetLastCommittedOrigin().Serialize());
EXPECT_EQ("undefined",
content::EvalJs(web_contents, "typeof chrome.runtime"));
EXPECT_FALSE(ProcessMap::Get(profile())->Contains(
extension->id(), frame_host->GetProcess()->GetID()));
}
}
// Pages that are sandboxed with the HTML5 `sandbox` attribute are treated
// differently from pages specified in the "sandbox" attribute in the manifest.
// These pages *do* get extension APIs.
IN_PROC_BROWSER_TEST_F(SandboxedPagesTest,
Html5SandboxedIframeMessagingBindingsExposed) {
// Load an extension with an HTML5-sandbox'd page that ping-pongs a message to
// its service worker.
static constexpr char kManifest[] =
R"({
"name": "HTML5 Sandboxed API exposure test",
"version": "0.1",
"manifest_version": 3,
"background": {"service_worker": "background.js"}
})";
static constexpr char kBackgroundJs[] =
R"(chrome.runtime.onMessage.addListener(
(message, sender, sendResponse) => {
sendResponse(`ack ${message}`);
});)";
static constexpr char kMainHtml[] =
R"(<html>
<body>
<h1>Main Page</h1>
<iframe sandbox="allow-scripts" src="child.html"></iframe>
</body>
</html>)";
static constexpr char kChildHtml[] =
R"(<html>
<body>Child Page</body>
<script src="child.js"></script>
</html>)";
static constexpr char kChildJs[] =
R"(chrome.runtime.sendMessage('hello', (response) => {
domAutomationController.send(response);
});)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
test_dir.WriteFile(FILE_PATH_LITERAL("main.html"), kMainHtml);
test_dir.WriteFile(FILE_PATH_LITERAL("child.html"), kChildHtml);
test_dir.WriteFile(FILE_PATH_LITERAL("child.js"), kChildJs);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
content::DOMMessageQueue message_queue;
content::WebContents* web_contents = GetActiveWebContents();
ASSERT_TRUE(content::NavigateToURL(web_contents,
extension->GetResourceURL("main.html")));
std::string message;
ASSERT_TRUE(message_queue.WaitForMessage(&message));
EXPECT_EQ(R"("ack hello")", message);
// Unlike manifest-sandboxed pages, pages sandboxed with the HTML5 attribute
// are hosted in the normal extension process and do have access to the
// messaging APIs.
content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
content::RenderFrameHost* child_frame = content::ChildFrameAt(main_frame, 0);
EXPECT_EQ(main_frame->GetProcess(), child_frame->GetProcess());
EXPECT_TRUE(ProcessMap::Get(profile())->Contains(
extension->id(), child_frame->GetProcess()->GetID()));
}
} // namespace extensions