Bug 1461742, add a mechanism so that custom elements can implement interfaces akin to XBL implements. This is accomplished by an additional chrome-only callback getCustomInterface that can be implemented by custom elements, r=bz
☠☠ backed out by 17dd12ac3d4e ☠ ☠
authorNeil Deakin <neil@mozilla.com>
Thu, 19 Jul 2018 06:15:26 -0400
changeset 427265 30bd77de7bf5
parent 427264 a1e60967e3c4
child 427266 ca5415dbc51a
push id105427
push userneil@mozilla.com
push dateThu, 19 Jul 2018 10:16:55 +0000
treeherdermozilla-inbound@7eb107c36789 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1461742
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 1461742, add a mechanism so that custom elements can implement interfaces akin to XBL implements. This is accomplished by an additional chrome-only callback getCustomInterface that can be implemented by custom elements, r=bz
dom/base/CustomElementRegistry.cpp
dom/base/CustomElementRegistry.h
dom/base/Element.cpp
dom/base/Element.h
dom/base/nsIDocument.h
dom/webidl/WebComponents.webidl
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/dom/CustomElementRegistryBinding.h"
 #include "mozilla/dom/HTMLElementBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/WebComponentsBinding.h"
 #include "mozilla/dom/DocGroup.h"
 #include "nsHTMLTags.h"
 #include "jsapi.h"
+#include "xpcprivate.h"
 #include "nsGlobalWindow.h"
 
 namespace mozilla {
 namespace dom {
 
 //-----------------------------------------------------
 // CustomElementUpgradeReaction
 
@@ -87,16 +88,19 @@ CustomElementCallback::Call()
     case nsIDocument::eAdopted:
       static_cast<LifecycleAdoptedCallback *>(mCallback.get())->Call(mThisObject,
         mAdoptedCallbackArgs.mOldDocument, mAdoptedCallbackArgs.mNewDocument);
       break;
     case nsIDocument::eAttributeChanged:
       static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
         mArgs.name, mArgs.oldValue, mArgs.newValue, mArgs.namespaceURI);
       break;
+    case nsIDocument::eGetCustomInterface:
+      NS_NOTREACHED("Don't call GetCustomInterface through callback");
+      break;
   }
 }
 
 void
 CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const
 {
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject");
   aCb.NoteXPCOMChild(mThisObject);
@@ -470,16 +474,20 @@ CustomElementRegistry::CreateCustomEleme
       }
       break;
 
     case nsIDocument::eAttributeChanged:
       if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
         func = aDefinition->mCallbacks->mAttributeChangedCallback.Value();
       }
       break;
+
+    case nsIDocument::eGetCustomInterface:
+      NS_NOTREACHED("Don't call GetCustomInterface through callback");
+      break;
   }
 
   // If there is no such callback, stop.
   if (!func) {
     return nullptr;
   }
 
   // Add CALLBACK to ELEMENT's callback queue.
@@ -826,17 +834,18 @@ CustomElementRegistry::Define(JSContext*
       aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("constructor.prototype"));
       return;
     }
 
     /**
      * 10.3. Let lifecycleCallbacks be a map with the four keys
      *       "connectedCallback", "disconnectedCallback", "adoptedCallback", and
      *       "attributeChangedCallback", each of which belongs to an entry whose
-     *       value is null.
+     *       value is null. The 'getCustomInterface' callback is also included
+     *       for chrome usage.
      * 10.4. For each of the four keys callbackName in lifecycleCallbacks:
      *       1. Let callbackValue be Get(prototype, callbackName). Rethrow any
      *          exceptions.
      *       2. If callbackValue is not undefined, then set the value of the
      *          entry in lifecycleCallbacks with key callbackName to the result
      *          of converting callbackValue to the Web IDL Function callback type.
      *          Rethrow any exceptions from the conversion.
      */
