Skip to content

Commit

Permalink
preserve Window callback zones (flutter#3817)
Browse files Browse the repository at this point in the history
* preserve Window callback zones

Run Window callbacks in the zone they are registered in. This is consistent with how other native API work, such as `scheduleMicrotask`, `Timer`, and `dart:io`. This also enables the developers to use the `Zone` API to capture and log unhandled Dart errors.

* refactor wrapping

* new line

* fewer if checks; group getters/setters/fields

* inline _invokeOnPointerDataPacket
  • Loading branch information
yjbanov authored Jun 26, 2017
1 parent e07861a commit 926c9a5
Show file tree
Hide file tree
Showing 4 changed files with 388 additions and 25 deletions.
95 changes: 79 additions & 16 deletions lib/ui/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,50 +24,113 @@ void _updateWindowMetrics(double devicePixelRatio,
.._physicalSize = new Size(width, height)
.._padding = new WindowPadding._(
top: top, right: right, bottom: bottom, left: left);
if (window.onMetricsChanged != null)
window.onMetricsChanged();
_invoke(window.onMetricsChanged, window._onMetricsChangedZone);
}

void _updateLocale(String languageCode, String countryCode) {
window._locale = new Locale(languageCode, countryCode);
if (window.onLocaleChanged != null)
window.onLocaleChanged();
_invoke(window.onLocaleChanged, window._onLocaleChangedZone);
}

void _updateSemanticsEnabled(bool enabled) {
window._semanticsEnabled = enabled;
if (window.onSemanticsEnabledChanged != null)
window.onSemanticsEnabledChanged();
_invoke(window.onSemanticsEnabledChanged, window._onSemanticsEnabledChangedZone);
}

void _dispatchPlatformMessage(String name, ByteData data, int responseId) {
if (window.onPlatformMessage != null) {
window.onPlatformMessage(name, data, (ByteData responseData) {
window._respondToPlatformMessage(responseId, responseData);
});
_invoke3<String, ByteData, PlatformMessageResponseCallback>(
window.onPlatformMessage,
window._onPlatformMessageZone,
name,
data,
(ByteData responseData) {
window._respondToPlatformMessage(responseId, responseData);
},
);
} else {
window._respondToPlatformMessage(responseId, null);
}
}

void _dispatchPointerDataPacket(ByteData packet) {
if (window.onPointerDataPacket != null)
window.onPointerDataPacket(_unpackPointerDataPacket(packet));
_invoke1<PointerDataPacket>(window.onPointerDataPacket, window._onPointerDataPacketZone, _unpackPointerDataPacket(packet));
}

void _dispatchSemanticsAction(int id, int action) {
if (window.onSemanticsAction != null)
window.onSemanticsAction(id, SemanticsAction.values[action]);
_invoke2<int, SemanticsAction>(
window.onSemanticsAction,
window._onSemanticsActionZone,
id,
SemanticsAction.values[action],
);
}

void _beginFrame(int microseconds) {
if (window.onBeginFrame != null)
window.onBeginFrame(new Duration(microseconds: microseconds));
_invoke1<Duration>(window.onBeginFrame, window._onBeginFrameZone, new Duration(microseconds: microseconds));
}

void _drawFrame() {
if (window.onDrawFrame != null)
window.onDrawFrame();
_invoke(window.onDrawFrame, window._onDrawFrameZone);
}

/// Invokes [callback] inside the given [zone].
void _invoke(void callback(), Zone zone) {
if (callback == null)
return;

assert(zone != null);

if (identical(zone, Zone.current)) {
callback();
} else {
zone.runGuarded(callback);
}
}

/// Invokes [callback] inside the given [zone] passing it [arg].
void _invoke1<A>(void callback(A a), Zone zone, A arg) {
if (callback == null)
return;

assert(zone != null);

if (identical(zone, Zone.current)) {
callback(arg);
} else {
zone.runUnaryGuarded<Null, A>(callback, arg);
}
}

/// Invokes [callback] inside the given [zone] passing it [arg1] and [arg2].
void _invoke2<A1, A2>(void callback(A1 a1, A2 a2), Zone zone, A1 arg1, A2 arg2) {
if (callback == null)
return;

assert(zone != null);

if (identical(zone, Zone.current)) {
callback(arg1, arg2);
} else {
zone.runBinaryGuarded<Null, A1, A2>(callback, arg1, arg2);
}
}

/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2] and [arg3].
void _invoke3<A1, A2, A3>(void callback(A1 a1, A2 a2, A3 a3), Zone zone, A1 arg1, A2 arg2, A3 arg3) {
if (callback == null)
return;

assert(zone != null);

if (identical(zone, Zone.current)) {
callback(arg1, arg2, arg3);
} else {
zone.runGuarded(() {
callback(arg1, arg2, arg3);
});
}
}

