dotCMS / dotCMS/core

UVE: unintercepted in-page links navigate the iframe natively — stale editor state with `mode`, blank canvas without it

Open
#37,586 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

dotCMS : UVE OKR : Customer Support Team : Maintenance Type : Defect
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Summary

An in-page link that changes only a page-context query param is not intercepted by UVE. The browser performs a real navigation of the editor iframe, and uveStore.pageLoad() never runs — so the editor shell and the iframe contents fall out of sync.

Root cause

isSamePageNavigation (core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts:1161) compares pathname only:

return target.pathname === current.pathname;

A link to the same pathname with a different query string therefore passes as "same page". In handleInternalNav (core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts:781) this is rule 5, which returns early without calling preventDefault().

The predicate was written for hash anchors and client-side query UI — its docstring gives '/home?tab=2' -> true, "handled by the browser/client naturally". That is correct for client-side UI state and wrong for dotCMS page-context params, which must round-trip through the Page API.

Introduced in #35324 (PR #35326); the file-asset branch was added later by #35504 (PR #36925). Neither established a general contract for link following.

Two failure modes

Which one you get depends on whether the link happens to carry a mode param.

1. With mode present (customer-reported)

VelocityServlet.processPageMode (dotCMS/src/main/java/com/dotcms/rendering/velocity/servlet/VelocityServlet.java:63) short-circuits on an explicit mode param:

if (null != request.getParameter(WebKeys.PAGE_MODE_PARAMETER)){
    return PageMode.get(request);
}

The server returns rendered EDIT_MODE HTML. The iframe shows the new language, but the editor shell keeps the previous language_id and toolbar label. The editing overlay — add (+) buttons, edit pencils, floating toolbar — stops rendering, because it cannot bind to content the editor did not load. Container outlines still draw, which makes it look like a partial render rather than a desync.

Customer's link: /homepage/index.html?com.dotmarketing.htmlpage.language=1&language_id=102&host_id=...&mId=edit&mode=EDIT_MODE

2. With no mode param (reproduced in-house)

Falls through to PageMode.NAVIGATE_EDIT_MODE, served by VelocityNavigateEditMode (dotCMS/src/main/java/com/dotcms/rendering/velocity/servlet/VelocityNavigateEditMode.java:30). That handler does not return page HTML at all. Its entire response is the JS_CODE template at line 36, which wraps the page JSON in an ng-event / load-edit-mode-page CustomEvent and dispatches it to window.top.document.

Nothing in core-web listens for that event. A grep of core-web/libs and core-web/apps for load-edit-mode-page returns zero hits; the only references in the repo are the Java emitter and its integration test (VelocityServletIntegrationTest.java:507).

The result is a blank editor canvas. The markup exists in the payload at page.rendered and is simply never consumed.

This is the legacy edit-mode navigation contract: the server rendered in NAVIGATE_EDIT_MODE and told the parent editor to load the page. The legacy Angular editor listened. UVE handles link-following client-side instead and never reimplemented the listener, so the server half still fires into a void.

Steps to Reproduce (failure mode 2)

  1. Demo starter. Create /training/test-page-1 with versions in two languages (EN id 1, ES id 2).
  2. Add a link to the page body: <a href="/training/test-page-1?com.dotmarketing.htmlpage.language=1">EN</a>
  3. Open the page in UVE on the ES version.
  4. Click the link inside the preview.

Expected: the editor loads the EN version with the toolbar, URL and overlay all updated.
Actual: the iframe renders blank. The response body is the load-edit-mode-page bootstrap script.

Scope

The same predicate guards the headless path — dot-uve-actions-handler.service.ts:81, on NAVIGATION_UPDATE (set-url). An SDK client calling setUrl() with a changed page-context param desyncs identically. A fix in isSamePageNavigation covers both call sites.

Suggested direction

Return false from isSamePageNavigation when a dotCMS page-context param differs, so the navigation goes through pageLoad(). DotPageAssetKeys (core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api/dot-page-api.service.ts:35) is the canonical set: mode, depth, clientHost, variantName, language_id, experimentId, com.dotmarketing.persona.id, publishDate.

Note: the legacy VTL param com.dotmarketing.htmlpage.language must be treated as an alias of language_id, or the customer's exact case still slips through.

Worth deciding separately whether NAVIGATE_EDIT_MODE should remain reachable now that UVE has no consumer for its response. As it stands it turns a missed interception into a blank screen rather than a degraded one.

Workaround

The UVE toolbar language selector is unaffected — onLanguageSelected (core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts:437) calls pageLoad() directly. Editing controls survive and language_id updates. If the target language has no version of the page, the selector offers to create the translation instead of switching, which is expected behaviour.

Acceptance Criteria

  • isSamePageNavigation returns false when a dotCMS page-context param differs between the incoming and current URL, so the click is intercepted and routed through uveStore.pageLoad(). Canonical set per DotPageAssetKeys: mode, depth, clientHost, variantName, language_id, experimentId, com.dotmarketing.persona.id, publishDate.
  • The legacy VTL param com.dotmarketing.htmlpage.language is treated as an alias of language_id, so the customer's link shape is covered.
  • No regression on #35324: hash-only and client-side query navigation still short-circuit. From /home, both '/home#faq' and '/home?tab=2' continue to return true and are not intercepted.
  • Following an in-page link that changes language inside UVE loads the target language in the editor, with the toolbar selector, the address bar language_id, and the editing overlay (add + buttons, edit pencils, floating toolbar) all updated and functional — both with and without a mode param on the link.
  • The headless path behaves identically: an SDK setUrl() carrying a changed page-context param triggers a pageLoad() rather than a silent no-op (dot-uve-actions-handler.service.ts:81).
  • No navigation inside UVE can leave the iframe rendering the raw load-edit-mode-page bootstrap script.
  • Unit specs cover the predicate for each case: hash-only, client-side query, language change, persona change, mode change, and the legacy language alias.

dotCMS Version

Evergreen, release 26.09.14-01 — both failure modes reproduced and the workaround verified on this release.

Severity

Medium - Some functionality impacted

Links

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with isSamePageNavigation in core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts and its callers in edit-ema-editor.component.ts and dot-uve-actions-handler.service.ts. Review the existing predicate specs, then trace the Java navigation responses in VelocityServlet.java and VelocityNavigateEditMode.java. Done means page-context changes use pageLoad(), while hash-only and client-side query navigation remain unhandled.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, typescript
Domain
backend, frontend, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.