From 159aa067dfa9536cb368b9682983922ab7ee83d9 Mon Sep 17 00:00:00 2001 From: Patrick Brosset Date: Tue, 28 Oct 2014 11:15:25 +0100 Subject: [PATCH] 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 | 6 +++ dom/base/AnonymousContent.h | 1 + dom/base/nsDocument.cpp | 75 ++++++++++++++++++++++++++++++++ dom/base/nsIDocument.h | 12 +++++ dom/webidl/Document.webidl | 24 ++++++++++ layout/generic/nsCanvasFrame.cpp | 41 ++++++++++++++++- layout/generic/nsCanvasFrame.h | 6 +++ layout/generic/nsFrame.cpp | 6 ++- layout/generic/nsIFrame.h | 2 +- layout/style/ua.css | 14 ++++++ 10 files changed, 183 insertions(+), 4 deletions(-) diff --git a/dom/base/AnonymousContent.cpp b/dom/base/AnonymousContent.cpp index adcf9b0d8828..6c80b96c7844 100644 --- a/dom/base/AnonymousContent.cpp +++ b/dom/base/AnonymousContent.cpp @@ -33,6 +33,12 @@ AnonymousContent::GetContentNode() return mContentNode; } +void +AnonymousContent::SetContentNode(Element* aContentNode) +{ + mContentNode = aContentNode; +} + void AnonymousContent::SetTextContentForElement(const nsAString& aElementId, const nsAString& aText, diff --git a/dom/base/AnonymousContent.h b/dom/base/AnonymousContent.h index f6d9845260e3..44f073555836 100644 --- a/dom/base/AnonymousContent.h +++ b/dom/base/AnonymousContent.h @@ -26,6 +26,7 @@ public: explicit AnonymousContent(Element* aContentNode); nsCOMPtr GetContentNode(); + void SetContentNode(Element* aContentNode); JSObject* WrapObject(JSContext* aCx); // WebIDL methods diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 444dc0298322..e3d5d0abf265 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -68,6 +68,7 @@ #include "nsIServiceManager.h" #include "nsIServiceWorkerManager.h" +#include "nsCanvasFrame.h" #include "nsContentCID.h" #include "nsError.h" #include "nsPresShell.h" @@ -182,6 +183,7 @@ #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" @@ -1986,6 +1988,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument) 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) @@ -5138,6 +5141,78 @@ nsDocument::StyleRuleRemoved(nsIStyleSheet* aSheet, #undef DO_STYLESHEET_NOTIFICATION +already_AddRefed +nsIDocument::InsertAnonymousContent(Element& aElement, ErrorResult& aRv) +{ + nsIPresShell* shell = GetShell(); + if (!shell || !shell->GetCanvasFrame()) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsCOMPtr container = shell->GetCanvasFrame() + ->GetCustomContentContainer(); + if (!container) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + // Clone the node to avoid returning a direct reference + nsCOMPtr 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 = + 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 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 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 diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index fb26c33884cd..0550acdb2441 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -92,6 +92,7 @@ class ImageLoader; namespace dom { class AnimationTimeline; +class AnonymousContent; class Attr; class BoxObject; class CDATASection; @@ -715,6 +716,15 @@ public: return mDidDocumentOpen; } + already_AddRefed + InsertAnonymousContent(mozilla::dom::Element& aElement, + mozilla::ErrorResult& aError); + void RemoveAnonymousContent(mozilla::dom::AnonymousContent& aContent, + mozilla::ErrorResult& aError); + nsTArray>& GetAnonymousContents() { + return mAnonymousContents; + } + protected: virtual Element *GetRootElementInternal() const = 0; @@ -2747,6 +2757,8 @@ protected: nsRefPtr mXPathEvaluator; + nsTArray> mAnonymousContents; + uint32_t mBlockDOMContentLoaded; bool mDidFireDOMContentLoaded:1; }; diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl index cb1fedd45aa5..bafb78813b67 100644 --- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -355,6 +355,30 @@ 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; diff --git a/layout/generic/nsCanvasFrame.cpp b/layout/generic/nsCanvasFrame.cpp index b6501b2d5ef8..2b8185a048fd 100644 --- a/layout/generic/nsCanvasFrame.cpp +++ b/layout/generic/nsCanvasFrame.cpp @@ -21,6 +21,7 @@ #include "nsFrameManager.h" #include "gfxPlatform.h" #include "nsPrintfCString.h" +#include "mozilla/dom/AnonymousContent.h" // for touchcaret #include "nsContentList.h" #include "nsContentCreatorFunctions.h" @@ -35,6 +36,7 @@ //#define DEBUG_CANVAS_FOCUS using namespace mozilla; +using namespace mozilla::dom; using namespace mozilla::layout; using namespace mozilla::gfx; @@ -63,7 +65,7 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray& aElements) ErrorResult er; // We won't create touch caret element if preference is not enabled. if (PresShell::TouchCaretPrefEnabled()) { - nsRefPtr nodeInfo; + nsRefPtr nodeInfo; // Create and append touch caret frame. nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr, @@ -72,7 +74,7 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray& aElements) 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); @@ -103,6 +105,23 @@ nsCanvasFrame::CreateAnonymousContent(nsTArray& aElements) 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 node = doc->GetAnonymousContents()[i]->GetContentNode(); + mCustomContentContainer->AppendChildTo(node->AsContent(), true); + } + return NS_OK; } @@ -120,6 +139,8 @@ nsCanvasFrame::AppendAnonymousContentTo(nsTArray& aElements, uint32 if (mSelectionCaretsEndElement) { aElements.AppendElement(mSelectionCaretsEndElement); } + + aElements.AppendElement(mCustomContentContainer); } void @@ -134,6 +155,22 @@ nsCanvasFrame::DestroyFrom(nsIFrame* aDestructRoot) 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 doc = mContent->OwnerDoc(); + ErrorResult rv; + + for (int32_t i = doc->GetAnonymousContents().Length() - 1; i >= 0; --i) { + AnonymousContent* content = doc->GetAnonymousContents()[i]; + nsCOMPtr clonedElement = content->GetContentNode()->CloneNode(true, rv); + content->SetContentNode(clonedElement->AsElement()); + } + } + nsContentUtils::DestroyAnonymousContent(&mCustomContentContainer); + nsContainerFrame::DestroyFrom(aDestructRoot); } diff --git a/layout/generic/nsCanvasFrame.h b/layout/generic/nsCanvasFrame.h index 125e292f47a7..7e45f197b9e2 100644 --- a/layout/generic/nsCanvasFrame.h +++ b/layout/generic/nsCanvasFrame.h @@ -87,6 +87,11 @@ public: 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 */ @@ -138,6 +143,7 @@ protected: nsCOMPtr mTouchCaretElement; nsCOMPtr mSelectionCaretsStartElement; nsCOMPtr mSelectionCaretsEndElement; + nsCOMPtr mCustomContentContainer; }; /** diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index e757b964e311..ed331649e0dc 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -6914,8 +6914,12 @@ nsIFrame::GetFrameFromDirection(nsDirection aDirection, bool aVisual, 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) diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 7d156e28bb4d..3bf6d18d68b3 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -2410,7 +2410,7 @@ public: 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 diff --git a/layout/style/ua.css b/layout/style/ua.css index fc53de9b47d7..ba865d079fcf 100644 --- a/layout/style/ua.css +++ b/layout/style/ua.css @@ -390,3 +390,17 @@ div:-moz-native-anonymous.moz-selectioncaret-right.hidden { 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; +}