Repository navigation
Keep oversized xwidgets visible with GNU-compatible geometry (#301) - #328
Conversation
|
GUI verification on the release build (
The text area of that window is 480 px wide (502 minus fringes), so both resizes are width overflows and both are cropped to 480 exactly as GNU's |
eval-exec
left a comment
There was a problem hiding this comment.
I reviewed this against issue #301 and the separate GNU Emacs 31.0.90 image/xwidget paths in src/xdisp.c.
Blocking findings
-
Preserve intrinsic xwidget size separately from cropped layout width.
cropped_to_visible_widthoverwritesDisplayMediaReplacement.width. That becomes the only width onFrameGlyph::Xwidget, andcollect_frame_webviewsthen uses it as the native webview content rectangle. A 600px widget cropped to 304px therefore becomes a 304px browser viewport instead of a 600px viewport behind a 304px clip. GNU preserves the model/widget size and narrows only the glyph/visible region. Please model intrinsic content extent, layout advance extent, and visible clip extent separately. -
Use window-local width in the GNU quarter-width predicate.
for_replacementcompares againstright_edge_px / 4, but the caller supplies an absolute frame-space right edge. GNUlast_visible_xis window-local. In a right-hand split, the inflated threshold can chooseLeaveWhole, and the oversized xwidget can still be dropped. The policy should receive typed window-local available width/window width rather than an absolute coordinate. Please add a right-hand split regression. -
Do not apply the xwidget rule generically to all media.
builder.rs:1619-1636applies one rule to images, videos, xwidgets, and surfaces. Issue #301 is specifically about xwidgets. GNU intentionally uses different policies:- image cropping accounts for word wrapping, line-number prefixes, margins, and box geometry;
- xwidget cropping uses the simpler rule.
With
word-wrap, this PR can crop a mid-row image that GNU keeps whole so it can wrap. Video and surface behavior is also unrelated scope expansion. -
The image slice arithmetic is incorrect when margins or box space exist.
visible_width / self.widthuses total glyph width, including horizontal margins/box expansion, to scale the source rectangle. GNU subtracts the cropped pixel count from the image slice after calculating total glyph width. A margined image therefore samples too much source and rescales it. The current test only covers zero margins.
There is also a documentation mix-up: the positive box-line-width paragraph is attached to cropped_to_visible_width rather than with_positive_box_line_width.
Recommended shape
For #301, narrow the immediate policy to DisplayMediaReplacementKind::Xwidget, preserve the intrinsic widget width, calculate cropping with window-local coordinates, and test both a right-hand split and the final native placement (content width = 600, visible width = 304). Image overflow should be a separate typed policy carrying wrap mode, line-number-prefix state, margins, and box geometry.
|
Thanks for the review. Reworked in two commits on top of the original; the combined change is what to read. 1. Intrinsic size vs cropped width. New protocol type 2. Window-local width. 3. Xwidgets only. The policy is now 4. Image slice arithmetic. Removed along with the image path. Doc mix-up. The paragraph is back on Ledger of what is still not GNU (in the enum's doc): Not run here: the Linux wgpu draw paths ( |
e99b4de to
4b65faf
Compare
423ca62 to
67f5af1
Compare
|
Rebased onto current main (5195028) after GitHub flagged the conflict. What changed beyond the rebase itself, so nothing lands here silently:
Checked for semantic drift, not just merge cleanliness: of the 108 upstream commits since the previous base, 15 touch this branch's files; 1382937 was the only real overlap (above). Its skip phase never carries a media replacement, so it cannot reach the RejectOverflowingGlyph crop arm. Run on macOS: cargo fmt --check; cargo clippy --all-targets on display-protocol, layout-engine, display-runtime and renderer-wgpu (the latter two with webview); the four crates' test suites. On this machine main itself fails 26 tests in display-protocol/layout-engine (font-cluster shaping after the freetype removal, the gui_chrome Lisp loads, and glyph_pointer_token_has_small_niche_sized_overhead: Glyph is 104 bytes on main, the bound is 80; GlyphType is sized by AutomaticComposite, so the 8-byte XwidgetContentExtent on the Xwidget variant does not move it). This branch fails exactly that set and nothing else. Not run: the Linux wgpu paths and the WPE build. Left as-is, for the record: the policy seam (for_xwidget, xwidget_advance_cropped_to) still takes bare f32 rather than Px; the pointer hit test's new test sits in the existing inline mod webview_tests; body_render.rs has its own text_area_left from text_x, equal by construction (content_x = text_x + line_number_pixel_width). |
67f5af1 to
8b3156b
Compare
… dropping it
An xwidget (or image, video, surface) wider than the room left on its
row produced no glyph at all: `DisplayRowProgressWriter::push_item`
measured the freshly pushed media glyph against the row's right edge and,
under body text's `RejectOverflowingGlyph` policy, restored the checkpoint
-- unpushing it -- while the covered buffer text was still consumed. The
window showed an empty background, indistinguishable from a page that
failed to load (issue 301; both reported reproductions, 1326 px tall and
1004 px wide, were width overflows of a full-window-width widget).
GNU never declines the glyph. `produce_xwidget_glyph`
(src/xdisp.c:32700-32704) and `produce_image_glyph` (:32582-32598) crop it
when they produce it:
crop = it->pixel_width - (it->last_visible_x - it->current_x);
if (crop > 0 && (it->hpos == 0 || it->pixel_width > it->last_visible_x / 4))
it->pixel_width -= crop;
so a glyph that starts the row, or is wider than a quarter of the visible
width, fits exactly and `display_line` keeps it (:26254-26310); an image
also narrows its slice (:32597). Only a narrower glyph mid-row is left
whole for `display_line` to continue or truncate.
Model that decision as `DisplayMediaReplacementOverflowAction`
(`Fits | CropToVisibleWidth | LeaveWhole`) beside the existing character
overflow actions, give `DisplayMediaReplacement` a
`cropped_to_visible_width` that narrows an image's `ImageSourceRect`
proportionally and leaves other kinds' content to draw-time clipping, and
apply the action in the row writer's body-text path before its fit check.
A cropped glyph now completes with positive width, so the row still
grows to the widget's height and `text_clip_bounds` gives the glyph its
window clip.
`LeaveWhole` keeps today's behaviour for the narrow mid-row case, which
GNU instead continues onto the next row; that divergence is separate from
this issue and is left as it was.
Tests (red first): `layout_frame_rust_crops_an_xwidget_wider_than_its_window_like_gnu`
pins GNU's arithmetic on the TTY test frame (600 px at x=8 in a 312 px row
-> 304), the tall-widget test pins that height is kept and the row clip
bounds it, `media_wider_than_the_remaining_row_is_cropped_by_gnus_rule`
pins the predicate, and the image-slice test pins `slice.width -= crop`.
Closes eval-exec#301.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117NsCB7AbwgdEqduGF5Kww
…the right edge Rework of the previous commit after review (PR 328): the crop was the right idea applied to the wrong things. Three extents, as GNU keeps them. `produce_xwidget_glyph` crops only the glyph's layout advance (`it->pixel_width -= crop`, src/xdisp.c:32577-32579, emacs-31.0.90); the widget keeps `xw->width`/`xw->height`, and `x_draw_xwidget_glyph_string` sizes the native view from that and clips it to the window's text area (src/xwidget.c:2841-2847). The previous commit overwrote `DisplayMediaReplacement.width`, which is also what `collect_frame_webviews` fed the native view as its content rectangle: a 600 px page cropped to a 304 px slot became a 304 px viewport instead of a 600 px viewport behind a 304 px clip. A new protocol type, `XwidgetContentExtent`, now carries the widget's own size from `DisplayMediaReplacementKind::Xwidget` through `GlyphType::Xwidget` to `FrameGlyph::Xwidget`; the glyph's `width` stays the cropped slot (the cursor cell), `clip_rect` stays the text area, and the runtime's placement and pointer hit test read `content` for the view and the clip for what is visible. The Linux wgpu paths draw the content extent and cut it to the clip on all four sides, as the image path already did. Window-local coordinates. GNU's quarter-width predicate compares against `it->last_visible_x`, which is measured from the window's text area (src/dispextern.h:2785-2791); the row writer works in frame-absolute pixels and the previous commit fed those straight in, so in a right-hand split the threshold was about twice what GNU uses and a widget GNU crops was left whole and then dropped. `DisplayRowTextAreaOrigin` travels with `DisplayRowRenderBounds` from the append context (text-area left = content x minus the line-number prefix) to the writer, and `WindowLocalRowExtent` converts once before the rule runs. Xwidgets only. Issue 301 is about xwidgets, and GNU's image rule (src/xdisp.c:32457-32473) is a different one that also weighs word wrap, the line-number prefix and the frame's column width; the previous commit applied the xwidget rule to every media kind and scaled an image's slice by a ratio that included its margins. The policy is now `DisplayXwidgetOverflowAction`, applied only to `DisplayMediaReplacementKind::Xwidget`; images, videos and surfaces are back to the pre-existing behavior, and the image rule is left for its own typed policy. The doc comment that had been re-attached to the crop helper is back on `with_positive_box_line_width`. Tests, red first: - `the_quarter_width_rule_uses_the_windows_own_width_in_a_right_hand_split` and `layout_frame_rust_crops_an_xwidget_in_a_right_hand_split_by_the_windows_width` (a 300 px widget at frame x 1368 in the right window of a 1600 px frame: cropped to 224 with the window-local origin, dropped with the frame-absolute one); - `a_cropped_xwidget_slot_keeps_the_native_content_width_behind_the_clip` (`ResolvedWebViewPlacement` content width 600, visible width 304); - `hit_testing_uses_the_widgets_own_extent_not_the_cropped_slot`; - `cropping_an_xwidgets_advance_keeps_its_content_extent`; - the existing wide/tall engine tests now also assert the content extent. Not run here: the Linux wgpu draw paths (`draw_inline_webkit_views`, `render_frame_content`) are compiled only on Linux; the display-runtime webview tests need the macOS webview build fix from PR 327 to compile on this machine and were run on a branch carrying it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117NsCB7AbwgdEqduGF5Kww
…d ledger the GNU gaps
Findings of an adversarial pre-push review of the previous commit.
The Linux child-frame path (`render_frame_content`, content.rs) drew the
xwidget texture at its content size but never read `clip_rect`, so a
widget cropped at the right edge of a split child frame would have spilled
across the neighbouring window; before the content/slot split that glyph
was dropped, so the spill was new. Both Linux paths now cut the content
extent through `clipped_media_rect`, the helper the image branch already
uses, so the clip arithmetic has one owner. `FrameGlyph::Xwidget::height`
likewise had two writers (`add_xwidget` stored the content height,
`materialize` the glyph's layout height); both now store the content
height, which is what the row promoted anyway.
`DisplayXwidgetOverflowAction`'s doc now ledgers what the port does not
do relative to `produce_xwidget_glyph`: `LeaveWhole` drops the glyph where
`display_line` would continue or truncate it (src/xdisp.c:26411-26432),
the no-room `hpos == 0` case is dropped rather than produced at zero width
(:32605), box line widths are not added before the crop (:32562-32569),
and hscroll's `first_visible_x` is not carried (:3507). A phantom citation
in the tall-widget test (":32703", an R2L box-edge line) is replaced by
the real crop and clip lines, and the `x_draw_xwidget_glyph_string` range
now includes `clip_bottom` (src/xwidget.c:2841-2849).
The right-edge match in the row builder binds the xwidget kind in the
pattern instead of a `matches!` guard. A new engine test,
`layout_frame_rust_measures_the_quarter_width_rule_from_the_text_area_not_the_line_numbers`,
exercises `DisplayRowAppendFrame::text_area_origin` with a non-zero
line-number width end to end: a 200 px widget after 75 cells is cropped
to the row's remainder, and a 195 px one (not wider than a quarter of the
784 px text area, but wider than a quarter of the content area after the
prefix) is left whole, as GNU does.
Not run here: the Linux wgpu draw paths are compiled only on Linux.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117NsCB7AbwgdEqduGF5Kww
CI's cargo fmt jobs failed on one assertion I had edited by hand after the formatting pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117NsCB7AbwgdEqduGF5Kww
… xwidget origin from it Upstream 1382937 (fix(layout): preserve hscroll boundary items) added `DisplayRowAppendSurface::text_area_left` for hscroll truncation. It computes `content_x - line_number_width`, the same edge that `DisplayRowAppendFrame::text_area_origin` computed on its own for the window-local extent an overflowing xwidget is measured against (GNU's `it->current_x`, src/dispextern.h:2785-2791; the line-number prefix is produced as glyphs and counted, `maybe_produce_line_number`, src/xdisp.c:25701). Two definitions of one edge can drift apart. `DisplayRowAppendArea` now owns the computation; the surface delegates to it, and the frame holds the area itself instead of three copied fields, so `content_x`, `text_width`, `line_number_width` and the text-area origin are all read from the one value. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117NsCB7AbwgdEqduGF5Kww
…ization too
`XwidgetContentExtent::new` refuses a zero, negative or non-finite
dimension, but the derived `Deserialize` built the struct directly, so a
serialized frame could carry an extent the constructor would never
produce. Deserialize through `#[serde(try_from)]` and the one
constructor; the serialized shape is unchanged. Red-first test:
`{"width_px":0.0,"height_px":40.0}` deserialized before, is refused now.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117NsCB7AbwgdEqduGF5Kww
…d fix two citations The ledger on `DisplayXwidgetOverflowAction` now lists what else the port leaves out of the GNU function it is taken from (emacs-31.0.90): - a widget straddling `first_visible_x` under hscroll, which GNU keeps with a negative `row->x` and this port's skip phase consumes as a plain glyph before the rule can see it; - `it->hpos == 0` counting only visible glyphs (src/xdisp.c:25705-25706), which `at_row_start` matches only because the skip phase writes nothing before the first visible glyph; - the even ascent/descent split (src/xdisp.c:32546-32547). Citations corrected against the mirror: the box-width lines are 32557-32571, and the `display_line` continuation is the test at 26221-26224 plus the restore-and-continue branch at 26416-26434. The line-number crop test's comment counted a four-cell prefix; it is the six-cell `lnum_width + 2` gutter, which is how the pen reaches 1456. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117NsCB7AbwgdEqduGF5Kww
Keep intrinsic content, cropped layout advance, and visible clipping in one coordinate-space-branded protocol value. Translate and resolve that value once for native placement, GPU composition, and pointer input so those consumers cannot reconstruct incompatible rectangles.\n\nMake GNU's xwidget-only overflow decision consume and return a validated XwidgetLayoutAdvance, and expose crop application only through a DisplayXwidgetReplacement capability. This prevents image, video, and surface replacements from entering the xwidget policy.
Keep the mutable native state submitted with each pending WebView creation. Before publishing Ready, apply any model-size or navigation changes that arrived while the platform was creating the view. This preserves GNU Emacs's immediate xwidget navigation contract across asynchronous backends and prevents the first URI from being lost by WPE.
Treat wpe_buffer_import_to_pixels as a borrowed transfer instead of unreferencing WebKit's cached GBytes. Encode the callback borrow separately from accepted-buffer ownership, validate foreign row metadata before slicing or allocating, and copy padded rows directly into packed BGRA pixels.
Create a deterministic oversized WebKit xwidget and assert the GNU-compatible split between intrinsic content extent and cropped row advance. Require the page's magenta pixels in the real wgpu surface readback so the regression covers redisplay, async native creation, WPE capture, clipping, and composition together.
2ef002e to
809b1fb
Compare
Summary
Closes #301. An xwidget wider than the room remaining in a row was produced, then removed by the body row's generic overflow rollback while its source character was still consumed. The result contained no
FrameGlyph::Xwidget, so every backend correctly hid the view and showed only the buffer background.GNU Emacs handles xwidgets specially in
produce_xwidget_glyph: it may crop the glyph's layout advance to the remaining row width, whilex_draw_xwidget_glyph_stringkeeps the native widget at its intrinsic size and clips its presentation to the window text area. Narrow mid-row xwidgets are left whole;display_lineretains them in truncating rows and the presentation clip limits visibility.Design
DisplayXwidgetOverflowActionis xwidget-only. Images, videos, and shader surfaces retain their existing behavior; GNU's distinct image policy is not generalized here.XwidgetPresentationGeometry<Space>carries three separate concepts through layout, frame materialization, child-frame translation, rendering, native placement, and pointer hit-testing:XwidgetContentExtent;XwidgetLayoutAdvance;WindowLocalRowExtentconverts frame coordinates once, validates finite ordered geometry, and implements GNU's window-local quarter-width predicate. Invalid intervals cannot reach the crop policy.DisplayItemRightEdgeAdmission::PreserveWholeXwidgetis the typed, exhaustive exception that permits only GNU'sLeaveWholexwidget case past the row boundary. Frame materialization preserves that whole advance instead of replacing it with the visible slice.WebView reliability found by the strict GUI test
The end-to-end test initially exposed two independent Linux backend defects that prevented reliable pixel verification:
Ready;GBytes, causing use-after-free behavior on later callbacks.The lifecycle now converges through typed pending/desired state, and the WPE boundary uses borrowed wrappers plus validated software pixel layouts. The incorrect unref is no longer available in the generated binding surface.
Tests
LeaveWholecase, tall clipping, and invalid row geometry.data:page in an xwidget twice the live text-area size, asserts the semantic glyph geometry, and verifies actual pixels in the PNG. It passed 5 consecutive Wayland runs and 3 consecutive X11 runs.neomacs-webviewtests pass.Known GNU gaps are documented next to the policy: zero-room glyphs, positive box-line widths, horizontal scrolling across
first_visible_x, and GNU's ascent/descent split.