tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GeometryUtils.cpp (17801B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "GeometryUtils.h"
      8 
      9 #include "mozilla/PresShell.h"
     10 #include "mozilla/SVGUtils.h"
     11 #include "mozilla/dom/BrowserChild.h"
     12 #include "mozilla/dom/CharacterData.h"
     13 #include "mozilla/dom/DOMPoint.h"
     14 #include "mozilla/dom/DOMPointBinding.h"
     15 #include "mozilla/dom/DOMQuad.h"
     16 #include "mozilla/dom/DOMRect.h"
     17 #include "mozilla/dom/DocumentInlines.h"
     18 #include "mozilla/dom/Element.h"
     19 #include "mozilla/dom/GeometryUtilsBinding.h"
     20 #include "mozilla/dom/Text.h"
     21 #include "nsCSSFrameConstructor.h"
     22 #include "nsContainerFrame.h"
     23 #include "nsContentUtils.h"
     24 #include "nsIFrame.h"
     25 #include "nsLayoutUtils.h"
     26 
     27 using namespace mozilla;
     28 using namespace mozilla::dom;
     29 
     30 namespace mozilla {
     31 
     32 enum GeometryNodeType {
     33  GEOMETRY_NODE_ELEMENT,
     34  GEOMETRY_NODE_TEXT,
     35  GEOMETRY_NODE_DOCUMENT
     36 };
     37 
     38 static nsIFrame* GetFrameForNode(nsINode* aNode, GeometryNodeType aType,
     39                                 const GeometryUtilsOptions& aOptions) {
     40  RefPtr<Document> doc = aNode->GetComposedDoc();
     41  if (!doc) {
     42    return nullptr;
     43  }
     44 
     45  if (aOptions.mFlush) {
     46    if (aType == GEOMETRY_NODE_TEXT &&
     47        aOptions.mCreateFramesForSuppressedWhitespace) {
     48      if (PresShell* presShell = doc->GetPresShell()) {
     49        presShell->FrameConstructor()
     50            ->EnsureFrameForTextNodeIsCreatedAfterFlush(
     51                static_cast<CharacterData*>(aNode));
     52      }
     53    }
     54    doc->FlushPendingNotifications(FlushType::Layout);
     55  }
     56 
     57  switch (aType) {
     58    case GEOMETRY_NODE_TEXT:
     59    case GEOMETRY_NODE_ELEMENT:
     60      return aNode->AsContent()->GetPrimaryFrame();
     61    case GEOMETRY_NODE_DOCUMENT: {
     62      PresShell* presShell = doc->GetPresShell();
     63      return presShell ? presShell->GetRootFrame() : nullptr;
     64    }
     65    default:
     66      MOZ_ASSERT(false, "Unknown GeometryNodeType");
     67      return nullptr;
     68  }
     69 }
     70 
     71 static nsIFrame* GetFrameForGeometryNode(
     72    const Optional<OwningGeometryNode>& aGeometryNode, nsINode* aDefaultNode,
     73    const GeometryUtilsOptions& aOptions) {
     74  if (!aGeometryNode.WasPassed()) {
     75    return GetFrameForNode(aDefaultNode->OwnerDoc(), GEOMETRY_NODE_DOCUMENT,
     76                           aOptions);
     77  }
     78 
     79  const OwningGeometryNode& value = aGeometryNode.Value();
     80  if (value.IsElement()) {
     81    return GetFrameForNode(value.GetAsElement(), GEOMETRY_NODE_ELEMENT,
     82                           aOptions);
     83  }
     84  if (value.IsDocument()) {
     85    return GetFrameForNode(value.GetAsDocument(), GEOMETRY_NODE_DOCUMENT,
     86                           aOptions);
     87  }
     88  return GetFrameForNode(value.GetAsText(), GEOMETRY_NODE_TEXT, aOptions);
     89 }
     90 
     91 static nsIFrame* GetFrameForGeometryNode(const GeometryNode& aGeometryNode,
     92                                         const GeometryUtilsOptions& aOptions) {
     93  if (aGeometryNode.IsElement()) {
     94    return GetFrameForNode(&aGeometryNode.GetAsElement(), GEOMETRY_NODE_ELEMENT,
     95                           aOptions);
     96  }
     97  if (aGeometryNode.IsDocument()) {
     98    return GetFrameForNode(&aGeometryNode.GetAsDocument(),
     99                           GEOMETRY_NODE_DOCUMENT, aOptions);
    100  }
    101  return GetFrameForNode(&aGeometryNode.GetAsText(), GEOMETRY_NODE_TEXT,
    102                         aOptions);
    103 }
    104 
    105 static nsIFrame* GetFrameForNode(nsINode* aNode,
    106                                 const GeometryUtilsOptions& aOptions) {
    107  if (aNode->IsElement()) {
    108    return GetFrameForNode(aNode, GEOMETRY_NODE_ELEMENT, aOptions);
    109  }
    110  if (aNode == aNode->OwnerDoc()) {
    111    return GetFrameForNode(aNode, GEOMETRY_NODE_DOCUMENT, aOptions);
    112  }
    113  NS_ASSERTION(aNode->IsText(), "Unknown node type");
    114  return GetFrameForNode(aNode, GEOMETRY_NODE_TEXT, aOptions);
    115 }
    116 
    117 static nsIFrame* GetFirstNonAnonymousFrameForGeometryNode(
    118    const Optional<OwningGeometryNode>& aNode, nsINode* aDefaultNode,
    119    const GeometryUtilsOptions& aOptions) {
    120  if (nsIFrame* f = GetFrameForGeometryNode(aNode, aDefaultNode, aOptions)) {
    121    return nsLayoutUtils::GetFirstNonAnonymousFrame(f);
    122  }
    123  return nullptr;
    124 }
    125 
    126 static nsIFrame* GetFirstNonAnonymousFrameForGeometryNode(
    127    const GeometryNode& aNode, const GeometryUtilsOptions& aOptions) {
    128  if (nsIFrame* f = GetFrameForGeometryNode(aNode, aOptions)) {
    129    return nsLayoutUtils::GetFirstNonAnonymousFrame(f);
    130  }
    131  return nullptr;
    132 }
    133 
    134 static nsIFrame* GetFirstNonAnonymousFrameForNode(
    135    nsINode* aNode, const GeometryUtilsOptions& aOptions) {
    136  if (nsIFrame* f = GetFrameForNode(aNode, aOptions)) {
    137    return nsLayoutUtils::GetFirstNonAnonymousFrame(f);
    138  }
    139  return nullptr;
    140 }
    141 
    142 /**
    143 * This can modify aFrame to point to a different frame. This is needed to
    144 * handle SVG, where SVG elements can only compute a rect that's valid with
    145 * respect to the "outer SVG" frame.
    146 */
    147 static nsRect GetBoxRectForFrame(nsIFrame** aFrame, CSSBoxType aType) {
    148  nsRect r;
    149  nsIFrame* f = SVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r);
    150  if (f && f != *aFrame) {
    151    // For non-outer SVG frames, the BoxType is ignored.
    152    *aFrame = f;
    153    return r;
    154  }
    155 
    156  f = *aFrame;
    157  switch (aType) {
    158    case CSSBoxType::Content:
    159      r = f->GetContentRectRelativeToSelf();
    160      break;
    161    case CSSBoxType::Padding:
    162      r = f->GetPaddingRectRelativeToSelf();
    163      break;
    164    case CSSBoxType::Border:
    165      r = nsRect(nsPoint(0, 0), f->GetSize());
    166      break;
    167    case CSSBoxType::Margin:
    168      r = f->GetMarginRectRelativeToSelf();
    169      break;
    170    default:
    171      MOZ_ASSERT(false, "unknown box type");
    172      return r;
    173  }
    174 
    175  return r;
    176 }
    177 
    178 class MOZ_STACK_CLASS AccumulateQuadCallback final
    179    : public nsLayoutUtils::BoxCallback {
    180 public:
    181  AccumulateQuadCallback(Document* aParentObject,
    182                         nsTArray<RefPtr<DOMQuad>>& aResult,
    183                         nsIFrame* aRelativeToFrame,
    184                         const nsPoint& aRelativeToBoxTopLeft,
    185                         const BoxQuadOptions& aOptions)
    186      : mParentObject(ToSupports(aParentObject)),
    187        mResult(aResult),
    188        mRelativeToFrame(aRelativeToFrame),
    189        mRelativeToBoxTopLeft(aRelativeToBoxTopLeft),
    190        mOptions(aOptions) {
    191    if (mOptions.mBox == CSSBoxType::Margin) {
    192      // Don't include the caption margin when computing margins for a
    193      // table
    194      mIncludeCaptionBoxForTable = false;
    195    }
    196  }
    197 
    198  void AddBox(nsIFrame* aFrame) override {
    199    nsIFrame* f = aFrame;
    200    if (mOptions.mBox == CSSBoxType::Margin && f->IsTableFrame()) {
    201      // Margin boxes for table frames should be taken from the table wrapper
    202      // frame, since that has the margin.
    203      f = f->GetParent();
    204    }
    205    const nsRect box = GetBoxRectForFrame(&f, mOptions.mBox);
    206    CSSPoint points[4] = {CSSPoint::FromAppUnits(box.TopLeft()),
    207                          CSSPoint::FromAppUnits(box.TopRight()),
    208                          CSSPoint::FromAppUnits(box.BottomRight()),
    209                          CSSPoint::FromAppUnits(box.BottomLeft())};
    210    const auto delta = [&]() -> Maybe<CSSPoint> {
    211      if (mOptions.mIgnoreTransforms) {
    212        return Some(CSSPoint::FromAppUnits(f->GetOffsetTo(mRelativeToFrame) -
    213                                           mRelativeToBoxTopLeft));
    214      }
    215      nsLayoutUtils::TransformResult rv = nsLayoutUtils::TransformPoints(
    216          RelativeTo{f}, RelativeTo{mRelativeToFrame}, 4, points);
    217      if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
    218        return Nothing();
    219      }
    220      return Some(CSSPoint::FromAppUnits(-mRelativeToBoxTopLeft));
    221    }();
    222    if (delta) {
    223      for (auto& point : points) {
    224        point += *delta;
    225      }
    226    } else {
    227      PodArrayZero(points);
    228    }
    229    mResult.AppendElement(new DOMQuad(mParentObject, points));
    230  }
    231 
    232  nsISupports* const mParentObject;
    233  nsTArray<RefPtr<DOMQuad>>& mResult;
    234  nsIFrame* const mRelativeToFrame;
    235  const nsPoint mRelativeToBoxTopLeft;
    236  const BoxQuadOptions& mOptions;
    237 };
    238 
    239 static nsPresContext* FindTopLevelPresContext(nsPresContext* aPC) {
    240  bool isChrome = aPC->IsChrome();
    241  nsPresContext* pc = aPC;
    242  for (;;) {
    243    nsPresContext* parent = pc->GetParentPresContext();
    244    if (!parent || parent->IsChrome() != isChrome) {
    245      return pc;
    246    }
    247    pc = parent;
    248  }
    249 }
    250 
    251 static bool CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1,
    252                                                     nsIFrame* aFrame2,
    253                                                     CallerType aCallerType) {
    254  nsPresContext* pc1 = aFrame1->PresContext();
    255  nsPresContext* pc2 = aFrame2->PresContext();
    256  if (pc1 == pc2) {
    257    return true;
    258  }
    259  if (aCallerType == CallerType::System) {
    260    return true;
    261  }
    262  if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) {
    263    return true;
    264  }
    265  return false;
    266 }
    267 
    268 void GetBoxQuads(nsINode* aNode, const dom::BoxQuadOptions& aOptions,
    269                 nsTArray<RefPtr<DOMQuad>>& aResult, CallerType aCallerType,
    270                 ErrorResult& aRv) {
    271  nsIFrame* frame = GetFrameForNode(aNode, aOptions);
    272  if (!frame) {
    273    // No boxes to return
    274    return;
    275  }
    276  AutoWeakFrame weakFrame(frame);
    277  Document* ownerDoc = aNode->OwnerDoc();
    278  nsIFrame* relativeToFrame = GetFirstNonAnonymousFrameForGeometryNode(
    279      aOptions.mRelativeTo, ownerDoc, aOptions);
    280  // The first frame might be destroyed now if the above call lead to an
    281  // EnsureFrameForTextNode call.  We need to get the first frame again
    282  // when that happens and re-check it.
    283  if (!weakFrame.IsAlive()) {
    284    frame = GetFrameForNode(aNode, aOptions);
    285    if (!frame) {
    286      // No boxes to return
    287      return;
    288    }
    289  }
    290  if (!relativeToFrame) {
    291    // XXXbz There's no spec for this.
    292    return aRv.ThrowNotFoundError("No box to get quads relative to");
    293  }
    294  if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame,
    295                                                aCallerType)) {
    296    aRv.ThrowNotFoundError(
    297        "Can't get quads relative to a box in a different toplevel browsing "
    298        "context");
    299    return;
    300  }
    301  // GetBoxRectForFrame can modify relativeToFrame so call it first.
    302  nsPoint relativeToTopLeft =
    303      GetBoxRectForFrame(&relativeToFrame, CSSBoxType::Border).TopLeft();
    304  AccumulateQuadCallback callback(ownerDoc, aResult, relativeToFrame,
    305                                  relativeToTopLeft, aOptions);
    306 
    307  // Bug 1624653: Refactor this to get boxes in layer pixels, which we will
    308  // then convert into CSS units.
    309  nsLayoutUtils::GetAllInFlowBoxes(frame, &callback);
    310 }
    311 
    312 void GetBoxQuadsFromWindowOrigin(nsINode* aNode,
    313                                 const dom::BoxQuadOptions& aOptions,
    314                                 nsTArray<RefPtr<DOMQuad>>& aResult,
    315                                 ErrorResult& aRv) {
    316  // We want the quads relative to the window. To do this, we ignore the
    317  // provided aOptions.mRelativeTo and instead use the document node of
    318  // the top-most in-process document. Later, we'll check if there is a
    319  // browserChild associated with that document, and if so, transform the
    320  // calculated quads with the browserChild's to-parent matrix, which
    321  // will get us to top-level coordinates.
    322  if (aOptions.mRelativeTo.WasPassed()) {
    323    return aRv.ThrowNotSupportedError(
    324        "Can't request quads in window origin space relative to another "
    325        "node.");
    326  }
    327 
    328  // We're going to call GetBoxQuads with our parameters, but we supply
    329  // a new BoxQuadOptions object that uses the top in-process document
    330  // as the relativeTo target.
    331  BoxQuadOptions bqo(aOptions);
    332 
    333  RefPtr<Document> topInProcessDoc =
    334      nsContentUtils::GetInProcessSubtreeRootDocument(aNode->OwnerDoc());
    335 
    336  OwningGeometryNode ogn;
    337  ogn.SetAsDocument() = topInProcessDoc;
    338  bqo.mRelativeTo.Construct(ogn);
    339 
    340  // Bug 1624653: Refactor this to get boxes in layer pixels, which we can
    341  // transform directly with the GetChildToParentConversionMatrix below,
    342  // and convert to CSS units as a final step.
    343  GetBoxQuads(aNode, bqo, aResult, CallerType::System, aRv);
    344  if (aRv.Failed()) {
    345    return;
    346  }
    347 
    348  // Now we have aResult filled with DOMQuads with values relative to the
    349  // top in-process document. See if topInProcessDoc is associated with a
    350  // BrowserChild, and if it is, get its transformation matrix and use that
    351  // to transform the DOMQuads in place to make them relative to the window
    352  // origin.
    353  nsIDocShell* docShell = topInProcessDoc->GetDocShell();
    354  if (!docShell) {
    355    return aRv.ThrowInvalidStateError(
    356        "Returning untranslated quads because top in process document has "
    357        "no docshell.");
    358  }
    359 
    360  BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
    361  if (!browserChild) {
    362    return;
    363  }
    364 
    365  nsPresContext* presContext = docShell->GetPresContext();
    366  if (!presContext) {
    367    return;
    368  }
    369  int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
    370 
    371  LayoutDeviceToLayoutDeviceMatrix4x4 matrix =
    372      browserChild->GetChildToParentConversionMatrix();
    373 
    374  // For each DOMQuad in aResult, change the css units into layer pixels,
    375  // then transform them by matrix, then change them back into css units
    376  // and overwrite the original points.
    377  LayoutDeviceToCSSScale ld2cScale((float)appUnitsPerDevPixel /
    378                                   (float)AppUnitsPerCSSPixel());
    379  CSSToLayoutDeviceScale c2ldScale = ld2cScale.Inverse();
    380 
    381  for (auto& quad : aResult) {
    382    for (uint32_t i = 0; i < 4; i++) {
    383      DOMPoint* p = quad->Point(i);
    384      CSSPoint cp(p->X(), p->Y());
    385 
    386      LayoutDevicePoint windowLdp = matrix.TransformPoint(cp * c2ldScale);
    387 
    388      CSSPoint windowCp = windowLdp * ld2cScale;
    389      p->SetX(windowCp.x);
    390      p->SetY(windowCp.y);
    391    }
    392  }
    393 }
    394 
    395 static void TransformPoints(nsINode* aTo, const GeometryNode& aFrom,
    396                            uint32_t aPointCount, CSSPoint* aPoints,
    397                            const ConvertCoordinateOptions& aOptions,
    398                            CallerType aCallerType, ErrorResult& aRv) {
    399  nsIFrame* fromFrame =
    400      GetFirstNonAnonymousFrameForGeometryNode(aFrom, aOptions);
    401  AutoWeakFrame weakFrame(fromFrame);
    402  nsIFrame* toFrame = GetFirstNonAnonymousFrameForNode(aTo, aOptions);
    403  // The first frame might be destroyed now if the above call lead to an
    404  // EnsureFrameForTextNode call.  We need to get the first frame again
    405  // when that happens.
    406  if (fromFrame && !weakFrame.IsAlive()) {
    407    fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom, aOptions);
    408  }
    409  if (!fromFrame || !toFrame) {
    410    aRv.ThrowNotFoundError(
    411        "Can't transform coordinates between nonexistent boxes");
    412    return;
    413  }
    414  if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame,
    415                                                aCallerType)) {
    416    aRv.ThrowNotFoundError(
    417        "Can't transform coordinates between boxes in different toplevel "
    418        "browsing contexts");
    419    return;
    420  }
    421 
    422  nsPoint fromOffset =
    423      GetBoxRectForFrame(&fromFrame, aOptions.mFromBox).TopLeft();
    424  nsPoint toOffset = GetBoxRectForFrame(&toFrame, aOptions.mToBox).TopLeft();
    425  CSSPoint fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.x),
    426                         nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.y));
    427  for (uint32_t i = 0; i < aPointCount; ++i) {
    428    aPoints[i] += fromOffsetGfx;
    429  }
    430  nsLayoutUtils::TransformResult rv = nsLayoutUtils::TransformPoints(
    431      RelativeTo{fromFrame}, RelativeTo{toFrame}, aPointCount, aPoints);
    432  if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
    433    CSSPoint toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset.x),
    434                         nsPresContext::AppUnitsToFloatCSSPixels(toOffset.y));
    435    for (uint32_t i = 0; i < aPointCount; ++i) {
    436      aPoints[i] -= toOffsetGfx;
    437    }
    438  } else {
    439    PodZero(aPoints, aPointCount);
    440  }
    441 }
    442 
    443 already_AddRefed<DOMQuad> ConvertQuadFromNode(
    444    nsINode* aTo, dom::DOMQuad& aQuad, const GeometryNode& aFrom,
    445    const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
    446    ErrorResult& aRv) {
    447  CSSPoint points[4];
    448  for (uint32_t i = 0; i < 4; ++i) {
    449    DOMPoint* p = aQuad.Point(i);
    450    if (p->W() != 1.0 || p->Z() != 0.0) {
    451      aRv.ThrowInvalidStateError("Point is not 2d");
    452      return nullptr;
    453    }
    454    points[i] = CSSPoint(p->X(), p->Y());
    455  }
    456  TransformPoints(aTo, aFrom, 4, points, aOptions, aCallerType, aRv);
    457  if (aRv.Failed()) {
    458    return nullptr;
    459  }
    460  return MakeAndAddRef<DOMQuad>(aTo->GetParentObject().mObject, points);
    461 }
    462 
    463 already_AddRefed<DOMQuad> ConvertRectFromNode(
    464    nsINode* aTo, dom::DOMRectReadOnly& aRect, const GeometryNode& aFrom,
    465    const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
    466    ErrorResult& aRv) {
    467  CSSPoint points[4];
    468  double x = aRect.X(), y = aRect.Y(), w = aRect.Width(), h = aRect.Height();
    469  points[0] = CSSPoint(x, y);
    470  points[1] = CSSPoint(x + w, y);
    471  points[2] = CSSPoint(x + w, y + h);
    472  points[3] = CSSPoint(x, y + h);
    473  TransformPoints(aTo, aFrom, 4, points, aOptions, aCallerType, aRv);
    474  if (aRv.Failed()) {
    475    return nullptr;
    476  }
    477  return MakeAndAddRef<DOMQuad>(aTo->GetParentObject().mObject, points);
    478 }
    479 
    480 already_AddRefed<DOMPoint> ConvertPointFromNode(
    481    nsINode* aTo, const dom::DOMPointInit& aPoint, const GeometryNode& aFrom,
    482    const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
    483    ErrorResult& aRv) {
    484  if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) {
    485    aRv.ThrowInvalidStateError("Point is not 2d");
    486    return nullptr;
    487  }
    488  CSSPoint point(aPoint.mX, aPoint.mY);
    489  TransformPoints(aTo, aFrom, 1, &point, aOptions, aCallerType, aRv);
    490  if (aRv.Failed()) {
    491    return nullptr;
    492  }
    493  return MakeAndAddRef<DOMPoint>(aTo->GetParentObject().mObject, point.x,
    494                                 point.y);
    495 }
    496 
    497 }  // namespace mozilla