Bug 950523 - Part 2: Move MediaQueryList to WebIDL bindings; r=bzbarsky
authorEhsan Akhgari <ehsan.akhgari@gmail.com>
Mon, 16 Dec 2013 09:03:34 -0500
changeset 160957 3f48392a62502d861e14e1eefa625f9af35b80ec
parent 160956 3fb12cbb6c965d9142219239f58410efe3c733a8
child 160983 69a5abf01ed1443b8fe1051c78e713e9988b8969
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersbzbarsky
bugs950523
milestone29.0a1
Bug 950523 - Part 2: Move MediaQueryList to WebIDL bindings; r=bzbarsky
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/bindings/Bindings.conf
dom/webidl/MediaQueryList.webidl
dom/webidl/Window.webidl
dom/webidl/moz.build
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/style/MediaQueryList.cpp
layout/style/MediaQueryList.h
layout/style/test/test_media_query_list.html
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -139,17 +139,16 @@
 // Drag and drop
 #include "nsIDOMDataTransfer.h"
 
 #include "nsIDOMFile.h"
 #include "nsDOMBlobBuilder.h" // nsDOMMultipartFile
 
 #include "nsIEventListenerService.h"
 #include "nsIMessageManager.h"
-#include "nsIDOMMediaQueryList.h"
 
 #include "nsDOMTouchEvent.h"
 
 #include "nsWrapperCacheInlines.h"
 #include "mozilla/dom/HTMLCollectionBinding.h"
 
 #include "nsIDOMWakeLock.h"
 #include "nsIDOMMobileMessageManager.h"
@@ -497,19 +496,16 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframeRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframesRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(CSSPageRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
-  NS_DEFINE_CLASSINFO_DATA(MediaQueryList, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
-
 #ifdef MOZ_B2G_RIL
   NS_DEFINE_CLASSINFO_DATA(MozIccManager, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #endif
 
 #ifdef MOZ_B2G_BT
   NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
@@ -1248,20 +1244,16 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframesRule, nsIDOMMozCSSKeyframesRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframesRule)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(CSSPageRule, nsIDOMCSSPageRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSPageRule)
   DOM_CLASSINFO_MAP_END
 
-  DOM_CLASSINFO_MAP_BEGIN(MediaQueryList, nsIDOMMediaQueryList)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMMediaQueryList)
-  DOM_CLASSINFO_MAP_END
-
 #ifdef MOZ_B2G_RIL
   DOM_CLASSINFO_MAP_BEGIN(MozIccManager, nsIDOMMozIccManager)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozIccManager)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
 #endif
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -108,18 +108,16 @@ DOMCI_CLASS(ContentFrameMessageManager)
 DOMCI_CLASS(ChromeMessageBroadcaster)
 DOMCI_CLASS(ChromeMessageSender)
 
 DOMCI_CLASS(MozCSSKeyframeRule)
 DOMCI_CLASS(MozCSSKeyframesRule)
 
 DOMCI_CLASS(CSSPageRule)
 
-DOMCI_CLASS(MediaQueryList)
-
 #ifdef MOZ_B2G_RIL
 DOMCI_CLASS(MozIccManager)
 #endif
 
 #ifdef MOZ_B2G_BT
 DOMCI_CLASS(BluetoothDevice)
 #endif
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -205,16 +205,17 @@
 #include "nsSandboxFlags.h"
 #include "TimeChangeObserver.h"
 #include "mozilla/dom/AudioContext.h"
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "nsIDOMMediaQueryList.h"
+#include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/ScriptSettings.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
 #ifdef MOZ_JSDEBUGGER
 #include "jsdIDebuggerService.h"
@@ -5012,17 +5013,17 @@ NS_IMETHODIMP
 nsGlobalWindow::GetMozAnimationStartTime(int64_t *aTime)
 {
   ErrorResult rv;
   *aTime = GetMozAnimationStartTime(rv);
 
   return rv.ErrorCode();
 }
 
