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

Side by Side Diff: content/browser/renderer_host/input/immediate_input_router.cc

Issue 45623005: [NOT FOR REVIEW] Patch demonstrating the changes required for browser side fling. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: patch Created 7 years, 1 month 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 | Annotate | Revision Log
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 "content/browser/renderer_host/input/immediate_input_router.h" 5 #include "content/browser/renderer_host/input/immediate_input_router.h"
6 6
7 #include "base/auto_reset.h" 7 #include "base/auto_reset.h"
8 #include "base/command_line.h" 8 #include "base/command_line.h"
9 #include "base/metrics/histogram.h" 9 #include "base/metrics/histogram.h"
10 #include "content/browser/renderer_host/input/gesture_event_filter.h" 10 #include "content/browser/renderer_host/input/base_gesture_event_filter.h"
11 #include "content/browser/renderer_host/input/input_ack_handler.h" 11 #include "content/browser/renderer_host/input/input_ack_handler.h"
12 #include "content/browser/renderer_host/input/input_router_client.h" 12 #include "content/browser/renderer_host/input/input_router_client.h"
13 #include "content/browser/renderer_host/input/touch_event_queue.h" 13 #include "content/browser/renderer_host/input/touch_event_queue.h"
14 #include "content/browser/renderer_host/input/touchpad_tap_suppression_controlle r.h"
15 #include "content/browser/renderer_host/overscroll_controller.h" 14 #include "content/browser/renderer_host/overscroll_controller.h"
16 #include "content/common/content_constants_internal.h" 15 #include "content/common/content_constants_internal.h"
17 #include "content/common/edit_command.h" 16 #include "content/common/edit_command.h"
18 #include "content/common/input/web_input_event_traits.h" 17 #include "content/common/input/web_input_event_traits.h"
19 #include "content/common/input_messages.h" 18 #include "content/common/input_messages.h"
20 #include "content/common/view_messages.h" 19 #include "content/common/view_messages.h"
21 #include "content/port/common/input_event_ack_state.h" 20 #include "content/port/common/input_event_ack_state.h"
22 #include "content/public/browser/notification_service.h" 21 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/notification_types.h" 22 #include "content/public/browser/notification_types.h"
24 #include "content/public/browser/user_metrics.h" 23 #include "content/public/browser/user_metrics.h"
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
80 client_(client), 79 client_(client),
81 ack_handler_(ack_handler), 80 ack_handler_(ack_handler),
82 routing_id_(routing_id), 81 routing_id_(routing_id),
83 select_range_pending_(false), 82 select_range_pending_(false),
84 move_caret_pending_(false), 83 move_caret_pending_(false),
85 mouse_move_pending_(false), 84 mouse_move_pending_(false),
86 mouse_wheel_pending_(false), 85 mouse_wheel_pending_(false),
87 has_touch_handler_(false), 86 has_touch_handler_(false),
88 current_ack_source_(ACK_SOURCE_NONE), 87 current_ack_source_(ACK_SOURCE_NONE),
89 touch_event_queue_(new TouchEventQueue(this)), 88 touch_event_queue_(new TouchEventQueue(this)),
90 gesture_event_filter_(new GestureEventFilter(this, this)) { 89 flinger_(new Flinger(this)),
90 event_filter_(new BaseGestureEventFilter(this)) {
91 DCHECK(sender); 91 DCHECK(sender);
92 DCHECK(client); 92 DCHECK(client);
93 DCHECK(ack_handler); 93 DCHECK(ack_handler);
94 } 94 }
95 95
96 ImmediateInputRouter::~ImmediateInputRouter() { 96 ImmediateInputRouter::~ImmediateInputRouter() {
97 } 97 }
98 98
99 void ImmediateInputRouter::Flush() { 99 void ImmediateInputRouter::Flush() {
100 NOTREACHED() << "ImmediateInputRouter will never request a flush."; 100 NOTREACHED() << "ImmediateInputRouter will never request a flush.";
(...skipping 11 matching lines...) Expand all
112 NOTREACHED() << "WebInputEvents should never be sent via SendInput."; 112 NOTREACHED() << "WebInputEvents should never be sent via SendInput.";
113 return false; 113 return false;
114 default: 114 default:
115 return Send(message.release()); 115 return Send(message.release());
116 } 116 }
117 } 117 }
118 118
119 void ImmediateInputRouter::SendMouseEvent( 119 void ImmediateInputRouter::SendMouseEvent(
120 const MouseEventWithLatencyInfo& mouse_event) { 120 const MouseEventWithLatencyInfo& mouse_event) {
121 // Order is important here; we need to convert all MouseEvents before they 121 // Order is important here; we need to convert all MouseEvents before they
122 // propagate further, e.g., to the tap suppression controller. 122 // propagate further.
123 if (CommandLine::ForCurrentProcess()->HasSwitch( 123 if (CommandLine::ForCurrentProcess()->HasSwitch(
124 switches::kSimulateTouchScreenWithMouse)) { 124 switches::kSimulateTouchScreenWithMouse)) {
125 SimulateTouchGestureWithMouse(mouse_event); 125 SimulateTouchGestureWithMouse(mouse_event);
126 return; 126 return;
127 } 127 }
128 128
129 if (mouse_event.event.type == WebInputEvent::MouseDown && 129 if (flinger_->HandleMouseEvent(mouse_event))
130 gesture_event_filter_->GetTouchpadTapSuppressionController()-> 130 return;
131 ShouldDeferMouseDown(mouse_event))
132 return;
133 if (mouse_event.event.type == WebInputEvent::MouseUp &&
134 gesture_event_filter_->GetTouchpadTapSuppressionController()->
135 ShouldSuppressMouseUp())
136 return;
137 131
138 SendMouseEventImmediately(mouse_event); 132 SendMouseEventImmediately(mouse_event);
139 } 133 }
140 134
141 void ImmediateInputRouter::SendWheelEvent( 135 void ImmediateInputRouter::SendWheelEvent(
142 const MouseWheelEventWithLatencyInfo& wheel_event) { 136 const MouseWheelEventWithLatencyInfo& wheel_event) {
137 if (flinger_->HandleWheelEvent(wheel_event))
138 return;
139
143 // If there's already a mouse wheel event waiting to be sent to the renderer, 140 // If there's already a mouse wheel event waiting to be sent to the renderer,
144 // add the new deltas to that event. Not doing so (e.g., by dropping the old 141 // add the new deltas to that event. Not doing so (e.g., by dropping the old
145 // event, as for mouse moves) results in very slow scrolling on the Mac (on 142 // event, as for mouse moves) results in very slow scrolling on the Mac (on
146 // which many, very small wheel events are sent). 143 // which many, very small wheel events are sent).
147 if (mouse_wheel_pending_) { 144 if (mouse_wheel_pending_) {
148 if (coalesced_mouse_wheel_events_.empty() || 145 if (coalesced_mouse_wheel_events_.empty() ||
149 !coalesced_mouse_wheel_events_.back().CanCoalesceWith(wheel_event)) { 146 !coalesced_mouse_wheel_events_.back().CanCoalesceWith(wheel_event)) {
150 coalesced_mouse_wheel_events_.push_back(wheel_event); 147 coalesced_mouse_wheel_events_.push_back(wheel_event);
151 } else { 148 } else {
152 coalesced_mouse_wheel_events_.back().CoalesceWith(wheel_event); 149 coalesced_mouse_wheel_events_.back().CoalesceWith(wheel_event);
153 } 150 }
154 return; 151 return;
155 } 152 }
156 mouse_wheel_pending_ = true; 153 mouse_wheel_pending_ = true;
157 current_wheel_event_ = wheel_event; 154 current_wheel_event_ = wheel_event;
158 155
159 HISTOGRAM_COUNTS_100("Renderer.WheelQueueSize", 156 HISTOGRAM_COUNTS_100("Renderer.WheelQueueSize",
160 coalesced_mouse_wheel_events_.size()); 157 coalesced_mouse_wheel_events_.size());
161 158
162 FilterAndSendWebInputEvent(wheel_event.event, wheel_event.latency, false); 159 FilterAndSendWebInputEvent(wheel_event.event, wheel_event.latency, false);
163 } 160 }
164 161
165 void ImmediateInputRouter::SendKeyboardEvent( 162 void ImmediateInputRouter::SendKeyboardEvent(
166 const NativeWebKeyboardEvent& key_event, 163 const NativeWebKeyboardEvent& key_event,
167 const ui::LatencyInfo& latency_info, 164 const ui::LatencyInfo& latency_info,
168 bool is_keyboard_shortcut) { 165 bool is_keyboard_shortcut) {
166 if (flinger_->HandleKeyboardEvent())
167 return;
168
169 // Put all WebKeyboardEvent objects in a queue since we can't trust the 169 // Put all WebKeyboardEvent objects in a queue since we can't trust the
170 // renderer and we need to give something to the HandleKeyboardEvent 170 // renderer and we need to give something to the HandleKeyboardEvent
171 // handler. 171 // handler.
172 key_queue_.push_back(key_event); 172 key_queue_.push_back(key_event);
173 HISTOGRAM_COUNTS_100("Renderer.KeyboardQueueSize", key_queue_.size()); 173 HISTOGRAM_COUNTS_100("Renderer.KeyboardQueueSize", key_queue_.size());
174 174
175 gesture_event_filter_->FlingHasBeenHalted();
176
177 // Only forward the non-native portions of our event. 175 // Only forward the non-native portions of our event.
178 FilterAndSendWebInputEvent(key_event, latency_info, is_keyboard_shortcut); 176 FilterAndSendWebInputEvent(key_event, latency_info, is_keyboard_shortcut);
179 } 177 }
180 178
181 void ImmediateInputRouter::SendGestureEvent( 179 void ImmediateInputRouter::SendGestureEvent(
182 const GestureEventWithLatencyInfo& gesture_event) { 180 const GestureEventWithLatencyInfo& gesture_event) {
183 HandleGestureScroll(gesture_event); 181 HandleGestureScroll(gesture_event);
184 182
183 if (flinger_->HandleGestureEvent(gesture_event))
184 return;
185
185 if (!IsInOverscrollGesture() && 186 if (!IsInOverscrollGesture() &&
186 !gesture_event_filter_->ShouldForward(gesture_event)) { 187 !event_filter_->ShouldForward(gesture_event)) {
187 OverscrollController* controller = client_->GetOverscrollController(); 188 OverscrollController* controller = client_->GetOverscrollController();
188 if (controller) 189 if (controller)
189 controller->DiscardingGestureEvent(gesture_event.event); 190 controller->DiscardingGestureEvent(gesture_event.event);
190 return; 191 return;
191 } 192 }
192 193
193 FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency, false); 194 FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency, false);
194 } 195 }
195 196
196 void ImmediateInputRouter::SendTouchEvent( 197 void ImmediateInputRouter::SendTouchEvent(
197 const TouchEventWithLatencyInfo& touch_event) { 198 const TouchEventWithLatencyInfo& touch_event) {
198 touch_event_queue_->QueueEvent(touch_event); 199 touch_event_queue_->QueueEvent(touch_event);
199 } 200 }
200 201
201 // Forwards MouseEvent without passing it through
202 // TouchpadTapSuppressionController.
203 void ImmediateInputRouter::SendMouseEventImmediately( 202 void ImmediateInputRouter::SendMouseEventImmediately(
204 const MouseEventWithLatencyInfo& mouse_event) { 203 const MouseEventWithLatencyInfo& mouse_event) {
205 // Avoid spamming the renderer with mouse move events. It is important 204 // Avoid spamming the renderer with mouse move events. It is important
206 // to note that WM_MOUSEMOVE events are anyways synthetic, but since our 205 // to note that WM_MOUSEMOVE events are anyways synthetic, but since our
207 // thread is able to rapidly consume WM_MOUSEMOVE events, we may get way 206 // thread is able to rapidly consume WM_MOUSEMOVE events, we may get way
208 // more WM_MOUSEMOVE events than we wish to send to the renderer. 207 // more WM_MOUSEMOVE events than we wish to send to the renderer.
209 if (mouse_event.event.type == WebInputEvent::MouseMove) { 208 if (mouse_event.event.type == WebInputEvent::MouseMove) {
210 if (mouse_move_pending_) { 209 if (mouse_move_pending_) {
211 if (!next_mouse_move_) 210 if (!next_mouse_move_)
212 next_mouse_move_.reset(new MouseEventWithLatencyInfo(mouse_event)); 211 next_mouse_move_.reset(new MouseEventWithLatencyInfo(mouse_event));
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 ack_handler_->OnTouchEventAck(event, ack_result); 269 ack_handler_->OnTouchEventAck(event, ack_result);
271 } 270 }
272 271
273 void ImmediateInputRouter::OnGestureEventAck( 272 void ImmediateInputRouter::OnGestureEventAck(
274 const GestureEventWithLatencyInfo& event, 273 const GestureEventWithLatencyInfo& event,
275 InputEventAckState ack_result) { 274 InputEventAckState ack_result) {
276 ProcessAckForOverscroll(event.event, ack_result); 275 ProcessAckForOverscroll(event.event, ack_result);
277 ack_handler_->OnGestureEventAck(event, ack_result); 276 ack_handler_->OnGestureEventAck(event, ack_result);
278 } 277 }
279 278
279 void ImmediateInputRouter::SendEventForFling(
280 const WebKit::WebInputEvent& event) {
281 ui::LatencyInfo latency_info;
282 if (Send(new InputMsg_HandleInputEvent(
283 routing_id(), &event, latency_info, false)))
284 in_process_messages_sources_.push(MESSAGE_SOURCE_FLING);
285 }
286
287 void ImmediateInputRouter::FlingFinished(
288 WebKit::WebGestureEvent::SourceDevice source) {
289 if (source == WebKit::WebGestureEvent::Touchscreen) {
290 ui::LatencyInfo latency_info;
291 WebKit::WebGestureEvent synthetic_gesture;
292 synthetic_gesture.type = WebKit::WebInputEvent::GestureScrollEnd;
293 synthetic_gesture.sourceDevice = WebKit::WebGestureEvent::Touchscreen;
294 SendWebInputEvent(synthetic_gesture, latency_info, false);
295 }
296 }
297
280 bool ImmediateInputRouter::SendSelectRange(scoped_ptr<IPC::Message> message) { 298 bool ImmediateInputRouter::SendSelectRange(scoped_ptr<IPC::Message> message) {
281 DCHECK(message->type() == InputMsg_SelectRange::ID); 299 DCHECK(message->type() == InputMsg_SelectRange::ID);
282 if (select_range_pending_) { 300 if (select_range_pending_) {
283 next_selection_range_ = message.Pass(); 301 next_selection_range_ = message.Pass();
284 return true; 302 return true;
285 } 303 }
286 304
287 select_range_pending_ = true; 305 select_range_pending_ = true;
288 return Send(message.release()); 306 return Send(message.release());
289 } 307 }
(...skipping 13 matching lines...) Expand all
303 return sender_->Send(message); 321 return sender_->Send(message);
304 } 322 }
305 323
306 void ImmediateInputRouter::SendWebInputEvent( 324 void ImmediateInputRouter::SendWebInputEvent(
307 const WebInputEvent& input_event, 325 const WebInputEvent& input_event,
308 const ui::LatencyInfo& latency_info, 326 const ui::LatencyInfo& latency_info,
309 bool is_keyboard_shortcut) { 327 bool is_keyboard_shortcut) {
310 input_event_start_time_ = TimeTicks::Now(); 328 input_event_start_time_ = TimeTicks::Now();
311 if (Send(new InputMsg_HandleInputEvent( 329 if (Send(new InputMsg_HandleInputEvent(
312 routing_id(), &input_event, latency_info, is_keyboard_shortcut))) { 330 routing_id(), &input_event, latency_info, is_keyboard_shortcut))) {
331 in_process_messages_sources_.push(MESSAGE_SOURCE_REGULAR);
313 client_->IncrementInFlightEventCount(); 332 client_->IncrementInFlightEventCount();
314 } 333 }
315 } 334 }
316 335
317 void ImmediateInputRouter::FilterAndSendWebInputEvent( 336 void ImmediateInputRouter::FilterAndSendWebInputEvent(
318 const WebInputEvent& input_event, 337 const WebInputEvent& input_event,
319 const ui::LatencyInfo& latency_info, 338 const ui::LatencyInfo& latency_info,
320 bool is_keyboard_shortcut) { 339 bool is_keyboard_shortcut) {
321 TRACE_EVENT1("input", "ImmediateInputRouter::FilterAndSendWebInputEvent", 340 TRACE_EVENT1("input", "ImmediateInputRouter::FilterAndSendWebInputEvent",
322 "type", WebInputEventTraits::GetName(input_event.type)); 341 "type", WebInputEventTraits::GetName(input_event.type));
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
355 OverscrollController::Disposition disposition = 374 OverscrollController::Disposition disposition =
356 controller->DispatchEvent(input_event, latency_info); 375 controller->DispatchEvent(input_event, latency_info);
357 376
358 bool consumed = disposition == OverscrollController::CONSUMED; 377 bool consumed = disposition == OverscrollController::CONSUMED;
359 378
360 if (disposition == OverscrollController::SHOULD_FORWARD_TO_GESTURE_FILTER) { 379 if (disposition == OverscrollController::SHOULD_FORWARD_TO_GESTURE_FILTER) {
361 DCHECK(WebInputEvent::isGestureEventType(input_event.type)); 380 DCHECK(WebInputEvent::isGestureEventType(input_event.type));
362 const WebKit::WebGestureEvent& gesture_event = 381 const WebKit::WebGestureEvent& gesture_event =
363 static_cast<const WebKit::WebGestureEvent&>(input_event); 382 static_cast<const WebKit::WebGestureEvent&>(input_event);
364 // An ACK is expected for the event, so mark it as consumed. 383 // An ACK is expected for the event, so mark it as consumed.
365 consumed = !gesture_event_filter_->ShouldForward( 384 consumed = !event_filter_->ShouldForward(
366 GestureEventWithLatencyInfo(gesture_event, latency_info)); 385 GestureEventWithLatencyInfo(gesture_event, latency_info));
367 } 386 }
368 387
369 if (consumed) { 388 if (consumed) {
370 InputEventAckState overscroll_ack = 389 InputEventAckState overscroll_ack =
371 WebInputEvent::isTouchEventType(input_event.type) ? 390 WebInputEvent::isTouchEventType(input_event.type) ?
372 INPUT_EVENT_ACK_STATE_NOT_CONSUMED : INPUT_EVENT_ACK_STATE_CONSUMED; 391 INPUT_EVENT_ACK_STATE_NOT_CONSUMED : INPUT_EVENT_ACK_STATE_CONSUMED;
373 ProcessInputEventAck(input_event.type, 392 ProcessInputEventAck(input_event.type,
374 overscroll_ack, 393 overscroll_ack,
375 latency_info, 394 latency_info,
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
407 } 426 }
408 427
409 void ImmediateInputRouter::OnInputEventAck( 428 void ImmediateInputRouter::OnInputEventAck(
410 WebInputEvent::Type event_type, 429 WebInputEvent::Type event_type,
411 InputEventAckState ack_result, 430 InputEventAckState ack_result,
412 const ui::LatencyInfo& latency_info) { 431 const ui::LatencyInfo& latency_info) {
413 // Log the time delta for processing an input event. 432 // Log the time delta for processing an input event.
414 TimeDelta delta = TimeTicks::Now() - input_event_start_time_; 433 TimeDelta delta = TimeTicks::Now() - input_event_start_time_;
415 UMA_HISTOGRAM_TIMES("MPArch.IIR_InputEventDelta", delta); 434 UMA_HISTOGRAM_TIMES("MPArch.IIR_InputEventDelta", delta);
416 435
436 InputMessageSource source = in_process_messages_sources_.front();
437 in_process_messages_sources_.pop();
438 if (source == MESSAGE_SOURCE_FLING) {
439 flinger_->ProcessEventAck(event_type, ack_result, latency_info);
440 return;
441 }
442
417 client_->DecrementInFlightEventCount(); 443 client_->DecrementInFlightEventCount();
418 444
419 ProcessInputEventAck(event_type, ack_result, latency_info, RENDERER); 445 ProcessInputEventAck(event_type, ack_result, latency_info, RENDERER);
420 // WARNING: |this| may be deleted at this point. 446 // WARNING: |this| may be deleted at this point.
421 447
422 // This is used only for testing, and the other end does not use the 448 // This is used only for testing, and the other end does not use the
423 // source object. On linux, specifying 449 // source object. On linux, specifying
424 // Source<RenderWidgetHost> results in a very strange 450 // Source<RenderWidgetHost> results in a very strange
425 // runtime error in the epilogue of the enclosing 451 // runtime error in the epilogue of the enclosing
426 // (ProcessInputEventAck) method, but not on other platforms; using 452 // (ProcessInputEventAck) method, but not on other platforms; using
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
545 SendWheelEvent(next_wheel_event); 571 SendWheelEvent(next_wheel_event);
546 } 572 }
547 } 573 }
548 574
549 void ImmediateInputRouter::ProcessGestureAck(WebInputEvent::Type type, 575 void ImmediateInputRouter::ProcessGestureAck(WebInputEvent::Type type,
550 InputEventAckState ack_result, 576 InputEventAckState ack_result,
551 const ui::LatencyInfo& latency) { 577 const ui::LatencyInfo& latency) {
552 // If |ack_result| originated from the overscroll controller, only 578 // If |ack_result| originated from the overscroll controller, only
553 // feed |gesture_event_filter_| the ack if it was expecting one. 579 // feed |gesture_event_filter_| the ack if it was expecting one.
554 if (current_ack_source_ == OVERSCROLL_CONTROLLER && 580 if (current_ack_source_ == OVERSCROLL_CONTROLLER &&
555 !gesture_event_filter_->HasQueuedGestureEvents()) { 581 !event_filter_->HasQueuedGestureEvents()) {
556 return; 582 return;
557 } 583 }
558 584
559 // |gesture_event_filter_| will forward to OnGestureEventAck when appropriate. 585 // |gesture_event_filter_| will forward to OnGestureEventAck when appropriate.
560 gesture_event_filter_->ProcessGestureAck(ack_result, type, latency); 586 event_filter_->ProcessGestureAck(ack_result, type, latency);
561 } 587 }
562 588
563 void ImmediateInputRouter::ProcessTouchAck( 589 void ImmediateInputRouter::ProcessTouchAck(
564 InputEventAckState ack_result, 590 InputEventAckState ack_result,
565 const ui::LatencyInfo& latency) { 591 const ui::LatencyInfo& latency) {
566 // |touch_event_queue_| will forward to OnTouchEventAck when appropriate. 592 // |touch_event_queue_| will forward to OnTouchEventAck when appropriate.
567 touch_event_queue_->ProcessTouchAck(ack_result, latency); 593 touch_event_queue_->ProcessTouchAck(ack_result, latency);
568 } 594 }
569 595
570 void ImmediateInputRouter::ProcessAckForOverscroll( 596 void ImmediateInputRouter::ProcessAckForOverscroll(
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
667 break; 693 break;
668 } 694 }
669 } 695 }
670 696
671 bool ImmediateInputRouter::IsInOverscrollGesture() const { 697 bool ImmediateInputRouter::IsInOverscrollGesture() const {
672 OverscrollController* controller = client_->GetOverscrollController(); 698 OverscrollController* controller = client_->GetOverscrollController();
673 return controller && controller->overscroll_mode() != OVERSCROLL_NONE; 699 return controller && controller->overscroll_mode() != OVERSCROLL_NONE;
674 } 700 }
675 701
676 } // namespace content 702 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698