[css-layout-api] Implement the guts of the javascript layout runner.
This implements the hooks from the LayoutCustom object into a web
developer defined layout class.
See: https://drafts.css-houdini.org/css-layout-api/#invoke-a-layout-callback
Currently the only useful thing this does to allow the web developer to
return an "auto" block-size, e.g.
registerLayout('foo', class {
*intrinsicSizes() {}
*layout() {
return { autoBlockSize: Math.random() * 100; } // FUN!
}
});
The engine calculates the inline-size, and adjusts the block-size for the
web developer. This is a simplification on the current API as spec'd but
we'll see what the HoudiniTF thinks of this approach.
The other large behaviour change is appropriate "fallback" handling when
something goes wrong. See "fallback" tests.
Bug: 726125
Change-Id: I08cd2e439f4321bd534a4e3f7222fbdb898e1785
Reviewed-on: https://chromium-review.googlesource.com/917423
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Christian Biesinger <cbiesinger@chromium.org>
Reviewed-by: Morten Stenshorne <mstensho@chromium.org>
Reviewed-by: Hiroki Nakagawa <nhiroki@chromium.org>
Commit-Queue: Ian Kilpatrick <ikilpatrick@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537371}
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-absolute-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-absolute-ref.html
new file mode 100644
index 0000000..416d57c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-absolute-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<style>
+.container {
+ position: relative;
+ margin: 20px 0;
+ width: 200px;
+ height: 200px;
+ border: solid 2px;
+}
+
+.result {
+ position: absolute;
+ background: green;
+}
+</style>
+<div class="container">
+ <div class="result" style="width: 100px; height: 100px; bottom: 0px;"></div>
+</div>
+<div class="container">
+ <div class="result" style="width: 100px; height: 200px;"></div>
+</div>
+<div class="container">
+ <div class="result" style="width: 100px; height: 100px;"></div>
+</div>
+<div class="container">
+ <div class="result" style="width: 200px; height: 100px;"></div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-absolute.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-absolute.https.html
new file mode 100644
index 0000000..c2a58c9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-absolute.https.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-absolute-ref.html">
+<meta name="assert" content="This test checks that the absolute positioning respects the auto-block-size." />
+
+<style>
+.test {
+ position: absolute;
+ background: red;
+}
+
+@supports (display: layout(block-size-100)) {
+ .test {
+ display: layout(block-size-100);
+ background: green;
+ }
+}
+
+.container {
+ position: relative;
+ margin: 20px 0;
+ width: 200px;
+ height: 200px;
+ border: solid 2px;
+}
+
+.width-100 {
+ width: 100px;
+ writing-mode: horizontal-tb;
+}
+
+.height-100 {
+ height: 100px;
+ writing-mode: vertical-rl;
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- 100px x 100px, bottom-left -->
+<div class="container">
+ <div class="test width-100" style="bottom: 0px"></div>
+</div>
+
+<!-- 100px x 200px, left, auto-size is ignored. -->
+<div class="container">
+ <div class="test width-100" style="top: 0px; bottom: 0px"></div>
+</div>
+
+<!-- 100px x 100px, top-left -->
+<div class="container">
+ <div class="test height-100" style="left: 0px;"></div>
+</div>
+
+<!-- 100px x 100px, top, auto-size is ignored. -->
+<div class="container">
+ <div class="test height-100" style="left: 0px; right: 0px;"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-100', class {
+ *intrinsicSizes() {}
+ *layout() {
+ return {autoBlockSize: 100};
+ }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-flex-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-flex-ref.html
new file mode 100644
index 0000000..e71b104
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-flex-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<style>
+.container {
+ border: solid 2px;
+ display: flow-root;
+ width: 300px;
+}
+
+.child {
+ background: green;
+ border: solid 2px;
+ box-sizing: border-box;
+ float: left;
+ height: 100px;
+}
+</style>
+
+<div class="container">
+ <div class="child" style="width: 100px;"></div>
+ <div class="child" style="width: 200px;"></div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-flex.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-flex.https.html
new file mode 100644
index 0000000..fe95c7e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-flex.https.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-flex-ref.html">
+<meta name="assert" content="This test checks that the flex layout respects the auto-block-size." />
+
+<style>
+.flex {
+ width: 300px;
+ display: flex;
+ border: solid 2px;
+}
+
+.custom {
+ background: red;
+ box-sizing: border-box;
+ border: solid 2px;
+ height: 100px;
+ writing-mode: vertical-rl;
+}
+
+@supports (display: layout(block-size-100)) {
+ .custom-100 {
+ display: layout(block-size-100);
+ background: green;
+ }
+ .custom-50 {
+ display: layout(block-size-50);
+ background: green;
+ flex: 1;
+ }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- Tests that floats using an auto block size get positioned correctly. -->
+<div class="flex">
+ <div class="custom custom-100"></div>
+ <div class="custom custom-50"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-100', class {
+ *intrinsicSizes() {}
+ *layout() {
+ return {autoBlockSize: 100};
+ }
+});
+registerLayout('block-size-50', class {
+ *intrinsicSizes() {}
+ *layout() {
+ return {autoBlockSize: 50};
+ }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-floats-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-floats-ref.html
new file mode 100644
index 0000000..368d3d8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-floats-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<style>
+.container {
+ width: 150px;
+ border: solid 2px;
+}
+
+.left {
+ float: left;
+ background: green;
+ width: 100px;
+ height: 100px;
+}
+
+.right {
+ float: right;
+ background: green;
+ width: 100px;
+ height: 100px;
+}
+</style>
+
+<div class="container">
+ <div class="left"></div>
+ <div class="right"></div>
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-floats.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-floats.https.html
new file mode 100644
index 0000000..67775eb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-floats.https.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-floats-ref.html">
+<meta name="assert" content="This test checks that if the layout() is a float, the flow layout respects the auto-block-size." />
+
+<style>
+.test {
+ background: red;
+}
+
+@supports (display: layout(block-size-100)) {
+ .test {
+ display: layout(block-size-100);
+ background: green;
+ }
+}
+
+.container {
+ width: 150px;
+ border: solid 2px;
+}
+
+.left {
+ float: left;
+ width: 100px;
+ writing-mode: horizontal-tb;
+}
+
+.right {
+ float: right;
+ height: 100px;
+ writing-mode: vertical-rl;
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- Tests that floats using an auto block size get positioned correctly. -->
+<div class="container">
+ <div class="left test"></div>
+ <div class="right test"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-100', class {
+ *intrinsicSizes() {}
+ *layout() {
+ return {autoBlockSize: 100};
+ }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-inflow-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-inflow-ref.html
new file mode 100644
index 0000000..73f8481
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-inflow-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<style>
+.result {
+ margin: 20px 0;
+ background: green;
+}
+</style>
+<div class="result" style="width: 100px; height: 100px;"></div>
+<div class="result" style="width: 100px; height: 150px;"></div>
+<div class="result" style="width: 100px; height: 50px;"></div>
+<div class="result" style="width: 100px; height: 100px;"></div>
+<div class="result" style="width: 150px; height: 100px;"></div>
+<div class="result" style="width: 50px; height: 100px;"></div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-inflow.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-inflow.https.html
new file mode 100644
index 0000000..b8bc0d19
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-inflow.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-inflow-ref.html">
+<meta name="assert" content="This test checks that min/max-block-size constraints are applied correctly to a layout()." />
+
+<style>
+.test {
+ margin: 20px 0;
+ background: red;
+}
+
+@supports (display: layout(block-size-100)) {
+ .test {
+ display: layout(block-size-100);
+ background: green;
+ }
+}
+
+.width-100 {
+ width: 100px;
+ writing-mode: horizontal-tb;
+}
+
+.height-100 {
+ height: 100px;
+ writing-mode: vertical-rl;
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- 100px x 100px -->
+<div class="test width-100"></div>
+
+<!-- 100px x 150px -->
+<div class="test width-100" style="min-height: 150px"></div>
+
+<!-- 100px x 50px -->
+<div class="test width-100" style="max-height: 50px"></div>
+
+<!-- 100px x 100px -->
+<div class="test height-100"></div>
+
+<!-- 150px x 100px -->
+<div class="test height-100" style="min-width: 150px"></div>
+
+<!-- 50px x 100px -->
+<div class="test height-100" style="max-width: 50px"></div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-100', class {
+ *intrinsicSizes() {}
+ *layout() {
+ return {autoBlockSize: 100};
+ }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-negative-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-negative-ref.html
new file mode 100644
index 0000000..665b5c6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-negative-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+.result {
+ background: green;
+ border: solid 2px;
+ margin: 20px 0;
+}
+</style>
+
+<div class="result" style="width: 100px; height: 0px;"></div>
+<div class="result" style="width: 0px; height: 100px;"></div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-negative.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-negative.https.html
new file mode 100644
index 0000000..4e912ed
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/auto-block-size-negative.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#interaction-sizing">
+<link rel="match" href="auto-block-size-negative-ref.html">
+<meta name="assert" content="This test checks that auto-block-size is correctly clamped to zero." />
+<meta name="assert" content="TODO" />
+
+<style>
+
+.test {
+ background: red;
+ border: solid 2px;
+ margin: 20px 0;
+}
+
+.width-100 {
+ width: 100px;
+ writing-mode: horizontal-tb;
+}
+
+.height-100 {
+ height: 100px;
+ writing-mode: vertical-rl;
+}
+
+@supports (display: layout(block-size-negative)) {
+ .test {
+ display: layout(block-size-negative);
+ background: green;
+ }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<div class="test width-100"></div>
+<div class="test height-100"></div>
+
+<script id="code" type="text/worklet">
+registerLayout('block-size-negative', class {
+ *intrinsicSizes() {}
+ *layout() {
+ return {autoBlockSize: -100};
+ }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-constructor-error.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-constructor-error.https.html
new file mode 100644
index 0000000..590a9d0
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-constructor-error.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#get-a-layout-class-instance">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class with a throwing constructor will fallback to block layout." />
+<style>
+.test {
+ background: red;
+ border: solid 2px;
+ width: 100px;
+}
+
+.float {
+ float: left;
+ width: 50%;
+ height: 100px;
+}
+
+.fc {
+ display: flow-root;
+ height: 100px;
+}
+
+@supports (display: layout(throwing-ctor)) {
+ .test {
+ display: layout(throwing-ctor);
+ background: green;
+ }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- This tests that when the "layout()" constructor fails, it will fallback to block layout. -->
+<div class="test">
+ <div class="float"></div>
+ <div class="fc"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('throwing-ctor', class {
+ constructor() { throw Error('fail!'); }
+ *intrinsicSizes() {}
+ *layout() {}
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-layout-error.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-layout-error.https.html
new file mode 100644
index 0000000..54ea80b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-layout-error.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#invoke-a-layout-callback">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class with a throwing layout function will fallback to block layout." />
+<style>
+.test {
+ background: red;
+ border: solid 2px;
+ width: 100px;
+}
+
+.float {
+ float: left;
+ width: 50%;
+ height: 100px;
+}
+
+.fc {
+ display: flow-root;
+ height: 100px;
+}
+
+@supports (display: layout(throwing-layout)) {
+ .test {
+ display: layout(throwing-layout);
+ background: green;
+ }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- This tests that when the "layout()" layout function fails, it will fallback to block layout. -->
+<div class="test">
+ <div class="float"></div>
+ <div class="fc"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('throwing-layout', class {
+ *intrinsicSizes() {}
+ *layout() { throw Error('fail!'); }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-layout-return.https.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-layout-return.https.html
new file mode 100644
index 0000000..42ccfc5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-layout-return.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#invoke-a-layout-callback">
+<link rel="match" href="fallback-ref.html">
+<meta name="assert" content="This test checks that a layout() class with the layout function returning a bad value will fallback to block layout." />
+<style>
+.test {
+ background: red;
+ border: solid 2px;
+ width: 100px;
+}
+
+.float {
+ float: left;
+ width: 50%;
+ height: 100px;
+}
+
+.fc {
+ display: flow-root;
+ height: 100px;
+}
+
+@supports (display: layout(bad-return)) {
+ .test {
+ display: layout(bad-return);
+ background: green;
+ }
+}
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/worklet-reftest.js"></script>
+
+<!-- This tests that when the "layout()" layout function returns a bad value, it will fallback to block layout. -->
+<div class="test">
+ <div class="float"></div>
+ <div class="fc"></div>
+</div>
+
+<script id="code" type="text/worklet">
+registerLayout('bad-return', class {
+ *intrinsicSizes() {}
+ *layout() { return 42; }
+});
+</script>
+
+<script>
+importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, document.getElementById('code').textContent);
+</script>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-ref.html
new file mode 100644
index 0000000..63bb91e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-layout-api/fallback-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+.result {
+ background: green;
+ border: solid 2px;
+ height: 100px;
+ width: 100px;
+}
+</style>
+
+<div class="result"></div>
diff --git a/third_party/WebKit/Source/core/core_idl_files.gni b/third_party/WebKit/Source/core/core_idl_files.gni
index a61bb16..5eede34 100644
--- a/third_party/WebKit/Source/core/core_idl_files.gni
+++ b/third_party/WebKit/Source/core/core_idl_files.gni
@@ -621,6 +621,7 @@
"input/InputDeviceCapabilitiesInit.idl",
"input/TouchInit.idl",
"intersection_observer/IntersectionObserverInit.idl",
+ "layout/custom/FragmentResultOptions.idl",
"mojo/MojoCreateDataPipeOptions.idl",
"mojo/MojoCreateDataPipeResult.idl",
"mojo/MojoCreateMessagePipeResult.idl",
diff --git a/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.cpp b/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.cpp
index 8d567c75d..4cf83987 100644
--- a/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.cpp
+++ b/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.cpp
@@ -5,10 +5,15 @@
#include "core/layout/custom/CSSLayoutDefinition.h"
#include <memory>
+#include "bindings/core/v8/DictionaryIterator.h"
#include "bindings/core/v8/V8BindingForCore.h"
+#include "bindings/core/v8/V8FragmentResultOptions.h"
#include "core/dom/ExecutionContext.h"
+#include "core/inspector/ConsoleMessage.h"
+#include "core/layout/custom/FragmentResultOptions.h"
#include "platform/bindings/ScriptState.h"
#include "platform/bindings/V8BindingMacros.h"
+#include "platform/bindings/V8ObjectConstructor.h"
namespace blink {
@@ -24,7 +29,8 @@
: script_state_(script_state),
constructor_(script_state->GetIsolate(), constructor),
intrinsic_sizes_(script_state->GetIsolate(), intrinsic_sizes),
- layout_(script_state->GetIsolate(), layout) {
+ 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;
@@ -33,6 +39,117 @@
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(
+ FragmentResultOptions* fragment_result_options) {
+ 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::Function> layout = definition_->layout_.NewLocal(isolate);
+
+ // TODO(ikilpatrick): Build up arguments to pass into the layout function.
+ Vector<v8::Local<v8::Value>> argv = {};
+
+ 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);
+
+ DictionaryIterator iterator(generator, isolate);
+
+ // We run the generator until it's exhausted.
+ ExceptionState exception_state(isolate, ExceptionState::kExecutionContext,
+ "CSSLayoutAPI", "Layout");
+ while (iterator.Next(execution_context, exception_state)) {
+ // TODO(ikilpatrick): If we aren't done yet, we need to process the child
+ // layout requests.
+ }
+
+ if (exception_state.HadException()) {
+ 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."));
+ return false;
+ }
+
+ // The value should already be non-empty, if not it should have be caught be
+ // the exception_state.HadException() above.
+ v8::Local<v8::Value> value = iterator.GetValue().ToLocalChecked();
+
+ // Attempt to convert the result.
+ V8FragmentResultOptions::ToImpl(isolate, 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;
+ }
+
+ return true;
+}
+
+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::Instance::TraceWrappers(
+ const ScriptWrappableVisitor* visitor) const {
+ visitor->TraceWrappers(instance_.Cast<v8::Value>());
+}
+
void CSSLayoutDefinition::TraceWrappers(
const ScriptWrappableVisitor* visitor) const {
visitor->TraceWrappers(constructor_.Cast<v8::Value>());
diff --git a/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.h b/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.h
index 33b6a9f..8f0bedb8 100644
--- a/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.h
+++ b/third_party/WebKit/Source/core/layout/custom/CSSLayoutDefinition.h
@@ -15,6 +15,7 @@
namespace blink {
+class FragmentResultOptions;
class ScriptState;
// Represents a javascript class registered on the LayoutWorkletGlobalScope by
@@ -35,6 +36,29 @@
const Vector<AtomicString>& child_custom_invalidation_properties);
virtual ~CSSLayoutDefinition();
+ // This class represents an instance of the layout class defined by the
+ // CSSLayoutDefinition.
+ class Instance final : public GarbageCollectedFinalized<Instance>,
+ public TraceWrapperBase {
+ public:
+ Instance(CSSLayoutDefinition*, v8::Local<v8::Object> instance);
+
+ // Runs the web developer defined layout, returns true if everything
+ // succeeded, and populates the FragmentResultOptions dictionary.
+ bool Layout(FragmentResultOptions*);
+
+ void Trace(blink::Visitor*);
+ void TraceWrappers(const ScriptWrappableVisitor*) const override;
+
+ private:
+ Member<CSSLayoutDefinition> definition_;
+ TraceWrapperV8Reference<v8::Object> instance_;
+ };
+
+ // Creates an instance of the web developer defined class. May return a
+ // nullptr if constructing the instance failed.
+ Instance* CreateInstance();
+
const Vector<CSSPropertyID>& NativeInvalidationProperties() const {
return native_invalidation_properties_;
}
@@ -50,11 +74,6 @@
ScriptState* GetScriptState() const { return script_state_.get(); }
- v8::Local<v8::Function> IntrinsicSizesFunctionForTesting(
- v8::Isolate* isolate) {
- return intrinsic_sizes_.NewLocal(isolate);
- }
-
v8::Local<v8::Function> LayoutFunctionForTesting(v8::Isolate* isolate) {
return layout_.NewLocal(isolate);
}
@@ -72,10 +91,9 @@
TraceWrapperV8Reference<v8::Function> intrinsic_sizes_;
TraceWrapperV8Reference<v8::Function> layout_;
- // TODO(ikilpatrick): Once we have a LayoutObject to use this definition,
- // we'll need a map of LayoutObject to class instances. We'll also need a
- // did_call_constructor_ field to ensure that we correctly don't use a class
- // with a throwing constructor.
+ // If a constructor call ever fails, we'll refuse to create any more
+ // instances of the web developer provided class.
+ bool constructor_has_failed_;
Vector<CSSPropertyID> native_invalidation_properties_;
Vector<AtomicString> custom_invalidation_properties_;
diff --git a/third_party/WebKit/Source/core/layout/custom/FragmentResultOptions.idl b/third_party/WebKit/Source/core/layout/custom/FragmentResultOptions.idl
new file mode 100644
index 0000000..8d7f1b10
--- /dev/null
+++ b/third_party/WebKit/Source/core/layout/custom/FragmentResultOptions.idl
@@ -0,0 +1,11 @@
+// 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.
+
+// https://drafts.css-houdini.org/css-layout-api/#dictdef-fragmentresultoptions
+
+dictionary FragmentResultOptions {
+ double autoBlockSize = 0;
+ // sequence<Fragment> childFragments = [];
+ // any data = null;
+};
diff --git a/third_party/WebKit/Source/core/layout/custom/LayoutCustom.cpp b/third_party/WebKit/Source/core/layout/custom/LayoutCustom.cpp
index c3e1fee..4cf61918 100644
--- a/third_party/WebKit/Source/core/layout/custom/LayoutCustom.cpp
+++ b/third_party/WebKit/Source/core/layout/custom/LayoutCustom.cpp
@@ -6,7 +6,13 @@
#include "core/dom/Document.h"
#include "core/frame/LocalDOMWindow.h"
+#include "core/layout/LayoutState.h"
+#include "core/layout/TextAutosizer.h"
+#include "core/layout/custom/FragmentResultOptions.h"
#include "core/layout/custom/LayoutWorklet.h"
+#include "core/layout/custom/LayoutWorkletGlobalScope.h"
+#include "core/layout/custom/LayoutWorkletGlobalScopeProxy.h"
+#include "platform/bindings/ScriptForbiddenScope.h"
namespace blink {
@@ -35,4 +41,86 @@
}
}
+void LayoutCustom::UpdateBlockLayout(bool relayout_children) {
+ DCHECK(NeedsLayout());
+
+ if (!relayout_children && SimplifiedLayout())
+ return;
+
+ // We may end up with multiple SubtreeLayoutScopes on the stack for the same
+ // layout object. However we can't create one inside PerformLayout as it may
+ // not succeed.
+ SubtreeLayoutScope layout_scope(*this);
+
+ // TODO(ikilpatrick): We may need to clear the floating objects.
+ // TODO(ikilpatrick): We may need a RAII checker to ensure that if we fail,
+ // the children are put back into their initial state from before the custom
+ // layout was run.
+
+ // Attempt to run the custom layout, this may fail, and if so we'll have to
+ // fall back onto regular block layout.
+ bool success = PerformLayout(relayout_children, &layout_scope);
+
+ if (!success)
+ LayoutBlockFlow::UpdateBlockLayout(relayout_children);
+}
+
+bool LayoutCustom::PerformLayout(bool relayout_children,
+ SubtreeLayoutScope* layout_scope) {
+ // We need to fallback to block layout if we don't have a registered
+ // definition yet.
+ if (state_ == kUnloaded)
+ return false;
+
+ UpdateLogicalWidth();
+ LayoutUnit previous_height = LogicalHeight();
+
+ {
+ TextAutosizer::LayoutScope text_autosizer_layout_scope(this, layout_scope);
+ LayoutState state(*this);
+ ScriptForbiddenScope::AllowUserAgentScript allow_script;
+
+ const AtomicString& name = StyleRef().DisplayLayoutCustomName();
+ LayoutWorklet* worklet = LayoutWorklet::From(*GetDocument().domWindow());
+ CSSLayoutDefinition* definition = worklet->Proxy()->FindDefinition(name);
+
+ // TODO(ikilpatrick): We'll want to store the instance on either the
+ // CSSLayoutDefinition or on the LayoutCustom object.
+ CSSLayoutDefinition::Instance* instance = definition->CreateInstance();
+
+ if (!instance) {
+ GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ kJSMessageSource, kInfoMessageLevel,
+ "Unable to create an instance of layout class '" + name +
+ "', falling back to block layout."));
+ return false;
+ }
+
+ FragmentResultOptions fragment_result_options;
+ if (!instance->Layout(&fragment_result_options))
+ return false;
+
+ // TODO(ikilpatrick): Currently we need to "fail" if we have any children
+ // as they won't be laid out yet.
+ if (FirstChild())
+ return false;
+
+ SetLogicalHeight(LayoutUnit(fragment_result_options.autoBlockSize()));
+
+ LayoutUnit old_client_after_edge = ClientLogicalBottom();
+ UpdateLogicalHeight();
+
+ if (LogicalHeight() != previous_height)
+ relayout_children = true;
+
+ LayoutPositionedObjects(relayout_children || IsDocumentElement());
+ ComputeOverflow(old_client_after_edge);
+ }
+
+ UpdateAfterLayout();
+ ClearNeedsLayout();
+
+ return true;
+}
+
} // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/custom/LayoutCustom.h b/third_party/WebKit/Source/core/layout/custom/LayoutCustom.h
index 2243cca..91e57b6 100644
--- a/third_party/WebKit/Source/core/layout/custom/LayoutCustom.h
+++ b/third_party/WebKit/Source/core/layout/custom/LayoutCustom.h
@@ -30,12 +30,15 @@
bool CreatesNewFormattingContext() const override { return true; }
void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override;
+ void UpdateBlockLayout(bool relayout_children) override;
private:
bool IsOfType(LayoutObjectType type) const override {
return type == kLayoutObjectLayoutCustom || LayoutBlockFlow::IsOfType(type);
}
+ bool PerformLayout(bool relayout_children, SubtreeLayoutScope*);
+
LayoutCustomState state_;
};
diff --git a/third_party/WebKit/Source/core/layout/custom/LayoutWorklet.cpp b/third_party/WebKit/Source/core/layout/custom/LayoutWorklet.cpp
index d79e7cd..8748e66 100644
--- a/third_party/WebKit/Source/core/layout/custom/LayoutWorklet.cpp
+++ b/third_party/WebKit/Source/core/layout/custom/LayoutWorklet.cpp
@@ -46,6 +46,10 @@
pending_layout_registry_->AddPendingLayout(name, node);
}
+LayoutWorkletGlobalScopeProxy* LayoutWorklet::Proxy() {
+ return LayoutWorkletGlobalScopeProxy::From(FindAvailableGlobalScope());
+}
+
void LayoutWorklet::Trace(blink::Visitor* visitor) {
visitor->Trace(document_definition_map_);
visitor->Trace(pending_layout_registry_);
diff --git a/third_party/WebKit/Source/core/layout/custom/LayoutWorklet.h b/third_party/WebKit/Source/core/layout/custom/LayoutWorklet.h
index 4ddf8a3..9aacf34 100644
--- a/third_party/WebKit/Source/core/layout/custom/LayoutWorklet.h
+++ b/third_party/WebKit/Source/core/layout/custom/LayoutWorklet.h
@@ -14,6 +14,7 @@
namespace blink {
class Node;
+class LayoutWorkletGlobalScopeProxy;
extern DocumentLayoutDefinition* const kInvalidDocumentLayoutDefinition;
@@ -44,12 +45,16 @@
}
void AddPendingLayout(const AtomicString& name, Node*);
+ LayoutWorkletGlobalScopeProxy* Proxy();
void Trace(blink::Visitor*) override;
protected:
explicit LayoutWorklet(LocalFrame*);
+ // TODO(ikilpatrick): Make selection of the global scope non-deterministic.
+ size_t SelectGlobalScope() final { return 0u; }
+
private:
friend class LayoutWorkletTest;
diff --git a/third_party/WebKit/Source/core/layout/custom/LayoutWorkletGlobalScopeProxy.cpp b/third_party/WebKit/Source/core/layout/custom/LayoutWorkletGlobalScopeProxy.cpp
index 4dd82d6..f2ef098a 100644
--- a/third_party/WebKit/Source/core/layout/custom/LayoutWorkletGlobalScopeProxy.cpp
+++ b/third_party/WebKit/Source/core/layout/custom/LayoutWorkletGlobalScopeProxy.cpp
@@ -67,6 +67,12 @@
reporting_proxy_.reset();
}
+CSSLayoutDefinition* LayoutWorkletGlobalScopeProxy::FindDefinition(
+ const AtomicString& name) {
+ DCHECK(IsMainThread());
+ return global_scope_->FindDefinition(name);
+}
+
void LayoutWorkletGlobalScopeProxy::Trace(blink::Visitor* visitor) {
visitor->Trace(global_scope_);
}
diff --git a/third_party/WebKit/Source/core/layout/custom/LayoutWorkletGlobalScopeProxy.h b/third_party/WebKit/Source/core/layout/custom/LayoutWorkletGlobalScopeProxy.h
index b4a1845..4e8152c 100644
--- a/third_party/WebKit/Source/core/layout/custom/LayoutWorkletGlobalScopeProxy.h
+++ b/third_party/WebKit/Source/core/layout/custom/LayoutWorkletGlobalScopeProxy.h
@@ -38,6 +38,8 @@
void WorkletObjectDestroyed() override;
void TerminateWorkletGlobalScope() override;
+ CSSLayoutDefinition* FindDefinition(const AtomicString& name);
+
LayoutWorkletGlobalScope* global_scope() const { return global_scope_.Get(); }
void Trace(blink::Visitor*) override;