Bug 215083: Implement content: url(..) for elements. r?tnikkel,dholbert draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 16 May 2018 20:56:38 +0200
changeset 811312 6fd25d53776fefdded81715dedbacc78453902c5
parent 811244 65df1f8bb3cb7c95cadc91b542c1a8b9c11309ac
push id114261
push userbmo:emilio@crisal.io
push dateWed, 27 Jun 2018 12:32:10 +0000
reviewerstnikkel, dholbert
bugs215083
milestone63.0a1
Bug 215083: Implement content: url(..) for elements. r?tnikkel,dholbert Dynamic changes are handled correctly because content property changes already cause a reframe. This implements the same bits as Blink / WebKit do (single content item which is an image, otherwise gets ignored), except for the edge cases where you use this on an image. In order to handle the edge cases right, we completely isolate the nsImageLoadingContent usage based on `mKind`. Blink's and WebKit's behavior there makes no sense and it's erratic, what I implemented is consistent (we apply to images as long as they don't generate a box, and we don't look at alt text or broken icons), though I'll update to whatever the WG decides in https://github.com/w3c/csswg-drafts/issues/2831 / https://github.com/w3c/csswg-drafts/issues/2832. I don't think it matters in terms of web compat in any case. MozReview-Commit-ID: JUurhC60hWr
layout/base/nsCSSFrameConstructor.cpp
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/css/css-content/element-replacement.html.ini
testing/web-platform/tests/css/css-content/element-replacement-alt-ref.html
testing/web-platform/tests/css/css-content/element-replacement-alt.html
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -315,16 +315,19 @@ nsIFrame*
 NS_NewSliderFrame (nsIPresShell* aPresShell, ComputedStyle* aStyle);
 
 nsIFrame*
 NS_NewScrollbarFrame (nsIPresShell* aPresShell, ComputedStyle* aStyle);
 
 nsIFrame*
 NS_NewScrollbarButtonFrame (nsIPresShell* aPresShell, ComputedStyle* aStyle);
 
+nsIFrame*
+NS_NewImageFrameForContentProperty(nsIPresShell*, ComputedStyle*);
+
 
 #ifdef NOISY_FINDFRAME
 static int32_t FFWC_totalCount=0;
 static int32_t FFWC_doLoop=0;
 static int32_t FFWC_doSibling=0;
 static int32_t FFWC_recursions=0;
 static int32_t FFWC_nextInFlows=0;
 #endif
@@ -5615,16 +5618,27 @@ ShouldSuppressFrameInNonOpenDetails(cons
       !aChild->IsGeneratedContentContainerForBefore() &&
       !aChild->IsGeneratedContentContainerForAfter()) {
     return false;
   }
 
   return true;
 }
 
+static bool
+ShouldCreateImageFrameForContent(ComputedStyle& aStyle)
+{
+  auto& content = *aStyle.StyleContent();
+  if (content.ContentCount() != 1) {
+    return false;
+  }
+
+  return content.ContentAt(0).GetType() == eStyleContentType_Image;
+}
+
 void
 nsCSSFrameConstructor::AddFrameConstructionItemsInternal(nsFrameConstructorState& aState,
                                                          nsIContent* aContent,
                                                          nsContainerFrame* aParentFrame,
                                                          bool aSuppressWhiteSpaceOptimizations,
                                                          ComputedStyle* aComputedStyle,
                                                          uint32_t aFlags,
                                                          nsTArray<nsIAnonymousContentCreator::ContentInfo>* aAnonChildren,
@@ -5784,16 +5798,22 @@ nsCSSFrameConstructor::AddFrameConstruct
                          computedStyle);
     }
 
     // Now check for XUL display types
     if (!data) {
       data = FindXULDisplayData(display, element, computedStyle);
     }
 
