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

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

Issue 2393783005: re-land the "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
142 bool IsX11SendEventTrue(const base::NativeEvent& event) { 210 bool IsX11SendEventTrue(const base::NativeEvent& event) {
143 #if defined(USE_X11) 211 #if defined(USE_X11)
144 return event && event->xany.send_event; 212 return event && event->xany.send_event;
145 #else 213 #else
146 return false; 214 return false;
147 #endif 215 #endif
148 } 216 }
149 217
150 bool X11EventHasNonStandardState(const base::NativeEvent& event) { 218 bool X11EventHasNonStandardState(const base::NativeEvent& event) {
151 #if defined(USE_X11) 219 #if defined(USE_X11)
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after
336 : type_(type), 404 : type_(type),
337 time_stamp_(time_stamp), 405 time_stamp_(time_stamp),
338 flags_(flags), 406 flags_(flags),
339 native_event_(base::NativeEvent()), 407 native_event_(base::NativeEvent()),
340 delete_native_event_(false), 408 delete_native_event_(false),
341 cancelable_(true), 409 cancelable_(true),
342 target_(NULL), 410 target_(NULL),
343 phase_(EP_PREDISPATCH), 411 phase_(EP_PREDISPATCH),
344 result_(ER_UNHANDLED), 412 result_(ER_UNHANDLED),
345 source_device_id_(ED_UNKNOWN_DEVICE) { 413 source_device_id_(ED_UNKNOWN_DEVICE) {
346 if (type_ < ET_LAST) 414 if (type_ < ET_LAST) {
415 latency()->set_source_event_type(EventTypeToLatencySourceEventType(type));
347 name_ = EventTypeName(type_); 416 name_ = EventTypeName(type_);
417 }
348 } 418 }
349 419
350 Event::Event(const base::NativeEvent& native_event, 420 Event::Event(const base::NativeEvent& native_event,
351 EventType type, 421 EventType type,
352 int flags) 422 int flags)
353 : type_(type), 423 : type_(type),
354 time_stamp_(EventTimeFromNative(native_event)), 424 time_stamp_(EventTimeFromNative(native_event)),
355 flags_(flags), 425 flags_(flags),
356 native_event_(native_event), 426 native_event_(native_event),
357 delete_native_event_(false), 427 delete_native_event_(false),
358 cancelable_(true), 428 cancelable_(true),
359 target_(NULL), 429 target_(NULL),
360 phase_(EP_PREDISPATCH), 430 phase_(EP_PREDISPATCH),
361 result_(ER_UNHANDLED), 431 result_(ER_UNHANDLED),
362 source_device_id_(ED_UNKNOWN_DEVICE) { 432 source_device_id_(ED_UNKNOWN_DEVICE) {
363 base::TimeDelta delta = EventTimeForNow() - time_stamp_; 433 base::TimeDelta delta = EventTimeForNow() - time_stamp_;
364 if (type_ < ET_LAST) 434 if (type_ < ET_LAST) {
435 latency()->set_source_event_type(EventTypeToLatencySourceEventType(type));
365 name_ = EventTypeName(type_); 436 name_ = EventTypeName(type_);
437 }
366 base::HistogramBase::Sample delta_sample = 438 base::HistogramBase::Sample delta_sample =
367 static_cast<base::HistogramBase::Sample>(delta.InMicroseconds()); 439 static_cast<base::HistogramBase::Sample>(delta.InMicroseconds());
368 UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser", delta_sample, 1, 1000000, 440 UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser", delta_sample, 1, 1000000,
369 100); 441 100);
370 ComputeEventLatencyOS(native_event); 442 ComputeEventLatencyOS(native_event);
371 443
372 // Though it seems inefficient to generate the string twice, the first 444 // Though it seems inefficient to generate the string twice, the first
373 // instance will be used only for DCHECK builds and the second won't be 445 // instance will be used only for DCHECK builds and the second won't be
374 // executed at all if the histogram was previously accessed here. 446 // executed at all if the histogram was previously accessed here.
375 STATIC_HISTOGRAM_POINTER_GROUP( 447 STATIC_HISTOGRAM_POINTER_GROUP(
(...skipping 30 matching lines...) Expand all
406 result_(ER_UNHANDLED), 478 result_(ER_UNHANDLED),
407 source_device_id_(copy.source_device_id_) { 479 source_device_id_(copy.source_device_id_) {
408 if (type_ < ET_LAST) 480 if (type_ < ET_LAST)
409 name_ = EventTypeName(type_); 481 name_ = EventTypeName(type_);
410 } 482 }
411 483
412 void Event::SetType(EventType type) { 484 void Event::SetType(EventType type) {
413 if (type_ < ET_LAST) 485 if (type_ < ET_LAST)
414 name_ = std::string(); 486 name_ = std::string();
415 type_ = type; 487 type_ = type;
416 if (type_ < ET_LAST) 488 if (type_ < ET_LAST) {
417 name_ = EventTypeName(type_); 489 name_ = EventTypeName(type_);
490 latency()->set_source_event_type(EventTypeToLatencySourceEventType(type));
491 }
418 } 492 }
419 493
420 //////////////////////////////////////////////////////////////////////////////// 494 ////////////////////////////////////////////////////////////////////////////////
421 // CancelModeEvent 495 // CancelModeEvent
422 496
423 CancelModeEvent::CancelModeEvent() 497 CancelModeEvent::CancelModeEvent()
424 : Event(ET_CANCEL_MODE, base::TimeTicks(), 0) { 498 : Event(ET_CANCEL_MODE, base::TimeTicks(), 0) {
425 set_cancelable(false); 499 set_cancelable(false);
426 } 500 }
427 501
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after
889 return true; 963 return true;
890 default: 964 default:
891 return false; 965 return false;
892 } 966 }
893 } 967 }
894 968
895 PointerEvent::PointerEvent(const PointerEvent& pointer_event) 969 PointerEvent::PointerEvent(const PointerEvent& pointer_event)
896 : LocatedEvent(pointer_event), 970 : LocatedEvent(pointer_event),
897 pointer_id_(pointer_event.pointer_id()), 971 pointer_id_(pointer_event.pointer_id()),
898 changed_button_flags_(pointer_event.changed_button_flags()), 972 changed_button_flags_(pointer_event.changed_button_flags()),
899 details_(pointer_event.pointer_details()) {} 973 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 }
900 981
901 PointerEvent::PointerEvent(const MouseEvent& mouse_event) 982 PointerEvent::PointerEvent(const MouseEvent& mouse_event)
902 : LocatedEvent(mouse_event), 983 : LocatedEvent(mouse_event),
903 pointer_id_(kMousePointerId), 984 pointer_id_(kMousePointerId),
904 changed_button_flags_(mouse_event.changed_button_flags()), 985 changed_button_flags_(mouse_event.changed_button_flags()),
905 details_(mouse_event.pointer_details()) { 986 details_(mouse_event.pointer_details()) {
906 DCHECK(CanConvertFrom(mouse_event)); 987 DCHECK(CanConvertFrom(mouse_event));
907 switch (mouse_event.type()) { 988 switch (mouse_event.type()) {
908 case ET_MOUSE_PRESSED: 989 case ET_MOUSE_PRESSED:
909 SetType(ET_POINTER_DOWN); 990 SetType(ET_POINTER_DOWN);
991 latency()->set_source_event_type(ui::SourceEventType::OTHER);
910 break; 992 break;
911 993
912 case ET_MOUSE_DRAGGED: 994 case ET_MOUSE_DRAGGED:
913 case ET_MOUSE_MOVED: 995 case ET_MOUSE_MOVED:
914 SetType(ET_POINTER_MOVED); 996 SetType(ET_POINTER_MOVED);
997 latency()->set_source_event_type(ui::SourceEventType::OTHER);
915 break; 998 break;
916 999
917 case ET_MOUSE_ENTERED: 1000 case ET_MOUSE_ENTERED:
918 SetType(ET_POINTER_ENTERED); 1001 SetType(ET_POINTER_ENTERED);
1002 latency()->set_source_event_type(ui::SourceEventType::OTHER);
919 break; 1003 break;
920 1004
921 case ET_MOUSE_EXITED: 1005 case ET_MOUSE_EXITED:
922 SetType(ET_POINTER_EXITED); 1006 SetType(ET_POINTER_EXITED);
1007 latency()->set_source_event_type(ui::SourceEventType::OTHER);
923 break; 1008 break;
924 1009
925 case ET_MOUSE_RELEASED: 1010 case ET_MOUSE_RELEASED:
926 SetType(ET_POINTER_UP); 1011 SetType(ET_POINTER_UP);
1012 latency()->set_source_event_type(ui::SourceEventType::OTHER);
927 break; 1013 break;
928 1014
929 case ET_MOUSEWHEEL: 1015 case ET_MOUSEWHEEL:
930 SetType(ET_POINTER_WHEEL_CHANGED); 1016 SetType(ET_POINTER_WHEEL_CHANGED);
931 details_ = PointerDetails(EventPointerType::POINTER_TYPE_MOUSE, 1017 details_ = PointerDetails(EventPointerType::POINTER_TYPE_MOUSE,
932 mouse_event.AsMouseWheelEvent()->offset()); 1018 mouse_event.AsMouseWheelEvent()->offset());
1019 latency()->set_source_event_type(ui::SourceEventType::WHEEL);
933 break; 1020 break;
934 1021
935 case ET_MOUSE_CAPTURE_CHANGED: 1022 case ET_MOUSE_CAPTURE_CHANGED:
936 SetType(ET_POINTER_CAPTURE_CHANGED); 1023 SetType(ET_POINTER_CAPTURE_CHANGED);
937 break; 1024 break;
938 1025
939 default: 1026 default:
940 NOTREACHED(); 1027 NOTREACHED();
941 } 1028 }
942 } 1029 }
(...skipping 17 matching lines...) Expand all
960 SetType(ET_POINTER_UP); 1047 SetType(ET_POINTER_UP);
961 break; 1048 break;
962 1049
963 case ET_TOUCH_CANCELLED: 1050 case ET_TOUCH_CANCELLED:
964 SetType(ET_POINTER_CANCELLED); 1051 SetType(ET_POINTER_CANCELLED);
965 break; 1052 break;
966 1053
967 default: 1054 default:
968 NOTREACHED(); 1055 NOTREACHED();
969 } 1056 }
1057 latency()->set_source_event_type(ui::SourceEventType::TOUCH);
970 } 1058 }
971 1059
972 PointerEvent::PointerEvent(EventType type, 1060 PointerEvent::PointerEvent(EventType type,
973 const gfx::Point& location, 1061 const gfx::Point& location,
974 const gfx::Point& root_location, 1062 const gfx::Point& root_location,
975 int flags, 1063 int flags,
976 int pointer_id, 1064 int pointer_id,
977 int changed_button_flags, 1065 int changed_button_flags,
978 const PointerDetails& pointer_details, 1066 const PointerDetails& pointer_details,
979 base::TimeTicks time_stamp) 1067 base::TimeTicks time_stamp)
980 : LocatedEvent(type, 1068 : LocatedEvent(type,
981 gfx::PointF(location), 1069 gfx::PointF(location),
982 gfx::PointF(root_location), 1070 gfx::PointF(root_location),
983 time_stamp, 1071 time_stamp,
984 flags), 1072 flags),
985 pointer_id_(pointer_id), 1073 pointer_id_(pointer_id),
986 changed_button_flags_(changed_button_flags), 1074 changed_button_flags_(changed_button_flags),
987 details_(pointer_details) {} 1075 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 }
988 1083
989 const int PointerEvent::kMousePointerId = std::numeric_limits<int32_t>::max(); 1084 const int PointerEvent::kMousePointerId = std::numeric_limits<int32_t>::max();
990 1085
991 //////////////////////////////////////////////////////////////////////////////// 1086 ////////////////////////////////////////////////////////////////////////////////
992 // KeyEvent 1087 // KeyEvent
993 1088
994 // static 1089 // static
995 KeyEvent* KeyEvent::last_key_event_ = NULL; 1090 KeyEvent* KeyEvent::last_key_event_ = NULL;
996 1091
997 // static 1092 // static
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
1100 } 1195 }
1101 1196
1102 KeyEvent& KeyEvent::operator=(const KeyEvent& rhs) { 1197 KeyEvent& KeyEvent::operator=(const KeyEvent& rhs) {
1103 if (this != &rhs) { 1198 if (this != &rhs) {
1104 Event::operator=(rhs); 1199 Event::operator=(rhs);
1105 key_code_ = rhs.key_code_; 1200 key_code_ = rhs.key_code_;
1106 code_ = rhs.code_; 1201 code_ = rhs.code_;
1107 key_ = rhs.key_; 1202 key_ = rhs.key_;
1108 is_char_ = rhs.is_char_; 1203 is_char_ = rhs.is_char_;
1109 } 1204 }
1205 latency()->set_source_event_type(ui::SourceEventType::OTHER);
1110 return *this; 1206 return *this;
1111 } 1207 }
1112 1208
1113 KeyEvent::~KeyEvent() {} 1209 KeyEvent::~KeyEvent() {}
1114 1210
1115 void KeyEvent::ApplyLayout() const { 1211 void KeyEvent::ApplyLayout() const {
1116 ui::DomCode code = code_; 1212 ui::DomCode code = code_;
1117 if (code == DomCode::NONE) { 1213 if (code == DomCode::NONE) {
1118 // Catch old code that tries to do layout without a physical key, and try 1214 // Catch old code that tries to do layout without a physical key, and try
1119 // to recover using the KeyboardCode. Once key events are fully defined 1215 // to recover using the KeyboardCode. Once key events are fully defined
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
1275 } else if (type() == ET_SCROLL_FLING_START || 1371 } else if (type() == ET_SCROLL_FLING_START ||
1276 type() == ET_SCROLL_FLING_CANCEL) { 1372 type() == ET_SCROLL_FLING_CANCEL) {
1277 GetFlingData(native_event, 1373 GetFlingData(native_event,
1278 &x_offset_, &y_offset_, 1374 &x_offset_, &y_offset_,
1279 &x_offset_ordinal_, &y_offset_ordinal_, 1375 &x_offset_ordinal_, &y_offset_ordinal_,
1280 NULL); 1376 NULL);
1281 } else { 1377 } else {
1282 NOTREACHED() << "Unexpected event type " << type() 1378 NOTREACHED() << "Unexpected event type " << type()
1283 << " when constructing a ScrollEvent."; 1379 << " when constructing a ScrollEvent.";
1284 } 1380 }
1381 if (IsScrollEvent())
1382 latency()->set_source_event_type(ui::SourceEventType::WHEEL);
1383 else
1384 latency()->set_source_event_type(ui::SourceEventType::TOUCH);
1285 } 1385 }
1286 1386
1287 ScrollEvent::ScrollEvent(EventType type, 1387 ScrollEvent::ScrollEvent(EventType type,
1288 const gfx::Point& location, 1388 const gfx::Point& location,
1289 base::TimeTicks time_stamp, 1389 base::TimeTicks time_stamp,
1290 int flags, 1390 int flags,
1291 float x_offset, 1391 float x_offset,
1292 float y_offset, 1392 float y_offset,
1293 float x_offset_ordinal, 1393 float x_offset_ordinal,
1294 float y_offset_ordinal, 1394 float y_offset_ordinal,
1295 int finger_count) 1395 int finger_count)
1296 : MouseEvent(type, location, location, time_stamp, flags, 0), 1396 : MouseEvent(type, location, location, time_stamp, flags, 0),
1297 x_offset_(x_offset), 1397 x_offset_(x_offset),
1298 y_offset_(y_offset), 1398 y_offset_(y_offset),
1299 x_offset_ordinal_(x_offset_ordinal), 1399 x_offset_ordinal_(x_offset_ordinal),
1300 y_offset_ordinal_(y_offset_ordinal), 1400 y_offset_ordinal_(y_offset_ordinal),
1301 finger_count_(finger_count) { 1401 finger_count_(finger_count) {
1302 CHECK(IsScrollEvent()); 1402 CHECK(IsScrollEvent());
1403 latency()->set_source_event_type(ui::SourceEventType::WHEEL);
1303 } 1404 }
1304 1405
1305 void ScrollEvent::Scale(const float factor) { 1406 void ScrollEvent::Scale(const float factor) {
1306 x_offset_ *= factor; 1407 x_offset_ *= factor;
1307 y_offset_ *= factor; 1408 y_offset_ *= factor;
1308 x_offset_ordinal_ *= factor; 1409 x_offset_ordinal_ *= factor;
1309 y_offset_ordinal_ *= factor; 1410 y_offset_ordinal_ *= factor;
1310 } 1411 }
1311 1412
1312 //////////////////////////////////////////////////////////////////////////////// 1413 ////////////////////////////////////////////////////////////////////////////////
1313 // GestureEvent 1414 // GestureEvent
1314 1415
1315 GestureEvent::GestureEvent(float x, 1416 GestureEvent::GestureEvent(float x,
1316 float y, 1417 float y,
1317 int flags, 1418 int flags,
1318 base::TimeTicks time_stamp, 1419 base::TimeTicks time_stamp,
1319 const GestureEventDetails& details, 1420 const GestureEventDetails& details,
1320 uint32_t unique_touch_event_id) 1421 uint32_t unique_touch_event_id)
1321 : LocatedEvent(details.type(), 1422 : LocatedEvent(details.type(),
1322 gfx::PointF(x, y), 1423 gfx::PointF(x, y),
1323 gfx::PointF(x, y), 1424 gfx::PointF(x, y),
1324 time_stamp, 1425 time_stamp,
1325 flags | EF_FROM_TOUCH), 1426 flags | EF_FROM_TOUCH),
1326 details_(details), 1427 details_(details),
1327 unique_touch_event_id_(unique_touch_event_id) {} 1428 unique_touch_event_id_(unique_touch_event_id) {
1429 latency()->set_source_event_type(ui::SourceEventType::TOUCH);
1430 }
1328 1431
1329 GestureEvent::~GestureEvent() { 1432 GestureEvent::~GestureEvent() {
1330 } 1433 }
1331 1434
1332 } // namespace ui 1435 } // 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