etherpad-lite/src/static/js
John McLear 78943da92b
fix(editor): tag inserts with clientVars author until userAuthor propagates (Firefox authorship flake) (#7910)
* fix(editor): tag inserts with clientVars author until userAuthor propagates

Fixes the intermittent Firefox `clear_authorship_color` flake ("clear authorship
colors can be undone to restore author colors").

Root cause: the inner editor's `thisAuthor` starts '' and is only populated when
collab_client's `setProperty('userAuthor', userId)` reaches the inner frame —
that call is queued by the outer ace wrapper via pendingInit until the iframe
loads, so it is applied asynchronously. Under Firefox timing the first keystrokes
can beat it, so freshly typed text is tagged author=''. An empty author
canonicalizes to no-author, producing an unattributed insert (`Z:1>5+5$Hello`)
that the server's pad-corruption guard rejects ("submitted an insert without an
author attribute"), dropping the whole USER_CHANGES and losing authorship. Undo
then cannot restore an author color and the test sees `<span class="">`.

Fix: a `getLocalAuthor()` helper that falls back to `clientVars.userId` (the same
author id, available synchronously in the inner frame — already used as `myId`
elsewhere) whenever `thisAuthor` is still empty, applied at the three new-text
insert sites. The intentional clear-authorship path (`['author','']`) and the
server-side guard are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(editor): also seed line-attribute author to close the same race

The first commit fixed text inserts, but line-attribute changes (lists, headings,
alignment) emit a line-marker insert tagged with AttributeManager.author, which
likewise starts '' and is only set when the async setProperty('userAuthor')
lands. An early list/heading could therefore emit an unattributed line-marker
insert and hit the same server-side rejection.

Seed documentAttributeManager.author from getLocalAuthor() (clientVars fallback)
right after it is constructed; the userauthor handler keeps it in sync after.

Audited the other author-tagging paths: all ace2_inner text-insert sites go
through getLocalAuthor()/authorizer now, and contentcollector (paste/import)
derives authorship from the pasted DOM's author- classes via className2Author —
a different mechanism, not this async race — and self-guards on `if (state.author)`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(editor): read the local author from the TOP pad window, not the inner frame

v1 of this fix fell back to `window.clientVars?.userId`, but that did NOT resolve
the flake — CI reproduced the identical failure and the server log still showed
the unattributed-insert rejection.

Ground truth via direct DOM measurement of a running pad: the inner editor
iframe's `window.clientVars` is NEVER populated (undefined at t=0/1/3/6s for the
life of the pad), so the v1 fallback always returned ''. The author id lives on
the TOP pad window (`window.top.clientVars.userId`), set by pad.ts when the
CLIENT_VARS message arrives — which necessarily precedes editor creation, so it
is reliably available from the inner frame the moment the editor can accept
input.

getLocalAuthor() now reads window.top.clientVars.userId (guarded with try/catch
for cross-origin embedded pads). thisAuthor still takes precedence once the
queued setProperty('userAuthor') lands. All text-insert sites and the
line-attribute seed already route through getLocalAuthor(), so both paths are
covered.

Verification: ts-check passes; fix source confirmed by direct measurement (the
top window reliably holds the author at editor-init time). Local end-to-end
repro was not possible — the dev server's require-kernel bundle did not reflect
working-tree edits — so this is validated against the measured source + CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(editor): walk ancestor frames for the author instead of window.top

Addresses a correctness gap flagged in review: reading window.top.clientVars
fails for same-origin embeds, where window.top is the host page (accessible, so
no exception is thrown) and has no Etherpad clientVars — getLocalAuthor() then
returns '' and the unattributed-insert bug recurs.

Walk up the ancestor chain (inner -> ace_outer -> pad window) and return the
first frame that has clientVars.userId. This stops at the pad window and never
depends on window.top, so it is correct for normal pads, same-origin embeds, and
cross-origin embeds (the try/catch ends the walk if an ancestor is cross-origin,
though the pad window — always same-origin with the editor frames — is reached
first). Behavior in the non-embed case is unchanged (pad window is 2 hops up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(collab): stamp the author onto outgoing inserts in collab_client

Replaces the earlier getLocalAuthor attempts (v1-v3, reverted from ace2_inner.ts),
which all failed because they tried to *read* an author that genuinely does not
exist yet: the local author id (clientVars.userId) only arrives with the
CLIENT_VARS socket message, and under load (Firefox + plugins) the editor can
become editable and the user can type before that message lands. At that instant
there is no author anywhere client-side, so the editor emits an unattributed
insert that the server's pad-corruption guard rejects — dropping the change and
losing authorship (the clear_authorship_color flake).

Fix where the author IS reliably available: collab_client only exists after
CLIENT_VARS has arrived, so its `userId` is always populated. A new pure helper
stampAuthorOnInserts() rewrites the outgoing wire changeset so every '+' op
carries an author, stamping userId onto any that lack one, just before it goes on
the wire (both the USER_CHANGES send and the reconnect/further-changeset path).
Independent of editor-init timing.

Verification:
- Unit test (stampAuthorOnInserts.test.ts, 5 cases): stamps the exact flake
  changeset `Z:1>5+5$Hello` -> authored + checkRep-valid + text preserved; leaves
  already-attributed inserts and keep/remove-only changesets unchanged; never
  invents an author when userId is empty; preserves other attributes.
- ace2_inner.ts reverted to develop (the editor is unchanged; the fix is fully
  localized to the send path).
- Local run of the clear_authorship_color spec with the fix: all pass, no
  server-side unattributed-insert rejections; full backend-new vitest green
  (the unrelated backend-tests-glob env-only failure aside).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:41:13 +01:00
..
pluginfw Hardening: API request handling, random IDs, and plugin loading (#7906) 2026-06-07 19:17:39 +02:00
types security: allow integrator sessionID cookie to be HttpOnly (#7045) (#7755) 2026-05-15 19:44:55 +01:00
vendors timeslider: respect showAuthorshipColors and padFontFamily settings (#7899) 2026-06-05 13:39:41 +01:00
ace.ts fix(a11y): drop role=textbox / aria-multiline from innerdocbody (#7778) (#7782) 2026-05-16 18:35:32 +01:00
ace2_common.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
ace2_inner.ts fix: RTL content option no longer flips the whole page (#7900) (#7901) 2026-06-05 13:24:26 +01:00
AttributeManager.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
AttributeMap.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
AttributePool.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
attributes.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
basic_error_handler.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
broadcast.ts timeslider: respect showAuthorshipColors and padFontFamily settings (#7899) 2026-06-05 13:39:41 +01:00
broadcast_revisions.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
broadcast_slider.ts feat: add timeslider playback speed setting (#7541) 2026-04-19 09:08:37 +01:00
Builder.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
caretPosition.ts Feat/bundle js (#6511) 2024-07-18 08:51:30 +02:00
Changeset.ts build(deps): bump jose from 5.10.0 to 6.0.12 (#7049) 2025-08-04 19:21:59 +02:00
changesettracker.ts Fixed document not accessible in iframe (#6642) 2024-09-09 11:26:12 +02:00
ChangesetUtils.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
chat.ts feat(pad): scrub history in-place on the pad URL (#7659) (#7710) 2026-05-10 16:21:56 +01:00
ChatMessage.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
collab_client.ts fix(editor): tag inserts with clientVars author until userAuthor propagates (Firefox authorship flake) (#7910) 2026-06-08 20:41:13 +01:00
colorutils.ts fix(colors): pick WCAG-higher-contrast text for author colors (#7565) 2026-05-03 12:56:30 +08:00
contentcollector.ts fix(editor): preserve U+00A0 non-breaking space (#3037) (#7585) 2026-04-23 08:57:30 +01:00
cssmanager.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
domline.ts fix(editor): preserve U+00A0 non-breaking space (#3037) (#7585) 2026-04-23 08:57:30 +01:00
index.ts Fixed index html page 2024-07-23 18:14:49 +02:00
l10n.ts feat: make cookie names configurable with prefix setting (#7450) 2026-04-05 01:58:29 +01:00
linestylefilter.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
MergingOpAssembler.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
Op.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
OpAssembler.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
OpIter.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
pad.ts fix: pad-wide view settings apply to the creator's own view (#7900) (#7902) 2026-06-05 13:41:13 +01:00
pad_automatic_reconnect.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
pad_connectionstatus.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
pad_cookie.ts feat: make cookie names configurable with prefix setting (#7450) 2026-04-05 01:58:29 +01:00
pad_editbar.ts fix(a11y): name role=toolbar regions, hide linemetricsdiv from AT (#7255) (#7777) 2026-05-16 13:08:33 +01:00
pad_editor.ts feat(colors): add padOptions.fadeInactiveAuthorColors with toolbar UI (#7554) 2026-05-03 04:01:13 +08:00
pad_impexp.ts feat(export): native DOCX export via html-to-docx (opt-in) (#7568) 2026-05-08 18:33:50 +01:00
pad_modals.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
pad_mode.ts timeslider: respect showAuthorshipColors and padFontFamily settings (#7899) 2026-06-05 13:39:41 +01:00
pad_outdated_notice.ts fix(pad): redesign outdated-version notice (#7799) (#7804) 2026-05-18 12:23:40 +01:00
pad_savedrevs.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
pad_userlist.ts fix: URL-encode pad names in admin 'Open' button and recent pads (#7865) (#7895) 2026-06-05 14:02:35 +01:00
pad_utils.ts Hardening: API request handling, random IDs, and plugin loading (#7906) 2026-06-07 19:17:39 +02:00
privacy_banner.ts feat(gdpr): configurable privacy banner (PR4 of #6701) (#7549) 2026-05-03 13:59:38 +08:00
rjquery.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
scroll.ts feat: continued with pad link functionality 2025-08-01 21:42:05 +02:00
security.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
skin_toolbar_colors.ts fix(7606): sync theme-color meta with client-side dark-mode switch (#7690) 2026-05-07 10:43:29 +01:00
skin_variants.ts feat(pad): scrub history in-place on the pad URL (#7659) (#7710) 2026-05-10 16:21:56 +01:00
skiplist.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
SmartOpAssembler.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
socketio.ts fix(socket.io): readd long polling 2025-03-13 14:32:00 +01:00
stampAuthorOnInserts.ts fix(editor): tag inserts with clientVars author until userAuthor propagates (Firefox authorship flake) (#7910) 2026-06-08 20:41:13 +01:00
StringAssembler.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
StringIterator.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
TextLinesMutator.ts Feat/changeset ts (#6594) 2024-08-18 12:14:24 +02:00
timeslider.ts timeslider: respect showAuthorshipColors and padFontFamily settings (#7899) 2026-06-05 13:39:41 +01:00
underscore.ts Moved to ts (#6593) 2024-08-17 20:14:36 +02:00
undomodule.ts fix: allow undo of clear authorship colors without disconnect (#7430) 2026-04-19 09:09:03 +01:00
welcome.ts feat(gdpr): HttpOnly author-token cookie (PR3 of #6701) (#7548) 2026-05-03 05:56:56 +01:00