+    if (!data && ShouldCreateImageFrameForContent(*computedStyle)) {
+      static const FrameConstructionData sImgData =
+        SIMPLE_FCDATA(NS_NewImageFrameForContentProperty);
+      data = &sImgData;
+    }
+
     // And general display types
     if (!data) {
       data = FindDisplayData(display, element, computedStyle);
     }
 
     MOZ_ASSERT(data, "Should have frame construction data now");
 
     if (data->mBits & FCDATA_SUPPRESS_FRAME) {
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -125,25 +125,34 @@ inline bool HaveFixedSize(const ReflowIn
   // auto (especially for intrinsic width calculations and for heights).
   return aReflowInput.mStylePosition->mHeight.ConvertsToLength() &&
          aReflowInput.mStylePosition->mWidth.ConvertsToLength();
 }
 
 nsIFrame*
 NS_NewImageFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
 {
-  return new (aPresShell) nsImageFrame(aStyle);
+  return new (aPresShell) nsImageFrame(aStyle, nsImageFrame::Kind::ImageElement);
+}
+
+nsIFrame*
+NS_NewImageFrameForContentProperty(nsIPresShell* aPresShell,
+                                   ComputedStyle* aStyle)
+{
+  return new (aPresShell) nsImageFrame(aStyle,
+                                       nsImageFrame::Kind::ContentProperty);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
 
-nsImageFrame::nsImageFrame(ComputedStyle* aStyle, ClassID aID)
+nsImageFrame::nsImageFrame(ComputedStyle* aStyle, ClassID aID, Kind aKind)
   : nsAtomicContainerFrame(aStyle, aID)
   , mComputedSize(0, 0)
   , mIntrinsicRatio(0, 0)
+  , mKind(aKind)
   , mDisplayingIcon(false)
   , mFirstFrameComplete(false)
   , mReflowCallbackPosted(false)
   , mForceSyncDecoding(false)
 {
   EnableVisibilityTracking();
 
   // We assume our size is not constrained and we haven't gotten an
@@ -198,30 +207,35 @@ nsImageFrame::DestroyFrom(nsIFrame* aDes
     mReflowCallbackPosted = false;
   }
 
   // Tell our image map, if there is one, to clean up
   // This causes the nsImageMap to unregister itself as
   // a DOM listener.
   DisconnectMap();
 
-  // set the frame to null so we don't send messages to a dead object.
-  if (mListener) {
+  MOZ_ASSERT(mListener);
+
+  if (mKind == Kind::ImageElement) {
+    MOZ_ASSERT(!mContentURLRequest);
     nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-    if (imageLoader) {
-      // Notify our image loading content that we are going away so it can
-      // deregister with our refresh driver.
-      imageLoader->FrameDestroyed(this);
+    MOZ_ASSERT(imageLoader);
 
-      imageLoader->RemoveNativeObserver(mListener);
+    // Notify our image loading content that we are going away so it can
+    // deregister with our refresh driver.
+    imageLoader->FrameDestroyed(this);
+    imageLoader->RemoveNativeObserver(mListener);
+  } else {
+    if (mContentURLRequest) {
+      mContentURLRequest->Cancel(NS_BINDING_ABORTED);
     }
-
-    mListener->SetFrame(nullptr);
   }
 
+  // set the frame to null so we don't send messages to a dead object.
+  mListener->SetFrame(nullptr);
   mListener = nullptr;
 
   // If we were displaying an icon, take ourselves off the list
   if (mDisplayingIcon)
     gIconLoad->RemoveIconObserver(this);
 
   nsAtomicContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
 }
@@ -261,27 +275,31 @@ nsImageFrame::Init(nsIContent*       aCo
 {
   nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow);
 
   mListener = new nsImageListener(this);
 
   if (!gIconLoad)
     LoadIcons(PresContext());
 
-  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
-  if (!imageLoader) {
-    MOZ_CRASH("Why do we have an nsImageFrame here at all?");
+  if (mKind == Kind::ImageElement) {
+    nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
+    MOZ_ASSERT(imageLoader);
+    imageLoader->AddNativeObserver(mListener);
+    // We have a PresContext now, so we need to notify the image content node
+    // that it can register images.
+    imageLoader->FrameCreated(this);
+  } else {
+    if (auto* proxy = StyleContent()->ContentAt(0).GetImage()) {
+      proxy->Clone(mListener,
+                   mContent->OwnerDoc(),
+                   getter_AddRefs(mContentURLRequest));
+    }
   }
 
-  imageLoader->AddNativeObserver(mListener);
-
-  // We have a PresContext now, so we need to notify the image content node that
-  // it can register images.
-  imageLoader->FrameCreated(this);
-
   // Give image loads associated with an image frame a small priority boost.
   if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
     uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT;
 
     // Increase load priority further if intrinsic size might be important for layout.
     if (!HaveSpecifiedSize(StylePosition())) {
       categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY;
     }
@@ -325,17 +343,19 @@ nsImageFrame::UpdateIntrinsicSize(imgICo
     return false;
 
   IntrinsicSize oldIntrinsicSize = mIntrinsicSize;
   mIntrinsicSize = IntrinsicSize();
 
   // Set intrinsic size to match aImage's reported intrinsic width & height.
   nsSize intrinsicSize;
   if (NS_SUCCEEDED(aImage->GetIntrinsicSize(&intrinsicSize))) {
-    ScaleIntrinsicSizeForDensity(*mContent, intrinsicSize);
+    if (mKind == Kind::ImageElement) {
+      ScaleIntrinsicSizeForDensity(*mContent, intrinsicSize);
+    }
     // If the image has no intrinsic width, intrinsicSize.width will be -1, and
     // we can leave mIntrinsicSize.width at its default value of eStyleUnit_None.
     // Otherwise we use intrinsicSize.width. Height works the same way.
     if (intrinsicSize.width != -1)
       mIntrinsicSize.width.SetCoordValue(intrinsicSize.width);
     if (intrinsicSize.height != -1)
       mIntrinsicSize.height.SetCoordValue(intrinsicSize.height);
   } else {
@@ -406,19 +426,22 @@ nsImageFrame::GetSourceToDestTransform(n
 }
 
 // This function checks whether the given request is the current request for our
 // mContent.
 bool
 nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const
 {
   // Default to pending load in case of errors
+  if (mKind == Kind::ContentProperty) {
+    MOZ_ASSERT(aRequest == mContentURLRequest);
+    return false;
+  }
+
   nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
-  NS_ASSERTION(imageLoader, "No image loading content?");
-
   int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
   imageLoader->GetRequestType(aRequest, &requestType);
 
   return requestType != nsIImageLoadingContent::CURRENT_REQUEST;
 }
 
 nsRect
 nsImageFrame::SourceRectToDest(const nsIntRect& aRect)
@@ -556,24 +579,25 @@ nsImageFrame::Notify(imgIRequest* aReque
 static bool
 SizeIsAvailable(imgIRequest* aRequest)
 {
   if (!aRequest)
     return false;
 
   uint32_t imageStatus = 0;
   nsresult rv = aRequest->GetImageStatus(&imageStatus);
-
   return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE);
 }
 
 nsresult
 nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
 {
-  if (!aImage) return NS_ERROR_INVALID_ARG;
+  if (!aImage) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   /* Get requested animation policy from the pres context:
    *   normal = 0
    *   one frame = 1
    *   one loop = 2
    */
   nsPresContext *presContext = PresContext();
   aImage->SetAnimationMode(presContext->ImageAnimationMode());
@@ -820,56 +844,67 @@ nsImageFrame::PredictedDestRect(const ns
                                               StylePosition());
 }
 
 void
 nsImageFrame::EnsureIntrinsicSizeAndRatio()
 {
   // If mIntrinsicSize.width and height are 0, then we need to update from the
   // image container.
-  if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord &&
-      mIntrinsicSize.width.GetCoordValue() == 0 &&
-      mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
-      mIntrinsicSize.height.GetCoordValue() == 0) {
+  if (mIntrinsicSize.width.GetUnit() != eStyleUnit_Coord ||
+      mIntrinsicSize.width.GetCoordValue() != 0 ||
+      mIntrinsicSize.height.GetUnit() != eStyleUnit_Coord ||
+      mIntrinsicSize.height.GetCoordValue() != 0) {
+    return;
+  }
 
-    if (mImage) {
-      UpdateIntrinsicSize(mImage);
-      UpdateIntrinsicRatio(mImage);
-    } else {
-      // image request is null or image size not known, probably an
-      // invalid image specified
-      if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
-        bool imageInvalid = false;
+  if (mImage) {
+    UpdateIntrinsicSize(mImage);
+    UpdateIntrinsicRatio(mImage);
+    return;
+  }
+
+  // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit
+  // and Blink behave differently here for content: url(..), for now adapt to
+  // Blink's behavior.
+  const bool mayDisplayBrokenIcon =
+    mKind == Kind::ImageElement &&
+    !(GetStateBits() & NS_FRAME_GENERATED_CONTENT);
+
+  if (!mayDisplayBrokenIcon) {
+    return;
+  }
 
-        // check for broken images. valid null images (eg. img src="") are
-        // not considered broken because they have no image requests
-        if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
-          uint32_t imageStatus;
-          imageInvalid =
-            NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
-            (imageStatus & imgIRequest::STATUS_ERROR);
-        } else if (nsCOMPtr<nsIImageLoadingContent> loader = do_QueryInterface(mContent)) {
-          // check if images are user-disabled (or blocked for other
-          // reasons)
-          int16_t imageBlockingStatus;
-          loader->GetImageBlockingStatus(&imageBlockingStatus);
-          imageInvalid = imageBlockingStatus != nsIContentPolicy::ACCEPT;
-        }
+  // image request is null or image size not known, probably an
+  // invalid image specified
+  bool imageInvalid = false;
 
-        // invalid image specified. make the image big enough for the "broken" icon
-        if (imageInvalid) {
-          nscoord edgeLengthToUse =
-            nsPresContext::CSSPixelsToAppUnits(
-              ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
-          mIntrinsicSize.width.SetCoordValue(edgeLengthToUse);
-          mIntrinsicSize.height.SetCoordValue(edgeLengthToUse);
-          mIntrinsicRatio.SizeTo(1, 1);
-        }
-      }
-    }
+  // check for broken images. valid null images (eg. img src="") are
+  // not considered broken because they have no image requests
+  if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+    uint32_t imageStatus;
+    imageInvalid =
+      NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
+      (imageStatus & imgIRequest::STATUS_ERROR);
+  } else if (mKind == Kind::ImageElement) {
+    nsCOMPtr<nsIImageLoadingContent> loader = do_QueryInterface(mContent);
+    // check if images are user-disabled (or blocked for other reasons)
+    int16_t imageBlockingStatus;
+    loader->GetImageBlockingStatus(&imageBlockingStatus);
+    imageInvalid = imageBlockingStatus != nsIContentPolicy::ACCEPT;
+  }
+
+  // invalid image specified. make the image big enough for the "broken" icon
+  if (imageInvalid) {
+    nscoord edgeLengthToUse =
+      nsPresContext::CSSPixelsToAppUnits(
+        ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
+    mIntrinsicSize.width.SetCoordValue(edgeLengthToUse);
+    mIntrinsicSize.height.SetCoordValue(edgeLengthToUse);
+    mIntrinsicRatio.SizeTo(1, 1);
   }
 }
 
 /* virtual */
 LogicalSize
 nsImageFrame::ComputeSize(gfxContext *aRenderingContext,
                           WritingMode aWM,
                           const LogicalSize& aCBSize,
@@ -1460,24 +1495,21 @@ nsImageFrame::DisplayAltFeedback(gfxCont
         inner.x += paddedIconSize;
       }
       inner.width -= paddedIconSize;
     }
   }
 
   // If there's still room, display the alt-text
   if (!inner.IsEmpty()) {
-    nsIContent* content = GetContent();
-    if (content) {
-      nsAutoString altText;
-      nsCSSFrameConstructor::GetAlternateTextFor(content->AsElement(),
-                                                 content->NodeInfo()->NameAtom(),
-                                                 altText);
-      DisplayAltText(PresContext(), aRenderingContext, altText, inner);
-    }
+    nsAutoString altText;
+    nsCSSFrameConstructor::GetAlternateTextFor(mContent->AsElement(),
+                                               mContent->NodeInfo()->NameAtom(),
+                                               altText);
+    DisplayAltText(PresContext(), aRenderingContext, altText, inner);
   }
 
   aRenderingContext.Restore();
 
   return result;
 }
 
 #ifdef DEBUG
@@ -1779,24 +1811,26 @@ nsImageFrame::PaintImage(gfxContext& aRe
   }
 
   return result;
 }
 
 already_AddRefed<imgIRequest>
 nsImageFrame::GetCurrentRequest() const
 {
+  if (mKind == Kind::ContentProperty) {
+    return do_AddRef(mContentURLRequest);
+  }
+
+  MOZ_ASSERT(!mContentURLRequest);
+
   nsCOMPtr<imgIRequest> request;
-
   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-  MOZ_ASSERT(imageLoader);
-
   imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                           getter_AddRefs(request));
-
   return request.forget();
 }
 
 void
 nsImageFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                const nsDisplayListSet& aLists)
 {
   if (!IsVisibleForPainting(aBuilder))
@@ -2118,25 +2152,21 @@ nsImageFrame::AttributeChanged(int32_t a
 
   return NS_OK;
 }
 
 void
 nsImageFrame::OnVisibilityChange(Visibility aNewVisibility,
                                  const Maybe<OnNonvisible>& aNonvisibleAction)
 {
-  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-  if (!imageLoader) {
-    MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent");
-    nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
-    return;
+  if (mKind == Kind::ImageElement) {
+    nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+    imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
   }
 
-  imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
-
   if (aNewVisibility == Visibility::APPROXIMATELY_VISIBLE) {
     MaybeDecodeForPredictedSize();
   }
 
   nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
 }
 
 #ifdef DEBUG_FRAME_DUMP
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -175,23 +175,40 @@ public:
                                  InlineMinISizeData *aData) override;
 
   void DisconnectMap();
 
   // nsIReflowCallback
   virtual bool ReflowFinished() override;
   virtual void ReflowCallbackCanceled() override;
 
+  // The kind of frame we are.
+  enum class Kind : uint8_t
+  {
+    // For an nsImageLoadingContent.
+    ImageElement,
+    // For css content: url(..).
+    ContentProperty,
+  };
+
 private:
   friend nsIFrame* NS_NewImageFrame(nsIPresShell*, ComputedStyle*);
-  explicit nsImageFrame(ComputedStyle* aStyle)
-    : nsImageFrame(aStyle, kClassID) {}
+  friend nsIFrame* NS_NewImageFrameForContentProperty(nsIPresShell*, ComputedStyle*);
+
+  explicit nsImageFrame(ComputedStyle* aStyle, Kind aKind)
+    : nsImageFrame(aStyle, kClassID, aKind)
+  { }
+
+  nsImageFrame(ComputedStyle*, ClassID, Kind);
 
 protected:
-  nsImageFrame(ComputedStyle* aStyle, ClassID aID);
+  nsImageFrame(ComputedStyle* aStyle, ClassID aId)
+    : nsImageFrame(aStyle, aId, Kind::ImageElement)
+  { }
+
   virtual ~nsImageFrame();
 
   void EnsureIntrinsicSizeAndRatio();
 
   bool GotInitialReflow() const
   {
     return !HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
   }
@@ -333,22 +350,26 @@ private:
    */
   void InvalidateSelf(const nsIntRect* aLayerInvalidRect,
                       const nsRect* aFrameInvalidRect);
 
   RefPtr<nsImageMap> mImageMap;
 
   RefPtr<nsImageListener> mListener;
 
+  // An image request created for content: url(..).
+  RefPtr<imgRequestProxy> mContentURLRequest;
+
   nsCOMPtr<imgIContainer> mImage;
   nsCOMPtr<imgIContainer> mPrevImage;
   nsSize mComputedSize;
   mozilla::IntrinsicSize mIntrinsicSize;
   nsSize mIntrinsicRatio;
 
+  const Kind mKind;
   bool mDisplayingIcon;
   bool mFirstFrameComplete;
   bool mReflowCallbackPosted;
   bool mForceSyncDecoding;
 
   static nsIIOService* sIOService;
 
   /* loading / broken image icon support */
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -109322,16 +109322,28 @@
       [
        "/css/css-content/attr-case-insensitive-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/css-content/element-replacement-alt.html": [
+    [
+     "/css/css-content/element-replacement-alt.html",
+     [
+      [
+       "/css/css-content/element-replacement-alt-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-content/element-replacement.html": [
     [
      "/css/css-content/element-replacement.html",
      [
       [
        "/css/css-content/element-replacement-ref.html",
        "=="
       ]
@@ -240338,16 +240350,21 @@
      {}
     ]
    ],
    "css/css-content/attr-case-insensitive-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-content/element-replacement-alt-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-content/element-replacement-ref.html": [
     [
      {}
     ]
    ],
    "css/css-content/resources/rect.svg": [
     [
      {}
@@ -501833,16 +501850,24 @@
   "css/css-content/attr-case-insensitive-ref.html": [
    "30577fc39afb6ac028e25be11f363e060c0850b2",
    "support"
   ],
   "css/css-content/attr-case-insensitive.html": [
    "6b6cf2c15295940fb8831d17209635dc4e31cd78",
    "reftest"
   ],
+  "css/css-content/element-replacement-alt-ref.html": [
+   "6c67290991bc0ca57223e65a995054bae04bca0a",
+   "support"
+  ],
+  "css/css-content/element-replacement-alt.html": [
+   "383ba1ffc142ae6d783cc9300b296b83b4b2521f",
+   "reftest"
+  ],
   "css/css-content/element-replacement-ref.html": [
    "f1ad3fca133b1b671e45ae1307fbe9454c40e3ec",
    "support"
   ],
   "css/css-content/element-replacement.html": [
    "f491ddf2b3062ea2f9b616c968c88b9cc95f22eb",
    "reftest"
   ],
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-content/element-replacement.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[element-replacement.html]
-  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-content/element-replacement-alt-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<div style="content: url(broken);"></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-content/element-replacement-alt.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<title>The content CSS property with a broken image doesn't pull the alt attribute from that element</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="match" href="element-replacement-alt-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-content-3/#content-property">
+<div style="content: url(broken);" alt="Alt text">FAIL</div>