| /* |
| * 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 |