Skip to content

Commit 3b3c25f

Browse files
committed
Merge remote-tracking branch 'origin/master' into simple-release-notes
2 parents 1b901fc + 307d03e commit 3b3c25f

26 files changed

+273
-62
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ RELEASE_NOTE_EXCEPTION=[REASON GOES HERE]
2020

2121
![build.yaml badge]
2222

23-
If you need help, consider asking for advice on the #hackers-devexp-📐 channel on [Discord].
23+
If you need help, consider asking for help on [Discord].
2424

2525
<!-- Links -->
2626
[Contributor Guide]: https://github.com/flutter/devtools/blob/master/CONTRIBUTING.md
2727
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
2828
[Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
2929
[CLA]: https://cla.developers.google.com/
30-
[Discord]: https://discord.com/channels/608014603317936148/608020249119555594
30+
[Discord]: https://github.com/flutter/flutter/wiki/Chat
3131
[build.yaml badge]: https://github.com/flutter/devtools/actions/workflows/build.yaml/badge.svg

packages/devtools_app/lib/src/app.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,17 +229,17 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
229229
return ValueListenableBuilder<bool>(
230230
valueListenable: preferences.vmDeveloperModeEnabled,
231231
builder: (_, __, child) {
232-
final tabs = _visibleScreens()
232+
final screens = _visibleScreens()
233233
.where((p) => embed && page != null ? p.screenId == page : true)
234234
.where((p) => !hide.contains(p.screenId))
235235
.toList();
236-
if (tabs.isEmpty) return child ?? const SizedBox.shrink();
236+
if (screens.isEmpty) return child ?? const SizedBox.shrink();
237237
return _providedControllers(
238238
child: DevToolsScaffold(
239239
embed: embed,
240240
ideTheme: ideTheme,
241241
page: page,
242-
tabs: tabs,
242+
screens: screens,
243243
actions: [
244244
// TODO(https://github.com/flutter/devtools/issues/1941)
245245
if (serviceManager.connectedApp!.isFlutterAppNow!) ...[

packages/devtools_app/lib/src/framework/scaffold.dart

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import 'status_line.dart';
3737
class DevToolsScaffold extends StatefulWidget {
3838
const DevToolsScaffold({
3939
Key? key,
40-
required this.tabs,
40+
required this.screens,
4141
this.page,
4242
this.actions,
4343
this.embed = false,
@@ -51,7 +51,7 @@ class DevToolsScaffold extends StatefulWidget {
5151
List<Widget>? actions,
5252
}) : this(
5353
key: key,
54-
tabs: [SimpleScreen(child)],
54+
screens: [SimpleScreen(child)],
5555
ideTheme: ideTheme,
5656
actions: actions,
5757
);
@@ -77,7 +77,7 @@ class DevToolsScaffold extends StatefulWidget {
7777
EdgeInsets.symmetric(horizontal: isEmbedded() ? 2.0 : 16.0);
7878

7979
/// All of the [Screen]s that it's possible to navigate to from this Scaffold.
80-
final List<Screen> tabs;
80+
final List<Screen> screens;
8181

8282
/// The page being rendered.
8383
final String? page;
@@ -134,22 +134,23 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
134134
void didUpdateWidget(DevToolsScaffold oldWidget) {
135135
super.didUpdateWidget(oldWidget);
136136

137-
if (widget.tabs.length != oldWidget.tabs.length) {
137+
if (widget.screens.length != oldWidget.screens.length) {
138138
var newIndex = 0;
139139
// Stay on the current tab if possible when the collection of tabs changes.
140140
if (_tabController != null &&
141-
widget.tabs.contains(oldWidget.tabs[_tabController!.index])) {
142-
newIndex = widget.tabs.indexOf(oldWidget.tabs[_tabController!.index]);
141+
widget.screens.contains(oldWidget.screens[_tabController!.index])) {
142+
newIndex =
143+
widget.screens.indexOf(oldWidget.screens[_tabController!.index]);
143144
}
144145
// Create a new tab controller to reflect the changed tabs.
145146
_setupTabController();
146147
_tabController!.index = newIndex;
147-
} else if (widget.tabs[_tabController!.index].screenId != widget.page) {
148+
} else if (widget.screens[_tabController!.index].screenId != widget.page) {
148149
// If the page changed (eg. the route was modified by pressing back in the
149150
// browser), animate to the new one.
150151
final newIndex = widget.page == null
151152
? 0 // When there's no supplied page, we show the first one.
152-
: widget.tabs.indexWhere((t) => t.screenId == widget.page);
153+
: widget.screens.indexWhere((t) => t.screenId == widget.page);
153154
_tabController!.animateTo(newIndex);
154155
}
155156
}
@@ -183,19 +184,19 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
183184

184185
void _setupTabController() {
185186
_tabController?.dispose();
186-
_tabController = TabController(length: widget.tabs.length, vsync: this);
187+
_tabController = TabController(length: widget.screens.length, vsync: this);
187188

188189
if (widget.page != null) {
189190
final initialIndex =
190-
widget.tabs.indexWhere((screen) => screen.screenId == widget.page);
191+
widget.screens.indexWhere((screen) => screen.screenId == widget.page);
191192
if (initialIndex != -1) {
192193
_tabController!.index = initialIndex;
193194
}
194195
}
195196

196-
_currentScreen = widget.tabs[_tabController!.index];
197+
_currentScreen = widget.screens[_tabController!.index];
197198
_tabController!.addListener(() {
198-
final screen = widget.tabs[_tabController!.index];
199+
final screen = widget.screens[_tabController!.index];
199200

200201
if (_currentScreen != screen) {
201202
setState(() {
@@ -246,7 +247,7 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
246247
final existingTabIndex = _tabController!.index;
247248

248249
final newIndex =
249-
widget.tabs.indexWhere((screen) => screen.screenId == pageId);
250+
widget.screens.indexWhere((screen) => screen.screenId == pageId);
250251

251252
if (newIndex != -1 && newIndex != existingTabIndex) {
252253
DevToolsRouterDelegate.of(context).navigateIfNotCurrent(pageId);
@@ -278,7 +279,7 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
278279
Widget build(BuildContext context) {
279280
// Build the screens for each tab and wrap them in the appropriate styling.
280281
final tabBodies = [
281-
for (var screen in widget.tabs)
282+
for (var screen in widget.screens)
282283
Container(
283284
// TODO(kenz): this padding creates a flash when dragging and dropping
284285
// into the app size screen because it creates space that is outside
@@ -380,17 +381,17 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
380381

381382
// Add a leading [BulletSpacer] to the actions if the screen is not narrow.
382383
final actions = List<Widget>.from(widget.actions ?? []);
383-
if (!isNarrow && actions.isNotEmpty && widget.tabs.length > 1) {
384+
if (!isNarrow && actions.isNotEmpty && widget.screens.length > 1) {
384385
actions.insert(0, const BulletSpacer(useAccentColor: true));
385386
}
386387

387-
final bool hasMultipleTabs = widget.tabs.length > 1;
388+
final bool hasMultipleTabs = widget.screens.length > 1;
388389

389390
if (hasMultipleTabs) {
390391
tabBar = TabBar(
391392
controller: _tabController,
392393
isScrollable: true,
393-
tabs: [for (var screen in widget.tabs) screen.buildTab(context)],
394+
tabs: [for (var screen in widget.screens) screen.buildTab(context)],
394395
);
395396
preferredSize = isNarrow
396397
? Size.fromHeight(
@@ -461,7 +462,7 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
461462
// Approximate size of the title. Add [defaultSpacing] to account for
462463
// title's leading padding.
463464
double wideWidth = painter.width + defaultSpacing;
464-
for (var tab in widget.tabs) {
465+
for (var tab in widget.screens) {
465466
wideWidth += tab.approximateWidth(textTheme);
466467
}
467468
final actionsLength = widget.actions?.length ?? 0;

packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,17 @@ class ObjectSetDiff {
166166
final object = before.objectsByCodes[code] ?? after.objectsByCodes[code]!;
167167

168168
if (inBefore) {
169-
deleted.countInstance(object);
170-
delta.uncountInstance(object);
169+
final excludeFromRetained =
170+
before.notCountedInRetained.contains(object.code);
171+
deleted.countInstance(object, excludeFromRetained: excludeFromRetained);
172+
delta.uncountInstance(object, excludeFromRetained: excludeFromRetained);
171173
continue;
172174
}
173175
if (inAfter) {
174-
created.countInstance(object);
175-
delta.countInstance(object);
176+
final excludeFromRetained =
177+
after.notCountedInRetained.contains(object.code);
178+
created.countInstance(object, excludeFromRetained: excludeFromRetained);
179+
delta.countInstance(object, excludeFromRetained: excludeFromRetained);
176180
continue;
177181
}
178182
assert(false);

packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/class_filter_dialog.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,5 @@ const _helpText = 'Choose and customize the filter.\n'
135135
' MyClass\n'
136136
' package:myPackage/src/\n\n'
137137
'Specify:\n'
138-
' - ${ClassFilter.noPackageLibrariesAlias} for classes without package prefix\n'
138+
' - ${ClassFilter.dartInternalAlias} for dart internal objects, not assigned to any package\n'
139139
' - ${ClassFilter.dartAndFlutterLibrariesAlias} for most "dart:" and "package:" libraries published by Dart and Flutter orgs.';

packages/devtools_app/lib/src/screens/memory/shared/heap/class_filter.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ class ClassFilter {
3636
ClassFilter.empty()
3737
: this(
3838
filterType: ClassFilterType.showAll,
39-
except: '$noPackageLibrariesAlias\n$dartAndFlutterLibrariesAlias',
39+
except: '$dartInternalAlias\n$dartAndFlutterLibrariesAlias',
4040
only: null,
4141
);
4242

4343
static String _trimByLine(String value) =>
4444
value.split('\n').map((e) => e.trim()).join('\n');
4545

4646
static const String dartAndFlutterLibrariesAlias = '\$dart-flutter-libraries';
47-
static const String noPackageLibrariesAlias = '\$no-package-libraries';
47+
static const String dartInternalAlias = '\$dart-internal-objects';
4848

4949
final ClassFilterType filterType;
5050
final String except;
@@ -140,8 +140,7 @@ class ClassFilter {
140140
if (className.fullName.contains(filter)) return true;
141141
if (filter == dartAndFlutterLibrariesAlias && className.isDartOrFlutter)
142142
return true;
143-
if (filter == noPackageLibrariesAlias && className.isPackageless)
144-
return true;
143+
if (filter == dartInternalAlias && className.isPackageless) return true;
145144
return false;
146145
}
147146
}

packages/devtools_app/lib/src/screens/memory/shared/heap/heap.dart

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,19 @@ class SingleClassStats extends ClassStats {
133133
assert(!isSealed);
134134
final object = data.objects[objectIndex];
135135
assert(object.heapClass.fullName == heapClass.fullName);
136-
objects.countInstance(object);
137136

138137
final path = data.retainingPath(objectIndex);
138+
objects.countInstance(
139+
object,
140+
excludeFromRetained: path?.isRetainedBySameClass ?? false,
141+
);
142+
139143
if (path == null) return;
140144
final objectsForPath = statsByPath.putIfAbsent(
141145
ClassOnlyHeapPath(path),
142146
() => ObjectSet(),
143147
);
144-
objectsForPath.countInstance(object);
148+
objectsForPath.countInstance(object, excludeFromRetained: false);
145149
}
146150

147151
bool get isZero => objects.isZero;
@@ -174,16 +178,22 @@ class ObjectSetStats with Sealable {
174178
bool get isZero =>
175179
shallowSize == 0 && retainedSize == 0 && instanceCount == 0;
176180

177-
void countInstance(AdaptedHeapObject object) {
181+
void countInstance(
182+
AdaptedHeapObject object, {
183+
required bool excludeFromRetained,
184+
}) {
178185
assert(!isSealed);
179-
retainedSize += object.retainedSize!;
186+
if (!excludeFromRetained) retainedSize += object.retainedSize!;
180187
shallowSize += object.shallowSize;
181188
instanceCount++;
182189
}
183190

184-
void uncountInstance(AdaptedHeapObject object) {
191+
void uncountInstance(
192+
AdaptedHeapObject object, {
193+
required bool excludeFromRetained,
194+
}) {
185195
assert(!isSealed);
186-
retainedSize -= object.retainedSize!;
196+
if (!excludeFromRetained) retainedSize -= object.retainedSize!;
187197
shallowSize -= object.shallowSize;
188198
instanceCount--;
189199
}
@@ -194,19 +204,27 @@ class ObjectSet extends ObjectSetStats {
194204
static ObjectSet empty = ObjectSet()..seal();
195205

196206
final objectsByCodes = <IdentityHashCode, AdaptedHeapObject>{};
207+
final notCountedInRetained = <IdentityHashCode>{};
197208

198209
@override
199210
bool get isZero => objectsByCodes.isEmpty;
200211

201212
@override
202-
void countInstance(AdaptedHeapObject object) {
213+
void countInstance(
214+
AdaptedHeapObject object, {
215+
required bool excludeFromRetained,
216+
}) {
203217
if (objectsByCodes.containsKey(object.code)) return;
204-
super.countInstance(object);
218+
super.countInstance(object, excludeFromRetained: excludeFromRetained);
205219
objectsByCodes[object.code] = object;
220+
if (excludeFromRetained) notCountedInRetained.add(object.code);
206221
}
207222

208223
@override
209-
void uncountInstance(AdaptedHeapObject object) {
224+
void uncountInstance(
225+
AdaptedHeapObject object, {
226+
required bool excludeFromRetained,
227+
}) {
210228
throw AssertionError('uncountInstance is not valid for $ObjectSet');
211229
}
212230
}

packages/devtools_app/lib/src/screens/memory/shared/heap/model.dart

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,21 +108,26 @@ class AdaptedHeapData {
108108
typedef IdentityHashCode = int;
109109

110110
/// Sequence of ids of objects in the heap.
111-
///
112-
/// TODO(polina-c): maybe we do not need to store path by objects.
113-
/// It can be that only classes are interesting, and we can save some
114-
/// performance on this object. It will become clear when the leak tracking
115-
/// feature stabilizes.
116111
class HeapPath {
117112
HeapPath(this.objects);
118113

119114
final List<AdaptedHeapObject> objects;
120115

116+
late final bool isRetainedBySameClass = () {
117+
if (objects.length < 2) return false;
118+
119+
final theClass = objects.last.heapClass;
120+
121+
return objects
122+
.take(objects.length - 1)
123+
.any((object) => object.heapClass == theClass);
124+
}();
125+
121126
/// Retaining path for the object in string format.
122-
String? shortPath() => '/${objects.map((o) => o.shortName).join('/')}/';
127+
String shortPath() => '/${objects.map((o) => o.shortName).join('/')}/';
123128

124129
/// Retaining path for the object as an array of the retaining objects.
125-
List<String>? detailedPath() =>
130+
List<String> detailedPath() =>
126131
objects.map((o) => o.name).toList(growable: false);
127132
}
128133

0 commit comments

Comments
 (0)