1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/generic/ScrollbarActivity.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,465 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "ScrollbarActivity.h" 1.10 +#include "nsIScrollbarOwner.h" 1.11 +#include "nsIContent.h" 1.12 +#include "nsIDOMEvent.h" 1.13 +#include "nsIDOMElementCSSInlineStyle.h" 1.14 +#include "nsIDOMCSSStyleDeclaration.h" 1.15 +#include "nsIFrame.h" 1.16 +#include "nsContentUtils.h" 1.17 +#include "nsAString.h" 1.18 +#include "nsQueryFrame.h" 1.19 +#include "nsComponentManagerUtils.h" 1.20 +#include "mozilla/LookAndFeel.h" 1.21 +#include "mozilla/Preferences.h" 1.22 + 1.23 +namespace mozilla { 1.24 +namespace layout { 1.25 + 1.26 +NS_IMPL_ISUPPORTS(ScrollbarActivity, nsIDOMEventListener) 1.27 + 1.28 +static bool 1.29 +GetForceAlwaysVisiblePref() 1.30 +{ 1.31 + static bool sForceAlwaysVisible; 1.32 + static bool sForceAlwaysVisiblePrefCached = false; 1.33 + if (!sForceAlwaysVisiblePrefCached) { 1.34 + Preferences::AddBoolVarCache(&sForceAlwaysVisible, 1.35 + "layout.testing.overlay-scrollbars.always-visible"); 1.36 + sForceAlwaysVisiblePrefCached = true; 1.37 + } 1.38 + return sForceAlwaysVisible; 1.39 +} 1.40 + 1.41 +void 1.42 +ScrollbarActivity::QueryLookAndFeelVals() 1.43 +{ 1.44 + // Fade animation constants 1.45 + mScrollbarFadeBeginDelay = 1.46 + LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarFadeBeginDelay); 1.47 + mScrollbarFadeDuration = 1.48 + LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarFadeDuration); 1.49 + // Controls whether we keep the mouse move listener so we can display the 1.50 + // scrollbars whenever the user moves the mouse within the scroll area. 1.51 + mDisplayOnMouseMove = 1.52 + LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarDisplayOnMouseMove); 1.53 +} 1.54 + 1.55 +void 1.56 +ScrollbarActivity::Destroy() 1.57 +{ 1.58 + StopListeningForScrollbarEvents(); 1.59 + StopListeningForScrollAreaEvents(); 1.60 + UnregisterFromRefreshDriver(); 1.61 + CancelFadeBeginTimer(); 1.62 +} 1.63 + 1.64 +void 1.65 +ScrollbarActivity::ActivityOccurred() 1.66 +{ 1.67 + ActivityStarted(); 1.68 + ActivityStopped(); 1.69 +} 1.70 + 1.71 +void 1.72 +ScrollbarActivity::ActivityStarted() 1.73 +{ 1.74 + mNestedActivityCounter++; 1.75 + CancelFadeBeginTimer(); 1.76 + if (!SetIsFading(false)) { 1.77 + return; 1.78 + } 1.79 + UnregisterFromRefreshDriver(); 1.80 + StartListeningForScrollbarEvents(); 1.81 + StartListeningForScrollAreaEvents(); 1.82 + SetIsActive(true); 1.83 + 1.84 + NS_ASSERTION(mIsActive, "need to be active during activity"); 1.85 + NS_ASSERTION(!mIsFading, "must not be fading during activity"); 1.86 +} 1.87 + 1.88 +void 1.89 +ScrollbarActivity::ActivityStopped() 1.90 +{ 1.91 + NS_ASSERTION(IsActivityOngoing(), "activity stopped while none was going on"); 1.92 + NS_ASSERTION(mIsActive, "need to be active during activity"); 1.93 + NS_ASSERTION(!mIsFading, "must not be fading during ongoing activity"); 1.94 + 1.95 + mNestedActivityCounter--; 1.96 + 1.97 + if (!IsActivityOngoing()) { 1.98 + StartFadeBeginTimer(); 1.99 + 1.100 + NS_ASSERTION(mIsActive, "need to be active right after activity"); 1.101 + NS_ASSERTION(!mIsFading, "must not be fading right after activity"); 1.102 + } 1.103 +} 1.104 + 1.105 +NS_IMETHODIMP 1.106 +ScrollbarActivity::HandleEvent(nsIDOMEvent* aEvent) 1.107 +{ 1.108 + if (!mDisplayOnMouseMove && !mIsActive) 1.109 + return NS_OK; 1.110 + 1.111 + nsAutoString type; 1.112 + aEvent->GetType(type); 1.113 + 1.114 + if (type.EqualsLiteral("mousemove")) { 1.115 + // Mouse motions anywhere in the scrollable frame should keep the 1.116 + // scrollbars visible. 1.117 + ActivityOccurred(); 1.118 + return NS_OK; 1.119 + } 1.120 + 1.121 + nsCOMPtr<nsIDOMEventTarget> target; 1.122 + aEvent->GetOriginalTarget(getter_AddRefs(target)); 1.123 + nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target); 1.124 + 1.125 + HandleEventForScrollbar(type, targetContent, GetHorizontalScrollbar(), 1.126 + &mHScrollbarHovered); 1.127 + HandleEventForScrollbar(type, targetContent, GetVerticalScrollbar(), 1.128 + &mVScrollbarHovered); 1.129 + 1.130 + return NS_OK; 1.131 +} 1.132 + 1.133 +void 1.134 +ScrollbarActivity::WillRefresh(TimeStamp aTime) 1.135 +{ 1.136 + NS_ASSERTION(mIsActive, "should only fade while scrollbars are visible"); 1.137 + NS_ASSERTION(!IsActivityOngoing(), "why weren't we unregistered from the refresh driver when scrollbar activity started?"); 1.138 + NS_ASSERTION(mIsFading, "should only animate fading during fade"); 1.139 + 1.140 + if (!UpdateOpacity(aTime)) { 1.141 + return; 1.142 + } 1.143 + 1.144 + if (!IsStillFading(aTime)) { 1.145 + EndFade(); 1.146 + } 1.147 +} 1.148 + 1.149 +bool 1.150 +ScrollbarActivity::IsStillFading(TimeStamp aTime) 1.151 +{ 1.152 + return !mFadeBeginTime.IsNull() && (aTime - mFadeBeginTime < FadeDuration()); 1.153 +} 1.154 + 1.155 +void 1.156 +ScrollbarActivity::HandleEventForScrollbar(const nsAString& aType, 1.157 + nsIContent* aTarget, 1.158 + nsIContent* aScrollbar, 1.159 + bool* aStoredHoverState) 1.160 +{ 1.161 + if (!aTarget || !aScrollbar || 1.162 + !nsContentUtils::ContentIsDescendantOf(aTarget, aScrollbar)) 1.163 + return; 1.164 + 1.165 + if (aType.EqualsLiteral("mousedown")) { 1.166 + ActivityStarted(); 1.167 + } else if (aType.EqualsLiteral("mouseup")) { 1.168 + ActivityStopped(); 1.169 + } else if (aType.EqualsLiteral("mouseover") || 1.170 + aType.EqualsLiteral("mouseout")) { 1.171 + bool newHoveredState = aType.EqualsLiteral("mouseover"); 1.172 + if (newHoveredState && !*aStoredHoverState) { 1.173 + ActivityStarted(); 1.174 + HoveredScrollbar(aScrollbar); 1.175 + } else if (*aStoredHoverState && !newHoveredState) { 1.176 + ActivityStopped(); 1.177 + // Don't call HoveredScrollbar(nullptr) here because we want the hover 1.178 + // attribute to stick until the scrollbars are hidden. 1.179 + } 1.180 + *aStoredHoverState = newHoveredState; 1.181 + } 1.182 +} 1.183 + 1.184 +void 1.185 +ScrollbarActivity::StartListeningForScrollbarEvents() 1.186 +{ 1.187 + if (mListeningForScrollbarEvents) 1.188 + return; 1.189 + 1.190 + mHorizontalScrollbar = do_QueryInterface(GetHorizontalScrollbar()); 1.191 + mVerticalScrollbar = do_QueryInterface(GetVerticalScrollbar()); 1.192 + 1.193 + AddScrollbarEventListeners(mHorizontalScrollbar); 1.194 + AddScrollbarEventListeners(mVerticalScrollbar); 1.195 + 1.196 + mListeningForScrollbarEvents = true; 1.197 +} 1.198 + 1.199 +void 1.200 +ScrollbarActivity::StopListeningForScrollbarEvents() 1.201 +{ 1.202 + if (!mListeningForScrollbarEvents) 1.203 + return; 1.204 + 1.205 + RemoveScrollbarEventListeners(mHorizontalScrollbar); 1.206 + RemoveScrollbarEventListeners(mVerticalScrollbar); 1.207 + 1.208 + mHorizontalScrollbar = nullptr; 1.209 + mVerticalScrollbar = nullptr; 1.210 + mListeningForScrollbarEvents = false; 1.211 +} 1.212 + 1.213 +void 1.214 +ScrollbarActivity::StartListeningForScrollAreaEvents() 1.215 +{ 1.216 + if (mListeningForScrollAreaEvents) 1.217 + return; 1.218 + 1.219 + nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame); 1.220 + nsCOMPtr<nsIDOMEventTarget> scrollAreaTarget 1.221 + = do_QueryInterface(scrollArea->GetContent()); 1.222 + if (scrollAreaTarget) { 1.223 + scrollAreaTarget->AddEventListener(NS_LITERAL_STRING("mousemove"), this, 1.224 + true); 1.225 + } 1.226 + mListeningForScrollAreaEvents = true; 1.227 +} 1.228 + 1.229 +void 1.230 +ScrollbarActivity::StopListeningForScrollAreaEvents() 1.231 +{ 1.232 + if (!mListeningForScrollAreaEvents) 1.233 + return; 1.234 + 1.235 + nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame); 1.236 + nsCOMPtr<nsIDOMEventTarget> scrollAreaTarget = do_QueryInterface(scrollArea->GetContent()); 1.237 + if (scrollAreaTarget) { 1.238 + scrollAreaTarget->RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, true); 1.239 + } 1.240 + mListeningForScrollAreaEvents = false; 1.241 +} 1.242 + 1.243 +void 1.244 +ScrollbarActivity::AddScrollbarEventListeners(nsIDOMEventTarget* aScrollbar) 1.245 +{ 1.246 + if (aScrollbar) { 1.247 + aScrollbar->AddEventListener(NS_LITERAL_STRING("mousedown"), this, true); 1.248 + aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseup"), this, true); 1.249 + aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseover"), this, true); 1.250 + aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseout"), this, true); 1.251 + } 1.252 +} 1.253 + 1.254 +void 1.255 +ScrollbarActivity::RemoveScrollbarEventListeners(nsIDOMEventTarget* aScrollbar) 1.256 +{ 1.257 + if (aScrollbar) { 1.258 + aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true); 1.259 + aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, true); 1.260 + aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseover"), this, true); 1.261 + aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, true); 1.262 + } 1.263 +} 1.264 + 1.265 +void 1.266 +ScrollbarActivity::BeginFade() 1.267 +{ 1.268 + NS_ASSERTION(mIsActive, "can't begin fade when we're already inactive"); 1.269 + NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade begin timer cancelled when scrollbar activity started?"); 1.270 + NS_ASSERTION(!mIsFading, "shouldn't be fading just yet"); 1.271 + 1.272 + CancelFadeBeginTimer(); 1.273 + mFadeBeginTime = TimeStamp::Now(); 1.274 + if (!SetIsFading(true)) { 1.275 + return; 1.276 + } 1.277 + RegisterWithRefreshDriver(); 1.278 + 1.279 + NS_ASSERTION(mIsActive, "only fade while scrollbars are visible"); 1.280 + NS_ASSERTION(mIsFading, "should be fading now"); 1.281 +} 1.282 + 1.283 +void 1.284 +ScrollbarActivity::EndFade() 1.285 +{ 1.286 + NS_ASSERTION(mIsActive, "still need to be active at this point"); 1.287 + NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade end timer cancelled when scrollbar activity started?"); 1.288 + 1.289 + if (!SetIsFading(false)) { 1.290 + return; 1.291 + } 1.292 + SetIsActive(false); 1.293 + UnregisterFromRefreshDriver(); 1.294 + StopListeningForScrollbarEvents(); 1.295 + if (!mDisplayOnMouseMove) { 1.296 + StopListeningForScrollAreaEvents(); 1.297 + } 1.298 + 1.299 + NS_ASSERTION(!mIsActive, "should have gone inactive after fade end"); 1.300 + NS_ASSERTION(!mIsFading, "shouldn't be fading anymore"); 1.301 +} 1.302 + 1.303 +void 1.304 +ScrollbarActivity::RegisterWithRefreshDriver() 1.305 +{ 1.306 + nsRefreshDriver* refreshDriver = GetRefreshDriver(); 1.307 + if (refreshDriver) { 1.308 + refreshDriver->AddRefreshObserver(this, Flush_Style); 1.309 + } 1.310 +} 1.311 + 1.312 +void 1.313 +ScrollbarActivity::UnregisterFromRefreshDriver() 1.314 +{ 1.315 + nsRefreshDriver* refreshDriver = GetRefreshDriver(); 1.316 + if (refreshDriver) { 1.317 + refreshDriver->RemoveRefreshObserver(this, Flush_Style); 1.318 + } 1.319 +} 1.320 + 1.321 +static void 1.322 +SetBooleanAttribute(nsIContent* aContent, nsIAtom* aAttribute, bool aValue) 1.323 +{ 1.324 + if (aContent) { 1.325 + if (aValue) { 1.326 + aContent->SetAttr(kNameSpaceID_None, aAttribute, 1.327 + NS_LITERAL_STRING("true"), true); 1.328 + } else { 1.329 + aContent->UnsetAttr(kNameSpaceID_None, aAttribute, true); 1.330 + } 1.331 + } 1.332 +} 1.333 + 1.334 +void 1.335 +ScrollbarActivity::SetIsActive(bool aNewActive) 1.336 +{ 1.337 + if (mIsActive == aNewActive) 1.338 + return; 1.339 + 1.340 + mIsActive = aNewActive; 1.341 + if (!mIsActive) { 1.342 + // Clear sticky scrollbar hover status. 1.343 + HoveredScrollbar(nullptr); 1.344 + } 1.345 + 1.346 + SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, mIsActive); 1.347 + SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, mIsActive); 1.348 +} 1.349 + 1.350 +static void 1.351 +SetOpacityOnElement(nsIContent* aContent, double aOpacity) 1.352 +{ 1.353 + nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyleContent = 1.354 + do_QueryInterface(aContent); 1.355 + if (inlineStyleContent) { 1.356 + nsCOMPtr<nsIDOMCSSStyleDeclaration> decl; 1.357 + inlineStyleContent->GetStyle(getter_AddRefs(decl)); 1.358 + if (decl) { 1.359 + nsAutoString str; 1.360 + str.AppendFloat(aOpacity); 1.361 + decl->SetProperty(NS_LITERAL_STRING("opacity"), str, EmptyString()); 1.362 + } 1.363 + } 1.364 +} 1.365 + 1.366 +bool 1.367 +ScrollbarActivity::UpdateOpacity(TimeStamp aTime) 1.368 +{ 1.369 + double progress = (aTime - mFadeBeginTime) / FadeDuration(); 1.370 + double opacity = 1.0 - std::max(0.0, std::min(1.0, progress)); 1.371 + 1.372 + // 'this' may be getting destroyed during SetOpacityOnElement calls. 1.373 + nsWeakFrame weakFrame((do_QueryFrame(mScrollableFrame))); 1.374 + SetOpacityOnElement(GetHorizontalScrollbar(), opacity); 1.375 + if (!weakFrame.IsAlive()) { 1.376 + return false; 1.377 + } 1.378 + SetOpacityOnElement(GetVerticalScrollbar(), opacity); 1.379 + if (!weakFrame.IsAlive()) { 1.380 + return false; 1.381 + } 1.382 + return true; 1.383 +} 1.384 + 1.385 +static void 1.386 +UnsetOpacityOnElement(nsIContent* aContent) 1.387 +{ 1.388 + nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyleContent = 1.389 + do_QueryInterface(aContent); 1.390 + if (inlineStyleContent) { 1.391 + nsCOMPtr<nsIDOMCSSStyleDeclaration> decl; 1.392 + inlineStyleContent->GetStyle(getter_AddRefs(decl)); 1.393 + if (decl) { 1.394 + nsAutoString dummy; 1.395 + decl->RemoveProperty(NS_LITERAL_STRING("opacity"), dummy); 1.396 + } 1.397 + } 1.398 +} 1.399 + 1.400 +bool 1.401 +ScrollbarActivity::SetIsFading(bool aNewFading) 1.402 +{ 1.403 + if (mIsFading == aNewFading) 1.404 + return true; 1.405 + 1.406 + mIsFading = aNewFading; 1.407 + if (!mIsFading) { 1.408 + mFadeBeginTime = TimeStamp(); 1.409 + // 'this' may be getting destroyed during UnsetOpacityOnElement calls. 1.410 + nsWeakFrame weakFrame((do_QueryFrame(mScrollableFrame))); 1.411 + UnsetOpacityOnElement(GetHorizontalScrollbar()); 1.412 + if (!weakFrame.IsAlive()) { 1.413 + return false; 1.414 + } 1.415 + UnsetOpacityOnElement(GetVerticalScrollbar()); 1.416 + if (!weakFrame.IsAlive()) { 1.417 + return false; 1.418 + } 1.419 + } 1.420 + return true; 1.421 +} 1.422 + 1.423 +void 1.424 +ScrollbarActivity::StartFadeBeginTimer() 1.425 +{ 1.426 + if (GetForceAlwaysVisiblePref()) { 1.427 + return; 1.428 + } 1.429 + if (!mFadeBeginTimer) { 1.430 + mFadeBeginTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.431 + } 1.432 + mFadeBeginTimer->InitWithFuncCallback(FadeBeginTimerFired, this, 1.433 + mScrollbarFadeBeginDelay, 1.434 + nsITimer::TYPE_ONE_SHOT); 1.435 +} 1.436 + 1.437 +void 1.438 +ScrollbarActivity::CancelFadeBeginTimer() 1.439 +{ 1.440 + if (mFadeBeginTimer) { 1.441 + mFadeBeginTimer->Cancel(); 1.442 + } 1.443 +} 1.444 + 1.445 +void 1.446 +ScrollbarActivity::HoveredScrollbar(nsIContent* aScrollbar) 1.447 +{ 1.448 + SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::hover, false); 1.449 + SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::hover, false); 1.450 + SetBooleanAttribute(aScrollbar, nsGkAtoms::hover, true); 1.451 +} 1.452 + 1.453 +nsRefreshDriver* 1.454 +ScrollbarActivity::GetRefreshDriver() 1.455 +{ 1.456 + nsIFrame* scrollableFrame = do_QueryFrame(mScrollableFrame); 1.457 + return scrollableFrame->PresContext()->RefreshDriver(); 1.458 +} 1.459 + 1.460 +nsIContent* 1.461 +ScrollbarActivity::GetScrollbarContent(bool aVertical) 1.462 +{ 1.463 + nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical); 1.464 + return box ? box->GetContent() : nullptr; 1.465 +} 1.466 + 1.467 +} // namespace layout 1.468 +} // namespace mozilla