1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "web/ViewportAnchor.h" 33 34 #include "core/dom/ContainerNode.h" 35 #include "core/dom/Node.h" 36 #include "core/page/EventHandler.h" 37 #include "core/rendering/HitTestResult.h" 38 #include "platform/scroll/ScrollView.h" 39 40 namespace blink { 41 42 namespace { 43 44 static const float viewportAnchorRelativeEpsilon = 0.1f; 45 static const int viewportToNodeMaxRelativeArea = 2; 46 47 template <typename RectType> 48 int area(const RectType& rect) { 49 return rect.width() * rect.height(); 50 } 51 52 Node* findNonEmptyAnchorNode(const IntPoint& point, const IntRect& viewRect, EventHandler* eventHandler) 53 { 54 Node* node = eventHandler->hitTestResultAtPoint(point, HitTestRequest::ReadOnly | HitTestRequest::Active).innerNode(); 55 56 // If the node bounding box is sufficiently large, make a single attempt to 57 // find a smaller node; the larger the node bounds, the greater the 58 // variability under resize. 59 const int maxNodeArea = area(viewRect) * viewportToNodeMaxRelativeArea; 60 if (node && area(node->boundingBox()) > maxNodeArea) { 61 IntSize pointOffset = viewRect.size(); 62 pointOffset.scale(viewportAnchorRelativeEpsilon); 63 node = eventHandler->hitTestResultAtPoint(point + pointOffset, HitTestRequest::ReadOnly | HitTestRequest::Active).innerNode(); 64 } 65 66 while (node && node->boundingBox().isEmpty()) 67 node = node->parentNode(); 68 69 return node; 70 } 71 72 void moveToEncloseRect(IntRect& outer, const FloatRect& inner) 73 { 74 IntPoint minimumPosition = ceiledIntPoint(inner.location() + inner.size() - FloatSize(outer.size())); 75 IntPoint maximumPosition = flooredIntPoint(inner.location()); 76 77 IntPoint outerOrigin = outer.location(); 78 outerOrigin = outerOrigin.expandedTo(minimumPosition); 79 outerOrigin = outerOrigin.shrunkTo(maximumPosition); 80 81 outer.setLocation(outerOrigin); 82 } 83 84 void moveIntoRect(FloatRect& inner, const IntRect& outer) 85 { 86 FloatPoint minimumPosition = FloatPoint(outer.location()); 87 FloatPoint maximumPosition = minimumPosition + outer.size() - inner.size(); 88 89 // Adjust maximumPosition to the nearest lower integer because 90 // PinchViewport::maximumScrollPosition() does the same. 91 // The value of minumumPosition is already adjusted since it is 92 // constructed from an integer point. 93 maximumPosition = flooredIntPoint(maximumPosition); 94 95 FloatPoint innerOrigin = inner.location(); 96 innerOrigin = innerOrigin.expandedTo(minimumPosition); 97 innerOrigin = innerOrigin.shrunkTo(maximumPosition); 98 99 inner.setLocation(innerOrigin); 100 } 101 102 } // namespace 103 104 ViewportAnchor::ViewportAnchor(EventHandler* eventHandler) 105 : m_eventHandler(eventHandler) { } 106 107 void ViewportAnchor::setAnchor(const IntRect& outerViewRect, const IntRect& innerViewRect, 108 const FloatSize& anchorInInnerViewCoords) 109 { 110 // Preserve the inner viewport position in document in case we won't find the anchor 111 m_pinchViewportInDocument = innerViewRect.location(); 112 113 m_anchorNode.clear(); 114 m_anchorNodeBounds = LayoutRect(); 115 m_anchorInNodeCoords = FloatSize(); 116 m_anchorInInnerViewCoords = anchorInInnerViewCoords; 117 m_normalizedPinchViewportOffset = FloatSize(); 118 119 if (innerViewRect.isEmpty()) 120 return; 121 122 // Preserve origins at the absolute screen origin 123 if (innerViewRect.location() == IntPoint::zero()) 124 return; 125 126 // Inner rectangle should be within the outer one. 127 ASSERT(outerViewRect.contains(innerViewRect)); 128 129 // Outer rectangle is used as a scale, we need positive width and height. 130 ASSERT(!outerViewRect.isEmpty()); 131 132 m_normalizedPinchViewportOffset = innerViewRect.location() - outerViewRect.location(); 133 134 // Normalize by the size of the outer rect 135 m_normalizedPinchViewportOffset.scale(1.0 / outerViewRect.width(), 1.0 / outerViewRect.height()); 136 137 FloatSize anchorOffset = innerViewRect.size(); 138 anchorOffset.scale(anchorInInnerViewCoords.width(), anchorInInnerViewCoords.height()); 139 const FloatPoint anchorPoint = FloatPoint(innerViewRect.location()) + anchorOffset; 140 141 Node* node = findNonEmptyAnchorNode(flooredIntPoint(anchorPoint), innerViewRect, m_eventHandler); 142 if (!node) 143 return; 144 145 m_anchorNode = node; 146 m_anchorNodeBounds = node->boundingBox(); 147 m_anchorInNodeCoords = anchorPoint - m_anchorNodeBounds.location(); 148 m_anchorInNodeCoords.scale(1.f / m_anchorNodeBounds.width(), 1.f / m_anchorNodeBounds.height()); 149 } 150 151 void ViewportAnchor::computeOrigins(const ScrollView& scrollView, const FloatSize& innerSize, 152 IntPoint& mainFrameOffset, FloatPoint& pinchViewportOffset) const 153 { 154 IntSize outerSize = scrollView.visibleContentRect().size(); 155 156 // Compute the viewport origins in CSS pixels relative to the document. 157 FloatSize absPinchViewportOffset = m_normalizedPinchViewportOffset; 158 absPinchViewportOffset.scale(outerSize.width(), outerSize.height()); 159 160 FloatPoint innerOrigin = getInnerOrigin(innerSize); 161 FloatPoint outerOrigin = innerOrigin - absPinchViewportOffset; 162 163 IntRect outerRect = IntRect(flooredIntPoint(outerOrigin), outerSize); 164 FloatRect innerRect = FloatRect(innerOrigin, innerSize); 165 166 moveToEncloseRect(outerRect, innerRect); 167 168 outerRect.setLocation(scrollView.adjustScrollPositionWithinRange(outerRect.location())); 169 170 moveIntoRect(innerRect, outerRect); 171 172 mainFrameOffset = outerRect.location(); 173 pinchViewportOffset = FloatPoint(innerRect.location() - outerRect.location()); 174 } 175 176 FloatPoint ViewportAnchor::getInnerOrigin(const FloatSize& innerSize) const 177 { 178 if (!m_anchorNode || !m_anchorNode->inDocument()) 179 return m_pinchViewportInDocument; 180 181 const LayoutRect currentNodeBounds = m_anchorNode->boundingBox(); 182 if (m_anchorNodeBounds == currentNodeBounds) 183 return m_pinchViewportInDocument; 184 185 // Compute the new anchor point relative to the node position 186 FloatSize anchorOffsetFromNode = currentNodeBounds.size(); 187 anchorOffsetFromNode.scale(m_anchorInNodeCoords.width(), m_anchorInNodeCoords.height()); 188 FloatPoint anchorPoint = currentNodeBounds.location() + anchorOffsetFromNode; 189 190 // Compute the new origin point relative to the new anchor point 191 FloatSize anchorOffsetFromOrigin = innerSize; 192 anchorOffsetFromOrigin.scale(m_anchorInInnerViewCoords.width(), m_anchorInInnerViewCoords.height()); 193 return anchorPoint - anchorOffsetFromOrigin; 194 } 195 196 } // namespace blink 197