Title: [107972] trunk/Source/WebKit/blackberry
Revision
107972
Author
[email protected]
Date
2012-02-16 12:55:34 -0800 (Thu, 16 Feb 2012)

Log Message

2012-02-16 Antonio Gomes <[email protected]>

        [BlackBerry] Upstream touch handling related classes
        https://bugs.webkit.org/show_bug.cgi?id=78509

        Reviewed by Rob Buis.

        Initial upstream of the Blackberry specific single touch event
        handler class.

        * blackberry/WebKitSupport/TouchEventHandler.cpp: Added.
        (WebKit):
        (BlackBerry::WebKit::hasMouseMoveListener):
        (BlackBerry::WebKit::hasTouchListener):
        (BlackBerry::WebKit::elementExpectsMouseEvents):
        (BlackBerry::WebKit::shouldConvertTouchToMouse):
        (BlackBerry::WebKit::TouchEventHandler::TouchEventHandler):
        (BlackBerry::WebKit::TouchEventHandler::~TouchEventHandler):
        (BlackBerry::WebKit::TouchEventHandler::shouldSuppressMouseDownOnTouchDown):
        (BlackBerry::WebKit::TouchEventHandler::touchEventCancel):
        (BlackBerry::WebKit::TouchEventHandler::touchHoldEvent):
        (BlackBerry::WebKit::TouchEventHandler::handleTouchPoint):
        (BlackBerry::WebKit::TouchEventHandler::spellCheck):
        (BlackBerry::WebKit::TouchEventHandler::handleFatFingerPressed):
        (BlackBerry::WebKit::elementForTapHighlight):
        (BlackBerry::WebKit::TouchEventHandler::drawTapHighlight):
        * blackberry/WebKitSupport/TouchEventHandler.h: Added.
        (WebCore):
        (WebKit):
        (TouchEventHandler):
        (BlackBerry::WebKit::TouchEventHandler::lastFatFingersResult):
        (BlackBerry::WebKit::TouchEventHandler::resetLastFatFingersResult):

Added Paths

Diff

Added: trunk/Source/WebKit/blackberry/ChangeLog (0 => 107972)


--- trunk/Source/WebKit/blackberry/ChangeLog	                        (rev 0)
+++ trunk/Source/WebKit/blackberry/ChangeLog	2012-02-16 20:55:34 UTC (rev 107972)
@@ -0,0 +1,33 @@
+2012-02-16  Antonio Gomes  <[email protected]>
+
+        [BlackBerry] Upstream touch handling related classes
+        https://bugs.webkit.org/show_bug.cgi?id=78509
+
+        Reviewed by Rob Buis.
+
+        Initial upstream of the Blackberry specific single touch event
+        handler class.
+
+        * blackberry/WebKitSupport/TouchEventHandler.cpp: Added.
+        (WebKit):
+        (BlackBerry::WebKit::hasMouseMoveListener):
+        (BlackBerry::WebKit::hasTouchListener):
+        (BlackBerry::WebKit::elementExpectsMouseEvents):
+        (BlackBerry::WebKit::shouldConvertTouchToMouse):
+        (BlackBerry::WebKit::TouchEventHandler::TouchEventHandler):
+        (BlackBerry::WebKit::TouchEventHandler::~TouchEventHandler):
+        (BlackBerry::WebKit::TouchEventHandler::shouldSuppressMouseDownOnTouchDown):
+        (BlackBerry::WebKit::TouchEventHandler::touchEventCancel):
+        (BlackBerry::WebKit::TouchEventHandler::touchHoldEvent):
+        (BlackBerry::WebKit::TouchEventHandler::handleTouchPoint):
+        (BlackBerry::WebKit::TouchEventHandler::spellCheck):
+        (BlackBerry::WebKit::TouchEventHandler::handleFatFingerPressed):
+        (BlackBerry::WebKit::elementForTapHighlight):
+        (BlackBerry::WebKit::TouchEventHandler::drawTapHighlight):
+        * blackberry/WebKitSupport/TouchEventHandler.h: Added.
+        (WebCore):
+        (WebKit):
+        (TouchEventHandler):
+        (BlackBerry::WebKit::TouchEventHandler::lastFatFingersResult):
+        (BlackBerry::WebKit::TouchEventHandler::resetLastFatFingersResult):
+

Added: trunk/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp (0 => 107972)


