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

Side by Side Diff: ui/events/event.cc

Issue 2394593002: revert "SourceEventType added to LatencyInfo." (Closed)
Patch Set: Created 4 years, 2 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 | « ui/events/blink/web_input_event_traits.cc ('k') | ui/events/event_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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/events/event.h" 5 #include "ui/events/event.h"
6 6
7 #include <utility> 7 #include <utility>
8 8
9 #include "base/memory/ptr_util.h" 9 #include "base/memory/ptr_util.h"
10 10
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after
132 CASE_TYPE(ET_UMA_DATA); 132 CASE_TYPE(ET_UMA_DATA);
133 case ui::ET_LAST: NOTREACHED(); return std::string(); 133 case ui::ET_LAST: NOTREACHED(); return std::string();
134 // Don't include default, so that we get an error when new type is added. 134 // Don't include default, so that we get an error when new type is added.
135 } 135 }
136 #undef CASE_TYPE 136 #undef CASE_TYPE
137 137
138 NOTREACHED(); 138 NOTREACHED();
139 return std::string(); 139 return std::string();
140 } 140 }
141 141
142 ui::SourceEventType EventTypeToLatencySourceEventType(ui::EventType type) {
143 switch (type) {
144 case ui::ET_UNKNOWN:
145 // SourceEventType for PointerEvents/GestureEvents can be either TOUCH or
146 // WHEEL. The proper value is assigned in the constructors.
147 case ui::ET_POINTER_DOWN:
148 case ui::ET_POINTER_MOVED:
149 case ui::ET_POINTER_UP:
150 case ui::ET_POINTER_CANCELLED:
151 case ui::ET_POINTER_ENTERED:
152 case ui::ET_POINTER_EXITED:
153 case ui::ET_POINTER_CAPTURE_CHANGED:
154 case ui::ET_GESTURE_SCROLL_BEGIN:
155 case ui::ET_GESTURE_SCROLL_END:
156 case ui::ET_GESTURE_SCROLL_UPDATE:
157 case ui::ET_GESTURE_TAP:
158 case ui::ET_GESTURE_TAP_DOWN:
159 case ui::ET_GESTURE_TAP_CANCEL:
160 case ui::ET_GESTURE_TAP_UNCONFIRMED:
161 case ui::ET_GESTURE_DOUBLE_TAP:
162 case ui::ET_GESTURE_BEGIN:
163 case ui::ET_GESTURE_END:
164 case ui::ET_GESTURE_TWO_FINGER_TAP:
165 case ui::ET_GESTURE_PINCH_BEGIN:
166 case ui::ET_GESTURE_PINCH_END:
167 case ui::ET_GESTURE_PINCH_UPDATE:
168 case ui::ET_GESTURE_LONG_PRESS:
169 case ui::ET_GESTURE_LONG_TAP:
170 case ui::ET_GESTURE_SWIPE:
171 case ui::ET_GESTURE_SHOW_PRESS:
172 // Flings can be GestureEvents too.
173 case ui::ET_SCROLL_FLING_START:
174 case ui::ET_SCROLL_FLING_CANCEL:
175 return ui::SourceEventType::UNKNOWN;
176
177 case ui::ET_MOUSE_PRESSED:
178 case ui::ET_MOUSE_DRAGGED:
179 case ui::ET_MOUSE_RELEASED:
180 case ui::ET_MOUSE_MOVED:
181 case ui::ET_MOUSE_ENTERED:
182 case ui::ET_MOUSE_EXITED:
183 case ui::ET_KEY_PRESSED:
184 case ui::ET_KEY_RELEASED:
185 case ui::ET_MOUSE_CAPTURE_CHANGED:
186 case ui::ET_DROP_TARGET_EVENT:
187 case ui::ET_CANCEL_MODE:
188 case ui::ET_UMA_DATA:
189 return ui::SourceEventType::OTHER;
190
191 case ui::ET_TOUCH_RELEASED:
192 case ui::ET_TOUCH_PRESSED:
193 case ui::ET_TOUCH_MOVED:
194 case ui::ET_TOUCH_CANCELLED:
195 return ui::SourceEventType::TOUCH;
196
197 case ui::ET_MOUSEWHEEL:
198 case ui::ET_POINTER_WHEEL_CHANGED:
199 case ui::ET_SCROLL:
200 return ui::SourceEventType::WHEEL;
201
202 case ui::ET_LAST:
203 NOTREACHED();
204 return ui::SourceEventType::UNKNOWN;
205 }
206 NOTREACHED();
207 return ui::SourceEventType::UNKNOWN;
208 }
209
210 bool IsX11SendEventTrue(const base::NativeEvent& event) { 142 bool IsX11SendEventTrue(const base::NativeEvent& event) {
211 #if defined(USE_X11) 143 #if defined(USE_X11)
212 return event && event->xany.send_event; 144 return event && event->xany.send_event;
213 #else 145 #else
214 return false; 146 return false;
215 #endif 147 #endif
216 } 148 }
217 149
218 bool X11EventHasNonStandardState(const base::NativeEvent& event) { 150 bool X11EventHasNonStandardState(const base::NativeEvent& event) {
219 #if defined(USE_X11) 151 #if defined(USE_X11)
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after
404 : type_(type), 336 : type_(type),
405 time_stamp_(time_stamp), 337 time_stamp_(time_stamp),
406 flags_(flags), 338 flags_(flags),
407 native_event_(base::NativeEvent()), 339 native_event_(base::NativeEvent()),
408 delete_native_event_(false), 340 delete_native_event_(false),
409 cancelable_(true), 341 cancelable_(true),
410 target_(NULL), 342 target_(NULL),
411 phase_(EP_PREDISPATCH), 343 phase_(EP_PREDISPATCH),
412 result_(ER_UNHANDLED), 344 result_(ER_UNHANDLED),
413 source_device_id_(ED_UNKNOWN_DEVICE) { 345 source_device_id_(ED_UNKNOWN_DEVICE) {
414 if (type_ < ET_LAST) { 346 if (type_ < ET_LAST)
415 latency()->set_source_event_type(EventTypeToLatencySourceEventType(type));
416 name_ = EventTypeName(type_); 347 name_ = EventTypeName(type_);
417 }
418 } 348 }
419 349
420 Event::Event(const base::NativeEvent& native_event, 350 Event::Event(const base::NativeEvent& native_event,
421 EventType type, 351 EventType type,
422 int flags) 352 int flags)
423 : type_(type), 353 : type_(type),
424 time_stamp_(EventTimeFromNative(native_event)), 354 time_stamp_(EventTimeFromNative(native_event)),
425 flags_(flags), 355 flags_(flags),
426 native_event_(native_event), 356 native_event_(native_event),
427 delete_native_event_(false), 357 delete_native_event_(false),
428 cancelable_(true), 358 cancelable_(true),
429 target_(NULL), 359 target_(NULL),
430 phase_(EP_PREDISPATCH), 360 phase_(EP_PREDISPATCH),
431 result_(ER_UNHANDLED), 361 result_(ER_UNHANDLED),
432 source_device_id_(ED_UNKNOWN_DEVICE) { 362 source_device_id_(ED_UNKNOWN_DEVICE) {
433 base::TimeDelta delta = EventTimeForNow() - time_stamp_; 363 base::TimeDelta delta = EventTimeForNow() - time_stamp_;
434 if (type_ < ET_LAST) { 364 if (type_ < ET_LAST)
435 latency()->set_source_event_type(EventTypeToLatencySourceEventType(type));
436 name_ = EventTypeName(type_); 365 name_ = EventTypeName(type_);
437 }
438 base::HistogramBase::Sample delta_sample = 366 base::HistogramBase::Sample delta_sample =
439 static_cast<base::HistogramBase::Sample>(delta.InMicroseconds()); 367 static_cast<base::HistogramBase::Sample>(delta.InMicroseconds());
440 UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser", delta_sample, 1, 1000000, 368 UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser", delta_sample, 1, 1000000,
441 100); 369 100);
442 ComputeEventLatencyOS(native_event); 370 ComputeEventLatencyOS(native_event);
443 371
444 // Though it seems inefficient to generate the string twice, the first 372 // Though it seems inefficient to generate the string twice, the first
445 // instance will be used only for DCHECK builds and the second won't be 373 // instance will be used only for DCHECK builds and the second won't be
446 // executed at all if the histogram was previously accessed here. 374 // executed at all if the histogram was previously accessed here.
447 STATIC_HISTOGRAM_POINTER_GROUP( 375 STATIC_HISTOGRAM_POINTER_GROUP(
(...skipping 30 matching lines...) Expand all
478 result_(ER_UNHANDLED), 406 result_(ER_UNHANDLED),
479 source_device_id_(copy.source_device_id_) { 407 source_device_id_(copy.source_device_id_) {
480 if (type_ < ET_LAST) 408 if (type_ < ET_LAST)
481 name_ = EventTypeName(type_); 409 name_ = EventTypeName(type_);
482 } 410 }
483 411
484 void Event::SetType(EventType type) { 412 void Event::SetType(EventType type) {
485 if (type_ < ET_LAST) 413 if (type_ < ET_LAST)
486 name_ = std::string(); 414 name_ = std::string();
487 type_ = type; 415 type_ = type;
488 if (type_ < ET_LAST) { 416 if (type_ < ET_LAST)
489 name_ = EventTypeName(type_); 417 name_ = EventTypeName(type_);
490 latency()->set_source_event_type(EventTypeToLatencySourceEventType(type));
491 }
492 } 418 }
493 419
494 //////////////////////////////////////////////////////////////////////////////// 420 ////////////////////////////////////////////////////////////////////////////////
495 // CancelModeEvent 421 // CancelModeEvent
496 422
497 CancelModeEvent::CancelModeEvent() 423 CancelModeEvent::CancelModeEvent()
498 : Event(ET_CANCEL_MODE, base::TimeTicks(), 0) { 424 : Event(ET_CANCEL_MODE, base::TimeTicks(), 0) {
499 set_cancelable(false); 425 set_cancelable(false);
500 } 426 }
501 427
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after
963 return true; 889 return true;
964 default: 890 default:
965 return false; 891 return false;
966 } 892 }
967 } 893 }
968 894
969 PointerEvent::PointerEvent(const PointerEvent& pointer_event) 895 PointerEvent::PointerEvent(const PointerEvent& pointer_event)
970 : LocatedEvent(pointer_event), 896 : LocatedEvent(pointer_event),
971 pointer_id_(pointer_event.pointer_id()), 897 pointer_id_(pointer_event.pointer_id()),
972 changed_button_flags_(pointer_event.changed_button_flags()), 898 changed_button_flags_(pointer_event.changed_button_flags()),
973 details_(pointer_event.pointer_details()) { 899 details_(pointer_event.pointer_details()) {}
974 if (details_.pointer_type == EventPointerType::POINTER_TYPE_TOUCH)
975 latency()->set_source_event_type(ui::SourceEventType::TOUCH);
976 else if (pointer_event.type() == ET_POINTER_WHEEL_CHANGED)
977 latency()->set_source_event_type(ui::SourceEventType::WHEEL);
978 else
979 latency()->set_source_event_type(ui::SourceEventType::OTHER);
980 }
981 900
982 PointerEvent::PointerEvent(const MouseEvent& mouse_event) 901 PointerEvent::PointerEvent(const MouseEvent& mouse_event)
983 : LocatedEvent(mouse_event), 902 : LocatedEvent(mouse_event),
984 pointer_id_(kMousePointerId), 903 pointer_id_(kMousePointerId),
985 changed_button_flags_(mouse_event.changed_button_flags()), 904 changed_button_flags_(mouse_event.changed_button_flags()),
986 details_(mouse_event.pointer_details()) { 905 details_(mouse_event.pointer_details()) {
987 DCHECK(CanConvertFrom(mouse_event)); 906 DCHECK(CanConvertFrom(mouse_event));
988 switch (mouse_event.type()) { 907 switch (mouse_event.type()) {
989 case ET_MOUSE_PRESSED: 908 case ET_MOUSE_PRESSED:
990 SetType(ET_POINTER_DOWN); 909 SetType(ET_POINTER_DOWN);
991 latency()->set_source_event_type(ui::SourceEventType::OTHER);
992 break; 910 break;
993 911
994 case ET_MOUSE_DRAGGED: 912 case ET_MOUSE_DRAGGED:
995 case ET_MOUSE_MOVED: 913 case ET_MOUSE_MOVED:
996 SetType(ET_POINTER_MOVED); 914 SetType(ET_POINTER_MOVED);
997 latency()->set_source_event_type(ui::SourceEventType::OTHER);
998 break; 915 break;
999 916
1000 case ET_MOUSE_ENTERED: 917 case ET_MOUSE_ENTERED:
1001 SetType(ET_POINTER_ENTERED); 918 SetType(ET_POINTER_ENTERED);
1002 latency()->set_source_event_type(ui::SourceEventType::OTHER);
1003 break; 919 break;
1004 920
1005 case ET_MOUSE_EXITED: 921 case ET_MOUSE_EXITED:
1006 SetType(ET_POINTER_EXITED); 922 SetType(ET_POINTER_EXITED);
1007 latency()->set_source_event_type(ui::SourceEventType::OTHER);
1008 break; 923 break;
1009 924
1010 case ET_MOUSE_RELEASED: 925 case ET_MOUSE_RELEASED:
1011 SetType(ET_POINTER_UP); 926 SetType(ET_POINTER_UP);
1012 latency()->set_source_event_type(ui::SourceEventType::OTHER);
1013 break; 927 break;
1014 928
1015 case ET_MOUSEWHEEL: 929 case ET_MOUSEWHEEL:
1016 SetType(ET_POINTER_WHEEL_CHANGED); 930 SetType(ET_POINTER_WHEEL_CHANGED);
1017 details_ = PointerDetails(EventPointerType::POINTER_TYPE_MOUSE, 931 details_ = PointerDetails(EventPointerType::POINTER_TYPE_MOUSE,
1018 mouse_event.AsMouseWheelEvent()->offset()); 932 mouse_event.AsMouseWheelEvent()->offset());
1019 latency()->set_source_event_type(ui::SourceEventType::WHEEL);
1020 break; 933 break;
1021 934
1022 case ET_MOUSE_CAPTURE_CHANGED: 935 case ET_MOUSE_CAPTURE_CHANGED:
1023 SetType(ET_POINTER_CAPTURE_CHANGED); 936 SetType(ET_POINTER_CAPTURE_CHANGED);
1024 break; 937 break;
1025 938
1026 default: 939 default:
1027 NOTREACHED(); 940 NOTREACHED();
1028 } 941 }
1029 } 942 }
(...skipping 17 matching lines...) Expand all
1047 SetType(ET_POINTER_UP); 960 SetType(ET_POINTER_UP);
1048 break; 961 break;
1049 962
1050 case ET_TOUCH_CANCELLED: 963 case ET_TOUCH_CANCELLED:
1051 SetType(ET_POINTER_CANCELLED); 964 SetType(ET_POINTER_CANCELLED);
1052 break; 965 break;
1053 966
1054 default: 967 default:
1055 NOTREACHED(); 968 NOTREACHED();
1056 } 969 }
1057 latency()->set_source_event_type(ui::SourceEventType::TOUCH);
1058 } 970 }
1059 971
1060 PointerEvent::PointerEvent(EventType type, 972 PointerEvent::PointerEvent(EventType type,
1061 const gfx::Point& location, 973 const gfx::Point& location,
1062 const gfx::Point& root_location, 974 const gfx::Point& root_location,
1063 int flags, 975 int flags,
1064 int pointer_id, 976 int pointer_id,
1065 int changed_button_flags, 977 int changed_button_flags,
1066 const PointerDetails& pointer_details, 978 const PointerDetails& pointer_details,
1067 base::TimeTicks time_stamp) 979 base::TimeTicks time_stamp)
1068 : LocatedEvent(type, 980 : LocatedEvent(type,
1069 gfx::PointF(location), 981 gfx::PointF(location),
1070 gfx::PointF(root_location), 982 gfx::PointF(root_location),
1071 time_stamp, 983 time_stamp,
1072 flags), 984 flags),
1073 pointer_id_(pointer_id), 985 pointer_id_(pointer_id),
1074 changed_button_flags_(changed_button_flags), 986 changed_button_flags_(changed_button_flags),
1075 details_(pointer_details) { 987 details_(pointer_details) {}
1076 if (details_.pointer_type == EventPointerType::POINTER_TYPE_TOUCH)
1077 latency()->set_source_event_type(ui::SourceEventType::TOUCH);
1078 else if (type == ET_POINTER_WHEEL_CHANGED)
1079 latency()->set_source_event_type(ui::SourceEventType::WHEEL);
1080 else
1081 latency()->set_source_event_type(ui::SourceEventType::OTHER);
1082 }
1083 988
1084 const int PointerEvent::kMousePointerId = std::numeric_limits<int32_t>::max(); 989 const int PointerEvent::kMousePointerId = std::numeric_limits<int32_t>::max();
1085 990
1086 //////////////////////////////////////////////////////////////////////////////// 991 ////////////////////////////////////////////////////////////////////////////////
1087 // KeyEvent 992 // KeyEvent
1088 993
1089 // static 994 // static
1090 KeyEvent* KeyEvent::last_key_event_ = NULL; 995 KeyEvent* KeyEvent::last_key_event_ = NULL;
1091 996
1092 // static 997 // static
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
1195 } 1100 }
1196 1101
1197 KeyEvent& KeyEvent::operator=(const KeyEvent& rhs) { 1102 KeyEvent& KeyEvent::operator=(const KeyEvent& rhs) {
1198 if (this != &rhs) { 1103 if (this != &rhs) {
1199 Event::operator=(rhs); 1104 Event::operator=(rhs);
1200 key_code_ = rhs.key_code_; 1105 key_code_ = rhs.key_code_;
1201 code_ = rhs.code_; 1106 code_ = rhs.code_;
1202 key_ = rhs.key_; 1107 key_ = rhs.key_;
1203 is_char_ = rhs.is_char_; 1108 is_char_ = rhs.is_char_;
1204 } 1109 }
1205 latency()->set_source_event_type(ui::SourceEventType::OTHER);
1206 return *this; 1110 return *this;
1207 } 1111 }
1208 1112
1209 KeyEvent::~KeyEvent() {} 1113 KeyEvent::~KeyEvent() {}
1210 1114
1211 void KeyEvent::ApplyLayout() const { 1115 void KeyEvent::ApplyLayout() const {
1212 ui::DomCode code = code_; 1116 ui::DomCode code = code_;
1213 if (code == DomCode::NONE) { 1117 if (code == DomCode::NONE) {
1214 // Catch old code that tries to do layout without a physical key, and try 1118 // Catch old code that tries to do layout without a physical key, and try
1215 // to recover using the KeyboardCode. Once key events are fully defined 1119 // to recover using the KeyboardCode. Once key events are fully defined
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
1371 } else if (type() == ET_SCROLL_FLING_START || 1275 } else if (type() == ET_SCROLL_FLING_START ||
1372 type() == ET_SCROLL_FLING_CANCEL) { 1276 type() == ET_SCROLL_FLING_CANCEL) {
1373 GetFlingData(native_event, 1277 GetFlingData(native_event,
1374 &x_offset_, &y_offset_, 1278 &x_offset_, &y_offset_,
1375 &x_offset_ordinal_, &y_offset_ordinal_, 1279 &x_offset_ordinal_, &y_offset_ordinal_,
1376 NULL); 1280 NULL);
1377 } else { 1281 } else {
1378 NOTREACHED() << "Unexpected event type " << type() 1282 NOTREACHED() << "Unexpected event type " << type()
1379 << " when constructing a ScrollEvent."; 1283 << " when constructing a ScrollEvent.";
1380 } 1284 }
1381 if (IsScrollEvent())
1382 latency()->set_source_event_type(ui::SourceEventType::WHEEL);
1383 else
1384 latency()->set_source_event_type(ui::SourceEventType::TOUCH);
1385 } 1285 }
1386 1286
1387 ScrollEvent::ScrollEvent(EventType type, 1287 ScrollEvent::ScrollEvent(EventType type,
1388 const gfx::Point& location, 1288 const gfx::Point& location,
1389 base::TimeTicks time_stamp, 1289 base::TimeTicks time_stamp,
1390 int flags, 1290 int flags,
1391 float x_offset, 1291 float x_offset,
1392 float y_offset, 1292 float y_offset,
1393 float x_offset_ordinal, 1293 float x_offset_ordinal,
1394 float y_offset_ordinal, 1294 float y_offset_ordinal,
1395 int finger_count) 1295 int finger_count)
1396 : MouseEvent(type, location, location, time_stamp, flags, 0), 1296 : MouseEvent(type, location, location, time_stamp, flags, 0),
1397 x_offset_(x_offset), 1297 x_offset_(x_offset),
1398 y_offset_(y_offset), 1298 y_offset_(y_offset),
1399 x_offset_ordinal_(x_offset_ordinal), 1299 x_offset_ordinal_(x_offset_ordinal),
1400 y_offset_ordinal_(y_offset_ordinal), 1300 y_offset_ordinal_(y_offset_ordinal),
1401 finger_count_(finger_count) { 1301 finger_count_(finger_count) {
1402 CHECK(IsScrollEvent()); 1302 CHECK(IsScrollEvent());
1403 latency()->set_source_event_type(ui::SourceEventType::WHEEL);
1404 } 1303 }
1405 1304
1406 void ScrollEvent::Scale(const float factor) { 1305 void ScrollEvent::Scale(const float factor) {
1407 x_offset_ *= factor; 1306 x_offset_ *= factor;
1408 y_offset_ *= factor; 1307 y_offset_ *= factor;
1409 x_offset_ordinal_ *= factor; 1308 x_offset_ordinal_ *= factor;
1410 y_offset_ordinal_ *= factor; 1309 y_offset_ordinal_ *= factor;
1411 } 1310 }
1412 1311
1413 //////////////////////////////////////////////////////////////////////////////// 1312 ////////////////////////////////////////////////////////////////////////////////
1414 // GestureEvent 1313 // GestureEvent
1415 1314
1416 GestureEvent::GestureEvent(float x, 1315 GestureEvent::GestureEvent(float x,
1417 float y, 1316 float y,
1418 int flags, 1317 int flags,
1419 base::TimeTicks time_stamp, 1318 base::TimeTicks time_stamp,
1420 const GestureEventDetails& details, 1319 const GestureEventDetails& details,
1421 uint32_t unique_touch_event_id) 1320 uint32_t unique_touch_event_id)
1422 : LocatedEvent(details.type(), 1321 : LocatedEvent(details.type(),
1423 gfx::PointF(x, y), 1322 gfx::PointF(x, y),
1424 gfx::PointF(x, y), 1323 gfx::PointF(x, y),
1425 time_stamp, 1324 time_stamp,
1426 flags | EF_FROM_TOUCH), 1325 flags | EF_FROM_TOUCH),
1427 details_(details), 1326 details_(details),
1428 unique_touch_event_id_(unique_touch_event_id) { 1327 unique_touch_event_id_(unique_touch_event_id) {}
1429 latency()->set_source_event_type(ui::SourceEventType::TOUCH);
1430 }
1431 1328
1432 GestureEvent::~GestureEvent() { 1329 GestureEvent::~GestureEvent() {
1433 } 1330 }
1434 1331
1435 } // namespace ui 1332 } // namespace ui
OLDNEW
« no previous file with comments | « ui/events/blink/web_input_event_traits.cc ('k') | ui/events/event_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698