We don't need to pass the filter and sort to the render function from
all these locations, the callers always use values from the same
inputs. Instead, just use the values directly from cached references
the input elements.
This fixes the class filter not being applied correctly after a refresh
autofills the old value back into it.
This adds a stack trace to the JSON output from GC graph dumps which
is shown in a default-collapsed tray on the right side of the graph
explorer. When a stack pointer root is selected, the stack frame it
originated from is highlighted in the tray.
Remove `word-break: break-all` and instead insert word breaks before
each transition from non-word to word characters. As an example, with
breaks represented by vertical bars:
Foo<|Bar>::|baz()
Thus, when wrapping a line, it'll break on whitespace, or on characters
indicating that there is more to follow.
Match the C++ pipeline's block creation order by creating end_block
and update_block before evaluating the for-of RHS expression. This
ensures loop structure blocks get lower block numbers than blocks
created during RHS evaluation (e.g. from conditional expressions),
producing the same block layout as C++.
This adds a test case where the for-of iterable is a sequence
expression containing a conditional expression. The C++ pipeline
creates loop blocks before evaluating the iterable, giving them lower
block numbers, while the Rust pipeline evaluates the iterable first.
Move the self-move detection before the TDZ check in
emit_set_variable, matching C++ which returns early for self-moves
without emitting anything. A self-move only happens in compound and
logical assignments where the LHS is a local variable, and the LHS
was already read with a TDZ check, making the write-back's TDZ check
redundant.
This adds a test case for compound assignment to a variable that was
initialized via a let destructuring pattern. The Rust pipeline emits a
redundant ThrowIfTDZ after the compound assignment because the variable
is not tracked as initialized after destructuring.
When the LHS of an assignment is a destructuring pattern, don't pass
preferred_dst when generating the RHS expression. This matches the C++
pipeline which always allocates a fresh register for the RHS value.
The difference was visible when a destructuring assignment appeared
inside a logical AND expression: the C++ pipeline would allocate the
RHS into a fresh register and then copy it to the AND result register,
while the Rust pipeline would evaluate the RHS directly into the AND
result register, omitting the copy.
This adds a test case for array destructuring assignment inside a
logical AND expression, e.g. `t && ([a, b] = t(e))`. The C++ pipeline
allocates a separate register for the RHS and copies it to the result
register after destructuring, while the Rust pipeline evaluates the
RHS directly into the preferred destination, omitting the copy.
When a for-of loop creates a per-iteration lexical environment for
let/const declarations, push a LeaveLexicalEnvironment boundary so
that continue/break/return properly restores the lexical environment.
The for-in codegen already did this but for-of was missing it.
Test that continue inside a for-of loop body properly restores the
lexical environment when the for-of creates a per-iteration scope
for the loop variable.
When a switch statement creates a lexical environment for block-scoped
declarations (let/const), push a LeaveLexicalEnvironment boundary so
that perform_needed_unwinds correctly emits SetLexicalEnvironment
before Return/Throw instructions inside the switch body.
Test that returning from inside a switch statement that has a lexical
environment (for const/let declarations) properly emits
SetLexicalEnvironment to restore the parent environment before each
Return instruction.
Add PrivateIdentifier handling in generate_update_expression so that
postfix/prefix increment/decrement on private members (e.g. this.#c++)
correctly emits GetPrivateById, PostfixIncrement, and PutPrivateById.
Previously this fell through to an empty fallback that returned an
uninitialized register.
Add missing perform_needed_unwinds() calls before Throw instructions
in four places:
- Await continuation throw path
- Yield* throw_value_block
- Yield* iterator missing throw method
- Invalid left-hand side in assignment helper
This matches the C++ pipeline which calls perform_needed_unwinds<Throw>
before every Throw to restore lexical environments when throwing out of
scopes like with statements.
Pass the fully computed var_environment_bindings_count from the SFD
metadata to the codegen, instead of using the raw
non_local_var_count_for_parameter_expressions. The full count includes
additional bindings from Annex B function hoisting and strict-mode
lexical declarations that share the var environment.
Match the C++ pipeline behavior where PostfixIncrement/PostfixDecrement
always writes to a freshly allocated register. The Rust pipeline was
using the caller's preferred_dst, producing one fewer Mov instruction
but causing bytecode mismatches.
When break/continue trampolines through nested finally blocks, we need
to restore the unwind handler to the level that was active before each
finally context was pushed. Without this, trampoline blocks created for
inner finally dispatch incorrectly inherited the innermost exception
handler, causing exception handler range mismatches with C++.
Store the current_unwind_handler in each FinallyContext at push time,
and restore it in emit_trampoline_through_finally when popping through.
- Don't emit dead code after Throw for UsingDeclaration in for-of
LHS assignment. Guard loop body generation with
is_current_block_terminated() in both for-in and for-of.
- Add LeaveLexicalEnvironment boundary tracking to for-loop
per-iteration environment management, so that perform_needed_unwinds
correctly emits SetLexicalEnvironment before Throw instructions
inside the loop body.
The C++ pipeline has an optimization that uses the GetLengthWithThis
instruction instead of GetByIdWithThis when accessing the "length"
property. Add the same optimization to the Rust pipeline by
introducing an emit_get_by_id_with_this helper that checks for the
"length" property name and emits the optimized instruction.
Also update emit_get_by_value_with_this to use GetLengthWithThis
when the computed property is a constant "length" string.
Per spec, computed property key expressions should be evaluated
before calling ResolveSuperBase. Fix the Rust codegen for tagged
template literals with super member expressions to match the C++
pipeline's correct evaluation order.
Per spec, the property key expression should be evaluated before
calling ResolveSuperBase. Fix the Rust codegen to match the C++
pipeline's correct evaluation order.
When the left-hand side of an assignment, update, or for-in loop is
invalid (e.g. `foo() = "bar"`), the bytecode generator emits a Throw
instruction. Previously, it would also create a dead basic block after
the Throw, resulting in unreachable instructions in the output.
Fix this by returning early from the relevant codegen paths after
emitting the Throw, and by guarding for-in/for-of body generation
with an is_current_block_terminated() check.
ClassFieldInitializerStatement is a synthetic AST node that does not
support dump_to_string(). Guard the pipeline comparison code against
this case to avoid a VERIFY failure when comparing class field
initializer functions.
Fix two bugs in the Rust bytecode codegen:
1. has_parameter_expressions incorrectly treated any destructuring
parameter as a "parameter expression", when it should only do so
for patterns that contain expressions (defaults or computed keys).
This caused an unnecessary CreateLexicalEnvironment for simple
destructuring like `function f({a, b}) {}`. The same bug existed
in both codegen.rs and lib.rs (SFD metadata computation).
2. emit_set_variable used is_local_lexically_declared(index) for
argument locals, but that function indexes into the local_variables
array using the argument's index, checking the wrong variable.
This caused spurious ThrowIfTDZ instructions when assigning to
function arguments that happened to share an index with an
uninitialized let/const variable.
LIBJS_COMPARE_PIPELINES previously only compared top-level
script/eval/module bytecodes. Function bodies are compiled lazily
via compile_function(), and that path had no comparison at all.
Fix this by pairing each Rust-compiled SharedFunctionInstanceData
with its C++ counterpart during top-level compilation. When a
function is later lazily compiled, compile_function() runs both
pipelines and compares the bytecodes (crashing on mismatch, same
as the top-level comparisons). The pairing is done recursively so
nested functions are also covered.
This change means the right click context menu is displayed in the right
place when clicking inside an iframe on a scrolled page, including when
the iframe has CSS transforms applied to it.
We were incorrectly calling presentMetalFrame while force-cpu-painting
was enabled, so updateLayer never had any chance to be called.
However, in addition to that, it turned out that there's a subclass of
CALayer that is used by NSView by default which cannot be created in a
makeBackingLayer override, so updateLayer was never going to be called
anyway. In order to update the layer at the requisite time, a new
LadybirdWebViewContentLayer had to be added to simply call the delegate
on our view.
Setting the contentsRect on the layer was also incorrect, and caused
the contents to shift and stretch until WebContent's backing stores
shrank to fit the new view size.
As file:// URLs are considered opaque origins by default, we need
to special case them in the allowlist as any opaque origin will
not be matched in the allow list.
I intended to do this in: 1be69479a
However at that time I was switching back and forward between the
two settings, and must have accidentally left in that option.
The change in test expectation here is an exception being thrown
from a different point.
There have been a few times I have wanted to add a debug log in
one of these functions, but currently that causes a massive rebuild.
Let's just move these out of line.