blob: 6ad6380ee0db98a24a6403a593460a669c81367e [file] [log] [blame]
// Copyright 2020 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/core/document_transition/document_transition.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_document_transition_init.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
namespace blink {
namespace {
const int32_t kDefaultDurationMs = 300;
DocumentTransition::Request::Effect ParseEffect(const String& input) {
using MapType = HashMap<String, DocumentTransition::Request::Effect>;
DEFINE_STATIC_LOCAL(
MapType*, lookup_map,
(new MapType{
{"cover-down", DocumentTransition::Request::Effect::kCoverDown},
{"cover-left", DocumentTransition::Request::Effect::kCoverLeft},
{"cover-right", DocumentTransition::Request::Effect::kCoverRight},
{"cover-up", DocumentTransition::Request::Effect::kCoverUp},
{"explode", DocumentTransition::Request::Effect::kExplode},
{"fade", DocumentTransition::Request::Effect::kFade},
{"implode", DocumentTransition::Request::Effect::kImplode},
{"reveal-down", DocumentTransition::Request::Effect::kRevealDown},
{"reveal-left", DocumentTransition::Request::Effect::kRevealLeft},
{"reveal-right", DocumentTransition::Request::Effect::kRevealRight},
{"reveal-up", DocumentTransition::Request::Effect::kRevealUp}}));
auto it = lookup_map->find(input);
return it != lookup_map->end() ? it->value
: DocumentTransition::Request::Effect::kNone;
}
} // namespace
DocumentTransition::DocumentTransition(Document* document)
: ExecutionContextLifecycleObserver(document->GetExecutionContext()),
document_(document) {}
void DocumentTransition::Trace(Visitor* visitor) const {
visitor->Trace(document_);
visitor->Trace(prepare_promise_resolver_);
ScriptWrappable::Trace(visitor);
ActiveScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
}
void DocumentTransition::ContextDestroyed() {
if (prepare_promise_resolver_) {
prepare_promise_resolver_->Detach();
prepare_promise_resolver_ = nullptr;
}
}
bool DocumentTransition::HasPendingActivity() const {
if (prepare_promise_resolver_)
return true;
return false;
}
ScriptPromise DocumentTransition::prepare(
ScriptState* script_state,
const DocumentTransitionInit* params) {
// Reject any previous prepare promises.
if (state_ == State::kPreparing || state_ == State::kPrepared) {
if (prepare_promise_resolver_) {
prepare_promise_resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError, "Aborted due to prepare() call"));
prepare_promise_resolver_ = nullptr;
}
state_ = State::kIdle;
}
// Increment the sequence id before any early outs so we will correctly
// process callbacks from previous requests.
++prepare_sequence_id_;
// If we are not attached to a view, then we can't prepare a transition.
// Reject the promise. We also reject the promise if we're in any state other
// than idle.
if (!document_ || !document_->View() || state_ != State::kIdle) {
return ScriptPromise::RejectWithDOMException(
script_state,
MakeGarbageCollected<DOMException>(DOMExceptionCode::kInvalidStateError,
"Invalid state"));
}
// We're going to be creating a new transition, initialize the params.
ParseAndSetTransitionParameters(params);
prepare_promise_resolver_ =
MakeGarbageCollected<ScriptPromiseResolver>(script_state);
state_ = State::kPreparing;
pending_request_ = Request::CreatePrepare(
effect_, duration_,
ConvertToBaseOnceCallback(CrossThreadBindOnce(
&DocumentTransition::NotifyPrepareCommitted,
WrapCrossThreadWeakPersistent(this), prepare_sequence_id_)));
NotifyHasChangesToCommit();
return prepare_promise_resolver_->Promise();
}
void DocumentTransition::start() {
if (state_ != State::kPrepared)
return;
state_ = State::kStarted;
pending_request_ = Request::CreateStart(ConvertToBaseOnceCallback(
CrossThreadBindOnce(&DocumentTransition::NotifyStartCommitted,
WrapCrossThreadWeakPersistent(this))));
NotifyHasChangesToCommit();
}
void DocumentTransition::NotifyHasChangesToCommit() {
if (!document_ || !document_->GetPage() || !document_->View())
return;
// Schedule a new frame.
document_->GetPage()->Animator().ScheduleVisualUpdate(document_->GetFrame());
// Ensure paint artifact compositor does an update, since that's the mechanism
// we use to pass transition requests to the compositor.
document_->View()->SetPaintArtifactCompositorNeedsUpdate();
}
void DocumentTransition::NotifyPrepareCommitted(uint32_t sequence_id) {
// This notification is for a different sequence id.
if (sequence_id != prepare_sequence_id_)
return;
DCHECK(state_ == State::kPreparing);
DCHECK(prepare_promise_resolver_);
prepare_promise_resolver_->Resolve();
prepare_promise_resolver_ = nullptr;
state_ = State::kPrepared;
}
void DocumentTransition::NotifyStartCommitted() {
// TODO(vmpstr): This should only be cleared when the animation is actually
// over which means we need to plumb the callback all the way to viz.
state_ = State::kIdle;
}
std::unique_ptr<DocumentTransition::Request>
DocumentTransition::TakePendingRequest() {
return std::move(pending_request_);
}
void DocumentTransition::ParseAndSetTransitionParameters(
const DocumentTransitionInit* params) {
duration_ = base::TimeDelta::FromMilliseconds(
params->hasDuration() ? params->duration() : kDefaultDurationMs);
effect_ = params->hasRootTransition() ? ParseEffect(params->rootTransition())
: Request::Effect::kNone;
}
} // namespace blink