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 |