Implement window.matchMedia for matching of media queries and notification of media query changes. (Bug 542058, patch 3) r=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Thu, 21 Apr 2011 20:17:31 -0700
changeset 68411 1b37bbdb15b7
parent 68410 183330b5a008
child 68412 b5832780b4c2
push id19630
push userdbaron@mozilla.com
push date2011-04-22 03:18 +0000
treeherdermozilla-central@e00435bb54b5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs542058
milestone6.0a1
Implement window.matchMedia for matching of media queries and notification of media query changes. (Bug 542058, patch 3) r=bzbarsky

This is specified at:
http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/base/nsGlobalWindow.cpp
dom/interfaces/base/Makefile.in
dom/interfaces/base/nsIDOMMediaQueryList.idl
dom/interfaces/base/nsIDOMWindowInternal.idl
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/style/Makefile.in
layout/style/nsCSSStyleSheet.cpp
layout/style/nsDOMMediaQueryList.cpp
layout/style/nsDOMMediaQueryList.h
layout/style/test/Makefile.in
layout/style/test/test_media_queries_dynamic.html
layout/style/test/test_media_query_list.html
xpcom/glue/nsCycleCollectionParticipant.h
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -505,16 +505,18 @@
 #include "mozilla/dom/indexedDB/IDBObjectStore.h"
 #include "mozilla/dom/indexedDB/IDBTransaction.h"
 #include "mozilla/dom/indexedDB/IDBCursor.h"
 #include "mozilla/dom/indexedDB/IDBKeyRange.h"
 #include "mozilla/dom/indexedDB/IDBIndex.h"
 #include "nsIIDBDatabaseException.h"
 #include "nsIDOMEventException.h"
 
+#include "nsIDOMMediaQueryList.h"
+
 using namespace mozilla::dom;
 
 static NS_DEFINE_CID(kDOMSOF_CID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
 
 static const char kDOMStringBundleURL[] =
   "chrome://global/locale/dom/dom.properties";
 
 // NOTE: DEFAULT_SCRIPTABLE_FLAGS and DOM_DEFAULT_SCRIPTABLE_FLAGS
@@ -1497,16 +1499,19 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
 #ifdef MOZ_CSS_ANIMATIONS
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframeRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframesRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #endif
+
+  NS_DEFINE_CLASSINFO_DATA(MediaQueryList, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
 };
 
 // Objects that should be constructable through |new Name();|
 struct nsContractIDMapData
 {
   PRInt32 mDOMClassInfoID;
   const char *mContractID;
 };
@@ -4253,16 +4258,20 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframeRule)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframesRule, nsIDOMMozCSSKeyframesRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframesRule)
   DOM_CLASSINFO_MAP_END
 #endif
 
