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

Side by Side Diff: mandoline/ui/desktop_ui/browser_window.cc

Issue 1326443006: mandoline: Add back/forward support and UI. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase to ToT Created 5 years, 3 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 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 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 "mandoline/ui/desktop_ui/browser_window.h" 5 #include "mandoline/ui/desktop_ui/browser_window.h"
6 6
7 #include "base/command_line.h" 7 #include "base/command_line.h"
8 #include "base/strings/string16.h" 8 #include "base/strings/string16.h"
9 #include "base/strings/utf_string_conversions.h" 9 #include "base/strings/utf_string_conversions.h"
10 #include "base/time/time.h" 10 #include "base/time/time.h"
11 #include "components/view_manager/public/cpp/scoped_view_ptr.h" 11 #include "components/view_manager/public/cpp/scoped_view_ptr.h"
12 #include "components/view_manager/public/cpp/view_tree_host_factory.h" 12 #include "components/view_manager/public/cpp/view_tree_host_factory.h"
13 #include "mandoline/ui/aura/native_widget_view_manager.h" 13 #include "mandoline/ui/aura/native_widget_view_manager.h"
14 #include "mandoline/ui/desktop_ui/browser_commands.h" 14 #include "mandoline/ui/desktop_ui/browser_commands.h"
15 #include "mandoline/ui/desktop_ui/browser_manager.h" 15 #include "mandoline/ui/desktop_ui/browser_manager.h"
16 #include "mandoline/ui/desktop_ui/public/interfaces/omnibox.mojom.h" 16 #include "mandoline/ui/desktop_ui/public/interfaces/omnibox.mojom.h"
17 #include "mojo/common/common_type_converters.h" 17 #include "mojo/common/common_type_converters.h"
18 #include "mojo/converters/geometry/geometry_type_converters.h" 18 #include "mojo/converters/geometry/geometry_type_converters.h"
19 #include "mojo/services/tracing/public/cpp/switches.h" 19 #include "mojo/services/tracing/public/cpp/switches.h"
20 #include "mojo/services/tracing/public/interfaces/tracing.mojom.h" 20 #include "mojo/services/tracing/public/interfaces/tracing.mojom.h"
21 #include "ui/gfx/canvas.h" 21 #include "ui/gfx/canvas.h"
22 #include "ui/views/background.h" 22 #include "ui/views/background.h"
23 #include "ui/views/controls/button/label_button.h" 23 #include "ui/views/controls/button/label_button.h"
24 #include "ui/views/layout/box_layout.h"
24 #include "ui/views/widget/widget_delegate.h" 25 #include "ui/views/widget/widget_delegate.h"
25 26
26 namespace mandoline { 27 namespace mandoline {
27 28
29 class BrowserWindow::ToolbarView : public views::View,
sky 2015/09/09 20:43:28 Move this into its own file. No doubt it's going t
30 public views::ButtonListener {
31 public:
32 ToolbarView(BrowserWindow* browser_window)
33 : browser_window_(browser_window),
34 layout_(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5)),
35 back_button_(new views::LabelButton(this, base::ASCIIToUTF16("Back"))),
36 forward_button_(
37 new views::LabelButton(this, base::ASCIIToUTF16("Forward"))),
38 omnibox_launcher_(
39 new views::LabelButton(this, base::ASCIIToUTF16("Open Omnibox"))) {
40 SetLayoutManager(layout_);
41
42 AddChildView(back_button_);
43 AddChildView(forward_button_);
44 AddChildView(omnibox_launcher_);
45
46 layout_->SetDefaultFlex(0);
47 layout_->SetFlexForView(omnibox_launcher_, 1);
48 }
49
50 ~ToolbarView() override {}
51
52 void SetOmniboxText(const base::string16& text) {
53 omnibox_launcher_->SetText(text);
54 }
55
56 void SetBackForwardEnabled(bool back_enabled, bool forward_enabled) {
57 back_button_->SetEnabled(back_enabled);
58 forward_button_->SetEnabled(forward_enabled);
59 }
60
61 // Overridden from views::ButtonListener:
62 void ButtonPressed(views::Button* sender, const ui::Event& event) override {
63 if (sender == omnibox_launcher_)
64 browser_window_->ShowOmnibox();
65 else if (sender == back_button_)
66 browser_window_->GoBack();
67 else if (sender == forward_button_)
68 browser_window_->GoForward();
69 else
70 NOTIMPLEMENTED();
sky 2015/09/09 20:43:28 Shouldn't this be a NOTREACHED?
71 }
72
73 private:
74 BrowserWindow* browser_window_;
75
76 views::BoxLayout* layout_;
77 views::LabelButton* back_button_;
78 views::LabelButton* forward_button_;
79 views::LabelButton* omnibox_launcher_;
80
81 DISALLOW_COPY_AND_ASSIGN(ToolbarView);
82 };
83
28 class ProgressView : public views::View { 84 class ProgressView : public views::View {
29 public: 85 public:
30 ProgressView() : progress_(0.f), loading_(false) {} 86 ProgressView() : progress_(0.f), loading_(false) {}
31 ~ProgressView() override {} 87 ~ProgressView() override {}
32 88
33 void SetProgress(double progress) { 89 void SetProgress(double progress) {
34 progress_ = progress; 90 progress_ = progress;
35 SchedulePaint(); 91 SchedulePaint();
36 } 92 }
37 93
(...skipping 26 matching lines...) Expand all
64 120
65 //////////////////////////////////////////////////////////////////////////////// 121 ////////////////////////////////////////////////////////////////////////////////
66 // BrowserWindow, public: 122 // BrowserWindow, public:
67 123
68 BrowserWindow::BrowserWindow(mojo::ApplicationImpl* app, 124 BrowserWindow::BrowserWindow(mojo::ApplicationImpl* app,
69 mojo::ViewTreeHostFactory* host_factory, 125 mojo::ViewTreeHostFactory* host_factory,
70 BrowserManager* manager) 126 BrowserManager* manager)
71 : app_(app), 127 : app_(app),
72 host_client_binding_(this), 128 host_client_binding_(this),
73 manager_(manager), 129 manager_(manager),
74 omnibox_launcher_(nullptr), 130 toolbar_view_(nullptr),
75 progress_bar_(nullptr), 131 progress_bar_(nullptr),
76 root_(nullptr), 132 root_(nullptr),
77 content_(nullptr), 133 content_(nullptr),
78 omnibox_view_(nullptr), 134 omnibox_view_(nullptr),
79 web_view_(this) { 135 web_view_(this) {
80 mojo::ViewTreeHostClientPtr host_client; 136 mojo::ViewTreeHostClientPtr host_client;
81 host_client_binding_.Bind(GetProxy(&host_client)); 137 host_client_binding_.Bind(GetProxy(&host_client));
82 mojo::CreateViewTreeHost(host_factory, host_client.Pass(), this, &host_); 138 mojo::CreateViewTreeHost(host_factory, host_client.Pass(), this, &host_);
83 } 139 }
84 140
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
139 content_->SetVisible(true); 195 content_->SetVisible(true);
140 196
141 web_view_.Init(app_, content_); 197 web_view_.Init(app_, content_);
142 198
143 host_->AddAccelerator(static_cast<uint32_t>(BrowserCommand::CLOSE), 199 host_->AddAccelerator(static_cast<uint32_t>(BrowserCommand::CLOSE),
144 mojo::KEYBOARD_CODE_W, mojo::EVENT_FLAGS_CONTROL_DOWN); 200 mojo::KEYBOARD_CODE_W, mojo::EVENT_FLAGS_CONTROL_DOWN);
145 host_->AddAccelerator(static_cast<uint32_t>(BrowserCommand::FOCUS_OMNIBOX), 201 host_->AddAccelerator(static_cast<uint32_t>(BrowserCommand::FOCUS_OMNIBOX),
146 mojo::KEYBOARD_CODE_L, mojo::EVENT_FLAGS_CONTROL_DOWN); 202 mojo::KEYBOARD_CODE_L, mojo::EVENT_FLAGS_CONTROL_DOWN);
147 host_->AddAccelerator(static_cast<uint32_t>(BrowserCommand::NEW_WINDOW), 203 host_->AddAccelerator(static_cast<uint32_t>(BrowserCommand::NEW_WINDOW),
148 mojo::KEYBOARD_CODE_N, mojo::EVENT_FLAGS_CONTROL_DOWN); 204 mojo::KEYBOARD_CODE_N, mojo::EVENT_FLAGS_CONTROL_DOWN);
205 host_->AddAccelerator(static_cast<uint32_t>(BrowserCommand::GO_BACK),
206 mojo::KEYBOARD_CODE_LEFT, mojo::EVENT_FLAGS_ALT_DOWN);
207 host_->AddAccelerator(static_cast<uint32_t>(BrowserCommand::GO_FORWARD),
208 mojo::KEYBOARD_CODE_RIGHT, mojo::EVENT_FLAGS_ALT_DOWN);
149 209
150 // Now that we're ready, load the default url. 210 // Now that we're ready, load the default url.
151 LoadURL(default_url_); 211 LoadURL(default_url_);
152 212
153 // Record the time spent opening initial tabs, used for performance testing. 213 // Record the time spent opening initial tabs, used for performance testing.
154 const base::TimeDelta open_tabs_delta = base::Time::Now() - display_time; 214 const base::TimeDelta open_tabs_delta = base::Time::Now() - display_time;
155 215
156 // Record the browser startup time metrics, used for performance testing. 216 // Record the browser startup time metrics, used for performance testing.
157 static bool recorded_browser_startup_metrics = false; 217 static bool recorded_browser_startup_metrics = false;
158 if (!recorded_browser_startup_metrics && 218 if (!recorded_browser_startup_metrics &&
(...skipping 23 matching lines...) Expand all
182 switch (static_cast<BrowserCommand>(id)) { 242 switch (static_cast<BrowserCommand>(id)) {
183 case BrowserCommand::CLOSE: 243 case BrowserCommand::CLOSE:
184 Close(); 244 Close();
185 break; 245 break;
186 case BrowserCommand::NEW_WINDOW: 246 case BrowserCommand::NEW_WINDOW:
187 manager_->CreateBrowser(GURL()); 247 manager_->CreateBrowser(GURL());
188 break; 248 break;
189 case BrowserCommand::FOCUS_OMNIBOX: 249 case BrowserCommand::FOCUS_OMNIBOX:
190 ShowOmnibox(); 250 ShowOmnibox();
191 break; 251 break;
252 case BrowserCommand::GO_BACK:
253 GoBack();
254 break;
255 case BrowserCommand::GO_FORWARD:
256 GoForward();
257 break;
192 default: 258 default:
193 NOTREACHED(); 259 NOTREACHED();
194 break; 260 break;
195 } 261 }
196 } 262 }
197 263
198 //////////////////////////////////////////////////////////////////////////////// 264 ////////////////////////////////////////////////////////////////////////////////
199 // BrowserWindow, web_view::mojom::WebViewClient implementation: 265 // BrowserWindow, web_view::mojom::WebViewClient implementation:
200 266
201 void BrowserWindow::TopLevelNavigate(mojo::URLRequestPtr request) { 267 void BrowserWindow::TopLevelNavigate(mojo::URLRequestPtr request) {
202 Embed(request.Pass()); 268 Embed(request.Pass());
203 } 269 }
204 270
205 void BrowserWindow::LoadingStateChanged(bool is_loading) { 271 void BrowserWindow::LoadingStateChanged(bool is_loading) {
206 progress_bar_->SetIsLoading(is_loading); 272 progress_bar_->SetIsLoading(is_loading);
207 } 273 }
208 274
209 void BrowserWindow::ProgressChanged(double progress) { 275 void BrowserWindow::ProgressChanged(double progress) {
210 progress_bar_->SetProgress(progress); 276 progress_bar_->SetProgress(progress);
211 } 277 }
212 278
279 void BrowserWindow::BackForwardChanged(
280 web_view::mojom::ButtonState back_button,
281 web_view::mojom::ButtonState forward_button) {
282 toolbar_view_->SetBackForwardEnabled(
283 back_button == web_view::mojom::ButtonState::BUTTON_STATE_ENABLED,
284 forward_button == web_view::mojom::ButtonState::BUTTON_STATE_ENABLED);
285 }
286
213 void BrowserWindow::TitleChanged(const mojo::String& title) { 287 void BrowserWindow::TitleChanged(const mojo::String& title) {
214 base::string16 formatted = 288 base::string16 formatted =
215 title.is_null() ? base::ASCIIToUTF16("Untitled") 289 title.is_null() ? base::ASCIIToUTF16("Untitled")
216 : title.To<base::string16>() + 290 : title.To<base::string16>() +
217 base::ASCIIToUTF16(" - Mandoline"); 291 base::ASCIIToUTF16(" - Mandoline");
218 host_->SetTitle(mojo::String::From(formatted)); 292 host_->SetTitle(mojo::String::From(formatted));
219 } 293 }
220 294
221 //////////////////////////////////////////////////////////////////////////////// 295 ////////////////////////////////////////////////////////////////////////////////
222 // BrowserWindow, ViewEmbedder implementation: 296 // BrowserWindow, ViewEmbedder implementation:
223 297
224 void BrowserWindow::Embed(mojo::URLRequestPtr request) { 298 void BrowserWindow::Embed(mojo::URLRequestPtr request) {
225 const std::string string_url = request->url.To<std::string>(); 299 const std::string string_url = request->url.To<std::string>();
226 if (string_url == "mojo:omnibox") { 300 if (string_url == "mojo:omnibox") {
227 EmbedOmnibox(); 301 EmbedOmnibox();
228 return; 302 return;
229 } 303 }
230 304
231 GURL gurl(string_url); 305 GURL gurl(string_url);
232 bool changed = current_url_ != gurl; 306 bool changed = current_url_ != gurl;
233 current_url_ = gurl; 307 current_url_ = gurl;
234 if (changed) 308 if (changed)
235 omnibox_launcher_->SetText(base::UTF8ToUTF16(current_url_.spec())); 309 toolbar_view_->SetOmniboxText(base::UTF8ToUTF16(current_url_.spec()));
236 310
237 web_view_.web_view()->LoadRequest(request.Pass()); 311 web_view_.web_view()->LoadRequest(request.Pass());
238 } 312 }
239 313
240 //////////////////////////////////////////////////////////////////////////////// 314 ////////////////////////////////////////////////////////////////////////////////
241 // BrowserWindow, mojo::InterfaceFactory<ViewEmbedder> implementation: 315 // BrowserWindow, mojo::InterfaceFactory<ViewEmbedder> implementation:
242 316
243 void BrowserWindow::Create(mojo::ApplicationConnection* connection, 317 void BrowserWindow::Create(mojo::ApplicationConnection* connection,
244 mojo::InterfaceRequest<ViewEmbedder> request) { 318 mojo::InterfaceRequest<ViewEmbedder> request) {
245 view_embedder_bindings_.AddBinding(this, request.Pass()); 319 view_embedder_bindings_.AddBinding(this, request.Pass());
246 } 320 }
247 321
248 //////////////////////////////////////////////////////////////////////////////// 322 ////////////////////////////////////////////////////////////////////////////////
249 // BrowserWindow, views::LayoutManager implementation: 323 // BrowserWindow, views::LayoutManager implementation:
250 324
251 gfx::Size BrowserWindow::GetPreferredSize(const views::View* view) const { 325 gfx::Size BrowserWindow::GetPreferredSize(const views::View* view) const {
252 return gfx::Size(); 326 return gfx::Size();
253 } 327 }
254 328
255 void BrowserWindow::Layout(views::View* host) { 329 void BrowserWindow::Layout(views::View* host) {
256 // TODO(fsamuel): All bounds should be in physical pixels. 330 // TODO(fsamuel): All bounds should be in physical pixels.
257 gfx::Rect bounds_in_physical_pixels(host->bounds()); 331 gfx::Rect bounds_in_physical_pixels(host->bounds());
258 float inverse_device_pixel_ratio = 332 float inverse_device_pixel_ratio =
259 1.0f / root_->viewport_metrics().device_pixel_ratio; 333 1.0f / root_->viewport_metrics().device_pixel_ratio;
260 334
261 gfx::Rect omnibox_launcher_bounds = 335 gfx::Rect toolbar_bounds = gfx::ToEnclosingRect(
262 gfx::ToEnclosingRect(gfx::ScaleRect(bounds_in_physical_pixels, 336 gfx::ScaleRect(bounds_in_physical_pixels, inverse_device_pixel_ratio));
263 inverse_device_pixel_ratio)); 337 toolbar_bounds.Inset(10, 10, 10, toolbar_bounds.height() - 40);
264 omnibox_launcher_bounds.Inset(10, 10, 10, 338 toolbar_view_->SetBoundsRect(toolbar_bounds);
265 omnibox_launcher_bounds.height() - 40);
266 omnibox_launcher_->SetBoundsRect(omnibox_launcher_bounds);
267 339
268 gfx::Rect progress_bar_bounds(omnibox_launcher_bounds.x(), 340 gfx::Rect progress_bar_bounds(toolbar_bounds.x(), toolbar_bounds.bottom() + 2,
269 omnibox_launcher_bounds.bottom() + 2, 341 toolbar_bounds.width(), 5);
270 omnibox_launcher_bounds.width(),
271 5);
272 progress_bar_->SetBoundsRect(progress_bar_bounds); 342 progress_bar_->SetBoundsRect(progress_bar_bounds);
273 343
274 // The content view bounds are in physical pixels. 344 // The content view bounds are in physical pixels.
275 mojo::Rect content_bounds_mojo; 345 mojo::Rect content_bounds_mojo;
276 content_bounds_mojo.x = DIPSToPixels(progress_bar_bounds.x()); 346 content_bounds_mojo.x = DIPSToPixels(progress_bar_bounds.x());
277 content_bounds_mojo.y = DIPSToPixels(progress_bar_bounds.bottom()+ 10); 347 content_bounds_mojo.y = DIPSToPixels(progress_bar_bounds.bottom()+ 10);
278 content_bounds_mojo.width = DIPSToPixels(progress_bar_bounds.width()); 348 content_bounds_mojo.width = DIPSToPixels(progress_bar_bounds.width());
279 content_bounds_mojo.height = 349 content_bounds_mojo.height =
280 host->bounds().height() - content_bounds_mojo.y - DIPSToPixels(10); 350 host->bounds().height() - content_bounds_mojo.y - DIPSToPixels(10);
281 content_->SetBounds(content_bounds_mojo); 351 content_->SetBounds(content_bounds_mojo);
282 352
283 // The omnibox view bounds are in physical pixels. 353 // The omnibox view bounds are in physical pixels.
284 omnibox_view_->SetBounds( 354 omnibox_view_->SetBounds(
285 mojo::TypeConverter<mojo::Rect, gfx::Rect>::Convert( 355 mojo::TypeConverter<mojo::Rect, gfx::Rect>::Convert(
286 bounds_in_physical_pixels)); 356 bounds_in_physical_pixels));
287
288 } 357 }
289 358
290 //////////////////////////////////////////////////////////////////////////////// 359 ////////////////////////////////////////////////////////////////////////////////
291 // BrowserWindow, views::ButtonListener implementation:
292
293 void BrowserWindow::ButtonPressed(views::Button* sender,
294 const ui::Event& event) {
295 DCHECK_EQ(sender, omnibox_launcher_);
296 ShowOmnibox();
297 }
298
299 ////////////////////////////////////////////////////////////////////////////////
300 // BrowserWindow, private: 360 // BrowserWindow, private:
301 361
302 void BrowserWindow::Init(mojo::View* root) { 362 void BrowserWindow::Init(mojo::View* root) {
303 DCHECK_GT(root->viewport_metrics().device_pixel_ratio, 0); 363 DCHECK_GT(root->viewport_metrics().device_pixel_ratio, 0);
304 if (!aura_init_) 364 if (!aura_init_)
305 aura_init_.reset(new AuraInit(root, app_->shell())); 365 aura_init_.reset(new AuraInit(root, app_->shell()));
306 366
307 root_ = root; 367 root_ = root;
308 omnibox_view_ = root_->connection()->CreateView(); 368 omnibox_view_ = root_->connection()->CreateView();
309 root_->AddChild(omnibox_view_); 369 root_->AddChild(omnibox_view_);
310 370
311 views::WidgetDelegateView* widget_delegate = new views::WidgetDelegateView; 371 views::WidgetDelegateView* widget_delegate = new views::WidgetDelegateView;
312 widget_delegate->GetContentsView()->set_background( 372 widget_delegate->GetContentsView()->set_background(
313 views::Background::CreateSolidBackground(0xFFDDDDDD)); 373 views::Background::CreateSolidBackground(0xFFDDDDDD));
314 omnibox_launcher_ = 374 toolbar_view_ = new ToolbarView(this);
315 new views::LabelButton(this, base::ASCIIToUTF16("Open Omnibox"));
316 progress_bar_ = new ProgressView; 375 progress_bar_ = new ProgressView;
317 376 widget_delegate->GetContentsView()->AddChildView(toolbar_view_);
318 widget_delegate->GetContentsView()->AddChildView(omnibox_launcher_);
319 widget_delegate->GetContentsView()->AddChildView(progress_bar_); 377 widget_delegate->GetContentsView()->AddChildView(progress_bar_);
320 widget_delegate->GetContentsView()->SetLayoutManager(this); 378 widget_delegate->GetContentsView()->SetLayoutManager(this);
321 379
322 views::Widget* widget = new views::Widget; 380 views::Widget* widget = new views::Widget;
323 views::Widget::InitParams params( 381 views::Widget::InitParams params(
324 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); 382 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
325 params.native_widget = 383 params.native_widget =
326 new NativeWidgetViewManager(widget, app_->shell(), root_); 384 new NativeWidgetViewManager(widget, app_->shell(), root_);
327 params.delegate = widget_delegate; 385 params.delegate = widget_delegate;
328 params.bounds = root_->bounds().To<gfx::Rect>(); 386 params.bounds = root_->bounds().To<gfx::Rect>();
(...skipping 12 matching lines...) Expand all
341 omnibox_connection_->SetRemoteServiceProviderConnectionErrorHandler( 399 omnibox_connection_->SetRemoteServiceProviderConnectionErrorHandler(
342 [this]() { 400 [this]() {
343 // This will cause the connection to be re-established the next time 401 // This will cause the connection to be re-established the next time
344 // we come through this codepath. 402 // we come through this codepath.
345 omnibox_.reset(); 403 omnibox_.reset();
346 }); 404 });
347 } 405 }
348 omnibox_->ShowForURL(mojo::String::From(current_url_.spec())); 406 omnibox_->ShowForURL(mojo::String::From(current_url_.spec()));
349 } 407 }
350 408
409 void BrowserWindow::GoBack() {
410 web_view_.web_view()->GoBack();
411 }
412
413 void BrowserWindow::GoForward() {
414 web_view_.web_view()->GoForward();
415 }
416
351 void BrowserWindow::EmbedOmnibox() { 417 void BrowserWindow::EmbedOmnibox() {
352 mojo::ViewTreeClientPtr view_tree_client; 418 mojo::ViewTreeClientPtr view_tree_client;
353 omnibox_->GetViewTreeClient(GetProxy(&view_tree_client)); 419 omnibox_->GetViewTreeClient(GetProxy(&view_tree_client));
354 omnibox_view_->Embed(view_tree_client.Pass()); 420 omnibox_view_->Embed(view_tree_client.Pass());
355 421
356 // TODO(beng): This should be handled sufficiently by 422 // TODO(beng): This should be handled sufficiently by
357 // OmniboxImpl::ShowWindow() but unfortunately view manager policy 423 // OmniboxImpl::ShowWindow() but unfortunately view manager policy
358 // currently prevents the embedded app from changing window z for 424 // currently prevents the embedded app from changing window z for
359 // its own window. 425 // its own window.
360 omnibox_view_->MoveToFront(); 426 omnibox_view_->MoveToFront();
361 } 427 }
362 428
363 } // namespace mandoline 429 } // namespace mandoline
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698