OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 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 parseThresholds(const DoubleOrDoubleArray& thresholdParameter, Vecto r<float>& thresholds, ExceptionState& exceptionState) | |
26 { | |
27 if (thresholdParameter.isDouble()) { | |
28 thresholds.append(static_cast<float>(thresholdParameter.getAsDouble())); | |
29 } else { | |
30 for (auto thresholdValue : thresholdParameter.getAsDoubleArray()) | |
31 thresholds.append(static_cast<float>(thresholdValue)); | |
32 } | |
33 | |
34 for (auto thresholdValue : thresholds) { | |
35 if (thresholdValue < 0.0 || thresholdValue > 1.0) { | |
36 exceptionState.throwTypeError("Threshold values must be between 0 an d 1"); | |
37 break; | |
38 } | |
39 } | |
40 | |
41 std::sort(thresholds.begin(), thresholds.end()); | |
42 } | |
43 | |
44 IntersectionObserver* IntersectionObserver::create(const IntersectionObserverIni t& observerInit, IntersectionObserverCallback& callback, ExceptionState& excepti onState) | |
45 { | |
46 RefPtrWillBeRawPtr<Element> root = observerInit.root(); | |
47 if (!root) { | |
48 // TODO(szager): Use Document instead of document element for implicit r oot. (crbug.com/570538) | |
49 ExecutionContext* context = callback.executionContext(); | |
50 ASSERT(context->isDocument()); | |
51 Frame* mainFrame = toDocument(context)->frame()->tree().top(); | |
52 if (mainFrame && mainFrame->isLocalFrame()) | |
53 root = toLocalFrame(mainFrame)->document()->documentElement(); | |
54 } | |
55 if (!root) { | |
56 exceptionState.throwDOMException(HierarchyRequestError, "Unable to get r oot element in main frame to track."); | |
57 return nullptr; | |
58 } | |
59 | |
60 Vector<float> thresholds; | |
61 if (observerInit.hasThreshold()) | |
62 parseThresholds(observerInit.threshold(), thresholds, exceptionState); | |
63 else | |
64 thresholds.append(0); | |
65 if (exceptionState.hadException()) | |
66 return nullptr; | |
67 | |
68 return new IntersectionObserver(callback, *root, thresholds); | |
69 } | |
70 | |
71 IntersectionObserver::IntersectionObserver(IntersectionObserverCallback& callbac k, Element& root, const Vector<float>& thresholds) | |
72 : m_callback(&callback) | |
73 , m_root(root.ensureIntersectionObserverData().createWeakPtr(&root)) | |
74 , m_thresholds(thresholds) | |
75 { | |
76 root.document().ensureIntersectionObserverController().addTrackedObserver(*t his); | |
77 } | |
78 | |
79 LayoutObject* IntersectionObserver::rootLayoutObject() | |
80 { | |
81 Element* rootElement = root(); | |
82 if (rootElement == rootElement->document().documentElement()) | |
83 return rootElement->document().layoutView(); | |
84 return rootElement->layoutObject(); | |
85 } | |
86 | |
87 bool IntersectionObserver::isDescendantOfRoot(const Element* target) const | |
88 { | |
89 // Is m_root an ancestor, through the DOM and frame trees, of target? | |
90 Element* rootElement = m_root.get(); | |
91 if (!rootElement || !target || target == rootElement) | |
92 return false; | |
93 if (!target->inDocument() || !rootElement->inDocument()) | |
94 return false; | |
95 | |
96 Document* rootDocument = &rootElement->document(); | |
97 Document* targetDocument = &target->document(); | |
98 while (targetDocument != rootDocument) { | |
99 target = targetDocument->ownerElement(); | |
100 if (!target) | |
101 return false; | |
102 targetDocument = &target->document(); | |
103 } | |
104 return target->isDescendantOf(rootElement); | |
105 } | |
106 | |
107 void IntersectionObserver::observe(Element* target, ExceptionState& exceptionSta te) | |
108 { | |
109 checkRootAndDetachIfNeeded(); | |
110 if (!m_root) { | |
111 exceptionState.throwDOMException(HierarchyRequestError, "Invalid observe r: root element or containing document has been deleted."); | |
112 return; | |
113 } | |
114 if (!target) { | |
115 exceptionState.throwTypeError("Observation target must be an element."); | |
116 return; | |
117 } | |
118 if (m_root.get() == target) { | |
119 exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the same element for root and target."); | |
120 return; | |
121 } | |
122 if (!isDescendantOfRoot(target)) { | |
123 exceptionState.throwDOMException(HierarchyRequestError, "Observed elemen t must be a descendant of the observer's root element."); | |
124 return; | |
125 } | |
126 | |
127 bool shouldReportRootBounds = target->document().frame()->securityContext()- >securityOrigin()->canAccess(root()->document().frame()->securityContext()->secu rityOrigin()); | |
128 | |
129 if (target->ensureIntersectionObserverData().getObservationFor(*this)) | |
130 return; | |
131 | |
132 IntersectionObservation* observation = new IntersectionObservation(*this, *t arget, shouldReportRootBounds); | |
133 target->ensureIntersectionObserverData().addObservation(*observation); | |
134 m_observations.add(observation); | |
135 } | |
136 | |
137 void IntersectionObserver::unobserve(Element* target, ExceptionState&) | |
138 { | |
139 checkRootAndDetachIfNeeded(); | |
140 if (!target || !target->intersectionObserverData()) | |
141 return; | |
142 // TODO(szager): unobserve callback | |
143 if (IntersectionObservation* observation = target->intersectionObserverData( )->getObservationFor(*this)) | |
144 observation->disconnect(); | |
145 } | |
146 | |
147 void IntersectionObserver::computeIntersectionObservations(double timestamp) | |
148 { | |
149 checkRootAndDetachIfNeeded(); | |
150 if (!m_root) | |
151 return; | |
152 for (auto& observation : m_observations) | |
153 observation->computeIntersectionObservations(timestamp); | |
154 } | |
155 | |
156 void IntersectionObserver::disconnect() | |
157 { | |
158 HeapVector<Member<IntersectionObservation>> observationsToDisconnect; | |
159 copyToVector(m_observations, observationsToDisconnect); | |
160 for (auto& observation : observationsToDisconnect) | |
161 observation->disconnect(); | |
162 ASSERT(m_observations.isEmpty()); | |
163 m_root.clear(); | |
164 } | |
165 | |
166 HeapVector<Member<IntersectionObserverEntry>> IntersectionObserver::takeRecords( ) | |
167 { | |
168 checkRootAndDetachIfNeeded(); | |
169 HeapVector<Member<IntersectionObserverEntry>> entries; | |
170 entries.swap(m_entries); | |
171 return entries; | |
172 } | |
173 | |
174 void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserver Entry& entry) | |
175 { | |
176 m_entries.append(&entry); | |
177 toDocument(m_callback->executionContext())->ensureIntersectionObserverContro ller().scheduleIntersectionObserverForDelivery(*this); | |
178 } | |
179 | |
180 unsigned IntersectionObserver::firstThresholdGreaterThan(float ratio) const | |
181 { | |
182 unsigned result = 0; | |
183 while (result < m_thresholds.size() && m_thresholds[result] < ratio) | |
184 ++result; | |
185 return result; | |
186 } | |
187 | |
188 bool IntersectionObserver::shouldBeSuspended() const | |
haraken
2016/01/07 00:48:17
Factor out this method to a follow-up CL.
szager1
2016/01/07 07:12:22
Done.
| |
189 { | |
190 return m_callback->executionContext() && m_callback->executionContext()->act iveDOMObjectsAreSuspended(); | |
191 } | |
192 | |
193 void IntersectionObserver::deliver() | |
194 { | |
195 checkRootAndDetachIfNeeded(); | |
196 | |
197 ASSERT(!shouldBeSuspended()); | |
198 | |
199 if (m_entries.isEmpty()) | |
200 return; | |
201 | |
202 HeapVector<Member<IntersectionObserverEntry>> entries; | |
203 entries.swap(m_entries); | |
204 m_callback->handleEvent(entries, *this); | |
205 } | |
206 | |
207 void IntersectionObserver::setActive(bool active) | |
208 { | |
209 checkRootAndDetachIfNeeded(); | |
210 for (auto& observation : m_observations) | |
211 observation->setActive(m_root && active && isDescendantOfRoot(observatio n->target())); | |
212 } | |
213 | |
214 void IntersectionObserver::checkRootAndDetachIfNeeded() | |
haraken
2016/01/07 00:48:17
This implementation is clearly wrong in oilpan. We
szager1
2016/01/07 07:12:22
Acknowledged.
| |
215 { | |
216 #if ENABLE(OILPAN) | |
217 // TODO(szager): Pre-oilpan, ElementIntersectionObserverData::dispose() will take | |
218 // care of this cleanup. When oilpan ships, there will be a potential leak of the | |
219 // callback's execution context when the root goes away. For a detailed exp lanation: | |
220 // | |
221 // https://goo.gl/PC2Baj | |
222 // | |
223 // When that happens, this method should catch most potential leaks, but a c omplete | |
224 // solution will still be needed, along the lines described in the above lin k. | |
225 | |
226 // TODO(szager): As a performance optimization, clear the hash tables before disconnecting | |
haraken
2016/01/07 00:48:17
Remove this TODO.
szager1
2016/01/07 07:12:22
Done.
| |
227 // the observations, and avoid hash lookups in IntersectionObservation::deta ch. | |
228 // That would make the ASSERT superfluous, but let's leave it as is for a wh ile | |
229 // to see if the ASSERT ever fails. | |
230 if (m_root) | |
231 return; | |
232 disconnect(); | |
233 #endif | |
234 } | |
235 | |
236 DEFINE_TRACE(IntersectionObserver) | |
237 { | |
238 visitor->trace(m_callback); | |
239 visitor->trace(m_root); | |
240 visitor->trace(m_observations); | |
241 visitor->trace(m_entries); | |
242 } | |
243 | |
244 } // namespace blink | |
OLD | NEW |