Bug 1020244 - Ability to insert AnonymousContent nodes in the canvasFrame via a chrome-only Document API; r=smaug; r=roc; r=ehsan
authorPatrick Brosset <pbrosset@mozilla.com>
Tue, 28 Oct 2014 11:15:25 +0100
changeset 212699 8a8090c2051edf8d3578a68fc3e5582774b67efe
parent 212698 e661e86180ea014bfdb6bc03ce00c1c73ff9dd8f
child 212700 cf419403a2eb60b5444908c2633e35318e21ddab
push id51042
push userryanvm@gmail.com
push dateTue, 28 Oct 2014 20:25:03 +0000
treeherdermozilla-inbound@53d84829b2b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, roc, ehsan
bugs1020244
milestone36.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 1020244 - Ability to insert AnonymousContent nodes in the canvasFrame via a chrome-only Document API; r=smaug; r=roc; r=ehsan
dom/base/AnonymousContent.cpp
dom/base/AnonymousContent.h
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/webidl/Document.webidl
layout/generic/nsCanvasFrame.cpp
layout/generic/nsCanvasFrame.h
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/style/ua.css
--- a/dom/base/AnonymousContent.cpp
+++ b/dom/base/AnonymousContent.cpp
@@ -29,16 +29,22 @@ AnonymousContent::~AnonymousContent()
 
 nsCOMPtr<Element>
 AnonymousContent::GetContentNode()
 {
   return mContentNode;
 }
 
 void
+AnonymousContent::SetContentNode(Element* aContentNode)
+{
+  mContentNode = aContentNode;
+}
+
+void
 AnonymousContent::SetTextContentForElement(const nsAString& aElementId,
                                            const nsAString& aText,
                                            ErrorResult& aRv)
 {
   Element* element = GetElementById(aElementId);
   if (!element) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
--- a/dom/base/AnonymousContent.h
+++ b/dom/base/AnonymousContent.h
@@ -21,16 +21,17 @@ class AnonymousContent MOZ_FINAL
 {
 public:
   // Ref counting and cycle collection
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnonymousContent)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnonymousContent)
 
   explicit AnonymousContent(Element* aContentNode);
   nsCOMPtr<Element> GetContentNode();
+  void SetContentNode(Element* aContentNode);
   JSObject* WrapObject(JSContext* aCx);
 
   // WebIDL methods
   void SetTextContentForElement(const nsAString& aElementId,
                                 const nsAString& aText,
                                 ErrorResult& aRv);
 
   void GetTextContentForElement(const nsAString& aElementId,
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -63,16 +63,17 @@
 #include "nsIDOMComment.h"
 #include "mozilla/dom/DocumentType.h"
 #include "mozilla/dom/NodeIterator.h"
 #include "mozilla/dom/TreeWalker.h"
 
 #include "nsIServiceManager.h"
 #include "nsIServiceWorkerManager.h"
 
+#include "nsCanvasFrame.h"
 #include "nsContentCID.h"
 #include "nsError.h"
 #include "nsPresShell.h"
 #include "nsPresContext.h"
 #include "nsIJSON.h"
 #include "nsThreadUtils.h"
 #include "nsNodeInfoManager.h"
 #include "nsIFileChannel.h"
@@ -177,16 +178,17 @@
 
 #include "mozilla/Preferences.h"
 
 #include "imgILoader.h"
 #include "imgRequestProxy.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsSandboxFlags.h"
 #include "nsIAppsService.h"
+#include "mozilla/dom/AnonymousContent.h"
 #include "mozilla/dom/AnimationTimeline.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/NodeFilterBinding.h"
 #include "mozilla/dom/OwningNonNull.h"
@@ -1981,16 +1983,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUndoManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnimationTimeline)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegistry)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
 
   // Traverse all our nsCOMArrays.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnDemandBuiltInUASheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubImportLinks)
 
@@ -5133,16 +5136,88 @@ nsDocument::StyleRuleRemoved(nsIStyleShe
                                "StyleRuleRemoved",
                                mRule,
                                rule ? rule->GetDOMRule() : nullptr);
   }
 }
 
 #undef DO_STYLESHEET_NOTIFICATION
 
