From 4c5f4704dae3415b440cec4e6117c19f8f0bd7a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 04:40:56 +0000 Subject: [PATCH 1/6] Initial plan From d83640f08c529ad3456ec1a1fdeb6bfae38071c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 04:48:17 +0000 Subject: [PATCH 2/6] Add focus management to clipper on invocation for accessibility Co-authored-by: aanchalbhansali <185747873+aanchalbhansali@users.noreply.github.com> --- src/scripts/clipperUI/mainController.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/scripts/clipperUI/mainController.tsx b/src/scripts/clipperUI/mainController.tsx index ec8c533a..7f487ce8 100644 --- a/src/scripts/clipperUI/mainController.tsx +++ b/src/scripts/clipperUI/mainController.tsx @@ -104,6 +104,25 @@ export class MainControllerClass extends ComponentBase { this.setState({currentPanel: PanelType.None}); }, onBeforeAnimateIn: () => { this.props.clipperState.reset(); }, onAnimateInExpand: () => { this.setState({currentPanel: this.getPanelTypeToShow()}); }, + onAfterAnimateIn: (el: HTMLElement) => { + // Set focus to the clipper when it opens for accessibility (MAS 2.4.3 - Focus Order) + // Find the first tabbable element and focus on it + let tabbables = el.querySelectorAll("[tabindex]"); + let lowestTabIndexElement: HTMLElement; + if (tabbables.length > 0) { + for (let i = 0; i < tabbables.length; i++) { + let tabbable = tabbables[i] as HTMLElement; + if (!lowestTabIndexElement || tabbable.tabIndex < lowestTabIndexElement.tabIndex) { + if (tabbable.tabIndex >= 0) { + lowestTabIndexElement = tabbable; + } + } + } + if (lowestTabIndexElement) { + lowestTabIndexElement.focus(); + } + } + }, onAfterAnimateOut: () => { Clipper.getInjectCommunicator().callRemoteFunction(Constants.FunctionKeys.hideUi); } }); From 8be79d6cb4966131f25dc754c6483de7edae8e91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 05:14:22 +0000 Subject: [PATCH 3/6] Focus iframe on invocation to support keyboard-triggered extension activation Co-authored-by: aanchalbhansali <185747873+aanchalbhansali@users.noreply.github.com> --- src/scripts/extensions/clipperInject.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/scripts/extensions/clipperInject.ts b/src/scripts/extensions/clipperInject.ts index 8a66cd3d..35a0933b 100644 --- a/src/scripts/extensions/clipperInject.ts +++ b/src/scripts/extensions/clipperInject.ts @@ -134,6 +134,10 @@ export class ClipperInject extends FrameInjectBase { } } }; + + // Set focus to the iframe on initial invocation to ensure keyboard users can access it + // immediately, regardless of how the extension was invoked (MAS 2.4.3 - Focus Order) + this.frame.focus(); } catch (e) { this.handleConstructorError(e); throw e; @@ -375,6 +379,9 @@ export class ClipperInject extends FrameInjectBase { private toggleClipper() { if (this.frame.style.display === "none") { this.frame.style.display = ""; + // Set focus to the iframe when showing it to ensure focus moves from browser chrome + // This enables keyboard users to access the clipper immediately (MAS 2.4.3 - Focus Order) + this.frame.focus(); } this.uiCommunicator.callRemoteFunction(Constants.FunctionKeys.toggleClipper); } From 13e6de4c0772464af86155d13a35a59c8b64b1c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 04:41:45 +0000 Subject: [PATCH 4/6] Use requestAnimationFrame and tabindex to ensure reliable iframe focus Co-authored-by: aanchalbhansali <185747873+aanchalbhansali@users.noreply.github.com> --- src/scripts/extensions/clipperInject.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/scripts/extensions/clipperInject.ts b/src/scripts/extensions/clipperInject.ts index 35a0933b..0eb7b7e7 100644 --- a/src/scripts/extensions/clipperInject.ts +++ b/src/scripts/extensions/clipperInject.ts @@ -137,7 +137,10 @@ export class ClipperInject extends FrameInjectBase { // Set focus to the iframe on initial invocation to ensure keyboard users can access it // immediately, regardless of how the extension was invoked (MAS 2.4.3 - Focus Order) - this.frame.focus(); + // Use requestAnimationFrame to ensure the iframe is rendered before focusing + requestAnimationFrame(() => { + this.frame.focus(); + }); } catch (e) { this.handleConstructorError(e); throw e; @@ -189,6 +192,8 @@ export class ClipperInject extends FrameInjectBase { this.frame = StyledFrameFactory.getStyledFrame(Frame.WebClipper); this.frame.id = Constants.Ids.clipperUiFrame; this.frame.src = this.options.frameUrl; + // Set tabindex to make iframe focusable for keyboard accessibility (MAS 2.4.3 - Focus Order) + this.frame.tabIndex = -1; } protected handleConstructorError(e: Error) { @@ -381,7 +386,10 @@ export class ClipperInject extends FrameInjectBase { this.frame.style.display = ""; // Set focus to the iframe when showing it to ensure focus moves from browser chrome // This enables keyboard users to access the clipper immediately (MAS 2.4.3 - Focus Order) - this.frame.focus(); + // Use requestAnimationFrame to ensure the iframe is visible before focusing + requestAnimationFrame(() => { + this.frame.focus(); + }); } this.uiCommunicator.callRemoteFunction(Constants.FunctionKeys.toggleClipper); } From b51d2c17ad5068d96ea82953b2e23ec6d3ae41f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 04:56:13 +0000 Subject: [PATCH 5/6] Add multiple focus attempts with increased delays and onload handler Co-authored-by: aanchalbhansali <185747873+aanchalbhansali@users.noreply.github.com> --- src/scripts/extensions/clipperInject.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/scripts/extensions/clipperInject.ts b/src/scripts/extensions/clipperInject.ts index 0eb7b7e7..bed019b8 100644 --- a/src/scripts/extensions/clipperInject.ts +++ b/src/scripts/extensions/clipperInject.ts @@ -137,9 +137,11 @@ export class ClipperInject extends FrameInjectBase { // Set focus to the iframe on initial invocation to ensure keyboard users can access it // immediately, regardless of how the extension was invoked (MAS 2.4.3 - Focus Order) - // Use requestAnimationFrame to ensure the iframe is rendered before focusing + // Use both requestAnimationFrame and setTimeout to handle different browser behaviors requestAnimationFrame(() => { - this.frame.focus(); + setTimeout(() => { + this.frame.focus(); + }, 50); }); } catch (e) { this.handleConstructorError(e); @@ -194,6 +196,15 @@ export class ClipperInject extends FrameInjectBase { this.frame.src = this.options.frameUrl; // Set tabindex to make iframe focusable for keyboard accessibility (MAS 2.4.3 - Focus Order) this.frame.tabIndex = -1; + + // Add onload handler to focus the iframe once content is loaded + // This ensures focus transfer works even when invoked from browser chrome + this.frame.onload = () => { + // Small delay to ensure browser chrome has released focus + setTimeout(() => { + this.frame.focus(); + }, 100); + }; } protected handleConstructorError(e: Error) { @@ -386,10 +397,10 @@ export class ClipperInject extends FrameInjectBase { this.frame.style.display = ""; // Set focus to the iframe when showing it to ensure focus moves from browser chrome // This enables keyboard users to access the clipper immediately (MAS 2.4.3 - Focus Order) - // Use requestAnimationFrame to ensure the iframe is visible before focusing - requestAnimationFrame(() => { + // Use setTimeout to ensure the iframe is fully visible before focusing + setTimeout(() => { this.frame.focus(); - }); + }, 50); } this.uiCommunicator.callRemoteFunction(Constants.FunctionKeys.toggleClipper); } From efae44e9fea59a4dedc8a9087394cf250ffbd0ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 05:46:53 +0000 Subject: [PATCH 6/6] Call window.focus() before iframe focus to transfer focus from browser chrome Co-authored-by: aanchalbhansali <185747873+aanchalbhansali@users.noreply.github.com> --- src/scripts/extensions/clipperInject.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/scripts/extensions/clipperInject.ts b/src/scripts/extensions/clipperInject.ts index bed019b8..ed83e3b7 100644 --- a/src/scripts/extensions/clipperInject.ts +++ b/src/scripts/extensions/clipperInject.ts @@ -139,7 +139,12 @@ export class ClipperInject extends FrameInjectBase { // immediately, regardless of how the extension was invoked (MAS 2.4.3 - Focus Order) // Use both requestAnimationFrame and setTimeout to handle different browser behaviors requestAnimationFrame(() => { + // First, try to move focus from browser chrome to the page content + // This is necessary when invoked via keyboard from extensions list + window.focus(); + setTimeout(() => { + // Then focus the iframe this.frame.focus(); }, 50); }); @@ -200,6 +205,9 @@ export class ClipperInject extends FrameInjectBase { // Add onload handler to focus the iframe once content is loaded // This ensures focus transfer works even when invoked from browser chrome this.frame.onload = () => { + // First, try to move focus from browser chrome to the page content + window.focus(); + // Small delay to ensure browser chrome has released focus setTimeout(() => { this.frame.focus(); @@ -397,6 +405,9 @@ export class ClipperInject extends FrameInjectBase { this.frame.style.display = ""; // Set focus to the iframe when showing it to ensure focus moves from browser chrome // This enables keyboard users to access the clipper immediately (MAS 2.4.3 - Focus Order) + // First move focus to page, then to iframe + window.focus(); + // Use setTimeout to ensure the iframe is fully visible before focusing setTimeout(() => { this.frame.focus();