Bug 1472403: Simplify generated image content. r=bz,dholbert
☠☠ backed out by 63239b786a26 ☠ ☠
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 30 Jun 2018 06:13:46 +0200
changeset 426730 365a0841117ae364c69dc777deddce8b0aaf3999
parent 426729 26163df1083ea2847022e60518ab973360b9d7c1
child 426731 93e4dff7e34689b0aeb6882aa5233214e93f40b3
push id34284
push userbtara@mozilla.com
push dateMon, 16 Jul 2018 21:55:18 +0000
treeherdermozilla-central@da5b3e1dca89 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, dholbert
bugs1472403
milestone63.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 1472403: Simplify generated image content. r=bz,dholbert 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. 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,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/. */
+
+#include "mozilla/dom/GeneratedImageContent.h"
+
+#include "nsContentCreatorFunctions.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsNodeInfoManager.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/NameSpaceConstants.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,64 @@
+/* -*- 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 dom_base_GeneratedImageContent_h
+#define dom_base_GeneratedImageContent_h
+
+/* A content node that keeps track of an index in the parent's `content`
+ * property value, used for url() values in the content of a ::before or ::after
+ * pseudo-element. */
+
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsGenericHTMLElement.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;
+
+  nsresult CopyInnerTo(GeneratedImageContent* aDest, bool aPreallocateChildren)
+  {
+    nsresult rv =
+      nsGenericHTMLElement::CopyInnerTo(aDest, aPreallocateChildren);
+    NS_ENSURE_SUCCESS(rv, rv);
+    aDest->mIndex = mIndex;
+    return NS_OK;
+  }
+
+  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 // dom_base_GeneratedImageContent_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -174,16 +174,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',
@@ -264,16 +265,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',
@@ -303,17 +305,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
@@ -10,16 +10,17 @@
  */
 
 #include "nsCSSFrameConstructor.h"
 
 #include "mozilla/AutoRestore.h"
 #include "mozilla/ComputedStyleInlines.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/GeneratedImageContent.h"
 #include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLSelectElement.h"
 #include "mozilla/dom/HTMLSummaryElement.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Likely.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PresShell.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
@@ -1713,27 +1717,17 @@ nsCSSFrameConstructor::CreateGeneratedCo
 {
   // Get the content value
   const nsStyleContentData& data =
     aComputedStyle->StyleContent()->ContentAt(aContentIndex);
   const StyleContentType type = data.GetType();
 
   switch (type) {
     case StyleContentType::Image: {
-      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...
-
-      return CreateGenConImageContent(mDocument, image);
+      return GeneratedImageContent::Create(*mDocument, aContentIndex);
     }
 
     case StyleContentType::String:
       return CreateGenConTextNode(aState,
                                   nsDependentString(data.GetString()),
                                   nullptr, nullptr);
 
     case StyleContentType::Attr: {
@@ -3613,17 +3607,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),
@@ -3649,16 +3643,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
@@ -13,16 +13,17 @@
 #include "gfxUtils.h"
 #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/GeneratedImageContent.h"
 #include "mozilla/dom/HTMLImageElement.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"
@@ -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);
 }
@@ -311,17 +320,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* styleContent = 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());
+      styleContent = nonAnonymousParent->StyleContent();
+      contentIndex = static_cast<GeneratedImageContent*>(aContent)->Index();
+    }
+    MOZ_RELEASE_ASSERT(contentIndex < styleContent->ContentCount());
+    MOZ_RELEASE_ASSERT(styleContent->ContentAt(contentIndex).GetType() ==
+                       StyleContentType::Image);
+    if (auto* proxy = styleContent->ContentAt(contentIndex).GetImage()) {
       proxy->Clone(mListener,
                    mContent->OwnerDoc(),
                    getter_AddRefs(mContentURLRequest));
       SetupForContentURLRequest();
     }
   }
 
   // Give image loads associated with an image frame a small priority boost.
@@ -482,17 +509,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;
@@ -916,17 +943,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
@@ -1863,17 +1890,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
@@ -179,37 +179,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: