blob: 9ccc064435f199c9c646c104cfbf9af317e6bc2b [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/frame/fullscreen_controller.h"
#include "base/memory/ptr_util.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom-blink.h"
#include "third_party/blink/public/web/web_local_frame_client.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_fullscreen_options.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/exported/web_view_impl.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h"
#include "third_party/blink/renderer/core/frame/screen.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/spatial_navigation.h"
#include "third_party/blink/renderer/core/page/spatial_navigation_controller.h"
namespace blink {
namespace {
mojom::blink::FullscreenOptionsPtr ToMojoOptions(
LocalFrame* frame,
const FullscreenOptions* options,
FullscreenRequestType request_type) {
auto fullscreen_options = mojom::blink::FullscreenOptions::New();
fullscreen_options->prefers_navigation_bar =
options->navigationUI() != "hide";
if (options->hasScreen()) {
DCHECK(RuntimeEnabledFeatures::WindowPlacementEnabled(frame->DomWindow()));
if (options->screen()->DisplayId() != Screen::kInvalidDisplayId)
fullscreen_options->display_id = options->screen()->DisplayId();
}
// Propagate the type of fullscreen request (prefixed or unprefixed) to
// OOPIF ancestor frames so that they fire matching prefixed or unprefixed
// fullscreen events.
fullscreen_options->is_prefixed =
request_type & FullscreenRequestType::kPrefixed;
fullscreen_options->is_xr_overlay =
request_type & FullscreenRequestType::kForXrOverlay;
return fullscreen_options;
}
} // namespace
FullscreenController::FullscreenController(WebViewImpl* web_view_base)
: web_view_base_(web_view_base),
pending_frames_(MakeGarbageCollected<PendingFullscreenSet>()) {}
void FullscreenController::DidEnterFullscreen() {
// |Browser::EnterFullscreenModeForTab()| can enter fullscreen without going
// through |Fullscreen::RequestFullscreen()|, in which case there will be no
// fullscreen element. Do nothing.
if (state_ != State::kEnteringFullscreen)
return;
UpdatePageScaleConstraints(false);
// Only reset the scale for the local main frame.
if (web_view_base_->MainFrameImpl()) {
web_view_base_->SetPageScaleFactor(1.0f);
web_view_base_->SetVisualViewportOffset(FloatPoint());
}
state_ = State::kFullscreen;
NotifyFramesOfFullscreenEntry(true /* success */);
// TODO(foolip): If the top level browsing context (main frame) ends up with
// no fullscreen element, exit fullscreen again to recover.
}
void FullscreenController::DidExitFullscreen() {
// The browser process can exit fullscreen at any time, e.g. if the user
// presses Esc. After |Browser::EnterFullscreenModeForTab()|,
// |Browser::ExitFullscreenModeForTab()| will make it seem like we exit when
// not even in fullscreen. Do nothing.
if (state_ == State::kInitial)
return;
UpdatePageScaleConstraints(true);
state_ = State::kInitial;
// Notify the topmost local frames that we have exited fullscreen.
// |Fullscreen::DidExitFullscreen()| will take care of descendant frames.
for (Frame* frame = web_view_base_->GetPage()->MainFrame(); frame;) {
Frame* next_frame = frame->Tree().TraverseNext();
if (frame->IsRemoteFrame()) {
frame = next_frame;
continue;
}
auto* local_frame = To<LocalFrame>(frame);
DCHECK(local_frame->IsLocalRoot());
if (Document* document = local_frame->GetDocument())
Fullscreen::DidExitFullscreen(*document);
// Skip over all descendant frames.
while (next_frame && next_frame->Tree().IsDescendantOf(frame))
next_frame = next_frame->Tree().TraverseNext();
frame = next_frame;
}
}
void FullscreenController::EnterFullscreen(LocalFrame& frame,
const FullscreenOptions* options,
FullscreenRequestType request_type) {
// TODO(dtapuska): If we are already in fullscreen. If the options are
// different than the currently requested one we may wish to request
// fullscreen mode again.
// If already fullscreen or exiting fullscreen, synchronously call
// |DidEnterFullscreen()|. When exiting, the coming |DidExitFullscreen()| call
// will again notify all frames.
if (state_ == State::kFullscreen || state_ == State::kExitingFullscreen) {
State old_state = state_;
state_ = State::kEnteringFullscreen;
DidEnterFullscreen();
state_ = old_state;
return;
}
// We need to store these values here rather than in |DidEnterFullscreen()|
// since by the time the latter is called, a Resize has already occurred,
// clamping the scroll offset. Don't save values if we're still waiting to
// restore a previous set. This can happen if we exit and quickly reenter
// fullscreen without performing a layout.
if (state_ == State::kInitial) {
initial_background_color_override_enabled_ =
web_view_base_->BackgroundColorOverrideEnabled();
initial_background_color_override_ =
web_view_base_->BackgroundColorOverride();
}
pending_frames_->insert(&frame);
// If already entering fullscreen, just wait.
if (state_ == State::kEnteringFullscreen)
return;
DCHECK(state_ == State::kInitial);
auto fullscreen_options = ToMojoOptions(&frame, options, request_type);
#if DCHECK_IS_ON()
DVLOG(2) << __func__ << ": request_type="
<< FullscreenRequestTypeToDebugString(request_type)
<< " fullscreen_options={display_id="
<< fullscreen_options->display_id
<< ", is_prefixed=" << fullscreen_options->is_prefixed
<< ", is_xr_overlay=" << fullscreen_options->is_xr_overlay << "}";
#endif
// Don't send redundant EnterFullscreen message to the browser for the
// ancestor frames if the subframe has already entered fullscreen.
if (!(request_type & FullscreenRequestType::kForCrossProcessDescendant)) {
frame.GetLocalFrameHostRemote().EnterFullscreen(
std::move(fullscreen_options),
WTF::Bind(&FullscreenController::EnterFullscreenCallback,
WTF::Unretained(this)));
}
state_ = State::kEnteringFullscreen;
}
void FullscreenController::ExitFullscreen(LocalFrame& frame) {
// If not in fullscreen, ignore any attempt to exit. In particular, when
// entering fullscreen, allow the transition into fullscreen to complete. Note
// that the browser process is ultimately in control and can still exit
// fullscreen at any time.
if (state_ != State::kFullscreen)
return;
frame.GetLocalFrameHostRemote().ExitFullscreen();
state_ = State::kExitingFullscreen;
}
void FullscreenController::FullscreenElementChanged(
Element* old_element,
Element* new_element,
const FullscreenOptions* options,
FullscreenRequestType request_type) {
DCHECK_NE(old_element, new_element);
// We only override the WebView's background color for overlay fullscreen
// video elements, so have to restore the override when the element changes.
auto* old_video_element = DynamicTo<HTMLVideoElement>(old_element);
if (old_video_element)
RestoreBackgroundColorOverride();
if (new_element) {
DCHECK(Fullscreen::IsFullscreenElement(*new_element));
if (auto* video_element = DynamicTo<HTMLVideoElement>(*new_element)) {
video_element->DidEnterFullscreen();
// If the video uses overlay fullscreen mode, make the background
// transparent.
if (video_element->UsesOverlayFullscreenVideo())
web_view_base_->SetBackgroundColorOverride(Color::kTransparent);
}
}
if (old_element) {
DCHECK(!Fullscreen::IsFullscreenElement(*old_element));
if (old_video_element)
old_video_element->DidExitFullscreen();
}
// Tell the browser the fullscreen state has changed.
if (Element* owner = new_element ? new_element : old_element) {
Document& doc = owner->GetDocument();
bool in_fullscreen = !!new_element;
if (LocalFrame* frame = doc.GetFrame()) {
mojom::blink::FullscreenOptionsPtr mojo_options;
if (in_fullscreen)
mojo_options = ToMojoOptions(frame, options, request_type);
frame->GetLocalFrameHostRemote().FullscreenStateChanged(
in_fullscreen, std::move(mojo_options));
if (IsSpatialNavigationEnabled(frame)) {
doc.GetPage()->GetSpatialNavigationController().FullscreenStateChanged(
new_element);
}
}
}
}
void FullscreenController::RestoreBackgroundColorOverride() {
if (web_view_base_->BackgroundColorOverrideEnabled() !=
initial_background_color_override_enabled_ ||
web_view_base_->BackgroundColorOverride() !=
initial_background_color_override_) {
if (initial_background_color_override_enabled_) {
web_view_base_->SetBackgroundColorOverride(
initial_background_color_override_);
} else {
web_view_base_->ClearBackgroundColorOverride();
}
}
}
void FullscreenController::NotifyFramesOfFullscreenEntry(bool granted) {
// Notify all pending local frames in order whether or not we successfully
// entered fullscreen.
for (LocalFrame* frame : *pending_frames_) {
if (frame) {
if (Document* document = frame->GetDocument()) {
Fullscreen::DidResolveEnterFullscreenRequest(*document, granted);
}
}
}
// Notify all local frames whether or not we successfully entered fullscreen.
for (Frame* frame = web_view_base_->GetPage()->MainFrame(); frame;
frame = frame->Tree().TraverseNext()) {
auto* local_frame = DynamicTo<LocalFrame>(frame);
if (!local_frame)
continue;
if (Document* document = local_frame->GetDocument()) {
Fullscreen::DidResolveEnterFullscreenRequest(*document, granted);
}
}
pending_frames_->clear();
}
void FullscreenController::EnterFullscreenCallback(bool granted) {
if (granted) {
// If the fullscreen is granted, then the VisualPropertiesUpdated message
// will later be fired and the state will be updated then.
} else {
state_ = State::kInitial;
NotifyFramesOfFullscreenEntry(false /* granted */);
}
}
void FullscreenController::UpdateSize() {
DCHECK(web_view_base_->GetPage());
if (state_ != State::kFullscreen && state_ != State::kExitingFullscreen)
return;
UpdatePageScaleConstraints(false);
}
void FullscreenController::UpdatePageScaleConstraints(bool reset_constraints) {
PageScaleConstraints fullscreen_constraints;
if (reset_constraints) {
web_view_base_->GetPageScaleConstraintsSet().SetNeedsReset(true);
} else {
fullscreen_constraints = PageScaleConstraints(1.0, 1.0, 1.0);
fullscreen_constraints.layout_size = FloatSize(web_view_base_->Size());
}
web_view_base_->GetPageScaleConstraintsSet().SetFullscreenConstraints(
fullscreen_constraints);
web_view_base_->GetPageScaleConstraintsSet().ComputeFinalConstraints();
// Although we called |ComputeFinalConstraints()| above, the "final"
// constraints are not actually final. They are still subject to scale factor
// clamping by contents size. Normally they should be dirtied due to contents
// size mutation after layout, however the contents size is not guaranteed to
// mutate, and the scale factor may remain unclamped. Just fire the event
// again to ensure the final constraints pick up the latest contents size.
web_view_base_->DidChangeContentsSize();
if (web_view_base_->MainFrameImpl() &&
web_view_base_->MainFrameImpl()->GetFrameView())
web_view_base_->MainFrameImpl()->GetFrameView()->SetNeedsLayout();
web_view_base_->UpdateMainFrameLayoutSize();
}
} // namespace blink