OLD | NEW |
---|---|
(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/html/HTMLFrameOwnerElement.h" | |
17 #include "platform/Timer.h" | |
18 #include "wtf/MainThread.h" | |
19 #include <algorithm> | |
20 | |
21 namespace blink { | |
22 | |
23 PassRefPtrWillBeRawPtr<IntersectionObserver> IntersectionObserver::create(const IntersectionObserverInit& observerInit, PassOwnPtrWillBeRawPtr<IntersectionObser verCallback> callback, ExceptionState& exceptionState) | |
24 { | |
25 ASSERT(isMainThread()); | |
26 | |
27 int rootMargin = 0; | |
28 if (observerInit.hasRootMargin()) { | |
29 CSSParserTokenRange tokenRange = CSSTokenizer::Scope(observerInit.rootMa rgin()).tokenRange(); | |
30 const CSSParserToken& token = tokenRange.consumeIncludingWhitespace(); | |
31 switch (token.type()) { | |
32 case EOFToken: | |
33 break; | |
34 case NumberToken: | |
35 rootMargin = static_cast<int>(token.numericValue()); | |
36 break; | |
37 case DimensionToken: | |
38 switch (token.unitType()) { | |
39 case CSSPrimitiveValue::UnitType::Number: | |
40 case CSSPrimitiveValue::UnitType::Pixels: | |
41 rootMargin = static_cast<int>(token.numericValue()); | |
eae
2015/11/16 23:34:44
Is flooring the number the desired result? If so c
szager1
2015/11/19 19:15:38
Done in next patch.
| |
42 break; | |
43 default: | |
44 exceptionState.throwTypeError("rootMargin must be specified in p ixels."); | |
45 break; | |
46 } | |
47 break; | |
48 default: | |
49 exceptionState.throwTypeError("rootMargin must be specified in pixel s."); | |
50 } | |
51 } | |
52 if (exceptionState.hadException()) | |
53 return PassRefPtrWillBeRawPtr<IntersectionObserver>(); | |
54 | |
55 Vector<float> thresholds; | |
56 if (observerInit.hasThreshold()) { | |
57 const DoubleOrDoubleArray& thresholdParam = observerInit.threshold(); | |
58 if (thresholdParam.isDouble()) { | |
59 thresholds.append(static_cast<float>(thresholdParam.getAsDouble())); | |
60 } else { | |
61 for (auto thresholdValue: thresholdParam.getAsDoubleArray()) | |
62 thresholds.append(static_cast<float>(thresholdValue)); | |
63 } | |
64 } else { | |
65 thresholds.append(0); | |
66 } | |
67 for (auto thresholdValue: thresholds) { | |
68 if (thresholdValue < 0.0 || thresholdValue > 1.0) { | |
69 exceptionState.throwTypeError("Threshold values must be between 0 an d 1"); | |
70 break; | |
71 } | |
72 } | |
73 std::sort(thresholds.begin(), thresholds.end()); | |
74 if (exceptionState.hadException()) | |
75 return PassRefPtrWillBeRawPtr<IntersectionObserver>(); | |
76 | |
77 return adoptRefWillBeNoop(new IntersectionObserver(callback, observerInit.ro ot(), rootMargin, thresholds)); | |
78 } | |
79 | |
80 IntersectionObserver::IntersectionObserver(PassOwnPtrWillBeRawPtr<IntersectionOb serverCallback> callback, RefPtrWillBeRawPtr<Element> root, int rootMargin, cons t Vector<float>& thresholds) | |
81 : m_callback(callback) | |
82 , m_thresholds(thresholds) | |
83 , m_rootMargin(rootMargin) | |
84 , m_hasRoot(root) | |
85 { | |
86 if (root) { | |
87 // This is a phony observation, created just to ensure the root element keeps | |
88 // a reference to the observer. That reference will be used to disconne ct | |
89 // the observer when the root element is destroyed. | |
90 m_root = root->createWeakPtr(); | |
91 m_trackingDocument = root->document().createWeakPtr(); | |
92 createObservation(root.get()); | |
93 } | |
94 } | |
95 | |
96 IntersectionObserver::~IntersectionObserver() | |
97 { | |
98 ASSERT(!m_observations.size()); | |
99 ASSERT(!m_entries.size()); | |
100 } | |
101 | |
102 bool IntersectionObserver::checkTargetHierarchy(Element* target) | |
eae
2015/11/16 23:34:45
hasSameRoot or sharesAncestor might be better name
szager1
2015/11/19 19:15:38
Maybe isValidTarget? Or isDescendantOfRoot? I'll
| |
103 { | |
104 if (!target->inDocument()) | |
105 return false; | |
106 if (!hasRoot()) | |
107 return true; | |
108 Element* rootElement = root(); | |
109 if (!rootElement || !rootElement->inDocument()) | |
110 return false; | |
111 LocalFrame* rootFrame = rootElement->document().frame(); | |
112 LocalFrame* targetFrame = target->document().frame(); | |
113 if (!rootFrame || !targetFrame || !targetFrame->tree().isDescendantOf(rootFr ame)) | |
114 return false; | |
115 while (targetFrame != rootFrame) { | |
116 FrameOwner* targetOwner = targetFrame->owner(); | |
117 if (!targetOwner || !targetOwner->isLocal()) | |
118 return false; | |
119 target = toHTMLFrameOwnerElement(targetOwner); | |
120 ASSERT(target); | |
121 targetFrame = target->document().frame(); | |
122 if (!targetFrame) | |
123 return false; | |
124 } | |
125 return target->isDescendantOf(rootElement); | |
126 } | |
127 | |
128 Document& IntersectionObserver::getTrackingDocumentForTarget(Element* target) | |
129 { | |
130 if (root()) | |
131 return root()->document(); | |
132 ASSERT(target); | |
133 Frame* frame = target->document().frame(); | |
134 ASSERT(frame); | |
135 frame = frame->tree().top(); | |
136 ASSERT(frame && frame->isLocalFrame() && toLocalFrame(frame)->document()); | |
137 return *toLocalFrame(frame)->document(); | |
138 } | |
139 | |
140 void IntersectionObserver::observe(Element* target, ExceptionState& exceptionSta te) | |
141 { | |
142 if (!target) | |
143 exceptionState.throwTypeError("Observation target must be an element."); | |
144 else if ((m_hasRoot && !root()) || (m_observations.size() && !trackingDocume nt())) | |
145 exceptionState.throwDOMException(HierarchyRequestError, "Invalid observe r: root element or containing document has been deleted."); | |
146 else if (root() == target) | |
147 exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the same element for root and target."); | |
148 if (exceptionState.hadException()) | |
149 return; | |
150 | |
151 if (IntersectionObservation::contains(m_observations, this, target)) | |
152 return; | |
153 | |
154 // If there's no root and this is the first target, then we didn't know unti l | |
155 // now which document will track the observation and generate notifications. | |
156 if (!trackingDocument()) { | |
157 ASSERT(!m_root && !m_observations.size()); | |
158 m_trackingDocument = getTrackingDocumentForTarget(target).createWeakPtr( ); | |
159 } else if (m_trackingDocument.get() != &getTrackingDocumentForTarget(target) ) { | |
160 exceptionState.throwDOMException(HierarchyRequestError, "All target elem ents must be in the same frame tree."); | |
161 return; | |
162 } | |
163 | |
164 createObservation(target); | |
165 } | |
166 | |
167 void IntersectionObserver::unobserve(Element* target, ExceptionState&) | |
168 { | |
169 if (!target) | |
170 return; | |
171 IntersectionObservation::HashSet::iterator observationIterator = Intersectio nObservation::find(m_observations, this, target); | |
172 if (observationIterator == m_observations.end()) | |
173 return; | |
174 (*observationIterator)->disconnect(); | |
175 } | |
176 | |
177 void IntersectionObserver::disconnect(IntersectionObservation& observation) | |
178 { | |
179 // This will get called during Element destruction, so we must be careful | |
180 // to check the weak pointers (root, target, trackingDocument). | |
181 m_observations.remove(adoptRef(&observation)); | |
182 if (observation.target()) | |
183 observation.target()->removeIntersectionObservation(observation); | |
184 if (root()) | |
185 root()->removeIntersectionObservation(observation); | |
186 // TODO: This will probably break things if there are unsent observations. | |
187 if (trackingDocument()) | |
188 trackingDocument()->intersectionObservationRegistry()->removeObservation (observation); | |
189 } | |
190 | |
191 void IntersectionObserver::disconnect() | |
192 { | |
193 WillBeHeapVector<RefPtr<IntersectionObservation>> toDisconnect; | |
194 for (auto& observation: m_observations) | |
195 toDisconnect.append(observation); | |
196 for (auto& observation: toDisconnect) | |
197 observation->disconnect(); | |
198 ASSERT(!m_observations.size()); | |
199 } | |
200 | |
201 IntersectionObserverEntryVector IntersectionObserver::takeRecords() | |
202 { | |
203 IntersectionObserverEntryVector entries; | |
204 entries.swap(m_entries); | |
205 return entries; | |
206 } | |
207 | |
208 void IntersectionObserver::enqueueIntersectionObserverEntry(PassRefPtrWillBeRawP tr<IntersectionObserverEntry> entry) | |
209 { | |
210 ASSERT(isMainThread()); | |
211 m_entries.append(entry); | |
212 m_trackingDocument->intersectionObservationRegistry()->activateIntersectionO bserver(*this); | |
213 } | |
214 | |
215 size_t IntersectionObserver::firstThresholdGreaterThan(float ratio) const | |
216 { | |
217 size_t result = 0; | |
218 size_t max = m_thresholds.size(); | |
219 while (result < max && m_thresholds[result] <= ratio) | |
220 ++result; | |
221 return result; | |
222 } | |
223 | |
224 bool IntersectionObserver::shouldBeSuspended() const | |
225 { | |
226 return m_callback->executionContext() && m_callback->executionContext()->act iveDOMObjectsAreSuspended(); | |
227 } | |
228 | |
229 void IntersectionObserver::deliver() | |
230 { | |
231 ASSERT(!shouldBeSuspended()); | |
232 | |
233 if (m_entries.isEmpty()) | |
234 return; | |
235 | |
236 IntersectionObserverEntryVector entries; | |
237 entries.swap(m_entries); | |
238 m_callback->handleEvent(entries, this); | |
239 } | |
240 | |
241 PassRefPtr<IntersectionObservation> IntersectionObserver::createObservation(Elem ent* target) | |
242 { | |
243 RefPtrWillBeRawPtr<IntersectionObservation> observation = IntersectionObserv ation::create(*this, target->createWeakPtr(), false); | |
244 m_observations.add(observation); | |
245 m_trackingDocument->intersectionObservationRegistry()->addObservation(*obser vation); | |
246 target->addIntersectionObservation(*observation); | |
247 return observation; | |
248 } | |
249 | |
250 DEFINE_TRACE(IntersectionObserver) | |
251 { | |
252 visitor->trace(m_callback); | |
253 visitor->trace(m_root); | |
254 // TODO: Uncommenting the next line causes a compilation failure. Fix it. | |
255 // visitor->trace(m_observations); | |
haraken
2015/11/16 00:41:34
You need to use:
#if ENABLE(OILPAN)
visitor->tr
szager1
2015/11/19 19:15:38
Working on it.
| |
256 visitor->trace(m_entries); | |
257 } | |
258 | |
259 } // namespace blink | |
OLD | NEW |