diff --git a/dom/base/CustomElementRegistry.cpp b/dom/base/CustomElementRegistry.cpp index 5d90627d5c36..c477d18bdbc8 100644 --- a/dom/base/CustomElementRegistry.cpp +++ b/dom/base/CustomElementRegistry.cpp @@ -14,6 +14,7 @@ #include "mozilla/dom/DocGroup.h" #include "nsHTMLTags.h" #include "jsapi.h" +#include "xpcprivate.h" #include "nsGlobalWindow.h" namespace mozilla { @@ -92,6 +93,9 @@ CustomElementCallback::Call() static_cast(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; } } @@ -475,6 +479,10 @@ CustomElementRegistry::CreateCustomElementCallback( 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. @@ -831,7 +839,8 @@ CustomElementRegistry::Define(JSContext* aCx, * 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. @@ -1161,6 +1170,51 @@ CustomElementRegistry::Upgrade(Element* aElement, aElement->SetCustomElementDefinition(aDefinition); } +already_AddRefed +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 customInterface(RootingCx()); + + nsCOMPtr iid = nsJSID::NewID(aIID); + func->Call(aElement, iid, &customInterface); + if (customInterface) { + RefPtr 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 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 supports; + if (NS_SUCCEEDED(aElement->QueryInterface(aIID, getter_AddRefs(supports)))) { + return supports.forget(); + } + + return nullptr; +} + //----------------------------------------------------- // CustomElementReactionsStack @@ -1372,6 +1426,11 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementDefinition) 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 diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h index a28858cdc693..8f28fa2ae920 100644 --- a/dom/base/CustomElementRegistry.h +++ b/dom/base/CustomElementRegistry.h @@ -416,6 +416,20 @@ public: */ 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 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 diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index ba1c9b8c60dc..234f595688ac 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -4304,6 +4304,16 @@ Element::UpdateIntersectionObservation(DOMIntersectionObserver* aObserver, int32 return updated; } +template void +Element::GetCustomInterface(nsGetterAddRefs aResult) +{ + nsCOMPtr iface = + CustomElementRegistry::CallGetCustomInterface(this, NS_GET_TEMPLATE_IID(T)); + if (iface) { + CallQueryInterface(iface, static_cast(aResult)); + } +} + void Element::ClearServoData(nsIDocument* aDoc) { MOZ_ASSERT(aDoc); diff --git a/dom/base/Element.h b/dom/base/Element.h index aec29dfdc324..7e1cdce055ab 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -1987,6 +1987,14 @@ private: */ 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 void GetCustomInterface(nsGetterAddRefs aResult); + // Prevent people from doing pointless checks/casts on Element instances. void IsElement() = delete; void AsElement() = delete; diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index e3be29158839..28370be59f79 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -3083,7 +3083,8 @@ public: eConnected, eDisconnected, eAdopted, - eAttributeChanged + eAttributeChanged, + eGetCustomInterface }; nsIDocument* GetTopLevelContentDocument(); diff --git a/dom/webidl/WebComponents.webidl b/dom/webidl/WebComponents.webidl index ab2fc9c2ced4..23343fd8d932 100644 --- a/dom/webidl/WebComponents.webidl +++ b/dom/webidl/WebComponents.webidl @@ -10,6 +10,8 @@ * liability, trademark and document use rules apply. */ +interface IID; + callback LifecycleConnectedCallback = void(); callback LifecycleDisconnectedCallback = void(); callback LifecycleAdoptedCallback = void(Document? oldDocument, @@ -18,10 +20,12 @@ 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; };