| // Copyright 2023 The Flutter Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. |
| |
| import 'dart:async'; |
| |
| import 'package:devtools_shared/devtools_extensions.dart'; |
| import 'package:flutter/foundation.dart'; |
| import 'package:logging/logging.dart'; |
| |
| import 'globals.dart'; |
| import 'managers/survey.dart'; |
| |
| // This file contains helpers that can be used during local development. Any |
| // changes to variables in this file (like flipping a bool to true or setting |
| // a non-null value for a debug String) should be intended for local |
| // development only, and should never be checked into source control. The |
| // default values for variables in this file are test covered in |
| // `development_helpers_test.dart`. |
| |
| final _log = Logger('dev_helpers'); |
| |
| /// Set this to a real DTD URI String for ease of developing features that use |
| /// the Dart Tooling Daemon. |
| /// |
| /// Without using this flag, you would need to run DevTools with the DevTools |
| /// server (dt serve) in order to pass a DTD URI to the DevTools |
| /// server, which is not convenient for development. |
| /// |
| /// If you do need the DevTools server, then you should run |
| /// `dt serve --dtd-uri=<uri>` instead of setting this debug flag. |
| /// |
| /// You can use a real DTD URI from an IDE (VS Code or IntelliJ / Android |
| /// Studio) using the "Copy DTD URI" action, or you can run a Dart or Flutter |
| /// app from the command line with the `--print-dtd` flag. |
| String? get debugDtdUri => kReleaseMode ? null : _debugDtdUri; |
| String? _debugDtdUri; |
| |
| /// Enable this flag to send and debug analytics when DevTools is run in debug |
| /// or profile mode, otherwise analytics will only be sent in release builds. |
| bool debugSendAnalytics = false; |
| |
| /// Enable this flag to always show the analytics consent message, regardless |
| /// of whether any other conditions are met. |
| bool debugShowAnalyticsConsentMessage = false; |
| |
| /// Whether to build DevTools for conveniently debugging DevTools extensions. |
| /// |
| /// Turning this flag to `true` allows for debugging the extensions framework |
| /// without a server connection. |
| /// |
| /// This flag should never be checked in with a value of true - this is covered |
| /// by a test. |
| final debugDevToolsExtensions = |
| _debugDevToolsExtensions || testMode || stagerMode; |
| const _debugDevToolsExtensions = false; |
| |
| List<DevToolsExtensionConfig> debugHandleRefreshAvailableExtensions({ |
| bool includeRuntime = true, |
| }) => StubDevToolsExtensions.extensions(includeRuntime: includeRuntime); |
| |
| ExtensionEnabledState debugHandleExtensionEnabledState({ |
| required String extensionName, |
| bool? enable, |
| }) { |
| if (enable != null) { |
| stubExtensionEnabledStates[extensionName] = enable |
| ? ExtensionEnabledState.enabled |
| : ExtensionEnabledState.disabled; |
| } |
| return stubExtensionEnabledStates.putIfAbsent( |
| extensionName, |
| () => ExtensionEnabledState.none, |
| ); |
| } |
| |
| @visibleForTesting |
| void resetDevToolsExtensionEnabledStates() => |
| stubExtensionEnabledStates.clear(); |
| |
| /// Stubbed activation states so we can develop DevTools extensions without a |
| /// server connection. |
| final stubExtensionEnabledStates = <String, ExtensionEnabledState>{}; |
| |
| /// A namespace for stubbed DevTools extensions. |
| /// |
| /// These are useful for testing and local development. |
| extension StubDevToolsExtensions on Never { |
| /// Extension for package:foo detected from a running app that requires a |
| /// connected app. |
| static final fooExtension = DevToolsExtensionConfig.parse({ |
| DevToolsExtensionConfig.nameKey: 'foo', |
| DevToolsExtensionConfig.issueTrackerKey: 'www.google.com', |
| DevToolsExtensionConfig.versionKey: '1.0.0', |
| DevToolsExtensionConfig.materialIconCodePointKey: '0xe0b1', |
| DevToolsExtensionConfig.extensionAssetsPathKey: '/absolute/path/to/foo', |
| DevToolsExtensionConfig.devtoolsOptionsUriKey: |
| 'file:///path/to/options/file', |
| DevToolsExtensionConfig.isPubliclyHostedKey: 'false', |
| DevToolsExtensionConfig.detectedFromStaticContextKey: 'false', |
| }); |
| |
| /// Extension for package:provider detected from a running app that requires a |
| /// connected app. |
| static final providerExtension = DevToolsExtensionConfig.parse({ |
| DevToolsExtensionConfig.nameKey: 'provider', |
| DevToolsExtensionConfig.issueTrackerKey: |
| 'https://github.com/rrousselGit/provider/issues', |
| DevToolsExtensionConfig.versionKey: '3.0.0', |
| DevToolsExtensionConfig.materialIconCodePointKey: 0xe50a, |
| DevToolsExtensionConfig.extensionAssetsPathKey: |
| '/absolute/path/to/provider', |
| DevToolsExtensionConfig.devtoolsOptionsUriKey: |
| 'file:///path/to/options/file', |
| DevToolsExtensionConfig.isPubliclyHostedKey: 'true', |
| DevToolsExtensionConfig.detectedFromStaticContextKey: 'false', |
| }); |
| |
| /// Extension for package:some_tool detected from a running app, but that does |
| /// not require a connected app. |
| static final someToolExtension = DevToolsExtensionConfig.parse({ |
| DevToolsExtensionConfig.nameKey: 'some_tool', |
| DevToolsExtensionConfig.issueTrackerKey: 'www.google.com', |
| DevToolsExtensionConfig.versionKey: '1.0.0', |
| DevToolsExtensionConfig.materialIconCodePointKey: '0xe00c', |
| DevToolsExtensionConfig.requiresConnectionKey: 'false', |
| DevToolsExtensionConfig.extensionAssetsPathKey: |
| '/absolute/path/to/some_tool', |
| DevToolsExtensionConfig.devtoolsOptionsUriKey: |
| 'file:///path/to/options/file', |
| DevToolsExtensionConfig.isPubliclyHostedKey: 'false', |
| DevToolsExtensionConfig.detectedFromStaticContextKey: 'false', |
| }); |
| |
| /// Extension for package:bar detected from a static context that does not |
| /// require a connected app. |
| static final barExtension = DevToolsExtensionConfig.parse({ |
| DevToolsExtensionConfig.nameKey: 'bar', |
| DevToolsExtensionConfig.issueTrackerKey: 'www.google.com', |
| DevToolsExtensionConfig.versionKey: '2.0.0', |
| DevToolsExtensionConfig.materialIconCodePointKey: 0xe638, |
| DevToolsExtensionConfig.requiresConnectionKey: 'false', |
| DevToolsExtensionConfig.extensionAssetsPathKey: '/absolute/path/to/bar', |
| DevToolsExtensionConfig.devtoolsOptionsUriKey: |
| 'file:///path/to/options/file', |
| DevToolsExtensionConfig.isPubliclyHostedKey: 'false', |
| DevToolsExtensionConfig.detectedFromStaticContextKey: 'true', |
| }); |
| |
| /// Extension for package:bar detected from a static context that does not |
| /// require a connected app and that is also a newer version of another static |
| /// extension. |
| static final newerBarExtension = DevToolsExtensionConfig.parse({ |
| DevToolsExtensionConfig.nameKey: 'bar', |
| DevToolsExtensionConfig.issueTrackerKey: 'www.google.com', |
| DevToolsExtensionConfig.versionKey: '2.1.0', // Newer version. |
| DevToolsExtensionConfig.materialIconCodePointKey: 0xe638, |
| DevToolsExtensionConfig.requiresConnectionKey: 'false', |
| DevToolsExtensionConfig.extensionAssetsPathKey: '/absolute/path/to/bar', |
| DevToolsExtensionConfig.devtoolsOptionsUriKey: |
| 'file:///path/to/options/file', |
| DevToolsExtensionConfig.isPubliclyHostedKey: 'false', |
| DevToolsExtensionConfig.detectedFromStaticContextKey: 'true', |
| }); |
| |
| /// Extension for package:baz detected from a static context that requires a |
| /// connected app. |
| static final bazExtension = DevToolsExtensionConfig.parse({ |
| DevToolsExtensionConfig.nameKey: 'baz', |
| DevToolsExtensionConfig.issueTrackerKey: 'www.google.com', |
| DevToolsExtensionConfig.versionKey: '1.0.0', |
| DevToolsExtensionConfig.materialIconCodePointKey: 0xe716, |
| DevToolsExtensionConfig.extensionAssetsPathKey: '/absolute/path/to/baz', |
| DevToolsExtensionConfig.devtoolsOptionsUriKey: |
| 'file:///path/to/options/file', |
| DevToolsExtensionConfig.isPubliclyHostedKey: 'false', |
| DevToolsExtensionConfig.detectedFromStaticContextKey: 'true', |
| }); |
| |
| /// Extension for package:foo detected from a static context that is a duplicate |
| /// of a runtime extension [fooExtension], which requires a connected app. |
| static final duplicateFooExtension = DevToolsExtensionConfig.parse({ |
| DevToolsExtensionConfig.nameKey: 'foo', |
| DevToolsExtensionConfig.issueTrackerKey: 'www.google.com', |
| DevToolsExtensionConfig.versionKey: '1.0.0', |
| DevToolsExtensionConfig.materialIconCodePointKey: '0xe0b1', |
| DevToolsExtensionConfig.extensionAssetsPathKey: '/absolute/path/to/foo', |
| DevToolsExtensionConfig.devtoolsOptionsUriKey: |
| 'file:///path/to/options/file', |
| DevToolsExtensionConfig.isPubliclyHostedKey: 'false', |
| DevToolsExtensionConfig.detectedFromStaticContextKey: 'true', |
| }); |
| |
| /// Stubbed extensions so we can develop DevTools Extensions without a server |
| /// connection. |
| static List<DevToolsExtensionConfig> extensions({ |
| bool includeRuntime = true, |
| }) => [ |
| if (includeRuntime) ...[fooExtension, providerExtension, someToolExtension], |
| barExtension, |
| newerBarExtension, |
| bazExtension, |
| duplicateFooExtension, |
| ]; |
| } |
| |
| /// Enable this flag to debug the DevTools survey logic. |
| /// |
| /// When this flag is true, [debugSurveyMetadata] will be used instead of what |
| /// we normally fetch from |
| /// 'https://storage.googleapis.com/flutter-uxr/surveys/devtools-survey-metadata.json'. |
| bool debugSurvey = false; |
| |
| /// The survey metadata that will be used instead of the live data from |
| /// 'https://storage.googleapis.com/flutter-uxr/surveys/devtools-survey-metadata.json' |
| /// when [debugSurvey] is true. |
| final debugSurveyMetadata = DevToolsSurvey.fromJson({ |
| '_comments': [ |
| 'uniqueId must be updated with each new survey so DevTools knows to re-prompt users.', |
| 'title should not exceed 45 characters.', |
| 'startDate and endDate should follow ISO 8601 standard with a timezone offset.', |
| ], |
| 'uniqueId': '2024-Q2', |
| 'title': 'Take our survey to help us improve DevTools!', |
| 'url': 'https://google.qualtrics.com/jfe/form/SV_2l4XcyscF8mQtDM', |
| 'startDate': '2024-06-18T09:00:00-07:00', |
| 'endDate': '2024-07-02T09:00:00-07:00', |
| 'minDevToolsVersion': '2.35.0', |
| // This list is optional and can be used to limit the survey to a specific |
| // set of users based on their development environment. |
| 'devEnvironments': ['Android-Studio', 'IntelliJ-IDEA', 'VSCode', 'CLI'], |
| }); |
| |
| /// Enable this flag to debug Perfetto trace processing in the Performance |
| /// screen. |
| /// |
| /// When this flag is true, helpful print debugging will be emitted signaling |
| /// important data for the trace processing logic. |
| /// |
| /// This flag has performance implications, since printing a lot of data to the |
| /// command line can be expensive. |
| const debugPerfettoTraceProcessing = !kReleaseMode && false; |
| |
| /// Helper method to call a callback only when debugging issues related to trace |
| /// event duplicates (for example https://github.com/dart-lang/sdk/issues/46605). |
| void debugTraceCallback(void Function() callback) { |
| if (debugPerfettoTraceProcessing) { |
| callback(); |
| } |
| } |
| |
| /// Enable this flag to print timing information for callbacks wrapped in |
| /// [debugTimeSync] or [debugTimeAsync]. |
| const debugTimers = !kReleaseMode && false; |
| |
| /// Debug helper to run a synchronous [callback] and print the time it took to |
| /// run to stdout. |
| /// |
| /// This will only time the operation when [debugTimers] is true. |
| void debugTimeSync(void Function() callback, {required String debugName}) { |
| if (!debugTimers) { |
| callback(); |
| return; |
| } |
| final now = DateTime.now().millisecondsSinceEpoch; |
| callback(); |
| final time = DateTime.now().millisecondsSinceEpoch - now; |
| _log.info('$debugName: $time ms'); |
| } |
| |
| /// Debug helper to run an asynchronous [callback] and print the time it took to |
| /// run to stdout. |
| /// |
| /// This will only time the operation when [debugTimers] is true. |
| FutureOr<void> debugTimeAsync( |
| FutureOr<void> Function() callback, { |
| required String debugName, |
| }) async { |
| if (!debugTimers) { |
| await callback(); |
| return; |
| } |
| final now = DateTime.now().millisecondsSinceEpoch; |
| await callback(); |
| final time = DateTime.now().millisecondsSinceEpoch - now; |
| _log.info('$debugName: $time ms'); |
| } |