| OLD | NEW | 
|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 "core/dom/IntersectionObserver.h" | 5 #include "core/dom/IntersectionObserver.h" | 
| 6 | 6 | 
| 7 #include "bindings/core/v8/ExceptionState.h" | 7 #include "bindings/core/v8/ExceptionState.h" | 
| 8 #include "core/css/parser/CSSParserTokenRange.h" | 8 #include "core/css/parser/CSSParserTokenRange.h" | 
| 9 #include "core/css/parser/CSSTokenizer.h" | 9 #include "core/css/parser/CSSTokenizer.h" | 
| 10 #include "core/dom/ElementIntersectionObserverData.h" | 10 #include "core/dom/ElementIntersectionObserverData.h" | 
| 11 #include "core/dom/ExceptionCode.h" | 11 #include "core/dom/ExceptionCode.h" | 
| 12 #include "core/dom/ExecutionContext.h" | 12 #include "core/dom/ExecutionContext.h" | 
| 13 #include "core/dom/IntersectionObserverCallback.h" | 13 #include "core/dom/IntersectionObserverCallback.h" | 
| 14 #include "core/dom/IntersectionObserverController.h" | 14 #include "core/dom/IntersectionObserverController.h" | 
| 15 #include "core/dom/IntersectionObserverEntry.h" | 15 #include "core/dom/IntersectionObserverEntry.h" | 
| 16 #include "core/dom/IntersectionObserverInit.h" | 16 #include "core/dom/IntersectionObserverInit.h" | 
| 17 #include "core/html/HTMLFrameOwnerElement.h" | 17 #include "core/html/HTMLFrameOwnerElement.h" | 
| 18 #include "core/layout/LayoutView.h" | 18 #include "core/layout/LayoutView.h" | 
| 19 #include "platform/Timer.h" | 19 #include "platform/Timer.h" | 
| 20 #include "wtf/MainThread.h" | 20 #include "wtf/MainThread.h" | 
| 21 #include <algorithm> | 21 #include <algorithm> | 
| 22 | 22 | 
| 23 namespace blink { | 23 namespace blink { | 
| 24 | 24 | 
|  | 25 static void parseRootMargin(String rootMarginParameter, Vector<Length>& rootMarg
     in, ExceptionState& exceptionState) | 
|  | 26 { | 
|  | 27     // TODO(szager): Make sure this exact syntax and behavior is spec-ed somewhe
     re. | 
|  | 28 | 
|  | 29     // The root margin argument accepts syntax similar to that for CSS margin: | 
|  | 30     // | 
|  | 31     // "1px" = top/right/bottom/left | 
|  | 32     // "1px 2px" = top/bottom left/right | 
|  | 33     // "1px 2px 3px" = top left/right bottom | 
|  | 34     // "1px 2px 3px 4px" = top left right bottom | 
|  | 35     // | 
|  | 36     // Any extra stuff after the first four tokens is ignored. | 
|  | 37     CSSTokenizer::Scope tokenizerScope(rootMarginParameter); | 
|  | 38     CSSParserTokenRange tokenRange = tokenizerScope.tokenRange(); | 
|  | 39     while (rootMargin.size() < 4 && tokenRange.peek().type() != EOFToken && !exc
     eptionState.hadException()) { | 
|  | 40         const CSSParserToken& token = tokenRange.consumeIncludingWhitespace(); | 
|  | 41         switch (token.type()) { | 
|  | 42         case PercentageToken: | 
|  | 43             rootMargin.append(Length(token.numericValue(), Percent)); | 
|  | 44             break; | 
|  | 45         case DimensionToken: | 
|  | 46             switch (token.unitType()) { | 
|  | 47             case CSSPrimitiveValue::UnitType::Pixels: | 
|  | 48                 rootMargin.append(Length(static_cast<int>(floor(token.numericVal
     ue())), Fixed)); | 
|  | 49                 break; | 
|  | 50             case CSSPrimitiveValue::UnitType::Percentage: | 
|  | 51                 rootMargin.append(Length(token.numericValue(), Percent)); | 
|  | 52                 break; | 
|  | 53             default: | 
|  | 54                 exceptionState.throwTypeError("rootMargin must be specified in p
     ixels or percent."); | 
|  | 55             } | 
|  | 56             break; | 
|  | 57         default: | 
|  | 58             exceptionState.throwTypeError("rootMargin must be specified in pixel
     s or percent."); | 
|  | 59         } | 
|  | 60     } | 
|  | 61 } | 
|  | 62 | 
| 25 static void parseThresholds(const DoubleOrDoubleArray& thresholdParameter, Vecto
     r<float>& thresholds, ExceptionState& exceptionState) | 63 static void parseThresholds(const DoubleOrDoubleArray& thresholdParameter, Vecto
     r<float>& thresholds, ExceptionState& exceptionState) | 
| 26 { | 64 { | 
| 27     if (thresholdParameter.isDouble()) { | 65     if (thresholdParameter.isDouble()) { | 
| 28         thresholds.append(static_cast<float>(thresholdParameter.getAsDouble())); | 66         thresholds.append(static_cast<float>(thresholdParameter.getAsDouble())); | 
| 29     } else { | 67     } else { | 
| 30         for (auto thresholdValue : thresholdParameter.getAsDoubleArray()) | 68         for (auto thresholdValue : thresholdParameter.getAsDoubleArray()) | 
| 31             thresholds.append(static_cast<float>(thresholdValue)); | 69             thresholds.append(static_cast<float>(thresholdValue)); | 
| 32     } | 70     } | 
| 33 | 71 | 
| 34     for (auto thresholdValue : thresholds) { | 72     for (auto thresholdValue : thresholds) { | 
| (...skipping 15 matching lines...) Expand all  Loading... | 
| 50         ASSERT(context->isDocument()); | 88         ASSERT(context->isDocument()); | 
| 51         Frame* mainFrame = toDocument(context)->frame()->tree().top(); | 89         Frame* mainFrame = toDocument(context)->frame()->tree().top(); | 
| 52         if (mainFrame && mainFrame->isLocalFrame()) | 90         if (mainFrame && mainFrame->isLocalFrame()) | 
| 53             root = toLocalFrame(mainFrame)->document()->documentElement(); | 91             root = toLocalFrame(mainFrame)->document()->documentElement(); | 
| 54     } | 92     } | 
| 55     if (!root) { | 93     if (!root) { | 
| 56         exceptionState.throwDOMException(HierarchyRequestError, "Unable to get r
     oot element in main frame to track."); | 94         exceptionState.throwDOMException(HierarchyRequestError, "Unable to get r
     oot element in main frame to track."); | 
| 57         return nullptr; | 95         return nullptr; | 
| 58     } | 96     } | 
| 59 | 97 | 
|  | 98     Vector<Length> rootMargin; | 
|  | 99     if (observerInit.hasRootMargin()) | 
|  | 100         parseRootMargin(observerInit.rootMargin(), rootMargin, exceptionState); | 
|  | 101     if (exceptionState.hadException()) | 
|  | 102         return nullptr; | 
|  | 103 | 
| 60     Vector<float> thresholds; | 104     Vector<float> thresholds; | 
| 61     if (observerInit.hasThreshold()) | 105     if (observerInit.hasThreshold()) | 
| 62         parseThresholds(observerInit.threshold(), thresholds, exceptionState); | 106         parseThresholds(observerInit.threshold(), thresholds, exceptionState); | 
| 63     else | 107     else | 
| 64         thresholds.append(0); | 108         thresholds.append(0); | 
| 65     if (exceptionState.hadException()) | 109     if (exceptionState.hadException()) | 
| 66         return nullptr; | 110         return nullptr; | 
| 67 | 111 | 
| 68     return new IntersectionObserver(callback, *root, thresholds); | 112     return new IntersectionObserver(callback, *root, rootMargin, thresholds); | 
| 69 } | 113 } | 
| 70 | 114 | 
| 71 IntersectionObserver::IntersectionObserver(IntersectionObserverCallback& callbac
     k, Element& root, const Vector<float>& thresholds) | 115 IntersectionObserver::IntersectionObserver(IntersectionObserverCallback& callbac
     k, Element& root, const Vector<Length>& rootMargin, const Vector<float>& thresho
     lds) | 
| 72     : m_callback(&callback) | 116     : m_callback(&callback) | 
| 73     , m_root(root.ensureIntersectionObserverData().createWeakPtr(&root)) | 117     , m_root(root.ensureIntersectionObserverData().createWeakPtr(&root)) | 
| 74     , m_thresholds(thresholds) | 118     , m_thresholds(thresholds) | 
|  | 119     , m_topMargin(Fixed) | 
|  | 120     , m_rightMargin(Fixed) | 
|  | 121     , m_bottomMargin(Fixed) | 
|  | 122     , m_leftMargin(Fixed) | 
| 75 { | 123 { | 
|  | 124     switch (rootMargin.size()) { | 
|  | 125     case 0: | 
|  | 126         break; | 
|  | 127     case 1: | 
|  | 128         m_topMargin = m_rightMargin = m_bottomMargin = m_leftMargin = rootMargin
     [0]; | 
|  | 129         break; | 
|  | 130     case 2: | 
|  | 131         m_topMargin = m_bottomMargin = rootMargin[0]; | 
|  | 132         m_rightMargin = m_leftMargin = rootMargin[1]; | 
|  | 133         break; | 
|  | 134     case 3: | 
|  | 135         m_topMargin = rootMargin[0]; | 
|  | 136         m_rightMargin = m_leftMargin = rootMargin[1]; | 
|  | 137         m_bottomMargin = rootMargin[2]; | 
|  | 138         break; | 
|  | 139     case 4: | 
|  | 140         m_topMargin = rootMargin[0]; | 
|  | 141         m_rightMargin = rootMargin[1]; | 
|  | 142         m_bottomMargin = rootMargin[2]; | 
|  | 143         m_leftMargin = rootMargin[3]; | 
|  | 144         break; | 
|  | 145     default: | 
|  | 146         ASSERT_NOT_REACHED(); | 
|  | 147         break; | 
|  | 148     } | 
| 76     root.document().ensureIntersectionObserverController().addTrackedObserver(*t
     his); | 149     root.document().ensureIntersectionObserverController().addTrackedObserver(*t
     his); | 
| 77 } | 150 } | 
| 78 | 151 | 
| 79 LayoutObject* IntersectionObserver::rootLayoutObject() | 152 LayoutObject* IntersectionObserver::rootLayoutObject() | 
| 80 { | 153 { | 
| 81     Element* rootElement = root(); | 154     Element* rootElement = root(); | 
| 82     if (rootElement == rootElement->document().documentElement()) | 155     if (rootElement == rootElement->document().documentElement()) | 
| 83         return rootElement->document().layoutView(); | 156         return rootElement->document().layoutView(); | 
| 84     return rootElement->layoutObject(); | 157     return rootElement->layoutObject(); | 
| 85 } | 158 } | 
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 117     } | 190     } | 
| 118     if (m_root.get() == target) { | 191     if (m_root.get() == target) { | 
| 119         exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the 
     same element for root and target."); | 192         exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the 
     same element for root and target."); | 
| 120         return; | 193         return; | 
| 121     } | 194     } | 
| 122     if (!isDescendantOfRoot(target)) { | 195     if (!isDescendantOfRoot(target)) { | 
| 123         exceptionState.throwDOMException(HierarchyRequestError, "Observed elemen
     t must be a descendant of the observer's root element."); | 196         exceptionState.throwDOMException(HierarchyRequestError, "Observed elemen
     t must be a descendant of the observer's root element."); | 
| 124         return; | 197         return; | 
| 125     } | 198     } | 
| 126 | 199 | 
|  | 200     // TODO(szager): Add a pointer to the spec that describes this policy. | 
| 127     bool shouldReportRootBounds = target->document().frame()->securityContext()-
     >securityOrigin()->canAccess(root()->document().frame()->securityContext()->secu
     rityOrigin()); | 201     bool shouldReportRootBounds = target->document().frame()->securityContext()-
     >securityOrigin()->canAccess(root()->document().frame()->securityContext()->secu
     rityOrigin()); | 
|  | 202     if (!shouldReportRootBounds && hasPercentMargin()) { | 
|  | 203         exceptionState.throwDOMException(HierarchyRequestError, "Cannot observe 
     a cross-origin target because the observer has a root margin value specified as 
     a percent."); | 
|  | 204         return; | 
|  | 205     } | 
| 128 | 206 | 
| 129     if (target->ensureIntersectionObserverData().getObservationFor(*this)) | 207     if (target->ensureIntersectionObserverData().getObservationFor(*this)) | 
| 130         return; | 208         return; | 
| 131 | 209 | 
| 132     IntersectionObservation* observation = new IntersectionObservation(*this, *t
     arget, shouldReportRootBounds); | 210     IntersectionObservation* observation = new IntersectionObservation(*this, *t
     arget, shouldReportRootBounds); | 
| 133     target->ensureIntersectionObserverData().addObservation(*observation); | 211     target->ensureIntersectionObserverData().addObservation(*observation); | 
| 134     m_observations.add(observation); | 212     m_observations.add(observation); | 
| 135 } | 213 } | 
| 136 | 214 | 
| 137 void IntersectionObserver::unobserve(Element* target, ExceptionState&) | 215 void IntersectionObserver::unobserve(Element* target, ExceptionState&) | 
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 174     entries.swap(m_entries); | 252     entries.swap(m_entries); | 
| 175     return entries; | 253     return entries; | 
| 176 } | 254 } | 
| 177 | 255 | 
| 178 void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserver
     Entry& entry) | 256 void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserver
     Entry& entry) | 
