Summary
A new TeamsJS capability, mouseRelay, lets an app inside a Teams iframe send mouse back (X1) / forward (X2) presses to the host for Teams history navigation — the mouse analog of shortcutRelay.
Motivation
Mouse back/forward (X1/X2) already works on the web client: the browser handles the buttons as a user-agent action over the tab's joint session history, so they navigate correctly even when focus is inside an app iframe. Web behavior is not changing.
The goal is to make the desktop client (WebView2) behave the same way. Today it doesn't: when focus is inside an app iframe, the X1/X2 event never reaches the host, so it can't drive Teams history navigation. mouseRelay lets the iframe send the button to the host, bringing desktop to parity with web.
Proposed public API
New module src/public/mouseRelay.ts (used as import * as mouseRelay from '@microsoft/teams-js'):
export type NavigationDirection = "back" | "forward";
export function isSupported(): boolean;
export function enableMouseRelayCapability(): Promise<void>;
export function resetIsMouseRelayCapabilityEnabled(): void;
Behavior (iframe)
enableMouseRelayCapability() attaches 3 capture-phase listeners on document.
- It posts a message on
mouseup to match the browser's native release timing (so press-and-hold does nothing until release, exactly like web);
mousedown and auxclick exist only to cancel the iframe's own native X1/X2 navigation and the synthetic click.
// 3 = back (X1), 4 = forward (X2); every other button is ignored.
const directionFor = (b: number) =>
b === 3 ? "back" : b === 4 ? "forward" : undefined;
function enableMouseRelayCapability(): Promise<void> {
if (!isSupported()) throw errorNotSupportedOnPlatform;
document.addEventListener("mousedown", suppressX1X2, { capture: true }); // suppress (early)
document.addEventListener("mouseup", onMouseUp, { capture: true }); // postMessage on release
document.addEventListener("auxclick", suppressX1X2, { capture: true }); // suppress synthetic click
}
function onMouseUp(e: MouseEvent): void {
const direction = directionFor(e.button);
if (!direction) {
return; // not X1/X2 → do nothing
}
e.preventDefault();
e.stopImmediatePropagation(); // cancel the iframe's own native nav on release
callFunctionInHost("mouseRelay.navigateHistory", [{ direction }]); // postMessage once, on release
}
function suppressX1X2(e: MouseEvent): void {
if (directionFor(e.button) !== undefined) {
e.preventDefault();
e.stopImmediatePropagation(); // suppress only
}
}
Call sequence for one physical X1/X2 press (Chromium dispatch order):
pointerdown → mousedown → pointerup → mouseup → auxclick
│ │ │
suppress postMessage suppress
│ {direction} │
cancel native 1 msg to host cancel native + click
One mouseRelay.navigateHistory message per press, fired on mouse release to match how the browser navigates on web; the iframe performs no navigation of its own.
Host responsibilities
The host implements a service that maps the received direction onto its existing history navigation.
Teams knows the platform type, so it advertises this capability (runtime.supports.mouseRelay) on the desktop client only, keeping the browser's native back/forward behavior untouched on web. Apps therefore see isSupported() as true only on desktop.
The iframe is agnostic to this decision: an app only calls isSupported() / enableMouseRelayCapability() and never reasons about platform. So if Teams later chooses to support the capability on web too (or with new behavior), it simply advertises it there — nothing more is required from iframes.
Why a new capability (not extending shortcutRelay)
- Clean negotiation: a dedicated
isSupported() distinguishes hosts that understand mouse from those that don't. Overloading shortcutRelay would need a version flag anyway.
- Simpler: mouse needs no
getHostShortcuts handshake.
Implementation
Summary
A new TeamsJS capability,
mouseRelay, lets an app inside a Teams iframe send mouse back (X1) / forward (X2) presses to the host for Teams history navigation — the mouse analog ofshortcutRelay.Motivation
Mouse back/forward (X1/X2) already works on the web client: the browser handles the buttons as a user-agent action over the tab's joint session history, so they navigate correctly even when focus is inside an app iframe. Web behavior is not changing.
The goal is to make the desktop client (WebView2) behave the same way. Today it doesn't: when focus is inside an app iframe, the X1/X2 event never reaches the host, so it can't drive Teams history navigation.
mouseRelaylets the iframe send the button to the host, bringing desktop to parity with web.Proposed public API
New module
src/public/mouseRelay.ts(used asimport * as mouseRelay from '@microsoft/teams-js'):Behavior (iframe)
enableMouseRelayCapability()attaches 3 capture-phase listeners ondocument.mouseupto match the browser's native release timing (so press-and-hold does nothing until release, exactly like web);mousedownandauxclickexist only to cancel the iframe's own native X1/X2 navigation and the syntheticclick.Call sequence for one physical X1/X2 press (Chromium dispatch order):
One
mouseRelay.navigateHistorymessage per press, fired on mouse release to match how the browser navigates on web; the iframe performs no navigation of its own.Host responsibilities
The host implements a service that maps the received
directiononto its existing history navigation.Teams knows the platform type, so it advertises this capability (
runtime.supports.mouseRelay) on the desktop client only, keeping the browser's native back/forward behavior untouched on web. Apps therefore seeisSupported()astrueonly on desktop.The iframe is agnostic to this decision: an app only calls
isSupported()/enableMouseRelayCapability()and never reasons about platform. So if Teams later chooses to support the capability on web too (or with new behavior), it simply advertises it there — nothing more is required from iframes.Why a new capability (not extending
shortcutRelay)isSupported()distinguishes hosts that understand mouse from those that don't. OverloadingshortcutRelaywould need a version flag anyway.getHostShortcutshandshake.Implementation