Improve tracing (#93086)
This commit is contained in:
parent
14cf445acd
commit
840e109e07
12
.ci.yaml
12
.ci.yaml
@ -398,7 +398,9 @@ targets:
|
|||||||
{"dependency": "goldctl"},
|
{"dependency": "goldctl"},
|
||||||
{"dependency": "clang"},
|
{"dependency": "clang"},
|
||||||
{"dependency": "cmake"},
|
{"dependency": "cmake"},
|
||||||
{"dependency": "ninja"}
|
{"dependency": "ninja"},
|
||||||
|
{"dependency": "open_jdk"},
|
||||||
|
{"dependency": "android_sdk", "version": "version:29.0"}
|
||||||
]
|
]
|
||||||
shard: framework_tests
|
shard: framework_tests
|
||||||
subshard: misc
|
subshard: misc
|
||||||
@ -2108,7 +2110,9 @@ targets:
|
|||||||
[
|
[
|
||||||
{"dependency": "goldctl"},
|
{"dependency": "goldctl"},
|
||||||
{"dependency": "xcode"},
|
{"dependency": "xcode"},
|
||||||
{"dependency": "gems"}
|
{"dependency": "gems"},
|
||||||
|
{"dependency": "open_jdk"},
|
||||||
|
{"dependency": "android_sdk", "version": "version:29.0"}
|
||||||
]
|
]
|
||||||
shard: framework_tests
|
shard: framework_tests
|
||||||
subshard: misc
|
subshard: misc
|
||||||
@ -3642,7 +3646,9 @@ targets:
|
|||||||
dependencies: >-
|
dependencies: >-
|
||||||
[
|
[
|
||||||
{"dependency": "goldctl"},
|
{"dependency": "goldctl"},
|
||||||
{"dependency": "vs_build", "version": "version:vs2019"}
|
{"dependency": "vs_build", "version": "version:vs2019"},
|
||||||
|
{"dependency": "open_jdk"},
|
||||||
|
{"dependency": "android_sdk", "version": "version:29.0"}
|
||||||
]
|
]
|
||||||
shard: framework_tests
|
shard: framework_tests
|
||||||
subshard: misc
|
subshard: misc
|
||||||
|
@ -15,11 +15,11 @@ dependencies:
|
|||||||
platform: 3.1.0
|
platform: 3.1.0
|
||||||
process: 4.2.4
|
process: 4.2.4
|
||||||
test: 1.19.5
|
test: 1.19.5
|
||||||
|
archive: 3.1.6
|
||||||
|
|
||||||
_discoveryapis_commons: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
_discoveryapis_commons: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
_fe_analyzer_shared: 31.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
_fe_analyzer_shared: 31.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
analyzer: 2.8.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
analyzer: 2.8.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
archive: 3.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
|
||||||
async: 2.8.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
async: 2.8.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
boolean_selector: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
boolean_selector: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
charcode: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
charcode: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
@ -6,7 +6,9 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
import 'package:file/file.dart' as fs;
|
import 'package:file/file.dart' as fs;
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
@ -744,6 +746,89 @@ Future<void> _runFrameworkTests() async {
|
|||||||
await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), options: soundNullSafetyOptions);
|
await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), options: soundNullSafetyOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> runTracingTests() async {
|
||||||
|
final String tracingDirectory = path.join(flutterRoot, 'dev', 'tracing_tests');
|
||||||
|
|
||||||
|
// run the tests for debug mode
|
||||||
|
await _runFlutterTest(tracingDirectory, options: <String>['--enable-vmservice']);
|
||||||
|
|
||||||
|
Future<List<String>> verifyTracingAppBuild({
|
||||||
|
required String modeArgument,
|
||||||
|
required String sourceFile,
|
||||||
|
required Set<String> allowed,
|
||||||
|
required Set<String> disallowed,
|
||||||
|
}) async {
|
||||||
|
await runCommand(
|
||||||
|
flutter,
|
||||||
|
<String>[
|
||||||
|
'build', 'appbundle', '--$modeArgument', path.join('lib', sourceFile),
|
||||||
|
],
|
||||||
|
workingDirectory: tracingDirectory,
|
||||||
|
);
|
||||||
|
final Archive archive = ZipDecoder().decodeBytes(File(path.join(tracingDirectory, 'build', 'app', 'outputs', 'bundle', modeArgument, 'app-$modeArgument.aab')).readAsBytesSync());
|
||||||
|
final ArchiveFile libapp = archive.findFile('base/lib/arm64-v8a/libapp.so')!;
|
||||||
|
final Uint8List libappBytes = libapp.content as Uint8List; // bytes decompressed here
|
||||||
|
final String libappStrings = utf8.decode(libappBytes, allowMalformed: true);
|
||||||
|
await runCommand(flutter, <String>['clean'], workingDirectory: tracingDirectory);
|
||||||
|
final List<String> results = <String>[];
|
||||||
|
for (final String pattern in allowed) {
|
||||||
|
if (!libappStrings.contains(pattern)) {
|
||||||
|
results.add('When building with --$modeArgument, expected to find "$pattern" in libapp.so but could not find it.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final String pattern in disallowed) {
|
||||||
|
if (libappStrings.contains(pattern)) {
|
||||||
|
results.add('When building with --$modeArgument, expected to not find "$pattern" in libapp.so but did find it.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> results = <String>[];
|
||||||
|
results.addAll(await verifyTracingAppBuild(
|
||||||
|
modeArgument: 'profile',
|
||||||
|
sourceFile: 'control.dart', // this is the control, the other two below are the actual test
|
||||||
|
allowed: <String>{
|
||||||
|
'TIMELINE ARGUMENTS TEST CONTROL FILE',
|
||||||
|
'toTimelineArguments used in non-debug build', // we call toTimelineArguments directly to check the message does exist
|
||||||
|
},
|
||||||
|
disallowed: <String>{
|
||||||
|
'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE',
|
||||||
|
},
|
||||||
|
));
|
||||||
|
results.addAll(await verifyTracingAppBuild(
|
||||||
|
modeArgument: 'profile',
|
||||||
|
sourceFile: 'test.dart',
|
||||||
|
allowed: <String>{
|
||||||
|
'BUILT IN PROFILE MODE', 'RenderTest.performResize called', // controls
|
||||||
|
'BUILD', 'LAYOUT', 'PAINT', // we output these to the timeline in profile builds
|
||||||
|
// (LAYOUT and PAINT also exist because of NEEDS-LAYOUT and NEEDS-PAINT in RenderObject.toStringShort)
|
||||||
|
},
|
||||||
|
disallowed: <String>{
|
||||||
|
'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE',
|
||||||
|
'TestWidget.debugFillProperties called', 'RenderTest.debugFillProperties called', // debug only
|
||||||
|
'toTimelineArguments used in non-debug build', // entire function should get dropped by tree shaker
|
||||||
|
},
|
||||||
|
));
|
||||||
|
results.addAll(await verifyTracingAppBuild(
|
||||||
|
modeArgument: 'release',
|
||||||
|
sourceFile: 'test.dart',
|
||||||
|
allowed: <String>{
|
||||||
|
'BUILT IN RELEASE MODE', 'RenderTest.performResize called', // controls
|
||||||
|
},
|
||||||
|
disallowed: <String>{
|
||||||
|
'BUILT IN DEBUG MODE', 'BUILT IN PROFILE MODE',
|
||||||
|
'BUILD', 'LAYOUT', 'PAINT', // these are only used in Timeline.startSync calls that should not appear in release builds
|
||||||
|
'TestWidget.debugFillProperties called', 'RenderTest.debugFillProperties called', // debug only
|
||||||
|
'toTimelineArguments used in non-debug build', // not included in release builds
|
||||||
|
},
|
||||||
|
));
|
||||||
|
if (results.isNotEmpty) {
|
||||||
|
print(results.join('\n'));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> runFixTests() async {
|
Future<void> runFixTests() async {
|
||||||
final List<String> args = <String>[
|
final List<String> args = <String>[
|
||||||
'fix',
|
'fix',
|
||||||
@ -808,10 +893,7 @@ Future<void> _runFrameworkTests() async {
|
|||||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), options: soundNullSafetyOptions);
|
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), options: soundNullSafetyOptions);
|
||||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), options: soundNullSafetyOptions);
|
await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), options: soundNullSafetyOptions);
|
||||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: mixedModeNullSafetyOptions);
|
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: mixedModeNullSafetyOptions);
|
||||||
await _runFlutterTest(
|
await runTracingTests();
|
||||||
path.join(flutterRoot, 'dev', 'tracing_tests'),
|
|
||||||
options: <String>['--enable-vmservice'],
|
|
||||||
);
|
|
||||||
await runFixTests();
|
await runFixTests();
|
||||||
await runPrivateTests();
|
await runPrivateTests();
|
||||||
const String httpClientWarning =
|
const String httpClientWarning =
|
||||||
|
@ -382,14 +382,12 @@ class _VideoDemoState extends State<VideoDemo> with SingleTickerProviderStateMix
|
|||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
Future<void> initController(VideoPlayerController controller, String name) async {
|
Future<void> initController(VideoPlayerController controller, String name) async {
|
||||||
print('> VideoDemo initController "$name" ${isDisposed ? "DISPOSED" : ""}');
|
|
||||||
controller.setLooping(true);
|
controller.setLooping(true);
|
||||||
controller.setVolume(0.0);
|
controller.setVolume(0.0);
|
||||||
controller.play();
|
controller.play();
|
||||||
await connectedCompleter.future;
|
await connectedCompleter.future;
|
||||||
await controller.initialize();
|
await controller.initialize();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
print('< VideoDemo initController "$name" done ${isDisposed ? "DISPOSED" : ""}');
|
|
||||||
setState(() { });
|
setState(() { });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,11 +401,9 @@ class _VideoDemoState extends State<VideoDemo> with SingleTickerProviderStateMix
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
print('> VideoDemo dispose');
|
|
||||||
isDisposed = true;
|
isDisposed = true;
|
||||||
butterflyController.dispose();
|
butterflyController.dispose();
|
||||||
beeController.dispose();
|
beeController.dispose();
|
||||||
print('< VideoDemo dispose');
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,4 +2,20 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
export 'package:flutter_goldens/flutter_goldens.dart' show testExecutable;
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_goldens/flutter_goldens.dart' as flutter_goldens show testExecutable;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
Future<void> testExecutable(FutureOr<void> Function() testMain) {
|
||||||
|
// Enable extra checks since this package exercises a lot of the framework.
|
||||||
|
debugCheckIntrinsicSizes = true;
|
||||||
|
|
||||||
|
// Make tap() et al fail if the given finder specifies a widget that would not
|
||||||
|
// receive the event.
|
||||||
|
WidgetController.hitTestWarningShouldBeFatal = true;
|
||||||
|
|
||||||
|
// Enable golden file testing using Skia Gold.
|
||||||
|
return flutter_goldens.testExecutable(testMain);
|
||||||
|
}
|
||||||
|
@ -118,7 +118,7 @@ Future<void> smokeOptionsPage(WidgetTester tester) async {
|
|||||||
// Switch back to system theme setting: first menu button, choose 'System Default'
|
// Switch back to system theme setting: first menu button, choose 'System Default'
|
||||||
await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
|
await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
await tester.tap(find.text('System Default').at(1));
|
await tester.tap(find.text('System Default').at(1), warnIfMissed: false); // https://github.com/flutter/flutter/issues/82908
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Switch text direction: first switch
|
// Switch text direction: first switch
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
# Tracing tests
|
# Tracing tests
|
||||||
|
|
||||||
|
## "Application"
|
||||||
|
|
||||||
|
The `lib/test.dart` and `lib/control.dart` files in this directory are
|
||||||
|
used by `dev/bots/test.dart`'s `runTracingTests` function to check
|
||||||
|
whether aspects of the tracing logic in the framework get compiled out
|
||||||
|
in profile and release builds. They're not meant to be run directly.
|
||||||
|
|
||||||
|
The strings in these files are used in `dev/bots/test.dart`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
The tests in this folder must be run with `flutter test --enable-vmservice`,
|
The tests in this folder must be run with `flutter test --enable-vmservice`,
|
||||||
since they test that trace data is written to the timeline by connecting to
|
since they test that trace data is written to the timeline by connecting to
|
||||||
the observatory.
|
the observatory.
|
||||||
|
13
dev/tracing_tests/android/.gitignore
vendored
Normal file
13
dev/tracing_tests/android/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
72
dev/tracing_tests/android/app/build.gradle
Normal file
72
dev/tracing_tests/android/app/build.gradle
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.example.tracing_tests"
|
||||||
|
minSdkVersion flutter.minSdkVersion
|
||||||
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
11
dev/tracing_tests/android/app/src/debug/AndroidManifest.xml
Normal file
11
dev/tracing_tests/android/app/src/debug/AndroidManifest.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.tracing_tests">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
38
dev/tracing_tests/android/app/src/main/AndroidManifest.xml
Normal file
38
dev/tracing_tests/android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.tracing_tests">
|
||||||
|
<application
|
||||||
|
android:label="tracing_tests"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.example.tracing_tests
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
@ -0,0 +1,15 @@
|
|||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
Binary file not shown.
After Width: | Height: | Size: 544 B |
Binary file not shown.
After Width: | Height: | Size: 442 B |
Binary file not shown.
After Width: | Height: | Size: 721 B |
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,21 @@
|
|||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
21
dev/tracing_tests/android/app/src/main/res/values/styles.xml
Normal file
21
dev/tracing_tests/android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -0,0 +1,11 @@
|
|||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.tracing_tests">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
35
dev/tracing_tests/android/build.gradle
Normal file
35
dev/tracing_tests/android/build.gradle
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.6.0'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
3
dev/tracing_tests/android/gradle.properties
Normal file
3
dev/tracing_tests/android/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
6
dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#Fri Jun 23 08:50:38 CEST 2017
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
15
dev/tracing_tests/android/settings.gradle
Normal file
15
dev/tracing_tests/android/settings.gradle
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
16
dev/tracing_tests/lib/control.dart
Normal file
16
dev/tracing_tests/lib/control.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file is part of dev/bots/test.dart's runTracingTests test.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// This file is intended to be compiled in profile mode.
|
||||||
|
// In that mode, the function below throws an exception.
|
||||||
|
// The dev/bots/test.dart test looks for the string from that exception.
|
||||||
|
// The string below is matched verbatim in dev/bots/test.dart as a control
|
||||||
|
// to make sure this file did get compiled.
|
||||||
|
DiagnosticsNode.message('TIMELINE ARGUMENTS TEST CONTROL FILE').toTimelineArguments();
|
||||||
|
}
|
70
dev/tracing_tests/lib/test.dart
Normal file
70
dev/tracing_tests/lib/test.dart
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2014 The Flutter 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:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class TestWidget extends LeafRenderObjectWidget {
|
||||||
|
const TestWidget({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) => RenderTest();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
// This string is searched for verbatim by dev/bots/test.dart:
|
||||||
|
properties.add(MessageProperty('test', 'TestWidget.debugFillProperties called'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderTest extends RenderBox {
|
||||||
|
@override
|
||||||
|
bool get sizedByParent => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performResize() {
|
||||||
|
Timeline.instantSync('RenderTest.performResize called');
|
||||||
|
size = constraints.biggest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
// This string is searched for verbatim by dev/bots/test.dart:
|
||||||
|
properties.add(MessageProperty('test', 'RenderTest.debugFillProperties called'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
// This section introduces strings that we can search for in dev/bots/test.dart
|
||||||
|
// as a sanity check:
|
||||||
|
if (kDebugMode) {
|
||||||
|
print('BUILT IN DEBUG MODE');
|
||||||
|
}
|
||||||
|
if (kProfileMode) {
|
||||||
|
print('BUILT IN PROFILE MODE');
|
||||||
|
}
|
||||||
|
if (kReleaseMode) {
|
||||||
|
print('BUILT IN RELEASE MODE');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The point of this file is to make sure that toTimelineArguments is not
|
||||||
|
// called when we have debugProfileBuildsEnabled (et al) turned on. If that
|
||||||
|
// method is not called then the debugFillProperties methods above should also
|
||||||
|
// not get called and we should end up tree-shaking the entire Diagnostics
|
||||||
|
// logic out of the app. The dev/bots/test.dart test checks for this by
|
||||||
|
// looking for the strings in the methods above.
|
||||||
|
|
||||||
|
debugProfileBuildsEnabled = true;
|
||||||
|
debugProfileLayoutsEnabled = true;
|
||||||
|
debugProfilePaintsEnabled = true;
|
||||||
|
runApp(const TestWidget());
|
||||||
|
}
|
34
dev/tracing_tests/test/common.dart
Normal file
34
dev/tracing_tests/test/common.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2014 The Flutter 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:developer' as developer;
|
||||||
|
import 'dart:isolate' as isolate;
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:vm_service/vm_service.dart';
|
||||||
|
import 'package:vm_service/vm_service_io.dart';
|
||||||
|
|
||||||
|
export 'package:vm_service/vm_service.dart' show TimelineEvent;
|
||||||
|
|
||||||
|
late String isolateId;
|
||||||
|
|
||||||
|
late VmService _vmService;
|
||||||
|
|
||||||
|
void initTimelineTests() {
|
||||||
|
setUpAll(() async {
|
||||||
|
final developer.ServiceProtocolInfo info = await developer.Service.getInfo();
|
||||||
|
if (info.serverUri == null) {
|
||||||
|
fail('This test _must_ be run with --enable-vmservice.');
|
||||||
|
}
|
||||||
|
_vmService = await vmServiceConnectUri('ws://localhost:${info.serverUri!.port}${info.serverUri!.path}ws');
|
||||||
|
await _vmService.setVMTimelineFlags(<String>['Dart']);
|
||||||
|
isolateId = developer.Service.getIsolateID(isolate.Isolate.current)!;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TimelineEvent>> fetchTimelineEvents() async {
|
||||||
|
final Timeline timeline = await _vmService.getVMTimeline();
|
||||||
|
await _vmService.clearVMTimeline();
|
||||||
|
return timeline.traceEvents!;
|
||||||
|
}
|
@ -3,31 +3,15 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer' as developer;
|
|
||||||
import 'dart:isolate' as isolate;
|
|
||||||
|
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:vm_service/vm_service.dart';
|
|
||||||
import 'package:vm_service/vm_service_io.dart';
|
import 'common.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late VmService vmService;
|
initTimelineTests();
|
||||||
late String isolateId;
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
setUpAll(() async {
|
|
||||||
final developer.ServiceProtocolInfo info = await developer.Service.getInfo();
|
|
||||||
|
|
||||||
if (info.serverUri == null) {
|
|
||||||
fail('This test _must_ be run with --enable-vmservice.');
|
|
||||||
}
|
|
||||||
|
|
||||||
vmService = await vmServiceConnectUri('ws://localhost:${info.serverUri!.port}${info.serverUri!.path}ws');
|
|
||||||
await vmService.setVMTimelineFlags(<String>['Dart']);
|
|
||||||
isolateId = developer.Service.getIsolateID(isolate.Isolate.current)!;
|
|
||||||
|
|
||||||
// Initialize the image cache.
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Image cache tracing', () async {
|
test('Image cache tracing', () async {
|
||||||
final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
|
final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
|
||||||
@ -45,9 +29,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
PaintingBinding.instance!.imageCache!.evict('Test2');
|
PaintingBinding.instance!.imageCache!.evict('Test2');
|
||||||
|
|
||||||
final Timeline timeline = await vmService.getVMTimeline();
|
|
||||||
_expectTimelineEvents(
|
_expectTimelineEvents(
|
||||||
timeline.traceEvents!,
|
await fetchTimelineEvents(),
|
||||||
<Map<String, dynamic>>[
|
<Map<String, dynamic>>[
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'name': 'ImageCache.putIfAbsent',
|
'name': 'ImageCache.putIfAbsent',
|
||||||
|
155
dev/tracing_tests/test/timeline_test.dart
Normal file
155
dev/tracing_tests/test/timeline_test.dart
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2014 The Flutter 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:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
final Set<String> interestingLabels = <String>{
|
||||||
|
'BUILD',
|
||||||
|
'LAYOUT',
|
||||||
|
'UPDATING COMPOSITING BITS',
|
||||||
|
'PAINT',
|
||||||
|
'COMPOSITING',
|
||||||
|
'FINALIZE TREE',
|
||||||
|
'$Placeholder',
|
||||||
|
'$CustomPaint',
|
||||||
|
'$RenderCustomPaint',
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<List<TimelineEvent>> fetchInterestingEvents() async {
|
||||||
|
return (await fetchTimelineEvents()).where((TimelineEvent event) {
|
||||||
|
return interestingLabels.contains(event.json!['name'])
|
||||||
|
&& event.json!['ph'] == 'B'; // "Begin" mark of events, vs E which is for the "End" mark of events.
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
String eventToName(TimelineEvent event) => event.json!['name'] as String;
|
||||||
|
|
||||||
|
class TestRoot extends StatefulWidget {
|
||||||
|
const TestRoot({ Key? key }) : super(key: key);
|
||||||
|
|
||||||
|
static late final TestRootState state;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TestRoot> createState() => TestRootState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestRootState extends State<TestRoot> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
TestRoot.state = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _widget = const Placeholder();
|
||||||
|
|
||||||
|
void updateWidget(Widget newWidget) {
|
||||||
|
setState(() {
|
||||||
|
_widget = newWidget;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuild() {
|
||||||
|
setState(() {
|
||||||
|
// no change, just force a rebuild
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _widget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> runFrame(VoidCallback callback) {
|
||||||
|
final Future<void> result = SchedulerBinding.instance!.endOfFrame; // schedules a frame
|
||||||
|
callback();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
initTimelineTests();
|
||||||
|
test('Timeline', () async {
|
||||||
|
// We don't have expectations around the first frame because there's a race around
|
||||||
|
// the warm-up frame that we don't want to get involved in here.
|
||||||
|
await runFrame(() { runApp(const TestRoot()); });
|
||||||
|
await SchedulerBinding.instance!.endOfFrame;
|
||||||
|
await fetchInterestingEvents();
|
||||||
|
|
||||||
|
// The next few cases build the exact same tree so should have no effect.
|
||||||
|
|
||||||
|
debugProfileBuildsEnabled = true;
|
||||||
|
await runFrame(() { TestRoot.state.rebuild(); });
|
||||||
|
expect(
|
||||||
|
(await fetchInterestingEvents()).map<String>(eventToName),
|
||||||
|
<String>['BUILD', 'LAYOUT', 'UPDATING COMPOSITING BITS', 'PAINT', 'COMPOSITING', 'FINALIZE TREE'],
|
||||||
|
);
|
||||||
|
debugProfileBuildsEnabled = false;
|
||||||
|
|
||||||
|
debugProfileLayoutsEnabled = true;
|
||||||
|
await runFrame(() { TestRoot.state.rebuild(); });
|
||||||
|
expect(
|
||||||
|
(await fetchInterestingEvents()).map<String>(eventToName),
|
||||||
|
<String>['BUILD', 'LAYOUT', 'UPDATING COMPOSITING BITS', 'PAINT', 'COMPOSITING', 'FINALIZE TREE'],
|
||||||
|
);
|
||||||
|
debugProfileLayoutsEnabled = false;
|
||||||
|
|
||||||
|
debugProfilePaintsEnabled = true;
|
||||||
|
await runFrame(() { TestRoot.state.rebuild(); });
|
||||||
|
expect(
|
||||||
|
(await fetchInterestingEvents()).map<String>(eventToName),
|
||||||
|
<String>['BUILD', 'LAYOUT', 'UPDATING COMPOSITING BITS', 'PAINT', 'COMPOSITING', 'FINALIZE TREE'],
|
||||||
|
);
|
||||||
|
debugProfilePaintsEnabled = false;
|
||||||
|
|
||||||
|
|
||||||
|
// Now we replace the widgets each time to cause a rebuild.
|
||||||
|
|
||||||
|
List<TimelineEvent> events;
|
||||||
|
Map<String, String> args;
|
||||||
|
|
||||||
|
debugProfileBuildsEnabled = true;
|
||||||
|
await runFrame(() { TestRoot.state.updateWidget(Placeholder(key: UniqueKey(), color: const Color(0xFFFFFFFF))); });
|
||||||
|
events = await fetchInterestingEvents();
|
||||||
|
expect(
|
||||||
|
events.map<String>(eventToName),
|
||||||
|
<String>['BUILD', 'Placeholder', 'CustomPaint', 'LAYOUT', 'UPDATING COMPOSITING BITS', 'PAINT', 'COMPOSITING', 'FINALIZE TREE'],
|
||||||
|
);
|
||||||
|
args = (events.where((TimelineEvent event) => event.json!['name'] == '$Placeholder').single.json!['args'] as Map<String, Object?>).cast<String, String>();
|
||||||
|
expect(args['color'], 'Color(0xffffffff)');
|
||||||
|
debugProfileBuildsEnabled = false;
|
||||||
|
|
||||||
|
debugProfileLayoutsEnabled = true;
|
||||||
|
await runFrame(() { TestRoot.state.updateWidget(Placeholder(key: UniqueKey())); });
|
||||||
|
events = await fetchInterestingEvents();
|
||||||
|
expect(
|
||||||
|
events.map<String>(eventToName),
|
||||||
|
<String>['BUILD', 'LAYOUT', 'RenderCustomPaint', 'UPDATING COMPOSITING BITS', 'PAINT', 'COMPOSITING', 'FINALIZE TREE'],
|
||||||
|
);
|
||||||
|
args = (events.where((TimelineEvent event) => event.json!['name'] == '$RenderCustomPaint').single.json!['args'] as Map<String, Object?>).cast<String, String>();
|
||||||
|
expect(args['creator'], startsWith('CustomPaint'));
|
||||||
|
expect(args['creator'], contains('Placeholder'));
|
||||||
|
expect(args['foregroundPainter'], startsWith('_PlaceholderPainter#'));
|
||||||
|
debugProfileLayoutsEnabled = false;
|
||||||
|
|
||||||
|
debugProfilePaintsEnabled = true;
|
||||||
|
await runFrame(() { TestRoot.state.updateWidget(Placeholder(key: UniqueKey())); });
|
||||||
|
events = await fetchInterestingEvents();
|
||||||
|
expect(
|
||||||
|
events.map<String>(eventToName),
|
||||||
|
<String>['BUILD', 'LAYOUT', 'UPDATING COMPOSITING BITS', 'PAINT', 'RenderCustomPaint', 'COMPOSITING', 'FINALIZE TREE'],
|
||||||
|
);
|
||||||
|
args = (events.where((TimelineEvent event) => event.json!['name'] == '$RenderCustomPaint').single.json!['args'] as Map<String, Object?>).cast<String, String>();
|
||||||
|
expect(args['creator'], startsWith('CustomPaint'));
|
||||||
|
expect(args['creator'], contains('Placeholder'));
|
||||||
|
expect(args['foregroundPainter'], startsWith('_PlaceholderPainter#'));
|
||||||
|
debugProfilePaintsEnabled = false;
|
||||||
|
|
||||||
|
}, skip: isBrowser); // [intended] uses dart:isolate and io.
|
||||||
|
}
|
@ -35,6 +35,18 @@ bool debugAssertAllFoundationVarsUnset(String reason, { DebugPrintCallback debug
|
|||||||
|
|
||||||
/// Boolean value indicating whether [debugInstrumentAction] will instrument
|
/// Boolean value indicating whether [debugInstrumentAction] will instrument
|
||||||
/// actions in debug builds.
|
/// actions in debug builds.
|
||||||
|
///
|
||||||
|
/// The framework does not use [debugInstrumentAction] internally, so this
|
||||||
|
/// does not enable any additional instrumentation for the framework itself.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [debugProfileBuildsEnabled], which enables additional tracing of builds
|
||||||
|
/// in [Widget]s.
|
||||||
|
/// * [debugProfileLayoutsEnabled], which enables additional tracing of layout
|
||||||
|
/// events in [RenderObject]s.
|
||||||
|
/// * [debugProfilePaintsEnabled], which enables additional tracing of paint
|
||||||
|
/// events in [RenderObject]s.
|
||||||
bool debugInstrumentationEnabled = false;
|
bool debugInstrumentationEnabled = false;
|
||||||
|
|
||||||
/// Runs the specified [action], timing how long the action takes in debug
|
/// Runs the specified [action], timing how long the action takes in debug
|
||||||
@ -76,6 +88,9 @@ Future<T> debugInstrumentAction<T>(String description, Future<T> Function() acti
|
|||||||
///
|
///
|
||||||
/// Generally these indicate landmark events such as the build phase or layout.
|
/// Generally these indicate landmark events such as the build phase or layout.
|
||||||
///
|
///
|
||||||
|
/// [DiagnosticsNode.toTimelineArguments] includes these properties in its
|
||||||
|
/// result.
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [dart:developer.Timeline.startSync], which typically takes this value as
|
/// * [dart:developer.Timeline.startSync], which typically takes this value as
|
||||||
|
@ -1265,7 +1265,7 @@ class TextTreeRenderer {
|
|||||||
// we should not place a separator between the name and the value.
|
// we should not place a separator between the name and the value.
|
||||||
// Essentially in this case the properties are treated a bit like a value.
|
// Essentially in this case the properties are treated a bit like a value.
|
||||||
if ((properties.isNotEmpty || children.isNotEmpty || node.emptyBodyDescription != null) &&
|
if ((properties.isNotEmpty || children.isNotEmpty || node.emptyBodyDescription != null) &&
|
||||||
(node.showSeparator || description?.isNotEmpty == true)) {
|
(node.showSeparator || description.isNotEmpty)) {
|
||||||
builder.write(config.afterDescriptionIfBody);
|
builder.write(config.afterDescriptionIfBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1474,7 +1474,7 @@ abstract class DiagnosticsNode {
|
|||||||
/// `parentConfiguration` specifies how the parent is rendered as text art.
|
/// `parentConfiguration` specifies how the parent is rendered as text art.
|
||||||
/// For example, if the parent does not line break between properties, the
|
/// For example, if the parent does not line break between properties, the
|
||||||
/// description of a property should also be a single line if possible.
|
/// description of a property should also be a single line if possible.
|
||||||
String? toDescription({ TextTreeConfiguration? parentConfiguration });
|
String toDescription({ TextTreeConfiguration? parentConfiguration });
|
||||||
|
|
||||||
/// Whether to show a separator between [name] and description.
|
/// Whether to show a separator between [name] and description.
|
||||||
///
|
///
|
||||||
@ -1544,6 +1544,44 @@ abstract class DiagnosticsNode {
|
|||||||
|
|
||||||
String get _separator => showSeparator ? ':' : '';
|
String get _separator => showSeparator ? ':' : '';
|
||||||
|
|
||||||
|
/// Converts the properties ([getProperties]) of this node to a form useful
|
||||||
|
/// for [Timeline] event arguments (as in [Timeline.startSync]).
|
||||||
|
///
|
||||||
|
/// The properties specified by [timelineArgumentsIndicatingLandmarkEvent] are
|
||||||
|
/// included in the result.
|
||||||
|
///
|
||||||
|
/// Children ([getChildren]) are omitted.
|
||||||
|
///
|
||||||
|
/// This method is only valid in debug builds. In profile builds, this method
|
||||||
|
/// throws an exception. In release builds, it returns a copy of
|
||||||
|
/// [timelineArgumentsIndicatingLandmarkEvent] with no arguments added.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [toJsonMap], which converts this node to a structured form intended for
|
||||||
|
/// data exchange (e.g. with an IDE).
|
||||||
|
Map<String, String> toTimelineArguments() {
|
||||||
|
final Map<String, String> result = Map<String, String>.of(timelineArgumentsIndicatingLandmarkEvent);
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
// We don't throw in release builds, to avoid hurting users. We also don't do anything useful.
|
||||||
|
if (kProfileMode) {
|
||||||
|
throw FlutterError(
|
||||||
|
// Parts of this string are searched for verbatim by a test in dev/bots/test.dart.
|
||||||
|
'$DiagnosticsNode.toTimelineArguments used in non-debug build.\n'
|
||||||
|
'The $DiagnosticsNode.toTimelineArguments API is expensive and causes timeline traces '
|
||||||
|
'to be non-representative. As such, it should not be used in profile builds. However, '
|
||||||
|
'this application is compiled in profile mode and yet still invoked the method.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (final DiagnosticsNode property in getProperties()) {
|
||||||
|
if (property.name != null) {
|
||||||
|
result[property.name!] = property.toDescription(parentConfiguration: singleLineTextConfiguration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// Serialize the node to a JSON map according to the configuration provided
|
/// Serialize the node to a JSON map according to the configuration provided
|
||||||
/// in the [DiagnosticsSerializationDelegate].
|
/// in the [DiagnosticsSerializationDelegate].
|
||||||
///
|
///
|
||||||
@ -1655,7 +1693,7 @@ abstract class DiagnosticsNode {
|
|||||||
if (_isSingleLine(style)) {
|
if (_isSingleLine(style)) {
|
||||||
result = toStringDeep(parentConfiguration: parentConfiguration, minLevel: minLevel);
|
result = toStringDeep(parentConfiguration: parentConfiguration, minLevel: minLevel);
|
||||||
} else {
|
} else {
|
||||||
final String description = toDescription(parentConfiguration: parentConfiguration)!;
|
final String description = toDescription(parentConfiguration: parentConfiguration);
|
||||||
|
|
||||||
if (name == null || name!.isEmpty || !showName) {
|
if (name == null || name!.isEmpty || !showName) {
|
||||||
result = description;
|
result = description;
|
||||||
@ -3558,7 +3596,7 @@ class DiagnosticsBlock extends DiagnosticsNode {
|
|||||||
this.allowTruncate = false,
|
this.allowTruncate = false,
|
||||||
List<DiagnosticsNode> children = const<DiagnosticsNode>[],
|
List<DiagnosticsNode> children = const<DiagnosticsNode>[],
|
||||||
List<DiagnosticsNode> properties = const <DiagnosticsNode>[],
|
List<DiagnosticsNode> properties = const <DiagnosticsNode>[],
|
||||||
}) : _description = description,
|
}) : _description = description ?? '',
|
||||||
_children = children,
|
_children = children,
|
||||||
_properties = properties,
|
_properties = properties,
|
||||||
super(
|
super(
|
||||||
@ -3575,7 +3613,7 @@ class DiagnosticsBlock extends DiagnosticsNode {
|
|||||||
@override
|
@override
|
||||||
final DiagnosticLevel level;
|
final DiagnosticLevel level;
|
||||||
|
|
||||||
final String? _description;
|
final String _description;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final Object? value;
|
final Object? value;
|
||||||
@ -3590,7 +3628,7 @@ class DiagnosticsBlock extends DiagnosticsNode {
|
|||||||
List<DiagnosticsNode> getProperties() => _properties;
|
List<DiagnosticsNode> getProperties() => _properties;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? toDescription({TextTreeConfiguration? parentConfiguration}) => _description;
|
String toDescription({TextTreeConfiguration? parentConfiguration}) => _description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A delegate that configures how a hierarchy of [DiagnosticsNode]s should be
|
/// A delegate that configures how a hierarchy of [DiagnosticsNode]s should be
|
||||||
|
@ -56,6 +56,9 @@ class _InputBorderGap extends ChangeNotifier {
|
|||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection
|
||||||
int get hashCode => hashValues(start, extent);
|
int get hashCode => hashValues(start, extent);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => describeIdentity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to interpolate between two InputBorders.
|
// Used to interpolate between two InputBorders.
|
||||||
@ -124,6 +127,9 @@ class _InputBorderPainter extends CustomPainter {
|
|||||||
|| gap != oldPainter.gap
|
|| gap != oldPainter.gap
|
||||||
|| textDirection != oldPainter.textDirection;
|
|| textDirection != oldPainter.textDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => describeIdentity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// An analog of AnimatedContainer, which can animate its shaped border, for
|
// An analog of AnimatedContainer, which can animate its shaped border, for
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
@ -589,4 +590,7 @@ abstract class ToggleablePainter extends ChangeNotifier implements CustomPainter
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRebuildSemantics(covariant CustomPainter oldDelegate) => false;
|
bool shouldRebuildSemantics(covariant CustomPainter oldDelegate) => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => describeIdentity(this);
|
||||||
}
|
}
|
||||||
|
@ -508,11 +508,15 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
|||||||
Future<void> performReassemble() async {
|
Future<void> performReassemble() async {
|
||||||
await super.performReassemble();
|
await super.performReassemble();
|
||||||
if (BindingBase.debugReassembleConfig?.widgetName == null) {
|
if (BindingBase.debugReassembleConfig?.widgetName == null) {
|
||||||
Timeline.startSync('Dirty Render Tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
if (!kReleaseMode) {
|
||||||
|
Timeline.startSync('Preparing Hot Reload (layout)', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
renderView.reassemble();
|
renderView.reassemble();
|
||||||
} finally {
|
} finally {
|
||||||
Timeline.finishSync();
|
if (!kReleaseMode) {
|
||||||
|
Timeline.finishSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scheduleWarmUpFrame();
|
scheduleWarmUpFrame();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:developer' show Timeline;
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui' as ui show lerpDouble;
|
import 'dart:ui' as ui show lerpDouble;
|
||||||
|
|
||||||
@ -1358,6 +1359,7 @@ abstract class RenderBox extends RenderObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<_IntrinsicDimensionsCacheEntry, double>? _cachedIntrinsicDimensions;
|
Map<_IntrinsicDimensionsCacheEntry, double>? _cachedIntrinsicDimensions;
|
||||||
|
static int _debugIntrinsicsDepth = 0;
|
||||||
|
|
||||||
double _computeIntrinsicDimension(_IntrinsicDimension dimension, double argument, double Function(double argument) computer) {
|
double _computeIntrinsicDimension(_IntrinsicDimension dimension, double argument, double Function(double argument) computer) {
|
||||||
assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize); // performResize should not depend on anything except the incoming constraints
|
assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize); // performResize should not depend on anything except the incoming constraints
|
||||||
@ -1370,11 +1372,38 @@ abstract class RenderBox extends RenderObject {
|
|||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
if (shouldCache) {
|
if (shouldCache) {
|
||||||
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
if (debugProfileLayoutsEnabled) {
|
||||||
|
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
|
||||||
|
} else {
|
||||||
|
debugTimelineArguments = Map<String, String>.of(debugTimelineArguments);
|
||||||
|
}
|
||||||
|
debugTimelineArguments['intrinsics dimension'] = describeEnum(dimension);
|
||||||
|
debugTimelineArguments['intrinsics argument'] = '$argument';
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) {
|
||||||
|
Timeline.startSync(
|
||||||
|
'$runtimeType intrinsics',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_debugIntrinsicsDepth += 1;
|
||||||
|
}
|
||||||
_cachedIntrinsicDimensions ??= <_IntrinsicDimensionsCacheEntry, double>{};
|
_cachedIntrinsicDimensions ??= <_IntrinsicDimensionsCacheEntry, double>{};
|
||||||
return _cachedIntrinsicDimensions!.putIfAbsent(
|
final double result = _cachedIntrinsicDimensions!.putIfAbsent(
|
||||||
_IntrinsicDimensionsCacheEntry(dimension, argument),
|
_IntrinsicDimensionsCacheEntry(dimension, argument),
|
||||||
() => computer(argument),
|
() => computer(argument),
|
||||||
);
|
);
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
_debugIntrinsicsDepth -= 1;
|
||||||
|
if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) {
|
||||||
|
Timeline.finishSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return computer(argument);
|
return computer(argument);
|
||||||
}
|
}
|
||||||
@ -1807,8 +1836,34 @@ abstract class RenderBox extends RenderObject {
|
|||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
if (shouldCache) {
|
if (shouldCache) {
|
||||||
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
if (debugProfileLayoutsEnabled) {
|
||||||
|
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
|
||||||
|
} else {
|
||||||
|
debugTimelineArguments = Map<String, String>.of(debugTimelineArguments);
|
||||||
|
}
|
||||||
|
debugTimelineArguments['getDryLayout constraints'] = '$constraints';
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) {
|
||||||
|
Timeline.startSync(
|
||||||
|
'$runtimeType.getDryLayout',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_debugIntrinsicsDepth += 1;
|
||||||
|
}
|
||||||
_cachedDryLayoutSizes ??= <BoxConstraints, Size>{};
|
_cachedDryLayoutSizes ??= <BoxConstraints, Size>{};
|
||||||
return _cachedDryLayoutSizes!.putIfAbsent(constraints, () => _computeDryLayout(constraints));
|
final Size result = _cachedDryLayoutSizes!.putIfAbsent(constraints, () => _computeDryLayout(constraints));
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
_debugIntrinsicsDepth -= 1;
|
||||||
|
if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) {
|
||||||
|
Timeline.finishSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return _computeDryLayout(constraints);
|
return _computeDryLayout(constraints);
|
||||||
}
|
}
|
||||||
|
@ -1017,4 +1017,14 @@ class RenderCustomPaint extends RenderProxyBox {
|
|||||||
|
|
||||||
return newChild;
|
return newChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties.add(MessageProperty('painter', '$painter'));
|
||||||
|
properties.add(MessageProperty('foregroundPainter', '$foregroundPainter', level: foregroundPainter != null ? DiagnosticLevel.info : DiagnosticLevel.fine));
|
||||||
|
properties.add(DiagnosticsProperty<Size>('preferredSize', preferredSize, defaultValue: Size.zero));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('isComplex', isComplex, defaultValue: false));
|
||||||
|
properties.add(DiagnosticsProperty<bool>('willChange', willChange, defaultValue: false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,9 +96,18 @@ bool debugCheckIntrinsicSizes = false;
|
|||||||
|
|
||||||
/// Adds [dart:developer.Timeline] events for every [RenderObject] layout.
|
/// Adds [dart:developer.Timeline] events for every [RenderObject] layout.
|
||||||
///
|
///
|
||||||
/// For details on how to use [dart:developer.Timeline] events in the Dart
|
/// The timing information this flag exposes is not representative of the actual
|
||||||
/// Observatory to optimize your app, see:
|
/// cost of layout, because the overhead of adding timeline events is
|
||||||
/// <https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md>
|
/// significant relative to the time each object takes to lay out. However, it
|
||||||
|
/// can expose unexpected layout behavior in the timeline.
|
||||||
|
///
|
||||||
|
/// In debug builds, additional information is included in the trace (such as
|
||||||
|
/// the properties of render objects being laid out). Collecting this data is
|
||||||
|
/// expensive and further makes these traces non-representative of actual
|
||||||
|
/// performance. This data is omitted in profile builds.
|
||||||
|
///
|
||||||
|
/// For more information about performance debugging in Flutter, see
|
||||||
|
/// <https://flutter.dev/docs/perf/rendering>.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
@ -112,11 +121,17 @@ bool debugProfileLayoutsEnabled = false;
|
|||||||
/// Adds [dart:developer.Timeline] events for every [RenderObject] painted.
|
/// Adds [dart:developer.Timeline] events for every [RenderObject] painted.
|
||||||
///
|
///
|
||||||
/// The timing information this flag exposes is not representative of actual
|
/// The timing information this flag exposes is not representative of actual
|
||||||
/// paints. However, it can expose unexpected painting in the timeline.
|
/// paints, because the overhead of adding timeline events is significant
|
||||||
|
/// relative to the time each object takes to paint. However, it can expose
|
||||||
|
/// unexpected painting in the timeline.
|
||||||
///
|
///
|
||||||
/// For details on how to use [dart:developer.Timeline] events in the Dart
|
/// In debug builds, additional information is included in the trace (such as
|
||||||
/// Observatory to optimize your app, see:
|
/// the properties of render objects being painted). Collecting this data is
|
||||||
/// <https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md>
|
/// expensive and further makes these traces non-representative of actual
|
||||||
|
/// performance. This data is omitted in profile builds.
|
||||||
|
///
|
||||||
|
/// For more information about performance debugging in Flutter, see
|
||||||
|
/// <https://flutter.dev/docs/perf/rendering>.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
|
@ -1155,8 +1155,10 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
|
|||||||
@override
|
@override
|
||||||
String toStringShort() {
|
String toStringShort() {
|
||||||
String header = super.toStringShort();
|
String header = super.toStringShort();
|
||||||
if (_hasOverflow)
|
if (!kReleaseMode) {
|
||||||
header += ' OVERFLOWING';
|
if (_hasOverflow)
|
||||||
|
header += ' OVERFLOWING';
|
||||||
|
}
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,8 +175,6 @@ class PaintingContext extends ClipContext {
|
|||||||
/// into the layer subtree associated with this painting context. Otherwise,
|
/// into the layer subtree associated with this painting context. Otherwise,
|
||||||
/// the child will be painted into the current PictureLayer for this context.
|
/// the child will be painted into the current PictureLayer for this context.
|
||||||
void paintChild(RenderObject child, Offset offset) {
|
void paintChild(RenderObject child, Offset offset) {
|
||||||
if (!kReleaseMode && debugProfilePaintsEnabled)
|
|
||||||
Timeline.startSync('${child.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
|
||||||
assert(() {
|
assert(() {
|
||||||
debugOnProfilePaint?.call(child);
|
debugOnProfilePaint?.call(child);
|
||||||
return true;
|
return true;
|
||||||
@ -188,9 +186,6 @@ class PaintingContext extends ClipContext {
|
|||||||
} else {
|
} else {
|
||||||
child._paintWithContext(this, offset);
|
child._paintWithContext(this, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!kReleaseMode && debugProfilePaintsEnabled)
|
|
||||||
Timeline.finishSync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _compositeChild(RenderObject child, Offset offset) {
|
void _compositeChild(RenderObject child, Offset offset) {
|
||||||
@ -863,14 +858,27 @@ class PipelineOwner {
|
|||||||
/// See [RendererBinding] for an example of how this function is used.
|
/// See [RendererBinding] for an example of how this function is used.
|
||||||
void flushLayout() {
|
void flushLayout() {
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
Timeline.startSync('Layout', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
if (debugProfileLayoutsEnabled) {
|
||||||
|
debugTimelineArguments = <String, String>{
|
||||||
|
...debugTimelineArguments,
|
||||||
|
'dirty count': '${_nodesNeedingLayout.length}',
|
||||||
|
'dirty list': '$_nodesNeedingLayout',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'LAYOUT',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
assert(() {
|
assert(() {
|
||||||
_debugDoingLayout = true;
|
_debugDoingLayout = true;
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
try {
|
try {
|
||||||
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
|
|
||||||
while (_nodesNeedingLayout.isNotEmpty) {
|
while (_nodesNeedingLayout.isNotEmpty) {
|
||||||
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
|
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
|
||||||
_nodesNeedingLayout = <RenderObject>[];
|
_nodesNeedingLayout = <RenderObject>[];
|
||||||
@ -923,7 +931,7 @@ class PipelineOwner {
|
|||||||
/// [flushPaint].
|
/// [flushPaint].
|
||||||
void flushCompositingBits() {
|
void flushCompositingBits() {
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
Timeline.startSync('Compositing bits');
|
Timeline.startSync('UPDATING COMPOSITING BITS', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||||
}
|
}
|
||||||
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
|
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
|
||||||
for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
|
for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
|
||||||
@ -956,7 +964,21 @@ class PipelineOwner {
|
|||||||
/// See [RendererBinding] for an example of how this function is used.
|
/// See [RendererBinding] for an example of how this function is used.
|
||||||
void flushPaint() {
|
void flushPaint() {
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
Timeline.startSync('Paint', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
if (debugProfilePaintsEnabled) {
|
||||||
|
debugTimelineArguments = <String, String>{
|
||||||
|
...debugTimelineArguments,
|
||||||
|
'dirty count': '${_nodesNeedingPaint.length}',
|
||||||
|
'dirty list': '$_nodesNeedingPaint',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'PAINT',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
assert(() {
|
assert(() {
|
||||||
_debugDoingPaint = true;
|
_debugDoingPaint = true;
|
||||||
@ -1058,7 +1080,7 @@ class PipelineOwner {
|
|||||||
if (_semanticsOwner == null)
|
if (_semanticsOwner == null)
|
||||||
return;
|
return;
|
||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
Timeline.startSync('Semantics');
|
Timeline.startSync('SEMANTICS', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||||
}
|
}
|
||||||
assert(_semanticsOwner != null);
|
assert(_semanticsOwner != null);
|
||||||
assert(() {
|
assert(() {
|
||||||
@ -1745,9 +1767,17 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
|||||||
@pragma('vm:notify-debugger-on-exception')
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void layout(Constraints constraints, { bool parentUsesSize = false }) {
|
void layout(Constraints constraints, { bool parentUsesSize = false }) {
|
||||||
assert(!_debugDisposed);
|
assert(!_debugDisposed);
|
||||||
if (!kReleaseMode && debugProfileLayoutsEnabled)
|
if (!kReleaseMode && debugProfileLayoutsEnabled) {
|
||||||
Timeline.startSync('$runtimeType', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'$runtimeType',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
assert(constraints != null);
|
assert(constraints != null);
|
||||||
assert(constraints.debugAssertIsValid(
|
assert(constraints.debugAssertIsValid(
|
||||||
isAppliedConstraint: true,
|
isAppliedConstraint: true,
|
||||||
@ -2336,6 +2366,17 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
|||||||
// a different layer, because there's a repaint boundary between us.
|
// a different layer, because there's a repaint boundary between us.
|
||||||
if (_needsLayout)
|
if (_needsLayout)
|
||||||
return;
|
return;
|
||||||
|
if (!kReleaseMode && debugProfilePaintsEnabled) {
|
||||||
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'$runtimeType',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
assert(() {
|
assert(() {
|
||||||
if (_needsCompositingBitsUpdate) {
|
if (_needsCompositingBitsUpdate) {
|
||||||
if (parent is RenderObject) {
|
if (parent is RenderObject) {
|
||||||
@ -2412,6 +2453,8 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
|||||||
_debugDoingThisPaint = false;
|
_debugDoingThisPaint = false;
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
|
if (!kReleaseMode && debugProfilePaintsEnabled)
|
||||||
|
Timeline.finishSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An estimate of the bounds within which this render object will paint.
|
/// An estimate of the bounds within which this render object will paint.
|
||||||
@ -2885,27 +2928,29 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
|||||||
@override
|
@override
|
||||||
String toStringShort() {
|
String toStringShort() {
|
||||||
String header = describeIdentity(this);
|
String header = describeIdentity(this);
|
||||||
if (_debugDisposed) {
|
if (!kReleaseMode) {
|
||||||
header += ' DISPOSED';
|
if (_debugDisposed) {
|
||||||
return header;
|
header += ' DISPOSED';
|
||||||
}
|
return header;
|
||||||
if (_relayoutBoundary != null && _relayoutBoundary != this) {
|
|
||||||
int count = 1;
|
|
||||||
RenderObject? target = parent as RenderObject?;
|
|
||||||
while (target != null && target != _relayoutBoundary) {
|
|
||||||
target = target.parent as RenderObject?;
|
|
||||||
count += 1;
|
|
||||||
}
|
}
|
||||||
header += ' relayoutBoundary=up$count';
|
if (_relayoutBoundary != null && _relayoutBoundary != this) {
|
||||||
|
int count = 1;
|
||||||
|
RenderObject? target = parent as RenderObject?;
|
||||||
|
while (target != null && target != _relayoutBoundary) {
|
||||||
|
target = target.parent as RenderObject?;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
header += ' relayoutBoundary=up$count';
|
||||||
|
}
|
||||||
|
if (_needsLayout)
|
||||||
|
header += ' NEEDS-LAYOUT';
|
||||||
|
if (_needsPaint)
|
||||||
|
header += ' NEEDS-PAINT';
|
||||||
|
if (_needsCompositingBitsUpdate)
|
||||||
|
header += ' NEEDS-COMPOSITING-BITS-UPDATE';
|
||||||
|
if (!attached)
|
||||||
|
header += ' DETACHED';
|
||||||
}
|
}
|
||||||
if (_needsLayout)
|
|
||||||
header += ' NEEDS-LAYOUT';
|
|
||||||
if (_needsPaint)
|
|
||||||
header += ' NEEDS-PAINT';
|
|
||||||
if (_needsCompositingBitsUpdate)
|
|
||||||
header += ' NEEDS-COMPOSITING-BITS-UPDATE';
|
|
||||||
if (!attached)
|
|
||||||
header += ' DETACHED';
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -838,8 +838,10 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO
|
|||||||
@override
|
@override
|
||||||
String toStringShort() {
|
String toStringShort() {
|
||||||
String header = super.toStringShort();
|
String header = super.toStringShort();
|
||||||
if (_isOverflowing)
|
if (!kReleaseMode) {
|
||||||
header += ' OVERFLOWING';
|
if (_isOverflowing)
|
||||||
|
header += ' OVERFLOWING';
|
||||||
|
}
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,9 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
|
|||||||
///
|
///
|
||||||
/// Actually causes the output of the rendering pipeline to appear on screen.
|
/// Actually causes the output of the rendering pipeline to appear on screen.
|
||||||
void compositeFrame() {
|
void compositeFrame() {
|
||||||
Timeline.startSync('Compositing', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
if (!kReleaseMode) {
|
||||||
|
Timeline.startSync('COMPOSITING', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||||
final ui.Scene scene = layer!.buildScene(builder);
|
final ui.Scene scene = layer!.buildScene(builder);
|
||||||
@ -234,7 +236,9 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
|
|||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
} finally {
|
} finally {
|
||||||
Timeline.finishSync();
|
if (!kReleaseMode) {
|
||||||
|
Timeline.finishSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +94,18 @@ bool debugPrintGlobalKeyedWidgetLifecycle = false;
|
|||||||
|
|
||||||
/// Adds [Timeline] events for every Widget built.
|
/// Adds [Timeline] events for every Widget built.
|
||||||
///
|
///
|
||||||
/// For details on how to use [Timeline] events in the Dart Observatory to
|
/// The timing information this flag exposes is not representative of the actual
|
||||||
/// optimize your app, see https://flutter.dev/docs/testing/debugging#tracing-any-dart-code-performance
|
/// cost of building, because the overhead of adding timeline events is
|
||||||
/// and https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md
|
/// significant relative to the time each object takes to build. However, it can
|
||||||
|
/// expose unexpected widget behavior in the timeline.
|
||||||
|
///
|
||||||
|
/// In debug builds, additional information is included in the trace (such as
|
||||||
|
/// the properties of widgets being built). Collecting this data is
|
||||||
|
/// expensive and further makes these traces non-representative of actual
|
||||||
|
/// performance. This data is omitted in profile builds.
|
||||||
|
///
|
||||||
|
/// For more information about performance debugging in Flutter, see
|
||||||
|
/// <https://flutter.dev/docs/perf/rendering>.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
|
@ -2306,10 +2306,18 @@ abstract class BuildContext {
|
|||||||
/// data down to them.
|
/// data down to them.
|
||||||
void visitChildElements(ElementVisitor visitor);
|
void visitChildElements(ElementVisitor visitor);
|
||||||
|
|
||||||
/// Returns a description of an [Element] from the current build context.
|
/// Returns a description of the [Element] associated with the current build context.
|
||||||
|
///
|
||||||
|
/// The `name` is typically something like "The element being rebuilt was".
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [Element.describeElements], which can be used to describe a list of elements.
|
||||||
DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
|
DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
|
||||||
|
|
||||||
/// Returns a description of the [Widget] associated with the current build context.
|
/// Returns a description of the [Widget] associated with the current build context.
|
||||||
|
///
|
||||||
|
/// The `name` is typically something like "The widget being rebuilt was".
|
||||||
DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
|
DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
|
||||||
|
|
||||||
/// Adds a description of a specific type of widget missing from the current
|
/// Adds a description of a specific type of widget missing from the current
|
||||||
@ -2525,7 +2533,25 @@ class BuildOwner {
|
|||||||
_debugBuilding = true;
|
_debugBuilding = true;
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
if (!kReleaseMode) {
|
||||||
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
if (debugProfileBuildsEnabled) {
|
||||||
|
debugTimelineArguments = <String, String>{
|
||||||
|
...debugTimelineArguments,
|
||||||
|
'dirty count': '${_dirtyElements.length}',
|
||||||
|
'dirty list': '$_dirtyElements',
|
||||||
|
'lock level': '$_debugStateLockLevel',
|
||||||
|
'scope context': '$context',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'BUILD',
|
||||||
|
arguments: debugTimelineArguments
|
||||||
|
);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
_scheduledFlushDirtyElements = true;
|
_scheduledFlushDirtyElements = true;
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
@ -2555,10 +2581,11 @@ class BuildOwner {
|
|||||||
int dirtyCount = _dirtyElements.length;
|
int dirtyCount = _dirtyElements.length;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (index < dirtyCount) {
|
while (index < dirtyCount) {
|
||||||
assert(_dirtyElements[index] != null);
|
final Element element = _dirtyElements[index];
|
||||||
assert(_dirtyElements[index]._inDirtyList);
|
assert(element != null);
|
||||||
|
assert(element._inDirtyList);
|
||||||
assert(() {
|
assert(() {
|
||||||
if (_dirtyElements[index]._lifecycleState == _ElementLifecycle.active && !_dirtyElements[index]._debugIsInScope(context)) {
|
if (element._lifecycleState == _ElementLifecycle.active && !element._debugIsInScope(context)) {
|
||||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
ErrorSummary('Tried to build dirty widget in the wrong build scope.'),
|
ErrorSummary('Tried to build dirty widget in the wrong build scope.'),
|
||||||
ErrorDescription(
|
ErrorDescription(
|
||||||
@ -2578,15 +2605,26 @@ class BuildOwner {
|
|||||||
),
|
),
|
||||||
DiagnosticsProperty<Element>(
|
DiagnosticsProperty<Element>(
|
||||||
'The offending element (which does not appear to be a descendant of the root of the build scope) was',
|
'The offending element (which does not appear to be a descendant of the root of the build scope) was',
|
||||||
_dirtyElements[index],
|
element,
|
||||||
style: DiagnosticsTreeStyle.errorProperty,
|
style: DiagnosticsTreeStyle.errorProperty,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
|
if (!kReleaseMode && debugProfileBuildsEnabled) {
|
||||||
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
debugTimelineArguments = element.widget.toDiagnosticsNode().toTimelineArguments();
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'${element.widget.runtimeType}',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
_dirtyElements[index].rebuild();
|
element.rebuild();
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
_debugReportException(
|
_debugReportException(
|
||||||
ErrorDescription('while rebuilding dirty elements'),
|
ErrorDescription('while rebuilding dirty elements'),
|
||||||
@ -2594,14 +2632,16 @@ class BuildOwner {
|
|||||||
stack,
|
stack,
|
||||||
informationCollector: () sync* {
|
informationCollector: () sync* {
|
||||||
if (index < _dirtyElements.length) {
|
if (index < _dirtyElements.length) {
|
||||||
yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index]));
|
yield DiagnosticsDebugCreator(DebugCreator(element));
|
||||||
yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
|
yield element.describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
|
||||||
} else {
|
} else {
|
||||||
yield ErrorHint('The element being rebuilt at the time was index $index of $dirtyCount, but _dirtyElements only had ${_dirtyElements.length} entries. This suggests some confusion in the framework internals.');
|
yield ErrorHint('The element being rebuilt at the time was index $index of $dirtyCount, but _dirtyElements only had ${_dirtyElements.length} entries. This suggests some confusion in the framework internals.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||||
|
Timeline.finishSync();
|
||||||
index += 1;
|
index += 1;
|
||||||
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
|
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
|
||||||
_dirtyElements.sort(Element._sort);
|
_dirtyElements.sort(Element._sort);
|
||||||
@ -2637,7 +2677,9 @@ class BuildOwner {
|
|||||||
_dirtyElements.clear();
|
_dirtyElements.clear();
|
||||||
_scheduledFlushDirtyElements = false;
|
_scheduledFlushDirtyElements = false;
|
||||||
_dirtyElementsNeedsResorting = null;
|
_dirtyElementsNeedsResorting = null;
|
||||||
Timeline.finishSync();
|
if (!kReleaseMode) {
|
||||||
|
Timeline.finishSync();
|
||||||
|
}
|
||||||
assert(_debugBuilding);
|
assert(_debugBuilding);
|
||||||
assert(() {
|
assert(() {
|
||||||
_debugBuilding = false;
|
_debugBuilding = false;
|
||||||
@ -2842,7 +2884,9 @@ class BuildOwner {
|
|||||||
/// about changes to global keys will run.
|
/// about changes to global keys will run.
|
||||||
@pragma('vm:notify-debugger-on-exception')
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void finalizeTree() {
|
void finalizeTree() {
|
||||||
Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
if (!kReleaseMode) {
|
||||||
|
Timeline.startSync('FINALIZE TREE', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
lockState(() {
|
lockState(() {
|
||||||
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
|
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
|
||||||
@ -2937,7 +2981,9 @@ class BuildOwner {
|
|||||||
// cause more exceptions.
|
// cause more exceptions.
|
||||||
_debugReportException(ErrorSummary('while finalizing the widget tree'), e, stack);
|
_debugReportException(ErrorSummary('while finalizing the widget tree'), e, stack);
|
||||||
} finally {
|
} finally {
|
||||||
Timeline.finishSync();
|
if (!kReleaseMode) {
|
||||||
|
Timeline.finishSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2948,14 +2994,18 @@ class BuildOwner {
|
|||||||
///
|
///
|
||||||
/// This is expensive and should not be called except during development.
|
/// This is expensive and should not be called except during development.
|
||||||
void reassemble(Element root, DebugReassembleConfig? reassembleConfig) {
|
void reassemble(Element root, DebugReassembleConfig? reassembleConfig) {
|
||||||
Timeline.startSync('Dirty Element Tree');
|
if (!kReleaseMode) {
|
||||||
|
Timeline.startSync('Preparing Hot Reload (widgets)');
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
assert(root._parent == null);
|
assert(root._parent == null);
|
||||||
assert(root.owner == this);
|
assert(root.owner == this);
|
||||||
root._debugReassembleConfig = reassembleConfig;
|
root._debugReassembleConfig = reassembleConfig;
|
||||||
root.reassemble();
|
root.reassemble();
|
||||||
} finally {
|
} finally {
|
||||||
Timeline.finishSync();
|
if (!kReleaseMode) {
|
||||||
|
Timeline.finishSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3021,8 +3071,24 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||||||
Element? _parent;
|
Element? _parent;
|
||||||
DebugReassembleConfig? _debugReassembleConfig;
|
DebugReassembleConfig? _debugReassembleConfig;
|
||||||
|
|
||||||
// Custom implementation of `operator ==` optimized for the ".of" pattern
|
/// Compare two widgets for equality.
|
||||||
// used with `InheritedWidgets`.
|
///
|
||||||
|
/// When a widget is rebuilt with another that compares equal according
|
||||||
|
/// to `operator ==`, it is assumed that the update is redundant and the
|
||||||
|
/// work to update that branch of the tree is skipped.
|
||||||
|
///
|
||||||
|
/// It is generally discouraged to override `operator ==` on any widget that
|
||||||
|
/// has children, since a correct implementation would have to defer to the
|
||||||
|
/// children's equality operator also, and that is an O(N²) operation: each
|
||||||
|
/// child would need to itself walk all its children, each step of the tree.
|
||||||
|
///
|
||||||
|
/// It is sometimes reasonable for a leaf widget (one with no children) to
|
||||||
|
/// implement this method, if rebuilding the widget is known to be much more
|
||||||
|
/// expensive than checking the widgets' parameters for equality and if the
|
||||||
|
/// widget is expected to often be rebuilt with identical parameters.
|
||||||
|
///
|
||||||
|
/// In general, however, it is more efficient to cache the widgets used
|
||||||
|
/// in a build method if it is known that they will not change.
|
||||||
@nonVirtual
|
@nonVirtual
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
@ -3347,6 +3413,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||||||
deactivateChild(child);
|
deactivateChild(child);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Element newChild;
|
final Element newChild;
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
bool hasSameSuperclass = true;
|
bool hasSameSuperclass = true;
|
||||||
@ -3372,13 +3439,29 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
if (hasSameSuperclass && child.widget == newWidget) {
|
if (hasSameSuperclass && child.widget == newWidget) {
|
||||||
|
// We don't insert a timeline event here, because otherwise it's
|
||||||
|
// confusing that widgets that "don't update" (because they didn't
|
||||||
|
// change) get "charged" on the timeline.
|
||||||
if (child.slot != newSlot)
|
if (child.slot != newSlot)
|
||||||
updateSlotForChild(child, newSlot);
|
updateSlotForChild(child, newSlot);
|
||||||
newChild = child;
|
newChild = child;
|
||||||
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
|
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
|
||||||
if (child.slot != newSlot)
|
if (child.slot != newSlot)
|
||||||
updateSlotForChild(child, newSlot);
|
updateSlotForChild(child, newSlot);
|
||||||
|
if (!kReleaseMode && debugProfileBuildsEnabled) {
|
||||||
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'${newWidget.runtimeType}',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
child.update(newWidget);
|
child.update(newWidget);
|
||||||
|
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||||
|
Timeline.finishSync();
|
||||||
assert(child.widget == newWidget);
|
assert(child.widget == newWidget);
|
||||||
assert(() {
|
assert(() {
|
||||||
child.owner!._debugElementWasRebuilt(child);
|
child.owner!._debugElementWasRebuilt(child);
|
||||||
@ -3388,10 +3471,36 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||||||
} else {
|
} else {
|
||||||
deactivateChild(child);
|
deactivateChild(child);
|
||||||
assert(child._parent == null);
|
assert(child._parent == null);
|
||||||
|
if (!kReleaseMode && debugProfileBuildsEnabled) {
|
||||||
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'${newWidget.runtimeType}',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
newChild = inflateWidget(newWidget, newSlot);
|
newChild = inflateWidget(newWidget, newSlot);
|
||||||
|
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||||
|
Timeline.finishSync();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (!kReleaseMode && debugProfileBuildsEnabled) {
|
||||||
|
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||||
|
assert(() {
|
||||||
|
debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
Timeline.startSync(
|
||||||
|
'${newWidget.runtimeType}',
|
||||||
|
arguments: debugTimelineArguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
newChild = inflateWidget(newWidget, newSlot);
|
newChild = inflateWidget(newWidget, newSlot);
|
||||||
|
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||||
|
Timeline.finishSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(() {
|
assert(() {
|
||||||
@ -4291,6 +4400,9 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||||||
owner!.scheduleBuildFor(this);
|
owner!.scheduleBuildFor(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cause the widget to update itself. In debug builds, also verify various
|
||||||
|
/// invariants.
|
||||||
|
///
|
||||||
/// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
|
/// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
|
||||||
/// called to mark this element dirty, by [mount] when the element is first
|
/// called to mark this element dirty, by [mount] when the element is first
|
||||||
/// built, and by [update] when the widget has changed.
|
/// built, and by [update] when the widget has changed.
|
||||||
@ -4328,7 +4440,9 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
|||||||
assert(!_dirty);
|
assert(!_dirty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called by rebuild() after the appropriate checks have been made.
|
/// Cause the widget to update itself.
|
||||||
|
///
|
||||||
|
/// Called by [rebuild] after the appropriate checks have been made.
|
||||||
@protected
|
@protected
|
||||||
void performRebuild();
|
void performRebuild();
|
||||||
}
|
}
|
||||||
@ -4574,7 +4688,8 @@ abstract class ComponentElement extends Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _firstBuild() {
|
void _firstBuild() {
|
||||||
rebuild();
|
// StatefulElement overrides this to also call state.didChangeDependencies.
|
||||||
|
rebuild(); // This eventually calls performRebuild.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
|
/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
|
||||||
@ -4586,9 +4701,6 @@ abstract class ComponentElement extends Element {
|
|||||||
@override
|
@override
|
||||||
@pragma('vm:notify-debugger-on-exception')
|
@pragma('vm:notify-debugger-on-exception')
|
||||||
void performRebuild() {
|
void performRebuild() {
|
||||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
|
||||||
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
|
||||||
|
|
||||||
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
|
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
|
||||||
Widget? built;
|
Widget? built;
|
||||||
try {
|
try {
|
||||||
@ -4636,9 +4748,6 @@ abstract class ComponentElement extends Element {
|
|||||||
);
|
);
|
||||||
_child = updateChild(null, built, slot);
|
_child = updateChild(null, built, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
|
||||||
Timeline.finishSync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subclasses should override this function to actually call the appropriate
|
/// Subclasses should override this function to actually call the appropriate
|
||||||
@ -5490,16 +5599,7 @@ abstract class RenderObjectElement extends Element {
|
|||||||
_debugUpdateRenderObjectOwner();
|
_debugUpdateRenderObjectOwner();
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
assert(() {
|
_performRebuild(); // calls widget.updateRenderObject()
|
||||||
_debugDoingBuild = true;
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
widget.updateRenderObject(this, renderObject);
|
|
||||||
assert(() {
|
|
||||||
_debugDoingBuild = false;
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
_dirty = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _debugUpdateRenderObjectOwner() {
|
void _debugUpdateRenderObjectOwner() {
|
||||||
@ -5511,6 +5611,11 @@ abstract class RenderObjectElement extends Element {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void performRebuild() {
|
void performRebuild() {
|
||||||
|
_performRebuild(); // calls widget.updateRenderObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
void _performRebuild() {
|
||||||
assert(() {
|
assert(() {
|
||||||
_debugDoingBuild = true;
|
_debugDoingBuild = true;
|
||||||
return true;
|
return true;
|
||||||
@ -6367,8 +6472,10 @@ class IndexedSlot<T extends Element?> {
|
|||||||
int get hashCode => hashValues(index, value);
|
int get hashCode => hashValues(index, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used as a placeholder in [List<Element>] objects when the actual
|
||||||
|
/// elements are not yet determined.
|
||||||
class _NullElement extends Element {
|
class _NullElement extends Element {
|
||||||
_NullElement() : super(_NullWidget());
|
_NullElement() : super(const _NullWidget());
|
||||||
|
|
||||||
static _NullElement instance = _NullElement();
|
static _NullElement instance = _NullElement();
|
||||||
|
|
||||||
@ -6376,10 +6483,12 @@ class _NullElement extends Element {
|
|||||||
bool get debugDoingBuild => throw UnimplementedError();
|
bool get debugDoingBuild => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performRebuild() { }
|
void performRebuild() => throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NullWidget extends Widget {
|
class _NullWidget extends Widget {
|
||||||
|
const _NullWidget();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Element createElement() => throw UnimplementedError();
|
Element createElement() => throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
@ -526,6 +526,11 @@ class _GlowController extends ChangeNotifier {
|
|||||||
canvas.drawCircle(center, radius, paint);
|
canvas.drawCircle(center, radius, paint);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '_GlowController(color: $color, axis: ${describeEnum(axis)})';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GlowingOverscrollIndicatorPainter extends CustomPainter {
|
class _GlowingOverscrollIndicatorPainter extends CustomPainter {
|
||||||
@ -595,6 +600,11 @@ class _GlowingOverscrollIndicatorPainter extends CustomPainter {
|
|||||||
return oldDelegate.leadingController != leadingController
|
return oldDelegate.leadingController != leadingController
|
||||||
|| oldDelegate.trailingController != trailingController;
|
|| oldDelegate.trailingController != trailingController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '_GlowingOverscrollIndicatorPainter($leadingController, $trailingController)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Material Design visual indication that a scroll view has overscrolled.
|
/// A Material Design visual indication that a scroll view has overscrolled.
|
||||||
@ -891,6 +901,9 @@ class _StretchController extends ChangeNotifier {
|
|||||||
_stretchController.dispose();
|
_stretchController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '_StretchController()';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A notification that either a [GlowingOverscrollIndicator] or a
|
/// A notification that either a [GlowingOverscrollIndicator] or a
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
|
|
||||||
@ -95,4 +97,13 @@ class Placeholder extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties.add(ColorProperty('color', color, defaultValue: const Color(0xFF455A64)));
|
||||||
|
properties.add(DoubleProperty('strokeWidth', strokeWidth, defaultValue: 2.0));
|
||||||
|
properties.add(DoubleProperty('fallbackWidth', fallbackWidth, defaultValue: 400.0));
|
||||||
|
properties.add(DoubleProperty('fallbackHeight', fallbackHeight, defaultValue: 400.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -750,6 +750,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
SemanticsBuilderCallback? get semanticsBuilder => null;
|
SemanticsBuilderCallback? get semanticsBuilder => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => describeIdentity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An extendable base class for building scrollbars that fade in and out.
|
/// An extendable base class for building scrollbars that fade in and out.
|
||||||
|
@ -226,6 +226,11 @@ void main() {
|
|||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
||||||
' │ size: Size(800.0, 600.0)\n'
|
' │ size: Size(800.0, 600.0)\n'
|
||||||
|
' │ painter: null\n'
|
||||||
|
' │ foregroundPainter:\n'
|
||||||
|
' │ _GlowingOverscrollIndicatorPainter(_GlowController(color:\n'
|
||||||
|
' │ Color(0xffffffff), axis: vertical), _GlowController(color:\n'
|
||||||
|
' │ Color(0xffffffff), axis: vertical))\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderRepaintBoundary#00000\n'
|
' └─child: RenderRepaintBoundary#00000\n'
|
||||||
' │ needs compositing\n'
|
' │ needs compositing\n'
|
||||||
@ -322,6 +327,9 @@ void main() {
|
|||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' │ size: Size(800.0, 400.0)\n'
|
' │ size: Size(800.0, 400.0)\n'
|
||||||
|
' │ painter: null\n'
|
||||||
|
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' ├─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here
|
' ├─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here
|
||||||
' │ │ parentData: index=1; layoutOffset=400.0\n'
|
' │ │ parentData: index=1; layoutOffset=400.0\n'
|
||||||
@ -334,6 +342,9 @@ void main() {
|
|||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' │ size: Size(800.0, 400.0)\n'
|
' │ size: Size(800.0, 400.0)\n'
|
||||||
|
' │ painter: null\n'
|
||||||
|
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' └─child with index 2: RenderLimitedBox#00000 NEEDS-PAINT\n'
|
' └─child with index 2: RenderLimitedBox#00000 NEEDS-PAINT\n'
|
||||||
' │ parentData: index=2; layoutOffset=800.0\n'
|
' │ parentData: index=2; layoutOffset=800.0\n'
|
||||||
@ -345,7 +356,10 @@ void main() {
|
|||||||
' └─child: RenderCustomPaint#00000 NEEDS-PAINT\n'
|
' └─child: RenderCustomPaint#00000 NEEDS-PAINT\n'
|
||||||
' parentData: <none> (can use size)\n'
|
' parentData: <none> (can use size)\n'
|
||||||
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' size: Size(800.0, 400.0)\n',
|
' size: Size(800.0, 400.0)\n'
|
||||||
|
' painter: null\n'
|
||||||
|
' foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' preferredSize: Size(Infinity, Infinity)\n',
|
||||||
));
|
));
|
||||||
const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(true);
|
const GlobalObjectKey<_LeafState>(0).currentState!.setKeepAlive(true);
|
||||||
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
|
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
|
||||||
@ -375,6 +389,11 @@ void main() {
|
|||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
||||||
' │ size: Size(800.0, 600.0)\n'
|
' │ size: Size(800.0, 600.0)\n'
|
||||||
|
' │ painter: null\n'
|
||||||
|
' │ foregroundPainter:\n'
|
||||||
|
' │ _GlowingOverscrollIndicatorPainter(_GlowController(color:\n'
|
||||||
|
' │ Color(0xffffffff), axis: vertical), _GlowController(color:\n'
|
||||||
|
' │ Color(0xffffffff), axis: vertical))\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderRepaintBoundary#00000\n'
|
' └─child: RenderRepaintBoundary#00000\n'
|
||||||
' │ needs compositing\n'
|
' │ needs compositing\n'
|
||||||
@ -471,6 +490,9 @@ void main() {
|
|||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' │ size: Size(800.0, 400.0)\n'
|
' │ size: Size(800.0, 400.0)\n'
|
||||||
|
' │ painter: null\n'
|
||||||
|
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0
|
' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0
|
||||||
' │ │ parentData: index=5; layoutOffset=2000.0\n'
|
' │ │ parentData: index=5; layoutOffset=2000.0\n'
|
||||||
@ -483,6 +505,9 @@ void main() {
|
|||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' │ size: Size(800.0, 400.0)\n'
|
' │ size: Size(800.0, 400.0)\n'
|
||||||
|
' │ painter: null\n'
|
||||||
|
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' ├─child with index 6: RenderLimitedBox#00000\n'
|
' ├─child with index 6: RenderLimitedBox#00000\n'
|
||||||
' │ │ parentData: index=6; layoutOffset=2400.0\n'
|
' │ │ parentData: index=6; layoutOffset=2400.0\n'
|
||||||
@ -495,6 +520,9 @@ void main() {
|
|||||||
' │ parentData: <none> (can use size)\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' │ size: Size(800.0, 400.0)\n'
|
' │ size: Size(800.0, 400.0)\n'
|
||||||
|
' │ painter: null\n'
|
||||||
|
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' ├─child with index 7: RenderLimitedBox#00000 NEEDS-PAINT\n'
|
' ├─child with index 7: RenderLimitedBox#00000 NEEDS-PAINT\n'
|
||||||
' ╎ │ parentData: index=7; layoutOffset=2800.0\n'
|
' ╎ │ parentData: index=7; layoutOffset=2800.0\n'
|
||||||
@ -507,6 +535,9 @@ void main() {
|
|||||||
' ╎ parentData: <none> (can use size)\n'
|
' ╎ parentData: <none> (can use size)\n'
|
||||||
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' ╎ size: Size(800.0, 400.0)\n'
|
' ╎ size: Size(800.0, 400.0)\n'
|
||||||
|
' ╎ painter: null\n'
|
||||||
|
' ╎ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' ╎ preferredSize: Size(Infinity, Infinity)\n'
|
||||||
' ╎\n'
|
' ╎\n'
|
||||||
' ╎╌child with index 0 (kept alive but not laid out): RenderLimitedBox#00000\n' // <----- this one is index 0 and is marked as being kept alive but not laid out
|
' ╎╌child with index 0 (kept alive but not laid out): RenderLimitedBox#00000\n' // <----- this one is index 0 and is marked as being kept alive but not laid out
|
||||||
' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n'
|
' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n'
|
||||||
@ -519,6 +550,9 @@ void main() {
|
|||||||
' ╎ parentData: <none> (can use size)\n'
|
' ╎ parentData: <none> (can use size)\n'
|
||||||
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' ╎ size: Size(800.0, 400.0)\n'
|
' ╎ size: Size(800.0, 400.0)\n'
|
||||||
|
' ╎ painter: null\n'
|
||||||
|
' ╎ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' ╎ preferredSize: Size(Infinity, Infinity)\n'
|
||||||
' ╎\n' // <----- dashed line ends here
|
' ╎\n' // <----- dashed line ends here
|
||||||
' └╌child with index 3 (kept alive but not laid out): RenderLimitedBox#00000\n'
|
' └╌child with index 3 (kept alive but not laid out): RenderLimitedBox#00000\n'
|
||||||
' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n'
|
' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n'
|
||||||
@ -530,7 +564,10 @@ void main() {
|
|||||||
' └─child: RenderCustomPaint#00000\n'
|
' └─child: RenderCustomPaint#00000\n'
|
||||||
' parentData: <none> (can use size)\n'
|
' parentData: <none> (can use size)\n'
|
||||||
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||||
' size: Size(800.0, 400.0)\n',
|
' size: Size(800.0, 400.0)\n'
|
||||||
|
' painter: null\n'
|
||||||
|
' foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||||
|
' preferredSize: Size(Infinity, Infinity)\n',
|
||||||
));
|
));
|
||||||
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/87876
|
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/87876
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class TestDiagnosticsNode extends DiagnosticsNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? toDescription({TextTreeConfiguration? parentConfiguration}) {
|
String toDescription({TextTreeConfiguration? parentConfiguration}) {
|
||||||
return 'Test Description';
|
return 'Test Description';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user