// If this value changes, update the encoding code in the following files:
Expand Down
107 changes: 98 additions & 9 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,16 @@ class Window {
/// [physicalSize], or [padding] values change, for example when the device is
/// rotated or when the application is resized (e.g. when showing applications
/// side-by-side on Android).
VoidCallback onMetricsChanged;
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
VoidCallback get onMetricsChanged => _onMetricsChanged;
VoidCallback _onMetricsChanged;
Zone _onMetricsChangedZone;
set onMetricsChanged(VoidCallback callback) {
_onMetricsChanged = callback;
_onMetricsChangedZone = Zone.current;
}

/// The system-reported locale.
///
Expand All @@ -228,11 +237,20 @@ class Window {

/// A callback that is invoked whenever [locale] changes value.
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
///
/// See also:
///
/// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
/// observe when this callback is invoked.
VoidCallback onLocaleChanged;
VoidCallback get onLocaleChanged => _onLocaleChanged;
VoidCallback _onLocaleChanged;
Zone _onLocaleChangedZone;
set onLocaleChanged(VoidCallback callback) {
_onLocaleChanged = callback;
_onLocaleChangedZone = Zone.current;
}

/// A callback that is invoked to notify the application that it is an
/// appropriate time to provide a scene using the [SceneBuilder] API and the
Expand All @@ -244,34 +262,61 @@ class Window {
/// after draining any microtasks (e.g. completions of any [Future]s) queued
/// by the [onBeginFrame] handler.
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
///
/// See also:
///
/// * [SchedulerBinding], the Flutter framework class which manages the
/// scheduling of frames.
/// * [RendererBinding], the Flutter framework class which manages layout and
/// painting.
FrameCallback onBeginFrame;
FrameCallback get onBeginFrame => _onBeginFrame;
FrameCallback _onBeginFrame;
Zone _onBeginFrameZone;
set onBeginFrame(FrameCallback callback) {
_onBeginFrame = callback;
_onBeginFrameZone = Zone.current;
}

/// A callback that is invoked for each frame after [onBeginFrame] has
/// completed and after the microtask queue has been drained. This can be
/// used to implement a second phase of frame rendering that happens
/// after any deferred work queued by the [onBeginFrame] phase.
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
///
/// See also:
///
/// * [SchedulerBinding], the Flutter framework class which manages the
/// scheduling of frames.
/// * [RendererBinding], the Flutter framework class which manages layout and
/// painting.
VoidCallback onDrawFrame;
VoidCallback get onDrawFrame => _onDrawFrame;
VoidCallback _onDrawFrame;
Zone _onDrawFrameZone;
set onDrawFrame(VoidCallback callback) {
_onDrawFrame = callback;
_onDrawFrameZone = Zone.current;
}

/// A callback that is invoked when pointer data is available.
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
///
/// See also:
///
/// * [GestureBinding], the Flutter framework class which manages pointer
/// events.
PointerDataPacketCallback onPointerDataPacket;
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
PointerDataPacketCallback _onPointerDataPacket;
Zone _onPointerDataPacketZone;
set onPointerDataPacket(PointerDataPacketCallback callback) {
_onPointerDataPacket = callback;
_onPointerDataPacketZone = Zone.current;
}

/// The route or path that the operating system requested when the application
/// was launched.
Expand Down Expand Up @@ -322,14 +367,32 @@ class Window {
bool _semanticsEnabled = false;

/// A callback that is invoked when the value of [semanticsEnabled] changes.
VoidCallback onSemanticsEnabledChanged;
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
VoidCallback get onSemanticsEnabledChanged => _onSemanticsEnabledChanged;
VoidCallback _onSemanticsEnabledChanged;
Zone _onSemanticsEnabledChangedZone;
set onSemanticsEnabledChanged(VoidCallback callback) {
_onSemanticsEnabledChanged = callback;
_onSemanticsEnabledChangedZone = Zone.current;
}

/// A callback that is invoked whenever the user requests an action to be
/// performed.
///
/// This callback is used when the user expresses the action they wish to
/// perform based on the semantics supplied by [updateSemantics].
SemanticsActionCallback onSemanticsAction;
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
SemanticsActionCallback get onSemanticsAction => _onSemanticsAction;
SemanticsActionCallback _onSemanticsAction;
Zone _onSemanticsActionZone;
set onSemanticsAction(SemanticsActionCallback callback) {
_onSemanticsAction = callback;
_onSemanticsActionZone = Zone.current;
}

/// Change the retained semantics data about this window.
///
Expand All @@ -346,10 +409,13 @@ class Window {
/// `data` parameter contains the message payload and is typically UTF-8
/// encoded JSON but can be arbitrary data. If the plugin replies to the
/// message, `callback` will be called with the response.
///
/// The framework invokes [callback] in the same zone in which this method
/// was called.
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
_sendPlatformMessage(name, callback, data);
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
}
void _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
Expand All @@ -365,11 +431,34 @@ class Window {
/// Message handlers must call the function given in the `callback` parameter.
/// If the handler does not need to respond, the handler should pass `null` to
/// the callback.
PlatformMessageCallback onPlatformMessage;
///
/// The framework invokes this callback in the same zone in which the
/// callback was set.
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
PlatformMessageCallback _onPlatformMessage;
Zone _onPlatformMessageZone;
set onPlatformMessage(PlatformMessageCallback callback) {
_onPlatformMessage = callback;
_onPlatformMessageZone = Zone.current;
}

/// Called by [_dispatchPlatformMessage].
void _respondToPlatformMessage(int responseId, ByteData data)
native "Window_respondToPlatformMessage";

/// Wraps the given [callback] in another callback that ensures that the
/// original callback is called in the zone it was registered in.
static PlatformMessageResponseCallback _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback callback) {
if (callback == null)
return null;

// Store the zone in which the callback is being registered.
final Zone registrationZone = Zone.current;

return (ByteData data) {
registrationZone.runUnaryGuarded(callback, data);
};
}
}

/// The [Window] singleton. This object exposes the size of the display, the
Expand Down
Loading

0 comments on commit 926c9a5

Please sign in to comment.