Skip to content

Commit 03f36f5

Browse files
DanTupsrawlins
authored andcommitted
Handle shortcuts for actions like copy/paste when embedded inside VS Code (#9472)
On macOS, short-cut keys like Cmd+C, Cmd+V don't work in embedded iframes. This happens because VS Code calls `setIgnoreMenuShortcuts(true)` and disables the OS/browser functionality for these actions. For the top-level webviews, it handles keypresses, passes them up to VS Code, and VS Code then passes back down commands for copy/paste/etc. However for our nested iframes, this keypress handling (and command passing) does not occur. I wasn't able to find any way to fix this inside VS Code (it can't reach into our iframes because they're a different origin) but I found that we can simulate what it's doing itself. I thought I'd tried this in the past and determined this wouldn't work, however it seems to work fine today (maybe I was trying to handle the Copy/Paste events rather than the keys? 🤔) I can't write automated tests for this because we need to verify the actual behaviour of pressing keys, but I've tested on both my Mac and Windows (it already worked on Windows and doesn't need this, but I've left this code to run for all platforms in case there is a difference between using VS Code's native menus vs not) by using `dart.customDevTools` and in both of them, I can copy/paste/select-all/undo. Fixes Dart-Code/Dart-Code#3488 Fixes #7253 Fixes #9435 See #8190 (I don't know if we still want to do other work here) See microsoft/vscode#129178 (comment) cc @elliette
1 parent 606e1d7 commit 03f36f5

2 files changed

Lines changed: 53 additions & 10 deletions

File tree

packages/devtools_app/lib/src/shared/config_specific/framework_initialize/_framework_initialize_web.dart

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import 'package:devtools_app_shared/web_utils.dart';
99
import 'package:logging/logging.dart';
1010
import 'package:web/web.dart' hide Storage;
1111

12-
import '../../../service/service_manager.dart';
13-
import '../../globals.dart';
1412
import '../../primitives/storage.dart';
1513
import '../../server/server.dart' as server;
1614
import '../../server/server_api_client.dart';
@@ -83,14 +81,18 @@ void _sendKeyPressToParent(KeyboardEvent event) {
8381
// will need to be posted up to the parent
8482
// https://github.com/flutter/devtools/issues/2775
8583

86-
// Check we have a connection and we appear to be embedded somewhere expected
87-
// because we can't use targetOrigin in postMessage as only the scheme is fixed
88-
// for VS Code (vscode-webview://[some guid]).
89-
if (globals.containsKey(ServiceConnectionManager) &&
90-
!serviceConnection.serviceManager.connectedState.value.connected) {
84+
// This handling is only required for when embedded in VS Code and forks that
85+
// also use Dart-Code.
86+
if (!window.navigator.userAgent.contains('Electron')) {
9187
return;
9288
}
93-
if (!window.navigator.userAgent.contains('Electron')) return;
89+
90+
// Because VS Code prevents built-in behaviour for copy/paste/etc. from
91+
// working (on macOS), we have to handle those specially (and do not need to
92+
// send them upwards).
93+
// https://github.com/microsoft/vscode/issues/129178
94+
// https://github.com/microsoft/vscode/issues/129178#issuecomment-3410886795
95+
if (_handleStandardShortcuts(event)) return;
9496

9597
final data = {
9698
'altKey': event.altKey,
@@ -109,6 +111,45 @@ void _sendKeyPressToParent(KeyboardEvent event) {
109111
);
110112
}
111113

114+
bool _handleStandardShortcuts(KeyboardEvent event) {
115+
const keyInsert = 45;
116+
const keyA = 65;
117+
const keyC = 67;
118+
const keyV = 86;
119+
const keyX = 88;
120+
const keyZ = 90;
121+
122+
final hasMeta = event.ctrlKey || event.metaKey;
123+
final hasShift = event.shiftKey;
124+
final code = event.keyCode;
125+
126+
// Determine which command (if any) we should execute.
127+
final command = switch (code) {
128+
keyA when hasMeta => 'selectAll',
129+
keyC when hasMeta => 'copy',
130+
keyV when hasMeta => 'paste',
131+
keyInsert when hasShift => 'paste',
132+
keyX when hasMeta => 'cut',
133+
keyZ when hasMeta => 'undo',
134+
_ => null,
135+
};
136+
137+
// No command, just fall back to normal handling.
138+
if (command == null) {
139+
return false;
140+
}
141+
142+
// Otherwise, try to invoke the command and prevent the browser.
143+
try {
144+
document.execCommand(command);
145+
event.preventDefault();
146+
return true;
147+
} catch (_) {
148+
// If we failed, then also fall back to normal handling.
149+
return false;
150+
}
151+
}
152+
112153
class BrowserStorage implements Storage {
113154
@override
114155
Future<String?> getValue(String key) async {

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ TODO: Remove this section if there are not any general updates.
5353

5454
TODO: Remove this section if there are not any general updates.
5555

56-
## VS Code Sidebar updates
56+
## VS Code updates
5757

58-
TODO: Remove this section if there are not any general updates.
58+
- On macOS, shortcuts like `Cmd`+`C` and `Cmd`+`V` now work when DevTools is
59+
embedded inside VS Code -
60+
[#9472](https://github.com/flutter/devtools/pull/9472)
5961

6062
## DevTools Extension updates
6163

0 commit comments

Comments
 (0)