--- trunk/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp	                        (rev 0)
+++ trunk/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp	2012-02-16 20:55:34 UTC (rev 107972)
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "config.h"
+#include "TouchEventHandler.h"
+
+#include "DOMSupport.h"
+#include "Document.h"
+#include "DocumentMarkerController.h"
+#include "FocusController.h"
+#include "Frame.h"
+#include "FrameView.h"
+#include "HTMLAnchorElement.h"
+#include "HTMLAreaElement.h"
+#include "HTMLImageElement.h"
+#include "HTMLInputElement.h"
+#include "HTMLNames.h"
+#include "HTMLPlugInElement.h"
+#include "InputHandler.h"
+#include "IntRect.h"
+#include "IntSize.h"
+#include "Node.h"
+#include "Page.h"
+#include "PlatformMouseEvent.h"
+#include "PlatformTouchEvent.h"
+#include "RenderLayer.h"
+#include "RenderTheme.h"
+#include "RenderView.h"
+#include "RenderedDocumentMarker.h"
+#include "SelectionHandler.h"
+#include "WebPage_p.h"
+
+#include <wtf/MathExtras.h>
+
+using namespace WebCore;
+using namespace WTF;
+
+namespace BlackBerry {
+namespace WebKit {
+
+static bool hasMouseMoveListener(Element* element)
+{
+    ASSERT(element);
+    return element->hasEventListeners(eventNames().mousemoveEvent) || element->document()->hasEventListeners(eventNames().mousemoveEvent);
+}
+
+static bool hasTouchListener(Element* element)
+{
+    ASSERT(element);
+    return element->hasEventListeners(eventNames().touchstartEvent)
+        || element->hasEventListeners(eventNames().touchmoveEvent)
+        || element->hasEventListeners(eventNames().touchcancelEvent)
+        || element->hasEventListeners(eventNames().touchendEvent);
+}
+
+static bool elementExpectsMouseEvents(Element* element)
+{
+    // Make sure we are not operating a shadow node here, since the webpages
+    // aren't able to attach event listeners to shadow content.
+    ASSERT(element);
+    while (element->isInShadowTree())
+        element = toElement(element->shadowAncestorNode());
+
+    return hasMouseMoveListener(element) && !hasTouchListener(element);
+}
+
+static bool shouldConvertTouchToMouse(Element* element)
+{
+    if (!element)
+        return false;
+
+    // Range element are a special case that require natural mouse events in order to allow
+    // dragging of the slider handle.
+    if (element->hasTagName(HTMLNames::inputTag)) {
+        HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(element);
+        if (inputElement->isRangeControl())
+            return true;
+    }
+
+    if ((element->hasTagName(HTMLNames::objectTag) || element->hasTagName(HTMLNames::embedTag)) && static_cast<HTMLPlugInElement*>(element))
+        return true;
+
+    // Check if the element has a mouse listener and no touch listener. If so,
+    // the field will require touch events be converted to mouse events to function properly.
+    if (elementExpectsMouseEvents(element))
+        return true;
+
+    return false;
+}
+
+TouchEventHandler::TouchEventHandler(WebPagePrivate* webpage)
+    : m_webPage(webpage)
+    , m_didCancelTouch(false)
+    , m_convertTouchToMouse(false)
+    , m_existingTouchMode(ProcessedTouchEvents)
+{
+}
+
+TouchEventHandler::~TouchEventHandler()
+{
+}
+
+bool TouchEventHandler::shouldSuppressMouseDownOnTouchDown() const
+{
+    return m_lastFatFingersResult.isTextInput() || m_webPage->m_inputHandler->isInputMode() || m_webPage->m_selectionHandler->isSelectionActive();
+}
+
+void TouchEventHandler::touchEventCancel()
+{
+    m_webPage->m_inputHandler->processPendingClientNavigationModeChangeNotification();
+
+    if (!shouldSuppressMouseDownOnTouchDown()) {
+        // Input elements delay mouse down and do not need to be released on touch cancel.
+        m_webPage->m_page->focusController()->focusedOrMainFrame()->eventHandler()->setMousePressed(false);
+    }
+    m_convertTouchToMouse = false;
+    m_didCancelTouch = true;
+
+    // If we cancel a single touch event, we need to also clean up any hover
+    // state we get into by synthetically moving the mouse to the m_fingerPoint.
+    Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
+    if (elementUnderFatFinger && elementUnderFatFinger->renderer()) {
+
+        HitTestRequest request(HitTestRequest::FingerUp);
+        // The HitTestResult point is not actually needed.
+        HitTestResult result(IntPoint::zero());
+        result.setInnerNode(elementUnderFatFinger);
+
+        Document* document = elementUnderFatFinger->document();
+        ASSERT(document);
+        document->renderView()->layer()->updateHoverActiveState(request, result);
+        document->updateStyleIfNeeded();
+        // Updating the document style may destroy the renderer.
+        if (elementUnderFatFinger->renderer())
+            elementUnderFatFinger->renderer()->repaint();
+        ASSERT(!elementUnderFatFinger->hovered());
+    }
+
+    m_lastFatFingersResult.reset();
+}
+
+void TouchEventHandler::touchEventCancelAndClearFocusedNode()
+{
+    touchEventCancel();
+    m_webPage->clearFocusNode();
+}
+
+void TouchEventHandler::touchHoldEvent()
+{
+    // This is a hack for our hack that converts the touch pressed event that we've delayed because the user has focused a input field
+    // to the page as a mouse pressed event.
+    if (shouldSuppressMouseDownOnTouchDown())
+        handleFatFingerPressed();
+
+    // Clear the focus ring indication if tap-and-hold'ing on a link.
+    if (m_lastFatFingersResult.validNode() && m_lastFatFingersResult.validNode()->isLink())
+        m_webPage->clearFocusNode();
+}
+
+bool TouchEventHandler::handleTouchPoint(Platform::TouchPoint& point)
+{
+    switch (point.m_state) {
+    case Platform::TouchPoint::TouchPressed:
+        {
+            m_lastFatFingersResult.reset(); // Theoretically this shouldn't be required. Keep it just in case states get mangled.
+            m_didCancelTouch = false;
+            m_lastScreenPoint = point.m_screenPos;
+
+            IntPoint contentPos(m_webPage->mapFromViewportToContents(point.m_pos));
+
+            m_lastFatFingersResult = FatFingers(m_webPage, contentPos, FatFingers::ClickableElement).findBestPoint();
+
+            Element* elementUnderFatFinger = 0;
+            if (m_lastFatFingersResult.positionWasAdjusted() && m_lastFatFingersResult.validNode()) {
+                ASSERT(m_lastFatFingersResult.validNode()->isElementNode());
+                elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
+            }
+
+            // Set or reset the touch mode.
+            Element* possibleTargetNodeForMouseMoveEvents = static_cast<Element*>(m_lastFatFingersResult.positionWasAdjusted() ? elementUnderFatFinger : m_lastFatFingersResult.validNode());
+            m_convertTouchToMouse = shouldConvertTouchToMouse(possibleTargetNodeForMouseMoveEvents);
+
+            if (elementUnderFatFinger)
+                drawTapHighlight();
+
+            // Lets be conservative here: since we have problems on major website having
+            // mousemove listener for no good reason (e.g. google.com, desktop edition),
+            // let only delay client notifications when there is not input text node involved.
+            if (m_convertTouchToMouse
+                && (m_webPage->m_inputHandler->isInputMode() && !m_lastFatFingersResult.isTextInput())) {
+                m_webPage->m_inputHandler->setDelayClientNotificationOfNavigationModeChange(true);
+                handleFatFingerPressed();
+            } else if (!shouldSuppressMouseDownOnTouchDown())
+                handleFatFingerPressed();
+
+            return true;
+        }
+    case Platform::TouchPoint::TouchReleased:
+        {
+            m_webPage->m_inputHandler->processPendingClientNavigationModeChangeNotification();
+
+            if (shouldSuppressMouseDownOnTouchDown())
+                handleFatFingerPressed();
+
+            // The rebase has eliminated a necessary event when the mouse does not
+            // trigger an actual selection change preventing re-showing of the
+            // keyboard. If input mode is active, call setNavigationMode which
+            // will update the state and display keyboard if needed.
+            if (m_webPage->m_inputHandler->isInputMode())
+                m_webPage->m_inputHandler->setNavigationMode(true);
+
+            IntPoint adjustedPoint;
+            if (m_convertTouchToMouse) {
+                adjustedPoint = point.m_pos;
+                m_convertTouchToMouse = false;
+            } else // Fat finger point in viewport coordinates.
+                adjustedPoint = m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition());
+
+            PlatformMouseEvent mouseEvent(adjustedPoint, m_lastScreenPoint, MouseEventReleased, 1, LeftButton, TouchScreen);
+            m_webPage->handleMouseEvent(mouseEvent);
+            m_lastFatFingersResult.reset(); // Reset the fat finger result as its no longer valid when a user's finger is not on the screen.
+
+            unsigned spellLength = spellCheck(point);
+            if (spellLength) {
+                unsigned end = m_webPage->m_inputHandler->caretPosition();
+                unsigned start = end - spellLength;
+                m_webPage->m_client->requestSpellingSuggestionsForString(start, end);
+            }
+            return true;
+        }
+    case Platform::TouchPoint::TouchMoved:
+        if (m_convertTouchToMouse) {
+            PlatformMouseEvent mouseEvent(point.m_pos, m_lastScreenPoint, MouseEventMoved, 1, LeftButton, TouchScreen);
+            m_lastScreenPoint = point.m_screenPos;
+            if (!m_webPage->handleMouseEvent(mouseEvent)) {
+                m_convertTouchToMouse = false;
+                return false;
+            }
+            return true;
+        }
+        break;
+    default:
+        break;
+    }
+    return false;
+}
+
+unsigned TouchEventHandler::spellCheck(Platform::TouchPoint& touchPoint)
+{
+    Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
+    if (!m_lastFatFingersResult.isTextInput() || !elementUnderFatFinger)
+        return 0;
+
+    IntPoint contentPos(m_webPage->mapFromViewportToContents(touchPoint.m_pos));
+    contentPos = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), m_webPage->focusedOrMainFrame(), contentPos);
+
+    Document* document = elementUnderFatFinger->document();
+    ASSERT(document);
+    RenderedDocumentMarker* marker = document->markers()->renderedMarkerContainingPoint(contentPos, DocumentMarker::Spelling);
+    if (!marker)
+        return 0;
+
+    IntRect rect = marker->renderedRect();
+    IntPoint newContentPos = IntPoint(rect.x() + rect.width(), rect.y() + rect.height() / 2);
+    Frame* frame = m_webPage->focusedOrMainFrame();
+    if (frame != m_webPage->mainFrame())
+        newContentPos = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(newContentPos));
+    m_lastFatFingersResult.m_adjustedPosition = newContentPos;
+    m_lastFatFingersResult.m_positionWasAdjusted = true;
+    return marker->endOffset() - marker->startOffset();
+}
+
+void TouchEventHandler::handleFatFingerPressed()
+{
+    if (!m_didCancelTouch) {
+
+        // First update the mouse position with a MouseMoved event.
+        PlatformMouseEvent mouseMoveEvent(m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()), m_lastScreenPoint, MouseEventMoved, 0, LeftButton, TouchScreen);
+        m_webPage->handleMouseEvent(mouseMoveEvent);
+
+        // Then send the MousePressed event.
+        PlatformMouseEvent mousePressedEvent(m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()), m_lastScreenPoint, MouseEventPressed, 1, LeftButton, TouchScreen);
+        m_webPage->handleMouseEvent(mousePressedEvent);
+    }
+}
+
+// This method filters what element will get tap-highlight'ed or not. To start with,
+// we are going to highlight links (anchors with a valid href element), and elements
+// whose tap highlight color value is different than the default value.
+static Element* elementForTapHighlight(Element* elementUnderFatFinger)
+{
+    // Do not bail out right way here if there element does not have a renderer.
+    // It is the casefor <map> (descendent of <area>) elements. The associated <image>
+    // element actually has the renderer.
+    if (elementUnderFatFinger->renderer()) {
+        Color tapHighlightColor = elementUnderFatFinger->renderStyle()->tapHighlightColor();
+        if (tapHighlightColor != RenderTheme::defaultTheme()->platformTapHighlightColor())
+            return elementUnderFatFinger;
+    }
+
+    bool isArea = elementUnderFatFinger->hasTagName(HTMLNames::areaTag);
+    Node* linkNode = elementUnderFatFinger->enclosingLinkEventParentOrSelf();
+    if (!linkNode || !linkNode->isHTMLElement() || (!linkNode->renderer() && !isArea))
+        return 0;
+
+    ASSERT(linkNode->isLink());
+
+    // FatFingers class selector ensure only anchor with valid href attr value get here.
+    // It includes empty hrefs.
+    Element* highlightCandidateElement = static_cast<Element*>(linkNode);
+
+    if (!isArea)
+        return highlightCandidateElement;
+
+    HTMLAreaElement* area = static_cast<HTMLAreaElement*>(highlightCandidateElement);
+    HTMLImageElement* image = area->imageElement();
+    if (image && image->renderer())
+        return image;
+
+    return 0;
+}
+
+void TouchEventHandler::drawTapHighlight()
+{
+    Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable();
+    if (!elementUnderFatFinger)
+        return;
+
+    Element* element = elementForTapHighlight(elementUnderFatFinger);
+    if (!element)
+        return;
+
+    // Get the element bounding rect in transformed coordinates so we can extract
+    // the focus ring relative position each rect.
+    RenderObject* renderer = element->renderer();
+    ASSERT(renderer);
+
+    Frame* elementFrame = element->document()->frame();
+    ASSERT(elementFrame);
+
+    FrameView* elementFrameView = elementFrame->view();
+    if (!elementFrameView)
+        return;
+
+    // Tell the client if the element is either in a scrollable container or in a fixed positioned container.
+    // On the client side, this info is being used to hide the tap highlight window on scroll.
+    RenderLayer* layer = m_webPage->enclosingFixedPositionedAncestorOrSelfIfFixedPositioned(renderer->enclosingLayer());
+    bool shouldHideTapHighlightRightAfterScrolling = !layer->renderer()->isRenderView();
+    shouldHideTapHighlightRightAfterScrolling |= !!m_webPage->m_inRegionScrollStartingNode.get();
+
+    IntPoint framePos(m_webPage->frameOffset(elementFrame));
+
+    // FIXME: We can get more precise on the <map> case by calculating the rect with HTMLAreaElement::computeRect().
+    IntRect absoluteRect = renderer->absoluteClippedOverflowRect();
+    absoluteRect.move(framePos.x(), framePos.y());
+
+    IntRect clippingRect;
+    if (elementFrame == m_webPage->mainFrame())
+        clippingRect = IntRect(IntPoint(0, 0), elementFrameView->contentsSize());
+    else
+        clippingRect = m_webPage->mainFrame()->view()->windowToContents(m_webPage->getRecursiveVisibleWindowRect(elementFrameView, true /*noClipToMainFrame*/));
+    clippingRect = intersection(absoluteRect, clippingRect);
+
+    Vector<FloatQuad> focusRingQuads;
+    renderer->absoluteFocusRingQuads(focusRingQuads);
+
+    Platform::IntRectRegion region;
+    for (size_t i = 0; i < focusRingQuads.size(); ++i) {
+        IntRect rect = focusRingQuads[i].enclosingBoundingBox();
+        rect.move(framePos.x(), framePos.y());
+        IntRect clippedRect = intersection(clippingRect, rect);
+        clippedRect.inflate(2);
+        region = unionRegions(region, Platform::IntRect(clippedRect));
+    }
+
+    Color highlightColor = element->renderStyle()->tapHighlightColor();
+
+    m_webPage->m_client->drawTapHighlight(region,
+                                          highlightColor.red(),
+                                          highlightColor.green(),
+                                          highlightColor.blue(),
+                                          highlightColor.alpha(),
+                                          shouldHideTapHighlightRightAfterScrolling);
+}
+
+}
+}

