Bug 1225412 Part 2 - Add support to dispatch toggle events to details element. r?smaug draft
authorTing-Yu Lin <tlin@mozilla.com>
Sat, 12 Mar 2016 19:53:51 +0800
changeset 339676 e6937a6cfca984e2487027c5e654f8839294def4
parent 339675 00398edeabc2473fe66edae5874c894652595861
child 516046 be653ce1c4276d004dc7452d76dc6bd27d37a267
push id12795
push usertlin@mozilla.com
push dateSat, 12 Mar 2016 12:02:01 +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