gpui-pre-web 0.3.2

Zed's `gpui_web` crate (gpui-pre snapshot of zed@801c087)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
//! The hidden `<textarea>` that connects browser IMEs to GPUI.
//!
//! IMEs (software keyboards, composition engines) decide what backspace,
//! autocorrect, and suggestions mean by inspecting the focused editable
//! element's value and selection. This module owns that element and keeps a
//! window of the document's text mirrored into it, so IME edits arrive as
//! interpretable events instead of operations against an empty field.
//!
//! The element's value and selection are only ever written by [`sync`],
//! reached through [`ImeMirror::schedule_sync`]: every write is observed by
//! the IME and makes the browser restart the IME's input connection, so
//! writes must be coalesced to at most one per browser event-loop turn,
//! landing only after the current gesture's events have all dispatched.
//! Keeping the element and its write path private to this module makes that
//! discipline a compile-time guarantee rather than a convention.

use std::cell::{Cell, RefCell};
use std::rc::Rc;

use gpui::{Autocapitalize, TextInputAction, TextInputConfiguration};
use wasm_bindgen::JsCast;

use crate::window::WebWindowInner;

/// UTF-16 code units of document text mirrored on each side of the
/// selection.
///
/// There is no demand-driven protocol to size this against: an IME's
/// context requests (e.g. Android's `getTextBeforeCursor`) are answered by
/// the browser from the element's current state, invisibly to the page, so
/// the window must be provisioned ahead of time. The lower bound is what
/// IMEs actually read — sentence-scale context, on the order of a hundred
/// units. The upper bound is that the window size is a per-keystroke cost,
/// not a one-time cost: every imported edit diffs the element's full value
/// against the stored mirror, and every rebuild writes the full window into
/// the element and re-snapshots it across the browser–IME boundary, all on
/// the main thread between frames. An IME that would read further than the
/// window simply sees text truncated at the window's edge — the same thing
/// it sees near the start of any short field — so oversizing buys nothing.
const CONTEXT_CHARS: usize = 512;

/// The element is left alone until the selection gets this close to the
/// mirrored window's edge (unless it desynchronizes outright). Must exceed
/// the span an IME plausibly reads or edits around the caret within a
/// single gesture (a long word plus autocorrect lookback); beyond that,
/// recentering lazily is strictly better, because every recenter is an
/// element write and therefore an IME restart.
const MIN_EDGE_CHARS: usize = 64;

/// The hidden `<textarea>` IMEs edit, plus the bookkeeping that relates it
/// to the document.
///
/// The element and every value/selection write on it are private to this
/// module; other code interacts through read accessors, focus and
/// read-only control, and [`ImeMirror::schedule_sync`].
pub(crate) struct ImeMirror {
    element: web_sys::HtmlTextAreaElement,
    /// The mirror text most recently synced to (or observed in) the hidden
    /// element. `input` events diff the element's new value against this to
    /// recover what edit the IME performed.
    text: RefCell<String>,
    /// The element's selection (in element-local UTF-16 offsets) as of the
    /// last sync or imported edit. Gives IME edits their position relative
    /// to the caret; deliberately element-local, never document
    /// coordinates, which go stale in a collaborative document.
    selection: Cell<(u32, u32)>,
    /// Document offset where the mirror window starts — as a *hint only*.
    /// It is never trusted for edits: every use first re-verifies the
    /// stored window text against the document at this alignment, so a
    /// stale hint costs a window rebuild instead of a misplaced edit.
    window_hint: Cell<usize>,
    /// Whether a coalesced sync is already scheduled for the next task.
    /// Multiple sync requests within one gesture must collapse into a
    /// single element write after the gesture: keyboards sample the field
    /// between writes, and a mid-gesture barrage desynchronizes their word
    /// model.
    sync_scheduled: Cell<bool>,
    /// Whether the `selectionchange` import saw an element selection move
    /// it could not apply (the app selection changed underneath). Sync
    /// normally defers to a pending import when the element's live
    /// selection has moved; a rejected import means no import is coming,
    /// so the next sync must reassert the app's state instead of waiting.
    selection_import_rejected: Cell<bool>,
}

/// Whether the device's primary pointer is coarse (a touch screen). The
/// distinction drives virtual-keyboard policy: touch-first browsers summon
/// the keyboard for any focused editable element on a user gesture.
fn primary_pointer_is_coarse() -> bool {
    web_sys::window()
        .and_then(|window| window.match_media("(pointer: coarse)").ok().flatten())
        .is_some_and(|media_query_list| media_query_list.matches())
}

