Changeset 176212 in webkit
- Timestamp:
- Nov 17, 2014, 11:23:43 AM (10 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 16 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r176210 r176212 1 2014-11-17 Chris Dumez <cdumez@apple.com> 2 3 Throttle timers that change the style of elements outside the viewport 4 https://bugs.webkit.org/show_bug.cgi?id=138292 5 6 Reviewed by Antti Koivisto. 7 8 Throttle timers that change the style of elements outside the viewport 9 to 1 second, similarly to what was already done for timers interacting 10 with non user observable plugins. To be conservative, we don't throttle 11 timers that also cause DOM Tree modifications (e.g. adding/removing 12 nodes, modify element attributes). 13 14 On huffingtonpost.com, the CPU usage is at ~17% when the top scrolling 15 banner is inside the viewport on my machine. Without this patch, CPU 16 usage would stay ~17% when the banner is outside the viewport. Thanks 17 to timer throttling, CPU usage now goes down to ~1.5%, without user 18 observable side effects. The timers get unthrottled when they are 19 inside the viewport again (i.e. due to scrolling or layout). 20 21 On espn.com, the CPU usage goes down from ~7% at the top of the page 22 to ~1% when scrolling to the bottom of the page. On ebay.com, CPU 23 usage goes down from ~25% at the top of the page to less than 1% when 24 scrolling to the bottom of the page. 25 1 26 2014-11-17 peavo@outlook.com <peavo@outlook.com> 2 27 -
trunk/Source/WebCore/bindings/js/JSCSSStyleDeclarationCustom.cpp
r173410 r176212 340 340 341 341 ExceptionCode ec = 0; 342 impl().setPropertyInternal(static_cast<CSSPropertyID>(propertyInfo.propertyID), propValue, important, ec);342 bool changed = impl().setPropertyInternal(static_cast<CSSPropertyID>(propertyInfo.propertyID), propValue, important, ec); 343 343 setDOMException(exec, ec); 344 345 // Choke point for interaction with style of element; notify DOMTimer of the event. 346 if (auto* element = impl().parentElement()) 347 DOMTimer::scriptDidUpdateStyleOfElement(*element, changed); 348 344 349 return true; 345 350 } -
trunk/Source/WebCore/css/CSSComputedStyleDeclaration.cpp
r176050 r176212 3332 3332 } 3333 3333 3334 voidCSSComputedStyleDeclaration::setPropertyInternal(CSSPropertyID, const String&, bool, ExceptionCode& ec)3334 bool CSSComputedStyleDeclaration::setPropertyInternal(CSSPropertyID, const String&, bool, ExceptionCode& ec) 3335 3335 { 3336 3336 ec = NO_MODIFICATION_ALLOWED_ERR; 3337 return false; 3337 3338 } 3338 3339 -
trunk/Source/WebCore/css/CSSComputedStyleDeclaration.h
r175391 r176212 115 115 virtual PassRefPtr<CSSValue> getPropertyCSSValueInternal(CSSPropertyID) override; 116 116 virtual String getPropertyValueInternal(CSSPropertyID) override; 117 virtual voidsetPropertyInternal(CSSPropertyID, const String& value, bool important, ExceptionCode&) override;117 virtual bool setPropertyInternal(CSSPropertyID, const String& value, bool important, ExceptionCode&) override; 118 118 virtual PassRef<MutableStyleProperties> copyProperties() const override; 119 119 -
trunk/Source/WebCore/css/CSSStyleDeclaration.h
r159856 r176212 46 46 virtual void deref() = 0; 47 47 48 virtual StyledElement* parentElement() const { return nullptr; } 48 49 virtual CSSRule* parentRule() const = 0; 49 50 virtual String cssText() const = 0; … … 64 65 virtual PassRefPtr<CSSValue> getPropertyCSSValueInternal(CSSPropertyID) = 0; 65 66 virtual String getPropertyValueInternal(CSSPropertyID) = 0; 66 virtual voidsetPropertyInternal(CSSPropertyID, const String& value, bool important, ExceptionCode&) = 0;67 virtual bool setPropertyInternal(CSSPropertyID, const String& value, bool important, ExceptionCode&) = 0; 67 68 68 69 virtual PassRef<MutableStyleProperties> copyProperties() const = 0; -
trunk/Source/WebCore/css/PropertySetCSSStyleDeclaration.cpp
r173659 r176212 256 256 } 257 257 258 voidPropertySetCSSStyleDeclaration::setPropertyInternal(CSSPropertyID propertyID, const String& value, bool important, ExceptionCode& ec)258 bool PropertySetCSSStyleDeclaration::setPropertyInternal(CSSPropertyID propertyID, const String& value, bool important, ExceptionCode& ec) 259 259 { 260 260 StyleAttributeMutationScope mutationScope(this); 261 261 if (!willMutate()) 262 return ;262 return false; 263 263 264 264 ec = 0; … … 269 269 if (changed) 270 270 mutationScope.enqueueMutationRecord(); 271 return changed; 271 272 } 272 273 -
trunk/Source/WebCore/css/PropertySetCSSStyleDeclaration.h
r175391 r176212 45 45 PropertySetCSSStyleDeclaration(MutableStyleProperties* propertySet) : m_propertySet(propertySet) { } 46 46 47 virtual StyledElement* parentElement() const { return nullptr; }48 47 virtual void clearParentElement() { ASSERT_NOT_REACHED(); } 49 48 StyleSheetContents* contextStyleSheet() const; … … 67 66 virtual PassRefPtr<CSSValue> getPropertyCSSValueInternal(CSSPropertyID) override final; 68 67 virtual String getPropertyValueInternal(CSSPropertyID) override final; 69 virtual voidsetPropertyInternal(CSSPropertyID, const String& value, bool important, ExceptionCode&) override final;68 virtual bool setPropertyInternal(CSSPropertyID, const String& value, bool important, ExceptionCode&) override final; 70 69 71 70 virtual PassRef<MutableStyleProperties> copyProperties() const override final; -
trunk/Source/WebCore/dom/Element.cpp
r176084 r176212 2348 2348 } 2349 2349 2350 bool Element::isInsideViewport(const IntRect* visibleRect) const 2351 { 2352 return renderer() && renderer()->isInsideViewport(visibleRect); 2353 } 2354 2350 2355 DOMTokenList& Element::classList() 2351 2356 { -
trunk/Source/WebCore/dom/Element.h
r176174 r176212 201 201 double offsetHeight(); 202 202 203 bool isInsideViewport(const IntRect* visibleRect = nullptr) const; 204 203 205 // FIXME: Replace uses of offsetParent in the platform with calls 204 206 // to the render layer and merge bindingsOffsetParent and offsetParent. -
trunk/Source/WebCore/page/DOMTimer.cpp
r176065 r176212 1 1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.2 * Copyright (C) 2008, 2014 Apple Inc. All Rights Reserved. 3 3 * 4 4 * Redistribution and use in source and binary forms, with or without … … 28 28 #include "DOMTimer.h" 29 29 30 #include "FrameView.h" 30 31 #include "HTMLPlugInElement.h" 31 32 #include "InspectorInstrumentation.h" 33 #include "Logging.h" 32 34 #include "PluginViewBase.h" 33 35 #include "ScheduledAction.h" … … 51 53 52 54 static const int maxIntervalForUserGestureForwarding = 1000; // One second matches Gecko. 53 static const int minIntervalForNonUserObservable PluginScriptTimers = 1000; // Empirically determined to maximize battery life.55 static const int minIntervalForNonUserObservableChangeTimers = 1000; // Empirically determined to maximize battery life. 54 56 static const int maxTimerNestingLevel = 5; 55 57 static const double oneMillisecond = 0.001; 56 58 57 struct DOMTimerFireState { 59 class DOMTimerFireState { 60 public: 58 61 explicit DOMTimerFireState(ScriptExecutionContext& context) 59 : scriptDidInteractWithNonUserObservablePlugin(false) 60 , scriptDidInteractWithUserObservablePlugin(false) 61 , shouldSetCurrent(is<Document>(context)) 62 : m_context(context) 63 , m_contextIsDocument(is<Document>(m_context)) 62 64 { 63 65 // For worker threads, don't update the current DOMTimerFireState. 64 66 // Setting this from workers would not be thread-safe, and its not relevant to current uses. 65 if (shouldSetCurrent) { 66 previous = current; 67 if (m_contextIsDocument) { 68 m_initialDOMTreeVersion = downcast<Document>(context).domTreeVersion(); 69 m_previous = current; 67 70 current = this; 68 71 } … … 71 74 ~DOMTimerFireState() 72 75 { 73 if (shouldSetCurrent) 74 current = previous; 76 if (m_contextIsDocument) 77 current = m_previous; 78 } 79 80 void setScriptMadeUserObservableChanges() { m_scriptMadeUserObservableChanges = true; } 81 void setScriptMadeNonUserObservableChanges() { m_scriptMadeNonUserObservableChanges = true; } 82 void setScriptMadeNonUserObservableChangesToElementStyle(StyledElement& element) 83 { 84 m_scriptMadeNonUserObservableChanges = true; 85 m_elementsChangedOutsideViewport.add(&element); 86 } 87 88 bool scriptMadeNonUserObservableChanges() const { return m_scriptMadeNonUserObservableChanges; } 89 bool scriptMadeUserObservableChanges() const 90 { 91 if (m_scriptMadeUserObservableChanges) 92 return true; 93 94 // To be conservative, we also consider any DOM Tree change to be user observable. 95 return m_contextIsDocument && downcast<Document>(m_context).domTreeVersion() != m_initialDOMTreeVersion; 96 } 97 98 void setChangedStyleOfElementOutsideViewport(StyledElement& element) 99 { 100 m_elementsChangedOutsideViewport.add(&element); 101 } 102 103 void elementsChangedOutsideViewport(Vector<RefPtr<StyledElement>>& elements) const 104 { 105 copyToVector(m_elementsChangedOutsideViewport, elements); 75 106 } 76 107 77 108 static DOMTimerFireState* current; 78 109 79 bool scriptDidInteractWithNonUserObservablePlugin;80 bool scriptDidInteractWithUserObservablePlugin;81 82 110 private: 83 bool shouldSetCurrent; 84 DOMTimerFireState* previous; 111 ScriptExecutionContext& m_context; 112 uint64_t m_initialDOMTreeVersion; 113 DOMTimerFireState* m_previous; 114 HashSet<RefPtr<StyledElement>> m_elementsChangedOutsideViewport; 115 bool m_contextIsDocument; 116 bool m_scriptMadeNonUserObservableChanges { false }; 117 bool m_scriptMadeUserObservableChanges { false }; 85 118 }; 86 119 … … 169 202 else 170 203 startRepeating(m_currentTimerInterval); 204 } 205 206 DOMTimer::~DOMTimer() 207 { 208 if (isIntervalDependentOnViewport()) 209 unregisterForViewportChanges(); 171 210 } 172 211 … … 215 254 void DOMTimer::updateThrottlingStateIfNecessary(const DOMTimerFireState& fireState) 216 255 { 217 if (fireState.script DidInteractWithUserObservablePlugin) {256 if (fireState.scriptMadeUserObservableChanges()) { 218 257 if (m_throttleState != ShouldNotThrottle) { 219 258 m_throttleState = ShouldNotThrottle; 259 ASSERT(m_elementsCausingThrottling.isEmpty()); 220 260 updateTimerIntervalIfNecessary(); 221 261 } 222 } else if (fireState.script DidInteractWithNonUserObservablePlugin) {262 } else if (fireState.scriptMadeNonUserObservableChanges()) { 223 263 if (m_throttleState != ShouldThrottle) { 224 264 m_throttleState = ShouldThrottle; 265 fireState.elementsChangedOutsideViewport(m_elementsCausingThrottling); 225 266 updateTimerIntervalIfNecessary(); 226 267 } … … 234 275 235 276 if (pluginElement.isUserObservable()) 236 DOMTimerFireState::current->s criptDidInteractWithUserObservablePlugin = true;277 DOMTimerFireState::current->setScriptMadeUserObservableChanges(); 237 278 else 238 DOMTimerFireState::current->scriptDidInteractWithNonUserObservablePlugin = true; 279 DOMTimerFireState::current->setScriptMadeNonUserObservableChanges(); 280 } 281 282 void DOMTimer::scriptDidUpdateStyleOfElement(StyledElement& styledElement, bool changed) 283 { 284 if (!DOMTimerFireState::current) 285 return; 286 287 if (!changed) { 288 // The script set a CSS property on the Element but it did not cause any change. 289 DOMTimerFireState::current->setScriptMadeNonUserObservableChanges(); 290 return; 291 } 292 293 if (styledElement.isInsideViewport()) 294 DOMTimerFireState::current->setScriptMadeUserObservableChanges(); 295 else 296 DOMTimerFireState::current->setScriptMadeNonUserObservableChangesToElementStyle(styledElement); 239 297 } 240 298 … … 250 308 251 309 DOMTimerFireState fireState(context); 310 if (isIntervalDependentOnViewport()) { 311 // We re-evaluate if the timer interval is dependent on the viewport every time it fires. 312 unregisterForViewportChanges(); 313 } 252 314 253 315 #if PLATFORM(IOS) … … 342 404 } 343 405 406 void DOMTimer::registerForViewportChanges() 407 { 408 ASSERT(isIntervalDependentOnViewport()); 409 if (auto* frameView = downcast<Document>(*scriptExecutionContext()).view()) 410 frameView->registerThrottledDOMTimer(this); 411 } 412 413 void DOMTimer::unregisterForViewportChanges() 414 { 415 if (auto* frameView = downcast<Document>(*scriptExecutionContext()).view()) 416 frameView->unregisterThrottledDOMTimer(this); 417 418 m_elementsCausingThrottling.clear(); 419 } 420 344 421 void DOMTimer::updateTimerIntervalIfNecessary() 345 422 { … … 349 426 m_currentTimerInterval = intervalClampedToMinimum(); 350 427 351 if (WTF::areEssentiallyEqual(previousInterval, m_currentTimerInterval)) 352 return; 428 if (WTF::areEssentiallyEqual(previousInterval, m_currentTimerInterval, oneMillisecond)) 429 return; 430 431 // Timer was throttled / unthrottled, make sure we register / unregister 432 // from the FrameView if the timer's interval is dependent on viewport. 433 if (isIntervalDependentOnViewport()) 434 registerForViewportChanges(); 435 else if (m_throttleState == ShouldNotThrottle) 436 unregisterForViewportChanges(); 353 437 354 438 if (repeatInterval()) { 355 ASSERT(WTF::areEssentiallyEqual(repeatInterval(), previousInterval)); 439 ASSERT(WTF::areEssentiallyEqual(repeatInterval(), previousInterval, oneMillisecond)); 440 LOG(DOMTimers, "%p - Updating DOMTimer's repeat interval from %g ms to %g ms due to throttling.", this, previousInterval * 1000., m_currentTimerInterval * 1000.); 356 441 augmentRepeatInterval(m_currentTimerInterval - previousInterval); 357 } else 442 } else { 443 LOG(DOMTimers, "%p - Updating DOMTimer's fire interval from %g ms to %g ms due to throttling.", this, previousInterval * 1000., m_currentTimerInterval * 1000.); 358 444 augmentFireInterval(m_currentTimerInterval - previousInterval); 445 } 446 } 447 448 void DOMTimer::updateThrottlingStateAfterViewportChange(const IntRect& visibleRect) 449 { 450 ASSERT(isIntervalDependentOnViewport()); 451 // Check if the elements that caused this timer to be throttled are still outside the viewport. 452 for (auto& element : m_elementsCausingThrottling) { 453 // Skip elements that were removed from the document. 454 if (!element->inDocument()) 455 continue; 456 457 if (element->isInsideViewport(&visibleRect)) { 458 LOG(DOMTimers, "%p - Script is changing style of an element that is now inside the viewport, unthrottling the timer.", this); 459 m_throttleState = ShouldNotThrottle; 460 updateTimerIntervalIfNecessary(); 461 break; 462 } 463 } 359 464 } 360 465 … … 373 478 intervalInSeconds = std::max(intervalInSeconds, scriptExecutionContext()->minimumTimerInterval()); 374 479 if (m_throttleState == ShouldThrottle) 375 intervalInSeconds = std::max(intervalInSeconds, minIntervalForNonUserObservable PluginScriptTimers * oneMillisecond);480 intervalInSeconds = std::max(intervalInSeconds, minIntervalForNonUserObservableChangeTimers * oneMillisecond); 376 481 return intervalInSeconds; 377 482 } -
trunk/Source/WebCore/page/DOMTimer.h
r176065 r176212 1 1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.2 * Copyright (C) 2008, 2014 Apple Inc. All Rights Reserved. 3 3 * 4 4 * Redistribution and use in source and binary forms, with or without … … 30 30 #include "SuspendableTimer.h" 31 31 #include <memory> 32 #include <wtf/HashSet.h> 32 33 #include <wtf/RefCounted.h> 33 34 34 35 namespace WebCore { 35 36 36 structDOMTimerFireState;37 class DOMTimerFireState; 37 38 class HTMLPlugInElement; 39 class IntRect; 38 40 class ScheduledAction; 41 class StyledElement; 39 42 40 43 class DOMTimer final : public RefCounted<DOMTimer>, public SuspendableTimer { … … 42 45 WTF_MAKE_FAST_ALLOCATED; 43 46 public: 47 virtual ~DOMTimer(); 48 44 49 // Creates a new timer owned by specified ScriptExecutionContext, starts it 45 50 // and returns its Id. … … 50 55 // setting for the context has changed). 51 56 void updateTimerIntervalIfNecessary(); 57 void updateThrottlingStateAfterViewportChange(const IntRect& visibleRect); 52 58 53 59 static void scriptDidInteractWithPlugin(HTMLPlugInElement&); 60 static void scriptDidUpdateStyleOfElement(StyledElement&, bool changed); 54 61 55 62 private: 56 63 DOMTimer(ScriptExecutionContext&, std::unique_ptr<ScheduledAction>, int interval, bool singleShot); 57 64 double intervalClampedToMinimum() const; 65 66 bool isIntervalDependentOnViewport() const { return m_throttleState == ShouldThrottle && !m_elementsCausingThrottling.isEmpty(); } 67 void registerForViewportChanges(); 68 void unregisterForViewportChanges(); 69 58 70 void updateThrottlingStateIfNecessary(const DOMTimerFireState&); 59 71 … … 76 88 double m_currentTimerInterval; 77 89 bool m_shouldForwardUserGesture; 90 // Hold a reference to the elements in case they get removed from the 91 // Document after the timer is throttled. 92 Vector<RefPtr<StyledElement>> m_elementsCausingThrottling; 78 93 }; 79 94 -
trunk/Source/WebCore/page/FrameView.cpp
r175719 r176212 4 4 * 1999 Antti Koivisto <koivisto@kde.org> 5 5 * 2000 Dirk Mueller <mueller@kde.org> 6 * Copyright (C) 2004 , 2005, 2006, 2007, 2008, 2013Apple Inc. All rights reserved.6 * Copyright (C) 2004-2008, 2013, 2014 Apple Inc. All rights reserved. 7 7 * (C) 2006 Graham Dennis (graham.dennis@gmail.com) 8 8 * (C) 2006 Alexey Proskuryakov (ap@nypop.com) … … 287 287 m_firstVisuallyNonEmptyLayoutCallbackPending = true; 288 288 m_maintainScrollPositionAnchor = 0; 289 m_throttledTimers.clear(); 289 290 } 290 291 … … 2102 2103 2103 2104 resumeVisibleImageAnimationsIncludingSubframes(); 2105 updateThrottledDOMTimersState(); 2104 2106 } 2105 2107 … … 2918 2920 2919 2921 sendResizeEventIfNeeded(); 2922 2923 // Check if we should unthrottle DOMTimers after layout as the position 2924 // of Elements may have changed. 2925 updateThrottledDOMTimersState(); 2920 2926 } 2921 2927 … … 3003 3009 { 3004 3010 performPostLayoutTasks(); 3011 } 3012 3013 void FrameView::registerThrottledDOMTimer(DOMTimer* timer) 3014 { 3015 m_throttledTimers.add(timer); 3016 } 3017 3018 void FrameView::unregisterThrottledDOMTimer(DOMTimer* timer) 3019 { 3020 m_throttledTimers.remove(timer); 3021 } 3022 3023 void FrameView::updateThrottledDOMTimersState() 3024 { 3025 if (m_throttledTimers.isEmpty()) 3026 return; 3027 3028 IntRect visibleRect = windowToContents(windowClipRect()); 3029 3030 // Do not iterate over the HashSet because calling DOMTimer::updateThrottlingStateAfterViewportChange() 3031 // may cause timers to remove themselves from it while we are iterating. 3032 Vector<DOMTimer*> timers; 3033 copyToVector(m_throttledTimers, timers); 3034 for (auto* timer : timers) 3035 timer->updateThrottlingStateAfterViewportChange(visibleRect); 3005 3036 } 3006 3037 -
trunk/Source/WebCore/page/FrameView.h
r175719 r176212 5 5 (C) 1999 Lars Knoll (knoll@kde.org) 6 6 (C) 1999 Antti Koivisto (koivisto@kde.org) 7 Copyright (C) 2004 , 2005, 2006, 2007, 2008, 2009Apple Inc. All rights reserved.7 Copyright (C) 2004-2009, 2014 Apple Inc. All rights reserved. 8 8 9 9 This library is free software; you can redistribute it and/or … … 36 36 #include <memory> 37 37 #include <wtf/Forward.h> 38 #include <wtf/HashSet.h> 38 39 #include <wtf/ListHashSet.h> 39 40 #include <wtf/text/WTFString.h> … … 42 43 43 44 class AXObjectCache; 45 class DOMTimer; 44 46 class Element; 45 47 class FloatSize; … … 303 305 304 306 void postLayoutTimerFired(Timer&); 307 308 void registerThrottledDOMTimer(DOMTimer*); 309 void unregisterThrottledDOMTimer(DOMTimer*); 305 310 306 311 WEBCORE_EXPORT bool wasScrolledByUser() const; … … 562 567 void performPostLayoutTasks(); 563 568 void autoSizeIfEnabled(); 569 void updateThrottledDOMTimersState(); 564 570 565 571 void updateLayerFlushThrottling(); … … 750 756 std::unique_ptr<ViewportConstrainedObjectSet> m_viewportConstrainedObjects; 751 757 758 HashSet<DOMTimer*> m_throttledTimers; 759 752 760 int m_headerHeight; 753 761 int m_footerHeight; -
trunk/Source/WebCore/platform/Logging.h
r174524 r176212 43 43 M(BackForward) \ 44 44 M(Compositing) \ 45 M(DOMTimers) \ 45 46 M(Editing) \ 46 47 M(Events) \ -
trunk/Source/WebCore/rendering/RenderElement.cpp
r175583 r176212 1334 1334 } 1335 1335 1336 bool RenderElement::isInsideViewport(const IntRect* visibleRect) const 1337 { 1338 auto& frameView = view().frameView(); 1339 if (frameView.isOffscreen()) 1340 return false; 1341 1342 // Compute viewport rect if it was not provided. 1343 const IntRect& viewportRect = visibleRect ? *visibleRect : frameView.windowToContents(frameView.windowClipRect()); 1344 return viewportRect.intersects(enclosingIntRect(absoluteClippedOverflowRect())); 1345 } 1346 1336 1347 static bool shouldRepaintForImageAnimation(const RenderElement& renderer, const IntRect& visibleRect) 1337 1348 { 1338 1349 const Document& document = renderer.document(); 1339 1350 if (document.inPageCache()) 1340 return false;1341 auto& frameView = renderer.view().frameView();1342 if (frameView.isOffscreen())1343 1351 return false; 1344 1352 #if PLATFORM(IOS) … … 1350 1358 if (renderer.style().visibility() != VISIBLE) 1351 1359 return false; 1352 if (! visibleRect.intersects(renderer.absoluteBoundingBoxRect()))1360 if (!renderer.isInsideViewport(&visibleRect)) 1353 1361 return false; 1354 1362 -
trunk/Source/WebCore/rendering/RenderElement.h
r175716 r176212 121 121 122 122 bool borderImageIsLoadedAndCanBeRendered() const; 123 bool isInsideViewport(const IntRect* visibleRect = nullptr) const; 123 124 124 125 // Returns true if this renderer requires a new stacking context.
Note:
See TracChangeset
for help on using the changeset viewer.