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

Side by Side Diff: ui/views/controls/button/menu_button_unittest.cc

Issue 1670473002: MacViews: Fix views_unittests MenuButtonTest.{ActivateDropDownOnMouseClick,DraggableMenuButtonActiv… (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Make EventGenerator private. Created 4 years, 10 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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 "ui/views/controls/button/menu_button.h" 5 #include "ui/views/controls/button/menu_button.h"
6 6
7 #include "base/macros.h" 7 #include "base/macros.h"
8 #include "base/memory/scoped_ptr.h" 8 #include "base/memory/scoped_ptr.h"
9 #include "base/strings/utf_string_conversions.h" 9 #include "base/strings/utf_string_conversions.h"
10 #include "build/build_config.h" 10 #include "build/build_config.h"
(...skipping 12 matching lines...) Expand all
23 using base::ASCIIToUTF16; 23 using base::ASCIIToUTF16;
24 24
25 namespace views { 25 namespace views {
26 26
27 class MenuButtonTest : public ViewsTestBase { 27 class MenuButtonTest : public ViewsTestBase {
28 public: 28 public:
29 MenuButtonTest() : widget_(nullptr), button_(nullptr) {} 29 MenuButtonTest() : widget_(nullptr), button_(nullptr) {}
30 ~MenuButtonTest() override {} 30 ~MenuButtonTest() override {}
31 31
32 void TearDown() override { 32 void TearDown() override {
33 generator_.reset();
33 if (widget_ && !widget_->IsClosed()) 34 if (widget_ && !widget_->IsClosed())
34 widget_->Close(); 35 widget_->Close();
35 36
36 ViewsTestBase::TearDown(); 37 ViewsTestBase::TearDown();
37 } 38 }
38 39
39 Widget* widget() { return widget_; } 40 Widget* widget() { return widget_; }
40 MenuButton* button() { return button_; } 41 MenuButton* button() { return button_; }
42 ui::test::EventGenerator* generator() { return generator_.get(); }
41 43
42 protected: 44 protected:
43 // Creates a MenuButton with no button listener. 45 // Creates a MenuButton with no button listener.
44 void CreateMenuButtonWithNoListener() { CreateMenuButton(nullptr); } 46 void CreateMenuButtonWithNoListener() { CreateMenuButton(nullptr); }
45 47
46 // Creates a MenuButton with a MenuButtonListener. In this case, when the 48 // Creates a MenuButton with a MenuButtonListener. In this case, when the
47 // MenuButton is pushed, it notifies the MenuButtonListener to open a 49 // MenuButton is pushed, it notifies the MenuButtonListener to open a
48 // drop-down menu. 50 // drop-down menu.
49 void CreateMenuButtonWithMenuButtonListener( 51 void CreateMenuButtonWithMenuButtonListener(
50 MenuButtonListener* menu_button_listener) { 52 MenuButtonListener* menu_button_listener) {
51 CreateMenuButton(menu_button_listener); 53 CreateMenuButton(menu_button_listener);
52 } 54 }
53 55
54 private: 56 private:
55 void CreateMenuButton(MenuButtonListener* menu_button_listener) { 57 void CreateMenuButton(MenuButtonListener* menu_button_listener) {
56 CreateWidget(); 58 CreateWidget();
59 generator_.reset(new ui::test::EventGenerator(GetContext(),
60 widget_->GetNativeWindow()));
61 // Set initial mouse location in a consistent way so that the menu button we
62 // are about to create initializes its hover state in a consistent manner.
63 generator_->set_current_location(gfx::Point(10, 10));
57 64
58 const base::string16 label(ASCIIToUTF16("button")); 65 const base::string16 label(ASCIIToUTF16("button"));
59 button_ = new MenuButton(label, menu_button_listener, false); 66 button_ = new MenuButton(label, menu_button_listener, false);
60 button_->SetBoundsRect(gfx::Rect(0, 0, 200, 20)); 67 button_->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
61 widget_->SetContentsView(button_); 68 widget_->SetContentsView(button_);
62 69
63 widget_->Show(); 70 widget_->Show();
64 } 71 }
65 72
66 void CreateWidget() { 73 void CreateWidget() {
67 DCHECK(!widget_); 74 DCHECK(!widget_);
68 75
69 widget_ = new Widget; 76 widget_ = new Widget;
70 Widget::InitParams params = 77 Widget::InitParams params =
71 CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); 78 CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
72 params.bounds = gfx::Rect(0, 0, 200, 200); 79 params.bounds = gfx::Rect(0, 0, 200, 200);
73 widget_->Init(params); 80 widget_->Init(params);
74 } 81 }
75 82
76 Widget* widget_; 83 Widget* widget_;
77 MenuButton* button_; 84 MenuButton* button_;
85 scoped_ptr<ui::test::EventGenerator> generator_;
86
87 DISALLOW_COPY_AND_ASSIGN(MenuButtonTest);
78 }; 88 };
79 89
80 class TestButtonListener : public ButtonListener { 90 class TestButtonListener : public ButtonListener {
81 public: 91 public:
82 TestButtonListener() 92 TestButtonListener()
83 : last_sender_(nullptr), 93 : last_sender_(nullptr),
84 last_sender_state_(Button::STATE_NORMAL), 94 last_sender_state_(Button::STATE_NORMAL),
85 last_event_type_(ui::ET_UNKNOWN) {} 95 last_event_type_(ui::ET_UNKNOWN) {}
86 ~TestButtonListener() override {} 96 ~TestButtonListener() override {}
87 97
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
259 private: 269 private:
260 DISALLOW_COPY_AND_ASSIGN(TestShowSiblingButtonListener); 270 DISALLOW_COPY_AND_ASSIGN(TestShowSiblingButtonListener);
261 }; 271 };
262 272
263 // Tests if the listener is notified correctly when a mouse click happens on a 273 // Tests if the listener is notified correctly when a mouse click happens on a
264 // MenuButton that has a MenuButtonListener. 274 // MenuButton that has a MenuButtonListener.
265 TEST_F(MenuButtonTest, ActivateDropDownOnMouseClick) { 275 TEST_F(MenuButtonTest, ActivateDropDownOnMouseClick) {
266 TestMenuButtonListener menu_button_listener; 276 TestMenuButtonListener menu_button_listener;
267 CreateMenuButtonWithMenuButtonListener(&menu_button_listener); 277 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
268 278
269 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow()); 279 generator()->ClickLeftButton();
270
271 generator.set_current_location(gfx::Point(10, 10));
272 generator.ClickLeftButton();
273 280
274 // Check that MenuButton has notified the listener, while it was in pressed 281 // Check that MenuButton has notified the listener, while it was in pressed
275 // state. 282 // state.
276 EXPECT_EQ(button(), menu_button_listener.last_source()); 283 EXPECT_EQ(button(), menu_button_listener.last_source());
277 EXPECT_EQ(Button::STATE_HOVERED, menu_button_listener.last_source_state()); 284 EXPECT_EQ(Button::STATE_HOVERED, menu_button_listener.last_source_state());
278 } 285 }
279 286
280 // Test that the MenuButton stays pressed while there are any PressedLocks. 287 // Test that the MenuButton stays pressed while there are any PressedLocks.
281 TEST_F(MenuButtonTest, MenuButtonPressedLock) { 288 TEST_F(MenuButtonTest, MenuButtonPressedLock) {
282 CreateMenuButtonWithNoListener(); 289 CreateMenuButtonWithNoListener();
283 290
284 // Move the mouse over the button; the button should be in a hovered state. 291 // Move the mouse over the button; the button should be in a hovered state.
285 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow()); 292 generator()->MoveMouseTo(gfx::Point(10, 10));
286 generator.MoveMouseTo(gfx::Point(10, 10));
287 EXPECT_EQ(Button::STATE_HOVERED, button()->state()); 293 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
288 294
289 // Introduce a PressedLock, which should make the button pressed. 295 // Introduce a PressedLock, which should make the button pressed.
290 scoped_ptr<MenuButton::PressedLock> pressed_lock1( 296 scoped_ptr<MenuButton::PressedLock> pressed_lock1(
291 new MenuButton::PressedLock(button())); 297 new MenuButton::PressedLock(button()));
292 EXPECT_EQ(Button::STATE_PRESSED, button()->state()); 298 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
293 299
294 // Even if we move the mouse outside of the button, it should remain pressed. 300 // Even if we move the mouse outside of the button, it should remain pressed.
295 generator.MoveMouseTo(gfx::Point(300, 10)); 301 generator()->MoveMouseTo(gfx::Point(300, 10));
296 EXPECT_EQ(Button::STATE_PRESSED, button()->state()); 302 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
297 303
298 // Creating a new lock should obviously keep the button pressed. 304 // Creating a new lock should obviously keep the button pressed.
299 scoped_ptr<MenuButton::PressedLock> pressed_lock2( 305 scoped_ptr<MenuButton::PressedLock> pressed_lock2(
300 new MenuButton::PressedLock(button())); 306 new MenuButton::PressedLock(button()));
301 EXPECT_EQ(Button::STATE_PRESSED, button()->state()); 307 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
302 308
303 // The button should remain pressed while any locks are active. 309 // The button should remain pressed while any locks are active.
304 pressed_lock1.reset(); 310 pressed_lock1.reset();
305 EXPECT_EQ(Button::STATE_PRESSED, button()->state()); 311 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
306 312
307 // Reseting the final lock should return the button's state to normal... 313 // Reseting the final lock should return the button's state to normal...
308 pressed_lock2.reset(); 314 pressed_lock2.reset();
309 EXPECT_EQ(Button::STATE_NORMAL, button()->state()); 315 EXPECT_EQ(Button::STATE_NORMAL, button()->state());
310 316
311 // ...And it should respond to mouse movement again. 317 // ...And it should respond to mouse movement again.
312 generator.MoveMouseTo(gfx::Point(10, 10)); 318 generator()->MoveMouseTo(gfx::Point(10, 10));
313 EXPECT_EQ(Button::STATE_HOVERED, button()->state()); 319 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
314 320
315 // Test that the button returns to the appropriate state after the press; if 321 // Test that the button returns to the appropriate state after the press; if
316 // the mouse ends over the button, the button should be hovered. 322 // the mouse ends over the button, the button should be hovered.
317 pressed_lock1.reset(new MenuButton::PressedLock(button())); 323 pressed_lock1.reset(new MenuButton::PressedLock(button()));
318 EXPECT_EQ(Button::STATE_PRESSED, button()->state()); 324 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
319 pressed_lock1.reset(); 325 pressed_lock1.reset();
320 EXPECT_EQ(Button::STATE_HOVERED, button()->state()); 326 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
321 327
322 // If the button is disabled before the pressed lock, it should be disabled 328 // If the button is disabled before the pressed lock, it should be disabled
323 // after the pressed lock. 329 // after the pressed lock.
324 button()->SetState(Button::STATE_DISABLED); 330 button()->SetState(Button::STATE_DISABLED);
325 pressed_lock1.reset(new MenuButton::PressedLock(button())); 331 pressed_lock1.reset(new MenuButton::PressedLock(button()));
326 EXPECT_EQ(Button::STATE_PRESSED, button()->state()); 332 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
327 pressed_lock1.reset(); 333 pressed_lock1.reset();
328 EXPECT_EQ(Button::STATE_DISABLED, button()->state()); 334 EXPECT_EQ(Button::STATE_DISABLED, button()->state());
329 335
330 generator.MoveMouseTo(gfx::Point(300, 10)); 336 generator()->MoveMouseTo(gfx::Point(300, 10));
331 337
332 // Edge case: the button is disabled, a pressed lock is added, and then the 338 // Edge case: the button is disabled, a pressed lock is added, and then the
333 // button is re-enabled. It should be enabled after the lock is removed. 339 // button is re-enabled. It should be enabled after the lock is removed.
334 pressed_lock1.reset(new MenuButton::PressedLock(button())); 340 pressed_lock1.reset(new MenuButton::PressedLock(button()));
335 EXPECT_EQ(Button::STATE_PRESSED, button()->state()); 341 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
336 button()->SetState(Button::STATE_NORMAL); 342 button()->SetState(Button::STATE_NORMAL);
337 pressed_lock1.reset(); 343 pressed_lock1.reset();
338 EXPECT_EQ(Button::STATE_NORMAL, button()->state()); 344 EXPECT_EQ(Button::STATE_NORMAL, button()->state());
339 } 345 }
340 346
341 // Test that if a sibling menu is shown, the original menu button releases its 347 // Test that if a sibling menu is shown, the original menu button releases its
342 // PressedLock. 348 // PressedLock.
343 TEST_F(MenuButtonTest, PressedStateWithSiblingMenu) { 349 TEST_F(MenuButtonTest, PressedStateWithSiblingMenu) {
344 TestShowSiblingButtonListener listener; 350 TestShowSiblingButtonListener listener;
345 CreateMenuButtonWithMenuButtonListener(&listener); 351 CreateMenuButtonWithMenuButtonListener(&listener);
346 352
347 // Move the mouse over the button; the button should be in a hovered state. 353 // Move the mouse over the button; the button should be in a hovered state.
348 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow()); 354 generator()->MoveMouseTo(gfx::Point(10, 10));
349 generator.MoveMouseTo(gfx::Point(10, 10));
350 EXPECT_EQ(Button::STATE_HOVERED, button()->state()); 355 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
351 generator.ClickLeftButton(); 356 generator()->ClickLeftButton();
352 // Test is continued in TestShowSiblingButtonListener::OnMenuButtonClicked(). 357 // Test is continued in TestShowSiblingButtonListener::OnMenuButtonClicked().
353 } 358 }
354 359
355 // Test that the MenuButton does not become pressed if it can be dragged, until 360 // Test that the MenuButton does not become pressed if it can be dragged, until
356 // a release occurs. 361 // a release occurs.
357 TEST_F(MenuButtonTest, DraggableMenuButtonActivatesOnRelease) { 362 TEST_F(MenuButtonTest, DraggableMenuButtonActivatesOnRelease) {
358 TestMenuButtonListener menu_button_listener; 363 TestMenuButtonListener menu_button_listener;
359 CreateMenuButtonWithMenuButtonListener(&menu_button_listener); 364 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
360 TestDragController drag_controller; 365 TestDragController drag_controller;
361 button()->set_drag_controller(&drag_controller); 366 button()->set_drag_controller(&drag_controller);
362 367
363 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow()); 368 generator()->PressLeftButton();
364
365 generator.set_current_location(gfx::Point(10, 10));
366 generator.PressLeftButton();
367 EXPECT_EQ(nullptr, menu_button_listener.last_source()); 369 EXPECT_EQ(nullptr, menu_button_listener.last_source());
368 370
369 generator.ReleaseLeftButton(); 371 generator()->ReleaseLeftButton();
370 EXPECT_EQ(button(), menu_button_listener.last_source()); 372 EXPECT_EQ(button(), menu_button_listener.last_source());
371 EXPECT_EQ(Button::STATE_HOVERED, menu_button_listener.last_source_state()); 373 EXPECT_EQ(Button::STATE_HOVERED, menu_button_listener.last_source_state());
372 } 374 }
373 375
374 #if defined(USE_AURA) 376 #if defined(USE_AURA)
375 377
376 // Tests that the MenuButton does not become pressed if it can be dragged, and a 378 // Tests that the MenuButton does not become pressed if it can be dragged, and a
377 // DragDropClient is processing the events. 379 // DragDropClient is processing the events.
378 TEST_F(MenuButtonTest, DraggableMenuButtonDoesNotActivateOnDrag) { 380 TEST_F(MenuButtonTest, DraggableMenuButtonDoesNotActivateOnDrag) {
379 TestMenuButtonListener menu_button_listener; 381 TestMenuButtonListener menu_button_listener;
380 CreateMenuButtonWithMenuButtonListener(&menu_button_listener); 382 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
381 TestDragController drag_controller; 383 TestDragController drag_controller;
382 button()->set_drag_controller(&drag_controller); 384 button()->set_drag_controller(&drag_controller);
383 385
384 TestDragDropClient drag_client; 386 TestDragDropClient drag_client;
385 SetDragDropClient(GetContext(), &drag_client); 387 SetDragDropClient(GetContext(), &drag_client);
386 button()->PrependPreTargetHandler(&drag_client); 388 button()->PrependPreTargetHandler(&drag_client);
387 389
388 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow()); 390 generator()->DragMouseBy(10, 0);
389 generator.set_current_location(gfx::Point(10, 10));
390 generator.DragMouseBy(10, 0);
391 EXPECT_EQ(nullptr, menu_button_listener.last_source()); 391 EXPECT_EQ(nullptr, menu_button_listener.last_source());
392 EXPECT_EQ(Button::STATE_NORMAL, menu_button_listener.last_source_state()); 392 EXPECT_EQ(Button::STATE_NORMAL, menu_button_listener.last_source_state());
393 } 393 }
394 394
395 #endif // USE_AURA 395 #endif // USE_AURA
396 396
397 // No touch on desktop Mac. Tracked in http://crbug.com/445520. 397 // No touch on desktop Mac. Tracked in http://crbug.com/445520.
398 #if !defined(OS_MACOSX) || defined(USE_AURA) 398 #if !defined(OS_MACOSX) || defined(USE_AURA)
399 399
400 // Tests if the listener is notified correctly when a gesture tap happens on a 400 // Tests if the listener is notified correctly when a gesture tap happens on a
401 // MenuButton that has a MenuButtonListener. 401 // MenuButton that has a MenuButtonListener.
402 TEST_F(MenuButtonTest, ActivateDropDownOnGestureTap) { 402 TEST_F(MenuButtonTest, ActivateDropDownOnGestureTap) {
403 TestMenuButtonListener menu_button_listener; 403 TestMenuButtonListener menu_button_listener;
404 CreateMenuButtonWithMenuButtonListener(&menu_button_listener); 404 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
405 405
406 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
407
408 // Move the mouse outside the menu button so that it doesn't impact the 406 // Move the mouse outside the menu button so that it doesn't impact the
409 // button state. 407 // button state.
410 generator.MoveMouseTo(400, 400); 408 generator()->MoveMouseTo(400, 400);
411 EXPECT_FALSE(button()->IsMouseHovered()); 409 EXPECT_FALSE(button()->IsMouseHovered());
412 410
413 generator.GestureTapAt(gfx::Point(10, 10)); 411 generator()->GestureTapAt(gfx::Point(10, 10));
414 412
415 // Check that MenuButton has notified the listener, while it was in pressed 413 // Check that MenuButton has notified the listener, while it was in pressed
416 // state. 414 // state.
417 EXPECT_EQ(button(), menu_button_listener.last_source()); 415 EXPECT_EQ(button(), menu_button_listener.last_source());
418 EXPECT_EQ(Button::STATE_HOVERED, menu_button_listener.last_source_state()); 416 EXPECT_EQ(Button::STATE_HOVERED, menu_button_listener.last_source_state());
419 417
420 // The button should go back to it's normal state since the gesture ended. 418 // The button should go back to it's normal state since the gesture ended.
421 EXPECT_EQ(Button::STATE_NORMAL, button()->state()); 419 EXPECT_EQ(Button::STATE_NORMAL, button()->state());
422 } 420 }
423 421
424 // Tests that the button enters a hovered state upon a tap down, before becoming 422 // Tests that the button enters a hovered state upon a tap down, before becoming
425 // pressed at activation. 423 // pressed at activation.
426 TEST_F(MenuButtonTest, TouchFeedbackDuringTap) { 424 TEST_F(MenuButtonTest, TouchFeedbackDuringTap) {
427 TestMenuButtonListener menu_button_listener; 425 TestMenuButtonListener menu_button_listener;
428 CreateMenuButtonWithMenuButtonListener(&menu_button_listener); 426 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
429 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow()); 427 generator()->PressTouch();
430 generator.set_current_location(gfx::Point(10, 10));
431 generator.PressTouch();
432 EXPECT_EQ(Button::STATE_HOVERED, button()->state()); 428 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
433 429
434 generator.ReleaseTouch(); 430 generator()->ReleaseTouch();
435 EXPECT_EQ(Button::STATE_HOVERED, menu_button_listener.last_source_state()); 431 EXPECT_EQ(Button::STATE_HOVERED, menu_button_listener.last_source_state());
436 } 432 }
437 433
438 // Tests that a move event that exits the button returns it to the normal state, 434 // Tests that a move event that exits the button returns it to the normal state,
439 // and that the button did not activate the listener. 435 // and that the button did not activate the listener.
440 TEST_F(MenuButtonTest, TouchFeedbackDuringTapCancel) { 436 TEST_F(MenuButtonTest, TouchFeedbackDuringTapCancel) {
441 TestMenuButtonListener menu_button_listener; 437 TestMenuButtonListener menu_button_listener;
442 CreateMenuButtonWithMenuButtonListener(&menu_button_listener); 438 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
443 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow()); 439 generator()->PressTouch();
444 generator.set_current_location(gfx::Point(10, 10));
445 generator.PressTouch();
446 EXPECT_EQ(Button::STATE_HOVERED, button()->state()); 440 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
447 441
448 generator.MoveTouch(gfx::Point(10, 30)); 442 generator()->MoveTouch(gfx::Point(10, 30));
449 generator.ReleaseTouch(); 443 generator()->ReleaseTouch();
450 EXPECT_EQ(Button::STATE_NORMAL, button()->state()); 444 EXPECT_EQ(Button::STATE_NORMAL, button()->state());
451 EXPECT_EQ(nullptr, menu_button_listener.last_source()); 445 EXPECT_EQ(nullptr, menu_button_listener.last_source());
452 } 446 }
453 447
454 #endif // !defined(OS_MACOSX) || defined(USE_AURA) 448 #endif // !defined(OS_MACOSX) || defined(USE_AURA)
455 449
456 } // namespace views 450 } // namespace views
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698