Bug 1225412 Part 2 - Add support to dispatch toggle events to details element. r=smaug
authorTing-Yu Lin <tlin@mozilla.com>
Sat, 12 Mar 2016 19:53:51 +0800
changeset 339903 5ee96a182b8d7e872b657b8ed0cb2dfccb58711f
parent 339902 48adfd045db5e7ee610408b2014ca74c845fced3
child 339904 e9f0c40c06e1add86ac1831f0561a8931fc16889
push id12823
push userjmaher@mozilla.com
push dateMon, 14 Mar 2016 10:52:22 +0000
reviewerssmaug
bugs1225412
milestone48.0a1
Bug 1225412 Part 2 - Add support to dispatch toggle events to details element. r=smaug Add ontoggle event handler, and dispatch toggle events to the details element if the open attribute is added or changed. According to the spec, if a new toggle event has been queued, previous toggle events should be aborted. MozReview-Commit-ID: EN6Jf5hVHHD
dom/base/nsGkAtomList.h
dom/events/EventNameList.h
dom/html/HTMLDetailsElement.cpp
dom/html/HTMLDetailsElement.h
dom/webidl/EventHandler.webidl
testing/web-platform/meta/html/dom/interfaces.html.ini
testing/web-platform/meta/html/semantics/interactive-elements/the-details-element/toggleEvent.html.ini
testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html
widget/EventMessageList.h
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -918,16 +918,17 @@ GK_ATOM(onstatuschanged, "onstatuschange
 GK_ATOM(onstkcommand, "onstkcommand")
 GK_ATOM(onstksessionend, "onstksessionend")
 GK_ATOM(onstorage, "onstorage")
 GK_ATOM(onstorageareachanged, "onstorageareachanged")
 GK_ATOM(onsubmit, "onsubmit")
 GK_ATOM(onsuccess, "onsuccess")
 GK_ATOM(ontypechange, "ontypechange")
 GK_ATOM(ontext, "ontext")
+GK_ATOM(ontoggle, "ontoggle")
 GK_ATOM(ontouchstart, "ontouchstart")
 GK_ATOM(ontouchend, "ontouchend")
 GK_ATOM(ontouchmove, "ontouchmove")
 GK_ATOM(ontouchcancel, "ontouchcancel")
 GK_ATOM(ontransitionend, "ontransitionend")
 GK_ATOM(onunderflow, "onunderflow")
 GK_ATOM(onunload, "onunload")
 GK_ATOM(onupdatefound, "onupdatefound")
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -416,16 +416,20 @@ EVENT(submit,
 EVENT(suspend,
       eSuspend,
       EventNameType_HTML,
       eBasicEventClass)
 EVENT(timeupdate,
       eTimeUpdate,
       EventNameType_HTML,
       eBasicEventClass)
+EVENT(toggle,
+      eToggle,
+      EventNameType_HTML,
+      eBasicEventClass)
 EVENT(volumechange,
       eVolumeChange,
       EventNameType_HTML,
       eBasicEventClass)
 EVENT(waiting,
       eWaiting,
       EventNameType_HTML,
       eBasicEventClass)
--- a/dom/html/HTMLDetailsElement.cpp
+++ b/dom/html/HTMLDetailsElement.cpp
@@ -66,16 +66,35 @@ HTMLDetailsElement::GetAttributeChangeHi
   nsChangeHint hint =
     nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
   if (aAttribute == nsGkAtoms::open) {
     NS_UpdateHint(hint, nsChangeHint_ReconstructFrame);
   }
   return hint;
 }
 
+nsresult
+HTMLDetailsElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                                  nsAttrValueOrString* aValue, bool aNotify)
+{
+  if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::open) {
+    bool setOpen = aValue != nullptr;
+    if (Open() != setOpen) {
+      if (mToggleEventDispatcher) {
+        mToggleEventDispatcher->Cancel();
+      }
+      mToggleEventDispatcher = new ToggleEventDispatcher(this);
+      mToggleEventDispatcher->PostDOMEvent();
+    }
+  }
+
+  return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
+                                             aNotify);
+}
+
 JSObject*
 HTMLDetailsElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLDetailsElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLDetailsElement.h
+++ b/dom/html/HTMLDetailsElement.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_HTMLDetailsElement_h
 #define mozilla_dom_HTMLDetailsElement_h
 
