Bug 1404842 - P1: Implement Element.attachShadow and Element.slot, r=smaug draft
authorBen Tian <btian@mozilla.com>
Thu, 19 Oct 2017 14:44:35 +0800
changeset 687348 d8378628b8f7028f6b3600c5abd20dc4fc6a9ccb
parent 687186 aa958b29c149a67fce772f8473e9586e71fbdb46
child 687349 fc0bbcc59f2fea23f0b38b21d98372f4a88efbe5
child 687446 95837a286a77514901257d906c6d4a355b1e92e9
push id86478
push userbmo:btian@mozilla.com
push dateFri, 27 Oct 2017 06:14:53 +0000
reviewerssmaug
bugs1404842
milestone58.0a1
Bug 1404842 - P1: Implement Element.attachShadow and Element.slot, r=smaug MozReview-Commit-ID: KWy8mDqEw4o
dom/base/Element.cpp
dom/base/Element.h
dom/base/ShadowRoot.cpp
dom/base/ShadowRoot.h
dom/webidl/Element.webidl
dom/webidl/ShadowRoot.webidl
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1137,19 +1137,88 @@ Element::RemoveFromIdTable()
   } else {
     nsIDocument* doc = GetUncomposedDoc();
     if (doc && (!IsInAnonymousSubtree() || doc->IsXULDocument())) {
       doc->RemoveFromIdTable(this, id);
     }
   }
 }
 
