computeContentBoxLogicalWidth method

void computeContentBoxLogicalWidth()

Implementation

void computeContentBoxLogicalWidth() {
  // RenderBoxModel current = renderBoxModel!;
  RenderStyle renderStyle = this;
  double? logicalWidth;

  CSSDisplay? effectiveDisplay = renderStyle.effectiveDisplay;

  // Special handling for absolutely/fixed positioned non-replaced elements.
  // Follow CSS abs-non-replaced width algorithm:
  // - If width is auto and both left and right are auto: keep width as auto (shrink-to-fit in layout).
  // - If width is auto and both left and right are not auto: solve width from containing block padding box.
  if ((renderStyle.position == CSSPositionType.absolute || renderStyle.position == CSSPositionType.fixed) &&
      !renderStyle.isSelfRenderReplaced()) {
    if (renderStyle.width.isNotAuto) {
      logicalWidth = renderStyle.width.computedValue;
    } else if (renderStyle.left.isNotAuto && renderStyle.right.isNotAuto) {
      // https://www.w3.org/TR/css-position-3/#abs-non-replaced-width
      if (renderStyle.isParentRenderBoxModel()) {
        RenderStyle parentRenderStyle = renderStyle.getParentRenderStyle()!;
        if (parentRenderStyle.paddingBoxLogicalWidth != null) {
          // Width of positioned element should subtract its horizontal margin.
          logicalWidth = (parentRenderStyle.paddingBoxLogicalWidth!) -
              renderStyle.left.computedValue -
              renderStyle.right.computedValue -
              renderStyle.marginLeft.computedValue -
              renderStyle.marginRight.computedValue;
        }
      } else {
        logicalWidth = null;
      }
    }
  }

  // Width applies to all elements except non-replaced inline elements.
  // https://drafts.csswg.org/css-sizing-3/#propdef-width
  if (effectiveDisplay == CSSDisplay.inline && !renderStyle.isSelfRenderReplaced()) {
    _contentBoxLogicalWidth = null;
    return;
  } else if (effectiveDisplay == CSSDisplay.block ||
      effectiveDisplay == CSSDisplay.flex ||
      effectiveDisplay == CSSDisplay.grid) {
    CSSRenderStyle? parentStyle = renderStyle.getParentRenderStyle();
    if (logicalWidth == null && renderStyle.width.isNotAuto) {
      logicalWidth = renderStyle.width.computedValue;
    } else if (logicalWidth == null && aspectRatio != null && renderStyle.height.isNotAuto) {
      // Prefer aspect-ratio when height is definite and width is auto.
      double contentH = renderStyle.height.computedValue - renderStyle.border.vertical - renderStyle.padding.vertical;
      contentH = math.max(0, contentH);
      final double contentW = contentH * aspectRatio!;
      logicalWidth = contentW + renderStyle.border.horizontal + renderStyle.padding.horizontal;
    } else if (logicalWidth == null && renderStyle.isSelfHTMLElement()) {
      // Avoid defaulting to the viewport width when this element participates
      // in an inline-block shrink-to-fit context. Children of an inline-block
      // with auto width must not assume a definite containing block width; doing so
      // causes percentage widths (e.g., 100%) to immediately resolve to the viewport
      // and force the inline-block to expand to the full line width. Instead, keep
      // width auto here so the child can measure intrinsically and the parent can
      // shrink-wrap to its contents.
      final CSSRenderStyle? p = renderStyle.getParentRenderStyle();
      final bool parentInlineBlockAuto = p != null &&
          p.effectiveDisplay == CSSDisplay.inlineBlock && p.width.isAuto;
      if (!parentInlineBlockAuto) {
        logicalWidth = target.ownerView.viewport!.boxSize!.width;
      }
    } else if (logicalWidth == null && (renderStyle.isSelfRouterLinkElement() && getCurrentViewportBox() is! RootRenderViewportBox)) {
      logicalWidth = getCurrentViewportBox()!.boxSize!.width;
    } else if (logicalWidth == null && parentStyle != null) {
      // Resolve whether the direct parent is a flex item (its render box's parent is a flex container).
      // Determine if our direct parent is a flex item: i.e., the parent's parent is a flex container.
      final bool parentIsFlexItem = parentStyle.isParentRenderFlexLayout();
      // Whether THIS element is a flex item (its own parent is a flex container).
      // When true, width:auto must not be stretched to the parent’s width in the main axis;
      // the flex base size is content-based per CSS Flexbox §9.2.
      final bool thisIsFlexItem = this.isParentRenderFlexLayout();

      // Case A: inside a flex item — stretch block-level auto width to the flex item's measured width.
      // For WebF widget elements (custom elements backed by Flutter widgets), only use the
      // direct constraints exposed via `WebFWidgetElementChild` instead of inferring from
      // the RenderWidget's own content constraints, which can vary by adapter implementation.
      if (parentIsFlexItem && !thisIsFlexItem &&
          !renderStyle.isSelfRenderReplaced() &&
          renderStyle.position != CSSPositionType.absolute &&
          renderStyle.position != CSSPositionType.fixed) {
        if (parentStyle.isSelfRenderWidget()) {
          RenderWidgetElementChild? childWrapper = target.attachedRenderer?.findWidgetElementChild();
          double? maxConstraintWidth;
          try {
            maxConstraintWidth = childWrapper?.constraints.maxWidth;
          } catch (_) {}

          if (childWrapper != null && maxConstraintWidth != null) {
            logicalWidth = maxConstraintWidth;
          }
          // If there is no WebFWidgetElementChild (or no constraints yet),
          // fall through and let the parent (flex) constraints logic handle it.
        } else {
          final RenderBoxModel? parentBox = parentStyle.attachedRenderBoxModel;
          final BoxConstraints? pcc = parentBox?.contentConstraints;
          if (pcc != null && pcc.hasBoundedWidth && pcc.maxWidth.isFinite) {
            logicalWidth = pcc.maxWidth - renderStyle.margin.horizontal;
          }
        }

      // Case B: normal flow (not inside a flex item) — find the nearest non-inline ancestor
      // and adopt its content box logical width or bounded content constraints.
      } else if (!parentIsFlexItem &&
          !renderStyle.isSelfRenderReplaced() &&
          renderStyle.position != CSSPositionType.absolute &&
          renderStyle.position != CSSPositionType.fixed &&
          !renderStyle.isParentRenderFlexLayout()) {
        RenderStyle? ancestorRenderStyle = _findAncestorWithNoDisplayInline();
        // Should ignore renderStyle of display inline when searching for ancestors to stretch width.
        if (ancestorRenderStyle != null) {
          RenderWidgetElementChild? childWrapper = target.attachedRenderer?.findWidgetElementChild();
          double? maxConstraintWidth;
          try {
            maxConstraintWidth = childWrapper?.constraints.maxWidth;
          } catch (_) {}

          if (ancestorRenderStyle.isSelfRenderWidget() && childWrapper != null && maxConstraintWidth != null) {
            logicalWidth = maxConstraintWidth;
          } else {
            logicalWidth = ancestorRenderStyle.contentBoxLogicalWidth;
          }

          // No fallback to unrelated ancestors for flex scenarios here; if ancestor is a
          // flex item but has no bounded width yet, defer stretching (leave null).

          // Should subtract horizontal margin of own from its parent content width.
          if (logicalWidth != null) {
            logicalWidth -= renderStyle.margin.horizontal;
          }
        }
      }
    }
  } else if (effectiveDisplay == CSSDisplay.inlineBlock ||
      effectiveDisplay == CSSDisplay.inlineFlex ||
      effectiveDisplay == CSSDisplay.inlineGrid ||
      effectiveDisplay == CSSDisplay.inline) {
    if (logicalWidth == null && renderStyle.width.isNotAuto) {
      logicalWidth = renderStyle.width.computedValue;
    }
  }

  // Get width by aspect ratio if width is auto.
  if (logicalWidth == null && aspectRatio != null) {
    // If a definite height is specified, prefer converting via the preferred aspect-ratio.
    if (renderStyle.height.isNotAuto) {
      double contentH = renderStyle.height.computedValue - renderStyle.border.vertical - renderStyle.padding.vertical;
      contentH = math.max(0, contentH);
      final double contentW = contentH * aspectRatio!;
      logicalWidth = contentW + renderStyle.border.horizontal + renderStyle.padding.horizontal;
    }
    // Fallback for replaced/intrinsic scenarios.
    logicalWidth ??= renderStyle.getWidthByAspectRatio();
  }

  // Constrain width by min-width and max-width.
  if (renderStyle.minWidth.isNotAuto) {
    double minWidth = renderStyle.minWidth.computedValue;
    if (logicalWidth != null && logicalWidth < minWidth) {
      logicalWidth = minWidth;
    }
  }
  if (renderStyle.maxWidth.isNotNone) {
    double maxWidth = renderStyle.maxWidth.computedValue;
    if (logicalWidth != null && logicalWidth > maxWidth) {
      logicalWidth = maxWidth;
    }
  }

  double? logicalContentWidth;
  // Subtract padding and border width to get content width.
  if (logicalWidth != null) {
    logicalContentWidth = logicalWidth - renderStyle.border.horizontal - renderStyle.padding.horizontal;
    // Logical width may be smaller than its border and padding width,
    // in this case, content width will be negative which is illegal.
    logicalContentWidth = math.max(0, logicalContentWidth);
  }

  _contentBoxLogicalWidth = logicalContentWidth;
}