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/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()); |
| 27 |
| 28 RefPtrWillBeRawPtr<Element> root = observerInit.root(); |
| 29 if (!root) { |
| 30 ASSERT(callback); |
| 31 ExecutionContext* context = callback->executionContext(); |
| 32 if (context->isDocument()) { |
| 33 Frame* mainFrame = static_cast<Document*>(context)->frame()->tree().
top(); |
| 34 if (mainFrame && mainFrame->isLocalFrame()) |
| 35 root = toLocalFrame(mainFrame)->document()->documentElement(); |
| 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 int rootMargin = 0; |
| 44 if (observerInit.hasRootMargin()) { |
| 45 CSSParserTokenRange tokenRange = CSSTokenizer::Scope(observerInit.rootMa
rgin()).tokenRange(); |
| 46 const CSSParserToken& token = tokenRange.consumeIncludingWhitespace(); |
| 47 switch (token.type()) { |
| 48 case EOFToken: |
| 49 break; |
| 50 case NumberToken: |
| 51 rootMargin = static_cast<int>(floor(token.numericValue())); |
| 52 break; |
| 53 case DimensionToken: |
| 54 switch (token.unitType()) { |
| 55 case CSSPrimitiveValue::UnitType::Number: |
| 56 case CSSPrimitiveValue::UnitType::Pixels: |
| 57 rootMargin = static_cast<int>(token.numericValue()); |
| 58 break; |
| 59 default: |
| 60 exceptionState.throwTypeError("rootMargin must be specified in p
ixels."); |
| 61 break; |
| 62 } |
| 63 break; |
| 64 default: |
| 65 exceptionState.throwTypeError("rootMargin must be specified in pixel
s."); |
| 66 } |
| 67 } |
| 68 if (exceptionState.hadException()) |
| 69 return nullptr; |
| 70 |
| 71 Vector<float> thresholds; |
| 72 if (observerInit.hasThreshold()) { |
| 73 const DoubleOrDoubleArray& thresholdParam = observerInit.threshold(); |
| 74 if (thresholdParam.isDouble()) { |
| 75 thresholds.append(static_cast<float>(thresholdParam.getAsDouble())); |
| 76 } else { |
| 77 for (auto thresholdValue: thresholdParam.getAsDoubleArray()) |
| 78 thresholds.append(static_cast<float>(thresholdValue)); |
| 79 } |
| 80 } else { |
| 81 thresholds.append(0); |
| 82 } |
| 83 for (auto thresholdValue: thresholds) { |
| 84 if (thresholdValue < 0.0 || thresholdValue > 1.0) { |
| 85 exceptionState.throwTypeError("Threshold values must be between 0 an
d 1"); |
| 86 break; |
| 87 } |
| 88 } |
| 89 std::sort(thresholds.begin(), thresholds.end()); |
| 90 if (exceptionState.hadException()) |
| 91 return nullptr; |
| 92 |
| 93 return new IntersectionObserver(callback, root.get(), rootMargin, thresholds
); |
| 94 } |
| 95 |
| 96 IntersectionObserver::IntersectionObserver(IntersectionObserverCallback* callbac
k, Element* root, int rootMargin, const Vector<float>& thresholds) |
| 97 : m_callback(callback) |
| 98 , m_root(root->createWeakPtr()) |
| 99 , m_observations(new IntersectionObservation::WeakHashSet()) |
| 100 , m_entries(new IntersectionObserverEntryVector()) |
| 101 , m_thresholds(thresholds) |
| 102 , m_rootMargin(rootMargin) |
| 103 { |
| 104 root->document().intersectionObserverRegistry()->addTrackedObserver(*this); |
| 105 } |
| 106 |
| 107 bool IntersectionObserver::isDescendantOfRoot(Element* target) |
| 108 { |
| 109 checkRootAndDetachIfNecessary(); |
| 110 Element* rootElement = m_root.get(); |
| 111 if (!rootElement) |
| 112 return false; |
| 113 |
| 114 if (target == rootElement || !target->inDocument() || !rootElement->inDocume
nt()) |
| 115 return false; |
| 116 LocalFrame* rootFrame = rootElement->document().frame(); |
| 117 LocalFrame* targetFrame = target->document().frame(); |
| 118 if (!rootFrame || !targetFrame || !targetFrame->tree().isDescendantOf(rootFr
ame)) |
| 119 return false; |
| 120 while (targetFrame != rootFrame) { |
| 121 FrameOwner* targetOwner = targetFrame->owner(); |
| 122 if (!targetOwner || !targetOwner->isLocal()) |
| 123 return false; |
| 124 target = toHTMLFrameOwnerElement(targetOwner); |
| 125 ASSERT(target); |
| 126 targetFrame = target->document().frame(); |
| 127 if (!targetFrame) |
| 128 return false; |
| 129 } |
| 130 return target->isDescendantOf(rootElement); |
| 131 } |
| 132 |
| 133 void IntersectionObserver::observe(Element* target, ExceptionState& exceptionSta
te) |
| 134 { |
| 135 checkRootAndDetachIfNecessary(); |
| 136 if (!m_root) |
| 137 exceptionState.throwDOMException(HierarchyRequestError, "Invalid observe
r: root element or containing document has been deleted."); |
| 138 else if (!target) |
| 139 exceptionState.throwTypeError("Observation target must be an element."); |
| 140 else if (m_root.get() == target) |
| 141 exceptionState.throwDOMException(HierarchyRequestError, "Cannot use the
same element for root and target."); |
| 142 if (exceptionState.hadException()) |
| 143 return; |
| 144 |
| 145 if (IntersectionObservation::contains(*m_observations, this, target)) |
| 146 return; |
| 147 |
| 148 createObservation(target); |
| 149 } |
| 150 |
| 151 void IntersectionObserver::unobserve(Element* target, ExceptionState&) |
| 152 { |
| 153 checkRootAndDetachIfNecessary(); |
| 154 if (!target) |
| 155 return; |
| 156 IntersectionObservation::WeakHashSet::iterator observationIterator = Interse
ctionObservation::find(*m_observations, this, target); |
| 157 // TODO: unobserve callback |
| 158 if (observationIterator != m_observations->end()) |
| 159 (*observationIterator)->disconnect(); |
| 160 } |
| 161 |
| 162 void IntersectionObserver::computeIntersectionObservations(int timestamp) |
| 163 { |
| 164 checkRootAndDetachIfNecessary(); |
| 165 if (!m_root) |
| 166 return; |
| 167 for (auto& observation: *m_observations) |
| 168 observation->computeIntersectionObservations(timestamp); |
| 169 } |
| 170 |
| 171 void IntersectionObserver::disconnect(IntersectionObservation& observation) |
| 172 { |
| 173 m_observations->remove(&observation); |
| 174 } |
| 175 |
| 176 void IntersectionObserver::disconnect() |
| 177 { |
| 178 checkRootAndDetachIfNecessary(); |
| 179 HeapVector<Member<IntersectionObservation>> toDisconnect; |
| 180 for (auto& observation: *m_observations) |
| 181 toDisconnect.append(observation); |
| 182 for (auto& observation: toDisconnect) |
| 183 observation->disconnect(); |
| 184 ASSERT(!m_observations->size()); |
| 185 } |
| 186 |
| 187 IntersectionObserverEntryVector IntersectionObserver::takeRecords() |
| 188 { |
| 189 checkRootAndDetachIfNecessary(); |
| 190 IntersectionObserverEntryVector entries; |
| 191 entries.swap(*m_entries); |
| 192 return entries; |
| 193 } |
| 194 |
| 195 void IntersectionObserver::enqueueIntersectionObserverEntry(IntersectionObserver
Entry* entry) |
| 196 { |
| 197 ASSERT(isMainThread()); |
| 198 m_entries->append(entry); |
| 199 static_cast<Document*>(m_callback->executionContext())->intersectionObserver
Registry()->scheduleIntersectionObserverForDelivery(*this); |
| 200 } |
| 201 |
| 202 size_t IntersectionObserver::firstThresholdGreaterThan(float ratio) const |
| 203 { |
| 204 size_t result = 0; |
| 205 size_t max = m_thresholds.size(); |
| 206 while (result < max && m_thresholds[result] < ratio) |
| 207 ++result; |
| 208 return result; |
| 209 } |
| 210 |
| 211 bool IntersectionObserver::shouldBeSuspended() const |
| 212 { |
| 213 return m_callback->executionContext() && m_callback->executionContext()->act
iveDOMObjectsAreSuspended(); |
| 214 } |
| 215 |
| 216 void IntersectionObserver::deliver() |
| 217 { |
| 218 checkRootAndDetachIfNecessary(); |
| 219 |
| 220 ASSERT(!shouldBeSuspended()); |
| 221 |
| 222 if (m_entries->isEmpty()) |
| 223 return; |
| 224 |
| 225 IntersectionObserverEntryVector entries; |
| 226 entries.swap(*m_entries); |
| 227 m_callback->handleEvent(entries, this); |
| 228 } |
| 229 |
| 230 void IntersectionObserver::setActive(bool active) |
| 231 { |
| 232 checkRootAndDetachIfNecessary(); |
| 233 for (auto& observation: *m_observations) |
| 234 observation->setActive(m_root && active && isDescendantOfRoot(observatio
n->target())); |
| 235 } |
| 236 |
| 237 IntersectionObservation* IntersectionObserver::createObservation(Element* target
) |
| 238 { |
| 239 IntersectionObservation* observation = new IntersectionObservation(*this, ta
rget); |
| 240 m_observations->add(observation); |
| 241 return observation; |
| 242 } |
| 243 |
| 244 void IntersectionObserver::checkRootAndDetachIfNecessary() |
| 245 { |
| 246 if (m_root) |
| 247 return; |
| 248 m_callback.clear(); |
| 249 HeapVector<Member<IntersectionObservation>> toDisconnect; |
| 250 for (auto& observation: *m_observations) |
| 251 toDisconnect.append(observation); |
| 252 for (auto& observation: toDisconnect) |
| 253 observation->disconnect(); |
| 254 ASSERT(!m_observations->size()); |
| 255 // TODO: should we deliver pending notifications? |
| 256 m_entries.clear(); |
| 257 } |
| 258 |
| 259 DEFINE_TRACE(IntersectionObserver) |
| 260 { |
| 261 visitor->trace(m_callback); |
| 262 visitor->trace(m_root); |
| 263 visitor->trace(m_observations); |
| 264 visitor->trace(m_entries); |
| 265 } |
| 266 |
| 267 } // namespace blink |
OLD | NEW |