impl ImeMirror {
    pub(crate) fn new(
        document: &web_sys::Document,
        body: &web_sys::HtmlElement,
    ) -> anyhow::Result<Self> {
        // A textarea rather than an input: single-line inputs silently strip
        // newlines from assigned values, which would make the mirror text
        // disagree with what was written into it.
        let element: web_sys::HtmlTextAreaElement = document
            .create_element("textarea")
            .map_err(|e| anyhow::anyhow!("Failed to create textarea element: {e:?}"))?
            .dyn_into()
            .map_err(|e| anyhow::anyhow!("Created element is not a textarea: {e:?}"))?;
        let style = element.style();
        style.set_property("position", "fixed").ok();
        style.set_property("top", "0").ok();
        style.set_property("left", "0").ok();
        style.set_property("width", "1px").ok();
        style.set_property("height", "1px").ok();
        style.set_property("opacity", "0").ok();
        // Android Chrome zooms the visual viewport onto a focused text input
        // whose font is smaller than 16px; with page zoom disabled the user
        // can never zoom back out, so keep the hidden IME input at 16px.
        style.set_property("font-size", "16px").ok();
        body.append_child(&element)
            .map_err(|e| anyhow::anyhow!("Failed to append input to body: {e:?}"))?;
        element.focus().ok();
        // The element must stay focused to receive hardware-key and IME
        // events, but on touch-first devices a focused *editable* element
        // invites the browser to summon the virtual keyboard on the next
        // user gesture — including a scroll. Start read-only there; only a
        // recognized tap on text input lifts it (`sync_virtual_keyboard`).
        if primary_pointer_is_coarse() {
            element.set_read_only(true);
        }

        let this = Self {
            element,
            text: RefCell::new(String::new()),
            selection: Cell::new((0, 0)),
            window_hint: Cell::new(0),
            sync_scheduled: Cell::new(false),
            selection_import_rejected: Cell::new(false),
        };
        // Until an input handler asks otherwise, the element is an IME
        // conduit, not a form field: browser-side text assistance would
        // mutate it behind the app's back.
        this.apply_configuration(&TextInputConfiguration::default());
        Ok(this)
    }

    /// Maps a [`TextInputConfiguration`] onto the element's text-assistance
    /// attributes. Callers must only invoke this on actual configuration
    /// changes (GPUI diffs before forwarding): mutating the focused element
    /// can restart the IME's input connection.
    pub(crate) fn apply_configuration(&self, configuration: &TextInputConfiguration) {
        let element: &web_sys::Element = self.element.as_ref();
        self.element.set_spellcheck(configuration.suggestions);
        let on_off = |enabled: bool| if enabled { "on" } else { "off" };
        element
            .set_attribute("autocomplete", on_off(configuration.suggestions))
            .ok();
        element
            .set_attribute("autocorrect", on_off(configuration.autocorrect))
            .ok();
        element
            .set_attribute(
                "autocapitalize",
                match configuration.autocapitalize {
                    Autocapitalize::None => "off",
                    Autocapitalize::Words => "words",
                    Autocapitalize::Sentences => "sentences",
                    Autocapitalize::Characters => "characters",
                },
            )
            .ok();
        let enter_key_hint = match configuration.input_action {
            TextInputAction::Unspecified => None,
            TextInputAction::Enter => Some("enter"),
            TextInputAction::Done => Some("done"),
            TextInputAction::Go => Some("go"),
            TextInputAction::Next => Some("next"),
            TextInputAction::Previous => Some("previous"),
            TextInputAction::Search => Some("search"),
            TextInputAction::Send => Some("send"),
        };
        match enter_key_hint {
            Some(hint) => element.set_attribute("enterkeyhint", hint).ok(),
            None => element.remove_attribute("enterkeyhint").ok(),
        };
    }

    pub(crate) fn event_target(&self) -> &web_sys::EventTarget {
        self.element.as_ref()
    }

    pub(crate) fn focus(&self) {
        self.element.focus().ok();
    }

    pub(crate) fn is_focused(&self) -> bool {
        let element: &web_sys::Element = self.element.as_ref();
        web_sys::window()
            .and_then(|window| window.document())
            .and_then(|document| document.active_element())
            .is_some_and(|active| &active == element)
    }

    pub(crate) fn blur(&self) {
        self.element.blur().ok();
    }

    pub(crate) fn read_only(&self) -> bool {
        self.element.read_only()
    }

    pub(crate) fn set_read_only(&self, read_only: bool) {
        self.element.set_read_only(read_only);
    }

    pub(crate) fn remove(&self) {
        let element: &web_sys::Element = self.element.as_ref();
        element.remove();
    }

