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

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: Implemented root margin Created 5 years 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 "config.h"
6 #include "core/dom/IntersectionObserver.h"
7
8 #include "bindings/core/v8/ExceptionState.h"
9 #include "core/css/parser/CSSParserTokenRange.h"
10 #include "core/css/parser/CSSTokenizer.h"
11 #include "core/dom/ExceptionCode.h"
12 #include "core/dom/ExecutionContext.h"
13 #include "core/dom/IntersectionObserverCallback.h"
14 #include "core/dom/IntersectionObserverEntry.h"
15 #include "core/dom/IntersectionObserverInit.h"
16 #include "core/dom/IntersectionObserverRegistry.h"
17 #include "core/html/HTMLFrameOwnerElement.h"
18 #include "platform/Timer.h"
19 #include "wtf/MainThread.h"
20 #include <algorithm>
21
22 namespace blink {
23
24 IntersectionObserver* IntersectionObserver::create(const IntersectionObserverIni t& observerInit, IntersectionObserverCallback* callback, ExceptionState& excepti onState)
25 {
26 ASSERT(isMainThread());
esprehn 2015/12/12 00:14:14 no need for thread checks, the DOM is not thread s
szager1 2015/12/16 19:15:36 Fixed.
27
28 RefPtrWillBeRawPtr<Element> root = observerInit.root();
29 if (!root) {
30 ASSERT(callback);
31 ExecutionContext* context = callback->executionContext();
32 if (context->isDocument()) {
esprehn 2015/12/12 00:14:14 can we actually get here with an execution context
szager1 2015/12/16 19:15:35 Changed to ASSERT(context->isDocument());
33 Frame* mainFrame = static_cast<Document*>(context)->frame()->tree(). top();
esprehn 2015/12/12 00:14:14 toDocument, never static_cast DOM types, it misses
szager1 2015/12/16 19:15:35 Done.
34 if (mainFrame && mainFrame->isLocalFrame())
35 root = toLocalFrame(mainFrame)->document()->documentElement();
esprehn 2015/12/12 00:14:14 documentElement can be null, does the spec say tha
ojan 2015/12/12 01:06:29 If there's no root specified, then the root should
szager1 2015/12/16 19:15:36 That's going to make the code a lot grosser. Curr
36 }
37 }
38 if (!root) {
39 exceptionState.throwDOMException(HierarchyRequestError, "Unable to get r oot element in main frame to track.");
40 return nullptr;
41 }
42
43 Vector<Length> rootMargin;
44 if (observerInit.hasRootMargin()) {
45 CSSTokenizer::Scope tokenizerScope(observerInit.rootMargin());
esprehn 2015/12/12 00:14:16 move this into a static (or anon namespace) parse*
szager1 2015/12/16 19:15:36 Done.
46 CSSParserTokenRange tokenRange = tokenizerScope.tokenRange();
47 while (rootMargin.size() < 5 && tokenRange.peek().type() != EOFToken && !exceptionState.hadException()) {
48 const CSSParserToken& token = tokenRange.consumeIncludingWhitespace( );
49 switch (token.type()) {
50 case NumberToken:
51 rootMargin.append(Length(static_cast<int>(floor(token.numericVal ue())), Fixed));
52 break;
53 case PercentageToken:
54 rootMargin.append(Length(token.numericValue(), Percent));
55 break;
56 case DimensionToken:
57 switch (token.unitType()) {
58 case CSSPrimitiveValue::UnitType::Number:
ojan 2015/12/12 01:06:29 Is this Number thing correct? In regular CSS, you
szager1 2015/12/16 19:15:35 This is very unfamiliar territory for me, but the
59 case CSSPrimitiveValue::UnitType::Pixels:
60 rootMargin.append(Length(static_cast<int>(floor(token.numeri cValue())), Fixed));
61 break;
62 case CSSPrimitiveValue::UnitType::Percentage:
63 rootMargin.append(Length(token.numericValue(), Percent));
64 break;
65 default:
66 exceptionState.throwTypeError("rootMargin must be specified in pixels or percent.");
67 }
68 break;
69 default:
70 exceptionState.throwTypeError("rootMargin must be specified in p ixels or percent.");
71 }
72 }
73 }
74 if (exceptionState.hadException())
75 return nullptr;
76
77 Vector<float> thresholds;
78 if (observerInit.hasThreshold()) {
79 const DoubleOrDoubleArray& thresholdParam = observerInit.threshold();
80 if (thresholdParam.isDouble()) {
81 thresholds.append(static_cast<float>(thresholdParam.getAsDouble()));
82 } else {
83 for (auto thresholdValue: thresholdParam.getAsDoubleArray())
esprehn 2015/12/12 00:14:15 ditto
szager1 2015/12/16 19:15:36 Done.
84 thresholds.append(static_cast<float>(thresholdValue));
85 }
86 } else {
87 thresholds.append(0);
88 }
89 for (auto thresholdValue: thresholds) {
esprehn 2015/12/12 00:14:15 space before :
szager1 2015/12/16 19:15:35 Done.
90 if (thresholdValue < 0.0 || thresholdValue > 1.0) {
91 exceptionState.throwTypeError("Threshold values must be between 0 an d 1");
92 break;
93 }
94 }
95 std::sort(thresholds.begin(), thresholds.end());
96 if (exceptionState.hadException())
97 return nullptr;
98
99 return new IntersectionObserver(callback, root.get(), rootMargin, thresholds );
esprehn 2015/12/12 00:14:15 root can't be null, so this should be *root
szager1 2015/12/16 19:15:35 Done.
100 }
101
102 IntersectionObserver::IntersectionObserver(IntersectionObserverCallback* callbac k, Element* root, const Vector<Length>& rootMargin, const Vector<float>& thresho lds)
103 : m_callback(callback)
104 , m_root(root->createWeakPtr())
105 , m_observations(new IntersectionObservation::WeakHashSet())
106 , m_entries(new IntersectionObserverEntryVector())
esprehn 2015/12/12 00:14:14 write types, why are the sets heap allocated thoug
szager1 2015/12/16 19:15:36 Done.
107 , m_thresholds(thresholds)
108 {
109 switch (rootMargin.size()) {
esprehn 2015/12/12 00:14:15 what happens if the size is > 4? Shouldn't we thro
szager1 2015/12/16 19:15:36 I figured we would just ignore extra junk at the e
110 case 0:
111 break;
112 case 1:
113 m_topMargin = m_rightMargin = m_bottomMargin = m_leftMargin = rootMargin [0];
114 break;
115 case 2:
116 m_topMargin = m_bottomMargin = rootMargin[0];
117 m_rightMargin = m_leftMargin = rootMargin[1];
118 break;
119 case 3:
120 m_topMargin = rootMargin[0];
121 m_rightMargin = m_leftMargin = rootMargin[1];
122 m_bottomMargin = rootMargin[2];
123 break;
124 default:
125 m_topMargin = rootMargin[0];
126 m_rightMargin = rootMargin[1];
127 m_bottomMargin = rootMargin[2];
128 m_leftMargin = rootMargin[3];
esprehn 2015/12/12 00:14:15 we could be clever and I think you can actually ex
szager1 2015/12/16 19:15:35 Acknowledged.
129 break;
130 }
131 root->document().intersectionObserverRegistry()->addTrackedObserver(*this);
132 }
133
134 bool IntersectionObserver::isDescendantOfRoot(Element* target) const
esprehn 2015/12/12 00:14:14 reference
szager1 2015/12/16 19:15:36 Same comment as header: since this method assigns
135 {
136 Element* rootElement = m_root.get();
137 if (!rootElement)
138 return false;
139
140 if (target == rootElement || !target->inDocument() || !rootElement->inDocume nt())
esprehn 2015/12/12 00:14:15 multiple return statements
szager1 2015/12/16 19:15:36 Done.
141 return false;
142 LocalFrame* rootFrame = rootElement->document().frame();
143 LocalFrame* targetFrame = target->document().frame();
144 if (!rootFrame || !targetFrame || !targetFrame->tree().isDescendantOf(rootFr ame))
145 return false;
146 while (targetFrame != rootFrame) {
esprehn 2015/12/12 00:14:14 why do you need this loop if you have the targetFr
szager1 2015/12/16 19:15:36 I need to make sure that the root element is an an
147 FrameOwner* targetOwner = targetFrame->owner();
148 if (!targetOwner || !targetOwner->isLocal())
149 return false;
150 target = toHTMLFrameOwnerElement(targetOwner);
esprehn 2015/12/12 00:14:15 use ownerElement() and loop through the document p
szager1 2015/12/16 19:15:36 Done.
151 ASSERT(target);
esprehn 2015/12/12 00:14:14 this isn't possible, you null checked targetOwner
szager1 2015/12/16 19:15:35 Fixed.
152 targetFrame = target->document().frame();
153 if (!targetFrame)
esprehn 2015/12/12 00:14:15 normally you'd loop through document().ownerElemen
szager1 2015/12/16 19:15:36 Done.
154 return false;
155 }
156 return target->isDescendantOf(rootElement);
157 }
158
159 void IntersectionObserver::observe(Element* target, ExceptionState& exceptionSta te)
160 {
161 checkRootAndDetachIfNecessary();
162 if (!m_root)
163 exceptionState.throwDOMException(HierarchyRequestError, "Invalid observe r: root element or containing document has been deleted.");
164 else if (!target)
165 exceptionState.throwTypeError("Observation target must be an element.");
166 else if (m_root.get() == target)
167 exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the same element for root and target.");
ojan 2015/12/12 01:06:29 Is this not covered by the isDescendantOfRoot chec
szager1 2015/12/16 19:15:35 I thought it might be nice to call this error out
168 else if (!isDescendantOfRoot(target))
169 exceptionState.throwDOMException(HierarchyRequestError, "Observed elemen t must be a descendant of the observer's root element.");
170 if (exceptionState.hadException())
esprehn 2015/12/12 00:14:15 we'd normally do each one as a separate block with
szager1 2015/12/16 19:15:35 Done.
171 return;
172
173 bool canReportRootBounds = target->document().frame()->securityContext()->se curityOrigin()->canAccess(root()->document().frame()->securityContext()->securit yOrigin());
174 if (!canReportRootBounds && hasPercentMargin()) {
175 exceptionState.throwDOMException(HierarchyRequestError, "Cannot observe a cross-origin target because the observer has a root margin value specified as a percent.");
176 return;
177 }
178
179 if (IntersectionObservation::contains(*m_observations, this, target))
180 return;
181
182 IntersectionObservation* observation = new IntersectionObservation(*this, ta rget, canReportRootBounds);
183 m_observations->add(observation);
184 }
185
186 void IntersectionObserver::unobserve(Element* target, ExceptionState&)
187 {
188 checkRootAndDetachIfNecessary();
189 if (!target)
190 return;
191 IntersectionObservation::WeakHashSet::iterator observationIterator = Interse ctionObservation::find(*m_observations, this, target);
esprehn 2015/12/12 00:14:15 auto for the type here, also use the controller, n
szager1 2015/12/16 19:15:35 Done.
192 // TODO: unobserve callback
193 if (observationIterator != m_observations->end())
194 (*observationIterator)->disconnect();
195 }
196
197 void IntersectionObserver::computeIntersectionObservations(int timestamp)
esprehn 2015/12/12 00:14:15 double?
szager1 2015/12/16 19:15:35 Fixed.
198 {
199 checkRootAndDetachIfNecessary();
200 if (!m_root)
201 return;
202 for (auto& observation: *m_observations)
esprehn 2015/12/12 00:14:14 space
szager1 2015/12/16 19:15:36 Done.
203 observation->computeIntersectionObservations(timestamp);
204 }
205
206 void IntersectionObserver::disconnect(IntersectionObservation& observation)
207 {
208 m_observations->remove(&observation);
209 }
210
211 void IntersectionObserver::disconnect()
212 {
213 checkRootAndDetachIfNecessary();
214 HeapVector<Member<IntersectionObservation>> toDisconnect;
215 for (auto& observation: *m_observations)
esprehn 2015/12/12 00:14:16 missing space before colon in all your loops
szager1 2015/12/16 19:15:35 Fixed.
216 toDisconnect.append(observation);
217 for (auto& observation: toDisconnect)
esprehn 2015/12/12 00:14:14 itto
szager1 2015/12/16 19:15:36 Done.
218 observation->disconnect();
219 ASSERT(!m_observations->size());
esprehn 2015/12/12 00:14:14 ->isEmpty()
szager1 2015/12/16 19:15:36 Done.
220 }
221
222 IntersectionObserverEntryVector IntersectionObserver::takeRecords()
esprehn 2015/12/12 00:14:14 put type here
szager1 2015/12/16 19:15:36 Done.
223 {
224 checkRootAndDetachIfNecessary();
225 IntersectionObserverEntryVector entries;
226 entries.swap(*m_entries);
227 return entries;
228 }
229
230 void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserver Entry* entry)
esprehn 2015/12/12 00:14:14 reference
szager1 2015/12/16 19:15:36 Done.
231 {
232 ASSERT(isMainThread());
esprehn 2015/12/12 00:14:14 remove
szager1 2015/12/16 19:15:36 Done.
233 m_entries->append(entry);
234 static_cast<Document*>(m_callback->executionContext())->intersectionObserver Registry()->scheduleIntersectionObserverForDelivery(*this);
esprehn 2015/12/12 00:14:14 toDocument()
szager1 2015/12/16 19:15:36 Done.
235 }
236
237 static int computeMargin(const Length& length, LayoutUnit referenceLength)
238 {
239 if (length.type() == Percent)
240 return static_cast<int>(referenceLength.toFloat() * length.percent() / 1 00.);
esprehn 2015/12/12 00:14:15 .0
szager1 2015/12/16 19:15:36 Done.
241 return length.intValue();
242 }
243
244 void IntersectionObserver::applyRootMargin(LayoutRect& rect) const
245 {
246 int topMargin = computeMargin(m_topMargin, rect.height());
247 int rightMargin = computeMargin(m_rightMargin, rect.width());
248 int bottomMargin = computeMargin(m_bottomMargin, rect.height());
249 int leftMargin = computeMargin(m_leftMargin, rect.width());
250
251 rect.setX(rect.x() - leftMargin);
252 rect.setWidth(rect.width() + leftMargin + rightMargin);
253 rect.setY(rect.y() - topMargin);
254 rect.setHeight(rect.height() + topMargin + bottomMargin);
255 }
256
257 size_t IntersectionObserver::firstThresholdGreaterThan(float ratio) const
258 {
259 size_t result = 0;
260 size_t max = m_thresholds.size();
261 while (result < max && m_thresholds[result] < ratio)
esprehn 2015/12/12 00:14:14 no reason to cache the max on the stack like that
szager1 2015/12/16 19:15:35 Done.
262 ++result;
263 return result;
264 }
265
266 bool IntersectionObserver::shouldBeSuspended() const
267 {
268 return m_callback->executionContext() && m_callback->executionContext()->act iveDOMObjectsAreSuspended();
269 }
270
271 void IntersectionObserver::deliver()
272 {
273 checkRootAndDetachIfNecessary();
274
275 ASSERT(!shouldBeSuspended());
276
277 if (m_entries->isEmpty())
esprehn 2015/12/12 00:14:14 why is m_entries a pointer and not just a member?
szager1 2015/12/16 19:15:36 Fixed.
278 return;
279
280 IntersectionObserverEntryVector entries;
281 entries.swap(*m_entries);
282 m_callback->handleEvent(entries, this);
esprehn 2015/12/12 00:14:14 *this
szager1 2015/12/16 19:15:35 Done.
283 }
284
285 void IntersectionObserver::setActive(bool active)
286 {
287 checkRootAndDetachIfNecessary();
288 for (auto& observation: *m_observations)
esprehn 2015/12/12 00:14:14 space
szager1 2015/12/16 19:15:35 Done.
289 observation->setActive(m_root && active && isDescendantOfRoot(observatio n->target()));
290 }
291
292 bool IntersectionObserver::hasPercentMargin() const
293 {
294 return m_topMargin.type() == Percent || m_rightMargin.type() == Percent || m _bottomMargin.type() == Percent || m_leftMargin.type() == Percent;
295 }
296
297 void IntersectionObserver::checkRootAndDetachIfNecessary()
298 {
299 if (m_root)
300 return;
301 m_callback.clear();
302 HeapVector<Member<IntersectionObservation>> toDisconnect;
303 for (auto& observation: *m_observations)
esprehn 2015/12/12 00:14:15 space
szager1 2015/12/16 19:15:35 Done.
304 toDisconnect.append(observation);
305 for (auto& observation: toDisconnect)
esprehn 2015/12/12 00:14:15 space
szager1 2015/12/16 19:15:35 Done.
306 observation->disconnect();
307 ASSERT(!m_observations->size());
esprehn 2015/12/12 00:14:14 isEmpty()
szager1 2015/12/16 19:15:36 Done.
308 // TODO: should we deliver pending notifications?
309 m_entries.clear();
310 }
311
312 DEFINE_TRACE(IntersectionObserver)
313 {
314 visitor->trace(m_callback);
315 visitor->trace(m_root);
316 visitor->trace(m_observations);
317 visitor->trace(m_entries);
318 }
319
320 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698