/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ResizeObserver.h" #include "mozilla/dom/DOMRect.h" #include "mozilla/dom/Document.h" #include "mozilla/SVGUtils.h" #include "nsIContent.h" #include "nsIContentInlines.h" #include "nsIScrollableFrame.h" #include "nsLayoutUtils.h" #include namespace mozilla::dom { /** * Returns the length of the parent-traversal path (in terms of the number of * nodes) to an unparented/root node from aNode. An unparented/root node is * considered to have a depth of 1, its children have a depth of 2, etc. * aNode is expected to be non-null. * Note: The shadow root is not part of the calculation because the caller, * ResizeObserver, doesn't observe the shadow root, and only needs relative * depths among all the observed targets. In other words, we calculate the * depth of the flattened tree. * * However, these is a spec issue about how to handle shadow DOM case. We * may need to update this function later: * https://github.com/w3c/csswg-drafts/issues/3840 * * https://drafts.csswg.org/resize-observer/#calculate-depth-for-node-h */ static uint32_t GetNodeDepth(nsINode* aNode) { uint32_t depth = 1; MOZ_ASSERT(aNode, "Node shouldn't be null"); // Use GetFlattenedTreeParentNode to bypass the shadow root and cross the // shadow boundary to calculate the node depth without the shadow root. while ((aNode = aNode->GetFlattenedTreeParentNode())) { ++depth; } return depth; } static nsSize GetContentRectSize(const nsIFrame& aFrame) { if (const nsIScrollableFrame* f = do_QueryFrame(&aFrame)) { // We return the scrollport rect for compat with other UAs, see bug 1733042. // But the scrollPort includes padding (but not border!), so remove it. nsRect scrollPort = f->GetScrollPortRect(); nsMargin padding = aFrame.GetUsedPadding().ApplySkipSides(aFrame.GetSkipSides()); scrollPort.Deflate(padding); // This can break in some edge cases like when layout overflows sizes or // what not. NS_ASSERTION( !aFrame.PresContext()->UseOverlayScrollbars() || scrollPort.Size() == aFrame.GetContentRectRelativeToSelf().Size(), "Wrong scrollport?"); return scrollPort.Size(); } return aFrame.GetContentRectRelativeToSelf().Size(); } /** * Returns |aTarget|'s size in the form of gfx::Size (in pixels). * If the target is an SVG that does not participate in CSS layout, * its width and height are determined from bounding box. * * https://www.w3.org/TR/resize-observer-1/#calculate-box-size */ static AutoTArray CalculateBoxSize( Element* aTarget, ResizeObserverBoxOptions aBox, const ResizeObserver& aObserver) { nsIFrame* frame = aTarget->GetPrimaryFrame(); if (!frame) { // TODO: Should this return an empty array instead? // https://github.com/w3c/csswg-drafts/issues/7734 return {LogicalPixelSize()}; } if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { // Per the spec, this target's SVG size is always its bounding box size no // matter what box option you choose, because SVG elements do not use // standard CSS box model. // TODO: what if the SVG is fragmented? // https://github.com/w3c/csswg-drafts/issues/7736 const gfxRect bbox = SVGUtils::GetBBox(frame); gfx::Size size(static_cast(bbox.width), static_cast(bbox.height)); const WritingMode wm = frame->GetWritingMode(); if (aBox == ResizeObserverBoxOptions::Device_pixel_content_box) { // Per spec, we calculate the inline/block sizes to target’s bounding box // {inline|block} length, in integral device pixels, so we round the final // result. // https://drafts.csswg.org/resize-observer/#dom-resizeobserverboxoptions-device-pixel-content-box const LayoutDeviceIntSize snappedSize = RoundedToInt(CSSSize::FromUnknownSize(size) * frame->PresContext()->CSSToDevPixelScale()); return {LogicalPixelSize(wm, gfx::Size(snappedSize.ToUnknownSize()))}; } return {LogicalPixelSize(wm, size)}; } // Per the spec, non-replaced inline Elements will always have an empty // content rect. Therefore, we always use the same trivially-empty size // for non-replaced inline elements here, and their IsActive() will // always return false. (So its observation won't be fired.) // TODO: Should we use an empty array instead? // https://github.com/w3c/csswg-drafts/issues/7734 if (!frame->IsFrameOfType(nsIFrame::eReplaced) && frame->IsFrameOfType(nsIFrame::eLineParticipant)) { return {LogicalPixelSize()}; } auto GetFrameSize = [aBox](nsIFrame* aFrame) { switch (aBox) { case ResizeObserverBoxOptions::Border_box: return CSSPixel::FromAppUnits(aFrame->GetSize()).ToUnknownSize(); case ResizeObserverBoxOptions::Device_pixel_content_box: { // Simply converting from app units to device units is insufficient - we // need to take subpixel snapping into account. Subpixel snapping // happens with respect to the reference frame, so do the dev pixel // conversion with our rectangle positioned relative to the reference // frame, then get the size from there. const auto* referenceFrame = nsLayoutUtils::GetReferenceFrame(aFrame); // GetOffsetToCrossDoc version handles