Compare commits

...

33 Commits

Author SHA1 Message Date
Ciro Spaciari MacBook
5478bb23d0 fix(cmake): quote JSON variables in SetupBuildkite.cmake to fix parsing of large builds
CMake's string(JSON) fails to parse large unquoted variables (1MB+) because
unquoted expansion causes argument splitting issues; quoting preserves the full string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 14:41:17 -08:00
Ciro Spaciari MacBook
aebff0e7db fix(yoga): reset m_freed in replaceYogaConfig; add try/finally cleanup in tests 2026-02-09 13:34:58 -08:00
autofix-ci[bot]
74e9d8a47e [autofix.ci] apply automated fixes 2026-02-09 21:22:31 +00:00
Ciro Spaciari MacBook
1c5c520830 fix(yoga): fix memory leak in YGNodeClone by replacing shallow clone with deep clone
YGNodeClone() is a shallow copy that shares child pointers between original and
clone. The previous workaround set m_ownsNode=false on original impls to prevent
double-free, leaking all original nodes' YGNode allocations on GC. The fix
properly deep-clones each node independently (detaching shared children and
re-inserting fresh clones) so original and clone trees are fully independent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:19:02 -08:00
Ciro Spaciari MacBook
39ed8963bd fix(yoga): add factory functions, GC visitor macros, rename tests to .ts
- Implement createJSYogaConfig/createJSYogaNode factory functions so Yoga.Config.create() and Yoga.Node.create() work without new keyword
- Add DECLARE_VISIT_CHILDREN/DEFINE_VISIT_CHILDREN to JSYogaNode and JSYogaConfig to properly trace WriteBarrier fields during GC marking
- Rename yoga test files from .test.js to .test.ts per project conventions
2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
c619b7cc06 fix(yoga): add freed-node checks, fix clone WriteBarrier, optimize impl allocation
- Add CHECK_YOGA_NODE_FREED validation on child/source arguments in insertChild(), removeChild(), and copyStyle() to prevent crashes when operating on freed nodes
- Fix clone() to add cloned children to parent's m_children WriteBarrier array, preventing GC from collecting cloned child nodes
- Optimize JSYogaNode constructor to accept config parameter directly, avoiding unnecessary default YogaNodeImpl allocation when config is provided
2026-02-09 13:05:38 -08:00
autofix-ci[bot]
797410e56c [autofix.ci] apply automated fixes 2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
42de479784 refactor(yoga): cache JSYogaModule Structure in ZigGlobalObject
Cache the JSYogaModule Structure via LazyClassStructure in ZigGlobalObject instead of creating a fresh one on each access.
2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
6eb38d9042 fix(yoga): fix GC lifecycle leak, setConfig WriteBarrier, missing constants, and test bug
- Free YGNode in JSYogaNode::destroy() instead of relying on WeakHandleOwner::finalize() ref-count chain, which may not complete during VM shutdown (fixes ASAN leak)
- Add WriteBarrier update in setConfig() to prevent JSYogaConfig from being GC'd while Yoga still references it
- Add 10 missing constants to JSYogaConstants.cpp (DISPLAY_CONTENTS, LOG_LEVEL_*, UNIT_*)
- Fix copy-paste bug in YGDirtiedTest.test.ts (root_child0 → root_child1)
2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
79b091afc1 Revert "revert(yoga): remove YGNodeFinalize from destructor to fix GC sweep crash"
This reverts commit fcee6de2a9.
2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
41124ac14c revert(yoga): remove LSAN suppressions for now 2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
c8c1445cdf revert(yoga): remove YGNodeFinalize from destructor to fix GC sweep crash
Calling delete on yoga::Node during GC finalization is unsafe because
the GC sweeps objects in arbitrary order and the allocator behavior
on musl can cause null-pointer dereferences. The LSAN suppressions
in test/leaksan.supp handle the leak detection false positives.

This reverts the destructor and replaceYogaNode changes from 660e4b3c
while keeping the LSAN suppressions from c17c345d.
2026-02-09 13:05:38 -08:00
Claude
ef0f050935 fix(yoga): suppress LSAN false positives for GC-managed YGNode allocations 2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
99cf796bc4 fix(yoga): free YGNode in YogaNodeImpl destructor to fix LSAN leak 2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
cfaeddb398 fix(yoga): add missing JSC exception scope checks
- Add RETURN_IF_EXCEPTION after constructEmptyObject/constructEmptyArray
  calls in getter functions and create() methods
- Add exception checks between consecutive obj->get() calls in style
  setters to prevent operating on stale values after a throw
- Add exception checks in array manipulation (insertChild, removeChild,
  removeAllChildren) for setLength, getIndex, putDirectIndex
- Convert Yoga C callbacks (measure, dirtied, baseline) from
  DECLARE_THROW_SCOPE to DECLARE_TOP_EXCEPTION_SCOPE since they are
  invoked from C code and need proper re-entrant exception handling
- Release throw scopes before calling YGNodeCalculateLayout and
  YGNodeMarkDirty which can invoke JS callbacks
- Fix JSYogaNode::create() signatures to include JSGlobalObject* and
  add RETURN_IF_EXCEPTION at all call sites
- Remove redundant constructEmptyArray in finishCreation (already done
  with proper exception handling in create())
2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
9dd913e575 fix: prevent use-after-free in Yoga node free() and freeRecursive()
Nullify m_yogaNode via replaceYogaNode(nullptr) after YGNodeFree()
and before YGNodeFreeRecursive() to ensure CHECK_YOGA_NODE_FREED
properly detects freed nodes on subsequent method calls.
2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
daf5d8c880 fix: resolve GC segfault in JSYogaNode lifecycle management
- Add missing cellLock() in JSYogaNode::visitOutputConstraints to
  prevent concurrent GC reads of WriteBarriers during mutation
- Clear all WriteBarriers (m_children, m_measureFunc, m_dirtiedFunc,
  m_baselineFunc, m_config) in free() and freeRecursive()
- Empty m_children JSArray in removeAllChildren() to prevent stale
  child references
2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
7620bbd070 fix: implement missing Yoga binding methods and fix measure callback
- Add setPositionAuto(edge), setFlexBasisAuto(), markLayoutSeen(),
  hasNewLayout() methods to Node prototype
- Add "max-content", "fit-content", "stretch" string value support
  to setWidth/setHeight/setFlexBasis/setMinWidth/setMinHeight/
  setMaxWidth/setMaxHeight
- Fix measure callback to pass 4 positional arguments (width,
  widthMode, height, heightMode) instead of a single object

All 665 official Yoga tests now pass (45 skipped upstream).
2026-02-09 13:05:38 -08:00
autofix-ci[bot]
3fe3f63224 [autofix.ci] apply automated fixes 2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
3ff238f47a test: port official Facebook Yoga JavaScript test suite
Ports all 35 test files from facebook/yoga/javascript/tests/ to verify
Bun's native Yoga bindings against the official test suite.

Results: 535 pass, 25 fail, 45 skip (upstream)

25 failures are due to missing binding methods (not layout bugs):
- setPositionAuto (4 failures)
- intrinsic sizing keywords: "max-content"/"fit-content"/"stretch" (13)
- markLayoutSeen/hasNewLayout (6)
- setFlexBasisAuto (1)
- measure cache behavior (1)
2026-02-09 13:05:38 -08:00
Ciro Spaciari MacBook
cbde3d5d0d feat: add native Yoga layout engine bindings
Adds native C++/JSC bindings for Facebook's Yoga flexbox layout engine,
exposed as Bun.Yoga. This is a high-performance alternative to the
yoga-layout npm package's WASM-based implementation.

