Commit 33972945 authored by Avi Drissman's avatar Avi Drissman Committed by Commit Bot

Fix fullscreen dropping for security

If a WebContents performs a UI-sensitive action (such as showing a
dialog or popup), we want to drop fullscreen from all related
WebContentses, and to prevent those WebContentses from gaining
fullscreen until the UI-sensitive situation is over.

Currently the search for related WebContentses is achieved by walking
up the opener and outer chains, but that misses related WebContentses
that are down those chains. These are one-directional chains that
aren't easily walked in the other direction.

This is fixed with two changes.

First, we keep a list of WebContentses that are in fullscreen, which
can then be searched to determine if they are down the chain from the
affected WebContents.

Second, when a request comes in to go fullscreen, we not only check if
the WebContents is prohibited from entering fullscreen, but we now
also check if one of the WebContentses up the chain is prohibited.

Bug: 1090835
Change-Id: I031e2e0a9ff79b387543a22ec3d643ab468d4d29
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2249090
Commit-Queue: Charlie Harrison <csharrison@chromium.org>
Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Auto-Submit: Avi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#780290}
parent 2cdd6145
...@@ -6,12 +6,14 @@ ...@@ -6,12 +6,14 @@
#include <stddef.h> #include <stddef.h>
#include <algorithm>
#include <cmath> #include <cmath>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/debug/dump_without_crashing.h" #include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
...@@ -356,6 +358,72 @@ int SendToAllFramesImpl(FrameTree& frame_tree, ...@@ -356,6 +358,72 @@ int SendToAllFramesImpl(FrameTree& frame_tree,
return number_of_messages; return number_of_messages;
} }
// Returns the set of all WebContentses that are reachable from |web_contents|
// by applying some combination of WebContents::GetOriginalOpener() and
// WebContents::GetOuterWebContents(). The |web_contents| parameter will be
// included in the returned set.
base::flat_set<WebContentsImpl*> GetAllOpeningWebContents(
WebContentsImpl* web_contents) {
base::flat_set<WebContentsImpl*> result;
base::flat_set<WebContentsImpl*> current;
current.insert(web_contents);
while (!current.empty()) {
WebContentsImpl* current_contents = *current.begin();
current.erase(current.begin());
auto insert_result = result.insert(current_contents);
if (insert_result.second) {
RenderFrameHostImpl* opener_rfh = current_contents->GetOriginalOpener();
if (opener_rfh) {
current.insert(static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(opener_rfh)));
}
WebContentsImpl* outer_contents = current_contents->GetOuterWebContents();
if (outer_contents)
current.insert(outer_contents);
}
}
return result;
}
// Used to attach the "set of fullscreen contents" to a browser context. Storing
// sets of WebContents on their browser context is done for two reasons. One,
// related WebContentses must necessarily share a browser context, so this saves
// lookup time by restricting to one specific browser context. Two, separating
// by browser context is preemptive paranoia about keeping things separate.
class FullscreenContentsHolder : public base::SupportsUserData::Data {
public:
FullscreenContentsHolder() = default;
~FullscreenContentsHolder() override = default;
FullscreenContentsHolder(const FullscreenContentsHolder&) = delete;
FullscreenContentsHolder& operator=(const FullscreenContentsHolder&) = delete;
base::flat_set<WebContentsImpl*>* set() { return &set_; }
private:
base::flat_set<WebContentsImpl*> set_;
};
const char kFullscreenContentsSet[] = "fullscreen-contents";
base::flat_set<WebContentsImpl*>* FullscreenContentsSet(
BrowserContext* browser_context) {
auto* set_holder = static_cast<FullscreenContentsHolder*>(
browser_context->GetUserData(kFullscreenContentsSet));
if (!set_holder) {
auto new_holder = std::make_unique<FullscreenContentsHolder>();
set_holder = new_holder.get();
browser_context->SetUserData(kFullscreenContentsSet, std::move(new_holder));
}
return set_holder->set();
}
} // namespace } // namespace
CreatedWindow::CreatedWindow() = default; CreatedWindow::CreatedWindow() = default;
...@@ -701,6 +769,8 @@ WebContentsImpl::~WebContentsImpl() { ...@@ -701,6 +769,8 @@ WebContentsImpl::~WebContentsImpl() {
// observers. // observers.
CHECK(!is_notifying_observers_); CHECK(!is_notifying_observers_);
FullscreenContentsSet(GetBrowserContext())->erase(this);
rwh_input_event_router_.reset(); rwh_input_event_router_.reset();
for (auto& entry : receiver_sets_) for (auto& entry : receiver_sets_)
...@@ -2522,7 +2592,14 @@ RenderWidgetHostImpl* WebContentsImpl::GetRenderWidgetHostWithPageFocus() { ...@@ -2522,7 +2592,14 @@ RenderWidgetHostImpl* WebContentsImpl::GetRenderWidgetHostWithPageFocus() {
} }
bool WebContentsImpl::CanEnterFullscreenMode() { bool WebContentsImpl::CanEnterFullscreenMode() {
return fullscreen_blocker_count_ == 0; // It's possible that this WebContents was spawned while blocking UI was on
// the screen, or that it was downstream from a WebContents when UI was
// blocked. Therefore, disqualify it from fullscreen if it or any upstream
// WebContents has an active blocker.
auto openers = GetAllOpeningWebContents(this);
return std::all_of(openers.begin(), openers.end(), [](auto* opener) {
return opener->fullscreen_blocker_count_ == 0;
});
} }
void WebContentsImpl::EnterFullscreenMode( void WebContentsImpl::EnterFullscreenMode(
...@@ -2547,6 +2624,8 @@ void WebContentsImpl::EnterFullscreenMode( ...@@ -2547,6 +2624,8 @@ void WebContentsImpl::EnterFullscreenMode(
for (auto& observer : observers_) for (auto& observer : observers_)
observer.DidToggleFullscreenModeForTab(IsFullscreenForCurrentTab(), false); observer.DidToggleFullscreenModeForTab(IsFullscreenForCurrentTab(), false);
FullscreenContentsSet(GetBrowserContext())->insert(this);
} }
void WebContentsImpl::ExitFullscreenMode(bool will_cause_resize) { void WebContentsImpl::ExitFullscreenMode(bool will_cause_resize) {
...@@ -2589,6 +2668,8 @@ void WebContentsImpl::ExitFullscreenMode(bool will_cause_resize) { ...@@ -2589,6 +2668,8 @@ void WebContentsImpl::ExitFullscreenMode(bool will_cause_resize) {
if (display_cutout_host_impl_) if (display_cutout_host_impl_)
display_cutout_host_impl_->DidExitFullscreen(); display_cutout_host_impl_->DidExitFullscreen();
FullscreenContentsSet(GetBrowserContext())->erase(this);
} }
void WebContentsImpl::FullscreenStateChanged(RenderFrameHost* rfh, void WebContentsImpl::FullscreenStateChanged(RenderFrameHost* rfh,
...@@ -4247,37 +4328,47 @@ void WebContentsImpl::ExitFullscreen(bool will_cause_resize) { ...@@ -4247,37 +4328,47 @@ void WebContentsImpl::ExitFullscreen(bool will_cause_resize) {
} }
base::ScopedClosureRunner WebContentsImpl::ForSecurityDropFullscreen() { base::ScopedClosureRunner WebContentsImpl::ForSecurityDropFullscreen() {
// There are two chains of WebContents to kick out of fullscreen. // Kick WebContentses that are "related" to this WebContents out of
// // fullscreen. This needs to be done with two passes, because it is simple to
// Chain 1, the inner/outer WebContents chain. If an inner WebContents has // walk _up_ the chain of openers and outer contents, but it not simple to
// done something that requires the browser to drop fullscreen, drop // walk _down_ the chain.
// fullscreen from it and any outer WebContents that may be in fullscreen.
// // First, determine if any WebContents that is in fullscreen has this
// Chain 2, the opener WebContents chain. If a WebContents has done something // WebContents as an upstream contents. Drop that WebContents out of
// that requires the browser to drop fullscreen, drop fullscreen from any // fullscreen if it does. This is theoretically quadratic-ish (fullscreen
// WebContents that was involved in the chain of opening it. // contentses x each one's opener length) but neither of those is expected to
// // ever be a large number.
// Note that these two chains don't interact, as only a top-level WebContents
// can have an opener. This simplifies things. auto fullscreen_set_copy = *FullscreenContentsSet(GetBrowserContext());
for (auto* fullscreen_contents : fullscreen_set_copy) {
// Checking IsFullscreenForCurrentTab() for tabs in the fullscreen set may
// seem redundant, but teeeeechnically fullscreen is run by the delegate,
// and it's possible that the delegate's notion of fullscreen may have
// changed outside of WebContents's notice.
if (fullscreen_contents->IsFullscreenForCurrentTab()) {
auto opener_contentses = GetAllOpeningWebContents(fullscreen_contents);
if (opener_contentses.count(this))
fullscreen_contents->ExitFullscreen(true);
}
}
// Second, walk upstream from this WebContents, and drop the fullscreen of
// all WebContentses that are in fullscreen. Block all the WebContentses in
// the chain from entering fullscreen while the returned closure runner is
// alive. It's OK that this set doesn't contain downstream WebContentses, as
// any request to enter fullscreen will have the upstream of the WebContents
// checked. (See CanEnterFullscreenMode().)
std::vector<base::WeakPtr<WebContentsImpl>> blocked_contentses; std::vector<base::WeakPtr<WebContentsImpl>> blocked_contentses;
WebContentsImpl* web_contents = this; for (auto* opener : GetAllOpeningWebContents(this)) {
while (web_contents) {
// Drop fullscreen if the WebContents is in it, and... // Drop fullscreen if the WebContents is in it, and...
if (web_contents->IsFullscreenForCurrentTab()) if (opener->IsFullscreenForCurrentTab())
web_contents->ExitFullscreen(true); opener->ExitFullscreen(true);
// ...block the WebContents from entering fullscreen until further notice. // ...block the WebContents from entering fullscreen until further notice.
++web_contents->fullscreen_blocker_count_; ++opener->fullscreen_blocker_count_;
blocked_contentses.push_back(web_contents->weak_factory_.GetWeakPtr()); blocked_contentses.push_back(opener->weak_factory_.GetWeakPtr());
if (web_contents->HasOriginalOpener()) {
web_contents = static_cast<WebContentsImpl*>(
FromRenderFrameHost(web_contents->GetOriginalOpener()));
} else {
web_contents = web_contents->GetOuterWebContents();
}
} }
return base::ScopedClosureRunner(base::BindOnce( return base::ScopedClosureRunner(base::BindOnce(
......
...@@ -1518,7 +1518,7 @@ class TestWCDelegateForDialogsAndFullscreen : public JavaScriptDialogManager, ...@@ -1518,7 +1518,7 @@ class TestWCDelegateForDialogsAndFullscreen : public JavaScriptDialogManager,
std::string last_message() { return last_message_; } std::string last_message() { return last_message_; }
WebContents* last_popup() { return popup_.get(); } WebContents* last_popup() { return popups_.back().get(); }
// WebContentsDelegate // WebContentsDelegate
...@@ -1553,7 +1553,7 @@ class TestWCDelegateForDialogsAndFullscreen : public JavaScriptDialogManager, ...@@ -1553,7 +1553,7 @@ class TestWCDelegateForDialogsAndFullscreen : public JavaScriptDialogManager,
const gfx::Rect& initial_rect, const gfx::Rect& initial_rect,
bool user_gesture, bool user_gesture,
bool* was_blocked) override { bool* was_blocked) override {
popup_ = std::move(new_contents); popups_.push_back(std::move(new_contents));
if (waiting_for_ == kNewContents) { if (waiting_for_ == kNewContents) {
waiting_for_ = kNothing; waiting_for_ = kNothing;
...@@ -1615,7 +1615,7 @@ class TestWCDelegateForDialogsAndFullscreen : public JavaScriptDialogManager, ...@@ -1615,7 +1615,7 @@ class TestWCDelegateForDialogsAndFullscreen : public JavaScriptDialogManager,
bool is_fullscreen_ = false; bool is_fullscreen_ = false;
std::unique_ptr<WebContents> popup_; std::vector<std::unique_ptr<WebContents>> popups_;
std::unique_ptr<base::RunLoop> run_loop_ = std::make_unique<base::RunLoop>(); std::unique_ptr<base::RunLoop> run_loop_ = std::make_unique<base::RunLoop>();
...@@ -2497,7 +2497,7 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ...@@ -2497,7 +2497,7 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
EXPECT_FALSE(wc->IsFullscreenForCurrentTab()); EXPECT_FALSE(wc->IsFullscreenForCurrentTab());
} }
// Tests that if a popup is opened, all WebContentses down the opener chain are // Tests that if a popup is opened, a WebContents *up* the opener chain is
// kicked out of fullscreen. // kicked out of fullscreen.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
PopupsOfPopupsFromJavaScriptEndFullscreen) { PopupsOfPopupsFromJavaScriptEndFullscreen) {
...@@ -2529,6 +2529,38 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ...@@ -2529,6 +2529,38 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
EXPECT_FALSE(wc->IsFullscreenForCurrentTab()); EXPECT_FALSE(wc->IsFullscreenForCurrentTab());
} }
// Tests that if a popup is opened, a WebContents *down* the opener chain is
// kicked out of fullscreen.
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
PopupsFromJavaScriptEndFullscreenDownstream) {
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
TestWCDelegateForDialogsAndFullscreen test_delegate(wc);
GURL url("about:blank");
EXPECT_TRUE(NavigateToURL(shell(), url));
// Make a popup.
std::string popup_script = "window.open('', '', 'width=200,height=100')";
test_delegate.WillWaitForNewContents();
EXPECT_TRUE(content::ExecuteScript(wc, popup_script));
test_delegate.Wait();
WebContentsImpl* popup =
static_cast<WebContentsImpl*>(test_delegate.last_popup());
// Put the popup into fullscreen.
TestWCDelegateForDialogsAndFullscreen popup_test_delegate(popup);
popup->EnterFullscreenMode(popup->GetMainFrame(), {});
EXPECT_TRUE(popup->IsFullscreenForCurrentTab());
// Have the original page open a new popup.
test_delegate.WillWaitForNewContents();
EXPECT_TRUE(content::ExecuteScript(wc, popup_script));
test_delegate.Wait();
// Ensure the popup, being downstream from the opener, loses fullscreen.
EXPECT_FALSE(popup->IsFullscreenForCurrentTab());
}
IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
FocusFromJavaScriptEndsFullscreen) { FocusFromJavaScriptEndsFullscreen) {
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents()); WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
......
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