@@ -1156,16 +1165,61 @@ CustomElementRegistry::Upgrade(Element* 
   // Step 8.
   data->mState = CustomElementData::State::eCustom;
   aElement->SetDefined(true);
 
   // Step 9.
   aElement->SetCustomElementDefinition(aDefinition);
 }
 
+already_AddRefed<nsISupports>
+CustomElementRegistry::CallGetCustomInterface(Element* aElement,
+                                              const nsIID& aIID)
+{
+  MOZ_ASSERT(aElement);
+
+  if (nsContentUtils::IsChromeDoc(aElement->OwnerDoc())) {
+    CustomElementDefinition* definition = aElement->GetCustomElementDefinition();
+    if (definition && definition->mCallbacks &&
+        definition->mCallbacks->mGetCustomInterfaceCallback.WasPassed() &&
+        definition->mLocalName == aElement->NodeInfo()->NameAtom()) {
+
+      LifecycleGetCustomInterfaceCallback* func =
+        definition->mCallbacks->mGetCustomInterfaceCallback.Value();
+      JS::Rooted<JSObject*> customInterface(RootingCx());
+
+      nsCOMPtr<nsIJSID> iid = nsJSID::NewID(aIID);
+      func->Call(aElement, iid, &customInterface);
+      if (customInterface) {
+        RefPtr<nsXPCWrappedJS> wrappedJS;
+        nsresult rv =
+          nsXPCWrappedJS::GetNewOrUsed(customInterface,
+                                       NS_GET_IID(nsISupports),
+                                       getter_AddRefs(wrappedJS)); 
+        if (NS_SUCCEEDED(rv) && wrappedJS) {
+          // Check if the returned object implements the desired interface. 
+          nsCOMPtr<nsISupports> retval;
+          if (NS_SUCCEEDED(wrappedJS->QueryInterface(aIID,
+                                                     getter_AddRefs(retval)))) {        
+            return retval.forget();
+          }
+        }
+      }
+    }
+  }
+
+  // Otherwise, check if the element supports the interface directly, and just use that.
+  nsCOMPtr<nsISupports> supports;
+  if (NS_SUCCEEDED(aElement->QueryInterface(aIID, getter_AddRefs(supports)))) {
+    return supports.forget();
+  }
+
+  return nullptr;
+}
+
 //-----------------------------------------------------
 // CustomElementReactionsStack
 
 void
 CustomElementReactionsStack::CreateAndPushElementQueue()
 {
   MOZ_ASSERT(mRecursionDepth);
   MOZ_ASSERT(!mIsElementQueuePushedForCurrentRecursionDepth);
@@ -1367,16 +1421,21 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
     cb.NoteXPCOMChild(callbacks->mDisconnectedCallback.Value());
   }
 
   if (callbacks->mAdoptedCallback.WasPassed()) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mAdoptedCallback");
     cb.NoteXPCOMChild(callbacks->mAdoptedCallback.Value());
   }
 
+  if (callbacks->mGetCustomInterfaceCallback.WasPassed()) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCallbacks->mGetCustomInterfaceCallback");
+    cb.NoteXPCOMChild(callbacks->mGetCustomInterfaceCallback.Value());
+  }
+
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mConstructor");
   cb.NoteXPCOMChild(tmp->mConstructor);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementDefinition)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CustomElementDefinition, AddRef)
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -412,16 +412,30 @@ public:
 
   /**
    * Upgrade an element.
    * https://html.spec.whatwg.org/multipage/scripting.html#upgrades
    */
   static void Upgrade(Element* aElement, CustomElementDefinition* aDefinition, ErrorResult& aRv);
 
   /**
+   * To allow native code to call methods of chrome-implemented custom elements,
+   * a helper method may be defined in the custom element called
+   * 'getCustomInterfaceCallback'. This method takes an IID and returns an
+   * object which implements an XPCOM interface. If there is no
+   * getCustomInterfaceCallback or the callback doesn't return an object,
+   * QueryInterface is called on aElement to see if this interface is
+   * implemented directly.
+   *
+   * This returns null if aElement is not from a chrome document.
+   */
+  static already_AddRefed<nsISupports> CallGetCustomInterface(
+    Element* aElement, const nsIID& aIID);
+
+  /**
    * Registers an unresolved custom element that is a candidate for
    * upgrade. |aTypeName| is the name of the custom element type, if it is not
    * provided, then element name is used. |aTypeName| should be provided
    * when registering a custom element that extends an existing
    * element. e.g. <button is="x-button">.
    */
   void RegisterUnresolvedElement(Element* aElement,
                                  nsAtom* aTypeName = nullptr);
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -4299,16 +4299,26 @@ Element::UpdateIntersectionObservation(D
   bool updated = false;
   if (auto entry = observers->Lookup(aObserver)) {
     updated = entry.Data() != aThreshold;
     entry.Data() = aThreshold;
   }
   return updated;
 }
 