+already_AddRefed<AnonymousContent>
+nsIDocument::InsertAnonymousContent(Element& aElement, ErrorResult& aRv)
+{
+  nsIPresShell* shell = GetShell();
+  if (!shell || !shell->GetCanvasFrame()) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsCOMPtr<Element> container = shell->GetCanvasFrame()
+                                     ->GetCustomContentContainer();
+  if (!container) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  // Clone the node to avoid returning a direct reference
+  nsCOMPtr<nsINode> clonedElement = aElement.CloneNode(true, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // Insert the element into the container
+  nsresult rv;
+  rv = container->AppendChildTo(clonedElement->AsContent(), true);
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  nsRefPtr<AnonymousContent> anonymousContent =
+    new AnonymousContent(clonedElement->AsElement());
+  mAnonymousContents.AppendElement(anonymousContent);
+
+  return anonymousContent.forget();
+}
+
+void
+nsIDocument::RemoveAnonymousContent(AnonymousContent& aContent,
+                                    ErrorResult& aRv)
+{
+  nsIPresShell* shell = GetShell();
+  if (!shell) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  nsCOMPtr<Element> container = shell->GetCanvasFrame()
+                                     ->GetCustomContentContainer();
+  if (!container) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  // Iterate over know customContents to get and remove the right one
+  for (int32_t i = mAnonymousContents.Length() - 1; i >= 0; --i) {
+    if (mAnonymousContents[i] == &aContent) {
+      // Get the node from the customContent
+      nsCOMPtr<Element> node = aContent.GetContentNode();
+
+      // Remove the entry in mAnonymousContents
+      mAnonymousContents.RemoveElementAt(i);
+
+      // Remove the node from its container
+      container->RemoveChild(*node, aRv);
+      if (aRv.Failed()) {
+        return;
+      }
+
+      break;
+    }
+  }
+}
 
 //
 // nsIDOMDocument interface
 //
 DocumentType*
 nsIDocument::GetDoctype() const
 {
   for (nsIContent* child = GetFirstChild();
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -87,16 +87,17 @@ class SVGAttrAnimationRuleProcessor;
 
 namespace css {
 class Loader;
 class ImageLoader;
 } // namespace css
 
 namespace dom {
 class AnimationTimeline;
+class AnonymousContent;
 class Attr;
 class BoxObject;
 class CDATASection;
 class Comment;
 struct CustomElementDefinition;
 class DocumentFragment;
 class DocumentType;
 class DOMImplementation;
@@ -710,16 +711,25 @@ public:
    * the channel has not been set.
    */
   nsresult GetSrcdocData(nsAString& aSrcdocData);
 
   bool DidDocumentOpen() {
     return mDidDocumentOpen;
   }
 
+  already_AddRefed<mozilla::dom::AnonymousContent>
+  InsertAnonymousContent(mozilla::dom::Element& aElement,
+                         mozilla::ErrorResult& aError);
+  void RemoveAnonymousContent(mozilla::dom::AnonymousContent& aContent,
+                              mozilla::ErrorResult& aError);
+  nsTArray<nsRefPtr<mozilla::dom::AnonymousContent>>& GetAnonymousContents() {
+    return mAnonymousContents;
+  }
+
 protected:
   virtual Element *GetRootElementInternal() const = 0;
 
 private:
   class SelectorCacheKey
   {
     public:
       explicit SelectorCacheKey(const nsAString& aString) : mKey(aString)
@@ -2742,16 +2752,18 @@ protected:
 
   nsCOMPtr<nsIStructuredCloneContainer> mStateObjectContainer;
   nsCOMPtr<nsIVariant> mStateObjectCached;
 
   uint32_t mInSyncOperationCount;
 
   nsRefPtr<mozilla::dom::XPathEvaluator> mXPathEvaluator;
 
+  nsTArray<nsRefPtr<mozilla::dom::AnonymousContent>> mAnonymousContents;
+
   uint32_t mBlockDOMContentLoaded;
   bool mDidFireDOMContentLoaded:1;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)
 
 /**
  * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -350,15 +350,39 @@ partial interface Document {
 };
 
 // Extension to give chrome JS the ability to determine when a document was
 // created to satisfy an iframe with srcdoc attribute.
 partial interface Document {
   [ChromeOnly] readonly attribute boolean isSrcdocDocument;
 };
 
+/**
+ * Chrome document anonymous content management.
+ * This is a Chrome-only API that allows inserting fixed positioned anonymous
+ * content on top of the current page displayed in the document.
+ * The supplied content is cloned and inserted into the document's CanvasFrame.
+ * Note that this only works for HTML documents.
+ */
+partial interface Document {
+  /**
+   * Deep-clones the provided element and inserts it into the CanvasFrame.
+   * Returns an AnonymousContent instance that can be used to manipulate the
+   * inserted element.
+   */
+  [ChromeOnly, NewObject, Throws]
+  AnonymousContent insertAnonymousContent(Element aElement);
+
+  /**
+   * Removes the element inserted into the CanvasFrame given an AnonymousContent
+   * instance.
+   */
+  [ChromeOnly, Throws]
+  void removeAnonymousContent(AnonymousContent aContent);
+};
+
 Document implements XPathEvaluator;
 Document implements GlobalEventHandlers;
 Document implements TouchEventHandlers;
 Document implements ParentNode;
 Document implements OnErrorEventHandlerForNodes;
 Document implements GeometryUtils;
 Document implements FontFaceSource;
--- a/layout/generic/nsCanvasFrame.cpp
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -16,30 +16,32 @@
 #include "nsGkAtoms.h"
 #include "nsPresShell.h"
 #include "nsIPresShell.h"
 #include "nsDisplayList.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsFrameManager.h"
 #include "gfxPlatform.h"
 #include "nsPrintfCString.h"
+#include "mozilla/dom/AnonymousContent.h"
 // for touchcaret
 #include "nsContentList.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsContentUtils.h"
 #include "nsStyleSet.h"
 // for focus
 #include "nsIScrollableFrame.h"
 #ifdef DEBUG_CANVAS_FOCUS
 #include "nsIDocShell.h"
 #endif
 
 //#define DEBUG_CANVAS_FOCUS
 
 using namespace mozilla;
+using namespace mozilla::dom;
 using namespace mozilla::layout;
 using namespace mozilla::gfx;
 
 nsCanvasFrame*
 NS_NewCanvasFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsCanvasFrame(aContext);
 }
@@ -58,26 +60,26 @@ nsCanvasFrame::CreateAnonymousContent(ns
     return NS_OK;
   }
 
   nsCOMPtr<nsIDocument> doc = mContent->OwnerDoc();
   nsresult rv = NS_OK;
   ErrorResult er;
   // We won't create touch caret element if preference is not enabled.
   if (PresShell::TouchCaretPrefEnabled()) {
-    nsRefPtr<dom::NodeInfo> nodeInfo;
+    nsRefPtr<NodeInfo> nodeInfo;
 
     // Create and append touch caret frame.
     nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
                                                    kNameSpaceID_XHTML,
                                                    nsIDOMNode::ELEMENT_NODE);
     NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
 
     rv = NS_NewHTMLElement(getter_AddRefs(mTouchCaretElement), nodeInfo.forget(),
-                           mozilla::dom::NOT_FROM_PARSER);
+                           NOT_FROM_PARSER);
     NS_ENSURE_SUCCESS(rv, rv);
     aElements.AppendElement(mTouchCaretElement);
 
     // Set touch caret to visibility: hidden by default.
     nsAutoString classValue;
     classValue.AppendLiteral("moz-touchcaret hidden");
     rv = mTouchCaretElement->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
                                      classValue, true);
@@ -98,16 +100,33 @@ nsCanvasFrame::CreateAnonymousContent(ns
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mSelectionCaretsEndElement->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
                                              NS_LITERAL_STRING("moz-selectioncaret-right hidden"),
                                              true);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  // Create the custom content container.
+  mCustomContentContainer = doc->CreateHTMLElement(nsGkAtoms::div);
+  aElements.AppendElement(mCustomContentContainer);
+
+  // XXX add :moz-native-anonymous or will that be automatically set?
+  rv = mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                                        NS_LITERAL_STRING("moz-custom-content-container"),
+                                        true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Append all existing AnonymousContent nodes stored at document level if any.
+  int32_t anonymousContentCount = doc->GetAnonymousContents().Length();
+  for (int32_t i = 0; i < anonymousContentCount; ++i) {
+    nsCOMPtr<Element> node = doc->GetAnonymousContents()[i]->GetContentNode();
+    mCustomContentContainer->AppendChildTo(node->AsContent(), true);
+  }
+
   return NS_OK;
 }
 
 void
 nsCanvasFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, uint32_t aFilter)
 {
   if (mTouchCaretElement) {
     aElements.AppendElement(mTouchCaretElement);
@@ -115,30 +134,48 @@ nsCanvasFrame::AppendAnonymousContentTo(
 
   if (mSelectionCaretsStartElement) {
     aElements.AppendElement(mSelectionCaretsStartElement);
   }
 
   if (mSelectionCaretsEndElement) {
     aElements.AppendElement(mSelectionCaretsEndElement);
   }
+
+  aElements.AppendElement(mCustomContentContainer);
 }
 
 void
 nsCanvasFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   nsIScrollableFrame* sf =
     PresContext()->GetPresShell()->GetRootScrollFrameAsScrollable();
   if (sf) {
     sf->RemoveScrollPositionListener(this);
   }
 
   nsContentUtils::DestroyAnonymousContent(&mTouchCaretElement);
   nsContentUtils::DestroyAnonymousContent(&mSelectionCaretsStartElement);
   nsContentUtils::DestroyAnonymousContent(&mSelectionCaretsEndElement);
+
+  // Elements inserted in the custom content container have the same lifetime as
+  // the document, so before destroying the container, make sure to keep a clone
+  // of each of them at document level so they can be re-appended on reframe.
+  if (mCustomContentContainer) {
+    nsCOMPtr<nsIDocument> doc = mContent->OwnerDoc();
+    ErrorResult rv;
+
+    for (int32_t i = doc->GetAnonymousContents().Length() - 1; i >= 0; --i) {
+      AnonymousContent* content = doc->GetAnonymousContents()[i];
+      nsCOMPtr<nsINode> clonedElement = content->GetContentNode()->CloneNode(true, rv);
+      content->SetContentNode(clonedElement->AsElement());
+    }
+  }
+  nsContentUtils::DestroyAnonymousContent(&mCustomContentContainer);
+
   nsContainerFrame::DestroyFrom(aDestructRoot);
 }
 
 void
 nsCanvasFrame::ScrollPositionWillChange(nscoord aX, nscoord aY)
 {
   if (mDoPaintFocus) {
     mDoPaintFocus = false;
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -82,16 +82,21 @@ public:
     return mSelectionCaretsStartElement;
   }
 
   mozilla::dom::Element* GetSelectionCaretsEndElement() const
   {
     return mSelectionCaretsEndElement;
   }
 
+  mozilla::dom::Element* GetCustomContentContainer() const
+  {
+    return mCustomContentContainer;
+  }
+
   /** SetHasFocus tells the CanvasFrame to draw with focus ring
    *  @param aHasFocus true to show focus ring, false to hide it
    */
   NS_IMETHOD SetHasFocus(bool aHasFocus);
 
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
@@ -133,16 +138,17 @@ public:
 protected:
   // Data members
   bool                      mDoPaintFocus;
   bool                      mAddedScrollPositionListener;
 
   nsCOMPtr<mozilla::dom::Element> mTouchCaretElement;
   nsCOMPtr<mozilla::dom::Element> mSelectionCaretsStartElement;
   nsCOMPtr<mozilla::dom::Element> mSelectionCaretsEndElement;
+  nsCOMPtr<mozilla::dom::Element> mCustomContentContainer;
 };
 
 /**
  * Override nsDisplayBackground methods so that we pass aBGClipRect to
  * PaintBackground, covering the whole overflow area.
  * We can also paint an "extra background color" behind the normal
  * background.
  */
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -6909,18 +6909,22 @@ nsIFrame::GetFrameFromDirection(nsDirect
       return result;
 
     if (aDirection == eDirNext)
       frameTraversal->Next();
     else
       frameTraversal->Prev();
 
     traversedFrame = frameTraversal->CurrentItem();
-    if (!traversedFrame)
+
+    // Skip anonymous elements
+    if (!traversedFrame ||
+        traversedFrame->GetContent()->IsRootOfNativeAnonymousSubtree())
       return NS_ERROR_FAILURE;
+
     traversedFrame->IsSelectable(&selectable, nullptr);
   } // while (!selectable)
 
   *aOutOffset = (aDirection == eDirNext) ? 0 : -1;
 
   if (aVisual) {
     uint8_t newLevel = NS_GET_EMBEDDING_LEVEL(traversedFrame);
     uint8_t newBaseLevel = NS_GET_BASE_LEVEL(traversedFrame);
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2405,17 +2405,17 @@ public:
    *  nsIFrame and the frame offset.  THIS DOES NOT CHANGE SELECTION STATE
    *  uses frame's begin selection state to start. if no selection on this frame will 
    *  return NS_ERROR_FAILURE
    *  @param aPOS is defined in nsFrameSelection
    */
   virtual nsresult PeekOffset(nsPeekOffsetStruct *aPos);
 
   /**
-   *  called to find the previous/next selectable leaf frame.
+   *  called to find the previous/next non-anonymous selectable leaf frame.
    *  @param aDirection [in] the direction to move in (eDirPrevious or eDirNext)
    *  @param aVisual [in] whether bidi caret behavior is visual (true) or logical (false)
    *  @param aJumpLines [in] whether to allow jumping across line boundaries
    *  @param aScrollViewStop [in] whether to stop when reaching a scroll frame boundary
    *  @param aOutFrame [out] the previous/next selectable leaf frame
    *  @param aOutOffset [out] 0 indicates that we arrived at the beginning of the output frame;
    *                          -1 indicates that we arrived at its end.
    *  @param aOutJumpedLine [out] whether this frame and the returned frame are on different lines
--- a/layout/style/ua.css
+++ b/layout/style/ua.css
@@ -385,8 +385,22 @@ div:-moz-native-anonymous.moz-selectionc
 div:-moz-native-anonymous.moz-touchcaret.hidden,
 div:-moz-native-anonymous.moz-selectioncaret-left.hidden,
 div:-moz-native-anonymous.moz-selectioncaret-right.hidden {
   width: 0px;
   height: 0px;
   margin: 0px;
   visibility: hidden;
 }
+
+/* Custom content container in the CanvasFrame, fixed positioned on top of
+   everything else, not reacting to pointer events. */
+div:-moz-native-anonymous.moz-custom-content-container {
+  pointer-events: none;
+
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+
+  z-index: 2147483648;
+}