--- a/layout/style/CSS.cpp
+++ b/layout/style/CSS.cpp
@@ -11,55 +11,90 @@
#include "mozilla/ServoBindings.h"
#include "nsCSSParser.h"
#include "nsGlobalWindow.h"
#include "nsIDocument.h"
#include "nsIURI.h"
#include "nsStyleUtil.h"
#include "xpcpublic.h"
+// For custom properties support.
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/PropertyDescriptorDictBinding.h"
+#include "mozilla/CSSVariableRegistration.h"
+#include "mozilla/CSSVariableSyntax.h"
+#include "nsCSSParser.h"
+#include "nsCSSProps.h"
+#include "nsCSSScanner.h"
+
namespace mozilla {
namespace dom {
-struct SupportsParsingInfo
+struct ParsingInfo
{
+ CSSVariableRegistrations* mRegistrations;
nsIURI* mDocURI;
nsIURI* mBaseURI;
nsIPrincipal* mPrincipal;
StyleBackendType mStyleBackendType;
};
static nsresult
GetParsingInfo(const GlobalObject& aGlobal,
- SupportsParsingInfo& aInfo)
+ ParsingInfo& aInfo)
{
nsGlobalWindow* win = xpc::WindowOrNull(aGlobal.Get());
if (!win) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDocument> doc = win->GetDoc();
if (!doc) {
return NS_ERROR_FAILURE;
}
+ aInfo.mRegistrations = doc->GetCSSVariableRegistrations();
aInfo.mDocURI = nsCOMPtr<nsIURI>(doc->GetDocumentURI()).get();
aInfo.mBaseURI = nsCOMPtr<nsIURI>(doc->GetBaseURI()).get();
aInfo.mPrincipal = win->GetPrincipal();
aInfo.mStyleBackendType = doc->GetStyleBackendType();
return NS_OK;
}
+static nsresult
+RebuildAllStyleData(const GlobalObject& aGlobal)
+{
+ // The inner window (the script compilation context)
+ nsGlobalWindow* win = xpc::WindowOrNull(aGlobal.Get());
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc = win->GetDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIPresShell* shell;
+ nsPresContext* pcx;
+ if ((shell = doc->GetShell()) && (pcx = shell->GetPresContext())) {
+ pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW, eRestyle_Subtree);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
/* static */ bool
CSS::Supports(const GlobalObject& aGlobal,
const nsAString& aProperty,
const nsAString& aValue,
- ErrorResult& aRv)
+ mozilla::ErrorResult& aRv)
{
- SupportsParsingInfo info;
+ ParsingInfo info;
nsresult rv = GetParsingInfo(aGlobal, info);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return false;
}
if (info.mStyleBackendType == StyleBackendType::Servo) {
@@ -68,60 +103,255 @@ CSS::Supports(const GlobalObject& aGloba
return Servo_CSSSupports(reinterpret_cast<const uint8_t*>(property.get()),
property.Length(),
reinterpret_cast<const uint8_t*>(value.get()),
value.Length());
}
nsCSSParser parser;
+ parser.SetVariableRegistrations(info.mRegistrations);
return parser.EvaluateSupportsDeclaration(aProperty, aValue, info.mDocURI,
info.mBaseURI, info.mPrincipal);
}
/* static */ bool
CSS::Supports(const GlobalObject& aGlobal,
const nsAString& aCondition,
- ErrorResult& aRv)
+ mozilla::ErrorResult& aRv)
{
- SupportsParsingInfo info;
+ ParsingInfo info;
nsresult rv = GetParsingInfo(aGlobal, info);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return false;
}
if (info.mStyleBackendType == StyleBackendType::Servo) {
MOZ_CRASH("stylo: CSS.supports() with arguments is not yet implemented");
}
nsCSSParser parser;
+ parser.SetVariableRegistrations(info.mRegistrations);
return parser.EvaluateSupportsCondition(aCondition, info.mDocURI,
info.mBaseURI, info.mPrincipal);
}
/* static */ void
CSS::Escape(const GlobalObject& aGlobal,
const nsAString& aIdent,
nsAString& aReturn)
{
nsStyleUtil::AppendEscapedCSSIdent(aIdent, aReturn);
}
+static bool
+IsIdempotentValue(const nsCSSValue& aValue)
+{
+ if (aValue.IsRelativeLengthUnit()) {
+ // Initial values need to be computationally idempotent.
+ // NB: Variables are not computationally idempotent!
+ // That's caught by ParseTypedValue, which will parse them as a var
+ // function (which doesn't exist). Variables would have been resolved by
+ // ResolveVariableValue (see CSSVariableResolver).
+ return false;
+ }
+
+ if (aValue.GetUnit() >= eCSSUnit_Null &&
+ aValue.GetUnit() <= eCSSUnit_All) {
+ // Same reason. Can't have 'inherit', 'unset', 'revert', etc. -- not
+ // computationally idempotent.
+ return false;
+ }
+
+ if (aValue.GetUnit() == eCSSUnit_Calc) {
+ const nsCSSValue::Array* arr = aValue.GetArrayValue();
+ MOZ_ASSERT(arr->Count() == 1, "unexpected length");
+ return IsIdempotentValue(arr->Item(0));
+ }
+
+ if (aValue.GetUnit() >= eCSSUnit_Calc_Plus &&
+ aValue.GetUnit() <= eCSSUnit_Calc_Divided) {
+ const nsCSSValue::Array* arr = aValue.GetArrayValue();
+ MOZ_ASSERT(arr->Count() == 2, "unexpected length");
+ return IsIdempotentValue(arr->Item(0)) &&
+ IsIdempotentValue(arr->Item(1));
+ }
+
+ if (aValue.GetUnit() == eCSSUnit_Function) {
+ const nsCSSValue::Array* func = aValue.GetArrayValue();
+ for (size_t argID = 0; argID < func->Count() - 1; argID++) {
+ const nsCSSValue& arg = func->Item(1 + argID);
+ if (!IsIdempotentValue(arg)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (aValue.GetUnit() == eCSSUnit_List) {
+ const nsCSSValueList* list = aValue.GetListValue();
+ while (list) {
+ if (!IsIdempotentValue(list->mValue)) {
+ return false;
+ }
+ list = list->mNext;
+ }
+ }
+
+ return true;
+}
+
/* static */ void
CSS::RegisterProperty(const GlobalObject& aGlobal,
const mozilla::dom::PropertyDescriptorDict& aDescriptor,
mozilla::ErrorResult& aRv)
{
- // STUB: populated by a later patch in this series
+ ParsingInfo info;
+ nsresult rv = GetParsingInfo(aGlobal, info);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (!nsCSSProps::IsCustomPropertyName(aDescriptor.mName)) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ // First two characters are guaranteed to be -- by the previous guard.
+ const nsDependentSubstring name =
+ Substring(aDescriptor.mName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+
+ // Verify that the part after -- is a valid ident, as required.
+ nsCSSScanner scanner(name, /* aLineNumber = */ 0);
+ nsCSSToken token;
+ if (!scanner.Next(token, eCSSScannerExclude_None) ||
+ token.mType != eCSSToken_Ident) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ if (info.mRegistrations->mData.Contains(name)) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
+ return;
+ }
+
+ nsAutoPtr<CSSVariableRegistration> registration(new CSSVariableRegistration);
+ registration->mInitialContext =
+ CSSVariableExprContext(info.mDocURI, info.mBaseURI, info.mPrincipal);
+
+ if (aDescriptor.mSyntax.WasPassed()) {
+ if (!registration->mSyntax.SetSyntax(aDescriptor.mSyntax.Value())) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+ } else {
+ DebugOnly<bool> ok = registration->mSyntax.SetSyntax(NS_LITERAL_STRING("*"));
+ MOZ_ASSERT(ok);
+ }
+
+ registration->mInherited = (aDescriptor.mInherits.WasPassed() &&
+ aDescriptor.mInherits.Value()) ||
+ false;
+ registration->mAtom = NS_Atomize(name);
+
+ // If no initialValue is provided and the syntax is *, then a special initial
+ // value used [sic]. This initial value must be considered parseable by
+ // registerProperty() but invalid at computed value time.
+ if (aDescriptor.mInitialValue.WasPassed()) {
+ nsCSSParser parser;
+
+ // The initial value, if specified, should type properly.
+ nsCSSValue value;
+ CSSValueType type;
+ if (!parser.ParseTypedValue(registration->mSyntax,
+ aDescriptor.mInitialValue.Value(),
+ info.mDocURI, info.mBaseURI, info.mPrincipal,
+ value, type)) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ if (!IsIdempotentValue(value)) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ registration->mInitialExpr = aDescriptor.mInitialValue.Value();
+ registration->mInitialValue = value;
+ registration->mInitialType = type;
+
+ nsAutoString _result;
+ MOZ_ASSERT(parser.ResolveVariableValue(aDescriptor.mInitialValue.Value(),
+ // There should be no variables!
+ // Caught by ParseTypedValue.
+ nullptr, _result,
+ registration->mInitialFirstToken,
+ registration->mInitialLastToken));
+ } else {
+ // The special 'empty string' initial expression is not valid for variable
+ // declarations (see CSS Variables, need at least one character of
+ // whitespace) so we check for this in CSSVariableResolver.
+ registration->mInitialExpr = EmptyString();
+ registration->mInitialFirstToken = eCSSTokenSerialization_Nothing;
+ registration->mInitialLastToken = eCSSTokenSerialization_Nothing;
+ }
+
+ info.mRegistrations->mGeneration++;
+ info.mRegistrations->mData.Put(name, registration.forget());
+
+ rv = RebuildAllStyleData(aGlobal);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ return;
}
/* static */ void
CSS::UnregisterProperty(const GlobalObject& aGlobal,
const nsAString& aName,
mozilla::ErrorResult& aRv)
{
- // STUB: populated by a later patch in this series
+ ParsingInfo info;
+ nsresult rv = GetParsingInfo(aGlobal, info);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (!nsCSSProps::IsCustomPropertyName(aName)) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ // First two characters are guaranteed to be -- by the previous guard.
+ const nsDependentSubstring name = Substring(aName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+
+ // Verify that the part after -- is a valid ident, as required.
+ nsCSSScanner scanner(name, /* aLineNumber = */ 0);
+ nsCSSToken token;
+ if (!scanner.Next(token, eCSSScannerExclude_None) ||
+ token.mType != eCSSToken_Ident) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ if (!info.mRegistrations->mData.Contains(name)) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+
+ info.mRegistrations->mGeneration++;
+ info.mRegistrations->mData.Remove(name);
+
+ rv = RebuildAllStyleData(aGlobal);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ return;
}
} // namespace dom
} // namespace mozilla