+#include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Attributes.h"
 #include "nsGenericHTMLElement.h"
 
 namespace mozilla {
 namespace dom {
 
 // HTMLDetailsElement implements the <details> tag, which is used as a
 // disclosure widget from which the user can obtain additional information or
@@ -33,35 +34,57 @@ public:
 
   nsIContent* GetFirstSummary() const;
 
   nsresult Clone(NodeInfo* aNodeInfo, nsINode** aResult) const override;
 
   nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
                                       int32_t aModType) const override;
 
+  nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                         nsAttrValueOrString* aValue, bool aNotify) override;
+
   // HTMLDetailsElement WebIDL
   bool Open() const { return GetBoolAttr(nsGkAtoms::open); }
 
   void SetOpen(bool aOpen, ErrorResult& aError)
   {
-    // TODO: Bug 1225412: Need to follow the spec to fire "toggle" event.
     SetHTMLBoolAttr(nsGkAtoms::open, aOpen, aError);
   }
 
   void ToggleOpen()
   {
     ErrorResult rv;
     SetOpen(!Open(), rv);
     rv.SuppressException();
   }
 
 protected:
   virtual ~HTMLDetailsElement();
 
   JSObject* WrapNode(JSContext* aCx,
                      JS::Handle<JSObject*> aGivenProto) override;
+
+  class ToggleEventDispatcher final : public AsyncEventDispatcher
+  {
+  public:
+    // According to the html spec, a 'toggle' event is a simple event which does
+    // not bubble.
+    explicit ToggleEventDispatcher(nsINode* aTarget)
+      : AsyncEventDispatcher(aTarget, NS_LITERAL_STRING("toggle"), false)
+    {
+    }
+
+    NS_IMETHOD Run() override
+    {
+      auto* details = static_cast<HTMLDetailsElement*>(mTarget.get());
+      details->mToggleEventDispatcher = nullptr;
+      return AsyncEventDispatcher::Run();
+    }
+  };
+
+  RefPtr<ToggleEventDispatcher> mToggleEventDispatcher;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_HTMLDetailsElement_h */
--- a/dom/webidl/EventHandler.webidl
+++ b/dom/webidl/EventHandler.webidl
@@ -87,16 +87,19 @@ interface GlobalEventHandlers {
            attribute EventHandler onsuspend;
            attribute EventHandler ontimeupdate;
            attribute EventHandler onvolumechange;
            attribute EventHandler onwaiting;
 
            [Pref="dom.select_events.enabled"]
            attribute EventHandler onselectstart;
 
+           [Pref="dom.details_element.enabled"]
+           attribute EventHandler ontoggle;
+
            // Pointer events handlers
            [Pref="dom.w3c_pointer_events.enabled"]
            attribute EventHandler onpointercancel;
            [Pref="dom.w3c_pointer_events.enabled"]
            attribute EventHandler onpointerdown;
            [Pref="dom.w3c_pointer_events.enabled"]
            attribute EventHandler onpointerup;
            [Pref="dom.w3c_pointer_events.enabled"]
--- a/testing/web-platform/meta/html/dom/interfaces.html.ini
+++ b/testing/web-platform/meta/html/dom/interfaces.html.ini
@@ -131,19 +131,16 @@
     expected: FAIL
 
   [Document interface: attribute onmousewheel]
     expected: FAIL
 
   [Document interface: attribute onsort]
     expected: FAIL
 
-  [Document interface: attribute ontoggle]
-    expected: FAIL
-
   [Stringification of iframe.contentDocument]
     expected: FAIL
 
   [Document interface: iframe.contentDocument must inherit property "origin" with the proper type (3)]
     expected: FAIL
 
   [Document interface: iframe.contentDocument must inherit property "styleSheetSets" with the proper type (31)]
     expected: FAIL
@@ -200,19 +197,16 @@
     expected: FAIL
 
   [Document interface: iframe.contentDocument must inherit property "onmousewheel" with the proper type (135)]
     expected: FAIL
 
   [Document interface: iframe.contentDocument must inherit property "onsort" with the proper type (148)]
     expected: FAIL
 
-  [Document interface: iframe.contentDocument must inherit property "ontoggle" with the proper type (153)]
-    expected: FAIL
-
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "origin" with the proper type (3)]
     expected: FAIL
 
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "styleSheetSets" with the proper type (31)]
     expected: FAIL
 
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "domain" with the proper type (34)]
     expected: FAIL
@@ -404,19 +398,16 @@
     expected: FAIL
 
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "onmousewheel" with the proper type (135)]
     expected: FAIL
 
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "onsort" with the proper type (148)]
     expected: FAIL
 
-  [Document interface: document.implementation.createDocument(null, "", null) must inherit property "ontoggle" with the proper type (153)]
-    expected: FAIL
-
   [Touch interface: attribute region]
     expected: FAIL
 
   [HTMLAllCollection must be primary interface of document.all]
     expected: FAIL
 
   [Stringification of document.all]
     expected: FAIL
@@ -587,19 +578,16 @@
     expected: FAIL
 
   [HTMLElement interface: attribute onmousewheel]
     expected: FAIL
 
   [HTMLElement interface: attribute onsort]
     expected: FAIL
 