- Wraps Yoga v3.x C API via JSDestructibleObject/RefCounted pattern
- Full API: Node, Config, all style setters/getters, layout calculation
- All yoga-layout constants (ALIGN_*, FLEX_DIRECTION_*, EDGE_*, etc.)
- Measure, dirtied, and baseline callback support
- GC integration with IsoSubspaces and weak handle owners
- 105 tests with 375 assertions
2026-02-09 13:05:38 -08:00
SUZUKI Sosuke
b7475d8768 fix(buffer): return fixed-length view from slice on resizable ArrayBuffer (#26822)
## Summary

Follow-up to #26819 ([review
comment](https://github.com/oven-sh/bun/pull/26819#discussion_r2781484939)).
Fixes `Buffer.slice()` / `Buffer.subarray()` on resizable `ArrayBuffer`
/ growable `SharedArrayBuffer` to return a **fixed-length view** instead
of a length-tracking view.

## Problem

The resizable/growable branch was passing `std::nullopt` to
`JSUint8Array::create()`, which creates a length-tracking view. When the
underlying buffer grows, the sliced view's length would incorrectly
expand:

```js
const rab = new ArrayBuffer(10, { maxByteLength: 20 });
const buf = Buffer.from(rab);
const sliced = buf.slice(0, 5);
sliced.length; // 5

rab.resize(20);
sliced.length; // was 10 (wrong), now 5 (correct)
```

Node.js specifies that `Buffer.slice()` always returns a fixed-length
view (verified on Node.js v22).

## Fix

Replace `std::nullopt` with `newLength` in the
`isResizableOrGrowableShared()` branch of
`jsBufferPrototypeFunction_sliceBody`.

## Test

Added a regression test that creates a `Buffer` from a resizable
`ArrayBuffer`, slices it, resizes the buffer, and verifies the slice
length doesn't change.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 04:48:20 -08:00
Jarred Sumner
4494170f74 perf(event_loop): avoid eventfd wakeup for setImmediate on POSIX (#26821)
### What does this PR do?

Instead of calling event_loop.wakeup() (which writes to the eventfd)
when there are pending immediate tasks, use a zero timeout in
getTimeout() so epoll/kqueue returns immediately. This avoids the
overhead of the eventfd write/read cycle on each setImmediate iteration.

On Windows, continue to call .wakeup() since that's cheap for libuv.

Verified with strace: system bun makes ~44k eventfd writes for a 5s
setImmediate loop, while this change makes 0.


### How did you verify your code works?

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-09 04:47:52 -08:00
SUZUKI Sosuke
9484218ba4 perf(buffer): move Buffer.slice/subarray to native C++ with int32 fast path (#26819)
## Summary

Move `Buffer.slice()` / `Buffer.subarray()` from a JS builtin to a
native C++ implementation, eliminating the `adjustOffset` closure
allocation and JS→C++ constructor overhead on every call. Additionally,
add an int32 fast path that skips `toNumber()` (which can invoke
`valueOf`/`Symbol.toPrimitive`) when arguments are already int32—the
common case for calls like `buf.slice(0, 10)`.

## Changes

- **`src/bun.js/bindings/JSBuffer.cpp`**: Add
`jsBufferPrototypeFunction_sliceBody` with `adjustSliceOffsetInt32` /
`adjustSliceOffsetDouble` helpers. Update prototype hash table entries
from `BuiltinGeneratorType` to `NativeFunctionType` for both `slice` and
`subarray`.
- **`src/js/builtins/JSBufferPrototype.ts`**: Remove the JS `slice`
function (was lines 667–687).
- **`bench/snippets/buffer-slice.mjs`**: Add mitata benchmark.

## Benchmark (Apple M4 Max)

| Benchmark | Before (v1.3.8) | After | Speedup |
|---|---|---|---|
| `Buffer(64).slice()` | 27.19 ns | **14.56 ns** | **1.87x** |
| `Buffer(1024).slice()` | 27.84 ns | **14.62 ns** | **1.90x** |
| `Buffer(1M).slice()` | 29.20 ns | **14.89 ns** | **1.96x** |
| `Buffer(64).slice(10)` | 30.26 ns | **16.01 ns** | **1.89x** |
| `Buffer(1024).slice(10, 100)` | 30.92 ns | **18.32 ns** | **1.69x** |
| `Buffer(1024).slice(-100, -10)` | 28.82 ns | **17.37 ns** | **1.66x**
|
| `Buffer(1024).subarray(10, 100)` | 28.67 ns | **16.32 ns** | **1.76x**
|

**~1.7–1.9x faster** across all cases. All 449 buffer tests pass.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 01:46:33 -08:00
robobun
2a5e8ef38c fix(kqueue): fix incorrect filter comparison causing excessive CPU on macOS (#26812)
## Summary

Fixes the remaining kqueue filter comparison bug in
`packages/bun-usockets/src/eventing/epoll_kqueue.c` that caused
excessive CPU usage with network requests on macOS:

- **`us_loop_run_bun_tick` filter comparison (line 302-303):** kqueue
filter values (`EVFILT_READ=-1`, `EVFILT_WRITE=-2`) were compared using
bitwise AND (`&`) instead of equality (`==`). Since these are signed
negative integers (not bitmasks), `(-2) & (-1)` = `-2` (truthy), meaning
every `EVFILT_WRITE` event was also misidentified as `EVFILT_READ`. This
was already fixed in `us_loop_run` (by PR #25475) but the same bug
remained in `us_loop_run_bun_tick`, which is the primary event loop
function used by Bun.

This is a macOS-only issue (Linux uses epoll, which is unaffected).

Closes #26811

## Test plan

- [x] Added regression test at `test/regression/issue/26811.test.ts`
that makes concurrent HTTPS POST requests
- [x] Test passes with `bun bd test test/regression/issue/26811.test.ts`
- [ ] Manual verification on macOS: run the reporter's [repro
script](https://gist.github.com/jkoppel/d26732574dfcdcc6bfc4958596054d2e)
and confirm CPU usage stays low

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-09 00:52:17 -08:00
robobun
a84f12b816 Use edge-triggered epoll for eventfd wakeups (#26815)
## Summary

- Switch both eventfd wakeup sites (Zig IO watcher loop and usockets
async) to edge-triggered (`EPOLLET`) epoll mode, eliminating unnecessary
`read()` syscalls on every event loop wakeup
- Add `EAGAIN`/`EINTR` overflow handling in `us_internal_async_wakeup`,
matching libuv's approach ([commit
`e5cb1d3d`](https://github.com/libuv/libuv/commit/e5cb1d3d))

With edge-triggered mode, each `write()` to the eventfd produces a new
edge event regardless of the current counter value, so draining the
counter via `read()` is unnecessary. The counter will never overflow in
practice (~18 quintillion wakeups), but overflow handling is included
defensively.

### Files changed

- **`src/io/io.zig`** — Add `EPOLL.ET` to eventfd registration, replace
drain `read()` with `continue`
- **`packages/bun-usockets/src/eventing/epoll_kqueue.c`** — Set
`leave_poll_ready = 1` for async callbacks, upgrade to `EPOLLET` via
`EPOLL_CTL_MOD`, add `EAGAIN`/`EINTR` handling in wakeup write

## Test plan

- [x] Verified with `strace -f -e trace=read,eventfd2` that eventfd
reads are fully eliminated after the change (0 reads on the eventfd fd)
- [x] Confirmed remaining 8-byte reads in traces are timerfd reads
(legitimate, required)
- [x] Stress tested with 50 concurrent async tasks (1000 total
`Bun.sleep(1)` iterations) — all completed correctly
- [x] `LinuxWaker.wait()` (used by `BundleThread` as a blocking sleep)
is intentionally unchanged

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-09 00:36:30 -08:00
SUZUKI Sosuke
0f43ea9bec perf(structuredClone): add fast path for root-level dense arrays (#26814)
## Summary

Add a fast path for `structuredClone` and `postMessage` when the root
value is a dense array of primitives or strings. This bypasses the full
`CloneSerializer`/`CloneDeserializer` machinery by keeping data in
native C++ structures instead of serializing to a byte stream.

**Important:** This optimization only applies when the root value passed
to `structuredClone()` / `postMessage()` is an array. Nested arrays
within objects still go through the normal serialization path.

## Implementation

Three tiers of array fast paths, checked in order:

| Tier | Indexing Type | Strategy | Applies When |
|------|--------------|----------|--------------|
| **Tier 1** | `ArrayWithInt32` | `memcpy` butterfly data | Dense int32
array, no holes, no named properties |
| **Tier 2** | `ArrayWithDouble` | `memcpy` butterfly data | Dense
double array, no holes, no named properties |
| **Tier 3** | `ArrayWithContiguous` | Copy elements into
`FixedVector<variant<JSValue, String>>` | Dense array of
primitives/strings, no holes, no named properties |

All tiers fall through to the normal serialization path when:
- The array has holes that must forward to the prototype
- The array has named properties (e.g., `arr.foo = "bar"`) — checked via
`structure->maxOffset() != invalidOffset`
- Elements contain non-primitive, non-string values (objects, arrays,
etc.)
- The context requires wire-format serialization (storage, cross-process
transfer)

### Deserialization

- **Tier 1/2:** Allocate a new `Butterfly` via `vm.auxiliarySpace()`,
`memcpy` data back, create array with `JSArray::createWithButterfly()`.
Falls back to normal deserialization if `isHavingABadTime` (forced
ArrayStorage mode).
- **Tier 3:** Pre-convert elements to `JSValue` (including `jsString()`
allocation), then use `JSArray::tryCreateUninitializedRestricted()` +
`initializeIndex()`.

## Benchmarks

Apple M4 Max, comparing system Bun 1.3.8 vs this branch (release build):

| Benchmark | Before | After | Speedup |
|-----------|--------|-------|---------|
| `structuredClone([10 numbers])` | 308.71 ns | 40.38 ns | **7.6x** |
| `structuredClone([100 numbers])` | 1.62 µs | 86.87 ns | **18.7x** |
| `structuredClone([1000 numbers])` | 13.79 µs | 544.56 ns | **25.3x** |
| `structuredClone([10 strings])` | 642.38 ns | 307.38 ns | **2.1x** |
| `structuredClone([100 strings])` | 5.67 µs | 2.57 µs | **2.2x** |
| `structuredClone([10 mixed])` | 446.32 ns | 198.35 ns | **2.3x** |
| `structuredClone(nested array)` | 1.84 µs | 1.79 µs | 1.0x (not
eligible) |
| `structuredClone({a: 123})` | 95.98 ns | 100.07 ns | 1.0x (no
regression) |

Int32 arrays see the largest gains (up to 25x) since they use a direct
`memcpy` of butterfly memory. String/mixed arrays see ~2x improvement.
No performance regression on non-eligible inputs.

## Bug Fix

Also fixes a correctness bug where arrays with named properties (e.g.,
`arr.foo = "bar"`) would lose those properties when going through the
array fast path. Added a `structure->maxOffset() != invalidOffset` guard
to fall back to normal serialization for such arrays.

Fixed a minor double-counting issue in `computeMemoryCost` where
`JSValue` elements in `SimpleArray` were counted both by `byteSize()`
and individually.

## Test Plan

38 tests in `test/js/web/structured-clone-fastpath.test.ts` covering:

- Basic array types: empty, numbers, strings, mixed primitives, special
numbers (`-0`, `NaN`, `Infinity`)
- Large arrays (10,000 elements)
- Tier 2: double arrays, Int32→Double transition
- Deep clone independence verification
- Named properties on Int32, Double, and Contiguous arrays
- `postMessage` via `MessageChannel` for Int32, Double, and mixed arrays
- Edge cases: frozen/sealed arrays, deleted elements (holes), `length`
extension, single-element arrays
- Prototype modification (custom prototype, indexed prototype properties
with holes)
- `Array` subclass identity loss (per spec)
- `undefined`-only and `null`-only arrays
- Multiple independent clones from the same source

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-08 21:36:59 -08:00
Jarred Sumner
0889897a1c Revert "feat(bundler): add configurable CJS→ESM unwrapping via unwrapCJSToESM"
This reverts commit e3c25260ed.
2026-02-08 19:49:26 -08:00
Jarred Sumner
68f2ea4b95 Fix release script 2026-02-08 01:39:10 -08:00
Jarred Sumner
d4ebfd9771 Bump 2026-02-08 01:32:25 -08:00
Jarred Sumner
e3c25260ed feat(bundler): add configurable CJS→ESM unwrapping via unwrapCJSToESM
Add `minify.unwrapCJSToESM` JS API option and `--unwrap-cjs-to-esm` CLI
flag to force CJS-to-ESM conversion for specific packages, eliminating
the `__commonJS` wrapper. Supports wildcard patterns (e.g. `"@scope/*"`).
User entries extend the default React family list.

Also removes the react/react-dom version check that gated conversion,
and fixes `packageName()` to handle scoped packages (`@scope/pkg`).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 01:32:10 -08:00
Alistair Smith
1bded85718 types: Enable --splitting with compile (#26796)
### What does this PR do?

Enables --splitting with compile

### How did you verify your code works?

Bun types integration test fixture updates
2026-02-07 13:39:18 -08:00
Dylan Conway
cf6cdbbbad Revert "Mimalloc v3 update (#26379)" (#26783)
This reverts commit c63415c9c9.

### What does this PR do?

### How did you verify your code works?
2026-02-06 18:05:17 -08:00
95 changed files with 56472 additions and 252 deletions

2
LATEST
View File

@@ -1 +1 @@
1.3.8
1.3.9

View File

@@ -0,0 +1,38 @@
// @runtime bun,node
import { bench, group, run } from "../runner.mjs";
const small = Buffer.alloc(64, 0x42);
const medium = Buffer.alloc(1024, 0x42);
const large = Buffer.alloc(1024 * 1024, 0x42);
group("slice - no args", () => {
bench("Buffer(64).slice()", () => small.slice());
bench("Buffer(1024).slice()", () => medium.slice());
bench("Buffer(1M).slice()", () => large.slice());
});
group("slice - one int arg", () => {
bench("Buffer(64).slice(10)", () => small.slice(10));
bench("Buffer(1024).slice(10)", () => medium.slice(10));
bench("Buffer(1M).slice(1024)", () => large.slice(1024));
});
group("slice - two int args", () => {
bench("Buffer(64).slice(10, 50)", () => small.slice(10, 50));
bench("Buffer(1024).slice(10, 100)", () => medium.slice(10, 100));
bench("Buffer(1M).slice(1024, 4096)", () => large.slice(1024, 4096));
});
group("slice - negative args", () => {
bench("Buffer(64).slice(-10)", () => small.slice(-10));
bench("Buffer(1024).slice(-100, -10)", () => medium.slice(-100, -10));
bench("Buffer(1M).slice(-4096, -1024)", () => large.slice(-4096, -1024));
});
group("subarray - two int args", () => {
bench("Buffer(64).subarray(10, 50)", () => small.subarray(10, 50));
bench("Buffer(1024).subarray(10, 100)", () => medium.subarray(10, 100));
bench("Buffer(1M).subarray(1024, 4096)", () => large.subarray(1024, 4096));
});
await run();

View File

@@ -33,7 +33,23 @@ var testArray = [
import { bench, run } from "../runner.mjs";
bench("structuredClone(array)", () => structuredClone(testArray));
bench("structuredClone(nested array)", () => structuredClone(testArray));
bench("structuredClone(123)", () => structuredClone(123));
bench("structuredClone({a: 123})", () => structuredClone({ a: 123 }));
// Array fast path targets
var numbersSmall = Array.from({ length: 10 }, (_, i) => i);
var numbersMedium = Array.from({ length: 100 }, (_, i) => i);
var numbersLarge = Array.from({ length: 1000 }, (_, i) => i);
var stringsSmall = Array.from({ length: 10 }, (_, i) => `item-${i}`);
var stringsMedium = Array.from({ length: 100 }, (_, i) => `item-${i}`);
var mixed = [1, "hello", true, null, undefined, 3.14, "world", false, 42, "test"];
bench("structuredClone([10 numbers])", () => structuredClone(numbersSmall));
bench("structuredClone([100 numbers])", () => structuredClone(numbersMedium));
bench("structuredClone([1000 numbers])", () => structuredClone(numbersLarge));
bench("structuredClone([10 strings])", () => structuredClone(stringsSmall));
bench("structuredClone([100 strings])", () => structuredClone(stringsMedium));
bench("structuredClone([10 mixed])", () => structuredClone(mixed));
await run();

View File

@@ -61,6 +61,7 @@ set(BUN_DEPENDENCIES
LibArchive # must be loaded after zlib
HdrHistogram # must be loaded after zlib
Zstd
Yoga
)
# TinyCC is optional - disabled on Windows ARM64 where it's not supported

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
oven-sh/mimalloc
COMMIT
ffa38ab8ac914f9eb7af75c1f8ad457643dc14f2
1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a
)
set(MIMALLOC_CMAKE_ARGS
@@ -14,7 +14,7 @@ set(MIMALLOC_CMAKE_ARGS
-DMI_BUILD_TESTS=OFF
-DMI_USE_CXX=ON
-DMI_SKIP_COLLECT_ON_EXIT=ON
# ```
# mimalloc_allow_large_os_pages=0 BUN_PORT=3004 mem bun http-hello.js
# Started development server: http://localhost:3004
@@ -51,7 +51,7 @@ if(ENABLE_ASAN)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_DEBUG_UBSAN=ON)
elseif(APPLE OR LINUX)
if(APPLE)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
else()
@@ -87,9 +87,9 @@ endif()
if(WIN32)
if(DEBUG)
set(MIMALLOC_LIBRARY mimalloc-debug)
set(MIMALLOC_LIBRARY mimalloc-static-debug)
else()
set(MIMALLOC_LIBRARY mimalloc)
set(MIMALLOC_LIBRARY mimalloc-static)
endif()
elseif(DEBUG)
if (ENABLE_ASAN)

View File

@@ -0,0 +1,26 @@
register_repository(
NAME
yoga
REPOSITORY
facebook/yoga
COMMIT
dc2581f229cb05c7d2af8dee37b2ee0b59fd5326
)
register_cmake_command(
TARGET
yoga
TARGETS
yogacore
ARGS
-DBUILD_SHARED_LIBS=OFF
-DYOGA_BUILD_TESTS=OFF
-DYOGA_BUILD_SAMPLES=OFF
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
LIB_PATH
yoga
LIBRARIES
yogacore
INCLUDES
.
)

View File

@@ -66,9 +66,9 @@ string(REPLACE "\\r" "\\\\r" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "\\t" "\\\\t" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(REPLACE "${BKSLASH_PLACEHOLDER}" "\\\\" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(JSON BUILDKITE_BUILD_UUID GET ${BUILDKITE_BUILD} id)
string(JSON BUILDKITE_JOBS GET ${BUILDKITE_BUILD} jobs)
string(JSON BUILDKITE_JOBS_COUNT LENGTH ${BUILDKITE_JOBS})
string(JSON BUILDKITE_BUILD_UUID GET "${BUILDKITE_BUILD}" id)
string(JSON BUILDKITE_JOBS GET "${BUILDKITE_BUILD}" jobs)
string(JSON BUILDKITE_JOBS_COUNT LENGTH "${BUILDKITE_JOBS}")
if(NOT BUILDKITE_JOBS_COUNT GREATER 0)
message(FATAL_ERROR "No jobs found: ${BUILDKITE_BUILD_URL}")
@@ -83,14 +83,14 @@ set(BUILDKITE_JOBS_MATCH)
math(EXPR BUILDKITE_JOBS_MAX_INDEX "${BUILDKITE_JOBS_COUNT} - 1")
foreach(i RANGE ${BUILDKITE_JOBS_MAX_INDEX})
string(JSON BUILDKITE_JOB GET ${BUILDKITE_JOBS} ${i})
string(JSON BUILDKITE_JOB_ID GET ${BUILDKITE_JOB} id)
string(JSON BUILDKITE_JOB_PASSED GET ${BUILDKITE_JOB} passed)
string(JSON BUILDKITE_JOB_GROUP_ID GET ${BUILDKITE_JOB} group_uuid)
string(JSON BUILDKITE_JOB_GROUP_KEY GET ${BUILDKITE_JOB} group_identifier)
string(JSON BUILDKITE_JOB_NAME GET ${BUILDKITE_JOB} step_key)
string(JSON BUILDKITE_JOB GET "${BUILDKITE_JOBS}" ${i})
string(JSON BUILDKITE_JOB_ID GET "${BUILDKITE_JOB}" id)
string(JSON BUILDKITE_JOB_PASSED GET "${BUILDKITE_JOB}" passed)
string(JSON BUILDKITE_JOB_GROUP_ID GET "${BUILDKITE_JOB}" group_uuid)
string(JSON BUILDKITE_JOB_GROUP_KEY GET "${BUILDKITE_JOB}" group_identifier)
string(JSON BUILDKITE_JOB_NAME GET "${BUILDKITE_JOB}" step_key)
if(NOT BUILDKITE_JOB_NAME)
string(JSON BUILDKITE_JOB_NAME GET ${BUILDKITE_JOB} name)
string(JSON BUILDKITE_JOB_NAME GET "${BUILDKITE_JOB}" name)
endif()
if(NOT BUILDKITE_JOB_PASSED)
@@ -121,7 +121,7 @@ foreach(i RANGE ${BUILDKITE_JOBS_MAX_INDEX})
endif()
file(READ ${BUILDKITE_ARTIFACTS_PATH} BUILDKITE_ARTIFACTS)
string(JSON BUILDKITE_ARTIFACTS_LENGTH LENGTH ${BUILDKITE_ARTIFACTS})
string(JSON BUILDKITE_ARTIFACTS_LENGTH LENGTH "${BUILDKITE_ARTIFACTS}")
if(NOT BUILDKITE_ARTIFACTS_LENGTH GREATER 0)
list(APPEND BUILDKITE_JOBS_NO_ARTIFACTS ${BUILDKITE_JOB_NAME})
continue()
@@ -129,9 +129,9 @@ foreach(i RANGE ${BUILDKITE_JOBS_MAX_INDEX})
math(EXPR BUILDKITE_ARTIFACTS_MAX_INDEX "${BUILDKITE_ARTIFACTS_LENGTH} - 1")
foreach(i RANGE 0 ${BUILDKITE_ARTIFACTS_MAX_INDEX})
string(JSON BUILDKITE_ARTIFACT GET ${BUILDKITE_ARTIFACTS} ${i})
string(JSON BUILDKITE_ARTIFACT_ID GET ${BUILDKITE_ARTIFACT} id)
string(JSON BUILDKITE_ARTIFACT_PATH GET ${BUILDKITE_ARTIFACT} path)
string(JSON BUILDKITE_ARTIFACT GET "${BUILDKITE_ARTIFACTS}" ${i})
string(JSON BUILDKITE_ARTIFACT_ID GET "${BUILDKITE_ARTIFACT}" id)
string(JSON BUILDKITE_ARTIFACT_PATH GET "${BUILDKITE_ARTIFACT}" path)
if(NOT BUILDKITE_ARTIFACT_PATH MATCHES "\\.(o|a|lib|zip|tar|gz)")
continue()

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "bun",
"version": "1.3.9",
"version": "1.3.10",
"workspaces": [
"./packages/bun-types",
"./packages/@types/bun"

View File

@@ -95,12 +95,12 @@ export const platforms: Platform[] = [
bin: "bun-windows-x64-baseline",
exe: "bin/bun.exe",
},
{
os: "win32",
arch: "arm64",
bin: "bun-windows-aarch64",
exe: "bin/bun.exe",
},
// {
// os: "win32",
// arch: "arm64",
// bin: "bun-windows-aarch64",
// exe: "bin/bun.exe",
// },
];
export const supportedPlatforms: Platform[] = platforms

View File

@@ -2445,7 +2445,12 @@ declare module "bun" {
/**
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
*/
interface BuildConfigBase {
interface BuildConfig {
/**
* Enable code splitting
*/
splitting?: boolean;
/**
* List of entrypoints, usually file paths
*/
@@ -2774,6 +2779,33 @@ declare module "bun" {
metafile?: boolean;
outdir?: string;
/**
* Create a standalone executable
*
* When `true`, creates an executable for the current platform.
* When a target string, creates an executable for that platform.
*
* @example
* ```ts
* // Create executable for current platform
* await Bun.build({
* entrypoints: ['./app.js'],
* compile: {
* target: 'linux-x64',
* },
* outfile: './my-app'
* });
*
* // Cross-compile for Linux x64
* await Bun.build({
* entrypoints: ['./app.js'],
* compile: 'linux-x64',
* outfile: './my-app'
* });
* ```
*/
compile?: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
}
interface CompileBuildOptions {
@@ -2832,57 +2864,6 @@ declare module "bun" {
};
}
// Compile build config - uses outfile for executable output
interface CompileBuildConfig extends BuildConfigBase {
/**
* Create a standalone executable
*
* When `true`, creates an executable for the current platform.
* When a target string, creates an executable for that platform.
*
* @example
* ```ts
* // Create executable for current platform
* await Bun.build({
* entrypoints: ['./app.js'],
* compile: {
* target: 'linux-x64',
* },
* outfile: './my-app'
* });
*
* // Cross-compile for Linux x64
* await Bun.build({
* entrypoints: ['./app.js'],
* compile: 'linux-x64',
* outfile: './my-app'
* });
* ```
*/
compile: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
/**
* Splitting is not currently supported with `.compile`
*/
splitting?: never;
}
interface NormalBuildConfig extends BuildConfigBase {
/**
* Enable code splitting
*
* This does not currently work with {@link CompileBuildConfig.compile `compile`}
*
* @default true
*/
splitting?: boolean;
}
/**
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
*/
type BuildConfig = CompileBuildConfig | NormalBuildConfig;
/**
* Hash and verify passwords using argon2 or bcrypt
*

View File

@@ -188,6 +188,103 @@ struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t
return loop;
}
/* Shared dispatch loop for both us_loop_run and us_loop_run_bun_tick */
static void us_internal_dispatch_ready_polls(struct us_loop_t *loop) {
#ifdef LIBUS_USE_EPOLL
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
if (LIKELY(poll)) {
if (CLEAR_POINTER_TAG(poll) != poll) {
Bun__internal_dispatch_ready_poll(loop, poll);
continue;
}
int events = loop->ready_polls[loop->current_ready_poll].events;
const int error = events & EPOLLERR;
const int eof = events & EPOLLHUP;
events &= us_poll_events(poll);
if (events || error || eof) {
us_internal_dispatch_ready_poll(poll, error, eof, events);
}
}
}
#else
/* Kqueue delivers each filter (READ, WRITE, TIMER, etc.) as a separate kevent,
* so the same fd/poll can appear twice in ready_polls. We coalesce them into a
* single set of flags per poll before dispatching, matching epoll's behavior
* where each fd appears once with a combined bitmask. */
struct kevent_flags {
uint8_t readable : 1;
uint8_t writable : 1;
uint8_t error : 1;
uint8_t eof : 1;
uint8_t skip : 1;
uint8_t _pad : 3;
};
_Static_assert(sizeof(struct kevent_flags) == 1, "kevent_flags must be 1 byte");
struct kevent_flags coalesced[LIBUS_MAX_READY_POLLS]; /* no zeroing needed — every index is written in the first pass */
/* First pass: decode kevents and coalesce same-poll entries */
for (int i = 0; i < loop->num_ready_polls; i++) {
struct us_poll_t *poll = GET_READY_POLL(loop, i);
if (!poll || CLEAR_POINTER_TAG(poll) != poll) {
coalesced[i] = (struct kevent_flags){ .skip = 1 };
continue;
}
const int16_t filter = loop->ready_polls[i].filter;
const uint16_t flags = loop->ready_polls[i].flags;
struct kevent_flags bits = {
.readable = (filter == EVFILT_READ || filter == EVFILT_TIMER || filter == EVFILT_MACHPORT),
.writable = (filter == EVFILT_WRITE),
.error = !!(flags & EV_ERROR),
.eof = !!(flags & EV_EOF),
};
/* Look backward for a prior entry with the same poll to coalesce into.
* Kqueue returns at most 2 kevents per fd (READ + WRITE). */
int merged = 0;
for (int j = i - 1; j >= 0; j--) {
if (!coalesced[j].skip && GET_READY_POLL(loop, j) == poll) {
coalesced[j].readable |= bits.readable;
coalesced[j].writable |= bits.writable;
coalesced[j].error |= bits.error;
coalesced[j].eof |= bits.eof;
coalesced[i] = (struct kevent_flags){ .skip = 1 };
merged = 1;
break;
}
}
if (!merged) {
coalesced[i] = bits;
}
}
/* Second pass: dispatch everything in order — tagged pointers and coalesced events */
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
if (!poll) continue;
/* Tagged pointers (FilePoll) go through Bun's own dispatch */
if (CLEAR_POINTER_TAG(poll) != poll) {
Bun__internal_dispatch_ready_poll(loop, poll);
continue;
}
struct kevent_flags bits = coalesced[loop->current_ready_poll];
if (bits.skip) continue;
int events = (bits.readable ? LIBUS_SOCKET_READABLE : 0)
| (bits.writable ? LIBUS_SOCKET_WRITABLE : 0);
events &= us_poll_events(poll);
if (events || bits.error || bits.eof) {
us_internal_dispatch_ready_poll(poll, bits.error, bits.eof, events);
}
}
#endif
}
void us_loop_run(struct us_loop_t *loop) {
us_loop_integrate(loop);
@@ -205,41 +302,7 @@ void us_loop_run(struct us_loop_t *loop) {
} while (IS_EINTR(loop->num_ready_polls));
#endif
/* Iterate ready polls, dispatching them by type */
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
/* Any ready poll marked with nullptr will be ignored */
if (LIKELY(poll)) {
if (CLEAR_POINTER_TAG(poll) != poll) {
Bun__internal_dispatch_ready_poll(loop, poll);
continue;
}
#ifdef LIBUS_USE_EPOLL
int events = loop->ready_polls[loop->current_ready_poll].events;
const int error = events & EPOLLERR;
const int eof = events & EPOLLHUP;
#else
const struct kevent64_s* current_kevent = &loop->ready_polls[loop->current_ready_poll];
const int16_t filter = current_kevent->filter;
const uint16_t flags = current_kevent->flags;
const uint32_t fflags = current_kevent->fflags;
// > Multiple events which trigger the filter do not result in multiple kevents being placed on the kqueue
// > Instead, the filter will aggregate the events into a single kevent struct
// Note: EV_ERROR only sets the error in data as part of changelist. Not in this call!
int events = 0
| ((filter == EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
| ((filter == EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
const int error = (flags & (EV_ERROR)) ? ((int)fflags || 1) : 0;
const int eof = (flags & (EV_EOF));
#endif
/* Always filter all polls by what they actually poll for (callback polls always poll for readable) */
events &= us_poll_events(poll);
if (events || error || eof) {
us_internal_dispatch_ready_poll(poll, error, eof, events);
}
}
}
us_internal_dispatch_ready_polls(loop);
/* Emit post callback */
us_internal_loop_post(loop);
@@ -263,57 +326,33 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout
/* Emit pre callback */
us_internal_loop_pre(loop);
if (loop->data.jsc_vm)
const unsigned int had_wakeups = __atomic_exchange_n(&loop->pending_wakeups, 0, __ATOMIC_ACQUIRE);
const int will_idle_inside_event_loop = had_wakeups == 0 && (!timeout || (timeout->tv_nsec != 0 || timeout->tv_sec != 0));
if (will_idle_inside_event_loop && loop->data.jsc_vm)
Bun__JSC_onBeforeWait(loop->data.jsc_vm);
/* Fetch ready polls */
#ifdef LIBUS_USE_EPOLL
/* A zero timespec already has a fast path in ep_poll (fs/eventpoll.c):
* it sets timed_out=1 (line 1952) and returns before any scheduler
* interaction (line 1975). No equivalent of KEVENT_FLAG_IMMEDIATE needed. */
loop->num_ready_polls = bun_epoll_pwait2(loop->fd, loop->ready_polls, 1024, timeout);
#else
do {
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, timeout);
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024,
/* When we won't idle (pending wakeups or zero timeout), use KEVENT_FLAG_IMMEDIATE.
* In XNU's kqueue_scan (bsd/kern/kern_event.c):
* - KEVENT_FLAG_IMMEDIATE: returns immediately after kqueue_process() (line 8031)
* - Zero timespec without the flag: falls through to assert_wait_deadline (line 8039)
* and thread_block (line 8048), doing a full context switch cycle (~14us) even
* though the deadline is already in the past. */
will_idle_inside_event_loop ? 0 : KEVENT_FLAG_IMMEDIATE,
timeout);
} while (IS_EINTR(loop->num_ready_polls));
#endif
/* Iterate ready polls, dispatching them by type */
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
/* Any ready poll marked with nullptr will be ignored */
if (LIKELY(poll)) {
if (CLEAR_POINTER_TAG(poll) != poll) {
Bun__internal_dispatch_ready_poll(loop, poll);
continue;
}
#ifdef LIBUS_USE_EPOLL
int events = loop->ready_polls[loop->current_ready_poll].events;
const int error = events & EPOLLERR;
const int eof = events & EPOLLHUP;
#else
const struct kevent64_s* current_kevent = &loop->ready_polls[loop->current_ready_poll];
const int16_t filter = current_kevent->filter;
const uint16_t flags = current_kevent->flags;
const uint32_t fflags = current_kevent->fflags;
// > Multiple events which trigger the filter do not result in multiple kevents being placed on the kqueue
// > Instead, the filter will aggregate the events into a single kevent struct
int events = 0
| ((filter & EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
| ((filter & EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
// Note: EV_ERROR only sets the error in data as part of changelist. Not in this call!
const int error = (flags & (EV_ERROR)) ? ((int)fflags || 1) : 0;
const int eof = (flags & (EV_EOF));
#endif
/* Always filter all polls by what they actually poll for (callback polls always poll for readable) */
events &= us_poll_events(poll);
if (events || error || eof) {
us_internal_dispatch_ready_poll(poll, error, eof, events);
}
}
}
us_internal_dispatch_ready_polls(loop);
/* Emit post callback */
us_internal_loop_post(loop);
@@ -613,7 +652,7 @@ struct us_internal_async *us_internal_create_async(struct us_loop_t *loop, int f
struct us_internal_callback_t *cb = (struct us_internal_callback_t *) p;
cb->loop = loop;
cb->cb_expects_the_loop = 1;
cb->leave_poll_ready = 0;
cb->leave_poll_ready = 1; /* Edge-triggered: skip reading eventfd on wakeup */
return (struct us_internal_async *) cb;
}
@@ -635,12 +674,28 @@ void us_internal_async_set(struct us_internal_async *a, void (*cb)(struct us_int
internal_cb->cb = (void (*)(struct us_internal_callback_t *)) cb;
us_poll_start((struct us_poll_t *) a, internal_cb->loop, LIBUS_SOCKET_READABLE);
#ifdef LIBUS_USE_EPOLL
/* Upgrade to edge-triggered to avoid reading the eventfd on each wakeup */
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.ptr = (struct us_poll_t *) a;
epoll_ctl(internal_cb->loop->fd, EPOLL_CTL_MOD,
us_poll_fd((struct us_poll_t *) a), &event);
#endif
}
void us_internal_async_wakeup(struct us_internal_async *a) {
uint64_t one = 1;
int written = write(us_poll_fd((struct us_poll_t *) a), &one, 8);
(void)written;
int fd = us_poll_fd((struct us_poll_t *) a);
uint64_t val;
for (val = 1; ; val = 1) {
if (write(fd, &val, 8) >= 0) return;
if (errno == EINTR) continue;
if (errno == EAGAIN) {
/* Counter overflow — drain and retry */
if (read(fd, &val, 8) > 0 || errno == EAGAIN || errno == EINTR) continue;
}
break;
}
}
#else

View File

@@ -54,6 +54,10 @@ struct us_loop_t {
/* Number of polls owned by bun */
unsigned int bun_polls;
/* Incremented atomically by wakeup(), swapped to 0 before epoll/kqueue.
* If non-zero, the event loop will return immediately so we can skip the GC safepoint. */
unsigned int pending_wakeups;
/* The list of ready polls */
#ifdef LIBUS_USE_EPOLL
alignas(LIBUS_EXT_ALIGNMENT) struct epoll_event ready_polls[1024];

View File

@@ -93,6 +93,9 @@ void us_internal_loop_data_free(struct us_loop_t *loop) {
}
void us_wakeup_loop(struct us_loop_t *loop) {
#ifndef LIBUS_USE_LIBUV
__atomic_fetch_add(&loop->pending_wakeups, 1, __ATOMIC_RELEASE);
#endif
us_internal_async_wakeup(loop->data.wakeup_async);
}
@@ -393,8 +396,12 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
if (events & LIBUS_SOCKET_WRITABLE && !error) {
s->flags.last_write_failed = 0;
#ifdef LIBUS_USE_KQUEUE
/* Kqueue is one-shot so is not writable anymore */
p->state.poll_type = us_internal_poll_type(p) | ((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0);
/* Kqueue EVFILT_WRITE is one-shot so the filter is removed after delivery.
* Clear POLLING_OUT to reflect this.
* Keep POLLING_IN from the poll's own state, NOT from `events`: kqueue delivers
* each filter as a separate kevent, so a pure EVFILT_WRITE event won't have
* LIBUS_SOCKET_READABLE set even though the socket is still registered for reads. */
p->state.poll_type = us_internal_poll_type(p) | (p->state.poll_type & POLL_TYPE_POLLING_IN);
#endif
s = s->context->on_writable(s);
@@ -412,7 +419,7 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
us_poll_change(&s->p, loop, us_poll_events(&s->p) & LIBUS_SOCKET_READABLE);
} else {
#ifdef LIBUS_USE_KQUEUE
/* Kqueue one-shot writable needs to be re-enabled */
/* Kqueue one-shot writable needs to be re-registered */
us_poll_change(&s->p, loop, us_poll_events(&s->p) | LIBUS_SOCKET_WRITABLE);
#endif
}

View File

@@ -2,10 +2,7 @@
const Self = @This();
const safety_checks = bun.Environment.isDebug or bun.Environment.enable_asan;
#heap: *mimalloc.Heap,
thread_id: if (safety_checks) std.Thread.Id else void,
#heap: if (safety_checks) Owned(*DebugHeap) else *mimalloc.Heap,
/// Uses the default thread-local heap. This type is zero-sized.
///
@@ -23,18 +20,18 @@ pub const Default = struct {
///
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
pub const Borrowed = struct {
#heap: *mimalloc.Heap,
#heap: BorrowedHeap,
pub fn allocator(self: Borrowed) std.mem.Allocator {
return .{ .ptr = self.#heap, .vtable = c_allocator_vtable };
return .{ .ptr = self.#heap, .vtable = &c_allocator_vtable };
}
pub fn getDefault() Borrowed {
return .{ .#heap = mimalloc.mi_heap_main() };
return .{ .#heap = getThreadHeap() };
}
pub fn gc(self: Borrowed) void {
mimalloc.mi_heap_collect(self.#heap, false);
mimalloc.mi_heap_collect(self.getMimallocHeap(), false);
}
pub fn helpCatchMemoryIssues(self: Borrowed) void {
@@ -44,17 +41,30 @@ pub const Borrowed = struct {
}
}
pub fn ownsPtr(self: Borrowed, ptr: *const anyopaque) bool {
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
}
fn fromOpaque(ptr: *anyopaque) Borrowed {
return .{ .#heap = @ptrCast(@alignCast(ptr)) };
}
fn getMimallocHeap(self: Borrowed) *mimalloc.Heap {
return if (comptime safety_checks) self.#heap.inner else self.#heap;
}
fn assertThreadLock(self: Borrowed) void {
if (comptime safety_checks) self.#heap.thread_lock.assertLocked();
}
fn alignedAlloc(self: Borrowed, len: usize, alignment: Alignment) ?[*]u8 {
log("Malloc: {d}\n", .{len});
const heap = self.getMimallocHeap();
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
mimalloc.mi_heap_malloc_aligned(self.#heap, len, alignment.toByteUnits())
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
else
mimalloc.mi_heap_malloc(self.#heap, len);
mimalloc.mi_heap_malloc(heap, len);
if (comptime bun.Environment.isDebug) {
const usable = mimalloc.mi_malloc_usable_size(ptr);
@@ -79,17 +89,42 @@ pub const Borrowed = struct {
}
};
const BorrowedHeap = if (safety_checks) *DebugHeap else *mimalloc.Heap;
const DebugHeap = struct {
inner: *mimalloc.Heap,
thread_lock: bun.safety.ThreadLock,
pub const deinit = void;
};
threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;
fn getThreadHeap() BorrowedHeap {
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
if (thread_heap == null) {
thread_heap = .{
.inner = mimalloc.mi_heap_get_default(),
.thread_lock = .initLocked(),
};
}
return &thread_heap.?;
}
const log = bun.Output.scoped(.mimalloc, .hidden);
pub fn allocator(self: Self) std.mem.Allocator {
self.assertThreadOwnership();
return self.borrow().allocator();
}
pub fn borrow(self: Self) Borrowed {
return .{ .#heap = self.#heap };
return .{ .#heap = if (comptime safety_checks) self.#heap.get() else self.#heap };
}
/// Internally, mimalloc calls mi_heap_get_default()
/// to get the default heap.
/// It uses pthread_getspecific to do that.
/// We can save those extra calls if we just do it once in here
pub fn getThreadLocalDefault() std.mem.Allocator {
if (bun.Environment.enable_asan) return bun.default_allocator;
return Borrowed.getDefault().allocator();
@@ -122,15 +157,22 @@ pub fn dumpStats(_: Self) void {
}
pub fn deinit(self: *Self) void {
mimalloc.mi_heap_destroy(self.#heap);
const mimalloc_heap = self.borrow().getMimallocHeap();
if (comptime safety_checks) {
self.#heap.deinit();
}
mimalloc.mi_heap_destroy(mimalloc_heap);
self.* = undefined;
}
pub fn init() Self {
return .{
.#heap = mimalloc.mi_heap_new() orelse bun.outOfMemory(),
.thread_id = if (safety_checks) std.Thread.getCurrentId() else {},
};
const mimalloc_heap = mimalloc.mi_heap_new() orelse bun.outOfMemory();
if (comptime !safety_checks) return .{ .#heap = mimalloc_heap };
const heap: Owned(*DebugHeap) = .new(.{
.inner = mimalloc_heap,
.thread_lock = .initLocked(),
});
return .{ .#heap = heap };
}
pub fn gc(self: Self) void {
@@ -141,16 +183,8 @@ pub fn helpCatchMemoryIssues(self: Self) void {
self.borrow().helpCatchMemoryIssues();
}
fn assertThreadOwnership(self: Self) void {
if (comptime safety_checks) {
const current_thread = std.Thread.getCurrentId();
if (current_thread != self.thread_id) {
std.debug.panic(
"MimallocArena used from wrong thread: arena belongs to thread {d}, but current thread is {d}",
.{ self.thread_id, current_thread },
);
}
}
pub fn ownsPtr(self: Self, ptr: *const anyopaque) bool {
return self.borrow().ownsPtr(ptr);
}
fn alignedAllocSize(ptr: [*]u8) usize {
@@ -159,10 +193,13 @@ fn alignedAllocSize(ptr: [*]u8) usize {
fn vtable_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
const self: Borrowed = .fromOpaque(ptr);
self.assertThreadLock();
return self.alignedAlloc(len, alignment);
}
fn vtable_resize(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
fn vtable_resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
const self: Borrowed = .fromOpaque(ptr);
self.assertThreadLock();
return mimalloc.mi_expand(buf.ptr, new_len) != null;
}
@@ -186,17 +223,39 @@ fn vtable_free(
}
}
/// Attempt to expand or shrink memory, allowing relocation.
///
/// `memory.len` must equal the length requested from the most recent
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
/// equal the same value that was passed as the `alignment` parameter to
/// the original `alloc` call.
///
/// A non-`null` return value indicates the resize was successful. The
/// allocation may have same address, or may have been relocated. In either
/// case, the allocation now has size of `new_len`. A `null` return value
/// indicates that the resize would be equivalent to allocating new memory,
/// copying the bytes from the old memory, and then freeing the old memory.
/// In such case, it is more efficient for the caller to perform the copy.
///
/// `new_len` must be greater than zero.
///
/// `ret_addr` is optionally provided as the first return address of the
/// allocation call stack. If the value is `0` it means no return address
/// has been provided.
fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
const self: Borrowed = .fromOpaque(ptr);
const value = mimalloc.mi_heap_realloc_aligned(self.#heap, buf.ptr, new_len, alignment.toByteUnits());
self.assertThreadLock();
const heap = self.getMimallocHeap();
const aligned_size = alignment.toByteUnits();
const value = mimalloc.mi_heap_realloc_aligned(heap, buf.ptr, new_len, aligned_size);
return @ptrCast(value);
}
pub fn isInstance(alloc: std.mem.Allocator) bool {
return alloc.vtable == c_allocator_vtable;
return alloc.vtable == &c_allocator_vtable;
}
const c_allocator_vtable = &std.mem.Allocator.VTable{
const c_allocator_vtable = std.mem.Allocator.VTable{
.alloc = vtable_alloc,
.resize = vtable_resize,
.remap = vtable_remap,
@@ -209,3 +268,5 @@ const Alignment = std.mem.Alignment;
const bun = @import("bun");
const assert = bun.assert;
const mimalloc = bun.mimalloc;
const Owned = bun.ptr.Owned;
const safety_checks = bun.Environment.ci_assert;

View File

@@ -60,29 +60,17 @@ pub const Heap = opaque {
return mi_heap_realloc(self, p, newsize);
}
pub fn isOwned(self: *Heap, p: ?*const anyopaque) bool {
return mi_heap_contains(self, p);
pub fn isOwned(self: *Heap, p: ?*anyopaque) bool {
return mi_heap_check_owned(self, p);
}
};
pub extern fn mi_heap_new() ?*Heap;
pub extern fn mi_heap_delete(heap: *Heap) void;
pub extern fn mi_heap_destroy(heap: *Heap) void;
pub extern fn mi_heap_set_default(heap: *Heap) *Heap;
pub extern fn mi_heap_get_default() *Heap;
pub extern fn mi_heap_get_backing() *Heap;
pub extern fn mi_heap_collect(heap: *Heap, force: bool) void;
pub extern fn mi_heap_main() *Heap;
// Thread-local heap (theap) API - new in mimalloc v3
pub const THeap = opaque {};
pub extern fn mi_theap_get_default() *THeap;
pub extern fn mi_theap_set_default(theap: *THeap) *THeap;
pub extern fn mi_theap_collect(theap: *THeap, force: bool) void;
pub extern fn mi_theap_malloc(theap: *THeap, size: usize) ?*anyopaque;
pub extern fn mi_theap_zalloc(theap: *THeap, size: usize) ?*anyopaque;
pub extern fn mi_theap_calloc(theap: *THeap, count: usize, size: usize) ?*anyopaque;
pub extern fn mi_theap_malloc_small(theap: *THeap, size: usize) ?*anyopaque;
pub extern fn mi_theap_malloc_aligned(theap: *THeap, size: usize, alignment: usize) ?*anyopaque;
pub extern fn mi_theap_realloc(theap: *THeap, p: ?*anyopaque, newsize: usize) ?*anyopaque;
pub extern fn mi_theap_destroy(theap: *THeap) void;
pub extern fn mi_heap_theap(heap: *Heap) *THeap;
pub extern fn mi_heap_malloc(heap: *Heap, size: usize) ?*anyopaque;
pub extern fn mi_heap_zalloc(heap: *Heap, size: usize) ?*anyopaque;
pub extern fn mi_heap_calloc(heap: *Heap, count: usize, size: usize) ?*anyopaque;
@@ -114,7 +102,8 @@ pub extern fn mi_heap_rezalloc_aligned(heap: *Heap, p: ?*anyopaque, newsize: usi
pub extern fn mi_heap_rezalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newsize: usize, alignment: usize, offset: usize) ?*anyopaque;
pub extern fn mi_heap_recalloc_aligned(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize) ?*anyopaque;
pub extern fn mi_heap_recalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize, offset: usize) ?*anyopaque;
pub extern fn mi_heap_contains(heap: *const Heap, p: ?*const anyopaque) bool;
pub extern fn mi_heap_contains_block(heap: *Heap, p: *const anyopaque) bool;
pub extern fn mi_heap_check_owned(heap: *Heap, p: *const anyopaque) bool;
pub extern fn mi_check_owned(p: ?*const anyopaque) bool;
pub const struct_mi_heap_area_s = extern struct {
blocks: ?*anyopaque,

View File

@@ -245,6 +245,16 @@ pub const All = struct {
}
pub fn getTimeout(this: *All, spec: *timespec, vm: *VirtualMachine) bool {
// On POSIX, if there are pending immediate tasks, use a zero timeout
// so epoll/kqueue returns immediately without the overhead of writing
// to the eventfd via wakeup().
if (comptime Environment.isPosix) {
if (vm.event_loop.immediate_tasks.items.len > 0) {
spec.* = .{ .nsec = 0, .sec = 0 };
return true;
}
}
var maybe_now: ?timespec = null;
while (this.timers.peek()) |min| {
const now = maybe_now orelse now: {

View File

@@ -335,6 +335,12 @@ static JSValue constructBunSQLObject(VM& vm, JSObject* bunObject)
}
extern "C" JSC::EncodedJSValue JSPasswordObject__create(JSGlobalObject*);
extern "C" JSC::EncodedJSValue Bun__createYogaModule(Zig::GlobalObject*);
static JSValue constructYogaObject(VM& vm, JSObject* bunObject)
{
return JSValue::decode(Bun__createYogaModule(jsCast<Zig::GlobalObject*>(bunObject->globalObject())));
}
static JSValue constructPasswordObject(VM& vm, JSObject* bunObject)
{
@@ -924,6 +930,7 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
markdown BunObject_lazyPropCb_wrap_markdown DontDelete|PropertyCallback
TOML BunObject_lazyPropCb_wrap_TOML DontDelete|PropertyCallback
YAML BunObject_lazyPropCb_wrap_YAML DontDelete|PropertyCallback
Yoga constructYogaObject ReadOnly|DontDelete|PropertyCallback
Transpiler BunObject_lazyPropCb_wrap_Transpiler DontDelete|PropertyCallback
embeddedFiles BunObject_lazyPropCb_wrap_embeddedFiles DontDelete|PropertyCallback
S3Client BunObject_lazyPropCb_wrap_S3Client DontDelete|PropertyCallback

View File

@@ -119,6 +119,7 @@ JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap16);
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap32);
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap64);
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_toString);
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_slice);
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_write);
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_writeBigInt64LE);
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_writeBigInt64BE);
@@ -1879,6 +1880,103 @@ bool inline parseArrayIndex(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalO
return true;
}
static ALWAYS_INLINE size_t adjustSliceOffsetInt32(int32_t offset, size_t length)
{
if (offset < 0) {
int64_t adjusted = static_cast<int64_t>(offset) + static_cast<int64_t>(length);
return adjusted > 0 ? static_cast<size_t>(adjusted) : 0;
}
return static_cast<size_t>(offset) < length ? static_cast<size_t>(offset) : length;
}
static ALWAYS_INLINE size_t adjustSliceOffsetDouble(double offset, size_t length)
{
if (std::isnan(offset)) {
return 0;
}
offset = std::trunc(offset);
if (offset == 0) {
return 0;
} else if (offset < 0) {
double adjusted = offset + static_cast<double>(length);
return adjusted > 0 ? static_cast<size_t>(adjusted) : 0;
} else {
return offset < static_cast<double>(length) ? static_cast<size_t>(offset) : length;
}
}
static JSC::EncodedJSValue jsBufferPrototypeFunction_sliceBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
size_t byteLength = castedThis->byteLength();
size_t byteOffset = castedThis->byteOffset();
size_t startOffset = 0;
size_t endOffset = byteLength;
unsigned argCount = callFrame->argumentCount();
if (argCount > 0) {
JSValue startArg = callFrame->uncheckedArgument(0);
if (startArg.isInt32()) {
startOffset = adjustSliceOffsetInt32(startArg.asInt32(), byteLength);
} else if (!startArg.isUndefined()) {
double startD = startArg.toNumber(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
startOffset = adjustSliceOffsetDouble(startD, byteLength);
}
}
if (argCount > 1) {
JSValue endArg = callFrame->uncheckedArgument(1);
if (endArg.isInt32()) {
endOffset = adjustSliceOffsetInt32(endArg.asInt32(), byteLength);
} else if (!endArg.isUndefined()) {
double endD = endArg.toNumber(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
endOffset = adjustSliceOffsetDouble(endD, byteLength);
}
}
size_t newLength = endOffset > startOffset ? endOffset - startOffset : 0;
if (castedThis->isDetached()) [[unlikely]] {
throwVMTypeError(lexicalGlobalObject, throwScope, "Buffer is detached"_s);
return {};
}
RefPtr<ArrayBuffer> buffer = castedThis->possiblySharedBuffer();
if (!buffer) {
throwOutOfMemoryError(globalObject, throwScope);
return {};
}
if (castedThis->isResizableOrGrowableShared()) {
auto* subclassStructure = globalObject->JSResizableOrGrowableSharedBufferSubclassStructure();
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTF::move(buffer), byteOffset + startOffset, newLength);
RETURN_IF_EXCEPTION(throwScope, {});
if (!uint8Array) [[unlikely]] {
throwOutOfMemoryError(globalObject, throwScope);
return {};
}
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
}
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTF::move(buffer), byteOffset + startOffset, newLength);
RETURN_IF_EXCEPTION(throwScope, {});
if (!uint8Array) [[unlikely]] {
throwOutOfMemoryError(globalObject, throwScope);
return {};
}
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
}
// https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L834
// using byteLength and byte offsets here is intentional
static JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
@@ -2430,6 +2528,11 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_swap64, (JSGlobalObject * lex
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_swap64Body>(*lexicalGlobalObject, *callFrame, "swap64");
}
JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_slice, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_sliceBody>(*lexicalGlobalObject, *callFrame, "slice");
}
JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_toString, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_toStringBody>(*lexicalGlobalObject, *callFrame, "toString");
@@ -2711,8 +2814,8 @@ static const HashTableValue JSBufferPrototypeTableValues[]
{ "readUIntBE"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntBECodeGenerator, 1 } },
{ "readUIntLE"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntLECodeGenerator, 1 } },
{ "slice"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeSliceCodeGenerator, 2 } },
{ "subarray"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeSliceCodeGenerator, 2 } },
{ "slice"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_slice, 2 } },
{ "subarray"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_slice, 2 } },
{ "swap16"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap16, 0 } },
{ "swap32"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap32, 0 } },
{ "swap64"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap64, 0 } },

View File

@@ -0,0 +1,117 @@
#include "root.h"
#include "JSYogaConfig.h"
#include "YogaConfigImpl.h"
#include "webcore/DOMIsoSubspaces.h"
#include "webcore/DOMClientIsoSubspaces.h"
#include "webcore/WebCoreJSClientData.h"
#include <yoga/Yoga.h>
namespace Bun {
using namespace JSC;
const JSC::ClassInfo JSYogaConfig::s_info = { "Config"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaConfig) };
JSYogaConfig::JSYogaConfig(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
, m_impl(YogaConfigImpl::create())
{
}
JSYogaConfig::JSYogaConfig(JSC::VM& vm, JSC::Structure* structure, Ref<YogaConfigImpl>&& impl)
: Base(vm, structure)
, m_impl(std::move(impl))
{
}
JSYogaConfig::~JSYogaConfig()
{
// The WeakHandleOwner::finalize should handle cleanup
// Don't interfere with that mechanism
}
JSYogaConfig* JSYogaConfig::create(JSC::VM& vm, JSC::Structure* structure)
{
JSYogaConfig* config = new (NotNull, JSC::allocateCell<JSYogaConfig>(vm)) JSYogaConfig(vm, structure);
config->finishCreation(vm);
return config;
}
JSYogaConfig* JSYogaConfig::create(JSC::VM& vm, JSC::Structure* structure, Ref<YogaConfigImpl>&& impl)
{
JSYogaConfig* config = new (NotNull, JSC::allocateCell<JSYogaConfig>(vm)) JSYogaConfig(vm, structure, std::move(impl));
config->finishCreation(vm);
return config;
}
void JSYogaConfig::finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);
// Set this JS wrapper in the C++ impl
m_impl->setJSWrapper(this);
}
JSC::Structure* JSYogaConfig::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
void JSYogaConfig::destroy(JSC::JSCell* cell)
{
static_cast<JSYogaConfig*>(cell)->~JSYogaConfig();
}
template<typename MyClassT, JSC::SubspaceAccess mode>
JSC::GCClient::IsoSubspace* JSYogaConfig::subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSYogaConfig.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSYogaConfig = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSYogaConfig.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSYogaConfig = std::forward<decltype(space)>(space); });
}
template<typename Visitor>
void JSYogaConfig::visitChildrenImpl(JSC::JSCell* cell, Visitor& visitor)
{
JSYogaConfig* thisObject = jsCast<JSYogaConfig*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_context);
visitor.append(thisObject->m_loggerFunc);
visitor.append(thisObject->m_cloneNodeFunc);
}
DEFINE_VISIT_CHILDREN(JSYogaConfig);
template<typename Visitor>
void JSYogaConfig::visitAdditionalChildren(Visitor& visitor)
{
visitor.append(m_context);
visitor.append(m_loggerFunc);
visitor.append(m_cloneNodeFunc);
}
DEFINE_VISIT_ADDITIONAL_CHILDREN(JSYogaConfig);
template<typename Visitor>
void JSYogaConfig::visitOutputConstraints(JSC::JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<JSYogaConfig*>(cell);
// Lock for concurrent GC thread safety
WTF::Locker locker { thisObject->cellLock() };
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitOutputConstraints(thisObject, visitor);
thisObject->visitAdditionalChildren(visitor);
}
template void JSYogaConfig::visitOutputConstraints(JSC::JSCell*, JSC::AbstractSlotVisitor&);
template void JSYogaConfig::visitOutputConstraints(JSC::JSCell*, JSC::SlotVisitor&);
} // namespace Bun

View File

@@ -0,0 +1,56 @@
#pragma once
#include "root.h"
#include <memory>
#include <JavaScriptCore/JSDestructibleObject.h>
#include <JavaScriptCore/WriteBarrier.h>
#include <wtf/Ref.h>
// Forward declarations
typedef struct YGConfig* YGConfigRef;
namespace Bun {
class YogaConfigImpl;
class JSYogaConfig final : public JSC::JSDestructibleObject {
public:
using Base = JSC::JSDestructibleObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static constexpr JSC::DestructionMode needsDestruction = JSC::NeedsDestruction;
static JSYogaConfig* create(JSC::VM&, JSC::Structure*);
static JSYogaConfig* create(JSC::VM&, JSC::Structure*, Ref<YogaConfigImpl>&&);
static void destroy(JSC::JSCell*);
static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue);
~JSYogaConfig();
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM&);
DECLARE_INFO;
DECLARE_VISIT_CHILDREN;
template<typename Visitor> void visitAdditionalChildren(Visitor&);
template<typename Visitor> static void visitOutputConstraints(JSC::JSCell*, Visitor&);
YogaConfigImpl& impl() { return m_impl.get(); }
const YogaConfigImpl& impl() const { return m_impl.get(); }
// Context storage
JSC::WriteBarrier<JSC::Unknown> m_context;
// Logger callback
JSC::WriteBarrier<JSC::JSObject> m_loggerFunc;
// Clone node callback
JSC::WriteBarrier<JSC::JSObject> m_cloneNodeFunc;
private:
JSYogaConfig(JSC::VM&, JSC::Structure*);
JSYogaConfig(JSC::VM&, JSC::Structure*, Ref<YogaConfigImpl>&&);
void finishCreation(JSC::VM&);
Ref<YogaConfigImpl> m_impl;
};
} // namespace Bun

View File

@@ -0,0 +1,39 @@
#include "JSYogaConfigOwner.h"
#include "YogaConfigImpl.h"
#include "JSYogaConfig.h"
#include <JavaScriptCore/JSCInlines.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Compiler.h>
namespace Bun {
void JSYogaConfigOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
{
// This is where we deref the C++ YogaConfigImpl wrapper
// The context contains our YogaConfigImpl
auto* impl = static_cast<YogaConfigImpl*>(context);
// Deref the YogaConfigImpl - this will decrease its reference count
// and potentially destroy it if no other references exist
impl->deref();
}
bool JSYogaConfigOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void* context, JSC::AbstractSlotVisitor& visitor, ASCIILiteral* reason)
{
UNUSED_PARAM(handle);
UNUSED_PARAM(context);
// YogaConfig doesn't currently use opaque roots, so always return false
// This allows normal GC collection based on JS reference reachability
if (reason)
*reason = "YogaConfig not using opaque roots"_s;
return false;
}
JSYogaConfigOwner& jsYogaConfigOwner()
{
static NeverDestroyed<JSYogaConfigOwner> owner;
return owner.get();
}
} // namespace Bun

View File

@@ -0,0 +1,20 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/WeakHandleOwner.h>
#include <JavaScriptCore/Weak.h>
namespace Bun {
class YogaConfigImpl;
class JSYogaConfig;
class JSYogaConfigOwner : public JSC::WeakHandleOwner {
public:
void finalize(JSC::Handle<JSC::Unknown>, void* context) final;
bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, ASCIILiteral*) final;
};
JSYogaConfigOwner& jsYogaConfigOwner();
} // namespace Bun

View File

@@ -0,0 +1,121 @@
#include "root.h"
#include "JSYogaConstants.h"
#include <JavaScriptCore/JSCInlines.h>
#include <yoga/Yoga.h>
namespace Bun {
using namespace JSC;
const JSC::ClassInfo JSYogaConstants::s_info = { "YogaConstants"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaConstants) };
void JSYogaConstants::finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);
// Align values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_AUTO"_s), JSC::jsNumber(static_cast<int>(YGAlignAuto)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_FLEX_START"_s), JSC::jsNumber(static_cast<int>(YGAlignFlexStart)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_CENTER"_s), JSC::jsNumber(static_cast<int>(YGAlignCenter)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_FLEX_END"_s), JSC::jsNumber(static_cast<int>(YGAlignFlexEnd)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_STRETCH"_s), JSC::jsNumber(static_cast<int>(YGAlignStretch)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_BASELINE"_s), JSC::jsNumber(static_cast<int>(YGAlignBaseline)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_SPACE_BETWEEN"_s), JSC::jsNumber(static_cast<int>(YGAlignSpaceBetween)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_SPACE_AROUND"_s), JSC::jsNumber(static_cast<int>(YGAlignSpaceAround)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ALIGN_SPACE_EVENLY"_s), JSC::jsNumber(static_cast<int>(YGAlignSpaceEvenly)), 0);
// Direction values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DIRECTION_INHERIT"_s), JSC::jsNumber(static_cast<int>(YGDirectionInherit)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DIRECTION_LTR"_s), JSC::jsNumber(static_cast<int>(YGDirectionLTR)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DIRECTION_RTL"_s), JSC::jsNumber(static_cast<int>(YGDirectionRTL)), 0);
// Display values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DISPLAY_FLEX"_s), JSC::jsNumber(static_cast<int>(YGDisplayFlex)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DISPLAY_NONE"_s), JSC::jsNumber(static_cast<int>(YGDisplayNone)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "DISPLAY_CONTENTS"_s), JSC::jsNumber(static_cast<int>(YGDisplayContents)), 0);
// Edge values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_LEFT"_s), JSC::jsNumber(static_cast<int>(YGEdgeLeft)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_TOP"_s), JSC::jsNumber(static_cast<int>(YGEdgeTop)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_RIGHT"_s), JSC::jsNumber(static_cast<int>(YGEdgeRight)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_BOTTOM"_s), JSC::jsNumber(static_cast<int>(YGEdgeBottom)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_START"_s), JSC::jsNumber(static_cast<int>(YGEdgeStart)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_END"_s), JSC::jsNumber(static_cast<int>(YGEdgeEnd)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_HORIZONTAL"_s), JSC::jsNumber(static_cast<int>(YGEdgeHorizontal)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_VERTICAL"_s), JSC::jsNumber(static_cast<int>(YGEdgeVertical)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EDGE_ALL"_s), JSC::jsNumber(static_cast<int>(YGEdgeAll)), 0);
// Experimental feature values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS"_s), JSC::jsNumber(static_cast<int>(YGExperimentalFeatureWebFlexBasis)), 0);
// Flex direction values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "FLEX_DIRECTION_COLUMN"_s), JSC::jsNumber(static_cast<int>(YGFlexDirectionColumn)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "FLEX_DIRECTION_COLUMN_REVERSE"_s), JSC::jsNumber(static_cast<int>(YGFlexDirectionColumnReverse)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "FLEX_DIRECTION_ROW"_s), JSC::jsNumber(static_cast<int>(YGFlexDirectionRow)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "FLEX_DIRECTION_ROW_REVERSE"_s), JSC::jsNumber(static_cast<int>(YGFlexDirectionRowReverse)), 0);
// Gutter values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "GUTTER_COLUMN"_s), JSC::jsNumber(static_cast<int>(YGGutterColumn)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "GUTTER_ROW"_s), JSC::jsNumber(static_cast<int>(YGGutterRow)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "GUTTER_ALL"_s), JSC::jsNumber(static_cast<int>(YGGutterAll)), 0);
// Justify values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_FLEX_START"_s), JSC::jsNumber(static_cast<int>(YGJustifyFlexStart)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_CENTER"_s), JSC::jsNumber(static_cast<int>(YGJustifyCenter)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_FLEX_END"_s), JSC::jsNumber(static_cast<int>(YGJustifyFlexEnd)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_SPACE_BETWEEN"_s), JSC::jsNumber(static_cast<int>(YGJustifySpaceBetween)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_SPACE_AROUND"_s), JSC::jsNumber(static_cast<int>(YGJustifySpaceAround)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "JUSTIFY_SPACE_EVENLY"_s), JSC::jsNumber(static_cast<int>(YGJustifySpaceEvenly)), 0);
// Log level values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_ERROR"_s), JSC::jsNumber(static_cast<int>(YGLogLevelError)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_WARN"_s), JSC::jsNumber(static_cast<int>(YGLogLevelWarn)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_INFO"_s), JSC::jsNumber(static_cast<int>(YGLogLevelInfo)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_DEBUG"_s), JSC::jsNumber(static_cast<int>(YGLogLevelDebug)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_VERBOSE"_s), JSC::jsNumber(static_cast<int>(YGLogLevelVerbose)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "LOG_LEVEL_FATAL"_s), JSC::jsNumber(static_cast<int>(YGLogLevelFatal)), 0);
// Measure mode values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "MEASURE_MODE_UNDEFINED"_s), JSC::jsNumber(static_cast<int>(YGMeasureModeUndefined)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "MEASURE_MODE_EXACTLY"_s), JSC::jsNumber(static_cast<int>(YGMeasureModeExactly)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "MEASURE_MODE_AT_MOST"_s), JSC::jsNumber(static_cast<int>(YGMeasureModeAtMost)), 0);
// Node type values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "NODE_TYPE_DEFAULT"_s), JSC::jsNumber(static_cast<int>(YGNodeTypeDefault)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "NODE_TYPE_TEXT"_s), JSC::jsNumber(static_cast<int>(YGNodeTypeText)), 0);
// Overflow values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "OVERFLOW_VISIBLE"_s), JSC::jsNumber(static_cast<int>(YGOverflowVisible)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "OVERFLOW_HIDDEN"_s), JSC::jsNumber(static_cast<int>(YGOverflowHidden)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "OVERFLOW_SCROLL"_s), JSC::jsNumber(static_cast<int>(YGOverflowScroll)), 0);
// Position type values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "POSITION_TYPE_STATIC"_s), JSC::jsNumber(static_cast<int>(YGPositionTypeStatic)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "POSITION_TYPE_RELATIVE"_s), JSC::jsNumber(static_cast<int>(YGPositionTypeRelative)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "POSITION_TYPE_ABSOLUTE"_s), JSC::jsNumber(static_cast<int>(YGPositionTypeAbsolute)), 0);
// Unit values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_UNDEFINED"_s), JSC::jsNumber(static_cast<int>(YGUnitUndefined)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_POINT"_s), JSC::jsNumber(static_cast<int>(YGUnitPoint)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_PERCENT"_s), JSC::jsNumber(static_cast<int>(YGUnitPercent)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_AUTO"_s), JSC::jsNumber(static_cast<int>(YGUnitAuto)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_MAX_CONTENT"_s), JSC::jsNumber(static_cast<int>(YGUnitMaxContent)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_FIT_CONTENT"_s), JSC::jsNumber(static_cast<int>(YGUnitFitContent)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "UNIT_STRETCH"_s), JSC::jsNumber(static_cast<int>(YGUnitStretch)), 0);
// Wrap values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "WRAP_NO_WRAP"_s), JSC::jsNumber(static_cast<int>(YGWrapNoWrap)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "WRAP_WRAP"_s), JSC::jsNumber(static_cast<int>(YGWrapWrap)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "WRAP_WRAP_REVERSE"_s), JSC::jsNumber(static_cast<int>(YGWrapWrapReverse)), 0);
// Errata values
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_NONE"_s), JSC::jsNumber(static_cast<int>(YGErrataNone)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_STRETCH_FLEX_BASIS"_s), JSC::jsNumber(static_cast<int>(YGErrataStretchFlexBasis)), 0);
// YGErrataAbsolutePositioningIncorrect is not available in this version of Yoga
// putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_ABSOLUTE_POSITIONING_INCORRECT"_s), JSC::jsNumber(static_cast<int>(YGErrataAbsolutePositioningIncorrect)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE"_s), JSC::jsNumber(static_cast<int>(YGErrataAbsolutePercentAgainstInnerSize)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_ALL"_s), JSC::jsNumber(static_cast<int>(YGErrataAll)), 0);
putDirectWithoutTransition(vm, JSC::Identifier::fromString(vm, "ERRATA_CLASSIC"_s), JSC::jsNumber(static_cast<int>(YGErrataClassic)), 0);
}
} // namespace Bun

View File

@@ -0,0 +1,41 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/JSObject.h>
namespace Bun {
class JSYogaConstants final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSYogaConstants* create(JSC::VM& vm, JSC::Structure* structure)
{
JSYogaConstants* constants = new (NotNull, allocateCell<JSYogaConstants>(vm)) JSYogaConstants(vm, structure);
constants->finishCreation(vm);
return constants;
}
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.plainObjectSpace();
}
private:
JSYogaConstants(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM&);
};
} // namespace Bun

View File

@@ -0,0 +1,229 @@
#include "root.h"
#include "JSYogaConstructor.h"
#include "JSYogaConfig.h"
#include "YogaConfigImpl.h"
#include "JSYogaNode.h"
#include "JSYogaModule.h"
#include "JSYogaPrototype.h"
#include "ZigGlobalObject.h"
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/JSCInlines.h>
#include <yoga/Yoga.h>
#ifndef UNLIKELY
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#endif
namespace Bun {
// Forward declarations for constructor functions
static JSC_DECLARE_HOST_FUNCTION(constructJSYogaConfig);
static JSC_DECLARE_HOST_FUNCTION(callJSYogaConfig);
static JSC_DECLARE_HOST_FUNCTION(constructJSYogaNode);
static JSC_DECLARE_HOST_FUNCTION(callJSYogaNode);
static JSC_DECLARE_HOST_FUNCTION(createJSYogaConfig);
static JSC_DECLARE_HOST_FUNCTION(createJSYogaNode);
// Config Constructor implementation
const JSC::ClassInfo JSYogaConfigConstructor::s_info = { "Config"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaConfigConstructor) };
JSYogaConfigConstructor::JSYogaConfigConstructor(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, callJSYogaConfig, constructJSYogaConfig)
{
}
void JSYogaConfigConstructor::finishCreation(JSC::VM& vm, JSC::JSObject* prototype)
{
Base::finishCreation(vm, 0, "Config"_s, PropertyAdditionMode::WithStructureTransition);
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
// Add static methods - create() is a factory function that doesn't require 'new'
putDirectNativeFunction(vm, this->globalObject(), JSC::Identifier::fromString(vm, "create"_s), 0, createJSYogaConfig, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
}
// Node Constructor implementation
const JSC::ClassInfo JSYogaNodeConstructor::s_info = { "Node"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaNodeConstructor) };
JSYogaNodeConstructor::JSYogaNodeConstructor(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, callJSYogaNode, constructJSYogaNode)
{
}
void JSYogaNodeConstructor::finishCreation(JSC::VM& vm, JSC::JSObject* prototype)
{
Base::finishCreation(vm, 1, "Node"_s, PropertyAdditionMode::WithStructureTransition); // 1 for optional config parameter
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
// Add static methods - create() is a factory function that doesn't require 'new'
putDirectNativeFunction(vm, this->globalObject(), JSC::Identifier::fromString(vm, "create"_s), 1, createJSYogaNode, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
}
// Constructor functions
JSC_DEFINE_HOST_FUNCTION(constructJSYogaConfig, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* zigGlobalObject = defaultGlobalObject(globalObject);
JSC::Structure* structure = zigGlobalObject->m_JSYogaConfigClassStructure.get(zigGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
// Handle subclassing
JSC::JSValue newTarget = callFrame->newTarget();
if (UNLIKELY(zigGlobalObject->m_JSYogaConfigClassStructure.constructor(zigGlobalObject) != newTarget)) {
if (!newTarget) {
throwTypeError(globalObject, scope, "Class constructor Config cannot be invoked without 'new'"_s);
return {};
}
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
RETURN_IF_EXCEPTION(scope, {});
structure = JSC::InternalFunction::createSubclassStructure(
globalObject, newTarget.getObject(), functionGlobalObject->m_JSYogaConfigClassStructure.get(functionGlobalObject));
RETURN_IF_EXCEPTION(scope, {});
}
RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSYogaConfig::create(vm, structure)));
}
JSC_DEFINE_HOST_FUNCTION(callJSYogaConfig, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
throwTypeError(globalObject, scope, "Class constructor Config cannot be invoked without 'new'"_s);
return {};
}
JSC_DEFINE_HOST_FUNCTION(constructJSYogaNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* zigGlobalObject = defaultGlobalObject(globalObject);
JSC::Structure* structure = zigGlobalObject->m_JSYogaNodeClassStructure.get(zigGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
// Handle subclassing
JSC::JSValue newTarget = callFrame->newTarget();
if (UNLIKELY(zigGlobalObject->m_JSYogaNodeClassStructure.constructor(zigGlobalObject) != newTarget)) {
if (!newTarget) {
throwTypeError(globalObject, scope, "Class constructor Node cannot be invoked without 'new'"_s);
return {};
}
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
RETURN_IF_EXCEPTION(scope, {});
structure = JSC::InternalFunction::createSubclassStructure(
globalObject, newTarget.getObject(), functionGlobalObject->m_JSYogaNodeClassStructure.get(functionGlobalObject));
RETURN_IF_EXCEPTION(scope, {});
}
// Optional config parameter
YGConfigRef config = nullptr;
JSYogaConfig* jsConfig = nullptr;
if (callFrame->argumentCount() > 0) {
JSC::JSValue configArg = callFrame->uncheckedArgument(0);
if (!configArg.isUndefinedOrNull()) {
jsConfig = JSC::jsDynamicCast<JSYogaConfig*>(configArg);
if (!jsConfig) {
throwTypeError(globalObject, scope, "First argument must be a Yoga.Config instance"_s);
return {};
}
config = jsConfig->impl().yogaConfig();
}
}
auto* node = JSYogaNode::create(vm, globalObject, structure, config, jsConfig);
RETURN_IF_EXCEPTION(scope, {});
return JSC::JSValue::encode(node);
}
JSC_DEFINE_HOST_FUNCTION(callJSYogaNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
throwTypeError(globalObject, scope, "Class constructor Node cannot be invoked without 'new'"_s);
return {};
}
// Factory functions that don't require 'new'
JSC_DEFINE_HOST_FUNCTION(createJSYogaConfig, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* zigGlobalObject = defaultGlobalObject(globalObject);
JSC::Structure* structure = zigGlobalObject->m_JSYogaConfigClassStructure.get(zigGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSYogaConfig::create(vm, structure)));
}
JSC_DEFINE_HOST_FUNCTION(createJSYogaNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* zigGlobalObject = defaultGlobalObject(globalObject);
JSC::Structure* structure = zigGlobalObject->m_JSYogaNodeClassStructure.get(zigGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
// Optional config parameter
YGConfigRef config = nullptr;
JSYogaConfig* jsConfig = nullptr;
if (callFrame->argumentCount() > 0) {
JSC::JSValue configArg = callFrame->uncheckedArgument(0);
if (!configArg.isUndefinedOrNull()) {
jsConfig = JSC::jsDynamicCast<JSYogaConfig*>(configArg);
if (!jsConfig) {
throwTypeError(globalObject, scope, "First argument must be a Yoga.Config instance"_s);
return {};
}
config = jsConfig->impl().yogaConfig();
}
}
auto* node = JSYogaNode::create(vm, globalObject, structure, config, jsConfig);
RETURN_IF_EXCEPTION(scope, {});
return JSC::JSValue::encode(node);
}
// Setup functions for lazy initialization
void setupJSYogaConfigClassStructure(JSC::LazyClassStructure::Initializer& init)
{
auto* prototypeStructure = JSYogaConfigPrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
auto* prototype = JSYogaConfigPrototype::create(init.vm, init.global, prototypeStructure);
auto* constructorStructure = JSYogaConfigConstructor::createStructure(init.vm, init.global, init.global->functionPrototype());
auto* constructor = JSYogaConfigConstructor::create(init.vm, constructorStructure, prototype);
auto* structure = JSYogaConfig::createStructure(init.vm, init.global, prototype);
init.setPrototype(prototype);
init.setStructure(structure);
init.setConstructor(constructor);
}
void setupJSYogaNodeClassStructure(JSC::LazyClassStructure::Initializer& init)
{
auto* prototypeStructure = JSYogaNodePrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
auto* prototype = JSYogaNodePrototype::create(init.vm, init.global, prototypeStructure);
auto* constructorStructure = JSYogaNodeConstructor::createStructure(init.vm, init.global, init.global->functionPrototype());
auto* constructor = JSYogaNodeConstructor::create(init.vm, constructorStructure, prototype);
auto* structure = JSYogaNode::createStructure(init.vm, init.global, prototype);
init.setPrototype(prototype);
init.setStructure(structure);
init.setConstructor(constructor);
}
void setupJSYogaModuleClassStructure(JSC::LazyClassStructure::Initializer& init)
{
auto* structure = JSYogaModule::createStructure(init.vm, init.global, init.global->objectPrototype());
init.setStructure(structure);
}
} // namespace Bun

View File

@@ -0,0 +1,72 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/InternalFunction.h>
namespace Bun {
class JSYogaConfigConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSYogaConfigConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype)
{
JSYogaConfigConstructor* constructor = new (NotNull, JSC::allocateCell<JSYogaConfigConstructor>(vm)) JSYogaConfigConstructor(vm, structure);
constructor->finishCreation(vm, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.internalFunctionSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
private:
JSYogaConfigConstructor(JSC::VM& vm, JSC::Structure* structure);
void finishCreation(JSC::VM& vm, JSC::JSObject* prototype);
};
class JSYogaNodeConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSYogaNodeConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype)
{
JSYogaNodeConstructor* constructor = new (NotNull, JSC::allocateCell<JSYogaNodeConstructor>(vm)) JSYogaNodeConstructor(vm, structure);
constructor->finishCreation(vm, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.internalFunctionSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
private:
JSYogaNodeConstructor(JSC::VM& vm, JSC::Structure* structure);
void finishCreation(JSC::VM& vm, JSC::JSObject* prototype);
};
// Helper functions to set up class structures
void setupJSYogaConfigClassStructure(JSC::LazyClassStructure::Initializer&);
void setupJSYogaNodeClassStructure(JSC::LazyClassStructure::Initializer&);
void setupJSYogaModuleClassStructure(JSC::LazyClassStructure::Initializer&);
} // namespace Bun

View File

@@ -0,0 +1,19 @@
#include "root.h"
#include "JSYogaConstructor.h"
#include "ZigGlobalObject.h"
using namespace JSC;
extern "C" {
JSC::EncodedJSValue Bun__JSYogaConfigConstructor(Zig::GlobalObject* globalObject)
{
return JSValue::encode(globalObject->m_JSYogaConfigClassStructure.constructor(globalObject));
}
JSC::EncodedJSValue Bun__JSYogaNodeConstructor(Zig::GlobalObject* globalObject)
{
return JSValue::encode(globalObject->m_JSYogaNodeClassStructure.constructor(globalObject));
}
} // extern "C"

View File

@@ -0,0 +1,177 @@
#include "root.h"
#include "JSYogaModule.h"
#include "JSYogaConstructor.h"
#include "JSYogaPrototype.h"
#include <yoga/Yoga.h>
#include "ZigGlobalObject.h"
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/FunctionPrototype.h>
namespace Bun {
static const HashTableValue JSYogaModuleTableValues[] = {
// Align values
{ "ALIGN_AUTO"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignAuto) } },
{ "ALIGN_FLEX_START"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignFlexStart) } },
{ "ALIGN_CENTER"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignCenter) } },
{ "ALIGN_FLEX_END"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignFlexEnd) } },
{ "ALIGN_STRETCH"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignStretch) } },
{ "ALIGN_BASELINE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignBaseline) } },
{ "ALIGN_SPACE_BETWEEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignSpaceBetween) } },
{ "ALIGN_SPACE_AROUND"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignSpaceAround) } },
{ "ALIGN_SPACE_EVENLY"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGAlignSpaceEvenly) } },
// Box sizing values
{ "BOX_SIZING_BORDER_BOX"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGBoxSizingBorderBox) } },
{ "BOX_SIZING_CONTENT_BOX"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGBoxSizingContentBox) } },
// Dimension values
{ "DIMENSION_WIDTH"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDimensionWidth) } },
{ "DIMENSION_HEIGHT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDimensionHeight) } },
// Direction values
{ "DIRECTION_INHERIT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDirectionInherit) } },
{ "DIRECTION_LTR"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDirectionLTR) } },
{ "DIRECTION_RTL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDirectionRTL) } },
// Display values
{ "DISPLAY_FLEX"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDisplayFlex) } },
{ "DISPLAY_NONE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDisplayNone) } },
{ "DISPLAY_CONTENTS"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGDisplayContents) } },
// Edge values
{ "EDGE_LEFT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeLeft) } },
{ "EDGE_TOP"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeTop) } },
{ "EDGE_RIGHT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeRight) } },
{ "EDGE_BOTTOM"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeBottom) } },
{ "EDGE_START"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeStart) } },
{ "EDGE_END"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeEnd) } },
{ "EDGE_HORIZONTAL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeHorizontal) } },
{ "EDGE_VERTICAL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeVertical) } },
{ "EDGE_ALL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGEdgeAll) } },
// Errata values
{ "ERRATA_NONE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataNone) } },
{ "ERRATA_STRETCH_FLEX_BASIS"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataStretchFlexBasis) } },
{ "ERRATA_ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataAbsolutePositionWithoutInsetsExcludesPadding) } },
{ "ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataAbsolutePercentAgainstInnerSize) } },
{ "ERRATA_ALL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataAll) } },
{ "ERRATA_CLASSIC"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGErrataClassic) } },
// Experimental feature values
{ "EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGExperimentalFeatureWebFlexBasis) } },
// Flex direction values
{ "FLEX_DIRECTION_COLUMN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGFlexDirectionColumn) } },
{ "FLEX_DIRECTION_COLUMN_REVERSE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGFlexDirectionColumnReverse) } },
{ "FLEX_DIRECTION_ROW"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGFlexDirectionRow) } },
{ "FLEX_DIRECTION_ROW_REVERSE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGFlexDirectionRowReverse) } },
// Gutter values
{ "GUTTER_COLUMN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGGutterColumn) } },
{ "GUTTER_ROW"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGGutterRow) } },
{ "GUTTER_ALL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGGutterAll) } },
// Justify values
{ "JUSTIFY_FLEX_START"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifyFlexStart) } },
{ "JUSTIFY_CENTER"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifyCenter) } },
{ "JUSTIFY_FLEX_END"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifyFlexEnd) } },
{ "JUSTIFY_SPACE_BETWEEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifySpaceBetween) } },
{ "JUSTIFY_SPACE_AROUND"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifySpaceAround) } },
{ "JUSTIFY_SPACE_EVENLY"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGJustifySpaceEvenly) } },
// Log level values
{ "LOG_LEVEL_ERROR"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelError) } },
{ "LOG_LEVEL_WARN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelWarn) } },
{ "LOG_LEVEL_INFO"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelInfo) } },
{ "LOG_LEVEL_DEBUG"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelDebug) } },
{ "LOG_LEVEL_VERBOSE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelVerbose) } },
{ "LOG_LEVEL_FATAL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGLogLevelFatal) } },
// Measure mode values
{ "MEASURE_MODE_UNDEFINED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGMeasureModeUndefined) } },
{ "MEASURE_MODE_EXACTLY"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGMeasureModeExactly) } },
{ "MEASURE_MODE_AT_MOST"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGMeasureModeAtMost) } },
// Node type values
{ "NODE_TYPE_DEFAULT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGNodeTypeDefault) } },
{ "NODE_TYPE_TEXT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGNodeTypeText) } },
// Overflow values
{ "OVERFLOW_VISIBLE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGOverflowVisible) } },
{ "OVERFLOW_HIDDEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGOverflowHidden) } },
{ "OVERFLOW_SCROLL"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGOverflowScroll) } },
// Position type values
{ "POSITION_TYPE_STATIC"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGPositionTypeStatic) } },
{ "POSITION_TYPE_RELATIVE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGPositionTypeRelative) } },
{ "POSITION_TYPE_ABSOLUTE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGPositionTypeAbsolute) } },
// Unit values
{ "UNIT_UNDEFINED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitUndefined) } },
{ "UNIT_POINT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitPoint) } },
{ "UNIT_PERCENT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitPercent) } },
{ "UNIT_AUTO"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitAuto) } },
{ "UNIT_MAX_CONTENT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitMaxContent) } },
{ "UNIT_FIT_CONTENT"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitFitContent) } },
{ "UNIT_STRETCH"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGUnitStretch) } },
// Wrap values
{ "WRAP_NO_WRAP"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGWrapNoWrap) } },
{ "WRAP_WRAP"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGWrapWrap) } },
{ "WRAP_WRAP_REVERSE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, static_cast<int>(YGWrapWrapReverse) } },
};
const JSC::ClassInfo JSYogaModule::s_info = { "Yoga"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaModule) };
JSYogaModule* JSYogaModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSYogaModule* module = new (NotNull, allocateCell<JSYogaModule>(vm)) JSYogaModule(vm, structure);
module->finishCreation(vm, globalObject);
return module;
}
void JSYogaModule::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
Base::finishCreation(vm);
// Create Config constructor and prototype
auto* configPrototype = JSYogaConfigPrototype::create(vm, globalObject,
JSYogaConfigPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
auto* configConstructor = JSYogaConfigConstructor::create(vm,
JSYogaConfigConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()),
configPrototype);
// Set constructor property on prototype
configPrototype->setConstructor(vm, configConstructor);
// Create Node constructor and prototype
auto* nodePrototype = JSYogaNodePrototype::create(vm, globalObject,
JSYogaNodePrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
auto* nodeConstructor = JSYogaNodeConstructor::create(vm,
JSYogaNodeConstructor::createStructure(vm, globalObject, globalObject->functionPrototype()),
nodePrototype);
// Set constructor property on prototype
nodePrototype->setConstructor(vm, nodeConstructor);
// Add constructors to module
putDirect(vm, JSC::Identifier::fromString(vm, "Config"_s), configConstructor, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
putDirect(vm, JSC::Identifier::fromString(vm, "Node"_s), nodeConstructor, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
// Add all Yoga constants via static hash table
reifyStaticProperties(vm, JSYogaModule::info(), JSYogaModuleTableValues, *this);
}
// Export function for Zig integration
extern "C" JSC::EncodedJSValue Bun__createYogaModule(Zig::GlobalObject* globalObject)
{
JSC::VM& vm = globalObject->vm();
auto* structure = globalObject->JSYogaModuleStructure();
auto* module = JSYogaModule::create(vm, globalObject, structure);
return JSC::JSValue::encode(module);
}
} // namespace Bun

View File

@@ -0,0 +1,36 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/JSObject.h>
namespace Bun {
class JSYogaModule final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSYogaModule* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*);
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.plainObjectSpace();
}
private:
JSYogaModule(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
};
} // namespace Bun