    pub(crate) fn value(&self) -> String {
        self.element.value()
    }

    pub(crate) fn selection_start(&self) -> Option<u32> {
        self.element.selection_start().ok().flatten()
    }

    pub(crate) fn element_selection_end(&self) -> Option<u32> {
        self.element.selection_end().ok().flatten()
    }

    pub(crate) fn stored_text(&self) -> String {
        self.text.borrow().clone()
    }

    pub(crate) fn stored_selection(&self) -> (u32, u32) {
        self.selection.get()
    }

    /// Adopts the element's current value and selection as the mirror
    /// baseline without writing to the element. Used when the browser
    /// itself applied an edit (an imported IME edit, a composition commit):
    /// the element is already what the IME expects, and echoing a write
    /// back would restart the IME mid-gesture.
    pub(crate) fn adopt_element_state(&self) {
        *self.text.borrow_mut() = self.element.value();
        let selection_start = self.selection_start().unwrap_or(0);
        let selection_end = self
            .element
            .selection_end()
            .ok()
            .flatten()
            .unwrap_or(selection_start);
        self.selection.set((selection_start, selection_end));
    }

    /// Records that an element selection move could not be imported, so
    /// the next sync reasserts the app's state rather than deferring to an
    /// import that is no longer coming.
    pub(crate) fn reject_selection_import(&self) {
        self.selection_import_rejected.set(true);
    }

    /// Schedules a coalesced sync of the mirror for the next task.
    ///
    /// Event handlers must not write to the mirror element mid-gesture:
    /// every write (value, selection) is observed by the IME, and a
    /// sequence of writes inside one gesture desynchronizes its model of
    /// the field (every native-behaving reference — a plain textarea —
    /// performs at most one such change per gesture). Deferring to a
    /// zero-delay timeout coalesces all sync requests from one gesture into
    /// a single write that lands after the browser has finished processing
    /// the gesture's events.
    ///
    /// `sync` is deliberately nested here so that scheduling is the only
    /// way to reach it: a direct synchronous call would reintroduce the
    /// mid-gesture writes this indirection exists to prevent.
    pub(crate) fn schedule_sync(window: &Rc<WebWindowInner>) {
        if window.ime_mirror.sync_scheduled.replace(true) {
            return;
        }
        let closure = wasm_bindgen::closure::Closure::once_into_js({
            let window = Rc::clone(window);
            move || {
                window.ime_mirror.sync_scheduled.set(false);
                sync(&window);
            }
        });
        window
            .browser_window
            .set_timeout_with_callback(closure.unchecked_ref())
            .ok();

        /// Mirrors the text surrounding the selection into the hidden
        /// element.
        ///
        /// With an empty element, Gboard deletes against its private buffer
        /// (the keypress reaches the page only as an `"Unidentified"`
        /// placeholder) and its suggestion strip has no context. Mirroring
        /// a window of real text makes those operations arrive as
        /// interpretable `beforeinput` events.
        ///
        /// All offsets are UTF-16 code units on both sides: GPUI's
        /// input-handler protocol and JavaScript string indexing agree by
        /// construction.
        ///
        /// Writing to the element is a last resort: any rewrite of its
        /// value or selection makes the browser restart the IME's input
        /// connection, which resets the keyboard's state — fatal in the
        /// middle of a keyboard's multi-step edit sequence (suggestion
        /// picks arrive as delete-then-insert pairs). After an imported
        /// edit, the element already *is* a faithful — if off-center —
        /// window of the document, so this first verifies the element
        /// against the document at its current alignment and skips every
        /// write while that holds. The window is rebuilt only when the app
        /// changed independently (caret moved by tap or keybinding, remote
        /// edit inside the window) or the selection drifted too close to
        /// the window's edge to give the IME context.
        fn sync(window: &WebWindowInner) {
            if window.is_composing.get() {
                return;
            }
            let mirror = &window.ime_mirror;
            // A live element selection that differs from the stored baseline
            // while the value still matches is an IME-driven selection move
            // whose `selectionchange` import hasn't dispatched yet (the event
            // is asynchronous, and this sync may run first). The element owns
            // the selection until that import runs: writing now would clobber
            // an in-progress gesture, e.g. Android's slide-on-backspace
            // growing its selection. The import reconciles the two sides and
            // schedules a fresh sync when it cannot adopt the move.
            if !mirror.selection_import_rejected.replace(false)
                && *mirror.text.borrow() == mirror.element.value()
            {
                let live_start = mirror.selection_start().unwrap_or(0);
                let live_end = mirror.element_selection_end().unwrap_or(live_start);
                if (live_start, live_end) != mirror.selection.get() {
                    return;
                }
            }
            let selection = window
                .with_input_handler(|handler| handler.selected_text_range(false))
                .flatten();
            let Some(selection) = selection else {
                if !mirror.text.borrow().is_empty() {
                    mirror.element.set_value("");
                    mirror.text.borrow_mut().clear();
                }
                mirror.selection.set((0, 0));
                return;
            };
            // The mirrored window never crosses the handler's editable
            // range: everything in the element is reachable by multi-step
            // IME edit gestures (word deletion, autocorrect rewrites), so
            // text outside the range must not be mirrored at all. The IME
            // sees the range's edges as the field's edges.
            let editable_range = window
                .with_input_handler(|handler| handler.text_input_editable_range())
                .flatten();

            if is_consistent(
                window,
                &selection.range,
                editable_range.as_ref(),
                MIN_EDGE_CHARS,
            ) {
                return;
            }

            // A caret move within the existing window (a tap into nearby
            // text) must update only the element's selection, like a native
            // tap in a plain textarea. Rewriting the value restarts the IME
            // connection, which desynchronizes the keyboard's word model
            // right when it is about to act on the tapped word.
            if move_selection_within_window(
                window,
                &selection.range,
                editable_range.as_ref(),
                MIN_EDGE_CHARS,
            ) {
                return;
            }

            let mut window_range = selection.range.start.saturating_sub(CONTEXT_CHARS)
                ..selection.range.end + CONTEXT_CHARS;
            if let Some(editable_range) = &editable_range {
                window_range.start = window_range.start.max(editable_range.start);
                window_range.end = window_range
                    .end
                    .min(editable_range.end)
                    .max(window_range.start);
            }
            let mut adjusted = None;
            let text = window
                .with_input_handler(|handler| {
                    handler.text_for_range(window_range.clone(), &mut adjusted)
                })
                .flatten()
                .unwrap_or_default();
            let window_start = adjusted.unwrap_or(window_range).start;

            if *mirror.text.borrow() != text || mirror.element.value() != text {
                mirror.element.set_value(&text);
                *mirror.text.borrow_mut() = text;
            }

            mirror.window_hint.set(window_start);
            let selection_start = selection.range.start.saturating_sub(window_start) as u32;
            let selection_end = selection.range.end.saturating_sub(window_start) as u32;
            if mirror.element.selection_start().ok().flatten() != Some(selection_start)
                || mirror.element.selection_end().ok().flatten() != Some(selection_end)
            {
                mirror
                    .element
                    .set_selection_range(selection_start, selection_end)
                    .ok();
            }
            // Read the selection back rather than trusting the computed
            // values: the browser clamps out-of-bounds positions, and a
            // stored selection the element doesn't actually have would
            // corrupt the next diff.
            let actual_start = mirror.element.selection_start().ok().flatten();
            let actual_end = mirror.element.selection_end().ok().flatten();
            mirror.selection.set((
                actual_start.unwrap_or(selection_start),
                actual_end.unwrap_or(selection_end),
            ));
        }
    }
}

