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 <wtf/text/TextStream.h> | |
43 #include <wtf/Vector.h> | |
44 #include <algorithm> | 42 #include <algorithm> |
45 #include <functional> | 43 #include <functional> |
| 44 #include <wtf/Vector.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 return threshold >= 0.0 && threshold <= 1.0; | 50 { |
| 51 return threshold >= 0 && threshold <= 1; |
51 } | 52 } |
52 | 53 |
53 static ExceptionOr<LengthBox> parseRootMargin(String& rootMargin) | 54 static ExceptionOr<LengthBox> parseRootMargin(String& rootMargin) |
54 { | 55 { |
55 CSSTokenizer tokenizer(rootMargin); | 56 CSSTokenizer tokenizer(rootMargin); |
56 auto tokenRange = tokenizer.tokenRange(); | 57 auto tokenRange = tokenizer.tokenRange(); |
57 Vector<Length, 4> margins; | 58 Vector<Length, 4> margins; |
58 while (!tokenRange.atEnd()) { | 59 while (!tokenRange.atEnd()) { |
59 if (margins.size() == 4) | 60 if (margins.size() == 4) |
60 return Exception { SyntaxError, "Failed to construct 'IntersectionOb
server': Extra text found at the end of rootMargin." }; | 61 return Exception { SyntaxError, "Failed to construct 'IntersectionOb
server': Extra text found at the end of rootMargin." }; |
61 RefPtr<CSSPrimitiveValue> parsedValue = CSSPropertyParserHelpers::consum
eLengthOrPercent(tokenRange, HTMLStandardMode, ValueRangeAll); | 62 RefPtr<CSSPrimitiveValue> parsedValue = CSSPropertyParserHelpers::consum
eLengthOrPercent(tokenRange, HTMLStandardMode, ValueRangeAll); |
62 if (!parsedValue || parsedValue->isCalculated()) | 63 if (!parsedValue || parsedValue->isCalculated()) |
63 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." }; |
64 if (parsedValue->isPercentage()) | 65 if (parsedValue->isPercentage()) |
65 margins.append(Length(parsedValue->doubleValue(), Percent)); | 66 margins.append(Length(parsedValue->doubleValue(), Percent)); |
66 else if (parsedValue->isPx()) | 67 else if (parsedValue->isPx()) |
67 margins.append(Length(parsedValue->intValue(), Fixed)); | 68 margins.append(Length(parsedValue->intValue(), Fixed)); |
68 else | 69 else |
69 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." }; |
70 } | 71 } |
71 | |
72 Length top, right, bottom, left; | |
73 switch (margins.size()) { | 72 switch (margins.size()) { |
74 case 0: | 73 case 0: |
75 for (int i = 0; i < 4; ++i) | 74 for (unsigned i = 0; i < 4; ++i) |
76 margins.append(Length()); | 75 margins.append(Length()); |
77 break; | 76 break; |
78 case 1: | 77 case 1: |
79 for (int i = 0; i < 3; ++i) | 78 for (unsigned i = 0; i < 3; ++i) |
80 margins.append(margins[0]); | 79 margins.append(margins[0]); |
81 break; | 80 break; |
82 case 2: | 81 case 2: |
83 margins.append(margins[0]); | 82 margins.append(margins[0]); |
84 margins.append(margins[1]); | 83 margins.append(margins[1]); |
85 break; | 84 break; |
86 case 3: | 85 case 3: |
87 margins.append(margins[1]); | 86 margins.append(margins[1]); |
88 break; | 87 break; |
89 case 4: | 88 case 4: |
90 break; | 89 break; |
91 default: | 90 default: |
92 ASSERT_NOT_REACHED(); | 91 ASSERT_NOT_REACHED(); |
93 } | 92 } |
94 | |
95 StringBuilder stringBuilder; | |
96 for (size_t i = 0; i < 4; ++i) { | |
97 stringBuilder.appendNumber(margins[i].intValue()); | |
98 if (margins[i].type() == Percent) | |
99 stringBuilder.append('%'); | |
100 else | |
101 stringBuilder.append("px", 2); | |
102 if (i < 3) | |
103 stringBuilder.append(' '); | |
104 } | |
105 rootMargin = stringBuilder.toString(); | |
106 | 93 |
107 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])); |
108 } | 95 } |
109 | 96 |
110 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) |
111 { | 98 { |
112 auto rootMarginOrException = parseRootMargin(init.rootMargin); | 99 auto rootMarginOrException = parseRootMargin(init.rootMargin); |
113 if (rootMarginOrException.hasException()) | 100 if (rootMarginOrException.hasException()) |
114 return rootMarginOrException.releaseException(); | 101 return rootMarginOrException.releaseException(); |
115 | 102 |
116 bool foundOutOfRangeThreshold = false; | 103 bool foundOutOfRangeThreshold = false; |
117 if (WTF::holds_alternative<double>(init.threshold)) | 104 if (WTF::holds_alternative<double>(init.threshold)) |
118 foundOutOfRangeThreshold = !isThresholdWithinRange(WTF::get<double>(init
.threshold)); | 105 foundOutOfRangeThreshold = !isThresholdWithinRange(WTF::get<double>(init
.threshold)); |
119 else { | 106 else { |
120 for (auto& threshold : WTF::get<Vector<double>>(init.threshold)) | 107 for (auto& threshold : WTF::get<Vector<double>>(init.threshold)) |
121 foundOutOfRangeThreshold |= !isThresholdWithinRange(threshold); | 108 foundOutOfRangeThreshold |= !isThresholdWithinRange(threshold); |
122 } | 109 } |
123 if (foundOutOfRangeThreshold) { | 110 if (foundOutOfRangeThreshold) |
124 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]." }; |
125 } | |
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 (&element.document() != &m_document) { | 161 for (size_t i = 0; i < elementObservers.size(); ++i) { |
153 m_document.domWindow()->printErrorMessage("IntersectionObserver.observe(
) on an element from another document is not allowed."); | 162 if (elementObservers.at(i) == this) { |
154 return Exception { WrongDocumentError }; | 163 elementObservers.remove(i); |
155 } | 164 break; |
156 | 165 } |
| 166 } |
| 167 } |
| 168 } |
| 169 |
| 170 void IntersectionObserver::observe(Element& element) |
| 171 { |
| 172 if (!trackingDocument()) |
| 173 return; |
157 element.startObservingIntersections(*this); | 174 element.startObservingIntersections(*this); |
158 return { }; | |
159 } | 175 } |
160 | 176 |
161 void IntersectionObserver::unobserve(Element& element) | 177 void IntersectionObserver::unobserve(Element& element) |
162 { | 178 { |
163 if (&element.document() != &m_document) | |
164 return; | |
165 | |
166 element.endObservingIntersections(*this); | 179 element.endObservingIntersections(*this); |
167 } | 180 } |
168 | 181 |
169 void IntersectionObserver::disconnect() | 182 void IntersectionObserver::disconnect() |
170 { | 183 { |
171 for (auto* target : m_observationTargets) | 184 for (auto* target : m_observationTargets) |
172 unobserve(*target); | 185 unobserve(*target); |
173 } | 186 } |
174 | 187 |
175 Vector<Ref<IntersectionObserverEntry>> IntersectionObserver::takeRecords() | 188 Vector<Ref<IntersectionObserverEntry>> IntersectionObserver::takeRecords() |
176 { | 189 { |
177 return WTFMove(m_queuedEntries); | 190 return WTFMove(m_queuedEntries); |
178 } | 191 } |
179 | 192 |
180 void IntersectionObserver::addObservationTarget(Element& element) | 193 void IntersectionObserver::addObservationTarget(Element& element) |
181 { | 194 { |
182 ASSERT(!m_observationTargets.contains(&element)); | 195 ASSERT(!m_observationTargets.contains(&element)); |
183 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 }); |
184 } | 202 } |
185 | 203 |
186 void IntersectionObserver::removeObservationTarget(Element& element) | 204 void IntersectionObserver::removeObservationTarget(Element& element) |
187 { | 205 { |
188 m_observationTargets.removeFirst(&element); | 206 m_observationTargets.removeFirst(&element); |
| 207 if (!hasObservationTargets()) { |
| 208 if (auto* document = trackingDocument()) |
| 209 document->deactivateIntersectionObserver(*this); |
| 210 } |
189 } | 211 } |
190 | 212 |
191 bool IntersectionObserver::hasObservationTarget(Element& element) const | 213 bool IntersectionObserver::hasObservationTarget(Element& element) const |
192 { | 214 { |
193 return m_observationTargets.contains(&element); | 215 return m_observationTargets.contains(&element); |
194 } | 216 } |
195 | 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 |
196 void IntersectionObserver::appendQueuedEntry(Ref<IntersectionObserverEntry>&& en
try) | 248 void IntersectionObserver::appendQueuedEntry(Ref<IntersectionObserverEntry>&& en
try) |
197 { | 249 { |
198 BACKTRACE(); | |
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 |