ControlTests.cs 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Linq;
  5. using System.Reactive.Disposables;
  6. using Avalonia.Collections;
  7. using Avalonia.Controls;
  8. using Avalonia.Controls.Presenters;
  9. using Avalonia.Controls.Primitives;
  10. using Avalonia.Controls.Shapes;
  11. using Avalonia.Controls.Templates;
  12. using Avalonia.Data;
  13. using Avalonia.Diagnostics;
  14. using Avalonia.Input;
  15. using Avalonia.Media;
  16. using Avalonia.Platform;
  17. using Avalonia.Rendering;
  18. using Avalonia.Styling;
  19. using Avalonia.Threading;
  20. using Avalonia.UnitTests;
  21. using Avalonia.VisualTree;
  22. using JetBrains.dotMemoryUnit;
  23. using Moq;
  24. using Xunit;
  25. using Xunit.Abstractions;
  26. namespace Avalonia.LeakTests
  27. {
  28. [DotMemoryUnit(FailIfRunWithoutSupport = false)]
  29. public class ControlTests
  30. {
  31. // Need to have the collection as field, so GC will not free it
  32. private readonly ObservableCollection<string> _observableCollection = new();
  33. public ControlTests(ITestOutputHelper atr)
  34. {
  35. DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
  36. }
  37. [Fact]
  38. public void DataGrid_Is_Freed()
  39. {
  40. using (Start())
  41. {
  42. // When attached to INotifyCollectionChanged, DataGrid will subscribe to it's events, potentially causing leak
  43. Func<Window> run = () =>
  44. {
  45. var window = new Window
  46. {
  47. Content = new DataGrid
  48. {
  49. Items = _observableCollection
  50. }
  51. };
  52. window.Show();
  53. // Do a layout and make sure that DataGrid gets added to visual tree.
  54. window.LayoutManager.ExecuteInitialLayoutPass();
  55. Assert.IsType<DataGrid>(window.Presenter.Child);
  56. // Clear the content and ensure the DataGrid is removed.
  57. window.Content = null;
  58. window.LayoutManager.ExecuteLayoutPass();
  59. Assert.Null(window.Presenter.Child);
  60. return window;
  61. };
  62. var result = run();
  63. // Process all Loaded events to free control reference(s)
  64. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  65. dotMemory.Check(memory =>
  66. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<DataGrid>()).ObjectsCount));
  67. }
  68. }
  69. [Fact]
  70. public void Canvas_Is_Freed()
  71. {
  72. using (Start())
  73. {
  74. Func<Window> run = () =>
  75. {
  76. var window = new Window
  77. {
  78. Content = new Canvas()
  79. };
  80. window.Show();
  81. // Do a layout and make sure that Canvas gets added to visual tree.
  82. window.LayoutManager.ExecuteInitialLayoutPass();
  83. Assert.IsType<Canvas>(window.Presenter.Child);
  84. // Clear the content and ensure the Canvas is removed.
  85. window.Content = null;
  86. window.LayoutManager.ExecuteLayoutPass();
  87. Assert.Null(window.Presenter.Child);
  88. return window;
  89. };
  90. var result = run();
  91. // Process all Loaded events to free control reference(s)
  92. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  93. dotMemory.Check(memory =>
  94. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  95. }
  96. }
  97. [Fact]
  98. public void Named_Canvas_Is_Freed()
  99. {
  100. using (Start())
  101. {
  102. Func<Window> run = () =>
  103. {
  104. var scope = new NameScope();
  105. var window = new Window
  106. {
  107. Content = new Canvas
  108. {
  109. Name = "foo"
  110. }.RegisterInNameScope(scope)
  111. };
  112. NameScope.SetNameScope(window, scope);
  113. window.Show();
  114. // Do a layout and make sure that Canvas gets added to visual tree.
  115. window.LayoutManager.ExecuteInitialLayoutPass();
  116. Assert.IsType<Canvas>(window.Find<Canvas>("foo"));
  117. Assert.IsType<Canvas>(window.Presenter.Child);
  118. // Clear the content and ensure the Canvas is removed.
  119. window.Content = null;
  120. NameScope.SetNameScope(window, null);
  121. window.LayoutManager.ExecuteLayoutPass();
  122. Assert.Null(window.Presenter.Child);
  123. return window;
  124. };
  125. var result = run();
  126. // Process all Loaded events to free control reference(s)
  127. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  128. dotMemory.Check(memory =>
  129. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  130. }
  131. }
  132. [Fact]
  133. public void ScrollViewer_With_Content_Is_Freed()
  134. {
  135. using (Start())
  136. {
  137. Func<Window> run = () =>
  138. {
  139. var window = new Window
  140. {
  141. Content = new ScrollViewer
  142. {
  143. Content = new Canvas()
  144. }
  145. };
  146. window.Show();
  147. // Do a layout and make sure that ScrollViewer gets added to visual tree and its
  148. // template applied.
  149. window.LayoutManager.ExecuteInitialLayoutPass();
  150. Assert.IsType<ScrollViewer>(window.Presenter.Child);
  151. Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child);
  152. // Clear the content and ensure the ScrollViewer is removed.
  153. window.Content = null;
  154. window.LayoutManager.ExecuteLayoutPass();
  155. Assert.Null(window.Presenter.Child);
  156. return window;
  157. };
  158. var result = run();
  159. // Process all Loaded events to free control reference(s)
  160. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  161. dotMemory.Check(memory =>
  162. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  163. dotMemory.Check(memory =>
  164. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  165. }
  166. }
  167. [Fact]
  168. public void TextBox_Is_Freed()
  169. {
  170. using (Start())
  171. {
  172. Func<Window> run = () =>
  173. {
  174. var window = new Window
  175. {
  176. Content = new TextBox()
  177. };
  178. window.Show();
  179. // Do a layout and make sure that TextBox gets added to visual tree and its
  180. // template applied.
  181. window.LayoutManager.ExecuteInitialLayoutPass();
  182. Assert.IsType<TextBox>(window.Presenter.Child);
  183. Assert.NotEmpty(window.Presenter.Child.GetVisualChildren());
  184. // Clear the content and ensure the TextBox is removed.
  185. window.Content = null;
  186. window.LayoutManager.ExecuteLayoutPass();
  187. Assert.Null(window.Presenter.Child);
  188. return window;
  189. };
  190. var result = run();
  191. // Process all Loaded events to free control reference(s)
  192. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  193. dotMemory.Check(memory =>
  194. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  195. }
  196. }
  197. [Fact]
  198. public void TextBox_With_Xaml_Binding_Is_Freed()
  199. {
  200. using (Start())
  201. {
  202. Func<Window> run = () =>
  203. {
  204. var window = new Window
  205. {
  206. DataContext = new Node { Name = "foo" },
  207. Content = new TextBox()
  208. };
  209. var binding = new Avalonia.Data.Binding
  210. {
  211. Path = "Name"
  212. };
  213. var textBox = (TextBox)window.Content;
  214. textBox.Bind(TextBox.TextProperty, binding);
  215. window.Show();
  216. // Do a layout and make sure that TextBox gets added to visual tree and its
  217. // Text property set.
  218. window.LayoutManager.ExecuteInitialLayoutPass();
  219. Assert.IsType<TextBox>(window.Presenter.Child);
  220. Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text);
  221. // Clear the content and DataContext and ensure the TextBox is removed.
  222. window.Content = null;
  223. window.DataContext = null;
  224. window.LayoutManager.ExecuteLayoutPass();
  225. Assert.Null(window.Presenter.Child);
  226. return window;
  227. };
  228. var result = run();
  229. // Process all Loaded events to free control reference(s)
  230. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  231. dotMemory.Check(memory =>
  232. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  233. dotMemory.Check(memory =>
  234. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Node>()).ObjectsCount));
  235. }
  236. }
  237. [Fact]
  238. public void TextBox_Class_Listeners_Are_Freed()
  239. {
  240. using (Start())
  241. {
  242. TextBox textBox;
  243. var window = new Window
  244. {
  245. Content = textBox = new TextBox()
  246. };
  247. window.Show();
  248. // Do a layout and make sure that TextBox gets added to visual tree and its
  249. // template applied.
  250. window.LayoutManager.ExecuteInitialLayoutPass();
  251. Assert.Same(textBox, window.Presenter.Child);
  252. // Get the border from the TextBox template.
  253. var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border");
  254. // The TextBox should have subscriptions to its Classes collection from the
  255. // default theme.
  256. Assert.NotEqual(0, textBox.Classes.ListenerCount);
  257. // Clear the content and ensure the TextBox is removed.
  258. window.Content = null;
  259. window.LayoutManager.ExecuteLayoutPass();
  260. Assert.Null(window.Presenter.Child);
  261. // Check that the TextBox has no subscriptions to its Classes collection.
  262. Assert.Null(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  263. }
  264. }
  265. [Fact]
  266. public void TreeView_Is_Freed()
  267. {
  268. using (Start())
  269. {
  270. Func<Window> run = () =>
  271. {
  272. var nodes = new[]
  273. {
  274. new Node
  275. {
  276. Children = new[] { new Node() },
  277. }
  278. };
  279. TreeView target;
  280. var window = new Window
  281. {
  282. Content = target = new TreeView
  283. {
  284. DataTemplates =
  285. {
  286. new FuncTreeDataTemplate<Node>(
  287. (x, _) => new TextBlock { Text = x.Name },
  288. x => x.Children)
  289. },
  290. Items = nodes
  291. }
  292. };
  293. window.Show();
  294. // Do a layout and make sure that TreeViewItems get realized.
  295. window.LayoutManager.ExecuteInitialLayoutPass();
  296. Assert.Single(target.GetRealizedContainers());
  297. // Clear the content and ensure the TreeView is removed.
  298. window.Content = null;
  299. window.LayoutManager.ExecuteLayoutPass();
  300. Assert.Null(window.Presenter.Child);
  301. return window;
  302. };
  303. var result = run();
  304. // Process all Loaded events to free control reference(s)
  305. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  306. dotMemory.Check(memory =>
  307. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
  308. }
  309. }
  310. [Fact]
  311. public void Slider_Is_Freed()
  312. {
  313. using (Start())
  314. {
  315. Func<Window> run = () =>
  316. {
  317. var window = new Window
  318. {
  319. Content = new Slider()
  320. };
  321. window.Show();
  322. // Do a layout and make sure that Slider gets added to visual tree.
  323. window.LayoutManager.ExecuteInitialLayoutPass();
  324. Assert.IsType<Slider>(window.Presenter.Child);
  325. // Clear the content and ensure the Slider is removed.
  326. window.Content = null;
  327. window.LayoutManager.ExecuteLayoutPass();
  328. Assert.Null(window.Presenter.Child);
  329. return window;
  330. };
  331. var result = run();
  332. // Process all Loaded events to free control reference(s)
  333. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  334. dotMemory.Check(memory =>
  335. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
  336. }
  337. }
  338. [Fact]
  339. public void TabItem_Is_Freed()
  340. {
  341. using (Start())
  342. {
  343. Func<Window> run = () =>
  344. {
  345. var window = new Window
  346. {
  347. Content = new TabControl
  348. {
  349. Items = new[] { new TabItem() }
  350. }
  351. };
  352. window.Show();
  353. // Do a layout and make sure that TabControl and TabItem gets added to visual tree.
  354. window.LayoutManager.ExecuteInitialLayoutPass();
  355. var tabControl = Assert.IsType<TabControl>(window.Presenter.Child);
  356. Assert.IsType<TabItem>(tabControl.Presenter.Panel.Children[0]);
  357. // Clear the items and ensure the TabItem is removed.
  358. tabControl.Items = null;
  359. window.LayoutManager.ExecuteLayoutPass();
  360. Assert.Empty(tabControl.Presenter.Panel.Children);
  361. return window;
  362. };
  363. var result = run();
  364. // Process all Loaded events to free control reference(s)
  365. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  366. dotMemory.Check(memory =>
  367. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TabItem>()).ObjectsCount));
  368. }
  369. }
  370. [Fact]
  371. public void RendererIsDisposed()
  372. {
  373. using (Start())
  374. {
  375. var renderer = new Mock<IRenderer>();
  376. renderer.Setup(x => x.Dispose());
  377. var impl = new Mock<IWindowImpl>();
  378. impl.SetupGet(x => x.RenderScaling).Returns(1);
  379. impl.SetupProperty(x => x.Closed);
  380. impl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
  381. impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
  382. AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>()
  383. .ToConstant(new MockWindowingPlatform(() => impl.Object));
  384. var window = new Window()
  385. {
  386. Content = new Button()
  387. };
  388. window.Show();
  389. window.Close();
  390. renderer.Verify(r => r.Dispose());
  391. }
  392. }
  393. [Fact]
  394. public void Control_With_Style_RenderTransform_Is_Freed()
  395. {
  396. // # Issue #3545
  397. using (Start())
  398. {
  399. Func<Window> run = () =>
  400. {
  401. var window = new Window
  402. {
  403. Styles =
  404. {
  405. new Style(x => x.OfType<Canvas>())
  406. {
  407. Setters =
  408. {
  409. new Setter
  410. {
  411. Property = Visual.RenderTransformProperty,
  412. Value = new RotateTransform(45),
  413. }
  414. }
  415. }
  416. },
  417. Content = new Canvas()
  418. };
  419. window.Show();
  420. // Do a layout and make sure that Canvas gets added to visual tree with
  421. // its render transform.
  422. window.LayoutManager.ExecuteInitialLayoutPass();
  423. var canvas = Assert.IsType<Canvas>(window.Presenter.Child);
  424. Assert.IsType<RotateTransform>(canvas.RenderTransform);
  425. // Clear the content and ensure the Canvas is removed.
  426. window.Content = null;
  427. window.LayoutManager.ExecuteLayoutPass();
  428. Assert.Null(window.Presenter.Child);
  429. return window;
  430. };
  431. var result = run();
  432. // Process all Loaded events to free control reference(s)
  433. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  434. dotMemory.Check(memory =>
  435. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  436. }
  437. }
  438. [Fact]
  439. public void Attached_ContextMenu_Is_Freed()
  440. {
  441. using (Start())
  442. {
  443. void AttachShowAndDetachContextMenu(Control control)
  444. {
  445. var contextMenu = new ContextMenu
  446. {
  447. Items = new[]
  448. {
  449. new MenuItem { Header = "Foo" },
  450. new MenuItem { Header = "Foo" },
  451. }
  452. };
  453. control.ContextMenu = contextMenu;
  454. contextMenu.Open(control);
  455. contextMenu.Close();
  456. control.ContextMenu = null;
  457. }
  458. var window = new Window();
  459. window.Show();
  460. Assert.Same(window, FocusManager.Instance.Current);
  461. // Context menu in resources means the baseline may not be 0.
  462. var initialMenuCount = 0;
  463. var initialMenuItemCount = 0;
  464. dotMemory.Check(memory =>
  465. {
  466. initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
  467. initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
  468. });
  469. AttachShowAndDetachContextMenu(window);
  470. // Process all Loaded events to free control reference(s)
  471. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  472. Mock.Get(window.PlatformImpl).Invocations.Clear();
  473. dotMemory.Check(memory =>
  474. Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
  475. dotMemory.Check(memory =>
  476. Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
  477. }
  478. }
  479. [Fact]
  480. public void Standalone_ContextMenu_Is_Freed()
  481. {
  482. using (Start())
  483. {
  484. void BuildAndShowContextMenu(Control control)
  485. {
  486. var contextMenu = new ContextMenu
  487. {
  488. Items = new[]
  489. {
  490. new MenuItem { Header = "Foo" },
  491. new MenuItem { Header = "Foo" },
  492. }
  493. };
  494. contextMenu.Open(control);
  495. contextMenu.Close();
  496. }
  497. var window = new Window();
  498. window.Show();
  499. Assert.Same(window, FocusManager.Instance.Current);
  500. // Context menu in resources means the baseline may not be 0.
  501. var initialMenuCount = 0;
  502. var initialMenuItemCount = 0;
  503. dotMemory.Check(memory =>
  504. {
  505. initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
  506. initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
  507. });
  508. BuildAndShowContextMenu(window);
  509. BuildAndShowContextMenu(window);
  510. // Process all Loaded events to free control reference(s)
  511. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  512. Mock.Get(window.PlatformImpl).Invocations.Clear();
  513. dotMemory.Check(memory =>
  514. Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
  515. dotMemory.Check(memory =>
  516. Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
  517. }
  518. }
  519. [Fact]
  520. public void Path_Is_Freed()
  521. {
  522. using (Start())
  523. {
  524. var geometry = new EllipseGeometry { Rect = new Rect(0, 0, 10, 10) };
  525. Func<Window> run = () =>
  526. {
  527. var window = new Window
  528. {
  529. Content = new Path
  530. {
  531. Data = geometry
  532. }
  533. };
  534. window.Show();
  535. window.LayoutManager.ExecuteInitialLayoutPass();
  536. Assert.IsType<Path>(window.Presenter.Child);
  537. window.Content = null;
  538. window.LayoutManager.ExecuteLayoutPass();
  539. Assert.Null(window.Presenter.Child);
  540. return window;
  541. };
  542. var result = run();
  543. // Process all Loaded events to free control reference(s)
  544. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  545. dotMemory.Check(memory =>
  546. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Path>()).ObjectsCount));
  547. // We are keeping geometry alive to simulate a resource that outlives the control.
  548. GC.KeepAlive(geometry);
  549. }
  550. }
  551. [Fact]
  552. public void ItemsRepeater_Is_Freed()
  553. {
  554. using (Start())
  555. {
  556. Func<Window> run = () =>
  557. {
  558. var window = new Window
  559. {
  560. Content = new ItemsRepeater(),
  561. };
  562. window.Show();
  563. window.LayoutManager.ExecuteInitialLayoutPass();
  564. Assert.IsType<ItemsRepeater>(window.Presenter.Child);
  565. window.Content = null;
  566. window.LayoutManager.ExecuteLayoutPass();
  567. Assert.Null(window.Presenter.Child);
  568. return window;
  569. };
  570. var result = run();
  571. // Process all Loaded events to free control reference(s)
  572. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  573. dotMemory.Check(memory =>
  574. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ItemsRepeater>()).ObjectsCount));
  575. }
  576. }
  577. [Fact]
  578. public void ElementName_Binding_In_DataTemplate_Is_Freed()
  579. {
  580. using (Start())
  581. {
  582. var items = new ObservableCollection<int>(Enumerable.Range(0, 10));
  583. NameScope ns;
  584. TextBox tb;
  585. ListBox lb;
  586. var window = new Window
  587. {
  588. [NameScope.NameScopeProperty] = ns = new NameScope(),
  589. Width = 100,
  590. Height = 100,
  591. Content = new StackPanel
  592. {
  593. Children =
  594. {
  595. (tb = new TextBox
  596. {
  597. Name = "tb",
  598. Text = "foo",
  599. }),
  600. (lb = new ListBox
  601. {
  602. Items = items,
  603. ItemTemplate = new FuncDataTemplate<int>((_, _) =>
  604. new Canvas
  605. {
  606. Width = 10,
  607. Height = 10,
  608. [!Control.TagProperty] = new Binding
  609. {
  610. ElementName = "tb",
  611. Path = "Text",
  612. NameScope = new WeakReference<INameScope>(ns),
  613. }
  614. })
  615. }),
  616. }
  617. }
  618. };
  619. tb.RegisterInNameScope(ns);
  620. window.Show();
  621. window.LayoutManager.ExecuteInitialLayoutPass();
  622. void AssertInitialItemState()
  623. {
  624. var item0 = (ListBoxItem)lb.GetRealizedContainers().First();
  625. var canvas0 = (Canvas)item0.Presenter.Child;
  626. Assert.Equal("foo", canvas0.Tag);
  627. }
  628. Assert.Equal(10, lb.GetRealizedContainers().Count());
  629. AssertInitialItemState();
  630. items.Clear();
  631. window.LayoutManager.ExecuteLayoutPass();
  632. Assert.Empty(lb.GetRealizedContainers());
  633. // Process all Loaded events to free control reference(s)
  634. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  635. dotMemory.Check(memory =>
  636. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  637. }
  638. }
  639. [Fact]
  640. public void HotKeyManager_Should_Release_Reference_When_Control_Detached()
  641. {
  642. using (Start())
  643. {
  644. Func<Window> run = () =>
  645. {
  646. var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
  647. var tl = new Window
  648. {
  649. Content = new ItemsRepeater(),
  650. };
  651. tl.Show();
  652. var button = new Button();
  653. tl.Content = button;
  654. tl.Template = CreateWindowTemplate();
  655. tl.ApplyTemplate();
  656. tl.Presenter.ApplyTemplate();
  657. HotKeyManager.SetHotKey(button, gesture1);
  658. // Detach the button from the logical tree, so there is no reference to it
  659. tl.Content = null;
  660. tl.ApplyTemplate();
  661. return tl;
  662. };
  663. var result = run();
  664. // Process all Loaded events to free control reference(s)
  665. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  666. dotMemory.Check(memory =>
  667. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Button>()).ObjectsCount));
  668. }
  669. }
  670. [Fact]
  671. public void HotKeyManager_Should_Release_Reference_When_Control_In_Item_Template_Detached()
  672. {
  673. using (Start())
  674. {
  675. Func<Window> run = () =>
  676. {
  677. var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
  678. var tl = new Window { SizeToContent = SizeToContent.WidthAndHeight, IsVisible = true };
  679. var lm = tl.LayoutManager;
  680. tl.Show();
  681. var keyGestures = new AvaloniaList<KeyGesture> { gesture1 };
  682. var listBox = new ListBox
  683. {
  684. Width = 100,
  685. Height = 100,
  686. // Create a button with binding to the KeyGesture in the template and add it to references list
  687. ItemTemplate = new FuncDataTemplate(typeof(KeyGesture), (o, scope) =>
  688. {
  689. var keyGesture = o as KeyGesture;
  690. return new Button
  691. {
  692. DataContext = keyGesture,
  693. [!Button.HotKeyProperty] = new Binding("")
  694. };
  695. })
  696. };
  697. // Add the listbox and render it
  698. tl.Content = listBox;
  699. lm.ExecuteInitialLayoutPass();
  700. listBox.Items = keyGestures;
  701. lm.ExecuteLayoutPass();
  702. // Let the button detach when clearing the source items
  703. keyGestures.Clear();
  704. lm.ExecuteLayoutPass();
  705. // Add it again to double check,and render
  706. keyGestures.Add(gesture1);
  707. lm.ExecuteLayoutPass();
  708. keyGestures.Clear();
  709. lm.ExecuteLayoutPass();
  710. return tl;
  711. };
  712. var result = run();
  713. // Process all Loaded events to free control reference(s)
  714. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  715. dotMemory.Check(memory =>
  716. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Button>()).ObjectsCount));
  717. }
  718. }
  719. [Fact]
  720. public void ToolTip_Is_Freed()
  721. {
  722. using (Start())
  723. {
  724. Func<Window> run = () =>
  725. {
  726. var window = new Window();
  727. var source = new Button
  728. {
  729. Template = new FuncControlTemplate<Button>((parent, _) =>
  730. new Decorator
  731. {
  732. [ToolTip.TipProperty] = new TextBlock
  733. {
  734. [~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty)
  735. }
  736. }),
  737. };
  738. window.Content = source;
  739. window.Show();
  740. var templateChild = (Decorator)source.GetVisualChildren().Single();
  741. ToolTip.SetIsOpen(templateChild, true);
  742. ToolTip.SetIsOpen(templateChild, false);
  743. // Detach the button from the logical tree, so there is no reference to it
  744. window.Content = null;
  745. // Mock keep reference on a Popup via InvocationsCollection. So let's clear it before.
  746. Mock.Get(window.PlatformImpl).Invocations.Clear();
  747. return window;
  748. };
  749. var result = run();
  750. // Process all Loaded events to free control reference(s)
  751. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  752. dotMemory.Check(memory =>
  753. {
  754. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBlock>()).ObjectsCount);
  755. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ToolTip>()).ObjectsCount);
  756. });
  757. }
  758. }
  759. [Fact]
  760. public void Flyout_Is_Freed()
  761. {
  762. using (Start())
  763. {
  764. Func<Window> run = () =>
  765. {
  766. var window = new Window();
  767. var source = new Button
  768. {
  769. Template = new FuncControlTemplate<Button>((parent, _) =>
  770. new Button
  771. {
  772. Flyout = new Flyout
  773. {
  774. Content = new TextBlock
  775. {
  776. [~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty)
  777. }
  778. }
  779. }),
  780. };
  781. window.Content = source;
  782. window.Show();
  783. var templateChild = (Button)source.GetVisualChildren().Single();
  784. templateChild.Flyout!.ShowAt(templateChild);
  785. templateChild.Flyout!.Hide();
  786. // Detach the button from the logical tree, so there is no reference to it
  787. window.Content = null;
  788. // Mock keep reference on a Popup via InvocationsCollection. So let's clear it before.
  789. Mock.Get(window.PlatformImpl).Invocations.Clear();
  790. return window;
  791. };
  792. var result = run();
  793. // Process all Loaded events to free control reference(s)
  794. Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
  795. dotMemory.Check(memory =>
  796. {
  797. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBlock>()).ObjectsCount);
  798. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Flyout>()).ObjectsCount);
  799. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Popup>()).ObjectsCount);
  800. });
  801. }
  802. }
  803. private FuncControlTemplate CreateWindowTemplate()
  804. {
  805. return new FuncControlTemplate<Window>((parent, scope) =>
  806. {
  807. return new ContentPresenter
  808. {
  809. Name = "PART_ContentPresenter",
  810. [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
  811. }.RegisterInNameScope(scope);
  812. });
  813. }
  814. private IDisposable Start()
  815. {
  816. static void Cleanup()
  817. {
  818. // KeyboardDevice holds a reference to the focused item.
  819. KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
  820. // Empty the dispatcher queue.
  821. Dispatcher.UIThread.RunJobs();
  822. }
  823. return new CompositeDisposable
  824. {
  825. Disposable.Create(Cleanup),
  826. UnitTestApplication.Start(TestServices.StyledWindow.With(
  827. focusManager: new FocusManager(),
  828. keyboardDevice: () => new KeyboardDevice(),
  829. inputManager: new InputManager()))
  830. };
  831. }
  832. private class Node
  833. {
  834. public string Name { get; set; }
  835. public IEnumerable<Node> Children { get; set; }
  836. }
  837. private class NullRenderer : IRenderer
  838. {
  839. public bool DrawFps { get; set; }
  840. public bool DrawDirtyRects { get; set; }
  841. #pragma warning disable CS0067
  842. public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
  843. #pragma warning restore CS0067
  844. public void AddDirty(Visual visual)
  845. {
  846. }
  847. public void Dispose()
  848. {
  849. }
  850. public IEnumerable<Visual> HitTest(Point p, Visual root, Func<Visual, bool> filter) => null;
  851. public Visual HitTestFirst(Point p, Visual root, Func<Visual, bool> filter) => null;
  852. public void Paint(Rect rect)
  853. {
  854. }
  855. public void RecalculateChildren(Visual visual)
  856. {
  857. }
  858. public void Resized(Size size)
  859. {
  860. }
  861. public void Start()
  862. {
  863. }
  864. public void Stop()
  865. {
  866. }
  867. }
  868. }
  869. }