| 179 { | 257 { | 
| 180     m_entries.append(&entry); | 258     m_entries.append(&entry); | 
| 181     toDocument(m_callback->executionContext())->ensureIntersectionObserverContro
     ller().scheduleIntersectionObserverForDelivery(*this); | 259     toDocument(m_callback->executionContext())->ensureIntersectionObserverContro
     ller().scheduleIntersectionObserverForDelivery(*this); | 
| 182 } | 260 } | 
| 183 | 261 | 
|  | 262 static LayoutUnit computeMargin(const Length& length, LayoutUnit referenceLength
     ) | 
|  | 263 { | 
|  | 264     if (length.type() == Percent) | 
|  | 265         return LayoutUnit(static_cast<int>(referenceLength.toFloat() * length.pe
     rcent() / 100.0)); | 
|  | 266     ASSERT(length.type() == Fixed); | 
|  | 267     return LayoutUnit(length.intValue()); | 
|  | 268 } | 
|  | 269 | 
|  | 270 void IntersectionObserver::applyRootMargin(LayoutRect& rect) const | 
|  | 271 { | 
|  | 272     // TODO(szager): Make sure the spec is clear that left/right margins are res
     olved against | 
|  | 273     // width and not height. | 
|  | 274     LayoutUnit topMargin = computeMargin(m_topMargin, rect.height()); | 
|  | 275     LayoutUnit rightMargin = computeMargin(m_rightMargin, rect.width()); | 
|  | 276     LayoutUnit bottomMargin = computeMargin(m_bottomMargin, rect.height()); | 
|  | 277     LayoutUnit leftMargin = computeMargin(m_leftMargin, rect.width()); | 
|  | 278 | 
|  | 279     rect.setX(rect.x() - leftMargin); | 
|  | 280     rect.setWidth(rect.width() + leftMargin + rightMargin); | 
|  | 281     rect.setY(rect.y() - topMargin); | 
|  | 282     rect.setHeight(rect.height() + topMargin + bottomMargin); | 
|  | 283 } | 
|  | 284 | 
| 184 unsigned IntersectionObserver::firstThresholdGreaterThan(float ratio) const | 285 unsigned IntersectionObserver::firstThresholdGreaterThan(float ratio) const | 
| 185 { | 286 { | 
| 186     unsigned result = 0; | 287     unsigned result = 0; | 
| 187     while (result < m_thresholds.size() && m_thresholds[result] < ratio) | 288     while (result < m_thresholds.size() && m_thresholds[result] < ratio) | 
| 188         ++result; | 289         ++result; | 
| 189     return result; | 290     return result; | 
| 190 } | 291 } | 
| 191 | 292 | 
| 192 void IntersectionObserver::deliver() | 293 void IntersectionObserver::deliver() | 
| 193 { | 294 { | 
| 194     checkRootAndDetachIfNeeded(); | 295     checkRootAndDetachIfNeeded(); | 
| 195 | 296 | 
| 196     if (m_entries.isEmpty()) | 297     if (m_entries.isEmpty()) | 
| 197         return; | 298         return; | 
| 198 | 299 | 
| 199     HeapVector<Member<IntersectionObserverEntry>> entries; | 300     HeapVector<Member<IntersectionObserverEntry>> entries; | 
| 200     entries.swap(m_entries); | 301     entries.swap(m_entries); | 
| 201     m_callback->handleEvent(entries, *this); | 302     m_callback->handleEvent(entries, *this); | 
| 202 } | 303 } | 
| 203 | 304 | 
| 204 void IntersectionObserver::setActive(bool active) | 305 void IntersectionObserver::setActive(bool active) | 
| 205 { | 306 { | 
| 206     checkRootAndDetachIfNeeded(); | 307     checkRootAndDetachIfNeeded(); | 
| 207     for (auto& observation : m_observations) | 308     for (auto& observation : m_observations) | 
| 208         observation->setActive(m_root && active && isDescendantOfRoot(observatio
     n->target())); | 309         observation->setActive(m_root && active && isDescendantOfRoot(observatio
     n->target())); | 
| 209 } | 310 } | 
| 210 | 311 | 
|  | 312 bool IntersectionObserver::hasPercentMargin() const | 
|  | 313 { | 
|  | 314     return (m_topMargin.type() == Percent | 
|  | 315         || m_rightMargin.type() == Percent | 
|  | 316         || m_bottomMargin.type() == Percent | 
|  | 317         || m_leftMargin.type() == Percent); | 
|  | 318 } | 
|  | 319 | 
| 211 void IntersectionObserver::checkRootAndDetachIfNeeded() | 320 void IntersectionObserver::checkRootAndDetachIfNeeded() | 
| 212 { | 321 { | 
| 213 #if ENABLE(OILPAN) | 322 #if ENABLE(OILPAN) | 
| 214     // TODO(szager): Pre-oilpan, ElementIntersectionObserverData::dispose() will
      take | 323     // TODO(szager): Pre-oilpan, ElementIntersectionObserverData::dispose() will
      take | 
| 215     // care of this cleanup.  When oilpan ships, there will be a potential leak 
     of the | 324     // care of this cleanup.  When oilpan ships, there will be a potential leak 
     of the | 
| 216     // callback's execution context when the root goes away.  For a detailed exp
     lanation: | 325     // callback's execution context when the root goes away.  For a detailed exp
     lanation: | 
| 217     // | 326     // | 
| 218     //   https://goo.gl/PC2Baj | 327     //   https://goo.gl/PC2Baj | 
| 219     // | 328     // | 
| 220     // When that happens, this method should catch most potential leaks, but a c
     omplete | 329     // When that happens, this method should catch most potential leaks, but a c
     omplete | 
| 221     // solution will still be needed, along the lines described in the above lin
     k. | 330     // solution will still be needed, along the lines described in the above lin
     k. | 
| 222     if (m_root) | 331     if (m_root) | 
| 223         return; | 332         return; | 
| 224     disconnect(); | 333     disconnect(); | 
| 225 #endif | 334 #endif | 
| 226 } | 335 } | 
| 227 | 336 | 
| 228 DEFINE_TRACE(IntersectionObserver) | 337 DEFINE_TRACE(IntersectionObserver) | 
| 229 { | 338 { | 
| 230     visitor->trace(m_callback); | 339     visitor->trace(m_callback); | 
| 231     visitor->trace(m_root); | 340     visitor->trace(m_root); | 
| 232     visitor->trace(m_observations); | 341     visitor->trace(m_observations); | 
| 233     visitor->trace(m_entries); | 342     visitor->trace(m_entries); | 
| 234 } | 343 } | 
| 235 | 344 | 
| 236 } // namespace blink | 345 } // namespace blink | 
| OLD | NEW | 
|---|