Bug 957668. Add and implement plumbing for a display port margins api. r=kats
authorTimothy Nikkel <tnikkel@gmail.com>
Wed, 26 Mar 2014 21:46:23 -0400
changeset 191342 156c60792f9730045fa7d4b90e4ec1afb41ac80c
parent 191341 80984c5e9b3a54b2dc5b8c28df888dba14efa075
child 191343 b730b23743d3636ddee87e81765726347f24ed42
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs957668
milestone30.0a2
Bug 957668. Add and implement plumbing for a display port margins api. r=kats
content/base/src/nsGkAtomList.h
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -2017,16 +2017,18 @@ GK_ATOM(el, "el")
 GK_ATOM(TypingTxnName, "Typing")
 GK_ATOM(IMETxnName, "IME")
 GK_ATOM(DeleteTxnName, "Deleting")
 
 // IPC stuff
 GK_ATOM(Remote, "remote")
 GK_ATOM(RemoteId, "_remote_id")
 GK_ATOM(DisplayPort, "_displayport")
+GK_ATOM(DisplayPortMargins, "_displayportmargins")
+GK_ATOM(DisplayPortBase, "_displayportbase")
 GK_ATOM(CriticalDisplayPort, "_critical_displayport")
 
 // Names for system metrics
 GK_ATOM(color_picker_available, "color-picker-available")
 GK_ATOM(scrollbar_start_backward, "scrollbar-start-backward")
 GK_ATOM(scrollbar_start_forward, "scrollbar-start-forward")
 GK_ATOM(scrollbar_end_backward, "scrollbar-end-backward")
 GK_ATOM(scrollbar_end_forward, "scrollbar-end-forward")
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -308,16 +308,24 @@ nsDOMWindowUtils::GetViewportInfo(uint32
 static void DestroyDisplayPortPropertyData(void* aObject, nsIAtom* aPropertyName,
                                            void* aPropertyValue, void* aData)
 {
   DisplayPortPropertyData* data =
     static_cast<DisplayPortPropertyData*>(aPropertyValue);
   delete data;
 }
 
+static void DestroyDisplayPortMarginsPropertyData(void* aObject, nsIAtom* aPropertyName,
+                                                  void* aPropertyValue, void* aData)
+{
+  DisplayPortMarginsPropertyData* data =
+    static_cast<DisplayPortMarginsPropertyData*>(aPropertyValue);
+  delete data;
+}
+
 static void DestroyNsRect(void* aObject, nsIAtom* aPropertyName,
                           void* aPropertyValue, void* aData)
 {
   nsRect* rect = static_cast<nsRect*>(aPropertyValue);
   delete rect;
 }
 
 static void
@@ -446,16 +454,119 @@ nsDOMWindowUtils::SetDisplayPortForEleme
       }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::SetDisplayPortMarginsForElement(float aLeftMargin,
+                                                  float aTopMargin,
+                                                  float aRightMargin,
+                                                  float aBottomMargin,
+                                                  uint32_t aAlignment,
+                                                  nsIDOMElement* aElement,
+                                                  uint32_t aPriority)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsIPresShell* presShell = GetPresShell();
+  if (!presShell) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Note order change of arguments between our function signature and
+  // LayerMargin constructor.
+  LayerMargin displayportMargins(aTopMargin,
+                                 aRightMargin,
+                                 aBottomMargin,
+                                 aLeftMargin);
+
+  if (!aElement) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+
+  if (!content) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (content->GetCurrentDoc() != presShell->GetDocument()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  DisplayPortMarginsPropertyData* currentData =
+    static_cast<DisplayPortMarginsPropertyData*>(content->GetProperty(nsGkAtoms::DisplayPortMargins));
+  if (currentData && currentData->mPriority > aPriority) {
+    return NS_OK;
+  }
+
+  content->SetProperty(nsGkAtoms::DisplayPortMargins,
+                       new DisplayPortMarginsPropertyData(displayportMargins, aAlignment, aPriority),
+                       DestroyDisplayPortMarginsPropertyData);
+
+  nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+  if (rootScrollFrame) {
+    if (content == rootScrollFrame->GetContent()) {
+      // We are setting a root displayport for a document.
+      // The pres shell needs a special flag set.
+      presShell->SetIgnoreViewportScrolling(true);
+    }
+  }
+
+  nsIFrame* rootFrame = presShell->FrameManager()->GetRootFrame();
+  if (rootFrame) {
+    rootFrame->SchedulePaint();
+  }
+
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetDisplayPortBaseForElement(int32_t aX,
+                                               int32_t aY,
+                                               int32_t aWidth,
+                                               int32_t aHeight,
+                                               nsIDOMElement* aElement)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsIPresShell* presShell = GetPresShell();
+  if (!presShell) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!aElement) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+
+  if (!content) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (content->GetCurrentDoc() != presShell->GetDocument()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  content->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aX, aY, aWidth, aHeight),
+                       DestroyNsRect);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::SetCriticalDisplayPortForElement(float aXPx, float aYPx,
                                                    float aWidthPx, float aHeightPx,
                                                    nsIDOMElement* aElement)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -38,17 +38,17 @@ interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsICompositionStringSynthesizer;
 
