Bug 866102 - Implement -webkit-line-clamp. r=mats,emilio,dholbert
authorCameron McCormack <cam@mcc.id.au>
Thu, 09 May 2019 02:32:30 +0000
changeset 535048 c2250a23fd66e3c812cb023c2d12f6f5e8d5a2f2
parent 535047 d94d1e2c6f436c33725709bf93c99f6930fd8e99
child 535049 9d0b46d9bb9ac851d04c0cce35e00d247ec77a6b
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats, emilio, dholbert
bugs866102
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 866102 - Implement -webkit-line-clamp. r=mats,emilio,dholbert Differential Revision: https://phabricator.services.mozilla.com/D20115
devtools/server/actors/animation-type-longhand.js
devtools/shared/css/generated/properties-db.js
layout/generic/BlockReflowInput.cpp
layout/generic/BlockReflowInput.h
layout/generic/ReflowInput.cpp
layout/generic/ReflowInput.h
layout/generic/TextOverflow.cpp
layout/generic/TextOverflow.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsBlockFrame.h
layout/generic/nsFlexContainerFrame.cpp
layout/generic/nsFlexContainerFrame.h
layout/generic/nsFrameStateBits.h
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsLineBox.h
layout/generic/nsLineLayout.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/res/ua.css
layout/style/test/property_database.js
layout/style/test/test_transitions_per_property.html
modules/libpref/init/StaticPrefList.h
servo/components/style/properties/data.py
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhands/box.mako.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/mod.rs
testing/web-platform/meta/css/css-overflow/webkit-line-clamp/webkit-line-clamp-with-line-height.tentative.html.ini
testing/web-platform/tests/css/css-overflow/parsing/webkit-line-clamp-invalid.html
testing/web-platform/tests/css/css-overflow/parsing/webkit-line-clamp-valid.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-001-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-005-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-006-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-007-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-008-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-009-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-010-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-011-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-012-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-013-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-014-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-015-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-016-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-017-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-018-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-019-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-020-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-021-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-022-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-023-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-024-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-025-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-026-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-027-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-029-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-030-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-031-ref.html
testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-032-ref.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-001.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-002.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-003.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-004.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-005.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-006.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-007.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-008.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-009.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-010.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-011.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-012.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-013.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-014.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-015.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-016.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-017.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-018.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-019.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-020.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-021.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-022.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-023.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-024.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-025.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-026.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-027.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-029.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-030.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-031.html
testing/web-platform/tests/css/css-overflow/webkit-line-clamp-032.html
--- a/devtools/server/actors/animation-type-longhand.js
+++ b/devtools/server/actors/animation-type-longhand.js
@@ -274,16 +274,17 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "order",
     "perspective-origin",
     "shape-outside",
     "stroke-dasharray",
     "transform",
     "transform-origin",
     "-moz-window-transform",
     "-moz-window-transform-origin",