-  [HTMLElement interface: attribute ontoggle]
-    expected: FAIL
-
   [HTMLElement interface: document.createElement("noscript") must inherit property "translate" with the proper type (2)]
     expected: FAIL
 
   [HTMLElement interface: document.createElement("noscript") must inherit property "dropzone" with the proper type (20)]
     expected: FAIL
 
   [HTMLElement interface: document.createElement("noscript") must inherit property "forceSpellCheck" with the proper type (25)]
     expected: FAIL
@@ -641,19 +629,16 @@
     expected: FAIL
 
   [HTMLElement interface: document.createElement("noscript") must inherit property "onmousewheel" with the proper type (74)]
     expected: FAIL
 
   [HTMLElement interface: document.createElement("noscript") must inherit property "onsort" with the proper type (87)]
     expected: FAIL
 
-  [HTMLElement interface: document.createElement("noscript") must inherit property "ontoggle" with the proper type (92)]
-    expected: FAIL
-
   [Element interface: document.createElement("noscript") must inherit property "prepend" with the proper type (31)]
     expected: FAIL
 
   [Element interface: calling prepend([object Object\],[object Object\]) on document.createElement("noscript") with too few arguments must throw TypeError]
     expected: FAIL
 
   [Element interface: document.createElement("noscript") must inherit property "append" with the proper type (32)]
     expected: FAIL
@@ -1960,19 +1945,16 @@
     expected: FAIL
 
   [Window interface: attribute onmousewheel]
     expected: FAIL
 
   [Window interface: attribute onsort]
     expected: FAIL
 
-  [Window interface: attribute ontoggle]
-    expected: FAIL
-
   [Window interface: operation createImageBitmap(ImageBitmapSource,long,long,long,long)]
     expected: FAIL
 
   [Window interface: window must inherit property "showModalDialog" with the proper type (34)]
     expected:
       if not e10s: PASS
       FAIL
 
@@ -1995,19 +1977,16 @@
     expected: FAIL
 
   [Window interface: window must inherit property "onmousewheel" with the proper type (80)]
     expected: FAIL
 
   [Window interface: window must inherit property "onsort" with the proper type (93)]
     expected: FAIL
 
-  [Window interface: window must inherit property "ontoggle" with the proper type (98)]
-    expected: FAIL
-
   [Window interface: calling showModalDialog(DOMString,any) on window with too few arguments must throw TypeError]
     expected:
       if not e10s: PASS
       FAIL
 
   [Window interface: calling createImageBitmap(ImageBitmapSource,long,long,long,long) on window with too few arguments must throw TypeError]
     expected: FAIL
 
@@ -2510,19 +2489,16 @@
     expected: FAIL
 
   [Document interface: iframe.contentDocument must inherit property "onmousewheel" with the proper type (136)]
     expected: FAIL
 
   [Document interface: iframe.contentDocument must inherit property "onsort" with the proper type (149)]
     expected: FAIL
 
-  [Document interface: iframe.contentDocument must inherit property "ontoggle" with the proper type (154)]
-    expected: FAIL
-
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "styleSheetSets" with the proper type (32)]
     expected: FAIL
 
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "domain" with the proper type (35)]
     expected: FAIL
 
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "cookie" with the proper type (37)]
     expected: FAIL
@@ -2660,19 +2636,16 @@
     expected: FAIL
 
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "onmousewheel" with the proper type (136)]
     expected: FAIL
 
   [Document interface: document.implementation.createDocument(null, "", null) must inherit property "onsort" with the proper type (149)]
     expected: FAIL
 
-  [Document interface: document.implementation.createDocument(null, "", null) must inherit property "ontoggle" with the proper type (154)]
-    expected: FAIL
-
   [Location interface: window.location must have own property "ancestorOrigins"]
     expected: FAIL
 
   [WorkerLocation interface: attribute href]
     expected: FAIL
 
   [WorkerLocation interface: attribute origin]
     expected: FAIL
--- a/testing/web-platform/meta/html/semantics/interactive-elements/the-details-element/toggleEvent.html.ini
+++ b/testing/web-platform/meta/html/semantics/interactive-elements/the-details-element/toggleEvent.html.ini
@@ -1,21 +1,3 @@
 [toggleEvent.html]
   type: testharness