View File

@@ -0,0 +1,201 @@
#include "root.h"
#include "JSYogaNode.h"
#include "YogaNodeImpl.h"
#include "JSYogaConfig.h"
#include "JSYogaNodeOwner.h"
#include "webcore/DOMIsoSubspaces.h"
#include "webcore/DOMClientIsoSubspaces.h"
#include "webcore/WebCoreJSClientData.h"
#include <yoga/Yoga.h>
namespace Bun {
using namespace JSC;
const JSC::ClassInfo JSYogaNode::s_info = { "Node"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSYogaNode) };
JSYogaNode::JSYogaNode(JSC::VM& vm, JSC::Structure* structure, YGConfigRef config)
: Base(vm, structure)
, m_impl(YogaNodeImpl::create(config))
{
}
JSYogaNode::JSYogaNode(JSC::VM& vm, JSC::Structure* structure, Ref<YogaNodeImpl>&& impl)
: Base(vm, structure)
, m_impl(std::move(impl))
{
}
JSYogaNode::~JSYogaNode()
{
// The WeakHandleOwner::finalize should handle cleanup
// Don't interfere with that mechanism
}
JSYogaNode* JSYogaNode::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, YGConfigRef config, JSYogaConfig* jsConfig)
{
auto scope = DECLARE_THROW_SCOPE(vm);
JSYogaNode* node = new (NotNull, JSC::allocateCell<JSYogaNode>(vm)) JSYogaNode(vm, structure, config);
node->finishCreation(vm, jsConfig);
// Initialize children array - this can throw so it must be done here
// where exceptions can be properly propagated to callers
JSC::JSArray* children = JSC::constructEmptyArray(globalObject, nullptr, 0);
RETURN_IF_EXCEPTION(scope, nullptr);
node->m_children.set(vm, node, children);
return node;
}
JSYogaNode* JSYogaNode::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, Ref<YogaNodeImpl>&& impl)
{
auto scope = DECLARE_THROW_SCOPE(vm);
JSYogaNode* node = new (NotNull, JSC::allocateCell<JSYogaNode>(vm)) JSYogaNode(vm, structure, std::move(impl));
node->finishCreation(vm);
// Initialize children array - this can throw so it must be done here
// where exceptions can be properly propagated to callers
JSC::JSArray* children = JSC::constructEmptyArray(globalObject, nullptr, 0);
RETURN_IF_EXCEPTION(scope, nullptr);
node->m_children.set(vm, node, children);
return node;
}
void JSYogaNode::finishCreation(JSC::VM& vm, JSYogaConfig* jsConfig)
{
Base::finishCreation(vm);
// Set this JS wrapper in the C++ impl
m_impl->setJSWrapper(this);
// Store the JSYogaConfig if provided
if (jsConfig) {
m_config.set(vm, this, jsConfig);
}
// Note: m_children is initialized by create() after finishCreation returns,
// with proper exception scope handling. Do not initialize it here.
}
void JSYogaNode::finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);
// Set this JS wrapper in the C++ impl
m_impl->setJSWrapper(this);
// No JSYogaConfig in this path - it's only set when explicitly provided
// Note: m_children is initialized by create() after finishCreation returns,
// with proper exception scope handling. Do not initialize it here.
}
JSC::Structure* JSYogaNode::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
void JSYogaNode::destroy(JSC::JSCell* cell)
{
auto* thisObject = static_cast<JSYogaNode*>(cell);
// Explicitly free the YGNode here because the ref-counting chain
// (destroy() deref + finalize() deref -> ~YogaNodeImpl -> YGNodeFinalize)
// may not complete during VM shutdown if WeakHandleOwner::finalize()
// doesn't fire for all handles. This ensures the native Yoga memory
// is always freed when the JSYogaNode cell is swept.
auto& impl = thisObject->m_impl.get();
YGNodeRef node = impl.yogaNode();
if (node && impl.ownsNode()) {
// Use YGNodeFinalize (raw delete) instead of YGNodeFree (tree-traversing)
// because GC can sweep parent/child nodes in arbitrary order.
YGNodeFinalize(node);
impl.replaceYogaNode(nullptr); // Prevent double-free in ~YogaNodeImpl
}
thisObject->~JSYogaNode();
}
JSYogaNode* JSYogaNode::fromYGNode(YGNodeRef nodeRef)
{
if (!nodeRef) return nullptr;
if (auto* impl = YogaNodeImpl::fromYGNode(nodeRef)) {
return impl->jsWrapper();
}
return nullptr;
}
JSC::JSGlobalObject* JSYogaNode::globalObject() const
{
return this->structure()->globalObject();
}
template<typename MyClassT, JSC::SubspaceAccess mode>
JSC::GCClient::IsoSubspace* JSYogaNode::subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSYogaNode.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSYogaNode = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSYogaNode.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSYogaNode = std::forward<decltype(space)>(space); });
}
template<typename Visitor>
void JSYogaNode::visitChildrenImpl(JSC::JSCell* cell, Visitor& visitor)
{
JSYogaNode* thisObject = jsCast<JSYogaNode*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_measureFunc);
visitor.append(thisObject->m_dirtiedFunc);
visitor.append(thisObject->m_baselineFunc);
visitor.append(thisObject->m_config);
visitor.append(thisObject->m_children);
}
DEFINE_VISIT_CHILDREN(JSYogaNode);
template<typename Visitor>
void JSYogaNode::visitAdditionalChildren(Visitor& visitor)
{
visitor.append(m_measureFunc);
visitor.append(m_dirtiedFunc);
visitor.append(m_baselineFunc);
visitor.append(m_config);
visitor.append(m_children);
// Use the YogaNodeImpl pointer as opaque root instead of YGNodeRef
// This avoids use-after-free when YGNode memory is freed but YogaNodeImpl still exists
visitor.addOpaqueRoot(&m_impl.get());
}
DEFINE_VISIT_ADDITIONAL_CHILDREN(JSYogaNode);
template<typename Visitor>
void JSYogaNode::visitOutputConstraints(JSC::JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<JSYogaNode*>(cell);
// Lock for concurrent GC thread safety - the mutator thread may be modifying
// WriteBarriers (m_children, m_measureFunc, etc.) concurrently via insertChild,
// removeChild, setMeasureFunc, free(), etc. Without this lock, the GC thread
// can read a torn/partially-written pointer from a WriteBarrier, leading to
// a segfault in validateCell when it tries to decode a corrupted StructureID.
WTF::Locker locker { thisObject->cellLock() };
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitOutputConstraints(thisObject, visitor);
// Re-visit after mutator execution in case callbacks changed references
// This is critical for objects whose reachability can change during runtime
thisObject->visitAdditionalChildren(visitor);
}
template void JSYogaNode::visitOutputConstraints(JSC::JSCell*, JSC::AbstractSlotVisitor&);
template void JSYogaNode::visitOutputConstraints(JSC::JSCell*, JSC::SlotVisitor&);
} // namespace Bun