+    "-webkit-line-clamp",
   ])],
   ["coord", new Set([
     "border-bottom-left-radius",
     "border-bottom-right-radius",
     "border-top-left-radius",
     "border-top-right-radius",
     "border-start-start-radius",
     "border-start-end-radius",
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -2312,16 +2312,30 @@ exports.CSS_PROPERTIES = {
       "space-between",
       "space-evenly",
       "start",
       "stretch",
       "unsafe",
       "unset"
     ]
   },
+  "-webkit-line-clamp": {
+    "isInherited": false,
+    "subproperties": [
+      "-webkit-line-clamp"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "none",
+      "revert",
+      "unset"
+    ]
+  },
   "-webkit-mask": {
     "isInherited": false,
     "subproperties": [
       "mask-mode",
       "mask-repeat",
       "mask-clip",
       "mask-origin",
       "mask-composite",
@@ -3099,16 +3113,17 @@ exports.CSS_PROPERTIES = {
       "-moz-appearance",
       "-moz-binding",
       "-moz-orient",
       "will-change",
       "shape-image-threshold",
       "shape-margin",
       "shape-outside",
       "touch-action",
+      "-webkit-line-clamp",
       "color",
       "column-width",
       "column-count",
       "column-fill",
       "column-rule-width",
       "column-rule-color",
       "column-span",
       "column-rule-style",
@@ -10513,16 +10528,20 @@ exports.PREFERENCES = [
     "touch-action",
     "layout.css.touch_action.enabled"
   ],
   [
     "transform-box",
     "svg.transform-box.enabled"
   ],
   [
+    "-webkit-line-clamp",
+    "layout.css.webkit-line-clamp.enabled"
+  ],
+  [
     "overflow-clip-box-block",
     "layout.css.overflow-clip-box.enabled"
   ],
   [
     "overflow-clip-box-inline",
     "layout.css.overflow-clip-box.enabled"
   ],
   [
--- a/layout/generic/BlockReflowInput.cpp
+++ b/layout/generic/BlockReflowInput.cpp
@@ -92,17 +92,25 @@ BlockReflowInput::BlockReflowInput(const
   }
   if ((aBEndMarginRoot && !logicalSkipSides.BEnd()) ||
       0 != mBorderPadding.BEnd(wm)) {
     mFlags.mIsBEndMarginRoot = true;
   }
   if (aBlockNeedsFloatManager) {
     mFlags.mBlockNeedsFloatManager = true;
   }
-  mFlags.mCanHaveTextOverflow = css::TextOverflow::CanHaveTextOverflow(mBlock);
+
+  // We need to check mInsideLineClamp here since we are here before the block
+  // has been reflowed, and CanHaveOverflowMarkers() relies on the block's
+  // NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS state bit to know if a -webkit-line-clamp
+  // ellipsis is set on one of the block's lines.  And that state bit is only
+  // set after we do the bsize measuring reflow of the flex item.
+  mFlags.mCanHaveOverflowMarkers =
+      aReflowInput.mFlags.mInsideLineClamp ||
+      css::TextOverflow::CanHaveOverflowMarkers(mBlock);
 
   MOZ_ASSERT(FloatManager(),
              "Float manager should be valid when creating BlockReflowInput!");
 
   // Save the coordinate system origin for later.
   FloatManager()->GetTranslation(mFloatManagerI, mFloatManagerB);
   FloatManager()->PushState(&mFloatManagerStateBefore);  // never popped
 
--- a/layout/generic/BlockReflowInput.h
+++ b/layout/generic/BlockReflowInput.h
@@ -35,17 +35,17 @@ class BlockReflowInput {
           mShouldApplyBStartMargin(false),
           mIsFirstInflow(false),
           mHasLineAdjacentToTop(false),
           mBlockNeedsFloatManager(false),
           mIsLineLayoutEmpty(false),
           mIsOverflowContainer(false),
           mIsFloatListInBlockPropertyTable(false),
           mFloatFragmentsInsideColumnEnabled(false),
-          mCanHaveTextOverflow(false) {}
+          mCanHaveOverflowMarkers(false) {}
 
     // Set in the BlockReflowInput constructor when the frame being reflowed has
     // been given NS_UNCONSTRAINEDSIZE as its available BSize in the
     // ReflowInput. If set, NS_UNCONSTRAINEDSIZE is passed to nsLineLayout as
     // the available BSize.
     bool mHasUnconstrainedBSize : 1;
 
     // Set in the BlockReflowInput constructor when reflowing a "block margin
@@ -99,18 +99,18 @@ class BlockReflowInput {
     bool mIsOverflowContainer : 1;
 
     // Set when our mPushedFloats list is stored on the block's property table.
     bool mIsFloatListInBlockPropertyTable : 1;
 
     // Set when the pref layout.float-fragments-inside-column.enabled is true.
     bool mFloatFragmentsInsideColumnEnabled : 1;
 
-    // Set when we need text-overflow processing.
-    bool mCanHaveTextOverflow : 1;
+    // Set when we need text-overflow or -webkit-line-clamp processing.
+    bool mCanHaveOverflowMarkers : 1;
   };
 
  public:
   BlockReflowInput(const ReflowInput& aReflowInput, nsPresContext* aPresContext,
                    nsBlockFrame* aFrame, bool aBStartMarginRoot,
                    bool aBEndMarginRoot, bool aBlockNeedsFloatManager,
                    nscoord aConsumedBSize = NS_INTRINSICSIZE);
 
--- a/layout/generic/ReflowInput.cpp
+++ b/layout/generic/ReflowInput.cpp
@@ -210,16 +210,17 @@ ReflowInput::ReflowInput(nsPresContext* 
   mFlags.mDummyParentReflowInput = false;
   mFlags.mShrinkWrap = !!(aFlags & COMPUTE_SIZE_SHRINK_WRAP);
   mFlags.mUseAutoBSize = !!(aFlags & COMPUTE_SIZE_USE_AUTO_BSIZE);
   mFlags.mStaticPosIsCBOrigin = !!(aFlags & STATIC_POS_IS_CB_ORIGIN);
   mFlags.mIOffsetsNeedCSSAlign = mFlags.mBOffsetsNeedCSSAlign = false;
   mFlags.mIClampMarginBoxMinSize = !!(aFlags & I_CLAMP_MARGIN_BOX_MIN_SIZE);
   mFlags.mBClampMarginBoxMinSize = !!(aFlags & B_CLAMP_MARGIN_BOX_MIN_SIZE);
   mFlags.mApplyAutoMinSize = !!(aFlags & I_APPLY_AUTO_MIN_SIZE);
+  mFlags.mApplyLineClamp = false;
 
   if ((aFlags & DUMMY_PARENT_REFLOW_INPUT) ||
       (mParentReflowInput->mFlags.mDummyParentReflowInput &&
        mFrame->IsTableFrame())) {
     mFlags.mDummyParentReflowInput = true;
   }
 
   if (!(aFlags & CALLER_WILL_INIT)) {
--- a/layout/generic/ReflowInput.h
+++ b/layout/generic/ReflowInput.h
@@ -265,16 +265,30 @@ struct SizeComputationInput {
     // area, in that axis -- and these offsets need to be further-resolved
     // (with CSS Box Alignment) after we know the OOF frame's size.
     // NOTE: The "I" and "B" (for "Inline" and "Block") refer the axes of the
     // *containing block's writing-mode*, NOT mFrame's own writing-mode. This
     // is purely for convenience, since that's the writing-mode we're dealing
     // with when we set & react to these bits.
     bool mIOffsetsNeedCSSAlign : 1;
     bool mBOffsetsNeedCSSAlign : 1;
+
+    // Are we somewhere inside an element with -webkit-line-clamp set?
+    // This flag is inherited into descendant ReflowInputs, but we don't bother
+    // resetting it to false when crossing over into a block descendant that
+    // -webkit-line-clamp skips over (such as a BFC).
+    bool mInsideLineClamp : 1;
+
+    // Is this a flex item, and should we add or remove a -webkit-line-clamp
+    // ellipsis on a descendant line?  It's possible for this flag to be true
+    // when mInsideLineClamp is false if we previously had a numeric
+    // -webkit-line-clamp value, but now have 'none' and we need to find the
+    // line with the ellipsis flag and clear it.
+    // This flag is not inherited into descendant ReflowInputs.
+    bool mApplyLineClamp : 1;
   };
 
 #ifdef DEBUG
   // Reflow trace methods.  Defined in nsFrame.cpp so they have access
   // to the display-reflow infrastructure.
   static void* DisplayInitOffsetsEnter(nsIFrame* aFrame,
                                        SizeComputationInput* aState,
                                        nscoord aPercentBasis,
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -147,21 +147,21 @@ static bool IsFrameDescendantOfAny(
   }
   return false;
 }
 
 class nsDisplayTextOverflowMarker final : public nsPaintedDisplayItem {
  public:
   nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                               const nsRect& aRect, nscoord aAscent,
-                              const nsStyleTextOverflowSide* aStyle,
+                              nsStyleTextOverflowSide aStyle,
                               uint32_t aLineNumber, uint16_t aIndex)
       : nsPaintedDisplayItem(aBuilder, aFrame),
         mRect(aRect),
-        mStyle(*aStyle),
+        mStyle(aStyle),
         mAscent(aAscent),
         mIndex((aLineNumber << 1) + aIndex) {
     MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayTextOverflowMarker() {
     MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
   }
@@ -339,19 +339,20 @@ TextOverflow::TextOverflow(nsDisplayList
   }
   // The left/right marker string is setup in ExamineLineFrames when a line
   // has overflow on that side.
 }
 
 /* static */
 Maybe<TextOverflow> TextOverflow::WillProcessLines(
     nsDisplayListBuilder* aBuilder, nsIFrame* aBlockFrame) {
-  // Ignore 'text-overflow' for event and frame visibility processing.
+  // Ignore text-overflow and -webkit-line-clamp for event and frame visibility
+  // processing.
   if (aBuilder->IsForEventDelivery() || aBuilder->IsForFrameVisibility() ||
-      !CanHaveTextOverflow(aBlockFrame)) {
+      !CanHaveOverflowMarkers(aBlockFrame)) {
     return Nothing();
   }
   nsIScrollableFrame* scrollableFrame =
       nsLayoutUtils::GetScrollableFrameFor(aBlockFrame);
   if (scrollableFrame && scrollableFrame->IsTransformingByAPZ()) {
     // If the APZ is actively scrolling this, don't bother with markers.
     return Nothing();
   }
@@ -456,60 +457,66 @@ void TextOverflow::AnalyzeMarkerEdges(ns
           LogicalRect snappedRect = borderRect;
           if (istartOverlap > 0) {
             snappedRect.IStart(mBlockWM) += snappedIStart;
             snappedRect.ISize(mBlockWM) -= snappedIStart;
           }
           if (iendOverlap > 0) {
             snappedRect.ISize(mBlockWM) -= snappedIEnd;
           }
-          aAlignmentEdges->Accumulate(mBlockWM, snappedRect);
+          aAlignmentEdges->AccumulateInner(mBlockWM, snappedRect);
           *aFoundVisibleTextOrAtomic = true;
         }
       }
     } else {
       aFramesToHide->PutEntry(aFrame);
     }
   } else if (!insideIStartEdge || !insideIEndEdge) {
     // frame is outside
+    if (!insideIStartEdge) {
+      aAlignmentEdges->AccumulateOuter(mBlockWM, borderRect);
+    }
     if (IsAtomicElement(aFrame, aFrameType)) {
       aFramesToHide->PutEntry(aFrame);
     }
   } else {
     // frame is inside
-    aAlignmentEdges->Accumulate(mBlockWM, borderRect);
+    aAlignmentEdges->AccumulateInner(mBlockWM, borderRect);
     if (aFrameType == LayoutFrameType::Text) {
       auto textFrame = static_cast<nsTextFrame*>(aFrame);
       if (textFrame->HasNonSuppressedText()) {
         *aFoundVisibleTextOrAtomic = true;
       }
     } else {
       *aFoundVisibleTextOrAtomic = true;
     }
   }
 }
 
 LogicalRect TextOverflow::ExamineLineFrames(nsLineBox* aLine,
                                             FrameHashtable* aFramesToHide,
                                             AlignmentEdges* aAlignmentEdges) {
   // No ellipsing for 'clip' style.
-  bool suppressIStart = mIStart.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
-  bool suppressIEnd = mIEnd.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
+  bool suppressIStart = mIStart.IsSuppressed();
+  bool suppressIEnd = mIEnd.IsSuppressed();
   if (mCanHaveInlineAxisScrollbar) {
     LogicalPoint pos(mBlockWM, mScrollableFrame->GetScrollPosition(),
                      mBlockSize);
     LogicalRect scrollRange(mBlockWM, mScrollableFrame->GetScrollRange(),
                             mBlockSize);
     // No ellipsing when nothing to scroll to on that side (this includes
     // overflow:auto that doesn't trigger a horizontal scrollbar).
     if (pos.I(mBlockWM) <= scrollRange.IStart(mBlockWM)) {
       suppressIStart = true;
     }
     if (pos.I(mBlockWM) >= scrollRange.IEnd(mBlockWM)) {
-      suppressIEnd = true;
+      // Except that we always want to display a -webkit-line-clamp ellipsis.
+      if (!mIEnd.mHasBlockEllipsis) {
+        suppressIEnd = true;
+      }
     }
   }
 
   LogicalRect contentArea = mContentArea;
   bool snapStart = true, snapEnd = true;
   nscoord startEdge, endEdge;
   if (aLine->GetFloatEdges(&startEdge, &endEdge)) {
     // Narrow the |contentArea| to account for any floats on this line, and
@@ -538,32 +545,39 @@ LogicalRect TextOverflow::ExamineLineFra
     }
     if (snapEnd) {
       InflateIEnd(mBlockWM, &contentArea, scrollAdjust);
     }
   }
 
   LogicalRect lineRect(mBlockWM, aLine->GetScrollableOverflowArea(),
                        mBlockSize);
-  const bool istartOverflow =
+  const bool istartWantsMarker =
       !suppressIStart &&
       lineRect.IStart(mBlockWM) < contentArea.IStart(mBlockWM);
-  const bool iendOverflow =
+  const bool iendWantsTextOverflowMarker =
       !suppressIEnd && lineRect.IEnd(mBlockWM) > contentArea.IEnd(mBlockWM);
-  if (!istartOverflow && !iendOverflow) {
-    // The line does not overflow on a side we should ellipsize.
+  const bool iendWantsBlockEllipsisMarker =
+      !suppressIEnd && mIEnd.mHasBlockEllipsis;
+  const bool iendWantsMarker =
+      iendWantsTextOverflowMarker || iendWantsBlockEllipsisMarker;
+  if (!istartWantsMarker && !iendWantsMarker) {
+    // We don't need any markers on this line.
     return nonSnappedContentArea;
   }
 
   int pass = 0;
   bool retryEmptyLine = true;
-  bool guessIStart = istartOverflow;
-  bool guessIEnd = iendOverflow;
-  mIStart.mActive = istartOverflow;
-  mIEnd.mActive = iendOverflow;
+  bool guessIStart = istartWantsMarker;
+  bool guessIEnd = iendWantsMarker;
+  mIStart.mActive = istartWantsMarker;
+  mIEnd.mActive = iendWantsMarker;
+  mIStart.mEdgeAligned = mCanHaveInlineAxisScrollbar && istartWantsMarker;
+  mIEnd.mEdgeAligned =
+      mCanHaveInlineAxisScrollbar && iendWantsTextOverflowMarker;
   bool clippedIStartMarker = false;
   bool clippedIEndMarker = false;
   do {
     // Setup marker strings as needed.
     if (guessIStart) {
       mIStart.SetupString(mBlock);
     }
     if (guessIEnd) {
@@ -596,17 +610,18 @@ LogicalRect TextOverflow::ExamineLineFra
     nsIFrame* child = aLine->mFirstChild;
     InnerClipEdges clippedMarkerEdges;
     for (; n-- > 0; child = child->GetNextSibling()) {
       ExamineFrameSubtree(child, contentArea, insideMarkersArea, aFramesToHide,
                           aAlignmentEdges, &foundVisibleTextOrAtomic,
                           &clippedMarkerEdges);
     }
     if (!foundVisibleTextOrAtomic && retryEmptyLine) {
-      aAlignmentEdges->mAssigned = false;
+      aAlignmentEdges->mAssignedInner = false;
+      aAlignmentEdges->mIEndOuter = 0;
       aFramesToHide->Clear();
       pass = -1;
       if (mIStart.IsNeeded() && mIStart.mActive && !clippedIStartMarker) {
         if (clippedMarkerEdges.mAssignedIStart &&
             clippedMarkerEdges.mIStart >
                 nonSnappedContentArea.IStart(mBlockWM)) {
           mIStart.mISize = clippedMarkerEdges.mIStart -
                            nonSnappedContentArea.IStart(mBlockWM);
@@ -630,85 +645,122 @@ LogicalRect TextOverflow::ExamineLineFra
           mIEnd.mActive = guessIEnd = false;
         }
         continue;
       }
       // The line simply has no visible content even without markers,
       // so examine the line again without suppressing markers.
       retryEmptyLine = false;
       mIStart.mISize = mIStart.mIntrinsicISize;
-      mIStart.mActive = guessIStart = istartOverflow;
+      mIStart.mActive = guessIStart = istartWantsMarker;
       mIEnd.mISize = mIEnd.mIntrinsicISize;
-      mIEnd.mActive = guessIEnd = iendOverflow;
+      mIEnd.mActive = guessIEnd = iendWantsMarker;
+      // If we wanted to place a block ellipsis but didn't, due to not having
+      // any visible content to align to or the line's content being scrolled
+      // out of view, then clip the ellipsis so that it looks like it is aligned
+      // with the out of view content.
+      if (mIEnd.IsNeeded() && mIEnd.mActive && mIEnd.mHasBlockEllipsis) {
+        NS_ASSERTION(nonSnappedContentArea.IStart(mBlockWM) >
+                         aAlignmentEdges->mIEndOuter,
+                     "Expected the alignment edge for the out of view content "
+                     "to be before the start of the content area");
+        mIEnd.mISize = std::max(
+            mIEnd.mIntrinsicISize - (nonSnappedContentArea.IStart(mBlockWM) -
+                                     aAlignmentEdges->mIEndOuter),
+            0);
+      }
       continue;
     }
     if (guessIStart == (mIStart.mActive && mIStart.IsNeeded()) &&
         guessIEnd == (mIEnd.mActive && mIEnd.IsNeeded())) {
       break;
     } else {
       guessIStart = mIStart.mActive && mIStart.IsNeeded();
       guessIEnd = mIEnd.mActive && mIEnd.IsNeeded();
       mIStart.Reset();
       mIEnd.Reset();
       aFramesToHide->Clear();
     }
     NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
   } while (++pass != 2);
-  if (!istartOverflow || !mIStart.mActive) {
+  if (!istartWantsMarker || !mIStart.mActive) {
     mIStart.Reset();
   }
-  if (!iendOverflow || !mIEnd.mActive) {
+  if (!iendWantsMarker || !mIEnd.mActive) {
     mIEnd.Reset();
   }
   return nonSnappedContentArea;
 }
 
 void TextOverflow::ProcessLine(const nsDisplayListSet& aLists, nsLineBox* aLine,
                                uint32_t aLineNumber) {
-  NS_ASSERTION(mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
-                   mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP,
-               "TextOverflow with 'clip' for both sides");
+  if (mIStart.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
+      mIEnd.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
+      !aLine->HasLineClampEllipsis()) {
+    return;
+  }
+
   mIStart.Reset();
   mIStart.mActive = mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
   mIEnd.Reset();
-  mIEnd.mActive = mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
+  mIEnd.mHasBlockEllipsis = aLine->HasLineClampEllipsis();
+  mIEnd.mActive = mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
+                  aLine->HasLineClampEllipsis();
 
   FrameHashtable framesToHide(64);
   AlignmentEdges alignmentEdges;
   const LogicalRect contentArea =
       ExamineLineFrames(aLine, &framesToHide, &alignmentEdges);
   bool needIStart = mIStart.IsNeeded();
   bool needIEnd = mIEnd.IsNeeded();
   if (!needIStart && !needIEnd) {
     return;
   }
-  NS_ASSERTION(
-      mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP || !needIStart,
-      "left marker for 'clip'");
-  NS_ASSERTION(mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP || !needIEnd,
-               "right marker for 'clip'");
+  NS_ASSERTION(!mIStart.IsSuppressed() || !needIStart,
+               "left marker when not needed");
+  NS_ASSERTION(!mIEnd.IsSuppressed() || !needIEnd,
+               "right marker when not needed");
 
   // If there is insufficient space for both markers then keep the one on the
   // end side per the block's 'direction'.
   if (needIStart && needIEnd &&
       mIStart.mISize + mIEnd.mISize > contentArea.ISize(mBlockWM)) {
     needIStart = false;
   }
   LogicalRect insideMarkersArea = contentArea;
   if (needIStart) {
     InflateIStart(mBlockWM, &insideMarkersArea, -mIStart.mISize);
   }
   if (needIEnd) {
     InflateIEnd(mBlockWM, &insideMarkersArea, -mIEnd.mISize);
   }
-  if (!mCanHaveInlineAxisScrollbar && alignmentEdges.mAssigned) {
+
+  if (alignmentEdges.mAssignedInner) {
+    if (mIStart.mEdgeAligned) {
+      alignmentEdges.mIStart = insideMarkersArea.IStart(mBlockWM);
+    }
+    if (mIEnd.mEdgeAligned) {
+      alignmentEdges.mIEnd = insideMarkersArea.IEnd(mBlockWM);
+    }
     LogicalRect alignmentRect(mBlockWM, alignmentEdges.mIStart,
                               insideMarkersArea.BStart(mBlockWM),
                               alignmentEdges.ISize(), 1);
     insideMarkersArea.IntersectRect(insideMarkersArea, alignmentRect);
+  } else {
+    // There was no content on the line that was visible at the current scolled
+    // position.  If we wanted to place a block ellipsis but failed due to
+    // having no visible content to align it to, we still need to ensure it
+    // is displayed.  It goes at the start of the line, even though it's an
+    // IEnd marker, since that is the side of the line that the content has
+    // been scrolled past.  We set the insideMarkersArea to a zero-sized
+    // rectangle placed next to the scrolled-out-of-view content.
+    if (mIEnd.mHasBlockEllipsis) {
+      insideMarkersArea = LogicalRect(mBlockWM, alignmentEdges.mIEndOuter,
+                                      insideMarkersArea.BStart(mBlockWM), 0, 1);
+    }
   }
 
   // Clip and remove display items as needed at the final marker edges.
   nsDisplayList* lists[] = {aLists.Content(), aLists.PositionedDescendants()};
   for (uint32_t i = 0; i < ArrayLength(lists); ++i) {
     PruneDisplayListContents(lists[i], framesToHide, insideMarkersArea);
   }
   CreateMarkers(aLine, needIStart, needIEnd, insideMarkersArea, contentArea,
@@ -757,26 +809,38 @@ void TextOverflow::PruneDisplayListConte
     }
 
     saved.AppendToTop(item);
   }
   aList->AppendToTop(&saved);
 }
 
 /* static */
-bool TextOverflow::HasClippedOverflow(nsIFrame* aBlockFrame) {
+bool TextOverflow::HasClippedTextOverflow(nsIFrame* aBlockFrame) {
   const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
   return style->mTextOverflow.mLeft.mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
          style->mTextOverflow.mRight.mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
 }
 
 /* static */
-bool TextOverflow::CanHaveTextOverflow(nsIFrame* aBlockFrame) {
+bool TextOverflow::HasBlockEllipsis(nsIFrame* aBlockFrame) {
+  nsBlockFrame* f = do_QueryFrame(aBlockFrame);
+  return f && f->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
+}
+
+/* static */
+bool TextOverflow::CanHaveOverflowMarkers(nsIFrame* aBlockFrame) {
+  // Treat a line with a -webkit-line-clamp ellipsis as a kind of text
+  // overflow.
+  if (aBlockFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
+    return true;
+  }
+
   // Nothing to do for text-overflow:clip or if 'overflow-x/y:visible'.
-  if (HasClippedOverflow(aBlockFrame) ||
+  if (HasClippedTextOverflow(aBlockFrame) ||
       IsInlineAxisOverflowVisible(aBlockFrame)) {
     return false;
   }
 
   // Skip ComboboxControlFrame because it would clip the drop-down arrow.
   // Its anon block inherits 'text-overflow' and does what is expected.
   if (aBlockFrame->IsComboboxControlFrame()) {
     return false;
@@ -810,43 +874,51 @@ void TextOverflow::CreateMarkers(const n
         mBlockWM, aInsideMarkersArea.IStart(mBlockWM) - mIStart.mIntrinsicISize,
         aLine->BStart(), mIStart.mIntrinsicISize, aLine->BSize());
     nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
     nsRect markerRect =
         markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
     ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
                markerRect, clipState);
     mMarkerList.AppendNewToTop<nsDisplayTextOverflowMarker>(
-        mBuilder, mBlock, markerRect, aLine->GetLogicalAscent(), mIStart.mStyle,
-        aLineNumber, 0);
+        mBuilder, mBlock, markerRect, aLine->GetLogicalAscent(),
+        *mIStart.mStyle, aLineNumber, 0);
   }
 
   if (aCreateIEnd) {
     DisplayListClipState::AutoSaveRestore clipState(mBuilder);
 
     LogicalRect markerLogicalRect(mBlockWM, aInsideMarkersArea.IEnd(mBlockWM),
                                   aLine->BStart(), mIEnd.mIntrinsicISize,
                                   aLine->BSize());
     nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
     nsRect markerRect =
         markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
     ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
                markerRect, clipState);
     mMarkerList.AppendNewToTop<nsDisplayTextOverflowMarker>(
-        mBuilder, mBlock, markerRect, aLine->GetLogicalAscent(), mIEnd.mStyle,
+        mBuilder, mBlock, markerRect, aLine->GetLogicalAscent(),
+        mIEnd.mHasBlockEllipsis ? nsStyleTextOverflowSide::Ellipsis()
+                                : *mIEnd.mStyle,
         aLineNumber, 1);
   }
 }
 
 void TextOverflow::Marker::SetupString(nsIFrame* aFrame) {
   if (mInitialized) {
     return;
   }
 
-  if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
+  // A limitation here is that at the IEnd of a line, we only ever render one of
+  // a text-overflow marker and a -webkit-line-clamp block ellipsis.  Since we
+  // don't track the block ellipsis string and the text-overflow marker string
+  // separately, if both apply to the element, we will always use "…" as the
+  // string for text-overflow.
+  if (HasBlockEllipsis(aFrame) ||
+      mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
     gfxTextRun* textRun = GetEllipsisTextRun(aFrame);
     if (textRun) {
       mISize = textRun->GetAdvanceWidth();
     } else {
       mISize = 0;
     }
   } else {
     RefPtr<gfxContext> rc =
--- a/layout/generic/TextOverflow.h
+++ b/layout/generic/TextOverflow.h
@@ -59,47 +59,61 @@ class TextOverflow final {
                    uint32_t aLineNumber);
 
   /**
    * Get the resulting text-overflow markers (the list may be empty).
    * @return a DisplayList containing any text-overflow markers.
    */
   nsDisplayList& GetMarkers() { return mMarkerList; }
 
-  /**
-   * @return true if aBlockFrmae has text-overflow:clip on both sides.
-   */
-  static bool HasClippedOverflow(nsIFrame* aBlockFrame);
-  /**
-   * @return true if aBlockFrame needs analysis for text overflow.
-   */
-  static bool CanHaveTextOverflow(nsIFrame* aBlockFrame);
+  // Returns whether aBlockFrame has text-overflow:clip on both sides.
+  static bool HasClippedTextOverflow(nsIFrame* aBlockFrame);
+
+  // Returns whether aBlockFrame has a block ellipsis on one of its lines.
+  static bool HasBlockEllipsis(nsIFrame* aBlockFrame);
+
+  // Returns whether aBlockFrame needs analysis for text overflow.
+  static bool CanHaveOverflowMarkers(nsIFrame* aBlockFrame);
 
   typedef nsTHashtable<nsPtrHashKey<nsIFrame>> FrameHashtable;
 
  private:
   typedef mozilla::WritingMode WritingMode;
   typedef mozilla::LogicalRect LogicalRect;
 
+  // Edges to align the IStart and IEnd markers to.
   struct AlignmentEdges {
-    AlignmentEdges() : mIStart(0), mIEnd(0), mAssigned(false) {}
-    void Accumulate(WritingMode aWM, const LogicalRect& aRect) {
-      if (MOZ_LIKELY(mAssigned)) {
+    AlignmentEdges()
+        : mIStart(0), mIEnd(0), mIEndOuter(0), mAssignedInner(false) {}
+    void AccumulateInner(WritingMode aWM, const LogicalRect& aRect) {
+      if (MOZ_LIKELY(mAssignedInner)) {
         mIStart = std::min(mIStart, aRect.IStart(aWM));
         mIEnd = std::max(mIEnd, aRect.IEnd(aWM));
       } else {
         mIStart = aRect.IStart(aWM);
         mIEnd = aRect.IEnd(aWM);
-        mAssigned = true;
+        mAssignedInner = true;
       }
     }
+    void AccumulateOuter(WritingMode aWM, const LogicalRect& aRect) {
+      mIEndOuter = std::max(mIEndOuter, aRect.IEnd(aWM));
+    }
     nscoord ISize() { return mIEnd - mIStart; }
+
+    // The outermost edges of all text and atomic inline-level frames that are
+    // inside the area between the markers.
     nscoord mIStart;
     nscoord mIEnd;
-    bool mAssigned;
+
+    // The closest IEnd edge of all text and atomic inline-level frames that
+    // fall completely before the IStart edge of the content area.  (Used to
+    // align a block ellipsis when there are no visible frames to align to.)
+    nscoord mIEndOuter;
+
+    bool mAssignedInner;
   };
 
   struct InnerClipEdges {
     InnerClipEdges()
         : mIStart(0), mIEnd(0), mAssignedIStart(false), mAssignedIEnd(false) {}
     void AccumulateIStart(WritingMode aWM, const LogicalRect& aRect) {
       if (MOZ_LIKELY(mAssignedIStart)) {
         mIStart = std::max(mIStart, aRect.IStart(aWM));
@@ -131,33 +145,37 @@ class TextOverflow final {
   }
 
   /**
    * Examines frames on the line to determine whether we should draw a left
    * and/or right marker, and if so, which frames should be completely hidden
    * and the bounds of what will be displayed between the markers.
    * @param aLine the line we're processing
    * @param aFramesToHide frames that should have their display items removed
-   * @param aAlignmentEdges the outermost edges of all text and atomic
-   *   inline-level frames that are inside the area between the markers
+   * @param aAlignmentEdges edges the markers will be aligned to, including
+   *   the outermost edges of all text and atomic inline-level frames that
+   *   are inside the content area, and the closest IEnd edge of such a frame
+   *   outside the content area
    * @return the area inside which we should add any markers;
    *   this is the block's content area narrowed by any floats on this line.
    */
   LogicalRect ExamineLineFrames(nsLineBox* aLine, FrameHashtable* aFramesToHide,
                                 AlignmentEdges* aAlignmentEdges);
 
   /**
    * LineHasOverflowingText calls this to analyze edges, both the block's
    * content edges and the hypothetical marker edges aligned at the block edges.
    * @param aFrame the descendant frame of mBlock that we're analyzing
    * @param aContentArea the block's content area
    * @param aInsideMarkersArea the rectangle between the markers
    * @param aFramesToHide frames that should have their display items removed
-   * @param aAlignmentEdges the outermost edges of all text and atomic
-   *   inline-level frames that are inside the area between the markers
+   * @param aAlignmentEdges edges the markers will be aligned to, including
+   *   the outermost edges of all text and atomic inline-level frames that
+   *   are inside the content area, and the closest IEnd edge of such a frame
+   *   outside the content area
    * @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
    *   inline-level frame is visible between the marker edges
    * @param aClippedMarkerEdges the innermost edges of all text and atomic
    *   inline-level frames that are clipped by the current marker width
    */
   void ExamineFrameSubtree(nsIFrame* aFrame, const LogicalRect& aContentArea,
                            const LogicalRect& aInsideMarkersArea,
                            FrameHashtable* aFramesToHide,
@@ -174,16 +192,20 @@ class TextOverflow final {
    * aFramesToHide.
    * @param aFrame the descendant frame of mBlock that we're analyzing
    * @param aFrameType aFrame's frame type
    * @param aInsideMarkersArea the rectangle between the markers
    * @param aFramesToHide frames that should have their display items removed
    * @param aAlignmentEdges the outermost edges of all text and atomic
    *   inline-level frames that are inside the area between the markers
    *                       inside aInsideMarkersArea
+   * @param aAlignmentEdges edges the markers will be aligned to, including
+   *   the outermost edges of all text and atomic inline-level frames that
+   *   are inside aInsideMarkersArea, and the closest IEnd edge of such a frame
+   *   outside the content area
    * @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
    *   inline-level frame is visible between the marker edges
    * @param aClippedMarkerEdges the innermost edges of all text and atomic
    *   inline-level frames that are clipped by the current marker width
    */
   void AnalyzeMarkerEdges(nsIFrame* aFrame, mozilla::LayoutFrameType aFrameType,
                           const LogicalRect& aInsideMarkersArea,
                           FrameHashtable* aFramesToHide,
@@ -229,40 +251,55 @@ class TextOverflow final {
   class Marker {
    public:
     void Init(const nsStyleTextOverflowSide& aStyle) {
       mInitialized = false;
       mISize = 0;
       mStyle = &aStyle;
       mIntrinsicISize = 0;
       mHasOverflow = false;
+      mHasBlockEllipsis = false;
       mActive = false;
+      mEdgeAligned = false;
     }
 
     /**
      * Setup the marker string and calculate its size, if not done already.
      */
     void SetupString(nsIFrame* aFrame);
 
-    bool IsNeeded() const { return mHasOverflow; }
-    void Reset() { mHasOverflow = false; }
+    bool IsSuppressed() const {
+      return !mHasBlockEllipsis && mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
+    }
+    bool IsNeeded() const { return mHasOverflow || mHasBlockEllipsis; }
+    void Reset() {
+      mHasOverflow = false;
+      mHasBlockEllipsis = false;
+      mEdgeAligned = false;
+    }
 
     // The current width of the marker, the range is [0 .. mIntrinsicISize].
     nscoord mISize;
     // The intrinsic width of the marker.
     nscoord mIntrinsicISize;
-    // The style for this side.
+    // The text-overflow style for this side.  Ignored if we're rendering a
+    // block ellipsis.
     const nsStyleTextOverflowSide* mStyle;
     // True if there is visible overflowing inline content on this side.
     bool mHasOverflow;
-    // True if mMarkerString and mWidth have been setup from style.
+    // True if this side has a block ellipsis (from -webkit-line-clamp).
+    bool mHasBlockEllipsis;
+    // True if mISize and mIntrinsicISize have been setup from style.
     bool mInitialized;
-    // True if the style is text-overflow:clip on this side and the marker
+    // True if the style is not text-overflow:clip on this side and the marker
     // won't cause the line to become empty.
     bool mActive;
+    // True if this marker is aligned to the edge of the content box, so that
+    // when scrolling the marker doesn't jump around.
+    bool mEdgeAligned;
   };
 
   Marker mIStart;  // the inline start marker
   Marker mIEnd;    // the inline end marker
 };
 
 }  // namespace css
 }  // namespace mozilla
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -53,16 +53,17 @@
 #include "CounterStyleManager.h"
 #include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLSummaryElement.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/RestyleManager.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/Telemetry.h"
+#include "nsFlexContainerFrame.h"
 
 #include "nsBidiPresUtils.h"
 
 #include <inttypes.h>
 
 static const int MIN_LINES_NEEDING_CURSOR = 20;
 
 static const char16_t kDiscCharacter = 0x2022;
@@ -1052,16 +1053,149 @@ static LogicalSize CalculateContainingBl
                             logicalScrollbars.BStartEnd(aWM));
       }
     }
   }
 
   return cbSize;
 }
 
+/**
+ * Returns aFrame if it is a non-BFC block frame, and null otherwise.
+ *
+ * This is used to determine whether to recurse into aFrame when applying
+ * -webkit-line-clamp.
+ */
+static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) {
+  if (nsBlockFrame* block = do_QueryFrame(aFrame)) {
+    if (!block->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
+      return block;
+    }
+  }
+  return nullptr;
+}
+
+/**
+ * Iterator over all descendant inline line boxes, except for those that are
+ * under an independent formatting context.
+ */
+class MOZ_RAII LineClampLineIterator {
+ public:
+  explicit LineClampLineIterator(nsBlockFrame* aFrame)
+      : mCur(aFrame->LinesBegin()),
+        mEnd(aFrame->LinesEnd()),
+        mCurrentFrame(mCur == mEnd ? nullptr : aFrame) {
+    if (mCur != mEnd && !mCur->IsInline()) {
+      Advance();
+    }
+  }
+
+  nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; }
+  nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; }
+
+  // Advances the iterator to the next line line.
+  //
+  // Next() shouldn't be called once the iterator is at the end, which can be
+  // checked for by GetCurrentLine() or GetCurrentFrame() returning null.
+  void Next() {
+    MOZ_ASSERT(mCur != mEnd && mCurrentFrame,
+               "Don't call Next() when the iterator is at the end");
+    ++mCur;
+    Advance();
+  }
+
+ private:
+  void Advance() {
+    for (;;) {
+      if (mCur == mEnd) {
+        // Reached the end of the current block.  Pop the parent off the
+        // stack; if there isn't one, then we've reached the end.
+        if (mStack.IsEmpty()) {
+          mCurrentFrame = nullptr;
+          break;
+        }
+        auto entry = mStack.PopLastElement();
+        mCurrentFrame = entry.first();
+        mCur = entry.second();
+        mEnd = mCurrentFrame->LinesEnd();
+      } else if (mCur->IsBlock()) {
+        if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) {
+          nsBlockFrame::LineIterator next = mCur;
+          ++next;
+          mStack.AppendElement(MakePair(mCurrentFrame, next));
+          mCur = child->LinesBegin();
+          mEnd = child->LinesEnd();
+          mCurrentFrame = child;
+        } else {
+          // Some kind of frame we shouldn't descend into.
+          ++mCur;
+        }
+      } else {
+        MOZ_ASSERT(mCur->IsInline());
+        break;
+      }
+    }
+  }
+
+  // The current line within the current block.
+  //
+  // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame
+  // is set to null.
+  nsBlockFrame::LineIterator mCur;
+
+  // The iterator end for the current block.
+  nsBlockFrame::LineIterator mEnd;
+
+  // The current block.
+  nsBlockFrame* mCurrentFrame;
+
+  // Stack of mCurrentFrame and mEnd values that we push and pop as we enter and
+  // exist blocks.
+  AutoTArray<Pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 8> mStack;
+};
+
+static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) {
+  if (!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
+    for (nsIFrame* f : aFrame->PrincipalChildList()) {
+      if (nsBlockFrame* child = GetAsLineClampDescendant(f)) {
+        if (ClearLineClampEllipsis(child)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
+
+  nsBlockFrame::LineIterator line = aFrame->LinesBegin();
+  nsBlockFrame::LineIterator end = aFrame->LinesEnd();
+  while (line != end) {
+    if (line->HasLineClampEllipsis()) {
+      line->ClearHasLineClampEllipsis();
+      return true;
+    }
+    ++line;
+  }
+
+  MOZ_ASSERT_UNREACHABLE("expected to find a line with HasLineClampEllipsis");
+  return true;
+}
+
+void nsBlockFrame::ClearLineClampEllipsis() {
+  ::ClearLineClampEllipsis(this);
+}
+
+static bool IsLineClampItem(const ReflowInput& aReflowInput) {
+  return aReflowInput.mFlags.mApplyLineClamp ||
+         (aReflowInput.mParentReflowInput &&
+          aReflowInput.mParentReflowInput->mFrame->IsScrollFrame() &&
+          aReflowInput.mParentReflowInput->mFlags.mApplyLineClamp);
+}
+
 void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
                           const ReflowInput& aReflowInput,
                           nsReflowStatus& aStatus) {
   MarkInReflow();
   DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 
@@ -1298,16 +1432,21 @@ void nsBlockFrame::Reflow(nsPresContext*
       }
       bbox.BStart(wm) = position.mBaseline - markerBaseline;
       marker->SetRect(wm, bbox, reflowOutput.PhysicalSize());
     }
     // Otherwise just leave the ::marker where it is, up against our
     // block-start padding.
   }
 
+  // Clear any existing -webkit-line-clamp ellipsis.
+  if (IsLineClampItem(aReflowInput)) {
+    ClearLineClampEllipsis();
+  }
+
   CheckFloats(state);
 
   // Compute our final size
   nscoord blockEndEdgeOfChildren;
   ComputeFinalSize(*reflowInput, state, aMetrics, &blockEndEdgeOfChildren);
 
   // If the block direction is right-to-left, we need to update the bounds of
   // lines that were placed relative to mContainerSize during reflow, as
@@ -1510,16 +1649,119 @@ bool nsBlockFrame::CheckForCollapsedBEnd
     }
     if (line->HasClearance()) {
       return true;
     }
   }
   // not reached
 }
 
+static nsLineBox* FindLineClampTarget(nsBlockFrame*& aFrame,
+                                      uint32_t aLineNumber) {
+  MOZ_ASSERT(aLineNumber > 0);
+  MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
+             "Should have been removed earlier in nsBlockReflow::Reflow");
+
+  nsLineBox* target = nullptr;
+  nsBlockFrame* targetFrame = nullptr;
+  bool foundFollowingLine = false;
+
+  LineClampLineIterator iter(aFrame);
+
+  while (nsLineBox* line = iter.GetCurrentLine()) {
+    MOZ_ASSERT(!line->HasLineClampEllipsis(),
+               "Should have been removed earlier in nsBlockFrame::Reflow");
+    MOZ_ASSERT(!iter.GetCurrentFrame()->HasAnyStateBits(
+                   NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS),
+               "Should have been removed earlier in nsBlockReflow::Reflow");
+
+    // Don't count a line that only has collapsible white space (as might exist
+    // after calling e.g. getBoxQuads).
+    if (line->IsEmpty()) {
+      continue;
+    }
+
+    if (aLineNumber == 0) {
+      // We already previously found our target line, and now we have
+      // confirmed that there is another line after it.
+      foundFollowingLine = true;
+      break;
+    }
+
+    if (--aLineNumber == 0) {
+      // This is our target line.  Continue looping to confirm that we
+      // have another line after us.
+      target = line;
+      targetFrame = iter.GetCurrentFrame();
+    }
+
+    iter.Next();
+  }
+
+  if (!foundFollowingLine) {
+    aFrame = nullptr;
+    return nullptr;
+  }
+
+  MOZ_ASSERT(target);
+  MOZ_ASSERT(targetFrame);
+
+  aFrame = targetFrame;
+  return target;
+}
+
+static nscoord ApplyLineClamp(const ReflowInput& aReflowInput,
+                              nsBlockFrame* aFrame, nscoord aContentBSize) {
+  // We only do the work of applying the -webkit-line-clamp value during the
+  // measuring bsize reflow.  Boxes affected by -webkit-line-clamp are always
+  // inflexible, so we will never need to select a different line to place the
+  // ellipsis on in the subsequent real reflow.
+  if (!IsLineClampItem(aReflowInput)) {
+    return aContentBSize;
+  }
+
+  auto container =
+      static_cast<nsFlexContainerFrame*>(nsLayoutUtils::GetClosestFrameOfType(
+          aFrame, LayoutFrameType::FlexContainer));
+  MOZ_ASSERT(container,
+             "A flex item affected by -webkit-line-clamp must have an ancestor "
+             "flex container");
+
+  uint32_t lineClamp = container->GetLineClampValue();
+  if (lineClamp == 0) {
+    // -webkit-line-clamp is none or doesn't apply.
+    return aContentBSize;
+  }
+
+  MOZ_ASSERT(container->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX),
+             "Should only have an effective -webkit-line-clamp value if we "
+             "are in a legacy flex container");
+
+  nsBlockFrame* frame = aFrame;
+  nsLineBox* line = FindLineClampTarget(frame, lineClamp);
+  if (!line) {
+    // The number of lines did not exceed the -webkit-line-clamp value.
+    return aContentBSize;
+  }
+
+  // Mark the line as having an ellipsis so that TextOverflow will render it.
+  line->SetHasLineClampEllipsis();
+  frame->AddStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
+  container->AddStateBits(NS_STATE_FLEX_HAS_LINE_CLAMP_ELLIPSIS);
+
+  // Translate the b-end edge of the line up to aFrame's space.
+  nscoord edge = line->BEnd();
+  for (nsIFrame* f = frame; f != aFrame; f = f->GetParent()) {
+    edge +=
+        f->GetLogicalPosition(f->GetParent()->GetSize()).B(f->GetWritingMode());
+  }
+
+  return edge;
+}
+
 void nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
                                     BlockReflowInput& aState,
                                     ReflowOutput& aMetrics,
                                     nscoord* aBEndEdgeOfChildren) {
   WritingMode wm = aState.mReflowInput.GetWritingMode();
   const LogicalMargin& borderPadding = aState.BorderPadding();
 #ifdef NOISY_FINAL_SIZE
   ListTag(stdout);
@@ -1620,20 +1862,22 @@ void nsBlockFrame::ComputeFinalSize(cons
     // Hence this case is a simplified version of the case below.
     nscoord contentBSize = 0;
     nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(contentBSize);
     aMetrics.mCarriedOutBEndMargin.Zero();
     autoBSize += borderPadding.BStartEnd(wm);
     finalSize.BSize(wm) = autoBSize;
   } else if (aState.mReflowStatus.IsComplete()) {
     nscoord contentBSize = blockEndEdgeOfChildren - borderPadding.BStart(wm);
-    nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(contentBSize);
+    nscoord lineClampedContentBSize =
+        ApplyLineClamp(aReflowInput, this, contentBSize);
+    nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(lineClampedContentBSize);
     if (autoBSize != contentBSize) {
-      // Our min- or max-bsize value made our bsize change.  Don't carry out
-      // our kids' block-end margins.
+      // Our min-block-size, max-block-size, or -webkit-line-clamp value made
+      // our bsize change.  Don't carry out our kids' block-end margins.
       aMetrics.mCarriedOutBEndMargin.Zero();
     }
     autoBSize += borderPadding.BStart(wm) + borderPadding.BEnd(wm);
     finalSize.BSize(wm) = autoBSize;
   } else {
     NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
                  "Shouldn't be incomplete if availableBSize is UNCONSTRAINED.");
     finalSize.BSize(wm) =
@@ -2803,19 +3047,19 @@ void nsBlockFrame::ReflowLine(BlockReflo
 
   // Now that we know what kind of line we have, reflow it
   if (aLine->IsBlock()) {
     ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
   } else {
     aLine->SetLineWrapped(false);
     ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
 
-    // Store the line's float edges for text-overflow analysis if needed.
+    // Store the line's float edges for overflow marker analysis if needed.
     aLine->ClearFloatEdges();
-    if (aState.mFlags.mCanHaveTextOverflow) {
+    if (aState.mFlags.mCanHaveOverflowMarkers) {
       WritingMode wm = aLine->mWritingMode;
       nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(
           aLine->BStart(), aLine->BSize(), nullptr);
       if (r.HasFloats()) {
         LogicalRect so = aLine->GetOverflowArea(eScrollableOverflow, wm,
                                                 aLine->mContainerSize);
         nscoord s = r.mRect.IStart(wm);
         nscoord e = r.mRect.IEnd(wm);
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -572,16 +572,22 @@ class nsBlockFrame : public nsContainerF
       return pushedFloats && !pushedFloats->IsEmpty();
     }
     return false;
   }
 
   // @see nsIFrame::AddSizeOfExcludingThisForTree
   void AddSizeOfExcludingThisForTree(nsWindowSizes&) const override;
 
+  /**
+   * Clears any -webkit-line-clamp ellipsis on a line in this block or one
+   * of its descendants.
+   */
+  void ClearLineClampEllipsis();
+
  protected:
   /** @see DoRemoveFrame */
   void DoRemoveFrameInternal(nsIFrame* aDeletedFrame, uint32_t aFlags,
                              PostDestroyData& data);
 
   /** grab overflow lines from this block's prevInFlow, and make them
    * part of this block's mLines list.
    * @return true if any lines were drained.
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -828,16 +828,20 @@ class nsFlexContainerFrame::FlexItem : p
 
   uint32_t GetNumAutoMarginsInAxis(AxisOrientationType aAxis) const;
 
   // Once the main size has been resolved, should we bother doing layout to
   // establish the cross size?
   bool CanMainSizeInfluenceCrossSize(
       const FlexboxAxisTracker& aAxisTracker) const;
 
+  // Gets the block frame that contains the flex item's content.  This is
+  // Frame() itself or one of its descendants.
+  nsBlockFrame* BlockFrame() const;
+
  protected:
   // Helper called by the constructor, to set mNeedsMinSizeAutoResolution:
   void CheckForMinSizeAuto(const ReflowInput& aFlexItemReflowInput,
                            const FlexboxAxisTracker& aAxisTracker);
 
   // Values that we already know in constructor (and are hence mostly 'const'):
   nsIFrame* const mFrame;  // The flex item's frame.
   const float mFlexGrow;
@@ -1257,29 +1261,35 @@ uint16_t nsFlexContainerFrame::CSSAlignm
   }
 
   return (alignment | alignmentFlags);
 }
 
 UniquePtr<FlexItem> nsFlexContainerFrame::GenerateFlexItemForChild(
     nsPresContext* aPresContext, nsIFrame* aChildFrame,
     const ReflowInput& aParentReflowInput,
-    const FlexboxAxisTracker& aAxisTracker) {
+    const FlexboxAxisTracker& aAxisTracker, bool aHasLineClampEllipsis) {
   // Create temporary reflow input just for sizing -- to get hypothetical
   // main-size and the computed values of min / max main-size property.
   // (This reflow input will _not_ be used for reflow.)
   ReflowInput childRI(
       aPresContext, aParentReflowInput, aChildFrame,
       aParentReflowInput.ComputedSize(aChildFrame->GetWritingMode()));
+  childRI.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
 
   // FLEX GROW & SHRINK WEIGHTS
   // --------------------------
   float flexGrow, flexShrink;
   if (IsLegacyBox(this)) {
-    flexGrow = flexShrink = aChildFrame->StyleXUL()->mBoxFlex;
+    if (GetLineClampValue() != 0) {
+      // Items affected by -webkit-line-clamp are always inflexible.
+      flexGrow = flexShrink = 0;
+    } else {
+      flexGrow = flexShrink = aChildFrame->StyleXUL()->mBoxFlex;
+    }
   } else {
     const nsStylePosition* stylePos = aChildFrame->StylePosition();
     flexGrow = stylePos->mFlexGrow;
     flexShrink = stylePos->mFlexShrink;
   }
 
   WritingMode childWM = childRI.GetWritingMode();
 
@@ -1374,17 +1384,18 @@ UniquePtr<FlexItem> nsFlexContainerFrame
       item->SetWasMinClamped();
     } else if (flexBaseSize > mainMaxSize) {
       item->SetWasMaxClamped();
     }
   }
 
   // Resolve "flex-basis:auto" and/or "min-[width|height]:auto" (which might
   // require us to reflow the item to measure content height)
-  ResolveAutoFlexBasisAndMinSize(aPresContext, *item, childRI, aAxisTracker);
+  ResolveAutoFlexBasisAndMinSize(aPresContext, *item, childRI, aAxisTracker,
+                                 aHasLineClampEllipsis);
   return item;
 }
 
 // Static helper-functions for ResolveAutoFlexBasisAndMinSize():
 // -------------------------------------------------------------
 // Indicates whether the cross-size property is set to something definite,
 // for the purpose of intrinsic ratio calculations.
 // The logic here should be similar to the logic for isAutoISize/isAutoBSize
@@ -1549,18 +1560,18 @@ static bool ResolveAutoFlexBasisFromRati
   }
   return false;
 }
 
 // Note: If & when we handle "min-height: min-content" for flex items,
 // we may want to resolve that in this function, too.
 void nsFlexContainerFrame::ResolveAutoFlexBasisAndMinSize(
     nsPresContext* aPresContext, FlexItem& aFlexItem,
-    const ReflowInput& aItemReflowInput,
-    const FlexboxAxisTracker& aAxisTracker) {
+    const ReflowInput& aItemReflowInput, const FlexboxAxisTracker& aAxisTracker,
+    bool aHasLineClampEllipsis) {
   // (Note: We can guarantee that the flex-basis will have already been
   // resolved if the main axis is the same is the same as the item's inline
   // axis. Inline-axis values should always be resolvable without reflow.)
   const bool isMainSizeAuto = (!aFlexItem.IsInlineAxisMainAxis() &&
                                NS_AUTOHEIGHT == aFlexItem.GetFlexBaseSize());
 
   const bool isMainMinSizeAuto = aFlexItem.NeedsMinSizeAutoResolution();
 
@@ -1653,17 +1664,17 @@ void nsFlexContainerFrame::ResolveAutoFl
       // need.)
       bool forceBResizeForMeasuringReflow =
           !aFlexItem.IsFrozen() ||          // Is the item flexible?
           !flexBasisNeedsToMeasureContent;  // Are we *only* measuring it for
                                             // 'min-block-size:auto'?
 
       nscoord contentBSize = MeasureFlexItemContentBSize(
           aPresContext, aFlexItem, forceBResizeForMeasuringReflow,
-          *flexContainerRI);
+          aHasLineClampEllipsis, *flexContainerRI);
       if (minSizeNeedsToMeasureContent) {
         resolvedMinSize = std::min(resolvedMinSize, contentBSize);
       }
       if (flexBasisNeedsToMeasureContent) {
         aFlexItem.SetFlexBaseSizeAndMainSize(contentBSize);
       }
     }
   }
@@ -1818,26 +1829,29 @@ void nsFlexContainerFrame::MarkIntrinsic
   mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
   mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
 
   nsContainerFrame::MarkIntrinsicISizesDirty();
 }
 
 nscoord nsFlexContainerFrame::MeasureFlexItemContentBSize(
     nsPresContext* aPresContext, FlexItem& aFlexItem,
-    bool aForceBResizeForMeasuringReflow,
+    bool aForceBResizeForMeasuringReflow, bool aHasLineClampEllipsis,
     const ReflowInput& aParentReflowInput) {
   // Set up a reflow input for measuring the flex item's auto-height:
   WritingMode wm = aFlexItem.Frame()->GetWritingMode();
   LogicalSize availSize = aParentReflowInput.ComputedSize(wm);
   availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
   ReflowInput childRIForMeasuringBSize(aPresContext, aParentReflowInput,
                                        aFlexItem.Frame(), availSize, Nothing(),
                                        ReflowInput::CALLER_WILL_INIT);
   childRIForMeasuringBSize.mFlags.mIsFlexContainerMeasuringBSize = true;
+  childRIForMeasuringBSize.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
+  childRIForMeasuringBSize.mFlags.mApplyLineClamp =
+      childRIForMeasuringBSize.mFlags.mInsideLineClamp || aHasLineClampEllipsis;
   childRIForMeasuringBSize.Init(aPresContext);
 
   if (aFlexItem.IsStretched()) {
     childRIForMeasuringBSize.SetComputedISize(aFlexItem.GetCrossSize());
     childRIForMeasuringBSize.SetIResize(true);
   }
 
   if (aForceBResizeForMeasuringReflow) {
@@ -3350,16 +3364,32 @@ void FlexItem::ResolveStretchedCrossSize
   stretchedSize = NS_CSS_MINMAX(stretchedSize, mCrossMinSize, mCrossMaxSize);
 
   // Update the cross-size & make a note that it's stretched, so we know to
   // override the reflow input's computed cross-size in our final reflow.
   SetCrossSize(stretchedSize);
   mIsStretched = true;
 }
 
+static nsBlockFrame* FindFlexItemBlockFrame(nsIFrame* aFrame) {
+  if (nsBlockFrame* block = do_QueryFrame(aFrame)) {
+    return block;
+  }
+  for (nsIFrame* f : aFrame->PrincipalChildList()) {
+    if (nsBlockFrame* block = FindFlexItemBlockFrame(f)) {
+      return block;
+    }
+  }
+  return nullptr;
+}
+
+nsBlockFrame* FlexItem::BlockFrame() const {
+  return FindFlexItemBlockFrame(Frame());
+}
+
 void SingleLineCrossAxisPositionTracker::ResolveAutoMarginsInCrossAxis(
     const FlexLine& aLine, FlexItem& aItem) {
   // Subtract the space that our item is already occupying, to see how much
   // space (if any) is available for its auto margins.
   nscoord spaceForAutoMargins =
       aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis);
 
   if (spaceForAutoMargins <= 0) {
@@ -3723,17 +3753,18 @@ bool nsFlexContainerFrame::ShouldUseMozB
 
   return false;
 }
 
 void nsFlexContainerFrame::GenerateFlexLines(
     nsPresContext* aPresContext, const ReflowInput& aReflowInput,
     nscoord aContentBoxMainSize, nscoord aAvailableBSizeForContent,
     const nsTArray<StrutInfo>& aStruts, const FlexboxAxisTracker& aAxisTracker,
-    nscoord aMainGapSize, nsTArray<nsIFrame*>& aPlaceholders, /* out */
+    nscoord aMainGapSize, bool aHasLineClampEllipsis,
+    nsTArray<nsIFrame*>& aPlaceholders, /* out */
     LinkedList<FlexLine>& aLines /* out */) {
   MOZ_ASSERT(aLines.isEmpty(), "Expecting outparam to start out empty");
 
   const bool isSingleLine =
       NS_STYLE_FLEX_WRAP_NOWRAP == aReflowInput.mStylePosition->mFlexWrap;
 
   // If we're transparently reversing axes, then we'll need to link up our
   // FlexItems and FlexLines in the reverse order, so that the rest of flex
@@ -3822,17 +3853,17 @@ void nsFlexContainerFrame::GenerateFlexL
                aStruts[nextStrutIdx].mItemIdx == itemIdxInContainer) {
       // Use the simplified "strut" FlexItem constructor:
       item = MakeUnique<FlexItem>(childFrame,
                                   aStruts[nextStrutIdx].mStrutCrossSize,
                                   aReflowInput.GetWritingMode());
       nextStrutIdx++;
     } else {
       item = GenerateFlexItemForChild(aPresContext, childFrame, aReflowInput,
-                                      aAxisTracker);
+                                      aAxisTracker, aHasLineClampEllipsis);
     }
 
     nscoord itemInnerHypotheticalMainSize = item->GetMainSize();
     nscoord itemOuterHypotheticalMainSize =
         item->GetOuterMainSize(aAxisTracker.GetMainAxis());
 
     // Check if we need to wrap |item| to a new line
     // (i.e. check if its outer hypothetical main size pushes our line over
@@ -4218,16 +4249,23 @@ void nsFlexContainerFrame::Reflow(nsPres
   const auto& bsize = stylePos->BSize(wm);
   if (bsize.HasPercent() || (StyleDisplay()->IsAbsolutelyPositionedStyle() &&
                              (bsize.IsAuto() || bsize.IsExtremumLength()) &&
                              !stylePos->mOffset.GetBStart(wm).IsAuto() &&
                              !stylePos->mOffset.GetBEnd(wm).IsAuto())) {
     AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
   }
 
+  // Check if there is a -webkit-line-clamp ellipsis somewhere inside at least
+  // one of the flex items, so we can clear the flag before the block frame
+  // re-sets it on the appropriate line during its bsize measuring reflow.
+  bool hasLineClampEllipsis =
+      HasAnyStateBits(NS_STATE_FLEX_HAS_LINE_CLAMP_ELLIPSIS);
+  RemoveStateBits(NS_STATE_FLEX_HAS_LINE_CLAMP_ELLIPSIS);
+
   const FlexboxAxisTracker axisTracker(this, aReflowInput.GetWritingMode());
 
   // Check to see if we need to create a computed info structure, to
   // be filled out for use by devtools.
   if (HasAnyStateBits(NS_STATE_FLEX_GENERATE_COMPUTED_VALUES)) {
     // This state bit will never be cleared. That's acceptable because
     // it's only set in a Chrome API invoked by devtools, and won't
     // impact normal browsing.
@@ -4275,24 +4313,24 @@ void nsFlexContainerFrame::Reflow(nsPres
                          "flows)");
     crossGapSize = nsLayoutUtils::ResolveGapToLength(
         stylePos->mColumnGap, aReflowInput.ComputedISize());
   }
 
   AutoTArray<StrutInfo, 1> struts;
   DoFlexLayout(aPresContext, aDesiredSize, aReflowInput, aStatus,
                contentBoxMainSize, availableBSizeForContent, struts,
-               axisTracker, mainGapSize, crossGapSize);
+               axisTracker, mainGapSize, crossGapSize, hasLineClampEllipsis);
 
   if (!struts.IsEmpty()) {
     // We're restarting flex layout, with new knowledge of collapsed items.
     aStatus.Reset();
     DoFlexLayout(aPresContext, aDesiredSize, aReflowInput, aStatus,
                  contentBoxMainSize, availableBSizeForContent, struts,
-                 axisTracker, mainGapSize, crossGapSize);
+                 axisTracker, mainGapSize, crossGapSize, hasLineClampEllipsis);
   }
 }
 
 // Class to let us temporarily provide an override value for the the main-size
 // CSS property ('width' or 'height') on a flex item, for use in
 // nsFrame::ComputeSizeWithIntrinsicDimensions.
 // (We could use this overridden size more broadly, too, but it's probably
 // better to avoid property-table accesses.  So, where possible, we communicate
@@ -4497,25 +4535,26 @@ static mozilla::dom::FlexPhysicalDirecti
   }
 }
 
 void nsFlexContainerFrame::DoFlexLayout(
     nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
     const ReflowInput& aReflowInput, nsReflowStatus& aStatus,
     nscoord aContentBoxMainSize, nscoord aAvailableBSizeForContent,
     nsTArray<StrutInfo>& aStruts, const FlexboxAxisTracker& aAxisTracker,
-    nscoord aMainGapSize, nscoord aCrossGapSize) {
+    nscoord aMainGapSize, nscoord aCrossGapSize, bool aHasLineClampEllipsis) {
   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 
   AutoCleanLinkedList<FlexLine> lines;
   nsTArray<nsIFrame*> placeholderKids;
 
   GenerateFlexLines(aPresContext, aReflowInput, aContentBoxMainSize,
                     aAvailableBSizeForContent, aStruts, aAxisTracker,
-                    aMainGapSize, placeholderKids, lines);
+                    aMainGapSize, aHasLineClampEllipsis, placeholderKids,
+                    lines);
 
   if ((lines.getFirst()->IsEmpty() && !lines.getFirst()->getNext()) ||
       aReflowInput.mStyleDisplay->IsContainLayout()) {
     // If have no flex items, or if we  are layout contained and
     // want to behave as if we have none, our parent
     // should synthesize a baseline if needed.
     AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
   } else {
@@ -4651,16 +4690,17 @@ void nsFlexContainerFrame::DoFlexLayout(
           sizeOverride.emplace(*item, aAxisTracker);
         }
 
         WritingMode wm = item->Frame()->GetWritingMode();
         LogicalSize availSize = aReflowInput.ComputedSize(wm);
         availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
         ReflowInput childReflowInput(aPresContext, aReflowInput, item->Frame(),
                                      availSize);
+        childReflowInput.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
         if (!sizeOverride) {
           // Directly override the computed main-size, by tweaking reflow input:
           if (item->IsInlineAxisMainAxis()) {
             childReflowInput.SetComputedISize(item->GetMainSize());
           } else {
             childReflowInput.SetComputedBSize(item->GetMainSize());
           }
         }
@@ -4832,17 +4872,18 @@ void nsFlexContainerFrame::DoFlexLayout(
       // (i.e. its frame rect), instead of the container's content-box:
       framePos += containerContentBoxOrigin;
 
       // (Intentionally snapshotting this before ApplyRelativePositioning, to
       // maybe use for setting the flex container's baseline.)
       const nscoord itemNormalBPos = framePos.B(flexWM);
 
       // Check if we actually need to reflow the item -- if we already reflowed
-      // it with the right size, we can just reposition it as-needed.
+      // it with the right size, and there is no need to do a reflow to clear
+      // out a -webkit-line-clamp ellipsis, we can just reposition it as-needed.
       bool itemNeedsReflow = true;  // (Start out assuming the worst.)
       if (item->HadMeasuringReflow()) {
         LogicalSize finalFlexItemCBSize =
             aAxisTracker.LogicalSizeFromFlexRelativeSizes(item->GetMainSize(),
                                                           item->GetCrossSize());
         // We've already reflowed the child once. Was the size we gave it in
         // that reflow the same as its final (post-flexing/stretching) size?
         if (finalFlexItemCBSize ==
@@ -4864,17 +4905,27 @@ void nsFlexContainerFrame::DoFlexLayout(
         if (itemNeedsReflow) {
           MOZ_LOG(gFlexContainerLog, LogLevel::Debug,
                   ("[perf] Flex item needed both a measuring reflow and "
                    "a final reflow\n"));
         }
       }
       if (itemNeedsReflow) {
         ReflowFlexItem(aPresContext, aAxisTracker, aReflowInput, *item,
-                       framePos, containerSize);
+                       framePos, containerSize, aHasLineClampEllipsis);
+      }
+
+      // If we didn't perform a final reflow of the item, we still have a
+      // -webkit-line-clamp ellipsis hanging around, but we shouldn't have one
+      // any more, we need to clear that now.  We strictly only need to do this
+      // if we didn't do a bsize measuring reflow of the item earlier (since
+      // that is normally when we deal with -webkit-line-clamp ellipses) but
+      // not all flex items need such a reflow.
+      if (!itemNeedsReflow && aHasLineClampEllipsis && GetLineClampValue() == 0) {
+        item->BlockFrame()->ClearLineClampEllipsis();
       }
 
       // If the item has auto margins, and we were tracking the UsedMargin
       // property, set the property to the computed margin values.
       if (item->HasAnyAutoMargin()) {
         nsMargin* propValue =
             item->Frame()->GetProperty(nsIFrame::UsedMarginProperty());
         if (propValue) {
@@ -5024,23 +5075,31 @@ void nsFlexContainerFrame::MoveFlexItemT
   aItem.Frame()->SetPosition(outerWM, aFramePos, aContainerSize);
   PositionFrameView(aItem.Frame());
   PositionChildViews(aItem.Frame());
 }
 
 void nsFlexContainerFrame::ReflowFlexItem(
     nsPresContext* aPresContext, const FlexboxAxisTracker& aAxisTracker,
     const ReflowInput& aReflowInput, const FlexItem& aItem,
-    LogicalPoint& aFramePos, const nsSize& aContainerSize) {
+    LogicalPoint& aFramePos, const nsSize& aContainerSize,
+    bool aHasLineClampEllipsis) {
   WritingMode outerWM = aReflowInput.GetWritingMode();
   WritingMode wm = aItem.Frame()->GetWritingMode();
   LogicalSize availSize = aReflowInput.ComputedSize(wm);
   availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
   ReflowInput childReflowInput(aPresContext, aReflowInput, aItem.Frame(),
                                availSize);
+  childReflowInput.mFlags.mInsideLineClamp = GetLineClampValue() != 0;
+  // This is the final reflow of this flex item; if we previously had a
+  // -webkit-line-clamp, and we missed our chance to clear the ellipsis
+  // because we didn't need to call MeasureFlexItemContentBSize, we set
+  // mApplyLineClamp to cause it to get cleared here.
+  childReflowInput.mFlags.mApplyLineClamp =
+      !childReflowInput.mFlags.mInsideLineClamp && aHasLineClampEllipsis;
 
   // Keep track of whether we've overriden the child's computed ISize
   // and/or BSize, so we can set its resize flags accordingly.
   bool didOverrideComputedISize = false;
   bool didOverrideComputedBSize = false;
 
   // Override computed main-size
   if (aItem.IsInlineAxisMainAxis()) {
@@ -5135,16 +5194,18 @@ void nsFlexContainerFrame::ReflowPlaceho
   // |aPlaceholders| at the container's content-box origin.
   for (nsIFrame* placeholder : aPlaceholders) {
     MOZ_ASSERT(placeholder->IsPlaceholderFrame(),
                "placeholders array should only contain placeholder frames");
     WritingMode wm = placeholder->GetWritingMode();
     LogicalSize availSize = aReflowInput.ComputedSize(wm);
     ReflowInput childReflowInput(aPresContext, aReflowInput, placeholder,
                                  availSize);
+    // No need to set the -webkit-line-clamp related flags when reflowing
+    // a placeholder.
     ReflowOutput childDesiredSize(childReflowInput);
     nsReflowStatus childReflowStatus;
     ReflowChild(placeholder, aPresContext, childDesiredSize, childReflowInput,
                 outerWM, aContentBoxOrigin, aContainerSize, 0,
                 childReflowStatus);
 
     FinishReflowChild(placeholder, aPresContext, childDesiredSize,
                       &childReflowInput, outerWM, aContentBoxOrigin,
@@ -5232,8 +5293,22 @@ nscoord nsFlexContainerFrame::GetPrefISi
     mCachedPrefISize =
         StyleDisplay()->IsContainSize()
             ? 0
             : IntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE);
   }
 
   return mCachedPrefISize;
 }
+
+uint32_t nsFlexContainerFrame::GetLineClampValue() const {
+  // -webkit-line-clamp should only work on items in flex containers that are
+  // display:-webkit-(inline-)box and -webkit-box-orient:vertical.
+  //
+  // This check makes -webkit-line-clamp work on display:-moz-box too, but
+  // that shouldn't be a big deal.
+  if (!HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX) ||
+      StyleXUL()->mBoxOrient != StyleBoxOrient::Vertical) {
+    return 0;
+  }
+
+  return StyleDisplay()->mLineClamp;
+}
--- a/layout/generic/nsFlexContainerFrame.h
+++ b/layout/generic/nsFlexContainerFrame.h
@@ -146,16 +146,24 @@ class nsFlexContainerFrame final : publi
       return false;
     }
     *aBaseline = aBaselineGroup == BaselineSharingGroup::First
                      ? mBaselineFromLastReflow
                      : mLastBaselineFromLastReflow;
     return true;
   }
 
+  /**
+   * Returns the effective value of -webkit-line-clamp for this flex container.
+   *
+   * This will be 0 if the property is 'none', or if the element is not
+   * display:-webkit-(inline-)box and -webkit-box-orient:vertical.
+   */
+  uint32_t GetLineClampValue() const;
+
   // nsContainerFrame overrides
   uint16_t CSSAlignmentForAbsPosChild(
       const ReflowInput& aChildRI,
       mozilla::LogicalAxis aLogicalAxis) const override;
 
   /**
    * Helper function to calculate packing space and initial offset of alignment
    * subjects in MainAxisPositionTracker() and CrossAxisPositionTracker() for
@@ -259,17 +267,18 @@ class nsFlexContainerFrame final : publi
    * everything before that point.)
    */
   void DoFlexLayout(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
                     const ReflowInput& aReflowInput, nsReflowStatus& aStatus,
                     nscoord aContentBoxMainSize,
                     nscoord aAvailableBSizeForContent,
                     nsTArray<StrutInfo>& aStruts,
                     const FlexboxAxisTracker& aAxisTracker,
-                    nscoord aMainGapSize, nscoord aCrossGapSize);
+                    nscoord aMainGapSize, nscoord aCrossGapSize,
+                    bool aHasLineClampEllipsis);
 
   /**
    * Checks whether our child-frame list "mFrames" is sorted, using the given
    * IsLessThanOrEqual function, and sorts it if it's not already sorted.
    *
    * XXXdholbert Once we support pagination, we need to make this function
    * check our continuations as well (or wrap it in a function that does).
    *
@@ -292,17 +301,17 @@ class nsFlexContainerFrame final : publi
    * flex basis (including e.g. auto-height) as well as to resolve
    * "min-height:auto", via ResolveAutoFlexBasisAndMinSize(). (Basically, the
    * returned FlexItem will be ready to participate in the "Resolve the
    * Flexible Lengths" step of the Flex Layout Algorithm.)
    */
   mozilla::UniquePtr<FlexItem> GenerateFlexItemForChild(
       nsPresContext* aPresContext, nsIFrame* aChildFrame,
       const ReflowInput& aParentReflowInput,
-      const FlexboxAxisTracker& aAxisTracker);
+      const FlexboxAxisTracker& aAxisTracker, bool aHasLineClampEllipsis);
 
   /**
    * This method gets a cached measuring reflow for a flex item, or does it and
    * caches it.
    *
    * This avoids exponential reflows, see the comment on
    * CachedMeasuringReflowResult.
    */
@@ -314,27 +323,29 @@ class nsFlexContainerFrame final : publi
    * This method performs a "measuring" reflow to get the content BSize of
    * aFlexItem.Frame() (treating it as if it had a computed BSize of "auto"),
    * and returns the resulting BSize measurement.
    * (Helper for ResolveAutoFlexBasisAndMinSize().)
    */
   nscoord MeasureFlexItemContentBSize(nsPresContext* aPresContext,
                                       FlexItem& aFlexItem,
                                       bool aForceBResizeForMeasuringReflow,
+                                      bool aHasLineClampEllipsis,
                                       const ReflowInput& aParentReflowInput);
 
   /**
    * This method resolves an "auto" flex-basis and/or min-main-size value
    * on aFlexItem, if needed.
    * (Helper for GenerateFlexItemForChild().)
    */
   void ResolveAutoFlexBasisAndMinSize(nsPresContext* aPresContext,
                                       FlexItem& aFlexItem,
                                       const ReflowInput& aItemReflowInput,
-                                      const FlexboxAxisTracker& aAxisTracker);
+                                      const FlexboxAxisTracker& aAxisTracker,
+                                      bool aHasLineClampEllipsis);
 
   /**
    * Returns true if "this" is the nsFlexContainerFrame for a -moz-box or
    * a -moz-inline-box -- these boxes have special behavior for flex items with
    * "visibility:collapse".
    *
    * @param aFlexStyleDisp This frame's StyleDisplay(). (Just an optimization to
    *                       avoid repeated lookup; some callers already have it.)
@@ -353,17 +364,17 @@ class nsFlexContainerFrame final : publi
    * (Absolutely positioned children of a flex container are *not* flex items.)
    */
   void GenerateFlexLines(nsPresContext* aPresContext,
                          const ReflowInput& aReflowInput,
                          nscoord aContentBoxMainSize,
                          nscoord aAvailableBSizeForContent,
                          const nsTArray<StrutInfo>& aStruts,
                          const FlexboxAxisTracker& aAxisTracker,
-                         nscoord aMainGapSize,
+                         nscoord aMainGapSize, bool aHasLineClampEllipsis,
                          nsTArray<nsIFrame*>& aPlaceholders,
                          mozilla::LinkedList<FlexLine>& aLines);
 
   nscoord GetMainSizeFromReflowInput(const ReflowInput& aReflowInput,
                                      const FlexboxAxisTracker& aAxisTracker);
 
   nscoord ComputeCrossSize(const ReflowInput& aReflowInput,
                            const FlexboxAxisTracker& aAxisTracker,
@@ -406,17 +417,17 @@ class nsFlexContainerFrame final : publi
    *                        be placed. (pre-relative positioning)
    * @param aContainerSize  The flex container's size (required by some methods
    *                        that we call, to interpret aFramePos correctly).
    */
   void ReflowFlexItem(nsPresContext* aPresContext,
                       const FlexboxAxisTracker& aAxisTracker,
                       const ReflowInput& aReflowInput, const FlexItem& aItem,
                       mozilla::LogicalPoint& aFramePos,
-                      const nsSize& aContainerSize);
+                      const nsSize& aContainerSize, bool aHasLineClampEllipsis);
 
   /**
    * Helper-function to perform a "dummy reflow" on all our nsPlaceholderFrame
    * children, at the container's content-box origin.
    *
    * This doesn't actually represent the static position of the placeholders'
    * out-of-flow (OOF) frames -- we can't compute that until we've reflowed the
    * OOF, because (depending on the CSS Align properties) the static position
--- a/layout/generic/nsFrameStateBits.h
+++ b/layout/generic/nsFrameStateBits.h
@@ -345,16 +345,20 @@ FRAME_STATE_BIT(FlexContainer, 20,
 FRAME_STATE_BIT(FlexContainer, 21, NS_STATE_FLEX_IS_EMULATING_LEGACY_BOX)
 
 // True iff computed flex values should be generated on the next reflow
 FRAME_STATE_BIT(FlexContainer, 22, NS_STATE_FLEX_GENERATE_COMPUTED_VALUES)
 
 // True if the container has no flex items; may lie if there is a pending reflow
 FRAME_STATE_BIT(FlexContainer, 23, NS_STATE_FLEX_SYNTHESIZE_BASELINE)
 
+// True if any flex item in the container has a line with a
+// -webkit-line-ellipsis marker.
+FRAME_STATE_BIT(FlexContainer, 24, NS_STATE_FLEX_HAS_LINE_CLAMP_ELLIPSIS)
+
 // == Frame state bits that apply to grid container frames ====================
 
 FRAME_STATE_GROUP(GridContainer, nsGridContainerFrame)
 
 // True iff the normal flow children are already in CSS 'order' in the
 // order they occur in the child frame list.
 FRAME_STATE_BIT(GridContainer, 20,
                 NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER)
@@ -563,16 +567,22 @@ FRAME_STATE_BIT(Block, 28, NS_BLOCK_CLIP
 FRAME_STATE_BIT(Block, 29, NS_BLOCK_HAS_FIRST_LETTER_STYLE)
 
 // NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER and NS_BLOCK_FRAME_HAS_INSIDE_MARKER
 // means the block has an associated ::marker frame, they are mutually
 // exclusive.
 FRAME_STATE_BIT(Block, 30, NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER)
 FRAME_STATE_BIT(Block, 31, NS_BLOCK_FRAME_HAS_INSIDE_MARKER)
 
+// NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS indicates that exactly one line in this
+// block has the LineClampEllipsis flag set, and that such a line must be found
+// and have that flag cleared when reflowing this element's nearest legacy box
+// container.
+FRAME_STATE_BIT(Block, 60, NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)
+
 // This block has had a child marked dirty, so before we reflow we need
 // to look through the lines to find any such children and mark
 // appropriate lines dirty.
 FRAME_STATE_BIT(Block, 61, NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)
 
 // Are our cached intrinsic widths intrinsic widths for font size
 // inflation?  i.e., what was the current state of
 // GetPresContext()->mInflationDisabledForShrinkWrap at the time they
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1180,16 +1180,17 @@ a11y::AccType nsHTMLScrollFrame::Accessi
 }
 #endif
 
 NS_QUERYFRAME_HEAD(nsHTMLScrollFrame)
   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
   NS_QUERYFRAME_ENTRY(nsIScrollableFrame)
   NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
   NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
+  NS_QUERYFRAME_ENTRY(nsHTMLScrollFrame)
 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
 
 //----------nsXULScrollFrame-------------------------------------------
 
 nsXULScrollFrame* NS_NewXULScrollFrame(PresShell* aPresShell,
                                        ComputedStyle* aStyle, bool aIsRoot,
                                        bool aClipAllDescendants) {
   return new (aPresShell) nsXULScrollFrame(aStyle, aPresShell->GetPresContext(),
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -466,18 +466,19 @@ class ScrollFrameHelper : public nsIRefl
   void MarkNotRecentlyScrolled();
   nsExpirationState* GetExpirationState() { return &mActivityExpirationState; }
 
   void SetTransformingByAPZ(bool aTransforming) {
     if (mTransformingByAPZ && !aTransforming) {
       PostScrollEndEvent();
     }
     mTransformingByAPZ = aTransforming;
-    if (!mozilla::css::TextOverflow::HasClippedOverflow(mOuter)) {
-      // If the block has some text-overflow stuff we should kick off a paint
+    if (!mozilla::css::TextOverflow::HasClippedTextOverflow(mOuter) ||
+        mozilla::css::TextOverflow::HasBlockEllipsis(mScrolledFrame)) {
+      // If the block has some overflow marker stuff we should kick off a paint
       // because we have special behaviour for it when APZ scrolling is active.
       mOuter->SchedulePaint();
     }
   }
   bool IsTransformingByAPZ() const { return mTransformingByAPZ; }
   void SetScrollableByAPZ(bool aScrollable);
   void SetZoomableByAPZ(bool aZoomable);
   void SetHasOutOfFlowContentInsideFilter();
--- a/layout/generic/nsLineBox.h
+++ b/layout/generic/nsLineBox.h
@@ -262,16 +262,21 @@ class nsLineBox final : public nsLineLin
   }
   bool HasMarker() const { return mFlags.mHasMarker; }
 
   // mHadFloatPushed bit
   void SetHadFloatPushed() { mFlags.mHadFloatPushed = true; }
   void ClearHadFloatPushed() { mFlags.mHadFloatPushed = false; }
   bool HadFloatPushed() const { return mFlags.mHadFloatPushed; }
 
+  // mHasLineClampEllipsis bit
+  void SetHasLineClampEllipsis() { mFlags.mHasLineClampEllipsis = true; }
+  void ClearHasLineClampEllipsis() { mFlags.mHasLineClampEllipsis = false; }
+  bool HasLineClampEllipsis() const { return mFlags.mHasLineClampEllipsis; }
+
  private:
   // Add a hash table for fast lookup when the line has more frames than this.
   static const uint32_t kMinChildCountForHashtable = 200;
 
   /**
    * Take ownership of aFromLine's hash table and remove the frames that
    * stay on aFromLine from it, i.e. aFromLineNewCount frames starting with
    * mFirstChild.  This method is used to optimize moving a large number
@@ -603,16 +608,23 @@ class nsLineBox final : public nsLineLin
     bool mEmptyCacheState : 1;
     // mHasMarker indicates that this is an inline line whose block's
     // ::marker is adjacent to this line and non-empty.
     bool mHasMarker : 1;
     // Indicates that this line *may* have a placeholder for a float
     // that was pushed to a later column or page.
     bool mHadFloatPushed : 1;
     bool mHasHashedFrames : 1;
+    // Indicates that this line is the one identified by an ancestor block
+    // with -webkit-line-clamp on its legacy flex container, and that subsequent
+    // lines under that block are "clamped" away, and therefore we need to
+    // render a 'text-overflow: ellipsis'-like marker in this line.  At most one
+    // line in the set of lines found by LineClampLineIterator for a given
+    // block will have this flag set.
+    bool mHasLineClampEllipsis : 1;
     StyleClear mBreakType;
   };
 
   struct ExtraData {
     explicit ExtraData(const nsRect& aBounds)
         : mOverflowAreas(aBounds, aBounds) {}
     nsOverflowAreas mOverflowAreas;
   };
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -907,17 +907,18 @@ void nsLineLayout::ReflowFrame(nsIFrame*
             // there's something like a ::marker before or what not. We actually
             // need to place them now, because they're pretty nasty and they
             // create continuations that are in flow and not a kid of the
             // previous continuation's parent. We don't want the deferred reflow
             // of the letter frame to kill a continuation after we've stored it
             // in the line layout data structures. See bug 1490281 to fix the
             // underlying issue. When that's fixed this check should be removed.
             !outOfFlowFrame->IsLetterFrame() &&
-            !GetOutermostLineLayout()->mBlockRI->mFlags.mCanHaveTextOverflow) {
+            !GetOutermostLineLayout()
+                 ->mBlockRI->mFlags.mCanHaveOverflowMarkers) {
           // We'll do this at the next break opportunity.
           RecordNoWrapFloat(outOfFlowFrame);
         } else {
           TryToPlaceFloat(outOfFlowFrame);
         }
       }
     } else if (isText) {
       // Note non-empty text-frames for inline frame compatibility hackery
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2952,16 +2952,17 @@ nsStyleDisplay::nsStyleDisplay(const Doc
       mOverscrollBehaviorY(StyleOverscrollBehavior::Auto),
       mOverflowAnchor(StyleOverflowAnchor::Auto),
       mScrollSnapType(
           {StyleScrollSnapAxis::Both, StyleScrollSnapStrictness::None}),
       mScrollSnapPointsX(eStyleUnit_None),
       mScrollSnapPointsY(eStyleUnit_None),
       mScrollSnapDestination(
           {LengthPercentage::Zero(), LengthPercentage::Zero()}),
+      mLineClamp(0),
       mBackfaceVisibility(NS_STYLE_BACKFACE_VISIBILITY_VISIBLE),
       mTransformStyle(NS_STYLE_TRANSFORM_STYLE_FLAT),
       mTransformBox(StyleGeometryBox::BorderBox),
       mTransformOrigin{LengthPercentage::FromPercentage(0.5),
                        LengthPercentage::FromPercentage(0.5),
                        {0.}},
       mChildPerspective(StylePerspective::None()),
       mPerspectiveOrigin(Position::FromPercentage(0.5f)),
@@ -3017,16 +3018,17 @@ nsStyleDisplay::nsStyleDisplay(const nsS
       mScrollBehavior(aSource.mScrollBehavior),
       mOverscrollBehaviorX(aSource.mOverscrollBehaviorX),
       mOverscrollBehaviorY(aSource.mOverscrollBehaviorY),
       mScrollSnapType(aSource.mScrollSnapType),
       mScrollSnapPointsX(aSource.mScrollSnapPointsX),
       mScrollSnapPointsY(aSource.mScrollSnapPointsY),
       mScrollSnapDestination(aSource.mScrollSnapDestination),
       mScrollSnapCoordinate(aSource.mScrollSnapCoordinate),
+      mLineClamp(aSource.mLineClamp),
       mBackfaceVisibility(aSource.mBackfaceVisibility),
       mTransformStyle(aSource.mTransformStyle),
       mTransformBox(aSource.mTransformBox),
       mSpecifiedTransform(aSource.mSpecifiedTransform),
       mSpecifiedRotate(aSource.mSpecifiedRotate),
       mSpecifiedTranslate(aSource.mSpecifiedTranslate),
       mSpecifiedScale(aSource.mSpecifiedScale),
       // We intentionally leave mIndividualTransform as null, is the caller's
@@ -3214,16 +3216,20 @@ nsChangeHint nsStyleDisplay::CalcDiffere
       hint |= nsChangeHint_ReflowHintsForFloatAreaChange;
     } else {
       // shape-outside or shape-margin or shape-image-threshold changed,
       // but we don't need to reflow because we're not floating.
       hint |= nsChangeHint_NeutralChange;
     }
   }
 
+  if (mLineClamp != aNewData.mLineClamp) {
+    hint |= NS_STYLE_HINT_REFLOW;
+  }
+
   if (mVerticalAlign != aNewData.mVerticalAlign) {
     // XXX Can this just be AllReflowHints + RepaintFrame, and be included in
     // the block below?
     hint |= NS_STYLE_HINT_REFLOW;
   }
 
   // XXX the following is conservative, for now: changing float breaking
   // shouldn't necessarily require a repaint, reflow should suffice.
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1353,16 +1353,22 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
     return aCoord.IsLengthPercentage() &&
            aCoord.AsLengthPercentage().HasPercent();
   }
 };
 
 struct nsStyleTextOverflowSide {
   nsStyleTextOverflowSide() : mType(NS_STYLE_TEXT_OVERFLOW_CLIP) {}
 
+  static nsStyleTextOverflowSide Ellipsis() {
+    nsStyleTextOverflowSide side;
+    side.mType = NS_STYLE_TEXT_OVERFLOW_ELLIPSIS;
+    return side;
+  }
+
   bool operator==(const nsStyleTextOverflowSide& aOther) const {
     return mType == aOther.mType && (mType != NS_STYLE_TEXT_OVERFLOW_STRING ||
                                      mString == aOther.mString);
   }
   bool operator!=(const nsStyleTextOverflowSide& aOther) const {
     return !(*this == aOther);
   }
 
@@ -1896,16 +1902,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   mozilla::StyleOverscrollBehavior mOverscrollBehaviorY;
   mozilla::StyleOverflowAnchor mOverflowAnchor;
   mozilla::StyleScrollSnapAlign mScrollSnapAlign;
   mozilla::StyleScrollSnapType mScrollSnapType;
   nsStyleCoord mScrollSnapPointsX;
   nsStyleCoord mScrollSnapPointsY;
   mozilla::Position mScrollSnapDestination;
   nsTArray<mozilla::Position> mScrollSnapCoordinate;
+  uint32_t mLineClamp;
 
   // mSpecifiedTransform is the list of transform functions as
   // specified, or null to indicate there is no transform.  (inherit or
   // initial are replaced by an actual list of transform functions, or
   // null, as appropriate.)
   uint8_t mBackfaceVisibility;
   uint8_t mTransformStyle;
   StyleGeometryBox mTransformBox;
--- a/layout/style/res/ua.css
+++ b/layout/style/res/ua.css
@@ -212,16 +212,17 @@
   /* Flex container */
   flex-direction: inherit;
   flex-wrap: inherit;
   /* -webkit-box container (aliased from -webkit versions to -moz versions) */
   -moz-box-orient: inherit;
   -moz-box-direction: inherit;
   -moz-box-pack: inherit;
   -moz-box-align: inherit;
