Commit b269640d authored by Kevin Marshall's avatar Kevin Marshall Committed by Commit Bot

Fuchsia: Add ExecuteJavaScript() to Frame FIDL interface.

Enables Frame clients to inject arbitrary JS into browser frames,
so that Fuchsia native applications may interact with web content.

* Define and implement ScriptInjector Mojo service for RenderFrames.
* Rename AllowInjectingJavaScriptForAndroidWebView() to AllowInjectingJavaScript(),
to reflect the fact that this feature is no longer exclusive to Android.

Bug: 893236
Change-Id: I25b7364db21662af8a763e0788ddb3719d91328a
Reviewed-on: https://chromium-review.googlesource.com/c/1295173
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@google.com>
Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarNasko Oskov <nasko@chromium.org>
Reviewed-by: default avatarWez <wez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#604811}
parent 1b79ddef
......@@ -118,7 +118,7 @@ void AwBrowserMainParts::PreMainMessageLoopRun() {
AwBrowserContext* context = browser_client_->InitBrowserContext();
context->PreMainMessageLoopRun(browser_client_->GetNetLog());
content::RenderFrameHost::AllowInjectingJavaScriptForAndroidWebView();
content::RenderFrameHost::AllowInjectingJavaScript();
}
bool AwBrowserMainParts::MainMessageLoopRun(int* result_code) {
......
......@@ -214,9 +214,9 @@ int g_next_accessibility_reset_token = 1;
// The next value to use for the javascript callback id.
int g_next_javascript_callback_id = 1;
#if defined(OS_ANDROID)
// Whether to allow injecting javascript into any kind of frame (for Android
// WebView).
#if defined(OS_ANDROID) || defined(OS_FUCHSIA)
// Whether to allow injecting javascript into any kind of frame, for Android
// WebView and Fuchsia web.ContextProvider.
bool g_allow_injecting_javascript = false;
#endif
......@@ -605,12 +605,12 @@ RenderFrameHost* RenderFrameHost::FromID(int render_process_id,
return RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
}
#if defined(OS_ANDROID)
#if defined(OS_ANDROID) || defined(OS_FUCHSIA)
// static
void RenderFrameHost::AllowInjectingJavaScriptForAndroidWebView() {
void RenderFrameHost::AllowInjectingJavaScript() {
g_allow_injecting_javascript = true;
}
#endif // defined(OS_ANDROID)
#endif // defined(OS_ANDROID) || defined(OS_FUCHSIA)
// static
RenderFrameHostImpl* RenderFrameHostImpl::FromID(int process_id,
......@@ -5090,7 +5090,7 @@ bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactoryInternal(
}
bool RenderFrameHostImpl::CanExecuteJavaScript() {
#if defined(OS_ANDROID)
#if defined(OS_ANDROID) || defined(OS_FUCHSIA)
if (g_allow_injecting_javascript)
return true;
#endif
......
......@@ -72,11 +72,11 @@ class CONTENT_EXPORT RenderFrameHost : public IPC::Listener,
// Returns nullptr if the IDs do not correspond to a live RenderFrameHost.
static RenderFrameHost* FromID(int render_process_id, int render_frame_id);
#if defined(OS_ANDROID)
#if defined(OS_ANDROID) || defined(OS_FUCHSIA)
// Globally allows for injecting JavaScript into the main world. This feature
// is present only to support Android WebView and must not be used in other
// configurations.
static void AllowInjectingJavaScriptForAndroidWebView();
// is present only to support Android WebView and Fuchsia web.Contexts, and
// must not be used in other configurations.
static void AllowInjectingJavaScript();
#endif
// Returns a RenderFrameHost given its accessibility tree ID.
......
......@@ -7,6 +7,7 @@ assert(is_fuchsia)
import("//build/config/fuchsia/fidl_library.gni")
import("//build/config/fuchsia/rules.gni")
import("//build/util/process_version.gni")
import("//mojo/public/tools/bindings/mojom.gni")
import("//testing/test.gni")
import("//tools/grit/repack.gni")
......@@ -110,8 +111,19 @@ fuchsia_package_runner("service_runner") {
install_only = true
}
mojom("mojo_bindings") {
sources = [
"common/on_load_script_injector.mojom",
]
public_deps = [
"//mojo/public/mojom/base",
]
}
component("service_lib") {
deps = [
":mojo_bindings",
":service_pak",
"//base",
"//components/version_info",
......@@ -162,6 +174,10 @@ component("service_lib") {
"common/webrunner_content_client.cc",
"common/webrunner_content_client.h",
"common/webrunner_export.h",
"renderer/on_load_script_injector.cc",
"renderer/on_load_script_injector.h",
"renderer/webrunner_content_renderer_client.cc",
"renderer/webrunner_content_renderer_client.h",
"service/common.cc",
"service/common.h",
"service/context_provider_impl.cc",
......@@ -215,6 +231,8 @@ test("webrunner_browsertests") {
sources = [
"browser/context_impl_browsertest.cc",
"browser/frame_impl_browsertest.cc",
"browser/run_with_timeout.cc",
"browser/run_with_timeout.h",
"browser/test_common.cc",
"browser/test_common.h",
"browser/webrunner_browser_test.cc",
......
include_rules = [
"+content/public/browser",
"+content/public/test",
"+third_party/blink/public/common/associated_interfaces",
"+ui/aura",
"+ui/display",
"+ui/ozone/public",
......
......@@ -32,6 +32,10 @@ void ContextImpl::DestroyFrame(FrameImpl* frame) {
frames_.erase(frames_.find(frame));
}
bool ContextImpl::IsJavaScriptInjectionAllowed() {
return allow_javascript_injection_;
}
FrameImpl* ContextImpl::GetFrameImplForTest(
chromium::web::FramePtr* frame_ptr) {
DCHECK(frame_ptr);
......
......@@ -40,6 +40,9 @@ class WEBRUNNER_EXPORT ContextImpl : public chromium::web::Context {
// Removes and destroys the specified |frame|.
void DestroyFrame(FrameImpl* frame);
// Returns |true| if JS injection was enabled for this Context.
bool IsJavaScriptInjectionAllowed();
// chromium::web::Context implementation.
void CreateFrame(fidl::InterfaceRequest<chromium::web::Frame> frame) override;
......@@ -50,6 +53,10 @@ class WEBRUNNER_EXPORT ContextImpl : public chromium::web::Context {
private:
content::BrowserContext* browser_context_;
// TODO(crbug.com/893236): Make this false by default, and allow it to be
// initialized at Context creation time.
bool allow_javascript_injection_ = true;
// Tracks all active FrameImpl instances, so that we can request their
// destruction when this ContextImpl is destroyed.
std::set<std::unique_ptr<FrameImpl>, base::UniquePtrComparator> frames_;
......
......@@ -4,8 +4,11 @@
#include "webrunner/browser/frame_impl.h"
#include <zircon/syscalls.h>
#include <string>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
......@@ -13,6 +16,7 @@
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host_platform.h"
......@@ -119,6 +123,32 @@ bool FrameFocusRules::SupportsChildActivation(aura::Window*) const {
return true;
}
bool IsOriginWhitelisted(const GURL& url,
const std::vector<std::string>& allowed_origins) {
constexpr const char kWildcard[] = "*";
for (const std::string& origin : allowed_origins) {
if (origin == kWildcard)
return true;
GURL origin_url(origin);
if (!origin_url.is_valid()) {
DLOG(WARNING) << "Ignored invalid origin spec for whitelisting: "
<< origin;
continue;
}
if (origin_url != url.GetOrigin())
continue;
// TODO(crbug.com/893236): Add handling for nonstandard origins
// (e.g. data: URIs).
return true;
}
return false;
}
} // namespace
FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
......@@ -284,6 +314,48 @@ void FrameImpl::SetNavigationEventObserver(
}
}
void FrameImpl::ExecuteJavaScript(fidl::VectorPtr<::fidl::StringPtr> origins,
fuchsia::mem::Buffer script,
chromium::web::ExecuteMode mode,
ExecuteJavaScriptCallback callback) {
if (!context_->IsJavaScriptInjectionAllowed()) {
callback(false);
return;
}
if (origins->empty()) {
callback(false);
return;
}
std::string script_utf8;
script_utf8.reserve(script.size);
zx_status_t status = script.vmo.read(&script_utf8.front(), 0, script.size);
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_read";
base::string16 script_utf16;
if (!base::UTF8ToUTF16(&script_utf8.front(), script.size, &script_utf16)) {
DLOG(WARNING) << "Ignored non-UTF8 script passed to ExecuteJavaScript().";
callback(false);
return;
}
script_utf8.clear();
script_utf8.shrink_to_fit();
std::vector<std::string> origins_strings;
for (const auto& origin : *origins)
origins_strings.push_back(origin);
if (mode == chromium::web::ExecuteMode::IMMEDIATE_ONCE) {
if (IsOriginWhitelisted(web_contents_->GetLastCommittedURL(),
origins_strings))
web_contents_->GetMainFrame()->ExecuteJavaScript(script_utf16);
} else {
before_load_scripts_.emplace_back(origins_strings, script_utf16);
}
callback(true);
}
void FrameImpl::DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) {
if (web_contents_->GetMainFrame() != render_frame_host) {
......@@ -321,4 +393,35 @@ void FrameImpl::MaybeSendNavigationEvent() {
}
}
void FrameImpl::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) {
if (before_load_scripts_.empty())
return;
if (!navigation_handle->IsInMainFrame() ||
navigation_handle->IsSameDocument() || navigation_handle->IsErrorPage())
return;
mojom::OnLoadScriptInjectorAssociatedPtr before_load_script_injector;
navigation_handle->GetRenderFrameHost()
->GetRemoteAssociatedInterfaces()
->GetInterface(&before_load_script_injector);
// Provision the renderer's ScriptInjector with the scripts scoped to this
// page's origin.
before_load_script_injector->ClearOnLoadScripts();
for (const OriginScopedScript& script : before_load_scripts_) {
if (IsOriginWhitelisted(navigation_handle->GetURL(), script.origins)) {
before_load_script_injector->AddOnLoadScript(script.script);
}
}
}
FrameImpl::OriginScopedScript::OriginScopedScript(
std::vector<std::string> origins,
base::string16 script)
: origins(std::move(origins)), script(script) {}
FrameImpl::OriginScopedScript::~OriginScopedScript() = default;
} // namespace webrunner
......@@ -7,9 +7,11 @@
#include <lib/fidl/cpp/binding_set.h>
#include <lib/zx/channel.h>
#include <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "content/public/browser/web_contents_delegate.h"
......@@ -17,6 +19,7 @@
#include "ui/aura/window_tree_host.h"
#include "ui/wm/core/focus_controller.h"
#include "url/gurl.h"
#include "webrunner/common/on_load_script_injector.mojom.h"
#include "webrunner/fidl/chromium/web/cpp/fidl.h"
namespace aura {
......@@ -66,6 +69,10 @@ class FrameImpl : public chromium::web::Frame,
void SetNavigationEventObserver(
fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer)
override;
void ExecuteJavaScript(fidl::VectorPtr<::fidl::StringPtr> origins,
fuchsia::mem::Buffer script,
chromium::web::ExecuteMode mode,
ExecuteJavaScriptCallback callback) override;
private:
FRIEND_TEST_ALL_PREFIXES(FrameImplTest, DelayedNavigationEventAck);
......@@ -74,6 +81,17 @@ class FrameImpl : public chromium::web::Frame,
FRIEND_TEST_ALL_PREFIXES(FrameImplTest, ReloadFrame);
FRIEND_TEST_ALL_PREFIXES(FrameImplTest, Stop);
struct OriginScopedScript {
OriginScopedScript(std::vector<std::string> origins, base::string16 script);
~OriginScopedScript();
std::vector<std::string> origins;
base::string16 script;
private:
DISALLOW_COPY_AND_ASSIGN(OriginScopedScript);
};
aura::Window* root_window() const { return window_tree_host_->window(); }
// Sends |pending_navigation_event_| to the observer if there are any changes
......@@ -94,6 +112,8 @@ class FrameImpl : public chromium::web::Frame,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) override;
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
// content::WebContentsObserver implementation.
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
......@@ -108,6 +128,7 @@ class FrameImpl : public chromium::web::Frame,
chromium::web::NavigationEvent pending_navigation_event_;
bool waiting_for_navigation_event_ack_;
bool pending_navigation_event_is_dirty_;
std::list<OriginScopedScript> before_load_scripts_;
ContextImpl* context_ = nullptr;
fidl::Binding<chromium::web::Frame> binding_;
......
......@@ -4,6 +4,7 @@
#include <lib/fidl/cpp/binding.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/macros.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
......@@ -15,6 +16,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "url/url_constants.h"
#include "webrunner/browser/frame_impl.h"
#include "webrunner/browser/run_with_timeout.h"
#include "webrunner/browser/test_common.h"
#include "webrunner/browser/webrunner_browser_test.h"
#include "webrunner/service/common.h"
......@@ -33,6 +35,7 @@ using NavigationDetails = chromium::web::NavigationEvent;
const char kPage1Path[] = "/title1.html";
const char kPage2Path[] = "/title2.html";
const char kDynamicTitlePath[] = "/dynamic_title.html";
const char kPage1Title[] = "title 1";
const char kPage2Title[] = "title 2";
const char kDataUrl[] =
......@@ -69,7 +72,7 @@ class FrameImplTest : public WebRunnerBrowserTest {
Field(&NavigationDetails::url, url))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(url, nullptr);
run_loop.Run();
CheckRunWithTimeout(&run_loop);
Mock::VerifyAndClearExpectations(this);
navigation_observer_.Acknowledge();
}
......@@ -372,6 +375,212 @@ IN_PROC_BROWSER_TEST_F(FrameImplTest, NoNavigationObserverAttached) {
}
}
fuchsia::mem::Buffer CreateBuffer(base::StringPiece data) {
fuchsia::mem::Buffer output;
output.size = data.size();
zx_status_t status = zx::vmo::create(data.size(), 0, &output.vmo);
ZX_CHECK(status == ZX_OK, ZX_OK) << "zx_vmo_create";
status = output.vmo.write(data.data(), 0, data.size());
ZX_CHECK(status == ZX_OK, status) << "zx_vmo_write";
return output;
}
// Test JS injection by using Javascript to trigger document navigation.
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptImmediate) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL title1(embedded_test_server()->GetURL(kPage1Path));
GURL title2(embedded_test_server()->GetURL(kPage2Path));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(title1.spec(), kPage1Title, controller.get());
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(title1.GetOrigin().spec());
frame->ExecuteJavaScript(
std::move(origins),
CreateBuffer("window.location.href = \"" + title2.spec() + "\";"),
chromium::web::ExecuteMode::IMMEDIATE_ONCE,
[](bool success) { EXPECT_TRUE(success); });
base::RunLoop run_loop;
EXPECT_CALL(navigation_observer_,
MockableOnNavigationStateChanged(
testing::AllOf(Field(&NavigationDetails::title, kPage2Title),
Field(&NavigationDetails::url, IsSet()))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
CheckRunWithTimeout(&run_loop);
frame.Unbind();
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptOnLoad) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(url.GetOrigin().spec());
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), "hello", controller.get());
frame.Unbind();
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavascriptOnLoadWrongOrigin) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back("http://example.com");
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
// Expect that the original HTML title is used, because we didn't inject a
// script with a replacement title.
CheckLoadUrl(url.spec(), "Welcome to Stan the Offline Dino's Homepage",
controller.get());
frame.Unbind();
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptOnLoadWildcardOrigin) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back("*");
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
// Test script injection for the origin 127.0.0.1.
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), "hello", controller.get());
CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
// Test script injection using a different origin ("localhost"), which should
// still be picked up by the wildcard.
GURL alt_url = embedded_test_server()->GetURL("localhost", kDynamicTitlePath);
CheckLoadUrl(alt_url.spec(), "hello", controller.get());
frame.Unbind();
}
// Test that consecutive scripts are executed in order by computing a cumulative
// result.
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteMultipleJavaScriptsOnLoad) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(url.GetOrigin().spec());
frame->ExecuteJavaScript(origins.Clone(),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title += ' there';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), "hello there", controller.get());
frame.Unbind();
}
// Test that we can inject scripts before and after RenderFrame creation.
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteOnLoadEarlyAndLateRegistrations) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kDynamicTitlePath));
chromium::web::FramePtr frame = CreateFrame();
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(url.GetOrigin().spec());
frame->ExecuteJavaScript(origins.Clone(),
CreateBuffer("stashed_title = 'hello';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), "hello", controller.get());
frame->ExecuteJavaScript(std::move(origins),
CreateBuffer("stashed_title += ' there';"),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { EXPECT_TRUE(success); });
// Navigate away to clean the slate.
CheckLoadUrl(url::kAboutBlankURL, url::kAboutBlankURL, controller.get());
// Navigate back and see if both scripts are working.
CheckLoadUrl(url.spec(), "hello there", controller.get());
frame.Unbind();
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, ExecuteJavaScriptBadEncoding) {
chromium::web::FramePtr frame = CreateFrame();
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(kPage1Path));
chromium::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
CheckLoadUrl(url.spec(), kPage1Title, controller.get());
base::RunLoop run_loop;
// 0xFE is an illegal UTF-8 byte; it should cause UTF-8 conversion to fail.
fidl::VectorPtr<fidl::StringPtr> origins =
fidl::VectorPtr<fidl::StringPtr>::New(0);
origins.push_back(url.host());
frame->ExecuteJavaScript(std::move(origins), CreateBuffer("true;\xfe"),
chromium::web::ExecuteMode::IMMEDIATE_ONCE,
[&run_loop](bool success) {
EXPECT_FALSE(success);
run_loop.Quit();
});
CheckRunWithTimeout(&run_loop);
frame.Unbind();
}
// Verifies that a Frame will handle navigation observer disconnection events
// gracefully.
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigationObserverDisconnected) {
......
// Copyright 2018 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.
#ifndef WEBRUNNER_COMMON_TEST_RUN_WITH_TIMEOUT_H_
#define WEBRUNNER_COMMON_TEST_RUN_WITH_TIMEOUT_H_
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace webrunner {
void CheckRunWithTimeout(base::RunLoop* run_loop,
const base::TimeDelta& timeout) {
bool did_timeout = false;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](bool* did_timeout, base::OnceClosure quit_closure) {
*did_timeout = true;
std::move(quit_closure).Run();
},
base::Unretained(&did_timeout), run_loop->QuitClosure()),
timeout);
run_loop->Run();
if (did_timeout)
ADD_FAILURE() << "Timeout: RunLoop did not Quit() within "
<< timeout.InSecondsF() << " seconds.";
}
} // namespace webrunner
#endif // WEBRUNNER_COMMON_RUNLOOP_WITH_DEADLINE_H_
// Copyright 2018 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.
#ifndef WEBRUNNER_BROWSER_RUN_WITH_TIMEOUT_H_
#define WEBRUNNER_BROWSER_RUN_WITH_TIMEOUT_H_
#include "base/run_loop.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
namespace webrunner {
// Runs |run_loop| and logs a test failure if it fails to finish before
// |timeout|.
void CheckRunWithTimeout(
base::RunLoop* run_loop,
const base::TimeDelta& timeout = TestTimeouts::action_timeout());
} // namespace webrunner
#endif // WEBRUNNER_BROWSER_RUN_WITH_TIMEOUT_H_
<html>
<head><title>Welcome to Stan the Offline Dino's Homepage</title></head>
<body>
<script>
// This page surfaces the contents of "stashed_title" to tests via
// the page title. If the script was not executed, or if the script
// failed to execute, then the title will be
// "Welcome to Stan the Offline Dino's Homepage".
document.title = stashed_title;
</script>
</body>
</html>
......@@ -8,6 +8,7 @@
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "content/public/browser/render_frame_host.h"
#include "ui/aura/screen_ozone.h"
#include "ui/ozone/public/ozone_platform.h"
#include "webrunner/browser/context_impl.h"
......@@ -55,6 +56,11 @@ void WebRunnerBrowserMainParts::PreMainMessageLoopRun() {
context_service_.reset();
std::move(quit_closure_).Run();
});
// Disable RenderFrameHost's Javascript injection restrictions so that the
// Context and Frames can implement their own JS injection policy at a higher
// level.
content::RenderFrameHost::AllowInjectingJavaScript();
}
void WebRunnerBrowserMainParts::PreDefaultMainMessageLoopRun(
......
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
// Copyright 2018 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.
module webrunner.mojom;
import "mojo/public/mojom/base/string16.mojom";
// Interface associated with RenderFrames for managing on-load JavaScript
// injection tasks the frame. Does not enforce script injection policies,
// which must be implemented at a higher level.
interface OnLoadScriptInjector {
AddOnLoadScript(mojo_base.mojom.String16 script);
ClearOnLoadScripts();
};
......@@ -4,9 +4,15 @@
library chromium.web;
using fuchsia.mem;
using fuchsia.sys;
using fuchsia.ui.viewsv1token;
enum ExecuteMode {
IMMEDIATE_ONCE = 1; // Will evaluate the script immediately.
ON_PAGE_LOAD = 2; // Will evaluate the script on all subsequent page loads.
};
interface Frame {
// Creates and registers a view with the view manager and returns its
// view owner which may subsequently be passed to |View.AddChild()|
......@@ -24,6 +30,30 @@ interface Frame {
// NavigationController.
2: GetNavigationController(request<NavigationController> controller);
// Executes |script| in the frame if the frame's URL has an origin which
// matches entries in |origins|.
// At least one |origins| entry must be specified.
// If a wildcard "*" is specified in |origins|, then the script will be
// evaluated for all documents.
// If |mode| is NOW, then the script is evaluated immediately.
// If |mode| is ON_PAGE_LOAD, then the script is evaluated on every future
// document load prior to the page's script's execution.
//
// Multiple scripts can be registered by calling ExecuteJavascript()
// repeatedly.
//
// Note that scripts share the same execution context as the document,
// meaning that document may modify variables, classes, or objects set by the
// script in arbitrary or unpredictable ways.
// TODO(crbug.com/900391): Investigate if we can run the scripts in isolated
// JS worlds.
//
// Returns |true| if the script was executed, |false| if the script was
// rejected due to injection being blocked by the parent Context, or because
// the script's text encoding was invalid.
3: ExecuteJavaScript(
vector<string> origins, fuchsia.mem.Buffer script, ExecuteMode mode) ->
(bool success);
// Sets the observer for handling page navigation events.
//
......
include_rules = [
"+content/public/renderer",
"+mojo/public/cpp/bindings",
"+services/service_manager/public/cpp",
"+third_party/blink/public/common/associated_interfaces",
]
// Copyright 2018 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 "webrunner/renderer/on_load_script_injector.h"
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "content/public/renderer/render_frame.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
namespace webrunner {
OnLoadScriptInjector::OnLoadScriptInjector(content::RenderFrame* frame)
: RenderFrameObserver(frame), weak_ptr_factory_(this) {
render_frame()->GetAssociatedInterfaceRegistry()->AddInterface(
base::BindRepeating(&OnLoadScriptInjector::BindToRequest,
weak_ptr_factory_.GetWeakPtr()));
}
OnLoadScriptInjector::~OnLoadScriptInjector() {}
void OnLoadScriptInjector::BindToRequest(
mojom::OnLoadScriptInjectorAssociatedRequest request) {
bindings_.AddBinding(this, std::move(request));
}
void OnLoadScriptInjector::DidClearWindowObject() {
// The script may cause the window to be cleared (e.g. by producing a page
// load event), so the guard is used to prevent reentrancy and potential
// infinite loops.
if (is_handling_clear_window_object_)
return;
base::AutoReset<bool> clear_window_reset(&is_handling_clear_window_object_,
true);
for (const base::string16& script : on_load_scripts_)
render_frame()->ExecuteJavaScript(script);
}
void OnLoadScriptInjector::AddOnLoadScript(const base::string16& script) {
on_load_scripts_.push_back(script);
}
void OnLoadScriptInjector::ClearOnLoadScripts() {
on_load_scripts_.clear();
}
void OnLoadScriptInjector::OnDestruct() {
delete this;
}
} // namespace webrunner
// Copyright 2018 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.
#ifndef WEBRUNNER_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
#define WEBRUNNER_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "content/public/renderer/render_frame_observer.h"
#include "mojo/public/cpp/bindings/associated_binding_set.h"
#include "webrunner/common/on_load_script_injector.mojom.h"
namespace webrunner {
// Injects one or more scripts into a RenderFrame at the earliest possible time
// during the page load process.
class OnLoadScriptInjector : public content::RenderFrameObserver,
public mojom::OnLoadScriptInjector {
public:
explicit OnLoadScriptInjector(content::RenderFrame* frame);
void BindToRequest(mojom::OnLoadScriptInjectorAssociatedRequest request);
void AddOnLoadScript(const base::string16& script) override;
void ClearOnLoadScripts() override;
// RenderFrameObserver override:
void OnDestruct() override;
void DidClearWindowObject() override;
private:
// Called by OnDestruct(), when the RenderFrame is destroyed.
~OnLoadScriptInjector() override;
std::vector<base::string16> on_load_scripts_;
bool is_handling_clear_window_object_;
mojo::AssociatedBindingSet<mojom::OnLoadScriptInjector> bindings_;
base::WeakPtrFactory<OnLoadScriptInjector> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(OnLoadScriptInjector);
};
} // namespace webrunner
#endif // WEBRUNNER_RENDERER_ON_LOAD_SCRIPT_INJECTOR_H_
// Copyright 2018 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 "webrunner/renderer/webrunner_content_renderer_client.h"
#include "base/macros.h"
#include "content/public/renderer/render_frame.h"
#include "services/service_manager/public/cpp/binder_registry.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "webrunner/renderer/on_load_script_injector.h"
namespace webrunner {
WebRunnerContentRendererClient::WebRunnerContentRendererClient() = default;
WebRunnerContentRendererClient::~WebRunnerContentRendererClient() = default;
void WebRunnerContentRendererClient::RenderFrameCreated(
content::RenderFrame* render_frame) {
// Add WebRunner services to the new RenderFrame.
// The objects' lifetimes are bound to the RenderFrame's lifetime.
new OnLoadScriptInjector(render_frame);
}
} // namespace webrunner
// Copyright 2018 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.
#ifndef WEBRUNNER_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
#define WEBRUNNER_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
#include "base/macros.h"
#include "content/public/renderer/content_renderer_client.h"
namespace webrunner {
class WebRunnerContentRendererClient : public content::ContentRendererClient {
public:
WebRunnerContentRendererClient();
~WebRunnerContentRendererClient() override;
// content::ContentRendererClient overrides.
void RenderFrameCreated(content::RenderFrame* render_frame) override;
private:
DISALLOW_COPY_AND_ASSIGN(WebRunnerContentRendererClient);
};
} // namespace webrunner
#endif // WEBRUNNER_RENDERER_WEBRUNNER_CONTENT_RENDERER_CLIENT_H_
......@@ -55,6 +55,14 @@ class FakeFrame : public chromium::web::Frame {
chromium::web::NavigationEventObserver* observer() { return observer_.get(); }
// chromium::web::Frame implementation.
void SetNavigationEventObserver(
fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer)
override {
observer_.Bind(std::move(observer));
std::move(on_set_observer_callback_).Run();
}
void CreateView(
fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) override {
......@@ -64,12 +72,10 @@ class FakeFrame : public chromium::web::Frame {
fidl::InterfaceRequest<chromium::web::NavigationController> controller)
override {}
void SetNavigationEventObserver(
fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer)
override {
observer_.Bind(std::move(observer));
std::move(on_set_observer_callback_).Run();
}
void ExecuteJavaScript(fidl::VectorPtr<::fidl::StringPtr> origins,
fuchsia::mem::Buffer script,
chromium::web::ExecuteMode mode,
ExecuteJavaScriptCallback callback) override {}
private:
fidl::Binding<chromium::web::Frame> binding_;
......
......@@ -14,6 +14,7 @@
#include "webrunner/browser/webrunner_browser_main.h"
#include "webrunner/browser/webrunner_content_browser_client.h"
#include "webrunner/common/webrunner_content_client.h"
#include "webrunner/renderer/webrunner_content_renderer_client.h"
#include "webrunner/service/common.h"
namespace webrunner {
......@@ -91,4 +92,10 @@ WebRunnerMainDelegate::CreateContentBrowserClient() {
return browser_client_.get();
}
content::ContentRendererClient*
WebRunnerMainDelegate::CreateContentRendererClient() {
renderer_client_ = std::make_unique<WebRunnerContentRendererClient>();
return renderer_client_.get();
}
} // namespace webrunner
......@@ -20,6 +20,7 @@ class ContentClient;
namespace webrunner {
class WebRunnerContentBrowserClient;
class WebRunnerContentRendererClient;
class WEBRUNNER_EXPORT WebRunnerMainDelegate
: public content::ContentMainDelegate {
......@@ -40,10 +41,12 @@ class WEBRUNNER_EXPORT WebRunnerMainDelegate
const std::string& process_type,
const content::MainFunctionParams& main_function_params) override;
content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentRendererClient* CreateContentRendererClient() override;
private:
std::unique_ptr<content::ContentClient> content_client_;
std::unique_ptr<WebRunnerContentBrowserClient> browser_client_;
std::unique_ptr<WebRunnerContentRendererClient> renderer_client_;
zx::channel context_channel_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment