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

[Fuchsia] Adds popup window support to the fuchsia.web API.

Allows embedders to handle popup window requests from web content
(e.g. "window.open", navigation to links with target="_blank").

Bug: 888131
Change-Id: I7fef0525b6b043494cd462deb66f854e657af670
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1548586
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Reviewed-by: default avatarWez <wez@chromium.org>
Cr-Commit-Position: refs/heads/master@{#688305}
parent 4bdaeeb2
...@@ -25,6 +25,15 @@ ContextImpl::ContextImpl(content::BrowserContext* browser_context) ...@@ -25,6 +25,15 @@ ContextImpl::ContextImpl(content::BrowserContext* browser_context)
ContextImpl::~ContextImpl() = default; ContextImpl::~ContextImpl() = default;
fidl::InterfaceHandle<fuchsia::web::Frame>
ContextImpl::CreateFrameForPopupWebContents(
std::unique_ptr<content::WebContents> web_contents) {
fidl::InterfaceHandle<fuchsia::web::Frame> frame_handle;
frames_.insert(std::make_unique<FrameImpl>(std::move(web_contents), this,
frame_handle.NewRequest()));
return frame_handle;
}
void ContextImpl::DestroyFrame(FrameImpl* frame) { void ContextImpl::DestroyFrame(FrameImpl* frame) {
DCHECK(frames_.find(frame) != frames_.end()); DCHECK(frames_.find(frame) != frames_.end());
frames_.erase(frames_.find(frame)); frames_.erase(frames_.find(frame));
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
namespace content { namespace content {
class BrowserContext; class BrowserContext;
class WebContents;
} // namespace content } // namespace content
class FrameImpl; class FrameImpl;
...@@ -43,6 +44,10 @@ class WEB_ENGINE_EXPORT ContextImpl : public fuchsia::web::Context { ...@@ -43,6 +44,10 @@ class WEB_ENGINE_EXPORT ContextImpl : public fuchsia::web::Context {
// Returns |true| if JS injection was enabled for this Context. // Returns |true| if JS injection was enabled for this Context.
bool IsJavaScriptInjectionAllowed(); bool IsJavaScriptInjectionAllowed();
// Registers a Frame originating from web content (i.e. a popup).
fidl::InterfaceHandle<fuchsia::web::Frame> CreateFrameForPopupWebContents(
std::unique_ptr<content::WebContents> web_contents);
// Called by Frames to signal a document has been loaded and signal to the // Called by Frames to signal a document has been loaded and signal to the
// debug listeners in |web_engine_remote_debugging_| that they can now // debug listeners in |web_engine_remote_debugging_| that they can now
// successfully connect ChromeDriver. // successfully connect ChromeDriver.
......
...@@ -285,6 +285,117 @@ void FrameImpl::ExecuteJavaScriptInternal(std::vector<std::string> origins, ...@@ -285,6 +285,117 @@ void FrameImpl::ExecuteJavaScriptInternal(std::vector<std::string> origins,
} }
} }
FrameImpl::PendingPopup::PendingPopup() = default;
FrameImpl::PendingPopup::~PendingPopup() = default;
bool FrameImpl::ShouldCreateWebContents(
content::WebContents* web_contents,
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
int32_t route_id,
int32_t main_frame_route_id,
int32_t main_frame_widget_route_id,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) {
// Specify a generous upper bound for unacknowledged popup windows, so that we
// can catch bad client behavior while not interfering with normal operation.
constexpr size_t kMaxPendingWebContentsCount = 10;
DCHECK_EQ(web_contents, web_contents_.get());
if (!popup_listener_)
return false;
if (pending_popups_.size() >= kMaxPendingWebContentsCount) {
// The content is producing popups faster than the embedder can process
// them. Drop the popups so as to prevent resource exhaustion.
LOG(WARNING) << "Too many pending popups, ignoring request.";
// Don't produce a WebContents for this popup.
return false;
}
return true;
}
void FrameImpl::AddNewContents(
content::WebContents* source,
std::unique_ptr<content::WebContents> new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_rect,
bool user_gesture,
bool* was_blocked) {
DCHECK_EQ(source, web_contents_.get());
DCHECK(!last_popup_url_.is_empty());
// TODO(crbug.com/995395): Add window disposition to the FIDL interface.
switch (disposition) {
case WindowOpenDisposition::NEW_FOREGROUND_TAB:
case WindowOpenDisposition::NEW_BACKGROUND_TAB:
case WindowOpenDisposition::NEW_POPUP:
case WindowOpenDisposition::NEW_WINDOW: {
pending_popups_.emplace_back();
PendingPopup& popup = pending_popups_.back();
popup.creation_info.set_initial_url(last_popup_url_.spec());
popup.creation_info.set_initiated_by_user(user_gesture);
popup.web_contents = std::move(new_contents);
last_popup_url_ = {};
MaybeSendPopup();
return;
}
// These kinds of windows don't produce Frames.
case WindowOpenDisposition::CURRENT_TAB:
case WindowOpenDisposition::SINGLETON_TAB:
case WindowOpenDisposition::SAVE_TO_DISK:
case WindowOpenDisposition::OFF_THE_RECORD:
case WindowOpenDisposition::IGNORE_ACTION:
case WindowOpenDisposition::SWITCH_TO_TAB:
case WindowOpenDisposition::UNKNOWN:
NOTIMPLEMENTED() << "Dropped new web contents (disposition: "
<< static_cast<int>(disposition) << ")";
return;
}
}
void FrameImpl::WebContentsCreated(content::WebContents* source_contents,
int opener_render_process_id,
int opener_render_frame_id,
const std::string& frame_name,
const GURL& target_url,
content::WebContents* new_contents) {
last_popup_url_ = target_url;
}
void FrameImpl::MaybeSendPopup() {
if (!popup_listener_)
return;
if (popup_ack_outstanding_ || pending_popups_.empty())
return;
PendingPopup& popup = pending_popups_.front();
popup_listener_->OnPopupFrameCreated(
context_->CreateFrameForPopupWebContents(std::move(popup.web_contents)),
std::move(popup.creation_info), [this] {
popup_ack_outstanding_ = false;
MaybeSendPopup();
});
pending_popups_.pop_front();
popup_ack_outstanding_ = true;
}
void FrameImpl::OnPopupListenerDisconnected(zx_status_t status) {
ZX_LOG_IF(WARNING, status != ZX_ERR_PEER_CLOSED, status)
<< "Popup listener disconnected.";
pending_popups_.clear();
}
void FrameImpl::CreateView(fuchsia::ui::views::ViewToken view_token) { void FrameImpl::CreateView(fuchsia::ui::views::ViewToken view_token) {
// If a View to this Frame is already active then disconnect it. // If a View to this Frame is already active then disconnect it.
TearDownView(); TearDownView();
...@@ -485,6 +596,13 @@ void FrameImpl::SetEnableInput(bool enable_input) { ...@@ -485,6 +596,13 @@ void FrameImpl::SetEnableInput(bool enable_input) {
discarding_event_filter_.set_discard_events(!enable_input); discarding_event_filter_.set_discard_events(!enable_input);
} }
void FrameImpl::SetPopupFrameCreationListener(
fidl::InterfaceHandle<fuchsia::web::PopupFrameCreationListener> listener) {
popup_listener_ = listener.Bind();
popup_listener_.set_error_handler(
fit::bind_member(this, &FrameImpl::OnPopupListenerDisconnected));
}
void FrameImpl::CloseContents(content::WebContents* source) { void FrameImpl::CloseContents(content::WebContents* source) {
DCHECK_EQ(source, web_contents_.get()); DCHECK_EQ(source, web_contents_.get());
context_->DestroyFrame(this); context_->DestroyFrame(this);
...@@ -529,29 +647,6 @@ bool FrameImpl::DidAddMessageToConsole( ...@@ -529,29 +647,6 @@ bool FrameImpl::DidAddMessageToConsole(
return true; return true;
} }
bool FrameImpl::ShouldCreateWebContents(
content::WebContents* web_contents,
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
int32_t route_id,
int32_t main_frame_route_id,
int32_t main_frame_widget_route_id,
content::mojom::WindowContainerType window_container_type,
const GURL& opener_url,
const std::string& frame_name,
const GURL& target_url,
const std::string& partition_id,
content::SessionStorageNamespace* session_storage_namespace) {
DCHECK_EQ(web_contents, web_contents_.get());
// Prevent any child WebContents (popup windows, tabs, etc.) from spawning.
// TODO(crbug.com/888131): Implement support for popup windows.
NOTIMPLEMENTED() << "Ignored popup window request for URL: "
<< target_url.spec();
return false;
}
void FrameImpl::ReadyToCommitNavigation( void FrameImpl::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) { content::NavigationHandle* navigation_handle) {
if (before_load_scripts_.empty()) if (before_load_scripts_.empty())
......
...@@ -61,6 +61,14 @@ class FrameImpl : public fuchsia::web::Frame, ...@@ -61,6 +61,14 @@ class FrameImpl : public fuchsia::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 PendingPopup {
PendingPopup();
~PendingPopup();
std::unique_ptr<content::WebContents> web_contents;
fuchsia::web::PopupFrameCreationInfo creation_info;
};
class OriginScopedScript { class OriginScopedScript {
public: public:
OriginScopedScript(); OriginScopedScript();
...@@ -92,6 +100,11 @@ class FrameImpl : public fuchsia::web::Frame, ...@@ -92,6 +100,11 @@ class FrameImpl : public fuchsia::web::Frame,
ExecuteJavaScriptCallback callback, ExecuteJavaScriptCallback callback,
bool need_result); bool need_result);
// Sends the next entry in |pending_popups_| to |popup_listener_|.
void MaybeSendPopup();
void OnPopupListenerDisconnected(zx_status_t status);
// fuchsia::web::Frame implementation. // fuchsia::web::Frame implementation.
void CreateView(fuchsia::ui::views::ViewToken view_token) override; void CreateView(fuchsia::ui::views::ViewToken view_token) override;
void GetNavigationController( void GetNavigationController(
...@@ -118,6 +131,9 @@ class FrameImpl : public fuchsia::web::Frame, ...@@ -118,6 +131,9 @@ class FrameImpl : public fuchsia::web::Frame,
override; override;
void SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel level) override; void SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel level) override;
void SetEnableInput(bool enable_input) override; void SetEnableInput(bool enable_input) override;
void SetPopupFrameCreationListener(
fidl::InterfaceHandle<fuchsia::web::PopupFrameCreationListener> listener)
override;
// content::WebContentsDelegate implementation. // content::WebContentsDelegate implementation.
void CloseContents(content::WebContents* source) override; void CloseContents(content::WebContents* source) override;
...@@ -139,6 +155,18 @@ class FrameImpl : public fuchsia::web::Frame, ...@@ -139,6 +155,18 @@ class FrameImpl : public fuchsia::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 WebContentsCreated(content::WebContents* source_contents,
int opener_render_process_id,
int opener_render_frame_id,
const std::string& frame_name,
const GURL& target_url,
content::WebContents* new_contents) override;
void AddNewContents(content::WebContents* source,
std::unique_ptr<content::WebContents> new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_rect,
bool user_gesture,
bool* was_blocked) override;
// content::WebContentsObserver implementation. // content::WebContentsObserver implementation.
void ReadyToCommitNavigation( void ReadyToCommitNavigation(
...@@ -158,6 +186,12 @@ class FrameImpl : public fuchsia::web::Frame, ...@@ -158,6 +186,12 @@ class FrameImpl : public fuchsia::web::Frame,
std::vector<uint64_t> before_load_scripts_order_; std::vector<uint64_t> before_load_scripts_order_;
base::RepeatingCallback<void(base::StringPiece)> console_log_message_hook_; base::RepeatingCallback<void(base::StringPiece)> console_log_message_hook_;
// Used for receiving and dispatching popup created by this Frame.
fuchsia::web::PopupFrameCreationListenerPtr popup_listener_;
GURL last_popup_url_;
std::list<PendingPopup> pending_popups_;
bool popup_ack_outstanding_ = false;
fidl::Binding<fuchsia::web::Frame> binding_; fidl::Binding<fuchsia::web::Frame> binding_;
DISALLOW_COPY_AND_ASSIGN(FrameImpl); DISALLOW_COPY_AND_ASSIGN(FrameImpl);
......
...@@ -51,6 +51,9 @@ const char kPage1Path[] = "/title1.html"; ...@@ -51,6 +51,9 @@ const char kPage1Path[] = "/title1.html";
const char kPage2Path[] = "/title2.html"; const char kPage2Path[] = "/title2.html";
const char kPage3Path[] = "/websql.html"; const char kPage3Path[] = "/websql.html";
const char kDynamicTitlePath[] = "/dynamic_title.html"; const char kDynamicTitlePath[] = "/dynamic_title.html";
const char kPopupPath[] = "/popup_parent.html";
const char kPopupRedirectPath[] = "/popup_child.html";
const char kPopupMultiplePath[] = "/popup_multiple.html";
const char kPage1Title[] = "title 1"; const char kPage1Title[] = "title 1";
const char kPage2Title[] = "title 2"; const char kPage2Title[] = "title 2";
const char kPage3Title[] = "websql not available"; const char kPage3Title[] = "websql not available";
...@@ -1616,3 +1619,100 @@ IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest, ExtraHeaders) { ...@@ -1616,3 +1619,100 @@ IN_PROC_BROWSER_TEST_F(RequestMonitoringFrameImplBrowserTest, ExtraHeaders) {
EXPECT_THAT(iter->second.headers, EXPECT_THAT(iter->second.headers,
testing::Contains(testing::Key("X-2ExtraHeaders"))); testing::Contains(testing::Key("X-2ExtraHeaders")));
} }
class TestPopupListener : public fuchsia::web::PopupFrameCreationListener {
public:
TestPopupListener() = default;
~TestPopupListener() override = default;
void GetAndAckNextPopup(fuchsia::web::FramePtr* frame,
fuchsia::web::PopupFrameCreationInfo* creation_info) {
if (!frame_) {
base::RunLoop run_loop;
received_popup_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
*frame = frame_.Bind();
*creation_info = std::move(creation_info_);
popup_ack_callback_();
popup_ack_callback_ = {};
}
private:
void OnPopupFrameCreated(fidl::InterfaceHandle<fuchsia::web::Frame> frame,
fuchsia::web::PopupFrameCreationInfo creation_info,
OnPopupFrameCreatedCallback callback) override {
creation_info_ = std::move(creation_info);
frame_ = std::move(frame);
popup_ack_callback_ = std::move(callback);
if (received_popup_callback_)
std::move(received_popup_callback_).Run();
}
fidl::InterfaceHandle<fuchsia::web::Frame> frame_;
fuchsia::web::PopupFrameCreationInfo creation_info_;
base::OnceClosure received_popup_callback_;
OnPopupFrameCreatedCallback popup_ack_callback_;
};
IN_PROC_BROWSER_TEST_F(FrameImplTest, PopupWindow) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL popup_url(embedded_test_server()->GetURL(kPopupPath));
GURL popup_child_url(embedded_test_server()->GetURL(kPopupRedirectPath));
GURL title1_url(embedded_test_server()->GetURL(kPage1Path));
fuchsia::web::FramePtr frame = CreateFrame();
TestPopupListener popup_listener;
fidl::Binding<fuchsia::web::PopupFrameCreationListener>
popup_listener_binding(&popup_listener);
frame->SetPopupFrameCreationListener(popup_listener_binding.NewBinding());
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(controller.get(), {},
popup_url.spec()));
// Verify the popup's initial URL, "popup_child.html".
fuchsia::web::FramePtr popup_frame;
fuchsia::web::PopupFrameCreationInfo popup_info;
popup_listener.GetAndAckNextPopup(&popup_frame, &popup_info);
EXPECT_EQ(popup_info.initial_url(), popup_child_url);
// Verify that the popup eventually redirects to "title1.html".
cr_fuchsia::TestNavigationListener popup_nav_listener;
fidl::Binding<fuchsia::web::NavigationEventListener>
popup_nav_listener_binding(&popup_nav_listener);
popup_frame->SetNavigationEventListener(
popup_nav_listener_binding.NewBinding());
popup_nav_listener.RunUntilUrlAndTitleEquals(title1_url, kPage1Title);
}
IN_PROC_BROWSER_TEST_F(FrameImplTest, MultiplePopups) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL popup_url(embedded_test_server()->GetURL(kPopupMultiplePath));
GURL title1_url(embedded_test_server()->GetURL(kPage1Path));
GURL title2_url(embedded_test_server()->GetURL(kPage2Path));
fuchsia::web::FramePtr frame = CreateFrame();
TestPopupListener popup_listener;
fidl::Binding<fuchsia::web::PopupFrameCreationListener>
popup_listener_binding(&popup_listener);
frame->SetPopupFrameCreationListener(popup_listener_binding.NewBinding());
fuchsia::web::NavigationControllerPtr controller;
frame->GetNavigationController(controller.NewRequest());
EXPECT_TRUE(cr_fuchsia::LoadUrlAndExpectResponse(controller.get(), {},
popup_url.spec()));
fuchsia::web::FramePtr popup_frame;
fuchsia::web::PopupFrameCreationInfo popup_info;
popup_listener.GetAndAckNextPopup(&popup_frame, &popup_info);
EXPECT_EQ(popup_info.initial_url(), title1_url);
popup_listener.GetAndAckNextPopup(&popup_frame, &popup_info);
EXPECT_EQ(popup_info.initial_url(), title2_url);
}
<html>
<head><title>Popup child</title></head>
<body>
<script>
window.location.href = 'title1.html';
</script>
</body>
</html>
<html>
<head><title>Let's Spawn Popups Like It's 2005</title></head>
<body>
<script>
window.open('title1.html');
window.open('title2.html');
</script>
</body>
</html>
<html>
<head><title>Popup parent</title></head>
<body>
<script>
window.open('popup_child.html');
</script>
</body>
</html>
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