-  expected: TIMEOUT
-  [Adding open to 'details' should fire a toggle event at the 'details' element]
-    expected: NOTRUN
-
-  [Removing open from 'details' should fire a toggle event at the 'details' element]
-    expected: NOTRUN
-
-  [Adding open to 'details' (display:none) should fire a toggle event at the 'details' element]
-    expected: NOTRUN
-
-  [Adding open from 'details' (no children) should fire a toggle event at the 'details' element]
-    expected: NOTRUN
-
-  [Calling open twice on 'details' fires only one toggle event]
-    expected: FAIL
-
-  [Adding open to 'details' (not in the document) should fire a toggle event at the 'details' element]
-    expected: TIMEOUT
-
+  prefs: [dom.details_element.enabled:true]
--- a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html
@@ -19,27 +19,52 @@
   <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
 </details>
 <details id=details4>
 </details>
 <details id=details6>
   <summary>Lorem ipsum</summary>
   <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
 </details>
+<details id=details7>
+  <summary>Lorem ipsum</summary>
+  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details8 open>
+  <summary>Lorem ipsum</summary>
+  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details9 open>
+  <summary>Lorem ipsum</summary>
+  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details10>
+  <summary>Lorem ipsum</summary>
+  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
 <script>
   var t1 = async_test("Adding open to 'details' should fire a toggle event at the 'details' element"),
   t2 = async_test("Removing open from 'details' should fire a toggle event at the 'details' element"),
   t3 = async_test("Adding open to 'details' (display:none) should fire a toggle event at the 'details' element"),
   t4 = async_test("Adding open from 'details' (no children) should fire a toggle event at the 'details' element"),
   t6 = async_test("Calling open twice on 'details' fires only one toggle event"),
+  t7 = async_test("Calling setAttribute('open', '') to 'details' should fire a toggle event at the 'details' element"),
+  t8 = async_test("Calling removeAttribute('open') to 'details' should fire a toggle event at the 'details' element"),
+  t9 = async_test("Setting open=true to opened 'details' element should not fire a toggle event at the 'details' element"),
+  t10 = async_test("Setting open=false to closed 'details' element should not fire a toggle event at the 'details' element"),
+
   details1 = document.getElementById('details1'),
   details2 = document.getElementById('details2'),
   details3 = document.getElementById('details3'),
   details4 = document.getElementById('details4'),
   details6 = document.getElementById('details6'),
+  details7 = document.getElementById('details7'),
+  details8 = document.getElementById('details8'),
+  details9 = document.getElementById('details9'),
+  details10 = document.getElementById('details10'),
   loop=false;
 
   function testEvent(evt) {
     assert_true(evt.isTrusted, "event is trusted");
     assert_false(evt.bubbles, "event doesn't bubble");
     assert_false(evt.cancelable, "event is not cancelable");
     assert_equals(Object.getPrototypeOf(evt), Event.prototype, "Prototype of toggle event is Event.prototype");
   }
@@ -85,9 +110,48 @@
     } else {
       loop = true;
     }
   });
   setTimeout(t6.step_func(function() {
     assert_true(loop);
     t6.done();
   }), 0);
+
+  details7.ontoggle = t7.step_func_done(function(evt) {
+    assert_true(details7.open);
+    testEvent(evt)
+  });
+  details7.setAttribute('open', ''); // opens details7
+
+  details8.ontoggle = t8.step_func_done(function(evt) {
+    assert_false(details8.open);
+    testEvent(evt)
+  });
+  details8.removeAttribute('open'); // closes details8
+
+  var toggleFiredOnDetails9 = false;
+  details9.ontoggle = t9.step_func_done(function(evt) {
+    if (toggleFiredOnDetails9) {
+      assert_unreached("toggle event fired twice on opened details element");
+    } else {
+      toggleFiredOnDetails9 = true;
+    }
+  });
+  // The toggle event should be fired once when declaring details9 with open
+  // attribute.
+  details9.open = true; // opens details9
+  setTimeout(t9.step_func(function() {
+    assert_true(details9.open);
+    assert_true(toggleFiredOnDetails9);
+    t9.done();
+  }), 0);
+
+  details10.ontoggle = t10.step_func_done(function(evt) {
+    assert_unreached("toggle event fired on closed details element");
+  });
+  details10.open = false;
+  setTimeout(t10.step_func(function() {
+    assert_false(details10.open);
+    t10.done();
+  }), 0);
+
 </script>
--- a/widget/EventMessageList.h
+++ b/widget/EventMessageList.h
@@ -410,12 +410,15 @@ NS_EVENT_MESSAGE_FIRST_LAST(eGamepadEven
 
 // input and beforeinput events.
 NS_EVENT_MESSAGE(eEditorInput)
 
 // selection events
 NS_EVENT_MESSAGE(eSelectStart)
 NS_EVENT_MESSAGE(eSelectionChange)
 
+// Details element events.
+NS_EVENT_MESSAGE(eToggle)
+
 #ifdef UNDEF_NS_EVENT_MESSAGE_FIRST_LAST
 #undef UNDEF_NS_EVENT_MESSAGE_FIRST_LAST
 #undef NS_EVENT_MESSAGE_FIRST_LAST
 #endif