Bug 1354441 - Update MediaQueryList to the latest version of the spec, r=jwatt
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 10 Apr 2017 16:29:06 +0200
changeset 402666 c467c6d0d17ea73b82e86ab1169925bfe03f9b2f
parent 402665 39ba362aa75302c21d81519ae1793998db130b81
child 402667 052805fecb605be79075766c9587740e00e389bb
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs1354441
milestone55.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 1354441 - Update MediaQueryList to the latest version of the spec, r=jwatt
dom/base/nsDocument.cpp
dom/events/test/test_all_synthetic_events.html
dom/tests/mochitest/general/test_interfaces.js
dom/webidl/MediaQueryList.webidl
dom/webidl/MediaQueryListEvent.webidl
dom/webidl/moz.build
layout/base/nsPresContext.cpp
layout/style/MediaQueryList.cpp
layout/style/MediaQueryList.h
layout/style/test/test_media_query_list.html
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1956,17 +1956,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   // We own only the items in mDOMMediaQueryLists that have listeners;
   // this reference is managed by their AddListener and RemoveListener
   // methods.
   for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
        l != &tmp->mDOMMediaQueryLists; ) {
     PRCList *next = PR_NEXT_LINK(l);
     MediaQueryList *mql = static_cast<MediaQueryList*>(l);
-    mql->RemoveAllListeners();
+    mql->Disconnect();
     l = next;
   }
 
   tmp->mInUnlinkOrDeletion = false;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 nsresult
 nsDocument::Init()
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -189,16 +189,20 @@ const kEventConstructors = {
                                              },
   MediaKeyMessageEvent:                      { create: function (aName, aProps) {
                                                          return new MediaKeyMessageEvent(aName, {
                                                            messageType: "license-request",
                                                            message: new ArrayBuffer(0)
                                                          });
                                                        },
                                              },