+void
+Element::SetSlot(const nsAString& aName, ErrorResult& aError)
+{
+  aError = SetAttr(kNameSpaceID_None, nsGkAtoms::slot, aName, true);
+}
+
+void
+Element::GetSlot(nsAString& aName)
+{
+  GetAttr(kNameSpaceID_None, nsGkAtoms::slot, aName);
+}
+
+// https://dom.spec.whatwg.org/#dom-element-attachshadow
+already_AddRefed<ShadowRoot>
+Element::AttachShadow(const ShadowRootInit& aInit, ErrorResult& aError)
+{
+  /**
+   * 1. If context object’s namespace is not the HTML namespace,
+   *    then throw a "NotSupportedError" DOMException.
+   */
+  if (!IsHTMLElement()) {
+    aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return nullptr;
+  }
+
+  /**
+   * 2. If context object’s local name is not
+   *      a valid custom element name, "article", "aside", "blockquote",
+   *      "body", "div", "footer", "h1", "h2", "h3", "h4", "h5", "h6",
+   *      "header", "main" "nav", "p", "section", or "span",
+   *    then throw a "NotSupportedError" DOMException.
+   */
+  nsAtom* nameAtom = NodeInfo()->NameAtom();
+  if (!(nsContentUtils::IsCustomElementName(nameAtom) ||
+        nameAtom == nsGkAtoms::article ||
+        nameAtom == nsGkAtoms::aside ||
+        nameAtom == nsGkAtoms::blockquote ||
+        nameAtom == nsGkAtoms::body ||
+        nameAtom == nsGkAtoms::div ||
+        nameAtom == nsGkAtoms::footer ||
+        nameAtom == nsGkAtoms::h1 ||
+        nameAtom == nsGkAtoms::h2 ||
+        nameAtom == nsGkAtoms::h3 ||
+        nameAtom == nsGkAtoms::h4 ||
+        nameAtom == nsGkAtoms::h5 ||
+        nameAtom == nsGkAtoms::h6 ||
+        nameAtom == nsGkAtoms::header ||
+        nameAtom == nsGkAtoms::main ||
+        nameAtom == nsGkAtoms::nav ||
+        nameAtom == nsGkAtoms::p ||
+        nameAtom == nsGkAtoms::section ||
+        nameAtom == nsGkAtoms::span)) {
+    aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return nullptr;
+  }
+
+  return AttachShadowInternal(aInit.mMode == ShadowRootMode::Closed, aError);
+}
+
 already_AddRefed<ShadowRoot>
 Element::CreateShadowRoot(ErrorResult& aError)
 {
+  return AttachShadowInternal(false, aError);
+}
+
+already_AddRefed<ShadowRoot>
+Element::AttachShadowInternal(bool aClosed, ErrorResult& aError)
+{
+  /**
+   * 3. If context object is a shadow host, then throw
+   *    an "InvalidStateError" DOMException.
+   */
   if (GetShadowRoot()) {
     aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   nsAutoScriptBlocker scriptBlocker;
 
   RefPtr<mozilla::dom::NodeInfo> nodeInfo;
@@ -1176,29 +1245,41 @@ Element::CreateShadowRoot(ErrorResult& a
   MOZ_ASSERT(!GetPrimaryFrame());
 
   // Unlike for XBL, false is the default for inheriting style.
   protoBinding->SetInheritsStyle(false);
 
   // Calling SetPrototypeBinding takes ownership of protoBinding.
   docInfo->SetPrototypeBinding(NS_LITERAL_CSTRING("shadowroot"), protoBinding);
 
-  RefPtr<ShadowRoot> shadowRoot = new ShadowRoot(this, nodeInfo.forget(),
-                                                   protoBinding);
+  /**
+   * 4. Let shadow be a new shadow root whose node document is
+   *    context object’s node document, host is context object,
+   *    and mode is init’s mode.
+   */
+  RefPtr<ShadowRoot> shadowRoot =
+    new ShadowRoot(this, aClosed, nodeInfo.forget(), protoBinding);
 
   shadowRoot->SetIsComposedDocParticipant(IsInComposedDoc());
 
+  /**
+   * 5. Set context object’s shadow root to shadow.
+   */
   SetShadowRoot(shadowRoot);
 
   // xblBinding takes ownership of docInfo.
   RefPtr<nsXBLBinding> xblBinding = new nsXBLBinding(shadowRoot, protoBinding);
   shadowRoot->SetAssociatedBinding(xblBinding);
   xblBinding->SetBoundElement(this);
 
   SetXBLBinding(xblBinding);
+
+  /**
+   * 6. Return shadow.
+   */
   return shadowRoot.forget();
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DestinationInsertionPointList, mParent,
                                       mDestinationPoints)
 
 NS_INTERFACE_TABLE_HEAD(DestinationInsertionPointList)
   NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -601,16 +601,19 @@ protected:
    * time and other places where we don't want to notify a state
    * change.
    */
   void RemoveStatesSilently(EventStates aStates)
   {
     mState &= ~aStates;
   }
 
+  already_AddRefed<ShadowRoot> AttachShadowInternal(bool aClosed,
+                                                    ErrorResult& aError);
+
 private:
   // Need to allow the ESM, nsGlobalWindow, and the focus manager to
   // set our state
   friend class mozilla::EventStateManager;
   friend class ::nsGlobalWindow;
   friend class ::nsFocusManager;
 
   // Allow CusomtElementRegistry to call AddStates.
@@ -1077,16 +1080,23 @@ public:
   Attr* GetAttributeNodeNS(const nsAString& aNamespaceURI,
                            const nsAString& aLocalName);
   already_AddRefed<Attr> SetAttributeNodeNS(Attr& aNewAttr,
                                             ErrorResult& aError);
 
   already_AddRefed<DOMRectList> GetClientRects();
   already_AddRefed<DOMRect> GetBoundingClientRect();
 
+  // Shadow DOM v1
+  already_AddRefed<ShadowRoot> AttachShadow(const ShadowRootInit& aInit,
+                                            ErrorResult& aError);
+  void SetSlot(const nsAString& aName, ErrorResult& aError);
+  void GetSlot(nsAString& aName);
+
+  // [deprecated] Shadow DOM v0
   already_AddRefed<ShadowRoot> CreateShadowRoot(ErrorResult& aError);
   already_AddRefed<DestinationInsertionPointList> GetDestinationInsertionPoints();
 
   ShadowRoot *FastGetShadowRoot() const
   {
     nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
     return slots ? slots->mShadowRoot.get() : nullptr;
   }
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -46,25 +46,26 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHE
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
 NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)
 
 NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)
 NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment)
 
