Skip to content

Commit 71c9266

Browse files
author
vakrilov
committed
WeakEvents refactoring and used in ListView
1 parent 0e8ea03 commit 71c9266

File tree

10 files changed

+387
-167
lines changed

10 files changed

+387
-167
lines changed

CrossPlatformModules.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<IISExpressAnonymousAuthentication />
1414
<IISExpressWindowsAuthentication />
1515
<IISExpressUseClassicPipelineMode />
16+
<UseGlobalApplicationHostFile />
1617
</PropertyGroup>
1718
<PropertyGroup>
1819
<Configuration Condition=" '$(Configuration)' == '' ">Cross</Configuration>
@@ -186,6 +187,7 @@
186187
<DependentUpon>main-page.xml</DependentUpon>
187188
</TypeScriptCompile>
188189
<TypeScriptCompile Include="apps\editable-text-demo\model.ts" />
190+
<TypeScriptCompile Include="apps\tests\weak-event-listener-tests.ts" />
189191
<TypeScriptCompile Include="apps\ui-tests-app\pages\i61.ts" />
190192
<TypeScriptCompile Include="apps\ui-tests-app\pages\i73.ts" />
191193
<TypeScriptCompile Include="apps\ui-tests-app\pages\gestures.ts" />
@@ -1575,7 +1577,7 @@
15751577
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
15761578
</WebProjectProperties>
15771579
</FlavorProperties>
1578-
<UserProperties ui_2scroll-view_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2editable-text-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2absolute-layout-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2gallery-app_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2content-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2web-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2linear-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2absolute-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2dock-layout_2package_1json__JSONSchema="" ui_2layouts_2grid-layout_2package_1json__JSONSchema="" ui_2layouts_2wrap-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" />
1580+
<UserProperties ui_2layouts_2wrap-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2grid-layout_2package_1json__JSONSchema="" ui_2layouts_2dock-layout_2package_1json__JSONSchema="" ui_2layouts_2absolute-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2linear-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2web-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2content-view_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2gallery-app_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2absolute-layout-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2editable-text-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2scroll-view_2package_1json__JSONSchema="http://json.schemastore.org/package" />
15791581
</VisualStudio>
15801582
</ProjectExtensions>
15811583
</Project>

apps/tests/testRunner.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ allTests["LIST-PICKER"] = require("./ui/list-picker/list-picker-tests");
6868
allTests["DATE-PICKER"] = require("./ui/date-picker/date-picker-tests");
6969
allTests["TIME-PICKER"] = require("./ui/time-picker/time-picker-tests");
7070
allTests["WEB-VIEW"] = require("./ui/web-view/web-view-tests");
71+
allTests["WEAK-EVENTS"] = require("./weak-event-listener-tests");
72+
7173
if (!isRunningOnEmulator()) {
7274
allTests["LOCATION"] = require("./location-tests");
7375
}

apps/tests/ui/helper.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import TKUnit = require("../TKUnit");
77
import utils = require("utils/utils");
88
import types = require("utils/types");
99
import styling = require("ui/styling");
10+
import platform = require("platform");
1011

1112
var DELTA = 0.1;
1213

