gfx/layers/apz/util/APZCCallbackHelper.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "APZCCallbackHelper.h"
     7 #include "gfxPrefs.h" // For gfxPrefs::LayersTilesEnabled, LayersTileWidth/Height
     8 #include "mozilla/Preferences.h"
     9 #include "nsIScrollableFrame.h"
    10 #include "nsLayoutUtils.h"
    11 #include "nsIDOMElement.h"
    12 #include "nsIInterfaceRequestorUtils.h"
    14 namespace mozilla {
    15 namespace layers {
    17 bool
    18 APZCCallbackHelper::HasValidPresShellId(nsIDOMWindowUtils* aUtils,
    19                                         const FrameMetrics& aMetrics)
    20 {
    21     MOZ_ASSERT(aUtils);
    23     uint32_t presShellId;
    24     nsresult rv = aUtils->GetPresShellId(&presShellId);
    25     MOZ_ASSERT(NS_SUCCEEDED(rv));
    26     return NS_SUCCEEDED(rv) && aMetrics.mPresShellId == presShellId;
    27 }
    29 /**
    30  * Expands a given rectangle to the next tile boundary. Note, this will
    31  * expand the rectangle if it is already on tile boundaries.
    32  */
    33 static CSSRect ExpandDisplayPortToTileBoundaries(
    34   const CSSRect& aDisplayPort,
    35   const CSSToLayerScale& aLayerPixelsPerCSSPixel)
    36 {
    37   // Convert the given rect to layer coordinates so we can inflate to tile
    38   // boundaries (layer space corresponds to texture pixel space here).
    39   LayerRect displayPortInLayerSpace = aDisplayPort * aLayerPixelsPerCSSPixel;
    41   // Inflate the rectangle by 1 so that we always push to the next tile
    42   // boundary. This is desirable to stop from having a rectangle with a
    43   // moving origin occasionally being smaller when it coincidentally lines
    44   // up to tile boundaries.
    45   displayPortInLayerSpace.Inflate(1);
    47   // Now nudge the rectangle to the nearest equal or larger tile boundary.
    48   int32_t tileWidth = gfxPrefs::LayersTileWidth();
    49   int32_t tileHeight = gfxPrefs::LayersTileHeight();
    50   gfxFloat left = tileWidth * floor(displayPortInLayerSpace.x / tileWidth);
    51   gfxFloat right = tileWidth * ceil(displayPortInLayerSpace.XMost() / tileWidth);
    52   gfxFloat top = tileHeight * floor(displayPortInLayerSpace.y / tileHeight);
    53   gfxFloat bottom = tileHeight * ceil(displayPortInLayerSpace.YMost() / tileHeight);
    55   displayPortInLayerSpace = LayerRect(left, top, right - left, bottom - top);
    56   CSSRect displayPort = displayPortInLayerSpace / aLayerPixelsPerCSSPixel;
    58   return displayPort;
    59 }
    61 static void
    62 MaybeAlignAndClampDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics,
    63                               const CSSPoint& aActualScrollOffset)
    64 {
    65   // Correct the display-port by the difference between the requested scroll
    66   // offset and the resulting scroll offset after setting the requested value.
    67   if (!aFrameMetrics.GetUseDisplayPortMargins()) {
    68       CSSRect& displayPort = aFrameMetrics.mDisplayPort;
    69       displayPort += aFrameMetrics.GetScrollOffset() - aActualScrollOffset;
    71       // Expand the display port to the next tile boundaries, if tiled thebes layers
    72       // are enabled.
    73       if (gfxPrefs::LayersTilesEnabled()) {
    74         // We don't use LayersPixelsPerCSSPixel() here as mCumulativeResolution on
    75         // this FrameMetrics may be incorrect (and is about to be reset by mZoom).
    76         displayPort =
    77           ExpandDisplayPortToTileBoundaries(displayPort + aActualScrollOffset,
    78                                             aFrameMetrics.GetZoom() *
    79                                             ScreenToLayerScale(1.0))
    80           - aActualScrollOffset;
    81       }
    83       // Finally, clamp the display port to the expanded scrollable rect.
    84       CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect();
    85       displayPort = scrollableRect.Intersect(displayPort + aActualScrollOffset)
    86         - aActualScrollOffset;
    87   } else {
    88       LayerPoint shift =
    89           (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
    90           aFrameMetrics.LayersPixelsPerCSSPixel();
    91       LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
    92       margins.left -= shift.x;
    93       margins.right += shift.x;
    94       margins.top -= shift.y;
    95       margins.bottom += shift.y;
    96       aFrameMetrics.SetDisplayPortMargins(margins);
    97   }
    98 }
   100 static void
   101 RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
   102 {
   103     if (!aFrameMetrics.GetUseDisplayPortMargins()) {
   104         CSSSize compositionSize = aFrameMetrics.CalculateCompositedSizeInCssPixels();
   105         aFrameMetrics.mDisplayPort.x = (compositionSize.width - aFrameMetrics.mDisplayPort.width) / 2;
   106         aFrameMetrics.mDisplayPort.y = (compositionSize.height - aFrameMetrics.mDisplayPort.height) / 2;
   107     } else {
   108         LayerMargin margins = aFrameMetrics.GetDisplayPortMargins();
   109         margins.right = margins.left = margins.LeftRight() / 2;
   110         margins.top = margins.bottom = margins.TopBottom() / 2;
   111         aFrameMetrics.SetDisplayPortMargins(margins);
   112     }
   113 }
   115 static CSSPoint
   116 ScrollFrameTo(nsIScrollableFrame* aFrame, const CSSPoint& aPoint, bool& aSuccessOut)
   117 {
   118   aSuccessOut = false;
   120   if (!aFrame) {
   121     return aPoint;
   122   }
   124   CSSPoint targetScrollPosition = aPoint;
   126   // If the frame is overflow:hidden on a particular axis, we don't want to allow
   127   // user-driven scroll on that axis. Simply set the scroll position on that axis
   128   // to whatever it already is. Note that this will leave the APZ's async scroll
   129   // position out of sync with the gecko scroll position, but APZ can deal with that
   130   // (by design). Note also that when we run into this case, even if both axes
   131   // have overflow:hidden, we want to set aSuccessOut to true, so that the displayport
   132   // follows the async scroll position rather than the gecko scroll position.
   133   CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
   134   if (aFrame->GetScrollbarStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
   135     targetScrollPosition.y = geckoScrollPosition.y;
   136   }
   137   if (aFrame->GetScrollbarStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
   138     targetScrollPosition.x = geckoScrollPosition.x;
   139   }
   141   // If the scrollable frame is currently in the middle of an async or smooth
   142   // scroll then we don't want to interrupt it (see bug 961280).
   143   // Also if the scrollable frame got a scroll request from something other than us
   144   // since the last layers update, then we don't want to push our scroll request
   145   // because we'll clobber that one, which is bad.
   146   if (!aFrame->IsProcessingAsyncScroll() &&
   147      (!aFrame->OriginOfLastScroll() || aFrame->OriginOfLastScroll() == nsGkAtoms::apz)) {
   148     aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz);
   149     geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
   150     aSuccessOut = true;
   151   }
   152   // Return the final scroll position after setting it so that anything that relies
   153   // on it can have an accurate value. Note that even if we set it above re-querying it
   154   // is a good idea because it may have gotten clamped or rounded.
   155   return geckoScrollPosition;
   156 }
   158 void
   159 APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils,
   160                                     FrameMetrics& aMetrics)
   161 {
   162     // Precondition checks
   163     MOZ_ASSERT(aUtils);
   164     if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
   165         return;
   166     }
   168     // Set the scroll port size, which determines the scroll range. For example if
   169     // a 500-pixel document is shown in a 100-pixel frame, the scroll port length would
   170     // be 100, and gecko would limit the maximum scroll offset to 400 (so as to prevent
   171     // overscroll). Note that if the content here was zoomed to 2x, the document would
   172     // be 1000 pixels long but the frame would still be 100 pixels, and so the maximum
   173     // scroll range would be 900. Therefore this calculation depends on the zoom applied
   174     // to the content relative to the container.
   175     CSSSize scrollPort = aMetrics.CalculateCompositedSizeInCssPixels();
   176     aUtils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height);
   178     // Scroll the window to the desired spot
   179     nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
   180     bool scrollUpdated = false;
   181     CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated);
   183     if (!scrollUpdated) {
   184       // For whatever reason we couldn't update the scroll offset on the scroll frame,
   185       // which means the data APZ used for its displayport calculation is stale. Fall
   186       // back to a sane default behaviour. Note that we don't tile-align the recentered
   187       // displayport because tile-alignment depends on the scroll position, and the
   188       // scroll position here is out of our control. See bug 966507 comment 21 for a
   189       // more detailed explanation.
   190       RecenterDisplayPort(aMetrics);
   191     }
   193     // Correct the display port due to the difference between mScrollOffset and the
   194     // actual scroll offset, possibly align it to tile boundaries (if tiled layers are
   195     // enabled), and clamp it to the scrollable rect.
   196     MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset);
   198     aMetrics.SetScrollOffset(actualScrollOffset);
   200     // The mZoom variable on the frame metrics stores the CSS-to-screen scale for this
   201     // frame. This scale includes all of the (cumulative) resolutions set on the presShells
   202     // from the root down to this frame. However, when setting the resolution, we only
   203     // want the piece of the resolution that corresponds to this presShell, rather than
   204     // all of the cumulative stuff, so we need to divide out the parent resolutions.
   205     // Finally, we multiply by a ScreenToLayerScale of 1.0f because the goal here is to
   206     // take the async zoom calculated by the APZC and tell gecko about it (turning it into
   207     // a "sync" zoom) which will update the resolution at which the layer is painted.
   208     ParentLayerToLayerScale presShellResolution =
   209         aMetrics.GetZoom()
   210         / aMetrics.mDevPixelsPerCSSPixel
   211         / aMetrics.GetParentResolution()
   212         * ScreenToLayerScale(1.0f);
   213     aUtils->SetResolution(presShellResolution.scale, presShellResolution.scale);
   215     // Finally, we set the displayport.
   216     nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
   217     if (!content) {
   218         return;
   219     }
   220     nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content);
   221     if (!element) {
   222         return;
   223     }
   224     if (!aMetrics.GetUseDisplayPortMargins()) {
   225         aUtils->SetDisplayPortForElement(aMetrics.mDisplayPort.x,
   226                                          aMetrics.mDisplayPort.y,
   227                                          aMetrics.mDisplayPort.width,
   228                                          aMetrics.mDisplayPort.height,
   229                                          element, 0);
   230     } else {
   231         gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
   232             ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
   233               gfx::IntSize(0, 0);
   234         LayerMargin margins = aMetrics.GetDisplayPortMargins();
   235         aUtils->SetDisplayPortMarginsForElement(margins.left,
   236                                                 margins.top,
   237                                                 margins.right,
   238                                                 margins.bottom,
   239                                                 alignment.width,
   240                                                 alignment.height,
   241                                                 element, 0);
   242         CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
   243         nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
   244                     baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
   245                     baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
   246                     baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
   247         nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, base);
   248     }
   249 }
   251 void
   252 APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent,
   253                                    FrameMetrics& aMetrics)
   254 {
   255     // Precondition checks
   256     MOZ_ASSERT(aContent);
   257     if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
   258         return;
   259     }
   261     nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent);
   262     if (!utils) {
   263         return;
   264     }
   266     // We currently do not support zooming arbitrary subframes. They can only
   267     // be scrolled, so here we only have to set the scroll position and displayport.
   269     nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
   270     bool scrollUpdated = false;
   271     CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics.GetScrollOffset(), scrollUpdated);
   273     nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent);
   274     if (element) {
   275         if (!scrollUpdated) {
   276             RecenterDisplayPort(aMetrics);
   277         }
   278         MaybeAlignAndClampDisplayPort(aMetrics, actualScrollOffset);
   279         if (!aMetrics.GetUseDisplayPortMargins()) {
   280             utils->SetDisplayPortForElement(aMetrics.mDisplayPort.x,
   281                                             aMetrics.mDisplayPort.y,
   282                                             aMetrics.mDisplayPort.width,
   283                                             aMetrics.mDisplayPort.height,
   284                                             element, 0);
   285         } else {
   286             gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled()
   287                 ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) :
   288                   gfx::IntSize(0, 0);
   289             LayerMargin margins = aMetrics.GetDisplayPortMargins();
   290             utils->SetDisplayPortMarginsForElement(margins.left,
   291                                                    margins.top,
   292                                                    margins.right,
   293                                                    margins.bottom,
   294                                                    alignment.width,
   295                                                    alignment.height,
   296                                                    element, 0);
   297             CSSRect baseCSS = aMetrics.mCompositionBounds / aMetrics.GetZoomToParent();
   298             nsRect base(baseCSS.x * nsPresContext::AppUnitsPerCSSPixel(),
   299                         baseCSS.y * nsPresContext::AppUnitsPerCSSPixel(),
   300                         baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
   301                         baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
   302             nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
   303         }
   304     }
   306     aMetrics.SetScrollOffset(actualScrollOffset);
   307 }
   309 already_AddRefed<nsIDOMWindowUtils>
   310 APZCCallbackHelper::GetDOMWindowUtils(const nsIDocument* aDoc)
   311 {
   312     nsCOMPtr<nsIDOMWindowUtils> utils;
   313     nsCOMPtr<nsIDOMWindow> window = aDoc->GetDefaultView();
   314     if (window) {
   315         utils = do_GetInterface(window);
   316     }
   317     return utils.forget();
   318 }
   320 already_AddRefed<nsIDOMWindowUtils>
   321 APZCCallbackHelper::GetDOMWindowUtils(const nsIContent* aContent)
   322 {
   323     nsCOMPtr<nsIDOMWindowUtils> utils;
   324     nsIDocument* doc = aContent->GetCurrentDoc();
   325     if (doc) {
   326         utils = GetDOMWindowUtils(doc);
   327     }
   328     return utils.forget();
   329 }
   331 bool
   332 APZCCallbackHelper::GetScrollIdentifiers(const nsIContent* aContent,
   333                                          uint32_t* aPresShellIdOut,
   334                                          FrameMetrics::ViewID* aViewIdOut)
   335 {
   336     if (!aContent || !nsLayoutUtils::FindIDFor(aContent, aViewIdOut)) {
   337         return false;
   338     }
   339     nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent);
   340     return utils && (utils->GetPresShellId(aPresShellIdOut) == NS_OK);
   341 }
   343 class AcknowledgeScrollUpdateEvent : public nsRunnable
   344 {
   345     typedef mozilla::layers::FrameMetrics::ViewID ViewID;
   347 public:
   348     AcknowledgeScrollUpdateEvent(const ViewID& aScrollId, const uint32_t& aScrollGeneration)
   349         : mScrollId(aScrollId)
   350         , mScrollGeneration(aScrollGeneration)
   351     {
   352     }
   354     NS_IMETHOD Run() {
   355         MOZ_ASSERT(NS_IsMainThread());
   357         nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(mScrollId);
   358         if (sf) {
   359             sf->ResetOriginIfScrollAtGeneration(mScrollGeneration);
   360         }
   362         return NS_OK;
   363     }
   365 protected:
   366     ViewID mScrollId;
   367     uint32_t mScrollGeneration;
   368 };
   370 void
   371 APZCCallbackHelper::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
   372                                             const uint32_t& aScrollGeneration)
   373 {
   374     nsCOMPtr<nsIRunnable> r1 = new AcknowledgeScrollUpdateEvent(aScrollId, aScrollGeneration);
   375     if (!NS_IsMainThread()) {
   376         NS_DispatchToMainThread(r1);
   377     } else {
   378         r1->Run();
   379     }
   380 }
   382 void
   383 APZCCallbackHelper::UpdateCallbackTransform(const FrameMetrics& aApzcMetrics, const FrameMetrics& aActualMetrics)
   384 {
   385     nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aApzcMetrics.GetScrollId());
   386     if (!content) {
   387         return;
   388     }
   389     CSSPoint scrollDelta = aApzcMetrics.GetScrollOffset() - aActualMetrics.GetScrollOffset();
   390     content->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta),
   391                          nsINode::DeleteProperty<CSSPoint>);
   392 }
   394 CSSPoint
   395 APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput, const ScrollableLayerGuid& aGuid)
   396 {
   397     // XXX: technically we need to walk all the way up the layer tree from the layer
   398     // represented by |aGuid.mScrollId| up to the root of the layer tree and apply
   399     // the input transforms at each level in turn. However, it is quite difficult
   400     // to do this given that the structure of the layer tree may be different from
   401     // the structure of the content tree. Also it may be impossible to do correctly
   402     // at this point because there are other CSS transforms and such interleaved in
   403     // between so applying the inputTransforms all in a row at the end may leave
   404     // some things transformed improperly. In practice we should rarely hit scenarios
   405     // where any of this matters, so I'm skipping it for now and just doing the single
   406     // transform for the layer that the input hit.
   408     if (aGuid.mScrollId != FrameMetrics::NULL_SCROLL_ID) {
   409         nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
   410         if (content) {
   411             void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
   412             if (property) {
   413                 CSSPoint delta = (*static_cast<CSSPoint*>(property));
   414                 return aInput + delta;
   415             }
   416         }
   417     }
   418     return aInput;
   419 }
   421 nsIntPoint
   422 APZCCallbackHelper::ApplyCallbackTransform(const nsIntPoint& aPoint,
   423                                         const ScrollableLayerGuid& aGuid,
   424                                         const CSSToLayoutDeviceScale& aScale)
   425 {
   426     LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
   427     point = ApplyCallbackTransform(point / aScale, aGuid) * aScale;
   428     LayoutDeviceIntPoint ret = gfx::RoundedToInt(point);
   429     return nsIntPoint(ret.x, ret.y);
   430 }
   432 }
   433 }

mercurial