Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(42)

Side by Side Diff: ash/wm/overview/window_selector.cc

Issue 251103005: Added arrow key navigation to Overview Mode (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Starting again from design doc Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ash/wm/overview/window_selector.h" 5 #include "ash/wm/overview/window_selector.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "ash/accessibility_delegate.h" 9 #include "ash/accessibility_delegate.h"
10 #include "ash/ash_switches.h" 10 #include "ash/ash_switches.h"
11 #include "ash/metrics/user_metrics_recorder.h" 11 #include "ash/metrics/user_metrics_recorder.h"
12 #include "ash/root_window_controller.h" 12 #include "ash/root_window_controller.h"
13 #include "ash/screen_util.h"
14 #include "ash/shell.h" 13 #include "ash/shell.h"
15 #include "ash/shell_window_ids.h" 14 #include "ash/shell_window_ids.h"
16 #include "ash/switchable_windows.h" 15 #include "ash/switchable_windows.h"
17 #include "ash/wm/overview/scoped_transform_overview_window.h" 16 #include "ash/wm/overview/scoped_transform_overview_window.h"
17 #include "ash/wm/overview/window_grid.h"
18 #include "ash/wm/overview/window_selector_delegate.h" 18 #include "ash/wm/overview/window_selector_delegate.h"
19 #include "ash/wm/overview/window_selector_item.h" 19 #include "ash/wm/overview/window_selector_item.h"
20 #include "ash/wm/overview/window_selector_panels.h" 20 #include "ash/wm/overview/window_selector_panels.h"
21 #include "ash/wm/overview/window_selector_window.h" 21 #include "ash/wm/overview/window_selector_window.h"
22 #include "ash/wm/window_state.h" 22 #include "ash/wm/window_state.h"
23 #include "base/auto_reset.h" 23 #include "base/auto_reset.h"
24 #include "base/command_line.h" 24 #include "base/command_line.h"
25 #include "base/metrics/histogram.h" 25 #include "base/metrics/histogram.h"
26 #include "base/strings/string_number_conversions.h" 26 #include "base/strings/string_number_conversions.h"
27 #include "third_party/skia/include/core/SkColor.h" 27 #include "third_party/skia/include/core/SkColor.h"
28 #include "ui/aura/client/cursor_client.h" 28 #include "ui/aura/client/cursor_client.h"
29 #include "ui/aura/client/focus_client.h" 29 #include "ui/aura/client/focus_client.h"
30 #include "ui/aura/window.h" 30 #include "ui/aura/window.h"
31 #include "ui/aura/window_event_dispatcher.h" 31 #include "ui/aura/window_event_dispatcher.h"
32 #include "ui/aura/window_observer.h" 32 #include "ui/aura/window_observer.h"
33 #include "ui/compositor/layer_animation_observer.h"
34 #include "ui/compositor/scoped_layer_animation_settings.h" 33 #include "ui/compositor/scoped_layer_animation_settings.h"
35 #include "ui/events/event.h" 34 #include "ui/events/event.h"
36 #include "ui/gfx/screen.h" 35 #include "ui/gfx/screen.h"
37 #include "ui/views/background.h" 36 #include "ui/views/background.h"
38 #include "ui/views/widget/widget.h" 37 #include "ui/views/widget/widget.h"
39 #include "ui/wm/core/window_util.h" 38 #include "ui/wm/core/window_util.h"
40 #include "ui/wm/public/activation_client.h" 39 #include "ui/wm/public/activation_client.h"
41 40
42 namespace ash { 41 namespace ash {
43 42
44 namespace { 43 namespace {
45 44
46 // Conceptually the window overview is a table or grid of cells having this
47 // fixed aspect ratio. The number of columns is determined by maximizing the
48 // area of them based on the number of windows.
49 const float kCardAspectRatio = 4.0f / 3.0f;
50
51 // In the conceptual overview table, the window margin is the space reserved
52 // around the window within the cell. This margin does not overlap so the
53 // closest distance between adjacent windows will be twice this amount.
54 const int kWindowMargin = 30;
55
56 // The minimum number of cards along the major axis (i.e. horizontally on a
57 // landscape orientation).
58 const int kMinCardsMajor = 3;
59
60 // A comparator for locating a given target window.
61 struct WindowSelectorItemComparator
62 : public std::unary_function<WindowSelectorItem*, bool> {
63 explicit WindowSelectorItemComparator(const aura::Window* target_window)
64 : target(target_window) {
65 }
66
67 bool operator()(WindowSelectorItem* window) const {
68 return window->HasSelectableWindow(target);
69 }
70
71 const aura::Window* target;
72 };
73
74 // An observer which holds onto the passed widget until the animation is
75 // complete.
76 class CleanupWidgetAfterAnimationObserver : public ui::LayerAnimationObserver {
77 public:
78 explicit CleanupWidgetAfterAnimationObserver(
79 scoped_ptr<views::Widget> widget);
80
81 // ui::LayerAnimationObserver:
82 virtual void OnLayerAnimationEnded(
83 ui::LayerAnimationSequence* sequence) OVERRIDE;
84 virtual void OnLayerAnimationAborted(
85 ui::LayerAnimationSequence* sequence) OVERRIDE;
86 virtual void OnLayerAnimationScheduled(
87 ui::LayerAnimationSequence* sequence) OVERRIDE;
88
89 private:
90 virtual ~CleanupWidgetAfterAnimationObserver();
91
92 scoped_ptr<views::Widget> widget_;
93
94 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
95 };
96
97 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
98 scoped_ptr<views::Widget> widget)
99 : widget_(widget.Pass()) {
100 widget_->GetNativeWindow()->layer()->GetAnimator()->AddObserver(this);
101 }
102
103 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
104 widget_->GetNativeWindow()->layer()->GetAnimator()->RemoveObserver(this);
105 }
106
107 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationEnded(
108 ui::LayerAnimationSequence* sequence) {
109 delete this;
110 }
111
112 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationAborted(
113 ui::LayerAnimationSequence* sequence) {
114 delete this;
115 }
116
117 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationScheduled(
118 ui::LayerAnimationSequence* sequence) {
119 }
120
121 // A comparator for locating a selectable window given a targeted window. 45 // A comparator for locating a selectable window given a targeted window.
122 struct WindowSelectorItemTargetComparator 46 struct WindowSelectorItemTargetComparator
123 : public std::unary_function<WindowSelectorItem*, bool> { 47 : public std::unary_function<WindowSelectorItem*, bool> {
124 explicit WindowSelectorItemTargetComparator(const aura::Window* target_window) 48 explicit WindowSelectorItemTargetComparator(const aura::Window* target_window)
125 : target(target_window) { 49 : target(target_window) {
126 } 50 }
127 51
128 bool operator()(WindowSelectorItem* window) const { 52 bool operator()(WindowSelectorItem* window) const {
129 return window->TargetedWindow(target) != NULL; 53 return window->TargetedWindow(target) != NULL;
130 } 54 }
(...skipping 26 matching lines...) Expand all
157 } 81 }
158 } 82 }
159 83
160 } // namespace 84 } // namespace
161 85
162 WindowSelector::WindowSelector(const WindowList& windows, 86 WindowSelector::WindowSelector(const WindowList& windows,
163 WindowSelectorDelegate* delegate) 87 WindowSelectorDelegate* delegate)
164 : delegate_(delegate), 88 : delegate_(delegate),
165 restore_focus_window_(aura::client::GetFocusClient( 89 restore_focus_window_(aura::client::GetFocusClient(
166 Shell::GetPrimaryRootWindow())->GetFocusedWindow()), 90 Shell::GetPrimaryRootWindow())->GetFocusedWindow()),
167 ignore_activations_(false) { 91 ignore_activations_(false),
92 root_index_(0) {
168 DCHECK(delegate_); 93 DCHECK(delegate_);
169 94
170 if (restore_focus_window_) 95 if (restore_focus_window_)
171 restore_focus_window_->AddObserver(this); 96 restore_focus_window_->AddObserver(this);
172 97
173 std::vector<WindowSelectorPanels*> panels_items; 98 std::vector<WindowSelectorPanels*> panels_items;
174 for (size_t i = 0; i < windows.size(); ++i) { 99 for (size_t i = 0; i < windows.size(); ++i) {
175 WindowSelectorItem* item = NULL; 100 WindowSelectorItem* item = NULL;
176 if (windows[i] != restore_focus_window_) 101 if (windows[i] != restore_focus_window_)
177 windows[i]->AddObserver(this); 102 windows[i]->AddObserver(this);
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
276 void WindowSelector::CancelSelection() { 201 void WindowSelector::CancelSelection() {
277 delegate_->OnSelectionCanceled(); 202 delegate_->OnSelectionCanceled();
278 } 203 }
279 204
280 void WindowSelector::OnKeyEvent(ui::KeyEvent* event) { 205 void WindowSelector::OnKeyEvent(ui::KeyEvent* event) {
281 if (GetTargetedWindow(static_cast<aura::Window*>(event->target()))) 206 if (GetTargetedWindow(static_cast<aura::Window*>(event->target())))
282 event->StopPropagation(); 207 event->StopPropagation();
283 if (event->type() != ui::ET_KEY_PRESSED) 208 if (event->type() != ui::ET_KEY_PRESSED)
284 return; 209 return;
285 210
286 if (event->key_code() == ui::VKEY_ESCAPE) 211 // Dummy value so that the compiler does not complain about uninitialized
tdanderson 2014/05/28 16:01:19 Comment not needed.
Nina 2014/05/29 17:12:24 Removed.
287 CancelSelection(); 212 // values.
213 Direction direction = WindowSelector::UP;
214 bool moving = false;
215 switch (event->key_code()) {
216 case ui::VKEY_ESCAPE:
217 CancelSelection();
218 break;
219 case ui::VKEY_UP:
220 direction = WindowSelector::UP;
tdanderson 2014/05/28 16:01:19 Consider defining a private helper with a single p
Nina 2014/05/29 17:12:24 Yep, this is much nicer. Done.
221 moving = true;
222 break;
223 case ui::VKEY_DOWN:
224 direction = WindowSelector::DOWN;
225 moving = true;
226 break;
227 case ui::VKEY_RIGHT:
228 direction = WindowSelector::RIGHT;
229 moving = true;
230 break;
231 case ui::VKEY_LEFT:
232 direction = WindowSelector::LEFT;
233 moving = true;
234 break;
235 case ui::VKEY_RETURN:
236 SelectWindow(
237 grid_list_[root_index_]->SelectedWindow()->SelectionWindow());
238 break;
239 default:
240 // Not a key we are interested in.
241 break;
242 }
243 if (moving) {
tdanderson 2014/05/28 16:01:19 It seems you can do a bit of simplification here b
Nina 2014/05/29 17:12:24 Refactoring made this not necessary anymore.
244 bool change_root = false;
tdanderson 2014/05/28 16:01:19 |change_root| is not necessary. Just say if (grid_
Nina 2014/05/29 17:12:24 I thought it was a bit harder to read this way, bu
245 change_root =
246 grid_list_[root_index_]->Move(direction);
247 if (change_root) {
tdanderson 2014/05/28 16:01:19 Your documentation for WindowGrid::Move() says tha
Nina 2014/05/29 17:12:24 Modified the comment.
248 if (root_index_ >= grid_list_.size() - 1)
249 root_index_ = 0;
250 else
251 root_index_++;
252 // The grid reported that the movement command corresponds to the next
253 // root window, call Move() on it to initialize the selection widget.
254 grid_list_[root_index_]->Move(direction);
tdanderson 2014/05/28 16:01:19 I don't like the idea of calling Move() for the pu
Nina 2014/05/29 17:12:24 Done.
255 }
256 }
288 } 257 }
289 258
290 void WindowSelector::OnMouseEvent(ui::MouseEvent* event) { 259 void WindowSelector::OnMouseEvent(ui::MouseEvent* event) {
291 aura::Window* target = GetEventTarget(event); 260 aura::Window* target = GetEventTarget(event);
292 if (!target) 261 if (!target)
293 return; 262 return;
294 263
295 event->SetHandled(); 264 event->SetHandled();
296 if (event->type() != ui::ET_MOUSE_RELEASED) 265 if (event->type() != ui::ET_MOUSE_RELEASED)
297 return; 266 return;
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
346 if (new_window->parent()->id() == kSwitchableWindowContainerIds[i] && 315 if (new_window->parent()->id() == kSwitchableWindowContainerIds[i] &&
347 !::wm::GetTransientParent(new_window)) { 316 !::wm::GetTransientParent(new_window)) {
348 // The new window is in one of the switchable containers, abort overview. 317 // The new window is in one of the switchable containers, abort overview.
349 CancelSelection(); 318 CancelSelection();
350 return; 319 return;
351 } 320 }
352 } 321 }
353 } 322 }
354 323
355 void WindowSelector::OnWindowDestroying(aura::Window* window) { 324 void WindowSelector::OnWindowDestroying(aura::Window* window) {
356 // window is one of a container, the restore_focus_window and/or 325 WindowSelectorItem* removed_item = NULL;
357 // one of the selectable windows in overview. 326
358 ScopedVector<WindowSelectorItem>::iterator iter = 327 ScopedVector<WindowGrid>::iterator grid_iter;
359 std::find_if(windows_.begin(), windows_.end(), 328 for (ScopedVector<WindowGrid>::iterator iter = grid_list_.begin();
360 WindowSelectorItemComparator(window)); 329 iter != grid_list_.end() && !removed_item; ++iter) {
330 removed_item = (*iter)->RemoveWindow(window);
tdanderson 2014/05/28 16:01:19 I prefer the previous approach: using find_if to d
Nina 2014/05/29 17:12:24 Well, if I do it like that I think I'll have to fi
331 grid_iter = iter;
332 }
333
361 window->RemoveObserver(this); 334 window->RemoveObserver(this);
362 observed_windows_.erase(window); 335 observed_windows_.erase(window);
363 if (window == restore_focus_window_) 336 if (window == restore_focus_window_)
364 restore_focus_window_ = NULL; 337 restore_focus_window_ = NULL;
365 if (iter == windows_.end()) 338
339 if (!removed_item)
366 return; 340 return;
367 341
368 (*iter)->RemoveWindow(window); 342 windows_.erase(std::find(windows_.begin(), windows_.end(), removed_item));
369 // If there are still windows in this selector entry then the overview is
370 // still active and the active selection remains the same.
371 if (!(*iter)->empty())
372 return;
373 343
374 windows_.erase(iter); 344 if (grid_iter != grid_list_.end() && (*grid_iter)->empty()) {
375 if (windows_.empty()) { 345 grid_list_.erase(grid_iter);
376 CancelSelection(); 346 root_index_ = 0;
377 return; 347 if (grid_list_.size() == 0)
348 CancelSelection();
378 } 349 }
379 PositionWindows(true);
tdanderson 2014/05/28 16:01:19 Why was this removed?
Nina 2014/05/29 17:12:24 Because RemoveWindow rearranges the windows on its
380 } 350 }
381 351
382 void WindowSelector::OnWindowBoundsChanged(aura::Window* window, 352 void WindowSelector::OnWindowBoundsChanged(aura::Window* window,
383 const gfx::Rect& old_bounds, 353 const gfx::Rect& old_bounds,
384 const gfx::Rect& new_bounds) { 354 const gfx::Rect& new_bounds) {
385 ScopedVector<WindowSelectorItem>::iterator iter = 355 ScopedVector<WindowSelectorItem>::iterator iter =
386 std::find_if(windows_.begin(), windows_.end(), 356 std::find_if(windows_.begin(), windows_.end(),
387 WindowSelectorItemTargetComparator(window)); 357 WindowSelectorItemTargetComparator(window));
388 if (iter == windows_.end()) 358 if (iter == windows_.end())
389 return; 359 return;
(...skipping 29 matching lines...) Expand all
419 aura::client::GetFocusClient( 389 aura::client::GetFocusClient(
420 Shell::GetPrimaryRootWindow())->FocusWindow(NULL); 390 Shell::GetPrimaryRootWindow())->FocusWindow(NULL);
421 391
422 Shell* shell = Shell::GetInstance(); 392 Shell* shell = Shell::GetInstance();
423 shell->OnOverviewModeStarting(); 393 shell->OnOverviewModeStarting();
424 394
425 for (WindowSelectorItemList::iterator iter = windows_.begin(); 395 for (WindowSelectorItemList::iterator iter = windows_.begin();
426 iter != windows_.end(); ++iter) { 396 iter != windows_.end(); ++iter) {
427 (*iter)->PrepareForOverview(); 397 (*iter)->PrepareForOverview();
428 } 398 }
429 PositionWindows(/* animate */ true); 399 PositionWindows(true);
430 DCHECK(!windows_.empty()); 400 DCHECK(!windows_.empty());
431 cursor_client_ = aura::client::GetCursorClient( 401 cursor_client_ = aura::client::GetCursorClient(
432 windows_.front()->GetRootWindow()); 402 windows_.front()->GetRootWindow());
433 if (cursor_client_) { 403 if (cursor_client_) {
434 cursor_client_->SetCursor(ui::kCursorPointer); 404 cursor_client_->SetCursor(ui::kCursorPointer);
435 cursor_client_->ShowCursor(); 405 cursor_client_->ShowCursor();
436 // TODO(flackr): Only prevent cursor changes for windows in the overview. 406 // TODO(flackr): Only prevent cursor changes for windows in the overview.
437 // This will be easier to do without exposing the overview mode code if the 407 // This will be easier to do without exposing the overview mode code if the
438 // cursor changes are moved to ToplevelWindowEventHandler::HandleMouseMoved 408 // cursor changes are moved to ToplevelWindowEventHandler::HandleMouseMoved
439 // as suggested there. 409 // as suggested there.
440 cursor_client_->LockCursor(); 410 cursor_client_->LockCursor();
441 } 411 }
442 shell->PrependPreTargetHandler(this); 412 shell->PrependPreTargetHandler(this);
443 shell->GetScreen()->AddObserver(this); 413 shell->GetScreen()->AddObserver(this);
444 shell->metrics()->RecordUserMetricsAction(UMA_WINDOW_OVERVIEW); 414 shell->metrics()->RecordUserMetricsAction(UMA_WINDOW_OVERVIEW);
445 HideAndTrackNonOverviewWindows(); 415 HideAndTrackNonOverviewWindows();
446 // Send an a11y alert. 416 // Send an a11y alert.
447 shell->accessibility_delegate()->TriggerAccessibilityAlert( 417 shell->accessibility_delegate()->TriggerAccessibilityAlert(
448 A11Y_ALERT_WINDOW_OVERVIEW_MODE_ENTERED); 418 A11Y_ALERT_WINDOW_OVERVIEW_MODE_ENTERED);
449 419
450 UpdateShelfVisibility(); 420 UpdateShelfVisibility();
451 } 421 }
452 422
453 void WindowSelector::PositionWindows(bool animate) { 423 void WindowSelector::PositionWindows(bool animate) {
424 grid_list_.clear();
454 aura::Window::Windows root_window_list = Shell::GetAllRootWindows(); 425 aura::Window::Windows root_window_list = Shell::GetAllRootWindows();
455 for (size_t i = 0; i < root_window_list.size(); ++i) 426 for (size_t i = 0; i < root_window_list.size(); ++i) {
456 PositionWindowsFromRoot(root_window_list[i], animate); 427 std::vector<WindowSelectorItem*> windows;
457 } 428 for (WindowSelectorItemList::iterator iter = windows_.begin();
458 429 iter != windows_.end(); ++iter) {
459 void WindowSelector::PositionWindowsFromRoot(aura::Window* root_window, 430 if ((*iter)->GetRootWindow() == root_window_list[i])
460 bool animate) { 431 windows.push_back(*iter);
461 std::vector<WindowSelectorItem*> windows; 432 }
462 for (WindowSelectorItemList::iterator iter = windows_.begin(); 433 if (!windows.empty())
463 iter != windows_.end(); ++iter) { 434 grid_list_.push_back(new WindowGrid(root_window_list[i], windows));
464 if ((*iter)->GetRootWindow() == root_window)
465 windows.push_back(*iter);
466 }
467
468 if (windows.empty())
469 return;
470
471 gfx::Size window_size;
472 gfx::Rect total_bounds = ScreenUtil::ConvertRectToScreen(
473 root_window,
474 ScreenUtil::GetDisplayWorkAreaBoundsInParent(
475 Shell::GetContainer(root_window, kShellWindowId_DefaultContainer)));
476
477 // Find the minimum number of windows per row that will fit all of the
478 // windows on screen.
479 size_t columns = std::max(
480 total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
481 static_cast<int>(ceil(sqrt(total_bounds.width() * windows.size() /
482 (kCardAspectRatio * total_bounds.height())))));
483 size_t rows = ((windows.size() + columns - 1) / columns);
484 window_size.set_width(std::min(
485 static_cast<int>(total_bounds.width() / columns),
486 static_cast<int>(total_bounds.height() * kCardAspectRatio / rows)));
487 window_size.set_height(window_size.width() / kCardAspectRatio);
488
489 // Calculate the X and Y offsets necessary to center the grid.
490 int x_offset = total_bounds.x() + ((windows.size() >= columns ? 0 :
491 (columns - windows.size()) * window_size.width()) +
492 (total_bounds.width() - columns * window_size.width())) / 2;
493 int y_offset = total_bounds.y() + (total_bounds.height() -
494 rows * window_size.height()) / 2;
495 for (size_t i = 0; i < windows.size(); ++i) {
496 gfx::Transform transform;
497 int column = i % columns;
498 int row = i / columns;
499 gfx::Rect target_bounds(window_size.width() * column + x_offset,
500 window_size.height() * row + y_offset,
501 window_size.width(),
502 window_size.height());
503 target_bounds.Inset(kWindowMargin, kWindowMargin);
504 windows[i]->SetBounds(root_window, target_bounds, animate);
505 } 435 }
506 } 436 }
507 437
508 void WindowSelector::HideAndTrackNonOverviewWindows() { 438 void WindowSelector::HideAndTrackNonOverviewWindows() {
509 // Add the windows to hidden_windows first so that if any are destroyed 439 // Add the windows to hidden_windows first so that if any are destroyed
510 // while hiding them they are tracked. 440 // while hiding them they are tracked.
511 aura::Window::Windows root_windows = Shell::GetAllRootWindows(); 441 aura::Window::Windows root_windows = Shell::GetAllRootWindows();
512 for (aura::Window::Windows::const_iterator root_iter = root_windows.begin(); 442 for (aura::Window::Windows::const_iterator root_iter = root_windows.begin();
513 root_iter != root_windows.end(); ++root_iter) { 443 root_iter != root_windows.end(); ++root_iter) {
514 for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) { 444 for (size_t i = 0; i < kSwitchableWindowContainerIdsLength; ++i) {
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
575 for (WindowSelectorItemList::iterator iter = windows_.begin(); 505 for (WindowSelectorItemList::iterator iter = windows_.begin();
576 iter != windows_.end(); ++iter) { 506 iter != windows_.end(); ++iter) {
577 aura::Window* selected = (*iter)->TargetedWindow(window); 507 aura::Window* selected = (*iter)->TargetedWindow(window);
578 if (selected) 508 if (selected)
579 return selected; 509 return selected;
580 } 510 }
581 return NULL; 511 return NULL;
582 } 512 }
583 513
584 } // namespace ash 514 } // namespace ash
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698