+  -webkit-line-clamp: inherit;
   /* Grid container */
   grid-auto-columns: inherit;
   grid-auto-rows: inherit;
   grid-auto-flow: inherit;
   grid-column-gap: inherit;
   grid-row-gap: inherit;
   grid-template-areas: inherit;
   grid-template-columns: inherit;
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -8536,16 +8536,27 @@ if (IsCSSPropertyPrefEnabled("layout.css
     domProp: "MozColumnSpan",
     inherited: false,
     type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
     alias_for: "column-span",
     subproperties: [ "column-span" ]
   };
 }
 
+if (IsCSSPropertyPrefEnabled("layout.css.webkit-line-clamp.enabled")) {
+  gCSSProperties["-webkit-line-clamp"] = {
+    domProp: "webkitLineClamp",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "none" ],
+    other_values: [ "1", "2" ],
+    invalid_values: [ "auto", "0", "-1" ],
+  };
+}
+
 if (false) {
   // TODO These properties are chrome-only, and are not exposed via CSSOM.
   // We may still want to find a way to test them. See bug 1206999.
   gCSSProperties["-moz-window-shadow"] = {
     //domProp: "MozWindowShadow",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "default" ],
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -293,16 +293,17 @@ var supported_properties = {
     "vertical-align": [ test_length_transition, test_percent_transition,
                         test_length_unclamped, test_percent_unclamped ],
     "visibility": [ test_visibility_transition ],
     "width": [ test_length_transition, test_percent_transition,
                test_length_percent_calc_transition,
                test_length_clamped, test_percent_clamped ],
     "word-spacing": [ test_length_transition, test_length_unclamped ],
     "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ],
+    "-webkit-line-clamp": [ test_pos_integer_or_none_transition ],
     "-webkit-text-fill-color": [ test_color_transition,
                                  test_currentcolor_transition ],
     "-webkit-text-stroke-color": [ test_color_transition,
                                    test_currentcolor_transition ]
 };
 
 if (IsCSSPropertyPrefEnabled("layout.css.motion-path.enabled")) {
   supported_properties["offset-path"] = [ test_path_function ];
@@ -2249,38 +2250,46 @@ function test_font_weight(prop) {
 
 function test_grid_gap(prop) {
   test_length_transition(prop);
   test_length_clamped(prop);
   test_percent_transition(prop);
   test_percent_clamped(prop);
 }
 
-function test_pos_integer_or_auto_transition(prop) {
+function test_pos_integer_or_keyword_transition(prop, keyword) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "4", "");
   is(cs.getPropertyValue(prop), "4",
      "integer-valued property " + prop + ": computed value before transition");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "11", "");
   is(cs.getPropertyValue(prop), "6",
      "integer-valued property " + prop + ": interpolation of integers");
   check_distance(prop, "4", "6", "12");
-  div.style.setProperty(prop, "auto", "");
-  is(cs.getPropertyValue(prop), "auto",
-     "integer-valued property " + prop + ": auto not interpolable");
+  div.style.setProperty(prop, keyword, "");
+  is(cs.getPropertyValue(prop), keyword,
+     "integer-valued property " + prop + ": " + keyword + " not interpolable");
   div.style.setProperty(prop, "8", "");
   is(cs.getPropertyValue(prop), "8",
      "integer-valued property " + prop + ": computed value before transition");
   div.style.setProperty(prop, "4", "");
   is(cs.getPropertyValue(prop), "7",
      "integer-valued property " + prop + ": interpolation of integers");
   check_distance(prop, "8", "7", "4");
 }
 
+function test_pos_integer_or_auto_transition(prop) {
+  return test_pos_integer_or_keyword_transition(prop, "auto");
+}
+
+function test_pos_integer_or_none_transition(prop) {
+  return test_pos_integer_or_keyword_transition(prop, "none");
+}
+
 function test_integer_at_least_one_clamping(prop) {
   div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "1", "");
   is(cs.getPropertyValue(prop), "1",
      "integer-valued property " + prop + ": flush before clamping test");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "5", "");
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1335,16 +1335,23 @@ VARCACHE_PREF(
 #endif
 VARCACHE_PREF(
   "xul.panel-animations.enabled",
    xul_panel_animations_enabled,
   bool, PREF_VALUE
 )
 #undef PREF_VALUE
 
+// Is support for -webkit-line-clamp enabled?
+VARCACHE_PREF(
+  "layout.css.webkit-line-clamp.enabled",
+  layout_css_webkit_line_clamp_enabled,
+  bool, true
+)
+
 //---------------------------------------------------------------------------
 // JavaScript prefs
 //---------------------------------------------------------------------------
 
 // nsJSEnvironmentObserver observes the memory-pressure notifications and
 // forces a garbage collection and cycle collection when it happens, if the
 // appropriate pref is set.
 #ifdef ANDROID
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -332,16 +332,17 @@ class Longhand(object):
                 "Opacity",
                 "OutlineStyle",
                 "Overflow",
                 "OverflowAnchor",
                 "OverflowClipBox",
                 "OverflowWrap",
                 "OverscrollBehavior",
                 "Percentage",
+                "PositiveIntegerOrNone",
                 "Resize",
                 "SVGOpacity",
                 "SVGPaintOrder",
                 "ScrollSnapAlign",
                 "ScrollSnapAxis",
                 "ScrollSnapStrictness",
                 "ScrollSnapType",
                 "TextAlign",
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2510,17 +2510,17 @@ fn static_assert() {
                           animation-direction animation-fill-mode animation-play-state
                           animation-iteration-count animation-timing-function
                           clear transition-duration transition-delay
                           transition-timing-function transition-property
                           transform-style
                           rotate scroll-snap-points-x scroll-snap-points-y
                           scroll-snap-coordinate -moz-binding will-change
                           offset-path shape-outside
-                          translate scale""" %>
+                          translate scale -webkit-line-clamp""" %>
 <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
     #[inline]
     pub fn generate_combined_transform(&mut self) {
         unsafe { bindings::Gecko_StyleDisplay_GenerateCombinedTransform(&mut self.gecko) };
     }
 
     #[inline]
     pub fn set_display(&mut self, v: longhands::display::computed_value::T) {
@@ -2919,16 +2919,37 @@ fn static_assert() {
         use crate::gecko_bindings::bindings::Gecko_CopyStyleMotions;
         unsafe { Gecko_CopyStyleMotions(&mut self.gecko.mMotion, other.gecko.mMotion.mPtr) };
     }
 
     pub fn reset_offset_path(&mut self, other: &Self) {
         self.copy_offset_path_from(other);
     }
 
+    #[allow(non_snake_case)]
+    pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
+        self.gecko.mLineClamp = match v {
+            Either::First(n) => n.0 as u32,
+            Either::Second(None_) => 0,
+        };
+    }
+
+    ${impl_simple_copy('_webkit_line_clamp', 'mLineClamp')}
+
+    #[allow(non_snake_case)]
+    pub fn clone__webkit_line_clamp(&self) -> longhands::_webkit_line_clamp::computed_value::T {
+        match self.gecko.mLineClamp {
+            0 => Either::Second(None_),
+            n => {
+                debug_assert!(n <= std::i32::MAX as u32);
+                Either::First((n as i32).into())
+            }
+        }
+    }
+
 </%self:impl_trait>
 
 <%def name="simple_image_array_property(name, shorthand, field_name)">
     <%
         image_layers_field = "mImage" if shorthand == "background" else "mMask"
         copy_simple_image_array_property(name, shorthand, image_layers_field, field_name)
     %>
 
--- a/servo/components/style/properties/longhands/box.mako.rs
+++ b/servo/components/style/properties/longhands/box.mako.rs
@@ -665,8 +665,20 @@
     "touch-action",
     "TouchAction",
     "computed::TouchAction::auto()",
     products="gecko",
     gecko_pref="layout.css.touch_action.enabled",
     animation_value_type="discrete",
     spec="https://compat.spec.whatwg.org/#touch-action",
 )}
+
+// Note that we only implement -webkit-line-clamp as a single, longhand
+// property for now, but the spec defines line-clamp as a shorthand for separate
+// max-lines, block-ellipsis, and continue properties.
+${helpers.predefined_type(
+    "-webkit-line-clamp",
+    "PositiveIntegerOrNone",
+    "Either::Second(None_)",
+    gecko_pref="layout.css.webkit-line-clamp.enabled",
+    animation_value_type="Integer",
+    spec="https://drafts.csswg.org/css-overflow-3/#line-clamp",
+)}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -635,16 +635,19 @@ impl ToAnimatedValue for PositiveInteger
 
 impl From<CSSInteger> for PositiveInteger {
     #[inline]
     fn from(int: CSSInteger) -> PositiveInteger {
         GreaterThanOrEqualToOne::<CSSInteger>(int)
     }
 }
 
+/// A computed positive `<integer>` value or `none`.
+pub type PositiveIntegerOrNone = Either<PositiveInteger, None_>;
+
 /// rect(...)
 pub type ClipRect = generics::ClipRect<LengthOrAuto>;
 
 /// rect(...) | auto
 pub type ClipRectOrAuto = Either<ClipRect, Auto>;
 
 /// The computed value of a grid `<track-breadth>`
 pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -7,17 +7,17 @@
 //! TODO(emilio): Enhance docs.
 
 use super::computed::transform::DirectionVector;
 use super::computed::{Context, ToComputedValue};
 use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
 use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize};
 use super::generics::transform::IsParallelTo;
 use super::generics::{self, GreaterThanOrEqualToOne, NonNegative};
