Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/bridge-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ A keydown inside an iframe never reaches the parent document, and every admin wi
| `os-window-switch` | iframe → parent | `{ direction: 'prev' \| 'next' }` | `` ` `` / `` Shift+` ``. Cycles window focus on the active desktop. |
| `os-window-close-all` | iframe → parent | *(none)* | `⌥⌘W` / `Ctrl+Alt+W`. Runs the shell's confirm-then-`closeAll()` path (see [`javascript-reference.md`](./javascript-reference.md#batch-close--closeall)). |

The Cmd/Ctrl+K forwarder yields to a keydown that arrives already `defaultPrevented`: that is the block editor's canvas re-dispatching a chord its rich-text Add Link shortcut has already handled, and Core's own palette bails on the same flag.

The backtick forwarder — and only that one — applies a **text-entry gate** before forwarding: a bare key with no modifier has to yield to whatever the user is typing into, including the `IFRAME` case that catches Gutenberg's block canvas re-dispatching cloned keydowns. The other two carry modifiers that type nothing, so they forward from anywhere.

### Background heartbeat throttle — `os-window-active`
Expand Down
9 changes: 9 additions & 0 deletions src/chromeless-bridge.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**

Check warning on line 1 in src/chromeless-bridge.js

View workflow job for this annotation

GitHub Actions / Lint · Types · Vitest

This file is 4000 lines — past the 1000-line comfort zone. Be smart, build robust software: modules of ~300–600 lines are easier to read, test, review and reuse. Consider splitting this one along its natural seams — future you will say thanks
* OpenStation — chromeless bridge (iframe side).
*
* Plain ES5-flavoured JavaScript on purpose: this runs inside a real
Expand Down Expand Up @@ -2202,11 +2202,20 @@
* against Gutenberg / TinyMCE / plugin handlers bound to the same
* shortcut. Shift/Alt modifiers pass through so user shortcuts using
* those combos keep working.
*
* A keydown that arrives already `defaultPrevented` was claimed by
* the editor before it reached this document: Gutenberg's block
* canvas is a nested iframe, its rich-text Add Link shortcut handles
* Cmd+K in there, and the canvas re-dispatches the cloned keydown up
* to this document with the flag set. Core's own palette bails on
* that flag, so we do too — otherwise Cmd+K in a block opens the
* palette instead of the link popover.
*/
document.addEventListener( 'keydown', function ( e ) {
if ( ! ( e.metaKey || e.ctrlKey ) ) return;
if ( e.key !== 'k' && e.key !== 'K' ) return;
if ( e.shiftKey || e.altKey ) return;
if ( e.defaultPrevented ) return;

e.preventDefault();
e.stopImmediatePropagation();
Expand Down
36 changes: 35 additions & 1 deletion tests/vitest/chromeless-bridge-links.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
* the parent can no longer add on our behalf.
*
* The page is a list screen (Media), so the same harness also checks
* that a content-change broadcast refreshes it.
* that a content-change broadcast refreshes it, and that Cmd+K
* yields to a block editor that has already claimed it.
*
* @vitest-environment-options { "url": "http://localhost/wp-admin/upload.php?openstation_chromeless=1" }
*/
Expand Down Expand Up @@ -574,3 +575,36 @@ describe( 'chromeless bridge: a content-change broadcast', () => {
}
} );
} );

describe( 'chromeless bridge: the Cmd+K forwarder', () => {
/** Presses Cmd+K on the iframe Gutenberg re-dispatches keydowns from. */
function pressCmdK( handledInCanvas: boolean ): void {
document.body.innerHTML = '<iframe name="editor-canvas"></iframe>';
const event = new KeyboardEvent( 'keydown', {
key: 'k',
metaKey: true,
bubbles: true,
cancelable: true,
} );
// The canvas clones a keydown its own handlers already claimed
// with the flag set — the rich-text Add Link shortcut does.
if ( handledInCanvas ) {
event.preventDefault();
}
document.querySelector( 'iframe' )?.dispatchEvent( event );
}

function paletteMessages(): Array< Record< string, unknown > > {
return posted.filter( ( m ) => m.type === 'os-palette-cycle' );
}

test( 'opens the shell palette when nothing in the page claimed it', () => {
pressCmdK( false );
expect( paletteMessages() ).toHaveLength( 1 );
} );

test( 'yields to the editor that already claimed it for Add Link', () => {
pressCmdK( true );
expect( paletteMessages() ).toHaveLength( 0 );
} );
} );
Loading