@@ -174,11 +175,7 @@ export function buildUIWithWeakRefAndInteract<T extends view.View>(createFunc: (
174175
}
175176

176177
sp.removeChild(weakRef.get());
177-
if (newPage.ios) {
178-
// Could cause GC on the next call.
179-
new ArrayBuffer(4 * 1024 * 1024);
180-
}
181-
utils.GC();
178+
forceGC();
182179

183180
TKUnit.assert(!weakRef.get(), weakRef.get() + " leaked!");
184181
testFinished = true;
@@ -222,4 +219,12 @@ export function assertAreClose(actual: number, expected: number, message: string
222219
var delta = Math.floor(density) !== density ? 1.1 : DELTA;
223220

224221
TKUnit.assertAreClose(actual, expected, delta, message);
222+
}
223+
224+
export function forceGC() {
225+
if (platform.device.os === platform.platformNames.ios) {
226+
// Could cause GC on the next call.
227+
new ArrayBuffer(4 * 1024 * 1024);
228+
}
229+
utils.GC();
225230
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import TKUnit = require("./TKUnit");
2+
import types = require("utils/types");
3+
import observable = require("data/observable");
4+
import weakEvents = require("ui/core/weak-event-listener");
5+
import helper = require("./ui/helper");
6+
7+
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_source() {
8+
TKUnit.assertThrows(() => {
9+
weakEvents.WeakEventListener.addWeakEventListener({
10+
source: undefined,
11+
target: {},
12+
handler: emptyHandler,
13+
eventName: observable.Observable.propertyChangeEvent
14+
});
15+
});
16+
}
17+
18+
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_target() {
19+
TKUnit.assertThrows(() => {
20+
weakEvents.WeakEventListener.addWeakEventListener({
21+
source: new observable.Observable(),
22+
target: undefined,
23+
handler: emptyHandler,
24+
eventName: observable.Observable.propertyChangeEvent
25+
});
26+
});
27+
}
28+
29+
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_handler() {
30+
TKUnit.assertThrows(() => {
31+
weakEvents.WeakEventListener.addWeakEventListener({
32+
source: new observable.Observable(),
33+
target: {},
34+
handler: undefined,
35+
eventName: observable.Observable.propertyChangeEvent
36+
});
37+
});
38+
}
39+
40+
export function test_addWeakEventListener_throwsWhenCalledwitnInvalid_name() {
41+
TKUnit.assertThrows(() => {
42+
weakEvents.WeakEventListener.addWeakEventListener({
43+
source: new observable.Observable(),
44+
target: {},
45+
handler: emptyHandler,
46+
eventName: undefined
47+
});
48+
});
49+
}
50+
51+
export function test_addWeakEventListener_listensForEvent() {
52+
var source = new observable.Observable();
53+
var target = new Object;
54+
var callbackCalled = false;
55+
var handler = function (args: observable.EventData) {
56+
callbackCalled = true;
57+
}
58+
59+
weakEvents.WeakEventListener.addWeakEventListener({
60+
source: source,
61+
target: target,
62+
handler: handler,
63+
eventName: observable.Observable.propertyChangeEvent
64+
})
65+
66+
source.set("testProp", "some value");
67+
68+
TKUnit.assert(callbackCalled, "Handler not called.");
69+
}
70+
71+
export function test_removeWeakEventListener_StopsListeningForEvet() {
72+
var source = new observable.Observable();
73+
var target = new Object;
74+
var callbackCalled = false;
75+
var handler = function (args: observable.EventData) {
76+
callbackCalled = true;
77+
}
78+
79+
var listenerID = weakEvents.WeakEventListener.addWeakEventListener({
80+
source: source,
81+
target: target,
82+
handler: handler,
83+
eventName: observable.Observable.propertyChangeEvent
84+
})
85+
86+
weakEvents.WeakEventListener.removeWeakEventListener(listenerID);
87+
88+
source.set("testProp", "some value");
89+
TKUnit.assert(!callbackCalled, "Handler should not be called.");
90+
}
91+
92+
export function test_handlerIsCalled_WithTargetAsThis() {
93+
var source = new observable.Observable();
94+
var target = new Object;
95+
var callbackCalled = false;
96+
var handler = function (args: observable.EventData) {
97+
TKUnit.assertEqual(this, target, "this should be the target");
98+
callbackCalled = true;
99+
}
100+
101+
weakEvents.WeakEventListener.addWeakEventListener({
102+
source: source,
103+
target: target,
104+
handler: handler,
105+
eventName: observable.Observable.propertyChangeEvent
106+
})
107+
108+
source.set("testProp", "some value");
109+
TKUnit.assert(callbackCalled, "Handler not called.");
110+
}
111+
112+
export function test_listnerDoesNotRetainTarget() {
113+
var source = new observable.Observable();
114+
var target = new Object;
115+
var callbackCalled = false;
116+
var handler = function (args: observable.EventData) {
117+
TKUnit.assertEqual(this, target, "this should be the target");
118+
callbackCalled = true;
119+
}
120+
121+
weakEvents.WeakEventListener.addWeakEventListener({
122+
source: source,
123+
target: target,
124+
handler: handler,
125+
eventName: observable.Observable.propertyChangeEvent
126+
})
127+
128+
var targetRef = new WeakRef(target);
129+
target = undefined;
130+
helper.forceGC();
131+
132+
TKUnit.assert(!targetRef.get(), "Target should be released after GC");
133+
}
134+
135+
export function test_listnerDoesNotRetainSource() {
136+
var source = new observable.Observable();
137+
var target = new Object();
138+
var callbackCalled = false;
139+
var handler = function (args: observable.EventData) {
140+
TKUnit.assertEqual(this, target, "this should be the target");
141+
callbackCalled = true;
142+
}
143+
144+
weakEvents.WeakEventListener.addWeakEventListener({
145+
source: source,
146+
target: target,
147+
handler: handler,
148+
eventName: observable.Observable.propertyChangeEvent
149+
})
150+
151+
var sourceRef = new WeakRef(source);
152+
source = undefined;
153+
helper.forceGC();
154+
155+
TKUnit.assert(!sourceRef.get(), "Source should be released after GC");
156+
}
157+
158+
export function test_listnerIsCleared_WhenTargetIsDead() {
159+
var source = new observable.Observable();
160+
161+
var listenerID = addListenerWithSource(source);
162+
helper.forceGC();
163+
164+
for (var i = 0; i < weakEvents.WeakEventListener.cleanDeadReferencesCountTrigger; i++) {
165+
addListenerWithSource(source);
166+
}
167+
168+
TKUnit.assert(types.isUndefined(weakEvents.WeakEventListener._weakEventListeners[listenerID]), "The first listener should be dead by now");
169+
}
170+
171+
export function test_listnerIsCleared_WhenSourceIsDead() {
172+
var target = {};
173+
174+
var listenerID = addListenerWithTarget(target);
175+
helper.forceGC();
176+
177+
for (var i = 0; i < weakEvents.WeakEventListener.cleanDeadReferencesCountTrigger; i++) {
178+
addListenerWithTarget(target);
179+
}
180+
181+
TKUnit.assert(types.isUndefined(weakEvents.WeakEventListener._weakEventListeners[listenerID]), "The first listener should be dead by now");
182+
}
183+
184+
function addListenerWithSource(source: observable.Observable): number {
185+
return weakEvents.WeakEventListener.addWeakEventListener({
186+
source: source,
187+
target: {},
188+
handler: emptyHandler,
189+
eventName: observable.Observable.propertyChangeEvent
190+
})
191+
}
192+
193+
function addListenerWithTarget(target: any): number {
194+
return weakEvents.WeakEventListener.addWeakEventListener({
195+
source: new observable.Observable(),
196+
target: target,
197+
handler: emptyHandler,
198+
eventName: observable.Observable.propertyChangeEvent
199+
})
200+
}
201+
202+
function emptyHandler(data: observable.EventData) {
203+
// Do nothing.
204+
}

ui/button/button-common.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ function onFormattedTextPropertyChanged(data: dependencyObservable.PropertyChang
3333
(<proxy.PropertyMetadata>formattedTextProperty.metadata).onSetNativeValue = onFormattedTextPropertyChanged;
3434

3535
export class Button extends view.View implements definition.Button {
36-
3736
public static tapEvent = "tap";
38-
3937
public static textProperty = textProperty;
4038
public static formattedTextProperty = formattedTextProperty;
4139

40+
private _formattedTextWeakListenerId: number;
41+
4242
public _onBindingContextChanged(oldValue: any, newValue: any) {
4343
super._onBindingContextChanged(oldValue, newValue);
4444
if (this.formattedText) {
@@ -60,20 +60,17 @@ export class Button extends view.View implements definition.Button {
6060

6161
set formattedText(value: formattedString.FormattedString) {
6262
if (this.formattedText !== value) {
63-
var weakEventOptions: weakEventListener.WeakEventListenerOptions = {
64-
targetWeakRef: new WeakRef(this),
65-
eventName: observable.Observable.propertyChangeEvent,
66-
sourceWeakRef: new WeakRef(value),
67-
handler: this.onFormattedTextChanged,
68-
handlerContext: this,
69-
key: "formattedText"
70-
};
7163
if (this.formattedText) {
72-
weakEventListener.WeakEventListener.removeWeakEventListener(weakEventOptions);
64+
weakEventListener.WeakEventListener.removeWeakEventListener(this._formattedTextWeakListenerId);
7365
}
7466
this._setValue(Button.formattedTextProperty, value);
7567
if (value) {
76-
weakEventListener.WeakEventListener.addWeakEventListener(weakEventOptions);
68+
this._formattedTextWeakListenerId = weakEventListener.WeakEventListener.addWeakEventListener({
69+
target: this,
70+
source: value,
71+
eventName: observable.Observable.propertyChangeEvent,
72+
handler: this.onFormattedTextChanged
73+
});
7774
}
7875
}
7976
}

0 commit comments

Comments
 (0)