/// Attempts to represent a changed app selection as a pure element
/// selection move within the existing mirror window.
///
/// The stored window-start hint is re-verified textually against the
/// document before use, so a stale hint (remote edit, any drift) fails
/// verification and falls through to a full window rebuild rather than
/// mispositioning the selection.
fn move_selection_within_window(
    window: &WebWindowInner,
    app_selection: &std::ops::Range<usize>,
    editable_range: Option<&std::ops::Range<usize>>,
    min_edge: usize,
) -> bool {
    let mirror = &window.ime_mirror;
    let stored_text = mirror.text.borrow().clone();
    let stored_length = stored_text.encode_utf16().count();
    if stored_length == 0 || mirror.element.value() != stored_text {
        return false;
    }
    let window_start = mirror.window_hint.get();

    // The new selection must sit inside the window with enough context
    // on both sides — except where the window is pinned to a boundary (of
    // the document or of the editable range), where less context is all
    // the context there is. This is the common case: a chat thread's
    // caret usually sits at the end of the document, where the window has
    // no right margin at all.
    // A window that leaks outside the editable range mirrors text the IME
    // must not reach, however consistent it is.
    if let Some(range) = editable_range
        && (window_start < range.start || window_start + stored_length > range.end)
    {
        return false;
    }
    let left_boundary = editable_range.map_or(0, |range| range.start);
    let Some(selection_start) = app_selection.start.checked_sub(window_start) else {
        return false;
    };
    let selection_end = selection_start + (app_selection.end - app_selection.start);
    if selection_end > stored_length {
        return false;
    }
    if selection_start < min_edge && window_start > left_boundary {
        return false;
    }

    // Verify the hint: the stored window text must still equal the
    // document at this alignment. Asking for one unit extra also
    // determines whether the window reaches the document's end, which
    // excuses a missing right margin, as does reaching the editable
    // range's end.
    let mut adjusted = None;
    let document_text = window
        .with_input_handler(|handler| {
            handler.text_for_range(
                window_start..window_start + stored_length + 1,
                &mut adjusted,
            )
        })
        .flatten()
        .unwrap_or_default();
    let document_text_length = document_text.encode_utf16().count();
    let window_at_right_boundary = document_text_length == stored_length
        || editable_range.is_some_and(|range| window_start + stored_length >= range.end);
    if selection_end + min_edge > stored_length && !window_at_right_boundary {
        return false;
    }
    if !document_text.starts_with(stored_text.as_str()) || document_text_length > stored_length + 1
    {
        return false;
    }

    mirror
        .element
        .set_selection_range(selection_start as u32, selection_end as u32)
        .ok();
    let actual_start = mirror.element.selection_start().ok().flatten();
    let actual_end = mirror.element.selection_end().ok().flatten();
    if actual_start != Some(selection_start as u32) || actual_end != Some(selection_end as u32) {
        return false;
    }
    mirror
        .selection
        .set((selection_start as u32, selection_end as u32));
    true
}

