blob: e533cab2fded69065b695c81132cfdd9ba50e87e [file] [log] [blame]
// Copyright 2014 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 "third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller_impl.h"
#include <memory>
#include <utility>
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_screen_info.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.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/fullscreen/scoped_allow_fullscreen.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/modules/screen_orientation/screen_orientation.h"
#include "third_party/blink/renderer/modules/screen_orientation/screen_orientation_dispatcher.h"
#include "third_party/blink/renderer/platform/web_test_support.h"
namespace blink {
ScreenOrientationControllerImpl::~ScreenOrientationControllerImpl() = default;
void ScreenOrientationControllerImpl::ProvideTo(LocalFrame& frame) {
ScreenOrientationController::ProvideTo(
frame, MakeGarbageCollected<ScreenOrientationControllerImpl>(frame));
}
ScreenOrientationControllerImpl* ScreenOrientationControllerImpl::From(
LocalFrame& frame) {
return static_cast<ScreenOrientationControllerImpl*>(
ScreenOrientationController::From(frame));
}
ScreenOrientationControllerImpl::ScreenOrientationControllerImpl(
LocalFrame& frame)
: ScreenOrientationController(frame),
ContextLifecycleObserver(frame.GetDocument()),
PlatformEventController(frame.GetDocument()),
dispatch_event_timer_(
frame.GetTaskRunner(TaskType::kMiscPlatformAPI),
this,
&ScreenOrientationControllerImpl::DispatchEventTimerFired) {
AssociatedInterfaceProvider* provider =
frame.GetRemoteNavigationAssociatedInterfaces();
if (provider)
provider->GetInterface(&screen_orientation_service_);
}
// Compute the screen orientation using the orientation angle and the screen
// width / height.
WebScreenOrientationType ScreenOrientationControllerImpl::ComputeOrientation(
const IntRect& rect,
uint16_t rotation) {
// Bypass orientation detection in web tests to get consistent results.
// FIXME: The screen dimension should be fixed when running the web tests
// to avoid such issues.
if (WebTestSupport::IsRunningWebTest())
return kWebScreenOrientationPortraitPrimary;
bool is_tall_display = rotation % 180 ? rect.Height() < rect.Width()
: rect.Height() > rect.Width();
// https://w3c.github.io/screen-orientation/#dfn-current-orientation-angle
// allows the UA to associate *-primary and *-secondary values at will. Blink
// arbitrarily chooses rotation 0 to always be portrait-primary or
// landscape-primary, and portrait-primary + 90 to be landscape-primary, which
// together fully determine the relationship.
switch (rotation) {
case 0:
return is_tall_display ? kWebScreenOrientationPortraitPrimary
: kWebScreenOrientationLandscapePrimary;
case 90:
return is_tall_display ? kWebScreenOrientationLandscapePrimary
: kWebScreenOrientationPortraitSecondary;
case 180:
return is_tall_display ? kWebScreenOrientationPortraitSecondary
: kWebScreenOrientationLandscapeSecondary;
case 270:
return is_tall_display ? kWebScreenOrientationLandscapeSecondary
: kWebScreenOrientationPortraitPrimary;
default:
NOTREACHED();
return kWebScreenOrientationPortraitPrimary;
}
}
void ScreenOrientationControllerImpl::UpdateOrientation() {
DCHECK(orientation_);
DCHECK(GetFrame());
DCHECK(GetFrame()->GetPage());
ChromeClient& chrome_client = GetFrame()->GetPage()->GetChromeClient();
WebScreenInfo screen_info = chrome_client.GetScreenInfo();
WebScreenOrientationType orientation_type = screen_info.orientation_type;
if (orientation_type == kWebScreenOrientationUndefined) {
// The embedder could not provide us with an orientation, deduce it
// ourselves.
orientation_type = ComputeOrientation(chrome_client.GetScreenInfo().rect,
screen_info.orientation_angle);
}
DCHECK(orientation_type != kWebScreenOrientationUndefined);
orientation_->SetType(orientation_type);
orientation_->SetAngle(screen_info.orientation_angle);
}
bool ScreenOrientationControllerImpl::IsActive() const {
return orientation_ && screen_orientation_service_;
}
bool ScreenOrientationControllerImpl::IsVisible() const {
return GetPage() && GetPage()->IsPageVisible();
}
bool ScreenOrientationControllerImpl::IsActiveAndVisible() const {
return IsActive() && IsVisible();
}
void ScreenOrientationControllerImpl::PageVisibilityChanged() {
NotifyDispatcher();
if (!IsActiveAndVisible())
return;
DCHECK(GetFrame());
DCHECK(GetFrame()->GetPage());
// The orientation type and angle are tied in a way that if the angle has
// changed, the type must have changed.
unsigned short current_angle = GetFrame()
->GetPage()
->GetChromeClient()
.GetScreenInfo()
.orientation_angle;
// FIXME: sendOrientationChangeEvent() currently send an event all the
// children of the frame, so it should only be called on the frame on
// top of the tree. We would need the embedder to call
// sendOrientationChangeEvent on every WebFrame part of a WebView to be
// able to remove this.
if (GetFrame() == GetFrame()->LocalFrameRoot() &&
orientation_->angle() != current_angle)
NotifyOrientationChanged();
}
void ScreenOrientationControllerImpl::NotifyOrientationChanged() {
if (!IsVisible() || !GetFrame())
return;
if (IsActive())
UpdateOrientation();
// Keep track of the frames that need to be notified before notifying the
// current frame as it will prevent side effects from the change event
// handlers.
HeapVector<Member<LocalFrame>> child_frames;
for (Frame* child = GetFrame()->Tree().FirstChild(); child;
child = child->Tree().NextSibling()) {
if (child->IsLocalFrame())
child_frames.push_back(ToLocalFrame(child));
}
// Notify current orientation object.
if (IsActive() && !dispatch_event_timer_.IsActive())
dispatch_event_timer_.StartOneShot(TimeDelta(), FROM_HERE);
// ... and child frames, if they have a ScreenOrientationControllerImpl.
for (LocalFrame* child_frame : child_frames) {
if (ScreenOrientationControllerImpl* controller =
ScreenOrientationControllerImpl::From(*child_frame)) {
controller->NotifyOrientationChanged();
}
}
}
void ScreenOrientationControllerImpl::SetOrientation(
ScreenOrientation* orientation) {
orientation_ = orientation;
if (orientation_)
UpdateOrientation();
NotifyDispatcher();
}
void ScreenOrientationControllerImpl::lock(
WebScreenOrientationLockType orientation,
std::unique_ptr<WebLockOrientationCallback> callback) {
// When detached, the |screen_orientation_service_| is no longer valid.
if (!screen_orientation_service_)
return;
CancelPendingLocks();
pending_callback_ = std::move(callback);
screen_orientation_service_->LockOrientation(
orientation,
WTF::Bind(&ScreenOrientationControllerImpl::OnLockOrientationResult,
WrapWeakPersistent(this), ++request_id_));
active_lock_ = true;
}
void ScreenOrientationControllerImpl::unlock() {
// When detached, the |screen_orientation_service_| is no longer valid.
if (!screen_orientation_service_)
return;
CancelPendingLocks();
screen_orientation_service_->UnlockOrientation();
active_lock_ = false;
}
bool ScreenOrientationControllerImpl::MaybeHasActiveLock() const {
return active_lock_;
}
void ScreenOrientationControllerImpl::DispatchEventTimerFired(TimerBase*) {
if (!orientation_)
return;
ScopedAllowFullscreen allow_fullscreen(
ScopedAllowFullscreen::kOrientationChange);
orientation_->DispatchEvent(*Event::Create(event_type_names::kChange));
}
void ScreenOrientationControllerImpl::DidUpdateData() {
// Do nothing.
}
void ScreenOrientationControllerImpl::RegisterWithDispatcher() {
ScreenOrientationDispatcher::Instance().AddController(this);
}
void ScreenOrientationControllerImpl::UnregisterWithDispatcher() {
ScreenOrientationDispatcher::Instance().RemoveController(this);
}
bool ScreenOrientationControllerImpl::HasLastData() {
return true;
}
void ScreenOrientationControllerImpl::ContextDestroyed(ExecutionContext*) {
StopUpdating();
screen_orientation_service_ = nullptr;
active_lock_ = false;
}
void ScreenOrientationControllerImpl::NotifyDispatcher() {
if (orientation_ && GetPage()->IsPageVisible())
StartUpdating();
else
StopUpdating();
}
void ScreenOrientationControllerImpl::Trace(blink::Visitor* visitor) {
visitor->Trace(orientation_);
ContextLifecycleObserver::Trace(visitor);
Supplement<LocalFrame>::Trace(visitor);
PlatformEventController::Trace(visitor);
}
void ScreenOrientationControllerImpl::SetScreenOrientationAssociatedPtrForTests(
ScreenOrientationAssociatedPtr screen_orientation_associated_ptr) {
screen_orientation_service_ = std::move(screen_orientation_associated_ptr);
}
void ScreenOrientationControllerImpl::OnLockOrientationResult(
int request_id,
ScreenOrientationLockResult result) {
if (!pending_callback_ || request_id != request_id_)
return;
switch (result) {
case ScreenOrientationLockResult::SCREEN_ORIENTATION_LOCK_RESULT_SUCCESS:
pending_callback_->OnSuccess();
break;
case ScreenOrientationLockResult::
SCREEN_ORIENTATION_LOCK_RESULT_ERROR_NOT_AVAILABLE:
pending_callback_->OnError(kWebLockOrientationErrorNotAvailable);
break;
case ScreenOrientationLockResult::
SCREEN_ORIENTATION_LOCK_RESULT_ERROR_FULLSCREEN_REQUIRED:
pending_callback_->OnError(kWebLockOrientationErrorFullscreenRequired);
break;
case ScreenOrientationLockResult::
SCREEN_ORIENTATION_LOCK_RESULT_ERROR_CANCELED:
pending_callback_->OnError(kWebLockOrientationErrorCanceled);
break;
default:
NOTREACHED();
break;
}
pending_callback_.reset();
}
void ScreenOrientationControllerImpl::CancelPendingLocks() {
if (!pending_callback_)
return;
pending_callback_->OnError(kWebLockOrientationErrorCanceled);
pending_callback_.reset();
}
int ScreenOrientationControllerImpl::GetRequestIdForTests() {
return pending_callback_ ? request_id_ : -1;
}
} // namespace blink