[M108 Migration] Support standard build for armv7hl architecture
[platform/framework/web/chromium-efl.git] / ash / shell_unittest.cc
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ash/shell.h"
6
7 #include <memory>
8 #include <queue>
9 #include <vector>
10
11 #include "ash/display/mouse_cursor_event_filter.h"
12 #include "ash/drag_drop/drag_drop_controller.h"
13 #include "ash/drag_drop/drag_drop_controller_test_api.h"
14 #include "ash/keyboard/ui/keyboard_ui_controller.h"
15 #include "ash/keyboard/ui/keyboard_util.h"
16 #include "ash/public/cpp/ash_prefs.h"
17 #include "ash/public/cpp/keyboard/keyboard_switches.h"
18 #include "ash/public/cpp/shell_window_ids.h"
19 #include "ash/public/cpp/test/shell_test_api.h"
20 #include "ash/root_window_controller.h"
21 #include "ash/session/session_controller_impl.h"
22 #include "ash/session/test_session_controller_client.h"
23 #include "ash/shelf/home_button.h"
24 #include "ash/shelf/shelf.h"
25 #include "ash/shelf/shelf_layout_manager.h"
26 #include "ash/shelf/shelf_navigation_widget.h"
27 #include "ash/shelf/shelf_widget.h"
28 #include "ash/system/status_area_widget.h"
29 #include "ash/test/ash_test_base.h"
30 #include "ash/test/ash_test_helper.h"
31 #include "ash/test/test_widget_builder.h"
32 #include "ash/test_shell_delegate.h"
33 #include "ash/wallpaper/wallpaper_widget_controller.h"
34 #include "ash/wm/desks/desks_util.h"
35 #include "ash/wm/overview/overview_controller.h"
36 #include "base/command_line.h"
37 #include "base/containers/flat_set.h"
38 #include "base/ranges/algorithm.h"
39 #include "base/strings/utf_string_conversions.h"
40 #include "base/threading/thread_task_runner_handle.h"
41 #include "components/account_id/account_id.h"
42 #include "ui/aura/env.h"
43 #include "ui/aura/window.h"
44 #include "ui/aura/window_event_dispatcher.h"
45 #include "ui/base/models/simple_menu_model.h"
46 #include "ui/display/scoped_display_for_new_windows.h"
47 #include "ui/events/test/event_generator.h"
48 #include "ui/events/test/events_test_utils.h"
49 #include "ui/events/test/test_event_handler.h"
50 #include "ui/gfx/geometry/size.h"
51 #include "ui/views/controls/menu/menu_controller.h"
52 #include "ui/views/controls/menu/menu_runner.h"
53 #include "ui/views/widget/widget.h"
54 #include "ui/views/widget/widget_delegate.h"
55 #include "ui/views/window/dialog_delegate.h"
56
57 using aura::RootWindow;
58
59 namespace ash {
60
61 namespace {
62
63 aura::Window* GetActiveDeskContainer() {
64   return Shell::GetContainer(Shell::GetPrimaryRootWindow(),
65                              desks_util::GetActiveDeskContainerId());
66 }
67
68 aura::Window* GetAlwaysOnTopContainer() {
69   return Shell::GetContainer(Shell::GetPrimaryRootWindow(),
70                              kShellWindowId_AlwaysOnTopContainer);
71 }
72
73 // Expect ALL the containers!
74 void ExpectAllContainers() {
75   aura::Window* root_window = Shell::GetPrimaryRootWindow();
76
77   // Validate no duplicate container IDs.
78   base::flat_set<int> container_ids;
79   std::queue<aura::Window*> window_queue;
80   window_queue.push(root_window);
81   while (!window_queue.empty()) {
82     aura::Window* current_window = window_queue.front();
83     window_queue.pop();
84     for (aura::Window* child : current_window->children())
85       window_queue.push(child);
86
87     const int id = current_window->GetId();
88
89     // Skip windows with no IDs.
90     if (id == aura::Window::kInitialId)
91       continue;
92
93     EXPECT_TRUE(container_ids.insert(id).second)
94         << "Found duplicate ID: " << id
95         << " at window: " << current_window->GetName();
96   }
97
98   EXPECT_TRUE(
99       Shell::GetContainer(root_window, kShellWindowId_WallpaperContainer));
100
101   for (int desk_id : desks_util::GetDesksContainersIds())
102     EXPECT_TRUE(Shell::GetContainer(root_window, desk_id));
103
104   EXPECT_TRUE(
105       Shell::GetContainer(root_window, kShellWindowId_AlwaysOnTopContainer));
106   EXPECT_TRUE(Shell::GetContainer(root_window, kShellWindowId_ShelfContainer));
107   EXPECT_TRUE(
108       Shell::GetContainer(root_window, kShellWindowId_SystemModalContainer));
109   EXPECT_TRUE(Shell::GetContainer(root_window,
110                                   kShellWindowId_LockScreenWallpaperContainer));
111   EXPECT_TRUE(
112       Shell::GetContainer(root_window, kShellWindowId_LockScreenContainer));
113   EXPECT_TRUE(Shell::GetContainer(root_window,
114                                   kShellWindowId_LockSystemModalContainer));
115   EXPECT_TRUE(Shell::GetContainer(root_window, kShellWindowId_MenuContainer));
116   EXPECT_TRUE(Shell::GetContainer(root_window,
117                                   kShellWindowId_DragImageAndTooltipContainer));
118   EXPECT_TRUE(
119       Shell::GetContainer(root_window, kShellWindowId_SettingBubbleContainer));
120   EXPECT_TRUE(
121       Shell::GetContainer(root_window, kShellWindowId_OverlayContainer));
122   EXPECT_TRUE(Shell::GetContainer(root_window,
123                                   kShellWindowId_ImeWindowParentContainer));
124   EXPECT_TRUE(Shell::GetContainer(root_window,
125                                   kShellWindowId_VirtualKeyboardContainer));
126   EXPECT_TRUE(
127       Shell::GetContainer(root_window, kShellWindowId_MouseCursorContainer));
128
129   // Phantom window is not a container.
130   EXPECT_EQ(0u, container_ids.count(kShellWindowId_PhantomWindow));
131   EXPECT_FALSE(Shell::GetContainer(root_window, kShellWindowId_PhantomWindow));
132 }
133
134 std::unique_ptr<views::WidgetDelegateView> CreateModalWidgetDelegate() {
135   auto delegate = std::make_unique<views::WidgetDelegateView>();
136   delegate->SetCanResize(true);
137   delegate->SetModalType(ui::MODAL_TYPE_SYSTEM);
138   delegate->SetOwnedByWidget(true);
139   delegate->SetTitle(u"Modal Window");
140   return delegate;
141 }
142
143 class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
144  public:
145   SimpleMenuDelegate() = default;
146
147   SimpleMenuDelegate(const SimpleMenuDelegate&) = delete;
148   SimpleMenuDelegate& operator=(const SimpleMenuDelegate&) = delete;
149
150   ~SimpleMenuDelegate() override = default;
151
152   bool IsCommandIdChecked(int command_id) const override { return false; }
153
154   bool IsCommandIdEnabled(int command_id) const override { return true; }
155
156   void ExecuteCommand(int command_id, int event_flags) override {}
157 };
158
159 }  // namespace
160
161 class ShellTest : public AshTestBase {
162  public:
163   void TestCreateWindow(views::Widget::InitParams::Type type,
164                         bool always_on_top,
165                         aura::Window* expected_container) {
166     TestWidgetBuilder builder;
167     if (always_on_top)
168       builder.SetZOrderLevel(ui::ZOrderLevel::kFloatingWindow);
169     views::Widget* widget =
170         builder.SetWidgetType(type).BuildOwnedByNativeWidget();
171
172     EXPECT_TRUE(
173         expected_container->Contains(widget->GetNativeWindow()->parent()))
174         << "TestCreateWindow: type=" << type
175         << ", always_on_top=" << always_on_top;
176
177     widget->Close();
178   }
179
180   void LockScreenAndVerifyMenuClosed() {
181     // Verify a menu is open before locking.
182     views::MenuController* menu_controller =
183         views::MenuController::GetActiveInstance();
184     DCHECK(menu_controller);
185     EXPECT_EQ(views::MenuController::ExitType::kNone,
186               menu_controller->exit_type());
187
188     // Create a LockScreen window.
189     views::Widget* lock_widget =
190         TestWidgetBuilder()
191             .SetWidgetType(views::Widget::InitParams::TYPE_WINDOW)
192             .SetShow(false)
193             .BuildOwnedByNativeWidget();
194     Shell::GetContainer(Shell::GetPrimaryRootWindow(),
195                         kShellWindowId_LockScreenContainer)
196         ->AddChild(lock_widget->GetNativeView());
197     lock_widget->Show();
198
199     // Simulate real screen locker to change session state to LOCKED
200     // when it is shown.
201     GetSessionControllerClient()->LockScreen();
202
203     SessionControllerImpl* controller = Shell::Get()->session_controller();
204     EXPECT_TRUE(controller->IsScreenLocked());
205     EXPECT_TRUE(lock_widget->GetNativeView()->HasFocus());
206
207     // Verify menu is closed.
208     EXPECT_EQ(nullptr, views::MenuController::GetActiveInstance());
209     lock_widget->Close();
210     GetSessionControllerClient()->UnlockScreen();
211   }
212 };
213
214 TEST_F(ShellTest, CreateWindow) {
215   // Normal window should be created in default container.
216   TestCreateWindow(views::Widget::InitParams::TYPE_WINDOW,
217                    false,  // always_on_top
218                    GetActiveDeskContainer());
219   TestCreateWindow(views::Widget::InitParams::TYPE_POPUP,
220                    false,  // always_on_top
221                    GetActiveDeskContainer());
222
223   // Always-on-top window and popup are created in always-on-top container.
224   TestCreateWindow(views::Widget::InitParams::TYPE_WINDOW,
225                    true,  // always_on_top
226                    GetAlwaysOnTopContainer());
227   TestCreateWindow(views::Widget::InitParams::TYPE_POPUP,
228                    true,  // always_on_top
229                    GetAlwaysOnTopContainer());
230 }
231
232 // Verifies that a window with a preferred size is created centered on the
233 // default display for new windows. Mojo apps like shortcut_viewer rely on this
234 // behavior.
235 TEST_F(ShellTest, CreateWindowWithPreferredSize) {
236   UpdateDisplay("1024x768,800x600");
237
238   aura::Window* secondary_root = Shell::GetAllRootWindows()[1];
239   display::ScopedDisplayForNewWindows scoped_display(secondary_root);
240
241   views::Widget::InitParams params;
242   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
243   // Don't specify bounds, parent or context.
244   {
245     auto delegate = std::make_unique<views::WidgetDelegateView>();
246     delegate->SetPreferredSize(gfx::Size(400, 300));
247     params.delegate = delegate.release();
248   }
249   views::Widget widget;
250   params.context = GetContext();
251   widget.Init(std::move(params));
252
253   // Widget is centered on secondary display.
254   EXPECT_EQ(secondary_root, widget.GetNativeWindow()->GetRootWindow());
255   EXPECT_EQ(GetSecondaryDisplay().work_area().CenterPoint(),
256             widget.GetRestoredBounds().CenterPoint());
257 }
258
259 TEST_F(ShellTest, ChangeZOrderLevel) {
260   // Creates a normal window.
261   views::Widget* widget = TestWidgetBuilder().BuildOwnedByNativeWidget();
262
263   // It should be in the active desk container.
264   EXPECT_TRUE(
265       GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
266
267   // Set the z-order to float.
268   widget->SetZOrderLevel(ui::ZOrderLevel::kFloatingWindow);
269   // And it should in always on top container now.
270   EXPECT_EQ(GetAlwaysOnTopContainer(), widget->GetNativeWindow()->parent());
271
272   // Put the z-order back to normal.
273   widget->SetZOrderLevel(ui::ZOrderLevel::kNormal);
274   // It should go back to the active desk container.
275   EXPECT_TRUE(
276       GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
277
278   // Set the z-order again to the normal value.
279   widget->SetZOrderLevel(ui::ZOrderLevel::kNormal);
280   // Should have no effect and we are still in the the active desk container.
281   EXPECT_TRUE(
282       GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
283
284   widget->Close();
285 }
286
287 TEST_F(ShellTest, CreateModalWindow) {
288   // Create a normal window.
289   views::Widget* widget = TestWidgetBuilder().BuildOwnedByNativeWidget();
290
291   // It should be in the active desk container.
292   EXPECT_TRUE(
293       GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
294
295   // Create a modal window.
296   views::Widget* modal_widget = views::Widget::CreateWindowWithParent(
297       CreateModalWidgetDelegate(), widget->GetNativeView());
298   modal_widget->Show();
299
300   // It should be in modal container.
301   aura::Window* modal_container = Shell::GetContainer(
302       Shell::GetPrimaryRootWindow(), kShellWindowId_SystemModalContainer);
303   EXPECT_EQ(modal_container, modal_widget->GetNativeWindow()->parent());
304
305   modal_widget->Close();
306   widget->Close();
307 }
308
309 TEST_F(ShellTest, CreateLockScreenModalWindow) {
310   // Create a normal window.
311   views::Widget* widget = TestWidgetBuilder().BuildOwnedByNativeWidget();
312   EXPECT_TRUE(widget->GetNativeView()->HasFocus());
313
314   // It should be in the active desk container.
315   EXPECT_TRUE(
316       GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
317
318   GetSessionControllerClient()->LockScreen();
319   // Create a LockScreen window.
320   views::Widget* lock_widget =
321       TestWidgetBuilder().SetShow(false).BuildOwnedByNativeWidget();
322   Shell::GetContainer(Shell::GetPrimaryRootWindow(),
323                       kShellWindowId_LockScreenContainer)
324       ->AddChild(lock_widget->GetNativeView());
325   lock_widget->Show();
326   EXPECT_TRUE(lock_widget->GetNativeView()->HasFocus());
327
328   // It should be in LockScreen container.
329   aura::Window* lock_screen = Shell::GetContainer(
330       Shell::GetPrimaryRootWindow(), kShellWindowId_LockScreenContainer);
331   EXPECT_EQ(lock_screen, lock_widget->GetNativeWindow()->parent());
332
333   // Create a modal window with a lock window as parent.
334   views::Widget* lock_modal_widget = views::Widget::CreateWindowWithParent(
335       CreateModalWidgetDelegate(), lock_widget->GetNativeView());
336   lock_modal_widget->Show();
337   EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
338
339   // It should be in LockScreen modal container.
340   aura::Window* lock_modal_container =
341       Shell::GetContainer(Shell::GetPrimaryRootWindow(),
342                           kShellWindowId_LockSystemModalContainer);
343   EXPECT_EQ(lock_modal_container,
344             lock_modal_widget->GetNativeWindow()->parent());
345
346   // Create a modal window with a normal window as parent.
347   views::Widget* modal_widget = views::Widget::CreateWindowWithParent(
348       CreateModalWidgetDelegate(), widget->GetNativeView());
349   modal_widget->Show();
350   // Window on lock screen shouldn't lost focus.
351   EXPECT_FALSE(modal_widget->GetNativeView()->HasFocus());
352   EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
353
354   // It should be in non-LockScreen modal container.
355   aura::Window* modal_container = Shell::GetContainer(
356       Shell::GetPrimaryRootWindow(), kShellWindowId_SystemModalContainer);
357   EXPECT_EQ(modal_container, modal_widget->GetNativeWindow()->parent());
358
359   // Modal widget without parent, caused crash see crbug.com/226141
360   views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
361       CreateModalWidgetDelegate(), GetContext(), nullptr);
362
363   modal_dialog->Show();
364   EXPECT_FALSE(modal_dialog->GetNativeView()->HasFocus());
365   EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
366
367   modal_dialog->Close();
368   modal_widget->Close();
369   modal_widget->Close();
370   lock_modal_widget->Close();
371   lock_widget->Close();
372   widget->Close();
373 }
374
375 TEST_F(ShellTest, IsScreenLocked) {
376   SessionControllerImpl* controller = Shell::Get()->session_controller();
377   GetSessionControllerClient()->LockScreen();
378   EXPECT_TRUE(controller->IsScreenLocked());
379   GetSessionControllerClient()->UnlockScreen();
380   EXPECT_FALSE(controller->IsScreenLocked());
381 }
382
383 TEST_F(ShellTest, LockScreenClosesActiveMenu) {
384   SimpleMenuDelegate menu_delegate;
385   std::unique_ptr<ui::SimpleMenuModel> menu_model(
386       new ui::SimpleMenuModel(&menu_delegate));
387   menu_model->AddItem(0, u"Menu item");
388   views::Widget* widget = Shell::GetPrimaryRootWindowController()
389                               ->wallpaper_widget_controller()
390                               ->GetWidget();
391   std::unique_ptr<views::MenuRunner> menu_runner(
392       new views::MenuRunner(menu_model.get(), views::MenuRunner::CONTEXT_MENU));
393
394   menu_runner->RunMenuAt(widget, nullptr, gfx::Rect(),
395                          views::MenuAnchorPosition::kTopLeft,
396                          ui::MENU_SOURCE_MOUSE);
397   LockScreenAndVerifyMenuClosed();
398 }
399
400 TEST_F(ShellTest, ManagedWindowModeBasics) {
401   // We start with the usual window containers.
402   ExpectAllContainers();
403   // Shelf is visible.
404   ShelfWidget* shelf_widget = GetPrimaryShelf()->shelf_widget();
405   EXPECT_TRUE(shelf_widget->IsVisible());
406   // Shelf is at bottom-left of screen.
407   EXPECT_EQ(0, shelf_widget->GetWindowBoundsInScreen().x());
408   EXPECT_EQ(
409       Shell::GetPrimaryRootWindow()->GetHost()->GetBoundsInPixels().height(),
410       shelf_widget->GetWindowBoundsInScreen().bottom());
411   // We have a wallpaper but not a bare layer.
412   // TODO (antrim): enable once we find out why it fails component build.
413   //  WallpaperWidgetController* wallpaper =
414   //      Shell::GetPrimaryRootWindow()->
415   //          GetProperty(kWindowDesktopComponent);
416   //  EXPECT_TRUE(wallpaper);
417   //  EXPECT_TRUE(wallpaper->widget());
418   //  EXPECT_FALSE(wallpaper->layer());
419
420   // Create a normal window.  It is not maximized.
421   views::Widget* widget = TestWidgetBuilder()
422                               .SetBounds(gfx::Rect(11, 22, 300, 400))
423                               .BuildOwnedByNativeWidget();
424   EXPECT_FALSE(widget->IsMaximized());
425
426   // Clean up.
427   widget->Close();
428 }
429
430 // Tests that the cursor-filter is ahead of the drag-drop controller in the
431 // pre-target list.
432 TEST_F(ShellTest, TestPreTargetHandlerOrder) {
433   Shell* shell = Shell::Get();
434   ui::EventTargetTestApi test_api(shell);
435   ShellTestApi shell_test_api;
436
437   ui::EventHandlerList handlers = test_api.GetPreTargetHandlers();
438   ui::EventHandlerList::const_iterator cursor_filter =
439       base::ranges::find(handlers, shell->mouse_cursor_filter());
440   ui::EventHandlerList::const_iterator drag_drop =
441       base::ranges::find(handlers, shell_test_api.drag_drop_controller());
442   EXPECT_NE(handlers.end(), cursor_filter);
443   EXPECT_NE(handlers.end(), drag_drop);
444   EXPECT_GT(drag_drop, cursor_filter);
445 }
446
447 // Verifies an EventHandler added to Env gets notified from EventGenerator.
448 TEST_F(ShellTest, EnvPreTargetHandler) {
449   ui::test::TestEventHandler event_handler;
450   aura::Env::GetInstance()->AddPreTargetHandler(&event_handler);
451   ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
452   generator.MoveMouseBy(1, 1);
453   EXPECT_NE(0, event_handler.num_mouse_events());
454   aura::Env::GetInstance()->RemovePreTargetHandler(&event_handler);
455 }
456
457 // Verifies that pressing tab on an empty shell (one with no windows visible)
458 // will put focus on the shelf. This enables keyboard only users to get to the
459 // shelf without knowing the more obscure accelerators. Tab should move focus to
460 // the home button, shift + tab to the status widget. From there, normal shelf
461 // tab behaviour takes over, and the shell no longer catches that event.
462 TEST_F(ShellTest, NoWindowTabFocus) {
463   ExpectAllContainers();
464
465   StatusAreaWidget* status_area_widget =
466       GetPrimaryShelf()->status_area_widget();
467   ShelfNavigationWidget* home_button = GetPrimaryShelf()->navigation_widget();
468
469   // Create a normal window.  It is not maximized.
470   auto widget = CreateTestWidget();
471
472   // Hit tab with window open, and expect that focus is not on the navigation
473   // widget or status widget.
474   PressAndReleaseKey(ui::VKEY_TAB);
475   EXPECT_FALSE(home_button->GetNativeView()->HasFocus());
476   EXPECT_FALSE(status_area_widget->GetNativeView()->HasFocus());
477
478   // Minimize the window, hit tab and expect that focus is on the launcher.
479   widget->Minimize();
480   PressAndReleaseKey(ui::VKEY_TAB);
481   EXPECT_TRUE(home_button->GetNativeView()->HasFocus());
482
483   // Show (to steal focus back before continuing testing) and close the window.
484   widget->Show();
485   widget->Close();
486   EXPECT_FALSE(home_button->GetNativeView()->HasFocus());
487
488   // Confirm that pressing tab when overview mode is open does not go to home
489   // button. Tab should be handled by overview mode and not hit the shell event
490   // handler.
491   EnterOverview();
492   PressAndReleaseKey(ui::VKEY_TAB);
493   EXPECT_FALSE(home_button->GetNativeView()->HasFocus());
494   ExitOverview();
495
496   // Hit shift tab and expect that focus is on status widget.
497   PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
498   EXPECT_TRUE(status_area_widget->GetNativeView()->HasFocus());
499 }
500
501 // This verifies WindowObservers are removed when a window is destroyed after
502 // the Shell is destroyed. This scenario (aura::Windows being deleted after the
503 // Shell) occurs if someone is holding a reference to an unparented Window, as
504 // is the case with a RenderWidgetHostViewAura that isn't on screen. As long as
505 // everything is ok, we won't crash. If there is a bug, window's destructor will
506 // notify some deleted object (say VideoDetector or ActivationController) and
507 // this will crash.
508 class ShellTest2 : public AshTestBase {
509  public:
510   ShellTest2() = default;
511
512   ShellTest2(const ShellTest2&) = delete;
513   ShellTest2& operator=(const ShellTest2&) = delete;
514
515   ~ShellTest2() override = default;
516
517  protected:
518   std::unique_ptr<aura::Window> window_;
519 };
520
521 TEST_F(ShellTest2, DontCrashWhenWindowDeleted) {
522   window_ = std::make_unique<aura::Window>(nullptr,
523                                            aura::client::WINDOW_TYPE_UNKNOWN);
524   window_->Init(ui::LAYER_NOT_DRAWN);
525 }
526
527 using ShellLoginTest = NoSessionAshTestBase;
528
529 TEST_F(ShellLoginTest, DragAndDropDisabledBeforeLogin) {
530   DragDropController* drag_drop_controller =
531       ShellTestApi().drag_drop_controller();
532   DragDropControllerTestApi drag_drop_controller_test_api(drag_drop_controller);
533   EXPECT_FALSE(drag_drop_controller_test_api.enabled());
534
535   SimulateUserLogin("user1@test.com");
536   EXPECT_TRUE(drag_drop_controller_test_api.enabled());
537 }
538
539 using NoDuplicateShellContainerIdsTest = AshTestBase;
540
541 TEST_F(NoDuplicateShellContainerIdsTest, ValidateContainersIds) {
542   ExpectAllContainers();
543 }
544
545 }  // namespace ash