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() { ...@@ -118,7 +118,7 @@ void AwBrowserMainParts::PreMainMessageLoopRun() {
AwBrowserContext* context = browser_client_->InitBrowserContext(); AwBrowserContext* context = browser_client_->InitBrowserContext();
context->PreMainMessageLoopRun(browser_client_->GetNetLog()); context->PreMainMessageLoopRun(browser_client_->GetNetLog());
content::RenderFrameHost::AllowInjectingJavaScriptForAndroidWebView(); content::RenderFrameHost::AllowInjectingJavaScript();
} }
bool AwBrowserMainParts::MainMessageLoopRun(int* result_code) { bool AwBrowserMainParts::MainMessageLoopRun(int* result_code) {
......
...@@ -214,9 +214,9 @@ int g_next_accessibility_reset_token = 1; ...@@ -214,9 +214,9 @@ int g_next_accessibility_reset_token = 1;
// The next value to use for the javascript callback id. // The next value to use for the javascript callback id.
int g_next_javascript_callback_id = 1; int g_next_javascript_callback_id = 1;
#if defined(OS_ANDROID) #if defined(OS_ANDROID) || defined(OS_FUCHSIA)
// Whether to allow injecting javascript into any kind of frame (for Android // Whether to allow injecting javascript into any kind of frame, for Android
// WebView). // WebView and Fuchsia web.ContextProvider.
bool g_allow_injecting_javascript = false; bool g_allow_injecting_javascript = false;
#endif #endif
...@@ -605,12 +605,12 @@ RenderFrameHost* RenderFrameHost::FromID(int render_process_id, ...@@ -605,12 +605,12 @@ RenderFrameHost* RenderFrameHost::FromID(int render_process_id,
return RenderFrameHostImpl::FromID(render_process_id, render_frame_id); return RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
} }
#if defined(OS_ANDROID) #if defined(OS_ANDROID) || defined(OS_FUCHSIA)
// static // static
void RenderFrameHost::AllowInjectingJavaScriptForAndroidWebView() { void RenderFrameHost::AllowInjectingJavaScript() {
g_allow_injecting_javascript = true; g_allow_injecting_javascript = true;
} }
#endif // defined(OS_ANDROID) #endif // defined(OS_ANDROID) || defined(OS_FUCHSIA)
// static // static
RenderFrameHostImpl* RenderFrameHostImpl::FromID(int process_id, RenderFrameHostImpl* RenderFrameHostImpl::FromID(int process_id,
...@@ -5090,7 +5090,7 @@ bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactoryInternal( ...@@ -5090,7 +5090,7 @@ bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactoryInternal(
} }
bool RenderFrameHostImpl::CanExecuteJavaScript() { bool RenderFrameHostImpl::CanExecuteJavaScript() {
#if defined(OS_ANDROID) #if defined(OS_ANDROID) || defined(OS_FUCHSIA)
if (g_allow_injecting_javascript) if (g_allow_injecting_javascript)
return true; return true;
#endif #endif
......
...@@ -72,11 +72,11 @@ class CONTENT_EXPORT RenderFrameHost : public IPC::Listener, ...@@ -72,11 +72,11 @@ class CONTENT_EXPORT RenderFrameHost : public IPC::Listener,
// Returns nullptr if the IDs do not correspond to a live RenderFrameHost. // Returns nullptr if the IDs do not correspond to a live RenderFrameHost.
static RenderFrameHost* FromID(int render_process_id, int render_frame_id); 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 // 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 // is present only to support Android WebView and Fuchsia web.Contexts, and
// configurations. // must not be used in other configurations.
static void AllowInjectingJavaScriptForAndroidWebView(); static void AllowInjectingJavaScript();
#endif #endif
// Returns a RenderFrameHost given its accessibility tree ID. // Returns a RenderFrameHost given its accessibility tree ID.
......
...@@ -7,6 +7,7 @@ assert(is_fuchsia) ...@@ -7,6 +7,7 @@ assert(is_fuchsia)
import("//build/config/fuchsia/fidl_library.gni") import("//build/config/fuchsia/fidl_library.gni")
import("//build/config/fuchsia/rules.gni") import("//build/config/fuchsia/rules.gni")
import("//build/util/process_version.gni") import("//build/util/process_version.gni")
import("//mojo/public/tools/bindings/mojom.gni")
import("//testing/test.gni") import("//testing/test.gni")
import("//tools/grit/repack.gni") import("//tools/grit/repack.gni")
...@@ -110,8 +111,19 @@ fuchsia_package_runner("service_runner") { ...@@ -110,8 +111,19 @@ fuchsia_package_runner("service_runner") {
install_only = true install_only = true
} }
mojom("mojo_bindings") {
sources = [
"common/on_load_script_injector.mojom",
]
public_deps = [
"//mojo/public/mojom/base",
]
}
component("service_lib") { component("service_lib") {
deps = [ deps = [
":mojo_bindings",
":service_pak", ":service_pak",
"//base", "//base",
"//components/version_info", "//components/version_info",
...@@ -162,6 +174,10 @@ component("service_lib") { ...@@ -162,6 +174,10 @@ component("service_lib") {
"common/webrunner_content_client.cc", "common/webrunner_content_client.cc",
"common/webrunner_content_client.h", "common/webrunner_content_client.h",
"common/webrunner_export.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.cc",
"service/common.h", "service/common.h",
"service/context_provider_impl.cc", "service/context_provider_impl.cc",
...@@ -215,6 +231,8 @@ test("webrunner_browsertests") { ...@@ -215,6 +231,8 @@ test("webrunner_browsertests") {
sources = [ sources = [
"browser/context_impl_browsertest.cc", "browser/context_impl_browsertest.cc",
"browser/frame_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.cc",
"browser/test_common.h", "browser/test_common.h",
"browser/webrunner_browser_test.cc", "browser/webrunner_browser_test.cc",
......
include_rules = [ include_rules = [
"+content/public/browser", "+content/public/browser",
"+content/public/test", "+content/public/test",
"+third_party/blink/public/common/associated_interfaces",
"+ui/aura", "+ui/aura",
"+ui/display", "+ui/display",
"+ui/ozone/public", "+ui/ozone/public",
......
...@@ -32,6 +32,10 @@ void ContextImpl::DestroyFrame(FrameImpl* frame) { ...@@ -32,6 +32,10 @@ void ContextImpl::DestroyFrame(FrameImpl* frame) {
frames_.erase(frames_.find(frame)); frames_.erase(frames_.find(frame));
} }
bool ContextImpl::IsJavaScriptInjectionAllowed() {
return allow_javascript_injection_;
}
FrameImpl* ContextImpl::GetFrameImplForTest( FrameImpl* ContextImpl::GetFrameImplForTest(
chromium::web::FramePtr* frame_ptr) { chromium::web::FramePtr* frame_ptr) {
DCHECK(frame_ptr); DCHECK(frame_ptr);
......
...@@ -40,6 +40,9 @@ class WEBRUNNER_EXPORT ContextImpl : public chromium::web::Context { ...@@ -40,6 +40,9 @@ class WEBRUNNER_EXPORT ContextImpl : public chromium::web::Context {
// Removes and destroys the specified |frame|. // Removes and destroys the specified |frame|.
void DestroyFrame(FrameImpl* frame); void DestroyFrame(FrameImpl* frame);
// Returns |true| if JS injection was enabled for this Context.
bool IsJavaScriptInjectionAllowed();
// chromium::web::Context implementation. // chromium::web::Context implementation.
void CreateFrame(fidl::InterfaceRequest<chromium::web::Frame> frame) override; void CreateFrame(fidl::InterfaceRequest<chromium::web::Frame> frame) override;
...@@ -50,6 +53,10 @@ class WEBRUNNER_EXPORT ContextImpl : public chromium::web::Context { ...@@ -50,6 +53,10 @@ class WEBRUNNER_EXPORT ContextImpl : public chromium::web::Context {
private: private:
content::BrowserContext* browser_context_; 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 // Tracks all active FrameImpl instances, so that we can request their
// destruction when this ContextImpl is destroyed. // destruction when this ContextImpl is destroyed.
std::set<std::unique_ptr<FrameImpl>, base::UniquePtrComparator> frames_; std::set<std::unique_ptr<FrameImpl>, base::UniquePtrComparator> frames_;
......
...@@ -4,8 +4,11 @@ ...@@ -4,8 +4,11 @@
#include "webrunner/browser/frame_impl.h" #include "webrunner/browser/frame_impl.h"
#include <zircon/syscalls.h>
#include <string> #include <string>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
...@@ -13,6 +16,7 @@ ...@@ -13,6 +16,7 @@
#include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.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/layout_manager.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/aura/window_tree_host_platform.h" #include "ui/aura/window_tree_host_platform.h"
...@@ -119,6 +123,32 @@ bool FrameFocusRules::SupportsChildActivation(aura::Window*) const { ...@@ -119,6 +123,32 @@ bool FrameFocusRules::SupportsChildActivation(aura::Window*) const {
return true; 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 } // namespace
FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents, FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
...@@ -284,6 +314,48 @@ void FrameImpl::SetNavigationEventObserver( ...@@ -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, void FrameImpl::DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) { const GURL& validated_url) {
if (web_contents_->GetMainFrame() != render_frame_host) { if (web_contents_->GetMainFrame() != render_frame_host) {
...@@ -321,4 +393,35 @@ void FrameImpl::MaybeSendNavigationEvent() { ...@@ -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 } // namespace webrunner
...@@ -7,9 +7,11 @@ ...@@ -7,9 +7,11 @@
#include <lib/fidl/cpp/binding_set.h> #include <lib/fidl/cpp/binding_set.h>
#include <lib/zx/channel.h> #include <lib/zx/channel.h>
#include <list>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector>
#include "base/macros.h" #include "base/macros.h"
#include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_delegate.h"
...@@ -17,6 +19,7 @@ ...@@ -17,6 +19,7 @@
#include "ui/aura/window_tree_host.h" #include "ui/aura/window_tree_host.h"
#include "ui/wm/core/focus_controller.h" #include "ui/wm/core/focus_controller.h"
#include "url/gurl.h" #include "url/gurl.h"
#include "webrunner/common/on_load_script_injector.mojom.h"
#include "webrunner/fidl/chromium/web/cpp/fidl.h" #include "webrunner/fidl/chromium/web/cpp/fidl.h"
namespace aura { namespace aura {
...@@ -66,6 +69,10 @@ class FrameImpl : public chromium::web::Frame, ...@@ -66,6 +69,10 @@ class FrameImpl : public chromium::web::Frame,
void SetNavigationEventObserver( void SetNavigationEventObserver(
fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer) fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer)
override; override;
void ExecuteJavaScript(fidl::VectorPtr<::fidl::StringPtr> origins,
fuchsia::mem::Buffer script,
chromium::web::ExecuteMode mode,
ExecuteJavaScriptCallback callback) override;
private: private:
FRIEND_TEST_ALL_PREFIXES(FrameImplTest, DelayedNavigationEventAck); FRIEND_TEST_ALL_PREFIXES(FrameImplTest, DelayedNavigationEventAck);
...@@ -74,6 +81,17 @@ class FrameImpl : public chromium::web::Frame, ...@@ -74,6 +81,17 @@ class FrameImpl : public chromium::web::Frame,
FRIEND_TEST_ALL_PREFIXES(FrameImplTest, ReloadFrame); FRIEND_TEST_ALL_PREFIXES(FrameImplTest, ReloadFrame);
FRIEND_TEST_ALL_PREFIXES(FrameImplTest, Stop); 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(); } aura::Window* root_window() const { return window_tree_host_->window(); }
// Sends |pending_navigation_event_| to the observer if there are any changes // Sends |pending_navigation_event_| to the observer if there are any changes
...@@ -94,6 +112,8 @@ class FrameImpl : public chromium::web::Frame, ...@@ -94,6 +112,8 @@ class FrameImpl : public chromium::web::Frame,
const GURL& target_url, const GURL& target_url,
const std::string& partition_id, const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) override; content::SessionStorageNamespace* session_storage_namespace) override;
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
// content::WebContentsObserver implementation. // content::WebContentsObserver implementation.
void DidFinishLoad(content::RenderFrameHost* render_frame_host, void DidFinishLoad(content::RenderFrameHost* render_frame_host,
...@@ -108,6 +128,7 @@ class FrameImpl : public chromium::web::Frame, ...@@ -108,6 +128,7 @@ class FrameImpl : public chromium::web::Frame,
chromium::web::NavigationEvent pending_navigation_event_; chromium::web::NavigationEvent pending_navigation_event_;
bool waiting_for_navigation_event_ack_; bool waiting_for_navigation_event_ack_;
bool pending_navigation_event_is_dirty_; bool pending_navigation_event_is_dirty_;
std::list<OriginScopedScript> before_load_scripts_;
ContextImpl* context_ = nullptr; ContextImpl* context_ = nullptr;
fidl::Binding<chromium::web::Frame> binding_; fidl::Binding<chromium::web::Frame> binding_;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include <lib/fidl/cpp/binding.h> #include <lib/fidl/cpp/binding.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
...@@ -15,6 +16,7 @@ ...@@ -15,6 +16,7 @@
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/url_constants.h" #include "url/url_constants.h"
#include "webrunner/browser/frame_impl.h" #include "webrunner/browser/frame_impl.h"
#include "webrunner/browser/run_with_timeout.h"
#include "webrunner/browser/test_common.h" #include "webrunner/browser/test_common.h"
#include "webrunner/browser/webrunner_browser_test.h" #include "webrunner/browser/webrunner_browser_test.h"
#include "webrunner/service/common.h" #include "webrunner/service/common.h"
...@@ -33,6 +35,7 @@ using NavigationDetails = chromium::web::NavigationEvent; ...@@ -33,6 +35,7 @@ using NavigationDetails = chromium::web::NavigationEvent;
const char kPage1Path[] = "/title1.html"; const char kPage1Path[] = "/title1.html";
const char kPage2Path[] = "/title2.html"; const char kPage2Path[] = "/title2.html";
const char kDynamicTitlePath[] = "/dynamic_title.html";
const char kPage1Title[] = "title 1"; const char kPage1Title[] = "title 1";
const char kPage2Title[] = "title 2"; const char kPage2Title[] = "title 2";
const char kDataUrl[] = const char kDataUrl[] =
...@@ -69,7 +72,7 @@ class FrameImplTest : public WebRunnerBrowserTest { ...@@ -69,7 +72,7 @@ class FrameImplTest : public WebRunnerBrowserTest {
Field(&NavigationDetails::url, url)))) Field(&NavigationDetails::url, url))))
.WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
controller->LoadUrl(url, nullptr); controller->LoadUrl(url, nullptr);
run_loop.Run(); CheckRunWithTimeout(&run_loop);
Mock::VerifyAndClearExpectations(this); Mock::VerifyAndClearExpectations(this);
navigation_observer_.Acknowledge(); navigation_observer_.Acknowledge();
} }
...@@ -372,6 +375,212 @@ IN_PROC_BROWSER_TEST_F(FrameImplTest, NoNavigationObserverAttached) { ...@@ -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 // Verifies that a Frame will handle navigation observer disconnection events
// gracefully. // gracefully.
IN_PROC_BROWSER_TEST_F(FrameImplTest, NavigationObserverDisconnected) { 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 @@ ...@@ -8,6 +8,7 @@
#include "base/command_line.h" #include "base/command_line.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "content/public/browser/render_frame_host.h"
#include "ui/aura/screen_ozone.h" #include "ui/aura/screen_ozone.h"
#include "ui/ozone/public/ozone_platform.h" #include "ui/ozone/public/ozone_platform.h"
#include "webrunner/browser/context_impl.h" #include "webrunner/browser/context_impl.h"
...@@ -55,6 +56,11 @@ void WebRunnerBrowserMainParts::PreMainMessageLoopRun() { ...@@ -55,6 +56,11 @@ void WebRunnerBrowserMainParts::PreMainMessageLoopRun() {
context_service_.reset(); context_service_.reset();
std::move(quit_closure_).Run(); 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( 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 @@ ...@@ -4,9 +4,15 @@
library chromium.web; library chromium.web;
using fuchsia.mem;
using fuchsia.sys; using fuchsia.sys;
using fuchsia.ui.viewsv1token; 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 { interface Frame {
// Creates and registers a view with the view manager and returns its // Creates and registers a view with the view manager and returns its
// view owner which may subsequently be passed to |View.AddChild()| // view owner which may subsequently be passed to |View.AddChild()|
...@@ -24,6 +30,30 @@ interface Frame { ...@@ -24,6 +30,30 @@ interface Frame {
// NavigationController. // NavigationController.
2: GetNavigationController(request<NavigationController> controller); 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. // 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 { ...@@ -55,6 +55,14 @@ class FakeFrame : public chromium::web::Frame {
chromium::web::NavigationEventObserver* observer() { return observer_.get(); } 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( void CreateView(
fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner, fidl::InterfaceRequest<fuchsia::ui::viewsv1token::ViewOwner> view_owner,
fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) override { fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> services) override {
...@@ -64,12 +72,10 @@ class FakeFrame : public chromium::web::Frame { ...@@ -64,12 +72,10 @@ class FakeFrame : public chromium::web::Frame {
fidl::InterfaceRequest<chromium::web::NavigationController> controller) fidl::InterfaceRequest<chromium::web::NavigationController> controller)
override {} override {}
void SetNavigationEventObserver( void ExecuteJavaScript(fidl::VectorPtr<::fidl::StringPtr> origins,
fidl::InterfaceHandle<chromium::web::NavigationEventObserver> observer) fuchsia::mem::Buffer script,
override { chromium::web::ExecuteMode mode,
observer_.Bind(std::move(observer)); ExecuteJavaScriptCallback callback) override {}
std::move(on_set_observer_callback_).Run();
}
private: private:
fidl::Binding<chromium::web::Frame> binding_; fidl::Binding<chromium::web::Frame> binding_;
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "webrunner/browser/webrunner_browser_main.h" #include "webrunner/browser/webrunner_browser_main.h"
#include "webrunner/browser/webrunner_content_browser_client.h" #include "webrunner/browser/webrunner_content_browser_client.h"
#include "webrunner/common/webrunner_content_client.h" #include "webrunner/common/webrunner_content_client.h"
#include "webrunner/renderer/webrunner_content_renderer_client.h"
#include "webrunner/service/common.h" #include "webrunner/service/common.h"
namespace webrunner { namespace webrunner {
...@@ -91,4 +92,10 @@ WebRunnerMainDelegate::CreateContentBrowserClient() { ...@@ -91,4 +92,10 @@ WebRunnerMainDelegate::CreateContentBrowserClient() {
return browser_client_.get(); return browser_client_.get();
} }
content::ContentRendererClient*
WebRunnerMainDelegate::CreateContentRendererClient() {
renderer_client_ = std::make_unique<WebRunnerContentRendererClient>();
return renderer_client_.get();
}
} // namespace webrunner } // namespace webrunner
...@@ -20,6 +20,7 @@ class ContentClient; ...@@ -20,6 +20,7 @@ class ContentClient;
namespace webrunner { namespace webrunner {
class WebRunnerContentBrowserClient; class WebRunnerContentBrowserClient;
class WebRunnerContentRendererClient;
class WEBRUNNER_EXPORT WebRunnerMainDelegate class WEBRUNNER_EXPORT WebRunnerMainDelegate
: public content::ContentMainDelegate { : public content::ContentMainDelegate {
...@@ -40,10 +41,12 @@ class WEBRUNNER_EXPORT WebRunnerMainDelegate ...@@ -40,10 +41,12 @@ class WEBRUNNER_EXPORT WebRunnerMainDelegate
const std::string& process_type, const std::string& process_type,
const content::MainFunctionParams& main_function_params) override; const content::MainFunctionParams& main_function_params) override;
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentRendererClient* CreateContentRendererClient() override;
private: private:
std::unique_ptr<content::ContentClient> content_client_; std::unique_ptr<content::ContentClient> content_client_;
std::unique_ptr<WebRunnerContentBrowserClient> browser_client_; std::unique_ptr<WebRunnerContentBrowserClient> browser_client_;
std::unique_ptr<WebRunnerContentRendererClient> renderer_client_;
zx::channel context_channel_; 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