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

Side by Side Diff: third_party/WebKit/Source/core/dom/IntersectionObserver.cpp

Issue 1449623002: IntersectionObserver: second cut. (Closed) Base URL: https://chromium.googlesource.com/chromium/src@master
Patch Set: sigbjornf nits Created 4 years, 11 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
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "core/dom/IntersectionObserver.h"
6
7 #include "bindings/core/v8/ExceptionState.h"
8 #include "core/css/parser/CSSParserTokenRange.h"
9 #include "core/css/parser/CSSTokenizer.h"
10 #include "core/dom/ElementIntersectionObserverData.h"
11 #include "core/dom/ExceptionCode.h"
12 #include "core/dom/ExecutionContext.h"
13 #include "core/dom/IntersectionObserverCallback.h"
14 #include "core/dom/IntersectionObserverController.h"
15 #include "core/dom/IntersectionObserverEntry.h"
16 #include "core/dom/IntersectionObserverInit.h"
17 #include "core/html/HTMLFrameOwnerElement.h"
18 #include "core/layout/LayoutView.h"
19 #include "platform/Timer.h"
20 #include "wtf/MainThread.h"
21 #include <algorithm>
22
23 namespace blink {
24
25 static void parseRootMargin(String rootMarginParameter, Vector<Length>& rootMarg in, ExceptionState& exceptionState)
26 {
27 // The root margin argument accepts syntax similar to that for CSS margin:
28 //
29 // "1px" = top/right/bottom/left
30 // "1px 2px" = top/bottom left/right
31 // "1px 2px 3px" = top left/right bottom
32 // "1px 2px 3px 4px" = top left right bottom
33 //
34 // Any extra stuff after the first four tokens is ignored.
35 CSSTokenizer::Scope tokenizerScope(rootMarginParameter);
36 CSSParserTokenRange tokenRange = tokenizerScope.tokenRange();
37 while (rootMargin.size() < 5 && tokenRange.peek().type() != EOFToken && !exc eptionState.hadException()) {
38 const CSSParserToken& token = tokenRange.consumeIncludingWhitespace();
39 switch (token.type()) {
40 case PercentageToken:
41 rootMargin.append(Length(token.numericValue(), Percent));
42 break;
43 case DimensionToken:
44 switch (token.unitType()) {
45 case CSSPrimitiveValue::UnitType::Pixels:
46 rootMargin.append(Length(static_cast<int>(floor(token.numericVal ue())), Fixed));
47 break;
48 case CSSPrimitiveValue::UnitType::Percentage:
49 rootMargin.append(Length(token.numericValue(), Percent));
50 break;
51 default:
52 exceptionState.throwTypeError("rootMargin must be specified in p ixels or percent.");
53 }
54 break;
55 default:
56 exceptionState.throwTypeError("rootMargin must be specified in pixel s or percent.");
57 }
58 }
59 }
60
61 static void parseThresholds(const DoubleOrDoubleArray& thresholdParameter, Vecto r<float>& thresholds, ExceptionState& exceptionState)
62 {
63 if (thresholdParameter.isDouble()) {
64 thresholds.append(static_cast<float>(thresholdParameter.getAsDouble()));
65 } else {
66 for (auto thresholdValue : thresholdParameter.getAsDoubleArray())
67 thresholds.append(static_cast<float>(thresholdValue));
68 }
69
70 for (auto thresholdValue : thresholds) {
71 if (thresholdValue < 0.0 || thresholdValue > 1.0) {
72 exceptionState.throwTypeError("Threshold values must be between 0 an d 1");
73 break;
74 }
75 }
76
77 std::sort(thresholds.begin(), thresholds.end());
78 }
79
80 IntersectionObserver* IntersectionObserver::create(const IntersectionObserverIni t& observerInit, IntersectionObserverCallback& callback, ExceptionState& excepti onState)
81 {
82 RefPtrWillBeRawPtr<Element> root = observerInit.root();
83 if (!root) {
84 // TODO(szager): Use Document instead of document element for implicit r oot. (crbug.com/570538)
85 ExecutionContext* context = callback.executionContext();
86 ASSERT(context->isDocument());
87 Frame* mainFrame = toDocument(context)->frame()->tree().top();
88 if (mainFrame && mainFrame->isLocalFrame())
89 root = toLocalFrame(mainFrame)->document()->documentElement();
90 }
91 if (!root) {
92 exceptionState.throwDOMException(HierarchyRequestError, "Unable to get r oot element in main frame to track.");
93 return nullptr;
94 }
95
96 Vector<Length> rootMargin;
97 if (observerInit.hasRootMargin())
98 parseRootMargin(observerInit.rootMargin(), rootMargin, exceptionState);
99 if (exceptionState.hadException())
100 return nullptr;
101
102 Vector<float> thresholds;
103 if (observerInit.hasThreshold())
104 parseThresholds(observerInit.threshold(), thresholds, exceptionState);
105 else
106 thresholds.append(0);
107 if (exceptionState.hadException())
108 return nullptr;
109
110 return new IntersectionObserver(callback, *root, rootMargin, thresholds);
111 }
112
113 IntersectionObserver::IntersectionObserver(IntersectionObserverCallback& callbac k, Element& root, const Vector<Length>& rootMargin, const Vector<float>& thresho lds)
114 : m_callback(&callback)
115 , m_root(root.ensureIntersectionObserverData().createWeakPtr(&root))
116 , m_thresholds(thresholds)
117 {
118 switch (rootMargin.size()) {
119 case 0:
120 break;
121 case 1:
122 m_topMargin = m_rightMargin = m_bottomMargin = m_leftMargin = rootMargin [0];
123 break;
124 case 2:
125 m_topMargin = m_bottomMargin = rootMargin[0];
126 m_rightMargin = m_leftMargin = rootMargin[1];
127 break;
128 case 3:
129 m_topMargin = rootMargin[0];
130 m_rightMargin = m_leftMargin = rootMargin[1];
131 m_bottomMargin = rootMargin[2];
132 break;
133 case 4:
134 m_topMargin = rootMargin[0];
135 m_rightMargin = rootMargin[1];
136 m_bottomMargin = rootMargin[2];
137 m_leftMargin = rootMargin[3];
138 break;
139 default:
140 ASSERT_NOT_REACHED();
141 break;
142 }
143 root.document().ensureIntersectionObserverController().addTrackedObserver(*t his);
144 }
145
146 LayoutObject* IntersectionObserver::rootLayoutObject()
147 {
148 Element* rootElement = root();
149 if (rootElement == rootElement->document().documentElement())
150 return rootElement->document().layoutView();
151 return rootElement->layoutObject();
152 }
153
154 bool IntersectionObserver::isDescendantOfRoot(const Element* target) const
155 {
156 // Is m_root an ancestor, through the DOM and frame trees, of target?
157 Element* rootElement = m_root.get();
158 if (!rootElement || !target || target == rootElement)
159 return false;
160 if (!target->inDocument() || !rootElement->inDocument())
161 return false;
162
163 Document* rootDocument = &rootElement->document();
164 Document* targetDocument = &target->document();
165 while (targetDocument != rootDocument) {
166 target = targetDocument->ownerElement();
167 if (!target)
168 return false;
169 targetDocument = &target->document();
170 }
171 return target->isDescendantOf(rootElement);
172 }
173
174 void IntersectionObserver::observe(Element* target, ExceptionState& exceptionSta te)
175 {
176 checkRootAndDetachIfNeeded();
177 if (!m_root) {
178 exceptionState.throwDOMException(HierarchyRequestError, "Invalid observe r: root element or containing document has been deleted.");
179 return;
180 }
181 if (!target) {
182 exceptionState.throwTypeError("Observation target must be an element.");
183 return;
184 }
185 if (m_root.get() == target) {
186 exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the same element for root and target.");
187 return;
188 }
189 if (!isDescendantOfRoot(target)) {
190 exceptionState.throwDOMException(HierarchyRequestError, "Observed elemen t must be a descendant of the observer's root element.");
191 return;
192 }
193
194 bool shouldReportRootBounds = target->document().frame()->securityContext()- >securityOrigin()->canAccess(root()->document().frame()->securityContext()->secu rityOrigin());
195 if (!shouldReportRootBounds && hasPercentMargin()) {
196 exceptionState.throwDOMException(HierarchyRequestError, "Cannot observe a cross-origin target because the observer has a root margin value specified as a percent.");
197 return;
198 }
199
200 if (target->ensureIntersectionObserverData().hasObservationFor(*this))
201 return;
202
203 IntersectionObservation* observation = new IntersectionObservation(*this, *t arget, shouldReportRootBounds);
204 target->ensureIntersectionObserverData().addObservation(*observation);
205 m_observations.add(observation);
206 }
207
208 void IntersectionObserver::unobserve(Element* target, ExceptionState&)
209 {
210 checkRootAndDetachIfNeeded();
211 if (!target || !target->intersectionObserverData())
212 return;
213 // TODO(szager): unobserve callback
214 target->ensureIntersectionObserverData().removeObservation(*this);
215 }
216
217 void IntersectionObserver::computeIntersectionObservations(double timestamp)
218 {
219 checkRootAndDetachIfNeeded();
220 if (!m_root)
221 return;
222 for (auto& observation : m_observations)
223 observation->computeIntersectionObservations(timestamp);
224 }
225
226 void IntersectionObserver::disconnect(IntersectionObservation& observation)
haraken 2016/01/02 13:47:40 As commented in IntersectionObserver::disconnect,
227 {
228 m_observations.remove(&observation);
229 }
230
231 void IntersectionObserver::disconnect()
232 {
233 checkRootAndDetachIfNeeded();
234 HeapVector<Member<IntersectionObservation>> toDisconnect;
235 for (auto& observation : m_observations)
236 toDisconnect.append(observation);
haraken 2016/01/02 13:47:40 Use copyToVector.
szager1 2016/01/02 19:18:34 Done.
237 for (auto& observation : toDisconnect)
238 observation->disconnect();
239 ASSERT(m_observations.isEmpty());
240 }
241
242 HeapVector<Member<IntersectionObserverEntry>> IntersectionObserver::takeRecords( )
243 {
244 checkRootAndDetachIfNeeded();
245 HeapVector<Member<IntersectionObserverEntry>> entries;
246 entries.swap(m_entries);
247 return entries;
248 }
249
250 void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserver Entry& entry)
251 {
252 m_entries.append(&entry);
253 toDocument(m_callback->executionContext())->ensureIntersectionObserverContro ller().scheduleIntersectionObserverForDelivery(*this);
254 }
255
256 static LayoutUnit computeMargin(const Length& length, LayoutUnit referenceLength )
257 {
258 if (length.type() == Percent)
259 return LayoutUnit(static_cast<int>(referenceLength.toFloat() * length.pe rcent() / 100.0));
260 return LayoutUnit(length.intValue());
261 }
262
263 void IntersectionObserver::applyRootMargin(LayoutRect& rect) const
264 {
265 LayoutUnit topMargin = computeMargin(m_topMargin, rect.height());
266 LayoutUnit rightMargin = computeMargin(m_rightMargin, rect.width());
267 LayoutUnit bottomMargin = computeMargin(m_bottomMargin, rect.height());
268 LayoutUnit leftMargin = computeMargin(m_leftMargin, rect.width());
269
270 rect.setX(rect.x() - leftMargin);
271 rect.setWidth(rect.width() + leftMargin + rightMargin);
272 rect.setY(rect.y() - topMargin);
273 rect.setHeight(rect.height() + topMargin + bottomMargin);
274 }
275
276 unsigned IntersectionObserver::firstThresholdGreaterThan(float ratio) const
277 {
278 unsigned result = 0;
279 while (result < m_thresholds.size() && m_thresholds[result] < ratio)
280 ++result;
281 return result;
282 }
283
284 bool IntersectionObserver::shouldBeSuspended() const
haraken 2016/01/02 13:47:40 To reduce the complexity of this CL, I'd propose n
szager1 2016/01/02 19:18:34 I'm a bit concerned that something could go very w
285 {
286 return m_callback->executionContext() && m_callback->executionContext()->act iveDOMObjectsAreSuspended();
287 }
288
289 void IntersectionObserver::deliver()
290 {
291 checkRootAndDetachIfNeeded();
292
293 ASSERT(!shouldBeSuspended());
294
295 if (m_entries.isEmpty())
296 return;
297
298 HeapVector<Member<IntersectionObserverEntry>> entries;
299 entries.swap(m_entries);
300 m_callback->handleEvent(entries, *this);
301 }
302
303 void IntersectionObserver::setActive(bool active)
304 {
305 checkRootAndDetachIfNeeded();
306 for (auto& observation : m_observations)
307 observation->setActive(m_root && active && isDescendantOfRoot(observatio n->target()));
308 }
309
310 bool IntersectionObserver::hasPercentMargin() const
311 {
312 return (m_topMargin.type() == Percent
313 || m_rightMargin.type() == Percent
314 || m_bottomMargin.type() == Percent
315 || m_leftMargin.type() == Percent);
316 }
317
318 #if !ENABLE(OILPAN)
319 void IntersectionObserver::dispose()
haraken 2016/01/02 13:47:40 I think you can remove this dispose method, for th
320 {
321 m_root.clear();
322 checkRootAndDetachIfNeeded();
323 }
324 #endif
325
326 void IntersectionObserver::checkRootAndDetachIfNeeded()
327 {
328 #if ENABLE(OILPAN)
329 // TODO(szager): Pre-oilpan, ElementIntersectionObserverData::dispose() will take
330 // care of this cleanup. When oilpan ships, there will be a potential leak of the
331 // callback's execution context when the root goes away. For a detailed exp lanation:
332 //
333 // https://goo.gl/PC2Baj
334 //
335 // When that happens, this method should catch most potential leaks, but a c omplete
336 // solution will still be needed, along the lines described in the above lin k.
337
338 // TODO(szager): As a performance optimization, clear the hash tables before disconnecting
339 // the observations, and avoid hash lookups in IntersectionObservation::deta ch.
340 // That would make the ASSERT superfluous, but let's leave it as is for a wh ile
341 // to see if the ASSERT ever fails.
342 if (m_root)
343 return;
344 m_callback.clear();
345 HeapVector<Member<IntersectionObservation>> toDisconnect;
346 for (auto& observation : m_observations)
347 toDisconnect.append(observation);
348 for (auto& observation : toDisconnect)
349 observation->disconnect();
350 ASSERT(m_observations.isEmpty());
351 // TODO(szager): should we deliver pending notifications?
352 m_entries.clear();
353 #endif
354 }
355
356 DEFINE_TRACE(IntersectionObserver)
357 {
358 visitor->trace(m_callback);
359 visitor->trace(m_root);
360 visitor->trace(m_observations);
361 visitor->trace(m_entries);
362 }
363
364 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698