Added: trunk/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.h (0 => 107972)


--- trunk/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.h	                        (rev 0)
+++ trunk/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.h	2012-02-16 20:55:34 UTC (rev 107972)
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef TouchEventHandler_h
+#define TouchEventHandler_h
+
+#include "ChromeClient.h"
+#include "FatFingers.h"
+#include "IntPoint.h"
+
+#include <BlackBerryPlatformTouchEvent.h>
+#include <BlackBerryPlatformWindow.h>
+
+namespace BlackBerry {
+namespace WebKit {
+
+class WebPagePrivate;
+
+class TouchEventHandler {
+public:
+    TouchEventHandler(WebPagePrivate* webpage);
+    ~TouchEventHandler();
+
+    bool handleTouchPoint(Platform::TouchPoint&);
+    void touchEventCancel();
+    void touchEventCancelAndClearFocusedNode();
+    void touchHoldEvent();
+
+    bool shouldSuppressMouseDownOnTouchDown() const;
+
+    const FatFingersResult& lastFatFingersResult() const { return m_lastFatFingersResult; }
+    void resetLastFatFingersResult() { m_lastFatFingersResult.reset(); }
+
+private:
+    unsigned spellCheck(Platform::TouchPoint&);
+    void handleFatFingerPressed();
+
+    void drawTapHighlight();
+
+private:
+    WebPagePrivate* m_webPage;
+
+    bool m_didCancelTouch;
+    bool m_convertTouchToMouse;
+
+    WebCore::TouchEventMode m_existingTouchMode;
+
+    WebCore::IntPoint m_lastScreenPoint; // Screen Position
+
+    FatFingersResult m_lastFatFingersResult;
+};
+
+}
+}
+
+#endif // TouchEventHandler_h
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to