-use super::{Auto, CSSFloat, CSSInteger, Either};
+use super::{Auto, CSSFloat, CSSInteger, Either, None_};
 use crate::context::QuirksMode;
 use crate::parser::{Parse, ParserContext};
 use crate::values::serialize_atom_identifier;
 use crate::values::specified::calc::CalcNode;
 use crate::{Atom, Namespace, Prefix};
 use cssparser::{Parser, Token};
 use num_traits::{One, Zero};
 use std::f32;
@@ -588,16 +588,19 @@ impl Parse for PositiveInteger {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
         Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne)
     }
 }
 
+/// A specified positive `<integer>` value or `none`.
+pub type PositiveIntegerOrNone = Either<PositiveInteger, None_>;
+
 /// The specified value of a grid `<track-breadth>`
 pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
 
 /// The specified value of a grid `<track-size>`
 pub type TrackSize = GenericTrackSize<LengthPercentage>;
 
 /// The specified value of a grid `<track-list>`
 /// (could also be `<auto-track-list>` or `<explicit-track-list>`)
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-overflow/webkit-line-clamp/webkit-line-clamp-with-line-height.tentative.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[webkit-line-clamp-with-line-height.tentative.html]
-  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/parsing/webkit-line-clamp-invalid.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Overflow: parsing -webkit-line-clamp with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-overflow/#webkit-line-clamp">
+<meta name="assert" content="-webkit-line-clamp supports only the grammar 'none | <integer>'.">
+<meta name="assert" content="Zero or negative max-lines integers are invalid.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("-webkit-line-clamp", 'auto');
+
+test_invalid_value("-webkit-line-clamp", '0');
+test_invalid_value("-webkit-line-clamp", '-5');
+
+test_invalid_value("-webkit-line-clamp", 'none "~"');
+test_invalid_value("-webkit-line-clamp", '1 "~"');
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/parsing/webkit-line-clamp-valid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Overflow: parsing -webkit-line-clamp with valid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-overflow/#webkit-line-clamp">
+<meta name="assert" content="-webkit-line-clamp supports the full grammar 'none | <integer>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("-webkit-line-clamp", 'none');
+
+test_valid_value("-webkit-line-clamp", '1');
+test_valid_value("-webkit-line-clamp", '6');
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-001-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  font: 16px / 32px serif;
+  white-space: pre;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-005-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4…</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-006-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4…</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-007-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4…</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-008-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2…</div><div>Line A
+Line B…</div>Line 一
+Line 二…</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-009-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+.child {
+  font: 24px / 48px serif;
+  color: blue;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<div class="child">Line 3…</div></div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-010-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+.child {
+  font: 24px / 48px serif;
+  color: blue;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<div class="child">Line 3
+Line 4</div>Line 5…</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-011-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+.child {
+  font: 24px / 48px serif;
+  color: blue;
+  padding: 0 4px;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<div class="child">Line 3
+Line 4</div>Line 5…</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-012-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+.child {
+  display: flex;
+  font: 24px / 48px serif;
+  color: blue;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<div class="child">Line 3
+Line 4</div>Line 5…</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-013-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+.child {
+  font: 24px / 48px serif;
+  color: blue;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<table class="child" border="1"><tr><td>Line 3
+Line 4</td></tr></table>Line 5…</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-014-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  direction: rtl;
+}
+</style>
+<div class="clamp">Line 1
+Line 2…</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-015-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: horizontal;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 4px 0;
+  background-color: yellow;
+  writing-mode: vertical-rl;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-016-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2…</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-017-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3…</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-018-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-019-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-020-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3…</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-021-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3…</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-022-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-023-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-024-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-inline-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+Before <div class="clamp">Line 1
+Line 2
+Line …</div> After</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-025-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  width: 11ch;
+}
+.float {
+  float: right;
+  color: orange;
+}
+</style>
+<div class="clamp"><div><div class="float">[f]</div>A B C D…</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-026-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-027-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  background-color: yellow;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<fieldset>Line 3
+Line 4<legend>Line 5
+Line 6</legend></fieldset>Line 7…</div></div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-029-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  background-color: yellow;
+}
+.child {
+  overflow: hidden;
+}
+</style>
+<div class="clamp"><div class="child">Line 1
+Line 2
+Line 3…</div></div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-030-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  background-color: yellow;
+  border: none;
+  overflow: hidden;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3…</div></div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-031-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+.big {
+  font-weight: bold;
+  border-top: 2px solid blue;
+  padding: 20px 0;
+}
+</style>
+<div class="clamp"><div class="item">Line 1
+Line 2
+Line 3 <span class="big">BIG</span>…</div></div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/reference/webkit-line-clamp-032-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Reference</title>
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+…</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-001.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp when not display:-webkit-box</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-001-ref.html">
+<meta name="assert" content="-webkit-line-clamp should not apply to an element that is not a flex item for a legacy -webkit-box or -webkit-inline-box flex container.">
+<style>
+.clamp {
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-002.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp when not -webkit-box-orient:vertical</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-001-ref.html">
+<meta name="assert" content="-webkit-line-clamp should not apply to an element whose parent is not -webkit-box-orient:vertical.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-003.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with fewer lines than specified</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-001-ref.html">
+<meta name="assert" content="-webkit-line-clamp should not have an effect on an element with fewer lines than specified.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 6;
+  font: 16px / 32px serif;
+  white-space: pre;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-004.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with exactly as many lines as specified</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-001-ref.html">
+<meta name="assert" content="-webkit-line-clamp should not have an effect on an element with exactly as many lines as specified.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 5;
+  font: 16px / 32px serif;
+  white-space: pre;
+  background-color: yellow;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-005.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with more lines than specified</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-005-ref.html">
+<meta name="assert" content="-webkit-line-clamp should clamp to the specified number of lines.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 4;
+  font: 16px / 32px serif;
+  white-space: pre;
+  background-color: yellow;
+  padding: 0 4px;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-006.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: sizing of -webkit-line-clamp affected elements</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-006-ref.html">
+<meta name="assert" content="-webkit-line-clamp should size the element to the clamped number of lines.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 4;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-007.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp applied to a non-anonymous flex item</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-007-ref.html">
+<meta name="assert" content="-webkit-line-clamp should apply to a flex item in a -webkit-box or -webkit-inline-box flex container.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 4;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-008.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp applied to all flex items</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-008-ref.html">
+<meta name="assert" content="-webkit-line-clamp should apply to all flex items in a -webkit-box or -webkit-inline-box flex container independently.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+}
+div {
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3</div><div>Line A
+Line B
+Line C</div>Line 一
+Line 二
+Line 三</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-009.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with non-BFC block children in flex item</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-009-ref.html">
+<meta name="assert" content="-webkit-line-clamp should count lines in non-BFC block children of the flex items.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.child {
+  font: 24px / 48px serif;
+  color: blue;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<div class="child">Line 3
+Line 4</div></div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-010.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with non-BFC block children in flex item</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-010-ref.html">
+<meta name="assert" content="-webkit-line-clamp should count lines in non-BFC block children of the flex items.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 5;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.child {
+  font: 24px / 48px serif;
+  color: blue;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<div class="child">Line 3
+Line 4</div>Line 5
+Line 6</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-011.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with BFC child in flex item</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-011-ref.html">
+<meta name="assert" content="-webkit-line-clamp should skip lines in BFC children of the flex items.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.child {
+  overflow: auto;
+  font: 24px / 48px serif;
+  color: blue;
+  padding: 0 4px;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<div class="child">Line 3
+Line 4</div>Line 5
+Line 6</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-012.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with flex container child in flex item</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-012-ref.html">
+<meta name="assert" content="-webkit-line-clamp should skip lines in independent formatting contexts in the flex items.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.child {
+  display: flex;
+  font: 24px / 48px serif;
+  color: blue;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<div class="child">Line 3
+Line 4</div>Line 5
+Line 6</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-013.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with table child in flex item</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-013-ref.html">
+<meta name="assert" content="-webkit-line-clamp should skip lines in independent formatting contexts in the flex items.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.child {
+  font: 24px / 48px serif;
+  color: blue;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<table class="child" border="1"><tr><td>Line 3
+Line 4</td></tr></table>Line 5
+Line 6</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-014.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp on RTL flex item</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-014-ref.html">
+<meta name="assert" content="-webkit-line-clamp should place the ellipsis appropriately in RTL content.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+  direction: rtl;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-015.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp on vertical writing mode flex item when -webkit-box-orient:horizontal</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-015-ref.html">
+<meta name="assert" content="-webkit-line-clamp should not apply to a -webkit-box-orient:horizontal flex item even if using a vertical writing mode.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: horizontal;
+  -webkit-line-clamp: 2;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 4px 0;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+  writing-mode: vertical-rl;
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-016.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp value on flex child</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-016-ref.html">
+<meta name="assert" content="-webkit-line-clamp value on the flex child should be ignored.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.child {
+  -webkit-line-clamp: 3;
+}
+</style>
+<div class="clamp"><div class="child">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-017.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: Dynamically changing -webkit-line-clamp</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-017-ref.html">
+<meta name="assert" content="Dynamically changing -webkit-line-clamp on an element from none to a number less than the number of lines should apply the clamping.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
+<script>
+window.onload = function() {
+  document.querySelector(".clamp").style.webkitLineClamp = 3;
+  document.documentElement.className = "";
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-018.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: Dynamically changing -webkit-line-clamp</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-018-ref.html">
+<meta name="assert" content="Dynamically changing -webkit-line-clamp on an element from a number to none remove the clamping.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
+<script>
+window.onload = function() {
+  document.querySelector(".clamp").style.webkitLineClamp = "none";
+  document.documentElement.className = "";
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-019.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: Dynamically changing -webkit-line-clamp</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-019-ref.html">
+<meta name="assert" content="Dynamically changing -webkit-line-clamp on an element from a number less than to a number greater than the number of lines.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
+<script>
+window.onload = function() {
+  document.querySelector(".clamp").style.webkitLineClamp = 6;
+  document.documentElement.className = "";
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-020.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: Dynamically changing -webkit-line-clamp</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-020-ref.html">
+<meta name="assert" content="Dynamically changing -webkit-line-clamp on an element from a number greater than to a number less than the number of lines.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 6;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
+<script>
+window.onload = function() {
+  document.querySelector(".clamp").style.webkitLineClamp = 3;
+  document.documentElement.className = "";
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-021.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: Dynamically changing contents of a -webkit-line-clamp affected element</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-021-ref.html">
+<meta name="assert" content="Dynamically changing contents of a -webkit-line-clamp element so that it exceeds the specified number of lines should start clamping.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2</div>
+<p>Following content.</p>
+<script>
+window.onload = function() {
+  document.querySelector(".clamp").textContent = `Line 1
+Line 2
+Line 3
+Line 4
+Line 5`;
+  document.documentElement.className = "";
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-022.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: Dynamically changing contents of a -webkit-line-clamp affected element</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-022-ref.html">
+<meta name="assert" content="Dynamically changing contents of a -webkit-line-clamp element so that it no longer exceeds the specified number of lines should stop clamping.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
+<script>
+window.onload = function() {
+  document.querySelector(".clamp").textContent = `Line 1
+Line 2`;
+  document.documentElement.className = "";
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-023.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: Dynamically changing contents of a -webkit-line-clamp affected element</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-023-ref.html">
+<meta name="assert" content="Dynamically changing contents of a -webkit-line-clamp element so that it matches the specified number of lines should stop clamping.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
+<script>
+window.onload = function() {
+  document.querySelector(".clamp").textContent = `Line 1
+Line 2
+Line 3`;
+  document.documentElement.className = "";
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-024.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp on a display:-webkit-inline-box container</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-024-ref.html">
+<meta name="assert" content="-webkit-line-clamp should apply to display:-webkit-inline-box containers.">
+<style>
+.clamp {
+  display: -webkit-inline-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+Before <div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div> After</div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-025.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with floats</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-025-ref.html">
+<meta name="assert" content="A -webkit-line-clamp induced ellipsis is placed after shrinking the line due to floats.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 1;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  width: 11ch;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.float {
+  float: right;
+  color: orange;
+}
+</style>
+<div class="clamp"><div><div class="float">[f]</div>A B C D
+E F G H</div></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-026.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>CSS Overflow: Dynamically changing -webkit-box-orient when -webkit-line-clamp applies</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-026-ref.html">
+<meta name="assert" content="Dynamically changing a -webkit-line-clamp affected element to be -webkit-box-orient:horizontal should cause the line clamp to be removed.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div>
+<p>Following content.</p>
+<script>
+window.onload = function() {
+  document.querySelector(".clamp").style.webkitBoxOrient = "horizontal";
+  document.documentElement.className = "";
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-027.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp and fieldset</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-027-ref.html">
+<meta name="assert" content="-webkit-line-clamp should skip over fieldsets.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2<fieldset>Line 3
+Line 4<legend>Line 5
+Line 6</legend></fieldset>Line 7
+Line 8</div></div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-029.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp and scrollable items</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-029-ref.html">
+<meta name="assert" content="-webkit-line-clamp should apply to flex items that are scrollable.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.child {
+  overflow: hidden;
+}
+</style>
+<div class="clamp"><div class="child">Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div></div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-030.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp and scrollable container</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-030-ref.html">
+<meta name="assert" content="-webkit-line-clamp should apply to flex items when the container is scrollable.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px monospace;
+  white-space: pre;
+  background-color: yellow;
+  overflow: hidden;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3
+Line 4
+Line 5</div></div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-031.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp and a line with inlines of different heights</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-031-ref.html">
+<meta name="assert" content="-webkit-line-clamp should clamp to the bottom of the line box.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+.big {
+  font-weight: bold;
+  border-top: 2px solid blue;
+  border-bottom: 2px solid blue;
+  padding: 20px 0;
+}
+</style>
+<div class="clamp"><div>Line 1
+Line 2
+Line 3 <span class="big">BIG</span>
+Line 4
+Line 5</div></div>
+<p>Following content.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/webkit-line-clamp-032.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Overflow: -webkit-line-clamp with an ellipsis on a blank line</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
+<link rel="match" href="reference/webkit-line-clamp-032-ref.html">
+<meta name="assert" content="-webkit-line-clamp should render an ellipsis on a blank line.">
+<style>
+.clamp {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 4;
+  font: 16px / 32px serif;
+  white-space: pre;
+  padding: 0 4px;
+  background-color: yellow;
+  overflow: hidden; /* can be removed once implementations update their old -webkit-line-clamp implementations */
+}
+</style>
+<div class="clamp">Line 1
+Line 2
+Line 3
+
+Line 5</div>