+  MediaQueryListEvent:                       { create: function (aName, aProps) {
+                                                         return new MediaQueryListEvent(aName, aProps);
+                                                       },
+                                             },
   MediaStreamEvent:                          { create: function (aName, aProps) {
                                                          return new MediaStreamEvent(aName, aProps);
                                                        },
                                              },
   MediaStreamTrackEvent:                     {
                                                // Difficult to test required arguments.
                                              },
   MessageEvent:                              { create: function (aName, aProps) {
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -633,16 +633,18 @@ var interfaceNamesInGlobalScope =
     "MediaKeyMessageEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaKeyStatusMap",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaQueryList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "MediaQueryListEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaRecorder",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaSource",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaStream",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MediaStreamAudioDestinationNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/MediaQueryList.webidl
+++ b/dom/webidl/MediaQueryList.webidl
@@ -1,20 +1,24 @@
 /* -*- Mode: IDL; 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/.
  *
  * The origin of this IDL file is
- * http://dev.w3.org/csswg/cssom-view/#the-mediaquerylist-interface
+ * https://drafts.csswg.org/cssom-view/#mediaquerylist
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
-interface MediaQueryList {
+interface MediaQueryList : EventTarget {
   readonly attribute DOMString media;
   readonly attribute boolean matches;
-  void addListener(MediaQueryListListener listener);
-  void removeListener(MediaQueryListListener listener);
+
+  [Throws]
+  void addListener(EventListener? listener);
+
+  [Throws]
+  void removeListener(EventListener? listener);
+
+           attribute EventHandler onchange;
 };
-
-callback MediaQueryListListener = void (MediaQueryList list);
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaQueryListEvent.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * https://drafts.csswg.org/cssom-view/#mediaquerylistevent
+ */
+
+[Constructor(DOMString type, optional MediaQueryListEventInit eventInitDict)]
+interface MediaQueryListEvent : Event {
+  readonly attribute DOMString media;
+  readonly attribute boolean matches;
+};
+
+dictionary MediaQueryListEventInit : EventInit {
+  DOMString media = "";
+  boolean matches = false;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -1072,16 +1072,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'FontFaceSetLoadEvent.webidl',
     'GamepadAxisMoveEvent.webidl',
     'GamepadButtonEvent.webidl',
     'GamepadEvent.webidl',
     'GroupedHistoryEvent.webidl',
     'HashChangeEvent.webidl',
     'HiddenPluginEvent.webidl',
     'ImageCaptureErrorEvent.webidl',
+    'MediaQueryListEvent.webidl',
     'MediaStreamEvent.webidl',
     'MediaStreamTrackEvent.webidl',
     'OfflineAudioCompletionEvent.webidl',
     'PageTransitionEvent.webidl',
     'PerformanceEntryEvent.webidl',
     'PluginCrashedEvent.webidl',
     'PopStateEvent.webidl',
     'PopupBlockedEvent.webidl',
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2089,43 +2089,23 @@ nsPresContext::MediaFeatureValuesChanged
   // (in HTML5 terms), although we also want to notify them on certain
   // flushes.  (We're already running off an event.)
   //
   // Note that we do this after the new style from media queries in
   // style sheets has been computed.
 
   if (!PR_CLIST_IS_EMPTY(mDocument->MediaQueryLists())) {
     // We build a list of all the notifications we're going to send
-    // before we send any of them.  (The spec says the notifications
-    // should be a queued task, so any removals that happen during the
-    // notifications shouldn't affect what gets notified.)  Furthermore,
-    // we hold strong pointers to everything we're going to make
-    // notification calls to, since each notification involves calling
-    // arbitrary script that might otherwise destroy these objects, or,
-    // for that matter, |this|.
-    //
-    // Note that we intentionally send the notifications to media query
-    // list in the order they were created and, for each list, to the
-    // listeners in the order added.
-    nsTArray<MediaQueryList::HandleChangeData> notifyList;
+    // before we send any of them.
     for (PRCList *l = PR_LIST_HEAD(mDocument->MediaQueryLists());
          l != mDocument->MediaQueryLists(); l = PR_NEXT_LINK(l)) {
+      nsAutoMicroTask mt;
       MediaQueryList *mql = static_cast<MediaQueryList*>(l);
-      mql->MediumFeaturesChanged(notifyList);
+      mql->MaybeNotify();
     }
-
-    if (!notifyList.IsEmpty()) {
-      for (uint32_t i = 0, i_end = notifyList.Length(); i != i_end; ++i) {
-        nsAutoMicroTask mt;
-        MediaQueryList::HandleChangeData &d = notifyList[i];
-        d.callback->Call(*d.mql);
-      }
-    }
-
-    // NOTE:  When |notifyList| goes out of scope, our destructor could run.
   }
 }
 
 void
 nsPresContext::PostMediaFeatureValuesChangedEvent()
 {
   // FIXME: We should probably replace this event with use of
   // nsRefreshDriver::AddStyleFlushObserver (except the pres shell would
--- a/layout/style/MediaQueryList.cpp
+++ b/layout/style/MediaQueryList.cpp
@@ -2,69 +2,71 @@
 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
 /* 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/. */
 
 /* implements DOM interface for querying and observing media queries */
 
 #include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/dom/MediaQueryListEvent.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/EventTargetBinding.h"
 #include "nsPresContext.h"
 #include "nsMediaList.h"
 #include "nsCSSParser.h"
 #include "nsIDocument.h"
 
+#define ONCHANGE_STRING NS_LITERAL_STRING("change")
+
 namespace mozilla {
 namespace dom {
 
 MediaQueryList::MediaQueryList(nsIDocument *aDocument,
                                const nsAString &aMediaQueryList)
-  : mDocument(aDocument),
-    mMediaList(new nsMediaList),
-    mMatchesValid(false)
+  : mDocument(aDocument)
+  , mMediaList(new nsMediaList)
+  , mMatchesValid(false)
+  , mIsKeptAlive(false)
 {
   PR_INIT_CLIST(this);
 
   nsCSSParser parser;
   parser.ParseMediaList(aMediaQueryList, nullptr, 0, mMediaList);
 }
 
 MediaQueryList::~MediaQueryList()
 {
   if (mDocument) {
     PR_REMOVE_LINK(this);
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaQueryList)
 
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaQueryList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaQueryList,
+                                                  DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallbacks)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaQueryList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaQueryList,
+                                                DOMEventTargetHelper)
   if (tmp->mDocument) {
     PR_REMOVE_LINK(tmp);
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
   }
-  tmp->RemoveAllListeners();
+  tmp->Disconnect();
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(MediaQueryList)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaQueryList)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
-NS_INTERFACE_MAP_BEGIN(MediaQueryList)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(MediaQueryList)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaQueryList)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaQueryList)
+NS_IMPL_ADDREF_INHERITED(MediaQueryList, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MediaQueryList, DOMEventTargetHelper)
 
 void
 MediaQueryList::GetMedia(nsAString &aMedia)
 {
   mMediaList->GetText(aMedia);
 }
 
 bool
@@ -75,67 +77,134 @@ MediaQueryList::Matches()
                "when listeners present, must keep mMatches current");
     RecomputeMatches();
   }
 
   return mMatches;
 }
 
 void
