diff --git a/src/static/js/ace2_inner.ts b/src/static/js/ace2_inner.ts index 8c32a222f5c..438f15fe2b3 100644 --- a/src/static/js/ace2_inner.ts +++ b/src/static/js/ace2_inner.ts @@ -683,7 +683,11 @@ function Ace2Inner(editorInfo, cssManagers) { rtlistrue: (value) => { targetBody.classList.toggle('rtl', value); targetBody.classList.toggle('ltr', !value); - document.documentElement.dir = value ? 'rtl' : 'ltr'; + // Apply the base direction to the inner editor document only. Using the + // top-level `document` here would flip the whole page (toolbar, chrome) + // even though this is a per-pad content option — see issue #7900. The + // page direction is governed solely by the UI language (see l10n.ts). + targetDoc.documentElement.dir = value ? 'rtl' : 'ltr'; }, fadeinactiveauthorcolors: (value) => { fadeInactiveAuthorColors = `${value}` !== 'false'; diff --git a/src/tests/frontend-new/specs/rtl_url_param.spec.ts b/src/tests/frontend-new/specs/rtl_url_param.spec.ts index f094da1016e..e931060651c 100644 --- a/src/tests/frontend-new/specs/rtl_url_param.spec.ts +++ b/src/tests/frontend-new/specs/rtl_url_param.spec.ts @@ -34,4 +34,30 @@ test.describe('RTL URL parameter', function () { await page.waitForSelector('#editorcontainer.initialized'); await expect(page.locator('#options-rtlcheck')).not.toBeChecked(); }); + + test('rtl content option flips only the pad inner contents, not the whole page', {tag: '@feature:rtl-toggle'}, async function ({page}) { + // The top-level page direction is owned by the UI language, not the pad's + // RTL content option. Capture whatever the language chose so the assertion + // is locale-independent (it could legitimately be rtl on an RTL locale). + const html = page.locator('html'); + await expect(html).toHaveAttribute('dir', /^(ltr|rtl)$/); + const initialPageDir = await html.getAttribute('dir'); + + await appendQueryParams(page, {rtl: 'true'}); + await expect(page.locator('#options-rtlcheck')).toBeChecked(); + + // The inner editor document is flipped to RTL. Use a frameLocator chain so + // Playwright auto-waits for the nested iframes/body to be ready. + const innerBody = page + .frameLocator('iframe[name="ace_outer"]') + .frameLocator('iframe[name="ace_inner"]') + .locator('#innerdocbody'); + await expect(innerBody).toHaveClass(/\brtl\b/); + await expect.poll(() => innerBody.evaluate((el) => + el.ownerDocument.defaultView!.getComputedStyle(el).direction)).toBe('rtl'); + + // ...but the top-level page (toolbar, chrome) is unaffected: its dir is + // whatever the UI language set and must not change when the pad flips. + await expect(html).toHaveAttribute('dir', initialPageDir!); + }); });