/// Whether the hidden element, at its current window alignment, is still
/// an accurate mirror of the document around the app selection with
/// enough context on both sides. When this holds, a sync must not touch
/// the element (see [`sync`] on why writes are harmful).
fn is_consistent(
    window: &WebWindowInner,
    app_selection: &std::ops::Range<usize>,
    editable_range: Option<&std::ops::Range<usize>>,
    min_edge: usize,
) -> bool {
    let mirror = &window.ime_mirror;
    let (element_selection_start, element_selection_end) = mirror.selection.get();
    let element_selection_start = element_selection_start as usize;
    let element_selection_end = element_selection_end as usize;
    let stored_text = mirror.text.borrow().clone();
    let stored_length = stored_text.encode_utf16().count();

    if stored_length == 0 {
        return false;
    }
    // The element's real selection must match what we believe it is.
    if mirror.element.selection_start().ok().flatten() != Some(element_selection_start as u32)
        || mirror.element.selection_end().ok().flatten() != Some(element_selection_end as u32)
    {
        return false;
    }
    // Enough context on both sides of the selection, unless the window
    // is pinned to a boundary of the document or of the editable range
    // (start of window at the left boundary, or window end at the right
    // boundary — the document's approximated by the stored window being
    // shorter than requested on that side).
    let app_window_start = match app_selection.start.checked_sub(element_selection_start) {
        Some(start) => start,
        None => return false,
    };
    // A window that leaks outside the editable range mirrors text the IME
    // must not reach, however consistent it is.
    if let Some(range) = editable_range
        && (app_window_start < range.start || app_window_start + stored_length > range.end)
    {
        return false;
    }
    let left_boundary = editable_range.map_or(0, |range| range.start);
    let has_left_context = element_selection_start >= min_edge || app_window_start <= left_boundary;
    let right_context = stored_length.saturating_sub(element_selection_end);
    if !has_left_context || right_context < min_edge {
        let window_end = app_window_start + stored_length;
        let at_right_boundary = if let Some(range) = editable_range {
            window_end >= range.end
        } else {
            // Verify the window genuinely reaches the end of the document
            // by asking for one unit past the stored window.
            let mut adjusted = None;
            window
                .with_input_handler(|handler| {
                    handler.text_for_range(window_end..window_end + 1, &mut adjusted)
                })
                .flatten()
                .unwrap_or_default()
                .is_empty()
        };
        if !has_left_context || !at_right_boundary {
            return false;
        }
    }
    // The stored window must still equal the document at this alignment
    // (a remote edit inside the window invalidates it), and the element
    // must still hold exactly the stored text.
    let mut adjusted = None;
    let document_text = window
        .with_input_handler(|handler| {
            handler.text_for_range(
                app_window_start..app_window_start + stored_length,
                &mut adjusted,
            )
        })
        .flatten()
        .unwrap_or_default();
    if document_text != stored_text {
        return false;
    }
    if mirror.element.value() != stored_text {
        return false;
    }
    // The element selection corresponds to the app selection end too?
    app_selection.end.checked_sub(app_window_start) == Some(element_selection_end)
}