Commit 4f0eff31 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fix crash when a RenderObject tree is rooted in a non-RenderObject environment (#5933)

parent f3813202
...@@ -35,7 +35,7 @@ void main() { ...@@ -35,7 +35,7 @@ void main() {
test('Exits with code 1 when fails to connect', () async { test('Exits with code 1 when fails to connect', () async {
expect(await runScript(<String>['smoke_test_setup_failure']), 1); expect(await runScript(<String>['smoke_test_setup_failure']), 1);
}); }, skip: true); // https://github.com/flutter/flutter/issues/5901
test('Exits with code 1 when results are mixed', () async { test('Exits with code 1 when results are mixed', () async {
expect( expect(
......
...@@ -26,7 +26,11 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding, ...@@ -26,7 +26,11 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
void initInstances() { void initInstances() {
super.initInstances(); super.initInstances();
_instance = this; _instance = this;
_pipelineOwner = new PipelineOwner(onNeedVisualUpdate: ensureVisualUpdate); _pipelineOwner = new PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onScheduleInitialSemantics: _scheduleInitialSemantics,
onClearSemantics: _clearSemantics,
);
ui.window.onMetricsChanged = handleMetricsChanged; ui.window.onMetricsChanged = handleMetricsChanged;
initRenderView(); initRenderView();
initSemantics(); initSemantics();
...@@ -96,12 +100,12 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding, ...@@ -96,12 +100,12 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
PipelineOwner _pipelineOwner; PipelineOwner _pipelineOwner;
/// The render tree that's attached to the output surface. /// The render tree that's attached to the output surface.
RenderView get renderView => _pipelineOwner.rootRenderObject; RenderView get renderView => _pipelineOwner.rootNode;
/// Sets the given [RenderView] object (which must not be null), and its tree, to /// Sets the given [RenderView] object (which must not be null), and its tree, to
/// be the new render tree to display. The previous tree, if any, is detached. /// be the new render tree to display. The previous tree, if any, is detached.
set renderView(RenderView value) { set renderView(RenderView value) {
assert(value != null); assert(value != null);
_pipelineOwner.rootRenderObject = value; _pipelineOwner.rootNode = value;
} }
/// Called when the system metrics change. /// Called when the system metrics change.
...@@ -210,7 +214,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding, ...@@ -210,7 +214,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
@override @override
void reassembleApplication() { void reassembleApplication() {
super.reassembleApplication(); super.reassembleApplication();
pipelineOwner.reassemble(); renderView.reassemble();
handleBeginFrame(null); handleBeginFrame(null);
} }
...@@ -229,6 +233,14 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding, ...@@ -229,6 +233,14 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
}; };
instance?.renderView?.visitChildren(visitor); instance?.renderView?.visitChildren(visitor);
} }
void _scheduleInitialSemantics() {
renderView.scheduleInitialSemantics();
}
void _clearSemantics() {
renderView.clearSemantics();
}
} }
/// Prints a textual representation of the entire render tree. /// Prints a textual representation of the entire render tree.
......
...@@ -76,6 +76,9 @@ class AbstractNode { ...@@ -76,6 +76,9 @@ class AbstractNode {
/// ///
/// Typically called only from the parent's attach(), and to mark the root of /// Typically called only from the parent's attach(), and to mark the root of
/// a tree attached. /// a tree attached.
///
/// Subclasses with children should attach all their children to the same
/// [owner] whenever this method is called.
@mustCallSuper @mustCallSuper
void attach(Object owner) { void attach(Object owner) {
assert(owner != null); assert(owner != null);
...@@ -87,6 +90,9 @@ class AbstractNode { ...@@ -87,6 +90,9 @@ class AbstractNode {
/// ///
/// Typically called only from the parent's detach(), and to mark the root of /// Typically called only from the parent's detach(), and to mark the root of
/// a tree detached. /// a tree detached.
///
/// Subclasses with children should detach all their children whenever this
/// method is called.
@mustCallSuper @mustCallSuper
void detach() { void detach() {
assert(_owner != null); assert(_owner != null);
......
...@@ -789,7 +789,11 @@ class PipelineOwner { ...@@ -789,7 +789,11 @@ class PipelineOwner {
/// Typically created by the binding (e.g., [RendererBinding]), but can be /// Typically created by the binding (e.g., [RendererBinding]), but can be
/// created separately from the binding to drive off-screen render objects /// created separately from the binding to drive off-screen render objects
/// through the rendering pipeline. /// through the rendering pipeline.
PipelineOwner({ this.onNeedVisualUpdate }); PipelineOwner({
this.onNeedVisualUpdate,
this.onScheduleInitialSemantics,
this.onClearSemantics,
});
/// Called when a render object associated with this pipeline owner wishes to /// Called when a render object associated with this pipeline owner wishes to
/// update its visual appearance. /// update its visual appearance.
...@@ -800,6 +804,21 @@ class PipelineOwner { ...@@ -800,6 +804,21 @@ class PipelineOwner {
/// duplicate calls quickly. /// duplicate calls quickly.
final VoidCallback onNeedVisualUpdate; final VoidCallback onNeedVisualUpdate;
/// Called when [addSemanticsListener] is called when there was no
/// [SemanticsOwner] present, to request that the
/// [RenderObject.scheduleInitialSemantics] method be called on the
/// appropriate object(s).
///
/// For example, the [RendererBinding] calls it on the [RenderView] object.
final VoidCallback onScheduleInitialSemantics;
/// Called when the last [SemanticsListener] is removed from the
/// [SemanticsOwner], to request that the [RenderObject.clearSemantics] method
/// be called on the appropriate object(s).
///
/// For example, the [RendererBinding] calls it on the [RenderView] object.
final VoidCallback onClearSemantics;
/// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null. /// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
/// ///
/// Used to notify the pipeline owner that an associated render object wishes /// Used to notify the pipeline owner that an associated render object wishes
...@@ -809,15 +828,17 @@ class PipelineOwner { ...@@ -809,15 +828,17 @@ class PipelineOwner {
onNeedVisualUpdate(); onNeedVisualUpdate();
} }
/// The unique render object managed by this pipeline that has no parent. /// The unique object managed by this pipeline that has no parent.
RenderObject get rootRenderObject => _rootRenderObject; ///
RenderObject _rootRenderObject; /// This object does not have to be a [RenderObject].
set rootRenderObject(RenderObject value) { AbstractNode get rootNode => _rootNode;
if (_rootRenderObject == value) AbstractNode _rootNode;
set rootNode(AbstractNode value) {
if (_rootNode == value)
return; return;
_rootRenderObject?.detach(); _rootNode?.detach();
_rootRenderObject = value; _rootNode = value;
_rootRenderObject?.attach(this); _rootNode?.attach(this);
} }
/// Calls the given listener whenever the semantics of the render tree change. /// Calls the given listener whenever the semantics of the render tree change.
...@@ -829,7 +850,8 @@ class PipelineOwner { ...@@ -829,7 +850,8 @@ class PipelineOwner {
initialListener: listener, initialListener: listener,
onLastListenerRemoved: _handleLastSemanticsListenerRemoved onLastListenerRemoved: _handleLastSemanticsListenerRemoved
); );
_rootRenderObject.scheduleInitialSemantics(); if (onScheduleInitialSemantics != null)
onScheduleInitialSemantics();
} else { } else {
_semanticsOwner.addListener(listener); _semanticsOwner.addListener(listener);
} }
...@@ -839,7 +861,8 @@ class PipelineOwner { ...@@ -839,7 +861,8 @@ class PipelineOwner {
void _handleLastSemanticsListenerRemoved() { void _handleLastSemanticsListenerRemoved() {
assert(!_debugDoingSemantics); assert(!_debugDoingSemantics);
rootRenderObject._clearSemantics(); if (onClearSemantics != null)
onClearSemantics();
_semanticsOwner.dispose(); _semanticsOwner.dispose();
_semanticsOwner = null; _semanticsOwner = null;
} }
...@@ -993,16 +1016,6 @@ class PipelineOwner { ...@@ -993,16 +1016,6 @@ class PipelineOwner {
} }
_semanticsOwner.sendSemanticsTree(); _semanticsOwner.sendSemanticsTree();
} }
/// Cause the entire render tree rooted at [rootRenderObject] to be entirely
/// reprocessed. This is used by development tools when the application code
/// has changed, to cause the rendering tree to pick up any changed
/// implementations.
///
/// This is expensive and should not be called except during development.
void reassemble() {
_rootRenderObject?._reassemble();
}
} }
// See _performLayout. // See _performLayout.
...@@ -1038,14 +1051,25 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1038,14 +1051,25 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
_performLayout = performLayout; _performLayout = performLayout;
} }
void _reassemble() { /// Cause the entire subtree rooted at the given [RenderObject] to be marked
/// dirty for layout, paint, etc. This is called by the [RendererBinding] in
/// response to the `ext.flutter.reassemble` hook, which is used by
/// development tools when the application code has changed, to cause the
/// widget tree to pick up any changed implementations.
///
/// This is expensive and should not be called except during development.
///
/// See also:
///
/// * [BindingBase.reassembleApplication].
void reassemble() {
_performLayout = performLayout; _performLayout = performLayout;
markNeedsLayout(); markNeedsLayout();
markNeedsCompositingBitsUpdate(); markNeedsCompositingBitsUpdate();
markNeedsPaint(); markNeedsPaint();
markNeedsSemanticsUpdate(); markNeedsSemanticsUpdate();
visitChildren((RenderObject child) { visitChildren((RenderObject child) {
child._reassemble(); child.reassemble();
}); });
} }
...@@ -1490,12 +1514,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1490,12 +1514,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
)); ));
assert(!_debugDoingThisResize); assert(!_debugDoingThisResize);
assert(!_debugDoingThisLayout); assert(!_debugDoingThisLayout);
final RenderObject parent = this.parent;
RenderObject relayoutBoundary; RenderObject relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this; relayoutBoundary = this;
else } else {
final RenderObject parent = this.parent;
relayoutBoundary = parent._relayoutBoundary; relayoutBoundary = parent._relayoutBoundary;
}
assert(parent == this.parent); assert(parent == this.parent);
assert(() { assert(() {
_debugCanParentUseSize = parentUsesSize; _debugCanParentUseSize = parentUsesSize;
...@@ -1992,12 +2017,17 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1992,12 +2017,17 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
} }
/// Removes all semantics from this render object and its descendants. /// Removes all semantics from this render object and its descendants.
void _clearSemantics() { ///
/// Should only be called in response to the [PipelineOwner] calling its
/// [PipelineOwner.onClearSemantics] callback.
///
/// Should only be called on objects whose [parent] is not a [RenderObject].
void clearSemantics() {
_needsSemanticsUpdate = true; _needsSemanticsUpdate = true;
_needsSemanticsGeometryUpdate = true; _needsSemanticsGeometryUpdate = true;
_semantics = null; _semantics = null;
visitChildren((RenderObject child) { visitChildren((RenderObject child) {
child._clearSemantics(); child.clearSemantics();
}); });
} }
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';
import 'rendering_tester.dart';
class RealRoot extends AbstractNode {
RealRoot(this.child) {
if (child != null)
adoptChild(child);
}
final RenderObject child;
@override
void redepthChildren() {
if (child != null)
redepthChild(child);
}
@override
void attach(Object owner) {
super.attach(owner);
child?.attach(owner);
}
@override
void detach() {
super.detach();
child?.detach();
}
@override
PipelineOwner get owner => super.owner;
void layout() {
child?.layout(new BoxConstraints.tight(const Size(500.0, 500.0)));
}
}
void main() {
test("non-RenderObject roots", () {
RenderPositionedBox child;
RealRoot root = new RealRoot(
child = new RenderPositionedBox(
alignment: FractionalOffset.center,
child: new RenderSizedBox(new Size(100.0, 100.0))
)
);
root.attach(new PipelineOwner());
child.scheduleInitialLayout();
root.layout();
child.markNeedsLayout();
root.layout();
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment