diff --git a/packages/devtools_app/lib/src/framework/settings_dialog.dart b/packages/devtools_app/lib/src/framework/settings_dialog.dart index 3705255a32b..0e850ef7082 100644 --- a/packages/devtools_app/lib/src/framework/settings_dialog.dart +++ b/packages/devtools_app/lib/src/framework/settings_dialog.dart @@ -11,10 +11,10 @@ import 'package:provider/provider.dart'; import '../shared/analytics/analytics_controller.dart'; import '../shared/analytics/constants.dart' as gac; import '../shared/common_widgets.dart'; +import '../shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart'; import '../shared/config_specific/server/server.dart'; import '../shared/globals.dart'; import '../shared/log_storage.dart'; -import '../shared/utils.dart'; class OpenSettingsAction extends ScaffoldAction { OpenSettingsAction({super.key, Color? color}) diff --git a/packages/devtools_app/lib/src/screens/network/network_screen.dart b/packages/devtools_app/lib/src/screens/network/network_screen.dart index 4ddd3f63d14..9ce787c78fc 100644 --- a/packages/devtools_app/lib/src/screens/network/network_screen.dart +++ b/packages/devtools_app/lib/src/screens/network/network_screen.dart @@ -13,6 +13,7 @@ import 'package:provider/provider.dart'; import '../../shared/analytics/analytics.dart' as ga; import '../../shared/analytics/constants.dart' as gac; import '../../shared/common_widgets.dart'; +import '../../shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart'; import '../../shared/globals.dart'; import '../../shared/http/curl_command.dart'; import '../../shared/http/http_request_data.dart'; diff --git a/packages/devtools_app/lib/src/shared/common_widgets.dart b/packages/devtools_app/lib/src/shared/common_widgets.dart index ece31bc34be..9ccbaeb2de2 100644 --- a/packages/devtools_app/lib/src/shared/common_widgets.dart +++ b/packages/devtools_app/lib/src/shared/common_widgets.dart @@ -17,6 +17,7 @@ import '../screens/debugger/debugger_controller.dart'; import '../screens/inspector/layout_explorer/ui/theme.dart'; import 'analytics/analytics.dart' as ga; import 'analytics/constants.dart' as gac; +import 'config_specific/copy_to_clipboard/copy_to_clipboard.dart'; import 'config_specific/launch_url/launch_url.dart'; import 'console/widgets/expandable_variable.dart'; import 'diagnostics/dart_object_node.dart'; diff --git a/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_desktop.dart b/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_desktop.dart new file mode 100644 index 00000000000..04bef782f20 --- /dev/null +++ b/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_desktop.dart @@ -0,0 +1,7 @@ +// Copyright 2023 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +void copyToClipboardVSCode(String _) { + // Do nothing +} diff --git a/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_web.dart b/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_web.dart new file mode 100644 index 00000000000..e00b987428e --- /dev/null +++ b/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/_copy_to_clipboard_web.dart @@ -0,0 +1,19 @@ +// Copyright 2023 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +import 'dart:js_interop'; +import 'package:web/web.dart'; + +void copyToClipboardVSCode(String data) { + // This post message is only relevant in VSCode. If the parent frame is + // listening for this command, then it will attempt to copy the contents + // to the clipboard in the context of the parent frame. + window.parent?.postMessage( + { + 'command': 'clipboard-write', + 'data': data, + }.jsify(), + '*'.toJS, + ); +} diff --git a/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart b/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart new file mode 100644 index 00000000000..455414e7898 --- /dev/null +++ b/packages/devtools_app/lib/src/shared/config_specific/copy_to_clipboard/copy_to_clipboard.dart @@ -0,0 +1,52 @@ +// Copyright 2023 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +import 'package:devtools_app_shared/ui.dart'; +import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; + +import '../../globals.dart'; +import '_copy_to_clipboard_desktop.dart' + if (dart.library.js_interop) '_copy_to_clipboard_web.dart'; + +final _log = Logger('copy_to_clipboard'); + +/// Attempts to copy a String of `data` to the clipboard. +/// +/// Shows a `successMessage` [Notification] on the passed in `context`, if the +/// copy is successfully done using the [Clipboard.setData] api. Otherwise it +/// attempts to post the [data] to the parent frame where the parent frame will +/// try to complete the copy (this fallback will only work in VSCode). +Future copyToClipboard( + String data, + String? successMessage, +) async { + try { + await Clipboard.setData( + ClipboardData( + text: data, + ), + ); + + if (successMessage != null) notificationService.push(successMessage); + } catch (e) { + if (ideTheme.embed) { + _log.warning( + 'DevTools copy failed. This may be as a result of a known bug in VSCode. ' + 'See https://github.com/Dart-Code/Dart-Code/issues/4540 for more ' + 'information. DevTools will now attempt to use a fallback method of ' + 'copying the contents.', + ); + // Trying to use Clipboard.setData to copy in vscode will not work as a + // result of a bug. So we should fallback to `copyToClipboardVSCode` which + // delegates the copy to the frame above DevTools. + // Sending this message in other environments will just have no effect. + // See https://github.com/Dart-Code/Dart-Code/issues/4540 for more + // information. + copyToClipboardVSCode(data); + } else { + rethrow; + } + } +} diff --git a/packages/devtools_app/lib/src/shared/dialogs.dart b/packages/devtools_app/lib/src/shared/dialogs.dart index 6b4abd22e66..f510379aabf 100644 --- a/packages/devtools_app/lib/src/shared/dialogs.dart +++ b/packages/devtools_app/lib/src/shared/dialogs.dart @@ -8,8 +8,8 @@ import 'package:devtools_app_shared/ui.dart'; import 'package:flutter/material.dart'; import '../shared/config_specific/launch_url/launch_url.dart'; +import 'config_specific/copy_to_clipboard/copy_to_clipboard.dart'; import 'globals.dart'; -import 'utils.dart'; /// A dialog, that reports unexpected error and allows to copy details and create issue. class UnexpectedErrorDialog extends StatelessWidget { diff --git a/packages/devtools_app/lib/src/shared/editable_list.dart b/packages/devtools_app/lib/src/shared/editable_list.dart index c6c08ba4a40..6d013120a67 100644 --- a/packages/devtools_app/lib/src/shared/editable_list.dart +++ b/packages/devtools_app/lib/src/shared/editable_list.dart @@ -10,7 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'common_widgets.dart'; -import 'utils.dart'; +import 'config_specific/copy_to_clipboard/copy_to_clipboard.dart'; /// A widget that displays the contents of [entries]. /// diff --git a/packages/devtools_app/lib/src/shared/utils.dart b/packages/devtools_app/lib/src/shared/utils.dart index 3c691b9c361..29cd8d3d73b 100644 --- a/packages/devtools_app/lib/src/shared/utils.dart +++ b/packages/devtools_app/lib/src/shared/utils.dart @@ -6,13 +6,10 @@ // other libraries in this package. // Utils, that do not have dependencies, should go to primitives/utils.dart. -import 'dart:async'; - import 'package:devtools_app_shared/service.dart'; import 'package:devtools_app_shared/ui.dart'; import 'package:devtools_app_shared/utils.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; @@ -25,22 +22,6 @@ import 'globals.dart'; final _log = Logger('lib/src/shared/utils'); -/// Attempts to copy a String of `data` to the clipboard. -/// -/// Shows a `successMessage` [Notification] on the passed in `context`. -Future copyToClipboard( - String data, - String? successMessage, -) async { - await Clipboard.setData( - ClipboardData( - text: data, - ), - ); - - if (successMessage != null) notificationService.push(successMessage); -} - /// Logging to debug console only in debug runs. void debugLogger(String message) { assert( diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 46d471fbb36..1ff55c23662 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -20,6 +20,7 @@ video provides a brief tutorial for each DevTools screen. * Fix a bug with service extension states not being cleared on app disconnect. [#6547](https://github.com/flutter/devtools/pull/6547) * Improved styling of bottom status bar when connected to an app. [#6525](https://github.com/flutter/devtools/pull/6525) +* Added a work around to fix copy button functionality in VSCode. [#6598](https://github.com/flutter/devtools/pull/6598) ## Inspector updates