+template<class T> void
+Element::GetCustomInterface(nsGetterAddRefs<T> aResult)
+{
+  nsCOMPtr<nsISupports> iface =
+    CustomElementRegistry::CallGetCustomInterface(this, NS_GET_TEMPLATE_IID(T));
+  if (iface) {
+    CallQueryInterface(iface, static_cast<T**>(aResult));
+  }
+}
+
 void
 Element::ClearServoData(nsIDocument* aDoc) {
   MOZ_ASSERT(aDoc);
   if (HasServoData()) {
     Servo_Element_ClearData(this);
   } else {
     UnsetFlags(kAllServoDescendantBits | NODE_NEEDS_FRAME);
   }
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1982,16 +1982,24 @@ private:
   const nsAttrValue* GetSVGAnimatedClass() const;
 
   /**
    * Get this element's client area rect in app units.
    * @return the frame's client area
    */
   MOZ_CAN_RUN_SCRIPT nsRect GetClientAreaRect();
 
+  /**
+   * GetCustomInterface is somewhat like a GetInterface, but it is expected
+   * that the implementation is provided by a custom element or via the 
+   * the XBL implements keyword. To use this, create a public method that
+   * wraps a call to GetCustomInterface.
+   */
+  template<class T> void GetCustomInterface(nsGetterAddRefs<T> aResult);
+
   // Prevent people from doing pointless checks/casts on Element instances.
   void IsElement() = delete;
   void AsElement() = delete;
 
   // Data members
   EventStates mState;
   // Per-node data managed by Servo.
   //
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -3078,17 +3078,18 @@ public:
   {
     return GetRootElement();
   }
 
   enum ElementCallbackType {
     eConnected,
     eDisconnected,
     eAdopted,
-    eAttributeChanged
+    eAttributeChanged,
+    eGetCustomInterface
   };
 
   nsIDocument* GetTopLevelContentDocument();
 
   // Returns the associated XUL window if this is a top-level chrome document,
   // null otherwise.
   already_AddRefed<nsIXULWindow> GetXULWindowIfToplevelChrome() const;
 
--- a/dom/webidl/WebComponents.webidl
+++ b/dom/webidl/WebComponents.webidl
@@ -5,23 +5,27 @@
  *
  * The origin of this IDL file is
  * http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
+interface IID;
+
 callback LifecycleConnectedCallback = void();
 callback LifecycleDisconnectedCallback = void();
 callback LifecycleAdoptedCallback = void(Document? oldDocument,
                                          Document? newDocment);
 callback LifecycleAttributeChangedCallback = void(DOMString attrName,
                                                   DOMString? oldValue,
                                                   DOMString? newValue,
                                                   DOMString? namespaceURI);
+callback LifecycleGetCustomInterfaceCallback = object?(IID iid);
 
 dictionary LifecycleCallbacks {
   LifecycleConnectedCallback connectedCallback;
   LifecycleDisconnectedCallback disconnectedCallback;
   LifecycleAdoptedCallback adoptedCallback;
   LifecycleAttributeChangedCallback attributeChangedCallback;
+  [ChromeOnly] LifecycleGetCustomInterfaceCallback getCustomInterfaceCallback;
 };