LEFT | RIGHT |
1 /* | 1 /* |
2 * Copyright (C) 2016 Apple Inc. All rights reserved. | 2 * Copyright (C) 2016 Apple Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
(...skipping 10 matching lines...) Expand all Loading... |
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 * THE POSSIBILITY OF SUCH DAMAGE. | 23 * THE POSSIBILITY OF SUCH DAMAGE. |
24 */ | 24 */ |
25 | 25 |
26 #include "config.h" | 26 #include "config.h" |
27 | 27 |
28 #if ENABLE(INTERSECTION_OBSERVER) | 28 #if ENABLE(INTERSECTION_OBSERVER) |
29 #include "IntersectionObserver.h" | 29 #include "IntersectionObserver.h" |
30 | 30 |
31 #include "CSSParser.h" | 31 #include "CSSParserTokenRange.h" |
32 #include "CSSPropertyParser.h" | |
33 #include "CSSPropertyParserHelpers.h" | 32 #include "CSSPropertyParserHelpers.h" |
34 #include "CSSTokenizer.h" | 33 #include "CSSTokenizer.h" |
35 #include "DOMWindow.h" | 34 #include "DOMWindow.h" |
36 #include "Element.h" | 35 #include "Element.h" |
37 #include "ExceptionCode.h" | 36 #include "ExceptionCode.h" |
38 #include "IntersectionObserverCallback.h" | 37 #include "IntersectionObserverCallback.h" |
39 #include "IntersectionObserverEntry.h" | 38 #include "IntersectionObserverEntry.h" |
40 #include "Logging.h" | 39 #include "Logging.h" |
| 40 #include "Performance.h" |
41 #include "StyleResolver.h" | 41 #include "StyleResolver.h" |
42 #include <algorithm> | 42 #include <algorithm> |
43 #include <functional> | 43 #include <functional> |
44 #include <wtf/Vector.h> | 44 #include <wtf/Vector.h> |
45 #include <wtf/text/TextStream.h> | 45 #include <wtf/text/TextStream.h> |
46 | 46 |
47 namespace WebCore { | 47 namespace WebCore { |
48 | 48 |
49 static bool isThresholdWithinRange(double threshold) | 49 static bool isThresholdWithinRange(double threshold) |
50 { | 50 { |
(...skipping 11 matching lines...) Expand all Loading... |
62 RefPtr<CSSPrimitiveValue> parsedValue = CSSPropertyParserHelpers::consum
eLengthOrPercent(tokenRange, HTMLStandardMode, ValueRangeAll); | 62 RefPtr<CSSPrimitiveValue> parsedValue = CSSPropertyParserHelpers::consum
eLengthOrPercent(tokenRange, HTMLStandardMode, ValueRangeAll); |
63 if (!parsedValue || parsedValue->isCalculated()) | 63 if (!parsedValue || parsedValue->isCalculated()) |
64 return Exception { SyntaxError, "Failed to construct 'IntersectionOb
server': rootMargin must be specified in pixels or percent." }; | 64 return Exception { SyntaxError, "Failed to construct 'IntersectionOb
server': rootMargin must be specified in pixels or percent." }; |
65 if (parsedValue->isPercentage()) | 65 if (parsedValue->isPercentage()) |
66 margins.append(Length(parsedValue->doubleValue(), Percent)); | 66 margins.append(Length(parsedValue->doubleValue(), Percent)); |
67 else if (parsedValue->isPx()) | 67 else if (parsedValue->isPx()) |
68 margins.append(Length(parsedValue->intValue(), Fixed)); | 68 margins.append(Length(parsedValue->intValue(), Fixed)); |
69 else | 69 else |
70 return Exception { SyntaxError, "Failed to construct 'IntersectionOb
server': rootMargin must be specified in pixels or percent." }; | 70 return Exception { SyntaxError, "Failed to construct 'IntersectionOb
server': rootMargin must be specified in pixels or percent." }; |
71 } | 71 } |
72 | |
73 Length top, right, bottom, left; | |
74 switch (margins.size()) { | 72 switch (margins.size()) { |
75 case 0: | 73 case 0: |
76 for (int i = 0; i < 4; ++i) | 74 for (unsigned i = 0; i < 4; ++i) |
77 margins.append(Length()); | 75 margins.append(Length()); |
78 break; | 76 break; |
79 case 1: | 77 case 1: |
80 for (int i = 0; i < 3; ++i) | 78 for (unsigned i = 0; i < 3; ++i) |
81 margins.append(margins[0]); | 79 margins.append(margins[0]); |
82 break; | 80 break; |
83 case 2: | 81 case 2: |
84 margins.append(margins[0]); | 82 margins.append(margins[0]); |
85 margins.append(margins[1]); | 83 margins.append(margins[1]); |
86 break; | 84 break; |
87 case 3: | 85 case 3: |
88 margins.append(margins[1]); | 86 margins.append(margins[1]); |
89 break; | 87 break; |
90 case 4: | 88 case 4: |
91 break; | 89 break; |
92 default: | 90 default: |
93 ASSERT_NOT_REACHED(); | 91 ASSERT_NOT_REACHED(); |
94 } | 92 } |
95 | |
96 StringBuilder stringBuilder; | |
97 for (size_t i = 0; i < 4; ++i) { | |
98 stringBuilder.appendNumber(margins[i].intValue()); | |
99 if (margins[i].type() == Percent) | |
100 stringBuilder.append('%'); | |
101 else | |
102 stringBuilder.append("px", 2); | |
103 if (i < 3) | |
104 stringBuilder.append(' '); | |
105 } | |
106 rootMargin = stringBuilder.toString(); | |
107 | 93 |
108 return LengthBox(WTFMove(margins[0]), WTFMove(margins[1]), WTFMove(margins[2
]), WTFMove(margins[3])); | 94 return LengthBox(WTFMove(margins[0]), WTFMove(margins[1]), WTFMove(margins[2
]), WTFMove(margins[3])); |
109 } | 95 } |
110 | 96 |
111 ExceptionOr<Ref<IntersectionObserver>> IntersectionObserver::create(Document& do
cument, Ref<IntersectionObserverCallback>&& callback, IntersectionObserver::Init
&& init) | 97 ExceptionOr<Ref<IntersectionObserver>> IntersectionObserver::create(Document& do
cument, Ref<IntersectionObserverCallback>&& callback, IntersectionObserver::Init
&& init) |
112 { | 98 { |
113 auto rootMarginOrException = parseRootMargin(init.rootMargin); | 99 auto rootMarginOrException = parseRootMargin(init.rootMargin); |
114 if (rootMarginOrException.hasException()) | 100 if (rootMarginOrException.hasException()) |
115 return rootMarginOrException.releaseException(); | 101 return rootMarginOrException.releaseException(); |
116 | 102 |
117 bool foundOutOfRangeThreshold = false; | 103 bool foundOutOfRangeThreshold = false; |
118 if (WTF::holds_alternative<double>(init.threshold)) | 104 if (WTF::holds_alternative<double>(init.threshold)) |
119 foundOutOfRangeThreshold = !isThresholdWithinRange(WTF::get<double>(init
.threshold)); | 105 foundOutOfRangeThreshold = !isThresholdWithinRange(WTF::get<double>(init
.threshold)); |
120 else { | 106 else { |
121 for (auto& threshold : WTF::get<Vector<double>>(init.threshold)) | 107 for (auto& threshold : WTF::get<Vector<double>>(init.threshold)) |
122 foundOutOfRangeThreshold |= !isThresholdWithinRange(threshold); | 108 foundOutOfRangeThreshold |= !isThresholdWithinRange(threshold); |
123 } | 109 } |
124 if (foundOutOfRangeThreshold) | 110 if (foundOutOfRangeThreshold) |
125 return Exception { RangeError, "Failed to construct 'IntersectionObserve
r': all thresholds must lie in the range [0.0, 1.0]." }; | 111 return Exception { RangeError, "Failed to construct 'IntersectionObserve
r': all thresholds must lie in the range [0.0, 1.0]." }; |
126 | 112 |
127 return adoptRef(*new IntersectionObserver(document, WTFMove(callback), WTFMo
ve(init), rootMarginOrException.releaseReturnValue())); | 113 return adoptRef(*new IntersectionObserver(document, WTFMove(callback), WTFMo
ve(init), rootMarginOrException.releaseReturnValue())); |
128 } | 114 } |
129 | 115 |
130 // FIXME: need to stop observing if the root is deleted. | 116 IntersectionObserver::IntersectionObserver(Document& document, Ref<IntersectionO
bserverCallback>&& callback, Init&& init, LengthBox&& parsedRootMargin) |
131 IntersectionObserver::IntersectionObserver(Document& document, Ref<IntersectionO
bserverCallback>&& callback, Init&& init, LengthBox&& marginBox) | 117 : m_implicitRootDocument(nullptr) |
132 : m_document(document) | |
133 , m_root(init.root) | 118 , m_root(init.root) |
134 , m_rootMargin(WTFMove(init.rootMargin)) | 119 , m_rootMargin(WTFMove(parsedRootMargin)) |
135 , m_callback(WTFMove(callback)) | 120 , m_callback(WTFMove(callback)) |
136 , m_marginBox(marginBox) | |
137 { | 121 { |
138 if (WTF::holds_alternative<double>(init.threshold)) | 122 if (WTF::holds_alternative<double>(init.threshold)) |
139 m_thresholds.append(WTF::get<double>(init.threshold)); | 123 m_thresholds.append(WTF::get<double>(init.threshold)); |
140 else | 124 else |
141 m_thresholds = WTF::get<Vector<double>>(WTFMove(init.threshold)); | 125 m_thresholds = WTF::get<Vector<double>>(WTFMove(init.threshold)); |
142 std::sort(m_thresholds.begin(), m_thresholds.end(), std::less<double>()); | 126 std::sort(m_thresholds.begin(), m_thresholds.end(), std::less<double>()); |
| 127 |
| 128 if (m_root) { |
| 129 auto& observerData = m_root->ensureIntersectionObserverData(); |
| 130 observerData.observers.append(this); |
| 131 } else if (auto* frame = document.frame()) { |
| 132 m_implicitRootDocument = frame->mainFrame().document(); |
| 133 m_implicitRootDocument->addViewportIntersectionObserver(*this); |
| 134 } |
| 135 } |
| 136 |
| 137 String IntersectionObserver::rootMargin() const |
| 138 { |
| 139 StringBuilder stringBuilder; |
| 140 PhysicalBoxSide sides[4] = { PhysicalBoxSide::Top, PhysicalBoxSide::Right, P
hysicalBoxSide::Bottom, PhysicalBoxSide::Left }; |
| 141 for (auto side : sides) { |
| 142 auto& length = m_rootMargin.at(side); |
| 143 stringBuilder.appendNumber(length.intValue()); |
| 144 if (length.type() == Percent) |
| 145 stringBuilder.append('%'); |
| 146 else |
| 147 stringBuilder.append("px", 2); |
| 148 if (side != PhysicalBoxSide::Left) |
| 149 stringBuilder.append(' '); |
| 150 } |
| 151 return stringBuilder.toString(); |
143 } | 152 } |
144 | 153 |
145 IntersectionObserver::~IntersectionObserver() | 154 IntersectionObserver::~IntersectionObserver() |
146 { | 155 { |
147 disconnect(); | 156 disconnect(); |
148 } | 157 if (m_implicitRootDocument) |
149 | 158 m_implicitRootDocument->removeViewportIntersectionObserver(*this); |
150 ExceptionOr<void> IntersectionObserver::observe(Element& element) | 159 else if (m_root) { |
151 { | 160 auto& elementObservers = m_root->intersectionObserverData()->observers; |
152 if (m_root && &element.document() != &m_document) { | 161 for (size_t i = 0; i < elementObservers.size(); ++i) { |
153 // FIXME: Should we be raising an exception or just silently doing nothi
ng (which is what the spec wants and what other browsers do). | 162 if (elementObservers.at(i) == this) { |
154 m_document.domWindow()->printErrorMessage("IntersectionObserver.observe(
) on an element from another document is not allowed."); | 163 elementObservers.remove(i); |
155 return Exception { WrongDocumentError }; | 164 break; |
156 } | 165 } |
157 | 166 } |
| 167 } |
| 168 } |
| 169 |
| 170 void IntersectionObserver::observe(Element& element) |
| 171 { |
| 172 if (!trackingDocument()) |
| 173 return; |
158 element.startObservingIntersections(*this); | 174 element.startObservingIntersections(*this); |
159 return { }; | |
160 } | 175 } |
161 | 176 |
162 void IntersectionObserver::unobserve(Element& element) | 177 void IntersectionObserver::unobserve(Element& element) |
163 { | 178 { |
164 if (&element.document() != &m_document) | |
165 return; | |
166 | |
167 element.endObservingIntersections(*this); | 179 element.endObservingIntersections(*this); |
168 } | 180 } |
169 | 181 |
170 void IntersectionObserver::disconnect() | 182 void IntersectionObserver::disconnect() |
171 { | 183 { |
172 for (auto* target : m_observationTargets) | 184 for (auto* target : m_observationTargets) |
173 unobserve(*target); | 185 unobserve(*target); |
174 } | 186 } |
175 | 187 |
176 Vector<Ref<IntersectionObserverEntry>> IntersectionObserver::takeRecords() | 188 Vector<Ref<IntersectionObserverEntry>> IntersectionObserver::takeRecords() |
177 { | 189 { |
178 return WTFMove(m_queuedEntries); | 190 return WTFMove(m_queuedEntries); |
179 } | 191 } |
180 | 192 |
181 void IntersectionObserver::addObservationTarget(Element& element) | 193 void IntersectionObserver::addObservationTarget(Element& element) |
182 { | 194 { |
183 ASSERT(!m_observationTargets.contains(&element)); | 195 ASSERT(!m_observationTargets.contains(&element)); |
184 m_observationTargets.append(&element); | 196 m_observationTargets.append(&element); |
| 197 auto* document = trackingDocument(); |
| 198 document->activateIntersectionObserver(*this); |
| 199 document->postTask([&trackingDocument = *document] (ScriptExecutionContext&)
mutable { |
| 200 trackingDocument.updateIntersectionObservations(); |
| 201 }); |
185 } | 202 } |
186 | 203 |
187 void IntersectionObserver::removeObservationTarget(Element& element) | 204 void IntersectionObserver::removeObservationTarget(Element& element) |
188 { | 205 { |
189 m_observationTargets.removeFirst(&element); | 206 m_observationTargets.removeFirst(&element); |
| 207 if (!hasObservationTargets()) { |
| 208 if (auto* document = trackingDocument()) |
| 209 document->deactivateIntersectionObserver(*this); |
| 210 } |
190 } | 211 } |
191 | 212 |
192 bool IntersectionObserver::hasObservationTarget(Element& element) const | 213 bool IntersectionObserver::hasObservationTarget(Element& element) const |
193 { | 214 { |
194 return m_observationTargets.contains(&element); | 215 return m_observationTargets.contains(&element); |
195 } | 216 } |
196 | 217 |
| 218 bool IntersectionObserver::createTimestamp(DOMHighResTimeStamp& timestamp) const |
| 219 { |
| 220 auto* context = m_callback->scriptExecutionContext(); |
| 221 if (!context) |
| 222 return false; |
| 223 ASSERT(context->isDocument()); |
| 224 auto& document = downcast<Document>(*context); |
| 225 if (auto* window = document.domWindow()) { |
| 226 if (auto* performance = window->performance()) { |
| 227 timestamp = performance->now(); |
| 228 return true; |
| 229 } |
| 230 } |
| 231 return false; |
| 232 } |
| 233 |
| 234 void IntersectionObserver::rootDestroyed() |
| 235 { |
| 236 ASSERT(m_root); |
| 237 disconnect(); |
| 238 m_root = nullptr; |
| 239 } |
| 240 |
| 241 void IntersectionObserver::implicitRootDocumentDestroyed() |
| 242 { |
| 243 ASSERT(m_implicitRootDocument); |
| 244 disconnect(); |
| 245 m_implicitRootDocument = nullptr; |
| 246 } |
| 247 |
197 void IntersectionObserver::appendQueuedEntry(Ref<IntersectionObserverEntry>&& en
try) | 248 void IntersectionObserver::appendQueuedEntry(Ref<IntersectionObserverEntry>&& en
try) |
198 { | 249 { |
199 LOG_WITH_STREAM(IntersectionObserver, stream << " adding queued entry " << e
ntry.get()); | 250 LOG_WITH_STREAM(IntersectionObserver, stream << " adding queued entry " << e
ntry.get()); |
200 | 251 |
201 m_queuedEntries.append(WTFMove(entry)); | 252 m_queuedEntries.append(WTFMove(entry)); |
202 } | 253 } |
203 | 254 |
204 void IntersectionObserver::notify() | 255 void IntersectionObserver::notify() |
205 { | 256 { |
206 if (m_queuedEntries.isEmpty()) | 257 if (m_queuedEntries.isEmpty() || !m_callback->canInvokeCallback()) |
207 return; | 258 return; |
208 | 259 |
209 LOG(IntersectionObserver, "IntersectionObserver %p callback(): %lu records",
this, m_queuedEntries.size()); | 260 LOG(IntersectionObserver, "IntersectionObserver %p callback(): %lu records",
this, m_queuedEntries.size()); |
210 | 261 |
211 m_callback->handleEvent(takeRecords(), *this); | 262 m_callback->handleEvent(takeRecords(), *this); |
212 } | 263 } |
213 | 264 |
214 } // namespace WebCore | 265 } // namespace WebCore |
215 | 266 |
216 #endif // ENABLE(INTERSECTION_OBSERVER) | 267 #endif // ENABLE(INTERSECTION_OBSERVER) |
LEFT | RIGHT |