-ShadowRoot::ShadowRoot(Element* aElement,
+ShadowRoot::ShadowRoot(Element* aElement, bool aClosed,
                        already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
                        nsXBLPrototypeBinding* aProtoBinding)
   : DocumentFragment(aNodeInfo)
   , mProtoBinding(aProtoBinding)
   , mInsertionPointChanged(false)
   , mIsComposedDocParticipant(false)
 {
   SetHost(aElement);
+  mMode = aClosed ? ShadowRootMode::Closed : ShadowRootMode::Open;
 
   // Nodes in a shadow tree should never store a value
   // in the subtree root pointer, nodes in the shadow tree
   // track the subtree root using GetContainingShadow().
   ClearSubtreeRootPointer();
 
   SetFlags(NODE_IS_IN_SHADOW_TREE);
 
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -36,20 +36,28 @@ public:
                                            DocumentFragment)
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
 
-  ShadowRoot(Element* aElement,
+  ShadowRoot(Element* aElement, bool aClosed,
              already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
              nsXBLPrototypeBinding* aProtoBinding);
 
+  // Shadow DOM v1
+  Element* Host();
+  ShadowRootMode Mode()
+  {
+    return mMode;
+  }
+
+  // [deprecated] Shadow DOM v0
   void AddToIdTable(Element* aElement, nsAtom* aId);
   void RemoveFromIdTable(Element* aElement, nsAtom* aId);
   void InsertSheet(StyleSheet* aSheet, nsIContent* aLinkingContent);
   void RemoveSheet(StyleSheet* aSheet);
   bool ApplyAuthorStyles();
   void SetApplyAuthorStyles(bool aApplyAuthorStyles);
   StyleSheetList* StyleSheets();
 
@@ -116,28 +124,29 @@ public:
     GetElementsByTagName(const nsAString& aNamespaceURI);
   already_AddRefed<nsContentList>
     GetElementsByTagNameNS(const nsAString& aNamespaceURI,
                            const nsAString& aLocalName);
   already_AddRefed<nsContentList>
     GetElementsByClassName(const nsAString& aClasses);
   void GetInnerHTML(nsAString& aInnerHTML);
   void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
-  Element* Host();
   void StyleSheetChanged();
 
   bool IsComposedDocParticipant() { return mIsComposedDocParticipant; }
   void SetIsComposedDocParticipant(bool aIsComposedDocParticipant)
   {
     mIsComposedDocParticipant = aIsComposedDocParticipant;
   }
 
 protected:
   virtual ~ShadowRoot();
 
+  ShadowRootMode mMode;
+
   // An array of content insertion points that are a descendant of the ShadowRoot
   // sorted in tree order. Insertion points are responsible for notifying
   // the ShadowRoot when they are removed or added as a descendant. The insertion
   // points are kept alive by the parent node, thus weak references are held
   // by the array.
   nsTArray<HTMLContentElement*> mInsertionPoints;
 
   nsTHashtable<nsIdentifierMapEntry> mIdentifierMap;
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -230,26 +230,38 @@ partial interface Element {
 // http://www.w3.org/TR/selectors-api/#interface-definitions
 partial interface Element {
   [Throws, Pure]
   Element?  querySelector(DOMString selectors);
   [Throws, Pure]
   NodeList  querySelectorAll(DOMString selectors);
 };
 
-// http://w3c.github.io/webcomponents/spec/shadow/#extensions-to-element-interface
+// https://dom.spec.whatwg.org/#dictdef-shadowrootinit
+dictionary ShadowRootInit {
+  required ShadowRootMode mode;
+};
+
+// https://dom.spec.whatwg.org/#element
 partial interface Element {
-  [Throws,Pref="dom.webcomponents.enabled"]
-  ShadowRoot createShadowRoot();
-  [Pref="dom.webcomponents.enabled"]
-  NodeList getDestinationInsertionPoints();
+  // Shadow DOM v1
+  [Throws, Pref="dom.webcomponents.enabled"]
+  ShadowRoot attachShadow(ShadowRootInit shadowRootInitDict);
   [Pref="dom.webcomponents.enabled"]
   readonly attribute ShadowRoot? shadowRoot;
   [Pref="dom.webcomponents.enabled"]
   readonly attribute HTMLSlotElement? assignedSlot;
+  [CEReactions, Unscopable, SetterThrows, Pref="dom.webcomponents.enabled"]
+           attribute DOMString slot;
+
+  // [deprecated] Shadow DOM v0
+  [Throws, Pref="dom.webcomponents.enabled"]
+  ShadowRoot createShadowRoot();
+  [Pref="dom.webcomponents.enabled"]
+  NodeList getDestinationInsertionPoints();
 };
 
 Element implements ChildNode;
 Element implements NonDocumentTypeChildNode;
 Element implements ParentNode;
 Element implements Animatable;
 Element implements GeometryUtils;
 
--- a/dom/webidl/ShadowRoot.webidl
+++ b/dom/webidl/ShadowRoot.webidl
@@ -5,22 +5,33 @@
  *
  * The origin of this IDL file is
  * https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
+// https://dom.spec.whatwg.org/#enumdef-shadowrootmode
+enum ShadowRootMode {
+  "open",
+  "closed"
+};
+
+// https://dom.spec.whatwg.org/#shadowroot
 [Pref="dom.webcomponents.enabled"]
 interface ShadowRoot : DocumentFragment
 {
+  // Shadow DOM v1
+  readonly attribute ShadowRootMode mode;
+  readonly attribute Element host;
+
+  // [deprecated] Shadow DOM v0
   Element? getElementById(DOMString elementId);
   HTMLCollection getElementsByTagName(DOMString localName);
   HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
   HTMLCollection getElementsByClassName(DOMString classNames);
   [CEReactions, SetterThrows, TreatNullAs=EmptyString]
   attribute DOMString innerHTML;
-  readonly attribute Element host;
   attribute boolean applyAuthorStyles;
   readonly attribute StyleSheetList styleSheets;
 };