blob: 5d8a75d4216191b5461c84ba99633274c6e921b3 [file] [log] [blame]
// Copyright 2018 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/layout/custom/css_layout_definition.h"
#include <memory>
#include "third_party/blink/renderer/bindings/core/v8/idl_types.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include "third_party/blink/renderer/bindings/core/v8/script_iterator.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_fragment_result_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_layout_fragment_request.h"
#include "third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/custom/custom_layout_constraints.h"
#include "third_party/blink/renderer/core/layout/custom/custom_layout_fragment.h"
#include "third_party/blink/renderer/core/layout/custom/fragment_result_options.h"
#include "third_party/blink/renderer/core/layout/custom/layout_custom.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding_macros.h"
#include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h"
namespace blink {
namespace {
bool IsLogicalHeightDefinite(const LayoutCustom& layout_custom) {
if (layout_custom.HasOverrideLogicalHeight())
return true;
// In quirks mode the document and body element stretch to the viewport.
if (layout_custom.StretchesToViewport())
return true;
if (layout_custom.HasDefiniteLogicalHeight())
return true;
return false;
}
} // namespace
CSSLayoutDefinition::CSSLayoutDefinition(
ScriptState* script_state,
v8::Local<v8::Function> constructor,
v8::Local<v8::Function> intrinsic_sizes,
v8::Local<v8::Function> layout,
const Vector<CSSPropertyID>& native_invalidation_properties,
const Vector<AtomicString>& custom_invalidation_properties,
const Vector<CSSPropertyID>& child_native_invalidation_properties,
const Vector<AtomicString>& child_custom_invalidation_properties)
: script_state_(script_state),
constructor_(script_state->GetIsolate(), constructor),
intrinsic_sizes_(script_state->GetIsolate(), intrinsic_sizes),
layout_(script_state->GetIsolate(), layout),
constructor_has_failed_(false) {
native_invalidation_properties_ = native_invalidation_properties;
custom_invalidation_properties_ = custom_invalidation_properties;
child_native_invalidation_properties_ = child_native_invalidation_properties;
child_custom_invalidation_properties_ = child_custom_invalidation_properties;
}
CSSLayoutDefinition::~CSSLayoutDefinition() = default;
CSSLayoutDefinition::Instance::Instance(CSSLayoutDefinition* definition,
v8::Local<v8::Object> instance)
: definition_(definition),
instance_(definition->script_state_->GetIsolate(), instance) {}
bool CSSLayoutDefinition::Instance::Layout(
const LayoutCustom& layout_custom,
FragmentResultOptions* fragment_result_options,
scoped_refptr<SerializedScriptValue>* fragment_result_data) {
ScriptState* script_state = definition_->GetScriptState();
ExecutionContext* execution_context = ExecutionContext::From(script_state);
if (!script_state->ContextIsValid()) {
return false;
}
ScriptState::Scope scope(script_state);
v8::Isolate* isolate = script_state->GetIsolate();
v8::Local<v8::Object> instance = instance_.NewLocal(isolate);
v8::Local<v8::Context> context = script_state->GetContext();
v8::Local<v8::Function> layout = definition_->layout_.NewLocal(isolate);
// TODO(ikilpatrick): Determine if knowing the size of the array ahead of
// time improves performance in any noticable way.
v8::Local<v8::Array> children = v8::Array::New(isolate, 0);
uint32_t index = 0;
for (LayoutBox* child = layout_custom.FirstChildBox(); child;
child = child->NextSiblingBox()) {
if (child->IsOutOfFlowPositioned())
continue;
CustomLayoutChild* layout_child = child->GetCustomLayoutChild();
DCHECK(layout_child);
bool success;
if (!children
->CreateDataProperty(
context, index++,
ToV8(layout_child, context->Global(), isolate))
.To(&success) &&
success)
return false;
}
LayoutUnit fixed_block_size(-1);
if (IsLogicalHeightDefinite(layout_custom)) {
LayoutBox::LogicalExtentComputedValues computed_values;
layout_custom.ComputeLogicalHeight(LayoutUnit(-1), LayoutUnit(),
computed_values);
fixed_block_size = computed_values.extent_;
}
CustomLayoutConstraints* constraints = new CustomLayoutConstraints(
layout_custom.LogicalWidth(), fixed_block_size,
layout_custom.GetConstraintData(), isolate);
// TODO(ikilpatrick): Instead of creating a new style_map each time here,
// store on LayoutCustom, and update when the style changes.
StylePropertyMapReadOnly* style_map =
new PrepopulatedComputedStylePropertyMap(
layout_custom.GetDocument(), layout_custom.StyleRef(),
layout_custom.GetNode(), definition_->native_invalidation_properties_,
definition_->custom_invalidation_properties_);
// TODO(ikilpatrick): Fill in layout constraints, and edges.
Vector<v8::Local<v8::Value>> argv = {
children,
v8::Undefined(isolate), // edges
ToV8(constraints, context->Global(), isolate),
ToV8(style_map, context->Global(), isolate),
};
v8::Local<v8::Value> generator_value;
if (!V8ScriptRunner::CallFunction(layout, execution_context, instance,
argv.size(), argv.data(), isolate)
.ToLocal(&generator_value))
return false;
DCHECK(generator_value->IsGeneratorObject());
v8::Local<v8::Object> generator =
v8::Local<v8::Object>::Cast(generator_value);
ScriptIterator iterator(generator, isolate);
v8::Local<v8::Value> next_value;
// We run the generator until it's exhausted.
ExceptionState exception_state(isolate, ExceptionState::kExecutionContext,
"CSSLayoutAPI", "Layout");
while (iterator.Next(execution_context, exception_state, next_value)) {
if (exception_state.HadException()) {
ReportException(&exception_state);
return false;
}
// The value should already be non-empty, if not it should have be caught
// by the exception_state.HadException() above.
v8::Local<v8::Value> value = iterator.GetValue().ToLocalChecked();
// Process a single fragment request.
if (V8LayoutFragmentRequest::hasInstance(value, isolate)) {
CustomLayoutFragmentRequest* fragment_request =
V8LayoutFragmentRequest::ToImpl(v8::Local<v8::Object>::Cast(value));
CustomLayoutFragment* fragment = fragment_request->PerformLayout(isolate);
if (!fragment) {
execution_context->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kInfoMessageLevel,
"Unable to perform layout request due to an invalid child, "
"falling back to block layout."));
return false;
}
next_value = ToV8(fragment, context->Global(), isolate);
continue;
}
// Process multiple fragment requests.
if (HasCallableIteratorSymbol(isolate, value, exception_state)) {
HeapVector<Member<CustomLayoutFragmentRequest>> requests =
NativeValueTraits<IDLSequence<CustomLayoutFragmentRequest>>::
NativeValue(isolate, value, exception_state);
if (exception_state.HadException()) {
ReportException(&exception_state);
return false;
}
v8::Local<v8::Array> results = v8::Array::New(isolate, requests.size());
uint32_t index = 0;
for (const auto& request : requests) {
CustomLayoutFragment* fragment = request->PerformLayout(isolate);
if (!fragment) {
execution_context->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kInfoMessageLevel,
"Unable to perform layout request due to an invalid child, "
"falling back to block layout."));
return false;
}
bool success;
if (!results
->CreateDataProperty(
context, index++,
ToV8(fragment, context->Global(), isolate))
.To(&success) &&
success)
return false;
}
next_value = results;
continue;
}
// We recieved something that wasn't either a CustomLayoutFragmentRequest,
// or a sequence of CustomLayoutFragmentRequests. Fallback to block layout.
execution_context->AddConsoleMessage(
ConsoleMessage::Create(kJSMessageSource, kInfoMessageLevel,
"Unable to parse the layout request, "
"falling back to block layout."));
return false;
}
if (exception_state.HadException()) {
ReportException(&exception_state);
return false;
}
// The value should already be non-empty, if not it should have be caught by
// the ReportException() above.
v8::Local<v8::Value> result_value = iterator.GetValue().ToLocalChecked();
// Attempt to convert the result.
V8FragmentResultOptions::ToImpl(isolate, result_value,
*fragment_result_options, exception_state);
if (exception_state.HadException()) {
V8ScriptRunner::ReportException(isolate, exception_state.GetException());
exception_state.ClearException();
execution_context->AddConsoleMessage(
ConsoleMessage::Create(kJSMessageSource, kInfoMessageLevel,
"Unable to parse the layout function "
"result, falling back to block layout."));
return false;
}
// Serialize any extra data provided by the web-developer to potentially pass
// up to the parent custom layout.
if (fragment_result_options->hasData()) {
// We serialize "kForStorage" so that SharedArrayBuffers can't be shared
// between LayoutWorkletGlobalScopes.
*fragment_result_data = SerializedScriptValue::Serialize(
isolate, fragment_result_options->data().V8Value(),
SerializedScriptValue::SerializeOptions(
SerializedScriptValue::kForStorage),
exception_state);
}
if (exception_state.HadException()) {
V8ScriptRunner::ReportException(isolate, exception_state.GetException());
exception_state.ClearException();
execution_context->AddConsoleMessage(
ConsoleMessage::Create(kJSMessageSource, kInfoMessageLevel,
"Unable to serialize the data provided in the "
"result, falling back to block layout."));
return false;
}
return true;
}
void CSSLayoutDefinition::Instance::ReportException(
ExceptionState* exception_state) {
ScriptState* script_state = definition_->GetScriptState();
v8::Isolate* isolate = script_state->GetIsolate();
ExecutionContext* execution_context = ExecutionContext::From(script_state);
// We synchronously report and clear the exception as we may never enter V8
// again (as the callbacks are invoked directly by the UA).
V8ScriptRunner::ReportException(isolate, exception_state->GetException());
exception_state->ClearException();
execution_context->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kInfoMessageLevel,
"The layout function failed, falling back to block layout."));
}
CSSLayoutDefinition::Instance* CSSLayoutDefinition::CreateInstance() {
if (constructor_has_failed_)
return nullptr;
// Ensure that we don't create an instance on a detached context.
if (!GetScriptState()->ContextIsValid()) {
return nullptr;
}
Instance* instance = nullptr;
ScriptState::Scope scope(GetScriptState());
v8::Isolate* isolate = script_state_->GetIsolate();
v8::Local<v8::Function> constructor = constructor_.NewLocal(isolate);
DCHECK(!IsUndefinedOrNull(constructor));
v8::Local<v8::Object> layout_instance;
if (V8ObjectConstructor::NewInstance(isolate, constructor)
.ToLocal(&layout_instance)) {
instance = new Instance(this, layout_instance);
} else {
constructor_has_failed_ = true;
}
return instance;
}
void CSSLayoutDefinition::Instance::Trace(blink::Visitor* visitor) {
visitor->Trace(definition_);
}
void CSSLayoutDefinition::Trace(Visitor* visitor) {
visitor->Trace(constructor_.Cast<v8::Value>());
visitor->Trace(intrinsic_sizes_.Cast<v8::Value>());
visitor->Trace(layout_.Cast<v8::Value>());
}
} // namespace blink