From 62b65d284559c134b89c16aa4e8aed7f54064877 Mon Sep 17 00:00:00 2001 From: "L. David Baron" Date: Sat, 26 Jul 2008 09:14:48 -0700 Subject: [PATCH] Implement Media Queries. (Bug 156716) r+sr=bzbarsky --- content/base/src/nsGkAtomList.h | 10 + .../en-US/chrome/layout/css.properties | 5 + layout/base/nsStyleConsts.h | 12 + layout/style/Makefile.in | 1 + layout/style/nsCSSDeclaration.h | 2 + layout/style/nsCSSKeywordList.h | 2 + layout/style/nsCSSParser.cpp | 407 ++++++++++++++---- layout/style/nsCSSStyleSheet.cpp | 362 ++++++++++++++-- layout/style/nsIMediaList.h | 97 ++++- layout/style/nsMediaFeatures.cpp | 328 ++++++++++++++ layout/style/nsMediaFeatures.h | 91 ++++ layout/style/nsRuleNode.cpp | 10 + layout/style/nsRuleNode.h | 4 + 13 files changed, 1190 insertions(+), 141 deletions(-) create mode 100644 layout/style/nsMediaFeatures.cpp create mode 100644 layout/style/nsMediaFeatures.h diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index cb96378fccf7..4b211d9e216d 100755 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -104,6 +104,7 @@ GK_ATOM(applyTemplates, "apply-templates") GK_ATOM(archive, "archive") GK_ATOM(area, "area") GK_ATOM(ascending, "ascending") +GK_ATOM(aspectRatio, "aspect-ratio") GK_ATOM(assign, "assign") GK_ATOM(attribute, "attribute") GK_ATOM(attributeSet, "attribute-set") @@ -202,6 +203,7 @@ GK_ATOM(colGroupList, "ColGroup-list") GK_ATOM(collapse, "collapse") GK_ATOM(collapsed, "collapsed") GK_ATOM(color, "color") +GK_ATOM(colorIndex, "color-index") GK_ATOM(cols, "cols") GK_ATOM(colspan, "colspan") GK_ATOM(column, "column") @@ -272,6 +274,9 @@ GK_ATOM(descendantOrSelf, "descendant-or-self") GK_ATOM(descending, "descending") GK_ATOM(description, "description") GK_ATOM(destructor, "destructor") +GK_ATOM(deviceAspectRatio, "device-aspect-ratio") +GK_ATOM(deviceHeight, "device-height") +GK_ATOM(deviceWidth, "device-width") GK_ATOM(dfn, "dfn") GK_ATOM(dialog, "dialog") GK_ATOM(difference, "difference") @@ -523,6 +528,7 @@ GK_ATOM(minwidth, "minwidth") GK_ATOM(mod, "mod") GK_ATOM(mode, "mode") GK_ATOM(modifiers, "modifiers") +GK_ATOM(monochrome, "monochrome") GK_ATOM(mousedown, "mousedown") GK_ATOM(mousemove, "mousemove") GK_ATOM(mouseout, "mouseout") @@ -616,6 +622,7 @@ GK_ATOM(onkeypress, "onkeypress") GK_ATOM(onkeyup, "onkeyup") GK_ATOM(onLoad, "onLoad") GK_ATOM(onload, "onload") +GK_ATOM(only, "only") // this one is not an event GK_ATOM(onmousedown, "onmousedown") GK_ATOM(onmousemove, "onmousemove") GK_ATOM(onmouseout, "onmouseout") @@ -650,6 +657,7 @@ GK_ATOM(_or, "or") GK_ATOM(order, "order") GK_ATOM(ordinal, "ordinal") GK_ATOM(orient, "orient") +GK_ATOM(orientation, "orientation") GK_ATOM(otherwise, "otherwise") GK_ATOM(output, "output") GK_ATOM(overflow, "overflow") @@ -739,6 +747,7 @@ GK_ATOM(reset, "reset") GK_ATOM(resizeafter, "resizeafter") GK_ATOM(resizebefore, "resizebefore") GK_ATOM(resizer, "resizer") +GK_ATOM(resolution, "resolution") GK_ATOM(resource, "resource") GK_ATOM(resources, "resources") GK_ATOM(result, "result") @@ -758,6 +767,7 @@ GK_ATOM(rule, "rule") GK_ATOM(rules, "rules") GK_ATOM(s, "s") GK_ATOM(samp, "samp") +GK_ATOM(scan, "scan") GK_ATOM(scheme, "scheme") GK_ATOM(scope, "scope") GK_ATOM(screen, "screen") diff --git a/dom/locales/en-US/chrome/layout/css.properties b/dom/locales/en-US/chrome/layout/css.properties index 8b5932c60592..77839c06d2c3 100644 --- a/dom/locales/en-US/chrome/layout/css.properties +++ b/dom/locales/en-US/chrome/layout/css.properties @@ -127,3 +127,8 @@ PEBadDeclOrRuleEnd2=Expected ';' or '}' to terminate declaration but found '%1$S PEInaccessibleProperty2=Cannot specify value for internal property. PECommentEOF=end of comment SEUnterminatedString=Found unclosed string '%1$S'. +PEMQExpectedExpressionStart=Expected '(' to start media query expression but found '%1$S'. +PEMQExpressionEOF=contents of media query expression +PEMQExpectedFeatureName=Expected media feature name but found '%1$S'. +PEMQExpectedFeatureNameEnd=Expected ':' or ')' after media feature name but found '%1$S'. +PEMQExpectedFeatureValue=Found invalid value for media feature. diff --git a/layout/base/nsStyleConsts.h b/layout/base/nsStyleConsts.h index de7ae3320579..1de267ac79dc 100644 --- a/layout/base/nsStyleConsts.h +++ b/layout/base/nsStyleConsts.h @@ -746,4 +746,16 @@ #endif // MOZ_SVG +/***************************************************************************** + * Constants for media features. * + *****************************************************************************/ + +// orientation +#define NS_STYLE_ORIENTATION_PORTRAIT 0 +#define NS_STYLE_ORIENTATION_LANDSCAPE 1 + +// scan +#define NS_STYLE_SCAN_PROGRESSIVE 0 +#define NS_STYLE_SCAN_INTERLACE 1 + #endif /* nsStyleConsts_h___ */ diff --git a/layout/style/Makefile.in b/layout/style/Makefile.in index 128062fa3726..2928df72678c 100644 --- a/layout/style/Makefile.in +++ b/layout/style/Makefile.in @@ -151,6 +151,7 @@ CPPSRCS = \ nsHTMLStyleSheet.cpp \ nsInspectorCSSUtils.cpp \ nsLayoutStylesheetCache.cpp \ + nsMediaFeatures.cpp \ nsROCSSPrimitiveValue.cpp \ nsRuleNode.cpp \ nsStyleContext.cpp \ diff --git a/layout/style/nsCSSDeclaration.h b/layout/style/nsCSSDeclaration.h index f977611a8355..9bd1ea1ec3eb 100644 --- a/layout/style/nsCSSDeclaration.h +++ b/layout/style/nsCSSDeclaration.h @@ -158,11 +158,13 @@ private: static void AppendImportanceToString(PRBool aIsImportant, nsAString& aString); // return whether there was a value in |aValue| (i.e., it had a non-null unit) PRBool AppendValueToString(nsCSSProperty aProperty, nsAString& aResult) const; +public: // return whether there was a value in |aValue| (i.e., it had a non-null unit) static PRBool AppendCSSValueToString(nsCSSProperty aProperty, const nsCSSValue& aValue, nsAString& aResult); +private: // May be called only for properties whose type is eCSSType_Value. nsresult GetValueOrImportantValue(nsCSSProperty aProperty, nsCSSValue& aValue) const; diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index 03a3b70d1513..ac312cdb22b4 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -293,6 +293,7 @@ CSS_KEY(hz, hz) CSS_KEY(icon, icon) CSS_KEY(ignore, ignore) CSS_KEY(in, in) +CSS_KEY(interlace, interlace) CSS_KEY(inactive, inactive) CSS_KEY(inactiveborder, inactiveborder) CSS_KEY(inactivecaption, inactivecaption) @@ -374,6 +375,7 @@ CSS_KEY(portrait, portrait) CSS_KEY(pre, pre) CSS_KEY(pre-wrap, pre_wrap) CSS_KEY(progress, progress) +CSS_KEY(progressive, progressive) CSS_KEY(pt, pt) CSS_KEY(px, px) CSS_KEY(rad, rad) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 332ec249029c..4b7dc8d66529 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -84,6 +84,62 @@ #include "nsContentUtils.h" #include "nsDOMError.h" +// Flags for ParseVariant method +#define VARIANT_KEYWORD 0x000001 // K +#define VARIANT_LENGTH 0x000002 // L +#define VARIANT_PERCENT 0x000004 // P +#define VARIANT_COLOR 0x000008 // C eCSSUnit_Color, eCSSUnit_String (e.g. "red") +#define VARIANT_URL 0x000010 // U +#define VARIANT_NUMBER 0x000020 // N +#define VARIANT_INTEGER 0x000040 // I +#define VARIANT_ANGLE 0x000080 // G +#define VARIANT_FREQUENCY 0x000100 // F +#define VARIANT_TIME 0x000200 // T +#define VARIANT_STRING 0x000400 // S +#define VARIANT_COUNTER 0x000800 // +#define VARIANT_ATTR 0x001000 // +#define VARIANT_IDENTIFIER 0x002000 // D +#define VARIANT_AUTO 0x010000 // A +#define VARIANT_INHERIT 0x020000 // H eCSSUnit_Initial, eCSSUnit_Inherit +#define VARIANT_NONE 0x040000 // O +#define VARIANT_NORMAL 0x080000 // M +#define VARIANT_SYSFONT 0x100000 // eCSSUnit_System_Font + +// Common combinations of variants +#define VARIANT_AL (VARIANT_AUTO | VARIANT_LENGTH) +#define VARIANT_LP (VARIANT_LENGTH | VARIANT_PERCENT) +#define VARIANT_AH (VARIANT_AUTO | VARIANT_INHERIT) +#define VARIANT_AHLP (VARIANT_AH | VARIANT_LP) +#define VARIANT_AHI (VARIANT_AH | VARIANT_INTEGER) +#define VARIANT_AHK (VARIANT_AH | VARIANT_KEYWORD) +#define VARIANT_AHKLP (VARIANT_AHLP | VARIANT_KEYWORD) +#define VARIANT_AUK (VARIANT_AUTO | VARIANT_URL | VARIANT_KEYWORD) +#define VARIANT_AHUK (VARIANT_AH | VARIANT_URL | VARIANT_KEYWORD) +#define VARIANT_AHL (VARIANT_AH | VARIANT_LENGTH) +#define VARIANT_AHKL (VARIANT_AHK | VARIANT_LENGTH) +#define VARIANT_HK (VARIANT_INHERIT | VARIANT_KEYWORD) +#define VARIANT_HKF (VARIANT_HK | VARIANT_FREQUENCY) +#define VARIANT_HKL (VARIANT_HK | VARIANT_LENGTH) +#define VARIANT_HKLP (VARIANT_HK | VARIANT_LP) +#define VARIANT_HKLPO (VARIANT_HKLP | VARIANT_NONE) +#define VARIANT_HL (VARIANT_INHERIT | VARIANT_LENGTH) +#define VARIANT_HI (VARIANT_INHERIT | VARIANT_INTEGER) +#define VARIANT_HLP (VARIANT_HL | VARIANT_PERCENT) +#define VARIANT_HLPN (VARIANT_HLP | VARIANT_NUMBER) +#define VARIANT_HLPO (VARIANT_HLP | VARIANT_NONE) +#define VARIANT_HTP (VARIANT_INHERIT | VARIANT_TIME | VARIANT_PERCENT) +#define VARIANT_HMK (VARIANT_HK | VARIANT_NORMAL) +#define VARIANT_HMKI (VARIANT_HMK | VARIANT_INTEGER) +#define VARIANT_HC (VARIANT_INHERIT | VARIANT_COLOR) +#define VARIANT_HCK (VARIANT_HK | VARIANT_COLOR) +#define VARIANT_HUO (VARIANT_INHERIT | VARIANT_URL | VARIANT_NONE) +#define VARIANT_AHUO (VARIANT_AUTO | VARIANT_HUO) +#define VARIANT_HPN (VARIANT_INHERIT | VARIANT_PERCENT | VARIANT_NUMBER) +#define VARIANT_HOK (VARIANT_HK | VARIANT_NONE) +#define VARIANT_HN (VARIANT_INHERIT | VARIANT_NUMBER) +#define VARIANT_HON (VARIANT_HN | VARIANT_NONE) +#define VARIANT_HOS (VARIANT_INHERIT | VARIANT_NONE | VARIANT_STRING) + //---------------------------------------------------------------------- // Your basic top-down recursive descent style parser @@ -227,8 +283,10 @@ protected: PRBool ParseCharsetRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aProcessData); PRBool ParseImportRule(nsresult& aErrorCode, RuleAppendFunc aAppendFunc, void* aProcessData); PRBool GatherURL(nsresult& aErrorCode, nsString& aURL); + // Callers must clear or throw out aMedia if GatherMedia returns false. PRBool GatherMedia(nsresult& aErrorCode, nsMediaList* aMedia, PRUnichar aStopSymbol); + PRBool ParseMediaQueryExpression(nsresult& aErrorCode, nsMediaQuery* aQuery); PRBool ProcessImport(nsresult& aErrorCode, const nsString& aURLSpec, nsMediaList* aMedia, @@ -1051,6 +1109,8 @@ CSSParserImpl::ParseMediaList(const nsSubstring& aBuffer, nsMediaList* aMediaList, PRBool aHTMLMode) { + // XXX Are there cases where the caller wants to keep what it already + // has in case of parser error? aMediaList->Clear(); AssertInitialState(); @@ -1094,8 +1154,12 @@ CSSParserImpl::DoParseMediaList(const nsSubstring& aBuffer, return rv; } - if (!GatherMedia(rv, aMediaList, PRUnichar(0)) && !mHTMLMediaMode) { - OUTPUT_ERROR(); + if (!GatherMedia(rv, aMediaList, PRUnichar(0))) { + aMediaList->Clear(); + aMediaList->SetNonEmpty(); // don't match anything + if (!mHTMLMediaMode) { + OUTPUT_ERROR(); + } } CLEAR_ERROR(); ReleaseScanner(); @@ -1417,50 +1481,261 @@ PRBool CSSParserImpl::GatherURL(nsresult& aErrorCode, nsString& aURL) return PR_FALSE; } +// Callers must clear or throw out aMedia if GatherMedia returns false. PRBool CSSParserImpl::GatherMedia(nsresult& aErrorCode, nsMediaList* aMedia, PRUnichar aStopSymbol) { - for (;;) { - if (!GetToken(aErrorCode, PR_TRUE)) { - REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); - break; - } - if (eCSSToken_Ident != mToken.mType) { - REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent); - UngetToken(); - break; - } - ToLowerCase(mToken.mIdent); // case insensitive from CSS - must be lower cased - nsCOMPtr medium = do_GetAtom(mToken.mIdent); - aMedia->AppendAtom(medium); - - if (!GetToken(aErrorCode, PR_TRUE)) { - // expected termination by EOF - if (aStopSymbol == PRUnichar(0)) - return PR_TRUE; - - // unexpected termination by EOF; if we were looking for a - // semicolon, return true anyway, for the same reason this is - // done by ExpectSymbol(). - REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); - if (aStopSymbol == PRUnichar(';')) - return PR_TRUE; - break; - } - - if (eCSSToken_Symbol == mToken.mType && - mToken.mSymbol == aStopSymbol) { - UngetToken(); + // "If the comma-separated list is the empty list it is assumed to + // specify the media query 'all'." (css3-mediaqueries, section + // "Media Queries") + if (!GetToken(aErrorCode, PR_TRUE)) { + // expected termination by EOF + if (aStopSymbol == PRUnichar(0)) return PR_TRUE; - } else if (eCSSToken_Symbol != mToken.mType || - mToken.mSymbol != ',') { - REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma); - UngetToken(); + + // unexpected termination by EOF; if we were looking for a + // semicolon, return true anyway, for the same reason this is + // done by ExpectSymbol(). + REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); + return aStopSymbol == PRUnichar(';'); + } + + if (eCSSToken_Symbol == mToken.mType && + mToken.mSymbol == aStopSymbol) { + UngetToken(); + return PR_TRUE; + } + UngetToken(); + aMedia->SetNonEmpty(); + + for (;;) { + // We want to still have |query| after we transfer ownership from + // |queryHolder| to |aMedia|. + nsMediaQuery *query; + { + nsAutoPtr queryHolder(new nsMediaQuery); + if (!queryHolder) { + aErrorCode = NS_ERROR_OUT_OF_MEMORY; + return PR_FALSE; + } + query = queryHolder; + + // In terms of error handling, it doesn't really matter when we + // append this, since aMedia's contents get dropped entirely + // whenever there is an error. + nsresult rv = aMedia->AppendQuery(queryHolder); + if (NS_FAILED(rv)) { + aErrorCode = rv; + return PR_FALSE; + } + NS_ASSERTION(!queryHolder, "ownership should have been transferred"); + } + + if (ExpectSymbol(aErrorCode, '(', PR_TRUE)) { + // we got an expression without a media type + UngetToken(); // so ParseMediaQueryExpression can handle it + query->SetType(nsGkAtoms::all); + query->SetTypeOmitted(); + // Just parse the first expression here. + if (!ParseMediaQueryExpression(aErrorCode, query)) { + OUTPUT_ERROR(); + query->SetHadUnknownExpression(); + } + } else { + nsCOMPtr mediaType; + PRBool gotNotOrOnly = PR_FALSE; + for (;;) { + if (!GetToken(aErrorCode, PR_TRUE)) { + REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); + return PR_FALSE; + } + if (eCSSToken_Ident != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent); + UngetToken(); + return PR_FALSE; + } + // case insensitive from CSS - must be lower cased + ToLowerCase(mToken.mIdent); + mediaType = do_GetAtom(mToken.mIdent); + if (gotNotOrOnly || + (mediaType != nsGkAtoms::_not && mediaType != nsGkAtoms::only)) + break; + gotNotOrOnly = PR_TRUE; + if (mediaType == nsGkAtoms::_not) + query->SetNegated(); + else + query->SetHasOnly(); + } + query->SetType(mediaType); + } + + for (;;) { + if (!GetToken(aErrorCode, PR_TRUE)) { + // expected termination by EOF + if (aStopSymbol == PRUnichar(0)) + return PR_TRUE; + + // unexpected termination by EOF; if we were looking for a + // semicolon, return true anyway, for the same reason this is + // done by ExpectSymbol(). + REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); + return aStopSymbol == PRUnichar(';'); + } + + if (eCSSToken_Symbol == mToken.mType && + mToken.mSymbol == aStopSymbol) { + UngetToken(); + return PR_TRUE; + } + if (eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') { + // Done with the expressions for this query + break; + } + if (eCSSToken_Ident != mToken.mType || + !mToken.mIdent.LowerCaseEqualsLiteral("and")) { + REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma); + UngetToken(); + return PR_FALSE; + } + if (!ParseMediaQueryExpression(aErrorCode, query)) { + OUTPUT_ERROR(); + query->SetHadUnknownExpression(); + } + } + } + NS_NOTREACHED("unreachable code"); + return PR_FALSE; // keep the compiler happy +} + +PRBool CSSParserImpl::ParseMediaQueryExpression(nsresult& aErrorCode, nsMediaQuery* aQuery) +{ + if (!ExpectSymbol(aErrorCode, '(', PR_TRUE)) { + REPORT_UNEXPECTED_TOKEN(PEMQExpectedExpressionStart); + return PR_FALSE; + } + if (! GetToken(aErrorCode, PR_TRUE)) { + REPORT_UNEXPECTED_EOF(PEMQExpressionEOF); + return PR_FALSE; + } + if (eCSSToken_Ident != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName); + SkipUntil(aErrorCode, ')'); + return PR_FALSE; + } + + nsMediaExpression *expr = aQuery->NewExpression(); + if (!expr) { + aErrorCode = NS_ERROR_OUT_OF_MEMORY; + SkipUntil(aErrorCode, ')'); + return PR_FALSE; + } + + // case insensitive from CSS - must be lower cased + ToLowerCase(mToken.mIdent); + const PRUnichar *featureString; + if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("min-"))) { + expr->mRange = nsMediaExpression::eMin; + featureString = mToken.mIdent.get() + 4; + } else if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("max-"))) { + expr->mRange = nsMediaExpression::eMax; + featureString = mToken.mIdent.get() + 4; + } else { + expr->mRange = nsMediaExpression::eEqual; + featureString = mToken.mIdent.get(); + } + + nsCOMPtr mediaFeatureAtom = do_GetAtom(featureString); + const nsMediaFeature *feature = nsMediaFeatures::features; + for (; feature->mName; ++feature) { + if (*(feature->mName) == mediaFeatureAtom) { break; } } - return PR_FALSE; + if (!feature->mName || + (expr->mRange != nsMediaExpression::eEqual && + feature->mRangeType != nsMediaFeature::eMinMaxAllowed)) { + REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName); + SkipUntil(aErrorCode, ')'); + return PR_FALSE; + } + expr->mFeature = feature; + + if (! GetToken(aErrorCode, PR_TRUE)) { + REPORT_UNEXPECTED_EOF(PEMQExpressionEOF); + return PR_FALSE; + } + if (eCSSToken_Symbol != mToken.mType || + (mToken.mSymbol != PRUnichar(':') && mToken.mSymbol != PRUnichar(')'))) { + REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureNameEnd); + SkipUntil(aErrorCode, ')'); + return PR_FALSE; + } + + if (mToken.mSymbol == PRUnichar(')')) { + // All query expressions can be given without a value. + expr->mValue.Reset(); + return PR_TRUE; + } + + PRBool rv; + switch (feature->mValueType) { + case nsMediaFeature::eLength: + rv = ParsePositiveVariant(aErrorCode, expr->mValue, + VARIANT_LENGTH, nsnull); + break; + case nsMediaFeature::eInteger: + rv = ParsePositiveVariant(aErrorCode, expr->mValue, + VARIANT_INTEGER, nsnull); + break; + case nsMediaFeature::eIntRatio: + { + // Two integers separated by '/', with optional whitespace on + // either side of the '/'. + nsRefPtr a = nsCSSValue::Array::Create(2); + if (!a) { + aErrorCode = NS_ERROR_OUT_OF_MEMORY; + SkipUntil(aErrorCode, ')'); + return PR_FALSE; + } + expr->mValue.SetArrayValue(a, eCSSUnit_Array); + // We don't bother with ParsePositiveVariant since we have to + // check for != 0 as well; no need to worry about the UngetToken + // since we're throwing out up to the next ')' anyway. + rv = ParseVariant(aErrorCode, a->Item(0), VARIANT_INTEGER, nsnull) && + a->Item(0).GetIntValue() > 0 && + ExpectSymbol(aErrorCode, '/', PR_TRUE) && + ParseVariant(aErrorCode, a->Item(1), VARIANT_INTEGER, nsnull) && + a->Item(1).GetIntValue() > 0; + } + break; + case nsMediaFeature::eResolution: + rv = GetToken(aErrorCode, PR_TRUE) && mToken.IsDimension() && + mToken.mIntegerValid && mToken.mNumber > 0.0f; + if (rv) { + // No worries about whether unitless zero is allowed, since the + // value must be positive (and we checked that above). + NS_ASSERTION(!mToken.mIdent.IsEmpty(), "IsDimension lied"); + if (mToken.mIdent.LowerCaseEqualsLiteral("dpi")) { + expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Inch); + } else if (mToken.mIdent.LowerCaseEqualsLiteral("dpcm")) { + expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Centimeter); + } else { + rv = PR_FALSE; + } + } + break; + case nsMediaFeature::eEnumerated: + rv = ParseVariant(aErrorCode, expr->mValue, VARIANT_KEYWORD, + feature->mKeywordTable); + break; + } + if (!rv) { + REPORT_UNEXPECTED(PEMQExpectedFeatureValue); + return PR_FALSE; + } + + return ExpectSymbol(aErrorCode, ')', PR_TRUE); } // Parse a CSS2 import rule: "@import STRING | URL [medium [, medium]]" @@ -3696,62 +3971,6 @@ CSSParserImpl::DoTransferTempData(nsCSSDeclaration* aDeclaration, } } -// Flags for ParseVariant method -#define VARIANT_KEYWORD 0x000001 // K -#define VARIANT_LENGTH 0x000002 // L -#define VARIANT_PERCENT 0x000004 // P -#define VARIANT_COLOR 0x000008 // C eCSSUnit_Color, eCSSUnit_String (e.g. "red") -#define VARIANT_URL 0x000010 // U -#define VARIANT_NUMBER 0x000020 // N -#define VARIANT_INTEGER 0x000040 // I -#define VARIANT_ANGLE 0x000080 // G -#define VARIANT_FREQUENCY 0x000100 // F -#define VARIANT_TIME 0x000200 // T -#define VARIANT_STRING 0x000400 // S -#define VARIANT_COUNTER 0x000800 // -#define VARIANT_ATTR 0x001000 // -#define VARIANT_IDENTIFIER 0x002000 // D -#define VARIANT_AUTO 0x010000 // A -#define VARIANT_INHERIT 0x020000 // H eCSSUnit_Initial, eCSSUnit_Inherit -#define VARIANT_NONE 0x040000 // O -#define VARIANT_NORMAL 0x080000 // M -#define VARIANT_SYSFONT 0x100000 // eCSSUnit_System_Font - -// Common combinations of variants -#define VARIANT_AL (VARIANT_AUTO | VARIANT_LENGTH) -#define VARIANT_LP (VARIANT_LENGTH | VARIANT_PERCENT) -#define VARIANT_AH (VARIANT_AUTO | VARIANT_INHERIT) -#define VARIANT_AHLP (VARIANT_AH | VARIANT_LP) -#define VARIANT_AHI (VARIANT_AH | VARIANT_INTEGER) -#define VARIANT_AHK (VARIANT_AH | VARIANT_KEYWORD) -#define VARIANT_AHKLP (VARIANT_AHLP | VARIANT_KEYWORD) -#define VARIANT_AUK (VARIANT_AUTO | VARIANT_URL | VARIANT_KEYWORD) -#define VARIANT_AHUK (VARIANT_AH | VARIANT_URL | VARIANT_KEYWORD) -#define VARIANT_AHL (VARIANT_AH | VARIANT_LENGTH) -#define VARIANT_AHKL (VARIANT_AHK | VARIANT_LENGTH) -#define VARIANT_HK (VARIANT_INHERIT | VARIANT_KEYWORD) -#define VARIANT_HKF (VARIANT_HK | VARIANT_FREQUENCY) -#define VARIANT_HKL (VARIANT_HK | VARIANT_LENGTH) -#define VARIANT_HKLP (VARIANT_HK | VARIANT_LP) -#define VARIANT_HKLPO (VARIANT_HKLP | VARIANT_NONE) -#define VARIANT_HL (VARIANT_INHERIT | VARIANT_LENGTH) -#define VARIANT_HI (VARIANT_INHERIT | VARIANT_INTEGER) -#define VARIANT_HLP (VARIANT_HL | VARIANT_PERCENT) -#define VARIANT_HLPN (VARIANT_HLP | VARIANT_NUMBER) -#define VARIANT_HLPO (VARIANT_HLP | VARIANT_NONE) -#define VARIANT_HTP (VARIANT_INHERIT | VARIANT_TIME | VARIANT_PERCENT) -#define VARIANT_HMK (VARIANT_HK | VARIANT_NORMAL) -#define VARIANT_HMKI (VARIANT_HMK | VARIANT_INTEGER) -#define VARIANT_HC (VARIANT_INHERIT | VARIANT_COLOR) -#define VARIANT_HCK (VARIANT_HK | VARIANT_COLOR) -#define VARIANT_HUO (VARIANT_INHERIT | VARIANT_URL | VARIANT_NONE) -#define VARIANT_AHUO (VARIANT_AUTO | VARIANT_HUO) -#define VARIANT_HPN (VARIANT_INHERIT | VARIANT_PERCENT | VARIANT_NUMBER) -#define VARIANT_HOK (VARIANT_HK | VARIANT_NONE) -#define VARIANT_HN (VARIANT_INHERIT | VARIANT_NUMBER) -#define VARIANT_HON (VARIANT_HN | VARIANT_NONE) -#define VARIANT_HOS (VARIANT_INHERIT | VARIANT_NONE | VARIANT_STRING) - static const nsCSSProperty kBorderTopIDs[] = { eCSSProperty_border_top_width, eCSSProperty_border_top_style, diff --git a/layout/style/nsCSSStyleSheet.cpp b/layout/style/nsCSSStyleSheet.cpp index 162b142c5ab2..f718211e646b 100644 --- a/layout/style/nsCSSStyleSheet.cpp +++ b/layout/style/nsCSSStyleSheet.cpp @@ -75,6 +75,8 @@ #include "nsIJSContextStack.h" #include "nsIScriptSecurityManager.h" #include "mozAutoDocUpdate.h" +#include "nsCSSDeclaration.h" +#include "nsRuleNode.h" // ------------------------------- // Style Rule List for the DOM @@ -163,6 +165,264 @@ CSSRuleListImpl::Item(PRUint32 aIndex, nsIDOMCSSRule** aReturn) return result; } +template +PRInt32 DoCompare(Numeric a, Numeric b) +{ + if (a == b) + return 0; + if (a < b) + return -1; + return 1; +} + +PRBool +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 PR_FALSE; + } + + // If the expression had no value to match, the match succeeds, + // unless the value is an integer 0. + if (required.GetUnit() == eCSSUnit_Null) { + return actual.GetUnit() != eCSSUnit_Integer || + actual.GetIntValue() != 0; + } + + NS_ASSERTION(mFeature->mRangeType == nsMediaFeature::eMinMaxAllowed || + mRange == nsMediaExpression::eEqual, "yikes"); + PRInt32 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: + { + NS_ASSERTION(actual.GetUnit() == eCSSUnit_Integer, + "bad actual value"); + NS_ASSERTION(required.GetUnit() == eCSSUnit_Integer, + "bad required value"); + cmp = DoCompare(actual.GetIntValue(), required.GetIntValue()); + } + 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 PRInt64 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). + PRInt64 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_Centimeter, + "bad actual value"); + NS_ASSERTION(required.GetUnit() == eCSSUnit_Inch || + required.GetUnit() == eCSSUnit_Centimeter, + "bad required value"); + float actualDPI = actual.GetFloatValue(); + if (actual.GetUnit() == eCSSUnit_Centimeter) + actualDPI = actualDPI * 2.54f; + float requiredDPI = required.GetFloatValue(); + if (required.GetUnit() == eCSSUnit_Centimeter) + requiredDPI = requiredDPI * 2.54f; + 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; + } + 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 PR_FALSE; +} + +void +nsMediaQuery::AppendToString(nsAString& aString) const +{ + nsAutoString buffer; + + 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 "); + } + mMediaType->ToString(buffer); + aString.Append(buffer); + buffer.Truncate(); + } + + for (PRUint32 i = 0, i_end = mExpressions.Length(); i < i_end; ++i) { + if (i > 0 || !mTypeOmitted) + aString.AppendLiteral(" and "); + aString.AppendLiteral("("); + + const nsMediaExpression &expr = mExpressions[i]; + if (expr.mRange == nsMediaExpression::eMin) { + aString.AppendLiteral("min-"); + } else if (expr.mRange == nsMediaExpression::eMax) { + aString.AppendLiteral("max-"); + } + + const nsMediaFeature *feature = expr.mFeature; + (*feature->mName)->ToString(buffer); + aString.Append(buffer); + buffer.Truncate(); + + 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. + nsCSSDeclaration::AppendCSSValueToString(eCSSProperty_width, + expr.mValue, aString); + break; + case nsMediaFeature::eInteger: + NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Integer, + "bad unit"); + // Use 'z-index' as a property that takes integer values + // written without anything extra. + nsCSSDeclaration::AppendCSSValueToString(eCSSProperty_z_index, + expr.mValue, aString); + 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"); + nsCSSDeclaration::AppendCSSValueToString(eCSSProperty_z_index, + array->Item(0), aString); + aString.AppendLiteral("/"); + nsCSSDeclaration::AppendCSSValueToString(eCSSProperty_z_index, + array->Item(1), aString); + } + break; + case nsMediaFeature::eResolution: + buffer.AppendFloat(expr.mValue.GetFloatValue()); + aString.Append(buffer); + buffer.Truncate(); + if (expr.mValue.GetUnit() == eCSSUnit_Inch) { + aString.AppendLiteral("dpi"); + } 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->mKeywordTable), + aString); + break; + } + } + + aString.AppendLiteral(")"); + } +} + +nsMediaQuery* +nsMediaQuery::Clone() const +{ + nsAutoPtr result(new nsMediaQuery(*this)); + NS_ENSURE_TRUE(result && + result->mExpressions.Length() == mExpressions.Length(), + nsnull); + return result.forget(); +} + +PRBool +nsMediaQuery::Matches(nsPresContext* aPresContext) const +{ + if (mHadUnknownExpression) + return PR_FALSE; + + PRBool match = + mMediaType == aPresContext->Medium() || mMediaType == nsGkAtoms::all; + for (PRUint32 i = 0, i_end = mExpressions.Length(); match && i < i_end; ++i) { + const nsMediaExpression &expr = mExpressions[i]; + nsCSSValue actual; + nsresult rv = (expr.mFeature->mGetter)(aPresContext, actual); + NS_ENSURE_SUCCESS(rv, PR_FALSE); // any better ideas? + match = expr.Matches(aPresContext, actual); + } + + return match == !mNegated; +} + NS_INTERFACE_MAP_BEGIN(nsMediaList) NS_INTERFACE_MAP_ENTRY(nsIDOMMediaList) NS_INTERFACE_MAP_ENTRY(nsISupports) @@ -174,7 +434,8 @@ NS_IMPL_RELEASE(nsMediaList) nsMediaList::nsMediaList() - : mStyleSheet(nsnull) + : mIsEmpty(PR_TRUE) + , mStyleSheet(nsnull) { } @@ -187,13 +448,16 @@ nsMediaList::GetText(nsAString& aMediaText) { aMediaText.Truncate(); - for (PRInt32 i = 0, i_end = mArray.Count(); i < i_end; ++i) { - nsIAtom* medium = mArray[i]; - NS_ENSURE_TRUE(medium, NS_ERROR_FAILURE); + if (mArray.Length() == 0 && !mIsEmpty) { + aMediaText.AppendLiteral("not all"); + } + + for (PRInt32 i = 0, i_end = mArray.Length(); i < i_end; ++i) { + nsMediaQuery* query = mArray[i]; + NS_ENSURE_TRUE(query, NS_ERROR_FAILURE); + + query->AppendToString(aMediaText); - nsAutoString buffer; - medium->ToString(buffer); - aMediaText.Append(buffer); if (i + 1 < i_end) { aMediaText.AppendLiteral(", "); } @@ -224,18 +488,15 @@ nsMediaList::SetText(const nsAString& aMediaText) this, htmlMode); } -/* - * aMatch is true when we contain the desired medium or contain the - * "all" medium or contain no media at all, which is the same as - * containing "all" - */ PRBool nsMediaList::Matches(nsPresContext* aPresContext) { - if (-1 != mArray.IndexOf(aPresContext->Medium()) || - -1 != mArray.IndexOf(nsGkAtoms::all)) - return PR_TRUE; - return mArray.Count() == 0; + for (PRInt32 i = 0, i_end = mArray.Length(); i < i_end; ++i) { + if (mArray[i]->Matches(aPresContext)) { + return PR_TRUE; + } + } + return mIsEmpty; } nsresult @@ -251,10 +512,13 @@ nsresult nsMediaList::Clone(nsMediaList** aResult) { nsRefPtr result = new nsMediaList(); - if (!result) - return NS_ERROR_OUT_OF_MEMORY; - if (!result->mArray.AppendObjects(mArray)) + if (!result || !result->mArray.AppendElements(mArray.Length())) return NS_ERROR_OUT_OF_MEMORY; + for (PRInt32 i = 0, i_end = mArray.Length(); i < i_end; ++i) { + if (!(result->mArray[i] = mArray[i]->Clone())) { + return NS_ERROR_OUT_OF_MEMORY; + } + } NS_ADDREF(*aResult = result); return NS_OK; } @@ -310,7 +574,7 @@ nsMediaList::GetLength(PRUint32* aLength) { NS_ENSURE_ARG_POINTER(aLength); - *aLength = mArray.Count(); + *aLength = mArray.Length(); return NS_OK; } @@ -319,7 +583,11 @@ nsMediaList::Item(PRUint32 aIndex, nsAString& aReturn) { PRInt32 index = aIndex; if (0 <= index && index < Count()) { - MediumAt(aIndex)->ToString(aReturn); + nsMediaQuery* query = mArray[index]; + NS_ENSURE_TRUE(query, NS_ERROR_FAILURE); + + aReturn.Truncate(); + query->AppendToString(aReturn); } else { SetDOMStringToNull(aReturn); } @@ -367,18 +635,19 @@ nsMediaList::Delete(const nsAString& aOldMedium) if (aOldMedium.IsEmpty()) return NS_ERROR_DOM_NOT_FOUND_ERR; - nsCOMPtr old = do_GetAtom(aOldMedium); - NS_ENSURE_TRUE(old, NS_ERROR_OUT_OF_MEMORY); + for (PRInt32 i = 0, i_end = mArray.Length(); i < i_end; ++i) { + nsMediaQuery* query = mArray[i]; + NS_ENSURE_TRUE(query, NS_ERROR_FAILURE); - PRInt32 indx = mArray.IndexOf(old); - - if (indx < 0) { - return NS_ERROR_DOM_NOT_FOUND_ERR; + nsAutoString buf; + query->AppendToString(buf); + if (buf == aOldMedium) { + mArray.RemoveElementAt(i); + return NS_OK; + } } - mArray.RemoveObjectAt(indx); - - return NS_OK; + return NS_ERROR_DOM_NOT_FOUND_ERR; } nsresult @@ -387,18 +656,31 @@ nsMediaList::Append(const nsAString& aNewMedium) if (aNewMedium.IsEmpty()) return NS_ERROR_DOM_NOT_FOUND_ERR; - nsCOMPtr media = do_GetAtom(aNewMedium); - NS_ENSURE_TRUE(media, NS_ERROR_OUT_OF_MEMORY); + Delete(aNewMedium); - PRInt32 indx = mArray.IndexOf(media); - - if (indx >= 0) { - mArray.RemoveObjectAt(indx); + nsresult rv = NS_OK; + nsTArray > buf; +#ifdef DEBUG + PRBool ok = +#endif + mArray.SwapElements(buf); + NS_ASSERTION(ok, "SwapElements should never fail when neither array " + "is an auto array"); + SetText(aNewMedium); + if (mArray.Length() == 1) { + nsMediaQuery *query = mArray[0].forget(); + if (!buf.AppendElement(query)) { + delete query; + rv = NS_ERROR_OUT_OF_MEMORY; + } } - - mArray.AppendObject(media); - - return NS_OK; +#ifdef DEBUG + ok = +#endif + mArray.SwapElements(buf); + NS_ASSERTION(ok, "SwapElements should never fail when neither array " + "is an auto array"); + return rv; } // ------------------------------- diff --git a/layout/style/nsIMediaList.h b/layout/style/nsIMediaList.h index 6867f352758f..f338a03618d2 100644 --- a/layout/style/nsIMediaList.h +++ b/layout/style/nsIMediaList.h @@ -21,6 +21,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): + * L. David Baron , Mozilla Corporation * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -46,12 +47,84 @@ #include "nsIDOMMediaList.h" #include "nsAString.h" -#include "nsCOMArray.h" +#include "nsTArray.h" #include "nsIAtom.h" +#include "nsMediaFeatures.h" +#include "nsCSSValue.h" + class nsPresContext; class nsICSSStyleSheet; class nsCSSStyleSheet; +struct nsMediaExpression { + enum Range { eMin, eMax, eEqual }; + + const nsMediaFeature *mFeature; + Range mRange; + nsCSSValue mValue; + + // aActualValue must be obtained from mFeature->mGetter + PRBool Matches(nsPresContext* aPresContext, + const nsCSSValue& aActualValue) const; +}; + +class nsMediaQuery { +public: + nsMediaQuery() + : mNegated(PR_FALSE) + , mHasOnly(PR_FALSE) + , mTypeOmitted(PR_FALSE) + , mHadUnknownExpression(PR_FALSE) + { + } + +private: + // for Clone only + nsMediaQuery(const nsMediaQuery& aOther) + : mNegated(aOther.mNegated) + , mHasOnly(aOther.mHasOnly) + , mTypeOmitted(aOther.mTypeOmitted) + , mHadUnknownExpression(aOther.mHadUnknownExpression) + , mMediaType(aOther.mMediaType) + // Clone checks the result of this deep copy for allocation failure + , mExpressions(aOther.mExpressions) + { + } + +public: + + void SetNegated() { mNegated = PR_TRUE; } + void SetHasOnly() { mHasOnly = PR_TRUE; } + void SetTypeOmitted() { mTypeOmitted = PR_TRUE; } + void SetHadUnknownExpression() { mHadUnknownExpression = PR_TRUE; } + void SetType(nsIAtom* aMediaType) { + NS_ASSERTION(aMediaType, + "expected non-null"); + mMediaType = aMediaType; + } + + // Return a new nsMediaExpression in the array for the caller to fill + // in. The caller must either fill it in completely, or call + // SetHadUnknownExpression on this nsMediaQuery. + // Returns null on out-of-memory. + nsMediaExpression* NewExpression() { return mExpressions.AppendElement(); } + + void AppendToString(nsAString& aString) const; + + nsMediaQuery* Clone() const; + + // Does this query apply to the presentation? + PRBool Matches(nsPresContext* aPresContext) const; + +private: + PRPackedBool mNegated; + PRPackedBool mHasOnly; // only needed for serialization + PRPackedBool mTypeOmitted; // only needed for serialization + PRPackedBool mHadUnknownExpression; + nsCOMPtr mMediaType; + nsTArray mExpressions; +}; + class nsMediaList : public nsIDOMMediaList { public: nsMediaList(); @@ -64,15 +137,24 @@ public: nsresult SetText(const nsAString& aMediaText); PRBool Matches(nsPresContext* aPresContext); nsresult SetStyleSheet(nsICSSStyleSheet* aSheet); - nsresult AppendAtom(nsIAtom* aMediumAtom) { - return mArray.AppendObject(aMediumAtom) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + nsresult AppendQuery(nsAutoPtr& aQuery) { + // Takes ownership of aQuery (if it succeeds) + if (!mArray.AppendElement(aQuery.get())) { + return NS_ERROR_OUT_OF_MEMORY; + } + aQuery.forget(); + return NS_OK; } nsresult Clone(nsMediaList** aResult); - PRInt32 Count() { return mArray.Count(); } - nsIAtom* MediumAt(PRInt32 aIndex) { return mArray[aIndex]; } - void Clear() { mArray.Clear(); } + PRInt32 Count() { return mArray.Length(); } + nsMediaQuery* MediumAt(PRInt32 aIndex) { return mArray[aIndex]; } + void Clear() { mArray.Clear(); mIsEmpty = PR_TRUE; } + // a media list with no items may not represent the lack of a media + // list; it could represent the empty string or something with parser + // errors, which means that the media list should never match + void SetNonEmpty() { mIsEmpty = PR_FALSE; } protected: ~nsMediaList(); @@ -80,7 +162,8 @@ protected: nsresult Delete(const nsAString & aOldMedium); nsresult Append(const nsAString & aOldMedium); - nsCOMArray mArray; + nsTArray > mArray; + PRBool mIsEmpty; // not refcounted; sheet will let us know when it goes away // mStyleSheet is the sheet that needs to be dirtied when this medialist // changes diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp new file mode 100644 index 000000000000..20b19d2e8580 --- /dev/null +++ b/layout/style/nsMediaFeatures.cpp @@ -0,0 +1,328 @@ +/* vim: set shiftwidth=4 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 nsMediaFeatures. + * + * The Initial Developer of the Original Code is the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * L. David Baron , 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 ***** */ + +/* the features that media queries can test */ + +#include "nsMediaFeatures.h" +#include "nsGkAtoms.h" +#include "nsCSSKeywords.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsIDeviceContext.h" +#include "nsCSSValue.h" + +static const PRInt32 kOrientationKeywords[] = { + eCSSKeyword_portrait, NS_STYLE_ORIENTATION_PORTRAIT, + eCSSKeyword_landscape, NS_STYLE_ORIENTATION_LANDSCAPE, + eCSSKeyword_UNKNOWN, -1 +}; + +static const PRInt32 kScanKeywords[] = { + eCSSKeyword_progressive, NS_STYLE_SCAN_PROGRESSIVE, + eCSSKeyword_interlace, NS_STYLE_SCAN_INTERLACE, + eCSSKeyword_UNKNOWN, -1 +}; + +PR_STATIC_CALLBACK(nsresult) +GetWidth(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + nscoord width = aPresContext->GetVisibleArea().width; + float pixelWidth = aPresContext->AppUnitsToFloatCSSPixels(width); + aResult.SetFloatValue(pixelWidth, eCSSUnit_Pixel); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetHeight(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + nscoord height = aPresContext->GetVisibleArea().height; + float pixelHeight = aPresContext->AppUnitsToFloatCSSPixels(height); + aResult.SetFloatValue(pixelHeight, eCSSUnit_Pixel); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetDeviceWidth(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + // XXX: I'm not sure if this is really the right thing for print: + // do we want to include unprintable areas / page margins? + nsIDeviceContext *dx = aPresContext->DeviceContext(); + nscoord width, height; + dx->GetDeviceSurfaceDimensions(width, height); + float pixelWidth = aPresContext->AppUnitsToFloatCSSPixels(width); + aResult.SetFloatValue(pixelWidth, eCSSUnit_Pixel); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetDeviceHeight(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + // XXX: I'm not sure if this is really the right thing for print: + // do we want to include unprintable areas / page margins? + nsIDeviceContext *dx = aPresContext->DeviceContext(); + nscoord width, height; + dx->GetDeviceSurfaceDimensions(width, height); + float pixelHeight = aPresContext->AppUnitsToFloatCSSPixels(height); + aResult.SetFloatValue(pixelHeight, eCSSUnit_Pixel); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetOrientation(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + nsSize size = aPresContext->GetVisibleArea().Size(); + PRInt32 orientation; + if (size.width > size.height) { + orientation = NS_STYLE_ORIENTATION_LANDSCAPE; + } else { + // Per spec, square viewports should be 'portrait' + orientation = NS_STYLE_ORIENTATION_PORTRAIT; + } + + aResult.SetIntValue(orientation, eCSSUnit_Enumerated); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetAspectRatio(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + nsRefPtr a = nsCSSValue::Array::Create(2); + NS_ENSURE_TRUE(a, NS_ERROR_OUT_OF_MEMORY); + + nsSize size = aPresContext->GetVisibleArea().Size(); + a->Item(0).SetIntValue(size.width, eCSSUnit_Integer); + a->Item(1).SetIntValue(size.height, eCSSUnit_Integer); + + aResult.SetArrayValue(a, eCSSUnit_Array); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetDeviceAspectRatio(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + nsRefPtr a = nsCSSValue::Array::Create(2); + NS_ENSURE_TRUE(a, NS_ERROR_OUT_OF_MEMORY); + + // XXX: I'm not sure if this is really the right thing for print: + // do we want to include unprintable areas / page margins? + nsIDeviceContext *dx = aPresContext->DeviceContext(); + nscoord width, height; + dx->GetDeviceSurfaceDimensions(width, height); + a->Item(0).SetIntValue(width, eCSSUnit_Integer); + a->Item(1).SetIntValue(height, eCSSUnit_Integer); + + aResult.SetArrayValue(a, eCSSUnit_Array); + return NS_OK; +} + + +PR_STATIC_CALLBACK(nsresult) +GetColor(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + // FIXME: This implementation is bogus. nsThebesDeviceContext + // doesn't provide reliable information (should be fixed in bug + // 424386). + // FIXME: On a monochrome device, return 0! + nsIDeviceContext *dx = aPresContext->DeviceContext(); + PRUint32 depth; + dx->GetDepth(depth); + // Some graphics backends may claim 32-bit depth when it's really 24 + // (because they're counting the Alpha component). + if (depth == 32) { + depth = 24; + } + // The spec says to use bits *per color component*, so divide by 3, + // and round down, since the spec says to use the smallest when the + // color components differ. + depth /= 3; + aResult.SetIntValue(PRInt32(depth), eCSSUnit_Integer); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetColorIndex(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + // We should return zero if the device does not use a color lookup + // table. Stuart says that our handling of displays with 8-bit + // color is bad enough that we never change the lookup table to + // match what we're trying to display, so perhaps we should always + // return zero. Given that there isn't any better information + // exposed, we don't have much other choice. + aResult.SetIntValue(0, eCSSUnit_Integer); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetMonochrome(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + // For color devices we should return 0. + // FIXME: On a monochrome device, return the actual color depth, not + // 0! + aResult.SetIntValue(0, eCSSUnit_Integer); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetResolution(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + // Resolution values are in device pixels, not CSS pixels. + nsIDeviceContext *dx = aPresContext->DeviceContext(); + float dpi = float(dx->AppUnitsPerInch()) / float(dx->AppUnitsPerDevPixel()); + aResult.SetFloatValue(dpi, eCSSUnit_Inch); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetScan(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + // Since Gecko doesn't support the 'tv' media type, the 'scan' + // feature is never present. + aResult.Reset(); + return NS_OK; +} + +PR_STATIC_CALLBACK(nsresult) +GetGrid(nsPresContext* aPresContext, nsCSSValue& aResult) +{ + // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' + // feature is always 0. + aResult.SetIntValue(0, eCSSUnit_Integer); + return NS_OK; +} + +/* static */ const nsMediaFeature +nsMediaFeatures::features[] = { + { + &nsGkAtoms::width, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eLength, + nsnull, + GetWidth + }, + { + &nsGkAtoms::height, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eLength, + nsnull, + GetHeight + }, + { + &nsGkAtoms::deviceWidth, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eLength, + nsnull, + GetDeviceWidth + }, + { + &nsGkAtoms::deviceHeight, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eLength, + nsnull, + GetDeviceHeight + }, + { + &nsGkAtoms::orientation, + nsMediaFeature::eMinMaxNotAllowed, + nsMediaFeature::eEnumerated, + kOrientationKeywords, + GetOrientation + }, + { + &nsGkAtoms::aspectRatio, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eIntRatio, + nsnull, + GetAspectRatio + }, + { + &nsGkAtoms::deviceAspectRatio, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eIntRatio, + nsnull, + GetDeviceAspectRatio + }, + { + &nsGkAtoms::color, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eInteger, + nsnull, + GetColor + }, + { + &nsGkAtoms::colorIndex, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eInteger, + nsnull, + GetColorIndex + }, + { + &nsGkAtoms::monochrome, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eInteger, + nsnull, + GetMonochrome + }, + { + &nsGkAtoms::resolution, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eResolution, + nsnull, + GetResolution + }, + { + &nsGkAtoms::scan, + nsMediaFeature::eMinMaxNotAllowed, + nsMediaFeature::eEnumerated, + kScanKeywords, + GetScan + }, + { + &nsGkAtoms::grid, + nsMediaFeature::eMinMaxNotAllowed, + nsMediaFeature::eInteger, + nsnull, + GetGrid + }, + // Null-mName terminator: + { + nsnull, + nsMediaFeature::eMinMaxAllowed, + nsMediaFeature::eInteger, + nsnull, + nsnull + }, +}; diff --git a/layout/style/nsMediaFeatures.h b/layout/style/nsMediaFeatures.h new file mode 100644 index 000000000000..5b92ed2cd14e --- /dev/null +++ b/layout/style/nsMediaFeatures.h @@ -0,0 +1,91 @@ +/* vim: set shiftwidth=4 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 nsMediaFeatures. + * + * The Initial Developer of the Original Code is the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * L. David Baron , 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 ***** */ + +/* the features that media queries can test */ + +#ifndef nsMediaFeatures_h_ +#define nsMediaFeatures_h_ + +#include "nscore.h" + +class nsIAtom; +class nsPresContext; +class nsCSSValue; + +typedef nsresult +(* PR_CALLBACK nsMediaFeatureValueGetter)(nsPresContext* aPresContext, + nsCSSValue& aResult); + +struct nsMediaFeature { + nsIAtom **mName; // extra indirection to point to nsGkAtoms members + + enum RangeType { eMinMaxAllowed, eMinMaxNotAllowed }; + RangeType mRangeType; + + enum ValueType { + // All value types allow eCSSUnit_Null to indicate that no value + // was given (in addition to the types listed below). + eLength, // values are such that nsCSSValue::IsLengthUnit() is true + eInteger, // values are eCSSUnit_Integer + eIntRatio, // values are eCSSUnit_Array of two eCSSUnit_Integer + eResolution, // values are in eCSSUnit_Inch (for dpi) or + // eCSSUnit_Centimeter (for dpcm) + eEnumerated // values are eCSSUnit_Enumerated (uses keyword table) + + // Note that a number of pieces of code (both for parsing and + // for matching of valueless expressions) assume that all numeric + // value types cannot be negative. The parsing code also does + // not allow zeros in eIntRatio types. + }; + ValueType mValueType; + + // The same format as the keyword tables in nsCSSProps. + const PRInt32* mKeywordTable; + + // A function that returns the current value for this feature for a + // given presentation. If it returns eCSSUnit_Null, the feature is + // not present. + nsMediaFeatureValueGetter mGetter; +}; + +class nsMediaFeatures { +public: + // Terminated with an entry whose mName is null. + static const nsMediaFeature features[]; +}; + +#endif /* !defined(nsMediaFeatures_h_) */ diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 228f8cd61468..a1f6a4be3694 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -241,6 +241,16 @@ static nscoord CalcLength(const nsCSSValue& aValue, return CalcLengthWith(aValue, -1, nsnull, aStyleContext, aPresContext, aInherited); } +/* static */ nscoord +nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext, + const nsCSSValue& aValue) +{ + nsStyleFont defaultFont(aPresContext); + PRBool inherited; + return CalcLengthWith(aValue, -1, &defaultFont, nsnull, aPresContext, + inherited); +} + #define SETCOORD_NORMAL 0x01 // N #define SETCOORD_AUTO 0x02 // A #define SETCOORD_INHERIT 0x04 // H diff --git a/layout/style/nsRuleNode.h b/layout/style/nsRuleNode.h index 343df4dc24f5..bd3724da8cdb 100644 --- a/layout/style/nsRuleNode.h +++ b/layout/style/nsRuleNode.h @@ -738,6 +738,10 @@ public: static PRBool HasAuthorSpecifiedRules(nsStyleContext* aStyleContext, PRUint32 ruleTypeMask); + + // Expose this so media queries can use it + static nscoord CalcLengthWithInitialFont(nsPresContext* aPresContext, + const nsCSSValue& aValue); }; #endif