-already_AddRefed<nsIDOMMediaQueryList>
+already_AddRefed<MediaQueryList>
 nsGlobalWindow::MatchMedia(const nsAString& aMediaQueryList,
                            ErrorResult& aError)
 {
   // FIXME: This whole forward-to-outer and then get a pres
   // shell/context off the docshell dance is sort of silly; it'd make
   // more sense to forward to the inner, but it's what everyone else
   // (GetSelection, GetScrollXY, etc.) does around here.
   FORWARD_TO_OUTER_OR_THROW(MatchMedia, (aMediaQueryList, aError), aError,
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -103,16 +103,17 @@ struct nsRect;
 
 class nsWindowSizes;
 
 namespace mozilla {
 namespace dom {
 class BarProp;
 class Function;
 class Gamepad;
+class MediaQueryList;
 class Navigator;
 class SpeechSynthesis;
 namespace indexedDB {
 class IDBFactory;
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
 
@@ -845,18 +846,18 @@ public:
             mozilla::ErrorResult& aError);
   nsIDOMStorage* GetSessionStorage(mozilla::ErrorResult& aError);
   nsIDOMStorage* GetLocalStorage(mozilla::ErrorResult& aError);
   nsISelection* GetSelection(mozilla::ErrorResult& aError);
   mozilla::dom::indexedDB::IDBFactory* GetIndexedDB(mozilla::ErrorResult& aError);
   already_AddRefed<nsICSSDeclaration>
     GetComputedStyle(mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
                      mozilla::ErrorResult& aError);
-  already_AddRefed<nsIDOMMediaQueryList> MatchMedia(const nsAString& aQuery,
-                                                    mozilla::ErrorResult& aError);
+  already_AddRefed<mozilla::dom::MediaQueryList> MatchMedia(const nsAString& aQuery,
+                                                            mozilla::ErrorResult& aError);
   nsScreen* GetScreen(mozilla::ErrorResult& aError);
   void MoveTo(int32_t aXPos, int32_t aYPos, mozilla::ErrorResult& aError);
   void MoveBy(int32_t aXDif, int32_t aYDif, mozilla::ErrorResult& aError);
   void ResizeTo(int32_t aWidth, int32_t aHeight,
                 mozilla::ErrorResult& aError);
   void ResizeBy(int32_t aWidthDif, int32_t aHeightDif,
                 mozilla::ErrorResult& aError);
   int32_t GetInnerWidth(mozilla::ErrorResult& aError);
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1858,17 +1858,16 @@ addExternalIface('mozIDOMApplication', n
 addExternalIface('CSSRuleList')
 addExternalIface('DOMStringList')
 addExternalIface('RTCDataChannel', nativeType='nsIDOMDataChannel')
 addExternalIface('File')
 addExternalIface('HitRegionOptions', nativeType='nsISupports')
 addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver')
 addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True)
 addExternalIface('LockedFile')
-addExternalIface('MediaQueryList')
 addExternalIface('MenuBuilder', nativeType='nsIMenuBuilder', notflattened=True)
 addExternalIface('MozBoxObject', nativeType='nsIBoxObject')
 addExternalIface('MozConnection', headerFile='nsIDOMConnection.h')
 addExternalIface('MozControllers', nativeType='nsIControllers')
 addExternalIface('MozFrameLoader', nativeType='nsIFrameLoader', notflattened=True)
 addExternalIface('MozFrameRequestCallback', nativeType='nsIFrameRequestCallback',
                  notflattened=True)
 addExternalIface('MozIccInfo', headerFile='nsIDOMIccInfo.h')
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaQueryList.webidl
@@ -0,0 +1,20 @@
+/* -*- 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
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+interface MediaQueryList {
+  readonly attribute DOMString media;
+  readonly attribute boolean matches;
+  void addListener(MediaQueryListListener listener);
+  void removeListener(MediaQueryListListener listener);
+};
+
+callback MediaQueryListListener = void (MediaQueryList list);
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -11,17 +11,16 @@
  * http://dev.w3.org/csswg/cssom-view/
  * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/RequestAnimationFrame/Overview.html
  * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html
  * https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html
  * http://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html
  */
 
 interface ApplicationCache;
-interface MediaQueryList;
 interface MozFrameRequestCallback;
 interface nsIDOMCrypto;
 interface Pkcs11;
 typedef any Transferable;
 
 // http://www.whatwg.org/specs/web-apps/current-work/
 [Global]
 /*sealed*/ interface Window : EventTarget {
@@ -142,18 +141,18 @@ partial interface Window {
 // http://dev.w3.org/csswg/cssom-view/
 enum ScrollBehavior { "auto", "instant", "smooth" };
 
 dictionary ScrollOptions {
   ScrollBehavior behavior = "auto";
 };
 
 partial interface Window {
-  //[Throws] MediaQueryList matchMedia(DOMString query);
-  [Throws] MediaQueryList? matchMedia(DOMString query);
+  //[Throws,NewObject] MediaQueryList matchMedia(DOMString query);
+  [Throws,NewObject] MediaQueryList? matchMedia(DOMString query);
   //[SameObject]
   [Throws] readonly attribute Screen screen;
 
   // browsing context
   //[Throws] void moveTo(double x, double y);
   //[Throws] void moveBy(double x, double y);
   //[Throws] void resizeTo(double x, double y);
   //[Throws] void resizeBy(double x, double y);
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -206,16 +206,17 @@ WEBIDL_FILES = [
     'KeyEvent.webidl',
     'LegacyQueryInterface.webidl',
     'LinkStyle.webidl',
     'LocalMediaStream.webidl',
     'Location.webidl',
     'MediaElementAudioSourceNode.webidl',
     'MediaError.webidl',
     'MediaList.webidl',
+    'MediaQueryList.webidl',
     'MediaRecorder.webidl',
     'MediaSource.webidl',
     'MediaStream.webidl',
     'MediaStreamAudioDestinationNode.webidl',
     'MediaStreamAudioSourceNode.webidl',
     'MediaStreamTrack.webidl',
     'MessageChannel.webidl',
     'MessageEvent.webidl',
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1853,17 +1853,24 @@ nsPresContext::MediaFeatureValuesChanged
       nsPIDOMWindow *win = mDocument->GetInnerWindow();
       nsCOMPtr<EventTarget> et = do_QueryInterface(win);
       nsCxPusher pusher;
 
       for (uint32_t i = 0, i_end = notifyList.Length(); i != i_end; ++i) {
         if (pusher.RePush(et)) {
           nsAutoMicroTask mt;
           MediaQueryList::HandleChangeData &d = notifyList[i];
-          d.listener->HandleChange(d.mql);
+          if (d.listener) {
+            d.listener->HandleChange(d.mql);
+          } else if (d.callback) {
+            ErrorResult result;
+            d.callback->Call(*d.mql, result);
+          } else {
+            MOZ_ASSERT(false, "How come we have no listener or callback?");
+          }
         }
       }
     }
 
     // NOTE:  When |notifyList| goes out of scope, our destructor could run.
   }
 }
 
@@ -1888,17 +1895,17 @@ nsPresContext::HandleMediaFeatureValuesC
 {
   // Null-check mShell in case the shell has been destroyed (and the
   // event is the only thing holding the pres context alive).
   if (mPendingMediaFeatureValuesChanged && mShell) {
     MediaFeatureValuesChanged(eRebuildStyleIfNeeded);
   }
 }
 
-already_AddRefed<nsIDOMMediaQueryList>
+already_AddRefed<MediaQueryList>
 nsPresContext::MatchMedia(const nsAString& aMediaQueryList)
 {
   nsRefPtr<MediaQueryList> result = new MediaQueryList(this, aMediaQueryList);
 
   // Insert the new item at the end of the linked list.
   PR_INSERT_BEFORE(result, &mDOMMediaQueryLists);
 
   return result.forget();
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -67,16 +67,19 @@ class nsTransitionManager;
 class nsAnimationManager;
 class nsIDOMMediaQueryList;
 class nsRefreshDriver;
 class nsIWidget;
 class nsDeviceContext;
 
 namespace mozilla {
 class RestyleManager;
+namespace dom {
+class MediaQueryList;
+}
 namespace layers {
 class ContainerLayer;
 }
 }
 
 // supported values for cached bool types
 enum nsPresContext_CachedBoolPrefType {
   kPresContext_UseDocumentColors = 1,
@@ -264,17 +267,17 @@ public:
   void FlushPendingMediaFeatureValuesChanged() {
     if (mPendingMediaFeatureValuesChanged)
       MediaFeatureValuesChanged(eRebuildStyleIfNeeded);
   }
 
   /**
    * Support for window.matchMedia()
    */
-  already_AddRefed<nsIDOMMediaQueryList>
+  already_AddRefed<mozilla::dom::MediaQueryList>
     MatchMedia(const nsAString& aMediaQueryList);
 
   /**
    * Access compatibility mode for this context.  This is the same as
    * our document's compatibility mode.
    */
   nsCompatibility CompatibilityMode() const;
 
--- a/layout/style/MediaQueryList.cpp
+++ b/layout/style/MediaQueryList.cpp
@@ -4,85 +4,96 @@
  * 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 "nsPresContext.h"
 #include "nsIMediaList.h"
 #include "nsCSSParser.h"
-#include "nsDOMClassInfoID.h" // DOMCI_DATA
-
-DOMCI_DATA(MediaQueryList, mozilla::dom::MediaQueryList)
+#include "nsIDocument.h"
 
 namespace mozilla {
 namespace dom {
 
 MediaQueryList::MediaQueryList(nsPresContext *aPresContext,
                                const nsAString &aMediaQueryList)
   : mPresContext(aPresContext),
     mMediaList(new nsMediaList),
     mMatchesValid(false)
 {
   PR_INIT_CLIST(this);
 
+  SetIsDOMBinding();
+
   nsCSSParser parser;
   parser.ParseMediaList(aMediaQueryList, nullptr, 0, mMediaList, false);
 }
 
 MediaQueryList::~MediaQueryList()
 {
   if (mPresContext) {
     PR_REMOVE_LINK(this);
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaQueryList)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaQueryList)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallbacks)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaQueryList)
-if (tmp->mPresContext) {
-  PR_REMOVE_LINK(tmp);
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext)
-}
-tmp->RemoveAllListeners();
+  if (tmp->mPresContext) {
+    PR_REMOVE_LINK(tmp);
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext)
+  }
+  tmp->RemoveAllListeners();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(MediaQueryList)
+
 NS_INTERFACE_MAP_BEGIN(MediaQueryList)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsIDOMMediaQueryList)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(MediaQueryList)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MediaQueryList)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaQueryList)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaQueryList)
 
 NS_IMETHODIMP
 MediaQueryList::GetMedia(nsAString &aMedia)
 {
   mMediaList->GetText(aMedia);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MediaQueryList::GetMatches(bool *aMatches)
 {
+  *aMatches = Matches();
+  return NS_OK;
+}
+
+bool
+MediaQueryList::Matches()
+{
   if (!mMatchesValid) {
     NS_ABORT_IF_FALSE(!HasListeners(),
                       "when listeners present, must keep mMatches current");
     RecomputeMatches();
   }
 
-  *aMatches = mMatches;
-  return NS_OK;
+  return mMatches;
 }
 
 NS_IMETHODIMP
 MediaQueryList::AddListener(nsIDOMMediaQueryListListener *aListener)
 {
   if (!aListener) {
     return NS_OK;
   }
@@ -105,36 +116,88 @@ MediaQueryList::AddListener(nsIDOMMediaQ
     if (!HasListeners()) {
       // Append failed; undo the AddRef above.
       NS_RELEASE_THIS();
     }
   }
   return NS_OK;
 }
 
+void
+MediaQueryList::AddListener(MediaQueryListListener& aListener)
+{
+  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 (!mMatchesValid) {
+    NS_ABORT_IF_FALSE(!HasListeners(),
+                      "when listeners present, must keep mMatches current");
+    RecomputeMatches();
+  }
+
+  CallbackType callback(&aListener);
+
+  for (uint32_t i = 0; i < mCallbacks.Length(); ++i) {
+    CallbackType thisCallback(mCallbacks[i]);
+    if (callback == thisCallback) {
+      // Already registered
+      return;
+    }
+  }
+
+  mCallbacks.AppendElement(&aListener);
+  if (!HasListeners()) {
+    // Append failed; undo the AddRef above.
+    NS_RELEASE_THIS();
+  }
+}
+
 NS_IMETHODIMP
 MediaQueryList::RemoveListener(nsIDOMMediaQueryListListener *aListener)
 {
   bool removed = mListeners.RemoveElement(aListener);
   NS_ABORT_IF_FALSE(!mListeners.Contains(aListener),
                     "duplicate occurrence of listeners");
 
   if (removed && !HasListeners()) {
     // See NS_ADDREF_THIS() in AddListener.
     NS_RELEASE_THIS();
   }
 
   return NS_OK;
 }
 
 void
+MediaQueryList::RemoveListener(MediaQueryListListener& aListener)
+{
+  CallbackType callback(&aListener);
+
+  for (uint32_t i = 0; i < mCallbacks.Length(); ++i) {
+    CallbackType thisCallback(mCallbacks[i]);
+    if (callback == thisCallback) {
+      mCallbacks.RemoveElementAt(i);
+      if (!HasListeners()) {
+        // See NS_ADDREF_THIS() in AddListener.
+        NS_RELEASE_THIS();
+      }
+      break;
+    }
+  }
+}
+
+void
 MediaQueryList::RemoveAllListeners()
 {
   bool hadListeners = HasListeners();
   mListeners.Clear();
+  mCallbacks.Clear();
   if (hadListeners) {
     // See NS_ADDREF_THIS() in AddListener.
     NS_RELEASE_THIS();
   }
 }
 
 void
 MediaQueryList::RecomputeMatches()
@@ -147,25 +210,47 @@ MediaQueryList::RecomputeMatches()
   mMatchesValid = true;
 }
 
 void
 MediaQueryList::MediumFeaturesChanged(NotifyList &aListenersToNotify)
 {
   mMatchesValid = false;
 
-  if (mListeners.Length()) {
+  if (HasListeners()) {
     bool oldMatches = mMatches;
     RecomputeMatches();
     if (mMatches != oldMatches) {
       for (uint32_t i = 0, i_end = mListeners.Length(); i != i_end; ++i) {
         HandleChangeData *d = aListenersToNotify.AppendElement();
         if (d) {
           d->mql = this;
           d->listener = mListeners[i];
         }
       }
+      for (uint32_t i = 0, i_end = mCallbacks.Length(); i != i_end; ++i) {
+        HandleChangeData *d = aListenersToNotify.AppendElement();
+        if (d) {
+          d->mql = this;
+          d->callback = mCallbacks[i];
+        }
+      }
     }
   }
 }
 
+nsISupports*
+MediaQueryList::GetParentObject() const
+{
+  if (!mPresContext) {
+    return nullptr;
+  }
+  return mPresContext->Document();
+}
+
+JSObject*
+MediaQueryList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return MediaQueryListBinding::Wrap(aCx, aScope, this);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/layout/style/MediaQueryList.h
+++ b/layout/style/MediaQueryList.h
@@ -10,55 +10,73 @@
 
 #include "nsIDOMMediaQueryList.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "prclist.h"
 #include "mozilla/Attributes.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/MediaQueryListBinding.h"
 
 class nsPresContext;
 class nsMediaList;
 
 namespace mozilla {
 namespace dom {
 
 class MediaQueryList MOZ_FINAL : public nsIDOMMediaQueryList,
+                                 public nsWrapperCache,
                                  public PRCList
 {
 public:
   // The caller who constructs is responsible for calling Evaluate
   // before calling any other methods.
   MediaQueryList(nsPresContext *aPresContext,
                  const nsAString &aMediaQueryList);
 private:
   ~MediaQueryList();
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(MediaQueryList)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaQueryList)
 
   NS_DECL_NSIDOMMEDIAQUERYLIST
 
+  nsISupports* GetParentObject() const;
+
+  typedef CallbackObjectHolder<mozilla::dom::MediaQueryListListener,
+                               nsIDOMMediaQueryListListener> CallbackType;
+
   struct HandleChangeData {
     nsRefPtr<MediaQueryList> mql;
     nsCOMPtr<nsIDOMMediaQueryListListener> listener;
+    nsCOMPtr<mozilla::dom::MediaQueryListListener> callback;
   };
 
   typedef FallibleTArray< nsCOMPtr<nsIDOMMediaQueryListListener> > ListenerList;
+  typedef FallibleTArray< nsRefPtr<mozilla::dom::MediaQueryListListener> > CallbackList;
   typedef FallibleTArray<HandleChangeData> NotifyList;
 
   // Appends listeners that need notification to aListenersToNotify
   void MediumFeaturesChanged(NotifyList &aListenersToNotify);
 
-  bool HasListeners() const { return !mListeners.IsEmpty(); }
+  bool HasListeners() const { return !mListeners.IsEmpty() || !mCallbacks.IsEmpty(); }
 
   void RemoveAllListeners();
 
+  JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  // WebIDL methods
+  // The XPCOM GetMedia method is good
+  bool Matches();
+  void AddListener(mozilla::dom::MediaQueryListListener& aListener);
+  void RemoveListener(mozilla::dom::MediaQueryListListener& aListener);
+
 private:
   void RecomputeMatches();
 
   // We only need a pointer to the pres context 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
@@ -72,14 +90,15 @@ private:
   // is equivalent to being in that pres context's mDOMMediaQueryLists
   // linked list.
   nsRefPtr<nsPresContext> mPresContext;
 
   nsRefPtr<nsMediaList> mMediaList;
   bool mMatches;
   bool mMatchesValid;
   ListenerList mListeners;
+  CallbackList mCallbacks;
 };
 
 } // 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
@@ -265,24 +265,30 @@ function run() {
   })();
 
   /* Bug 716751: null-dereference crash */
   (function() {
     iframe.style.width = "200px";
     subroot.offsetWidth; // flush layout
 
     var mql = subwin.matchMedia("(min-width: 150px)");
-    mql.addListener(null);
+    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.
 
-    mql.removeListener(null);
-    mql.removeListener(null);
+    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,