+  DOM_CLASSINFO_MAP_BEGIN(MediaQueryList, nsIDOMMediaQueryList)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMMediaQueryList)
+  DOM_CLASSINFO_MAP_END
+
 #ifdef NS_DEBUG
   {
     PRUint32 i = NS_ARRAY_LENGTH(sClassInfoData);
 
     if (i != eDOMClassInfoIDCount) {
       NS_ERROR("The number of items in sClassInfoData doesn't match the "
                "number of nsIDOMClassInfo ID's, this is bad! Fix it!");
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -512,8 +512,10 @@ DOMCI_CLASS(IDBVersionChangeRequest)
 DOMCI_CLASS(IDBDatabaseException)
 
 DOMCI_CLASS(EventException)
 
 #ifdef MOZ_CSS_ANIMATIONS
 DOMCI_CLASS(MozCSSKeyframeRule)
 DOMCI_CLASS(MozCSSKeyframesRule)
 #endif
+
+DOMCI_CLASS(MediaQueryList)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -81,16 +81,17 @@
 #include "nsGeolocation.h"
 #include "nsDesktopNotification.h"
 #include "nsContentCID.h"
 #include "nsLayoutStatics.h"
 #include "nsCycleCollector.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsDOMThreadService.h"
 #include "nsAutoJSValHolder.h"
+#include "nsDOMMediaQueryList.h"
 
 // Interfaces Needed
 #include "nsIFrame.h"
 #include "nsCanvasFrame.h"
 #include "nsIWidget.h"
 #include "nsIBaseWindow.h"
 #include "nsAccelerometer.h"
 #include "nsIContent.h"
@@ -3919,16 +3920,42 @@ nsGlobalWindow::GetMozAnimationStartTime
   }
 
   // If all else fails, just be compatible with Date.now()
   *aTime = JS_Now() / PR_USEC_PER_MSEC;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsGlobalWindow::MatchMedia(const nsAString& aMediaQueryList,
+                           nsIDOMMediaQueryList** aResult)
+{
+  // 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(MatchMedia, (aMediaQueryList, aResult),
+                   NS_ERROR_NOT_INITIALIZED);
+
+  *aResult = nsnull;
+
+  if (!mDocShell)
+    return NS_OK;
+
+  nsRefPtr<nsPresContext> presContext;
+  mDocShell->GetPresContext(getter_AddRefs(presContext));
+
+  if (!presContext)
+    return NS_OK;
+
+  presContext->MatchMedia(aMediaQueryList, aResult);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsGlobalWindow::SetScreenX(PRInt32 aScreenX)
 {
   FORWARD_TO_OUTER(SetScreenX, (aScreenX), NS_ERROR_NOT_INITIALIZED);
 
   /*
    * If caller is not chrome and the user has not explicitly exempted the site,
    * prevent setting window.screenX by exiting early
    */
--- a/dom/interfaces/base/Makefile.in
+++ b/dom/interfaces/base/Makefile.in
@@ -62,16 +62,17 @@ XPIDLSRCS =					\
 	nsIContentPrefService.idl		\
 	nsIContentURIGrouper.idl		\
 	nsIDOMClientInformation.idl		\
 	nsIDOMConstructor.idl			\
 	nsIDOMCRMFObject.idl			\
 	nsIDOMCrypto.idl			\
 	nsIDOMHistory.idl			\
 	nsIDOMLocation.idl			\
+	nsIDOMMediaQueryList.idl		\
 	nsIDOMMimeType.idl			\
 	nsIDOMMimeTypeArray.idl			\
 	nsIDOMNavigator.idl			\
 	nsIDOMPkcs11.idl			\
 	nsIDOMPlugin.idl			\
 	nsIDOMPluginArray.idl			\
 	nsIDOMScreen.idl			\
 	nsIDOMWindowInternal.idl		\
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/base/nsIDOMMediaQueryList.idl
@@ -0,0 +1,58 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "domstubs.idl"
+
+interface nsIDOMMediaQueryListListener;
+
+/* see http://dev.w3.org/csswg/cssom-view/#the-mediaquerylist-interface */
+
+[scriptable, uuid(e0e49c52-915b-40f9-9cba-6026305cdf3e)]
+interface nsIDOMMediaQueryList : nsISupports
+{
+  readonly attribute DOMString media;
+  readonly attribute boolean matches;
+
+  void addListener(in nsIDOMMediaQueryListListener listener);
+  void removeListener(in nsIDOMMediaQueryListListener listener);
+};
+
+[scriptable, function, uuid(279a5cbd-5c15-475d-847b-e0de1624eb77)]
+interface nsIDOMMediaQueryListListener : nsISupports
+{
+  void handleChange(in nsIDOMMediaQueryList mql);
+};
--- a/dom/interfaces/base/nsIDOMWindowInternal.idl
+++ b/dom/interfaces/base/nsIDOMWindowInternal.idl
@@ -39,18 +39,19 @@
 
 #include "nsIDOMWindow2.idl"
 
 interface nsIPrompt;
 interface nsIControllers;
 interface nsIDOMLocation;
 interface nsIVariant;
 interface nsIAnimationFrameListener;
+interface nsIDOMMediaQueryList;
 
-[scriptable, uuid(9d6a1157-0719-46a7-b49f-7ffeaa0b5c86)]
+[scriptable, uuid(04eafa93-efbe-4254-9d65-91c344fa7ff2)]
 interface nsIDOMWindowInternal : nsIDOMWindow2
 {
   readonly attribute nsIDOMWindowInternal        window;
 
   /* [replaceable] self */
   readonly attribute nsIDOMWindowInternal        self;
 
   readonly attribute nsIDOMNavigator             navigator;
@@ -227,16 +228,21 @@ interface nsIDOMWindowInternal : nsIDOMW
    */
   void
     mozRequestAnimationFrame([optional] in nsIAnimationFrameListener aListener);
 
   /**
    * The current animation start time in milliseconds since the epoch.
    */
   readonly attribute long long mozAnimationStartTime;
+
+  /**
+   * http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface
+   */
+  nsIDOMMediaQueryList matchMedia(in DOMString media_query_list);
 };
 
 [scriptable, uuid(8fc58f56-f769-4368-a098-edd08550cf1a)]
 interface nsIDOMMozURLProperty : nsISupports
 {
   DOMString createObjectURL(in nsIDOMBlob blob);
   void revokeObjectURL(in DOMString URL);
 };
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -95,16 +95,17 @@
 #include "nsObjectFrame.h"
 #include "nsTransitionManager.h"
 #ifdef MOZ_CSS_ANIMATIONS
 #include "nsAnimationManager.h"
 #endif
 #include "mozilla/dom/Element.h"
 #include "nsIFrameMessageManager.h"
 #include "FrameLayerBuilder.h"
+#include "nsDOMMediaQueryList.h"
 
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #endif // MOZ_SMIL
 
 #ifdef IBMBIDI
 #include "nsBidiPresUtils.h"
 #endif // IBMBIDI
@@ -249,23 +250,28 @@ nsPresContext::nsPresContext(nsIDocument
     mNeverAnimate = PR_TRUE;
   } else {
     mImageAnimationMode = imgIContainer::kNormalAnimMode;
     mNeverAnimate = PR_FALSE;
   }
   NS_ASSERTION(mDocument, "Null document");
   mUserFontSet = nsnull;
   mUserFontSetDirty = PR_TRUE;
+
+  PR_INIT_CLIST(&mDOMMediaQueryLists);
 }
 
 nsPresContext::~nsPresContext()
 {
   NS_PRECONDITION(!mShell, "Presshell forgot to clear our mShell pointer");
   SetShell(nsnull);
 
+  NS_ABORT_IF_FALSE(PR_CLIST_IS_EMPTY(&mDOMMediaQueryLists),
+                    "must not have media query lists left");
+
   // Disconnect the refresh driver *after* the transition manager, which
   // needs it.
   if (mRefreshDriver && mRefreshDriver->PresContext() == this) {
     mRefreshDriver->Disconnect();
   }
 
   if (mEventManager) {
     // unclear if these are needed, but can't hurt
@@ -1638,21 +1644,66 @@ void
 nsPresContext::MediaFeatureValuesChanged(PRBool aCallerWillRebuildStyleData)
 {
   mPendingMediaFeatureValuesChanged = PR_FALSE;
   if (mShell &&
       mShell->StyleSet()->MediumFeaturesChanged(this) &&
       !aCallerWillRebuildStyleData) {
     RebuildAllStyleData(nsChangeHint(0));
   }
+
+  if (!nsContentUtils::IsSafeToRunScript()) {
+    NS_ABORT_IF_FALSE(mDocument->IsBeingUsedAsImage(),
+                      "How did we get here?  Are we failing to notify "
+                      "listeners that we should notify?");
+    return;
+  }
+
+  // Media query list listeners should be notified from a queued task
+  // (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(&mDOMMediaQueryLists)) {
+    // 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.
+    nsDOMMediaQueryList::NotifyList notifyList;
+    for (PRCList *l = PR_LIST_HEAD(&mDOMMediaQueryLists);
+         l != &mDOMMediaQueryLists; l = PR_NEXT_LINK(l)) {
+      nsDOMMediaQueryList *mql = static_cast<nsDOMMediaQueryList*>(l);
+      mql->MediumFeaturesChanged(notifyList);
+    }
+
+    for (PRUint32 i = 0, i_end = notifyList.Length(); i != i_end; ++i) {
+      nsDOMMediaQueryList::HandleChangeData &d = notifyList[i];
+      d.listener->HandleChange(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
+  // need to track whether it's been added).
   if (!mPendingMediaFeatureValuesChanged) {
     nsCOMPtr<nsIRunnable> ev =
       NS_NewRunnableMethod(this, &nsPresContext::HandleMediaFeatureValuesChangedEvent);
     if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
       mPendingMediaFeatureValuesChanged = PR_TRUE;
     }
   }
 }
@@ -1663,16 +1714,29 @@ 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(PR_FALSE);
   }
 }
 
 void
+nsPresContext::MatchMedia(const nsAString& aMediaQueryList,
+                          nsIDOMMediaQueryList** aResult)
+{
+  nsRefPtr<nsDOMMediaQueryList> result =
+    new nsDOMMediaQueryList(this, aMediaQueryList);
+
+  // Insert the new item at the end of the linked list.
+  PR_INSERT_BEFORE(result, &mDOMMediaQueryLists);
+
+  result.forget(aResult);
+}
+
+void
 nsPresContext::SetPaginatedScrolling(PRBool aPaginated)
 {
   if (mType == eContext_PrintPreview || mType == eContext_PageLayout)
     mCanPaginatedScroll = aPaginated;
 }
 
 void
 nsPresContext::SetPrintSettings(nsIPrintSettings *aPrintSettings)
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -68,16 +68,17 @@
 #include "nsRegion.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 #include "nsContentUtils.h"
 #include "nsIWidget.h"
 #include "mozilla/TimeStamp.h"
 #include "nsIContent.h"
+#include "prclist.h"
 
 class nsImageLoader;
 #ifdef IBMBIDI
 class nsBidiPresUtils;
 #endif // IBMBIDI
 
 struct nsRect;
 
@@ -102,16 +103,17 @@ class nsUserFontSet;
 struct nsFontFaceRuleContainer;
 class nsObjectFrame;
 class nsTransitionManager;
 #ifdef MOZ_CSS_ANIMATIONS
 class nsAnimationManager;
 #endif
 class nsRefreshDriver;
 class imgIContainer;
+class nsIDOMMediaQueryList;
 
 #ifdef MOZ_REFLOW_PERF
 class nsRenderingContext;
 #endif
 
 enum nsWidgetType {
   eWidgetType_Button  	= 1,
   eWidgetType_Checkbox	= 2,
@@ -265,16 +267,22 @@ public:
   void PostMediaFeatureValuesChangedEvent();
   NS_HIDDEN_(void) HandleMediaFeatureValuesChangedEvent();
   void FlushPendingMediaFeatureValuesChanged() {
     if (mPendingMediaFeatureValuesChanged)
       MediaFeatureValuesChanged(PR_FALSE);
   }
 
   /**
+   * Support for window.matchMedia()
+   */
+  void MatchMedia(const nsAString& aMediaQueryList,
+                  nsIDOMMediaQueryList** aResult);
+
+  /**
    * Access compatibility mode for this context.  This is the same as
    * our document's compatibility mode.
    */
   nsCompatibility CompatibilityMode() const {
     return Document()->GetCompatibilityMode();
   }
   /**
    * Notify the context that the document's compatibility mode has changed
@@ -1072,16 +1080,18 @@ protected:
   // charset rather than explicitly specified as a lang attribute).
   nsIAtom*              mLanguage;      // [STRONG]
 
   nsRefPtrHashtable<nsVoidPtrHashKey, nsImageLoader>
                         mImageLoaders[IMAGE_LOAD_TYPE_COUNT];
 
   nsWeakPtr             mContainer;
 
+  PRCList               mDOMMediaQueryLists;
+
   PRInt32               mMinFontSize;   // Min font size, defaults to 0
   float                 mTextZoom;      // Text zoom, defaults to 1.0
   float                 mFullZoom;      // Page zoom, defaults to 1.0
 
   PRInt32               mCurAppUnitsPerDevPixel;
   PRInt32               mAutoQualityMinFontSizePixelsPref;
 
   nsCOMPtr<nsITheme> mTheme;
--- a/layout/style/Makefile.in
+++ b/layout/style/Makefile.in
@@ -66,16 +66,17 @@ EXPORTS		= \
 		nsCSSPseudoClasses.h \
 		nsCSSPseudoElementList.h \
 		nsCSSPseudoElements.h \
 		nsCSSRuleProcessor.h \
 		nsCSSStyleSheet.h \
 		nsCSSValue.h \
 		nsDOMCSSAttrDeclaration.h \
 		nsDOMCSSDeclaration.h \
+		nsDOMMediaQueryList.h \
 		nsICSSDeclaration.h \
 		nsICSSLoaderObserver.h \
 		nsICSSPseudoComparator.h \
 		nsICSSRule.h \
 		nsICSSRuleList.h \
 		nsICSSStyleRuleDOMWrapper.h \
 		nsIStyleRule.h \
 		nsIStyleRuleProcessor.h \
@@ -122,16 +123,17 @@ CPPSRCS		= \
 		nsCSSStyleSheet.cpp \
 		nsCSSValue.cpp \
 		nsComputedDOMStyle.cpp \
 		nsDOMCSSAttrDeclaration.cpp \
 		nsDOMCSSDeclaration.cpp \
 		nsDOMCSSRGBColor.cpp \
 		nsDOMCSSRect.cpp \
 		nsDOMCSSValueList.cpp \
+		nsDOMMediaQueryList.cpp \
 		nsFontFaceLoader.cpp \
 		nsHTMLCSSStyleSheet.cpp \
 		nsHTMLStyleSheet.cpp \
 		nsLayoutStylesheetCache.cpp \
 		nsMediaFeatures.cpp \
 		nsNthIndexCache.cpp \
 		nsROCSSPrimitiveValue.cpp \
 		nsRuleData.cpp \
--- a/layout/style/nsCSSStyleSheet.cpp
+++ b/layout/style/nsCSSStyleSheet.cpp
@@ -582,17 +582,17 @@ nsMediaList::SetText(const nsAString& aM
 
   PRBool htmlMode = PR_FALSE;
   if (mStyleSheet) {
     nsCOMPtr<nsIDOMNode> node;
     mStyleSheet->GetOwnerNode(getter_AddRefs(node));
     htmlMode = !!node;
   }
 
-  return parser.ParseMediaList(nsString(aMediaText), nsnull, 0,
+  return parser.ParseMediaList(aMediaText, nsnull, 0,
                                this, htmlMode);
 }
 
 PRBool
 nsMediaList::Matches(nsPresContext* aPresContext,
                      nsMediaQueryResultCacheKey* aKey)
 {
   for (PRInt32 i = 0, i_end = mArray.Length(); i < i_end; ++i) {
new file mode 100644
--- /dev/null
+++ b/layout/style/nsDOMMediaQueryList.cpp
@@ -0,0 +1,164 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsDOMMediaQueryList.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* implements DOM interface for querying and observing media queries */
+
+#include "nsDOMMediaQueryList.h"
+#include "nsPresContext.h"
+#include "nsIMediaList.h"
+#include "nsCSSParser.h"
+
+nsDOMMediaQueryList::nsDOMMediaQueryList(nsPresContext *aPresContext,
+                                         const nsAString &aMediaQueryList)
+  : mPresContext(aPresContext),
+    mMediaList(new nsMediaList),
+    mMatchesValid(PR_FALSE)
+{
+  PR_INIT_CLIST(this);
+
+  nsCSSParser parser;
+  parser.ParseMediaList(aMediaQueryList, nsnull, 0, mMediaList, PR_FALSE);
+}
+
+nsDOMMediaQueryList::~nsDOMMediaQueryList()
+{
+  if (mPresContext) {
+    PR_REMOVE_LINK(this);
+  }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMMediaQueryList)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMMediaQueryList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPresContext)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_OF_NSCOMPTR(mListeners)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMMediaQueryList)
+if (tmp->mPresContext) {
+  PR_REMOVE_LINK(tmp);
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPresContext)
+}
+NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mListeners)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+DOMCI_DATA(MediaQueryList, nsDOMMediaQueryList)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMMediaQueryList)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMediaQueryList)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMMediaQueryList)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MediaQueryList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMediaQueryList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMediaQueryList)
+
+NS_IMETHODIMP
+nsDOMMediaQueryList::GetMedia(nsAString &aMedia)
+{
+  mMediaList->GetText(aMedia);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMMediaQueryList::GetMatches(PRBool *aMatches)
+{
+  if (!mMatchesValid) {
+    NS_ABORT_IF_FALSE(mListeners.Length() == 0,
+                      "when listeners present, must keep mMatches current");
+    RecomputeMatches();
+  }
+
+  *aMatches = mMatches;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMMediaQueryList::AddListener(nsIDOMMediaQueryListListener *aListener)
+{
+  if (!mMatchesValid) {
+    NS_ABORT_IF_FALSE(mListeners.Length() == 0,
+                      "when listeners present, must keep mMatches current");
+    RecomputeMatches();
+  }
+
+  if (!mListeners.Contains(aListener)) {
+    mListeners.AppendElement(aListener);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMMediaQueryList::RemoveListener(nsIDOMMediaQueryListListener *aListener)
+{
+  mListeners.RemoveElement(aListener);
+  NS_ABORT_IF_FALSE(!mListeners.Contains(aListener),
+                    "duplicate occurrence of listeners");
+  return NS_OK;
+}
+
+void
+nsDOMMediaQueryList::RecomputeMatches()
+{
+  if (!mPresContext) {
+    return;
+  }
+
+  mMatches = mMediaList->Matches(mPresContext, nsnull);
+  mMatchesValid = PR_TRUE;
+}
+
+void
+nsDOMMediaQueryList::MediumFeaturesChanged(NotifyList &aListenersToNotify)
+{
+  mMatchesValid = PR_FALSE;
+
+  if (mListeners.Length()) {
+    PRPackedBool oldMatches = mMatches;
+    RecomputeMatches();
+    if (mMatches != oldMatches) {
+      for (PRUint32 i = 0, i_end = mListeners.Length(); i != i_end; ++i) {
+        HandleChangeData *d = aListenersToNotify.AppendElement();
+        if (d) {
+          d->mql = this;
+          d->listener = mListeners[i];
+        }
+      }
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/nsDOMMediaQueryList.h
@@ -0,0 +1,106 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsDOMMediaQueryList.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* implements DOM interface for querying and observing media queries */
+
+#ifndef nsDOMMediaQueryList_h_
+#define nsDOMMediaQueryList_h_
+
+#include "nsIDOMMediaQueryList.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "prclist.h"
+
+class nsPresContext;
+class nsMediaList;
+
+class nsDOMMediaQueryList : public nsIDOMMediaQueryList,
+                            public PRCList
+{
+public:
+  // The caller who constructs is responsible for calling Evaluate
+  // before calling any other methods.
+  nsDOMMediaQueryList(nsPresContext *aPresContext,
+                      const nsAString &aMediaQueryList);
+private:
+  ~nsDOMMediaQueryList();
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(nsDOMMediaQueryList)
+
+  NS_DECL_NSIDOMMEDIAQUERYLIST
+
+  struct HandleChangeData {
+    nsRefPtr<nsDOMMediaQueryList> mql;
+    nsCOMPtr<nsIDOMMediaQueryListListener> listener;
+  };
+
+  typedef FallibleTArray< nsCOMPtr<nsIDOMMediaQueryListListener> > ListenerList;
+  typedef FallibleTArray<HandleChangeData> NotifyList;
+
+  // Appends listeners that need notification to aListenersToNotify
+  void MediumFeaturesChanged(NotifyList &aListenersToNotify);
+
+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
+  // (after a single call to the .matches getter).
+  //
+  // This pointer does make us a little more dependent on cycle
+  // collection.
+  //
+  // We have a non-null mPresContext for our entire lifetime except
+  // after cycle collection unlinking.  Having a non-null mPresContext
+  // is equivalent to being in that pres context's mDOMMediaQueryLists
+  // linked list.
+  nsRefPtr<nsPresContext> mPresContext;
+
+  nsRefPtr<nsMediaList> mMediaList;
+  PRPackedBool mMatches;
+  PRPackedBool mMatchesValid;
+  ListenerList mListeners;
+};
+
+#endif /* !defined(nsDOMMediaQueryList_h_) */
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -150,16 +150,17 @@ GARBAGE += css_properties.js
 		test_ident_escaping.html \
 		test_inherit_computation.html \
 		test_inherit_storage.html \
 		test_initial_computation.html \
 		test_initial_storage.html \
 		test_media_queries.html \
 		test_media_queries_dynamic.html \
 		test_media_queries_dynamic_xbl.html \
+		test_media_query_list.html \
 		test_moz_device_pixel_ratio.html \
 		test_namespace_rule.html \
 		test_of_type_selectors.xhtml \
 		test_parse_ident.html \
 		test_parse_rule.html \
 		test_parse_url.html \
 		test_pixel_lengths.html \
 		test_pointer-events.html \
--- a/layout/style/test/test_media_queries_dynamic.html
+++ b/layout/style/test/test_media_queries_dynamic.html
@@ -52,21 +52,16 @@ function run() {
   function should_apply(cs) {
     ok(query_applies(cs), cs._originalQueryText + " should apply");
   }
 
   function should_not_apply(cs) {
     ok(!query_applies(cs), cs._originalQueryText + " should not apply");
   }
 
-  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-  const CI = Components.interfaces;
-  var iframe_docshell = subwin.QueryInterface(CI.nsIInterfaceRequestor).getInterface(CI.nsIWebNavigation).QueryInterface(CI.nsIDocShell);
-  var iframe_mudv = iframe_docshell.contentViewer.QueryInterface(CI.nsIMarkupDocumentViewer);
-
   var content_div = document.getElementById("content");
   content_div.style.font = "-moz-initial";
   var em_size =
     getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
 
   let width_val = 317; // pick two not-too-round numbers
   let height_val = 228;
   iframe_style.width = width_val + "px";
@@ -90,36 +85,36 @@ function run() {
                      (Math.floor(width_val/(em_size*2)) - 1) + "em)")
   ];
 
   is(wh_queries[0].fontSize, em_size + "px", "text zoom is 1.0");
   should_not_apply(wh_queries[0]);
   should_apply(wh_queries[1]);
   should_apply(wh_queries[2]);
   should_not_apply(wh_queries[3]);
-  iframe_mudv.textZoom = 2.0;
+  SpecialPowers.setTextZoom(subwin, 2.0);
   isnot(wh_queries[0].fontSize, em_size + "px", "text zoom is not 1.0");
   should_not_apply(wh_queries[4]);
   should_apply(wh_queries[5]);
   should_apply(wh_queries[6]);
   should_not_apply(wh_queries[7]);
-  iframe_mudv.textZoom = 1.0;
+  SpecialPowers.setTextZoom(subwin, 1.0);
   is(wh_queries[0].fontSize, em_size + "px", "text zoom is 1.0");
   is(subwin.innerHeight, 228, "full zoom is 1.0");
   should_not_apply(wh_queries[0]);
   should_apply(wh_queries[1]);
   should_apply(wh_queries[2]);
   should_not_apply(wh_queries[3]);
-  iframe_mudv.fullZoom = 2.0;
+  SpecialPowers.setFullZoom(subwin, 2.0);
   isnot(subwin.innerHeight, 228, "full zoom is not 1.0");
   should_not_apply(wh_queries[4]);
   should_apply(wh_queries[5]);
   should_apply(wh_queries[6]);
   should_not_apply(wh_queries[7]);
-  iframe_mudv.fullZoom = 1.0;
+  SpecialPowers.setFullZoom(subwin, 1.0);
   is(subwin.innerHeight, 228, "full zoom is 1.0");
 
   SimpleTest.finish();
 }
 
 </script>
 </pre>
 </body>
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_media_query_list.html
@@ -0,0 +1,259 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=542058
+-->
+<head>
+  <title>Test for MediaQueryList (Bug 542058)</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=542058">Mozilla Bug 542058</a>
+<iframe id="subdoc" src="about:blank"></iframe>
+<div id="content" style="display:none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for MediaQueryList (Bug 542058) **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+  var iframe = document.getElementById("subdoc");
+  var subdoc = iframe.contentDocument;
+  var subwin = iframe.contentWindow;
+  var subroot = subdoc.documentElement;
+
+  var content_div = document.getElementById("content");
+  content_div.style.font = "-moz-initial";
+  var em_size =
+    getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1];
+
+  var w = Math.floor(em_size * 9.3);
+  var h = Math.floor(em_size * 4.2);
+  iframe.style.width = w + "px";
+  iframe.style.height = h + "px";
+  subroot.offsetWidth; // flush layout
+
+  function setup_mql(str) {
+    var obj = { str: str, mql: subwin.matchMedia(str), notifyCount: 0 }
+    obj.mql.addListener(function(mql) {
+                          is(mql, obj.mql,
+                             "correct argument to listener: " + obj.str);
+                          ++obj.notifyCount;
+                          // Test the last match result only on odd
+                          // notifications.
+                          if (obj.notifyCount & 1) {
+                            obj.lastOddMatchResult = mql.matches;
+                          }
+                        });
+    return obj;
+  }
+
+  var w_exact_w = setup_mql("(width: " + w + "px)");
+  var w_min_9em = setup_mql("(min-width : 9em)");
+  var w_min_10em = setup_mql("(  min-width: 10em ) ");
+  var w_max_9em = setup_mql("(max-width: 9em)");
+  var w_max_10em = setup_mql("(max-width: 10em)");
+
+  is(w_exact_w.mql.media, "(width: " + w + "px)", "serialization");
+  is(w_min_9em.mql.media, "(min-width: 9em)", "serialization");
+  is(w_min_10em.mql.media, "(min-width: 10em)", "serialization");
+  is(w_max_9em.mql.media, "(max-width: 9em)", "serialization");
+  is(w_max_10em.mql.media, "(max-width: 10em)", "serialization");
+
+  function check_match(obj, expected, desc) {
+    is(obj.mql.matches, expected,
+       obj.str + " media query list .matches " + desc);
+    if (obj.notifyCount & 1) { // odd notifications only
+      is(obj.lastOddMatchResult, expected,
+       obj.str + " media query list last notify result " + desc);
+    }
+  }
+  function check_notify(obj, expected, desc) {
+    is(obj.notifyCount, expected,
+       obj.str + " media query list .notify count " + desc);
+  }
+  check_match(w_exact_w, true, "initially");
+  check_notify(w_exact_w, 0, "initially");
+  check_match(w_min_9em, true, "initially");
+  check_notify(w_min_9em, 0, "initially");
+  check_match(w_min_10em, false, "initially");
+  check_notify(w_min_10em, 0, "initially");
+  check_match(w_max_9em, false, "initially");
+  check_notify(w_max_9em, 0, "initially");
+  check_match(w_max_10em, true, "initially");
+  check_notify(w_max_10em, 0, "initially");
+
+  var w2 = Math.floor(em_size * 10.3);
+  iframe.style.width = w2 + "px";
+  subroot.offsetWidth; // flush layout
+
+  check_match(w_exact_w, false, "after width increase to around 10.3em");
+  check_notify(w_exact_w, 1, "after width increase to around 10.3em");
+  check_match(w_min_9em, true, "after width increase to around 10.3em");
+  check_notify(w_min_9em, 0, "after width increase to around 10.3em");
+  check_match(w_min_10em, true, "after width increase to around 10.3em");
+  check_notify(w_min_10em, 1, "after width increase to around 10.3em");
+  check_match(w_max_9em, false, "after width increase to around 10.3em");
+  check_notify(w_max_9em, 0, "after width increase to around 10.3em");
+  check_match(w_max_10em, false, "after width increase to around 10.3em");
+  check_notify(w_max_10em, 1, "after width increase to around 10.3em");
+
+  var w3 = w * 2;
+  iframe.style.width = w3 + "px";
+  subroot.offsetWidth; // flush layout
+
+  check_match(w_exact_w, false, "after width double from original");
+  check_notify(w_exact_w, 1, "after width double from original");
+  check_match(w_min_9em, true, "after width double from original");
+  check_notify(w_min_9em, 0, "after width double from original");
+  check_match(w_min_10em, true, "after width double from original");
+  check_notify(w_min_10em, 1, "after width double from original");
+  check_match(w_max_9em, false, "after width double from original");
+  check_notify(w_max_9em, 0, "after width double from original");
+  check_match(w_max_10em, false, "after width double from original");
+  check_notify(w_max_10em, 1, "after width double from original");
+
+  SpecialPowers.setFullZoom(subwin, 2.0);
+  subroot.offsetWidth; // flush layout
+
+  check_match(w_exact_w, true, "after zoom");
+  check_notify(w_exact_w, 2, "after zoom");
+  check_match(w_min_9em, true, "after zoom");
+  check_notify(w_min_9em, 0, "after zoom");
+  check_match(w_min_10em, false, "after zoom");
+  check_notify(w_min_10em, 2, "after zoom");
+  check_match(w_max_9em, false, "after zoom");
+  check_notify(w_max_9em, 0, "after zoom");
+  check_match(w_max_10em, true, "after zoom");
+  check_notify(w_max_10em, 2, "after zoom");
+
+  SpecialPowers.setFullZoom(subwin, 1.0);
+
+  // Additional tests of listener mutation.
+  (function() {
+    var received = [];
+    var received_mql = [];
+    function listener1(mql) {
+      received.push(1);
+      received_mql.push(mql);
+    }
+    function listener2(mql) {
+      received.push(2);
+      received_mql.push(mql);
+    }
+
+    iframe.style.width = "200px";
+    subroot.offsetWidth; // flush layout
+
+    var mql = subwin.matchMedia("(min-width: 150px)");
+    mql.addListener(listener1);
+    mql.addListener(listener1);
+    mql.addListener(listener2);
+    is(JSON.stringify(received), "[]", "listeners before notification");
+
+    iframe.style.width = "100px";
+    subroot.offsetWidth; // flush layout
+
+    is(JSON.stringify(received), "[1,2]", "duplicate listeners removed");
+    received = [];
+    mql.removeListener(listener1);
+
+    iframe.style.width = "200px";
+    subroot.offsetWidth; // flush layout
+
+    is(JSON.stringify(received), "[2]", "listener removal");
+    received = [];
+    mql.addListener(listener1);
+
+    iframe.style.width = "100px";
+    subroot.offsetWidth; // flush layout
+
+    is(JSON.stringify(received), "[2,1]", "listeners notified in order");
+    received = [];
+    mql.addListener(listener2);
+
+    iframe.style.width = "200px";
+    subroot.offsetWidth; // flush layout
+
+    is(JSON.stringify(received), "[2,1]", "add of existing listener is no-op");
+    received = [];
+    mql.addListener(listener1);
+
+    iframe.style.width = "100px";
+    subroot.offsetWidth; // flush layout
+
+    is(JSON.stringify(received), "[2,1]", "add of existing listener is no-op");
+    mql.removeListener(listener2);
+    received = [];
+    received_mql = [];
+
+    var mql2 = subwin.matchMedia("(min-width: 160px)");
+    mql2.addListener(listener1);
+    mql.addListener(listener2);
+
+    iframe.style.width = "200px";
+    subroot.offsetWidth; // flush layout
+
+    // mql (1, 2), mql2 (1)
+    is(JSON.stringify(received), "[1,2,1]",
+       "notification of lists in order created");
+    is(received_mql[0], mql,
+       "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) {
+      received.push(3);
+      received_mql.push(mql);
+      mql.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]",
+       "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)");
+  })();
+
+  SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/xpcom/glue/nsCycleCollectionParticipant.h
+++ b/xpcom/glue/nsCycleCollectionParticipant.h
@@ -395,16 +395,25 @@ public:
     {                                                                          \
       PRUint32 i, length = (_array).Length();                                  \
       for (i = 0; i < length; ++i)                                             \
         NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_PTR((_array)[i],              \
                                                      _element_class,           \
                                                      _name "[i]");             \
     }
 
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_OF_NSCOMPTR(_field)         \
+    {                                                                          \
+      PRUint32 i, length = tmp->_field.Length();                               \
+      for (i = 0; i < length; ++i) {                                           \
+        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, #_field "[i]");                 \
+        cb.NoteXPCOMChild(tmp->_field[i].get());                               \
+      }                                                                        \
+    }
+
 #define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY_MEMBER(_field,              \
                                                           _element_class)      \
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSTARRAY(tmp->_field, _element_class,    \
                                                #_field)
 
 #define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS                       \
     TraverseScriptObjects(tmp, cb);