Bug 1478372, allow QueryInterface to be used for custom element implemented interfaces, r=bz,bgrins
authorNeil Deakin <neil@mozilla.com>
Wed, 19 Sep 2018 06:46:41 -0400
changeset 492967 7e69cb7b920612ef9c4d88ea48d5bbdf47970720
parent 492966 6dd5d0b6ba70f12bef8b14d1bff19d15f3f3aac7
child 492968 f29ac15230108557e54eecef54a02b5c2f3cdada
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, bgrins
bugs1478372
milestone64.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 1478372, allow QueryInterface to be used for custom element implemented interfaces, r=bz,bgrins
dom/base/CustomElementRegistry.cpp
dom/base/CustomElementRegistry.h
dom/xul/nsXULElement.cpp
toolkit/content/tests/chrome/test_custom_element_base.xul
--- a/dom/base/CustomElementRegistry.cpp
+++ b/dom/base/CustomElementRegistry.cpp
@@ -1294,43 +1294,32 @@ CustomElementRegistry::CallGetCustomInte
       LifecycleGetCustomInterfaceCallback* func =
         definition->mCallbacks->mGetCustomInterfaceCallback.Value();
       JS::Rooted<JSObject*> customInterface(RootingCx());
 
       nsCOMPtr<nsIJSID> iid = nsJSID::NewID(aIID);
       func->Call(aElement, iid, &customInterface);
       JS::Rooted<JSObject*> funcGlobal(RootingCx(), func->CallbackGlobalOrNull());
       if (customInterface && funcGlobal) {
-        RefPtr<nsXPCWrappedJS> wrappedJS;
         AutoJSAPI jsapi;
         if (jsapi.Init(funcGlobal)) {
+          nsIXPConnect *xpConnect = nsContentUtils::XPConnect();
           JSContext* cx = jsapi.cx();
-          nsresult rv =
-            nsXPCWrappedJS::GetNewOrUsed(cx, 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();
-            }
+
+          nsCOMPtr<nsISupports> wrapper;
+          nsresult rv = xpConnect->WrapJSAggregatedToNative(aElement, cx, customInterface,
+                                                            aIID, getter_AddRefs(wrapper));
+          if (NS_SUCCEEDED(rv)) {
+            return wrapper.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()
--- a/dom/base/CustomElementRegistry.h
+++ b/dom/base/CustomElementRegistry.h
@@ -436,20 +436,17 @@ public:
    * 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.
+   * object which implements an XPCOM interface.
    *
    * 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
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -306,17 +306,27 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mBindingParent);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement)
 NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement)
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement)
     NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE
-NS_INTERFACE_MAP_END_INHERITING(nsStyledElement)
+
+    nsCOMPtr<nsISupports> iface =
+      CustomElementRegistry::CallGetCustomInterface(this, aIID);
+    if (iface) {
+      iface->QueryInterface(aIID, aInstancePtr);
+      if (*aInstancePtr) {
+        return NS_OK;
+      }
+    }
+
+NS_INTERFACE_MAP_END_INHERITING(Element)
 
 //----------------------------------------------------------------------
 // nsINode interface
 
 nsresult
 nsXULElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const
 {
     *aResult = nullptr;
--- a/toolkit/content/tests/chrome/test_custom_element_base.xul
+++ b/toolkit/content/tests/chrome/test_custom_element_base.xul
@@ -7,27 +7,30 @@
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
 
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
 
-  <simpleelement id="simple"/>
+  <button id="one"/>
+  <simpleelement id="two" style="-moz-user-focus: normal;"/>
+  <simpleelement id="three" disabled="true" style="-moz-user-focus: normal;"/>
+  <button id="four"/>
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
 
   SimpleTest.waitForExplicitFinish();
 
   async function runTests() {
     ok(MozXULElement, "MozXULElement defined on the window");
     testParseXULToFragment();
-    testCustomInterface();
+    await testCustomInterface();
 
     let htmlWin = await new Promise(resolve => {
       let htmlIframe = document.createElement("iframe");
       htmlIframe.src = "file_empty.xhtml";
       htmlIframe.onload = () => resolve(htmlIframe.contentWindow);
       document.documentElement.appendChild(htmlIframe);
     });
 
@@ -46,45 +49,86 @@
     let deck = document.documentElement.lastChild;
     ok(deck instanceof MozXULElement, "instance of MozXULElement");
     ok(deck instanceof XULElement, "instance of XULElement");
     is(deck.id, "foo", "attribute set");
     is(deck.selectedIndex, "0", "Custom Element is property attached");
     deck.remove();
   }
 
-  function testCustomInterface() {
+  async function testCustomInterface() {
     class SimpleElement extends MozXULElement {
       get disabled() {
-        return false;
+        return this.getAttribute("disabled") == "true";
       }
 
       set disabled(val) {
+        if (val) this.setAttribute("disabled", "true");
+        else this.removeAttribute("disabled");
+        return val;
       }
 
       get tabIndex() {
-        return 0;
+        return parseInt(this.getAttribute("tabIndex")) || 0;
       }
 
       set tabIndex(val) {
+        if (val) this.setAttribute("tabIndex", val);
+        else this.removeAttribute("tabIndex");
+        return val;
       }
     }
 
+    MozXULElement.implementCustomInterface(SimpleElement, [Ci.nsIDOMXULControlElement]);
     customElements.define("simpleelement", SimpleElement);
-    MozXULElement.implementCustomInterface(SimpleElement, [Ci.nsIDOMXULControlElement]);
+
+    let twoElement = document.getElementById("two");
 
     is(document.documentElement.getCustomInterfaceCallback, undefined,
        "No getCustomInterfaceCallback on non-custom element");
-    is(typeof document.getElementById("simple").getCustomInterfaceCallback, "function",
+    is(typeof twoElement.getCustomInterfaceCallback, "function",
        "getCustomInterfaceCallback available on custom element when set");
     try {
       document.documentElement.QueryInterface(Ci.nsIDOMXULControlElement)
       ok(false, "Non-custom element implements custom interface");
     } catch (ex) {
       ok(true, "Non-custom element implements custom interface");
     }
-    ok(document.getElementById("simple").QueryInterface(Ci.nsIDOMXULControlElement),
-       "Implements custom interface");
+
+    // Try various ways to get the custom interface.
+
+    let asControl = twoElement.getCustomInterfaceCallback(Ci.nsIDOMXULControlElement);
+    ok(asControl, twoElement, "getCustomInterface returns interface implementation ");
+
+    asControl = twoElement.QueryInterface(Ci.nsIDOMXULControlElement);
+    ok(asControl, "QueryInterface to nsIDOMXULControlElement");
+    ok(asControl instanceof Node, "Control is a Node");
+
+    // Now make sure that the custom element handles focus/tabIndex as needed by shitfing
+    // focus around and enabling/disabling the simple elements.
+
+    // Enable Full Keyboard Access emulation on Mac
+    await SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+
+    ok(!twoElement.disabled, "two is enabled");
+    ok(document.getElementById("three").disabled, "three is disabled");
+
+    await SimpleTest.promiseFocus();
+    ok(document.hasFocus(), "has focus");
+    
+    // This should skip the disabled simpleelement.
+    synthesizeKey("VK_TAB");
+    is(document.activeElement.id, "one", "Tab 1");
+    synthesizeKey("VK_TAB");
+    is(document.activeElement.id, "two", "Tab 2");
+    synthesizeKey("VK_TAB");
+    is(document.activeElement.id, "four", "Tab 3");
+
+    twoElement.disabled = true;
+    is(twoElement.getAttribute("disabled"), "true", "two disabled after change");
+
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    is(document.activeElement.id, "one", "Tab 1");
   }
   ]]>
   </script>
 </window>