blob: 398b84ba9e67066eb97822d31e26e6a7a9c005ea [file] [log] [blame]
// Copyright 2016 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/animationworklet/animation_worklet_global_scope.h"
#include "base/optional.h"
#include "third_party/blink/renderer/bindings/core/v8/generated_code_helper.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_function.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_animate_callback.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_animator_constructor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_state_callback.h"
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
#include "third_party/blink/renderer/core/workers/worker_thread.h"
#include "third_party/blink/renderer/modules/animationworklet/animation_worklet_proxy_client.h"
#include "third_party/blink/renderer/modules/animationworklet/worklet_animation_effect_timings.h"
#include "third_party/blink/renderer/modules/animationworklet/worklet_animation_options.h"
#include "third_party/blink/renderer/platform/bindings/callback_method_retriever.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding_macros.h"
#include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
void UpdateAnimation(v8::Isolate* isolate,
Animator* animator,
WorkletAnimationId id,
double current_time,
AnimationWorkletDispatcherOutput* result) {
AnimationWorkletDispatcherOutput::AnimationState animation_output(id);
if (animator->Animate(isolate, current_time, &animation_output)) {
result->animations.push_back(std::move(animation_output));
}
}
} // namespace
AnimationWorkletGlobalScope::AnimationWorkletGlobalScope(
std::unique_ptr<GlobalScopeCreationParams> creation_params,
WorkerThread* thread)
: WorkletGlobalScope(std::move(creation_params),
thread->GetWorkerReportingProxy(),
thread) {}
AnimationWorkletGlobalScope::~AnimationWorkletGlobalScope() = default;
void AnimationWorkletGlobalScope::Trace(Visitor* visitor) const {
visitor->Trace(animator_definitions_);
visitor->Trace(animators_);
WorkletGlobalScope::Trace(visitor);
}
void AnimationWorkletGlobalScope::Dispose() {
DCHECK(IsContextThread());
if (AnimationWorkletProxyClient* proxy_client =
AnimationWorkletProxyClient::From(Clients()))
proxy_client->Dispose();
WorkletGlobalScope::Dispose();
}
Animator* AnimationWorkletGlobalScope::CreateAnimatorFor(
int animation_id,
const String& name,
WorkletAnimationOptions options,
scoped_refptr<SerializedScriptValue> serialized_state,
const Vector<base::Optional<base::TimeDelta>>& local_times,
const Vector<Timing>& timings) {
DCHECK(!animators_.at(animation_id));
Animator* animator =
CreateInstance(name, options, serialized_state, local_times, timings);
if (!animator)
return nullptr;
animators_.Set(animation_id, animator);
return animator;
}
void AnimationWorkletGlobalScope::UpdateAnimatorsList(
const AnimationWorkletInput& input) {
DCHECK(IsContextThread());
ScriptState* script_state = ScriptController()->GetScriptState();
ScriptState::Scope scope(script_state);
for (const auto& worklet_animation_id : input.removed_animations)
animators_.erase(worklet_animation_id.animation_id);
for (const auto& animation : input.added_and_updated_animations) {
int id = animation.worklet_animation_id.animation_id;
DCHECK(!animators_.Contains(id));
const String name =
String::FromUTF8(animation.name.data(), animation.name.size());
WorkletAnimationOptions options(nullptr);
// Down casting to blink type to access the serialized value.
if (animation.options) {
options =
*(static_cast<WorkletAnimationOptions*>(animation.options.get()));
}
// Down casting to blink type
Vector<Timing> timings = (static_cast<WorkletAnimationEffectTimings*>(
animation.effect_timings.get()))
->GetTimings()
->data;
DCHECK_GE(timings.size(), 1u);
Vector<base::Optional<base::TimeDelta>> local_times(
static_cast<int>(timings.size()), base::nullopt);
CreateAnimatorFor(id, name, options, nullptr /* serialized_state */,
local_times, timings);
}
}
void AnimationWorkletGlobalScope::UpdateAnimators(
const AnimationWorkletInput& input,
AnimationWorkletOutput* output,
bool (*predicate)(Animator*)) {
DCHECK(IsContextThread());
ScriptState* script_state = ScriptController()->GetScriptState();
v8::Isolate* isolate = script_state->GetIsolate();
ScriptState::Scope scope(script_state);
for (const auto& animation : input.added_and_updated_animations) {
int id = animation.worklet_animation_id.animation_id;
Animator* animator = animators_.at(id);
// We don't try to create an animator if there isn't any.
// This can only happen if constructing an animator instance has failed
// e.g., the constructor throws an exception.
if (!animator || !predicate(animator))
continue;
UpdateAnimation(isolate, animator, animation.worklet_animation_id,
animation.current_time, output);
}
for (const auto& animation : input.updated_animations) {
int id = animation.worklet_animation_id.animation_id;
Animator* animator = animators_.at(id);
// We don't try to create an animator if there isn't any.
if (!animator || !predicate(animator))
continue;
UpdateAnimation(isolate, animator, animation.worklet_animation_id,
animation.current_time, output);
}
}
void AnimationWorkletGlobalScope::RegisterWithProxyClientIfNeeded() {
if (registered_)
return;
if (AnimationWorkletProxyClient* proxy_client =
AnimationWorkletProxyClient::From(Clients())) {
proxy_client->AddGlobalScope(this);
registered_ = true;
}
}
void AnimationWorkletGlobalScope::registerAnimator(
const String& name,
V8AnimatorConstructor* animator_ctor,
ExceptionState& exception_state) {
RegisterWithProxyClientIfNeeded();
DCHECK(IsContextThread());
if (animator_definitions_.Contains(name)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"A class with name:'" + name + "' is already registered.");
return;
}
if (name.IsEmpty()) {
exception_state.ThrowTypeError("The empty string is not a valid name.");
return;
}
if (!animator_ctor->IsConstructor()) {
exception_state.ThrowTypeError(
"The provided callback is not a constructor.");
return;
}
CallbackMethodRetriever retriever(animator_ctor);
retriever.GetPrototypeObject(exception_state);
if (exception_state.HadException())
return;
v8::Local<v8::Function> v8_animate =
retriever.GetMethodOrThrow("animate", exception_state);
if (exception_state.HadException())
return;
V8AnimateCallback* animate = V8AnimateCallback::Create(v8_animate);
v8::Local<v8::Value> v8_state =
retriever.GetMethodOrUndefined("state", exception_state);
if (exception_state.HadException())
return;
V8StateCallback* state =
v8_state->IsFunction()
? V8StateCallback::Create(v8_state.As<v8::Function>())
: nullptr;
AnimatorDefinition* definition =
MakeGarbageCollected<AnimatorDefinition>(animator_ctor, animate, state);
// TODO(https://crbug.com/923063): Ensure worklet definitions are compatible
// across global scopes.
animator_definitions_.Set(name, definition);
// TODO(crbug.com/920722): Currently one animator name is synced back per
// registration. Eventually all registered names should be synced in batch
// once a module completes its loading in the worklet scope.
if (AnimationWorkletProxyClient* proxy_client =
AnimationWorkletProxyClient::From(Clients())) {
proxy_client->SynchronizeAnimatorName(name);
}
}
Animator* AnimationWorkletGlobalScope::CreateInstance(
const String& name,
WorkletAnimationOptions options,
scoped_refptr<SerializedScriptValue> serialized_state,
const Vector<base::Optional<base::TimeDelta>>& local_times,
const Vector<Timing>& timings) {
DCHECK(IsContextThread());
AnimatorDefinition* definition = animator_definitions_.at(name);
if (!definition)
return nullptr;
ScriptState* script_state = ScriptController()->GetScriptState();
ScriptState::Scope scope(script_state);
v8::Isolate* isolate = script_state->GetIsolate();
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
v8::Local<v8::Value> v8_options =
options.GetData() ? options.GetData()->Deserialize(isolate)
: v8::Undefined(isolate).As<v8::Value>();
v8::Local<v8::Value> v8_state = serialized_state
? serialized_state->Deserialize(isolate)
: v8::Undefined(isolate).As<v8::Value>();
ScriptValue options_value(isolate, v8_options);
ScriptValue state_value(isolate, v8_state);
ScriptValue instance;
if (!definition->ConstructorFunction()
->Construct(options_value, state_value)
.To(&instance)) {
return nullptr;
}
return MakeGarbageCollected<Animator>(isolate, definition, instance.V8Value(),
name, std::move(options), local_times,
timings);
}
bool AnimationWorkletGlobalScope::IsAnimatorStateful(int animation_id) {
return animators_.at(animation_id)->IsStateful();
}
// Implementation of "Migrating an Animator Instance":
// https://drafts.css-houdini.org/css-animationworklet/#migrating-animator
// Note that per specification if the state function does not exist, the
// migration process should be aborted. However the following implementation
// is used for both the stateful and stateless animators. For the latter ones
// the migration (including name, options etc.) should be completed regardless
// the state function.
void AnimationWorkletGlobalScope::MigrateAnimatorsTo(
AnimationWorkletGlobalScope* target_global_scope) {
DCHECK_NE(this, target_global_scope);
ScriptState* script_state = ScriptController()->GetScriptState();
ScriptState::Scope scope(script_state);
v8::Isolate* isolate = script_state->GetIsolate();
for (const auto& animator_map : animators_) {
int animation_id = animator_map.key;
Animator* animator = animator_map.value;
scoped_refptr<SerializedScriptValue> serialized_state;
if (animator->IsStateful()) {
ExceptionState exception_state(script_state->GetIsolate(),
ExceptionState::kExecutionContext,
"Animator", "state");
// If an animator state function throws or the state is not
// serializable, the animator will be removed from the global scope.
// TODO(crbug.com/1090522): We should post an error message to console in
// case of exceptions.
v8::Local<v8::Value> state = animator->State(isolate, exception_state);
if (exception_state.HadException()) {
exception_state.ClearException();
continue;
}
// Do not skip migrating the stateful animator if its state is
// undefined.
if (!state->IsNullOrUndefined()) {
serialized_state = SerializedScriptValue::Serialize(
isolate, state, SerializedScriptValue::SerializeOptions(),
exception_state);
if (exception_state.HadException()) {
exception_state.ClearException();
continue;
}
}
}
Vector<base::Optional<base::TimeDelta>> local_times;
animator->GetLocalTimes(local_times);
target_global_scope->CreateAnimatorFor(
animation_id, animator->name(), animator->options(), serialized_state,
std::move(local_times), animator->GetTimings());
}
animators_.clear();
}
AnimatorDefinition* AnimationWorkletGlobalScope::FindDefinitionForTest(
const String& name) {
return animator_definitions_.at(name);
}
} // namespace blink