Bug 1290209 - Part 3: Move media queries related class implementations from CSSStyleSheet.cpp to nsMediaList.cpp. r?xidorn draft
authorCameron McCormack <cam@mcc.id.au>
Thu, 05 Jan 2017 13:25:52 +0800
changeset 456282 42822ccb907597d086f5a14b7348ce7d20ac6694
parent 456281 96e4b9614fe7195678bf38a6750f2b72de4a4ec4
child 456283 b1e6e7a5737ee812a1ddb928f5442c9efda5e025
push id40445
push userbmo:cam@mcc.id.au
push dateThu, 05 Jan 2017 09:33:32 +0000
reviewersxidorn
bugs1290209
milestone53.0a1
Bug 1290209 - Part 3: Move media queries related class implementations from CSSStyleSheet.cpp to nsMediaList.cpp. r?xidorn MozReview-Commit-ID: DA8lAPCXTkm
layout/style/CSSStyleSheet.cpp
layout/style/moz.build
layout/style/nsMediaList.cpp
--- a/layout/style/CSSStyleSheet.cpp
+++ b/layout/style/CSSStyleSheet.cpp
@@ -7,17 +7,16 @@
 /* representation of a CSS style sheet */
 
 #include "mozilla/CSSStyleSheet.h"
 
 #include "nsIAtom.h"
 #include "nsCSSRuleProcessor.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/Element.h"
-#include "mozilla/dom/MediaListBinding.h"
 #include "mozilla/css/NameSpaceRule.h"
 #include "mozilla/css/GroupRule.h"
 #include "mozilla/css/ImportRule.h"
 #include "nsCSSRules.h"
 #include "nsMediaList.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 #include "nsGkAtoms.h"
@@ -116,726 +115,16 @@ CSSRuleListImpl::IndexedGetter(uint32_t 
       return rule->GetDOMRule();
     }
   }
 
   // Per spec: "Return Value ... null if ... not a valid index."
   return nullptr;
 }
 
