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 796235 64deb4e7beea11db8da0119a21b7268d4b902a86
parent 796234 bfd4973918d6140b0c13903332a9b6da44533721
push id110186
push userbmo:emilio@crisal.io
push dateThu, 17 May 2018 09:04:51 +0000
reviewerstnikkel, dholbert
bugs215083, 1149357
milestone62.0a1
Bug 215083: Implement content: url(..) for elements. r?tnikkel,dholbert Take the review request as a feedback request for now. Pretty sure there's a bunch of image tracking stuff that could (should?) be done which is not done. Also need to figure out the right thing to do for animated images and that. That being said, this works. Dynamic changes are handled correctly because content property changes already cause a reframe. The mImage change for intrinsic sizing also fixes bug 1149357, since HTMLImageElement mungles the image natural width otherwise. I can (and will) submit that fix separately. This implements the same bits as Blink / WebKit do (single content item which is an image, otherwise gets ignored). MozReview-Commit-ID: JUurhC60hWr
layout/base/nsCSSFrameConstructor.cpp
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
testing/web-platform/meta/css/css-content/element-replacement.html.ini
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -5638,16 +5638,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,
@@ -5811,16 +5822,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_NewImageFrame);
+      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
@@ -193,29 +193,31 @@ nsImageFrame::DisconnectMap()
 void
 nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
 {
   if (mReflowCallbackPosted) {
     PresShell()->CancelReflowCallback(this);
     mReflowCallbackPosted = false;
   }
 
+  if (mContentURLRequest) {
+    mContentURLRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+  }
+
   // 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) {
-    nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-    if (imageLoader) {
+    if (nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent)) {
       // Notify our image loading content that we are going away so it can
       // deregister with our refresh driver.
       imageLoader->FrameDestroyed(this);
-
       imageLoader->RemoveNativeObserver(mListener);
     }
 
     mListener->SetFrame(nullptr);
   }
 
   mListener = nullptr;
 
@@ -261,27 +263,27 @@ 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 (nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent)) {
+    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->SyncClone(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);
-
   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;
     }
 
@@ -398,17 +400,20 @@ 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
   nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
-  NS_ASSERTION(imageLoader, "No image loading content?");
+  if (!imageLoader) {
+    MOZ_ASSERT(aRequest == mContentURLRequest);
+    return false;
+  }
 
   int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
   imageLoader->GetRequestType(aRequest, &requestType);
 
   return requestType != nsIImageLoadingContent::CURRENT_REQUEST;
 }
 
 nsRect
@@ -547,24 +552,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());
@@ -810,29 +816,30 @@ nsImageFrame::EnsureIntrinsicSizeAndRati
       mIntrinsicSize.height.GetCoordValue() == 0) {
 
     if (mImage) {
       UpdateIntrinsicSize(mImage);
       UpdateIntrinsicRatio(mImage);
     } else {
       // image request is null or image size not known, probably an
       // invalid image specified
+      //
+      // TODO(emilio): Do we really want to do this for content: url(..)?
       if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
         bool imageInvalid = false;
 
         // 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)
+          // 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 =
@@ -1795,23 +1802,22 @@ nsImageFrame::PaintImage(gfxContext& aRe
 
   return result;
 }
 
 already_AddRefed<imgIRequest>
 nsImageFrame::GetCurrentRequest() const
 {
   nsCOMPtr<imgIRequest> request;
-
-  nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
-  MOZ_ASSERT(imageLoader);
-
-  imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
-                          getter_AddRefs(request));
-
+  if (nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent)) {
+    imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+                            getter_AddRefs(request));
+  } else {
+    request = mContentURLRequest;
+  }
   return request.forget();
 }
 
 void
 nsImageFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                const nsDisplayListSet& aLists)
 {
   if (!IsVisibleForPainting(aBuilder))
@@ -2133,25 +2139,20 @@ 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 (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
@@ -326,16 +326,19 @@ 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;
 
   bool mDisplayingIcon;
   bool mFirstFrameComplete;
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