| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2008 Apple Inc. All rights reserved. | 2 * Copyright (C) 2008 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 |
| 11 * documentation and/or other materials provided with the distribution. | 11 * documentation and/or other materials provided with the distribution. |
| 12 * | 12 * |
| 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR | 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 */ | 24 */ |
| 25 | 25 |
| 26 #include "config.h" | 26 #include "config.h" |
| 27 #include "core/svg/animation/SMILTimeContainer.h" | 27 #include "core/svg/animation/SMILTimeContainer.h" |
| 28 | 28 |
| 29 #include "core/animation/AnimationClock.h" |
| 30 #include "core/animation/DocumentTimeline.h" |
| 29 #include "core/dom/ElementTraversal.h" | 31 #include "core/dom/ElementTraversal.h" |
| 32 #include "core/frame/FrameView.h" |
| 30 #include "core/svg/SVGSVGElement.h" | 33 #include "core/svg/SVGSVGElement.h" |
| 31 #include "core/svg/animation/SVGSMILElement.h" | 34 #include "core/svg/animation/SVGSMILElement.h" |
| 32 #include "wtf/CurrentTime.h" | |
| 33 | 35 |
| 34 using namespace std; | 36 using namespace std; |
| 35 | 37 |
| 36 namespace WebCore { | 38 namespace WebCore { |
| 37 | 39 |
| 38 static const double animationFrameDelay = 0.025; | |
| 39 | |
| 40 SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner) | 40 SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner) |
| 41 : m_beginTime(0) | 41 : m_beginTime(0) |
| 42 , m_pauseTime(0) | 42 , m_pauseTime(0) |
| 43 , m_resumeTime(0) | 43 , m_resumeTime(0) |
| 44 , m_accumulatedActiveTime(0) | 44 , m_accumulatedActiveTime(0) |
| 45 , m_presetStartTime(0) | 45 , m_presetStartTime(0) |
| 46 , m_documentOrderIndexesDirty(false) | 46 , m_documentOrderIndexesDirty(false) |
| 47 , m_timer(this, &SMILTimeContainer::timerFired) | 47 , m_framePending(false) |
| 48 , m_animationClock(AnimationClock::create()) |
| 49 , m_wakeupTimer(this, &SMILTimeContainer::wakeupTimerFired) |
| 48 , m_ownerSVGElement(owner) | 50 , m_ownerSVGElement(owner) |
| 49 #ifndef NDEBUG | 51 #ifndef NDEBUG |
| 50 , m_preventScheduledAnimationsChanges(false) | 52 , m_preventScheduledAnimationsChanges(false) |
| 51 #endif | 53 #endif |
| 52 { | 54 { |
| 53 } | 55 } |
| 54 | 56 |
| 55 SMILTimeContainer::~SMILTimeContainer() | 57 SMILTimeContainer::~SMILTimeContainer() |
| 56 { | 58 { |
| 57 cancelAnimationFrame(); | 59 cancelAnimationFrame(); |
| 58 ASSERT(!m_timer.isActive()); | 60 ASSERT(!m_wakeupTimer.isActive()); |
| 59 #ifndef NDEBUG | 61 #ifndef NDEBUG |
| 60 ASSERT(!m_preventScheduledAnimationsChanges); | 62 ASSERT(!m_preventScheduledAnimationsChanges); |
| 61 #endif | 63 #endif |
| 62 } | 64 } |
| 63 | 65 |
| 64 void SMILTimeContainer::schedule(SVGSMILElement* animation, SVGElement* target,
const QualifiedName& attributeName) | 66 void SMILTimeContainer::schedule(SVGSMILElement* animation, SVGElement* target,
const QualifiedName& attributeName) |
| 65 { | 67 { |
| 66 ASSERT(animation->timeContainer() == this); | 68 ASSERT(animation->timeContainer() == this); |
| 67 ASSERT(target); | 69 ASSERT(target); |
| 68 ASSERT(animation->hasValidAttributeName()); | 70 ASSERT(animation->hasValidAttributeName()); |
| (...skipping 23 matching lines...) Expand all Loading... |
| 92 #endif | 94 #endif |
| 93 | 95 |
| 94 ElementAttributePair key(target, attributeName); | 96 ElementAttributePair key(target, attributeName); |
| 95 AnimationsVector* scheduled = m_scheduledAnimations.get(key); | 97 AnimationsVector* scheduled = m_scheduledAnimations.get(key); |
| 96 ASSERT(scheduled); | 98 ASSERT(scheduled); |
| 97 size_t idx = scheduled->find(animation); | 99 size_t idx = scheduled->find(animation); |
| 98 ASSERT(idx != kNotFound); | 100 ASSERT(idx != kNotFound); |
| 99 scheduled->remove(idx); | 101 scheduled->remove(idx); |
| 100 } | 102 } |
| 101 | 103 |
| 104 bool SMILTimeContainer::hasAnimations() const |
| 105 { |
| 106 return !m_scheduledAnimations.isEmpty(); |
| 107 } |
| 108 |
| 102 void SMILTimeContainer::notifyIntervalsChanged() | 109 void SMILTimeContainer::notifyIntervalsChanged() |
| 103 { | 110 { |
| 104 // Schedule updateAnimations() to be called asynchronously so multiple inter
vals | 111 // Schedule updateAnimations() to be called asynchronously so multiple inter
vals |
| 105 // can change with updateAnimations() only called once at the end. | 112 // can change with updateAnimations() only called once at the end. |
| 106 scheduleAnimationFrame(); | 113 scheduleAnimationFrame(); |
| 107 } | 114 } |
| 108 | 115 |
| 109 SMILTime SMILTimeContainer::elapsed() const | 116 SMILTime SMILTimeContainer::elapsed() const |
| 110 { | 117 { |
| 111 if (!m_beginTime) | 118 if (!m_beginTime) |
| 112 return 0; | 119 return 0; |
| 113 | 120 |
| 114 if (isPaused()) | 121 if (isPaused()) |
| 115 return m_accumulatedActiveTime; | 122 return m_accumulatedActiveTime; |
| 116 | 123 |
| 117 return currentTime() + m_accumulatedActiveTime - lastResumeTime(); | 124 return m_animationClock->currentTime() + m_accumulatedActiveTime - lastResum
eTime(); |
| 118 } | 125 } |
| 119 | 126 |
| 120 bool SMILTimeContainer::isPaused() const | 127 bool SMILTimeContainer::isPaused() const |
| 121 { | 128 { |
| 122 return m_pauseTime; | 129 return m_pauseTime; |
| 123 } | 130 } |
| 124 | 131 |
| 125 bool SMILTimeContainer::isStarted() const | 132 bool SMILTimeContainer::isStarted() const |
| 126 { | 133 { |
| 127 return m_beginTime; | 134 return m_beginTime; |
| 128 } | 135 } |
| 129 | 136 |
| 130 void SMILTimeContainer::begin() | 137 void SMILTimeContainer::begin() |
| 131 { | 138 { |
| 132 ASSERT(!m_beginTime); | 139 ASSERT(!m_beginTime); |
| 133 double now = currentTime(); | 140 double now = m_animationClock->currentTime(); |
| 134 | 141 |
| 135 // If 'm_presetStartTime' is set, the timeline was modified via setElapsed()
before the document began. | 142 // If 'm_presetStartTime' is set, the timeline was modified via setElapsed()
before the document began. |
| 136 // In this case pass on 'seekToTime=true' to updateAnimations(). | 143 // In this case pass on 'seekToTime=true' to updateAnimations(). |
| 137 m_beginTime = now - m_presetStartTime; | 144 m_beginTime = now - m_presetStartTime; |
| 138 updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : fal
se); | 145 updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : fal
se); |
| 139 m_presetStartTime = 0; | 146 m_presetStartTime = 0; |
| 140 | 147 |
| 141 if (m_pauseTime) { | 148 if (m_pauseTime) { |
| 142 m_pauseTime = now; | 149 m_pauseTime = now; |
| 143 cancelAnimationFrame(); | 150 cancelAnimationFrame(); |
| 151 } else { |
| 152 // Latch the clock to this time (0 or the preset start time). |
| 153 m_animationClock->updateTime(now); |
| 144 } | 154 } |
| 145 } | 155 } |
| 146 | 156 |
| 147 void SMILTimeContainer::pause() | 157 void SMILTimeContainer::pause() |
| 148 { | 158 { |
| 149 ASSERT(!isPaused()); | 159 ASSERT(!isPaused()); |
| 150 m_pauseTime = currentTime(); | 160 m_pauseTime = m_animationClock->currentTime(); |
| 151 | 161 |
| 152 if (m_beginTime) { | 162 if (m_beginTime) { |
| 153 m_accumulatedActiveTime += m_pauseTime - lastResumeTime(); | 163 m_accumulatedActiveTime += m_pauseTime - lastResumeTime(); |
| 154 cancelAnimationFrame(); | 164 cancelAnimationFrame(); |
| 155 } | 165 } |
| 156 m_resumeTime = 0; | 166 m_resumeTime = 0; |
| 167 m_animationClock->unfreeze(); |
| 157 } | 168 } |
| 158 | 169 |
| 159 void SMILTimeContainer::resume() | 170 void SMILTimeContainer::resume() |
| 160 { | 171 { |
| 161 ASSERT(isPaused()); | 172 ASSERT(isPaused()); |
| 162 m_resumeTime = currentTime(); | 173 m_resumeTime = m_animationClock->currentTime(); |
| 163 | 174 |
| 164 m_pauseTime = 0; | 175 m_pauseTime = 0; |
| 165 scheduleAnimationFrame(); | 176 scheduleAnimationFrame(); |
| 177 m_animationClock->unfreeze(); |
| 166 } | 178 } |
| 167 | 179 |
| 168 void SMILTimeContainer::setElapsed(SMILTime time) | 180 void SMILTimeContainer::setElapsed(SMILTime time) |
| 169 { | 181 { |
| 170 // If the documment didn't begin yet, record a new start time, we'll seek to
once its possible. | 182 // If the documment didn't begin yet, record a new start time, we'll seek to
once its possible. |
| 171 if (!m_beginTime) { | 183 if (!m_beginTime) { |
| 172 m_presetStartTime = time.value(); | 184 m_presetStartTime = time.value(); |
| 173 return; | 185 return; |
| 174 } | 186 } |
| 175 | 187 |
| 176 if (m_beginTime) | 188 m_animationClock->unfreeze(); |
| 177 cancelAnimationFrame(); | |
| 178 | 189 |
| 179 double now = currentTime(); | 190 cancelAnimationFrame(); |
| 191 |
| 192 double now = m_animationClock->currentTime(); |
| 180 m_beginTime = now - time.value(); | 193 m_beginTime = now - time.value(); |
| 181 m_resumeTime = 0; | 194 m_resumeTime = 0; |
| 182 if (m_pauseTime) { | 195 if (m_pauseTime) { |
| 183 m_pauseTime = now; | 196 m_pauseTime = now; |
| 184 m_accumulatedActiveTime = time.value(); | 197 m_accumulatedActiveTime = time.value(); |
| 185 } else { | 198 } else { |
| 186 m_accumulatedActiveTime = 0; | 199 m_accumulatedActiveTime = 0; |
| 187 } | 200 } |
| 188 | 201 |
| 189 #ifndef NDEBUG | 202 #ifndef NDEBUG |
| 190 m_preventScheduledAnimationsChanges = true; | 203 m_preventScheduledAnimationsChanges = true; |
| 191 #endif | 204 #endif |
| 192 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end(); | 205 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end(); |
| 193 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it !
= end; ++it) { | 206 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it !
= end; ++it) { |
| 194 AnimationsVector* scheduled = it->value.get(); | 207 AnimationsVector* scheduled = it->value.get(); |
| 195 unsigned size = scheduled->size(); | 208 unsigned size = scheduled->size(); |
| 196 for (unsigned n = 0; n < size; n++) | 209 for (unsigned n = 0; n < size; n++) |
| 197 scheduled->at(n)->reset(); | 210 scheduled->at(n)->reset(); |
| 198 } | 211 } |
| 199 #ifndef NDEBUG | 212 #ifndef NDEBUG |
| 200 m_preventScheduledAnimationsChanges = false; | 213 m_preventScheduledAnimationsChanges = false; |
| 201 #endif | 214 #endif |
| 202 | 215 |
| 203 updateAnimations(time, true); | 216 updateAnimations(time, true); |
| 217 // Latch the clock to wait for this frame to be sampled by the frame interva
l. |
| 218 m_animationClock->updateTime(now); |
| 204 } | 219 } |
| 205 | 220 |
| 206 bool SMILTimeContainer::isTimelineRunning() const | 221 bool SMILTimeContainer::isTimelineRunning() const |
| 207 { | 222 { |
| 208 return m_beginTime && !isPaused(); | 223 return m_beginTime && !isPaused(); |
| 209 } | 224 } |
| 210 | 225 |
| 211 void SMILTimeContainer::scheduleAnimationFrame(SMILTime fireTime) | 226 void SMILTimeContainer::scheduleAnimationFrame(SMILTime fireTime) |
| 212 { | 227 { |
| 213 if (!isTimelineRunning()) | 228 if (!isTimelineRunning()) |
| 214 return; | 229 return; |
| 215 | 230 |
| 216 if (!fireTime.isFinite()) | 231 if (!fireTime.isFinite()) |
| 217 return; | 232 return; |
| 218 | 233 |
| 219 SMILTime delay = max(fireTime - elapsed(), SMILTime(animationFrameDelay)); | 234 SMILTime delay = fireTime - elapsed(); |
| 220 m_timer.startOneShot(delay.value()); | 235 if (delay.value() < DocumentTimeline::s_minimumDelay) |
| 236 serviceOnNextFrame(); |
| 237 else |
| 238 m_wakeupTimer.startOneShot(delay.value() - DocumentTimeline::s_minimumDe
lay); |
| 221 } | 239 } |
| 222 | 240 |
| 223 void SMILTimeContainer::scheduleAnimationFrame() | 241 void SMILTimeContainer::scheduleAnimationFrame() |
| 224 { | 242 { |
| 225 if (!isTimelineRunning()) | 243 if (!isTimelineRunning()) |
| 226 return; | 244 return; |
| 227 | 245 |
| 228 m_timer.startOneShot(0); | 246 // Could also schedule a wakeup at +0 seconds, but that could still |
| 247 // potentially race with the servicing of the next frame. |
| 248 serviceOnNextFrame(); |
| 229 } | 249 } |
| 230 | 250 |
| 231 void SMILTimeContainer::cancelAnimationFrame() | 251 void SMILTimeContainer::cancelAnimationFrame() |
| 232 { | 252 { |
| 233 m_timer.stop(); | 253 m_framePending = false; |
| 254 m_wakeupTimer.stop(); |
| 234 } | 255 } |
| 235 | 256 |
| 236 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*) | 257 void SMILTimeContainer::wakeupTimerFired(Timer<SMILTimeContainer>*) |
| 237 { | 258 { |
| 238 ASSERT(isTimelineRunning()); | 259 ASSERT(isTimelineRunning()); |
| 239 updateAnimations(elapsed()); | 260 serviceOnNextFrame(); |
| 240 } | 261 } |
| 241 | 262 |
| 242 void SMILTimeContainer::updateDocumentOrderIndexes() | 263 void SMILTimeContainer::updateDocumentOrderIndexes() |
| 243 { | 264 { |
| 244 unsigned timingElementCount = 0; | 265 unsigned timingElementCount = 0; |
| 245 for (Element* element = m_ownerSVGElement; element; element = ElementTravers
al::next(*element, m_ownerSVGElement)) { | 266 for (Element* element = m_ownerSVGElement; element; element = ElementTravers
al::next(*element, m_ownerSVGElement)) { |
| 246 if (isSVGSMILElement(*element)) | 267 if (isSVGSMILElement(*element)) |
| 247 toSVGSMILElement(element)->setDocumentOrderIndex(timingElementCount+
+); | 268 toSVGSMILElement(element)->setDocumentOrderIndex(timingElementCount+
+); |
| 248 } | 269 } |
| 249 m_documentOrderIndexesDirty = false; | 270 m_documentOrderIndexesDirty = false; |
| 250 } | 271 } |
| 251 | 272 |
| 252 struct PriorityCompare { | 273 struct PriorityCompare { |
| 253 PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {} | 274 PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {} |
| 254 bool operator()(const RefPtr<SVGSMILElement>& a, const RefPtr<SVGSMILElement
>& b) | 275 bool operator()(const RefPtr<SVGSMILElement>& a, const RefPtr<SVGSMILElement
>& b) |
| 255 { | 276 { |
| 256 // FIXME: This should also consider possible timing relations between th
e elements. | 277 // FIXME: This should also consider possible timing relations between th
e elements. |
| 257 SMILTime aBegin = a->intervalBegin(); | 278 SMILTime aBegin = a->intervalBegin(); |
| 258 SMILTime bBegin = b->intervalBegin(); | 279 SMILTime bBegin = b->intervalBegin(); |
| 259 // Frozen elements need to be prioritized based on their previous interv
al. | 280 // Frozen elements need to be prioritized based on their previous interv
al. |
| 260 aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin(
) : aBegin; | 281 aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin(
) : aBegin; |
| 261 bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin(
) : bBegin; | 282 bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin(
) : bBegin; |
| 262 if (aBegin == bBegin) | 283 if (aBegin == bBegin) |
| 263 return a->documentOrderIndex() < b->documentOrderIndex(); | 284 return a->documentOrderIndex() < b->documentOrderIndex(); |
| 264 return aBegin < bBegin; | 285 return aBegin < bBegin; |
| 265 } | 286 } |
| 266 SMILTime m_elapsed; | 287 SMILTime m_elapsed; |
| 267 }; | 288 }; |
| 268 | 289 |
| 290 Document& SMILTimeContainer::document() const |
| 291 { |
| 292 ASSERT(m_ownerSVGElement); |
| 293 return m_ownerSVGElement->document(); |
| 294 } |
| 295 |
| 296 void SMILTimeContainer::serviceOnNextFrame() |
| 297 { |
| 298 if (document().view()) { |
| 299 document().view()->scheduleAnimation(); |
| 300 m_framePending = true; |
| 301 } |
| 302 } |
| 303 |
| 304 void SMILTimeContainer::serviceAnimations(double monotonicAnimationStartTime) |
| 305 { |
| 306 if (!m_framePending) |
| 307 return; |
| 308 |
| 309 m_framePending = false; |
| 310 // If the clock is frozen at this point, it means the timeline has been |
| 311 // started, but the first animation frame hasn't yet been serviced. If so, |
| 312 // then just keep the clock frozen for this update. |
| 313 if (!m_animationClock->isFrozen()) |
| 314 m_animationClock->updateTime(monotonicAnimationStartTime); |
| 315 updateAnimations(elapsed()); |
| 316 m_animationClock->unfreeze(); |
| 317 } |
| 318 |
| 269 void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime) | 319 void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime) |
| 270 { | 320 { |
| 271 SMILTime earliestFireTime = SMILTime::unresolved(); | 321 SMILTime earliestFireTime = SMILTime::unresolved(); |
| 272 | 322 |
| 273 #ifndef NDEBUG | 323 #ifndef NDEBUG |
| 274 // This boolean will catch any attempts to schedule/unschedule scheduledAnim
ations during this critical section. | 324 // This boolean will catch any attempts to schedule/unschedule scheduledAnim
ations during this critical section. |
| 275 // Similarly, any elements removed will unschedule themselves, so this will
catch modification of animationsToApply. | 325 // Similarly, any elements removed will unschedule themselves, so this will
catch modification of animationsToApply. |
| 276 m_preventScheduledAnimationsChanges = true; | 326 m_preventScheduledAnimationsChanges = true; |
| 277 #endif | 327 #endif |
| 278 | 328 |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 351 | 401 |
| 352 if (animDiscard->inDocument()) { | 402 if (animDiscard->inDocument()) { |
| 353 animDiscard->remove(IGNORE_EXCEPTION); | 403 animDiscard->remove(IGNORE_EXCEPTION); |
| 354 ASSERT(!animDiscard->inDocument()); | 404 ASSERT(!animDiscard->inDocument()); |
| 355 } | 405 } |
| 356 } | 406 } |
| 357 } | 407 } |
| 358 } | 408 } |
| 359 | 409 |
| 360 } | 410 } |
| OLD | NEW |