View File

@@ -0,0 +1,66 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/JSDestructibleObject.h>
#include <JavaScriptCore/WriteBarrier.h>
#include <wtf/Ref.h>
// Forward declarations
typedef struct YGNode* YGNodeRef;
typedef struct YGConfig* YGConfigRef;
typedef const struct YGNode* YGNodeConstRef;
namespace Bun {
class JSYogaConfig;
class YogaNodeImpl;
class JSYogaNode final : public JSC::JSDestructibleObject {
public:
using Base = JSC::JSDestructibleObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static constexpr JSC::DestructionMode needsDestruction = JSC::NeedsDestruction;
static JSYogaNode* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, YGConfigRef config = nullptr, JSYogaConfig* jsConfig = nullptr);
static JSYogaNode* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, Ref<YogaNodeImpl>&&);
static void destroy(JSC::JSCell*);
static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue);
~JSYogaNode();
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM&);
DECLARE_INFO;
DECLARE_VISIT_CHILDREN;
template<typename Visitor> void visitAdditionalChildren(Visitor&);
template<typename Visitor> static void visitOutputConstraints(JSC::JSCell*, Visitor&);
YogaNodeImpl& impl() { return m_impl.get(); }
const YogaNodeImpl& impl() const { return m_impl.get(); }
// Helper to get JS wrapper from Yoga node
static JSYogaNode* fromYGNode(YGNodeRef);
JSC::JSGlobalObject* globalObject() const;
// Storage for JS callbacks
JSC::WriteBarrier<JSC::JSObject> m_measureFunc;
JSC::WriteBarrier<JSC::JSObject> m_dirtiedFunc;
JSC::WriteBarrier<JSC::JSObject> m_baselineFunc;
// Store the JSYogaConfig that was used to create this node
JSC::WriteBarrier<JSC::JSObject> m_config;
// Store children to prevent GC while still part of Yoga tree
// This mirrors React Native's _reactSubviews NSMutableArray pattern
JSC::WriteBarrier<JSC::JSArray> m_children;
private:
JSYogaNode(JSC::VM&, JSC::Structure*, YGConfigRef config = nullptr);
JSYogaNode(JSC::VM&, JSC::Structure*, Ref<YogaNodeImpl>&&);
void finishCreation(JSC::VM&, JSYogaConfig* jsConfig);
void finishCreation(JSC::VM&);
Ref<YogaNodeImpl> m_impl;
};
} // namespace Bun

View File

@@ -0,0 +1,77 @@
#include "JSYogaNodeOwner.h"
#include "YogaNodeImpl.h"
#include "JSYogaNode.h"
#include <JavaScriptCore/JSCInlines.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Compiler.h>
#include <yoga/Yoga.h>
namespace Bun {
void* root(YogaNodeImpl* impl)
{
if (!impl)
return nullptr;
YGNodeRef current = impl->yogaNode();
YGNodeRef root = current;
// Traverse up to find the root node
while (current) {
YGNodeRef parent = YGNodeGetParent(current);
if (!parent)
break;
root = parent;
current = parent;
}
return root;
}
void JSYogaNodeOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
{
// This is where we deref the C++ YogaNodeImpl wrapper
// The context contains our YogaNodeImpl
auto* impl = static_cast<YogaNodeImpl*>(context);
// TODO: YGNodeFree during concurrent GC causes heap-use-after-free crashes
// because YGNodeFree assumes parent/child nodes are still valid, but GC can
// free them in arbitrary order. We need a solution that either:
// 1. Defers YGNodeFree to run outside GC (e.g., via a cleanup queue)
// 2. Implements reference counting at the Yoga level
// 3. Uses a different lifecycle that mirrors React Native's manual memory management
//
// For now, skip YGNodeFree during GC to prevent crashes at the cost of memory leaks.
// This matches what React Native would do if their dealloc was never called.
// YGNodeRef node = impl->yogaNode();
// if (node) {
// YGNodeFree(node);
// }
// Deref the YogaNodeImpl - this will decrease its reference count
// and potentially destroy it if no other references exist
impl->deref();
}
bool JSYogaNodeOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void* context, JSC::AbstractSlotVisitor& visitor, ASCIILiteral* reason)
{
UNUSED_PARAM(handle);
auto* impl = static_cast<YogaNodeImpl*>(context);
// Standard WebKit pattern: check if reachable as opaque root
bool reachable = visitor.containsOpaqueRoot(impl);
if (reachable && reason)
*reason = "YogaNode reachable from opaque root"_s;
return reachable;
}
JSYogaNodeOwner& jsYogaNodeOwner()
{
static NeverDestroyed<JSYogaNodeOwner> owner;
return owner.get();
}
} // namespace Bun

View File

@@ -0,0 +1,23 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/WeakHandleOwner.h>
#include <JavaScriptCore/Weak.h>
namespace Bun {
class YogaNodeImpl;
class JSYogaNode;
class JSYogaNodeOwner : public JSC::WeakHandleOwner {
public:
void finalize(JSC::Handle<JSC::Unknown>, void* context) final;
bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, ASCIILiteral*) final;
};
JSYogaNodeOwner& jsYogaNodeOwner();
// Helper function to get root for YogaNodeImpl
void* root(YogaNodeImpl*);
} // namespace Bun

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
#pragma once
#include "root.h"
#include <JavaScriptCore/JSObject.h>
namespace Bun {
// Base class for Yoga prototypes
class JSYogaConfigPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSYogaConfigPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSYogaConfigPrototype* prototype = new (NotNull, allocateCell<JSYogaConfigPrototype>(vm)) JSYogaConfigPrototype(vm, structure);
prototype->finishCreation(vm, globalObject);
return prototype;
}
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.plainObjectSpace();
}
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
structure->setMayBePrototype(true);
return structure;
}
private:
JSYogaConfigPrototype(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
public:
void setConstructor(JSC::VM& vm, JSC::JSObject* constructor);
};
class JSYogaNodePrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSYogaNodePrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSYogaNodePrototype* prototype = new (NotNull, allocateCell<JSYogaNodePrototype>(vm)) JSYogaNodePrototype(vm, structure);
prototype->finishCreation(vm, globalObject);
return prototype;
}
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.plainObjectSpace();
}
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
structure->setMayBePrototype(true);
return structure;
}
private:
JSYogaNodePrototype(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
public:
void setConstructor(JSC::VM& vm, JSC::JSObject* constructor);
};
} // namespace Bun

View File

@@ -0,0 +1,75 @@
#include "YogaConfigImpl.h"
#include "JSYogaConfig.h"
#include "JSYogaConfigOwner.h"
#include <yoga/Yoga.h>
namespace Bun {
Ref<YogaConfigImpl> YogaConfigImpl::create()
{
return adoptRef(*new YogaConfigImpl());
}
YogaConfigImpl::YogaConfigImpl()
{
m_yogaConfig = YGConfigNew();
// Store this C++ wrapper in the Yoga config's context
// Note: YGConfig doesn't have context like YGNode, so we handle this differently
}
YogaConfigImpl::~YogaConfigImpl()
{
if (m_yogaConfig) {
YGConfigFree(m_yogaConfig);
m_yogaConfig = nullptr;
}
}
void YogaConfigImpl::setJSWrapper(JSYogaConfig* wrapper)
{
// Only increment ref count if we don't already have a wrapper
// This prevents ref count leaks if setJSWrapper is called multiple times
if (!m_wrapper) {
// Increment ref count for the weak handle context
this->ref();
}
// Create weak reference with our JS owner
m_wrapper = JSC::Weak<JSYogaConfig>(wrapper, &jsYogaConfigOwner(), this);
}
void YogaConfigImpl::clearJSWrapper()
{
m_wrapper.clear();
}
void YogaConfigImpl::clearJSWrapperWithoutDeref()
{
// Clear weak reference without deref - used by JS destructor
// when WeakHandleOwner::finalize will handle the deref
m_wrapper.clear();
}
JSYogaConfig* YogaConfigImpl::jsWrapper() const
{
return m_wrapper.get();
}
YogaConfigImpl* YogaConfigImpl::fromYGConfig(YGConfigRef configRef)
{
// YGConfig doesn't have context storage like YGNode
// We'd need to maintain a separate map if needed
return nullptr;
}
void YogaConfigImpl::replaceYogaConfig(YGConfigRef newConfig)
{
m_freed = false;
if (m_yogaConfig) {
YGConfigFree(m_yogaConfig);
}
m_yogaConfig = newConfig;
}
} // namespace Bun

View File

@@ -0,0 +1,44 @@
#pragma once
#include "root.h"
#include <wtf/RefCounted.h>
#include <JavaScriptCore/Weak.h>
#include <JavaScriptCore/JSObject.h>
#include <yoga/Yoga.h>
namespace Bun {
class JSYogaConfig;
class YogaConfigImpl : public RefCounted<YogaConfigImpl> {
public:
static Ref<YogaConfigImpl> create();
~YogaConfigImpl();
YGConfigRef yogaConfig() const { return m_freed ? nullptr : m_yogaConfig; }
// JS wrapper management
void setJSWrapper(JSYogaConfig*);
void clearJSWrapper();
void clearJSWrapperWithoutDeref(); // Clear weak ref without deref (for JS destructor)
JSYogaConfig* jsWrapper() const;
// Helper to get YogaConfigImpl from YGConfigRef
static YogaConfigImpl* fromYGConfig(YGConfigRef);
// Replace the internal YGConfigRef (used for advanced cases)
void replaceYogaConfig(YGConfigRef newConfig);
// Mark as freed (for JS free() method validation)
void markAsFreed() { m_freed = true; }
bool isFreed() const { return m_freed; }
private:
explicit YogaConfigImpl();
YGConfigRef m_yogaConfig;
JSC::Weak<JSYogaConfig> m_wrapper;
bool m_freed { false };
};
} // namespace Bun

View File

@@ -0,0 +1,111 @@
#include "YogaNodeImpl.h"
#include "JSYogaNode.h"
#include "JSYogaConfig.h"
#include "JSYogaNodeOwner.h"
#include <yoga/Yoga.h>
#include <wtf/HashSet.h>
#include <wtf/Lock.h>
namespace Bun {
Ref<YogaNodeImpl> YogaNodeImpl::create(YGConfigRef config)
{
return adoptRef(*new YogaNodeImpl(config));
}
YogaNodeImpl::YogaNodeImpl(YGConfigRef config)
{
if (config) {
m_yogaNode = YGNodeNewWithConfig(config);
} else {
m_yogaNode = YGNodeNew();
}
// Store this C++ wrapper in the Yoga node's context
YGNodeSetContext(m_yogaNode, this);
}
YogaNodeImpl::~YogaNodeImpl()
{
// Free the underlying Yoga node if it hasn't been freed already.
// When the user calls .free() explicitly, replaceYogaNode(nullptr) sets
// m_yogaNode to null first, so this guard prevents double-free.
if (m_yogaNode && m_ownsNode) {
// Use YGNodeFinalize instead of YGNodeFree: it frees the node's
// memory without disconnecting it from its owner or children.
// This is safe during GC, where nodes in the same tree may be
// swept in arbitrary order and parent/child pointers may already
// be dangling.
YGNodeFinalize(m_yogaNode);
}
m_yogaNode = nullptr;
}
void YogaNodeImpl::setJSWrapper(JSYogaNode* wrapper)
{
// Only increment ref count if we don't already have a wrapper
// This prevents ref count leaks if setJSWrapper is called multiple times
if (!m_wrapper) {
// Increment ref count for the weak handle context
this->ref();
}
// Create weak reference with our JS owner
m_wrapper = JSC::Weak<JSYogaNode>(wrapper, &jsYogaNodeOwner(), this);
}
void YogaNodeImpl::clearJSWrapper()
{
m_wrapper.clear();
}
void YogaNodeImpl::clearJSWrapperWithoutDeref()
{
// Clear weak reference without deref - used by JS destructor
// when WeakHandleOwner::finalize will handle the deref
m_wrapper.clear();
}
JSYogaNode* YogaNodeImpl::jsWrapper() const
{
return m_wrapper.get();
}
JSYogaConfig* YogaNodeImpl::jsConfig() const
{
// Access config through JS wrapper's WriteBarrier - this is GC-safe
if (auto* jsWrapper = m_wrapper.get()) {
return jsCast<JSYogaConfig*>(jsWrapper->m_config.get());
}
return nullptr;
}
YogaNodeImpl* YogaNodeImpl::fromYGNode(YGNodeRef nodeRef)
{
if (!nodeRef) return nullptr;
return static_cast<YogaNodeImpl*>(YGNodeGetContext(nodeRef));
}
void YogaNodeImpl::replaceYogaNode(YGNodeRef newNode)
{
if (newNode) {
// Free the old node if we are replacing it with a different one.
// This prevents leaks when, e.g., the clone path creates a throwaway
// YGNode via create(nullptr) and immediately replaces it.
if (m_yogaNode && m_yogaNode != newNode && m_ownsNode) {
YGNodeFinalize(m_yogaNode);
}
// Update the context pointer to point to this impl.
// YGNodeClone performs a deep clone (new YGNode objects throughout),
// so there is no sharing of nodes and no previous owner to notify.
YGNodeSetContext(newNode, this);
}
// When newNode is null (called from .free() after YGNodeFree), the old
// m_yogaNode was already freed by the caller -- just clear the pointer.
m_yogaNode = newNode;
m_ownsNode = (newNode != nullptr);
}
} // namespace Bun

View File

@@ -0,0 +1,45 @@
#pragma once
#include "root.h"
#include <wtf/RefCounted.h>
#include <JavaScriptCore/Weak.h>
#include <JavaScriptCore/JSObject.h>
#include <yoga/Yoga.h>
namespace Bun {
class JSYogaNode;
class JSYogaConfig;
class YogaNodeImpl : public RefCounted<YogaNodeImpl> {
public:
static Ref<YogaNodeImpl> create(YGConfigRef config = nullptr);
~YogaNodeImpl();
YGNodeRef yogaNode() const { return m_yogaNode; }
bool ownsNode() const { return m_ownsNode; }
// JS wrapper management
void setJSWrapper(JSYogaNode*);
void clearJSWrapper();
void clearJSWrapperWithoutDeref(); // Clear weak ref without deref (for JS destructor)
JSYogaNode* jsWrapper() const;
// Config access through JS wrapper's WriteBarrier
JSYogaConfig* jsConfig() const;
// Helper to get YogaNodeImpl from YGNodeRef
static YogaNodeImpl* fromYGNode(YGNodeRef);
// Replace the internal YGNodeRef (used for cloning)
void replaceYogaNode(YGNodeRef newNode);
private:
explicit YogaNodeImpl(YGConfigRef config);
YGNodeRef m_yogaNode;
bool m_ownsNode { true };
JSC::Weak<JSYogaNode> m_wrapper;
};
} // namespace Bun

View File

@@ -194,6 +194,7 @@
#include "node/NodeTimers.h"
#include "JSConnectionsList.h"
#include "JSHTTPParser.h"
#include "JSYogaConstructor.h"
#include <exception>
#include <mutex>
#include "JSBunRequest.h"
@@ -1795,6 +1796,19 @@ void GlobalObject::finishCreation(VM& vm)
setupHTTPParserClassStructure(init);
});
m_JSYogaConfigClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
setupJSYogaConfigClassStructure(init);
});
m_JSYogaNodeClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
setupJSYogaNodeClassStructure(init);
});
m_JSYogaModuleStructure.initLater(
[](LazyClassStructure::Initializer& init) {
setupJSYogaModuleClassStructure(init);
});
m_JSNodePerformanceHooksHistogramClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
Bun::setupJSNodePerformanceHooksHistogramClassStructure(init);

View File

@@ -316,6 +316,8 @@ public:
Structure* JSSQLStatementStructure() const { return m_JSSQLStatementStructure.getInitializedOnMainThread(this); }
JSC::Structure* JSYogaModuleStructure() const { return m_JSYogaModuleStructure.getInitializedOnMainThread(this); }
v8::shim::GlobalInternals* V8GlobalInternals() const { return m_V8GlobalInternals.getInitializedOnMainThread(this); }
Bun::BakeAdditionsToGlobalObject& bakeAdditions() { return m_bakeAdditions; }
@@ -640,7 +642,11 @@ public:
V(public, LazyPropertyOfGlobalObject<Symbol>, m_nodeVMDontContextify) \
V(public, LazyPropertyOfGlobalObject<Symbol>, m_nodeVMUseMainContextDefaultLoader) \
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcSerializeFunction) \
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcParseHandleFunction)
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcParseHandleFunction) \
\
V(public, LazyClassStructure, m_JSYogaConfigClassStructure) \
V(public, LazyClassStructure, m_JSYogaNodeClassStructure) \
V(public, LazyClassStructure, m_JSYogaModuleStructure)
#define DECLARE_GLOBALOBJECT_GC_MEMBER(visibility, T, name) \
visibility: \

View File

@@ -954,5 +954,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSConnectionsList;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSHTTPParser;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSYogaConfig;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSYogaNode;
};
} // namespace WebCore

View File

@@ -957,6 +957,8 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForJSConnectionsList;
std::unique_ptr<IsoSubspace> m_subspaceForJSHTTPParser;
std::unique_ptr<IsoSubspace> m_subspaceForJSYogaConfig;
std::unique_ptr<IsoSubspace> m_subspaceForJSYogaNode;
};
} // namespace WebCore

View File

@@ -78,6 +78,9 @@
#include <JavaScriptCore/ArrayBuffer.h>
#include <JavaScriptCore/JSArrayBufferView.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/JSArrayInlines.h>
#include <JavaScriptCore/ButterflyInlines.h>
#include <JavaScriptCore/ObjectInitializationScope.h>
#include <JavaScriptCore/JSDataView.h>
#include <JavaScriptCore/JSMapInlines.h>
#include <JavaScriptCore/JSMapIterator.h>
@@ -5574,6 +5577,13 @@ SerializedScriptValue::SerializedScriptValue(WTF::FixedVector<SimpleInMemoryProp
m_memoryCost = computeMemoryCost();
}
SerializedScriptValue::SerializedScriptValue(WTF::FixedVector<SimpleCloneableValue>&& elements)
: m_simpleArrayElements(WTF::move(elements))
, m_fastPath(FastPath::SimpleArray)
{
m_memoryCost = computeMemoryCost();
}
SerializedScriptValue::SerializedScriptValue(const String& fastPathString)
: m_fastPathString(fastPathString)
, m_fastPath(FastPath::String)
@@ -5581,6 +5591,14 @@ SerializedScriptValue::SerializedScriptValue(const String& fastPathString)
m_memoryCost = computeMemoryCost();
}
SerializedScriptValue::SerializedScriptValue(Vector<uint8_t>&& butterflyData, uint32_t length, FastPath fastPath)
: m_arrayButterflyData(WTF::move(butterflyData))
, m_arrayLength(length)
, m_fastPath(fastPath)
{
m_memoryCost = computeMemoryCost();
}
size_t SerializedScriptValue::computeMemoryCost() const
{
size_t cost = m_data.size();
@@ -5652,6 +5670,19 @@ size_t SerializedScriptValue::computeMemoryCost() const
}
}
break;
case FastPath::SimpleArray:
cost += m_simpleArrayElements.byteSize();
for (const auto& elem : m_simpleArrayElements) {
std::visit(WTF::makeVisitor(
[&](JSC::JSValue) { /* already included in byteSize() */ },
[&](const String& s) { cost += s.sizeInBytes(); }),
elem);
}
break;
case FastPath::Int32Array:
case FastPath::DoubleArray:
cost += m_arrayButterflyData.size();
break;
case FastPath::None:
break;
@@ -5843,7 +5874,9 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
if (canUseFastPath) {
bool canUseStringFastPath = false;
bool canUseObjectFastPath = false;
bool canUseArrayFastPath = false;
JSObject* object = nullptr;
JSArray* array = nullptr;
Structure* structure = nullptr;
if (value.isCell()) {
auto* cell = value.asCell();
@@ -5853,7 +5886,10 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
object = cell->getObject();
structure = object->structure();
if (isObjectFastPathCandidate(structure)) {
if (auto* jsArray = jsDynamicCast<JSArray*>(object)) {
canUseArrayFastPath = true;
array = jsArray;
} else if (isObjectFastPathCandidate(structure)) {
canUseObjectFastPath = true;
}
}
@@ -5866,6 +5902,84 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
return SerializedScriptValue::createStringFastPath(stringValue);
}
if (canUseArrayFastPath) {
ASSERT(array != nullptr);
// Arrays with named properties (e.g. arr.foo = "bar") cannot use fast path
// as we only copy indexed elements. maxOffset == invalidOffset means no named properties.
if (structure->maxOffset() != invalidOffset)
canUseArrayFastPath = false;
}
if (canUseArrayFastPath) {
ASSERT(array != nullptr);
unsigned length = array->length();
auto arrayType = array->indexingType();
// Tier 1/2: Int32 / Double butterfly memcpy fast path
if ((arrayType == ArrayWithInt32 || arrayType == ArrayWithDouble)
&& length <= array->butterfly()->vectorLength()
&& !array->structure()->holesMustForwardToPrototype(array)) {
if (arrayType == ArrayWithInt32) {
auto* data = array->butterfly()->contiguous().data();
if (!containsHole(data, length)) {
size_t byteSize = sizeof(JSValue) * length;
Vector<uint8_t> buffer(byteSize, 0);
memcpy(buffer.mutableSpan().data(), data, byteSize);
return SerializedScriptValue::createInt32ArrayFastPath(WTF::move(buffer), length);
}
} else {
auto* data = array->butterfly()->contiguousDouble().data();
if (!containsHole(data, length)) {
size_t byteSize = sizeof(double) * length;
Vector<uint8_t> buffer(byteSize, 0);
memcpy(buffer.mutableSpan().data(), data, byteSize);
return SerializedScriptValue::createDoubleArrayFastPath(WTF::move(buffer), length);
}
}
// Holes present → fall through to normal path
}
// Tier 3: Contiguous array with butterfly direct access
if (arrayType == ArrayWithContiguous
&& length <= array->butterfly()->vectorLength()
&& !array->structure()->holesMustForwardToPrototype(array)) {
auto* data = array->butterfly()->contiguous().data();
WTF::Vector<SimpleCloneableValue> elements;
elements.reserveInitialCapacity(length);
bool ok = true;
for (unsigned i = 0; i < length; i++) {
JSValue elem = data[i].get();
if (!elem) {
ok = false;
break;
}
if (elem.isCell()) {
if (!elem.isString()) {
ok = false;
break;
}
auto* str = asString(elem);
String strValue = str->value(&lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
elements.append(Bun::toCrossThreadShareable(strValue));
} else {
elements.append(elem);
}
}
if (ok) {
return SerializedScriptValue::createArrayFastPath(
WTF::FixedVector<SimpleCloneableValue>(WTF::move(elements)));
}
}
// ArrayStorage / Undecided / holes forwarding → fall through to normal serialization path
}
if (canUseObjectFastPath) {
ASSERT(object != nullptr);
@@ -6142,6 +6256,21 @@ Ref<SerializedScriptValue> SerializedScriptValue::createObjectFastPath(WTF::Fixe
return adoptRef(*new SerializedScriptValue(WTF::move(object)));
}
Ref<SerializedScriptValue> SerializedScriptValue::createArrayFastPath(WTF::FixedVector<SimpleCloneableValue>&& elements)
{
return adoptRef(*new SerializedScriptValue(WTF::move(elements)));
}
Ref<SerializedScriptValue> SerializedScriptValue::createInt32ArrayFastPath(Vector<uint8_t>&& data, uint32_t length)
{
return adoptRef(*new SerializedScriptValue(WTF::move(data), length, FastPath::Int32Array));
}
Ref<SerializedScriptValue> SerializedScriptValue::createDoubleArrayFastPath(Vector<uint8_t>&& data, uint32_t length)
{
return adoptRef(*new SerializedScriptValue(WTF::move(data), length, FastPath::DoubleArray));
}
RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSContextRef originContext, JSValueRef apiValue, JSValueRef* exception)
{
JSGlobalObject* lexicalGlobalObject = toJS(originContext);
@@ -6288,6 +6417,78 @@ JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject,
return object;
}
case FastPath::SimpleArray: {
unsigned length = m_simpleArrayElements.size();
// Pre-convert all elements to JSValues (including creating JSStrings)
// before entering ObjectInitializationScope, since jsString() allocates
// GC cells which is not allowed inside the initialization scope.
MarkedArgumentBuffer values;
values.ensureCapacity(length);
for (unsigned i = 0; i < length; i++) {
JSValue elemValue = std::visit(
WTF::makeVisitor(
[](JSValue v) -> JSValue { return v; },
[&](const String& s) -> JSValue { return jsString(vm, s); }),
m_simpleArrayElements[i]);
values.append(elemValue);
}
Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous);
ObjectInitializationScope initScope(vm);
JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(initScope, resultStructure, length);
if (!resultArray) [[unlikely]] {
if (didFail)
*didFail = true;
return {};
}
for (unsigned i = 0; i < length; i++)
resultArray->initializeIndex(initScope, i, values.at(i));
if (didFail)
*didFail = false;
return resultArray;
}
case FastPath::Int32Array:
case FastPath::DoubleArray: {
IndexingType arrayType = (m_fastPath == FastPath::Int32Array) ? ArrayWithInt32 : ArrayWithDouble;
Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(arrayType);
if (hasAnyArrayStorage(resultStructure->indexingType())) [[unlikely]]
break; // isHavingABadTime → fall through to normal deserialization
unsigned outOfLineStorage = resultStructure->outOfLineCapacity();
unsigned vectorLength = Butterfly::optimalContiguousVectorLength(resultStructure, m_arrayLength);
void* memory = vm.auxiliarySpace().allocate(
vm,
Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)),
nullptr, AllocationFailureMode::ReturnNull);
if (!memory) [[unlikely]] {
if (didFail)
*didFail = true;
return {};
}
Butterfly* butterfly = Butterfly::fromBase(memory, 0, outOfLineStorage);
butterfly->setVectorLength(vectorLength);
butterfly->setPublicLength(m_arrayLength);
if (m_fastPath == FastPath::DoubleArray)
memcpy(butterfly->contiguousDouble().data(), m_arrayButterflyData.span().data(), m_arrayButterflyData.size());
else
memcpy(butterfly->contiguous().data(), m_arrayButterflyData.span().data(), m_arrayButterflyData.size());
// Clear unused tail slots with hole values
Butterfly::clearRange(arrayType, butterfly, m_arrayLength, vectorLength);
JSArray* resultArray = JSArray::createWithButterfly(vm, nullptr, resultStructure, butterfly);
if (didFail)
*didFail = false;
return resultArray;
}
case FastPath::None: {
break;
}

View File

@@ -60,15 +60,12 @@ class MemoryHandle;
namespace WebCore {
// Shared value type for fast path cloning: primitives (JSValue) or strings.
using SimpleCloneableValue = std::variant<JSC::JSValue, WTF::String>;
class SimpleInMemoryPropertyTableEntry {
public:
// Only:
// - String
// - Number
// - Boolean
// - Null
// - Undefined
using Value = std::variant<JSC::JSValue, WTF::String>;
using Value = SimpleCloneableValue;
WTF::String propertyName;
Value value;
@@ -78,6 +75,9 @@ enum class FastPath : uint8_t {
None,
String,
SimpleObject,
SimpleArray,
Int32Array,
DoubleArray,
};
#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS)
@@ -129,6 +129,13 @@ public:
// Fast path for postMessage with simple objects
static Ref<SerializedScriptValue> createObjectFastPath(WTF::FixedVector<SimpleInMemoryPropertyTableEntry>&& object);
// Fast path for postMessage with dense arrays of primitives/strings
static Ref<SerializedScriptValue> createArrayFastPath(WTF::FixedVector<SimpleCloneableValue>&& elements);
// Fast path for postMessage with dense Int32/Double arrays (butterfly memcpy)
static Ref<SerializedScriptValue> createInt32ArrayFastPath(Vector<uint8_t>&& butterflyData, uint32_t length);
static Ref<SerializedScriptValue> createDoubleArrayFastPath(Vector<uint8_t>&& butterflyData, uint32_t length);
static Ref<SerializedScriptValue> nullValue();
WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr);
@@ -231,6 +238,9 @@ private:
// Constructor for string fast path
explicit SerializedScriptValue(const String& fastPathString);
explicit SerializedScriptValue(WTF::FixedVector<SimpleInMemoryPropertyTableEntry>&& object);
explicit SerializedScriptValue(WTF::FixedVector<SimpleCloneableValue>&& elements);
// Constructor for Int32Array/DoubleArray butterfly memcpy fast path
SerializedScriptValue(Vector<uint8_t>&& butterflyData, uint32_t length, FastPath fastPath);
size_t computeMemoryCost() const;
@@ -260,6 +270,13 @@ private:
size_t m_memoryCost { 0 };
FixedVector<SimpleInMemoryPropertyTableEntry> m_simpleInMemoryPropertyTable {};
// m_simpleArrayElements and m_arrayButterflyData/m_arrayLength are used exclusively:
// SimpleArray uses m_simpleArrayElements; Int32Array/DoubleArray use m_arrayButterflyData + m_arrayLength.
FixedVector<SimpleCloneableValue> m_simpleArrayElements {};
// Int32Array / DoubleArray fast path: raw butterfly data
Vector<uint8_t> m_arrayButterflyData {};
uint32_t m_arrayLength { 0 };
};
template<class Encoder>

View File