-template <class Numeric>
-int32_t DoCompare(Numeric a, Numeric b)
-{
-  if (a == b)
-    return 0;
-  if (a < b)
-    return -1;
-  return 1;
-}
-
-bool
-nsMediaExpression::Matches(nsPresContext *aPresContext,
-                           const nsCSSValue& aActualValue) const
-{
-  const nsCSSValue& actual = aActualValue;
-  const nsCSSValue& required = mValue;
-
-  // If we don't have the feature, the match fails.
-  if (actual.GetUnit() == eCSSUnit_Null) {
-    return false;
-  }
-
-  // If the expression had no value to match, the match succeeds,
-  // unless the value is an integer 0 or a zero length.
-  if (required.GetUnit() == eCSSUnit_Null) {
-    if (actual.GetUnit() == eCSSUnit_Integer)
-      return actual.GetIntValue() != 0;
-    if (actual.IsLengthUnit())
-      return actual.GetFloatValue() != 0;
-    return true;
-  }
-
-  NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxAllowed ||
-               mRange == nsMediaExpression::eEqual, "yikes");
-  int32_t cmp; // -1 (actual < required)
-               //  0 (actual == required)
-               //  1 (actual > required)
-  switch (mFeature->mValueType) {
-    case nsMediaFeature::eLength:
-      {
-        NS_ASSERTION(actual.IsLengthUnit(), "bad actual value");
-        NS_ASSERTION(required.IsLengthUnit(), "bad required value");
-        nscoord actualCoord = nsRuleNode::CalcLengthWithInitialFont(
-                                aPresContext, actual);
-        nscoord requiredCoord = nsRuleNode::CalcLengthWithInitialFont(
-                                  aPresContext, required);
-        cmp = DoCompare(actualCoord, requiredCoord);
-      }
-      break;
-    case nsMediaFeature::eInteger:
-    case nsMediaFeature::eBoolInteger:
-      {
-        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Integer,
-                     "bad actual value");
-        NS_ASSERTION(required.GetUnit() == eCSSUnit_Integer,
-                     "bad required value");
-        NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger ||
-                     actual.GetIntValue() == 0 || actual.GetIntValue() == 1,
-                     "bad actual bool integer value");
-        NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger ||
-                     required.GetIntValue() == 0 || required.GetIntValue() == 1,
-                     "bad required bool integer value");
-        cmp = DoCompare(actual.GetIntValue(), required.GetIntValue());
-      }
-      break;
-    case nsMediaFeature::eFloat:
-      {
-        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Number,
-                     "bad actual value");
-        NS_ASSERTION(required.GetUnit() == eCSSUnit_Number,
-                     "bad required value");
-        cmp = DoCompare(actual.GetFloatValue(), required.GetFloatValue());
-      }
-      break;
-    case nsMediaFeature::eIntRatio:
-      {
-        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Array &&
-                     actual.GetArrayValue()->Count() == 2 &&
-                     actual.GetArrayValue()->Item(0).GetUnit() ==
-                       eCSSUnit_Integer &&
-                     actual.GetArrayValue()->Item(1).GetUnit() ==
-                       eCSSUnit_Integer,
-                     "bad actual value");
-        NS_ASSERTION(required.GetUnit() == eCSSUnit_Array &&
-                     required.GetArrayValue()->Count() == 2 &&
-                     required.GetArrayValue()->Item(0).GetUnit() ==
-                       eCSSUnit_Integer &&
-                     required.GetArrayValue()->Item(1).GetUnit() ==
-                       eCSSUnit_Integer,
-                     "bad required value");
-        // Convert to int64_t so we can multiply without worry.  Note
-        // that while the spec requires that both halves of |required|
-        // be positive, the numerator or denominator of |actual| might
-        // be zero (e.g., when testing 'aspect-ratio' on a 0-width or
-        // 0-height iframe).
-        int64_t actualNum = actual.GetArrayValue()->Item(0).GetIntValue(),
-                actualDen = actual.GetArrayValue()->Item(1).GetIntValue(),
-                requiredNum = required.GetArrayValue()->Item(0).GetIntValue(),
-                requiredDen = required.GetArrayValue()->Item(1).GetIntValue();
-        cmp = DoCompare(actualNum * requiredDen, requiredNum * actualDen);
-      }
-      break;
-    case nsMediaFeature::eResolution:
-      {
-        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Inch ||
-                     actual.GetUnit() == eCSSUnit_Pixel ||
-                     actual.GetUnit() == eCSSUnit_Centimeter,
-                     "bad actual value");
-        NS_ASSERTION(required.GetUnit() == eCSSUnit_Inch ||
-                     required.GetUnit() == eCSSUnit_Pixel ||
-                     required.GetUnit() == eCSSUnit_Centimeter,
-                     "bad required value");
-        float actualDPI = actual.GetFloatValue();
-        float overrideDPPX = aPresContext->GetOverrideDPPX();
-
-        if (overrideDPPX > 0) {
-          actualDPI = overrideDPPX * 96.0f;
-        } else if (actual.GetUnit() == eCSSUnit_Centimeter) {
-          actualDPI = actualDPI * 2.54f;
-        } else if (actual.GetUnit() == eCSSUnit_Pixel) {
-          actualDPI = actualDPI * 96.0f;
-        }
-        float requiredDPI = required.GetFloatValue();
-        if (required.GetUnit() == eCSSUnit_Centimeter) {
-          requiredDPI = requiredDPI * 2.54f;
-        } else if (required.GetUnit() == eCSSUnit_Pixel) {
-          requiredDPI = requiredDPI * 96.0f;
-        }
-        cmp = DoCompare(actualDPI, requiredDPI);
-      }
-      break;
-    case nsMediaFeature::eEnumerated:
-      {
-        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Enumerated,
-                     "bad actual value");
-        NS_ASSERTION(required.GetUnit() == eCSSUnit_Enumerated,
-                     "bad required value");
-        NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxNotAllowed,
-                     "bad range"); // we asserted above about mRange
-        // We don't really need DoCompare, but it doesn't hurt (and
-        // maybe the compiler will condense this case with eInteger).
-        cmp = DoCompare(actual.GetIntValue(), required.GetIntValue());
-      }
-      break;
-    case nsMediaFeature::eIdent:
-      {
-        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Ident,
-                     "bad actual value");
-        NS_ASSERTION(required.GetUnit() == eCSSUnit_Ident,
-                     "bad required value");
-        NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxNotAllowed,
-                     "bad range"); 
-        cmp = !(actual == required); // string comparison
-      }
-      break;
-  }
-  switch (mRange) {
-    case nsMediaExpression::eMin:
-      return cmp != -1;
-    case nsMediaExpression::eMax:
-      return cmp != 1;
-    case nsMediaExpression::eEqual:
-      return cmp == 0;
-  }
-  NS_NOTREACHED("unexpected mRange");
-  return false;
-}
-
-void
-nsMediaQueryResultCacheKey::AddExpression(const nsMediaExpression* aExpression,
-                                          bool aExpressionMatches)
-{
-  const nsMediaFeature *feature = aExpression->mFeature;
-  FeatureEntry *entry = nullptr;
-  for (uint32_t i = 0; i < mFeatureCache.Length(); ++i) {
-    if (mFeatureCache[i].mFeature == feature) {
-      entry = &mFeatureCache[i];
-      break;
-    }
-  }
-  if (!entry) {
-    entry = mFeatureCache.AppendElement();
-    if (!entry) {
-      return; /* out of memory */
-    }
-    entry->mFeature = feature;
-  }
-
-  ExpressionEntry eentry = { *aExpression, aExpressionMatches };
-  entry->mExpressions.AppendElement(eentry);
-}
-
-bool
-nsMediaQueryResultCacheKey::Matches(nsPresContext* aPresContext) const
-{
-  if (aPresContext->Medium() != mMedium) {
-    return false;
-  }
-
-  for (uint32_t i = 0; i < mFeatureCache.Length(); ++i) {
-    const FeatureEntry *entry = &mFeatureCache[i];
-    nsCSSValue actual;
-    nsresult rv =
-      (entry->mFeature->mGetter)(aPresContext, entry->mFeature, actual);
-    NS_ENSURE_SUCCESS(rv, false); // any better ideas?
-
-    for (uint32_t j = 0; j < entry->mExpressions.Length(); ++j) {
-      const ExpressionEntry &eentry = entry->mExpressions[j];
-      if (eentry.mExpression.Matches(aPresContext, actual) !=
-          eentry.mExpressionMatches) {
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
-bool
-nsDocumentRuleResultCacheKey::AddMatchingRule(css::DocumentRule* aRule)
-{
-  MOZ_ASSERT(!mFinalized);
-  return mMatchingRules.AppendElement(aRule);
-}
-
-void
-nsDocumentRuleResultCacheKey::Finalize()
-{
-  mMatchingRules.Sort();
-#ifdef DEBUG
-  mFinalized = true;
-#endif
-}
-
-#ifdef DEBUG
-static bool
-ArrayIsSorted(const nsTArray<css::DocumentRule*>& aRules)
-{
-  for (size_t i = 1; i < aRules.Length(); i++) {
-    if (aRules[i - 1] > aRules[i]) {
-      return false;
-    }
-  }
-  return true;
-}
-#endif
-
-bool
-nsDocumentRuleResultCacheKey::Matches(
-                       nsPresContext* aPresContext,
-                       const nsTArray<css::DocumentRule*>& aRules) const
-{
-  MOZ_ASSERT(mFinalized);
-  MOZ_ASSERT(ArrayIsSorted(mMatchingRules));
-  MOZ_ASSERT(ArrayIsSorted(aRules));
-
-#ifdef DEBUG
-  for (css::DocumentRule* rule : mMatchingRules) {
-    MOZ_ASSERT(aRules.BinaryIndexOf(rule) != aRules.NoIndex,
-               "aRules must contain all rules in mMatchingRules");
-  }
-#endif
-
-  // First check that aPresContext matches all the rules listed in
-  // mMatchingRules.
-  for (css::DocumentRule* rule : mMatchingRules) {
-    if (!rule->UseForPresentation(aPresContext)) {
-      return false;
-    }
-  }
-
-  // Then check that all the rules in aRules that aren't also in
-  // mMatchingRules do not match.
-
-  // pointer to matching rules
-  auto pm     = mMatchingRules.begin();
-  auto pm_end = mMatchingRules.end();
-
-  // pointer to all rules
-  auto pr     = aRules.begin();
-  auto pr_end = aRules.end();
-
-  // mMatchingRules and aRules are both sorted by their pointer values,
-  // so we can iterate over the two lists simultaneously.
-  while (pr < pr_end) {
-    while (pm < pm_end && *pm < *pr) {
-      ++pm;
-    }
-    if (pm >= pm_end || *pm != *pr) {
-      if ((*pr)->UseForPresentation(aPresContext)) {
-        return false;
-      }
-    }
-    ++pr;
-  }
-  return true;
-}
-
-#ifdef DEBUG
-void
-nsDocumentRuleResultCacheKey::List(FILE* aOut, int32_t aIndent) const
-{
-  for (css::DocumentRule* rule : mMatchingRules) {
-    nsCString str;
-
-    for (int32_t i = 0; i < aIndent; i++) {
-      str.AppendLiteral("  ");
-    }
-    str.AppendLiteral("{ ");
-
-    nsString condition;
-    rule->GetConditionText(condition);
-    AppendUTF16toUTF8(condition, str);
-
-    str.AppendLiteral(" }\n");
-    fprintf_stderr(aOut, "%s", str.get());
-  }
-}
-#endif
-
-size_t
-nsDocumentRuleResultCacheKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
-{
-  size_t n = 0;
-  n += mMatchingRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  return n;
-}
-
-void
-nsMediaQuery::AppendToString(nsAString& aString) const
-{
-  if (mHadUnknownExpression) {
-    aString.AppendLiteral("not all");
-    return;
-  }
-
-  NS_ASSERTION(!mNegated || !mHasOnly, "can't have not and only");
-  NS_ASSERTION(!mTypeOmitted || (!mNegated && !mHasOnly),
-               "can't have not or only when type is omitted");
-  if (!mTypeOmitted) {
-    if (mNegated) {
-      aString.AppendLiteral("not ");
-    } else if (mHasOnly) {
-      aString.AppendLiteral("only ");
-    }
-    aString.Append(nsDependentAtomString(mMediaType));
-  }
-
-  for (uint32_t i = 0, i_end = mExpressions.Length(); i < i_end; ++i) {
-    if (i > 0 || !mTypeOmitted)
-      aString.AppendLiteral(" and ");
-    aString.Append('(');
-
-    const nsMediaExpression &expr = mExpressions[i];
-    const nsMediaFeature *feature = expr.mFeature;
-    if (feature->mReqFlags & nsMediaFeature::eHasWebkitPrefix) {
-      aString.AppendLiteral("-webkit-");
-    }
-    if (expr.mRange == nsMediaExpression::eMin) {
-      aString.AppendLiteral("min-");
-    } else if (expr.mRange == nsMediaExpression::eMax) {
-      aString.AppendLiteral("max-");
-    }
-
-    aString.Append(nsDependentAtomString(*feature->mName));
-
-    if (expr.mValue.GetUnit() != eCSSUnit_Null) {
-      aString.AppendLiteral(": ");
-      switch (feature->mValueType) {
-        case nsMediaFeature::eLength:
-          NS_ASSERTION(expr.mValue.IsLengthUnit(), "bad unit");
-          // Use 'width' as a property that takes length values
-          // written in the normal way.
-          expr.mValue.AppendToString(eCSSProperty_width, aString,
-                                     nsCSSValue::eNormalized);
-          break;
-        case nsMediaFeature::eInteger:
-        case nsMediaFeature::eBoolInteger:
-          NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Integer,
-                       "bad unit");
-          // Use 'z-index' as a property that takes integer values
-          // written without anything extra.
-          expr.mValue.AppendToString(eCSSProperty_z_index, aString,
-                                     nsCSSValue::eNormalized);
-          break;
-        case nsMediaFeature::eFloat:
-          {
-            NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Number,
-                         "bad unit");
-            // Use 'line-height' as a property that takes float values
-            // written in the normal way.
-            expr.mValue.AppendToString(eCSSProperty_line_height, aString,
-                                       nsCSSValue::eNormalized);
-          }
-          break;
-        case nsMediaFeature::eIntRatio:
-          {
-            NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Array,
-                         "bad unit");
-            nsCSSValue::Array *array = expr.mValue.GetArrayValue();
-            NS_ASSERTION(array->Count() == 2, "unexpected length");
-            NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer,
-                         "bad unit");
-            NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Integer,
-                         "bad unit");
-            array->Item(0).AppendToString(eCSSProperty_z_index, aString,
-                                          nsCSSValue::eNormalized);
-            aString.Append('/');
-            array->Item(1).AppendToString(eCSSProperty_z_index, aString,
-                                          nsCSSValue::eNormalized);
-          }
-          break;
-        case nsMediaFeature::eResolution:
-          {
-            aString.AppendFloat(expr.mValue.GetFloatValue());
-            if (expr.mValue.GetUnit() == eCSSUnit_Inch) {
-              aString.AppendLiteral("dpi");
-            } else if (expr.mValue.GetUnit() == eCSSUnit_Pixel) {
-              aString.AppendLiteral("dppx");
-            } else {
-              NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Centimeter,
-                           "bad unit");
-              aString.AppendLiteral("dpcm");
-            }
-          }
-          break;
-        case nsMediaFeature::eEnumerated:
-          NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Enumerated,
-                       "bad unit");
-          AppendASCIItoUTF16(
-              nsCSSProps::ValueToKeyword(expr.mValue.GetIntValue(),
-                                         feature->mData.mKeywordTable),
-              aString);
-          break;
-        case nsMediaFeature::eIdent:
-          NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Ident,
-                       "bad unit");
-          aString.Append(expr.mValue.GetStringBufferValue());
-          break;
-      }
-    }
-
-    aString.Append(')');
-  }
-}
-
-nsMediaQuery*
-nsMediaQuery::Clone() const
-{
-  return new nsMediaQuery(*this);
-}
-
-bool
-nsMediaQuery::Matches(nsPresContext* aPresContext,
-                      nsMediaQueryResultCacheKey* aKey) const
-{
-  if (mHadUnknownExpression)
-    return false;
-
-  bool match =
-    mMediaType == aPresContext->Medium() || mMediaType == nsGkAtoms::all;
-  for (uint32_t i = 0, i_end = mExpressions.Length(); match && i < i_end; ++i) {
-    const nsMediaExpression &expr = mExpressions[i];
-    nsCSSValue actual;
-    nsresult rv =
-      (expr.mFeature->mGetter)(aPresContext, expr.mFeature, actual);
-    NS_ENSURE_SUCCESS(rv, false); // any better ideas?
-
-    match = expr.Matches(aPresContext, actual);
-    if (aKey) {
-      aKey->AddExpression(&expr, match);
-    }
-  }
-
-  return match == !mNegated;
-}
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMediaList)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsIDOMMediaList)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMediaList)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMediaList)
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsMediaList)
-
-nsMediaList::nsMediaList()
-  : mStyleSheet(nullptr)
-{
-}
-
-nsMediaList::~nsMediaList()
-{
-}
-
-/* virtual */ JSObject*
-nsMediaList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return MediaListBinding::Wrap(aCx, this, aGivenProto);
-}
-
-void
-nsMediaList::GetText(nsAString& aMediaText)
-{
-  aMediaText.Truncate();
-
-  for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
-    nsMediaQuery* query = mArray[i];
-
-    query->AppendToString(aMediaText);
-
-    if (i + 1 < i_end) {
-      aMediaText.AppendLiteral(", ");
-    }
-  }
-}
-
-// XXXbz this is so ill-defined in the spec, it's not clear quite what
-// it should be doing....
-void
-nsMediaList::SetText(const nsAString& aMediaText)
-{
-  nsCSSParser parser;
-  parser.ParseMediaList(aMediaText, nullptr, 0, this);
-}
-
-bool
-nsMediaList::Matches(nsPresContext* aPresContext,
-                     nsMediaQueryResultCacheKey* aKey)
-{
-  for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
-    if (mArray[i]->Matches(aPresContext, aKey)) {
-      return true;
-    }
-  }
-  return mArray.IsEmpty();
-}
-
-void
-nsMediaList::SetStyleSheet(CSSStyleSheet* aSheet)
-{
-  NS_ASSERTION(aSheet == mStyleSheet || !aSheet || !mStyleSheet,
-               "multiple style sheets competing for one media list");
-  mStyleSheet = aSheet;
-}
-
-already_AddRefed<nsMediaList>
-nsMediaList::Clone()
-{
-  RefPtr<nsMediaList> result = new nsMediaList();
-  result->mArray.AppendElements(mArray.Length());
-  for (uint32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
-    result->mArray[i] = mArray[i]->Clone();
-    MOZ_ASSERT(result->mArray[i]);
-  }
-  return result.forget();
-}
-
-NS_IMETHODIMP
-nsMediaList::GetMediaText(nsAString& aMediaText)
-{
-  GetText(aMediaText);
-  return NS_OK;
-}
-
-// "sheet" should be a CSSStyleSheet and "doc" should be an
-// nsCOMPtr<nsIDocument>
-#define BEGIN_MEDIA_CHANGE(sheet, doc)                         \
-  if (sheet) {                                                 \
-    doc = sheet->GetOwningDocument();                          \
-  }                                                            \
-  mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, true);       \
-  if (sheet) {                                                 \
-    sheet->WillDirty();                                        \
-  }
-
-#define END_MEDIA_CHANGE(sheet, doc)                           \
-  if (sheet) {                                                 \
-    sheet->DidDirty();                                         \
-  }                                                            \
-  /* XXXldb Pass something meaningful? */                      \
-  if (doc) {                                                   \
-    doc->StyleRuleChanged(sheet, nullptr);                     \
-  }
-
-
-NS_IMETHODIMP
-nsMediaList::SetMediaText(const nsAString& aMediaText)
-{
-  nsCOMPtr<nsIDocument> doc;
-
-  BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
-
-  SetText(aMediaText);
-  
-  END_MEDIA_CHANGE(mStyleSheet, doc)
-
-  return NS_OK;
-}
-                               
-NS_IMETHODIMP
-nsMediaList::GetLength(uint32_t* aLength)
-{
-  NS_ENSURE_ARG_POINTER(aLength);
-
-  *aLength = Length();
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsMediaList::Item(uint32_t aIndex, nsAString& aReturn)
-{
-  bool dummy;
-  IndexedGetter(aIndex, dummy, aReturn);
-  return NS_OK;
-}
-
-void
-nsMediaList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aReturn)
-{
-  if (aIndex < Length()) {
-    aFound = true;
-    aReturn.Truncate();
-    mArray[aIndex]->AppendToString(aReturn);
-  } else {
-    aFound = false;
-    SetDOMStringToNull(aReturn);
-  }
-}
-
-NS_IMETHODIMP
-nsMediaList::DeleteMedium(const nsAString& aOldMedium)
-{
-  nsresult rv = NS_OK;
-  nsCOMPtr<nsIDocument> doc;
-
-  BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
-  
-  rv = Delete(aOldMedium);
-  if (NS_FAILED(rv))
-    return rv;
-
-  END_MEDIA_CHANGE(mStyleSheet, doc)
-  
-  return rv;
-}
-
-NS_IMETHODIMP
-nsMediaList::AppendMedium(const nsAString& aNewMedium)
-{
-  nsresult rv = NS_OK;
-  nsCOMPtr<nsIDocument> doc;
-
-  BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
-  
-  rv = Append(aNewMedium);
-  if (NS_FAILED(rv))
-    return rv;
-
-  END_MEDIA_CHANGE(mStyleSheet, doc)
-  
-  return rv;
-}
-
-nsresult
-nsMediaList::Delete(const nsAString& aOldMedium)
-{
-  if (aOldMedium.IsEmpty())
-    return NS_ERROR_DOM_NOT_FOUND_ERR;
-
-  for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
-    nsMediaQuery* query = mArray[i];
-
-    nsAutoString buf;
-    query->AppendToString(buf);
-    if (buf == aOldMedium) {
-      mArray.RemoveElementAt(i);
-      return NS_OK;
-    }
-  }
-
-  return NS_ERROR_DOM_NOT_FOUND_ERR;
-}
-
-nsresult
-nsMediaList::Append(const nsAString& aNewMedium)
-{
-  if (aNewMedium.IsEmpty())
-    return NS_ERROR_DOM_NOT_FOUND_ERR;
-
-  Delete(aNewMedium);
-
-  nsresult rv = NS_OK;
-  nsTArray<nsAutoPtr<nsMediaQuery> > buf;
-  mArray.SwapElements(buf);
-  SetText(aNewMedium);
-  if (mArray.Length() == 1) {
-    nsMediaQuery *query = mArray[0].forget();
-    if (!buf.AppendElement(query)) {
-      delete query;
-      rv = NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  mArray.SwapElements(buf);
-  return rv;
-}
-
 namespace mozilla {
 
 // -------------------------------
 // CSS Style Sheet Inner Data Container
 //
 
 
 CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -182,16 +182,17 @@ UNIFIED_SOURCES += [
     'nsDOMCSSRect.cpp',
     'nsDOMCSSRGBColor.cpp',
     'nsDOMCSSValueList.cpp',
     'nsFontFaceLoader.cpp',
     'nsFontFaceUtils.cpp',
     'nsHTMLCSSStyleSheet.cpp',
     'nsHTMLStyleSheet.cpp',
     'nsMediaFeatures.cpp',
+    'nsMediaList.cpp',
     'nsNthIndexCache.cpp',
     'nsROCSSPrimitiveValue.cpp',
     'nsRuleData.cpp',
     'nsRuleNode.cpp',
     'nsStyleContext.cpp',
     'nsStyleCoord.cpp',
     'nsStyleSet.cpp',
     'nsStyleStruct.cpp',
new file mode 100644
--- /dev/null
+++ b/layout/style/nsMediaList.cpp
@@ -0,0 +1,726 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * representation of media lists used when linking to style sheets or by
+ * @media rules
+ */
+
+#include "nsMediaList.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/CSSStyleSheet.h"
+#include "mozilla/dom/MediaListBinding.h"
+#include "nsCSSParser.h"
+
+template <class Numeric>
+int32_t DoCompare(Numeric a, Numeric b)
+{
+  if (a == b)
+    return 0;
+  if (a < b)
+    return -1;
+  return 1;
+}
+
+bool
+nsMediaExpression::Matches(nsPresContext *aPresContext,
+                           const nsCSSValue& aActualValue) const
+{
+  const nsCSSValue& actual = aActualValue;
+  const nsCSSValue& required = mValue;
+
+  // If we don't have the feature, the match fails.
+  if (actual.GetUnit() == eCSSUnit_Null) {
+    return false;
+  }
+
+  // If the expression had no value to match, the match succeeds,
+  // unless the value is an integer 0 or a zero length.
+  if (required.GetUnit() == eCSSUnit_Null) {
+    if (actual.GetUnit() == eCSSUnit_Integer)
+      return actual.GetIntValue() != 0;
+    if (actual.IsLengthUnit())
+      return actual.GetFloatValue() != 0;
+    return true;
+  }
+
+  NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxAllowed ||
+               mRange == nsMediaExpression::eEqual, "yikes");
+  int32_t cmp; // -1 (actual < required)
+               //  0 (actual == required)
+               //  1 (actual > required)
+  switch (mFeature->mValueType) {
+    case nsMediaFeature::eLength:
+      {
+        NS_ASSERTION(actual.IsLengthUnit(), "bad actual value");
+        NS_ASSERTION(required.IsLengthUnit(), "bad required value");
+        nscoord actualCoord = nsRuleNode::CalcLengthWithInitialFont(
+                                aPresContext, actual);
+        nscoord requiredCoord = nsRuleNode::CalcLengthWithInitialFont(
+                                  aPresContext, required);
+        cmp = DoCompare(actualCoord, requiredCoord);
+      }
+      break;
+    case nsMediaFeature::eInteger:
+    case nsMediaFeature::eBoolInteger:
+      {
+        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Integer,
+                     "bad actual value");
+        NS_ASSERTION(required.GetUnit() == eCSSUnit_Integer,
+                     "bad required value");
+        NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger ||
+                     actual.GetIntValue() == 0 || actual.GetIntValue() == 1,
+                     "bad actual bool integer value");
+        NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger ||
+                     required.GetIntValue() == 0 || required.GetIntValue() == 1,
+                     "bad required bool integer value");
+        cmp = DoCompare(actual.GetIntValue(), required.GetIntValue());
+      }
+      break;
+    case nsMediaFeature::eFloat:
+      {
+        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Number,
+                     "bad actual value");
+        NS_ASSERTION(required.GetUnit() == eCSSUnit_Number,
+                     "bad required value");
+        cmp = DoCompare(actual.GetFloatValue(), required.GetFloatValue());
+      }
+      break;
+    case nsMediaFeature::eIntRatio:
+      {
+        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Array &&
+                     actual.GetArrayValue()->Count() == 2 &&
+                     actual.GetArrayValue()->Item(0).GetUnit() ==
+                       eCSSUnit_Integer &&
+                     actual.GetArrayValue()->Item(1).GetUnit() ==
+                       eCSSUnit_Integer,
+                     "bad actual value");
+        NS_ASSERTION(required.GetUnit() == eCSSUnit_Array &&
+                     required.GetArrayValue()->Count() == 2 &&
+                     required.GetArrayValue()->Item(0).GetUnit() ==
+                       eCSSUnit_Integer &&
+                     required.GetArrayValue()->Item(1).GetUnit() ==
+                       eCSSUnit_Integer,
+                     "bad required value");
+        // Convert to int64_t so we can multiply without worry.  Note
+        // that while the spec requires that both halves of |required|
+        // be positive, the numerator or denominator of |actual| might
+        // be zero (e.g., when testing 'aspect-ratio' on a 0-width or
+        // 0-height iframe).
+        int64_t actualNum = actual.GetArrayValue()->Item(0).GetIntValue(),
+                actualDen = actual.GetArrayValue()->Item(1).GetIntValue(),
+                requiredNum = required.GetArrayValue()->Item(0).GetIntValue(),
+                requiredDen = required.GetArrayValue()->Item(1).GetIntValue();
+        cmp = DoCompare(actualNum * requiredDen, requiredNum * actualDen);
+      }
+      break;
+    case nsMediaFeature::eResolution:
+      {
+        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Inch ||
+                     actual.GetUnit() == eCSSUnit_Pixel ||
+                     actual.GetUnit() == eCSSUnit_Centimeter,
+                     "bad actual value");
+        NS_ASSERTION(required.GetUnit() == eCSSUnit_Inch ||
+                     required.GetUnit() == eCSSUnit_Pixel ||
+                     required.GetUnit() == eCSSUnit_Centimeter,
+                     "bad required value");
+        float actualDPI = actual.GetFloatValue();
+        float overrideDPPX = aPresContext->GetOverrideDPPX();
+
+        if (overrideDPPX > 0) {
+          actualDPI = overrideDPPX * 96.0f;
+        } else if (actual.GetUnit() == eCSSUnit_Centimeter) {
+          actualDPI = actualDPI * 2.54f;
+        } else if (actual.GetUnit() == eCSSUnit_Pixel) {
+          actualDPI = actualDPI * 96.0f;
+        }
+        float requiredDPI = required.GetFloatValue();
+        if (required.GetUnit() == eCSSUnit_Centimeter) {
+          requiredDPI = requiredDPI * 2.54f;
+        } else if (required.GetUnit() == eCSSUnit_Pixel) {
+          requiredDPI = requiredDPI * 96.0f;
+        }
+        cmp = DoCompare(actualDPI, requiredDPI);
+      }
+      break;
+    case nsMediaFeature::eEnumerated:
+      {
+        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Enumerated,
+                     "bad actual value");
+        NS_ASSERTION(required.GetUnit() == eCSSUnit_Enumerated,
+                     "bad required value");
+        NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxNotAllowed,
+                     "bad range"); // we asserted above about mRange
+        // We don't really need DoCompare, but it doesn't hurt (and
+        // maybe the compiler will condense this case with eInteger).
+        cmp = DoCompare(actual.GetIntValue(), required.GetIntValue());
+      }
+      break;
+    case nsMediaFeature::eIdent:
+      {
+        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Ident,
+                     "bad actual value");
+        NS_ASSERTION(required.GetUnit() == eCSSUnit_Ident,
+                     "bad required value");
+        NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxNotAllowed,
+                     "bad range"); 
+        cmp = !(actual == required); // string comparison
+      }
+      break;
+  }
+  switch (mRange) {
+    case nsMediaExpression::eMin:
+      return cmp != -1;
+    case nsMediaExpression::eMax:
+      return cmp != 1;
+    case nsMediaExpression::eEqual:
+      return cmp == 0;
+  }
+  NS_NOTREACHED("unexpected mRange");
+  return false;
+}
+
+void
+nsMediaQueryResultCacheKey::AddExpression(const nsMediaExpression* aExpression,
+                                          bool aExpressionMatches)
+{
+  const nsMediaFeature *feature = aExpression->mFeature;
+  FeatureEntry *entry = nullptr;
+  for (uint32_t i = 0; i < mFeatureCache.Length(); ++i) {
+    if (mFeatureCache[i].mFeature == feature) {
+      entry = &mFeatureCache[i];
+      break;
+    }
+  }
+  if (!entry) {
+    entry = mFeatureCache.AppendElement();
+    if (!entry) {
+      return; /* out of memory */
+    }
+    entry->mFeature = feature;
+  }
+
+  ExpressionEntry eentry = { *aExpression, aExpressionMatches };
+  entry->mExpressions.AppendElement(eentry);
+}
+
+bool
+nsMediaQueryResultCacheKey::Matches(nsPresContext* aPresContext) const
+{
+  if (aPresContext->Medium() != mMedium) {
+    return false;
+  }
+
+  for (uint32_t i = 0; i < mFeatureCache.Length(); ++i) {
+    const FeatureEntry *entry = &mFeatureCache[i];
+    nsCSSValue actual;
+    nsresult rv =
+      (entry->mFeature->mGetter)(aPresContext, entry->mFeature, actual);
+    NS_ENSURE_SUCCESS(rv, false); // any better ideas?
+
+    for (uint32_t j = 0; j < entry->mExpressions.Length(); ++j) {
+      const ExpressionEntry &eentry = entry->mExpressions[j];
+      if (eentry.mExpression.Matches(aPresContext, actual) !=
+          eentry.mExpressionMatches) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool
+nsDocumentRuleResultCacheKey::AddMatchingRule(css::DocumentRule* aRule)
+{
+  MOZ_ASSERT(!mFinalized);
+  return mMatchingRules.AppendElement(aRule);
+}
+
+void
+nsDocumentRuleResultCacheKey::Finalize()
+{
+  mMatchingRules.Sort();
+#ifdef DEBUG
+  mFinalized = true;
+#endif
+}
+
+#ifdef DEBUG
+static bool
+ArrayIsSorted(const nsTArray<css::DocumentRule*>& aRules)
+{
+  for (size_t i = 1; i < aRules.Length(); i++) {
+    if (aRules[i - 1] > aRules[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+#endif
+
+bool
+nsDocumentRuleResultCacheKey::Matches(
+                       nsPresContext* aPresContext,
+                       const nsTArray<css::DocumentRule*>& aRules) const
+{
+  MOZ_ASSERT(mFinalized);
+  MOZ_ASSERT(ArrayIsSorted(mMatchingRules));
+  MOZ_ASSERT(ArrayIsSorted(aRules));
+
+#ifdef DEBUG
+  for (css::DocumentRule* rule : mMatchingRules) {
+    MOZ_ASSERT(aRules.BinaryIndexOf(rule) != aRules.NoIndex,
+               "aRules must contain all rules in mMatchingRules");
+  }
+#endif
+
+  // First check that aPresContext matches all the rules listed in
+  // mMatchingRules.
+  for (css::DocumentRule* rule : mMatchingRules) {
+    if (!rule->UseForPresentation(aPresContext)) {
+      return false;
+    }
+  }
+
+  // Then check that all the rules in aRules that aren't also in
+  // mMatchingRules do not match.
+
+  // pointer to matching rules
+  auto pm     = mMatchingRules.begin();
+  auto pm_end = mMatchingRules.end();
+
+  // pointer to all rules
+  auto pr     = aRules.begin();
+  auto pr_end = aRules.end();
+
+  // mMatchingRules and aRules are both sorted by their pointer values,
+  // so we can iterate over the two lists simultaneously.
+  while (pr < pr_end) {
+    while (pm < pm_end && *pm < *pr) {
+      ++pm;
+    }
+    if (pm >= pm_end || *pm != *pr) {
+      if ((*pr)->UseForPresentation(aPresContext)) {
+        return false;
+      }
+    }
+    ++pr;
+  }
+  return true;
+}
+
+#ifdef DEBUG
+void
+nsDocumentRuleResultCacheKey::List(FILE* aOut, int32_t aIndent) const
+{
+  for (css::DocumentRule* rule : mMatchingRules) {
+    nsCString str;
+
+    for (int32_t i = 0; i < aIndent; i++) {
+      str.AppendLiteral("  ");
+    }
+    str.AppendLiteral("{ ");
+
+    nsString condition;
+    rule->GetConditionText(condition);
+    AppendUTF16toUTF8(condition, str);
+
+    str.AppendLiteral(" }\n");
+    fprintf_stderr(aOut, "%s", str.get());
+  }
+}
+#endif
+
+size_t
+nsDocumentRuleResultCacheKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t n = 0;
+  n += mMatchingRules.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  return n;
+}
+
+void
+nsMediaQuery::AppendToString(nsAString& aString) const
+{
+  if (mHadUnknownExpression) {
+    aString.AppendLiteral("not all");
+    return;
+  }
+
+  NS_ASSERTION(!mNegated || !mHasOnly, "can't have not and only");
+  NS_ASSERTION(!mTypeOmitted || (!mNegated && !mHasOnly),
+               "can't have not or only when type is omitted");
+  if (!mTypeOmitted) {
+    if (mNegated) {
+      aString.AppendLiteral("not ");
+    } else if (mHasOnly) {
+      aString.AppendLiteral("only ");
+    }
+    aString.Append(nsDependentAtomString(mMediaType));
+  }
+
+  for (uint32_t i = 0, i_end = mExpressions.Length(); i < i_end; ++i) {
+    if (i > 0 || !mTypeOmitted)
+      aString.AppendLiteral(" and ");
+    aString.Append('(');
+
+    const nsMediaExpression &expr = mExpressions[i];
+    const nsMediaFeature *feature = expr.mFeature;
+    if (feature->mReqFlags & nsMediaFeature::eHasWebkitPrefix) {
+      aString.AppendLiteral("-webkit-");
+    }
+    if (expr.mRange == nsMediaExpression::eMin) {
+      aString.AppendLiteral("min-");
+    } else if (expr.mRange == nsMediaExpression::eMax) {
+      aString.AppendLiteral("max-");
+    }
+
+    aString.Append(nsDependentAtomString(*feature->mName));
+
+    if (expr.mValue.GetUnit() != eCSSUnit_Null) {
+      aString.AppendLiteral(": ");
+      switch (feature->mValueType) {
+        case nsMediaFeature::eLength:
+          NS_ASSERTION(expr.mValue.IsLengthUnit(), "bad unit");
+          // Use 'width' as a property that takes length values
+          // written in the normal way.
+          expr.mValue.AppendToString(eCSSProperty_width, aString,
+                                     nsCSSValue::eNormalized);
+          break;
+        case nsMediaFeature::eInteger:
+        case nsMediaFeature::eBoolInteger:
+          NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Integer,
+                       "bad unit");
+          // Use 'z-index' as a property that takes integer values
+          // written without anything extra.
+          expr.mValue.AppendToString(eCSSProperty_z_index, aString,
+                                     nsCSSValue::eNormalized);
+          break;
+        case nsMediaFeature::eFloat:
+          {
+            NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Number,
+                         "bad unit");
+            // Use 'line-height' as a property that takes float values
+            // written in the normal way.
+            expr.mValue.AppendToString(eCSSProperty_line_height, aString,
+                                       nsCSSValue::eNormalized);
+          }
+          break;
+        case nsMediaFeature::eIntRatio:
+          {
+            NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Array,
+                         "bad unit");
+            nsCSSValue::Array *array = expr.mValue.GetArrayValue();
+            NS_ASSERTION(array->Count() == 2, "unexpected length");
+            NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer,
+                         "bad unit");
+            NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Integer,
+                         "bad unit");
+            array->Item(0).AppendToString(eCSSProperty_z_index, aString,
+                                          nsCSSValue::eNormalized);
+            aString.Append('/');
+            array->Item(1).AppendToString(eCSSProperty_z_index, aString,
+                                          nsCSSValue::eNormalized);
+          }
+          break;
+        case nsMediaFeature::eResolution:
+          {
+            aString.AppendFloat(expr.mValue.GetFloatValue());
+            if (expr.mValue.GetUnit() == eCSSUnit_Inch) {
+              aString.AppendLiteral("dpi");
+            } else if (expr.mValue.GetUnit() == eCSSUnit_Pixel) {
+              aString.AppendLiteral("dppx");
+            } else {
+              NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Centimeter,
+                           "bad unit");
+              aString.AppendLiteral("dpcm");
+            }
+          }
+          break;
+        case nsMediaFeature::eEnumerated:
+          NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Enumerated,
+                       "bad unit");
+          AppendASCIItoUTF16(
+              nsCSSProps::ValueToKeyword(expr.mValue.GetIntValue(),
+                                         feature->mData.mKeywordTable),
+              aString);
+          break;
+        case nsMediaFeature::eIdent:
+          NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Ident,
+                       "bad unit");
+          aString.Append(expr.mValue.GetStringBufferValue());
+          break;
+      }
+    }
+
+    aString.Append(')');
+  }
+}
+
+nsMediaQuery*
+nsMediaQuery::Clone() const
+{
+  return new nsMediaQuery(*this);
+}
+
+bool
+nsMediaQuery::Matches(nsPresContext* aPresContext,
+                      nsMediaQueryResultCacheKey* aKey) const
+{
+  if (mHadUnknownExpression)
+    return false;
+
+  bool match =
+    mMediaType == aPresContext->Medium() || mMediaType == nsGkAtoms::all;
+  for (uint32_t i = 0, i_end = mExpressions.Length(); match && i < i_end; ++i) {
+    const nsMediaExpression &expr = mExpressions[i];
+    nsCSSValue actual;
+    nsresult rv =
+      (expr.mFeature->mGetter)(aPresContext, expr.mFeature, actual);
+    NS_ENSURE_SUCCESS(rv, false); // any better ideas?
+
+    match = expr.Matches(aPresContext, actual);
+    if (aKey) {
+      aKey->AddExpression(&expr, match);
+    }
+  }
+
+  return match == !mNegated;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMediaList)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMediaList)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMediaList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMediaList)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsMediaList)
+
+nsMediaList::nsMediaList()
+  : mStyleSheet(nullptr)
+{
+}
+
+nsMediaList::~nsMediaList()
+{
+}
+
+/* virtual */ JSObject*
+nsMediaList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return MediaListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+nsMediaList::GetText(nsAString& aMediaText)
+{
+  aMediaText.Truncate();
+
+  for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
+    nsMediaQuery* query = mArray[i];
+
+    query->AppendToString(aMediaText);
+
+    if (i + 1 < i_end) {
+      aMediaText.AppendLiteral(", ");
+    }
+  }
+}
+
+// XXXbz this is so ill-defined in the spec, it's not clear quite what
+// it should be doing....
+void
+nsMediaList::SetText(const nsAString& aMediaText)
+{
+  nsCSSParser parser;
+  parser.ParseMediaList(aMediaText, nullptr, 0, this);
+}
+
+bool
+nsMediaList::Matches(nsPresContext* aPresContext,
+                     nsMediaQueryResultCacheKey* aKey)
+{
+  for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
+    if (mArray[i]->Matches(aPresContext, aKey)) {
+      return true;
+    }
+  }
+  return mArray.IsEmpty();
+}
+
+void
+nsMediaList::SetStyleSheet(CSSStyleSheet* aSheet)
+{
+  NS_ASSERTION(aSheet == mStyleSheet || !aSheet || !mStyleSheet,
+               "multiple style sheets competing for one media list");
+  mStyleSheet = aSheet;
+}
+
+already_AddRefed<nsMediaList>
+nsMediaList::Clone()
+{
+  RefPtr<nsMediaList> result = new nsMediaList();
+  result->mArray.AppendElements(mArray.Length());
+  for (uint32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
+    result->mArray[i] = mArray[i]->Clone();
+    MOZ_ASSERT(result->mArray[i]);
+  }
+  return result.forget();
+}
+
+NS_IMETHODIMP
+nsMediaList::GetMediaText(nsAString& aMediaText)
+{
+  GetText(aMediaText);
+  return NS_OK;
+}
+
+// "sheet" should be a CSSStyleSheet and "doc" should be an
+// nsCOMPtr<nsIDocument>
+#define BEGIN_MEDIA_CHANGE(sheet, doc)                         \
+  if (sheet) {                                                 \
+    doc = sheet->GetOwningDocument();                          \
+  }                                                            \
+  mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, true);       \
+  if (sheet) {                                                 \
+    sheet->WillDirty();                                        \
+  }
+
+#define END_MEDIA_CHANGE(sheet, doc)                           \
+  if (sheet) {                                                 \
+    sheet->DidDirty();                                         \
+  }                                                            \
+  /* XXXldb Pass something meaningful? */                      \
+  if (doc) {                                                   \
+    doc->StyleRuleChanged(sheet, nullptr);                     \
+  }
+
+
+NS_IMETHODIMP
+nsMediaList::SetMediaText(const nsAString& aMediaText)
+{
+  nsCOMPtr<nsIDocument> doc;
+
+  BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
+
+  SetText(aMediaText);
+  
+  END_MEDIA_CHANGE(mStyleSheet, doc)
+
+  return NS_OK;
+}
+                               
+NS_IMETHODIMP
+nsMediaList::GetLength(uint32_t* aLength)
+{
+  NS_ENSURE_ARG_POINTER(aLength);
+
+  *aLength = Length();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMediaList::Item(uint32_t aIndex, nsAString& aReturn)
+{
+  bool dummy;
+  IndexedGetter(aIndex, dummy, aReturn);
+  return NS_OK;
+}
+
+void
+nsMediaList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aReturn)
+{
+  if (aIndex < Length()) {
+    aFound = true;
+    aReturn.Truncate();
+    mArray[aIndex]->AppendToString(aReturn);
+  } else {
+    aFound = false;
+    SetDOMStringToNull(aReturn);
+  }
+}
+
+NS_IMETHODIMP
+nsMediaList::DeleteMedium(const nsAString& aOldMedium)
+{
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIDocument> doc;
+
+  BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
+  
+  rv = Delete(aOldMedium);
+  if (NS_FAILED(rv))
+    return rv;
+
+  END_MEDIA_CHANGE(mStyleSheet, doc)
+  
+  return rv;
+}
+
+NS_IMETHODIMP
+nsMediaList::AppendMedium(const nsAString& aNewMedium)
+{
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIDocument> doc;
+
+  BEGIN_MEDIA_CHANGE(mStyleSheet, doc)
+  
+  rv = Append(aNewMedium);
+  if (NS_FAILED(rv))
+    return rv;
+
+  END_MEDIA_CHANGE(mStyleSheet, doc)
+  
+  return rv;
+}
+
+nsresult
+nsMediaList::Delete(const nsAString& aOldMedium)
+{
+  if (aOldMedium.IsEmpty())
+    return NS_ERROR_DOM_NOT_FOUND_ERR;
+
+  for (int32_t i = 0, i_end = mArray.Length(); i < i_end; ++i) {
+    nsMediaQuery* query = mArray[i];
+
+    nsAutoString buf;
+    query->AppendToString(buf);
+    if (buf == aOldMedium) {
+      mArray.RemoveElementAt(i);
+      return NS_OK;
+    }
+  }
+
+  return NS_ERROR_DOM_NOT_FOUND_ERR;
+}
+
+nsresult
+nsMediaList::Append(const nsAString& aNewMedium)
+{
+  if (aNewMedium.IsEmpty())
+    return NS_ERROR_DOM_NOT_FOUND_ERR;
+
+  Delete(aNewMedium);
+
+  nsresult rv = NS_OK;
+  nsTArray<nsAutoPtr<nsMediaQuery> > buf;
+  mArray.SwapElements(buf);
+  SetText(aNewMedium);
+  if (mArray.Length() == 1) {
+    nsMediaQuery *query = mArray[0].forget();
+    if (!buf.AppendElement(query)) {
+      delete query;
+      rv = NS_ERROR_OUT_OF_MEMORY;
+    }
+  }
+
+  mArray.SwapElements(buf);
+  return rv;
+}