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

Side by Side Diff: third_party/WebKit/Source/core/input/EventHandler.cpp

Issue 2036643003: Creat a KeyboardEventManager class (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 6 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 /* 1 /*
2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserv ed. 2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserv ed.
3 * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) 3 * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
4 * Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies) 4 * Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies)
5 * 5 *
6 * Redistribution and use in source and binary forms, with or without 6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions 7 * modification, are permitted provided that the following conditions
8 * are met: 8 * are met:
9 * 1. Redistributions of source code must retain the above copyright 9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer. 10 * notice, this list of conditions and the following disclaimer.
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
73 #include "core/loader/DocumentLoader.h" 73 #include "core/loader/DocumentLoader.h"
74 #include "core/loader/FrameLoader.h" 74 #include "core/loader/FrameLoader.h"
75 #include "core/loader/FrameLoaderClient.h" 75 #include "core/loader/FrameLoaderClient.h"
76 #include "core/page/AutoscrollController.h" 76 #include "core/page/AutoscrollController.h"
77 #include "core/page/ChromeClient.h" 77 #include "core/page/ChromeClient.h"
78 #include "core/page/DragController.h" 78 #include "core/page/DragController.h"
79 #include "core/page/DragState.h" 79 #include "core/page/DragState.h"
80 #include "core/page/FocusController.h" 80 #include "core/page/FocusController.h"
81 #include "core/page/FrameTree.h" 81 #include "core/page/FrameTree.h"
82 #include "core/page/Page.h" 82 #include "core/page/Page.h"
83 #include "core/page/SpatialNavigation.h"
84 #include "core/page/TouchAdjustment.h" 83 #include "core/page/TouchAdjustment.h"
85 #include "core/page/scrolling/ScrollState.h" 84 #include "core/page/scrolling/ScrollState.h"
86 #include "core/paint/PaintLayer.h" 85 #include "core/paint/PaintLayer.h"
87 #include "core/style/ComputedStyle.h" 86 #include "core/style/ComputedStyle.h"
88 #include "core/style/CursorData.h" 87 #include "core/style/CursorData.h"
89 #include "core/svg/SVGDocumentExtensions.h" 88 #include "core/svg/SVGDocumentExtensions.h"
90 #include "platform/PlatformGestureEvent.h" 89 #include "platform/PlatformGestureEvent.h"
91 #include "platform/PlatformKeyboardEvent.h" 90 #include "platform/PlatformKeyboardEvent.h"
92 #include "platform/PlatformTouchEvent.h" 91 #include "platform/PlatformTouchEvent.h"
93 #include "platform/PlatformWheelEvent.h" 92 #include "platform/PlatformWheelEvent.h"
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
205 , m_mouseDownMayStartAutoscroll(false) 204 , m_mouseDownMayStartAutoscroll(false)
206 , m_fakeMouseMoveEventTimer(this, &EventHandler::fakeMouseMoveEventTimerFire d) 205 , m_fakeMouseMoveEventTimer(this, &EventHandler::fakeMouseMoveEventTimerFire d)
207 , m_svgPan(false) 206 , m_svgPan(false)
208 , m_eventHandlerWillResetCapturingMouseEventsNode(0) 207 , m_eventHandlerWillResetCapturingMouseEventsNode(0)
209 , m_clickCount(0) 208 , m_clickCount(0)
210 , m_shouldOnlyFireDragOverEvent(false) 209 , m_shouldOnlyFireDragOverEvent(false)
211 , m_mousePositionIsUnknown(true) 210 , m_mousePositionIsUnknown(true)
212 , m_mouseDownTimestamp(0) 211 , m_mouseDownTimestamp(0)
213 , m_pointerEventManager(frame) 212 , m_pointerEventManager(frame)
214 , m_scrollManager(frame) 213 , m_scrollManager(frame)
214 , m_keyboardEventManager(frame, &m_scrollManager)
215 , m_longTapShouldInvokeContextMenu(false) 215 , m_longTapShouldInvokeContextMenu(false)
216 , m_activeIntervalTimer(this, &EventHandler::activeIntervalTimerFired) 216 , m_activeIntervalTimer(this, &EventHandler::activeIntervalTimerFired)
217 , m_lastShowPressTimestamp(0) 217 , m_lastShowPressTimestamp(0)
218 { 218 {
219 } 219 }
220 220
221 EventHandler::~EventHandler() 221 EventHandler::~EventHandler()
222 { 222 {
223 ASSERT(!m_fakeMouseMoveEventTimer.isActive()); 223 ASSERT(!m_fakeMouseMoveEventTimer.isActive());
224 } 224 }
225 225
226 DEFINE_TRACE(EventHandler) 226 DEFINE_TRACE(EventHandler)
227 { 227 {
228 visitor->trace(m_frame); 228 visitor->trace(m_frame);
229 visitor->trace(m_mousePressNode); 229 visitor->trace(m_mousePressNode);
230 visitor->trace(m_capturingMouseEventsNode); 230 visitor->trace(m_capturingMouseEventsNode);
231 visitor->trace(m_nodeUnderMouse); 231 visitor->trace(m_nodeUnderMouse);
232 visitor->trace(m_lastMouseMoveEventSubframe); 232 visitor->trace(m_lastMouseMoveEventSubframe);
233 visitor->trace(m_lastScrollbarUnderMouse); 233 visitor->trace(m_lastScrollbarUnderMouse);
234 visitor->trace(m_clickNode); 234 visitor->trace(m_clickNode);
235 visitor->trace(m_dragTarget); 235 visitor->trace(m_dragTarget);
236 visitor->trace(m_frameSetBeingResized); 236 visitor->trace(m_frameSetBeingResized);
237 visitor->trace(m_lastDeferredTapElement); 237 visitor->trace(m_lastDeferredTapElement);
238 visitor->trace(m_selectionController); 238 visitor->trace(m_selectionController);
239 visitor->trace(m_pointerEventManager); 239 visitor->trace(m_pointerEventManager);
240 visitor->trace(m_scrollManager); 240 visitor->trace(m_scrollManager);
241 visitor->trace(m_keyboardEventManager);
241 } 242 }
242 243
243 DragState& EventHandler::dragState() 244 DragState& EventHandler::dragState()
244 { 245 {
245 DEFINE_STATIC_LOCAL(DragState, state, (new DragState)); 246 DEFINE_STATIC_LOCAL(DragState, state, (new DragState));
246 return state; 247 return state;
247 } 248 }
248 249
249 void EventHandler::clear() 250 void EventHandler::clear()
250 { 251 {
(...skipping 2335 matching lines...) Expand 10 before | Expand all | Expand 10 after
2586 void EventHandler::notifyElementActivated() 2587 void EventHandler::notifyElementActivated()
2587 { 2588 {
2588 // Since another element has been set to active, stop current timer and clea r reference. 2589 // Since another element has been set to active, stop current timer and clea r reference.
2589 if (m_activeIntervalTimer.isActive()) 2590 if (m_activeIntervalTimer.isActive())
2590 m_activeIntervalTimer.stop(); 2591 m_activeIntervalTimer.stop();
2591 m_lastDeferredTapElement = nullptr; 2592 m_lastDeferredTapElement = nullptr;
2592 } 2593 }
2593 2594
2594 bool EventHandler::handleAccessKey(const PlatformKeyboardEvent& evt) 2595 bool EventHandler::handleAccessKey(const PlatformKeyboardEvent& evt)
2595 { 2596 {
2596 // FIXME: Ignoring the state of Shift key is what neither IE nor Firefox do. 2597 return m_keyboardEventManager.handleAccessKey(evt);
2597 // IE matches lower and upper case access keys regardless of Shift key state - but if both upper and
2598 // lower case variants are present in a document, the correct element is mat ched based on Shift key state.
2599 // Firefox only matches an access key if Shift is not pressed, and does that case-insensitively.
2600 ASSERT(!(accessKeyModifiers() & PlatformEvent::ShiftKey));
2601 if ((evt.getModifiers() & (PlatformEvent::KeyModifiers & ~PlatformEvent::Shi ftKey)) != accessKeyModifiers())
2602 return false;
2603 String key = evt.unmodifiedText();
2604 Element* elem = m_frame->document()->getElementByAccessKey(key.lower());
2605 if (!elem)
2606 return false;
2607 elem->accessKeyAction(false);
2608 return true;
2609 } 2598 }
2610 2599
2611 WebInputEventResult EventHandler::keyEvent(const PlatformKeyboardEvent& initialK eyEvent) 2600 WebInputEventResult EventHandler::keyEvent(const PlatformKeyboardEvent& initialK eyEvent)
2612 { 2601 {
2613 m_frame->chromeClient().clearToolTip(); 2602 return m_keyboardEventManager.keyEvent(initialKeyEvent);
2614
2615 if (initialKeyEvent.windowsVirtualKeyCode() == VK_CAPITAL)
2616 capsLockStateMayHaveChanged();
2617
2618 #if OS(WIN)
2619 if (m_scrollManager.panScrollInProgress()) {
2620 // If a key is pressed while the panScroll is in progress then we want t o stop
2621 if (initialKeyEvent.type() == PlatformEvent::KeyDown || initialKeyEvent. type() == PlatformEvent::RawKeyDown)
2622 m_scrollManager.stopAutoscroll();
2623
2624 // If we were in panscroll mode, we swallow the key event
2625 return WebInputEventResult::HandledSuppressed;
2626 }
2627 #endif
2628
2629 // Check for cases where we are too early for events -- possible unmatched k ey up
2630 // from pressing return in the location bar.
2631 Node* node = eventTargetNodeForDocument(m_frame->document());
2632 if (!node)
2633 return WebInputEventResult::NotHandled;
2634
2635 UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
2636
2637 // In IE, access keys are special, they are handled after default keydown pr ocessing, but cannot be canceled - this is hard to match.
2638 // On Mac OS X, we process them before dispatching keydown, as the default k eydown handler implements Emacs key bindings, which may conflict
2639 // with access keys. Then we dispatch keydown, but suppress its default hand ling.
2640 // On Windows, WebKit explicitly calls handleAccessKey() instead of dispatch ing a keypress event for WM_SYSCHAR messages.
2641 // Other platforms currently match either Mac or Windows behavior, depending on whether they send combined KeyDown events.
2642 bool matchedAnAccessKey = false;
2643 if (initialKeyEvent.type() == PlatformEvent::KeyDown)
2644 matchedAnAccessKey = handleAccessKey(initialKeyEvent);
2645
2646 // FIXME: it would be fair to let an input method handle KeyUp events before DOM dispatch.
2647 if (initialKeyEvent.type() == PlatformEvent::KeyUp || initialKeyEvent.type() == PlatformEvent::Char) {
2648 KeyboardEvent* domEvent = KeyboardEvent::create(initialKeyEvent, m_frame ->document()->domWindow());
2649
2650 return toWebInputEventResult(node->dispatchEvent(domEvent));
2651 }
2652
2653 PlatformKeyboardEvent keyDownEvent = initialKeyEvent;
2654 if (keyDownEvent.type() != PlatformEvent::RawKeyDown)
2655 keyDownEvent.disambiguateKeyDownEvent(PlatformEvent::RawKeyDown);
2656 KeyboardEvent* keydown = KeyboardEvent::create(keyDownEvent, m_frame->docume nt()->domWindow());
2657 if (matchedAnAccessKey)
2658 keydown->setDefaultPrevented(true);
2659 keydown->setTarget(node);
2660
2661 DispatchEventResult dispatchResult = node->dispatchEvent(keydown);
2662 if (dispatchResult != DispatchEventResult::NotCanceled)
2663 return toWebInputEventResult(dispatchResult);
2664 // If frame changed as a result of keydown dispatch, then return early to av oid sending a subsequent keypress message to the new frame.
2665 bool changedFocusedFrame = m_frame->page() && m_frame != m_frame->page()->fo cusController().focusedOrMainFrame();
2666 if (changedFocusedFrame)
2667 return WebInputEventResult::HandledSystem;
2668
2669 if (initialKeyEvent.type() == PlatformEvent::RawKeyDown)
2670 return WebInputEventResult::NotHandled;
2671
2672 // Focus may have changed during keydown handling, so refetch node.
2673 // But if we are dispatching a fake backward compatibility keypress, then we pretend that the keypress happened on the original node.
2674 node = eventTargetNodeForDocument(m_frame->document());
2675 if (!node)
2676 return WebInputEventResult::NotHandled;
2677
2678 PlatformKeyboardEvent keyPressEvent = initialKeyEvent;
2679 keyPressEvent.disambiguateKeyDownEvent(PlatformEvent::Char);
2680 if (keyPressEvent.text().isEmpty())
2681 return WebInputEventResult::NotHandled;
2682 KeyboardEvent* keypress = KeyboardEvent::create(keyPressEvent, m_frame->docu ment()->domWindow());
2683 keypress->setTarget(node);
2684 return toWebInputEventResult(node->dispatchEvent(keypress));
2685 }
2686
2687 static WebFocusType focusDirectionForKey(const AtomicString& keyIdentifier)
2688 {
2689 DEFINE_STATIC_LOCAL(AtomicString, Down, ("Down"));
2690 DEFINE_STATIC_LOCAL(AtomicString, Up, ("Up"));
2691 DEFINE_STATIC_LOCAL(AtomicString, Left, ("Left"));
2692 DEFINE_STATIC_LOCAL(AtomicString, Right, ("Right"));
2693
2694 WebFocusType retVal = WebFocusTypeNone;
2695
2696 if (keyIdentifier == Down)
2697 retVal = WebFocusTypeDown;
2698 else if (keyIdentifier == Up)
2699 retVal = WebFocusTypeUp;
2700 else if (keyIdentifier == Left)
2701 retVal = WebFocusTypeLeft;
2702 else if (keyIdentifier == Right)
2703 retVal = WebFocusTypeRight;
2704
2705 return retVal;
2706 } 2603 }
2707 2604
2708 void EventHandler::defaultKeyboardEventHandler(KeyboardEvent* event) 2605 void EventHandler::defaultKeyboardEventHandler(KeyboardEvent* event)
2709 { 2606 {
2710 if (event->type() == EventTypeNames::keydown) { 2607 m_keyboardEventManager.defaultKeyboardEventHandler(event, m_mousePressNode);
2711 // Clear caret blinking suspended state to make sure that caret blinks
2712 // when we type again after long pressing on an empty input field.
2713 if (m_frame && m_frame->selection().isCaretBlinkingSuspended())
2714 m_frame->selection().setCaretBlinkingSuspended(false);
2715
2716 m_frame->editor().handleKeyboardEvent(event);
2717 if (event->defaultHandled())
2718 return;
2719 if (event->keyIdentifier() == "U+0009") {
2720 defaultTabEventHandler(event);
2721 } else if (event->keyIdentifier() == "U+0008") {
2722 defaultBackspaceEventHandler(event);
2723 } else if (event->keyIdentifier() == "U+001B") {
2724 defaultEscapeEventHandler(event);
2725 } else {
2726 WebFocusType type = focusDirectionForKey(AtomicString(event->keyIden tifier()));
2727 if (type != WebFocusTypeNone)
2728 defaultArrowEventHandler(type, event);
2729 }
2730 }
2731 if (event->type() == EventTypeNames::keypress) {
2732 m_frame->editor().handleKeyboardEvent(event);
2733 if (event->defaultHandled())
2734 return;
2735 if (event->charCode() == ' ')
2736 defaultSpaceEventHandler(event);
2737 }
2738 } 2608 }
2739 2609
2740 bool EventHandler::dragHysteresisExceeded(const IntPoint& dragLocationInRootFram e) const 2610 bool EventHandler::dragHysteresisExceeded(const IntPoint& dragLocationInRootFram e) const
2741 { 2611 {
2742 FrameView* view = m_frame->view(); 2612 FrameView* view = m_frame->view();
2743 if (!view) 2613 if (!view)
2744 return false; 2614 return false;
2745 IntPoint dragLocation = view->rootFrameToContents(dragLocationInRootFrame); 2615 IntPoint dragLocation = view->rootFrameToContents(dragLocationInRootFrame);
2746 IntSize delta = dragLocation - m_mouseDownPos; 2616 IntSize delta = dragLocation - m_mouseDownPos;
2747 2617
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after
2929 target->dispatchEvent(event); 2799 target->dispatchEvent(event);
2930 return event->defaultHandled() || event->defaultPrevented(); 2800 return event->defaultHandled() || event->defaultPrevented();
2931 } 2801 }
2932 2802
2933 void EventHandler::defaultTextInputEventHandler(TextEvent* event) 2803 void EventHandler::defaultTextInputEventHandler(TextEvent* event)
2934 { 2804 {
2935 if (m_frame->editor().handleTextEvent(event)) 2805 if (m_frame->editor().handleTextEvent(event))
2936 event->setDefaultHandled(); 2806 event->setDefaultHandled();
2937 } 2807 }
2938 2808
2939 void EventHandler::defaultSpaceEventHandler(KeyboardEvent* event)
2940 {
2941 ASSERT(event->type() == EventTypeNames::keypress);
2942
2943 if (event->ctrlKey() || event->metaKey() || event->altKey())
2944 return;
2945
2946 ScrollDirection direction = event->shiftKey() ? ScrollBlockDirectionBackward : ScrollBlockDirectionForward;
2947
2948 // FIXME: enable scroll customization in this case. See crbug.com/410974.
2949 if (m_scrollManager.logicalScroll(direction, ScrollByPage, nullptr, m_mouseP ressNode)) {
2950 event->setDefaultHandled();
2951 return;
2952 }
2953 }
2954
2955 void EventHandler::defaultBackspaceEventHandler(KeyboardEvent* event)
2956 {
2957 ASSERT(event->type() == EventTypeNames::keydown);
2958
2959 if (!RuntimeEnabledFeatures::backspaceDefaultHandlerEnabled())
2960 return;
2961
2962 if (event->ctrlKey() || event->metaKey() || event->altKey())
2963 return;
2964
2965 if (!m_frame->editor().behavior().shouldNavigateBackOnBackspace())
2966 return;
2967 UseCounter::count(m_frame->document(), UseCounter::BackspaceNavigatedBack);
2968 if (m_frame->page()->chromeClient().hadFormInteraction())
2969 UseCounter::count(m_frame->document(), UseCounter::BackspaceNavigatedBac kAfterFormInteraction);
2970 bool handledEvent = m_frame->loader().client()->navigateBackForward(event->s hiftKey() ? 1 : -1);
2971 if (handledEvent)
2972 event->setDefaultHandled();
2973 }
2974
2975 void EventHandler::defaultArrowEventHandler(WebFocusType focusType, KeyboardEven t* event)
2976 {
2977 ASSERT(event->type() == EventTypeNames::keydown);
2978
2979 if (event->ctrlKey() || event->metaKey() || event->shiftKey())
2980 return;
2981
2982 Page* page = m_frame->page();
2983 if (!page)
2984 return;
2985
2986 if (!isSpatialNavigationEnabled(m_frame))
2987 return;
2988
2989 // Arrows and other possible directional navigation keys can be used in desi gn
2990 // mode editing.
2991 if (m_frame->document()->inDesignMode())
2992 return;
2993
2994 if (page->focusController().advanceFocus(focusType))
2995 event->setDefaultHandled();
2996 }
2997
2998 void EventHandler::defaultTabEventHandler(KeyboardEvent* event)
2999 {
3000 ASSERT(event->type() == EventTypeNames::keydown);
3001
3002 // We should only advance focus on tabs if no special modifier keys are held down.
3003 if (event->ctrlKey() || event->metaKey())
3004 return;
3005
3006 #if !OS(MACOSX)
3007 // Option-Tab is a shortcut based on a system-wide preference on Mac but
3008 // should be ignored on all other platforms.
3009 if (event->altKey())
3010 return;
3011 #endif
3012
3013 Page* page = m_frame->page();
3014 if (!page)
3015 return;
3016 if (!page->tabKeyCyclesThroughElements())
3017 return;
3018
3019 WebFocusType focusType = event->shiftKey() ? WebFocusTypeBackward : WebFocus TypeForward;
3020
3021 // Tabs can be used in design mode editing.
3022 if (m_frame->document()->inDesignMode())
3023 return;
3024
3025 if (page->focusController().advanceFocus(focusType, InputDeviceCapabilities: :doesntFireTouchEventsSourceCapabilities()))
3026 event->setDefaultHandled();
3027 }
3028
3029 void EventHandler::defaultEscapeEventHandler(KeyboardEvent* event)
3030 {
3031 if (HTMLDialogElement* dialog = m_frame->document()->activeModalDialog())
3032 dialog->dispatchEvent(Event::createCancelable(EventTypeNames::cancel));
3033 }
3034
3035 void EventHandler::capsLockStateMayHaveChanged() 2809 void EventHandler::capsLockStateMayHaveChanged()
3036 { 2810 {
3037 if (Element* element = m_frame->document()->focusedElement()) { 2811 m_keyboardEventManager.capsLockStateMayHaveChanged();
3038 if (LayoutObject* r = element->layoutObject()) {
3039 if (r->isTextField())
3040 toLayoutTextControlSingleLine(r)->capsLockStateMayHaveChanged();
3041 }
3042 }
3043 } 2812 }
3044 2813
3045 bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults& mev) 2814 bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults& mev)
3046 { 2815 {
3047 Scrollbar* scrollbar = mev.scrollbar(); 2816 Scrollbar* scrollbar = mev.scrollbar();
3048 updateLastScrollbarUnderMouse(scrollbar, true); 2817 updateLastScrollbarUnderMouse(scrollbar, true);
3049 2818
3050 if (!scrollbar || !scrollbar->enabled()) 2819 if (!scrollbar || !scrollbar->enabled())
3051 return false; 2820 return false;
3052 m_scrollManager.setFrameWasScrolledByUser(); 2821 m_scrollManager.setFrameWasScrolledByUser();
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
3133 } 2902 }
3134 2903
3135 void EventHandler::focusDocumentView() 2904 void EventHandler::focusDocumentView()
3136 { 2905 {
3137 Page* page = m_frame->page(); 2906 Page* page = m_frame->page();
3138 if (!page) 2907 if (!page)
3139 return; 2908 return;
3140 page->focusController().focusDocumentView(m_frame); 2909 page->focusController().focusDocumentView(m_frame);
3141 } 2910 }
3142 2911
3143 PlatformEvent::Modifiers EventHandler::accessKeyModifiers()
3144 {
3145 #if OS(MACOSX)
3146 return static_cast<PlatformEvent::Modifiers>(PlatformEvent::CtrlKey | Platfo rmEvent::AltKey);
3147 #else
3148 return PlatformEvent::AltKey;
3149 #endif
3150 }
3151
3152 FrameHost* EventHandler::frameHost() const 2912 FrameHost* EventHandler::frameHost() const
3153 { 2913 {
3154 if (!m_frame->page()) 2914 if (!m_frame->page())
3155 return nullptr; 2915 return nullptr;
3156 2916
3157 return &m_frame->page()->frameHost(); 2917 return &m_frame->page()->frameHost();
3158 } 2918 }
3159 2919
3160 } // namespace blink 2920 } // namespace blink
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/core/input/EventHandler.h ('k') | third_party/WebKit/Source/core/input/KeyboardEventManager.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698