Bug 1472403: Simplify generated image content. draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 30 Jun 2018 06:13:46 +0200
changeset 814754 31ff07be3be3e79b3e8a25cb4edc317c2be934e4
parent 814753 e4fd3778f48d4aa423f9a81693b068cdaf61c492
child 814755 f56545a7fe9590ff892b7ac1a7e3869ae318c556
push id115330
push userbmo:emilio@crisal.io
push dateFri, 06 Jul 2018 02:47:53 +0000
bugs1472403
milestone63.0a1
Bug 1472403: Simplify generated image content. Summary: This way we reuse the same machinery everywhere for the content property. The only difference is that we need to look at the parent style for content instead of just our style, and at a given index. Again, this is fine because changing content reframes, so no chance to mess up. This allows the generated content stuff to not implement nsImageLoadingContent and all that stuff, nor deal with events, which makes it much simpler IMO. Now it just tracks an index. We may not even need for it to be an HTML element, but I've kept that for now. I added a crashtest that used to crash because of the bogus nsCSSFrameConstructor code which trusted the node name without checking it was native anonymous. Test Plan: crashtest. The rest should be covered by existing tests. Reviewers: dholbert, bzbarsky Tags: #secure-revision Bug #: 1472403 Differential Revision: https://phabricator.services.mozilla.com/D1897 MozReview-Commit-ID: 1pAzIvRRVnL
dom/base/GeneratedImageContent.cpp
dom/base/GeneratedImageContent.h
dom/base/moz.build
dom/base/nsContentCreatorFunctions.h
dom/base/nsGenConImageContent.cpp
dom/base/nsImageLoadingContent.cpp
dom/base/nsImageLoadingContent.h
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
layout/generic/crashtests/1472403.html
layout/generic/crashtests/crashtests.list
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
new file mode 100644
--- /dev/null
+++ b/dom/base/GeneratedImageContent.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentCreatorFunctions.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsNodeInfoManager.h"
+#include "mozilla/dom/GeneratedImageContent.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/NodeInfo.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ELEMENT_CLONE(GeneratedImageContent);
+
+already_AddRefed<GeneratedImageContent>
+GeneratedImageContent::Create(nsIDocument& aDocument, uint32_t aContentIndex)
+{
+  RefPtr<dom::NodeInfo> nodeInfo =
+    aDocument.NodeInfoManager()->
+      GetNodeInfo(nsGkAtoms::mozgeneratedcontentimage,
+                  nullptr,
+                  kNameSpaceID_XHTML,
+                  nsINode::ELEMENT_NODE);
+
+  // Work around not being able to bind a non-const lvalue reference
+  // to an rvalue of non-reference type by just creating an rvalue
+  // reference.  And we can't change the constructor signature,
+  // because then the macro-generated Clone() method fails to compile.
+  already_AddRefed<dom::NodeInfo>&& rvalue = nodeInfo.forget();
+
+  auto image =
+    MakeRefPtr<GeneratedImageContent>(rvalue);
+  image->mIndex = aContentIndex;
+  return image.forget();
+}
+
+JSObject*
+GeneratedImageContent::WrapNode(JSContext* aCx,
+                                JS::Handle<JSObject*> aGivenProto)
+{
+  return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/dom/base/GeneratedImageContent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GeneratedImageContent_h
+#define GeneratedImageContent_h
+
+/* A content node that keeps track of an index in the parent's `content`
+ * property value, used for ::before and ::after */
+
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+
+namespace mozilla {
+namespace dom {
+
+class GeneratedImageContent final
+  : public nsGenericHTMLElement
+{
+public:
+  static already_AddRefed<GeneratedImageContent>
+    Create(nsIDocument&, uint32_t aContentIndex);
+
+  explicit GeneratedImageContent(already_AddRefed<dom::NodeInfo>& aNodeInfo)
+    : nsGenericHTMLElement(aNodeInfo)
+  {
+    MOZ_ASSERT(IsInNamespace(kNameSpaceID_XHTML), "Someone messed up our nodeinfo");
+  }
+
+  nsresult Clone(dom::NodeInfo* aNodeInfo,
+                 nsINode** aResult,
+                 bool aPreallocateChildren) const final;
+
+  uint32_t Index() const
+  {
+    return mIndex;
+  }
+
+protected:
+  JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
+
+private:
+  virtual ~GeneratedImageContent() = default;
+  uint32_t mIndex = 0;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // GeneratedImageContent_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -177,16 +177,17 @@ EXPORTS.mozilla.dom += [
     'DOMStringList.h',
     'DOMTokenListSupportedTokens.h',
     'Element.h',
     'ElementInlines.h',
     'EventSource.h',
     'FormData.h',
     'FragmentOrElement.h',
     'FromParser.h',
+    'GeneratedImageContent.h',
     'IdleDeadline.h',
     'IdleRequest.h',
     'IDTracker.h',
     'ImageEncoder.h',
     'ImageTracker.h',
     'IntlUtils.h',
     'Link.h',
     'Location.h',
@@ -266,16 +267,17 @@ UNIFIED_SOURCES += [
     'DOMQuad.cpp',
     'DOMRect.cpp',
     'DOMRequest.cpp',
     'DOMStringList.cpp',
     'Element.cpp',
     'EventSource.cpp',
     'FormData.cpp',
     'FragmentOrElement.cpp',
+    'GeneratedImageContent.cpp',
     'IdleDeadline.cpp',
     'IdleRequest.cpp',
     'IDTracker.cpp',
     'ImageEncoder.cpp',
     'ImageTracker.cpp',
     'IntlUtils.cpp',
     'Link.cpp',
     'Location.cpp',
@@ -305,17 +307,16 @@ UNIFIED_SOURCES += [
     'nsDOMCaretPosition.cpp',
     'nsDOMMutationObserver.cpp',
     'nsDOMNavigationTiming.cpp',
     'nsDOMSerializer.cpp',
     'nsDOMTokenList.cpp',
     'nsDOMWindowList.cpp',
     'nsFocusManager.cpp',
     'nsFrameLoader.cpp',
-    'nsGenConImageContent.cpp',
     'nsGlobalWindowCommands.cpp',
     'nsHistory.cpp',
     'nsHTMLContentSerializer.cpp',
     'nsIGlobalObject.cpp',
     'nsINode.cpp',
     'nsInProcessTabChildGlobal.cpp',
     'nsJSEnvironment.cpp',
     'nsJSTimeoutHandler.cpp',
--- a/dom/base/nsContentCreatorFunctions.h
+++ b/dom/base/nsContentCreatorFunctions.h
@@ -71,16 +71,9 @@ NS_TrustedNewXULElement(mozilla::dom::El
                         already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
 #endif
 
 nsresult
 NS_NewSVGElement(mozilla::dom::Element** aResult,
                  already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
                  mozilla::dom::FromParser aFromParser);
 
-namespace mozilla {
-namespace dom {
-already_AddRefed<nsIContent>
-CreateGenConImageContent(nsIDocument* aDocument, imgRequestProxy* aImageRequest);
-} // namespace dom
-} // namespace mozilla
-
 #endif // nsContentCreatorFunctions_h__
deleted file mode 100644
--- a/dom/base/nsGenConImageContent.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * A fake content node class so that the image frame for
- *   content: url(foo);
- * in CSS can have an nsIImageLoadingContent but use an
- * imgIRequest that's already been loaded from the style system.
- */
-
-#include "nsContentCreatorFunctions.h"
-#include "nsGenericHTMLElement.h"
-#include "nsGkAtoms.h"
-#include "nsIDocument.h"
-#include "nsImageLoadingContent.h"
-#include "imgIRequest.h"
-#include "nsNodeInfoManager.h"
-#include "mozilla/BasicEvents.h"
-#include "mozilla/EventDispatcher.h"
-#include "mozilla/EventStates.h"
-#include "mozilla/dom/HTMLElementBinding.h"
-#include "mozilla/dom/NameSpaceConstants.h"
-
-using namespace mozilla;
-
-class nsGenConImageContent final : public nsGenericHTMLElement,
-                                   public nsImageLoadingContent
-{
-public:
-  explicit nsGenConImageContent(already_AddRefed<dom::NodeInfo>& aNodeInfo)
-    : nsGenericHTMLElement(aNodeInfo)
-  {
-    // nsImageLoadingContent starts out broken, so we start out
-    // suppressed to match it.
-    AddStatesSilently(NS_EVENT_STATE_SUPPRESSED);
-    MOZ_ASSERT(IsInNamespace(kNameSpaceID_XHTML), "Someone messed up our nodeinfo");
-  }
-
-  nsresult Init(imgRequestProxy* aImageRequest)
-  {
-    // No need to notify, since we have no frame.
-    return UseAsPrimaryRequest(aImageRequest, false, eImageLoadType_Normal);
-  }
-
-  // nsIContent overrides
-  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
-                              nsIContent* aBindingParent,
-                              bool aCompileEventHandlers) override;
-  virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
-  virtual EventStates IntrinsicState() const override;
-
-  void GetEventTargetParent(EventChainPreVisitor& aVisitor) override
-  {
-    MOZ_ASSERT(IsInNativeAnonymousSubtree());
-    if (aVisitor.mEvent->mMessage == eLoad ||
-        aVisitor.mEvent->mMessage == eLoadError) {
-      // Don't propagate the events to the parent.
-      return;
-    }
-    nsGenericHTMLElement::GetEventTargetParent(aVisitor);
-  }
-
-protected:
-  nsIContent* AsContent() override { return this; }
-
-  virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
-  virtual nsresult Clone(dom::NodeInfo* aNodeInfo,
-                         nsINode** aResult,
-                         bool aPreallocateChildren) const override;
-
-private:
-  virtual ~nsGenConImageContent();
-
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-};
-
-NS_IMPL_ISUPPORTS_INHERITED(nsGenConImageContent,
-                            nsGenericHTMLElement,
-                            nsIImageLoadingContent,
-                            imgINotificationObserver)
-
-NS_IMPL_ELEMENT_CLONE(nsGenConImageContent)
-
-namespace mozilla {
-namespace dom {
-
-already_AddRefed<nsIContent>
-CreateGenConImageContent(nsIDocument* aDocument, imgRequestProxy* aImageRequest)
-{
-  MOZ_ASSERT(aImageRequest, "Must have request!");
-  RefPtr<NodeInfo> nodeInfo =
-    aDocument->NodeInfoManager()->
-      GetNodeInfo(nsGkAtoms::mozgeneratedcontentimage,
-                  nullptr,
-                  kNameSpaceID_XHTML,
-                  nsINode::ELEMENT_NODE);
-  // Work around not being able to bind a non-const lvalue reference
-  // to an rvalue of non-reference type by just creating an rvalue
-  // reference.  And we can't change the constructor signature,
-  // because then the macro-generated Clone() method fails to compile.
-  already_AddRefed<NodeInfo>&& rvalue = nodeInfo.forget();
-  RefPtr<nsGenConImageContent> it = new nsGenConImageContent(rvalue);
-  nsresult rv = it->Init(aImageRequest);
-  if (NS_FAILED(rv)) {
-    return nullptr;
-  }
-
-  return it.forget();
-}
-
-} // namespace dom
-} // namespace mozilla
-
-nsGenConImageContent::~nsGenConImageContent()
-{
-  DestroyImageLoadingContent();
-}
-
-nsresult
-nsGenConImageContent::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
-                                 nsIContent* aBindingParent,
-                                 bool aCompileEventHandlers)
-{
-  nsresult rv;
-  rv = nsGenericHTMLElement::BindToTree(aDocument, aParent, aBindingParent,
-                                aCompileEventHandlers);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
-                                    aCompileEventHandlers);
-  return NS_OK;
-}
-
-void
-nsGenConImageContent::UnbindFromTree(bool aDeep, bool aNullParent)
-{
-  nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
-  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
-}
-
-EventStates
-nsGenConImageContent::IntrinsicState() const
-{
-  EventStates state = nsGenericHTMLElement::IntrinsicState();
-
-  EventStates imageState = nsImageLoadingContent::ImageState();
-  if (imageState.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED)) {
-    // We should never be in an error state; if the image fails to load, we
-    // just go to the suppressed state.
-    imageState |= NS_EVENT_STATE_SUPPRESSED;
-    imageState &= ~NS_EVENT_STATE_BROKEN;
-  }
-  imageState &= ~NS_EVENT_STATE_LOADING;
-  return state | imageState;
-}
-
-JSObject*
-nsGenConImageContent::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto);
-}
-
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -1160,42 +1160,16 @@ nsImageLoadingContent::UpdateImageState(
 void
 nsImageLoadingContent::CancelImageRequests(bool aNotify)
 {
   AutoStateChanger changer(this, aNotify);
   ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
   ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
 }
 
-nsresult
-nsImageLoadingContent::UseAsPrimaryRequest(imgRequestProxy* aRequest,
-                                           bool aNotify,
-                                           ImageLoadType aImageLoadType)
-{
-  // Our state will change. Watch it.
-  AutoStateChanger changer(this, aNotify);
-
-  // Get rid if our existing images
-  ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
-  ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
-
-  // Clone the request we were given.
-  RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
-  nsresult rv = aRequest->SyncClone(this, GetOurOwnerDoc(), getter_AddRefs(req));
-  if (NS_SUCCEEDED(rv)) {
-    CloneScriptedRequests(req);
-    TrackImage(req);
-  } else {
-    MOZ_ASSERT(!req, "Shouldn't have non-null request here");
-    return rv;
-  }
-
-  return NS_OK;
-}
-
 nsIDocument*
 nsImageLoadingContent::GetOurOwnerDoc()
 {
   return AsContent()->OwnerDoc();
 }
 
 nsIDocument*
 nsImageLoadingContent::GetOurCurrentDoc()
--- a/dom/base/nsImageLoadingContent.h
+++ b/dom/base/nsImageLoadingContent.h
@@ -186,25 +186,16 @@ protected:
   /**
    * CancelImageRequests is called by subclasses when they want to
    * cancel all image requests (for example when the subclass is
    * somehow not an image anymore).
    */
   void CancelImageRequests(bool aNotify);
 
   /**
-   * UseAsPrimaryRequest is called by subclasses when they have an existing
-   * imgRequestProxy that they want this nsImageLoadingContent to use.  This may
-   * effectively be called instead of LoadImage or LoadImageWithChannel.
-   * If aNotify is true, this method will notify on state changes.
-   */
-  nsresult UseAsPrimaryRequest(imgRequestProxy* aRequest, bool aNotify,
-                               ImageLoadType aImageLoadType);
-
-  /**
    * Derived classes of nsImageLoadingContent MUST call
    * DestroyImageLoadingContent from their destructor, or earlier.  It
    * does things that cannot be done in ~nsImageLoadingContent because
    * they rely on being able to QueryInterface to other derived classes,
    * which cannot happen once the derived class destructor has started
    * calling the base class destructors.
    */
   void DestroyImageLoadingContent();
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -13,16 +13,17 @@
 
 #include "mozilla/AutoRestore.h"
 #include "mozilla/ComputedStyleInlines.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLSelectElement.h"
 #include "mozilla/dom/HTMLSummaryElement.h"
+#include "mozilla/dom/GeneratedImageContent.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Likely.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoStyleSetInlines.h"
 #include "mozilla/StaticPrefs.h"
@@ -318,16 +319,19 @@ nsIFrame*
 NS_NewScrollbarFrame (nsIPresShell* aPresShell, ComputedStyle* aStyle);
 
 nsIFrame*
 NS_NewScrollbarButtonFrame (nsIPresShell* aPresShell, ComputedStyle* aStyle);
 
 nsIFrame*
 NS_NewImageFrameForContentProperty(nsIPresShell*, ComputedStyle*);
 
+nsIFrame*
+NS_NewImageFrameForGeneratedContentIndex(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
@@ -1729,30 +1733,17 @@ nsCSSFrameConstructor::CreateGeneratedCo
   switch (type) {
     case StyleContentType::Image: {
       if (ShouldCreateImageFrameForContent(*aComputedStyle)) {
         // We're done here, our parent will have an image frame either way so we
         // don't need to deal with this stuff.
         return nullptr;
       }
 
-      imgRequestProxy* image = data.GetImage();
-      if (!image) {
-        // CSS had something specified that couldn't be converted to an
-        // image object
-        return nullptr;
-      }
-
-      // Create an image content object and pass it the image request.
-      // XXX Check if it's an image type we can handle...
-      //
-      // TODO(emilio): Can we somehow make this anonymous element not an
-      // nsImageLoadingContent, and use the parent content style instead?
-
-      return CreateGenConImageContent(mDocument, image);
+      return GeneratedImageContent::Create(*mDocument, aContentIndex);
     }
 
     case StyleContentType::String:
       return CreateGenConTextNode(aState,
                                   nsDependentString(data.GetString()),
                                   nullptr, nullptr);
 
     case StyleContentType::Attr: {
@@ -3632,17 +3623,17 @@ nsCSSFrameConstructor::FindHTMLData(Elem
     // positioned legends we want to construct by display type and
     // not do special legend stuff.
     return nullptr;
   }
 
   static const FrameConstructionDataByTag sHTMLData[] = {
     SIMPLE_TAG_CHAIN(img, nsCSSFrameConstructor::FindImgData),
     SIMPLE_TAG_CHAIN(mozgeneratedcontentimage,
-                     nsCSSFrameConstructor::FindImgData),
+                     nsCSSFrameConstructor::FindGeneratedImageData),
     { &nsGkAtoms::br,
       FCDATA_DECL(FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_LINE_BREAK,
                   NS_NewBRFrame) },
     SIMPLE_TAG_CREATE(wbr, NS_NewWBRFrame),
     SIMPLE_TAG_CHAIN(input, nsCSSFrameConstructor::FindInputData),
     SIMPLE_TAG_CREATE(textarea, NS_NewTextControlFrame),
     COMPLEX_TAG_CREATE(select, &nsCSSFrameConstructor::ConstructSelectFrame),
     SIMPLE_TAG_CHAIN(object, nsCSSFrameConstructor::FindObjectData),
@@ -3668,16 +3659,30 @@ nsCSSFrameConstructor::FindHTMLData(Elem
   };
 
   return FindDataByTag(aTag, aElement, aComputedStyle, sHTMLData,
                        ArrayLength(sHTMLData));
 }
 
 /* static */
 const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindGeneratedImageData(Element* aElement,
+                                              ComputedStyle* aStyle)
+{
+  if (!aElement->IsInNativeAnonymousSubtree()) {
+    return nullptr;
+  }
+
+  static const FrameConstructionData sImgData =
+    SIMPLE_FCDATA(NS_NewImageFrameForGeneratedContentIndex);
+  return &sImgData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
 nsCSSFrameConstructor::FindImgData(Element* aElement,
                                    ComputedStyle* aComputedStyle)
 {
   if (!nsImageFrame::ShouldCreateImageFrameFor(aElement, aComputedStyle)) {
     return nullptr;
   }
 
   static const FrameConstructionData sImgData = SIMPLE_FCDATA(NS_NewImageFrame);
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -1451,26 +1451,24 @@ private:
   // aParentFrame might be null.  If it is, that means it was an
   // inline frame.
   static const FrameConstructionData* FindHTMLData(Element* aContent,
                                                    nsAtom* aTag,
                                                    int32_t aNameSpaceID,
                                                    nsIFrame* aParentFrame,
                                                    ComputedStyle* aComputedStyle);
   // HTML data-finding helper functions
-  static const FrameConstructionData*
-    FindImgData(Element* aElement, ComputedStyle* aComputedStyle);
-  static const FrameConstructionData*
-    FindImgControlData(Element* aElement, ComputedStyle* aComputedStyle);
-  static const FrameConstructionData*
-    FindInputData(Element* aElement, ComputedStyle* aComputedStyle);
-  static const FrameConstructionData*
-    FindObjectData(Element* aElement, ComputedStyle* aComputedStyle);
-  static const FrameConstructionData*
-    FindCanvasData(Element* aElement, ComputedStyle* aComputedStyle);
+  static const FrameConstructionData* FindImgData(Element*, ComputedStyle*);
+  static const FrameConstructionData* FindGeneratedImageData(Element*,
+                                                             ComputedStyle*);
+  static const FrameConstructionData* FindImgControlData(Element*,
+                                                         ComputedStyle*);
+  static const FrameConstructionData* FindInputData(Element*, ComputedStyle*);
+  static const FrameConstructionData* FindObjectData(Element*, ComputedStyle*);
+  static const FrameConstructionData* FindCanvasData(Element*, ComputedStyle*);
 
   /* Construct a frame from the given FrameConstructionItem.  This function
      will handle adding the frame to frame lists, processing children, setting
      the frame as the primary frame for the item's content, and so forth.
 
      @param aItem the FrameConstructionItem to use.
      @param aState the frame construction state to use.
      @param aParentFrame the frame to set as the parent of the
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/1472403.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<div id="target"></div>
+<script>
+  let element = document.createElement('_moz_generated_content_image');
+  target.appendChild(element);
+</script>
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -699,8 +699,9 @@ load 1431781-2.html
 asserts(6) load 1458028.html # bug 847368
 load 1459697.html
 load 1460158-1.html
 load 1460158-2.html
 load 1460158-3.html
 load 1461039.html
 load 1461979-1.html
 load 1467239.html
+load 1472403.html
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/ComputedStyle.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/GeneratedImageContent.h"
 #include "mozilla/dom/ResponsiveImageSelector.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Unused.h"
 
 #include "nsCOMPtr.h"
 #include "nsFontMetrics.h"
 #include "nsIImageLoadingContent.h"
@@ -134,17 +135,25 @@ NS_NewImageFrame(nsIPresShell* aPresShel
   return new (aPresShell) nsImageFrame(aStyle, nsImageFrame::Kind::ImageElement);
 }
 
 nsIFrame*
 NS_NewImageFrameForContentProperty(nsIPresShell* aPresShell,
                                    ComputedStyle* aStyle)
 {
   return new (aPresShell) nsImageFrame(
-    aStyle, nsImageFrame::Kind::NonGeneratedContentProperty);
+    aStyle, nsImageFrame::Kind::ContentProperty);
+}
+
+nsIFrame*
+NS_NewImageFrameForGeneratedContentIndex(nsIPresShell* aPresShell,
+                                         ComputedStyle* aStyle)
+{
+  return new (aPresShell) nsImageFrame(
+    aStyle, nsImageFrame::Kind::ContentPropertyAtIndex);
 }
 
 nsImageFrame*
 nsImageFrame::CreateContinuingFrame(nsIPresShell* aPresShell,
                                     ComputedStyle* aStyle) const
 {
   return new (aPresShell) nsImageFrame(aStyle, mKind);
 }
@@ -307,17 +316,35 @@ nsImageFrame::Init(nsIContent* aContent,
   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()) {
+    uint32_t contentIndex = 0;
+    const nsStyleContent* content = StyleContent();
+    if (mKind == Kind::ContentPropertyAtIndex) {
+      MOZ_RELEASE_ASSERT(
+        aParent->GetContent()->IsGeneratedContentContainerForAfter() ||
+        aParent->GetContent()->IsGeneratedContentContainerForBefore());
+      MOZ_RELEASE_ASSERT(aContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage));
+      nsIFrame* nonAnonymousParent = aParent;
+      while (nonAnonymousParent->Style()->IsAnonBox()) {
+        nonAnonymousParent = nonAnonymousParent->GetParent();
+      }
+      MOZ_RELEASE_ASSERT(aParent->GetContent() == nonAnonymousParent->GetContent());
+      content = nonAnonymousParent->StyleContent();
+      contentIndex = static_cast<GeneratedImageContent*>(aContent)->Index();
+    }
+    MOZ_RELEASE_ASSERT(contentIndex < content->ContentCount());
+    MOZ_RELEASE_ASSERT(content->ContentAt(contentIndex).GetType() ==
+                       StyleContentType::Image);
+    if (auto* proxy = content->ContentAt(contentIndex).GetImage()) {
       proxy->Clone(mListener,
                    mContent->OwnerDoc(),
                    getter_AddRefs(mContentURLRequest));
       // Make sure we get the intrinsic size and such ASAP if available.
       if (SizeIsAvailable(mContentURLRequest)) {
         nsCOMPtr<imgIContainer> image;
         mContentURLRequest->GetImage(getter_AddRefs(image));
         OnSizeAvailable(mContentURLRequest, image);
@@ -453,17 +480,17 @@ 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::NonGeneratedContentProperty) {
+  if (mKind != Kind::ImageElement) {
     MOZ_ASSERT(aRequest == mContentURLRequest);
     return false;
   }
 
   nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
   MOZ_ASSERT(imageLoader);
 
   int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
@@ -881,17 +908,17 @@ nsImageFrame::EnsureIntrinsicSizeAndRati
     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 = IsForNonGeneratedImageElement();
+  const bool mayDisplayBrokenIcon = mKind == Kind::ImageElement;
   if (!mayDisplayBrokenIcon) {
     return;
   }
   // image request is null or image size not known, probably an
   // invalid image specified
   bool imageInvalid = false;
 
   // check for broken images. valid null images (eg. img src="") are
@@ -1828,17 +1855,17 @@ nsImageFrame::PaintImage(gfxContext& aRe
   }
 
   return result;
 }
 
 already_AddRefed<imgIRequest>
 nsImageFrame::GetCurrentRequest() const
 {
-  if (mKind == Kind::NonGeneratedContentProperty) {
+  if (mKind != Kind::ImageElement) {
     return do_AddRef(mContentURLRequest);
   }
 
   MOZ_ASSERT(!mContentURLRequest);
 
   nsCOMPtr<imgIRequest> request;
   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
   MOZ_ASSERT(imageLoader);
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -178,37 +178,33 @@ public:
 
   // nsIReflowCallback
   virtual bool ReflowFinished() override;
   virtual void ReflowCallbackCanceled() override;
 
   // The kind of image frame we are.
   enum class Kind : uint8_t
   {
-    // For an nsImageLoadingContent, including generated ::before and ::after
-    // content, which are an nsGenConImageContent.
+    // For an nsImageLoadingContent.
     ImageElement,
-    // For css 'content: url(..)' on other elements.
-    NonGeneratedContentProperty,
+    // For css 'content: url(..)' on non-generated content, or on generated
+    // ::before / ::after with exactly one item in the content property.
+    ContentProperty,
+    // For a child of a ::before / ::after pseudo-element that had more than one
+    // item for the content property.
+    ContentPropertyAtIndex,
   };
 
-  // Whether this frame is for a non-generated image element, that is, one that
-  // isn't a ::before / ::after.
-  bool IsForNonGeneratedImageElement() const
-  {
-    return mKind == Kind::ImageElement &&
-      !HasAnyStateBits(NS_FRAME_GENERATED_CONTENT);
-  }
-
   // Creates a suitable continuing frame for this frame.
   nsImageFrame* CreateContinuingFrame(nsIPresShell*, ComputedStyle*) const;
 
 private:
   friend nsIFrame* NS_NewImageFrame(nsIPresShell*, ComputedStyle*);
   friend nsIFrame* NS_NewImageFrameForContentProperty(nsIPresShell*, ComputedStyle*);
+  friend nsIFrame* NS_NewImageFrameForGeneratedContentIndex(nsIPresShell*, ComputedStyle*);
 
   nsImageFrame(ComputedStyle* aStyle, Kind aKind)
     : nsImageFrame(aStyle, kClassID, aKind)
   { }
 
   nsImageFrame(ComputedStyle*, ClassID, Kind);
 
 protected: