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

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: Added dispose() methods for expicit cleanup 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/ElementIntersectionObserverData.h"
12 #include "core/dom/ExceptionCode.h"
13 #include "core/dom/ExecutionContext.h"
14 #include "core/dom/IntersectionObserverCallback.h"
15 #include "core/dom/IntersectionObserverController.h"
16 #include "core/dom/IntersectionObserverEntry.h"
17 #include "core/dom/IntersectionObserverInit.h"
18 #include "core/html/HTMLFrameOwnerElement.h"
19 #include "core/layout/LayoutView.h"
20 #include "platform/Timer.h"
21 #include "wtf/MainThread.h"
22 #include <algorithm>
23
24 namespace blink {
25
26 static void parseRootMargin(String rootMarginParameter, Vector<Length>& rootMarg in, ExceptionState& exceptionState)
27 {
28 CSSTokenizer::Scope tokenizerScope(rootMarginParameter);
29 CSSParserTokenRange tokenRange = tokenizerScope.tokenRange();
30 while (rootMargin.size() < 5 && tokenRange.peek().type() != EOFToken && !exc eptionState.hadException()) {
31 const CSSParserToken& token = tokenRange.consumeIncludingWhitespace();
32 switch (token.type()) {
33 case PercentageToken:
34 rootMargin.append(Length(token.numericValue(), Percent));
35 break;
36 case DimensionToken:
37 switch (token.unitType()) {
38 case CSSPrimitiveValue::UnitType::Pixels:
39 rootMargin.append(Length(static_cast<int>(floor(token.numericVal ue())), Fixed));
40 break;
41 case CSSPrimitiveValue::UnitType::Percentage:
42 rootMargin.append(Length(token.numericValue(), Percent));
43 break;
44 default:
45 exceptionState.throwTypeError("rootMargin must be specified in p ixels or percent.");
46 }
47 break;
48 default:
49 exceptionState.throwTypeError("rootMargin must be specified in pixel s or percent.");
50 }
51 }
52 }
53
54 static void parseThresholds(const DoubleOrDoubleArray& thresholdParameter, Vecto r<float>& thresholds, ExceptionState& exceptionState)
55 {
56 if (thresholdParameter.isDouble()) {
57 thresholds.append(static_cast<float>(thresholdParameter.getAsDouble()));
58 } else {
59 for (auto thresholdValue : thresholdParameter.getAsDoubleArray())
60 thresholds.append(static_cast<float>(thresholdValue));
61 }
62
63 for (auto thresholdValue : thresholds) {
64 if (thresholdValue < 0.0 || thresholdValue > 1.0) {
65 exceptionState.throwTypeError("Threshold values must be between 0 an d 1");
66 break;
67 }
68 }
69
70 std::sort(thresholds.begin(), thresholds.end());
71 }
72
73 IntersectionObserver* IntersectionObserver::create(const IntersectionObserverIni t& observerInit, IntersectionObserverCallback& callback, ExceptionState& excepti onState)
74 {
75 RefPtrWillBeRawPtr<Element> root = observerInit.root();
76 if (!root) {
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();
esprehn 2015/12/17 01:40:28 Add a TODO and maybe link to that new bug?
szager1 2015/12/17 20:27:26 Done.
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.intersectionObserverData().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 default:
esprehn 2015/12/17 01:40:28 case 4: and the default should ASSERT_NOT_REACHED
szager1 2015/12/17 20:27:26 Done.
126 m_topMargin = rootMargin[0];
127 m_rightMargin = rootMargin[1];
128 m_bottomMargin = rootMargin[2];
129 m_leftMargin = rootMargin[3];
130 break;
esprehn 2015/12/17 01:40:28 I feel like we should be able to ASSERT_NOT_REACHE
szager1 2015/12/17 20:27:26 Done.
131 }
132 root.document().intersectionObserverController()->addTrackedObserver(*this);
133 }
134
135 LayoutObject* IntersectionObserver::rootLayoutObject()
136 {
137 Element* rootElement = root();
138 bool rootIsDocumentElement = (rootElement == rootElement->document().documen tElement());
esprehn 2015/12/17 01:40:28 break this apart. if (rootElement == rootElement-
szager1 2015/12/17 20:27:26 Done.
139 return rootIsDocumentElement ? rootElement->document().layoutView() : rootEl ement->layoutObject();
140 }
141
142 bool IntersectionObserver::isDescendantOfRoot(const Element* target) const
143 {
144 Element* rootElement = m_root.get();
145 if (!rootElement || !target || target == rootElement)
146 return false;
147 if (!target->inDocument() || !rootElement->inDocument())
148 return false;
149
150 Document* rootDocument = &rootElement->document();
151 Document* targetDocument = &target->document();
152 while (targetDocument != rootDocument) {
153 target = targetDocument->ownerElement();
154 if (!target)
155 return false;
156 targetDocument = &target->document();
157 }
158 return target->isDescendantOf(rootElement);
159 }
160
161 void IntersectionObserver::observe(Element* target, ExceptionState& exceptionSta te)
162 {
163 checkRootAndDetachIfNeeded();
164 if (!m_root) {
165 exceptionState.throwDOMException(HierarchyRequestError, "Invalid observe r: root element or containing document has been deleted.");
166 return;
167 }
168 if (!target) {
169 exceptionState.throwTypeError("Observation target must be an element.");
170 return;
171 }
172 if (m_root.get() == target) {
173 exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the same element for root and target.");
174 return;
175 }
176 if (!isDescendantOfRoot(target)) {
177 exceptionState.throwDOMException(HierarchyRequestError, "Observed elemen t must be a descendant of the observer's root element.");
esprehn 2015/12/17 01:40:29 I do kind of wonder if we should relax this in the
szager1 2015/12/17 20:27:26 Acknowledged.
178 return;
179 }
180
181 bool shouldReportRootBounds = target->document().frame()->securityContext()- >securityOrigin()->canAccess(root()->document().frame()->securityContext()->secu rityOrigin());
182 if (!shouldReportRootBounds && hasPercentMargin()) {
183 exceptionState.throwDOMException(HierarchyRequestError, "Cannot observe a cross-origin target because the observer has a root margin value specified as a percent.");
184 return;
185 }
186
187 if (target->intersectionObserverData().hasObservationFor(*this))
188 return;
189
190 IntersectionObservation* observation = new IntersectionObservation(*this, *t arget, shouldReportRootBounds);
191 m_observations.add(observation);
192 }
193
194 void IntersectionObserver::unobserve(Element* target, ExceptionState&)
195 {
196 checkRootAndDetachIfNeeded();
197 if (!target || !target->hasIntersectionObserverData())
198 return;
199 // TODO: unobserve callback
esprehn 2015/12/17 01:40:28 TODO(szager): It's good to attribute your TODO's
szager1 2015/12/17 20:27:26 Done, here and elsewhere.
200 target->intersectionObserverData().removeObservation(*this);
201 }
202
203 void IntersectionObserver::computeIntersectionObservations(double timestamp)
204 {
205 checkRootAndDetachIfNeeded();
206 if (!m_root)
207 return;
208 for (auto& observation : m_observations)
209 observation->computeIntersectionObservations(timestamp);
210 }
211
212 void IntersectionObserver::disconnect(IntersectionObservation& observation)
213 {
214 m_observations.remove(&observation);
215 }
216
217 void IntersectionObserver::disconnect()
218 {
219 checkRootAndDetachIfNeeded();
220 HeapVector<Member<IntersectionObservation>> toDisconnect;
221 for (auto& observation : m_observations)
222 toDisconnect.append(observation);
223 for (auto& observation : toDisconnect)
224 observation->disconnect();
225 ASSERT(m_observations.isEmpty());
226 }
227
228 HeapVector<Member<IntersectionObserverEntry>> IntersectionObserver::takeRecords( )
229 {
230 checkRootAndDetachIfNeeded();
231 HeapVector<Member<IntersectionObserverEntry>> entries;
232 entries.swap(m_entries);
233 return entries;
234 }
235
236 void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserver Entry& entry)
237 {
238 m_entries.append(&entry);
239 toDocument(m_callback->executionContext())->intersectionObserverController() ->scheduleIntersectionObserverForDelivery(*this);
240 }
241
242 static int computeMargin(const Length& length, LayoutUnit referenceLength)
243 {
244 if (length.type() == Percent)
245 return static_cast<int>(referenceLength.toFloat() * length.percent() / 1 00.0);
246 return length.intValue();
247 }
248
249 void IntersectionObserver::applyRootMargin(LayoutRect& rect) const
250 {
251 int topMargin = computeMargin(m_topMargin, rect.height());
252 int rightMargin = computeMargin(m_rightMargin, rect.width());
253 int bottomMargin = computeMargin(m_bottomMargin, rect.height());
254 int leftMargin = computeMargin(m_leftMargin, rect.width());
255
256 rect.setX(rect.x() - leftMargin);
257 rect.setWidth(rect.width() + leftMargin + rightMargin);
258 rect.setY(rect.y() - topMargin);
259 rect.setHeight(rect.height() + topMargin + bottomMargin);
260 }
261
262 unsigned IntersectionObserver::firstThresholdGreaterThan(float ratio) const
263 {
264 unsigned result = 0;
265 while (result < m_thresholds.size() && m_thresholds[result] < ratio)
266 ++result;
267 return result;
268 }
269
270 bool IntersectionObserver::shouldBeSuspended() const
271 {
272 return m_callback->executionContext() && m_callback->executionContext()->act iveDOMObjectsAreSuspended();
273 }
274
275 void IntersectionObserver::deliver()
276 {
277 checkRootAndDetachIfNeeded();
278
279 ASSERT(!shouldBeSuspended());
280
281 if (m_entries.isEmpty())
282 return;
283
284 HeapVector<Member<IntersectionObserverEntry>> entries;
285 entries.swap(m_entries);
286 m_callback->handleEvent(entries, *this);
287 }
288
289 void IntersectionObserver::setActive(bool active)
290 {
291 checkRootAndDetachIfNeeded();
292 for (auto& observation : m_observations)
293 observation->setActive(m_root && active && isDescendantOfRoot(observatio n->target()));
294 }
295
296 bool IntersectionObserver::hasPercentMargin() const
297 {
298 return m_topMargin.type() == Percent || m_rightMargin.type() == Percent || m _bottomMargin.type() == Percent || m_leftMargin.type() == Percent;
esprehn 2015/12/17 01:40:29 I'd wrap each || so this is multiple lines
szager1 2015/12/17 20:27:26 Done.
299 }
300
301 #if !ENABLE(OILPAN)
302 void IntersectionObserver::dispose()
303 {
304 m_root.clear();
305 checkRootAndDetachIfNeeded();
306 }
307 #endif
308
309 void IntersectionObserver::checkRootAndDetachIfNeeded()
esprehn 2015/12/17 01:40:29 hmm, I think I need to understand why you need to
szager1 2015/12/17 20:27:26 Now that there's ElementIntersectionObserverData::
310 {
311 if (m_root)
312 return;
313 m_callback.clear();
314 HeapVector<Member<IntersectionObservation>> toDisconnect;
315 for (auto& observation : m_observations)
316 toDisconnect.append(observation);
317 for (auto& observation : toDisconnect)
318 observation->disconnect();
319 ASSERT(m_observations.isEmpty());
320 // TODO: should we deliver pending notifications?
321 m_entries.clear();
322 }
323
324 DEFINE_TRACE(IntersectionObserver)
325 {
326 visitor->trace(m_callback);
327 visitor->trace(m_root);
328 visitor->trace(m_observations);
329 visitor->trace(m_entries);
330 }
331
332 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698