@@ -351,11 +351,13 @@ pub fn autoTick(this: *EventLoop) void {
const ctx = this.virtual_machine;
this.tickImmediateTasks(ctx);
if (comptime Environment.isPosix) {
if (comptime Environment.isWindows) {
if (this.immediate_tasks.items.len > 0) {
this.wakeup();
}
}
// On POSIX, pending immediates are handled via an immediate timeout in
// getTimeout() instead of writing to the eventfd, avoiding that overhead.
if (comptime Environment.isPosix) {
// Some tasks need to keep the event loop alive for one more tick.
@@ -438,11 +440,13 @@ pub fn autoTickActive(this: *EventLoop) void {
var ctx = this.virtual_machine;
this.tickImmediateTasks(ctx);
if (comptime Environment.isPosix) {
if (comptime Environment.isWindows) {
if (this.immediate_tasks.items.len > 0) {
this.wakeup();
}
}
// On POSIX, pending immediates are handled via an immediate timeout in
// getTimeout() instead of writing to the eventfd, avoiding that overhead.
if (comptime Environment.isPosix) {
const pending_unref = ctx.pending_unref_counter;

View File

@@ -16,6 +16,10 @@ pub const PosixLoop = extern struct {
/// Number of polls owned by Bun
active: u32 = 0,
/// Incremented atomically by wakeup(), swapped to 0 before epoll/kqueue.
/// If non-zero, the event loop will return immediately so we can skip the GC safepoint.
pending_wakeups: u32 = 0,
/// The list of ready polls
ready_polls: [1024]EventType align(16),

View File

@@ -34,7 +34,7 @@ pub const Loop = struct {
{
var epoll = std.mem.zeroes(std.os.linux.epoll_event);
epoll.events = std.os.linux.EPOLL.IN | std.os.linux.EPOLL.ERR | std.os.linux.EPOLL.HUP;
epoll.events = std.os.linux.EPOLL.IN | std.os.linux.EPOLL.ET | std.os.linux.EPOLL.ERR | std.os.linux.EPOLL.HUP;
epoll.data.ptr = @intFromPtr(&loop);
const rc = std.os.linux.epoll_ctl(loop.epoll_fd.cast(), std.os.linux.EPOLL.CTL_ADD, loop.waker.getFd().cast(), &epoll);
@@ -165,9 +165,8 @@ pub const Loop = struct {
const pollable: Pollable = Pollable.from(event.data.u64);
if (pollable.tag() == .empty) {
if (event.data.ptr == @intFromPtr(&loop)) {
// this is the event poll, lets read it
var bytes: [8]u8 = undefined;
_ = bun.sys.read(loop.fd(), &bytes);
// Edge-triggered: no need to read the eventfd counter
continue;
}
}
_ = Poll.onUpdateEpoll(pollable.poll(), pollable.tag(), event);

View File

@@ -664,28 +664,6 @@ export function toJSON(this: BufferExt) {
return { type, data };
}
export function slice(this: BufferExt, start, end) {
var { buffer, byteOffset, byteLength } = this;
function adjustOffset(offset, length) {
// Use Math.trunc() to convert offset to an integer value that can be larger
// than an Int32. Hence, don't use offset | 0 or similar techniques.
offset = Math.trunc(offset);
if (offset === 0 || offset !== offset) {
return 0;
} else if (offset < 0) {
offset += length;
return offset > 0 ? offset : 0;
} else {
return offset < length ? offset : length;
}
}
var start_ = adjustOffset(start, byteLength);
var end_ = end !== undefined ? adjustOffset(end, byteLength) : byteLength;
return new $Buffer(buffer, byteOffset + start_, end_ > start_ ? end_ - start_ : 0);
}
$getter;
export function parent(this: BufferExt) {
return $isObject(this) && this instanceof $Buffer ? this.buffer : undefined;

View File

@@ -19,7 +19,6 @@ expectAssignable<Bun.Build.CompileTarget>("bun-windows-x64-modern");
Bun.build({
entrypoints: ["hey"],
splitting: false,
// @ts-expect-error Currently not supported
compile: {},
});

View File

@@ -0,0 +1,94 @@
// Regression test for kqueue filter comparison bug (macOS).
//
// On kqueue, EVFILT_READ (-1) and EVFILT_WRITE (-2) are negative integers. The old
// code used bitwise AND to identify filters:
//
// events |= (filter & EVFILT_READ) ? READABLE : 0
// events |= (filter & EVFILT_WRITE) ? WRITABLE : 0
//
// Since all negative numbers AND'd with -1 or -2 produce truthy values, EVERY kqueue
// event was misidentified as BOTH readable AND writable. This caused the drain handler
// to fire spuriously on every readable event and vice versa.
//
// The fix uses equality comparison (filter == EVFILT_READ), plus coalescing duplicate
// kevents for the same fd (kqueue returns separate events per filter) into a single
// dispatch with combined flags — matching epoll's single-entry-per-fd behavior.
//
// This test creates unix socket connections with small buffers to force partial writes
// (which registers EVFILT_WRITE). The client sends pings on each data callback, causing
// EVFILT_READ events on the server. With the bug, each EVFILT_READ also triggers drain,
// giving a drain/data ratio of ~2.0. With the fix, the ratio is ~1.0.
//
// Example output:
// system bun (bug): data: 38970 drain: 77940 ratio: 2.0
// fixed bun: data: 52965 drain: 52965 ratio: 1.0
import { setSocketOptions } from "bun:internal-for-testing";
const CHUNK = Buffer.alloc(64 * 1024, "x");
const PING = Buffer.from("p");
const sockPath = `kqueue-bench-${process.pid}.sock`;
let drainCalls = 0;
let dataCalls = 0;
const server = Bun.listen({
unix: sockPath,
socket: {
open(socket) {
setSocketOptions(socket, 1, 512);
setSocketOptions(socket, 2, 512);
socket.write(CHUNK);
},
data() {
dataCalls++;
},
drain(socket) {
drainCalls++;
socket.write(CHUNK);
},
close() {},
error() {},
},
});
const clients = [];
for (let i = 0; i < 10; i++) {
clients.push(
await Bun.connect({
unix: sockPath,
socket: {
open(socket) {
setSocketOptions(socket, 1, 512);
setSocketOptions(socket, 2, 512);
},
data(socket) {
socket.write(PING);
},
drain() {},
close() {},
error() {},
},
}),
);
}
await Bun.sleep(50);
drainCalls = 0;
dataCalls = 0;
await Bun.sleep(100);
const ratio = dataCalls > 0 ? drainCalls / dataCalls : 0;
console.log(`data: ${dataCalls} drain: ${drainCalls} ratio: ${ratio.toFixed(1)}`);
for (const c of clients) c.end();
server.stop(true);
try {
require("fs").unlinkSync(sockPath);
} catch {}
if (dataCalls === 0 || drainCalls === 0) {
console.error("test invalid: no data or drain callbacks fired");
process.exit(1);
}
process.exit(ratio < 1.5 ? 0 : 1);

View File

@@ -339,6 +339,10 @@ describe.concurrent("socket", () => {
expect([fileURLToPath(new URL("./socket-huge-fixture.js", import.meta.url))]).toRun();
}, 60_000);
it.skipIf(isWindows)("kqueue should not dispatch spurious drain events on readable", async () => {
expect([fileURLToPath(new URL("./kqueue-filter-coalesce-fixture.ts", import.meta.url))]).toRun();
});
it("it should not crash when getting a ReferenceError on client socket open", async () => {
using server = Bun.serve({
port: 0,

View File

@@ -68,6 +68,6 @@ describe("static initializers", () => {
expect(
bunInitializers.length,
`Do not add static initializers to Bun. Static initializers are called when Bun starts up, regardless of whether you use the variables or not. This makes Bun slower.`,
).toBe(process.arch === "arm64" ? 2 : 3);
).toBe(process.arch === "arm64" ? 1 : 2);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,125 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("align_baseline_parent_using_child_in_column_as_reference", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
root.setWidth(1000);
root.setHeight(1000);
root.setAlignItems(Yoga.ALIGN_BASELINE);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
root_child0.setWidth(500);
root_child0.setHeight(600);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
root_child1.setWidth(500);
root_child1.setHeight(800);
root.insertChild(root_child1, 1);
const root_child1_child0 = Yoga.Node.create(config);
root_child1_child0.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
root_child1_child0.setWidth(500);
root_child1_child0.setHeight(300);
root_child1.insertChild(root_child1_child0, 0);
const root_child1_child1 = Yoga.Node.create(config);
root_child1_child1.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
root_child1_child1.setWidth(500);
root_child1_child1.setHeight(400);
root_child1_child1.setIsReferenceBaseline(true);
root_child1.insertChild(root_child1_child1, 1);
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(500);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1_child0.getComputedLeft()).toBe(0);
expect(root_child1_child0.getComputedTop()).toBe(0);
expect(root_child1_child1.getComputedLeft()).toBe(0);
expect(root_child1_child1.getComputedTop()).toBe(300);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("align_baseline_parent_using_child_in_row_as_reference", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
root.setWidth(1000);
root.setHeight(1000);
root.setAlignItems(Yoga.ALIGN_BASELINE);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
root_child0.setWidth(500);
root_child0.setHeight(600);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
root_child1.setWidth(500);
root_child1.setHeight(800);
root.insertChild(root_child1, 1);
const root_child1_child0 = Yoga.Node.create(config);
root_child1_child0.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
root_child1_child0.setWidth(500);
root_child1_child0.setHeight(500);
root_child1.insertChild(root_child1_child0, 0);
const root_child1_child1 = Yoga.Node.create(config);
root_child1_child1.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
root_child1_child1.setWidth(500);
root_child1_child1.setHeight(400);
root_child1_child1.setIsReferenceBaseline(true);
root_child1.insertChild(root_child1_child1, 1);
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child1.getComputedLeft()).toBe(500);
expect(root_child1.getComputedTop()).toBe(200);
expect(root_child1_child0.getComputedLeft()).toBe(0);
expect(root_child1_child0.getComputedTop()).toBe(0);
expect(root_child1_child1.getComputedLeft()).toBe(500);
expect(root_child1_child1.getComputedTop()).toBe(0);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,437 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<1badc9ed5a0cb8d9a4a1b23653cfbe58>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAlignSelfTest.html
*/
test("align_self_center", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setAlignSelf(Align.Center);
root_child0.setWidth(10);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(45);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(45);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("align_self_flex_end", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setAlignSelf(Align.FlexEnd);
root_child0.setWidth(10);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(90);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("align_self_flex_start", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setAlignSelf(Align.FlexStart);
root_child0.setWidth(10);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(90);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("align_self_flex_end_override_flex_start", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setAlignItems(Align.FlexStart);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setAlignSelf(Align.FlexEnd);
root_child0.setWidth(10);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(90);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("align_self_baseline", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(FlexDirection.Row);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setAlignSelf(Align.Baseline);
root_child0.setWidth(50);
root_child0.setHeight(50);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setAlignSelf(Align.Baseline);
root_child1.setWidth(50);
root_child1.setHeight(20);
root.insertChild(root_child1, 1);
const root_child1_child0 = Yoga.Node.create(config);
root_child1_child0.setWidth(50);
root_child1_child0.setHeight(10);
root_child1.insertChild(root_child1_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(50);
expect(root_child1.getComputedTop()).toBe(40);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(20);
expect(root_child1_child0.getComputedLeft()).toBe(0);
expect(root_child1_child0.getComputedTop()).toBe(0);
expect(root_child1_child0.getComputedWidth()).toBe(50);
expect(root_child1_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(50);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(40);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(20);
expect(root_child1_child0.getComputedLeft()).toBe(0);
expect(root_child1_child0.getComputedTop()).toBe(0);
expect(root_child1_child0.getComputedWidth()).toBe(50);
expect(root_child1_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

View File

@@ -0,0 +1,459 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<e79ac212057a324d3ce2808face5a71c>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAndroidNewsFeed.html
*/
test("android_news_feed", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setAlignContent(Align.Stretch);
root.setPositionType(PositionType.Absolute);
root.setWidth(1080);
const root_child0 = Yoga.Node.create(config);
root.insertChild(root_child0, 0);
const root_child0_child0 = Yoga.Node.create(config);
root_child0_child0.setAlignContent(Align.Stretch);
root_child0.insertChild(root_child0_child0, 0);
const root_child0_child0_child0 = Yoga.Node.create(config);
root_child0_child0_child0.setAlignContent(Align.Stretch);
root_child0_child0.insertChild(root_child0_child0_child0, 0);
const root_child0_child0_child0_child0 = Yoga.Node.create(config);
root_child0_child0_child0_child0.setFlexDirection(FlexDirection.Row);
root_child0_child0_child0_child0.setAlignContent(Align.Stretch);
root_child0_child0_child0_child0.setAlignItems(Align.FlexStart);
root_child0_child0_child0_child0.setMargin(Edge.Start, 36);
root_child0_child0_child0_child0.setMargin(Edge.Top, 24);
root_child0_child0_child0.insertChild(root_child0_child0_child0_child0, 0);
const root_child0_child0_child0_child0_child0 = Yoga.Node.create(config);
root_child0_child0_child0_child0_child0.setFlexDirection(FlexDirection.Row);
root_child0_child0_child0_child0_child0.setAlignContent(Align.Stretch);
root_child0_child0_child0_child0.insertChild(root_child0_child0_child0_child0_child0, 0);
const root_child0_child0_child0_child0_child0_child0 = Yoga.Node.create(config);
root_child0_child0_child0_child0_child0_child0.setAlignContent(Align.Stretch);
root_child0_child0_child0_child0_child0_child0.setWidth(120);
root_child0_child0_child0_child0_child0_child0.setHeight(120);
root_child0_child0_child0_child0_child0.insertChild(root_child0_child0_child0_child0_child0_child0, 0);
const root_child0_child0_child0_child0_child1 = Yoga.Node.create(config);
root_child0_child0_child0_child0_child1.setAlignContent(Align.Stretch);
root_child0_child0_child0_child0_child1.setFlexShrink(1);
root_child0_child0_child0_child0_child1.setMargin(Edge.Right, 36);
root_child0_child0_child0_child0_child1.setPadding(Edge.Left, 36);
root_child0_child0_child0_child0_child1.setPadding(Edge.Top, 21);
root_child0_child0_child0_child0_child1.setPadding(Edge.Right, 36);
root_child0_child0_child0_child0_child1.setPadding(Edge.Bottom, 18);
root_child0_child0_child0_child0.insertChild(root_child0_child0_child0_child0_child1, 1);
const root_child0_child0_child0_child0_child1_child0 = Yoga.Node.create(config);
root_child0_child0_child0_child0_child1_child0.setFlexDirection(FlexDirection.Row);
root_child0_child0_child0_child0_child1_child0.setAlignContent(Align.Stretch);
root_child0_child0_child0_child0_child1_child0.setFlexShrink(1);
root_child0_child0_child0_child0_child1.insertChild(root_child0_child0_child0_child0_child1_child0, 0);
const root_child0_child0_child0_child0_child1_child1 = Yoga.Node.create(config);
root_child0_child0_child0_child0_child1_child1.setAlignContent(Align.Stretch);
root_child0_child0_child0_child0_child1_child1.setFlexShrink(1);
root_child0_child0_child0_child0_child1.insertChild(root_child0_child0_child0_child0_child1_child1, 1);
const root_child0_child0_child1 = Yoga.Node.create(config);
root_child0_child0_child1.setAlignContent(Align.Stretch);
root_child0_child0.insertChild(root_child0_child0_child1, 1);
const root_child0_child0_child1_child0 = Yoga.Node.create(config);
root_child0_child0_child1_child0.setFlexDirection(FlexDirection.Row);
root_child0_child0_child1_child0.setAlignContent(Align.Stretch);
root_child0_child0_child1_child0.setAlignItems(Align.FlexStart);
root_child0_child0_child1_child0.setMargin(Edge.Start, 174);
root_child0_child0_child1_child0.setMargin(Edge.Top, 24);
root_child0_child0_child1.insertChild(root_child0_child0_child1_child0, 0);
const root_child0_child0_child1_child0_child0 = Yoga.Node.create(config);
root_child0_child0_child1_child0_child0.setFlexDirection(FlexDirection.Row);
root_child0_child0_child1_child0_child0.setAlignContent(Align.Stretch);
root_child0_child0_child1_child0.insertChild(root_child0_child0_child1_child0_child0, 0);
const root_child0_child0_child1_child0_child0_child0 = Yoga.Node.create(config);
root_child0_child0_child1_child0_child0_child0.setAlignContent(Align.Stretch);
root_child0_child0_child1_child0_child0_child0.setWidth(72);
root_child0_child0_child1_child0_child0_child0.setHeight(72);
root_child0_child0_child1_child0_child0.insertChild(root_child0_child0_child1_child0_child0_child0, 0);
const root_child0_child0_child1_child0_child1 = Yoga.Node.create(config);
root_child0_child0_child1_child0_child1.setAlignContent(Align.Stretch);
root_child0_child0_child1_child0_child1.setFlexShrink(1);
root_child0_child0_child1_child0_child1.setMargin(Edge.Right, 36);
root_child0_child0_child1_child0_child1.setPadding(Edge.Left, 36);
root_child0_child0_child1_child0_child1.setPadding(Edge.Top, 21);
root_child0_child0_child1_child0_child1.setPadding(Edge.Right, 36);
root_child0_child0_child1_child0_child1.setPadding(Edge.Bottom, 18);
root_child0_child0_child1_child0.insertChild(root_child0_child0_child1_child0_child1, 1);
const root_child0_child0_child1_child0_child1_child0 = Yoga.Node.create(config);
root_child0_child0_child1_child0_child1_child0.setFlexDirection(FlexDirection.Row);
root_child0_child0_child1_child0_child1_child0.setAlignContent(Align.Stretch);
root_child0_child0_child1_child0_child1_child0.setFlexShrink(1);
root_child0_child0_child1_child0_child1.insertChild(root_child0_child0_child1_child0_child1_child0, 0);
const root_child0_child0_child1_child0_child1_child1 = Yoga.Node.create(config);
root_child0_child0_child1_child0_child1_child1.setAlignContent(Align.Stretch);
root_child0_child0_child1_child0_child1_child1.setFlexShrink(1);
root_child0_child0_child1_child0_child1.insertChild(root_child0_child0_child1_child0_child1_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(1080);
expect(root.getComputedHeight()).toBe(240);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(1080);
expect(root_child0.getComputedHeight()).toBe(240);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(1080);
expect(root_child0_child0.getComputedHeight()).toBe(240);
expect(root_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child0.getComputedWidth()).toBe(1080);
expect(root_child0_child0_child0.getComputedHeight()).toBe(144);
expect(root_child0_child0_child0_child0.getComputedLeft()).toBe(36);
expect(root_child0_child0_child0_child0.getComputedTop()).toBe(24);
expect(root_child0_child0_child0_child0.getComputedWidth()).toBe(1044);
expect(root_child0_child0_child0_child0.getComputedHeight()).toBe(120);
expect(root_child0_child0_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child0_child0_child0.getComputedWidth()).toBe(120);
expect(root_child0_child0_child0_child0_child0.getComputedHeight()).toBe(120);
expect(root_child0_child0_child0_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child0_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child0_child0_child0_child0.getComputedWidth()).toBe(120);
expect(root_child0_child0_child0_child0_child0_child0.getComputedHeight()).toBe(120);
expect(root_child0_child0_child0_child0_child1.getComputedLeft()).toBe(120);
expect(root_child0_child0_child0_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child0_child0_child0_child1.getComputedWidth()).toBe(72);
expect(root_child0_child0_child0_child0_child1.getComputedHeight()).toBe(39);
expect(root_child0_child0_child0_child0_child1_child0.getComputedLeft()).toBe(36);
expect(root_child0_child0_child0_child0_child1_child0.getComputedTop()).toBe(21);
expect(root_child0_child0_child0_child0_child1_child0.getComputedWidth()).toBe(0);
expect(root_child0_child0_child0_child0_child1_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0_child0_child0_child1_child1.getComputedLeft()).toBe(36);
expect(root_child0_child0_child0_child0_child1_child1.getComputedTop()).toBe(21);
expect(root_child0_child0_child0_child0_child1_child1.getComputedWidth()).toBe(0);
expect(root_child0_child0_child0_child0_child1_child1.getComputedHeight()).toBe(0);
expect(root_child0_child0_child1.getComputedLeft()).toBe(0);
expect(root_child0_child0_child1.getComputedTop()).toBe(144);
expect(root_child0_child0_child1.getComputedWidth()).toBe(1080);
expect(root_child0_child0_child1.getComputedHeight()).toBe(96);
expect(root_child0_child0_child1_child0.getComputedLeft()).toBe(174);
expect(root_child0_child0_child1_child0.getComputedTop()).toBe(24);
expect(root_child0_child0_child1_child0.getComputedWidth()).toBe(906);
expect(root_child0_child0_child1_child0.getComputedHeight()).toBe(72);
expect(root_child0_child0_child1_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child1_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child1_child0_child0.getComputedWidth()).toBe(72);
expect(root_child0_child0_child1_child0_child0.getComputedHeight()).toBe(72);
expect(root_child0_child0_child1_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child1_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child1_child0_child0_child0.getComputedWidth()).toBe(72);
expect(root_child0_child0_child1_child0_child0_child0.getComputedHeight()).toBe(72);
expect(root_child0_child0_child1_child0_child1.getComputedLeft()).toBe(72);
expect(root_child0_child0_child1_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child0_child1_child0_child1.getComputedWidth()).toBe(72);
expect(root_child0_child0_child1_child0_child1.getComputedHeight()).toBe(39);
expect(root_child0_child0_child1_child0_child1_child0.getComputedLeft()).toBe(36);
expect(root_child0_child0_child1_child0_child1_child0.getComputedTop()).toBe(21);
expect(root_child0_child0_child1_child0_child1_child0.getComputedWidth()).toBe(0);
expect(root_child0_child0_child1_child0_child1_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0_child1_child0_child1_child1.getComputedLeft()).toBe(36);
expect(root_child0_child0_child1_child0_child1_child1.getComputedTop()).toBe(21);
expect(root_child0_child0_child1_child0_child1_child1.getComputedWidth()).toBe(0);
expect(root_child0_child0_child1_child0_child1_child1.getComputedHeight()).toBe(0);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(1080);
expect(root.getComputedHeight()).toBe(240);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(1080);
expect(root_child0.getComputedHeight()).toBe(240);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(1080);
expect(root_child0_child0.getComputedHeight()).toBe(240);
expect(root_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child0.getComputedWidth()).toBe(1080);
expect(root_child0_child0_child0.getComputedHeight()).toBe(144);
expect(root_child0_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child0_child0.getComputedTop()).toBe(24);
expect(root_child0_child0_child0_child0.getComputedWidth()).toBe(1044);
expect(root_child0_child0_child0_child0.getComputedHeight()).toBe(120);
expect(root_child0_child0_child0_child0_child0.getComputedLeft()).toBe(924);
expect(root_child0_child0_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child0_child0_child0.getComputedWidth()).toBe(120);
expect(root_child0_child0_child0_child0_child0.getComputedHeight()).toBe(120);
expect(root_child0_child0_child0_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child0_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child0_child0_child0_child0.getComputedWidth()).toBe(120);
expect(root_child0_child0_child0_child0_child0_child0.getComputedHeight()).toBe(120);
expect(root_child0_child0_child0_child0_child1.getComputedLeft()).toBe(816);
expect(root_child0_child0_child0_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child0_child0_child0_child1.getComputedWidth()).toBe(72);
expect(root_child0_child0_child0_child0_child1.getComputedHeight()).toBe(39);
expect(root_child0_child0_child0_child0_child1_child0.getComputedLeft()).toBe(36);
expect(root_child0_child0_child0_child0_child1_child0.getComputedTop()).toBe(21);
expect(root_child0_child0_child0_child0_child1_child0.getComputedWidth()).toBe(0);
expect(root_child0_child0_child0_child0_child1_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0_child0_child0_child1_child1.getComputedLeft()).toBe(36);
expect(root_child0_child0_child0_child0_child1_child1.getComputedTop()).toBe(21);
expect(root_child0_child0_child0_child0_child1_child1.getComputedWidth()).toBe(0);
expect(root_child0_child0_child0_child0_child1_child1.getComputedHeight()).toBe(0);
expect(root_child0_child0_child1.getComputedLeft()).toBe(0);
expect(root_child0_child0_child1.getComputedTop()).toBe(144);
expect(root_child0_child0_child1.getComputedWidth()).toBe(1080);
expect(root_child0_child0_child1.getComputedHeight()).toBe(96);
expect(root_child0_child0_child1_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child1_child0.getComputedTop()).toBe(24);
expect(root_child0_child0_child1_child0.getComputedWidth()).toBe(906);
expect(root_child0_child0_child1_child0.getComputedHeight()).toBe(72);
expect(root_child0_child0_child1_child0_child0.getComputedLeft()).toBe(834);
expect(root_child0_child0_child1_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child1_child0_child0.getComputedWidth()).toBe(72);
expect(root_child0_child0_child1_child0_child0.getComputedHeight()).toBe(72);
expect(root_child0_child0_child1_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child1_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child1_child0_child0_child0.getComputedWidth()).toBe(72);
expect(root_child0_child0_child1_child0_child0_child0.getComputedHeight()).toBe(72);
expect(root_child0_child0_child1_child0_child1.getComputedLeft()).toBe(726);
expect(root_child0_child0_child1_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child0_child1_child0_child1.getComputedWidth()).toBe(72);
expect(root_child0_child0_child1_child0_child1.getComputedHeight()).toBe(39);
expect(root_child0_child0_child1_child0_child1_child0.getComputedLeft()).toBe(36);
expect(root_child0_child0_child1_child0_child1_child0.getComputedTop()).toBe(21);
expect(root_child0_child0_child1_child0_child1_child0.getComputedWidth()).toBe(0);
expect(root_child0_child0_child1_child0_child1_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0_child1_child0_child1_child1.getComputedLeft()).toBe(36);
expect(root_child0_child0_child1_child0_child1_child1.getComputedTop()).toBe(21);
expect(root_child0_child0_child1_child0_child1_child1.getComputedWidth()).toBe(0);
expect(root_child0_child0_child1_child0_child1_child1.getComputedHeight()).toBe(0);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

View File

@@ -0,0 +1,375 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<57064c3213fc22e0637ae5768ce6fea5>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAspectRatioTest.html
*/
test.skip("aspect_ratio_does_not_stretch_cross_axis_dim", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(300);
root.setHeight(300);
const root_child0 = Yoga.Node.create(config);
root_child0.setOverflow(Overflow.Scroll);
root_child0.setFlexGrow(1);
root_child0.setFlexShrink(1);
root_child0.setFlexBasis("0%");
root.insertChild(root_child0, 0);
const root_child0_child0 = Yoga.Node.create(config);
root_child0_child0.setFlexDirection(FlexDirection.Row);
root_child0.insertChild(root_child0_child0, 0);
const root_child0_child0_child0 = Yoga.Node.create(config);
root_child0_child0_child0.setFlexGrow(2);
root_child0_child0_child0.setFlexShrink(1);
root_child0_child0_child0.setFlexBasis("0%");
root_child0_child0_child0.setAspectRatio(1 / 1);
root_child0_child0.insertChild(root_child0_child0_child0, 0);
const root_child0_child0_child1 = Yoga.Node.create(config);
root_child0_child0_child1.setWidth(5);
root_child0_child0.insertChild(root_child0_child0_child1, 1);
const root_child0_child0_child2 = Yoga.Node.create(config);
root_child0_child0_child2.setFlexGrow(1);
root_child0_child0_child2.setFlexShrink(1);
root_child0_child0_child2.setFlexBasis("0%");
root_child0_child0.insertChild(root_child0_child0_child2, 2);
const root_child0_child0_child2_child0 = Yoga.Node.create(config);
root_child0_child0_child2_child0.setFlexGrow(1);
root_child0_child0_child2_child0.setFlexShrink(1);
root_child0_child0_child2_child0.setFlexBasis("0%");
root_child0_child0_child2_child0.setAspectRatio(1 / 1);
root_child0_child0_child2.insertChild(root_child0_child0_child2_child0, 0);
const root_child0_child0_child2_child0_child0 = Yoga.Node.create(config);
root_child0_child0_child2_child0_child0.setWidth(5);
root_child0_child0_child2_child0.insertChild(root_child0_child0_child2_child0_child0, 0);
const root_child0_child0_child2_child0_child1 = Yoga.Node.create(config);
root_child0_child0_child2_child0_child1.setFlexGrow(1);
root_child0_child0_child2_child0_child1.setFlexShrink(1);
root_child0_child0_child2_child0_child1.setFlexBasis("0%");
root_child0_child0_child2_child0_child1.setAspectRatio(1 / 1);
root_child0_child0_child2_child0.insertChild(root_child0_child0_child2_child0_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(300);
expect(root.getComputedHeight()).toBe(300);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(300);
expect(root_child0.getComputedHeight()).toBe(300);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(300);
expect(root_child0_child0.getComputedHeight()).toBe(197);
expect(root_child0_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child0.getComputedWidth()).toBe(197);
expect(root_child0_child0_child0.getComputedHeight()).toBe(197);
expect(root_child0_child0_child1.getComputedLeft()).toBe(197);
expect(root_child0_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child0_child1.getComputedWidth()).toBe(5);
expect(root_child0_child0_child1.getComputedHeight()).toBe(197);
expect(root_child0_child0_child2.getComputedLeft()).toBe(202);
expect(root_child0_child0_child2.getComputedTop()).toBe(0);
expect(root_child0_child0_child2.getComputedWidth()).toBe(98);
expect(root_child0_child0_child2.getComputedHeight()).toBe(197);
expect(root_child0_child0_child2_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child2_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child2_child0.getComputedWidth()).toBe(98);
expect(root_child0_child0_child2_child0.getComputedHeight()).toBe(197);
expect(root_child0_child0_child2_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child2_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child2_child0_child0.getComputedWidth()).toBe(5);
expect(root_child0_child0_child2_child0_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0_child2_child0_child1.getComputedLeft()).toBe(0);
expect(root_child0_child0_child2_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child0_child2_child0_child1.getComputedWidth()).toBe(98);
expect(root_child0_child0_child2_child0_child1.getComputedHeight()).toBe(197);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(300);
expect(root.getComputedHeight()).toBe(300);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(300);
expect(root_child0.getComputedHeight()).toBe(300);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(300);
expect(root_child0_child0.getComputedHeight()).toBe(197);
expect(root_child0_child0_child0.getComputedLeft()).toBe(103);
expect(root_child0_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child0.getComputedWidth()).toBe(197);
expect(root_child0_child0_child0.getComputedHeight()).toBe(197);
expect(root_child0_child0_child1.getComputedLeft()).toBe(98);
expect(root_child0_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child0_child1.getComputedWidth()).toBe(5);
expect(root_child0_child0_child1.getComputedHeight()).toBe(197);
expect(root_child0_child0_child2.getComputedLeft()).toBe(0);
expect(root_child0_child0_child2.getComputedTop()).toBe(0);
expect(root_child0_child0_child2.getComputedWidth()).toBe(98);
expect(root_child0_child0_child2.getComputedHeight()).toBe(197);
expect(root_child0_child0_child2_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0_child2_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child2_child0.getComputedWidth()).toBe(98);
expect(root_child0_child0_child2_child0.getComputedHeight()).toBe(197);
expect(root_child0_child0_child2_child0_child0.getComputedLeft()).toBe(93);
expect(root_child0_child0_child2_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0_child2_child0_child0.getComputedWidth()).toBe(5);
expect(root_child0_child0_child2_child0_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0_child2_child0_child1.getComputedLeft()).toBe(0);
expect(root_child0_child0_child2_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child0_child2_child0_child1.getComputedWidth()).toBe(98);
expect(root_child0_child0_child2_child0_child1.getComputedHeight()).toBe(197);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("zero_aspect_ratio_behaves_like_auto", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(300);
root.setHeight(300);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(50);
root_child0.setAspectRatio(0 / 1);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(300);
expect(root.getComputedHeight()).toBe(300);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(0);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(300);
expect(root.getComputedHeight()).toBe(300);
expect(root_child0.getComputedLeft()).toBe(250);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(0);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

View File

@@ -0,0 +1,491 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<234cdb7f76ac586e034a5b6ca2033fa4>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGAutoTest.html
*/
test("auto_width", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(FlexDirection.Row);
root.setPositionType(PositionType.Absolute);
root.setWidth("auto");
root.setHeight(50);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(50);
root_child0.setHeight(50);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setWidth(50);
root_child1.setHeight(50);
root.insertChild(root_child1, 1);
const root_child2 = Yoga.Node.create(config);
root_child2.setWidth(50);
root_child2.setHeight(50);
root.insertChild(root_child2, 2);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(150);
expect(root.getComputedHeight()).toBe(50);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(50);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(50);
expect(root_child2.getComputedLeft()).toBe(100);
expect(root_child2.getComputedTop()).toBe(0);
expect(root_child2.getComputedWidth()).toBe(50);
expect(root_child2.getComputedHeight()).toBe(50);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(150);
expect(root.getComputedHeight()).toBe(50);
expect(root_child0.getComputedLeft()).toBe(100);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(50);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(50);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(0);
expect(root_child2.getComputedWidth()).toBe(50);
expect(root_child2.getComputedHeight()).toBe(50);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("auto_height", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(50);
root.setHeight("auto");
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(50);
root_child0.setHeight(50);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setWidth(50);
root_child1.setHeight(50);
root.insertChild(root_child1, 1);
const root_child2 = Yoga.Node.create(config);
root_child2.setWidth(50);
root_child2.setHeight(50);
root.insertChild(root_child2, 2);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(150);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(50);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(50);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(100);
expect(root_child2.getComputedWidth()).toBe(50);
expect(root_child2.getComputedHeight()).toBe(50);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(150);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(50);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(50);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(100);
expect(root_child2.getComputedWidth()).toBe(50);
expect(root_child2.getComputedHeight()).toBe(50);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("auto_flex_basis", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(50);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(50);
root_child0.setHeight(50);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setWidth(50);
root_child1.setHeight(50);
root.insertChild(root_child1, 1);
const root_child2 = Yoga.Node.create(config);
root_child2.setWidth(50);
root_child2.setHeight(50);
root.insertChild(root_child2, 2);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(150);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(50);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(50);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(100);
expect(root_child2.getComputedWidth()).toBe(50);
expect(root_child2.getComputedHeight()).toBe(50);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(150);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(50);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(50);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(100);
expect(root_child2.getComputedWidth()).toBe(50);
expect(root_child2.getComputedHeight()).toBe(50);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("auto_position", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(50);
root.setHeight(50);
const root_child0 = Yoga.Node.create(config);
root_child0.setPositionAuto(Edge.Right);
root_child0.setWidth(25);
root_child0.setHeight(25);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(50);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(25);
expect(root_child0.getComputedHeight()).toBe(25);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(50);
expect(root_child0.getComputedLeft()).toBe(25);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(25);
expect(root_child0.getComputedHeight()).toBe(25);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("auto_margin", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(50);
root.setHeight(50);
const root_child0 = Yoga.Node.create(config);
root_child0.setMargin(Edge.Left, "auto");
root_child0.setWidth(25);
root_child0.setHeight(25);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(50);
expect(root_child0.getComputedLeft()).toBe(25);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(25);
expect(root_child0.getComputedHeight()).toBe(25);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(50);
expect(root_child0.getComputedLeft()).toBe(25);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(25);
expect(root_child0.getComputedHeight()).toBe(25);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

View File

@@ -0,0 +1,400 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<b1aa13bd88fcb494afb2db9bd80c40e0>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGBorderTest.html
*/
test("border_no_size", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setBorder(Edge.Left, 10);
root.setBorder(Edge.Top, 10);
root.setBorder(Edge.Right, 10);
root.setBorder(Edge.Bottom, 10);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(20);
expect(root.getComputedHeight()).toBe(20);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(20);
expect(root.getComputedHeight()).toBe(20);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("border_container_match_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setBorder(Edge.Left, 10);
root.setBorder(Edge.Top, 10);
root.setBorder(Edge.Right, 10);
root.setBorder(Edge.Bottom, 10);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(10);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(30);
expect(root.getComputedHeight()).toBe(30);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(30);
expect(root.getComputedHeight()).toBe(30);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("border_flex_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setBorder(Edge.Left, 10);
root.setBorder(Edge.Top, 10);
root.setBorder(Edge.Right, 10);
root.setBorder(Edge.Bottom, 10);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexGrow(1);
root_child0.setWidth(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(80);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(80);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(80);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("border_stretch_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setBorder(Edge.Left, 10);
root.setBorder(Edge.Top, 10);
root.setBorder(Edge.Right, 10);
root.setBorder(Edge.Bottom, 10);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(80);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(80);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("border_center_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setJustifyContent(Justify.Center);
root.setAlignItems(Align.Center);
root.setPositionType(PositionType.Absolute);
root.setBorder(Edge.Start, 10);
root.setBorder(Edge.End, 20);
root.setBorder(Edge.Bottom, 20);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(10);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(40);
expect(root_child0.getComputedTop()).toBe(35);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(50);
expect(root_child0.getComputedTop()).toBe(35);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("border_start", () => {
const root = Yoga.Node.create();
try {
root.setWidth(100);
root.setHeight(100);
root.setBorder(Yoga.EDGE_START, 10);
root.calculateLayout(100, 100, Yoga.DIRECTION_LTR);
expect(root.getComputedBorder(Yoga.EDGE_LEFT)).toBe(10);
expect(root.getComputedBorder(Yoga.EDGE_RIGHT)).toBe(0);
root.calculateLayout(100, 100, Yoga.DIRECTION_RTL);
expect(root.getComputedBorder(Yoga.EDGE_LEFT)).toBe(0);
expect(root.getComputedBorder(Yoga.EDGE_RIGHT)).toBe(10);
} finally {
root.freeRecursive();
}
});

View File

@@ -0,0 +1,30 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("margin_start", () => {
const root = Yoga.Node.create();
try {
root.setWidth(100);
root.setHeight(100);
root.setMargin(Yoga.EDGE_START, `10%`);
root.calculateLayout(100, 100, Yoga.DIRECTION_LTR);
expect(root.getComputedMargin(Yoga.EDGE_LEFT)).toBe(10);
expect(root.getComputedMargin(Yoga.EDGE_RIGHT)).toBe(0);
root.calculateLayout(100, 100, Yoga.DIRECTION_RTL);
expect(root.getComputedMargin(Yoga.EDGE_LEFT)).toBe(0);
expect(root.getComputedMargin(Yoga.EDGE_RIGHT)).toBe(10);
} finally {
root.freeRecursive();
}
});

View File

@@ -0,0 +1,30 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("padding_start", () => {
const root = Yoga.Node.create();
try {
root.setWidth(100);
root.setHeight(100);
root.setPadding(Yoga.EDGE_START, `10%`);
root.calculateLayout(100, 100, Yoga.DIRECTION_LTR);
expect(root.getComputedPadding(Yoga.EDGE_LEFT)).toBe(10);
expect(root.getComputedPadding(Yoga.EDGE_RIGHT)).toBe(0);
root.calculateLayout(100, 100, Yoga.DIRECTION_RTL);
expect(root.getComputedPadding(Yoga.EDGE_LEFT)).toBe(0);
expect(root.getComputedPadding(Yoga.EDGE_RIGHT)).toBe(10);
} finally {
root.freeRecursive();
}
});

View File

@@ -0,0 +1,273 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<719c8f3b9fea672881a47f593f4aabd0>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGDimensionTest.html
*/
test("wrap_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(100);
root_child0.setHeight(100);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(100);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(100);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("wrap_grandchild", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
const root_child0 = Yoga.Node.create(config);
root.insertChild(root_child0, 0);
const root_child0_child0 = Yoga.Node.create(config);
root_child0_child0.setWidth(100);
root_child0_child0.setHeight(100);
root_child0.insertChild(root_child0_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(100);
expect(root_child0_child0.getComputedHeight()).toBe(100);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(100);
expect(root_child0_child0.getComputedHeight()).toBe(100);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

View File

@@ -0,0 +1,158 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("dirtied", () => {
const root = Yoga.Node.create();
root.setAlignItems(Yoga.ALIGN_FLEX_START);
root.setWidth(100);
root.setHeight(100);
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
let dirtied = 0;
root.setDirtiedFunc(() => {
dirtied++;
});
// only nodes with a measure function can be marked dirty
root.setMeasureFunc(() => ({ width: 0, height: 0 }));
expect(dirtied).toBe(0);
// dirtied func MUST be called in case of explicit dirtying.
root.markDirty();
expect(dirtied).toBe(1);
// dirtied func MUST be called ONCE.
root.markDirty();
expect(dirtied).toBe(1);
root.freeRecursive();
});
test("dirtied_propagation", () => {
const root = Yoga.Node.create();
root.setAlignItems(Yoga.ALIGN_FLEX_START);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create();
root_child0.setAlignItems(Yoga.ALIGN_FLEX_START);
root_child0.setWidth(50);
root_child0.setHeight(20);
root_child0.setMeasureFunc(() => ({ width: 0, height: 0 }));
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create();
root_child1.setAlignItems(Yoga.ALIGN_FLEX_START);
root_child1.setWidth(50);
root_child1.setHeight(20);
root.insertChild(root_child1, 0);
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
let dirtied = 0;
root.setDirtiedFunc(() => {
dirtied++;
});
expect(dirtied).toBe(0);
// dirtied func MUST be called for the first time.
root_child0.markDirty();
expect(dirtied).toBe(1);
// dirtied func must NOT be called for the second time.
root_child0.markDirty();
expect(dirtied).toBe(1);
root.freeRecursive();
});
test("dirtied_hierarchy", () => {
const root = Yoga.Node.create();
root.setAlignItems(Yoga.ALIGN_FLEX_START);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create();
root_child0.setAlignItems(Yoga.ALIGN_FLEX_START);
root_child0.setWidth(50);
root_child0.setHeight(20);
root_child0.setMeasureFunc(() => ({ width: 0, height: 0 }));
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create();
root_child1.setAlignItems(Yoga.ALIGN_FLEX_START);
root_child1.setWidth(50);
root_child1.setHeight(20);
root_child1.setMeasureFunc(() => ({ width: 0, height: 0 }));
root.insertChild(root_child1, 0);
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
let dirtied = 0;
root_child0.setDirtiedFunc(() => {
dirtied++;
});
expect(dirtied).toBe(0);
// dirtied func must NOT be called for descendants.
// NOTE: nodes without a measure function cannot be marked dirty manually,
// but nodes with a measure function can not have children.
// Update the width to dirty the node instead.
root.setWidth(110);
expect(dirtied).toBe(0);
// dirtied func MUST be called in case of explicit dirtying.
root_child0.markDirty();
expect(dirtied).toBe(1);
root.freeRecursive();
});
test("dirtied_reset", () => {
const root = Yoga.Node.create();
root.setAlignItems(Yoga.ALIGN_FLEX_START);
root.setWidth(100);
root.setHeight(100);
root.setMeasureFunc(() => ({ width: 0, height: 0 }));
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
let dirtied = 0;
root.setDirtiedFunc(() => {
dirtied++;
});
expect(dirtied).toBe(0);
// dirtied func MUST be called in case of explicit dirtying.
root.markDirty();
expect(dirtied).toBe(1);
// recalculate so the root is no longer dirty
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
root.reset();
root.setAlignItems(Yoga.ALIGN_FLEX_START);
root.setWidth(100);
root.setHeight(100);
root.setMeasureFunc(() => ({ width: 0, height: 0 }));
root.markDirty();
// dirtied func must NOT be called after reset.
root.markDirty();
expect(dirtied).toBe(1);
root.freeRecursive();
});

View File

@@ -0,0 +1,253 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<64d58fa2d33230f4eafc9ecbb6012a0d>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGDisplayContentsTest.html
*/
test("test1", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(FlexDirection.Row);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setDisplay(Display.Contents);
root.insertChild(root_child0, 0);
const root_child0_child0 = Yoga.Node.create(config);
root_child0_child0.setFlexGrow(1);
root_child0_child0.setFlexShrink(1);
root_child0_child0.setFlexBasis("0%");
root_child0_child0.setHeight(10);
root_child0.insertChild(root_child0_child0, 0);
const root_child0_child1 = Yoga.Node.create(config);
root_child0_child1.setFlexGrow(1);
root_child0_child1.setFlexShrink(1);
root_child0_child1.setFlexBasis("0%");
root_child0_child1.setHeight(20);
root_child0.insertChild(root_child0_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(0);
expect(root_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(50);
expect(root_child0_child0.getComputedHeight()).toBe(10);
expect(root_child0_child1.getComputedLeft()).toBe(50);
expect(root_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child1.getComputedWidth()).toBe(50);
expect(root_child0_child1.getComputedHeight()).toBe(20);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(0);
expect(root_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0.getComputedLeft()).toBe(50);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(50);
expect(root_child0_child0.getComputedHeight()).toBe(10);
expect(root_child0_child1.getComputedLeft()).toBe(0);
expect(root_child0_child1.getComputedTop()).toBe(0);
expect(root_child0_child1.getComputedWidth()).toBe(50);
expect(root_child0_child1.getComputedHeight()).toBe(20);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("errata_all_contains_example_errata", () => {
const config = Yoga.Config.create();
try {
config.setErrata(Yoga.ERRATA_ALL);
expect(config.getErrata()).toBe(Yoga.ERRATA_ALL);
expect(config.getErrata() & Yoga.ERRATA_STRETCH_FLEX_BASIS).not.toBe(0);
} finally {
config.free();
}
});
test("errata_none_omits_example_errata", () => {
const config = Yoga.Config.create();
try {
config.setErrata(Yoga.ERRATA_NONE);
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
expect(config.getErrata() & Yoga.ERRATA_STRETCH_FLEX_BASIS).toBe(0);
} finally {
config.free();
}
});
test("errata_is_settable", () => {
const config = Yoga.Config.create();
try {
config.setErrata(Yoga.ERRATA_ALL);
expect(config.getErrata()).toBe(Yoga.ERRATA_ALL);
config.setErrata(Yoga.ERRATA_NONE);
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
} finally {
config.free();
}
});

View File

@@ -0,0 +1,24 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("flex_basis_auto", () => {
const root = Yoga.Node.create();
expect(root.getFlexBasis().unit).toBe(Yoga.UNIT_AUTO);
root.setFlexBasis(10);
expect(root.getFlexBasis().unit).toBe(Yoga.UNIT_POINT);
expect(root.getFlexBasis().value).toBe(10);
root.setFlexBasisAuto();
expect(root.getFlexBasis().unit).toBe(Yoga.UNIT_AUTO);
root.freeRecursive();
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,738 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
test("flex_basis_flex_grow_column", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexGrow(1);
root_child0.setFlexBasis(50);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexGrow(1);
root.insertChild(root_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(75);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(75);
expect(root_child1.getComputedWidth()).toBe(100);
expect(root_child1.getComputedHeight()).toBe(25);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(75);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(75);
expect(root_child1.getComputedWidth()).toBe(100);
expect(root_child1.getComputedHeight()).toBe(25);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_shrink_flex_grow_row", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(FlexDirection.Row);
root.setPositionType(PositionType.Absolute);
root.setWidth(500);
root.setHeight(500);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexShrink(1);
root_child0.setWidth(500);
root_child0.setHeight(100);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexShrink(1);
root_child1.setWidth(500);
root_child1.setHeight(100);
root.insertChild(root_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(500);
expect(root.getComputedHeight()).toBe(500);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(250);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(250);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(250);
expect(root_child1.getComputedHeight()).toBe(100);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(500);
expect(root.getComputedHeight()).toBe(500);
expect(root_child0.getComputedLeft()).toBe(250);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(250);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(250);
expect(root_child1.getComputedHeight()).toBe(100);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_shrink_flex_grow_child_flex_shrink_other_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(FlexDirection.Row);
root.setPositionType(PositionType.Absolute);
root.setWidth(500);
root.setHeight(500);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexShrink(1);
root_child0.setWidth(500);
root_child0.setHeight(100);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexGrow(1);
root_child1.setFlexShrink(1);
root_child1.setWidth(500);
root_child1.setHeight(100);
root.insertChild(root_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(500);
expect(root.getComputedHeight()).toBe(500);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(250);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(250);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(250);
expect(root_child1.getComputedHeight()).toBe(100);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(500);
expect(root.getComputedHeight()).toBe(500);
expect(root_child0.getComputedLeft()).toBe(250);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(250);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(250);
expect(root_child1.getComputedHeight()).toBe(100);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_basis_flex_grow_row", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(FlexDirection.Row);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexGrow(1);
root_child0.setFlexBasis(50);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexGrow(1);
root.insertChild(root_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(75);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(75);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(25);
expect(root_child1.getComputedHeight()).toBe(100);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(25);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(75);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(25);
expect(root_child1.getComputedHeight()).toBe(100);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_basis_flex_shrink_column", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexShrink(1);
root_child0.setFlexBasis(100);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexBasis(50);
root.insertChild(root_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(50);
expect(root_child1.getComputedWidth()).toBe(100);
expect(root_child1.getComputedHeight()).toBe(50);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(50);
expect(root_child1.getComputedWidth()).toBe(100);
expect(root_child1.getComputedHeight()).toBe(50);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_basis_flex_shrink_row", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setFlexDirection(FlexDirection.Row);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexShrink(1);
root_child0.setFlexBasis(100);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexBasis(50);
root.insertChild(root_child1, 1);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(50);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(100);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(50);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(0);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(100);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_shrink_to_zero", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setHeight(75);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(50);
root_child0.setHeight(50);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexShrink(1);
root_child1.setWidth(50);
root_child1.setHeight(50);
root.insertChild(root_child1, 1);
const root_child2 = Yoga.Node.create(config);
root_child2.setWidth(50);
root_child2.setHeight(50);
root.insertChild(root_child2, 2);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(75);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(50);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(0);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(50);
expect(root_child2.getComputedWidth()).toBe(50);
expect(root_child2.getComputedHeight()).toBe(50);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(50);
expect(root.getComputedHeight()).toBe(75);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(50);
expect(root_child0.getComputedHeight()).toBe(50);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(50);
expect(root_child1.getComputedWidth()).toBe(50);
expect(root_child1.getComputedHeight()).toBe(0);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(50);
expect(root_child2.getComputedWidth()).toBe(50);
expect(root_child2.getComputedHeight()).toBe(50);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_basis_overrides_main_size", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexGrow(1);
root_child0.setFlexBasis(50);
root_child0.setHeight(20);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexGrow(1);
root_child1.setHeight(10);
root.insertChild(root_child1, 1);
const root_child2 = Yoga.Node.create(config);
root_child2.setFlexGrow(1);
root_child2.setHeight(10);
root.insertChild(root_child2, 2);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(60);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(60);
expect(root_child1.getComputedWidth()).toBe(100);
expect(root_child1.getComputedHeight()).toBe(20);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(80);
expect(root_child2.getComputedWidth()).toBe(100);
expect(root_child2.getComputedHeight()).toBe(20);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(60);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(60);
expect(root_child1.getComputedWidth()).toBe(100);
expect(root_child1.getComputedHeight()).toBe(20);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(80);
expect(root_child2.getComputedWidth()).toBe(100);
expect(root_child2.getComputedHeight()).toBe(20);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_grow_shrink_at_most", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root.insertChild(root_child0, 0);
const root_child0_child0 = Yoga.Node.create(config);
root_child0_child0.setFlexGrow(1);
root_child0_child0.setFlexShrink(1);
root_child0.insertChild(root_child0_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(100);
expect(root_child0_child0.getComputedHeight()).toBe(0);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(0);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(100);
expect(root_child0_child0.getComputedHeight()).toBe(0);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("flex_grow_less_than_factor_one", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(200);
root.setHeight(500);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexGrow(0.2);
root_child0.setFlexBasis(40);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create(config);
root_child1.setFlexGrow(0.2);
root.insertChild(root_child1, 1);
const root_child2 = Yoga.Node.create(config);
root_child2.setFlexGrow(0.4);
root.insertChild(root_child2, 2);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(200);
expect(root.getComputedHeight()).toBe(500);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(200);
expect(root_child0.getComputedHeight()).toBe(132);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(132);
expect(root_child1.getComputedWidth()).toBe(200);
expect(root_child1.getComputedHeight()).toBe(92);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(224);
expect(root_child2.getComputedWidth()).toBe(200);
expect(root_child2.getComputedHeight()).toBe(184);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(200);
expect(root.getComputedHeight()).toBe(500);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(200);
expect(root_child0.getComputedHeight()).toBe(132);
expect(root_child1.getComputedLeft()).toBe(0);
expect(root_child1.getComputedTop()).toBe(132);
expect(root_child1.getComputedWidth()).toBe(200);
expect(root_child1.getComputedHeight()).toBe(92);
expect(root_child2.getComputedLeft()).toBe(0);
expect(root_child2.getComputedTop()).toBe(224);
expect(root_child2.getComputedWidth()).toBe(200);
expect(root_child2.getComputedHeight()).toBe(184);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("new_layout_can_be_marked_seen", () => {
const root = Yoga.Node.create();
try {
root.markLayoutSeen();
expect(root.hasNewLayout()).toBe(false);
} finally {
root.freeRecursive();
}
});
test("new_layout_calculating_layout_marks_layout_as_unseen", () => {
const root = Yoga.Node.create();
try {
root.markLayoutSeen();
root.calculateLayout(undefined, undefined);
expect(root.hasNewLayout()).toBe(true);
} finally {
root.freeRecursive();
}
});
test("new_layout_calculated_layout_can_be_marked_seen", () => {
const root = Yoga.Node.create();
try {
root.calculateLayout(undefined, undefined);
root.markLayoutSeen();
expect(root.hasNewLayout()).toBe(false);
} finally {
root.freeRecursive();
}
});
test("new_layout_recalculating_layout_does_mark_as_unseen", () => {
const root = Yoga.Node.create();
try {
root.calculateLayout(undefined, undefined);
root.markLayoutSeen();
root.calculateLayout(undefined, undefined);
expect(root.hasNewLayout()).toBe(true);
} finally {
root.freeRecursive();
}
});
test("new_layout_reset_also_resets_layout_seen", () => {
const root = Yoga.Node.create();
try {
root.markLayoutSeen();
root.reset();
expect(root.hasNewLayout()).toBe(true);
} finally {
root.freeRecursive();
}
});
test("new_layout_children_sets_new_layout", () => {
const root = Yoga.Node.create();
try {
root.setAlignItems(Yoga.ALIGN_FLEX_START);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create();
root_child0.setAlignItems(Yoga.ALIGN_FLEX_START);
root_child0.setWidth(50);
root_child0.setHeight(20);
root.insertChild(root_child0, 0);
const root_child1 = Yoga.Node.create();
root_child1.setAlignItems(Yoga.ALIGN_FLEX_START);
root_child1.setWidth(50);
root_child1.setHeight(20);
root.insertChild(root_child1, 0);
expect(root.hasNewLayout()).toEqual(true);
expect(root_child0.hasNewLayout()).toEqual(true);
expect(root_child1.hasNewLayout()).toEqual(true);
root.markLayoutSeen();
root_child0.markLayoutSeen();
root_child1.markLayoutSeen();
expect(root.hasNewLayout()).toEqual(false);
expect(root_child0.hasNewLayout()).toEqual(false);
expect(root_child1.hasNewLayout()).toEqual(false);
root_child1.setHeight(30);
root.calculateLayout(undefined, undefined);
expect(root.hasNewLayout()).toEqual(true);
expect(root_child0.hasNewLayout()).toEqual(true);
expect(root_child1.hasNewLayout()).toEqual(true);
} finally {
root.freeRecursive();
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
type MeasureCounter = {
inc: (width: number, widthMode: number, height: number, heightMode: number) => { width?: number; height?: number };
get: () => number;
};
function getMeasureCounter(
cb?:
| ((width: number, widthMode: number, height: number, heightMode: number) => { width?: number; height?: number })
| null,
staticWidth = 0,
staticHeight = 0,
): MeasureCounter {
let counter = 0;
return {
inc: function (width: number, widthMode: number, height: number, heightMode: number) {
counter += 1;
return cb ? cb(width, widthMode, height, heightMode) : { width: staticWidth, height: staticHeight };
},
get: function () {
return counter;
},
};
}
function getMeasureCounterMax(): MeasureCounter {
return getMeasureCounter((width, widthMode, height, heightMode) => {
const measuredWidth = widthMode === Yoga.MEASURE_MODE_UNDEFINED ? 10 : width;
const measuredHeight = heightMode === Yoga.MEASURE_MODE_UNDEFINED ? 10 : height;
return { width: measuredWidth, height: measuredHeight };
});
}
function getMeasureCounterMin(): MeasureCounter {
return getMeasureCounter((width, widthMode, height, heightMode) => {
const measuredWidth =
widthMode === Yoga.MEASURE_MODE_UNDEFINED || (widthMode == Yoga.MEASURE_MODE_AT_MOST && width > 10) ? 10 : width;
const measuredHeight =
heightMode === Yoga.MEASURE_MODE_UNDEFINED || (heightMode == Yoga.MEASURE_MODE_AT_MOST && height > 10)
? 10
: height;
return { width: measuredWidth, height: measuredHeight };
});
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("measure_once_single_flexible_child", () => {
const root = Yoga.Node.create();
root.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
root.setAlignItems(Yoga.ALIGN_FLEX_START);
root.setWidth(100);
root.setHeight(100);
const measureCounter = getMeasureCounterMax();
const root_child0 = Yoga.Node.create();
root_child0.setMeasureFunc(measureCounter.inc);
root_child0.setFlexGrow(1);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
expect(measureCounter.get()).toBe(1);
root.freeRecursive();
});

View File

@@ -0,0 +1,207 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
type MeasureCounter = {
inc: (width: number, widthMode: number, height: number, heightMode: number) => { width?: number; height?: number };
get: () => number;
};
function getMeasureCounter(
cb?:
| ((width: number, widthMode: number, height: number, heightMode: number) => { width?: number; height?: number })
| null,
staticWidth = 0,
staticHeight = 0,
): MeasureCounter {
let counter = 0;
return {
inc: function (width: number, widthMode: number, height: number, heightMode: number) {
counter += 1;
return cb ? cb(width, widthMode, height, heightMode) : { width: staticWidth, height: staticHeight };
},
get: function () {
return counter;
},
};
}
function getMeasureCounterMax(): MeasureCounter {
return getMeasureCounter((width, widthMode, height, heightMode) => {
const measuredWidth = widthMode === Yoga.MEASURE_MODE_UNDEFINED ? 10 : width;
const measuredHeight = heightMode === Yoga.MEASURE_MODE_UNDEFINED ? 10 : height;
return { width: measuredWidth, height: measuredHeight };
});
}
function getMeasureCounterMin(): MeasureCounter {
return getMeasureCounter((width, widthMode, height, heightMode) => {
const measuredWidth =
widthMode === Yoga.MEASURE_MODE_UNDEFINED || (widthMode == Yoga.MEASURE_MODE_AT_MOST && width > 10) ? 10 : width;
const measuredHeight =
heightMode === Yoga.MEASURE_MODE_UNDEFINED || (heightMode == Yoga.MEASURE_MODE_AT_MOST && height > 10)
? 10
: height;
return { width: measuredWidth, height: measuredHeight };
});
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
test("dont_measure_single_grow_shrink_child", () => {
const root = Yoga.Node.create();
root.setWidth(100);
root.setHeight(100);
const measureCounter = getMeasureCounter(null, 100, 100);
const root_child0 = Yoga.Node.create();
root_child0.setMeasureFunc(measureCounter.inc);
root_child0.setFlexGrow(1);
root_child0.setFlexShrink(1);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
expect(measureCounter.get()).toBe(0);
root.freeRecursive();
});
test("dont_fail_with_incomplete_measure_dimensions", () => {
// @ts-expect-error Testing bad usage
const heightOnlyCallback = getMeasureCounter(() => ({ height: 10 }));
// @ts-expect-error Testing bad usage
const widthOnlyCallback = getMeasureCounter(() => ({ width: 10 }));
// @ts-expect-error Testing bad usage
const emptyCallback = getMeasureCounter(() => ({}));
const root = Yoga.Node.create();
root.setWidth(100);
root.setHeight(100);
const node1 = Yoga.Node.create();
const node2 = Yoga.Node.create();
const node3 = Yoga.Node.create();
root.insertChild(node1, root.getChildCount());
root.insertChild(node2, root.getChildCount());
root.insertChild(node3, root.getChildCount());
node1.setMeasureFunc(heightOnlyCallback.inc);
node2.setMeasureFunc(widthOnlyCallback.inc);
node3.setMeasureFunc(emptyCallback.inc);
root.calculateLayout(undefined, undefined, Yoga.DIRECTION_LTR);
expect(heightOnlyCallback.get()).toBe(1);
expect(widthOnlyCallback.get()).toBe(1);
expect(emptyCallback.get()).toBe(1);
expect(node1.getComputedWidth()).toBe(100);
expect(node1.getComputedHeight()).toBe(10);
expect(node2.getComputedWidth()).toBe(100);
expect(node2.getComputedHeight()).toBe(0);
expect(node3.getComputedWidth()).toBe(100);
expect(node3.getComputedHeight()).toBe(0);
root.freeRecursive();
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,498 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<07e65137c3055db0090d46067ec69d5e>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGPaddingTest.html
*/
test("padding_no_size", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setPadding(Edge.Left, 10);
root.setPadding(Edge.Top, 10);
root.setPadding(Edge.Right, 10);
root.setPadding(Edge.Bottom, 10);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(20);
expect(root.getComputedHeight()).toBe(20);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(20);
expect(root.getComputedHeight()).toBe(20);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("padding_container_match_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setPadding(Edge.Left, 10);
root.setPadding(Edge.Top, 10);
root.setPadding(Edge.Right, 10);
root.setPadding(Edge.Bottom, 10);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(10);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(30);
expect(root.getComputedHeight()).toBe(30);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(30);
expect(root.getComputedHeight()).toBe(30);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("padding_flex_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setPadding(Edge.Left, 10);
root.setPadding(Edge.Top, 10);
root.setPadding(Edge.Right, 10);
root.setPadding(Edge.Bottom, 10);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setFlexGrow(1);
root_child0.setWidth(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(80);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(80);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(80);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("padding_stretch_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setPadding(Edge.Left, 10);
root.setPadding(Edge.Top, 10);
root.setPadding(Edge.Right, 10);
root.setPadding(Edge.Bottom, 10);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(80);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(10);
expect(root_child0.getComputedTop()).toBe(10);
expect(root_child0.getComputedWidth()).toBe(80);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("padding_center_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setJustifyContent(Justify.Center);
root.setAlignItems(Align.Center);
root.setPositionType(PositionType.Absolute);
root.setPadding(Edge.Start, 10);
root.setPadding(Edge.End, 20);
root.setPadding(Edge.Bottom, 20);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(10);
root_child0.setHeight(10);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(40);
expect(root_child0.getComputedTop()).toBe(35);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(50);
expect(root_child0.getComputedTop()).toBe(35);
expect(root_child0.getComputedWidth()).toBe(10);
expect(root_child0.getComputedHeight()).toBe(10);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("child_with_padding_align_end", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setJustifyContent(Justify.FlexEnd);
root.setAlignItems(Align.FlexEnd);
root.setPositionType(PositionType.Absolute);
root.setWidth(200);
root.setHeight(200);
const root_child0 = Yoga.Node.create(config);
root_child0.setPadding(Edge.Left, 20);
root_child0.setPadding(Edge.Top, 20);
root_child0.setPadding(Edge.Right, 20);
root_child0.setPadding(Edge.Bottom, 20);
root_child0.setWidth(100);
root_child0.setHeight(100);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(200);
expect(root.getComputedHeight()).toBe(200);
expect(root_child0.getComputedLeft()).toBe(100);
expect(root_child0.getComputedTop()).toBe(100);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(100);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(200);
expect(root.getComputedHeight()).toBe(200);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(100);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(100);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("physical_and_relative_edge_defined", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setPadding(Edge.Left, 20);
root.setPadding(Edge.End, 50);
root.setWidth(200);
root.setHeight(200);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth("100%");
root_child0.setHeight(50);
root.insertChild(root_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(200);
expect(root.getComputedHeight()).toBe(200);
expect(root_child0.getComputedLeft()).toBe(20);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(130);
expect(root_child0.getComputedHeight()).toBe(50);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(200);
expect(root.getComputedHeight()).toBe(200);
expect(root_child0.getComputedLeft()).toBe(50);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(150);
expect(root_child0.getComputedHeight()).toBe(50);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,351 @@
import { expect, test } from "bun:test";
const Yoga = Bun.Yoga;
const Align = {
Center: Yoga.ALIGN_CENTER,
FlexEnd: Yoga.ALIGN_FLEX_END,
FlexStart: Yoga.ALIGN_FLEX_START,
Stretch: Yoga.ALIGN_STRETCH,
Baseline: Yoga.ALIGN_BASELINE,
SpaceBetween: Yoga.ALIGN_SPACE_BETWEEN,
SpaceAround: Yoga.ALIGN_SPACE_AROUND,
SpaceEvenly: Yoga.ALIGN_SPACE_EVENLY,
Auto: Yoga.ALIGN_AUTO,
};
const BoxSizing = {
BorderBox: Yoga.BOX_SIZING_BORDER_BOX,
ContentBox: Yoga.BOX_SIZING_CONTENT_BOX,
};
const Direction = {
LTR: Yoga.DIRECTION_LTR,
RTL: Yoga.DIRECTION_RTL,
Inherit: Yoga.DIRECTION_INHERIT,
};
const Display = {
Flex: Yoga.DISPLAY_FLEX,
None: Yoga.DISPLAY_NONE,
Contents: Yoga.DISPLAY_CONTENTS,
};
const Edge = {
Left: Yoga.EDGE_LEFT,
Top: Yoga.EDGE_TOP,
Right: Yoga.EDGE_RIGHT,
Bottom: Yoga.EDGE_BOTTOM,
Start: Yoga.EDGE_START,
End: Yoga.EDGE_END,
Horizontal: Yoga.EDGE_HORIZONTAL,
Vertical: Yoga.EDGE_VERTICAL,
All: Yoga.EDGE_ALL,
};
const FlexDirection = {
Column: Yoga.FLEX_DIRECTION_COLUMN,
ColumnReverse: Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
Row: Yoga.FLEX_DIRECTION_ROW,
RowReverse: Yoga.FLEX_DIRECTION_ROW_REVERSE,
};
const Justify = {
FlexStart: Yoga.JUSTIFY_FLEX_START,
Center: Yoga.JUSTIFY_CENTER,
FlexEnd: Yoga.JUSTIFY_FLEX_END,
SpaceBetween: Yoga.JUSTIFY_SPACE_BETWEEN,
SpaceAround: Yoga.JUSTIFY_SPACE_AROUND,
SpaceEvenly: Yoga.JUSTIFY_SPACE_EVENLY,
};
const MeasureMode = {
Undefined: Yoga.MEASURE_MODE_UNDEFINED,
Exactly: Yoga.MEASURE_MODE_EXACTLY,
AtMost: Yoga.MEASURE_MODE_AT_MOST,
};
const Overflow = {
Visible: Yoga.OVERFLOW_VISIBLE,
Hidden: Yoga.OVERFLOW_HIDDEN,
Scroll: Yoga.OVERFLOW_SCROLL,
};
const PositionType = {
Static: Yoga.POSITION_TYPE_STATIC,
Relative: Yoga.POSITION_TYPE_RELATIVE,
Absolute: Yoga.POSITION_TYPE_ABSOLUTE,
};
const Unit = {
Undefined: Yoga.UNIT_UNDEFINED,
Point: Yoga.UNIT_POINT,
Percent: Yoga.UNIT_PERCENT,
Auto: Yoga.UNIT_AUTO,
};
const Wrap = {
NoWrap: Yoga.WRAP_NO_WRAP,
Wrap: Yoga.WRAP_WRAP,
WrapReverse: Yoga.WRAP_WRAP_REVERSE,
};
function instrinsicSizeMeasureFunc(
this: { text: string; flexDirection: number },
width: number,
widthMode: number,
height: number,
heightMode: number,
): { width: number; height: number } {
const textLength = this.text.length;
const words = this.text.split(" ");
const flexDirection = this.flexDirection;
const widthPerChar = 10;
const heightPerChar = 10;
let measuredWidth: number;
let measuredHeight: number;
switch (widthMode) {
case MeasureMode.Exactly:
measuredWidth = width;
break;
case MeasureMode.AtMost:
measuredWidth = Math.min(width, textLength * widthPerChar);
break;
default:
measuredWidth = textLength * widthPerChar;
}
switch (heightMode) {
case MeasureMode.Exactly:
measuredHeight = height;
break;
case MeasureMode.AtMost:
measuredHeight = Math.min(height, calculateHeight());
break;
default:
measuredHeight = calculateHeight();
}
function longestWordWidth() {
return Math.max(...words.map(word => word.length)) * widthPerChar;
}
function calculateHeight() {
if (textLength * widthPerChar <= measuredWidth) {
return heightPerChar;
}
const maxLineWidth =
flexDirection == FlexDirection.Column ? measuredWidth : Math.max(longestWordWidth(), measuredWidth);
let lines = 1;
let currentLineLength = 0;
for (const word of words) {
const wordWidth = word.length * widthPerChar;
if (wordWidth > maxLineWidth) {
if (currentLineLength > 0) {
lines++;
}
lines++;
currentLineLength = 0;
} else if (currentLineLength + wordWidth <= maxLineWidth) {
currentLineLength += widthPerChar + wordWidth;
} else {
lines++;
currentLineLength = widthPerChar + wordWidth;
}
}
return (currentLineLength === 0 ? lines - 1 : lines) * heightPerChar;
}
return { width: measuredWidth, height: measuredHeight };
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<d4b41aec2c4130f06b3ee9e76d70d944>>
* generated by gentest/gentest-driver.ts from gentest/fixtures/YGSizeOverflowTest.html
*/
test("nested_overflowing_child", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root.insertChild(root_child0, 0);
const root_child0_child0 = Yoga.Node.create(config);
root_child0_child0.setWidth(200);
root_child0_child0.setHeight(200);
root_child0.insertChild(root_child0_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(200);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(200);
expect(root_child0_child0.getComputedHeight()).toBe(200);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(200);
expect(root_child0_child0.getComputedLeft()).toBe(-100);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(200);
expect(root_child0_child0.getComputedHeight()).toBe(200);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("nested_overflowing_child_in_constraint_parent", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(100);
root_child0.setHeight(100);
root.insertChild(root_child0, 0);
const root_child0_child0 = Yoga.Node.create(config);
root_child0_child0.setWidth(200);
root_child0_child0.setHeight(200);
root_child0.insertChild(root_child0_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(200);
expect(root_child0_child0.getComputedHeight()).toBe(200);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(100);
expect(root_child0_child0.getComputedLeft()).toBe(-100);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(200);
expect(root_child0_child0.getComputedHeight()).toBe(200);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});
test("parent_wrap_child_size_overflowing_parent", () => {
const config = Yoga.Config.create();
let root;
try {
root = Yoga.Node.create(config);
root.setPositionType(PositionType.Absolute);
root.setWidth(100);
root.setHeight(100);
const root_child0 = Yoga.Node.create(config);
root_child0.setWidth(100);
root.insertChild(root_child0, 0);
const root_child0_child0 = Yoga.Node.create(config);
root_child0_child0.setWidth(100);
root_child0_child0.setHeight(200);
root_child0.insertChild(root_child0_child0, 0);
root.calculateLayout(undefined, undefined, Direction.LTR);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(200);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(100);
expect(root_child0_child0.getComputedHeight()).toBe(200);
root.calculateLayout(undefined, undefined, Direction.RTL);
expect(root.getComputedLeft()).toBe(0);
expect(root.getComputedTop()).toBe(0);
expect(root.getComputedWidth()).toBe(100);
expect(root.getComputedHeight()).toBe(100);
expect(root_child0.getComputedLeft()).toBe(0);
expect(root_child0.getComputedTop()).toBe(0);
expect(root_child0.getComputedWidth()).toBe(100);
expect(root_child0.getComputedHeight()).toBe(200);
expect(root_child0_child0.getComputedLeft()).toBe(0);
expect(root_child0_child0.getComputedTop()).toBe(0);
expect(root_child0_child0.getComputedWidth()).toBe(100);
expect(root_child0_child0.getComputedHeight()).toBe(200);
} finally {
if (typeof root !== "undefined") {
root.freeRecursive();
}
config.free();
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
import { describe, expect, test } from "bun:test";
// Test if we can access Yoga via Bun.Yoga
const Yoga = Bun.Yoga;
describe("Yoga.Config", () => {
test("Config constructor", () => {
const config = new Yoga.Config();
expect(config).toBeDefined();
expect(config.constructor.name).toBe("Config");
});
test("Config.create() static method", () => {
const config = Yoga.Config.create();
expect(config).toBeDefined();
expect(config.constructor.name).toBe("Config");
});
test("setUseWebDefaults", () => {
const config = new Yoga.Config();
// Should not throw
expect(() => config.setUseWebDefaults(true)).not.toThrow();
expect(() => config.setUseWebDefaults(false)).not.toThrow();
expect(() => config.setUseWebDefaults()).not.toThrow(); // defaults to true
});
test("useWebDefaults (legacy)", () => {
const config = new Yoga.Config();
// Should not throw
expect(() => config.useWebDefaults()).not.toThrow();
});
test("setPointScaleFactor and getPointScaleFactor", () => {
const config = new Yoga.Config();
config.setPointScaleFactor(2.0);
expect(config.getPointScaleFactor()).toBe(2.0);
config.setPointScaleFactor(0); // disable pixel rounding
expect(config.getPointScaleFactor()).toBe(0);
config.setPointScaleFactor(3.5);
expect(config.getPointScaleFactor()).toBe(3.5);
});
test("setErrata and getErrata", () => {
const config = new Yoga.Config();
// Test with different errata values
config.setErrata(Yoga.ERRATA_NONE);
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
config.setErrata(Yoga.ERRATA_CLASSIC);
expect(config.getErrata()).toBe(Yoga.ERRATA_CLASSIC);
config.setErrata(Yoga.ERRATA_ALL);
expect(config.getErrata()).toBe(Yoga.ERRATA_ALL);
});
test("setExperimentalFeatureEnabled and isExperimentalFeatureEnabled", () => {
const config = new Yoga.Config();
// Test with a hypothetical experimental feature
const feature = 0; // Assuming 0 is a valid experimental feature
config.setExperimentalFeatureEnabled(feature, true);
expect(config.isExperimentalFeatureEnabled(feature)).toBe(true);
config.setExperimentalFeatureEnabled(feature, false);
expect(config.isExperimentalFeatureEnabled(feature)).toBe(false);
});
test("isEnabledForNodes", () => {
const config = new Yoga.Config();
// Should return true for a valid config
expect(config.isEnabledForNodes()).toBe(true);
});
test("free", () => {
const config = new Yoga.Config();
// Should not throw
expect(() => config.free()).not.toThrow();
// After free, double free should throw an error (this is correct behavior)
expect(() => config.free()).toThrow("Cannot perform operation on freed Yoga.Config");
});
test("error handling", () => {
const config = new Yoga.Config();
// Test invalid arguments
expect(() => config.setErrata()).toThrow();
expect(() => config.setExperimentalFeatureEnabled()).toThrow();
expect(() => config.setExperimentalFeatureEnabled(0)).toThrow(); // missing second arg
expect(() => config.isExperimentalFeatureEnabled()).toThrow();
expect(() => config.setPointScaleFactor()).toThrow();
});
});

View File

@@ -0,0 +1,119 @@
import { describe, expect, test } from "bun:test";
// Test if we can access Yoga via Bun.Yoga
const Yoga = Bun.Yoga;
describe("Yoga Constants", () => {
test("should export all alignment constants", () => {
expect(Yoga.ALIGN_AUTO).toBeDefined();
expect(Yoga.ALIGN_FLEX_START).toBeDefined();
expect(Yoga.ALIGN_CENTER).toBeDefined();
expect(Yoga.ALIGN_FLEX_END).toBeDefined();
expect(Yoga.ALIGN_STRETCH).toBeDefined();
expect(Yoga.ALIGN_BASELINE).toBeDefined();
expect(Yoga.ALIGN_SPACE_BETWEEN).toBeDefined();
expect(Yoga.ALIGN_SPACE_AROUND).toBeDefined();
expect(Yoga.ALIGN_SPACE_EVENLY).toBeDefined();
});
test("should export all direction constants", () => {
expect(Yoga.DIRECTION_INHERIT).toBeDefined();
expect(Yoga.DIRECTION_LTR).toBeDefined();
expect(Yoga.DIRECTION_RTL).toBeDefined();
});
test("should export all display constants", () => {
expect(Yoga.DISPLAY_FLEX).toBeDefined();
expect(Yoga.DISPLAY_NONE).toBeDefined();
});
test("should export all edge constants", () => {
expect(Yoga.EDGE_LEFT).toBeDefined();
expect(Yoga.EDGE_TOP).toBeDefined();
expect(Yoga.EDGE_RIGHT).toBeDefined();
expect(Yoga.EDGE_BOTTOM).toBeDefined();
expect(Yoga.EDGE_START).toBeDefined();
expect(Yoga.EDGE_END).toBeDefined();
expect(Yoga.EDGE_HORIZONTAL).toBeDefined();
expect(Yoga.EDGE_VERTICAL).toBeDefined();
expect(Yoga.EDGE_ALL).toBeDefined();
});
test("should export all experimental feature constants", () => {
expect(Yoga.EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS).toBeDefined();
});
test("should export all flex direction constants", () => {
expect(Yoga.FLEX_DIRECTION_COLUMN).toBeDefined();
expect(Yoga.FLEX_DIRECTION_COLUMN_REVERSE).toBeDefined();
expect(Yoga.FLEX_DIRECTION_ROW).toBeDefined();
expect(Yoga.FLEX_DIRECTION_ROW_REVERSE).toBeDefined();
});
test("should export all gutter constants", () => {
expect(Yoga.GUTTER_COLUMN).toBeDefined();
expect(Yoga.GUTTER_ROW).toBeDefined();
expect(Yoga.GUTTER_ALL).toBeDefined();
});
test("should export all justify constants", () => {
expect(Yoga.JUSTIFY_FLEX_START).toBeDefined();
expect(Yoga.JUSTIFY_CENTER).toBeDefined();
expect(Yoga.JUSTIFY_FLEX_END).toBeDefined();
expect(Yoga.JUSTIFY_SPACE_BETWEEN).toBeDefined();
expect(Yoga.JUSTIFY_SPACE_AROUND).toBeDefined();
expect(Yoga.JUSTIFY_SPACE_EVENLY).toBeDefined();
});
test("should export all measure mode constants", () => {
expect(Yoga.MEASURE_MODE_UNDEFINED).toBeDefined();
expect(Yoga.MEASURE_MODE_EXACTLY).toBeDefined();
expect(Yoga.MEASURE_MODE_AT_MOST).toBeDefined();
});
test("should export all node type constants", () => {
expect(Yoga.NODE_TYPE_DEFAULT).toBeDefined();
expect(Yoga.NODE_TYPE_TEXT).toBeDefined();
});
test("should export all overflow constants", () => {
expect(Yoga.OVERFLOW_VISIBLE).toBeDefined();
expect(Yoga.OVERFLOW_HIDDEN).toBeDefined();
expect(Yoga.OVERFLOW_SCROLL).toBeDefined();
});
test("should export all position type constants", () => {
expect(Yoga.POSITION_TYPE_STATIC).toBeDefined();
expect(Yoga.POSITION_TYPE_RELATIVE).toBeDefined();
expect(Yoga.POSITION_TYPE_ABSOLUTE).toBeDefined();
});
test("should export all unit constants", () => {
expect(Yoga.UNIT_UNDEFINED).toBeDefined();
expect(Yoga.UNIT_POINT).toBeDefined();
expect(Yoga.UNIT_PERCENT).toBeDefined();
expect(Yoga.UNIT_AUTO).toBeDefined();
});
test("should export all wrap constants", () => {
expect(Yoga.WRAP_NO_WRAP).toBeDefined();
expect(Yoga.WRAP_WRAP).toBeDefined();
expect(Yoga.WRAP_WRAP_REVERSE).toBeDefined();
});
test("should export all errata constants", () => {
expect(Yoga.ERRATA_NONE).toBeDefined();
expect(Yoga.ERRATA_STRETCH_FLEX_BASIS).toBeDefined();
expect(Yoga.ERRATA_ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING).toBeDefined();
expect(Yoga.ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE).toBeDefined();
expect(Yoga.ERRATA_ALL).toBeDefined();
expect(Yoga.ERRATA_CLASSIC).toBeDefined();
});
test("constants should have correct numeric values", () => {
// Check a few key constants have reasonable values
expect(typeof Yoga.EDGE_TOP).toBe("number");
expect(typeof Yoga.UNIT_PERCENT).toBe("number");
expect(typeof Yoga.FLEX_DIRECTION_ROW).toBe("number");
});
});

View File

@@ -0,0 +1,304 @@
import { describe, expect, test } from "bun:test";
const Yoga = Bun.Yoga;
describe("Yoga - Comprehensive Layout Tests", () => {
test("basic flexbox row layout with flex grow", () => {
const container = new Yoga.Node();
container.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
container.setWidth(300);
container.setHeight(100);
const child1 = new Yoga.Node();
child1.setFlex(1);
const child2 = new Yoga.Node();
child2.setFlex(2);
const child3 = new Yoga.Node();
child3.setWidth(50); // Fixed width
container.insertChild(child1, 0);
container.insertChild(child2, 1);
container.insertChild(child3, 2);
container.calculateLayout();
// Verify container layout
const containerLayout = container.getComputedLayout();
expect(containerLayout.width).toBe(300);
expect(containerLayout.height).toBe(100);
// Verify children layout
// Available space: 300 - 50 (fixed width) = 250
// child1 gets 1/3 of 250 = ~83.33
// child2 gets 2/3 of 250 = ~166.67
// child3 gets fixed 50
const child1Layout = child1.getComputedLayout();
const child2Layout = child2.getComputedLayout();
const child3Layout = child3.getComputedLayout();
expect(child1Layout.left).toBe(0);
expect(child1Layout.width).toBe(83);
expect(child1Layout.height).toBe(100);
expect(child2Layout.left).toBe(83);
expect(child2Layout.width).toBe(167);
expect(child2Layout.height).toBe(100);
expect(child3Layout.left).toBe(250);
expect(child3Layout.width).toBe(50);
expect(child3Layout.height).toBe(100);
});
test("column layout with justify content and align items", () => {
const container = new Yoga.Node();
container.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
container.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN);
container.setAlignItems(Yoga.ALIGN_CENTER);
container.setWidth(200);
container.setHeight(300);
const child1 = new Yoga.Node();
child1.setWidth(50);
child1.setHeight(50);
const child2 = new Yoga.Node();
child2.setWidth(80);
child2.setHeight(60);
const child3 = new Yoga.Node();
child3.setWidth(30);
child3.setHeight(40);
container.insertChild(child1, 0);
container.insertChild(child2, 1);
container.insertChild(child3, 2);
container.calculateLayout();
const child1Layout = child1.getComputedLayout();
const child2Layout = child2.getComputedLayout();
const child3Layout = child3.getComputedLayout();
// Verify vertical spacing (JUSTIFY_SPACE_BETWEEN)
// Total child height: 50 + 60 + 40 = 150
// Available space: 300 - 150 = 150
// Space between: 150 / 2 = 75
expect(child1Layout.top).toBe(0);
expect(child2Layout.top).toBe(125); // 50 + 75
expect(child3Layout.top).toBe(260); // 50 + 75 + 60 + 75
// Verify horizontal centering (ALIGN_CENTER)
expect(child1Layout.left).toBe(75); // (200 - 50) / 2
expect(child2Layout.left).toBe(60); // (200 - 80) / 2
expect(child3Layout.left).toBe(85); // (200 - 30) / 2
});
test("nested flexbox layout", () => {
const outerContainer = new Yoga.Node();
outerContainer.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
outerContainer.setWidth(400);
outerContainer.setHeight(200);
const leftPanel = new Yoga.Node();
leftPanel.setWidth(100);
const rightPanel = new Yoga.Node();
rightPanel.setFlex(1);
rightPanel.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN);
const topSection = new Yoga.Node();
topSection.setFlex(1);
const bottomSection = new Yoga.Node();
bottomSection.setHeight(50);
outerContainer.insertChild(leftPanel, 0);
outerContainer.insertChild(rightPanel, 1);
rightPanel.insertChild(topSection, 0);
rightPanel.insertChild(bottomSection, 1);
outerContainer.calculateLayout();
const leftLayout = leftPanel.getComputedLayout();
const rightLayout = rightPanel.getComputedLayout();
const topLayout = topSection.getComputedLayout();
const bottomLayout = bottomSection.getComputedLayout();
// Left panel
expect(leftLayout.left).toBe(0);
expect(leftLayout.width).toBe(100);
expect(leftLayout.height).toBe(200);
// Right panel
expect(rightLayout.left).toBe(100);
expect(rightLayout.width).toBe(300); // 400 - 100
expect(rightLayout.height).toBe(200);
// Top section of right panel
expect(topLayout.left).toBe(0); // Relative to right panel
expect(topLayout.top).toBe(0);
expect(topLayout.width).toBe(300);
expect(topLayout.height).toBe(150); // 200 - 50
// Bottom section of right panel
expect(bottomLayout.left).toBe(0);
expect(bottomLayout.top).toBe(150);
expect(bottomLayout.width).toBe(300);
expect(bottomLayout.height).toBe(50);
});
test("flex wrap with multiple lines", () => {
const container = new Yoga.Node();
container.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
container.setFlexWrap(Yoga.WRAP_WRAP);
container.setWidth(200);
container.setHeight(200);
// Create children that will overflow and wrap
for (let i = 0; i < 5; i++) {
const child = new Yoga.Node();
child.setWidth(80);
child.setHeight(50);
container.insertChild(child, i);
}
container.calculateLayout();
// First line: child 0, 1 (80 + 80 = 160, fits in 200)
// Second line: child 2, 3 (80 + 80 = 160, fits in 200)
// Third line: child 4 (80, fits in 200)
const child0Layout = container.getChild(0).getComputedLayout();
const child1Layout = container.getChild(1).getComputedLayout();
const child2Layout = container.getChild(2).getComputedLayout();
const child3Layout = container.getChild(3).getComputedLayout();
const child4Layout = container.getChild(4).getComputedLayout();
// First line
expect(child0Layout.top).toBe(0);
expect(child0Layout.left).toBe(0);
expect(child1Layout.top).toBe(0);
expect(child1Layout.left).toBe(80);
// Second line
expect(child2Layout.top).toBe(50);
expect(child2Layout.left).toBe(0);
expect(child3Layout.top).toBe(50);
expect(child3Layout.left).toBe(80);
// Third line
expect(child4Layout.top).toBe(100);
expect(child4Layout.left).toBe(0);
});
test("margin and padding calculations", () => {
const container = new Yoga.Node();
container.setPadding(Yoga.EDGE_ALL, 10);
container.setWidth(200);
container.setHeight(150);
const child = new Yoga.Node();
child.setMargin(Yoga.EDGE_ALL, 15);
child.setFlex(1);
container.insertChild(child, 0);
container.calculateLayout();
const containerLayout = container.getComputedLayout();
const childLayout = child.getComputedLayout();
// Container should maintain its size
expect(containerLayout.width).toBe(200);
expect(containerLayout.height).toBe(150);
// Child should account for container padding and its own margin
// Available width: 200 - (10+10 padding) - (15+15 margin) = 150
// Available height: 150 - (10+10 padding) - (15+15 margin) = 100
expect(childLayout.left).toBe(25); // container padding + child margin
expect(childLayout.top).toBe(25);
expect(childLayout.width).toBe(150);
expect(childLayout.height).toBe(100);
});
test("percentage-based dimensions", () => {
const container = new Yoga.Node();
container.setWidth(400);
container.setHeight(300);
const child = new Yoga.Node();
child.setWidth("50%"); // 50% of 400 = 200
child.setHeight("75%"); // 75% of 300 = 225
container.insertChild(child, 0);
container.calculateLayout();
const childLayout = child.getComputedLayout();
expect(childLayout.width).toBe(200);
expect(childLayout.height).toBe(225);
});
test("min/max constraints", () => {
const container = new Yoga.Node();
container.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
container.setWidth(500);
container.setHeight(100);
const child1 = new Yoga.Node();
child1.setFlex(1);
child1.setMinWidth(100);
child1.setMaxWidth(200);
const child2 = new Yoga.Node();
child2.setFlex(2);
container.insertChild(child1, 0);
container.insertChild(child2, 1);
container.calculateLayout();
const child1Layout = child1.getComputedLayout();
const child2Layout = child2.getComputedLayout();
// child1 would normally get 1/3 of 500 = ~166.67
// But it's clamped by maxWidth(200), so it gets 200
expect(child1Layout.width).toBe(200);
// child2 gets the remaining space: 500 - 200 = 300
expect(child2Layout.width).toBe(300);
});
test("absolute positioning", () => {
const container = new Yoga.Node();
container.setWidth(300);
container.setHeight(200);
const normalChild = new Yoga.Node();
normalChild.setWidth(100);
normalChild.setHeight(50);
const absoluteChild = new Yoga.Node();
absoluteChild.setPositionType(Yoga.POSITION_TYPE_ABSOLUTE);
absoluteChild.setPosition(Yoga.EDGE_TOP, 20);
absoluteChild.setPosition(Yoga.EDGE_LEFT, 50);
absoluteChild.setWidth(80);
absoluteChild.setHeight(60);
container.insertChild(normalChild, 0);
container.insertChild(absoluteChild, 1);
container.calculateLayout();
const normalLayout = normalChild.getComputedLayout();
const absoluteLayout = absoluteChild.getComputedLayout();
// Normal child positioned normally
expect(normalLayout.left).toBe(0);
expect(normalLayout.top).toBe(0);
// Absolute child positioned absolutely
expect(absoluteLayout.left).toBe(50);
expect(absoluteLayout.top).toBe(20);
expect(absoluteLayout.width).toBe(80);
expect(absoluteLayout.height).toBe(60);
});
});

View File

@@ -0,0 +1,791 @@
import { describe, expect, test } from "bun:test";
const Yoga = Bun.Yoga;
describe("Yoga.Node - Extended Tests", () => {
describe("Node creation and cloning", () => {
test("clone() creates independent copy", () => {
const original = new Yoga.Node();
original.setWidth(100);
original.setHeight(200);
original.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
const cloned = original.clone();
expect(cloned).toBeDefined();
expect(cloned).not.toBe(original);
// Verify cloned has same properties
const originalWidth = original.getWidth();
const clonedWidth = cloned.getWidth();
expect(clonedWidth.value).toBe(originalWidth.value);
expect(clonedWidth.unit).toBe(originalWidth.unit);
// Verify they're independent
original.setWidth(300);
expect(cloned.getWidth().value).toBe(100);
});
test("clone() preserves measure function", () => {
const original = new Yoga.Node();
let originalMeasureCalled = false;
let clonedMeasureCalled = false;
original.setMeasureFunc((width, height) => {
originalMeasureCalled = true;
return { width: 100, height: 50 };
});
const cloned = original.clone();
// Both should have measure functions
original.markDirty();
original.calculateLayout();
expect(originalMeasureCalled).toBe(true);
// Note: cloned nodes share the same measure function reference
cloned.markDirty();
cloned.calculateLayout();
// The original measure function is called again
expect(originalMeasureCalled).toBe(true);
});
test("clone() with hierarchy", () => {
const parent = new Yoga.Node();
const child1 = new Yoga.Node();
const child2 = new Yoga.Node();
parent.insertChild(child1, 0);
parent.insertChild(child2, 1);
const clonedParent = parent.clone();
expect(clonedParent.getChildCount()).toBe(2);
const clonedChild1 = clonedParent.getChild(0);
const clonedChild2 = clonedParent.getChild(1);
expect(clonedChild1).toBeDefined();
expect(clonedChild2).toBeDefined();
expect(clonedChild1).not.toBe(child1);
expect(clonedChild2).not.toBe(child2);
});
test("copyStyle() copies style properties", () => {
const source = new Yoga.Node();
source.setWidth(100);
source.setHeight(200);
source.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
source.setJustifyContent(Yoga.JUSTIFY_CENTER);
source.setAlignItems(Yoga.ALIGN_CENTER);
const target = new Yoga.Node();
target.copyStyle(source);
expect(target.getWidth()).toEqual(source.getWidth());
expect(target.getHeight()).toEqual(source.getHeight());
// Note: Can't verify flex direction directly as getter is not accessible
});
test("freeRecursive() frees node and children", () => {
const parent = new Yoga.Node();
const child1 = new Yoga.Node();
const child2 = new Yoga.Node();
const grandchild = new Yoga.Node();
parent.insertChild(child1, 0);
parent.insertChild(child2, 1);
child1.insertChild(grandchild, 0);
expect(() => parent.freeRecursive()).not.toThrow();
});
});
describe("Direction and layout", () => {
test("setDirection/getDirection", () => {
const node = new Yoga.Node();
node.setDirection(Yoga.DIRECTION_LTR);
expect(node.getDirection()).toBe(Yoga.DIRECTION_LTR);
node.setDirection(Yoga.DIRECTION_RTL);
expect(node.getDirection()).toBe(Yoga.DIRECTION_RTL);
node.setDirection(Yoga.DIRECTION_INHERIT);
expect(node.getDirection()).toBe(Yoga.DIRECTION_INHERIT);
});
test("getComputedLeft/Top/Width/Height", () => {
const node = new Yoga.Node();
node.setWidth(100);
node.setHeight(100);
node.calculateLayout();
expect(node.getComputedLeft()).toBe(0);
expect(node.getComputedTop()).toBe(0);
expect(node.getComputedWidth()).toBe(100);
expect(node.getComputedHeight()).toBe(100);
});
test("getComputedRight/Bottom calculations", () => {
const parent = new Yoga.Node();
parent.setWidth(200);
parent.setHeight(200);
const child = new Yoga.Node();
child.setWidth(100);
child.setHeight(100);
child.setPositionType(Yoga.POSITION_TYPE_ABSOLUTE);
child.setPosition(Yoga.EDGE_LEFT, 10);
child.setPosition(Yoga.EDGE_TOP, 20);
parent.insertChild(child, 0);
parent.calculateLayout();
expect(child.getComputedLeft()).toBe(10);
expect(child.getComputedTop()).toBe(20);
// Yoga's getComputedRight/Bottom return position offsets, not absolute coordinates
// Since we positioned with left/top, right/bottom will be the original position values
expect(child.getComputedRight()).toBe(10);
expect(child.getComputedBottom()).toBe(20);
});
test("getComputedMargin", () => {
const node = new Yoga.Node();
node.setMargin(Yoga.EDGE_TOP, 10);
node.setMargin(Yoga.EDGE_RIGHT, 20);
node.setMargin(Yoga.EDGE_BOTTOM, 30);
node.setMargin(Yoga.EDGE_LEFT, 40);
node.setWidth(100);
node.setHeight(100);
const parent = new Yoga.Node();
parent.setWidth(300);
parent.setHeight(300);
parent.insertChild(node, 0);
parent.calculateLayout();
expect(node.getComputedMargin(Yoga.EDGE_TOP)).toBe(10);
expect(node.getComputedMargin(Yoga.EDGE_RIGHT)).toBe(20);
expect(node.getComputedMargin(Yoga.EDGE_BOTTOM)).toBe(30);
expect(node.getComputedMargin(Yoga.EDGE_LEFT)).toBe(40);
});
test("getComputedPadding", () => {
const node = new Yoga.Node();
node.setPadding(Yoga.EDGE_ALL, 15);
node.setWidth(100);
node.setHeight(100);
node.calculateLayout();
expect(node.getComputedPadding(Yoga.EDGE_TOP)).toBe(15);
expect(node.getComputedPadding(Yoga.EDGE_RIGHT)).toBe(15);
expect(node.getComputedPadding(Yoga.EDGE_BOTTOM)).toBe(15);
expect(node.getComputedPadding(Yoga.EDGE_LEFT)).toBe(15);
});
test("getComputedBorder", () => {
const node = new Yoga.Node();
node.setBorder(Yoga.EDGE_ALL, 5);
node.setWidth(100);
node.setHeight(100);
node.calculateLayout();
expect(node.getComputedBorder(Yoga.EDGE_TOP)).toBe(5);
expect(node.getComputedBorder(Yoga.EDGE_RIGHT)).toBe(5);
expect(node.getComputedBorder(Yoga.EDGE_BOTTOM)).toBe(5);
expect(node.getComputedBorder(Yoga.EDGE_LEFT)).toBe(5);
});
});
describe("Flexbox properties", () => {
test("setAlignContent/getAlignContent", () => {
const node = new Yoga.Node();
node.setAlignContent(Yoga.ALIGN_FLEX_START);
expect(node.getAlignContent()).toBe(Yoga.ALIGN_FLEX_START);
node.setAlignContent(Yoga.ALIGN_CENTER);
expect(node.getAlignContent()).toBe(Yoga.ALIGN_CENTER);
node.setAlignContent(Yoga.ALIGN_STRETCH);
expect(node.getAlignContent()).toBe(Yoga.ALIGN_STRETCH);
});
test("setAlignSelf/getAlignSelf", () => {
const node = new Yoga.Node();
node.setAlignSelf(Yoga.ALIGN_AUTO);
expect(node.getAlignSelf()).toBe(Yoga.ALIGN_AUTO);
node.setAlignSelf(Yoga.ALIGN_FLEX_END);
expect(node.getAlignSelf()).toBe(Yoga.ALIGN_FLEX_END);
});
test("setAlignItems/getAlignItems", () => {
const node = new Yoga.Node();
node.setAlignItems(Yoga.ALIGN_FLEX_START);
expect(node.getAlignItems()).toBe(Yoga.ALIGN_FLEX_START);
node.setAlignItems(Yoga.ALIGN_BASELINE);
expect(node.getAlignItems()).toBe(Yoga.ALIGN_BASELINE);
});
test("getFlex", () => {
const node = new Yoga.Node();
node.setFlex(2.5);
expect(node.getFlex()).toBe(2.5);
node.setFlex(0);
expect(node.getFlex()).toBe(0);
});
test("setFlexWrap/getFlexWrap", () => {
const node = new Yoga.Node();
node.setFlexWrap(Yoga.WRAP_NO_WRAP);
expect(node.getFlexWrap()).toBe(Yoga.WRAP_NO_WRAP);
node.setFlexWrap(Yoga.WRAP_WRAP);
expect(node.getFlexWrap()).toBe(Yoga.WRAP_WRAP);
node.setFlexWrap(Yoga.WRAP_WRAP_REVERSE);
expect(node.getFlexWrap()).toBe(Yoga.WRAP_WRAP_REVERSE);
});
test("getFlexDirection", () => {
const node = new Yoga.Node();
node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
expect(node.getFlexDirection()).toBe(Yoga.FLEX_DIRECTION_ROW);
node.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN_REVERSE);
expect(node.getFlexDirection()).toBe(Yoga.FLEX_DIRECTION_COLUMN_REVERSE);
});
test("getFlexGrow/getFlexShrink", () => {
const node = new Yoga.Node();
node.setFlexGrow(2);
expect(node.getFlexGrow()).toBe(2);
node.setFlexShrink(0.5);
expect(node.getFlexShrink()).toBe(0.5);
});
test("getJustifyContent", () => {
const node = new Yoga.Node();
node.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN);
expect(node.getJustifyContent()).toBe(Yoga.JUSTIFY_SPACE_BETWEEN);
node.setJustifyContent(Yoga.JUSTIFY_SPACE_AROUND);
expect(node.getJustifyContent()).toBe(Yoga.JUSTIFY_SPACE_AROUND);
});
});
describe("Position properties", () => {
test("setPosition/getPosition", () => {
const node = new Yoga.Node();
node.setPosition(Yoga.EDGE_LEFT, 10);
expect(node.getPosition(Yoga.EDGE_LEFT)).toEqual({ unit: Yoga.UNIT_POINT, value: 10 });
node.setPosition(Yoga.EDGE_TOP, "20%");
expect(node.getPosition(Yoga.EDGE_TOP)).toEqual({ unit: Yoga.UNIT_PERCENT, value: 20 });
node.setPosition(Yoga.EDGE_RIGHT, { unit: Yoga.UNIT_POINT, value: 30 });
expect(node.getPosition(Yoga.EDGE_RIGHT)).toEqual({ unit: Yoga.UNIT_POINT, value: 30 });
});
test("setPositionType/getPositionType", () => {
const node = new Yoga.Node();
node.setPositionType(Yoga.POSITION_TYPE_ABSOLUTE);
expect(node.getPositionType()).toBe(Yoga.POSITION_TYPE_ABSOLUTE);
node.setPositionType(Yoga.POSITION_TYPE_RELATIVE);
expect(node.getPositionType()).toBe(Yoga.POSITION_TYPE_RELATIVE);
node.setPositionType(Yoga.POSITION_TYPE_STATIC);
expect(node.getPositionType()).toBe(Yoga.POSITION_TYPE_STATIC);
});
});
describe("Size properties", () => {
test("height/width with percentage", () => {
const parent = new Yoga.Node();
parent.setWidth(200);
parent.setHeight(200);
const child = new Yoga.Node();
child.setWidth("50%");
child.setHeight("75%");
parent.insertChild(child, 0);
parent.calculateLayout();
expect(child.getComputedWidth()).toBe(100); // 50% of 200
expect(child.getComputedHeight()).toBe(150); // 75% of 200
});
test("getAspectRatio", () => {
const node = new Yoga.Node();
node.setAspectRatio(1.5);
expect(node.getAspectRatio()).toBe(1.5);
node.setAspectRatio(undefined);
expect(node.getAspectRatio()).toBeNaN();
});
test("size constraints affect layout", () => {
const node = new Yoga.Node();
node.setMinWidth(50);
node.setMinHeight(50);
node.setMaxWidth(100);
node.setMaxHeight(100);
// Width/height beyond constraints
node.setWidth(200);
node.setHeight(200);
node.calculateLayout();
// Constraints are now working correctly - values should be clamped to max
expect(node.getComputedWidth()).toBe(100);
expect(node.getComputedHeight()).toBe(100);
});
});
describe("Spacing properties", () => {
test("setPadding/getPadding", () => {
const node = new Yoga.Node();
// Set padding on individual edges
node.setPadding(Yoga.EDGE_TOP, 10);
node.setPadding(Yoga.EDGE_RIGHT, 10);
node.setPadding(Yoga.EDGE_BOTTOM, 10);
node.setPadding(Yoga.EDGE_LEFT, 10);
expect(node.getPadding(Yoga.EDGE_TOP)).toEqual({ unit: Yoga.UNIT_POINT, value: 10 });
expect(node.getPadding(Yoga.EDGE_RIGHT)).toEqual({ unit: Yoga.UNIT_POINT, value: 10 });
// Set different values
node.setPadding(Yoga.EDGE_LEFT, 20);
node.setPadding(Yoga.EDGE_RIGHT, 20);
expect(node.getPadding(Yoga.EDGE_LEFT)).toEqual({ unit: Yoga.UNIT_POINT, value: 20 });
expect(node.getPadding(Yoga.EDGE_RIGHT)).toEqual({ unit: Yoga.UNIT_POINT, value: 20 });
node.setPadding(Yoga.EDGE_TOP, "15%");
expect(node.getPadding(Yoga.EDGE_TOP)).toEqual({ unit: Yoga.UNIT_PERCENT, value: 15 });
});
test("setBorder/getBorder", () => {
const node = new Yoga.Node();
// Set border on individual edges
node.setBorder(Yoga.EDGE_TOP, 5);
node.setBorder(Yoga.EDGE_RIGHT, 5);
node.setBorder(Yoga.EDGE_BOTTOM, 5);
node.setBorder(Yoga.EDGE_LEFT, 5);
expect(node.getBorder(Yoga.EDGE_TOP)).toBe(5);
expect(node.getBorder(Yoga.EDGE_RIGHT)).toBe(5);
node.setBorder(Yoga.EDGE_TOP, 10);
expect(node.getBorder(Yoga.EDGE_TOP)).toBe(10);
expect(node.getBorder(Yoga.EDGE_RIGHT)).toBe(5); // Should still be 5
});
test("getGap with different gutters", () => {
const node = new Yoga.Node();
node.setGap(Yoga.GUTTER_ROW, 10);
expect(node.getGap(Yoga.GUTTER_ROW)).toEqual({ value: 10, unit: Yoga.UNIT_POINT });
node.setGap(Yoga.GUTTER_COLUMN, 20);
expect(node.getGap(Yoga.GUTTER_COLUMN)).toEqual({ value: 20, unit: Yoga.UNIT_POINT });
// Verify row and column gaps are independent
expect(node.getGap(Yoga.GUTTER_ROW)).toEqual({ value: 10, unit: Yoga.UNIT_POINT });
});
});
describe("Node type and display", () => {
test("setNodeType/getNodeType", () => {
const node = new Yoga.Node();
expect(node.getNodeType()).toBe(Yoga.NODE_TYPE_DEFAULT);
node.setNodeType(Yoga.NODE_TYPE_TEXT);
expect(node.getNodeType()).toBe(Yoga.NODE_TYPE_TEXT);
node.setNodeType(Yoga.NODE_TYPE_DEFAULT);
expect(node.getNodeType()).toBe(Yoga.NODE_TYPE_DEFAULT);
});
test("setDisplay/getDisplay", () => {
const node = new Yoga.Node();
node.setDisplay(Yoga.DISPLAY_FLEX);
expect(node.getDisplay()).toBe(Yoga.DISPLAY_FLEX);
node.setDisplay(Yoga.DISPLAY_NONE);
expect(node.getDisplay()).toBe(Yoga.DISPLAY_NONE);
});
test("setOverflow/getOverflow", () => {
const node = new Yoga.Node();
node.setOverflow(Yoga.OVERFLOW_HIDDEN);
expect(node.getOverflow()).toBe(Yoga.OVERFLOW_HIDDEN);
node.setOverflow(Yoga.OVERFLOW_SCROLL);
expect(node.getOverflow()).toBe(Yoga.OVERFLOW_SCROLL);
});
});
describe("Box sizing", () => {
test("setBoxSizing/getBoxSizing", () => {
const node = new Yoga.Node();
// Default is border-box
expect(node.getBoxSizing()).toBe(Yoga.BOX_SIZING_BORDER_BOX);
node.setBoxSizing(Yoga.BOX_SIZING_CONTENT_BOX);
expect(node.getBoxSizing()).toBe(Yoga.BOX_SIZING_CONTENT_BOX);
node.setBoxSizing(Yoga.BOX_SIZING_BORDER_BOX);
expect(node.getBoxSizing()).toBe(Yoga.BOX_SIZING_BORDER_BOX);
});
});
describe("Layout state", () => {
test("setHasNewLayout/getHasNewLayout", () => {
const node = new Yoga.Node();
node.calculateLayout();
expect(node.getHasNewLayout()).toBe(true);
node.setHasNewLayout(false);
expect(node.getHasNewLayout()).toBe(false);
node.setHasNewLayout(true);
expect(node.getHasNewLayout()).toBe(true);
});
});
describe("Baseline", () => {
test("setIsReferenceBaseline/isReferenceBaseline", () => {
const node = new Yoga.Node();
expect(node.isReferenceBaseline()).toBe(false);
node.setIsReferenceBaseline(true);
expect(node.isReferenceBaseline()).toBe(true);
node.setIsReferenceBaseline(false);
expect(node.isReferenceBaseline()).toBe(false);
});
test("setBaselineFunc", () => {
const node = new Yoga.Node();
let baselineCalled = false;
node.setBaselineFunc((width, height) => {
baselineCalled = true;
return height * 0.8;
});
// Set up a scenario where baseline function is called
const container = new Yoga.Node();
container.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
container.setAlignItems(Yoga.ALIGN_BASELINE);
container.setWidth(300);
container.setHeight(100);
node.setWidth(100);
node.setHeight(50);
container.insertChild(node, 0);
// Add another child to trigger baseline alignment
const sibling = new Yoga.Node();
sibling.setWidth(100);
sibling.setHeight(60);
container.insertChild(sibling, 1);
container.calculateLayout();
// Clear the baseline function
node.setBaselineFunc(null);
});
});
describe("Hierarchy operations", () => {
test("removeAllChildren", () => {
const parent = new Yoga.Node();
const child1 = new Yoga.Node();
const child2 = new Yoga.Node();
const child3 = new Yoga.Node();
parent.insertChild(child1, 0);
parent.insertChild(child2, 1);
parent.insertChild(child3, 2);
expect(parent.getChildCount()).toBe(3);
parent.removeAllChildren();
expect(parent.getChildCount()).toBe(0);
expect(child1.getParent()).toBeNull();
expect(child2.getParent()).toBeNull();
expect(child3.getParent()).toBeNull();
});
test("getOwner", () => {
const parent = new Yoga.Node();
const child = new Yoga.Node();
parent.insertChild(child, 0);
// getOwner returns the parent node that owns this node
expect(child.getOwner()).toBe(parent);
const clonedParent = parent.clone();
const clonedChild = clonedParent.getChild(0);
// After a deep clone, the cloned child's owner is the cloned parent
expect(clonedChild.getOwner()).toBe(clonedParent);
});
});
describe("Config association", () => {
test("getConfig returns associated config", () => {
const config = new Yoga.Config();
const node = new Yoga.Node(config);
expect(node.getConfig()).toBe(config);
});
test("getConfig returns null for nodes without config", () => {
const node = new Yoga.Node();
expect(node.getConfig()).toBeNull();
});
});
describe("Edge cases and error handling", () => {
test("getChild with invalid index", () => {
const node = new Yoga.Node();
expect(node.getChild(-1)).toBeNull();
expect(node.getChild(0)).toBeNull();
expect(node.getChild(10)).toBeNull();
});
test("getParent for root node", () => {
const node = new Yoga.Node();
expect(node.getParent()).toBeNull();
});
// TODO: This test currently causes a segmentation fault
// Operations on freed nodes should be safe but currently crash
// test("operations on freed node", () => {
// const node = new Yoga.Node();
// node.free();
//
// // Operations on freed nodes should not crash
// expect(() => node.setWidth(100)).not.toThrow();
// expect(() => node.getWidth()).not.toThrow();
// });
test("markDirty edge cases", () => {
const node = new Yoga.Node();
// markDirty without measure function should throw
expect(() => node.markDirty()).toThrow("Only nodes with custom measure functions can be marked as dirty");
// With measure function it should work
node.setMeasureFunc(() => ({ width: 100, height: 50 }));
expect(() => node.markDirty()).not.toThrow();
});
test("calculateLayout with various dimensions", () => {
const node = new Yoga.Node();
expect(() => node.calculateLayout()).not.toThrow();
expect(() => node.calculateLayout(undefined, undefined)).not.toThrow();
expect(() => node.calculateLayout(Yoga.UNDEFINED, Yoga.UNDEFINED)).not.toThrow();
expect(() => node.calculateLayout(100, 100, Yoga.DIRECTION_LTR)).not.toThrow();
});
});
});
describe("Yoga.Config - Extended Tests", () => {
test("Config constructor and create", () => {
const config1 = new Yoga.Config();
expect(config1).toBeDefined();
expect(config1.constructor.name).toBe("Config");
const config2 = Yoga.Config.create();
expect(config2).toBeDefined();
expect(config2.constructor.name).toBe("Config");
});
test("setUseWebDefaults/getUseWebDefaults", () => {
const config = new Yoga.Config();
expect(config.getUseWebDefaults()).toBe(false);
config.setUseWebDefaults(true);
expect(config.getUseWebDefaults()).toBe(true);
config.setUseWebDefaults(false);
expect(config.getUseWebDefaults()).toBe(false);
});
test("setPointScaleFactor/getPointScaleFactor", () => {
const config = new Yoga.Config();
// Default is usually 1.0
const defaultScale = config.getPointScaleFactor();
expect(defaultScale).toBeGreaterThan(0);
config.setPointScaleFactor(2.0);
expect(config.getPointScaleFactor()).toBe(2.0);
config.setPointScaleFactor(0.0);
expect(config.getPointScaleFactor()).toBe(0.0);
});
test("setContext/getContext", () => {
const config = new Yoga.Config();
expect(config.getContext()).toBeNull();
const context = { foo: "bar", num: 42, arr: [1, 2, 3] };
config.setContext(context);
expect(config.getContext()).toBe(context);
config.setContext(null);
expect(config.getContext()).toBeNull();
});
test("setLogger callback", () => {
const config = new Yoga.Config();
// Set logger
config.setLogger((config, node, level, format) => {
console.log("Logger called");
return 0;
});
// Clear logger
config.setLogger(null);
// Setting invalid logger
expect(() => config.setLogger("not a function")).toThrow();
});
test("setCloneNodeFunc callback", () => {
const config = new Yoga.Config();
// Set clone function
config.setCloneNodeFunc((oldNode, owner, childIndex) => {
return oldNode.clone();
});
// Clear clone function
config.setCloneNodeFunc(null);
// Setting invalid clone function
expect(() => config.setCloneNodeFunc("not a function")).toThrow();
});
// TODO: This test currently causes a segmentation fault
// Operations on freed configs should be safe but currently crash
// test("free config", () => {
// const config = new Yoga.Config();
// expect(() => config.free()).not.toThrow();
//
// // Operations after free should not crash
// expect(() => config.setPointScaleFactor(2.0)).not.toThrow();
// });
test("setErrata/getErrata", () => {
const config = new Yoga.Config();
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
config.setErrata(Yoga.ERRATA_CLASSIC);
expect(config.getErrata()).toBe(Yoga.ERRATA_CLASSIC);
config.setErrata(Yoga.ERRATA_ALL);
expect(config.getErrata()).toBe(Yoga.ERRATA_ALL);
config.setErrata(Yoga.ERRATA_NONE);
expect(config.getErrata()).toBe(Yoga.ERRATA_NONE);
});
test("experimental features", () => {
const config = new Yoga.Config();
// Check if experimental feature methods exist
expect(typeof config.setExperimentalFeatureEnabled).toBe("function");
expect(typeof config.isExperimentalFeatureEnabled).toBe("function");
// Try enabling/disabling a feature (0 as example)
expect(() => config.setExperimentalFeatureEnabled(0, true)).not.toThrow();
expect(() => config.isExperimentalFeatureEnabled(0)).not.toThrow();
});
test("isEnabledForNodes", () => {
const config = new Yoga.Config();
expect(typeof config.isEnabledForNodes()).toBe("boolean");
});
});
describe("Yoga Constants Verification", () => {
test("All required constants are defined", () => {
// Edge constants
expect(typeof Yoga.EDGE_LEFT).toBe("number");
expect(typeof Yoga.EDGE_TOP).toBe("number");
expect(typeof Yoga.EDGE_RIGHT).toBe("number");
expect(typeof Yoga.EDGE_BOTTOM).toBe("number");
expect(typeof Yoga.EDGE_START).toBe("number");
expect(typeof Yoga.EDGE_END).toBe("number");
expect(typeof Yoga.EDGE_HORIZONTAL).toBe("number");
expect(typeof Yoga.EDGE_VERTICAL).toBe("number");
expect(typeof Yoga.EDGE_ALL).toBe("number");
// Unit constants
expect(typeof Yoga.UNIT_UNDEFINED).toBe("number");
expect(typeof Yoga.UNIT_POINT).toBe("number");
expect(typeof Yoga.UNIT_PERCENT).toBe("number");
expect(typeof Yoga.UNIT_AUTO).toBe("number");
// Direction constants
expect(typeof Yoga.DIRECTION_INHERIT).toBe("number");
expect(typeof Yoga.DIRECTION_LTR).toBe("number");
expect(typeof Yoga.DIRECTION_RTL).toBe("number");
// Display constants
expect(typeof Yoga.DISPLAY_FLEX).toBe("number");
expect(typeof Yoga.DISPLAY_NONE).toBe("number");
// Position type constants
expect(typeof Yoga.POSITION_TYPE_STATIC).toBe("number");
expect(typeof Yoga.POSITION_TYPE_RELATIVE).toBe("number");
expect(typeof Yoga.POSITION_TYPE_ABSOLUTE).toBe("number");
// Overflow constants
expect(typeof Yoga.OVERFLOW_VISIBLE).toBe("number");
expect(typeof Yoga.OVERFLOW_HIDDEN).toBe("number");
expect(typeof Yoga.OVERFLOW_SCROLL).toBe("number");
// Special value
// Note: Yoga.UNDEFINED is not currently exposed in Bun's implementation
// It would be YGUndefined (NaN) in the C++ code
// expect(typeof Yoga.UNDEFINED).toBe("number");
});
});

View File

@@ -0,0 +1,272 @@
import { describe, expect, test } from "bun:test";
const Yoga = Bun.Yoga;
describe("Yoga.Node", () => {
test("Node constructor", () => {
const node = new Yoga.Node();
expect(node).toBeDefined();
expect(node.constructor.name).toBe("Node");
});
test("Node.create() static method", () => {
const node = Yoga.Node.create();
expect(node).toBeDefined();
expect(node.constructor.name).toBe("Node");
});
test("Node with config", () => {
const config = new Yoga.Config();
const node = new Yoga.Node(config);
expect(node).toBeDefined();
});
test("setWidth with various values", () => {
const node = new Yoga.Node();
// Number
expect(() => node.setWidth(100)).not.toThrow();
// Percentage string
expect(() => node.setWidth("50%")).not.toThrow();
// Auto
expect(() => node.setWidth("auto")).not.toThrow();
// Object format
expect(() => node.setWidth({ unit: Yoga.UNIT_POINT, value: 200 })).not.toThrow();
expect(() => node.setWidth({ unit: Yoga.UNIT_PERCENT, value: 75 })).not.toThrow();
// Undefined/null
expect(() => node.setWidth(undefined)).not.toThrow();
expect(() => node.setWidth(null)).not.toThrow();
});
test("getWidth returns correct format", () => {
const node = new Yoga.Node();
node.setWidth(100);
let width = node.getWidth();
expect(width).toEqual({ unit: Yoga.UNIT_POINT, value: 100 });
node.setWidth("50%");
width = node.getWidth();
expect(width).toEqual({ unit: Yoga.UNIT_PERCENT, value: 50 });
node.setWidth("auto");
width = node.getWidth();
expect(width).toEqual({ unit: Yoga.UNIT_AUTO, value: expect.any(Number) });
});
test("setMargin/getPadding edge values", () => {
const node = new Yoga.Node();
// Set margins
node.setMargin(Yoga.EDGE_TOP, 10);
node.setMargin(Yoga.EDGE_RIGHT, "20%");
node.setMargin(Yoga.EDGE_BOTTOM, "auto");
node.setMargin(Yoga.EDGE_LEFT, { unit: Yoga.UNIT_POINT, value: 30 });
// Get margins
expect(node.getMargin(Yoga.EDGE_TOP)).toEqual({ unit: Yoga.UNIT_POINT, value: 10 });
expect(node.getMargin(Yoga.EDGE_RIGHT)).toEqual({ unit: Yoga.UNIT_PERCENT, value: 20 });
expect(node.getMargin(Yoga.EDGE_BOTTOM)).toEqual({ unit: Yoga.UNIT_AUTO, value: expect.any(Number) });
expect(node.getMargin(Yoga.EDGE_LEFT)).toEqual({ unit: Yoga.UNIT_POINT, value: 30 });
});
test("flexbox properties", () => {
const node = new Yoga.Node();
// Flex direction
expect(() => node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW)).not.toThrow();
expect(() => node.setFlexDirection(Yoga.FLEX_DIRECTION_COLUMN)).not.toThrow();
// Justify content
expect(() => node.setJustifyContent(Yoga.JUSTIFY_CENTER)).not.toThrow();
expect(() => node.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN)).not.toThrow();
// Align items
expect(() => node.setAlignItems(Yoga.ALIGN_CENTER)).not.toThrow();
expect(() => node.setAlignItems(Yoga.ALIGN_FLEX_START)).not.toThrow();
// Flex properties
expect(() => node.setFlex(1)).not.toThrow();
expect(() => node.setFlexGrow(2)).not.toThrow();
expect(() => node.setFlexShrink(0.5)).not.toThrow();
expect(() => node.setFlexBasis(100)).not.toThrow();
expect(() => node.setFlexBasis("auto")).not.toThrow();
});
test("hierarchy operations", () => {
const parent = new Yoga.Node();
const child1 = new Yoga.Node();
const child2 = new Yoga.Node();
// Insert children
parent.insertChild(child1, 0);
parent.insertChild(child2, 1);
expect(parent.getChildCount()).toBe(2);
expect(parent.getChild(0)).toBe(child1);
expect(parent.getChild(1)).toBe(child2);
expect(child1.getParent()).toBe(parent);
expect(child2.getParent()).toBe(parent);
// Remove child
parent.removeChild(child1);
expect(parent.getChildCount()).toBe(1);
expect(parent.getChild(0)).toBe(child2);
expect(child1.getParent()).toBeNull();
});
test("layout calculation", () => {
const root = new Yoga.Node();
root.setWidth(500);
root.setHeight(300);
root.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
const child = new Yoga.Node();
child.setFlex(1);
root.insertChild(child, 0);
// Calculate layout
root.calculateLayout(500, 300, Yoga.DIRECTION_LTR);
// Get computed layout
const layout = root.getComputedLayout();
expect(layout).toHaveProperty("left");
expect(layout).toHaveProperty("top");
expect(layout).toHaveProperty("width");
expect(layout).toHaveProperty("height");
expect(layout.width).toBe(500);
expect(layout.height).toBe(300);
const childLayout = child.getComputedLayout();
expect(childLayout.width).toBe(500); // Should fill parent width
expect(childLayout.height).toBe(300); // Should fill parent height
});
test("measure function", () => {
const node = new Yoga.Node();
let measureCalled = false;
const measureFunc = (width, widthMode, height, heightMode) => {
measureCalled = true;
return { width: 100, height: 50 };
};
node.setMeasureFunc(measureFunc);
node.markDirty();
// Calculate layout - this should call measure function
node.calculateLayout();
expect(measureCalled).toBe(true);
// Clear measure function
node.setMeasureFunc(null);
});
test("dirtied callback", () => {
const node = new Yoga.Node();
let dirtiedCalled = false;
const dirtiedFunc = () => {
dirtiedCalled = true;
};
node.setDirtiedFunc(dirtiedFunc);
// markDirty requires a measure function
node.setMeasureFunc(() => ({ width: 100, height: 50 }));
// Nodes start dirty, so clear the dirty flag first
node.calculateLayout();
expect(node.isDirty()).toBe(false);
// Now mark dirty - this should trigger the callback
node.markDirty();
expect(dirtiedCalled).toBe(true);
// Clear dirtied function
node.setDirtiedFunc(null);
});
test("reset node", () => {
const node = new Yoga.Node();
node.setWidth(100);
node.setHeight(200);
node.setFlexDirection(Yoga.FLEX_DIRECTION_ROW);
node.reset();
// After reset, width/height default to AUTO, not UNDEFINED
const width = node.getWidth();
expect(width.unit).toBe(Yoga.UNIT_AUTO);
});
test("dirty state", () => {
const node = new Yoga.Node();
// Nodes start as dirty by default in Yoga
expect(node.isDirty()).toBe(true);
// Calculate layout clears dirty flag
node.calculateLayout();
expect(node.isDirty()).toBe(false);
// Mark as dirty (requires measure function)
node.setMeasureFunc(() => ({ width: 100, height: 50 }));
node.markDirty();
expect(node.isDirty()).toBe(true);
// Calculate layout clears dirty flag again
node.calculateLayout();
expect(node.isDirty()).toBe(false);
});
test("free node", () => {
const node = new Yoga.Node();
expect(() => node.free()).not.toThrow();
// After free, the node should not crash but operations may not work
});
test("aspect ratio", () => {
const node = new Yoga.Node();
// Set aspect ratio
expect(() => node.setAspectRatio(16 / 9)).not.toThrow();
expect(() => node.setAspectRatio(undefined)).not.toThrow();
expect(() => node.setAspectRatio(null)).not.toThrow();
});
test("display type", () => {
const node = new Yoga.Node();
expect(() => node.setDisplay(Yoga.DISPLAY_FLEX)).not.toThrow();
expect(() => node.setDisplay(Yoga.DISPLAY_NONE)).not.toThrow();
});
test("overflow", () => {
const node = new Yoga.Node();
expect(() => node.setOverflow(Yoga.OVERFLOW_VISIBLE)).not.toThrow();
expect(() => node.setOverflow(Yoga.OVERFLOW_HIDDEN)).not.toThrow();
expect(() => node.setOverflow(Yoga.OVERFLOW_SCROLL)).not.toThrow();
});
test("position type", () => {
const node = new Yoga.Node();
expect(() => node.setPositionType(Yoga.POSITION_TYPE_RELATIVE)).not.toThrow();
expect(() => node.setPositionType(Yoga.POSITION_TYPE_ABSOLUTE)).not.toThrow();
});
test("gap property", () => {
const node = new Yoga.Node();
expect(() => node.setGap(Yoga.GUTTER_ROW, 10)).not.toThrow();
expect(() => node.setGap(Yoga.GUTTER_COLUMN, 20)).not.toThrow();
});
});

View File

@@ -887,6 +887,68 @@ for (let withOverridenBufferWrite of [false, true]) {
expect(f[1]).toBe(0x6f);
});
it("slice() with fractional offsets truncates toward zero", () => {
const buf = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
// -0.1 should truncate to 0, not -1
const a = buf.slice(-0.1);
expect(a.length).toBe(10);
expect(a[0]).toBe(0);
// -1.9 should truncate to -1, not -2
const b = buf.slice(-1.9);
expect(b.length).toBe(1);
expect(b[0]).toBe(9);
// 1.9 should truncate to 1
const c = buf.slice(1.9, 4.1);
expect(c.length).toBe(3);
expect(c[0]).toBe(1);
expect(c[1]).toBe(2);
expect(c[2]).toBe(3);
// NaN should be treated as 0
const d = buf.slice(NaN, NaN);
expect(d.length).toBe(0);
const e = buf.slice(NaN);
expect(e.length).toBe(10);
});
it("slice() on detached buffer throws TypeError", () => {
const ab = new ArrayBuffer(10);
const buf = Buffer.from(ab);
// Detach the ArrayBuffer by transferring it
structuredClone(ab, { transfer: [ab] });
expect(() => buf.slice(0, 5)).toThrow(TypeError);
});
it("subarray() on detached buffer throws TypeError", () => {
const ab = new ArrayBuffer(10);
const buf = Buffer.from(ab);
structuredClone(ab, { transfer: [ab] });
expect(() => buf.subarray(0, 5)).toThrow(TypeError);
});
it("slice() on resizable ArrayBuffer returns fixed-length view", () => {
const rab = new ArrayBuffer(10, { maxByteLength: 20 });
const buf = Buffer.from(rab);
buf[0] = 1;
buf[1] = 2;
buf[2] = 3;
buf[3] = 4;
buf[4] = 5;
const sliced = buf.slice(0, 5);
expect(sliced.length).toBe(5);
expect(sliced[0]).toBe(1);
expect(sliced[4]).toBe(5);
// Growing the buffer should NOT change the slice length
rab.resize(20);
expect(sliced.length).toBe(5);
});
function forEachUnicode(label, test) {
["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding =>
it(`${label} (${encoding})`, test.bind(null, encoding)),

View File

@@ -90,6 +90,273 @@ describe("Structured Clone Fast Path", () => {
expect(delta).toBeLessThan(1024 * 1024);
});
// === Array fast path tests ===
test("structuredClone should work with empty array", () => {
expect(structuredClone([])).toEqual([]);
});
test("structuredClone should work with array of numbers", () => {
const input = [1, 2, 3, 4, 5];
expect(structuredClone(input)).toEqual(input);
});
test("structuredClone should work with array of strings", () => {
const input = ["hello", "world", ""];
expect(structuredClone(input)).toEqual(input);
});
test("structuredClone should work with array of mixed primitives", () => {
const input = [1, "hello", true, false, null, undefined, 3.14];
const cloned = structuredClone(input);
expect(cloned).toEqual(input);
});
test("structuredClone should work with array of special numbers", () => {
const cloned = structuredClone([-0, NaN, Infinity, -Infinity]);
expect(Object.is(cloned[0], -0)).toBe(true);
expect(cloned[1]).toBeNaN();
expect(cloned[2]).toBe(Infinity);
expect(cloned[3]).toBe(-Infinity);
});
test("structuredClone should work with large array of numbers", () => {
const input = Array.from({ length: 10000 }, (_, i) => i);
expect(structuredClone(input)).toEqual(input);
});
test("structuredClone should fallback for arrays with nested objects", () => {
const input = [{ a: 1 }, { b: 2 }];
expect(structuredClone(input)).toEqual(input);
});
test("structuredClone should fallback for arrays with holes", () => {
const input = [1, , 3]; // sparse
const cloned = structuredClone(input);
// structured clone spec: holes become undefined
expect(cloned[0]).toBe(1);
expect(cloned[1]).toBe(undefined);
expect(cloned[2]).toBe(3);
});
test("structuredClone should work with array of doubles", () => {
const input = [1.5, 2.7, 3.14, 0.1 + 0.2];
const cloned = structuredClone(input);
expect(cloned).toEqual(input);
});
test("structuredClone creates independent copy of array", () => {
const input = [1, 2, 3];
const cloned = structuredClone(input);
cloned[0] = 999;
expect(input[0]).toBe(1);
});
test("structuredClone should preserve named properties on arrays", () => {
const input: any = [1, 2, 3];
input.foo = "bar";
const cloned = structuredClone(input);
expect(cloned.foo).toBe("bar");
expect(Array.from(cloned)).toEqual([1, 2, 3]);
});
test("postMessage should work with array fast path", async () => {
const { port1, port2 } = new MessageChannel();
const input = [1, 2, 3, "hello", true];
const { promise, resolve } = Promise.withResolvers();
port2.onmessage = (e: MessageEvent) => resolve(e.data);
port1.postMessage(input);
const result = await promise;
expect(result).toEqual(input);
port1.close();
port2.close();
});
// === Edge case tests ===
test("structuredClone of frozen array should produce a non-frozen clone", () => {
const input = Object.freeze([1, 2, 3]);
const cloned = structuredClone(input);
expect(cloned).toEqual([1, 2, 3]);
expect(Object.isFrozen(cloned)).toBe(false);
cloned[0] = 999;
expect(cloned[0]).toBe(999);
});
test("structuredClone of sealed array should produce a non-sealed clone", () => {
const input = Object.seal([1, 2, 3]);
const cloned = structuredClone(input);
expect(cloned).toEqual([1, 2, 3]);
expect(Object.isSealed(cloned)).toBe(false);
cloned.push(4);
expect(cloned).toEqual([1, 2, 3, 4]);
});
test("structuredClone of array with deleted element (hole via delete)", () => {
const input = [1, 2, 3];
delete (input as any)[1];
const cloned = structuredClone(input);
expect(cloned[0]).toBe(1);
expect(cloned[1]).toBe(undefined);
expect(cloned[2]).toBe(3);
expect(1 in cloned).toBe(false); // holes remain holes after structuredClone
});
test("structuredClone of array with length > actual elements", () => {
const input = [1, 2, 3];
input.length = 6;
const cloned = structuredClone(input);
expect(cloned.length).toBe(6);
expect(cloned[0]).toBe(1);
expect(cloned[1]).toBe(2);
expect(cloned[2]).toBe(3);
expect(cloned[3]).toBe(undefined);
});
test("structuredClone of single element arrays", () => {
expect(structuredClone([42])).toEqual([42]);
expect(structuredClone([3.14])).toEqual([3.14]);
expect(structuredClone(["hello"])).toEqual(["hello"]);
expect(structuredClone([true])).toEqual([true]);
expect(structuredClone([null])).toEqual([null]);
});
test("structuredClone of array with named properties on Int32 array", () => {
const input: any = [1, 2, 3]; // Int32 indexing
input.name = "test";
input.count = 42;
const cloned = structuredClone(input);
expect(cloned.name).toBe("test");
expect(cloned.count).toBe(42);
expect(Array.from(cloned)).toEqual([1, 2, 3]);
});
test("structuredClone of array with named properties on Double array", () => {
const input: any = [1.1, 2.2, 3.3]; // Double indexing
input.label = "doubles";
const cloned = structuredClone(input);
expect(cloned.label).toBe("doubles");
expect(Array.from(cloned)).toEqual([1.1, 2.2, 3.3]);
});
test("structuredClone of array that transitions Int32 to Double", () => {
const input = [1, 2, 3]; // starts as Int32
input.push(4.5); // transitions to Double
const cloned = structuredClone(input);
expect(cloned).toEqual([1, 2, 3, 4.5]);
});
test("structuredClone of array with modified prototype", () => {
const input = [1, 2, 3];
Object.setPrototypeOf(input, {
customMethod() {
return 42;
},
});
const cloned = structuredClone(input);
// Clone should have standard Array prototype, not the custom one
expect(Array.from(cloned)).toEqual([1, 2, 3]);
expect(cloned).toBeInstanceOf(Array);
expect((cloned as any).customMethod).toBeUndefined();
});
test("structuredClone of array with prototype indexed properties and holes", () => {
const proto = Object.create(Array.prototype);
proto[1] = "from proto";
const input = new Array(3);
Object.setPrototypeOf(input, proto);
input[0] = "a";
input[2] = "c";
// structuredClone only copies own properties; prototype values are not included
const cloned = structuredClone(input);
expect(cloned[0]).toBe("a");
expect(1 in cloned).toBe(false); // hole, not "from proto"
expect(cloned[2]).toBe("c");
expect(cloned).toBeInstanceOf(Array);
});
test("postMessage with Int32 array via MessageChannel", async () => {
const { port1, port2 } = new MessageChannel();
const input = [10, 20, 30, 40, 50];
const { promise, resolve } = Promise.withResolvers();
port2.onmessage = (e: MessageEvent) => resolve(e.data);
port1.postMessage(input);
const result = await promise;
expect(result).toEqual(input);
port1.close();
port2.close();
});
test("postMessage with Double array via MessageChannel", async () => {
const { port1, port2 } = new MessageChannel();
const input = [1.1, 2.2, 3.3];
const { promise, resolve } = Promise.withResolvers();
port2.onmessage = (e: MessageEvent) => resolve(e.data);
port1.postMessage(input);
const result = await promise;
expect(result).toEqual(input);
port1.close();
port2.close();
});
test("structuredClone of array multiple times produces independent copies", () => {
const input = [1, 2, 3];
const clones = Array.from({ length: 10 }, () => structuredClone(input));
clones[0][0] = 999;
clones[5][1] = 888;
// All other clones and the original should be unaffected
expect(input).toEqual([1, 2, 3]);
for (let i = 1; i < 10; i++) {
if (i === 5) {
expect(clones[i]).toEqual([1, 888, 3]);
} else {
expect(clones[i]).toEqual([1, 2, 3]);
}
}
});
test("structuredClone of Array subclass loses subclass identity", () => {
class MyArray extends Array {
customProp = "hello";
sum() {
return this.reduce((a: number, b: number) => a + b, 0);
}
}
const input = new MyArray(1, 2, 3);
input.customProp = "world";
const cloned = structuredClone(input);
// structuredClone spec: result is a plain Array, not a subclass
expect(Array.from(cloned)).toEqual([1, 2, 3]);
expect(cloned).toBeInstanceOf(Array);
expect((cloned as any).sum).toBeUndefined();
});
test("structuredClone of array with only undefined values", () => {
const input = [undefined, undefined, undefined];
const cloned = structuredClone(input);
expect(cloned).toEqual([undefined, undefined, undefined]);
expect(cloned.length).toBe(3);
// Ensure they are actual values, not holes
expect(0 in cloned).toBe(true);
expect(1 in cloned).toBe(true);
expect(2 in cloned).toBe(true);
});
test("structuredClone of array with only null values", () => {
const input = [null, null, null];
const cloned = structuredClone(input);
expect(cloned).toEqual([null, null, null]);
});
test("structuredClone of dense double array preserves -0 and NaN", () => {
const input = [-0, NaN, -0, NaN];
const cloned = structuredClone(input);
expect(Object.is(cloned[0], -0)).toBe(true);
expect(cloned[1]).toBeNaN();
expect(Object.is(cloned[2], -0)).toBe(true);
expect(cloned[3]).toBeNaN();
});
test("structuredClone on object with simple properties can exceed JSFinalObject::maxInlineCapacity", () => {
let largeValue = {};
for (let i = 0; i < 100; i++) {