-MediaQueryList::AddListener(MediaQueryListListener& aListener)
+MediaQueryList::AddListener(EventListener* aListener, ErrorResult& aRv)
 {
-  if (!HasListeners()) {
-    // When we have listeners, the pres context owns a reference to
-    // this.  This is a cyclic reference that can only be broken by
-    // cycle collection.
-    NS_ADDREF_THIS();
+  if (!aListener) {
+    return;
   }
 
+  AddEventListenerOptionsOrBoolean options;
+  options.SetAsBoolean() = false;
+
+  AddEventListener(ONCHANGE_STRING, aListener, options, false, aRv);
+}
+
+void
+MediaQueryList::AddEventListener(const nsAString& aType,
+                                 EventListener* aCallback,
+                                 const AddEventListenerOptionsOrBoolean& aOptions,
+                                 const dom::Nullable<bool>& aWantsUntrusted,
+                                 ErrorResult& aRv)
+{
   if (!mMatchesValid) {
     MOZ_ASSERT(!HasListeners(),
                "when listeners present, must keep mMatches current");
     RecomputeMatches();
   }
 
-  for (uint32_t i = 0; i < mCallbacks.Length(); ++i) {
-    if (aListener == *mCallbacks[i]) {
-      // Already registered
-      return;
-    }
+  DOMEventTargetHelper::AddEventListener(aType, aCallback, aOptions,
+                                         aWantsUntrusted, aRv);
+
+  if (aRv.Failed()) {
+    return;
   }
 
-  if (!mCallbacks.AppendElement(&aListener, fallible)) {
-    if (!HasListeners()) {
-      // Append failed; undo the AddRef above.
-      NS_RELEASE_THIS();
-    }
+  UpdateMustKeepAlive();
+}
+
+void
+MediaQueryList::RemoveListener(EventListener* aListener, ErrorResult& aRv)
+{
+  if (!aListener) {
+    return;
   }
+
+  EventListenerOptionsOrBoolean options;
+  options.SetAsBoolean() = false;
+
+  RemoveEventListener(ONCHANGE_STRING, aListener, options, aRv);
 }
 
 void
-MediaQueryList::RemoveListener(MediaQueryListListener& aListener)
+MediaQueryList::RemoveEventListener(const nsAString& aType,
+                                    EventListener* aCallback,
+                                    const EventListenerOptionsOrBoolean& aOptions,
+                                    ErrorResult& aRv)
+{
+  DOMEventTargetHelper::RemoveEventListener(aType, aCallback, aOptions, aRv);
+
+  if (aRv.Failed()) {
+    return;
+  }
+
+  UpdateMustKeepAlive();
+}
+
+EventHandlerNonNull*
+MediaQueryList::GetOnchange()
+{
+  if (NS_IsMainThread()) {
+    return GetEventHandler(nsGkAtoms::onchange, EmptyString());
+  }
+  return GetEventHandler(nullptr, ONCHANGE_STRING);
+}
+
+void
+MediaQueryList::SetOnchange(EventHandlerNonNull* aCallback)
 {
-  for (uint32_t i = 0; i < mCallbacks.Length(); ++i) {
-    if (aListener == *mCallbacks[i]) {
-      mCallbacks.RemoveElementAt(i);
-      if (!HasListeners()) {
-        // See NS_ADDREF_THIS() in AddListener.
-        NS_RELEASE_THIS();
-      }
-      break;
-    }
+  if (NS_IsMainThread()) {
+    SetEventHandler(nsGkAtoms::onchange, EmptyString(), aCallback);
+  } else {
+    SetEventHandler(nullptr, ONCHANGE_STRING, aCallback);
+  }
+
+  UpdateMustKeepAlive();
+}
+
+void
+MediaQueryList::UpdateMustKeepAlive()
+{
+  bool toKeepAlive = HasListeners();
+  if (toKeepAlive == mIsKeptAlive) {
+    return;
+  }
+
+  // When we have listeners, the pres context owns a reference to
+  // this.  This is a cyclic reference that can only be broken by
+  // cycle collection.
+
+  mIsKeptAlive = toKeepAlive;
+
+  if (toKeepAlive) {
+    NS_ADDREF_THIS();
+  } else {
+    NS_RELEASE_THIS();
   }
 }
 
-void
-MediaQueryList::RemoveAllListeners()
+bool
+MediaQueryList::HasListeners()
 {
-  bool hadListeners = HasListeners();
-  mCallbacks.Clear();
-  if (hadListeners) {
+  return HasListenersFor(ONCHANGE_STRING);
+}
+
+void
+MediaQueryList::Disconnect()
+{
+  DisconnectFromOwner();
+
+  if (mIsKeptAlive) {
+    mIsKeptAlive = false;
     // See NS_ADDREF_THIS() in AddListener.
     NS_RELEASE_THIS();
   }
 }
 
 void
 MediaQueryList::RecomputeMatches()
 {
@@ -164,43 +233,53 @@ MediaQueryList::RecomputeMatches()
     // XXXbz What's the right behavior here?  Spec doesn't say.
     return;
   }
 
   mMatches = mMediaList->Matches(presContext, nullptr);
   mMatchesValid = true;
 }
 
-void
-MediaQueryList::MediumFeaturesChanged(
-    nsTArray<HandleChangeData>& aListenersToNotify)
-{
-  mMatchesValid = false;
-
-  if (HasListeners()) {
-    bool oldMatches = mMatches;
-    RecomputeMatches();
-    if (mMatches != oldMatches) {
-      for (uint32_t i = 0, i_end = mCallbacks.Length(); i != i_end; ++i) {
-        HandleChangeData *d = aListenersToNotify.AppendElement(fallible);
-        if (d) {
-          d->mql = this;
-          d->callback = mCallbacks[i];
-        }
-      }
-    }
-  }
-}
-
 nsISupports*
 MediaQueryList::GetParentObject() const
 {
   return mDocument;
 }
 
 JSObject*
 MediaQueryList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return MediaQueryListBinding::Wrap(aCx, this, aGivenProto);
 }
 
+void
+MediaQueryList::MaybeNotify()
+{
+  mMatchesValid = false;
+
+  if (!HasListeners()) {
+    return;
+  }
+
+  bool oldMatches = mMatches;
+  RecomputeMatches();
+
+  // No need to notify the change.
+  if (mMatches == oldMatches) {
+    return;
+  }
+
+  MediaQueryListEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mMatches = mMatches;
+  mMediaList->GetText(init.mMedia);
+
+  RefPtr<MediaQueryListEvent> event =
+    MediaQueryListEvent::Constructor(this, ONCHANGE_STRING, init);
+  event->SetTrusted(true);
+
+  bool dummy;
+  DispatchEvent(event, &dummy);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/layout/style/MediaQueryList.h
+++ b/layout/style/MediaQueryList.h
@@ -11,65 +11,77 @@
 
 #include "nsISupports.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "prclist.h"
 #include "mozilla/Attributes.h"
 #include "nsWrapperCache.h"
+#include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/MediaQueryListBinding.h"
 
 class nsIDocument;
 class nsMediaList;
 
 namespace mozilla {
 namespace dom {
 
-class MediaQueryList final : public nsISupports,
-                             public nsWrapperCache,
+class MediaQueryList final : public DOMEventTargetHelper,
                              public PRCList
 {
 public:
   // The caller who constructs is responsible for calling Evaluate
   // before calling any other methods.
   MediaQueryList(nsIDocument *aDocument,
                  const nsAString &aMediaQueryList);
 private:
   ~MediaQueryList();
 
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaQueryList)
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaQueryList, DOMEventTargetHelper)
 
   nsISupports* GetParentObject() const;
 
-  struct HandleChangeData {
-    RefPtr<MediaQueryList> mql;
-    RefPtr<mozilla::dom::MediaQueryListListener> callback;
-  };
-
-  // Appends listeners that need notification to aListenersToNotify
-  void MediumFeaturesChanged(nsTArray<HandleChangeData>& aListenersToNotify);
-
-  bool HasListeners() const { return !mCallbacks.IsEmpty(); }
-
-  void RemoveAllListeners();
+  void MaybeNotify();
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL methods
   void GetMedia(nsAString& aMedia);
   bool Matches();
-  void AddListener(mozilla::dom::MediaQueryListListener& aListener);
-  void RemoveListener(mozilla::dom::MediaQueryListListener& aListener);
+  void AddListener(EventListener* aListener, ErrorResult& aRv);
+  void RemoveListener(EventListener* aListener, ErrorResult& aRv);
+
+  EventHandlerNonNull* GetOnchange();
+  void SetOnchange(EventHandlerNonNull* aCallback);
+
+  using nsIDOMEventTarget::AddEventListener;
+  using nsIDOMEventTarget::RemoveEventListener;
+
+  virtual void AddEventListener(const nsAString& aType,
+                                EventListener* aCallback,
+                                const AddEventListenerOptionsOrBoolean& aOptions,
+                                const Nullable<bool>& aWantsUntrusted,
+                                ErrorResult& aRv) override;
+  virtual void RemoveEventListener(const nsAString& aType,
+                                   EventListener* aCallback,
+                                   const EventListenerOptionsOrBoolean& aOptions,
+                                   ErrorResult& aRv) override;
+
+  bool HasListeners();
+
+  void Disconnect();
 
 private:
   void RecomputeMatches();
 
+  void UpdateMustKeepAlive();
+
   // We only need a pointer to the document to support lazy
   // reevaluation following dynamic changes.  However, this lazy
   // reevaluation is perhaps somewhat important, since some usage
   // patterns may involve the creation of large numbers of
   // MediaQueryList objects which almost immediately become garbage
   // (after a single call to the .matches getter).
   //
   // This pointer does make us a little more dependent on cycle
@@ -79,15 +91,15 @@ private:
   // after cycle collection unlinking.  Having a non-null mDocument
   // is equivalent to being in that document's mDOMMediaQueryLists
   // linked list.
   nsCOMPtr<nsIDocument> mDocument;
 
   RefPtr<nsMediaList> mMediaList;
   bool mMatches;
   bool mMatchesValid;
-  nsTArray<RefPtr<mozilla::dom::MediaQueryListListener>> mCallbacks;
+  bool mIsKeptAlive;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* !defined(mozilla_dom_MediaQueryList_h) */
--- a/layout/style/test/test_media_query_list.html
+++ b/layout/style/test/test_media_query_list.html
@@ -36,24 +36,28 @@ function run() {
   iframe.style.height = h + "px";
   subroot.offsetWidth; // flush layout
 
   function setup_mql(str) {
     var obj = {
       str: str,
       mql: subwin.matchMedia(str),
       notifyCount: 0,
-      listener: function(mql) {
-                  is(mql, obj.mql,
+      listener: function(event) {
+                  ok(event instanceof MediaQueryListEvent,
                      "correct argument to listener: " + obj.str);
+                  is(event.media, obj.mql.media,
+                     "correct media in the event: " + obj.str);
+                  is(event.target, obj.mql,
+                     "correct target in the event: " + obj.str);
                   ++obj.notifyCount;
                   // Test the last match result only on odd
                   // notifications.
                   if (obj.notifyCount & 1) {
-                    obj.lastOddMatchResult = mql.matches;
+                    obj.lastOddMatchResult = event.target.matches;
                   }
                 }
     }
     obj.mql.addListener(obj.listener);
     return obj;
   }
 
   function finish_mql(obj) {
@@ -146,23 +150,23 @@ function run() {
   finish_mql(w_min_10em);
   finish_mql(w_max_9em);
   finish_mql(w_max_10em);
 
   // Additional tests of listener mutation.
   (function() {
     var received = [];
     var received_mql = [];
-    function listener1(mql) {
+    function listener1(event) {
       received.push(1);
-      received_mql.push(mql);
+      received_mql.push(event.target);
     }
-    function listener2(mql) {
+    function listener2(event) {
       received.push(2);
-      received_mql.push(mql);
+      received_mql.push(event.target);
     }
 
     iframe.style.width = "200px";
     subroot.offsetWidth; // flush layout
 
     var mql = subwin.matchMedia("(min-width: 150px)");
     mql.addListener(listener1);
     mql.addListener(listener1);
@@ -219,78 +223,52 @@ function run() {
        "notification of lists in order created");
     is(received_mql[1], mql,
        "notification of lists in order created");
     is(received_mql[2], mql2,
        "notification of lists in order created");
     received = [];
     received_mql = [];
 
-    function removing_listener(mql) {
+    function removing_listener(event) {
       received.push(3);
-      received_mql.push(mql);
-      mql.removeListener(listener2);
+      received_mql.push(event.target);
+      event.target.removeListener(listener2);
       mql2.removeListener(listener1);
     }
 
     mql.addListener(removing_listener);
     mql.removeListener(listener2);
     mql.addListener(listener2); // after removing_listener (3)
 
     iframe.style.width = "100px";
     subroot.offsetWidth; // flush layout
 
-    // mql(1, 3, 2) mql2(1)
-    is(JSON.stringify(received), "[1,3,2,1]",
+    // mql(1, 3)
+    is(JSON.stringify(received), "[1,3]",
        "listeners still notified after removed if change was before");
     is(received_mql[0], mql,
        "notification order (removal tests)");
     is(received_mql[1], mql,
        "notification order (removal tests)");
-    is(received_mql[2], mql,
-       "notification order (removal tests)");
-    is(received_mql[3], mql2,
-       "notification order (removal tests)");
     received = [];
     received_mql = [];
 
     iframe.style.width = "200px";
     subroot.offsetWidth; // flush layout
 
     // mql(1, 3)
     is(JSON.stringify(received), "[1,3]",
        "listeners not notified for changes after their removal");
     is(received_mql[0], mql,
        "notification order (removal tests)");
     is(received_mql[1], mql,
        "notification order (removal tests)");
   })();
 
-  /* Bug 716751: null-dereference crash */
-  (function() {
-    iframe.style.width = "200px";
-    subroot.offsetWidth; // flush layout
-
-    var mql = subwin.matchMedia("(min-width: 150px)");
-    SimpleTest.doesThrow(function() {
-      mql.addListener(null);
-    }, "expected an exception");
-
-    iframe.style.width = "100px";
-    subroot.offsetWidth; // flush layout
-    // With the bug, we crash here.  No need for test assertions.
-
-    SimpleTest.doesThrow(function() {
-      mql.removeListener(null);
-    }, "expected an exception");
-    SimpleTest.doesThrow(function() {
-      mql.removeListener(null);
-    }, "expected an exception");
-  })();
-
   /* Bug 753777: test that things work in a freshly-created iframe */
   (function() {
     var iframe = document.createElement("iframe");
     document.body.appendChild(iframe);
 
     is(iframe.contentWindow.matchMedia("(min-width: 1px)").matches, true,
        "(min-width: 1px) should match in newly-created iframe");
     is(iframe.contentWindow.matchMedia("(max-width: 1px)").matches, false,
@@ -298,17 +276,17 @@ function run() {
 
     document.body.removeChild(iframe);
   })();
 
   /* Bug 716751: listeners lost due to GC */
   var gc_received = [];
   (function() {
     var received = [];
-    var listener1 = function(mql) {
+    var listener1 = function(event) {
       gc_received.push(1);
     }
 
     iframe.style.width = "200px";
     subroot.offsetWidth; // flush layout
 
     var mql = subwin.matchMedia("(min-width: 150px)");
     mql.addListener(listener1);
@@ -332,17 +310,17 @@ function run() {
 
     is(JSON.stringify(gc_received), "[1,1]", "GC test: after notification 2");
 
     bug1270626();
   }
 
   /* Bug 1270626: listeners that throw exceptions */
   function bug1270626() {
-    var throwingListener = function(mql) {
+    var throwingListener = function(event) {
       throw "error";
     }
 
     iframe.style.width = "200px";
     subroot.offsetWidth; // flush layout
 
     var mql = subwin.matchMedia("(min-width: 150px)");
     mql.addListener(throwingListener);