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

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: Add missing break, rebaseline, no config.h Created 4 years, 12 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 CSSTokenizer::Scope tokenizerScope(rootMarginParameter);
28 CSSParserTokenRange tokenRange = tokenizerScope.tokenRange();
29 while (rootMargin.size() < 5 && tokenRange.peek().type() != EOFToken && !exc eptionState.hadException()) {
haraken 2015/12/22 01:13:30 Where does 5 come from?
esprehn 2015/12/22 07:50:32 It's parsing a sequence of lengths, this could use
szager1 2015/12/22 07:53:41 It accepts syntax similar to padding, e.g.: "1px"
szager1 2015/12/22 08:49:50 Added comment.
30 const CSSParserToken& token = tokenRange.consumeIncludingWhitespace();
31 switch (token.type()) {
32 case PercentageToken:
33 rootMargin.append(Length(token.numericValue(), Percent));
34 break;
35 case DimensionToken:
36 switch (token.unitType()) {
37 case CSSPrimitiveValue::UnitType::Pixels:
38 rootMargin.append(Length(static_cast<int>(floor(token.numericVal ue())), Fixed));
39 break;
40 case CSSPrimitiveValue::UnitType::Percentage:
41 rootMargin.append(Length(token.numericValue(), Percent));
42 break;
43 default:
44 exceptionState.throwTypeError("rootMargin must be specified in p ixels or percent.");
45 }
46 break;
47 default:
48 exceptionState.throwTypeError("rootMargin must be specified in pixel s or percent.");
49 }
50 }
51 }
52
53 static void parseThresholds(const DoubleOrDoubleArray& thresholdParameter, Vecto r<float>& thresholds, ExceptionState& exceptionState)
54 {
55 if (thresholdParameter.isDouble()) {
56 thresholds.append(static_cast<float>(thresholdParameter.getAsDouble()));
57 } else {
58 for (auto thresholdValue : thresholdParameter.getAsDoubleArray())
59 thresholds.append(static_cast<float>(thresholdValue));
60 }
61
62 for (auto thresholdValue : thresholds) {
63 if (thresholdValue < 0.0 || thresholdValue > 1.0) {
64 exceptionState.throwTypeError("Threshold values must be between 0 an d 1");
65 break;
66 }
67 }
68
69 std::sort(thresholds.begin(), thresholds.end());
70 }
71
72 IntersectionObserver* IntersectionObserver::create(const IntersectionObserverIni t& observerInit, IntersectionObserverCallback& callback, ExceptionState& excepti onState)
73 {
74 RefPtrWillBeRawPtr<Element> root = observerInit.root();
75 if (!root) {
76 // TODO(szager): Use Document instead of document element for implicit r oot. (crbug.com/570538)
77 ExecutionContext* context = callback.executionContext();
78 ASSERT(context->isDocument());
79 Frame* mainFrame = toDocument(context)->frame()->tree().top();
80 if (mainFrame && mainFrame->isLocalFrame())
81 root = toLocalFrame(mainFrame)->document()->documentElement();
82 }
83 if (!root) {
84 exceptionState.throwDOMException(HierarchyRequestError, "Unable to get r oot element in main frame to track.");
85 return nullptr;
86 }
87
88 Vector<Length> rootMargin;
89 if (observerInit.hasRootMargin())
90 parseRootMargin(observerInit.rootMargin(), rootMargin, exceptionState);
91 if (exceptionState.hadException())
92 return nullptr;
93
94 Vector<float> thresholds;
95 if (observerInit.hasThreshold())
96 parseThresholds(observerInit.threshold(), thresholds, exceptionState);
97 else
98 thresholds.append(0);
99 if (exceptionState.hadException())
100 return nullptr;
101
102 return new IntersectionObserver(callback, *root, rootMargin, thresholds);
103 }
104
105 IntersectionObserver::IntersectionObserver(IntersectionObserverCallback& callbac k, Element& root, const Vector<Length>& rootMargin, const Vector<float>& thresho lds)
106 : m_callback(&callback)
107 , m_root(root.ensureIntersectionObserverData().createWeakPtr(&root))
108 , m_thresholds(thresholds)
109 {
110 switch (rootMargin.size()) {
111 case 0:
112 break;
113 case 1:
114 m_topMargin = m_rightMargin = m_bottomMargin = m_leftMargin = rootMargin [0];
115 break;
116 case 2:
117 m_topMargin = m_bottomMargin = rootMargin[0];
118 m_rightMargin = m_leftMargin = rootMargin[1];
119 break;
120 case 3:
121 m_topMargin = rootMargin[0];
122 m_rightMargin = m_leftMargin = rootMargin[1];
123 m_bottomMargin = rootMargin[2];
124 break;
125 case 4:
126 m_topMargin = rootMargin[0];
127 m_rightMargin = rootMargin[1];
128 m_bottomMargin = rootMargin[2];
129 m_leftMargin = rootMargin[3];
130 break;
131 default:
132 ASSERT_NOT_REACHED();
133 break;
134 }
135 root.document().intersectionObserverController().addTrackedObserver(*this);
136 }
137
138 LayoutObject* IntersectionObserver::rootLayoutObject()
139 {
140 Element* rootElement = root();
141 if (rootElement == rootElement->document().documentElement())
142 return rootElement->document().layoutView();
143 return rootElement->layoutObject();
144 }
145
146 bool IntersectionObserver::isDescendantOfRoot(const Element* target) const
haraken 2015/12/22 01:13:30 It would be helpful to have a comment to explain w
esprehn 2015/12/22 07:50:33 It's the m_root member variable. The only special
szager1 2015/12/22 07:53:41 Added a comment.
147 {
148 Element* rootElement = m_root.get();
149 if (!rootElement || !target || target == rootElement)
150 return false;
151 if (!target->inDocument() || !rootElement->inDocument())
152 return false;
153
154 Document* rootDocument = &rootElement->document();
155 Document* targetDocument = &target->document();
156 while (targetDocument != rootDocument) {
157 target = targetDocument->ownerElement();
158 if (!target)
159 return false;
160 targetDocument = &target->document();
161 }
162 return target->isDescendantOf(rootElement);
163 }
164
165 void IntersectionObserver::observe(Element* target, ExceptionState& exceptionSta te)
166 {
167 checkRootAndDetachIfNeeded();
168 if (!m_root) {
169 exceptionState.throwDOMException(HierarchyRequestError, "Invalid observe r: root element or containing document has been deleted.");
170 return;
171 }
172 if (!target) {
173 exceptionState.throwTypeError("Observation target must be an element.");
174 return;
175 }
176 if (m_root.get() == target) {
177 exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the same element for root and target.");
178 return;
179 }
180 if (!isDescendantOfRoot(target)) {
181 exceptionState.throwDOMException(HierarchyRequestError, "Observed elemen t must be a descendant of the observer's root element.");
182 return;
183 }
184
185 bool shouldReportRootBounds = target->document().frame()->securityContext()- >securityOrigin()->canAccess(root()->document().frame()->securityContext()->secu rityOrigin());
186 if (!shouldReportRootBounds && hasPercentMargin()) {
187 exceptionState.throwDOMException(HierarchyRequestError, "Cannot observe a cross-origin target because the observer has a root margin value specified as a percent.");
188 return;
189 }
190
191 if (target->ensureIntersectionObserverData().hasObservationFor(*this))
192 return;
193
194 IntersectionObservation* observation = new IntersectionObservation(*this, *t arget, shouldReportRootBounds);
195 m_observations.add(observation);
196 }
197
198 void IntersectionObserver::unobserve(Element* target, ExceptionState&)
199 {
200 checkRootAndDetachIfNeeded();
201 if (!target || !target->hasIntersectionObserverData())
202 return;
203 // TODO(szager): unobserve callback
204 target->ensureIntersectionObserverData().removeObservation(*this);
205 }
206
207 void IntersectionObserver::computeIntersectionObservations(double timestamp)
208 {
209 checkRootAndDetachIfNeeded();
210 if (!m_root)
211 return;
212 for (auto& observation : m_observations)
213 observation->computeIntersectionObservations(timestamp);
214 }
215
216 void IntersectionObserver::disconnect(IntersectionObservation& observation)
haraken 2015/12/22 01:13:30 Nit: We can just pass IntersectionObservation* ins
szager1 2015/12/22 07:53:41 This is contrary to esprehn's comments.
217 {
218 m_observations.remove(&observation);
haraken 2015/12/22 01:13:30 Add ASSERT(m_observations.contains(observation)).
szager1 2015/12/22 07:53:41 Done.
szager1 2015/12/22 19:04:36 Actually, no, that's a bad ASSERT. Because m_obse
219 }
220
221 void IntersectionObserver::disconnect()
222 {
223 checkRootAndDetachIfNeeded();
224 HeapVector<Member<IntersectionObservation>> toDisconnect;
225 for (auto& observation : m_observations)
226 toDisconnect.append(observation);
227 for (auto& observation : toDisconnect)
228 observation->disconnect();
229 ASSERT(m_observations.isEmpty());
230 }
231
232 HeapVector<Member<IntersectionObserverEntry>> IntersectionObserver::takeRecords( )
233 {
234 checkRootAndDetachIfNeeded();
esprehn 2015/12/22 07:50:33 having to call this in each method still feels wro
szager1 2015/12/22 08:49:50 I agree wholeheartedly, that is the next thing I p
235 HeapVector<Member<IntersectionObserverEntry>> entries;
236 entries.swap(m_entries);
237 return entries;
238 }
239
240 void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserver Entry& entry)
241 {
242 m_entries.append(&entry);
243 toDocument(m_callback->executionContext())->intersectionObserverController() .scheduleIntersectionObserverForDelivery(*this);
244 }
245
246 static int computeMargin(const Length& length, LayoutUnit referenceLength)
esprehn 2015/12/22 07:50:33 can this return LayoutUnit instead?
szager1 2015/12/22 08:49:50 Done.
247 {
248 if (length.type() == Percent)
249 return static_cast<int>(referenceLength.toFloat() * length.percent() / 1 00.0);
250 return length.intValue();
251 }
252
253 void IntersectionObserver::applyRootMargin(LayoutRect& rect) const
254 {
255 int topMargin = computeMargin(m_topMargin, rect.height());
256 int rightMargin = computeMargin(m_rightMargin, rect.width());
257 int bottomMargin = computeMargin(m_bottomMargin, rect.height());
258 int leftMargin = computeMargin(m_leftMargin, rect.width());
259
260 rect.setX(rect.x() - leftMargin);
esprehn 2015/12/22 07:50:32 LayoutRect is of LayoutUnit, so you convert from L
szager1 2015/12/22 08:49:50 Done.
261 rect.setWidth(rect.width() + leftMargin + rightMargin);
262 rect.setY(rect.y() - topMargin);
263 rect.setHeight(rect.height() + topMargin + bottomMargin);
264 }
265
266 unsigned IntersectionObserver::firstThresholdGreaterThan(float ratio) const
267 {
268 unsigned result = 0;
269 while (result < m_thresholds.size() && m_thresholds[result] < ratio)
270 ++result;
271 return result;
272 }
273
274 bool IntersectionObserver::shouldBeSuspended() const
275 {
276 return m_callback->executionContext() && m_callback->executionContext()->act iveDOMObjectsAreSuspended();
277 }
278
279 void IntersectionObserver::deliver()
280 {
281 checkRootAndDetachIfNeeded();
282
283 ASSERT(!shouldBeSuspended());
284
285 if (m_entries.isEmpty())
286 return;
287
288 HeapVector<Member<IntersectionObserverEntry>> entries;
289 entries.swap(m_entries);
290 m_callback->handleEvent(entries, *this);
291 }
292
293 void IntersectionObserver::setActive(bool active)
294 {
295 checkRootAndDetachIfNeeded();
296 for (auto& observation : m_observations)
297 observation->setActive(m_root && active && isDescendantOfRoot(observatio n->target()));
298 }
299
300 bool IntersectionObserver::hasPercentMargin() const
301 {
302 return (m_topMargin.type() == Percent
303 || m_rightMargin.type() == Percent
304 || m_bottomMargin.type() == Percent
305 || m_leftMargin.type() == Percent);
306 }
307
308 #if !ENABLE(OILPAN)
309 void IntersectionObserver::dispose()
310 {
311 m_root.clear();
312 checkRootAndDetachIfNeeded();
313 }
314 #endif
315
316 void IntersectionObserver::checkRootAndDetachIfNeeded()
317 {
318 #if ENABLE(OILPAN)
319 // TODO(szager): Pre-oilpan, ElementIntersectionObserverData::dispose() will take
haraken 2015/12/22 01:13:30 I'm concerned about this but will comment on it af
esprehn 2015/12/22 07:50:32 Yeah this doesn't seem like something we want with
szager1 2015/12/22 07:53:41 Just to be clear: I don't think your current round
szager1 2015/12/22 08:49:50 registerWeakMembers looks like it might work here,
320 // care of this cleanup. When oilpan ships, there will be a potential leak of the
321 // callback's execution context when the root goes away. For a detailed exp lanation:
322 //
323 // https://goo.gl/PC2Baj
324 //
325 // When that happens, this method should catch most potential leaks, but a c omplete
326 // solution will still be needed, along the lines described in the above lin k.
327
328 // TODO(szager): As a performance optimization, clear the hash tables before disconnecting
329 // the observations, and avoid hash lookups in IntersectionObservation::deta ch.
330 // That would make the ASSERT superfluous, but let's leave it as is for a wh ile
331 // to see if the ASSERT ever fails.
332 if (m_root)
333 return;
334 m_callback.clear();
335 HeapVector<Member<IntersectionObservation>> toDisconnect;
336 for (auto& observation : m_observations)
337 toDisconnect.append(observation);
338 for (auto& observation : toDisconnect)
339 observation->disconnect();
340 ASSERT(m_observations.isEmpty());
341 // TODO(szager): should we deliver pending notifications?
esprehn 2015/12/22 07:50:33 Pending records need to keep the root alive, other
szager1 2015/12/22 08:49:50 Once I figure out a non-leaky fix for the root lif
342 m_entries.clear();
343 #endif
344 }
345
346 DEFINE_TRACE(IntersectionObserver)
347 {
348 visitor->trace(m_callback);
349 visitor->trace(m_root);
350 visitor->trace(m_observations);
351 visitor->trace(m_entries);
352 }
353
354 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698