-[scriptable, uuid(ef70a299-033c-4adc-b214-6649aed9d828)]
+[scriptable, uuid(f3148b3e-6db8-4a49-aa5c-de726449054d)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -147,16 +147,48 @@ interface nsIDOMWindowUtils : nsISupport
    * aPriority is recorded along with the displayport rectangle. If this
    * method is called with a lower priority than the current priority, the
    * call is ignored.
    */
   void setDisplayPortForElement(in float aXPx, in float aYPx,
                                 in float aWidthPx, in float aHeightPx,
                                 in nsIDOMElement aElement,
                                 in uint32_t aPriority);
+  /**
+   * An alternate way to represent a displayport rect as a set of margins and a
+   * base rect to apply those margins to. A consumer of pixels may ask for as
+   * many extra pixels as it would like in each direction. Layout then sets
+   * the base rect to the "visible rect" of the element, which is just the
+   * subrect of the element that is drawn (it does not take in account content
+   * covering the element).
+   *
+   * If both a displayport rect and displayport margins with corresponding base
+   * rect are set with the same priority then the margins will take precendence.
+   *
+   * Specifying an alignment value will ensure that after the base rect has
+   * been expanded by the displayport margins, it will be further expanded so
+   * that each edge is located at a multiple of the "alignment" value.
+   *
+   * Note that both the margin values and alignment are treated as values in
+   * LayerPixels. Refer to layout/base/Units.h for a description of this unit.
+   * The base rect values are in app units.
+   */
+  void setDisplayPortMarginsForElement(in float aLeftMargin,
+                                       in float aTopMargin,
+                                       in float aRightMargin,
+                                       in float aBottomMargin,
+                                       in uint32_t aAlignment,
+                                       in nsIDOMElement aElement,
+                                       in uint32_t aPriority);
+
+  void setDisplayPortBaseForElement(in int32_t aX,
+                                    in int32_t aY,
+                                    in int32_t aWidth,
+                                    in int32_t aHeight,
+                                    in nsIDOMElement aElement);
 
   /**
    * When a display port is set, this allows a sub-section of that
    * display port to be marked as 'critical'. In this scenario, the
    * area outside of this rectangle may be rendered at a lower
    * detail (for example, by reducing its resolution), or not rendered
    * at all under some circumstances.
    * This call will have no effect if a display port has not been set.
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -651,24 +651,120 @@ nsLayoutUtils::FindScrollableFrameFor(Vi
     scrolledFrame = scrolledFrame->PresContext()->PresShell()->GetRootScrollFrame();
   }
   return scrolledFrame ? scrolledFrame->GetScrollTargetFrame() : nullptr;
 }
 
 bool
 nsLayoutUtils::GetDisplayPort(nsIContent* aContent, nsRect *aResult)
 {
-  void* property = aContent->GetProperty(nsGkAtoms::DisplayPort);
-  if (!property) {
+  DisplayPortPropertyData* rectData =
+    static_cast<DisplayPortPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPort));
+  DisplayPortMarginsPropertyData* marginsData =
+    static_cast<DisplayPortMarginsPropertyData*>(aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
+  if (!rectData && !marginsData) {
     return false;
   }
 
   if (aResult) {
-    *aResult = (static_cast<DisplayPortPropertyData*>(property))->mRect;
-  }
+    if (rectData && marginsData) {
+      // choose margins if equal priority
+      if (rectData->mPriority > marginsData->mPriority) {
+        marginsData = nullptr;
+      } else {
+        rectData = nullptr;
+      }
+    }
+
+    if (rectData) {
+      *aResult = rectData->mRect;
+    } else {
+      nsRect* baseData =
+        static_cast<nsRect*>(aContent->GetProperty(nsGkAtoms::DisplayPortBase));
+      nsRect base;
+      if (baseData) {
+        base = *baseData;
+      }
+
+      nsIFrame* frame = aContent->GetPrimaryFrame();
+      if (frame) {
+        bool isRoot = false;
+        if (aContent->OwnerDoc()->GetRootElement() == aContent) {
+          // We want the scroll frame, the root scroll frame differs from all
+          // others in that the primary frame is not the scroll frame.
+          frame = frame->PresContext()->PresShell()->GetRootScrollFrame();
+          isRoot = true;
+        }
+
+        // first convert the base rect to layer pixels
+        nsPresContext* presContext = frame->PresContext();
+        int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
+        gfxSize res = presContext->PresShell()->GetCumulativeResolution();
+        gfxSize parentRes = res;
+        if (isRoot) {
+          // the base rect for root scroll frames is specified in the parent document
+          // coordinate space, so it doesn't include the local resolution.
+          gfxSize localRes = presContext->PresShell()->GetResolution();
+          parentRes.width /= localRes.width;
+          parentRes.height /= localRes.height;
+        }
+        LayerRect rect;
+        rect.x = parentRes.width * NSAppUnitsToFloatPixels(base.x, auPerDevPixel);
+        rect.y = parentRes.height * NSAppUnitsToFloatPixels(base.y, auPerDevPixel);
+        rect.width =
+          parentRes.width * NSAppUnitsToFloatPixels(base.width, auPerDevPixel);
+        rect.height =
+          parentRes.height * NSAppUnitsToFloatPixels(base.height, auPerDevPixel);
+
+        rect.Inflate(marginsData->mMargins);
+
+        nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame();
+        nsPoint scrollPos(
+          scrollableFrame ? scrollableFrame->GetScrollPosition() : nsPoint(0,0));
+        if (marginsData->mAlignment > 0) {
+          LayerPoint scrollPosLayer(
+            res.width * NSAppUnitsToFloatPixels(scrollPos.x, auPerDevPixel),
+            res.height * NSAppUnitsToFloatPixels(scrollPos.y, auPerDevPixel));
+          rect += scrollPosLayer;
+
+          // Inflate the rectangle by 1 so that we always push to the next tile
+          // boundary. This is desirable to stop from having a rectangle with a
+          // moving origin occasionally being smaller when it coincidentally lines
+          // up to tile boundaries.
+          rect.Inflate(1);
+
+          float left =
+            marginsData->mAlignment * floor(rect.x / marginsData->mAlignment);
+          float top =
+            marginsData->mAlignment * floor(rect.y / marginsData->mAlignment);
+          float right =
+            marginsData->mAlignment * ceil(rect.XMost() / marginsData->mAlignment);
+          float bottom =
+            marginsData->mAlignment * ceil(rect.YMost() / marginsData->mAlignment);
+          rect = LayerRect(left, top, right - left, bottom - top);
+          rect -= scrollPosLayer;
+        }
+
+        nsRect result;
+        result.x = NSFloatPixelsToAppUnits(rect.x / res.width, auPerDevPixel);
+        result.y = NSFloatPixelsToAppUnits(rect.y / res.height, auPerDevPixel);
+        result.width =
+          NSFloatPixelsToAppUnits(rect.width / res.width, auPerDevPixel);
+        result.height =
+          NSFloatPixelsToAppUnits(rect.height / res.height, auPerDevPixel);
+
+        // Finally, clamp the display port to the expanded scrollable rect.
+        nsRect expandedScrollableRect = CalculateExpandedScrollableRect(frame);
+        result = expandedScrollableRect.Intersect(result + scrollPos) - scrollPos;
+
+        *aResult = result;
+      }
+    }
+  }
+
   return true;
 }
 
 bool
 nsLayoutUtils::GetCriticalDisplayPort(nsIContent* aContent, nsRect* aResult)
 {
   void* property = aContent->GetProperty(nsGkAtoms::CriticalDisplayPort);
   if (!property) {
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -78,16 +78,28 @@ struct DisplayPortPropertyData {
   DisplayPortPropertyData(const nsRect& aRect, uint32_t aPriority)
     : mRect(aRect)
     , mPriority(aPriority)
   {}
   nsRect mRect;
   uint32_t mPriority;
 };
 
+struct DisplayPortMarginsPropertyData {
+  DisplayPortMarginsPropertyData(const LayerMargin& aMargins,
+                                 uint32_t aAlignment, uint32_t aPriority)
+    : mMargins(aMargins)
+    , mAlignment(aAlignment)
+    , mPriority(aPriority)
+  {}
+  LayerMargin mMargins;
+  uint32_t mAlignment;
+  uint32_t mPriority;
+};
+
 template <class AnimationsOrTransitions>
 extern AnimationsOrTransitions* HasAnimationOrTransition(nsIContent* aContent,
                                                          nsIAtom* aAnimationProperty,
                                                          nsCSSProperty aProperty);
 
 } // namespace mozilla
 
 /**