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": "clang"},
|
||||
{"dependency": "cmake"},
|
||||
{"dependency": "ninja"}
|
||||
{"dependency": "ninja"},
|
||||
{"dependency": "open_jdk"},
|
||||
{"dependency": "android_sdk", "version": "version:29.0"}
|
||||
]
|
||||
shard: framework_tests
|
||||
subshard: misc
|
||||
@ -2108,7 +2110,9 @@ targets:
|
||||
[
|
||||
{"dependency": "goldctl"},
|
||||
{"dependency": "xcode"},
|
||||
{"dependency": "gems"}
|
||||
{"dependency": "gems"},
|
||||
{"dependency": "open_jdk"},
|
||||
{"dependency": "android_sdk", "version": "version:29.0"}
|
||||
]
|
||||
shard: framework_tests
|
||||
subshard: misc
|
||||
@ -3642,7 +3646,9 @@ targets:
|
||||
dependencies: >-
|
||||
[
|
||||
{"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
|
||||
subshard: misc
|
||||
|
@ -15,11 +15,11 @@ dependencies:
|
||||
platform: 3.1.0
|
||||
process: 4.2.4
|
||||
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"
|
||||
_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"
|
||||
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"
|
||||
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"
|
||||
|
@ -6,7 +6,9 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:file/file.dart' as fs;
|
||||
import 'package:file/local.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@ -744,6 +746,89 @@ Future<void> _runFrameworkTests() async {
|
||||
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 {
|
||||
final List<String> args = <String>[
|
||||
'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', 'fuchsia_remote_debug_protocol'), options: soundNullSafetyOptions);
|
||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: mixedModeNullSafetyOptions);
|
||||
await _runFlutterTest(
|
||||
path.join(flutterRoot, 'dev', 'tracing_tests'),
|
||||
options: <String>['--enable-vmservice'],
|
||||
);
|
||||
await runTracingTests();
|
||||
await runFixTests();
|
||||
await runPrivateTests();
|
||||
const String httpClientWarning =
|
||||
|
@ -382,14 +382,12 @@ class _VideoDemoState extends State<VideoDemo> with SingleTickerProviderStateMix
|
||||
super.initState();
|
||||
|
||||
Future<void> initController(VideoPlayerController controller, String name) async {
|
||||
print('> VideoDemo initController "$name" ${isDisposed ? "DISPOSED" : ""}');
|
||||
controller.setLooping(true);
|
||||
controller.setVolume(0.0);
|
||||
controller.play();
|
||||
await connectedCompleter.future;
|
||||
await controller.initialize();
|
||||
if (mounted) {
|
||||
print('< VideoDemo initController "$name" done ${isDisposed ? "DISPOSED" : ""}');
|
||||
setState(() { });
|
||||
}
|
||||
}
|
||||
@ -403,11 +401,9 @@ class _VideoDemoState extends State<VideoDemo> with SingleTickerProviderStateMix
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
print('> VideoDemo dispose');
|
||||
isDisposed = true;
|
||||
butterflyController.dispose();
|
||||
beeController.dispose();
|
||||
print('< VideoDemo dispose');
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -2,4 +2,20 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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'
|
||||
await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
|
||||
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();
|
||||
|
||||
// Switch text direction: first switch
|
||||
|
@ -1,5 +1,16 @@
|
||||
# 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`,
|
||||
since they test that trace data is written to the timeline by connecting to
|
||||
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.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:isolate' as isolate;
|
||||
|
||||
import 'package:flutter/painting.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() {
|
||||
late VmService vmService;
|
||||
late String isolateId;
|
||||
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();
|
||||
});
|
||||
initTimelineTests();
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test('Image cache tracing', () async {
|
||||
final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
|
||||
@ -45,9 +29,8 @@ void main() {
|
||||
);
|
||||
PaintingBinding.instance!.imageCache!.evict('Test2');
|
||||
|
||||
final Timeline timeline = await vmService.getVMTimeline();
|
||||
_expectTimelineEvents(
|
||||
timeline.traceEvents!,
|
||||
await fetchTimelineEvents(),
|
||||
<Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'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
|
||||
/// 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;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// [DiagnosticsNode.toTimelineArguments] includes these properties in its
|
||||
/// result.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [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.
|
||||
// Essentially in this case the properties are treated a bit like a value.
|
||||
if ((properties.isNotEmpty || children.isNotEmpty || node.emptyBodyDescription != null) &&
|
||||
(node.showSeparator || description?.isNotEmpty == true)) {
|
||||
(node.showSeparator || description.isNotEmpty)) {
|
||||
builder.write(config.afterDescriptionIfBody);
|
||||
}
|
||||
|
||||
@ -1474,7 +1474,7 @@ abstract class DiagnosticsNode {
|
||||
/// `parentConfiguration` specifies how the parent is rendered as text art.
|
||||
/// For example, if the parent does not line break between properties, the
|
||||
/// 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.
|
||||
///
|
||||
@ -1544,6 +1544,44 @@ abstract class DiagnosticsNode {
|
||||
|
||||
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
|
||||
/// in the [DiagnosticsSerializationDelegate].
|
||||
///
|
||||
@ -1655,7 +1693,7 @@ abstract class DiagnosticsNode {
|
||||
if (_isSingleLine(style)) {
|
||||
result = toStringDeep(parentConfiguration: parentConfiguration, minLevel: minLevel);
|
||||
} else {
|
||||
final String description = toDescription(parentConfiguration: parentConfiguration)!;
|
||||
final String description = toDescription(parentConfiguration: parentConfiguration);
|
||||
|
||||
if (name == null || name!.isEmpty || !showName) {
|
||||
result = description;
|
||||
@ -3558,7 +3596,7 @@ class DiagnosticsBlock extends DiagnosticsNode {
|
||||
this.allowTruncate = false,
|
||||
List<DiagnosticsNode> children = const<DiagnosticsNode>[],
|
||||
List<DiagnosticsNode> properties = const <DiagnosticsNode>[],
|
||||
}) : _description = description,
|
||||
}) : _description = description ?? '',
|
||||
_children = children,
|
||||
_properties = properties,
|
||||
super(
|
||||
@ -3575,7 +3613,7 @@ class DiagnosticsBlock extends DiagnosticsNode {
|
||||
@override
|
||||
final DiagnosticLevel level;
|
||||
|
||||
final String? _description;
|
||||
final String _description;
|
||||
|
||||
@override
|
||||
final Object? value;
|
||||
@ -3590,7 +3628,7 @@ class DiagnosticsBlock extends DiagnosticsNode {
|
||||
List<DiagnosticsNode> getProperties() => _properties;
|
||||
|
||||
@override
|
||||
String? toDescription({TextTreeConfiguration? parentConfiguration}) => _description;
|
||||
String toDescription({TextTreeConfiguration? parentConfiguration}) => _description;
|
||||
}
|
||||
|
||||
/// A delegate that configures how a hierarchy of [DiagnosticsNode]s should be
|
||||
|
@ -56,6 +56,9 @@ class _InputBorderGap extends ChangeNotifier {
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection
|
||||
int get hashCode => hashValues(start, extent);
|
||||
|
||||
@override
|
||||
String toString() => describeIdentity(this);
|
||||
}
|
||||
|
||||
// Used to interpolate between two InputBorders.
|
||||
@ -124,6 +127,9 @@ class _InputBorderPainter extends CustomPainter {
|
||||
|| gap != oldPainter.gap
|
||||
|| textDirection != oldPainter.textDirection;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => describeIdentity(this);
|
||||
}
|
||||
|
||||
// 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
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@ -589,4 +590,7 @@ abstract class ToggleablePainter extends ChangeNotifier implements CustomPainter
|
||||
|
||||
@override
|
||||
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 {
|
||||
await super.performReassemble();
|
||||
if (BindingBase.debugReassembleConfig?.widgetName == null) {
|
||||
Timeline.startSync('Dirty Render Tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
if (!kReleaseMode) {
|
||||
Timeline.startSync('Preparing Hot Reload (layout)', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
}
|
||||
try {
|
||||
renderView.reassemble();
|
||||
} finally {
|
||||
Timeline.finishSync();
|
||||
if (!kReleaseMode) {
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
scheduleWarmUpFrame();
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:developer' show Timeline;
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui show lerpDouble;
|
||||
|
||||
@ -1358,6 +1359,7 @@ abstract class RenderBox extends RenderObject {
|
||||
}
|
||||
|
||||
Map<_IntrinsicDimensionsCacheEntry, double>? _cachedIntrinsicDimensions;
|
||||
static int _debugIntrinsicsDepth = 0;
|
||||
|
||||
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
|
||||
@ -1370,11 +1372,38 @@ abstract class RenderBox extends RenderObject {
|
||||
return true;
|
||||
}());
|
||||
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>{};
|
||||
return _cachedIntrinsicDimensions!.putIfAbsent(
|
||||
final double result = _cachedIntrinsicDimensions!.putIfAbsent(
|
||||
_IntrinsicDimensionsCacheEntry(dimension, argument),
|
||||
() => computer(argument),
|
||||
);
|
||||
if (!kReleaseMode) {
|
||||
_debugIntrinsicsDepth -= 1;
|
||||
if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) {
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return computer(argument);
|
||||
}
|
||||
@ -1807,8 +1836,34 @@ abstract class RenderBox extends RenderObject {
|
||||
return true;
|
||||
}());
|
||||
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>{};
|
||||
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);
|
||||
}
|
||||
|
@ -1017,4 +1017,14 @@ class RenderCustomPaint extends RenderProxyBox {
|
||||
|
||||
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.
|
||||
///
|
||||
/// For details on how to use [dart:developer.Timeline] events in the Dart
|
||||
/// Observatory to optimize your app, see:
|
||||
/// <https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md>
|
||||
/// The timing information this flag exposes is not representative of the actual
|
||||
/// cost of layout, because the overhead of adding timeline events is
|
||||
/// 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:
|
||||
///
|
||||
@ -112,11 +121,17 @@ bool debugProfileLayoutsEnabled = false;
|
||||
/// Adds [dart:developer.Timeline] events for every [RenderObject] painted.
|
||||
///
|
||||
/// 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
|
||||
/// Observatory to optimize your app, see:
|
||||
/// <https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md>
|
||||
/// In debug builds, additional information is included in the trace (such as
|
||||
/// the properties of render objects being painted). 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:
|
||||
///
|
||||
|
@ -1155,8 +1155,10 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
|
||||
@override
|
||||
String toStringShort() {
|
||||
String header = super.toStringShort();
|
||||
if (_hasOverflow)
|
||||
header += ' OVERFLOWING';
|
||||
if (!kReleaseMode) {
|
||||
if (_hasOverflow)
|
||||
header += ' OVERFLOWING';
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
|
@ -175,8 +175,6 @@ class PaintingContext extends ClipContext {
|
||||
/// into the layer subtree associated with this painting context. Otherwise,
|
||||
/// the child will be painted into the current PictureLayer for this context.
|
||||
void paintChild(RenderObject child, Offset offset) {
|
||||
if (!kReleaseMode && debugProfilePaintsEnabled)
|
||||
Timeline.startSync('${child.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
assert(() {
|
||||
debugOnProfilePaint?.call(child);
|
||||
return true;
|
||||
@ -188,9 +186,6 @@ class PaintingContext extends ClipContext {
|
||||
} else {
|
||||
child._paintWithContext(this, offset);
|
||||
}
|
||||
|
||||
if (!kReleaseMode && debugProfilePaintsEnabled)
|
||||
Timeline.finishSync();
|
||||
}
|
||||
|
||||
void _compositeChild(RenderObject child, Offset offset) {
|
||||
@ -863,14 +858,27 @@ class PipelineOwner {
|
||||
/// See [RendererBinding] for an example of how this function is used.
|
||||
void flushLayout() {
|
||||
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(() {
|
||||
_debugDoingLayout = true;
|
||||
return true;
|
||||
}());
|
||||
try {
|
||||
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
|
||||
while (_nodesNeedingLayout.isNotEmpty) {
|
||||
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
|
||||
_nodesNeedingLayout = <RenderObject>[];
|
||||
@ -923,7 +931,7 @@ class PipelineOwner {
|
||||
/// [flushPaint].
|
||||
void flushCompositingBits() {
|
||||
if (!kReleaseMode) {
|
||||
Timeline.startSync('Compositing bits');
|
||||
Timeline.startSync('UPDATING COMPOSITING BITS', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
}
|
||||
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
|
||||
for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
|
||||
@ -956,7 +964,21 @@ class PipelineOwner {
|
||||
/// See [RendererBinding] for an example of how this function is used.
|
||||
void flushPaint() {
|
||||
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(() {
|
||||
_debugDoingPaint = true;
|
||||
@ -1058,7 +1080,7 @@ class PipelineOwner {
|
||||
if (_semanticsOwner == null)
|
||||
return;
|
||||
if (!kReleaseMode) {
|
||||
Timeline.startSync('Semantics');
|
||||
Timeline.startSync('SEMANTICS', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
}
|
||||
assert(_semanticsOwner != null);
|
||||
assert(() {
|
||||
@ -1745,9 +1767,17 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
||||
@pragma('vm:notify-debugger-on-exception')
|
||||
void layout(Constraints constraints, { bool parentUsesSize = false }) {
|
||||
assert(!_debugDisposed);
|
||||
if (!kReleaseMode && debugProfileLayoutsEnabled)
|
||||
Timeline.startSync('$runtimeType', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
|
||||
if (!kReleaseMode && debugProfileLayoutsEnabled) {
|
||||
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||
assert(() {
|
||||
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
|
||||
return true;
|
||||
}());
|
||||
Timeline.startSync(
|
||||
'$runtimeType',
|
||||
arguments: debugTimelineArguments,
|
||||
);
|
||||
}
|
||||
assert(constraints != null);
|
||||
assert(constraints.debugAssertIsValid(
|
||||
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.
|
||||
if (_needsLayout)
|
||||
return;
|
||||
if (!kReleaseMode && debugProfilePaintsEnabled) {
|
||||
Map<String, String> debugTimelineArguments = timelineArgumentsIndicatingLandmarkEvent;
|
||||
assert(() {
|
||||
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
|
||||
return true;
|
||||
}());
|
||||
Timeline.startSync(
|
||||
'$runtimeType',
|
||||
arguments: debugTimelineArguments,
|
||||
);
|
||||
}
|
||||
assert(() {
|
||||
if (_needsCompositingBitsUpdate) {
|
||||
if (parent is RenderObject) {
|
||||
@ -2412,6 +2453,8 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
||||
_debugDoingThisPaint = false;
|
||||
return true;
|
||||
}());
|
||||
if (!kReleaseMode && debugProfilePaintsEnabled)
|
||||
Timeline.finishSync();
|
||||
}
|
||||
|
||||
/// 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
|
||||
String toStringShort() {
|
||||
String header = describeIdentity(this);
|
||||
if (_debugDisposed) {
|
||||
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;
|
||||
if (!kReleaseMode) {
|
||||
if (_debugDisposed) {
|
||||
header += ' DISPOSED';
|
||||
return header;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -838,8 +838,10 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO
|
||||
@override
|
||||
String toStringShort() {
|
||||
String header = super.toStringShort();
|
||||
if (_isOverflowing)
|
||||
header += ' OVERFLOWING';
|
||||
if (!kReleaseMode) {
|
||||
if (_isOverflowing)
|
||||
header += ' OVERFLOWING';
|
||||
}
|
||||
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.
|
||||
void compositeFrame() {
|
||||
Timeline.startSync('Compositing', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
if (!kReleaseMode) {
|
||||
Timeline.startSync('COMPOSITING', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
}
|
||||
try {
|
||||
final ui.SceneBuilder builder = ui.SceneBuilder();
|
||||
final ui.Scene scene = layer!.buildScene(builder);
|
||||
@ -234,7 +236,9 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
|
||||
return true;
|
||||
}());
|
||||
} finally {
|
||||
Timeline.finishSync();
|
||||
if (!kReleaseMode) {
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,9 +94,18 @@ bool debugPrintGlobalKeyedWidgetLifecycle = false;
|
||||
|
||||
/// Adds [Timeline] events for every Widget built.
|
||||
///
|
||||
/// For details on how to use [Timeline] events in the Dart Observatory to
|
||||
/// optimize your app, see https://flutter.dev/docs/testing/debugging#tracing-any-dart-code-performance
|
||||
/// and https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md
|
||||
/// The timing information this flag exposes is not representative of the actual
|
||||
/// cost of building, because the overhead of adding timeline events is
|
||||
/// 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:
|
||||
///
|
||||
|
@ -2306,10 +2306,18 @@ abstract class BuildContext {
|
||||
/// data down to them.
|
||||
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});
|
||||
|
||||
/// 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});
|
||||
|
||||
/// Adds a description of a specific type of widget missing from the current
|
||||
@ -2525,7 +2533,25 @@ class BuildOwner {
|
||||
_debugBuilding = 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 {
|
||||
_scheduledFlushDirtyElements = true;
|
||||
if (callback != null) {
|
||||
@ -2555,10 +2581,11 @@ class BuildOwner {
|
||||
int dirtyCount = _dirtyElements.length;
|
||||
int index = 0;
|
||||
while (index < dirtyCount) {
|
||||
assert(_dirtyElements[index] != null);
|
||||
assert(_dirtyElements[index]._inDirtyList);
|
||||
final Element element = _dirtyElements[index];
|
||||
assert(element != null);
|
||||
assert(element._inDirtyList);
|
||||
assert(() {
|
||||
if (_dirtyElements[index]._lifecycleState == _ElementLifecycle.active && !_dirtyElements[index]._debugIsInScope(context)) {
|
||||
if (element._lifecycleState == _ElementLifecycle.active && !element._debugIsInScope(context)) {
|
||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||
ErrorSummary('Tried to build dirty widget in the wrong build scope.'),
|
||||
ErrorDescription(
|
||||
@ -2578,15 +2605,26 @@ class BuildOwner {
|
||||
),
|
||||
DiagnosticsProperty<Element>(
|
||||
'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,
|
||||
),
|
||||
]);
|
||||
}
|
||||
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 {
|
||||
_dirtyElements[index].rebuild();
|
||||
element.rebuild();
|
||||
} catch (e, stack) {
|
||||
_debugReportException(
|
||||
ErrorDescription('while rebuilding dirty elements'),
|
||||
@ -2594,14 +2632,16 @@ class BuildOwner {
|
||||
stack,
|
||||
informationCollector: () sync* {
|
||||
if (index < _dirtyElements.length) {
|
||||
yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index]));
|
||||
yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
|
||||
yield DiagnosticsDebugCreator(DebugCreator(element));
|
||||
yield element.describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
|
||||
} 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.');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||
Timeline.finishSync();
|
||||
index += 1;
|
||||
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
|
||||
_dirtyElements.sort(Element._sort);
|
||||
@ -2637,7 +2677,9 @@ class BuildOwner {
|
||||
_dirtyElements.clear();
|
||||
_scheduledFlushDirtyElements = false;
|
||||
_dirtyElementsNeedsResorting = null;
|
||||
Timeline.finishSync();
|
||||
if (!kReleaseMode) {
|
||||
Timeline.finishSync();
|
||||
}
|
||||
assert(_debugBuilding);
|
||||
assert(() {
|
||||
_debugBuilding = false;
|
||||
@ -2842,7 +2884,9 @@ class BuildOwner {
|
||||
/// about changes to global keys will run.
|
||||
@pragma('vm:notify-debugger-on-exception')
|
||||
void finalizeTree() {
|
||||
Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
if (!kReleaseMode) {
|
||||
Timeline.startSync('FINALIZE TREE', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
}
|
||||
try {
|
||||
lockState(() {
|
||||
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
|
||||
@ -2937,7 +2981,9 @@ class BuildOwner {
|
||||
// cause more exceptions.
|
||||
_debugReportException(ErrorSummary('while finalizing the widget tree'), e, stack);
|
||||
} finally {
|
||||
Timeline.finishSync();
|
||||
if (!kReleaseMode) {
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2948,14 +2994,18 @@ class BuildOwner {
|
||||
///
|
||||
/// This is expensive and should not be called except during development.
|
||||
void reassemble(Element root, DebugReassembleConfig? reassembleConfig) {
|
||||
Timeline.startSync('Dirty Element Tree');
|
||||
if (!kReleaseMode) {
|
||||
Timeline.startSync('Preparing Hot Reload (widgets)');
|
||||
}
|
||||
try {
|
||||
assert(root._parent == null);
|
||||
assert(root.owner == this);
|
||||
root._debugReassembleConfig = reassembleConfig;
|
||||
root.reassemble();
|
||||
} finally {
|
||||
Timeline.finishSync();
|
||||
if (!kReleaseMode) {
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3021,8 +3071,24 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
||||
Element? _parent;
|
||||
DebugReassembleConfig? _debugReassembleConfig;
|
||||
|
||||
// Custom implementation of `operator ==` optimized for the ".of" pattern
|
||||
// used with `InheritedWidgets`.
|
||||
/// Compare two widgets for equality.
|
||||
///
|
||||
/// 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
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
@ -3347,6 +3413,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
||||
deactivateChild(child);
|
||||
return null;
|
||||
}
|
||||
|
||||
final Element newChild;
|
||||
if (child != null) {
|
||||
bool hasSameSuperclass = true;
|
||||
@ -3372,13 +3439,29 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
||||
return true;
|
||||
}());
|
||||
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)
|
||||
updateSlotForChild(child, newSlot);
|
||||
newChild = child;
|
||||
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
|
||||
if (child.slot != 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);
|
||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||
Timeline.finishSync();
|
||||
assert(child.widget == newWidget);
|
||||
assert(() {
|
||||
child.owner!._debugElementWasRebuilt(child);
|
||||
@ -3388,10 +3471,36 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
||||
} else {
|
||||
deactivateChild(child);
|
||||
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);
|
||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||
Timeline.finishSync();
|
||||
}
|
||||
} 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);
|
||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||
Timeline.finishSync();
|
||||
}
|
||||
|
||||
assert(() {
|
||||
@ -4291,6 +4400,9 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
||||
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 to mark this element dirty, by [mount] when the element is first
|
||||
/// built, and by [update] when the widget has changed.
|
||||
@ -4328,7 +4440,9 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
||||
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
|
||||
void performRebuild();
|
||||
}
|
||||
@ -4574,7 +4688,8 @@ abstract class ComponentElement extends Element {
|
||||
}
|
||||
|
||||
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
|
||||
@ -4586,9 +4701,6 @@ abstract class ComponentElement extends Element {
|
||||
@override
|
||||
@pragma('vm:notify-debugger-on-exception')
|
||||
void performRebuild() {
|
||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||
Timeline.startSync('${widget.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
|
||||
|
||||
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
|
||||
Widget? built;
|
||||
try {
|
||||
@ -4636,9 +4748,6 @@ abstract class ComponentElement extends Element {
|
||||
);
|
||||
_child = updateChild(null, built, slot);
|
||||
}
|
||||
|
||||
if (!kReleaseMode && debugProfileBuildsEnabled)
|
||||
Timeline.finishSync();
|
||||
}
|
||||
|
||||
/// Subclasses should override this function to actually call the appropriate
|
||||
@ -5490,16 +5599,7 @@ abstract class RenderObjectElement extends Element {
|
||||
_debugUpdateRenderObjectOwner();
|
||||
return true;
|
||||
}());
|
||||
assert(() {
|
||||
_debugDoingBuild = true;
|
||||
return true;
|
||||
}());
|
||||
widget.updateRenderObject(this, renderObject);
|
||||
assert(() {
|
||||
_debugDoingBuild = false;
|
||||
return true;
|
||||
}());
|
||||
_dirty = false;
|
||||
_performRebuild(); // calls widget.updateRenderObject()
|
||||
}
|
||||
|
||||
void _debugUpdateRenderObjectOwner() {
|
||||
@ -5511,6 +5611,11 @@ abstract class RenderObjectElement extends Element {
|
||||
|
||||
@override
|
||||
void performRebuild() {
|
||||
_performRebuild(); // calls widget.updateRenderObject()
|
||||
}
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
void _performRebuild() {
|
||||
assert(() {
|
||||
_debugDoingBuild = true;
|
||||
return true;
|
||||
@ -6367,8 +6472,10 @@ class IndexedSlot<T extends Element?> {
|
||||
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 {
|
||||
_NullElement() : super(_NullWidget());
|
||||
_NullElement() : super(const _NullWidget());
|
||||
|
||||
static _NullElement instance = _NullElement();
|
||||
|
||||
@ -6376,10 +6483,12 @@ class _NullElement extends Element {
|
||||
bool get debugDoingBuild => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void performRebuild() { }
|
||||
void performRebuild() => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class _NullWidget extends Widget {
|
||||
const _NullWidget();
|
||||
|
||||
@override
|
||||
Element createElement() => throw UnimplementedError();
|
||||
}
|
||||
|
@ -526,6 +526,11 @@ class _GlowController extends ChangeNotifier {
|
||||
canvas.drawCircle(center, radius, paint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '_GlowController(color: $color, axis: ${describeEnum(axis)})';
|
||||
}
|
||||
}
|
||||
|
||||
class _GlowingOverscrollIndicatorPainter extends CustomPainter {
|
||||
@ -595,6 +600,11 @@ class _GlowingOverscrollIndicatorPainter extends CustomPainter {
|
||||
return oldDelegate.leadingController != leadingController
|
||||
|| oldDelegate.trailingController != trailingController;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '_GlowingOverscrollIndicatorPainter($leadingController, $trailingController)';
|
||||
}
|
||||
}
|
||||
|
||||
/// A Material Design visual indication that a scroll view has overscrolled.
|
||||
@ -891,6 +901,9 @@ class _StretchController extends ChangeNotifier {
|
||||
_stretchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '_StretchController()';
|
||||
}
|
||||
|
||||
/// 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
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'basic.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
|
||||
SemanticsBuilderCallback? get semanticsBuilder => null;
|
||||
|
||||
@override
|
||||
String toString() => describeIdentity(this);
|
||||
}
|
||||
|
||||
/// An extendable base class for building scrollbars that fade in and out.
|
||||
|
@ -226,6 +226,11 @@ void main() {
|
||||
' │ parentData: <none> (can use size)\n'
|
||||
' │ constraints: BoxConstraints(w=800.0, h=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'
|
||||
' └─child: RenderRepaintBoundary#00000\n'
|
||||
' │ needs compositing\n'
|
||||
@ -322,6 +327,9 @@ void main() {
|
||||
' │ parentData: <none> (can use size)\n'
|
||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||
' │ size: Size(800.0, 400.0)\n'
|
||||
' │ painter: null\n'
|
||||
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||
' │\n'
|
||||
' ├─child with index 1: RenderLimitedBox#00000\n' // <----- no dashed line starts here
|
||||
' │ │ parentData: index=1; layoutOffset=400.0\n'
|
||||
@ -334,6 +342,9 @@ void main() {
|
||||
' │ parentData: <none> (can use size)\n'
|
||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||
' │ size: Size(800.0, 400.0)\n'
|
||||
' │ painter: null\n'
|
||||
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||
' │\n'
|
||||
' └─child with index 2: RenderLimitedBox#00000 NEEDS-PAINT\n'
|
||||
' │ parentData: index=2; layoutOffset=800.0\n'
|
||||
@ -345,7 +356,10 @@ void main() {
|
||||
' └─child: RenderCustomPaint#00000 NEEDS-PAINT\n'
|
||||
' parentData: <none> (can use size)\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);
|
||||
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
|
||||
@ -375,6 +389,11 @@ void main() {
|
||||
' │ parentData: <none> (can use size)\n'
|
||||
' │ constraints: BoxConstraints(w=800.0, h=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'
|
||||
' └─child: RenderRepaintBoundary#00000\n'
|
||||
' │ needs compositing\n'
|
||||
@ -471,6 +490,9 @@ void main() {
|
||||
' │ parentData: <none> (can use size)\n'
|
||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||
' │ size: Size(800.0, 400.0)\n'
|
||||
' │ painter: null\n'
|
||||
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||
' │\n'
|
||||
' ├─child with index 5: RenderLimitedBox#00000\n' // <----- this is index 5, not 0
|
||||
' │ │ parentData: index=5; layoutOffset=2000.0\n'
|
||||
@ -483,6 +505,9 @@ void main() {
|
||||
' │ parentData: <none> (can use size)\n'
|
||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||
' │ size: Size(800.0, 400.0)\n'
|
||||
' │ painter: null\n'
|
||||
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||
' │\n'
|
||||
' ├─child with index 6: RenderLimitedBox#00000\n'
|
||||
' │ │ parentData: index=6; layoutOffset=2400.0\n'
|
||||
@ -495,6 +520,9 @@ void main() {
|
||||
' │ parentData: <none> (can use size)\n'
|
||||
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||
' │ size: Size(800.0, 400.0)\n'
|
||||
' │ painter: null\n'
|
||||
' │ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||
' │ preferredSize: Size(Infinity, Infinity)\n'
|
||||
' │\n'
|
||||
' ├─child with index 7: RenderLimitedBox#00000 NEEDS-PAINT\n'
|
||||
' ╎ │ parentData: index=7; layoutOffset=2800.0\n'
|
||||
@ -507,6 +535,9 @@ void main() {
|
||||
' ╎ parentData: <none> (can use size)\n'
|
||||
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
|
||||
' ╎ size: Size(800.0, 400.0)\n'
|
||||
' ╎ painter: null\n'
|
||||
' ╎ foregroundPainter: _PlaceholderPainter#00000()\n'
|
||||
' ╎ preferredSize: Size(Infinity, Infinity)\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
|
||||
' ╎ │ parentData: index=0; keepAlive; layoutOffset=0.0\n'
|
||||
@ -519,6 +550,9 @@ void main() {
|
||||
' ╎ parentData: <none> (can use size)\n'
|
||||
' ╎ constraints: BoxConstraints(w=800.0, h=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
|
||||
' └╌child with index 3 (kept alive but not laid out): RenderLimitedBox#00000\n'
|
||||
' │ parentData: index=3; keepAlive; layoutOffset=1200.0\n'
|
||||
@ -530,7 +564,10 @@ void main() {
|
||||
' └─child: RenderCustomPaint#00000\n'
|
||||
' parentData: <none> (can use size)\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
|
||||
|
||||
|
@ -31,7 +31,7 @@ class TestDiagnosticsNode extends DiagnosticsNode {
|
||||
}
|
||||
|
||||
@override
|
||||
String? toDescription({TextTreeConfiguration? parentConfiguration}) {
|
||||
String toDescription({TextTreeConfiguration? parentConfiguration}) {
|
||||
return 'Test Description';
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user