diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /layout/style/test | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/style/test')
317 files changed, 52497 insertions, 0 deletions
diff --git a/layout/style/test/BitPattern.woff b/layout/style/test/BitPattern.woff Binary files differnew file mode 100644 index 000000000..e4e824405 --- /dev/null +++ b/layout/style/test/BitPattern.woff diff --git a/layout/style/test/ListCSSProperties.cpp b/layout/style/test/ListCSSProperties.cpp new file mode 100644 index 000000000..718032f61 --- /dev/null +++ b/layout/style/test/ListCSSProperties.cpp @@ -0,0 +1,193 @@ +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* 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/. */ + +/* build (from code) lists of all supported CSS properties */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "mozilla/ArrayUtils.h" + +struct PropertyInfo { + const char *propName; + const char *domName; + const char *pref; +}; + +const PropertyInfo gLonghandProperties[] = { + +#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_ +#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \ + stylestruct_, stylestructoffset_, animtype_) \ + { #name_, #method_, pref_ }, +#define CSS_PROP_LIST_INCLUDE_LOGICAL + +#include "nsCSSPropList.h" + +#undef CSS_PROP_LIST_INCLUDE_LOGICAL +#undef CSS_PROP +#undef CSS_PROP_PUBLIC_OR_PRIVATE + +}; + +/* + * These are the properties for which domName in the above list should + * be used. They're in the same order as the above list, with some + * items skipped. + */ +const char* gLonghandPropertiesWithDOMProp[] = { + +#define CSS_PROP_LIST_EXCLUDE_INTERNAL +#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \ + stylestruct_, stylestructoffset_, animtype_) \ + #name_, +#define CSS_PROP_LIST_INCLUDE_LOGICAL + +#include "nsCSSPropList.h" + +#undef CSS_PROP_LIST_INCLUDE_LOGICAL +#undef CSS_PROP +#undef CSS_PROP_LIST_EXCLUDE_INTERNAL + +}; + +const PropertyInfo gShorthandProperties[] = { + +#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_ +// Need an extra level of macro nesting to force expansion of method_ +// params before they get pasted. +#define LISTCSSPROPERTIES_INNER_MACRO(method_) #method_ +#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \ + { #name_, LISTCSSPROPERTIES_INNER_MACRO(method_), pref_ }, + +#include "nsCSSPropList.h" + +#undef CSS_PROP_SHORTHAND +#undef LISTCSSPROPERTIES_INNER_MACRO +#undef CSS_PROP_PUBLIC_OR_PRIVATE + +#define CSS_PROP_ALIAS(name_, id_, method_, pref_) \ + { #name_, #method_, pref_ }, + +#include "nsCSSPropAliasList.h" + +#undef CSS_PROP_ALIAS + +}; + +/* see gLonghandPropertiesWithDOMProp */ +const char* gShorthandPropertiesWithDOMProp[] = { + +#define CSS_PROP_LIST_EXCLUDE_INTERNAL +#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \ + #name_, + +#include "nsCSSPropList.h" + +#undef CSS_PROP_SHORTHAND +#undef CSS_PROP_LIST_EXCLUDE_INTERNAL + +#define CSS_PROP_ALIAS(name_, id_, method_, pref_) \ + #name_, + +#include "nsCSSPropAliasList.h" + +#undef CSS_PROP_ALIAS + +}; + +const char *gInaccessibleProperties[] = { + // Don't print the properties that aren't accepted by the parser, per + // CSSParserImpl::ParseProperty + "-x-cols", + "-x-lang", + "-x-span", + "-x-system-font", + "-x-text-zoom", + "-moz-control-character-visibility", + "-moz-script-level", // parsed by UA sheets only + "-moz-script-size-multiplier", + "-moz-script-min-size", + "-moz-math-variant", + "-moz-math-display", // parsed by UA sheets only + "-moz-top-layer", // parsed by UA sheets only + "-moz-min-font-size-ratio", // parsed by UA sheets only + "-moz-window-shadow" // chrome-only internal properties +}; + +inline int +is_inaccessible(const char* aPropName) +{ + for (unsigned j = 0; j < MOZ_ARRAY_LENGTH(gInaccessibleProperties); ++j) { + if (strcmp(aPropName, gInaccessibleProperties[j]) == 0) + return 1; + } + return 0; +} + +void +print_array(const char *aName, + const PropertyInfo *aProps, unsigned aPropsLength, + const char * const * aDOMProps, unsigned aDOMPropsLength) +{ + printf("var %s = [\n", aName); + + int first = 1; + unsigned j = 0; // index into DOM prop list + for (unsigned i = 0; i < aPropsLength; ++i) { + const PropertyInfo *p = aProps + i; + + if (is_inaccessible(p->propName)) + // inaccessible properties never have DOM props, so don't + // worry about incrementing j. The assertion below will + // catch if they do. + continue; + + if (first) + first = 0; + else + printf(",\n"); + + printf("\t{ name: \"%s\", prop: ", p->propName); + if (j >= aDOMPropsLength || strcmp(p->propName, aDOMProps[j]) != 0) + printf("null"); + else { + ++j; + if (strncmp(p->domName, "Moz", 3) == 0) + printf("\"%s\"", p->domName); + else + // lowercase the first letter + printf("\"%c%s\"", p->domName[0] + 32, p->domName + 1); + } + if (p->pref[0]) { + printf(", pref: \"%s\"", p->pref); + } + printf(" }"); + } + + if (j != aDOMPropsLength) { + fprintf(stderr, "Assertion failure %s:%d\n", __FILE__, __LINE__); + fprintf(stderr, "j==%d, aDOMPropsLength == %d\n", j, aDOMPropsLength); + exit(1); + } + + printf("\n];\n\n"); +} + +int +main() +{ + print_array("gLonghandProperties", + gLonghandProperties, + MOZ_ARRAY_LENGTH(gLonghandProperties), + gLonghandPropertiesWithDOMProp, + MOZ_ARRAY_LENGTH(gLonghandPropertiesWithDOMProp)); + print_array("gShorthandProperties", + gShorthandProperties, + MOZ_ARRAY_LENGTH(gShorthandProperties), + gShorthandPropertiesWithDOMProp, + MOZ_ARRAY_LENGTH(gShorthandPropertiesWithDOMProp)); + return 0; +} diff --git a/layout/style/test/ParseCSS.cpp b/layout/style/test/ParseCSS.cpp new file mode 100644 index 000000000..3f407c6f0 --- /dev/null +++ b/layout/style/test/ParseCSS.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +// vim:cindent:ts=8:et:sw=4: +/* 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/. */ + +/* + * This file is meant to be used with |#define CSS_REPORT_PARSE_ERRORS| + * in mozilla/dom/html/style/src/nsCSSScanner.h uncommented, and the + * |#ifdef DEBUG| block in nsCSSScanner::OutputError (in + * nsCSSScanner.cpp in the same directory) used (even if not a debug + * build). + */ + +#include "nsXPCOM.h" +#include "nsCOMPtr.h" + +#include "nsIFile.h" +#include "nsNetUtil.h" + +#include "nsContentCID.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Loader.h" + +using namespace mozilla; + +static already_AddRefed<nsIURI> +FileToURI(const char *aFilename, nsresult *aRv = 0) +{ + nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, aRv)); + NS_ENSURE_TRUE(lf, nullptr); + // XXX Handle relative paths somehow. + lf->InitWithNativePath(nsDependentCString(aFilename)); + + nsIURI *uri = nullptr; + nsresult rv = NS_NewFileURI(&uri, lf); + if (aRv) + *aRv = rv; + return uri; +} + +static int +ParseCSSFile(nsIURI *aSheetURI) +{ + RefPtr<mozilla::css::Loader> = new mozilla::css::Loader(); + RefPtr<CSSStyleSheet> sheet; + loader->LoadSheetSync(aSheetURI, getter_AddRefs(sheet)); + NS_ASSERTION(sheet, "sheet load failed"); + /* This can happen if the file can't be found (e.g. you + * ask for a relative path and xpcom/io rejects it) + */ + if (!sheet) + return -1; + bool complete; + sheet->GetComplete(complete); + NS_ASSERTION(complete, "synchronous load did not complete"); + if (!complete) + return -2; + return 0; +} + +int main(int argc, char** argv) +{ + if (argc < 2) { + fprintf(stderr, "%s [FILE]...\n", argv[0]); + } + nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr); + if (NS_FAILED(rv)) + return (int)rv; + + int res = 0; + for (int i = 1; i < argc; ++i) { + const char *filename = argv[i]; + + printf("\nParsing %s.\n", filename); + + nsCOMPtr<nsIURI> uri = FileToURI(filename, &rv); + if (rv == NS_ERROR_OUT_OF_MEMORY) { + fprintf(stderr, "Out of memory.\n"); + return 1; + } + if (uri) + res = ParseCSSFile(uri); + } + + NS_ShutdownXPCOM(nullptr); + + return res; +} diff --git a/layout/style/test/TestCSSPropertyLookup.cpp b/layout/style/test/TestCSSPropertyLookup.cpp new file mode 100644 index 000000000..60a15311c --- /dev/null +++ b/layout/style/test/TestCSSPropertyLookup.cpp @@ -0,0 +1,172 @@ +/* -*- 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/. */ +#include <stdio.h> +#include "plstr.h" +#include "nsCSSProps.h" +#include "nsCSSKeywords.h" +#include "nsString.h" +#include "nsXPCOM.h" + +using namespace mozilla; + +static const char* const kJunkNames[] = { + nullptr, + "", + "123", + "backgroundz", + "zzzzzz", + "#@$&@#*@*$@$#" +}; + +static bool +TestProps() +{ + bool success = true; + nsCSSPropertyID id; + nsCSSPropertyID index; + + // Everything appears to assert if we don't do this first... + nsCSSProps::AddRefTable(); + + // First make sure we can find all of the tags that are supposed to + // be in the table. Futz with the case to make sure any case will + // work + extern const char* const kCSSRawProperties[]; + const char*const* et = &kCSSRawProperties[0]; + const char*const* end = &kCSSRawProperties[eCSSProperty_COUNT]; + index = eCSSProperty_UNKNOWN; + while (et < end) { + char tagName[100]; + PL_strcpy(tagName, *et); + index = nsCSSPropertyID(int32_t(index) + 1); + + id = nsCSSProps::LookupProperty(nsCString(tagName), + CSSEnabledState::eIgnoreEnabledState); + if (id == eCSSProperty_UNKNOWN) { + printf("bug: can't find '%s'\n", tagName); + success = false; + } + if (id != index) { + printf("bug: name='%s' id=%d index=%d\n", tagName, id, index); + success = false; + } + + // fiddle with the case to make sure we can still find it + if (('a' <= tagName[0]) && (tagName[0] <= 'z')) { + tagName[0] = tagName[0] - 32; + } + id = nsCSSProps::LookupProperty(NS_ConvertASCIItoUTF16(tagName), + CSSEnabledState::eIgnoreEnabledState); + if (id < 0) { + printf("bug: can't find '%s'\n", tagName); + success = false; + } + if (index != id) { + printf("bug: name='%s' id=%d index=%d\n", tagName, id, index); + success = false; + } + et++; + } + + // Now make sure we don't find some garbage + for (int i = 0; i < (int) (sizeof(kJunkNames) / sizeof(const char*)); i++) { + const char* const tag = kJunkNames[i]; + id = nsCSSProps::LookupProperty(nsAutoCString(tag), + CSSEnabledState::eIgnoreEnabledState); + if (id >= 0) { + printf("bug: found '%s'\n", tag ? tag : "(null)"); + success = false; + } + } + + nsCSSProps::ReleaseTable(); + return success; +} + +bool +TestKeywords() +{ + nsCSSKeywords::AddRefTable(); + + bool success = true; + nsCSSKeyword id; + nsCSSKeyword index; + + extern const char* const kCSSRawKeywords[]; + + // First make sure we can find all of the tags that are supposed to + // be in the table. Futz with the case to make sure any case will + // work + const char*const* et = &kCSSRawKeywords[0]; + const char*const* end = &kCSSRawKeywords[eCSSKeyword_COUNT - 1]; + index = eCSSKeyword_UNKNOWN; + while (et < end) { + char tagName[512]; + char* underscore = &(tagName[0]); + + PL_strcpy(tagName, *et); + while (*underscore) { + if (*underscore == '_') { + *underscore = '-'; + } + underscore++; + } + index = nsCSSKeyword(int32_t(index) + 1); + + id = nsCSSKeywords::LookupKeyword(nsCString(tagName)); + if (id <= eCSSKeyword_UNKNOWN) { + printf("bug: can't find '%s'\n", tagName); + success = false; + } + if (id != index) { + printf("bug: name='%s' id=%d index=%d\n", tagName, id, index); + success = false; + } + + // fiddle with the case to make sure we can still find it + if (('a' <= tagName[0]) && (tagName[0] <= 'z')) { + tagName[0] = tagName[0] - 32; + } + id = nsCSSKeywords::LookupKeyword(nsCString(tagName)); + if (id <= eCSSKeyword_UNKNOWN) { + printf("bug: can't find '%s'\n", tagName); + success = false; + } + if (id != index) { + printf("bug: name='%s' id=%d index=%d\n", tagName, id, index); + success = false; + } + et++; + } + + // Now make sure we don't find some garbage + for (int i = 0; i < (int) (sizeof(kJunkNames) / sizeof(const char*)); i++) { + const char* const tag = kJunkNames[i]; + id = nsCSSKeywords::LookupKeyword(nsAutoCString(tag)); + if (eCSSKeyword_UNKNOWN < id) { + printf("bug: found '%s'\n", tag ? tag : "(null)"); + success = false; + } + } + + nsCSSKeywords::ReleaseTable(); + return success; +} + +int +main(void) +{ + nsresult rv = NS_InitXPCOM2(nullptr, nullptr, nullptr); + NS_ENSURE_SUCCESS(rv, 2); + + bool testOK = true; + testOK &= TestProps(); + testOK &= TestKeywords(); + + rv = NS_ShutdownXPCOM(nullptr); + NS_ENSURE_SUCCESS(rv, 2); + + return testOK ? 0 : 1; +} diff --git a/layout/style/test/additional_sheets_helper.html b/layout/style/test/additional_sheets_helper.html new file mode 100644 index 000000000..306ddbf5b --- /dev/null +++ b/layout/style/test/additional_sheets_helper.html @@ -0,0 +1,7 @@ +<html> +<head> +</head> +<body> + some text +</body> +</html> diff --git a/layout/style/test/animation_utils.js b/layout/style/test/animation_utils.js new file mode 100644 index 000000000..131e9b755 --- /dev/null +++ b/layout/style/test/animation_utils.js @@ -0,0 +1,707 @@ +//---------------------------------------------------------------------- +// +// Common testing functions +// +//---------------------------------------------------------------------- + +function advance_clock(milliseconds) { + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds); +} + +// Test-element creation/destruction and event checking +(function() { + var gElem; + var gEventsReceived = []; + + function new_div(style) { + return new_element("div", style); + } + + // Creates a new |tagname| element with inline style |style| and appends + // it as a child of the element with ID 'display'. + // The element will also be given the class 'target' which can be used + // for additional styling. + function new_element(tagname, style) { + if (gElem) { + ok(false, "test author forgot to call done_div/done_elem"); + } + if (typeof(style) != "string") { + ok(false, "test author forgot to pass argument"); + } + if (!document.getElementById("display")) { + ok(false, "no 'display' element to append to"); + } + gElem = document.createElement(tagname); + gElem.setAttribute("style", style); + gElem.classList.add("target"); + document.getElementById("display").appendChild(gElem); + return [ gElem, getComputedStyle(gElem, "") ]; + } + + function listen() { + if (!gElem) { + ok(false, "test author forgot to call new_div before listen"); + } + gEventsReceived = []; + function listener(event) { + gEventsReceived.push(event); + } + gElem.addEventListener("animationstart", listener, false); + gElem.addEventListener("animationiteration", listener, false); + gElem.addEventListener("animationend", listener, false); + } + + function check_events(eventsExpected, desc) { + // This function checks that the list of eventsExpected matches + // the received events -- but it only checks the properties that + // are present on eventsExpected. + is(gEventsReceived.length, eventsExpected.length, + "number of events received for " + desc); + for (var i = 0, + i_end = Math.min(eventsExpected.length, gEventsReceived.length); + i != i_end; ++i) { + var exp = eventsExpected[i]; + var rec = gEventsReceived[i]; + for (var prop in exp) { + if (prop == "elapsedTime") { + // Allow floating point error. + ok(Math.abs(rec.elapsedTime - exp.elapsedTime) < 0.000002, + "events[" + i + "]." + prop + " for " + desc + + " received=" + rec.elapsedTime + " expected=" + exp.elapsedTime); + } else { + is(rec[prop], exp[prop], + "events[" + i + "]." + prop + " for " + desc); + } + } + } + for (var i = eventsExpected.length; i < gEventsReceived.length; ++i) { + ok(false, "unexpected " + gEventsReceived[i].type + " event for " + desc); + } + gEventsReceived = []; + } + + function done_element() { + if (!gElem) { + ok(false, "test author called done_element/done_div without matching" + + " call to new_element/new_div"); + } + gElem.remove(); + gElem = null; + if (gEventsReceived.length) { + ok(false, "caller should have called check_events"); + } + } + + [ new_div + , new_element + , listen + , check_events + , done_element ] + .forEach(function(fn) { + window[fn.name] = fn; + }); + window.done_div = done_element; +})(); + +function px_to_num(str) +{ + return Number(String(str).match(/^([\d.]+)px$/)[1]); +} + +function bezier(x1, y1, x2, y2) { + // Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1). + function x_for_t(t) { + var omt = 1-t; + return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t; + } + function y_for_t(t) { + var omt = 1-t; + return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t; + } + function t_for_x(x) { + // Binary subdivision. + var mint = 0, maxt = 1; + for (var i = 0; i < 30; ++i) { + var guesst = (mint + maxt) / 2; + var guessx = x_for_t(guesst); + if (x < guessx) + maxt = guesst; + else + mint = guesst; + } + return (mint + maxt) / 2; + } + return function bezier_closure(x) { + if (x == 0) return 0; + if (x == 1) return 1; + return y_for_t(t_for_x(x)); + } +} + +function step_end(nsteps) { + return function step_end_closure(x) { + return Math.floor(x * nsteps) / nsteps; + } +} + +function step_start(nsteps) { + var stepend = step_end(nsteps); + return function step_start_closure(x) { + return 1.0 - stepend(1.0 - x); + } +} + +var gTF = { + "ease": bezier(0.25, 0.1, 0.25, 1), + "linear": function(x) { return x; }, + "ease_in": bezier(0.42, 0, 1, 1), + "ease_out": bezier(0, 0, 0.58, 1), + "ease_in_out": bezier(0.42, 0, 0.58, 1), + "step_start": step_start(1), + "step_end": step_end(1), +}; + +function is_approx(float1, float2, error, desc) { + ok(Math.abs(float1 - float2) < error, + desc + ": " + float1 + " and " + float2 + " should be within " + error); +} + +function findKeyframesRule(name) { + for (var i = 0; i < document.styleSheets.length; i++) { + var match = [].find.call(document.styleSheets[i].cssRules, function(rule) { + return rule.type == CSSRule.KEYFRAMES_RULE && + rule.name == name; + }); + if (match) { + return match; + } + } + return undefined; +} + +// Checks if off-main thread animation (OMTA) is available, and if it is, runs +// the provided callback function. If OMTA is not available or is not +// functioning correctly, the second callback, aOnSkip, is run instead. +// +// This function also does an internal test to verify that OMTA is working at +// all so that if OMTA is not functioning correctly when it is expected to +// function only a single failure is produced. +// +// Since this function relies on various asynchronous operations, the caller is +// responsible for calling SimpleTest.waitForExplicitFinish() before calling +// this and SimpleTest.finish() within aTestFunction and aOnSkip. +// +// specialPowersForPrefs exists because some SpecialPowers objects apparently +// can get prefs and some can't; callers that would normally have one of the +// latter but can get their hands on one of the former can pass it in +// explicitly. +function runOMTATest(aTestFunction, aOnSkip, specialPowersForPrefs) { + const OMTAPrefKey = "layers.offmainthreadcomposition.async-animations"; + var utils = SpecialPowers.DOMWindowUtils; + if (!specialPowersForPrefs) { + specialPowersForPrefs = SpecialPowers; + } + var expectOMTA = utils.layerManagerRemote && + // ^ Off-main thread animation cannot be used if off-main + // thread composition (OMTC) is not available + specialPowersForPrefs.getBoolPref(OMTAPrefKey); + + isOMTAWorking().then(function(isWorking) { + if (expectOMTA) { + if (isWorking) { + aTestFunction(); + } else { + // We only call this when we know it will fail as otherwise in the + // regular success case we will end up inflating the "passed tests" + // count by 1 + ok(isWorking, "OMTA should work"); + aOnSkip(); + } + } else { + todo(isWorking, + "OMTA should ideally work, though we don't expect it to work on " + + "this platform/configuration"); + aOnSkip(); + } + }).catch(function(err) { + ok(false, err); + aOnSkip(); + }); + + function isOMTAWorking() { + // Create keyframes rule + const animationName = "a6ce3091ed85"; // Random name to avoid clashes + var ruleText = "@keyframes " + animationName + + " { from { opacity: 0.5 } to { opacity: 0.5 } }"; + var style = document.createElement("style"); + style.appendChild(document.createTextNode(ruleText)); + document.head.appendChild(style); + + // Create animation target + var div = document.createElement("div"); + document.body.appendChild(div); + + // Give the target geometry so it is eligible for layerization + div.style.width = "100px"; + div.style.height = "100px"; + div.style.backgroundColor = "white"; + + // Common clean up code + var cleanUp = function() { + div.parentNode.removeChild(div); + style.parentNode.removeChild(style); + if (utils.isTestControllingRefreshes) { + utils.restoreNormalRefresh(); + } + }; + + return waitForDocumentLoad() + .then(loadPaintListener) + .then(function() { + // Put refresh driver under test control and trigger animation + utils.advanceTimeAndRefresh(0); + div.style.animation = animationName + " 10s"; + + // Trigger style flush + div.clientTop; + return waitForPaints(); + }).then(function() { + var opacity = utils.getOMTAStyle(div, "opacity"); + cleanUp(); + return Promise.resolve(opacity == 0.5); + }).catch(function(err) { + cleanUp(); + return Promise.reject(err); + }); + } + + function waitForDocumentLoad() { + return new Promise(function(resolve, reject) { + if (document.readyState === "complete") { + resolve(); + } else { + window.addEventListener("load", resolve); + } + }); + } + + function waitForPaints() { + return new Promise(function(resolve, reject) { + waitForAllPaintsFlushed(resolve); + }); + } + + function loadPaintListener() { + return new Promise(function(resolve, reject) { + if (typeof(window.waitForAllPaints) !== "function") { + var script = document.createElement("script"); + script.onload = resolve; + script.onerror = function() { + reject(new Error("Failed to load paint listener")); + }; + script.src = "/tests/SimpleTest/paint_listener.js"; + var firstScript = document.scripts[0]; + firstScript.parentNode.insertBefore(script, firstScript); + } else { + resolve(); + } + }); + } +} + +// Common architecture for setting up a series of asynchronous animation tests +// +// Usage example: +// +// addAsyncAnimTest(function *() { +// .. do work .. +// yield functionThatReturnsAPromise(); +// .. do work .. +// }); +// runAllAsyncAnimTests().then(SimpleTest.finish()); +// +(function() { + var tests = []; + + window.addAsyncAnimTest = function(generator) { + tests.push(generator); + }; + + // Returns a promise when all tests have run + window.runAllAsyncAnimTests = function(aOnAbort) { + // runAsyncAnimTest returns a Promise that is resolved when the + // test is finished so we can chain them together + return tests.reduce(function(sequence, test) { + return sequence.then(function() { + return runAsyncAnimTest(test, aOnAbort); + }); + }, Promise.resolve() /* the start of the sequence */); + }; + + // Takes a generator function that represents a test case. Each point in the + // test case that waits asynchronously for some result yields a Promise that + // is resolved when the asynchronous action has completed. By chaining these + // intermediate results together we run the test to completion. + // + // This method itself returns a Promise that is resolved when the generator + // function has completed. + // + // This arrangement is based on add_task() which is currently only available + // in mochitest-chrome (bug 872229). If add_task becomes available in + // mochitest-plain, we can remove this function and use add_task instead. + function runAsyncAnimTest(aTestFunc, aOnAbort) { + var generator; + + function step(arg) { + var next; + try { + next = generator.next(arg); + } catch (e) { + return Promise.reject(e); + } + if (next.done) { + return Promise.resolve(next.value); + } else { + return Promise.resolve(next.value) + .then(step, function(err) { throw err; }); + } + } + + // Put refresh driver under test control + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0); + + // Run test + generator = aTestFunc(); + return step() + .catch(function(err) { + ok(false, err.message); + if (typeof aOnAbort == "function") { + aOnAbort(); + } + }).then(function() { + // Restore clock + SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + }); + } +})(); + +//---------------------------------------------------------------------- +// +// Helper functions for testing animated values on the compositor +// +//---------------------------------------------------------------------- + +const RunningOn = { + MainThread: 0, + Compositor: 1, + Either: 2, + TodoMainThread: 3 +}; + +const ExpectComparisonTo = { + Pass: 1, + Fail: 2 +}; + +(function() { + window.omta_todo_is = function(elem, property, expected, runningOn, desc, + pseudo) { + return omta_is_approx(elem, property, expected, 0, runningOn, desc, + ExpectComparisonTo.Fail, pseudo); + }; + + window.omta_is = function(elem, property, expected, runningOn, desc, + pseudo) { + return omta_is_approx(elem, property, expected, 0, runningOn, desc, + ExpectComparisonTo.Pass, pseudo); + }; + + // Many callers of this method will pass 'undefined' for + // expectedComparisonResult. + window.omta_is_approx = function(elem, property, expected, tolerance, + runningOn, desc, expectedComparisonResult, + pseudo) { + // Check input + const omtaProperties = [ "transform", "opacity" ]; + if (omtaProperties.indexOf(property) === -1) { + ok(false, property + " is not an OMTA property"); + return; + } + var isTransform = property == "transform"; + var normalize = isTransform ? convertTo3dMatrix : parseFloat; + var compare = isTransform ? + matricesRoughlyEqual : + function(a, b, error) { return Math.abs(a - b) <= error; }; + var normalizedToString = isTransform ? + convert3dMatrixToString : + JSON.stringify; + + // Get actual values + var compositorStr = + SpecialPowers.DOMWindowUtils.getOMTAStyle(elem, property, pseudo); + var computedStr = window.getComputedStyle(elem, pseudo)[property]; + + // Prepare expected value + var expectedValue = normalize(expected); + if (expectedValue === null) { + ok(false, desc + ": test author should provide a valid 'expected' value" + + " - got " + expected.toString()); + return; + } + + // Check expected value appears in the right place + var actualStr; + switch (runningOn) { + case RunningOn.Either: + runningOn = compositorStr !== "" ? + RunningOn.Compositor : + RunningOn.MainThread; + actualStr = compositorStr !== "" ? compositorStr : computedStr; + break; + + case RunningOn.Compositor: + if (compositorStr === "") { + ok(false, desc + ": should be animating on compositor"); + return; + } + actualStr = compositorStr; + break; + + case RunningOn.TodoMainThread: + todo(compositorStr === "", + desc + ": should NOT be animating on compositor"); + actualStr = compositorStr === "" ? computedStr : compositorStr; + break; + + default: + if (compositorStr !== "") { + ok(false, desc + ": should NOT be animating on compositor"); + return; + } + actualStr = computedStr; + break; + } + + var okOrTodo = expectedComparisonResult == ExpectComparisonTo.Fail ? + todo : + ok; + + // Compare animated value with expected + var actualValue = normalize(actualStr); + if (actualValue === null) { + ok(false, desc + ": should return a valid result - got " + actualStr); + return; + } + okOrTodo(compare(expectedValue, actualValue, tolerance), + desc + " - got " + actualStr + ", expected " + + normalizedToString(expectedValue)); + + // For compositor animations do an additional check that they match + // the value calculated on the main thread + if (actualStr === compositorStr) { + var computedValue = normalize(computedStr); + if (computedValue === null) { + ok(false, desc + ": test framework should parse computed style" + + " - got " + computedStr); + return; + } + okOrTodo(compare(computedValue, actualValue, 0), + desc + ": OMTA style and computed style should be equal" + + " - OMTA " + actualStr + ", computed " + computedStr); + } + }; + + window.matricesRoughlyEqual = function(a, b, tolerance) { + tolerance = tolerance || 0.00011; + for (var i = 0; i < 4; i++) { + for (var j = 0; j < 4; j++) { + var diff = Math.abs(a[i][j] - b[i][j]); + if (diff > tolerance || isNaN(diff)) + return false; + } + } + return true; + }; + + // Converts something representing an transform into a 3d matrix in + // column-major order. + // The following are supported: + // "matrix(...)" + // "matrix3d(...)" + // [ 1, 0, 0, ... ] + // { a: 1, ty: 23 } etc. + window.convertTo3dMatrix = function(matrixLike) { + if (typeof(matrixLike) == "string") { + return convertStringTo3dMatrix(matrixLike); + } else if (Array.isArray(matrixLike)) { + return convertArrayTo3dMatrix(matrixLike); + } else if (typeof(matrixLike) == "object") { + return convertObjectTo3dMatrix(matrixLike); + } else { + return null; + } + }; + + // In future most of these methods should be able to be replaced + // with DOMMatrix + window.isInvertible = function(matrix) { + return getDeterminant(matrix) != 0; + }; + + // Converts strings of the format "matrix(...)" and "matrix3d(...)" to a 3d + // matrix + function convertStringTo3dMatrix(str) { + if (str == "none") + return convertArrayTo3dMatrix([1, 0, 0, 1, 0, 0]); + var result = str.match("^matrix(3d)?\\("); + if (result === null) + return null; + + return convertArrayTo3dMatrix( + str.substring(result[0].length, str.length-1) + .split(",") + .map(function(component) { + return Number(component); + }) + ); + } + + // Takes an array of numbers of length 6 (2d matrix) or 16 (3d matrix) + // representing a matrix specified in column-major order and returns a 3d + // matrix represented as an array of arrays + function convertArrayTo3dMatrix(array) { + if (array.length == 6) { + return convertObjectTo3dMatrix( + { a: array[0], b: array[1], + c: array[2], d: array[3], + e: array[4], f: array[5] } ); + } else if (array.length == 16) { + return [ + array.slice(0, 4), + array.slice(4, 8), + array.slice(8, 12), + array.slice(12, 16) + ]; + } else { + return null; + } + } + + // Takes an object of the form { a: 1.1, e: 23 } and builds up a 3d matrix + // with unspecified values filled in with identity values. + function convertObjectTo3dMatrix(obj) { + return [ + [ + obj.a || obj.sx || obj.m11 || 1, + obj.b || obj.m12 || 0, + obj.m13 || 0, + obj.m14 || 0 + ], [ + obj.c || obj.m21 || 0, + obj.d || obj.sy || obj.m22 || 1, + obj.m23 || 0, + obj.m24 || 0 + ], [ + obj.m31 || 0, + obj.m32 || 0, + obj.sz || obj.m33 || 1, + obj.m34 || 0 + ], [ + obj.e || obj.tx || obj.m41 || 0, + obj.f || obj.ty || obj.m42 || 0, + obj.tz || obj.m43 || 0, + obj.m44 || 1 + ] + ]; + } + + function convert3dMatrixToString(matrix) { + if (is2d(matrix)) { + return "matrix(" + + [ matrix[0][0], matrix[0][1], + matrix[1][0], matrix[1][1], + matrix[3][0], matrix[3][1] ].join(", ") + ")"; + } else { + return "matrix3d(" + + matrix.reduce(function(outer, inner) { + return outer.concat(inner); + }).join(", ") + ")"; + } + } + + function is2d(matrix) { + return matrix[0][2] === 0 && matrix[0][3] === 0 && + matrix[1][2] === 0 && matrix[1][3] === 0 && + matrix[2][0] === 0 && matrix[2][1] === 0 && + matrix[2][2] === 1 && matrix[2][3] === 0 && + matrix[3][2] === 0 && matrix[3][3] === 1; + } + + function getDeterminant(matrix) { + if (is2d(matrix)) { + return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]; + } + + return matrix[0][3] * matrix[1][2] * matrix[2][1] * matrix[3][0] + - matrix[0][2] * matrix[1][3] * matrix[2][1] * matrix[3][0] + - matrix[0][3] * matrix[1][1] * matrix[2][2] * matrix[3][0] + + matrix[0][1] * matrix[1][3] * matrix[2][2] * matrix[3][0] + + matrix[0][2] * matrix[1][1] * matrix[2][3] * matrix[3][0] + - matrix[0][1] * matrix[1][2] * matrix[2][3] * matrix[3][0] + - matrix[0][3] * matrix[1][2] * matrix[2][0] * matrix[3][1] + + matrix[0][2] * matrix[1][3] * matrix[2][0] * matrix[3][1] + + matrix[0][3] * matrix[1][0] * matrix[2][2] * matrix[3][1] + - matrix[0][0] * matrix[1][3] * matrix[2][2] * matrix[3][1] + - matrix[0][2] * matrix[1][0] * matrix[2][3] * matrix[3][1] + + matrix[0][0] * matrix[1][2] * matrix[2][3] * matrix[3][1] + + matrix[0][3] * matrix[1][1] * matrix[2][0] * matrix[3][2] + - matrix[0][1] * matrix[1][3] * matrix[2][0] * matrix[3][2] + - matrix[0][3] * matrix[1][0] * matrix[2][1] * matrix[3][2] + + matrix[0][0] * matrix[1][3] * matrix[2][1] * matrix[3][2] + + matrix[0][1] * matrix[1][0] * matrix[2][3] * matrix[3][2] + - matrix[0][0] * matrix[1][1] * matrix[2][3] * matrix[3][2] + - matrix[0][2] * matrix[1][1] * matrix[2][0] * matrix[3][3] + + matrix[0][1] * matrix[1][2] * matrix[2][0] * matrix[3][3] + + matrix[0][2] * matrix[1][0] * matrix[2][1] * matrix[3][3] + - matrix[0][0] * matrix[1][2] * matrix[2][1] * matrix[3][3] + - matrix[0][1] * matrix[1][0] * matrix[2][2] * matrix[3][3] + + matrix[0][0] * matrix[1][1] * matrix[2][2] * matrix[3][3]; + } +})(); + +//---------------------------------------------------------------------- +// +// Promise wrappers for paint_listener.js +// +//---------------------------------------------------------------------- + +// Returns a Promise that resolves once all paints have completed +function waitForPaints() { + return new Promise(function(resolve, reject) { + waitForAllPaints(resolve); + }); +} + +// As with waitForPaints but also flushes pending style changes before waiting +function waitForPaintsFlushed() { + return new Promise(function(resolve, reject) { + waitForAllPaintsFlushed(resolve); + }); +} + +function waitForVisitedLinkColoring(visitedLink, waitProperty, waitValue) { + function checkLink(resolve) { + if (SpecialPowers.DOMWindowUtils + .getVisitedDependentComputedStyle(visitedLink, "", waitProperty) == + waitValue) { + // Our link has been styled as visited. Resolve. + resolve(true); + } else { + // Our link is not yet styled as visited. Poll for completion. + setTimeout(checkLink, 0, resolve); + } + } + return new Promise(function(resolve, reject) { + checkLink(resolve); + }); +} diff --git a/layout/style/test/browser.ini b/layout/style/test/browser.ini new file mode 100644 index 000000000..4e5e47aac --- /dev/null +++ b/layout/style/test/browser.ini @@ -0,0 +1,8 @@ +[DEFAULT] +support-files = + bug453896_iframe.html + media_queries_iframe.html + newtab_share_rule_processors.html + +[browser_bug453896.js] +[browser_newtab_share_rule_processors.js] diff --git a/layout/style/test/browser_bug453896.js b/layout/style/test/browser_bug453896.js new file mode 100644 index 000000000..0084ae33d --- /dev/null +++ b/layout/style/test/browser_bug453896.js @@ -0,0 +1,13 @@ +add_task(function* () { + let uri = getRootDirectory(gTestPath) + "bug453896_iframe.html"; + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: uri + }, function*(browser) { + return ContentTask.spawn(browser, null, function* () { + var fake_window = { ok: ok }; + content.wrappedJSObject.run(fake_window); + }); + }); +}); diff --git a/layout/style/test/browser_newtab_share_rule_processors.js b/layout/style/test/browser_newtab_share_rule_processors.js new file mode 100644 index 000000000..810f5f86e --- /dev/null +++ b/layout/style/test/browser_newtab_share_rule_processors.js @@ -0,0 +1,38 @@ +var theTab; +var theBrowser; + +function listener(evt) { + if (evt.target == theBrowser.contentDocument) { + doTest(); + } +} + +function test() { + waitForExplicitFinish(); + var testURL = getRootDirectory(gTestPath) + "newtab_share_rule_processors.html"; + theTab = gBrowser.addTab(testURL); + theBrowser = gBrowser.getBrowserForTab(theTab); + theBrowser.addEventListener("load", listener, true); +} + +function doTest() { + theBrowser.removeEventListener("load", listener, true); + var winUtils = theBrowser.contentWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + // The initial set of agent-level sheets should have a rule processor that's + // also being used by another document. + ok(winUtils.hasRuleProcessorUsedByMultipleStyleSets(Ci.nsIStyleSheetService.AGENT_SHEET), + "agent sheet rule processor is used by multiple style sets"); + // Document-level sheets currently never get shared rule processors. + ok(!winUtils.hasRuleProcessorUsedByMultipleStyleSets(Ci.nsIStyleSheetService.AUTHOR_SHEET), + "author sheet rule processor is not used by multiple style sets"); + // Adding a unique style sheet to the agent level will cause it to have a + // rule processor that is unique. + theBrowser.contentWindow.wrappedJSObject.addAgentSheet(); + ok(!winUtils.hasRuleProcessorUsedByMultipleStyleSets(Ci.nsIStyleSheetService.AGENT_SHEET), + "agent sheet rule processor is not used by multiple style sets after " + + "having a unique sheet added to it"); + gBrowser.removeTab(theTab); + finish(); +} diff --git a/layout/style/test/bug453896_iframe.html b/layout/style/test/bug453896_iframe.html new file mode 100644 index 000000000..c65388924 --- /dev/null +++ b/layout/style/test/bug453896_iframe.html @@ -0,0 +1,66 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en-US"> +<head> + <title>Bug 453896 Test middle frame</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <meta http-equiv="Content-Style-Type" content="text/css"> + <script type="application/javascript; version=1.7"> + +function run(test_window) +{ + var subdoc = document.getElementById("subdoc").contentDocument; + var subwin = document.getElementById("subdoc").contentWindow; + var style = subdoc.getElementById("style"); + var iframe_style = document.getElementById("subdoc").style; + var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body, ""); + + function query_applies(q) { + style.setAttribute("media", q); + return body_cs.getPropertyValue("text-decoration") == "underline"; + } + + function should_apply(q) { + test_window.ok(query_applies(q), q + " should apply"); + } + + function should_not_apply(q) { + test_window.ok(!query_applies(q), q + " should not apply"); + } + + // in this test, assume the common underlying implementation is correct + let width_val = 157; // pick two not-too-round numbers + let height_val = 182; + iframe_style.width = width_val + "px"; + iframe_style.height = height_val + "px"; + for (let [feature, value] of + Object.entries({ "width": width_val, "height": height_val })) { + should_apply("all and (" + feature + ": " + value + "px)"); + should_not_apply("all and (" + feature + ": " + (value + 1) + "px)"); + should_not_apply("all and (" + feature + ": " + (value - 1) + "px)"); + } + + iframe_style.width = "0"; + should_apply("all and (height)"); + should_not_apply("all and (width)"); + iframe_style.height = "0"; + should_not_apply("all and (height)"); + should_not_apply("all and (width)"); + should_apply("all and (device-height)"); + should_apply("all and (device-width)"); + iframe_style.width = width_val + "px"; + should_not_apply("all and (height)"); + should_apply("all and (width)"); + iframe_style.height = height_val + "px"; + should_apply("all and (height)"); + should_apply("all and (width)"); +} + + </script> +</head> +<body> + +<iframe id="subdoc" src="media_queries_iframe.html"></iframe> + +</body> +</html> diff --git a/layout/style/test/bug517224.sjs b/layout/style/test/bug517224.sjs new file mode 100644 index 000000000..5a730b055 --- /dev/null +++ b/layout/style/test/bug517224.sjs @@ -0,0 +1,24 @@ +function handleRequest(request, response) +{ + response.setHeader("Cache-Control", "no-cache", false); + switch (request.queryString) { + case "reset": + response.setHeader("Content-Type", "application/ecmascript", false); + setState("imageloaded", ""); + break; + case "image": + setState("imageloaded", "imageloaded"); + response.setStatusLine("1.1", 302, "Found"); + // redirect to a solid blue image + response.setHeader("Location", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12NgYPgPAAEDAQDZqt2zAAAAAElFTkSuQmCC"); + response.setHeader("Content-Type", "text/plain", false); + break; + case "result": + response.setHeader("Content-Type", "application/ecmascript", false); + var state = getState("imageloaded"); + response.write("is('" + state + + "', '', 'image should not have been loaded')\n"); + response.write("SimpleTest.finish()"); + break; + } +} diff --git a/layout/style/test/bug732209-css.sjs b/layout/style/test/bug732209-css.sjs new file mode 100644 index 000000000..95dc612d5 --- /dev/null +++ b/layout/style/test/bug732209-css.sjs @@ -0,0 +1,19 @@ +function handleRequest(request, response) +{ + // First item will be the ID; other items are optional + var query = request.queryString.split(/&/); + + response.setHeader("Content-Type", "text/css", false); + + if (query.indexOf("cors-anonymous") != -1) { + response.setHeader("Access-Control-Allow-Origin", "*", false); + } else if (query.indexOf("cors-credentials") != -1 && + request.hasHeader("Origin")) { + response.setHeader("Access-Control-Allow-Origin", + request.getHeader("Origin"), false) + response.setHeader("Access-Control-Allow-Credentials", "true", false); + } + + response.write("#" + query[0] + " { color: green !important }" + "\n" + + "#" + query[0] + ".reverse { color: red !important }"); +} diff --git a/layout/style/test/ccd-quirks.html b/layout/style/test/ccd-quirks.html new file mode 100644 index 000000000..f12204a2d --- /dev/null +++ b/layout/style/test/ccd-quirks.html @@ -0,0 +1,124 @@ +<!-- Intentionally in quirks mode. --> +<html><head> +<!-- baseline --> +<style> +body, html { margin: 0; padding: 0; overflow: hidden } +div { + width: 60px; + height: 20px; + position: relative; +} +p { + position: absolute; + top: 2px; left: 2px; + width: 16px; + height: 16px; + margin: 0; + padding: 0; +} +p + p { left: 22px } + +#IA1i, #IA1l, #IA2i, #IA2l, #IB1i, #IB1l, #IB2i, #IB2l, +#IC1i, #IC1l, #IC2i, #IC2l, #ID1i, #ID1l, #ID2i, #ID2l, +#JA1i, #JA1l, #JA2i, #JA2l, #JD1i, #JD1l, #JD2i, #JD2l + { background-color: red } + +#JB1i, #JB1l, #JC1i, #JC1l, +#JB2i, #JB2l, #JC2i, #JC2l + { background-color: lime } + +#IA3i, #IA3l, #IB3i, #IB3l, #IC3i, #IC3l, #ID3i, #ID3l, +#JA3i, #JA3l, #JB3i, #JB3l, #JC3i, #JC3l, #JD3i, #JD3l + { background-color: lime } +</style> + +<!-- @import rules --> +<style> +@import url("ccd.sjs?IA1iq"); +@import url("ccd.sjs?IA2iq"); +@import url("ccd.sjs?IA3iq"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB1iq"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB2iq"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB3iq"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1iq"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2iq"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3iq"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1iq"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2iq"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3iq"); +@import url("ccd.sjs?JA1iq"); +@import url("ccd.sjs?JA2iq"); +@import url("ccd.sjs?JA3iq"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB1iq"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB2iq"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB3iq"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1iq"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2iq"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3iq"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1iq"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2iq"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3iq"); +</style> + +<!-- link directives --> +<link rel="stylesheet" href="ccd.sjs?IA1lq"> +<link rel="stylesheet" href="ccd.sjs?IA2lq"> +<link rel="stylesheet" href="ccd.sjs?IA3lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB1lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB2lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB3lq"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1lq"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2lq"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3lq"> +<link rel="stylesheet" href="ccd.sjs?JA1lq"> +<link rel="stylesheet" href="ccd.sjs?JA2lq"> +<link rel="stylesheet" href="ccd.sjs?JA3lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB1lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB2lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB3lq"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1lq"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2lq"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2lq"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3lq"> + +</head><body> +<div></div> +<div></div> +<div><p id="IA1i"></p><p id="IA1l"></p></div> +<div><p id="IA2i"></p><p id="IA2l"></p></div> +<div><p id="IA3i"></p><p id="IA3l"></p></div> +<div></div> +<div><p id="IB1i"></p><p id="IB1l"></p></div> +<div><p id="IB2i"></p><p id="IB2l"></p></div> +<div><p id="IB3i"></p><p id="IB3l"></p></div> +<div></div> +<div><p id="IC1i"></p><p id="IC1l"></p></div> +<div><p id="IC2i"></p><p id="IC2l"></p></div> +<div><p id="IC3i"></p><p id="IC3l"></p></div> +<div></div> +<div><p id="ID1i"></p><p id="ID1l"></p></div> +<div><p id="ID2i"></p><p id="ID2l"></p></div> +<div><p id="ID3i"></p><p id="ID3l"></p></div> +<div></div> +<div></div> +<div><p id="JA1i"></p><p id="JA1l"></p></div> +<div><p id="JA2i"></p><p id="JA2l"></p></div> +<div><p id="JA3i"></p><p id="JA3l"></p></div> +<div></div> +<div><p id="JB1i"></p><p id="JB1l"></p></div> +<div><p id="JB2i"></p><p id="JB2l"></p></div> +<div><p id="JB3i"></p><p id="JB3l"></p></div> +<div></div> +<div><p id="JC1i"></p><p id="JC1l"></p></div> +<div><p id="JC2i"></p><p id="JC2l"></p></div> +<div><p id="JC3i"></p><p id="JC3l"></p></div> +<div></div> +<div><p id="JD1i"></p><p id="JD1l"></p></div> +<div><p id="JD2i"></p><p id="JD2l"></p></div> +<div><p id="JD3i"></p><p id="JD3l"></p></div> +</body></html> diff --git a/layout/style/test/ccd-standards.html b/layout/style/test/ccd-standards.html new file mode 100644 index 000000000..ae6f443a2 --- /dev/null +++ b/layout/style/test/ccd-standards.html @@ -0,0 +1,123 @@ +<!doctype html> +<html><head> +<!-- baseline --> +<style> +body, html { margin: 0; padding: 0; overflow: hidden } +div { + width: 60px; + height: 20px; + position: relative; +} +p { + position: absolute; + top: 2px; left: 2px; + width: 16px; + height: 16px; + margin: 0; + padding: 0; +} +p + p { left: 22px } + +#IA1i, #IA1l, #IA2i, #IA2l, #IB1i, #IB1l, #IB2i, #IB2l, +#IC1i, #IC1l, #IC2i, #IC2l, #ID1i, #ID1l, #ID2i, #ID2l + { background-color: red } + +#JA1i, #JA1l, #JA2i, #JA2l, #JB1i, #JB1l, #JB2i, #JB2l, +#JC1i, #JC1l, #JC2i, #JC2l, #JD1i, #JD1l, #JD2i, #JD2l + { background-color: lime } + +#IA3i, #IA3l, #IB3i, #IB3l, #IC3i, #IC3l, #ID3i, #ID3l, +#JA3i, #JA3l, #JB3i, #JB3l, #JC3i, #JC3l, #JD3i, #JD3l + { background-color: lime } +</style> + +<!-- @import rules --> +<style> +@import url("ccd.sjs?IA1is"); +@import url("ccd.sjs?IA2is"); +@import url("ccd.sjs?IA3is"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB1is"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB2is"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?IB3is"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1is"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2is"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3is"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1is"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2is"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3is"); +@import url("ccd.sjs?JA1is"); +@import url("ccd.sjs?JA2is"); +@import url("ccd.sjs?JA3is"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB1is"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB2is"); +@import url("http://example.org/tests/layout/style/test/ccd.sjs?JB3is"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1is"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2is"); +@import url("redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3is"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1is"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2is"); +@import url("http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3is"); +</style> + +<!-- link directives --> +<link rel="stylesheet" href="ccd.sjs?IA1ls"> +<link rel="stylesheet" href="ccd.sjs?IA2ls"> +<link rel="stylesheet" href="ccd.sjs?IA3ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB1ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB2ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?IB3ls"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC1ls"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC2ls"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?IC3ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID1ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID2ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?ccd.sjs?ID3ls"> +<link rel="stylesheet" href="ccd.sjs?JA1ls"> +<link rel="stylesheet" href="ccd.sjs?JA2ls"> +<link rel="stylesheet" href="ccd.sjs?JA3ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB1ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB2ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/ccd.sjs?JB3ls"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC1ls"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC2ls"> +<link rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/ccd.sjs?JC3ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD1ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD2ls"> +<link rel="stylesheet" href="http://example.org/tests/layout/style/test/redirect.sjs?http://mochi.test:8888/tests/layout/style/test/ccd.sjs?JD3ls"> + +</head><body> +<div></div> +<div></div> +<div><p id="IA1i"></p><p id="IA1l"></p></div> +<div><p id="IA2i"></p><p id="IA2l"></p></div> +<div><p id="IA3i"></p><p id="IA3l"></p></div> +<div></div> +<div><p id="IB1i"></p><p id="IB1l"></p></div> +<div><p id="IB2i"></p><p id="IB2l"></p></div> +<div><p id="IB3i"></p><p id="IB3l"></p></div> +<div></div> +<div><p id="IC1i"></p><p id="IC1l"></p></div> +<div><p id="IC2i"></p><p id="IC2l"></p></div> +<div><p id="IC3i"></p><p id="IC3l"></p></div> +<div></div> +<div><p id="ID1i"></p><p id="ID1l"></p></div> +<div><p id="ID2i"></p><p id="ID2l"></p></div> +<div><p id="ID3i"></p><p id="ID3l"></p></div> +<div></div> +<div></div> +<div><p id="JA1i"></p><p id="JA1l"></p></div> +<div><p id="JA2i"></p><p id="JA2l"></p></div> +<div><p id="JA3i"></p><p id="JA3l"></p></div> +<div></div> +<div><p id="JB1i"></p><p id="JB1l"></p></div> +<div><p id="JB2i"></p><p id="JB2l"></p></div> +<div><p id="JB3i"></p><p id="JB3l"></p></div> +<div></div> +<div><p id="JC1i"></p><p id="JC1l"></p></div> +<div><p id="JC2i"></p><p id="JC2l"></p></div> +<div><p id="JC3i"></p><p id="JC3l"></p></div> +<div></div> +<div><p id="JD1i"></p><p id="JD1l"></p></div> +<div><p id="JD2i"></p><p id="JD2l"></p></div> +<div><p id="JD3i"></p><p id="JD3l"></p></div> +</body></html> diff --git a/layout/style/test/ccd.sjs b/layout/style/test/ccd.sjs new file mode 100644 index 000000000..058393e82 --- /dev/null +++ b/layout/style/test/ccd.sjs @@ -0,0 +1,73 @@ +const DEBUG_all_valid = false; +const DEBUG_all_stub = false; + +function handleRequest(request, response) +{ + // Decode the query string to know what test we're doing. + + // character 1: 'I' = text/css response, 'J' = text/html response + let responseCSS = (request.queryString[0] == 'I'); + + // character 2: redirection type - we only care about whether we're + // ultimately same-origin with the requesting document ('A', 'D') or + // not ('B', 'C'). + let sameOrigin = (request.queryString[1] == 'A' || + request.queryString[1] == 'D'); + + // character 3: '1' = syntactically valid, '2' = invalid, '3' = http error + let malformed = (request.queryString[2] == '2'); + let httpError = (request.queryString[2] == '3'); + + // character 4: loaded with <link> or @import (no action required) + + // character 5: loading document mode: 'q' = quirks, 's' = standards + let quirksMode = (request.queryString[4] == 'q'); + + // Our response contains a CSS rule that selects an element whose + // ID is the first four characters of the query string. + let selector = '#' + request.queryString.substring(0,4); + + // "Malformed" responses wrap the CSS rule in the construct + // <html>{} ... </html> + // This mimics what the CSS parser might see if an actual HTML + // document were fed to it. Because CSS parsers recover from + // errors by skipping tokens until they find something + // recognizable, a style rule appearing where I wrote '...' above + // will be honored! + let leader = (malformed ? '<html>{}' : ''); + let trailer = (malformed ? '</html>' : ''); + + // Standards mode documents will ignore the style sheet if it is being + // served as text/html (regardless of its contents). Quirks mode + // documents will ignore the style sheet if it is being served as + // text/html _and_ it is not same-origin. Regardless, style sheets + // are ignored if they come as the body of an HTTP error response. + // + // Style sheets that should be ignored paint the element red; those + // that should be honored paint it lime. + let color = ((responseCSS || (quirksMode && sameOrigin)) && !httpError + ? 'lime' : 'red'); + + // For debugging the test itself, we have the capacity to make every style + // sheet well-formed, or every style sheet do nothing. + if (DEBUG_all_valid) { + // In this mode, every test chip should turn blue. + response.setHeader('Content-Type', 'text/css'); + response.write(selector + '{background-color:blue}\n'); + } else if (DEBUG_all_stub) { + // In this mode, every test chip for a case where the true test + // sheet would be honored, should turn red. + response.setHeader('Content-Type', 'text/css'); + response.write(selector + '{}\n'); + } else { + // Normal operation. + if (httpError) + response.setStatusLine(request.httpVersion, 500, + "Internal Server Error"); + response.setHeader('Content-Type', + responseCSS ? 'text/css' : 'text/html'); + response.write(leader + selector + + '{background-color:' + color + '}' + + trailer + '\n'); + } +} diff --git a/layout/style/test/chrome/bug418986-2.js b/layout/style/test/chrome/bug418986-2.js new file mode 100644 index 000000000..4336f4abd --- /dev/null +++ b/layout/style/test/chrome/bug418986-2.js @@ -0,0 +1,314 @@ +// # Bug 418986, part 2. + +/* jshint esnext:true */ +/* jshint loopfunc:true */ +/* global window, screen, ok, SpecialPowers, matchMedia */ + +// Expected values. Format: [name, pref_off_value, pref_on_value] +// If pref_*_value is an array with two values, then we will match +// any value in between those two values. If a value is null, then +// we skip the media query. +var expected_values = [ + ["color", null, 8], + ["color-index", null, 0], + ["aspect-ratio", null, window.innerWidth + "/" + window.innerHeight], + ["device-aspect-ratio", screen.width + "/" + screen.height, + window.innerWidth + "/" + window.innerHeight], + ["device-height", screen.height + "px", window.innerHeight + "px"], + ["device-width", screen.width + "px", window.innerWidth + "px"], + ["grid", null, 0], + ["height", window.innerHeight + "px", window.innerHeight + "px"], + ["monochrome", null, 0], + // Square is defined as portrait: + ["orientation", null, + window.innerWidth > window.innerHeight ? + "landscape" : "portrait"], + ["resolution", null, "96dpi"], + ["resolution", [0.999 * window.devicePixelRatio + "dppx", + 1.001 * window.devicePixelRatio + "dppx"], "1dppx"], + ["width", window.innerWidth + "px", window.innerWidth + "px"], + ["-moz-device-pixel-ratio", window.devicePixelRatio, 1], + ["-moz-device-orientation", screen.width > screen.height ? + "landscape" : "portrait", + window.innerWidth > window.innerHeight ? + "landscape" : "portrait"] +]; + +// These media queries return value 0 or 1 when the pref is off. +// When the pref is on, they should not match. +var suppressed_toggles = [ + "-moz-mac-graphite-theme", + // Not available on most OSs. +// "-moz-maemo-classic", + "-moz-scrollbar-end-backward", + "-moz-scrollbar-end-forward", + "-moz-scrollbar-start-backward", + "-moz-scrollbar-start-forward", + "-moz-scrollbar-thumb-proportional", + "-moz-touch-enabled", + "-moz-windows-compositor", + "-moz-windows-default-theme", + "-moz-windows-glass", +]; + +// Possible values for '-moz-os-version' +var windows_versions = [ + "windows-xp", + "windows-vista", + "windows-win7", + "windows-win8", + "windows-win10", +]; + +// Possible values for '-moz-windows-theme' +var windows_themes = [ + "aero", + "aero-lite", + "luna-blue", + "luna-olive", + "luna-silver", + "royale", + "generic", + "zune" +]; + +// Read the current OS. +var OS = SpecialPowers.Services.appinfo.OS; + +// If we are using Windows, add an extra toggle only +// available on that OS. +if (OS === "WINNT") { + suppressed_toggles.push("-moz-windows-classic"); +} + +// __keyValMatches(key, val)__. +// Runs a media query and returns true if key matches to val. +var keyValMatches = (key, val) => matchMedia("(" + key + ":" + val +")").matches; + +// __testMatch(key, val)__. +// Attempts to run a media query match for the given key and value. +// If value is an array of two elements [min max], then matches any +// value in-between. +var testMatch = function (key, val) { + if (val === null) { + return; + } else if (Array.isArray(val)) { + ok(keyValMatches("min-" + key, val[0]) && keyValMatches("max-" + key, val[1]), + "Expected " + key + " between " + val[0] + " and " + val[1]); + } else { + ok(keyValMatches(key, val), "Expected " + key + ":" + val); + } +}; + +// __testToggles(resisting)__. +// Test whether we are able to match the "toggle" media queries. +var testToggles = function (resisting) { + suppressed_toggles.forEach( + function (key) { + var exists = keyValMatches(key, 0) || keyValMatches(key, 1); + if (resisting) { + ok(!exists, key + " should not exist."); + } else { + ok(exists, key + " should exist."); + } + }); +}; + +// __testWindowsSpecific__. +// Runs a media query on the queryName with the given possible matching values. +var testWindowsSpecific = function (resisting, queryName, possibleValues) { + let foundValue = null; + possibleValues.forEach(function (val) { + if (keyValMatches(queryName, val)) { + foundValue = val; + } + }); + if (resisting) { + ok(!foundValue, queryName + " should have no match"); + } else { + ok(foundValue, foundValue ? ("Match found: '" + queryName + ":" + foundValue + "'") + : "Should have a match for '" + queryName + "'"); + } +}; + +// __generateHtmlLines(resisting)__. +// Create a series of div elements that look like: +// `<div class='spoof' id='resolution'>resolution</div>`, +// where each line corresponds to a different media query. +var generateHtmlLines = function (resisting) { + let lines = ""; + expected_values.forEach( + function ([key, offVal, onVal]) { + let val = resisting ? onVal : offVal; + if (val) { + lines += "<div class='spoof' id='" + key + "'>" + key + "</div>\n"; + } + }); + suppressed_toggles.forEach( + function (key) { + lines += "<div class='suppress' id='" + key + "'>" + key + "</div>\n"; + }); + if (OS === "WINNT") { + lines += "<div class='windows' id='-moz-os-version'>-moz-os-version</div>"; + lines += "<div class='windows' id='-moz-windows-theme'>-moz-windows-theme</div>"; + } + return lines; +}; + +// __cssLine__. +// Creates a line of css that looks something like +// `@media (resolution: 1ppx) { .spoof#resolution { background-color: green; } }`. +var cssLine = function (query, clazz, id, color) { + return "@media " + query + " { ." + clazz + "#" + id + + " { background-color: " + color + "; } }\n"; +}; + +// __constructQuery(key, val)__. +// Creates a CSS media query from key and val. If key is an array of +// two elements, constructs a range query (using min- and max-). +var constructQuery = function (key, val) { + return Array.isArray(val) ? + "(min-" + key + ": " + val[0] + ") and (max-" + key + ": " + val[1] + ")" : + "(" + key + ": " + val + ")"; +}; + +// __mediaQueryCSSLine(key, val, color)__. +// Creates a line containing a CSS media query and a CSS expression. +var mediaQueryCSSLine = function (key, val, color) { + if (val === null) { + return ""; + } + return cssLine(constructQuery(key, val), "spoof", key, color); +}; + +// __suppressedMediaQueryCSSLine(key, color)__. +// Creates a CSS line that matches the existence of a +// media query that is supposed to be suppressed. +var suppressedMediaQueryCSSLine = function (key, color, suppressed) { + let query = "(" + key + ": 0), (" + key + ": 1)"; + return cssLine(query, "suppress", key, color); +}; + +// __generateCSSLines(resisting)__. +// Creates a series of lines of CSS, each of which corresponds to +// a different media query. If the query produces a match to the +// expected value, then the element will be colored green. +var generateCSSLines = function (resisting) { + let lines = ".spoof { background-color: red;}\n"; + expected_values.forEach( + function ([key, offVal, onVal]) { + lines += mediaQueryCSSLine(key, resisting ? onVal : offVal, "green"); + }); + lines += ".suppress { background-color: " + (resisting ? "green" : "red") + ";}\n"; + suppressed_toggles.forEach( + function (key) { + lines += suppressedMediaQueryCSSLine(key, resisting ? "red" : "green"); + }); + if (OS === "WINNT") { + lines += ".windows { background-color: " + (resisting ? "green" : "red") + ";}\n"; + lines += windows_versions.map(val => "(-moz-os-version: " + val + ")").join(", ") + + " { #-moz-os-version { background-color: " + (resisting ? "red" : "green") + ";} }\n"; + lines += windows_themes.map(val => "(-moz-windows-theme: " + val + ")").join(",") + + " { #-moz-windows-theme { background-color: " + (resisting ? "red" : "green") + ";} }\n"; + } + return lines; +}; + +// __green__. +// Returns the computed color style corresponding to green. +var green = (function () { + let temp = document.createElement("span"); + temp.style.backgroundColor = "green"; + return getComputedStyle(temp).backgroundColor; +})(); + +// __testCSS(resisting)__. +// Creates a series of divs and CSS using media queries to set their +// background color. If all media queries match as expected, then +// all divs should have a green background color. +var testCSS = function (resisting) { + document.getElementById("display").innerHTML = generateHtmlLines(resisting); + document.getElementById("test-css").innerHTML = generateCSSLines(resisting); + let cssTestDivs = document.querySelectorAll(".spoof,.suppress"); + for (let div of cssTestDivs) { + let color = window.getComputedStyle(div).backgroundColor; + ok(color === green, "CSS for '" + div.id + "'"); + } +}; + +// __testOSXFontSmoothing(resisting)__. +// When fingerprinting resistance is enabled, the `getComputedStyle` +// should always return `undefined` for `MozOSXFontSmoothing`. +var testOSXFontSmoothing = function (resisting) { + let div = document.createElement("div"); + div.style.MozOsxFontSmoothing = "unset"; + let readBack = window.getComputedStyle(div).MozOsxFontSmoothing; + let smoothingPref = SpecialPowers.getBoolPref("layout.css.osx-font-smoothing.enabled", false); + is(readBack, resisting ? "" : (smoothingPref ? "auto" : ""), + "-moz-osx-font-smoothing"); +}; + +// __sleep(timeoutMs)__. +// Returns a promise that resolves after the given timeout. +var sleep = function (timeoutMs) { + return new Promise(function(resolve, reject) { + window.setTimeout(resolve); + }); +}; + +// __testMediaQueriesInPictureElements(resisting)__. +// Test to see if media queries are properly spoofed in picture elements +// when we are resisting fingerprinting. A generator function +// to be used with SpawnTask.js. +var testMediaQueriesInPictureElements = function* (resisting) { + let lines = ""; + for (let [key, offVal, onVal] of expected_values) { + let expected = resisting ? onVal : offVal; + if (expected) { + let query = constructQuery(key, expected); + lines += "<picture>\n"; + lines += " <source srcset='/tests/layout/style/test/chrome/match.png' media='" + query + "' />\n"; + lines += " <img title='" + key + ":" + expected + "' class='testImage' src='/tests/layout/style/test/chrome/mismatch.png' alt='" + key + "' />\n"; + lines += "</picture><br/>\n"; + } + } + document.getElementById("pictures").innerHTML = lines; + var testImages = document.getElementsByClassName("testImage"); + yield sleep(0); + for (let testImage of testImages) { + ok(testImage.currentSrc.endsWith("/match.png"), "Media query '" + testImage.title + "' in picture should match."); + } +}; + +// __pushPref(key, value)__. +// Set a pref value asynchronously, returning a promise that resolves +// when it succeeds. +var pushPref = function (key, value) { + return new Promise(function(resolve, reject) { + SpecialPowers.pushPrefEnv({"set": [[key, value]]}, resolve); + }); +}; + +// __test(isContent)__. +// Run all tests. A generator function to be used +// with SpawnTask.js. +var test = function* (isContent) { + for (prefValue of [false, true]) { + yield pushPref("privacy.resistFingerprinting", prefValue); + let resisting = prefValue && isContent; + expected_values.forEach( + function ([key, offVal, onVal]) { + testMatch(key, resisting ? onVal : offVal); + }); + testToggles(resisting); + if (OS === "WINNT") { + testWindowsSpecific(resisting, "-moz-os-version", windows_versions); + testWindowsSpecific(resisting, "-moz-windows-theme", windows_themes); + } + testCSS(resisting); + if (OS === "Darwin") { + testOSXFontSmoothing(resisting); + } + yield testMediaQueriesInPictureElements(resisting); + } +}; diff --git a/layout/style/test/chrome/bug535806-css.css b/layout/style/test/chrome/bug535806-css.css new file mode 100644 index 000000000..bda339f77 --- /dev/null +++ b/layout/style/test/chrome/bug535806-css.css @@ -0,0 +1 @@ +fooBar[fooBar] { color: green; } diff --git a/layout/style/test/chrome/bug535806-html.html b/layout/style/test/chrome/bug535806-html.html new file mode 100644 index 000000000..e4395da3f --- /dev/null +++ b/layout/style/test/chrome/bug535806-html.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" type="text/css" href="bug535806-css.css"> + </head> + <body onload="window.parent.wrappedJSObject.htmlLoaded()"> + </body> +</html> diff --git a/layout/style/test/chrome/bug535806-xul.xul b/layout/style/test/chrome/bug535806-xul.xul new file mode 100644 index 000000000..3d9a82b91 --- /dev/null +++ b/layout/style/test/chrome/bug535806-xul.xul @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="data:text/css,fooBar{color:red;}"?> +<?xml-stylesheet type="text/css" href="bug535806-css.css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="window.parent.wrappedJSObject.xulLoaded()"> + <fooBar fooBar="" id="s"/> +</window> diff --git a/layout/style/test/chrome/chrome.ini b/layout/style/test/chrome/chrome.ini new file mode 100644 index 000000000..e34fce671 --- /dev/null +++ b/layout/style/test/chrome/chrome.ini @@ -0,0 +1,21 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + bug418986-2.js + bug535806-css.css + bug535806-html.html + bug535806-xul.xul + hover_helper.html + match.png + mismatch.png + +[test_author_specified_style.html] +[test_bug418986-2.xul] +[test_bug1157097.html] +[test_bug1160724.xul] +[test_bug535806.xul] +[test_display_mode.html] +[test_display_mode_reflow.html] +tags = fullscreen +[test_hover.html] +[test_moz_document_rules.html] diff --git a/layout/style/test/chrome/hover_empty.html b/layout/style/test/chrome/hover_empty.html new file mode 100644 index 000000000..7879e1ce9 --- /dev/null +++ b/layout/style/test/chrome/hover_empty.html @@ -0,0 +1,4 @@ +<html> +<body> +</body> +</html> diff --git a/layout/style/test/chrome/hover_helper.html b/layout/style/test/chrome/hover_helper.html new file mode 100644 index 000000000..37e50f69e --- /dev/null +++ b/layout/style/test/chrome/hover_helper.html @@ -0,0 +1,270 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for :hover</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <style type="text/css"> + + div#one { height: 10px; width: 10px; } + div#one:hover { background: #00f; } + div#one > div { height: 5px; width: 20px; } + div#one > div:hover { background: #f00; } + + div#twoparent { overflow: hidden; height: 20px; } + div#two { width: 10px; height: 10px; } + div#two:hover { margin-left: 5px; background: #0f0; } + div#two + iframe { width: 50px; height: 10px; } + div#two:hover + iframe { width: 100px; } + + </style> +</head> +<!-- need a set timeout because we need things to start after painting suppression ends --> +<body onload="setTimeout(step1, 0)"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<div id="display" style="position: absolute; top: 0; left: 0; width: 300px; height: 300px"> + + <div id="one"><div></div></div> + + <div id="twoparent"> + <div id="two"></div> + <iframe id="twoi" src="hover_empty.html"></iframe> + <div style="width: 5000px; height: 10px;"></div> + </div> + +</div> +<pre id="test"> +<script type="application/javascript"> + +var imports = [ "SimpleTest", "is", "isnot", "ok" ]; +for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; +} + +var div = document.getElementById("display"); +var divtwo = document.getElementById("two"); +var iframe = document.getElementById("twoi"); +var divtwoparent = document.getElementById("twoparent"); + +iframe.contentDocument.open(); +iframe.contentDocument.write("<style type='text/css'>html, body { margin: 0; padding: 0; }<\/style><body>"); +iframe.contentDocument.close(); + +var moveEvent = { type: "mousemove", clickCount: "0" }; + +function setResize(str) { + var handler = function() { + iframe.contentWindow.removeEventListener("resize", arguments.callee, false); + setTimeout(str, 100); + }; + iframe.contentWindow.addEventListener("resize", handler, false); +} + +function step1() { + /** test basic hover **/ + var divone = document.getElementById("one"); + synthesizeMouse(divone, 5, 7, moveEvent, window); + is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)", + ":hover applies"); + is(getComputedStyle(divone.firstChild, "").backgroundColor, "transparent", + ":hover does not apply"); + synthesizeMouse(divone, 5, 2, moveEvent, window); + is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)", + ":hover applies hierarchically"); + is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgb(255, 0, 0)", + ":hover applies"); + synthesizeMouse(divone, 15, 7, moveEvent, window); + is(getComputedStyle(divone, "").backgroundColor, "transparent", + ":hover does not apply"); + is(getComputedStyle(divone.firstChild, "").backgroundColor, "transparent", + ":hover does not apply"); + synthesizeMouse(divone, 15, 2, moveEvent, window); + is(getComputedStyle(divone, "").backgroundColor, "rgb(0, 0, 255)", + ":hover applies hierarchically"); + is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgb(255, 0, 0)", + ":hover applies"); + + /** Test for Bug 302561 **/ + setResize("step2();"); + is(iframe.contentDocument.body.offsetWidth, 50, + ":hover does not apply (iframe body width)"); + synthesizeMouse(divtwoparent, 7, 5, moveEvent, window); + is(iframe.contentDocument.body.offsetWidth, 100, + ":hover applies (iframe body width)"); +} + +var step2called = false; +function step2() { + is(step2called, false, "step2 called only once"); + step2called = true; + is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)", + ":hover applies"); + is(iframe.contentDocument.body.offsetWidth, 100, + ":hover applies (iframe body width)"); + setResize("step3()"); + synthesizeMouse(divtwoparent, 2, 5, moveEvent, window); + is(iframe.contentDocument.body.offsetWidth, 50, + ":hover does not apply (iframe body width)"); +} + +var step3called = false; +function step3() { + is(step3called, false, "step3 called only once"); + step3called = true; + if (getComputedStyle(iframe, "").width == "100px") { + // The two resize events may be coalesced into a single one. + step4(); + return; + } + is(getComputedStyle(divtwo, "").backgroundColor, "transparent", + ":hover does not apply"); + setResize("step4()"); + /* expect to get a second resize from the oscillation */ +} + +var step4called = false; +function step4() { + is(step4called, false, "step4 called only once (more than two cycles of oscillation)"); + if (step4called) + return; + step4called = true; + is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)", + ":hover applies"); + setTimeout(step5, 500); // time to detect oscillations if they exist +} + +var step5called = false; +function step5() { + is(step5called, false, "step5 called only once"); + step5called = true; + setResize("step6()"); + synthesizeMouse(divtwoparent, 25, 5, moveEvent, window); +} + +var step6called = false; +function step6() { + is(step6called, false, "step6 called only once"); + step6called = true; + is(getComputedStyle(divtwo, "").backgroundColor, "transparent", + ":hover does not apply"); + synthesizeMouse(divtwoparent, 2, 5, moveEvent, window); + setTimeout(step7, 500); // time to detect oscillations if they exist +} + +var step7called = false; +function step7() { + is(step7called, false, "step7 called only once (more than two cycles of oscillation)"); + if (step7called) + return; + step7called = true; + is(getComputedStyle(divtwo, "").backgroundColor, "transparent", + ":hover does not apply"); + setTimeout(step8, 500); // time to detect oscillations if they exist +} + +/* test the same case with scrolltop */ + +var step8called = false; +function step8() { + is(step8called, false, "step8 called only once"); + step8called = true; + iframe.contentDocument.body.removeAttribute("onresize"); + /* move the mouse out of the way */ + synthesizeMouse(divtwoparent, 200, 5, moveEvent, window); + divtwoparent.scrollLeft = 5; + setResize("step9()"); + synthesizeMouse(divtwoparent, 2, 5, moveEvent, window); + /* mouse now over 7, 5 */ +} + +var step9called = false; +function step9() { + is(step9called, false, "step9 called only once"); + step9called = true; + is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)", + ":hover applies"); + setResize("step10()"); + divtwoparent.scrollLeft = 0; /* mouse now over 2,5 */ +} + +var step10called = false; +function step10() { + is(step10called, false, "step10 called only once"); + step10called = true; + if (getComputedStyle(iframe, "").width == "100px") { + // The two resize events may be coalesced into a single one. + step11(); + return; + } + is(getComputedStyle(divtwo, "").backgroundColor, "transparent", + ":hover does not apply"); + setResize("step11()"); + /* expect to get a second resize from the oscillation */ +} + +var step11called = false; +function step11() { + is(step11called, false, "step11 called only once (more than two cycles of oscillation)"); + if (step11called) + return; + step11called = true; + is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)", + ":hover applies"); + setTimeout(step12, 500); // time to detect oscillations if they exist +} + +var step12called = false; +function step12() { + is(step12called, false, "step12 called only once"); + step12called = true; + setResize("step13()"); + divtwoparent.scrollLeft = 25; /* mouse now over 27,5 */ +} + +var step13called = false; +function step13() { + is(step13called, false, "step13 called only once"); + step13called = true; + is(getComputedStyle(divtwo, "").backgroundColor, "transparent", + ":hover does not apply"); + setResize("step14()"); + divtwoparent.scrollLeft = 0; /* mouse now over 2,5 */ +} + +var step14called = false; +function step14() { + is(step14called, false, "step14 called only once"); + step14called = true; + if (getComputedStyle(iframe, "").width == "50px") { + // The two resize events may be coalesced into a single one. + step15(); + return; + } + is(getComputedStyle(divtwo, "").backgroundColor, "rgb(0, 255, 0)", + ":hover applies"); + setResize("step15()"); + /* expect to get a second resize from the oscillation */ +} + +var step15called = false; +function step15() { + is(step15called, false, "step15 called only once (more than two cycles of oscillation)"); + if (step15called) + return; + step15called = true; + is(getComputedStyle(divtwo, "").backgroundColor, "transparent", + ":hover does not apply"); + setTimeout(finish, 500); // time to detect oscillations if they exist +} + +function finish() { + document.getElementById("display").style.display = "none"; + + var tester = window.SimpleTest; + window.close(); + tester.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/chrome/match.png b/layout/style/test/chrome/match.png Binary files differnew file mode 100644 index 000000000..d3f299bf5 --- /dev/null +++ b/layout/style/test/chrome/match.png diff --git a/layout/style/test/chrome/mismatch.png b/layout/style/test/chrome/mismatch.png Binary files differnew file mode 100644 index 000000000..8f9da3f00 --- /dev/null +++ b/layout/style/test/chrome/mismatch.png diff --git a/layout/style/test/chrome/moz_document_helper.html b/layout/style/test/chrome/moz_document_helper.html new file mode 100644 index 000000000..8b331b19e --- /dev/null +++ b/layout/style/test/chrome/moz_document_helper.html @@ -0,0 +1,2 @@ +<!DOCTYPE HTML> +<div id="display" style="position: relative"></div> diff --git a/layout/style/test/chrome/test_author_specified_style.html b/layout/style/test/chrome/test_author_specified_style.html new file mode 100644 index 000000000..6b5e4f30e --- /dev/null +++ b/layout/style/test/chrome/test_author_specified_style.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>Test for CSSStyleDeclaration.getAuthoredPropertyValue()</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +<script> +var values = [ + // specified value // returned from getAuthoredPropertyValue() + "#12F", "#12f", + "#1122FF", "#1122ff", + "rgb(10,20,30)", "rgb(10, 20, 30)", + "Rgb(300,20,30)", "rgb(255, 20, 30)", + "rgba(10,20,30,0.250)", "rgba(10, 20, 30, 0.25)", + "OrangeRed", "OrangeRed", + "rgb(10%,25%,99%)", "rgb(10%, 25%, 99%)", + "rgb(6.66667%,0%,0.0%)", "rgb(6.66667%, 0%, 0%)", + "HSL(0,25%,75%)", "hsl(0, 25%, 75%)", + "hsl(60,0%,0%)", "hsl(60, 0%, 0%)", + "hsla(60,50%,50%,0.1250)", "hsla(60, 50%, 50%, 0.125)", + "rgba(0,0,0,0)", "rgba(0, 0, 0, 0)", + "rgba(50,50,50,1)", "rgb(50, 50, 50)", + "rgba(50%,50%,50%,1)", "rgb(50%, 50%, 50%)", + "hsla(0,25%,75%,1)", "hsl(0, 25%, 75%)", +]; + +var properties = [ + // property to test with // fixed suffix to ignore from getAuthoredPropertyValue() + "color", "", + "background-color", "", + "background", " none repeat scroll 0% 0%" +]; + +function runTest() { + var span = document.createElement("span"); + for (var j = 0; j < properties.length; j += 2) { + var propertyName = properties[j]; + var expectedSuffix = properties[j + 1]; + for (var i = 0; i < values.length; i += 2) { + var value = values[i]; + var expected = values[i + 1]; + span.setAttribute("style", propertyName + ": " + value); + is(span.style.getAuthoredPropertyValue(propertyName), expected + expectedSuffix, "specified " + value); + } + } + + // also test a custom property + span.setAttribute("style", "--color: rgb(10%,25%,99%)"); + is(span.style.getAuthoredPropertyValue("--color"), " rgb(10%,25%,99%)", "specified --color"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] }, + runTest); +</script> diff --git a/layout/style/test/chrome/test_bug1157097.html b/layout/style/test/chrome/test_bug1157097.html new file mode 100644 index 000000000..748a9eed2 --- /dev/null +++ b/layout/style/test/chrome/test_bug1157097.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Test for bug 1157097</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +<style> +.blue { color: blue; } +.red { color: red; } +.inline-block { display: inline-block; } +</style> +<body onload=run()> +<p><span id=s1 class=blue><b></b></span><span id=s2 class=red><b></b></span></p> +<script> +var Ci = Components.interfaces; +var windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + +function run() { + windowUtils.postRestyleSelfEvent(document.querySelector("p")); + document.querySelectorAll("span")[0].className = ""; + document.querySelectorAll("b")[0].className = "inline-block"; + document.querySelectorAll("span")[1].className = "blue"; + windowUtils.postRestyleSelfEvent(document.querySelectorAll("b")[1]); + + document.body.offsetTop; + + ok(true, "finished (hopefully we didn't assert)"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +</script> diff --git a/layout/style/test/chrome/test_bug1160724.xul b/layout/style/test/chrome/test_bug1160724.xul new file mode 100644 index 000000000..8a2b48617 --- /dev/null +++ b/layout/style/test/chrome/test_bug1160724.xul @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> + +<?xml-stylesheet href="data:text/css,:root{--test:9px}" type="text/css"?> + +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1160724 +--> +<window title="Mozilla Bug 1160724" onload="test()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1160724" + target="_blank">Mozilla Bug 1160724</a> + </body> + + <script type="application/javascript"> + <![CDATA[ + var errorLogged = false; + const serv = Components.classes["@mozilla.org/consoleservice;1"] + .getService(Components.interfaces.nsIConsoleService); + var listener = { + QueryInterface(iid) { + if (!iid.equals(Components.interfaces.nsISupports) && + !iid.equals(Components.interfaces.nsIConsoleListener)) { + throw Components.results.NS_NOINTERFACE; + } + return this; + }, + + observe(msg) { + if (msg.toString().indexOf("transform") != -1) { + errorLogged = true; + } + } + }; + serv.registerListener(listener); + ]]> + </script> + + <vbox id="w" style="-moz-binding: url(#binding)"> + <vbox id="v" style="display: none; transform: translateY(var(--test));" /> + </vbox> + + <bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="binding"> + <implementation> + <constructor>this.firstChild</constructor> + </implementation> + </binding> + </bindings> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 1160724 **/ + SimpleTest.waitForExplicitFinish(); + + function test() { + var v = document.getElementById("v"); + is(getComputedStyle(v, "").transform, "matrix(1, 0, 0, 1, 0, 9)"); + + // nsIConsoleListeners are notified by a runnable. + setTimeout(() => { + ok(!errorLogged, "Should be no errors"); + serv.unregisterListener(listener); + SimpleTest.finish(); + }) + } + ]]> + </script> +</window> diff --git a/layout/style/test/chrome/test_bug418986-2.xul b/layout/style/test/chrome/test_bug418986-2.xul new file mode 100644 index 000000000..2e5f8e687 --- /dev/null +++ b/layout/style/test/chrome/test_bug418986-2.xul @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=418986 +--> +<window title="Mozilla Bug 418986" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"/> + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <style id="test-css" scoped="true"></style> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986" + target="_blank">Mozilla Bug 418986</a> + <p id="display"></p> + <p id="pictures"></p> + </body> + + <script type="text/javascript;version=1.7" src="bug418986-2.js"></script> + <!-- test code goes here --> + <script type="text/javascript;version=1.7"> + // Run all tests now. + window.onload = function () { + add_task(function* () { + yield test(false); + }); + }; + </script> +</window> diff --git a/layout/style/test/chrome/test_bug535806.xul b/layout/style/test/chrome/test_bug535806.xul new file mode 100644 index 000000000..1c0b45c63 --- /dev/null +++ b/layout/style/test/chrome/test_bug535806.xul @@ -0,0 +1,43 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=535806 +--> +<window title="Mozilla Bug 535806" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=535806" + target="_blank">Mozilla Bug 535806</a> + </body> + + <iframe id="f"/> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 535806 **/ + SimpleTest.waitForExplicitFinish(); + + window.addEventListener("load", function() { + $("f").setAttribute("src", "bug535806-html.html"); + }, false); + + function htmlLoaded() { + $("f").setAttribute("src", "bug535806-xul.xul"); + } + + function xulLoaded() { + var doc = $("f").contentDocument; + is(doc.defaultView.getComputedStyle(doc.getElementById("s"), null).color, + "rgb(0, 128, 0)"); + SimpleTest.finish(); + } + + + ]]> + </script> +</window> diff --git a/layout/style/test/chrome/test_display_mode.html b/layout/style/test/chrome/test_display_mode.html new file mode 100644 index 000000000..244eefea2 --- /dev/null +++ b/layout/style/test/chrome/test_display_mode.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1104916 +--> +<head> + <meta charset="utf-8"> + <title>Test for Display Mode</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +/** Test for Display Mode **/ +SimpleTest.waitForExplicitFinish(); +Components.utils.import("resource://gre/modules/Services.jsm"); + +function waitOneEvent(element, name) { + return new Promise(function(resolve, reject) { + element.addEventListener(name, function listener() { + element.removeEventListener(name, listener); + resolve(); + }); + }); +} + +add_task(function* () { + yield waitOneEvent(window, "load"); + + var iframe = document.getElementById("subdoc"); + var subdoc = iframe.contentDocument; + var style = subdoc.getElementById("style"); + var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body, ""); + var win = Services.wm.getMostRecentWindow("navigator:browser"); + + function queryApplies(q) { + style.setAttribute("media", q); + return bodyComputedStyled.getPropertyValue("text-decoration") == "underline"; + } + + function shouldApply(q) { + ok(queryApplies(q), q + " should apply"); + } + + function shouldNotApply(q) { + ok(!queryApplies(q), q + " should not apply"); + } + + shouldApply("all and (display-mode: browser)"); + shouldNotApply("all and (display-mode: fullscreen)"); + shouldNotApply("all and (display-mode: standalone)"); + shouldNotApply("all and (display-mode: minimal-ui)"); + + // Test entering the OS's fullscreen mode. + var fullScreenEntered = waitOneEvent(win, "sizemodechange"); + synthesizeKey("VK_F11", {}); + yield fullScreenEntered; + shouldApply("all and (display-mode: fullscreen)"); + shouldNotApply("all and (display-mode: browser)"); + var fullScreenExited = waitOneEvent(win, "sizemodechange"); + synthesizeKey("VK_F11", {}); + yield fullScreenExited; + shouldNotApply("all and (display-mode: fullscreen)"); + shouldApply("all and (display-mode: browser)"); + + // Test entering fullscreen through document requestFullScreen. + fullScreenEntered = waitOneEvent(document, "mozfullscreenchange"); + document.body.mozRequestFullScreen(); + yield fullScreenEntered + ok(document.mozFullScreenElement, "window entered fullscreen"); + shouldApply("all and (display-mode: fullscreen)"); + shouldNotApply("all and (display-mode: browser)"); + fullScreenExited = waitOneEvent(document, "mozfullscreenchange"); + document.mozCancelFullScreen(); + yield fullScreenExited; + ok(!document.mozFullScreenElement, "window exited fullscreen"); + shouldNotApply("all and (display-mode: fullscreen)"); + shouldApply("all and (display-mode: browser)"); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1104916">Mozilla Bug 1104916</a> +<iframe id="subdoc" src="http://mochi.test:8888/tests/layout/style/test/media_queries_iframe.html"></iframe> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/chrome/test_display_mode_reflow.html b/layout/style/test/chrome/test_display_mode_reflow.html new file mode 100644 index 000000000..23546578f --- /dev/null +++ b/layout/style/test/chrome/test_display_mode_reflow.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1256084 +--> +<head> + <meta charset="utf-8"> + <title>Test for Display Mode</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +/** Test for Display Mode **/ +SimpleTest.waitForExplicitFinish(); +Components.utils.import("resource://gre/modules/Services.jsm"); + +function waitOneEvent(element, name) { + return new Promise(function(resolve, reject) { + element.addEventListener(name, function listener() { + element.removeEventListener(name, listener); + resolve(); + }); + }); +} + +add_task(function* () { + yield waitOneEvent(window, "load"); + + var iframe = document.getElementById("subdoc"); + var subdoc = iframe.contentDocument; + var style = subdoc.getElementById("style"); + var bodyComputedStyled = subdoc.defaultView.getComputedStyle(subdoc.body, ""); + var win = Services.wm.getMostRecentWindow("navigator:browser"); + + var secondDiv = subdoc.getElementById("b"); + var offsetTop = secondDiv.offsetTop; + + // Test entering the OS's fullscreen mode. + var fullScreenEntered = waitOneEvent(win, "sizemodechange"); + synthesizeKey("VK_F11", {}); + yield fullScreenEntered; + ok(offsetTop !== secondDiv.offsetTop, "offset top changes"); + var fullScreenExited = waitOneEvent(win, "sizemodechange"); + synthesizeKey("VK_F11", {}); + yield fullScreenExited; + ok(offsetTop === secondDiv.offsetTop, "offset top returns to original value"); + + offsetTop = secondDiv.offsetTop; + // Test entering fullscreen through document requestFullScreen. + fullScreenEntered = waitOneEvent(document, "mozfullscreenchange"); + document.body.mozRequestFullScreen(); + yield fullScreenEntered + ok(offsetTop !== secondDiv.offsetTop, "offset top changes"); + fullScreenExited = waitOneEvent(document, "mozfullscreenchange"); + document.mozCancelFullScreen(); + yield fullScreenExited; + ok(offsetTop === secondDiv.offsetTop, "offset top returns to original value"); +}); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1256084">Mozilla Bug 1256084</a> +<iframe id="subdoc" src="http://mochi.test:8888/tests/layout/style/test/display_mode_reflow_iframe.html"></iframe> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/chrome/test_hover.html b/layout/style/test/chrome/test_hover.html new file mode 100644 index 000000000..192562a8b --- /dev/null +++ b/layout/style/test/chrome/test_hover.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for :hover</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body onload="startTest();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<div id="display"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function startTest() { + // Run the test in a separate window so that the parent document doesn't have + // anything that will cause reflows and dispatch synth mouse moves when we don't + // want them and disturb our test. + window.open("hover_helper.html", "hover_helper", "width=200,height=300"); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/chrome/test_moz_document_rules.html b/layout/style/test/chrome/test_moz_document_rules.html new file mode 100644 index 000000000..87fba4055 --- /dev/null +++ b/layout/style/test/chrome/test_moz_document_rules.html @@ -0,0 +1,97 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for @-moz-document rules</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=398962">Mozilla Bug 398962</a> +<iframe id="iframe" src="http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html"></iframe> +<pre id="test"> +<script type="application/javascript; version=1.8"> + +var [gStyleSheetService, gIOService] = (function() { + return [ + Components.classes["@mozilla.org/content/style-sheet-service;1"] + .getService(Components.interfaces.nsIStyleSheetService), + Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService) + ]; +})(); +function set_user_sheet(sheeturi) +{ + var uri = gIOService.newURI(sheeturi, null, null); + gStyleSheetService.loadAndRegisterSheet(uri, gStyleSheetService.USER_SHEET); +} +function remove_user_sheet(sheeturi) +{ + var uri = gIOService.newURI(sheeturi, null, null); + gStyleSheetService.unregisterSheet(uri, gStyleSheetService.USER_SHEET); +} + +function run() +{ + var iframe = document.getElementById("iframe"); + var subdoc = iframe.contentDocument; + var subwin = iframe.contentWindow; + var cs = subwin.getComputedStyle(subdoc.getElementById("display"), ""); + var zIndexCounter = 0; + + function test_document_rule(urltests, shouldapply) + { + var zIndex = ++zIndexCounter; + var encodedRule = encodeURI("@-moz-document " + urltests + " { ") + + "%23" + // encoded hash character for "#display" + encodeURI("display { z-index: " + zIndex + " } }"); + var sheeturi = "data:text/css," + encodedRule; + set_user_sheet(sheeturi); + if (shouldapply) { + is(cs.zIndex, String(zIndex), + "@-moz-document " + urltests + + " should apply to this document"); + } else { + is(cs.zIndex, "auto", + "@-moz-document " + urltests + + " should NOT apply to this document"); + } + remove_user_sheet(sheeturi); + } + + test_document_rule("domain(mochi.test)", true); + test_document_rule("domain(\"mochi.test\")", true); + test_document_rule("domain('mochi.test')", true); + test_document_rule("domain('test')", true); + test_document_rule("domain(.test)", false); + test_document_rule("domain('.test')", false); + test_document_rule("domain('ochi.test')", false); + test_document_rule("domain(ochi.test)", false); + test_document_rule("url-prefix(http://moch)", true); + test_document_rule("url-prefix(http://och)", false); + test_document_rule("url-prefix(http://mochi.test)", true); + test_document_rule("url-prefix(http://mochi.test:88)", true); + test_document_rule("url-prefix(http://mochi.test:8888)", true); + test_document_rule("url-prefix(http://mochi.test:8888/)", true); + test_document_rule("url-prefix('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html')", true); + test_document_rule("url-prefix('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.htmlx')", false); + test_document_rule("url(http://mochi.test:8888/)", false); + test_document_rule("url('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html')", true); + test_document_rule("url('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.htmlx')", false); + test_document_rule("regexp(.*ochi.*)", false); // syntax error + test_document_rule("regexp('.*ochi.*')", true); + test_document_rule("regexp('ochi.*')", false); + test_document_rule("regexp('.*ochi')", false); + test_document_rule("regexp('http:.*ochi.*')", true); + test_document_rule("regexp('http:.*ochi')", false); + test_document_rule("regexp('http:.*oCHi.*')", false); // case sensitive + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/css_properties_like_longhand.js b/layout/style/test/css_properties_like_longhand.js new file mode 100644 index 000000000..60b8fd583 --- /dev/null +++ b/layout/style/test/css_properties_like_longhand.js @@ -0,0 +1,3 @@ +var gShorthandPropertiesLikeLonghand = [ + { name: "overflow", prop: "overflow"}, +]; diff --git a/layout/style/test/descriptor_database.js b/layout/style/test/descriptor_database.js new file mode 100644 index 000000000..da672d03b --- /dev/null +++ b/layout/style/test/descriptor_database.js @@ -0,0 +1,72 @@ +/* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 4 -*- */ +/* vim: set shiftwidth=4 tabstop=4 autoindent cindent noexpandtab: */ +/* 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/. */ + +// Each property has the following fields: +// domProp: The name of the relevant member of nsIDOM[NS]CSS2Properties +// values: Strings that are values for the descriptor and should be accepted. +// invalid_values: Things that are not values for the descriptor and +// should be rejected. + +var gCSSFontFaceDescriptors = { + "font-family": { + domProp: "fontFamily", + values: [ "\"serif\"", "\"cursive\"", "seriff", "Times New Roman", "TimesRoman", "\"Times New Roman\"" ], + /* not clear that the generics are really invalid */ + invalid_values: [ "sans-serif", "Times New Roman, serif", "'Times New Roman', serif", "cursive", "fantasy", "Times )", "Times !", "Times ! foo", "Times ! important" ] + }, + "font-stretch": { + domProp: "fontStretch", + values: [ "normal", "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" ], + invalid_values: [ "wider", "narrower", "normal ! important", "normal )" ] + }, + "font-style": { + domProp: "fontStyle", + values: [ "normal", "italic", "oblique" ], + invalid_values: [] + }, + "font-weight": { + domProp: "fontWeight", + values: [ "normal", "400", "bold", "100", "200", "300", "500", "600", "700", "800", "900" ], + invalid_values: [ "107", "399", "401", "699", "710", "bolder", "lighter" ] + }, + "src": { + domProp: null, + values: [ + "url(404.ttf)", + "url(\"404.eot\")", + "url(\'404.otf\')", + "url(404.ttf) format(\"truetype\")", + "url(404.ttf) format(\"truetype\", \"opentype\")", + "url(404.ttf) format(\"truetype\", \"opentype\"), url(\'404.eot\')", + "local(Times New Roman)", + "local(\'Times New Roman\')", + "local(\"Times New Roman\")", + "local(\"serif\")", + "url(404.ttf) format(\"truetype\", \"unknown\"), local(Times New Roman), url(\'404.eot\')", + ], + invalid_values: [ + "url(404.ttf) format(truetype)", + "url(404.ttf) format(\"truetype\" \"opentype\")", + "url(404.ttf) format(\"truetype\",)", + "local(\"Times New\" Roman)", + "local(serif)", /* is this valid? */ + "url(404.ttf) )", + "url(404.ttf) ) foo", + "url(404.ttf) ! important", + "url(404.ttf) ! hello", + ] + }, + "unicode-range": { + domProp: null, + values: [ "U+0-10FFFF", "U+3-7B3", "U+3??", "U+6A", "U+3????", "U+???", "U+302-302", "U+0-7,U+A-C", "U+100-17F,U+200-17F", "U+3??, U+500-513 ,U+612 , U+4????", "U+1FFF,U+200-27F" ], + invalid_values: [ "U+1????-2????", "U+0-7,A-C", "U+100-17F,200-17F", "U+6A!important", "U+6A)" ] + }, + "font-display": { + domProp: null, + values: [ "auto", "block", "swap", "fallback", "optional" ], + invalid_values: [ "normal", "initial" ] + } +} diff --git a/layout/style/test/display_mode_reflow_iframe.html b/layout/style/test/display_mode_reflow_iframe.html new file mode 100644 index 000000000..c05880ce7 --- /dev/null +++ b/layout/style/test/display_mode_reflow_iframe.html @@ -0,0 +1,23 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en-US"> +<head> + <title>Display Mode Reflow inner frame</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <meta http-equiv="Content-Style-Type" content="text/css"> + <style type="text/css" id="style" media="all"> + div { + border: 2px solid black; + width: 50px; + height: 50px; + } + @media (display-mode: fullscreen) { + #a { height: 100px; } + } + </style> +</head> +<body> + <div id="a"></div> + <div id="b"></div> +</body> +</html> diff --git a/layout/style/test/empty.html b/layout/style/test/empty.html new file mode 100644 index 000000000..734c5a1c0 --- /dev/null +++ b/layout/style/test/empty.html @@ -0,0 +1 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"><html><head><title></title></head><body></body></html>
\ No newline at end of file diff --git a/layout/style/test/file_animations_async_tests.html b/layout/style/test/file_animations_async_tests.html new file mode 100644 index 000000000..9d4dfa1fe --- /dev/null +++ b/layout/style/test/file_animations_async_tests.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1086937</title> + <script> + var is = opener.is.bind(opener); + var ok = opener.ok.bind(opener); + var todo = opener.todo.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> + <script type="application/javascript" src="animation_utils.js"></script> + <style> + /* must use implicit value at one end */ + @keyframes slide-left { from { margin-left: -1000px } } + </style> + <script type="application/javascript"> + + var gDisplay; + + function run() { + gDisplay = document.getElementById("display"); + opener.SimpleTest.executeSoon(test1); + } + + /* + * Bug 1086937 - Animations continue correctly across load of + * downloadable font. + */ + function test1() { + var animdiv = document.createElement("div"); + // Take control of the refresh driver right from the start + advance_clock(0); + animdiv.style.animation = "slide-left 100s linear"; // 10px per second + gDisplay.appendChild(animdiv); + var cs = getComputedStyle(animdiv, ""); + is(cs.marginLeft, "-1000px", "initial value of animation (force flush)"); + advance_clock(1000); + is(cs.marginLeft, "-990px", "value of animation before font load"); + + var font = new FontFace("DownloadedAhem", "url(Ahem.ttf)"); + document.fonts.add(font); + + var fontdiv = document.createElement("div"); + fontdiv.appendChild(document.createTextNode("A")); + fontdiv.style.fontFamily = "DownloadedAhem"; + gDisplay.appendChild(fontdiv); + + font.load().then(function(loadedFace) { + is(cs.marginLeft, "-990px", "value of animation after font load " + + "(clock only advances when we say so)"); + advance_clock(1000); + is(cs.marginLeft, "-980px", + "animation should still be advancing after font load"); + + SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + document.fonts.delete(font); + animdiv.remove(); + fontdiv.remove(); + + finish(); + }); + } + + </script> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a> +<div id="display"></div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/file_animations_effect_timing_duration.html b/layout/style/test/file_animations_effect_timing_duration.html new file mode 100644 index 000000000..545784665 --- /dev/null +++ b/layout/style/test/file_animations_effect_timing_duration.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <style type="text/css"> + @keyframes anim { + 0% { transform: translate(0px) } + 100% { transform: translate(100px) } + } + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> + <script> + var ok = opener.ok.bind(opener); + var is = opener.is.bind(opener); + var todo = opener.todo.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +runOMTATest(function() { + runAllAsyncAnimTests().then(function() { + finish(); + }); +}, finish, opener.SpecialPowers); + +addAsyncAnimTest(function *() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)', easing: "steps(2, start)" }, + { transform: 'translate(100px)' } ], 4000); + yield waitForPaints(); + + advance_clock(500); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, + "Animation is running on compositor"); + animation.effect.timing.duration = 2000; + // Setter of timing option should set up the changes to animations for the + // next layer transaction but it won't schedule a paint immediately so we need + // to tick the refresh driver before we can wait on the next paint. + advance_clock(0); + + yield waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, + "Animation remains on compositor"); + + advance_clock(1000); + omta_is(div, "transform", { tx: 100 }, RunningOn.Compositor, + "Animation is updated on compositor"); + + done_div(); +}); + +addAsyncAnimTest(function *() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)', easing: "steps(2, end)" }, + { transform: 'translate(100px)' } ], 4000); + yield waitForPaints(); + + advance_clock(1000); + animation.effect.timing.duration = 2000; + advance_clock(0); + yield waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, + "Animation is running on compositor"); + done_div(); +}) + +</script> +</body> +</html> diff --git a/layout/style/test/file_animations_effect_timing_enddelay.html b/layout/style/test/file_animations_effect_timing_enddelay.html new file mode 100644 index 000000000..bd7c5084f --- /dev/null +++ b/layout/style/test/file_animations_effect_timing_enddelay.html @@ -0,0 +1,146 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <style type="text/css"> + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> + <script> + var ok = opener.ok.bind(opener); + var is = opener.is.bind(opener); + var todo = opener.todo.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +runOMTATest(function() { + runAllAsyncAnimTests().then(function() { + finish(); + }); +}, finish, opener.SpecialPowers); + +addAsyncAnimTest(function *() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ], + { duration: 1000, fill: 'none' }); + yield waitForPaints(); + + advance_clock(100); + omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor, + "Animation is running on compositor"); + animation.effect.timing.endDelay = 1000; + + yield waitForPaints(); + omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor, + "Animation remains on compositor when endDelay is changed"); + + advance_clock(1000); + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread, + "Animation is updated on main thread"); + + done_div(); +}); + +addAsyncAnimTest(function *() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ], + { duration: 1000, endDelay: -500, fill: 'none' }); + yield waitForPaints(); + + advance_clock(400); + yield waitForPaints(); + omta_is(div, "transform", { tx: 40 }, RunningOn.Compositor, + "Animation is updated on compositor " + + "duration 1000, endDelay -500, fill none, current time 400"); + + advance_clock(100); + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread, + "Animation is updated on main thread " + + "duration 1000, endDelay -500, fill none, current time 500"); + + advance_clock(400); + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread, + "Animation is updated on main thread " + + "duration 1000, endDelay -500, fill none, current time 900"); + + advance_clock(100); + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread, + "Animation is updated on main thread " + + "duration 1000, endDelay -500, fill none, current time 1000"); + + done_div(); +}); + +addAsyncAnimTest(function *() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ], + { duration: 1000, endDelay: 1000, fill: 'forwards' }); + yield waitForPaints(); + + advance_clock(1500); + yield waitForPaints(); + omta_is(div, "transform", { tx: 100 }, RunningOn.MainThread, + "The end delay is performed on the main thread"); + + done_div(); +}); + +addAsyncAnimTest(function *() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ], + { duration: 1000, endDelay: -500, fill: 'forwards' }); + yield waitForPaints(); + + advance_clock(400); + yield waitForPaints(); + omta_is(div, "transform", { tx: 40 }, RunningOn.Compositor, + "Animation is updated on compositor " + + "duration 1000, endDelay -500, fill forwards, current time 400"); + + advance_clock(100); + yield waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread, + "Animation is updated on main thread " + + "duration 1000, endDelay -500, fill forwards, current time 500"); + + advance_clock(400); + yield waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread, + "Animation is updated on main thread " + + "duration 1000, endDelay -500, fill forwards, current time 900"); + + advance_clock(100); + yield waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread, + "Animation is updated on main thread " + + "duration 1000, endDelay -500, fill forwards, current time 1000"); + + done_div(); +}); + +</script> +</body> +</html> diff --git a/layout/style/test/file_animations_effect_timing_iterations.html b/layout/style/test/file_animations_effect_timing_iterations.html new file mode 100644 index 000000000..1c1c63f90 --- /dev/null +++ b/layout/style/test/file_animations_effect_timing_iterations.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <style type="text/css"> + @keyframes anim { + 0% { transform: translate(0px) } + 100% { transform: translate(100px) } + } + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> + <script> + var ok = opener.ok.bind(opener); + var is = opener.is.bind(opener); + var todo = opener.todo.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +runOMTATest(function() { + runAllAsyncAnimTests().then(function() { + finish(); + }); +}, finish, opener.SpecialPowers); + +addAsyncAnimTest(function *() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, + { transform: 'translate(100px)' } ], + { duration: 4000, + iterations: 2 + }); + yield waitForPaints(); + + advance_clock(6000); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, + "Animation is running on compositor"); + animation.effect.timing.iterations = 1; + advance_clock(0); + + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread, + "Animation is on MainThread"); + + animation.effect.timing.iterations = 3; + + advance_clock(0); + yield waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, + "Animation is running again on compositor"); + + done_div(); +}); + +</script> +</body> +</html> diff --git a/layout/style/test/file_animations_iterationstart.html b/layout/style/test/file_animations_iterationstart.html new file mode 100644 index 000000000..d1d8529ce --- /dev/null +++ b/layout/style/test/file_animations_iterationstart.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <style type="text/css"> + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> + <script> + var ok = opener.ok.bind(opener); + var is = opener.is.bind(opener); + var todo = opener.todo.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +runOMTATest(function() { + runAllAsyncAnimTests().then(function() { + finish(); + }); +}, finish, opener.SpecialPowers); + + +addAsyncAnimTest(function *() { + var [ div ] = new_div("test"); + var animation = div.animate( + { transform: ["translate(0px)", "translate(100px)"] }, + { iterationStart: 0.5, duration: 10000, fill: "both"} + ); + yield waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, "Start of Animation"); + + advance_clock(4000); + yield waitForPaints(); + omta_is(div, "transform", { tx: 90 }, RunningOn.Compositor, "40% of Animation"); + + advance_clock(6000); + yield waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread, "End of Animation"); + + done_div(); +}); + +</script> +</body> +</html> diff --git a/layout/style/test/file_animations_pausing.html b/layout/style/test/file_animations_pausing.html new file mode 100644 index 000000000..ce4a639c5 --- /dev/null +++ b/layout/style/test/file_animations_pausing.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <style type="text/css"> + @keyframes anim { + 0% { transform: translate(0px) } + 100% { transform: translate(100px) } + } + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> + <script> + var ok = opener.ok.bind(opener); + var is = opener.is.bind(opener); + var todo = opener.todo.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +runOMTATest(function() { + runAllAsyncAnimTests().then(function() { + finish(); + }); +}, finish, opener.SpecialPowers); + +addAsyncAnimTest(function *() { + var [ div, cs ] = new_div("animation: anim 10s 2 linear alternate"); + + // Animation is initially running on compositor + yield waitForPaintsFlushed(); + advance_clock(1000); + omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor, + "Animation is initally animating on compositor"); + + // pause() means it is no longer on the compositor + var animation = div.getAnimations()[0]; + animation.pause(); + // pause() should set up the changes to animations for the next layer + // transaction but it won't schedule a paint immediately so we need to tick + // the refresh driver before we can wait on the next paint. + advance_clock(0); + yield waitForPaints(); + omta_is(div, "transform", { tx: 10 }, RunningOn.MainThread, + "After pausing, animation is removed from compositor"); + + // Animation remains paused + advance_clock(1000); + omta_is(div, "transform", { tx: 10 }, RunningOn.MainThread, + "Animation remains paused"); + + // play() puts the animation back on the compositor + animation.play(); + // As with pause(), play() will set up pending animations for the next layer + // transaction but won't schedule a paint so we need to tick the refresh + // driver before waiting on the next paint. + advance_clock(0); + yield waitForPaints(); + omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor, + "After playing, animation is sent to compositor"); + + // Where it continues to run + advance_clock(1000); + omta_is(div, "transform", { tx: 20 }, RunningOn.Compositor, + "Animation continues playing on compositor"); + + done_div(); +}); +</script> +</body> +</html> diff --git a/layout/style/test/file_animations_playbackrate.html b/layout/style/test/file_animations_playbackrate.html new file mode 100644 index 000000000..93206594e --- /dev/null +++ b/layout/style/test/file_animations_playbackrate.html @@ -0,0 +1,102 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <style type="text/css"> + @keyframes anim { + 0% { transform: translate(0px) } + 100% { transform: translate(100px) } + } + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> + <script> + var ok = opener.ok.bind(opener); + var is = opener.is.bind(opener); + var todo = opener.todo.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +runOMTATest(function() { + runAllAsyncAnimTests().then(function() { + finish(); + }); +}, finish, opener.SpecialPowers); + +addAsyncAnimTest(function *() { + var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards"); + var animation = div.getAnimations()[0]; + animation.playbackRate = 10; + + advance_clock(300); + + yield waitForPaints(); + omta_is(div, "transform", { tx: 30 }, RunningOn.Compositor, + "at 300ms"); + done_div(); +}); + +addAsyncAnimTest(function *() { + var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards"); + var animation = div.getAnimations()[0]; + advance_clock(300); + yield waitForPaints(); + + animation.playbackRate = 0; + + yield waitForPaintsFlushed(); + + omta_is(div, "transform", { tx: 3 }, RunningOn.MainThread, + "animation with zero playback rate should stay in the " + + "same position and be running on the main thread"); + + done_div(); +}); + +addAsyncAnimTest(function *() { + var [ div, cs ] = new_div("animation: anim 10s 1s"); + var animation = div.getAnimations()[0]; + animation.playbackRate = 0.5; + + advance_clock(2000); // 1s * (1 / playbackRate) + + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.Compositor, + "animation with positive delay and playbackRate > 1 should " + + "start from the initial position at the beginning of the " + + "active duration"); + done_div(); +}); + +addAsyncAnimTest(function *() { + var [ div, cs ] = new_div("animation: anim 10s 1s"); + var animation = div.getAnimations()[0]; + animation.playbackRate = 2.0; + + advance_clock(500); // 1s * (1 / playbackRate) + + yield waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.Compositor, + "animation with positive delay and playbackRate < 1 should " + + "start from the initial position at the beginning of the " + + "active duration"); + done_div(); +}); +</script> +</body> +</html> diff --git a/layout/style/test/file_animations_styles_on_event.html b/layout/style/test/file_animations_styles_on_event.html new file mode 100644 index 000000000..b9cdb430c --- /dev/null +++ b/layout/style/test/file_animations_styles_on_event.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript" + src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" + src="animation_utils.js"></script> + <style type="text/css"> + @keyframes anim { + 0% { transform: translateX(0px) } + 100% { transform: translateX(100px) } + } + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> + <script> + var is = opener.is.bind(opener); + var ok = opener.ok.bind(opener); + var todo = opener.todo.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +window.onload = function () { + // To avoid the effect that newly created element's styles are + // not updated immediately, we need to add an element without + // animation properties first. + var [ div ] = new_div(""); + div.setAttribute("id", "bug1228137"); + + waitForPaints().then(function() { + var initialRect = div.getBoundingClientRect(); + + // Now we can set animation properties. + div.style.animation = "anim 100s linear forwards"; + + div.addEventListener("mousemove", function(event) { + is(event.target.id, "bug1228137", + "The target of the animation should receive the mouse move event " + + "on the position of the animation's effect end."); + done_div(); + finish(); + }, false); + + var animation = div.getAnimations()[0]; + animation.finish(); + + // Mouse over where the animation is positioned at finished state. + // We can't use synthesizeMouse here since synthesizeMouse causes + // layout flush. We need to check the position without explicit flushes. + synthesizeMouseAtPoint(initialRect.left + initialRect.width / 2 + 100, + initialRect.top + initialRect.height / 2, + { type: "mousemove" }, window); + }); +}; +</script> +</body> +</html> diff --git a/layout/style/test/file_animations_with_disabled_properties.html b/layout/style/test/file_animations_with_disabled_properties.html new file mode 100644 index 000000000..4b69c7f60 --- /dev/null +++ b/layout/style/test/file_animations_with_disabled_properties.html @@ -0,0 +1,50 @@ +<!doctype html> +<head> + <meta charset=utf-8> + <style> + @keyframes enabled-and-disabled { + from { + left: 0px; + -webkit-text-fill-color: green; + } + to { + left: 100px; + -webkit-text-fill-color: blue; + } + } + </style> + <script> + var is = opener.is.bind(opener); + var ok = opener.ok.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script> +'use strict'; + +var display = document.getElementById('display'); +display.style.animation = 'enabled-and-disabled 0.01s'; + +var animation = display.getAnimations()[0]; +is(animation.effect.getKeyframes().length, 2, + 'Got two frames on the generated animation'); + +ok(animation.effect.getKeyframes()[0].hasOwnProperty('left'), + 'Enabled property is set on initial keyframe'); +ok(!animation.effect.getKeyframes()[0].hasOwnProperty('webkitTextFillColor'), + 'Disabled property is not set on initial keyframe'); + +ok(animation.effect.getKeyframes()[1].hasOwnProperty('left'), + 'Enabled property is set on final keyframe'); +ok(!animation.effect.getKeyframes()[1].hasOwnProperty('webkitTextFillColor'), + 'Disabled property is not set on final keyframe'); + +finish(); +</script> +</body> diff --git a/layout/style/test/file_bug1055933_circle-xxl.png b/layout/style/test/file_bug1055933_circle-xxl.png Binary files differnew file mode 100644 index 000000000..3223a5690 --- /dev/null +++ b/layout/style/test/file_bug1055933_circle-xxl.png diff --git a/layout/style/test/file_bug1089417_iframe.html b/layout/style/test/file_bug1089417_iframe.html new file mode 100644 index 000000000..95208dbc5 --- /dev/null +++ b/layout/style/test/file_bug1089417_iframe.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1089417</title> + <style> + html { background: red } + @media (min-height: 300px) { html { background: green } } + </style> + <style id="s">/* empty */</style> + <script> + document.getElementById("s").disabled = true; + </script> +</head> +<body> + +</body> +</html> diff --git a/layout/style/test/file_bug645998-1.css b/layout/style/test/file_bug645998-1.css new file mode 100644 index 000000000..328e6ed79 --- /dev/null +++ b/layout/style/test/file_bug645998-1.css @@ -0,0 +1 @@ +@import url("file_bug645998-2.css"); diff --git a/layout/style/test/file_bug645998-2.css b/layout/style/test/file_bug645998-2.css new file mode 100644 index 000000000..2d5edbe21 --- /dev/null +++ b/layout/style/test/file_bug645998-2.css @@ -0,0 +1 @@ +@import url("file_bug645998-1.css"); diff --git a/layout/style/test/file_bug829816.css b/layout/style/test/file_bug829816.css Binary files differnew file mode 100644 index 000000000..8f12ba6f5 --- /dev/null +++ b/layout/style/test/file_bug829816.css diff --git a/layout/style/test/file_font_loading_api_vframe.html b/layout/style/test/file_font_loading_api_vframe.html new file mode 100644 index 000000000..51dbbbee9 --- /dev/null +++ b/layout/style/test/file_font_loading_api_vframe.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<style></style> diff --git a/layout/style/test/file_transitions_replacement_on_busy_frame.html b/layout/style/test/file_transitions_replacement_on_busy_frame.html new file mode 100644 index 000000000..c1678ab31 --- /dev/null +++ b/layout/style/test/file_transitions_replacement_on_busy_frame.html @@ -0,0 +1,93 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1167519 +--> +<head> + <meta charset=utf-8> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <style> + #target { + height: 100px; + width: 100px; + background: green; + transition: transform 100s linear; + } + </style> +</head> +<body> +<div id="target"></div> +<script> +'use strict'; + +var ok = opener.ok.bind(opener); +var isnot = opener.isnot.bind(opener); + +function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); +} + +var OMTAPrefKey = "layers.offmainthreadcomposition.async-animations"; +var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote && + opener.SpecialPowers.getBoolPref(OMTAPrefKey); +window.addEventListener("load", function() { + if (!omtaEnabled) { + ok(true, "Skipping the test since OMTA is disabled"); + finish(); + return; + } + + var div = document.getElementById("target"); + // Start first transition + div.style.transform = "translateX(300px)"; + getComputedStyle(div); + + // Wait for a paint to ensure that the first transition has started. + waitForAllPaints(function() { + var previousPropertyValue; + var previousKeyframeValue; + var anim; + requestAnimationFrame(function() { + // Start second transition + div.style.transform = "translateX(0px)"; + getComputedStyle(div).transform; + + anim = div.getAnimations()[0]; + var properties = SpecialPowers.wrap(anim.effect).getProperties(); + previousPropertyValue = properties[0].values[0].value; + previousKeyframeValue = anim.effect.getKeyframes()[0].transform; + }); + + requestAnimationFrame(function() { + // Tie up main thread for 300ms. In the meantime, the first transition + // will continue running on the compositor. If we don't update the start + // point of the second transition, it will appear to jump when it starts. + var startTime = performance.now(); + while (performance.now() - startTime < 300); + + // Ensure that our paint process has been done. + // Note that requestAnimationFrame is not suitable here since on Android + // there is a case where the paint process has not completed even when the + // requestAnimationFrame callback is run (and it is during the paint + // process that we update the transition start point). + waitForAllPaints(function() { + var properties = SpecialPowers.wrap(anim.effect).getProperties(); + var currentPropertyValue = properties[0].values[0].value; + isnot(currentPropertyValue, previousPropertyValue, + "From value of transition is updated since the moment when " + + "it was generated"); + isnot(anim.effect.getKeyframes()[0].transform, previousKeyframeValue, + "Keyframe value of transition is updated since the moment when " + + "it was generated"); + finish(); + }); + }); + }); +}); + +</script> +</body> +</html> diff --git a/layout/style/test/file_transitions_with_disabled_properties.html b/layout/style/test/file_transitions_with_disabled_properties.html new file mode 100644 index 000000000..75305f09b --- /dev/null +++ b/layout/style/test/file_transitions_with_disabled_properties.html @@ -0,0 +1,46 @@ +<!doctype html> +<head> + <meta charset=utf-8> + <style> + #display { + transition: all 0.01s; + } + </style> + <script> + var ok = opener.ok.bind(opener); + function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); + } + </script> +</head> +<body> +<div id="display"></div> +<script> +'use strict'; + +/* + * This tests for transitions generated on the -webkit-text-fill-color property. + * This property has an initial value of 'currentcolor' so by triggering a + * transition on the 'color' property we also--at least at the point when + * this test was written--trigger a transition on the -webkit-text-fill-color + * property (that behavior may change in bug 1260543). + * + * However, before beginning the test we disable -webkit-text-fill-color by + * setting layout.css.prefixes.webkit to false. This code tests that we don't + * end up triggering a transition on the (disabled) property in that case. + */ + +var display = document.getElementById('display'); +display.style.color = 'green'; + +var transitionedProperties = + display.getAnimations().map(transition => transition.transitionProperty); + +ok(!transitionedProperties.includes('-webkit-text-fill-color'), + 'We should not fire transitions for properties disabled by prefs'); + +finish(); +</script> +</body> diff --git a/layout/style/test/flexbox_layout_testcases.js b/layout/style/test/flexbox_layout_testcases.js new file mode 100644 index 000000000..719583630 --- /dev/null +++ b/layout/style/test/flexbox_layout_testcases.js @@ -0,0 +1,1398 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ + +/* + * This Source Code is subject to the terms of the Mozilla Public License + * version 2.0 (the "License"). You can obtain a copy of the License at + * http://mozilla.org/MPL/2.0/. + */ + +/** + * For the purposes of this test, flex items are specified as a hash with a + * hash-entry for each CSS property that is to be set. In these per-property + * entries, the key is the property-name, and the value can be either of the + * following: + * (a) the property's specified value (which indicates that we don't need to + * bother checking the computed value of this particular property) + * ...OR... + * (b) an array with 2-3 entries... + * [specifiedValue, expectedComputedValue (, epsilon) ] + * ...which indicates that the property's computed value should be + * checked. The array's first entry (for the specified value) may be + * null; this means that no value should be explicitly specified for this + * property. The second entry is the property's expected computed + * value. The third (optional) entry is an epsilon value, which allows for + * fuzzy equality when testing the computed value. + * + * To allow these testcases to be re-used in both horizontal and vertical + * flex containers, we specify "width"/"min-width"/etc. using the aliases + * "_main-size", "_min-main-size", etc. The test code can map these + * placeholder names to their corresponding property-names using the maps + * defined below -- gRowPropertyMapping, gColumnPropertyMapping, etc. + * + * If the testcase needs to customize its flex container at all (e.g. by + * specifying a custom container-size), it can do so by including a hash + * called "container_properties", with propertyName:propertyValue mappings. + * (This hash can use aliased property-names like "_main-size" as well.) + */ + +// The standard main-size we'll use for our flex container when setting up +// the testcases defined below: +var gDefaultFlexContainerSize = "200px"; + +// Left-to-right versions of placeholder property-names used in +// testcases below: +var gRowPropertyMapping = +{ + "_main-size": "width", + "_min-main-size": "min-width", + "_max-main-size": "max-width", + "_border-main-start-width": "border-left-width", + "_border-main-end-width": "border-right-width", + "_padding-main-start": "padding-left", + "_padding-main-end": "padding-right", + "_margin-main-start": "margin-left", + "_margin-main-end": "margin-right" +}; + +// Right-to-left versions of placeholder property-names used in +// testcases below: +var gRowReversePropertyMapping = +{ + "_main-size": "width", + "_min-main-size": "min-width", + "_max-main-size": "max-width", + "_border-main-start-width": "border-right-width", + "_border-main-end-width": "border-left-width", + "_padding-main-start": "padding-right", + "_padding-main-end": "padding-left", + "_margin-main-start": "margin-right", + "_margin-main-end": "margin-left" +}; + +// Top-to-bottom versions of placeholder property-names used in +// testcases below: +var gColumnPropertyMapping = +{ + "_main-size": "height", + "_min-main-size": "min-height", + "_max-main-size": "max-height", + "_border-main-start-width": "border-top-width", + "_border-main-end-width": "border-bottom-width", + "_padding-main-start": "padding-top", + "_padding-main-end": "padding-bottom", + "_margin-main-start": "margin-top", + "_margin-main-end": "margin-bottom" +}; + +// Bottom-to-top versions of placeholder property-names used in +// testcases below: +var gColumnReversePropertyMapping = +{ + "_main-size": "height", + "_min-main-size": "min-height", + "_max-main-size": "max-height", + "_border-main-start-width": "border-bottom-width", + "_border-main-end-width": "border-top-width", + "_padding-main-start": "padding-bottom", + "_padding-main-end": "padding-top", + "_margin-main-start": "margin-bottom", + "_margin-main-end": "margin-top" +}; + +// The list of actual testcase definitions: +var gFlexboxTestcases = +[ + // No flex properties specified --> should just use 'width' for sizing + { + items: + [ + { "_main-size": [ "40px", "40px" ] }, + { "_main-size": [ "65px", "65px" ] }, + ] + }, + // flex-basis is specified: + { + items: + [ + { "flex-basis": "50px", + "_main-size": [ null, "50px" ] + }, + { + "flex-basis": "20px", + "_main-size": [ null, "20px" ] + }, + ] + }, + // flex-basis is *large* -- sum of flex-basis values is > flex container size: + // (w/ 0 flex-shrink so we don't shrink): + { + items: + [ + { + "flex": "0 0 150px", + "_main-size": [ null, "150px" ] + }, + { + "flex": "0 0 90px", + "_main-size": [ null, "90px" ] + }, + ] + }, + // flex-basis is *large* -- each flex-basis value is > flex container size: + // (w/ 0 flex-shrink so we don't shrink): + { + items: + [ + { + "flex": "0 0 250px", + "_main-size": [ null, "250px" ] + }, + { + "flex": "0 0 400px", + "_main-size": [ null, "400px" ] + }, + ] + }, + // flex-basis has percentage value: + { + items: + [ + { + "flex-basis": "30%", + "_main-size": [ null, "60px" ] + }, + { + "flex-basis": "45%", + "_main-size": [ null, "90px" ] + }, + ] + }, + // flex-basis has calc(percentage) value: + { + items: + [ + { + "flex-basis": "calc(20%)", + "_main-size": [ null, "40px" ] + }, + { + "flex-basis": "calc(80%)", + "_main-size": [ null, "160px" ] + }, + ] + }, + // flex-basis has calc(percentage +/- length) value: + { + items: + [ + { + "flex-basis": "calc(10px + 20%)", + "_main-size": [ null, "50px" ] + }, + { + "flex-basis": "calc(60% - 1px)", + "_main-size": [ null, "119px" ] + }, + ] + }, + // flex-grow is specified: + { + items: + [ + { + "flex": "1", + "_main-size": [ null, "60px" ] + }, + { + "flex": "2", + "_main-size": [ null, "120px" ] + }, + { + "flex": "0 20px", + "_main-size": [ null, "20px" ] + } + ] + }, + // Same ratio as prev. testcase; making sure we handle float inaccuracy + { + items: + [ + { + "flex": "100000", + "_main-size": [ null, "60px" ] + }, + { + "flex": "200000", + "_main-size": [ null, "120px" ] + }, + { + "flex": "0.000001 20px", + "_main-size": [ null, "20px" ] + } + ] + }, + // Same ratio as prev. testcase, but with items cycled and w/ + // "flex: none" & explicit size instead of "flex: 0 20px" + { + items: + [ + { + "flex": "none", + "_main-size": [ "20px", "20px" ] + }, + { + "flex": "1", + "_main-size": [ null, "60px" ] + }, + { + "flex": "2", + "_main-size": [ null, "120px" ] + } + ] + }, + + // ...and now with flex-grow:[huge] to be sure we handle infinite float values + // gracefully. + { + items: + [ + { + "flex": "9999999999999999999999999999999999999999999999999999999", + "_main-size": [ null, "200px" ] + }, + ] + }, + { + items: + [ + { + "flex": "9999999999999999999999999999999999999999999999999999999", + "_main-size": [ null, "50px" ] + }, + { + "flex": "9999999999999999999999999999999999999999999999999999999", + "_main-size": [ null, "50px" ] + }, + { + "flex": "9999999999999999999999999999999999999999999999999999999", + "_main-size": [ null, "50px" ] + }, + { + "flex": "9999999999999999999999999999999999999999999999999999999", + "_main-size": [ null, "50px" ] + }, + ] + }, + { + items: + [ + { + "flex": "99999999999999999999999999999999999", + "_main-size": [ null, "50px" ] + }, + { + "flex": "99999999999999999999999999999999999", + "_main-size": [ null, "50px" ] + }, + { + "flex": "99999999999999999999999999999999999", + "_main-size": [ null, "50px" ] + }, + { + "flex": "99999999999999999999999999999999999", + "_main-size": [ null, "50px" ] + }, + ] + }, + + // And now, some testcases to check that we handle float accumulation error + // gracefully. + + // First, a testcase with just a custom-sized huge container, to be sure we'll + // be able to handle content on that scale, in the subsequent more-complex + // testcases: + { + container_properties: + { + "_main-size": "9000000px" + }, + items: + [ + { + "flex": "1", + "_main-size": [ null, "9000000px" ] + }, + ] + }, + // ...and now with two flex items dividing up that container's huge size: + { + container_properties: + { + "_main-size": "9000000px" + }, + items: + [ + { + "flex": "2", + "_main-size": [ null, "6000000px" ] + }, + { + "flex": "1", + "_main-size": [ null, "3000000px" ] + }, + ] + }, + + // OK, now to actually test accumulation error. Below, we have six flex items + // splitting up the container's size, with huge differences between flex + // weights. For simplicity, I've set up the weights so that they sum exactly + // to the container's size in px. So 1 unit of flex *should* get you 1px. + // + // NOTE: The expected computed "_main-size" values for the flex items below + // appear to add up to more than their container's size, which would suggest + // that they overflow their container unnecessarily. But they don't actually + // overflow -- this discrepancy is simply because Gecko's code for reporting + // computed-sizes rounds to 6 significant figures (in particular, the method + // (nsTSubstring_CharT::AppendFloat() does this). Internally, in app-units, + // the child frames' main-sizes add up exactly to the container's main-size, + // as you'd hope & expect. + { + container_properties: + { + "_main-size": "9000000px" + }, + items: + [ + { + "flex": "3000000", + "_main-size": [ null, "3000000px" ] + }, + { + "flex": "1", + "_main-size": [ null, "1px" ] + }, + { + "flex": "1", + "_main-size": [ null, "1px" ] + }, + { + "flex": "2999999", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths & when generating computed value string: + "_main-size": [ null, "3000000px" ] + }, + { + "flex": "2999998", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths & when generating computed value string: + "_main-size": [ null, "3000000px" ] + }, + { + "flex": "1", + "_main-size": [ null, "1px", 0.2 ] + }, + ] + }, + // Same flex items as previous testcase, but now reordered such that the items + // with tiny flex weights are all listed last: + { + container_properties: + { + "_main-size": "9000000px" + }, + items: + [ + { + "flex": "3000000", + "_main-size": [ null, "3000000px" ] + }, + { + "flex": "2999999", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths & when generating computed value string: + "_main-size": [ null, "3000000px" ] + }, + { + "flex": "2999998", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths & when generating computed value string: + "_main-size": [ null, "3000000px" ] + }, + { + "flex": "1", + "_main-size": [ null, "1px", 0.2 ] + }, + { + "flex": "1", + "_main-size": [ null, "1px", 0.2 ] + }, + { + "flex": "1", + "_main-size": [ null, "1px", 0.2 ] + }, + ] + }, + // Same flex items as previous testcase, but now reordered such that the items + // with tiny flex weights are all listed first: + { + container_properties: + { + "_main-size": "9000000px" + }, + items: + [ + { + "flex": "1", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths: + "_main-size": [ null, "1px", 0.2 ] + }, + { + "flex": "1", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths: + "_main-size": [ null, "1px", 0.2 ] + }, + { + "flex": "1", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths: + "_main-size": [ null, "1px", 0.2 ] + }, + { + "flex": "3000000", + "_main-size": [ null, "3000000px" ] + }, + { + "flex": "2999999", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths & when generating computed value string: + "_main-size": [ null, "3000000px" ] + }, + { + "flex": "2999998", + // NOTE: Expected value is off slightly, from float error when + // resolving flexible lengths & when generating computed value string: + "_main-size": [ null, "3000000px" ] + }, + ] + }, + + // Trying "flex: auto" (== "1 1 auto") w/ a mix of flex-grow/flex-basis values + { + items: + [ + { + "flex": "auto", + "_main-size": [ null, "45px" ] + }, + { + "flex": "2", + "_main-size": [ null, "90px" ] + }, + { + "flex": "20px 1 0", + "_main-size": [ null, "65px" ] + } + ] + }, + // Same as previous, but with items cycled & different syntax + { + items: + [ + { + "flex": "20px", + "_main-size": [ null, "65px" ] + }, + { + "flex": "1", + "_main-size": [ null, "45px" ] + }, + { + "flex": "2", + "_main-size": [ null, "90px" ] + } + ] + }, + { + items: + [ + { + "flex": "2", + "_main-size": [ null, "100px" ], + "border": "0px dashed", + "_border-main-start-width": [ "5px", "5px" ], + "_border-main-end-width": [ "15px", "15px" ], + "_margin-main-start": [ "22px", "22px" ], + "_margin-main-end": [ "8px", "8px" ] + }, + { + "flex": "1", + "_main-size": [ null, "50px" ], + "_margin-main-start": [ "auto", "0px" ], + "_padding-main-end": [ "auto", "0px" ], + } + ] + }, + // Test negative flexibility: + + // Basic testcase: just 1 item (relying on initial "flex-shrink: 1") -- + // should shrink to container size. + { + items: + [ + { "_main-size": [ "400px", "200px" ] }, + ], + }, + // ...and now with a "flex" specification and a different flex-shrink value: + { + items: + [ + { + "flex": "4 2 250px", + "_main-size": [ null, "200px" ] + }, + ], + }, + // ...and now with multiple items, which all shrink proportionally (by 50%) + // to fit to the container, since they have the same (initial) flex-shrink val + { + items: + [ + { "_main-size": [ "80px", "40px" ] }, + { "_main-size": [ "40px", "20px" ] }, + { "_main-size": [ "30px", "15px" ] }, + { "_main-size": [ "250px", "125px" ] }, + ] + }, + // ...and now with positive flexibility specified. (should have no effect, so + // everything still shrinks by the same proportion, since the flex-shrink + // values are all the same). + { + items: + [ + { + "flex": "4 3 100px", + "_main-size": [ null, "80px" ] + }, + { + "flex": "5 3 50px", + "_main-size": [ null, "40px" ] + }, + { + "flex": "0 3 100px", + "_main-size": [ null, "80px" ] + } + ] + }, + // ...and now with *different* flex-shrink values: + { + items: + [ + { + "flex": "4 2 50px", + "_main-size": [ null, "30px" ] + }, + { + "flex": "5 3 50px", + "_main-size": [ null, "20px" ] + }, + { + "flex": "0 0 150px", + "_main-size": [ null, "150px" ] + } + ] + }, + // Same ratio as prev. testcase; making sure we handle float inaccuracy + { + items: + [ + { + "flex": "4 20000000 50px", + "_main-size": [ null, "30px" ] + }, + { + "flex": "5 30000000 50px", + "_main-size": [ null, "20px" ] + }, + { + "flex": "0 0.0000001 150px", + "_main-size": [ null, "150px" ] + } + ] + }, + // Another "different flex-shrink values" testcase: + { + items: + [ + { + "flex": "4 2 115px", + "_main-size": [ null, "69px" ] + }, + { + "flex": "5 1 150px", + "_main-size": [ null, "120px" ] + }, + { + "flex": "1 4 30px", + "_main-size": [ null, "6px" ] + }, + { + "flex": "1 0 5px", + "_main-size": [ null, "5px" ] + }, + ] + }, + + // ...and now with min-size (clamping the effects of flex-shrink on one item): + { + items: + [ + { + "flex": "4 5 75px", + "_min-main-size": "50px", + "_main-size": [ null, "50px" ], + }, + { + "flex": "5 5 100px", + "_main-size": [ null, "62.5px" ] + }, + { + "flex": "0 4 125px", + "_main-size": [ null, "87.5px" ] + } + ] + }, + + // Test a min-size that's much larger than initial preferred size, but small + // enough that our flexed size pushes us over it: + { + items: + [ + { + "flex": "auto", + "_min-main-size": "110px", + "_main-size": [ "50px", "125px" ] + }, + { + "flex": "auto", + "_main-size": [ null, "75px" ] + } + ] + }, + + // Test a min-size that's much larger than initial preferred size, and is + // even larger than our positively-flexed size, so that we have to increase it + // (as a 'min violation') after we've flexed. + { + items: + [ + { + "flex": "auto", + "_min-main-size": "150px", + "_main-size": [ "50px", "150px" ] + }, + { + "flex": "auto", + "_main-size": [ null, "50px" ] + } + ] + }, + + // Test min-size on multiple items simultaneously: + { + items: + [ + { + "flex": "auto", + "_min-main-size": "20px", + "_main-size": [ null, "20px" ] + }, + { + "flex": "9 auto", + "_min-main-size": "150px", + "_main-size": [ "50px", "180px" ] + }, + ] + }, + { + items: + [ + { + "flex": "1 1 0px", + "_min-main-size": "90px", + "_main-size": [ null, "90px" ] + }, + { + "flex": "1 1 0px", + "_min-main-size": "80px", + "_main-size": [ null, "80px" ] + }, + { + "flex": "1 1 40px", + "_main-size": [ null, "30px" ] + } + ] + }, + + // Test a case where _min-main-size will be violated on different items in + // successive iterations of the "resolve the flexible lengths" loop + { + items: + [ + { + "flex": "1 2 100px", + "_min-main-size": "90px", + "_main-size": [ null, "90px" ] + }, + { + "flex": "1 1 100px", + "_min-main-size": "70px", + "_main-size": [ null, "70px" ] + }, + { + "flex": "1 1 100px", + "_main-size": [ null, "40px" ] + } + ] + }, + + // Test some cases that have a min-size violation on one item and a + // max-size violation on another: + + // Here, both items initially grow to 100px. That violates both + // items' sizing constraints (it's smaller than the min-size and larger than + // the max-size), so we clamp both of them and sum the clamping-differences: + // + // (130px - 100px) + (50px - 100px) = (30px) + (-50px) = -20px + // + // This sum is negative, so (per spec) we freeze the item that had its + // max-size violated (the second one) and restart the algorithm. This time, + // all the available space (200px - 50px = 150px) goes to the not-yet-frozen + // first item, and that puts it above its min-size, so all is well. + { + items: + [ + { + "flex": "auto", + "_min-main-size": "130px", + "_main-size": [ null, "150px" ] + }, + { + "flex": "auto", + "_max-main-size": "50px", + "_main-size": [ null, "50px" ] + }, + ] + }, + + // As above, both items initially grow to 100px, and that violates both items' + // constraints. However, now the sum of the clamping differences is: + // + // (130px - 100px) + (80px - 100px) = (30px) + (-20px) = 10px + // + // This sum is positive, so (per spec) we freeze the item that had its + // min-size violated (the first one) and restart the algorithm. This time, + // all the available space (200px - 130px = 70px) goes to the not-yet-frozen + // second item, and that puts it below its max-size, so all is well. + { + items: + [ + { + "flex": "auto", + "_min-main-size": "130px", + "_main-size": [ null, "130px" ] + }, + { + "flex": "auto", + "_max-main-size": "80px", + "_main-size": [ null, "70px" ] + }, + ] + }, + + // As above, both items initially grow to 100px, and that violates both items' + // constraints. So we clamp both items and sum the clamping differences to + // see what to do next. The sum is: + // + // (80px - 100px) + (120px - 100px) = (-20px) + (20px) = 0px + // + // Per spec, if the sum is 0, we're done -- we leave both items at their + // clamped sizes. + { + items: + [ + { + "flex": "auto", + "_max-main-size": "80px", + "_main-size": [ null, "80px" ] + }, + { + "flex": "auto", + "_min-main-size": "120px", + "_main-size": [ null, "120px" ] + }, + ] + }, + + // Test cases where flex-grow sums to less than 1: + // =============================================== + // This makes us treat the flexibilities like "fraction of free space" + // instead of weights, so that e.g. a single item with "flex-grow: 0.1" + // will only get 10% of the free space instead of all of the free space. + + // Basic cases where flex-grow sum is less than 1: + { + items: + [ + { + "flex": "0.1 100px", + "_main-size": [ null, "110px" ] // +10% of free space + }, + ] + }, + { + items: + [ + { + "flex": "0.8 0px", + "_main-size": [ null, "160px" ] // +80% of free space + }, + ] + }, + + // ... and now with two flex items: + { + items: + [ + { + "flex": "0.4 70px", + "_main-size": [ null, "110px" ] // +40% of free space + }, + { + "flex": "0.2 30px", + "_main-size": [ null, "50px" ] // +20% of free space + }, + ] + }, + + // ...and now with max-size modifying how much free space one item can take: + { + items: + [ + { + "flex": "0.4 70px", + "_main-size": [ null, "110px" ] // +40% of free space + }, + { + "flex": "0.2 30px", + "_max-main-size": "35px", + "_main-size": [ null, "35px" ] // +20% free space, then clamped + }, + ] + }, + // ...and now with a max-size smaller than our flex-basis: + // (This makes us freeze the second item right away, before we compute + // the initial free space.) + { + items: + [ + { + "flex": "0.4 70px", + "_main-size": [ null, "118px" ] // +40% of 200px-70px-10px + }, + { + "flex": "0.2 30px", + "_max-main-size": "10px", + "_main-size": [ null, "10px" ] // immediately frozen + }, + ] + }, + // ...and now with a max-size and a huge flex-basis, such that we initially + // have negative free space, which makes the "% of [original] free space" + // calculations a bit more subtle. We set the "original free space" after + // we've clamped the second item (the first time the free space is positive). + { + items: + [ + { + "flex": "0.4 70px", + "_main-size": [ null, "118px" ] // +40% of free space _after freezing + // the other item_ + }, + { + "flex": "0.2 150px", + "_max-main-size": "10px", + "_main-size": [ null, "10px" ] // clamped immediately + }, + ] + }, + + // Now with min-size modifying how much free space our items take: + { + items: + [ + { + "flex": "0.4 70px", + "_main-size": [ null, "110px" ] // +40% of free space + }, + { + "flex": "0.2 30px", + "_min-main-size": "70px", + "_main-size": [ null, "70px" ] // +20% free space, then clamped + }, + ] + }, + + // ...and now with a large enough min-size that it prevents the other flex + // item from taking its full desired portion of the original free space: + { + items: + [ + { + "flex": "0.4 70px", + "_main-size": [ null, "80px" ] // (Can't take my full +40% of + // free space due to other item's + // large min-size.) + }, + { + "flex": "0.2 30px", + "_min-main-size": "120px", + "_main-size": [ null, "120px" ] // +20% free space, then clamped + }, + ] + }, + // ...and now with a large-enough min-size that it pushes the other flex item + // to actually shrink a bit (with default "flex-shrink:1"): + { + items: + [ + { + "flex": "0.3 30px", + "_main-size": [ null, "20px" ] // -10px, instead of desired +45px + }, + { + "flex": "0.2 20px", + "_min-main-size": "180px", + "_main-size": [ null, "180px" ] // +160px, instead of desired +30px + }, + ] + }, + + // In this case, the items' flexibilities don't initially sum to < 1, but they + // do after we freeze the third item for violating its max-size. + { + items: + [ + { + "flex": "0.3 30px", + "_main-size": [ null, "75px" ] + // 1st loop: desires (0.3 / 5) * 150px = 9px. Tentatively granted. + // 2nd loop: desires 0.3 * 150px = 45px. Tentatively granted. + // 3rd loop: desires 0.3 * 150px = 45px. Granted +45px. + }, + { + "flex": "0.2 20px", + "_max-main-size": "30px", + "_main-size": [ null, "30px" ] + // First loop: desires (0.2 / 5) * 150px = 6px. Tentatively granted. + // Second loop: desires 0.2 * 150px = 30px. Frozen at +10px. + }, + { + "flex": "4.5 0px", + "_max-main-size": "20px", + "_main-size": [ null, "20px" ] + // First loop: desires (4.5 / 5) * 150px = 135px. Frozen at +20px. + }, + ] + }, + + // Make sure we calculate "original free space" correctly when one of our + // flex items will be clamped right away, due to max-size preventing it from + // growing at all: + { + // Here, the second flex item is effectively inflexible; it's + // immediately frozen at 40px since we're growing & this item's max size + // trivially prevents it from growing. This leaves us with an "original + // free space" of 60px. The first flex item takes half of that, due to + // its flex-grow value of 0.5. + items: + [ + { + "flex": "0.5 100px", + "_main-size": [ null, "130px" ] + }, + { + "flex": "1 98px", + "_max-main-size": "40px", + "_main-size": [ null, "40px" ] + }, + ] + }, + { + // Same as previous example, but with a larger flex-basis on the second + // element (which shouldn't ultimately matter, because its max size clamps + // its size immediately anyway). + items: + [ + { + "flex": "0.5 100px", + "_main-size": [ null, "130px" ] + }, + { + "flex": "1 101px", + "_max-main-size": "40px", + "_main-size": [ null, "40px" ] + }, + ] + }, + + { + // Here, the third flex item is effectively inflexible; it's immediately + // frozen at 0px since we're growing & this item's max size trivially + // prevents it from growing. This leaves us with an "original free space" of + // 100px. The first flex item takes 40px, and the third takes 50px, due to + // their flex values of 0.4 and 0.5. + items: + [ + { + "flex": "0.4 50px", + "_main-size": [ null, "90px" ] + }, + { + "flex": "0.5 50px", + "_main-size": [ null, "100px" ] + }, + { + "flex": "0 90px", + "_max-main-size": "0px", + "_main-size": [ null, "0px" ] + }, + ] + }, + { + // Same as previous example, but with slightly larger flex-grow values on + // the first and second items, which sum to 1.0 and produce slightly larger + // main sizes. This demonstrates that there's no discontinuity between the + // "< 1.0 sum" to ">= 1.0 sum" behavior, in this situation at least. + items: + [ + { + "flex": "0.45 50px", + "_main-size": [ null, "95px" ] + }, + { + "flex": "0.55 50px", + "_main-size": [ null, "105px" ] + }, + { + "flex": "0 90px", + "_max-main-size": "0px", + "_main-size": [ null, "0px" ] + }, + ] + }, + + // Test cases where flex-shrink sums to less than 1: + // ================================================= + // This makes us treat the flexibilities more like "fraction of (negative) + // free space" instead of weights, so that e.g. a single item with + // "flex-shrink: 0.1" will only shrink by 10% of amount that it overflows + // its container by. + // + // It gets a bit more complex when there are multiple flex items, because + // flex-shrink is scaled by the flex-basis before it's used as a weight. But + // even with that scaling, the general principal is that e.g. if the + // flex-shrink values *sum* to 0.6, then the items will collectively only + // shrink by 60% (and hence will still overflow). + + // Basic cases where flex-grow sum is less than 1: + { + items: + [ + { + "flex": "0 0.1 300px", + "_main-size": [ null, "290px" ] // +10% of (negative) free space + }, + ] + }, + { + items: + [ + { + "flex": "0 0.8 400px", + "_main-size": [ null, "240px" ] // +80% of (negative) free space + }, + ] + }, + + // ...now with two flex items, with the same flex-basis value: + { + items: + [ + { + "flex": "0 0.4 150px", + "_main-size": [ null, "110px" ] // +40% of (negative) free space + }, + { + "flex": "0 0.2 150px", + "_main-size": [ null, "130px" ] // +20% of (negative) free space + }, + ] + }, + + // ...now with two flex items, with different flex-basis values (and hence + // differently-scaled flex factors): + { + items: + [ + { + "flex": "0 0.3 100px", + "_main-size": [ null, "76px" ] + }, + { + "flex": "0 0.1 200px", + "_main-size": [ null, "184px" ] + } + ] + // Notes: + // - Free space: -100px + // - Sum of flex-shrink factors: 0.3 + 0.1 = 0.4 + // - Since that sum ^ is < 1, we'll only distribute that fraction of + // the free space. We'll distribute: -100px * 0.4 = -40px + // + // - 1st item's scaled flex factor: 0.3 * 100px = 30 + // - 2nd item's scaled flex factor: 0.1 * 200px = 20 + // - 1st item's share of distributed free space: 30/(30+20) = 60% + // - 2nd item's share of distributed free space: 20/(30+20) = 40% + // + // SO: + // - 1st item gets 60% * -40px = -24px. 100px-24px = 76px + // - 2nd item gets 40% * -40px = -16px. 200px-16px = 184px + }, + + // ...now with min-size modifying how much one item can shrink: + { + items: + [ + { + "flex": "0 0.3 100px", + "_main-size": [ null, "70px" ] + }, + { + "flex": "0 0.1 200px", + "_min-main-size": "190px", + "_main-size": [ null, "190px" ] + } + ] + // Notes: + // - We proceed as in previous testcase, but clamp the second flex item + // at its min main size. + // - After that point, we have a total flex-shrink of = 0.3, so we + // distribute 0.3 * -100px = -30px to the remaining unfrozen flex + // items. Since there's only one unfrozen item left, it gets all of it. + }, + + // ...now with min-size larger than our flex-basis: + // (This makes us freeze the second item right away, before we compute + // the initial free space.) + { + items: + [ + { + "flex": "0 0.3 100px", + "_main-size": [ null, "55px" ] // +30% of 200px-100px-250px + }, + { + "flex": "0 0.1 200px", + "_min-main-size": "250px", + "_main-size": [ null, "250px" ] // immediately frozen + } + ] + // (Same as previous example, except the min-main-size prevents the + // second item from shrinking at all) + }, + + // ...and now with a min-size and a small flex-basis, such that we initially + // have positive free space, which makes the "% of [original] free space" + // calculations a bit more subtle. We set the "original free space" after + // we've clamped the second item (the first time the free space is negative). + { + items: + [ + { + "flex": "0 0.3 100px", + "_main-size": [ null, "70px" ] + }, + { + "flex": "0 0.1 50px", + "_min-main-size": "200px", + "_main-size": [ null, "200px" ] + } + ] + }, + + // Now with max-size making an item shrink more than its flex-shrink value + // calls for: + { + items: + [ + { + "flex": "0 0.3 100px", + "_main-size": [ null, "70px" ] + }, + { + "flex": "0 0.1 200px", + "_max-main-size": "150px", + "_main-size": [ null, "150px" ] + } + ] + // Notes: + // - We proceed as in an earlier testcase, but clamp the second flex item + // at its max main size. + // - After that point, we have a total flex-shrink of = 0.3, so we + // distribute 0.3 * -100px = -30px to the remaining unfrozen flex + // items. Since there's only one unfrozen item left, it gets all of it. + }, + + // ...and now with a small enough max-size that it prevents the other flex + // item from taking its full desired portion of the (negative) original free + // space: + { + items: + [ + { + "flex": "0 0.3 100px", + "_main-size": [ null, "90px" ] + }, + { + "flex": "0 0.1 200px", + "_max-main-size": "110px", + "_main-size": [ null, "110px" ] + } + ] + // Notes: + // - We proceed as in an earlier testcase, but clamp the second flex item + // at its max main size. + // - After that point, we have a total flex-shrink of 0.3, which would + // have us distribute 0.3 * -100px = -30px to the (one) remaining + // unfrozen flex item. But our remaining free space is only -10px at + // that point, so we distribute that instead. + }, + + // ...and now with a small enough max-size that it pushes the other flex item + // to actually grow a bit (with custom "flex-grow: 1" for this testcase): + { + items: + [ + { + "flex": "1 0.3 100px", + "_main-size": [ null, "120px" ] + }, + { + "flex": "1 0.1 200px", + "_max-main-size": "80px", + "_main-size": [ null, "80px" ] + } + ] + }, + + // In this case, the items' flexibilities don't initially sum to < 1, but they + // do after we freeze the third item for violating its min-size. + { + items: + [ + { + "flex": "0 0.3 100px", + "_main-size": [ null, "76px" ] + }, + { + "flex": "0 0.1 150px", + "_main-size": [ null, "138px" ] + }, + { + "flex": "0 0.8 10px", + "_min-main-size": "40px", + "_main-size": [ null, "40px" ] + } + ] + // Notes: + // - We immediately freeze the 3rd item, since we're shrinking and its + // min size obviously prevents it from shrinking at all. This leaves + // 200px - 100px - 150px - 40px = -90px of "initial free space". + // + // - Our remaining flexible items have a total flex-shrink of 0.4, + // so we can distribute a total of 0.4 * -90px = -36px + // + // - We distribute that space using *scaled* flex factors: + // * 1st item's scaled flex factor: 0.3 * 100px = 30 + // * 2nd item's scaled flex factor: 0.1 * 150px = 15 + // ...which means... + // * 1st item's share of distributed free space: 30/(30+15) = 2/3 + // * 2nd item's share of distributed free space: 15/(30+15) = 1/3 + // + // SO: + // - 1st item gets 2/3 * -36px = -24px. 100px - 24px = 76px + // - 2nd item gets 1/3 * -36px = -12px. 150px - 12px = 138px + }, + + // In this case, the items' flexibilities sum to > 1, in part due to an item + // that *can't actually shrink* due to its 0 flex-basis (which gives it a + // "scaled flex factor" of 0). This prevents us from triggering the special + // behavior for flexibilities that sum to less than 1, and as a result, the + // first item ends up absorbing all of the free space. + { + items: + [ + { + "flex": "0 .5 300px", + "_main-size": [ null, "200px" ] + }, + { + "flex": "0 5 0px", + "_main-size": [ null, "0px" ] + } + ] + }, + + // This case is similar to the one above, but with a *barely* nonzero base + // size for the second item. This should produce a result similar to the case + // above. (In particular, we should first distribute a very small amount of + // negative free space to the second item, getting it to approximately zero, + // and distribute the bulk of the negative free space to the first item, + // getting it to approximately 200px.) + { + items: + [ + { + "flex": "0 .5 300px", + "_main-size": [ null, "200px" ] + }, + { + "flex": "0 1 0.01px", + "_main-size": [ null, "0px" ] + } + ] + }, + // This case is similar to the ones above, but now we've increased the + // flex-shrink value on the second-item so that it claims enough of the + // negative free space to go below its min-size (0px). So, it triggers a min + // violation & is frozen. For the loop *after* the min violation, the sum of + // the remaining flex items' flex-shrink values is less than 1, so we trigger + // the special <1 behavior and only distribute half of the remaining + // (negative) free space to the first item (instead of all of it). + { + items: + [ + { + "flex": "0 .5 300px", + "_main-size": [ null, "250px" ] + }, + { + "flex": "0 5 0.01px", + "_main-size": [ null, "0px" ] + } + ] + }, +]; diff --git a/layout/style/test/gen-css-properties.py b/layout/style/test/gen-css-properties.py new file mode 100644 index 000000000..7196c5e71 --- /dev/null +++ b/layout/style/test/gen-css-properties.py @@ -0,0 +1,24 @@ +# 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/. + +from __future__ import print_function + +import os +import sys +import subprocess + +def main(output, css_properties, exe): + # moz.build passes in the exe name without any path, so to run it we need to + # prepend the './' + run_exe = exe if os.path.isabs(exe) else './%s' % exe + + # Use universal_newlines so everything is '\n', which gets converted to + # '\r\n' when writing out the file in Windows. + data = subprocess.check_output([run_exe], universal_newlines=True) + with open(css_properties) as f: + data += f.read() + output.write(data) + +if __name__ == '__main__': + main(sys.stdout, *sys.argv[1:]) diff --git a/layout/style/test/media_queries_dynamic_xbl_binding.xml b/layout/style/test/media_queries_dynamic_xbl_binding.xml new file mode 100644 index 000000000..4b8515539 --- /dev/null +++ b/layout/style/test/media_queries_dynamic_xbl_binding.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="binding"> + <resources> + <stylesheet src="media_queries_dynamic_xbl_style.css" /> + </resources> + <content> + <html:div xmlns:html="http://www.w3.org/1999/xhtml"> + <children/> + </html:div> + </content> + </binding> +</bindings> diff --git a/layout/style/test/media_queries_dynamic_xbl_iframe.html b/layout/style/test/media_queries_dynamic_xbl_iframe.html new file mode 100644 index 000000000..50e8008fa --- /dev/null +++ b/layout/style/test/media_queries_dynamic_xbl_iframe.html @@ -0,0 +1,5 @@ +<!DOCTYPE HTML> +<style type="text/css"> +body { -moz-binding: url(media_queries_dynamic_xbl_binding.xml#binding); } +</style> +<p id="para">Hello</p> diff --git a/layout/style/test/media_queries_dynamic_xbl_style.css b/layout/style/test/media_queries_dynamic_xbl_style.css new file mode 100644 index 000000000..5c99c07ee --- /dev/null +++ b/layout/style/test/media_queries_dynamic_xbl_style.css @@ -0,0 +1,6 @@ +@media (orientation: portrait) { + div { color: purple; } +} +@media (orientation: landscape) { + div { color: blue; } +} diff --git a/layout/style/test/media_queries_iframe.html b/layout/style/test/media_queries_iframe.html new file mode 100644 index 000000000..141ecdcd9 --- /dev/null +++ b/layout/style/test/media_queries_iframe.html @@ -0,0 +1,15 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en-US"> +<head> + <title>Media Queries Test inner frame</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <meta http-equiv="Content-Style-Type" content="text/css"> + <style type="text/css" id="style" media="all"> + body { text-decoration: underline; } + </style> +</head> +<body> + +</body> +</html> diff --git a/layout/style/test/mochitest.ini b/layout/style/test/mochitest.ini new file mode 100644 index 000000000..406c6f901 --- /dev/null +++ b/layout/style/test/mochitest.ini @@ -0,0 +1,312 @@ +[DEFAULT] +support-files = + animation_utils.js + ccd-quirks.html + ccd.sjs + ccd-standards.html + chrome/bug418986-2.js + chrome/match.png + chrome/mismatch.png + descriptor_database.js + display_mode_reflow_iframe.html + empty.html + media_queries_dynamic_xbl_binding.xml + media_queries_dynamic_xbl_iframe.html + media_queries_dynamic_xbl_style.css + media_queries_iframe.html + neverending_font_load.sjs + neverending_stylesheet_load.sjs + post-redirect-1.css + post-redirect-2.css + post-redirect-3.css + property_database.js + redirect.sjs + style_attribute_tests.js + unstyled.css + unstyled-frame.css + unstyled-frame.xml + unstyled.xml + viewport_units_iframe.html + visited_image_loading_frame_empty.html + visited_image_loading_frame.html + visited_image_loading.sjs + visited-lying-inner.html + visited-pref-iframe.html + xbl_bindings.xml + +[test_acid3_test46.html] +[test_addSheet.html] +support-files = additional_sheets_helper.html +[test_additional_sheets.html] +support-files = additional_sheets_helper.html +[test_all_shorthand.html] +[test_animations.html] +skip-if = toolkit == 'android' +[test_animations_async_tests.html] +support-files = ../../reftests/fonts/Ahem.ttf file_animations_async_tests.html +[test_animations_dynamic_changes.html] +[test_animations_effect_timing_duration.html] +support-files = file_animations_effect_timing_duration.html +[test_animations_effect_timing_enddelay.html] +support-files = file_animations_effect_timing_enddelay.html +[test_animations_effect_timing_iterations.html] +support-files = file_animations_effect_timing_iterations.html +[test_animations_event_order.html] +[test_animations_event_handler_attribute.html] +[test_animations_iterationstart.html] +support-files = file_animations_iterationstart.html +[test_animations_omta.html] +[test_animations_omta_start.html] +[test_animations_pausing.html] +support-files = file_animations_pausing.html +[test_animations_playbackrate.html] +support-files = file_animations_playbackrate.html +[test_animations_styles_on_event.html] +support-files = file_animations_styles_on_event.html +[test_animations_with_disabled_properties.html] +support-files = file_animations_with_disabled_properties.html +[test_any_dynamic.html] +[test_at_rule_parse_serialize.html] +[test_attribute_selector_eof_behavior.html] +[test_background_blend_mode.html] +[test_box_size_keywords.html] +[test_bug73586.html] +[test_bug74880.html] +[test_bug98997.html] +[test_bug160403.html] +[test_bug200089.html] +[test_bug221428.html] +[test_bug229915.html] +[test_bug302186.html] +[test_bug319381.html] +[test_bug357614.html] +[test_bug363146.html] +[test_bug372770.html] +[test_bug373293.html] +[test_bug377947.html] +[test_bug379440.html] +skip-if = toolkit == 'android' +[test_bug379741.html] +[test_bug382027.html] +[test_bug383075.html] +[test_bug387615.html] +[test_bug389464.html] +[test_bug391034.html] +[test_bug391221.html] +[test_bug397427.html] +[test_bug399349.html] +[test_bug401046.html] +skip-if = true # Bug 701060 +[test_bug405818.html] +[test_bug412901.html] +skip-if = android_version == '18' # bug 1147986 +[test_bug413958.html] +[test_bug418986-2.html] +[test_bug437915.html] +[test_bug450191.html] +[test_bug453896_deck.html] +support-files = bug453896_iframe.html +[test_bug470769.html] +[test_bug499655.html] +[test_bug499655.xhtml] +[test_bug511909.html] +[test_bug517224.html] +support-files = bug517224.sjs +[test_bug524175.html] +[test_bug525952.html] +[test_bug534804.html] +[test_bug573255.html] +[test_bug580685.html] +[test_bug621351.html] +[test_bug635286.html] +[test_bug652486.html] +[test_bug657143.html] +[test_bug664955.html] +[test_bug667520.html] +[test_bug645998.html] +support-files = file_bug645998-1.css file_bug645998-2.css +[test_bug716226.html] +[test_bug732153.html] +[test_bug732209.html] +support-files = bug732209-css.sjs +[test_bug765590.html] +[test_bug771043.html] +[test_bug795520.html] +[test_bug798567.html] +[test_bug798843_pref.html] +[test_bug829816.html] +[test_bug874919.html] +support-files = file_bug829816.css +[test_bug887741_at-rules_in_declaration_lists.html] +[test_bug892929.html] +[test_bug1055933.html] +support-files = file_bug1055933_circle-xxl.png +[test_bug1089417.html] +support-files = file_bug1089417_iframe.html +[test_bug1112014.html] +[test_bug1203766.html] +[test_bug1232829.html] +[test_bug1292447.html] +[test_cascade.html] +[test_ch_ex_no_infloops.html] +[test_change_hint_optimizations.html] +[test_clip-path_polygon.html] +[test_compute_data_with_start_struct.html] +skip-if = toolkit == 'android' +[test_computed_style.html] +[test_computed_style_min_size_auto.html] +[test_computed_style_no_pseudo.html] +[test_computed_style_prefs.html] +[test_condition_text.html] +[test_condition_text_assignment.html] +[test_contain_formatting_context.html] +[test_counter_descriptor_storage.html] +[test_counter_style.html] +[test_css_cross_domain.html] +skip-if = toolkit == 'android' #bug 536603 +[test_css_eof_handling.html] +[test_css_escape_api.html] +[test_css_function_mismatched_parenthesis.html] +[test_css_loader_crossorigin_data_url.html] +[test_css_supports.html] +[test_css_supports_variables.html] +[test_default_bidi_css.html] +[test_default_computed_style.html] +[test_descriptor_storage.html] +[test_descriptor_syntax_errors.html] +[test_dont_use_document_colors.html] +[test_dynamic_change_causing_reflow.html] +[test_exposed_prop_accessors.html] +[test_extra_inherit_initial.html] +[test_align_justify_computed_values.html] +[test_flexbox_child_display_values.xhtml] +[test_flexbox_flex_grow_and_shrink.html] +[test_flexbox_flex_shorthand.html] +[test_flexbox_layout.html] +support-files = flexbox_layout_testcases.js +[test_flexbox_order.html] +[test_flexbox_order_abspos.html] +[test_flexbox_order_table.html] +[test_flexbox_reflow_counts.html] +[test_font_face_parser.html] +[test_font_family_parsing.html] +[test_font_feature_values_parsing.html] +[test_font_loading_api.html] +support-files = + BitPattern.woff + file_font_loading_api_vframe.html +[test_garbage_at_end_of_declarations.html] +[test_grid_container_shorthands.html] +[test_grid_item_shorthands.html] +[test_grid_shorthand_serialization.html] +[test_grid_computed_values.html] +[test_group_insertRule.html] +[test_hover_quirk.html] +[test_html_attribute_computed_values.html] +[test_ident_escaping.html] +[test_inherit_computation.html] +skip-if = toolkit == 'android' +[test_inherit_storage.html] +[test_initial_computation.html] +skip-if = toolkit == 'android' +[test_initial_storage.html] +[test_keyframes_rules.html] +[test_load_events_on_stylesheets.html] +[test_logical_properties.html] +[test_media_queries.html] +skip-if = android_version == '18' #debug-only failure; timed out #Android 4.3 aws only; bug 1030419 +[test_media_queries_dynamic.html] +[test_media_queries_dynamic_xbl.html] +[test_media_query_list.html] +[test_moz_device_pixel_ratio.html] +[test_namespace_rule.html] +[test_of_type_selectors.xhtml] +[test_page_parser.html] +[test_parse_eof.html] +[test_parse_ident.html] +[test_parse_rule.html] +[test_parse_url.html] +[test_parser_diagnostics_unprintables.html] +[test_pixel_lengths.html] +[test_pointer-events.html] +[test_position_float_display.html] +[test_position_sticky.html] +[test_priority_preservation.html] +[test_property_database.html] +[test_property_syntax_errors.html] +[test_pseudoelement_state.html] +[test_pseudoelement_parsing.html] +[test_redundant_font_download.html] +support-files = redundant_font_download.sjs +[test_rem_unit.html] +[test_restyles_in_smil_animation.html] +[test_root_node_display.html] +[test_rule_insertion.html] +[test_rule_serialization.html] +[test_rules_out_of_sheets.html] +[test_selectors.html] +skip-if = toolkit == 'android' #bug 775227 +[test_selectors_on_anonymous_content.html] +[test_setPropertyWithNull.html] +[test_shorthand_property_getters.html] +[test_specified_value_serialization.html] +[test_style_attribute_quirks.html] +[test_style_attribute_standards.html] +[test_style_struct_copy_constructors.html] +[test_supports_rules.html] +[test_system_font_serialization.html] +[test_text_decoration_shorthands.html] +[test_transitions_and_reframes.html] +[test_transitions_and_restyles.html] +[test_transitions_and_zoom.html] +[test_transitions_cancel_near_end.html] +[test_transitions_computed_values.html] +[test_transitions_computed_value_combinations.html] +[test_transitions_events.html] +[test_transitions.html] +skip-if = (android_version == '18' && debug) # bug 1159532 +[test_transitions_bug537151.html] +[test_transitions_dynamic_changes.html] +[test_transitions_per_property.html] +skip-if = toolkit == 'android' #bug 775227 +[test_transitions_replacement_on_busy_frame.html] +support-files = file_transitions_replacement_on_busy_frame.html +[test_transitions_step_functions.html] +[test_transitions_with_disabled_properties.html] +support-files = file_transitions_with_disabled_properties.html +[test_unclosed_parentheses.html] +[test_unicode_range_loading.html] +support-files = ../../reftests/fonts/markA.woff ../../reftests/fonts/markB.woff ../../reftests/fonts/markC.woff ../../reftests/fonts/markD.woff +[test_units_angle.html] +[test_units_frequency.html] +[test_units_length.html] +[test_units_time.html] +[test_unprefixing_service.html] +support-files = unprefixing_service_iframe.html unprefixing_service_utils.js +[test_unprefixing_service_prefs.html] +support-files = unprefixing_service_iframe.html unprefixing_service_utils.js +[test_value_cloning.html] +skip-if = toolkit == 'android' #bug 775227 +[test_value_computation.html] +skip-if = toolkit == 'android' +[test_value_storage.html] +[test_variable_serialization_computed.html] +[test_variable_serialization_specified.html] +[test_variables.html] +support-files = support/external-variable-url.css +[test_video_object_fit.html] +[test_viewport_units.html] +[test_visited_image_loading.html] +skip-if = toolkit == 'android' #TIMED_OUT +[test_visited_image_loading_empty.html] +skip-if = toolkit == 'android' #TIMED_OUT +[test_visited_lying.html] +skip-if = toolkit == 'android' #TIMED_OUT +[test_visited_pref.html] +skip-if = toolkit == 'android' #TIMED_OUT +[test_visited_reftests.html] +skip-if = toolkit == 'android' #TIMED_OUT +[test_webkit_device_pixel_ratio.html] +[test_webkit_flex_display.html] +[test_asyncopen2.html] +[test_align_shorthand_serialization.html] diff --git a/layout/style/test/moz.build b/layout/style/test/moz.build new file mode 100644 index 000000000..ca61f607a --- /dev/null +++ b/layout/style/test/moz.build @@ -0,0 +1,126 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# ** Note: The comment below along with the CPP_UNIT_TESTS and LIBS variables +# ** were commented out in the original Makefile.in, and should be restored +# ** some day, perhaps as a gtest. +# +# ParseCSS.cpp used to be built as a test program, but it was not +# being used for anything, and recent changes to the CSS loader have +# made it fail to link. Further changes are planned which should make +# it buildable again. +# +# TestCSSPropertyLookup.cpp needs the internal XPCOM APIs and so cannot +# be built with libxul enabled. +# +#CPP_UNIT_TESTS = TestCSSPropertyLookup.cpp +#LIBS += ../nsCSSKeywords.$(OBJ_SUFFIX) ../nsCSSProps.$(OBJ_SUFFIX) $(XPCOM_LIBS) + +HAS_MISC_RULE = True + +HostSimplePrograms([ + 'host_ListCSSProperties', +]) + +MOCHITEST_MANIFESTS += [ + 'mochitest.ini', +] +XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini'] +BROWSER_CHROME_MANIFESTS += ['browser.ini'] +MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini'] + +TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test.chrome += [ + 'chrome/moz_document_helper.html', +] + +TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test['css-visited'] += [ + '/layout/reftests/css-visited/border-1-ref.html', + '/layout/reftests/css-visited/border-1.html', + '/layout/reftests/css-visited/border-2-ref.html', + '/layout/reftests/css-visited/border-2a.html', + '/layout/reftests/css-visited/border-2b.html', + '/layout/reftests/css-visited/border-collapse-1-ref.html', + '/layout/reftests/css-visited/border-collapse-1.html', + '/layout/reftests/css-visited/color-choice-1-ref.html', + '/layout/reftests/css-visited/color-choice-1.html', + '/layout/reftests/css-visited/color-on-bullets-1-ref.html', + '/layout/reftests/css-visited/color-on-bullets-1.html', + '/layout/reftests/css-visited/color-on-link-1-ref.html', + '/layout/reftests/css-visited/color-on-link-1.html', + '/layout/reftests/css-visited/color-on-link-before-1.html', + '/layout/reftests/css-visited/color-on-text-decoration-1-ref.html', + '/layout/reftests/css-visited/color-on-text-decoration-1.html', + '/layout/reftests/css-visited/color-on-visited-1-ref.html', + '/layout/reftests/css-visited/color-on-visited-1.html', + '/layout/reftests/css-visited/color-on-visited-before-1.html', + '/layout/reftests/css-visited/column-rule-1-notref.html', + '/layout/reftests/css-visited/column-rule-1-ref.html', + '/layout/reftests/css-visited/column-rule-1.html', + '/layout/reftests/css-visited/content-before-1-ref.html', + '/layout/reftests/css-visited/content-color-on-link-before-1-ref.html', + '/layout/reftests/css-visited/content-color-on-link-before-1.html', + '/layout/reftests/css-visited/content-color-on-visited-before-1-ref.html', + '/layout/reftests/css-visited/content-color-on-visited-before-1.html', + '/layout/reftests/css-visited/content-on-link-before-1.html', + '/layout/reftests/css-visited/content-on-visited-before-1.html', + '/layout/reftests/css-visited/first-line-1-ref.html', + '/layout/reftests/css-visited/first-line-1.html', + '/layout/reftests/css-visited/inherit-keyword-1-ref.html', + '/layout/reftests/css-visited/inherit-keyword-1.xhtml', + '/layout/reftests/css-visited/link-root-1-ref.xhtml', + '/layout/reftests/css-visited/link-root-1.xhtml', + '/layout/reftests/css-visited/mathml-links-ref.html', + '/layout/reftests/css-visited/mathml-links.html', + '/layout/reftests/css-visited/outline-1-ref.html', + '/layout/reftests/css-visited/outline-1.html', + '/layout/reftests/css-visited/selector-adj-sibling-1-ref.html', + '/layout/reftests/css-visited/selector-adj-sibling-1.html', + '/layout/reftests/css-visited/selector-adj-sibling-2-ref.html', + '/layout/reftests/css-visited/selector-adj-sibling-2.html', + '/layout/reftests/css-visited/selector-any-sibling-1-ref.html', + '/layout/reftests/css-visited/selector-any-sibling-1.html', + '/layout/reftests/css-visited/selector-any-sibling-2-ref.html', + '/layout/reftests/css-visited/selector-any-sibling-2.html', + '/layout/reftests/css-visited/selector-child-1-ref.html', + '/layout/reftests/css-visited/selector-child-1.html', + '/layout/reftests/css-visited/selector-child-2-ref.xhtml', + '/layout/reftests/css-visited/selector-child-2.xhtml', + '/layout/reftests/css-visited/selector-descendant-1-ref.html', + '/layout/reftests/css-visited/selector-descendant-1.html', + '/layout/reftests/css-visited/selector-descendant-2-ref.xhtml', + '/layout/reftests/css-visited/selector-descendant-2.xhtml', + '/layout/reftests/css-visited/subject-of-selector-1-ref.html', + '/layout/reftests/css-visited/subject-of-selector-adj-sibling-1.html', + '/layout/reftests/css-visited/subject-of-selector-any-sibling-1.html', + '/layout/reftests/css-visited/subject-of-selector-child-1.html', + '/layout/reftests/css-visited/subject-of-selector-descendant-1.html', + '/layout/reftests/css-visited/subject-of-selector-descendant-2-ref.xhtml', + '/layout/reftests/css-visited/subject-of-selector-descendant-2.xhtml', + '/layout/reftests/css-visited/visited-page.html', + '/layout/reftests/css-visited/white-to-transparent-1-ref.html', + '/layout/reftests/css-visited/white-to-transparent-1.html', + '/layout/reftests/css-visited/width-1-ref.html', + '/layout/reftests/css-visited/width-on-link-1.html', + '/layout/reftests/css-visited/width-on-visited-1.html', + '/layout/reftests/svg/as-image/lime100x100.svg', + '/layout/reftests/svg/as-image/svg-image-visited-1-helper.svg', + '/layout/reftests/svg/as-image/svg-image-visited-2-helper.svg', + '/layout/reftests/svg/pseudo-classes-02-ref.svg', + '/layout/reftests/svg/pseudo-classes-02.svg', +] + +DEFINES['MOZILLA_INTERNAL_API'] = True +if CONFIG['MOZ_ENABLE_MASK_AS_SHORTHAND']: + HOST_DEFINES['MOZ_ENABLE_MASK_AS_SHORTHAND'] = True + +if CONFIG['COMPILE_ENVIRONMENT']: + GENERATED_FILES += ['css_properties.js'] + GENERATED_FILES['css_properties.js'].script = 'gen-css-properties.py' + GENERATED_FILES['css_properties.js'].inputs = [ + 'css_properties_like_longhand.js', + '!host_ListCSSProperties%s' % CONFIG['HOST_BIN_SUFFIX'], + ] + TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test += ['!css_properties.js'] diff --git a/layout/style/test/neverending_font_load.sjs b/layout/style/test/neverending_font_load.sjs new file mode 100644 index 000000000..7bf419aaf --- /dev/null +++ b/layout/style/test/neverending_font_load.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) +{ + response.processAsync(); + response.setHeader("Content-Type", "application/octet-stream", false); + response.write(""); +} diff --git a/layout/style/test/neverending_stylesheet_load.sjs b/layout/style/test/neverending_stylesheet_load.sjs new file mode 100644 index 000000000..386ffbe35 --- /dev/null +++ b/layout/style/test/neverending_stylesheet_load.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) +{ + response.processAsync(); + response.setHeader("Content-Type", "text/css", false); + response.write(""); +} diff --git a/layout/style/test/newtab_share_rule_processors.html b/layout/style/test/newtab_share_rule_processors.html new file mode 100644 index 000000000..bdfed1145 --- /dev/null +++ b/layout/style/test/newtab_share_rule_processors.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<style> +p { color: blue; } +</style> +<p>Hello</p> +<script> +var Cc = Components.classes; +var Ci = Components.interfaces; + +var sss = Cc["@mozilla.org/content/style-sheet-service;1"] + .getService(Ci.nsIStyleSheetService); +var io = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); +var winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + +function addAgentSheet() { + var sheetURI = io.newURI("data:text/css,p{background-color:yellow}", null, null); + var sheet = sss.preloadSheet(sheetURI, Ci.nsIStyleSheetService.AGENT_SHEET); + winUtils.addSheet(sheet, Ci.nsIDOMWindowUtils.AGENT_SHEET); +} +</script> diff --git a/layout/style/test/post-redirect-1.css b/layout/style/test/post-redirect-1.css new file mode 100644 index 000000000..c2ae5d6eb --- /dev/null +++ b/layout/style/test/post-redirect-1.css @@ -0,0 +1 @@ +#one { color: green; background: url("#"); } diff --git a/layout/style/test/post-redirect-2.css b/layout/style/test/post-redirect-2.css new file mode 100644 index 000000000..0a75299ce --- /dev/null +++ b/layout/style/test/post-redirect-2.css @@ -0,0 +1 @@ +#two { color: green; background: url("#"); } diff --git a/layout/style/test/post-redirect-3.css b/layout/style/test/post-redirect-3.css new file mode 100644 index 000000000..b33887ae3 --- /dev/null +++ b/layout/style/test/post-redirect-3.css @@ -0,0 +1 @@ +#three { color: green; background: url("#"); } diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js new file mode 100644 index 000000000..9c69e7d10 --- /dev/null +++ b/layout/style/test/property_database.js @@ -0,0 +1,7917 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +// Utility function. Returns true if the given boolean pref... +// (a) exists and (b) is set to true. +// Otherwise, returns false. +// +// This function also reports a test failure if the pref isn't set at all. This +// ensures that we remove pref-checks from mochitests (instead of accidentally +// disabling the tests that are controlled by that check) when we remove a +// mature feature's pref from the rest of the codebase. +function IsCSSPropertyPrefEnabled(prefName) +{ + try { + if (SpecialPowers.getBoolPref(prefName)) { + return true; + } + } catch (ex) { + ok(false, "Failed to look up property-controlling pref '" + + prefName + "' (" + ex + ")"); + } + + return false; +} + +// True longhand properties. +const CSS_TYPE_LONGHAND = 0; + +// True shorthand properties. +const CSS_TYPE_TRUE_SHORTHAND = 1; + +// Properties that we handle as shorthands but were longhands either in +// the current spec or earlier versions of the spec. +const CSS_TYPE_SHORTHAND_AND_LONGHAND = 2; + +// Each property has the following fields: +// domProp: The name of the relevant member of nsIDOM[NS]CSS2Properties +// inherited: Whether the property is inherited by default (stated as +// yes or no in the property header in all CSS specs) +// type: see above +// alias_for: optional, indicates that the property is an alias for +// some other property that is the preferred serialization. (Type +// must not be CSS_TYPE_LONGHAND.) +// logical: optional, indicates that the property is a logical directional +// property. (Type must be CSS_TYPE_LONGHAND.) +// axis: optional, indicates that the property is an axis-related logical +// directional property. (Type must be CSS_TYPE_LONGHAND and 'logical' +// must be true.) +// get_computed: if present, the property's computed value shows up on +// another property, and this is a function used to get it +// initial_values: Values whose computed value should be the same as the +// computed value for the property's initial value. +// other_values: Values whose computed value should be different from the +// computed value for the property's initial value. +// XXX Should have a third field for values whose computed value may or +// may not be the same as for the property's initial value. +// invalid_values: Things that are not values for the property and +// should be rejected, but which are balanced and should not absorb +// what follows +// quirks_values: Values that should be accepted in quirks mode only, +// mapped to the values they are equivalent to. +// unbalanced_values: Things that are not values for the property and +// should be rejected, and which also contain unbalanced constructs +// that should absorb what follows +// +// Note: By default, an alias is assumed to accept/reject the same values as +// the property that it aliases, and to have the same prerequisites. So, if +// "alias_for" is set, the "*_values" and "prerequisites" fields can simply +// be omitted, and they'll be populated automatically to match the aliased +// property's fields. + +// Helper functions used to construct gCSSProperties. + +function initial_font_family_is_sans_serif() +{ + // The initial value of 'font-family' might be 'serif' or + // 'sans-serif'. + var div = document.createElement("div"); + div.setAttribute("style", "font: initial"); + return getComputedStyle(div, "").fontFamily == "sans-serif"; +} +var gInitialFontFamilyIsSansSerif = initial_font_family_is_sans_serif(); + +// shared by background-image and border-image-source +var validGradientAndElementValues = [ + "-moz-element(#a)", + "-moz-element( #a )", + "-moz-element(#a-1)", + "-moz-element(#a\\:1)", + /* gradient torture test */ + "linear-gradient(red, blue)", + "linear-gradient(red, yellow, blue)", + "linear-gradient(red 1px, yellow 20%, blue 24em, green)", + "linear-gradient(red, yellow, green, blue 50%)", + "linear-gradient(red -50%, yellow -25%, green, blue)", + "linear-gradient(red -99px, yellow, green, blue 120%)", + "linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + "linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + "linear-gradient(red, green calc(50% + 20px), blue)", + + "linear-gradient(to top, red, blue)", + "linear-gradient(to bottom, red, blue)", + "linear-gradient(to left, red, blue)", + "linear-gradient(to right, red, blue)", + "linear-gradient(to top left, red, blue)", + "linear-gradient(to top right, red, blue)", + "linear-gradient(to bottom left, red, blue)", + "linear-gradient(to bottom right, red, blue)", + "linear-gradient(to left top, red, blue)", + "linear-gradient(to left bottom, red, blue)", + "linear-gradient(to right top, red, blue)", + "linear-gradient(to right bottom, red, blue)", + + "linear-gradient(-33deg, red, blue)", + "linear-gradient(30grad, red, blue)", + "linear-gradient(10deg, red, blue)", + "linear-gradient(1turn, red, blue)", + "linear-gradient(.414rad, red, blue)", + + "linear-gradient(.414rad, red, 50%, blue)", + "linear-gradient(.414rad, red, 0%, blue)", + "linear-gradient(.414rad, red, 100%, blue)", + + "linear-gradient(.414rad, red 50%, 50%, blue 50%)", + "linear-gradient(.414rad, red 50%, 20%, blue 50%)", + "linear-gradient(.414rad, red 50%, 30%, blue 10%)", + "linear-gradient(to right bottom, red, 20%, green 50%, 65%, blue)", + "linear-gradient(to right bottom, red, 20%, green 10%, blue)", + "linear-gradient(to right bottom, red, 50%, green 50%, 50%, blue)", + "linear-gradient(to right bottom, red, 0%, green 50%, 100%, blue)", + + "-moz-linear-gradient(red, blue)", + "-moz-linear-gradient(red, yellow, blue)", + "-moz-linear-gradient(red 1px, yellow 20%, blue 24em, green)", + "-moz-linear-gradient(red, yellow, green, blue 50%)", + "-moz-linear-gradient(red -50%, yellow -25%, green, blue)", + "-moz-linear-gradient(red -99px, yellow, green, blue 120%)", + "-moz-linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + "-moz-linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + + "-moz-linear-gradient(to top, red, blue)", + "-moz-linear-gradient(to bottom, red, blue)", + "-moz-linear-gradient(to left, red, blue)", + "-moz-linear-gradient(to right, red, blue)", + "-moz-linear-gradient(to top left, red, blue)", + "-moz-linear-gradient(to top right, red, blue)", + "-moz-linear-gradient(to bottom left, red, blue)", + "-moz-linear-gradient(to bottom right, red, blue)", + "-moz-linear-gradient(to left top, red, blue)", + "-moz-linear-gradient(to left bottom, red, blue)", + "-moz-linear-gradient(to right top, red, blue)", + "-moz-linear-gradient(to right bottom, red, blue)", + + "-moz-linear-gradient(top left, red, blue)", + "-moz-linear-gradient(0 0, red, blue)", + "-moz-linear-gradient(20% bottom, red, blue)", + "-moz-linear-gradient(center 20%, red, blue)", + "-moz-linear-gradient(left 35px, red, blue)", + "-moz-linear-gradient(10% 10em, red, blue)", + "-moz-linear-gradient(44px top, red, blue)", + + "-moz-linear-gradient(0px, red, blue)", + "-moz-linear-gradient(0, red, blue)", + "-moz-linear-gradient(top left 45deg, red, blue)", + "-moz-linear-gradient(20% bottom -300deg, red, blue)", + "-moz-linear-gradient(center 20% 1.95929rad, red, blue)", + "-moz-linear-gradient(left 35px 30grad, red, blue)", + "-moz-linear-gradient(left 35px 0.1turn, red, blue)", + "-moz-linear-gradient(10% 10em 99999deg, red, blue)", + "-moz-linear-gradient(44px top -33deg, red, blue)", + + "-moz-linear-gradient(-33deg, red, blue)", + "-moz-linear-gradient(30grad left 35px, red, blue)", + "-moz-linear-gradient(10deg 20px, red, blue)", + "-moz-linear-gradient(1turn 20px, red, blue)", + "-moz-linear-gradient(.414rad bottom, red, blue)", + + "-moz-linear-gradient(blue calc(0px) ,green calc(25%) ,red calc(40px) ,blue calc(60px) , yellow calc(100px))", + "-moz-linear-gradient(-33deg, blue calc(-25%) ,red 40px)", + "-moz-linear-gradient(10deg, blue calc(100px + -25%),red calc(40px))", + "-moz-linear-gradient(10deg, blue calc(-25px),red calc(100%))", + "-moz-linear-gradient(.414rad, blue calc(100px + -25px) ,green calc(100px + -25px) ,red calc(100px + -25%) ,blue calc(-25px) , yellow calc(-25px))", + "-moz-linear-gradient(1turn, blue calc(-25%) ,green calc(25px) ,red calc(25%),blue calc(0px),white 50px, yellow calc(-25px))", + + "radial-gradient(red, blue)", + "radial-gradient(red, yellow, blue)", + "radial-gradient(red 1px, yellow 20%, blue 24em, green)", + "radial-gradient(red, yellow, green, blue 50%)", + "radial-gradient(red -50%, yellow -25%, green, blue)", + "radial-gradient(red -99px, yellow, green, blue 120%)", + "radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + + "radial-gradient(0 0, red, blue)", + "radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + + "radial-gradient(at top left, red, blue)", + "radial-gradient(at 20% bottom, red, blue)", + "radial-gradient(at center 20%, red, blue)", + "radial-gradient(at left 35px, red, blue)", + "radial-gradient(at 10% 10em, red, blue)", + "radial-gradient(at 44px top, red, blue)", + "radial-gradient(at 0 0, red, blue)", + + "radial-gradient(farthest-corner, red, blue)", + "radial-gradient(circle, red, blue)", + "radial-gradient(ellipse closest-corner, red, blue)", + "radial-gradient(closest-corner ellipse, red, blue)", + + "radial-gradient(43px, red, blue)", + "radial-gradient(43px 43px, red, blue)", + "radial-gradient(50% 50%, red, blue)", + "radial-gradient(43px 50%, red, blue)", + "radial-gradient(50% 43px, red, blue)", + "radial-gradient(circle 43px, red, blue)", + "radial-gradient(43px circle, red, blue)", + "radial-gradient(ellipse 43px 43px, red, blue)", + "radial-gradient(ellipse 50% 50%, red, blue)", + "radial-gradient(ellipse 43px 50%, red, blue)", + "radial-gradient(ellipse 50% 43px, red, blue)", + "radial-gradient(50% 43px ellipse, red, blue)", + + "radial-gradient(farthest-corner at top left, red, blue)", + "radial-gradient(ellipse closest-corner at 45px, red, blue)", + "radial-gradient(circle farthest-side at 45px, red, blue)", + "radial-gradient(closest-side ellipse at 50%, red, blue)", + "radial-gradient(farthest-corner circle at 4em, red, blue)", + + "radial-gradient(30% 40% at top left, red, blue)", + "radial-gradient(50px 60px at 15% 20%, red, blue)", + "radial-gradient(7em 8em at 45px, red, blue)", + + "-moz-radial-gradient(red, blue)", + "-moz-radial-gradient(red, yellow, blue)", + "-moz-radial-gradient(red 1px, yellow 20%, blue 24em, green)", + "-moz-radial-gradient(red, yellow, green, blue 50%)", + "-moz-radial-gradient(red -50%, yellow -25%, green, blue)", + "-moz-radial-gradient(red -99px, yellow, green, blue 120%)", + "-moz-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + + "-moz-radial-gradient(top left, red, blue)", + "-moz-radial-gradient(20% bottom, red, blue)", + "-moz-radial-gradient(center 20%, red, blue)", + "-moz-radial-gradient(left 35px, red, blue)", + "-moz-radial-gradient(10% 10em, red, blue)", + "-moz-radial-gradient(44px top, red, blue)", + + "-moz-radial-gradient(top left 45deg, red, blue)", + "-moz-radial-gradient(0 0, red, blue)", + "-moz-radial-gradient(20% bottom -300deg, red, blue)", + "-moz-radial-gradient(center 20% 1.95929rad, red, blue)", + "-moz-radial-gradient(left 35px 30grad, red, blue)", + "-moz-radial-gradient(10% 10em 99999deg, red, blue)", + "-moz-radial-gradient(44px top -33deg, red, blue)", + "-moz-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + + "-moz-radial-gradient(-33deg, red, blue)", + "-moz-radial-gradient(30grad left 35px, red, blue)", + "-moz-radial-gradient(10deg 20px, red, blue)", + "-moz-radial-gradient(.414rad bottom, red, blue)", + + "-moz-radial-gradient(cover, red, blue)", + "-moz-radial-gradient(cover circle, red, blue)", + "-moz-radial-gradient(contain, red, blue)", + "-moz-radial-gradient(contain ellipse, red, blue)", + "-moz-radial-gradient(circle, red, blue)", + "-moz-radial-gradient(ellipse closest-corner, red, blue)", + "-moz-radial-gradient(farthest-side circle, red, blue)", + + "-moz-radial-gradient(top left, cover, red, blue)", + "-moz-radial-gradient(15% 20%, circle, red, blue)", + "-moz-radial-gradient(45px, ellipse closest-corner, red, blue)", + "-moz-radial-gradient(45px, farthest-side circle, red, blue)", + + "-moz-radial-gradient(99deg, cover, red, blue)", + "-moz-radial-gradient(-1.2345rad, circle, red, blue)", + "-moz-radial-gradient(399grad, ellipse closest-corner, red, blue)", + "-moz-radial-gradient(399grad, farthest-side circle, red, blue)", + + "-moz-radial-gradient(top left 99deg, cover, red, blue)", + "-moz-radial-gradient(15% 20% -1.2345rad, circle, red, blue)", + "-moz-radial-gradient(45px 399grad, ellipse closest-corner, red, blue)", + "-moz-radial-gradient(45px 399grad, farthest-side circle, red, blue)", + + "-moz-repeating-linear-gradient(red, blue)", + "-moz-repeating-linear-gradient(red, yellow, blue)", + "-moz-repeating-linear-gradient(red 1px, yellow 20%, blue 24em, green)", + "-moz-repeating-linear-gradient(red, yellow, green, blue 50%)", + "-moz-repeating-linear-gradient(red -50%, yellow -25%, green, blue)", + "-moz-repeating-linear-gradient(red -99px, yellow, green, blue 120%)", + "-moz-repeating-linear-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + "-moz-repeating-linear-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + + "-moz-repeating-linear-gradient(to top, red, blue)", + "-moz-repeating-linear-gradient(to bottom, red, blue)", + "-moz-repeating-linear-gradient(to left, red, blue)", + "-moz-repeating-linear-gradient(to right, red, blue)", + "-moz-repeating-linear-gradient(to top left, red, blue)", + "-moz-repeating-linear-gradient(to top right, red, blue)", + "-moz-repeating-linear-gradient(to bottom left, red, blue)", + "-moz-repeating-linear-gradient(to bottom right, red, blue)", + "-moz-repeating-linear-gradient(to left top, red, blue)", + "-moz-repeating-linear-gradient(to left bottom, red, blue)", + "-moz-repeating-linear-gradient(to right top, red, blue)", + "-moz-repeating-linear-gradient(to right bottom, red, blue)", + + "-moz-repeating-linear-gradient(top left, red, blue)", + "-moz-repeating-linear-gradient(0 0, red, blue)", + "-moz-repeating-linear-gradient(20% bottom, red, blue)", + "-moz-repeating-linear-gradient(center 20%, red, blue)", + "-moz-repeating-linear-gradient(left 35px, red, blue)", + "-moz-repeating-linear-gradient(10% 10em, red, blue)", + "-moz-repeating-linear-gradient(44px top, red, blue)", + + "-moz-repeating-linear-gradient(top left 45deg, red, blue)", + "-moz-repeating-linear-gradient(20% bottom -300deg, red, blue)", + "-moz-repeating-linear-gradient(center 20% 1.95929rad, red, blue)", + "-moz-repeating-linear-gradient(left 35px 30grad, red, blue)", + "-moz-repeating-linear-gradient(10% 10em 99999deg, red, blue)", + "-moz-repeating-linear-gradient(44px top -33deg, red, blue)", + + "-moz-repeating-linear-gradient(-33deg, red, blue)", + "-moz-repeating-linear-gradient(30grad left 35px, red, blue)", + "-moz-repeating-linear-gradient(10deg 20px, red, blue)", + "-moz-repeating-linear-gradient(.414rad bottom, red, blue)", + + "-moz-repeating-radial-gradient(red, blue)", + "-moz-repeating-radial-gradient(red, yellow, blue)", + "-moz-repeating-radial-gradient(red 1px, yellow 20%, blue 24em, green)", + "-moz-repeating-radial-gradient(red, yellow, green, blue 50%)", + "-moz-repeating-radial-gradient(red -50%, yellow -25%, green, blue)", + "-moz-repeating-radial-gradient(red -99px, yellow, green, blue 120%)", + "-moz-repeating-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + "-moz-repeating-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + + "repeating-radial-gradient(at top left, red, blue)", + "repeating-radial-gradient(at 0 0, red, blue)", + "repeating-radial-gradient(at 20% bottom, red, blue)", + "repeating-radial-gradient(at center 20%, red, blue)", + "repeating-radial-gradient(at left 35px, red, blue)", + "repeating-radial-gradient(at 10% 10em, red, blue)", + "repeating-radial-gradient(at 44px top, red, blue)", + + "-moz-repeating-radial-gradient(farthest-corner, red, blue)", + "-moz-repeating-radial-gradient(circle, red, blue)", + "-moz-repeating-radial-gradient(ellipse closest-corner, red, blue)", + + "repeating-radial-gradient(farthest-corner at top left, red, blue)", + "repeating-radial-gradient(closest-corner ellipse at 45px, red, blue)", + "repeating-radial-gradient(farthest-side circle at 45px, red, blue)", + "repeating-radial-gradient(ellipse closest-side at 50%, red, blue)", + "repeating-radial-gradient(circle farthest-corner at 4em, red, blue)", + + "repeating-radial-gradient(30% 40% at top left, red, blue)", + "repeating-radial-gradient(50px 60px at 15% 20%, red, blue)", + "repeating-radial-gradient(7em 8em at 45px, red, blue)", + + "-moz-image-rect(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), 2, 10, 10, 2)", + "-moz-image-rect(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), 10%, 50%, 30%, 0%)", + "-moz-image-rect(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), 10, 50%, 30%, 0)", + + "-moz-radial-gradient(calc(25%) top, red, blue)", + "-moz-radial-gradient(left calc(25%), red, blue)", + "-moz-radial-gradient(calc(25px) top, red, blue)", + "-moz-radial-gradient(left calc(25px), red, blue)", + "-moz-radial-gradient(calc(-25%) top, red, blue)", + "-moz-radial-gradient(left calc(-25%), red, blue)", + "-moz-radial-gradient(calc(-25px) top, red, blue)", + "-moz-radial-gradient(left calc(-25px), red, blue)", + "-moz-radial-gradient(calc(100px + -25%) top, red, blue)", + "-moz-radial-gradient(left calc(100px + -25%), red, blue)", + "-moz-radial-gradient(calc(100px + -25px) top, red, blue)", + "-moz-radial-gradient(left calc(100px + -25px), red, blue)" +]; +var invalidGradientAndElementValues = [ + "-moz-element(#a:1)", + "-moz-element(a#a)", + "-moz-element(#a a)", + "-moz-element(#a+a)", + "-moz-element(#a())", + /* no quirks mode colors */ + "linear-gradient(red, ff00ff)", + /* no quirks mode colors */ + "-moz-radial-gradient(10% bottom, ffffff, black) scroll no-repeat", + /* no quirks mode lengths */ + "-moz-linear-gradient(10 10px -45deg, red, blue) repeat", + "-moz-linear-gradient(10px 10 -45deg, red, blue) repeat", + "linear-gradient(red -99, yellow, green, blue 120%)", + /* Unitless 0 is invalid as an <angle> */ + "-moz-linear-gradient(top left 0, red, blue)", + "-moz-linear-gradient(5px 5px 0, red, blue)", + "linear-gradient(0, red, blue)", + /* Invalid color, calc() or -moz-image-rect() function */ + "linear-gradient(red, rgb(0, rubbish, 0) 50%, red)", + "linear-gradient(red, red calc(50% + rubbish), red)", + "linear-gradient(to top calc(50% + rubbish), red, blue)", + /* Old syntax */ + "-moz-linear-gradient(10px 10px, 20px, 30px 30px, 40px, from(blue), to(red))", + "-moz-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))", + "-moz-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))", + "-moz-linear-gradient(10px, 20px, 30px, 40px, color-stop(0.5, #00ccff))", + "-moz-linear-gradient(20px 20px, from(blue), to(red))", + "-moz-linear-gradient(40px 40px, 10px 10px, from(blue) to(red) color-stop(10%, fuchsia))", + "-moz-linear-gradient(20px 20px 30px, 10px 10px, from(red), to(#ff0000))", + "-moz-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))", + "-moz-linear-gradient(left left, top top, from(blue))", + "-moz-linear-gradient(inherit, 10px 10px, from(blue))", + /* New syntax */ + "-moz-linear-gradient(10px 10px, 20px, 30px 30px, 40px, blue 0, red 100%)", + "-moz-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))", + "-moz-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))", + "-moz-linear-gradient(10px, 20px, 30px, 40px, #00ccff 50%)", + "-moz-linear-gradient(40px 40px, 10px 10px, blue 0 fuchsia 10% red 100%)", + "-moz-linear-gradient(20px 20px 30px, 10px 10px, red 0, #ff0000 100%)", + "-moz-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))", + "-moz-linear-gradient(left left, top top, blue 0)", + "-moz-linear-gradient(inherit, 10px 10px, blue 0)", + "-moz-linear-gradient(left left blue red)", + "-moz-linear-gradient(left left blue, red)", + "-moz-linear-gradient()", + "-moz-linear-gradient(cover, red, blue)", + "-moz-linear-gradient(auto, red, blue)", + "-moz-linear-gradient(22 top, red, blue)", + "-moz-linear-gradient(10% red blue)", + "-moz-linear-gradient(10%, red blue)", + "-moz-linear-gradient(10%,, red, blue)", + "-moz-linear-gradient(45px, center, red, blue)", + "-moz-linear-gradient(45px, center red, blue)", + "-moz-radial-gradient(contain, ellipse, red, blue)", + "-moz-radial-gradient(10deg contain, red, blue)", + "-moz-radial-gradient(10deg, contain,, red, blue)", + "-moz-radial-gradient(contain contain, red, blue)", + "-moz-radial-gradient(ellipse circle, red, blue)", + "-moz-radial-gradient(to top left, red, blue)", + "-moz-radial-gradient(center, 10%, red, blue)", + "-moz-radial-gradient(5rad, 20px, red, blue)", + "-moz-radial-gradient(40%, -100px -10%, red, blue)", + + "-moz-radial-gradient(at top left to cover, red, blue)", + "-moz-radial-gradient(at 15% 20% circle, red, blue)", + + "-moz-radial-gradient(to cover, red, blue)", + "-moz-radial-gradient(to contain, red, blue)", + "-moz-radial-gradient(to closest-side circle, red, blue)", + "-moz-radial-gradient(to farthest-corner ellipse, red, blue)", + + "-moz-radial-gradient(ellipse at 45px closest-corner, red, blue)", + "-moz-radial-gradient(circle at 45px farthest-side, red, blue)", + "-moz-radial-gradient(ellipse 45px, closest-side, red, blue)", + "-moz-radial-gradient(circle 45px, farthest-corner, red, blue)", + "-moz-radial-gradient(ellipse, ellipse closest-side, red, blue)", + "-moz-radial-gradient(circle, circle farthest-corner, red, blue)", + + "-moz-radial-gradient(99deg to farthest-corner, red, blue)", + "-moz-radial-gradient(-1.2345rad circle, red, blue)", + "-moz-radial-gradient(ellipse 399grad to closest-corner, red, blue)", + "-moz-radial-gradient(circle 399grad to farthest-side, red, blue)", + + "-moz-radial-gradient(at top left 99deg, to farthest-corner, red, blue)", + "-moz-radial-gradient(circle at 15% 20% -1.2345rad, red, blue)", + "-moz-radial-gradient(to top left at 30% 40%, red, blue)", + "-moz-radial-gradient(ellipse at 45px 399grad, to closest-corner, red, blue)", + "-moz-radial-gradient(at 45px 399grad to farthest-side circle, red, blue)", + + "-moz-radial-gradient(to 50%, red, blue)", + "-moz-radial-gradient(circle to 50%, red, blue)", + "-moz-radial-gradient(circle to 43px 43px, red, blue)", + "-moz-radial-gradient(circle to 50% 50%, red, blue)", + "-moz-radial-gradient(circle to 43px 50%, red, blue)", + "-moz-radial-gradient(circle to 50% 43px, red, blue)", + "-moz-radial-gradient(ellipse to 43px, red, blue)", + "-moz-radial-gradient(ellipse to 50%, red, blue)", + + "-moz-linear-gradient(to 0 0, red, blue)", + "-moz-linear-gradient(to 20% bottom, red, blue)", + "-moz-linear-gradient(to center 20%, red, blue)", + "-moz-linear-gradient(to left 35px, red, blue)", + "-moz-linear-gradient(to 10% 10em, red, blue)", + "-moz-linear-gradient(to 44px top, red, blue)", + "-moz-linear-gradient(to top left 45deg, red, blue)", + "-moz-linear-gradient(to 20% bottom -300deg, red, blue)", + "-moz-linear-gradient(to center 20% 1.95929rad, red, blue)", + "-moz-linear-gradient(to left 35px 30grad, red, blue)", + "-moz-linear-gradient(to 10% 10em 99999deg, red, blue)", + "-moz-linear-gradient(to 44px top -33deg, red, blue)", + "-moz-linear-gradient(to -33deg, red, blue)", + "-moz-linear-gradient(to 30grad left 35px, red, blue)", + "-moz-linear-gradient(to 10deg 20px, red, blue)", + "-moz-linear-gradient(to .414rad bottom, red, blue)", + + "-moz-linear-gradient(to top top, red, blue)", + "-moz-linear-gradient(to bottom bottom, red, blue)", + "-moz-linear-gradient(to left left, red, blue)", + "-moz-linear-gradient(to right right, red, blue)", + + "-moz-repeating-linear-gradient(10px 10px, 20px, 30px 30px, 40px, blue 0, red 100%)", + "-moz-repeating-radial-gradient(20px 20px, 10px 10px, from(green), to(#ff00ff))", + "-moz-repeating-radial-gradient(10px 10px, 20%, 40px 40px, 10px, from(green), to(#ff00ff))", + "-moz-repeating-linear-gradient(10px, 20px, 30px, 40px, #00ccff 50%)", + "-moz-repeating-linear-gradient(40px 40px, 10px 10px, blue 0 fuchsia 10% red 100%)", + "-moz-repeating-linear-gradient(20px 20px 30px, 10px 10px, red 0, #ff0000 100%)", + "-moz-repeating-radial-gradient(left top, center, 20px 20px, 10px, from(blue), to(red))", + "-moz-repeating-linear-gradient(left left, top top, blue 0)", + "-moz-repeating-linear-gradient(inherit, 10px 10px, blue 0)", + "-moz-repeating-linear-gradient(left left blue red)", + "-moz-repeating-linear-gradient()", + + "-moz-repeating-linear-gradient(to 0 0, red, blue)", + "-moz-repeating-linear-gradient(to 20% bottom, red, blue)", + "-moz-repeating-linear-gradient(to center 20%, red, blue)", + "-moz-repeating-linear-gradient(to left 35px, red, blue)", + "-moz-repeating-linear-gradient(to 10% 10em, red, blue)", + "-moz-repeating-linear-gradient(to 44px top, red, blue)", + "-moz-repeating-linear-gradient(to top left 45deg, red, blue)", + "-moz-repeating-linear-gradient(to 20% bottom -300deg, red, blue)", + "-moz-repeating-linear-gradient(to center 20% 1.95929rad, red, blue)", + "-moz-repeating-linear-gradient(to left 35px 30grad, red, blue)", + "-moz-repeating-linear-gradient(to 10% 10em 99999deg, red, blue)", + "-moz-repeating-linear-gradient(to 44px top -33deg, red, blue)", + "-moz-repeating-linear-gradient(to -33deg, red, blue)", + "-moz-repeating-linear-gradient(to 30grad left 35px, red, blue)", + "-moz-repeating-linear-gradient(to 10deg 20px, red, blue)", + "-moz-repeating-linear-gradient(to .414rad bottom, red, blue)", + + "-moz-repeating-linear-gradient(to top top, red, blue)", + "-moz-repeating-linear-gradient(to bottom bottom, red, blue)", + "-moz-repeating-linear-gradient(to left left, red, blue)", + "-moz-repeating-linear-gradient(to right right, red, blue)", + + "-moz-repeating-radial-gradient(to top left at 30% 40%, red, blue)", + "-moz-repeating-radial-gradient(ellipse at 45px closest-corner, red, blue)", + "-moz-repeating-radial-gradient(circle at 45px farthest-side, red, blue)", + + "radial-gradient(circle 175px 20px, black, white)", + "radial-gradient(175px 20px circle, black, white)", + "radial-gradient(ellipse 175px, black, white)", + "radial-gradient(175px ellipse, black, white)", + "radial-gradient(50%, red, blue)", + "radial-gradient(circle 50%, red, blue)", + "radial-gradient(50% circle, red, blue)", + + /* Valid only when prefixed */ + "linear-gradient(top left, red, blue)", + "linear-gradient(0 0, red, blue)", + "linear-gradient(20% bottom, red, blue)", + "linear-gradient(center 20%, red, blue)", + "linear-gradient(left 35px, red, blue)", + "linear-gradient(10% 10em, red, blue)", + "linear-gradient(44px top, red, blue)", + + "linear-gradient(top left 45deg, red, blue)", + "linear-gradient(20% bottom -300deg, red, blue)", + "linear-gradient(center 20% 1.95929rad, red, blue)", + "linear-gradient(left 35px 30grad, red, blue)", + "linear-gradient(left 35px 0.1turn, red, blue)", + "linear-gradient(10% 10em 99999deg, red, blue)", + "linear-gradient(44px top -33deg, red, blue)", + + "linear-gradient(30grad left 35px, red, blue)", + "linear-gradient(10deg 20px, red, blue)", + "linear-gradient(1turn 20px, red, blue)", + "linear-gradient(.414rad bottom, red, blue)", + + "linear-gradient(to top, 0%, blue)", + "linear-gradient(to top, red, 100%)", + "linear-gradient(to top, red, 45%, 56%, blue)", + "linear-gradient(to top, red,, blue)", + "linear-gradient(to top, red, green 35%, 15%, 54%, blue)", + + + "radial-gradient(top left 45deg, red, blue)", + "radial-gradient(20% bottom -300deg, red, blue)", + "radial-gradient(center 20% 1.95929rad, red, blue)", + "radial-gradient(left 35px 30grad, red, blue)", + "radial-gradient(10% 10em 99999deg, red, blue)", + "radial-gradient(44px top -33deg, red, blue)", + + "radial-gradient(-33deg, red, blue)", + "radial-gradient(30grad left 35px, red, blue)", + "radial-gradient(10deg 20px, red, blue)", + "radial-gradient(.414rad bottom, red, blue)", + + "radial-gradient(cover, red, blue)", + "radial-gradient(ellipse contain, red, blue)", + "radial-gradient(cover circle, red, blue)", + + "radial-gradient(top left, cover, red, blue)", + "radial-gradient(15% 20%, circle, red, blue)", + "radial-gradient(45px, ellipse closest-corner, red, blue)", + "radial-gradient(45px, farthest-side circle, red, blue)", + + "radial-gradient(99deg, cover, red, blue)", + "radial-gradient(-1.2345rad, circle, red, blue)", + "radial-gradient(399grad, ellipse closest-corner, red, blue)", + "radial-gradient(399grad, farthest-side circle, red, blue)", + + "radial-gradient(top left 99deg, cover, red, blue)", + "radial-gradient(15% 20% -1.2345rad, circle, red, blue)", + "radial-gradient(45px 399grad, ellipse closest-corner, red, blue)", + "radial-gradient(45px 399grad, farthest-side circle, red, blue)", + + /* Valid only when unprefixed */ + "-moz-radial-gradient(at top left, red, blue)", + "-moz-radial-gradient(at 20% bottom, red, blue)", + "-moz-radial-gradient(at center 20%, red, blue)", + "-moz-radial-gradient(at left 35px, red, blue)", + "-moz-radial-gradient(at 10% 10em, red, blue)", + "-moz-radial-gradient(at 44px top, red, blue)", + "-moz-radial-gradient(at 0 0, red, blue)", + + "-moz-radial-gradient(circle 43px, red, blue)", + "-moz-radial-gradient(ellipse 43px 43px, red, blue)", + "-moz-radial-gradient(ellipse 50% 50%, red, blue)", + "-moz-radial-gradient(ellipse 43px 50%, red, blue)", + "-moz-radial-gradient(ellipse 50% 43px, red, blue)", + + "-moz-radial-gradient(farthest-corner at top left, red, blue)", + "-moz-radial-gradient(ellipse closest-corner at 45px, red, blue)", + "-moz-radial-gradient(circle farthest-side at 45px, red, blue)", + "-moz-radial-gradient(closest-side ellipse at 50%, red, blue)", + "-moz-radial-gradient(farthest-corner circle at 4em, red, blue)", + + "-moz-radial-gradient(30% 40% at top left, red, blue)", + "-moz-radial-gradient(50px 60px at 15% 20%, red, blue)", + "-moz-radial-gradient(7em 8em at 45px, red, blue)" +]; +var unbalancedGradientAndElementValues = [ + "-moz-element(#a()", +]; + +if (IsCSSPropertyPrefEnabled("layout.css.prefixes.webkit")) { + // Extend gradient lists with valid/invalid webkit-prefixed expressions: + validGradientAndElementValues.push( + // 2008 GRADIENTS: -webkit-gradient() + // ---------------------------------- + // linear w/ no color stops (valid) and a variety of position values: + "-webkit-gradient(linear, 1 2, 3 4)", + "-webkit-gradient(linear,1 2,3 4)", // (no extra space) + "-webkit-gradient(linear , 1 2 , 3 4 )", // (lots of extra space) + "-webkit-gradient(linear, 1 10% , 0% 4)", // percentages + "-webkit-gradient(linear, +1.0 -2%, +5.3% -0)", // (+/- & decimals are valid) + "-webkit-gradient(linear, left top, right bottom)", // keywords + "-webkit-gradient(linear, right center, center top)", + "-webkit-gradient(linear, center center, center center)", + "-webkit-gradient(linear, center 5%, 30 top)", // keywords mixed w/ nums + + // linear w/ just 1 color stop: + "-webkit-gradient(linear, 1 2, 3 4, from(lime))", + "-webkit-gradient(linear, 1 2, 3 4, to(lime))", + // * testing the various allowable stop values (<number> & <percent>): + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(-0, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(-30, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(+9999, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(-.1, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0%, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(100%, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(9999%, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(-.5%, lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(+0%, lime))", + // * testing the various color values: + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, transparent))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, rgb(1,2,3)))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, #00ff00))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, #00f))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, hsla(240, 30%, 50%, 0.8)))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, rgba(255, 230, 10, 0.5)))", + + // linear w/ multiple color stops: + // * using from()/to() -- note that out-of-order is OK: + "-webkit-gradient(linear, 1 2, 3 4, from(lime), from(blue))", + "-webkit-gradient(linear, 1 2, 3 4, to(lime), to(blue))", + "-webkit-gradient(linear, 1 2, 3 4, from(lime), to(blue))", + "-webkit-gradient(linear, 1 2, 3 4, to(lime), from(blue))", + "-webkit-gradient(linear, 1 2, 3 4, from(lime), to(blue), from(purple))", + // * using color-stop(): + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime), color-stop(30%, blue))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0, lime), color-stop(30%, blue), color-stop(100%, purple))", + // * using color-stop() intermixed with from()/to() functions: + "-webkit-gradient(linear, 1 2, 3 4, from(lime), color-stop(30%, blue))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(30%, blue), to(lime))", + // * overshooting endpoints (0 & 1.0) + "-webkit-gradient(linear, 1 2, 3 4, color-stop(-30%, lime), color-stop(.4, blue), color-stop(1.5, purple))", + // * repeating a stop position (valid) + "-webkit-gradient(linear, 1 2, 3 4, color-stop(30%, lime), color-stop(30%, blue))", + // * stops out of order (valid) + "-webkit-gradient(linear, 1 2, 3 4, color-stop(70%, lime), color-stop(20%, blue), color-stop(40%, purple))", + + // radial w/ no color stops (valid) and a several different radius values: + "-webkit-gradient(radial, 1 2, 8, 3 4, 9)", + "-webkit-gradient(radial, 0 0, 10, 0 0, 5)", + "-webkit-gradient(radial, 1 2, -1.5, center center, +99999.9999)", + + // radial w/ color stops + // (mostly leaning on more-robust 'linear' tests above; just testing a few + // examples w/ radial as a sanity-check): + "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(lime))", + "-webkit-gradient(radial, 1 2, 8, 3 4, 9, to(blue))", + "-webkit-gradient(radial, 1 2, 8, 3 4, 9, color-stop(0.5, #00f), color-stop(0.8, rgba(100, 200, 0, 0.5)))", + + // 2011 GRADIENTS: -webkit-linear-gradient(), -webkit-radial -gradient() + // --------------------------------------------------------------------- + // Basic linear-gradient syntax (valid when prefixed or unprefixed): + "-webkit-linear-gradient(red, green, blue)", + + // Angled linear-gradients (valid when prefixed or unprefixed): + "-webkit-linear-gradient(135deg, red, blue)", + "-webkit-linear-gradient(280deg, red 60%, blue)", + + // Linear-gradient with unitless-0 <angle> (normally invalid for <angle> + // but accepted here for better webkit emulation): + "-webkit-linear-gradient(0, red, blue)", + "-webkit-linear-gradient(0 red, blue)", + + // Basic radial-gradient syntax (valid when prefixed or unprefixed): + "-webkit-radial-gradient(circle, white, black)", + "-webkit-radial-gradient(circle, white, black)", + "-webkit-radial-gradient(ellipse closest-side, white, black)", + "-webkit-radial-gradient(circle farthest-corner, white, black)", + + // Contain/cover keywords (valid only for -moz/-webkit prefixed): + "-webkit-radial-gradient(cover, red, blue)", + "-webkit-radial-gradient(cover circle, red, blue)", + "-webkit-radial-gradient(contain, red, blue)", + "-webkit-radial-gradient(contain ellipse, red, blue)", + + // Initial side/corner/point (valid only for -moz/-webkit prefixed): + "-webkit-linear-gradient(left, red, blue)", + "-webkit-linear-gradient(right top, red, blue)", + "-webkit-linear-gradient(top right, red, blue)", + "-webkit-radial-gradient(right, red, blue)", + "-webkit-radial-gradient(left bottom, red, blue)", + "-webkit-radial-gradient(bottom left, red, blue)", + "-webkit-radial-gradient(center, red, blue)", + "-webkit-radial-gradient(center right, red, blue)", + "-webkit-radial-gradient(center center, red, blue)", + "-webkit-radial-gradient(center top, red, blue)", + "-webkit-radial-gradient(left 50%, red, blue)", + "-webkit-radial-gradient(20px top, red, blue)", + "-webkit-radial-gradient(20em 30%, red, blue)", + + // Point + keyword-sized shape (valid only for -moz/-webkit prefixed): + "-webkit-radial-gradient(center, circle closest-corner, red, blue)", + "-webkit-radial-gradient(10px 20px, cover circle, red, blue)", + "-webkit-radial-gradient(5em 50%, ellipse contain, red, blue)", + + // Repeating examples: + "-webkit-repeating-linear-gradient(red 10%, blue 30%)", + "-webkit-repeating-linear-gradient(30deg, pink 20px, orange 70px)", + "-webkit-repeating-linear-gradient(left, red, blue)", + "-webkit-repeating-linear-gradient(left, red 10%, blue 30%)", + "-webkit-repeating-radial-gradient(circle, red, blue 10%, red 20%)", + "-webkit-repeating-radial-gradient(circle farthest-corner, gray 10px, yellow 20px)", + "-webkit-repeating-radial-gradient(top left, circle, red, blue 4%, red 8%)" + ); + + invalidGradientAndElementValues.push( + // 2008 GRADIENTS: -webkit-gradient() + // https://www.webkit.org/blog/175/introducing-css-gradients/ + // ---------------------------------- + // Mostly-empty expressions (missing most required pieces): + "-webkit-gradient()", + "-webkit-gradient( )", + "-webkit-gradient(,)", + "-webkit-gradient(bogus)", + "-webkit-gradient(linear)", + "-webkit-gradient(linear,)", + "-webkit-gradient(,linear)", + "-webkit-gradient(radial)", + "-webkit-gradient(radial,)", + + // linear w/ partial/missing <point> expression(s) + "-webkit-gradient(linear, 1)", // Incomplete <point> + "-webkit-gradient(linear, left)", // Incomplete <point> + "-webkit-gradient(linear, center)", // Incomplete <point> + "-webkit-gradient(linear, top)", // Incomplete <point> + "-webkit-gradient(linear, 5%)", // Incomplete <point> + "-webkit-gradient(linear, 1 2)", // Missing 2nd <point> + "-webkit-gradient(linear, 1, 3)", // 2 incomplete <point>s + "-webkit-gradient(linear, 1, 3 4)", // Incomplete 1st <point> + "-webkit-gradient(linear, 1 2, 3)", // Incomplete 2nd <point> + "-webkit-gradient(linear, 1 2, 3, 4)", // Comma inside <point> + "-webkit-gradient(linear, 1, 2, 3 4)", // Comma inside <point> + "-webkit-gradient(linear, 1, 2, 3, 4)", // Comma inside <point> + + // linear w/ invalid units in <point> expression + "-webkit-gradient(linear, 1px 2, 3 4)", + "-webkit-gradient(linear, 1 2, 3 4px)", + "-webkit-gradient(linear, 1px 2px, 3px 4px)", + "-webkit-gradient(linear, calc(1) 2, 3 4)", + "-webkit-gradient(linear, 1 2em, 3 4)", + + // linear w/ <radius> (only valid for radial) + "-webkit-gradient(linear, 1 2, 8, 3 4, 9)", + + // linear w/ out-of-order position keywords in <point> expression + // (horizontal keyword is supposed to come first, for "x" coord) + "-webkit-gradient(linear, 0 0, top right)", + "-webkit-gradient(linear, bottom center, 0 0)", + "-webkit-gradient(linear, top bottom, 0 0)", + "-webkit-gradient(linear, bottom top, 0 0)", + "-webkit-gradient(linear, bottom top, 0 0)", + + // linear w/ trailing comma (which implies missing color-stops): + "-webkit-gradient(linear, 1 2, 3 4,)", + + // linear w/ invalid color values: + "-webkit-gradient(linear, 1 2, 3 4, from(invalidcolorname))", + "-webkit-gradient(linear, 1 2, 3 4, from(inherit))", + "-webkit-gradient(linear, 1 2, 3 4, from(initial))", + "-webkit-gradient(linear, 1 2, 3 4, from(currentColor))", + "-webkit-gradient(linear, 1 2, 3 4, from(00ff00))", + "-webkit-gradient(linear, 1 2, 3 4, from(##00ff00))", + "-webkit-gradient(linear, 1 2, 3 4, from(#00fff))", // wrong num hex digits + "-webkit-gradient(linear, 1 2, 3 4, from(xyz(0,0,0)))", // bogus color func + // Mixing <number> and <percentage> is invalid. + "-webkit-gradient(linear, 1 2, 3 4, from(rgb(100, 100%, 30)))", + + // linear w/ color stops that have comma issues + "-webkit-gradient(linear, 1 2, 3 4 from(lime))", + "-webkit-gradient(linear, 1 2, 3 4, from(lime,))", + "-webkit-gradient(linear, 1 2, 3 4, from(lime),)", + "-webkit-gradient(linear, 1 2, 3 4, from(lime) to(blue))", + "-webkit-gradient(linear, 1 2, 3 4, from(lime),, to(blue))", + "-webkit-gradient(linear, 1 2, 3 4, from(rbg(0, 0, 0,)))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0 lime))", + "-webkit-gradient(linear, 1 2, 3 4, color-stop(0,, lime))", + + // radial w/ broken <point>/radius expression(s) + "-webkit-gradient(radial, 1)", // Incomplete <point> + "-webkit-gradient(radial, 1 2)", // Missing radius + 2nd <point> + "-webkit-gradient(radial, 1 2, 8)", // Missing 2nd <point> + "-webkit-gradient(radial, 1 2, 8, 3)", // Incomplete 2nd <point> + "-webkit-gradient(radial, 1 2, 8, 3 4)", // Missing 2nd radius + "-webkit-gradient(radial, 1 2, 3 4, 9)", // Missing 1st radius + + // radial w/ incorrect units on radius (invalid; expecting <number>) + "-webkit-gradient(radial, 1 2, 8%, 3 4, 9)", + "-webkit-gradient(radial, 1 2, 8px, 3 4, 9)", + "-webkit-gradient(radial, 1 2, calc(8), 3 4, 9)", + "-webkit-gradient(radial, 1 2, 8em, 3 4, 9)", + "-webkit-gradient(radial, 1 2, top, 3 4, 9)", + + // radial w/ trailing comma (which implies missing color-stops): + "-webkit-gradient(linear, 1 2, 8, 3 4, 9,)", + + // radial w/ invalid color value (mostly leaning on 'linear' test above): + "-webkit-gradient(radial, 1 2, 8, 3 4, 9, from(invalidcolorname))", + + // 2011 GRADIENTS: -webkit-linear-gradient(), -webkit-radial -gradient() + // --------------------------------------------------------------------- + // Syntax that's invalid for all types of gradients: + // * empty gradient expressions: + "-webkit-linear-gradient()", + "-webkit-radial-gradient()", + "-webkit-repeating-linear-gradient()", + "-webkit-repeating-radial-gradient()", + + // Linear syntax that's invalid for both -webkit & unprefixed, but valid + // for -moz: + // * initial <legacy-gradient-line> which includes a length: + "-webkit-linear-gradient(10px, red, blue)", + "-webkit-linear-gradient(10px top, red, blue)", + // * initial <legacy-gradient-line> which includes a side *and* an angle: + "-webkit-linear-gradient(bottom 30deg, red, blue)", + "-webkit-linear-gradient(30deg bottom, red, blue)", + "-webkit-linear-gradient(10px top 50deg, red, blue)", + "-webkit-linear-gradient(50deg 10px top, red, blue)", + // * initial <legacy-gradient-line> which includes explicit "center": + "-webkit-linear-gradient(center, red, blue)", + "-webkit-linear-gradient(left center, red, blue)", + "-webkit-linear-gradient(top center, red, blue)", + "-webkit-linear-gradient(center top, red, blue)", + + // Linear syntax that's invalid for -webkit, but valid for -moz & unprefixed: + // * "to" syntax: + "-webkit-linear-gradient(to top, red, blue)", + + // * <shape> followed by angle: + "-webkit-radial-gradient(circle 10deg, red, blue)", + + // Radial syntax that's invalid for both -webkit & -moz, but valid for + // unprefixed: + // * "<shape> at <position>" syntax: + "-webkit-radial-gradient(circle at left bottom, red, blue)", + // * explicitly-sized shape: + "-webkit-radial-gradient(circle 10px, red, blue)", + "-webkit-radial-gradient(ellipse 40px 20px, red, blue)", + + // Radial syntax that's invalid for both -webkit & unprefixed, but valid + // for -moz: + // * initial angle + "-webkit-radial-gradient(30deg, red, blue)", + // * initial angle/position combo + "-webkit-radial-gradient(top 30deg, red, blue)", + "-webkit-radial-gradient(left top 30deg, red, blue)", + "-webkit-radial-gradient(10px 20px 30deg, red, blue)" + ); +} + +var gCSSProperties = { + "animation": { + domProp: "animation", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "animation-name", "animation-duration", "animation-timing-function", "animation-delay", "animation-direction", "animation-fill-mode", "animation-iteration-count", "animation-play-state" ], + initial_values: [ "none none 0s 0s ease normal running 1.0", "none", "0s", "ease", "normal", "running", "1.0" ], + other_values: [ "none none 0s 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) normal running 1.0", "bounce 1s linear 2s", "bounce 1s 2s linear", "bounce linear 1s 2s", "linear bounce 1s 2s", "linear 1s bounce 2s", "linear 1s 2s bounce", "1s bounce linear 2s", "1s bounce 2s linear", "1s 2s bounce linear", "1s linear bounce 2s", "1s linear 2s bounce", "1s 2s linear bounce", "bounce linear 1s", "bounce 1s linear", "linear bounce 1s", "linear 1s bounce", "1s bounce linear", "1s linear bounce", "1s 2s bounce", "1s bounce 2s", "bounce 1s 2s", "1s 2s linear", "1s linear 2s", "linear 1s 2s", "bounce 1s", "1s bounce", "linear 1s", "1s linear", "1s 2s", "2s 1s", "bounce", "linear", "1s", "height", "2s", "ease-in-out", "2s ease-in", "opacity linear", "ease-out 2s", "2s color, 1s bounce, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)", "1s \\32bounce linear 2s", "1s -bounce linear 2s", "1s -\\32bounce linear 2s", "1s \\32 0bounce linear 2s", "1s -\\32 0bounce linear 2s", "1s \\2bounce linear 2s", "1s -\\2bounce linear 2s", "2s, 1s bounce", "1s bounce, 2s", "2s all, 1s bounce", "1s bounce, 2s all", "1s bounce, 2s none", "2s none, 1s bounce", "2s bounce, 1s all", "2s all, 1s bounce" ], + invalid_values: [ "2s inherit", "inherit 2s", "2s bounce, 1s inherit", "2s inherit, 1s bounce", "2s initial", "2s all,, 1s bounce", "2s all, , 1s bounce", "bounce 1s cubic-bezier(0, rubbish) 2s", "bounce 1s steps(rubbish) 2s" ] + }, + "animation-delay": { + domProp: "animationDelay", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0s", "0ms" ], + other_values: [ "1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s"], + invalid_values: [ "0", "0px" ] + }, + "animation-direction": { + domProp: "animationDirection", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "alternate", "normal, alternate", "alternate, normal", "normal, normal", "normal, normal, normal", "reverse", "alternate-reverse", "normal, reverse, alternate-reverse, alternate" ], + invalid_values: [ "normal normal", "inherit, normal", "reverse-alternate" ] + }, + "animation-duration": { + domProp: "animationDuration", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0s", "0ms" ], + other_values: [ "1s", "250ms", "1s, 250ms, 2.3s"], + invalid_values: [ "0", "0px", "-1ms", "-2s" ] + }, + "animation-fill-mode": { + domProp: "animationFillMode", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "forwards", "backwards", "both", "none, none", "forwards, backwards", "forwards, none", "none, both" ], + invalid_values: [ "all"] + }, + "animation-iteration-count": { + domProp: "animationIterationCount", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1" ], + other_values: [ "infinite", "0", "0.5", "7.75", "-0.0", "1, 2, 3", "infinite, 2", "1, infinite" ], + // negatives forbidden per + // http://lists.w3.org/Archives/Public/www-style/2011Mar/0355.html + invalid_values: [ "none", "-1", "-0.5", "-1, infinite", "infinite, -3" ] + }, + "animation-name": { + domProp: "animationName", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "all", "ball", "mall", "color", "bounce, bubble, opacity", "foobar", "auto", "\\32bounce", "-bounce", "-\\32bounce", "\\32 0bounce", "-\\32 0bounce", "\\2bounce", "-\\2bounce" ], + invalid_values: [ "bounce, initial", "initial, bounce", "bounce, inherit", "inherit, bounce" ] + }, + "animation-play-state": { + domProp: "animationPlayState", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "running" ], + other_values: [ "paused", "running, running", "paused, running", "paused, paused", "running, paused", "paused, running, running, running, paused, running" ], + invalid_values: [ "0" ] + }, + "animation-timing-function": { + domProp: "animationTimingFunction", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "ease" ], + other_values: [ "cubic-bezier(0.25, 0.1, 0.25, 1.0)", "linear", "ease-in", "ease-out", "ease-in-out", "linear, ease-in, cubic-bezier(0.1, 0.2, 0.8, 0.9)", "cubic-bezier(0.5, 0.5, 0.5, 0.5)", "cubic-bezier(0.25, 1.5, 0.75, -0.5)", "step-start", "step-end", "steps(1)", "steps(2, start)", "steps(386)", "steps(3, end)" ], + invalid_values: [ "none", "auto", "cubic-bezier(0.25, 0.1, 0.25)", "cubic-bezier(0.25, 0.1, 0.25, 0.25, 1.0)", "cubic-bezier(-0.5, 0.5, 0.5, 0.5)", "cubic-bezier(1.5, 0.5, 0.5, 0.5)", "cubic-bezier(0.5, 0.5, -0.5, 0.5)", "cubic-bezier(0.5, 0.5, 1.5, 0.5)", "steps(2, step-end)", "steps(0)", "steps(-2)", "steps(0, step-end, 1)" ] + }, + "-moz-appearance": { + domProp: "MozAppearance", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "radio", "menulist" ], + invalid_values: [] + }, + "-moz-binding": { + domProp: "MozBinding", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "url(foo.xml)" ], + invalid_values: [] + }, + "-moz-border-bottom-colors": { + domProp: "MozBorderBottomColors", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "red green", "red #fc3", "#ff00cc", "currentColor", "blue currentColor orange currentColor" ], + invalid_values: [ "red none", "red inherit", "red, green", "none red", "inherit red", "ff00cc" ] + }, + "border-inline-end": { + domProp: "borderInlineEnd", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-inline-end-color", "border-inline-end-style", "border-inline-end-width" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], + other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ], + invalid_values: [ "5%", "5", "5 green none" ] + }, + "border-inline-end-color": { + domProp: "borderInlineEndColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000" ] + }, + "border-inline-end-style": { + domProp: "borderInlineEndStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge" ], + invalid_values: [] + }, + "border-inline-end-width": { + domProp: "borderInlineEndWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + prerequisites: { "border-inline-end-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0em)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%", "5" ] + }, + "border-image": { + domProp: "borderImage", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-image-source", "border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat" ], + initial_values: [ "none" ], + other_values: [ "url('border.png') 27 27 27 27", + "url('border.png') 27", + "stretch url('border.png')", + "url('border.png') 27 fill", + "url('border.png') 27 27 27 27 repeat", + "repeat url('border.png') 27 27 27 27", + "url('border.png') repeat 27 27 27 27", + "url('border.png') fill 27 27 27 27 repeat", + "url('border.png') fill 27 27 27 27 repeat space", + "url('border.png') 27 27 27 27 / 1em", + "27 27 27 27 / 1em url('border.png') ", + "url('border.png') 27 27 27 27 / 10 10 10 / 10 10 repeat", + "repeat 27 27 27 27 / 10 10 10 / 10 10 url('border.png')", + "url('border.png') 27 27 27 27 / / 10 10 1em", + "fill 27 27 27 27 / / 10 10 1em url('border.png')", + "url('border.png') 27 27 27 27 / 1em 1em 1em 1em repeat", + "url('border.png') 27 27 27 27 / 1em 1em 1em 1em stretch round" ], + invalid_values: [ "url('border.png') 27 27 27 27 27", + "url('border.png') 27 27 27 27 / 1em 1em 1em 1em 1em", + "url('border.png') 27 27 27 27 /", + "url('border.png') fill", + "url('border.png') fill repeat", + "fill repeat", + "url('border.png') fill / 1em", + "url('border.png') / repeat", + "url('border.png') 1 /", + "url('border.png') 1 / /", + "1 / url('border.png')", + "url('border.png') / 1", + "url('border.png') / / 1"] + }, + "border-image-source": { + domProp: "borderImageSource", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + "url('border.png')" + ].concat(validGradientAndElementValues), + invalid_values: [ + "url('border.png') url('border.png')", + ].concat(invalidGradientAndElementValues), + unbalanced_values: [ + ].concat(unbalancedGradientAndElementValues) + }, + "border-image-slice": { + domProp: "borderImageSlice", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "100%", "100% 100% 100% 100%" ], + other_values: [ "0%", "10", "10 100% 0 2", "0 0 0 0", "fill 10 10", "10 10 fill" ], + invalid_values: [ "-10%", "-10", "10 10 10 10 10", "10 10 10 10 -10", "10px", "-10px", "fill", "fill fill 10px", "10px fill fill" ] + }, + "border-image-width": { + domProp: "borderImageWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1", "1 1 1 1" ], + other_values: [ "0", "0%", "0px", "auto auto auto auto", "10 10% auto 15px", "10px 10px 10px 10px", "10", "10 10", "10 10 10" ], + invalid_values: [ "-10", "-10px", "-10%", "10 10 10 10 10", "10 10 10 10 auto", "auto auto auto auto auto", "10px calc(nonsense)", "1px red" ], + unbalanced_values: [ "10px calc(" ] + }, + "border-image-outset": { + domProp: "borderImageOutset", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0 0 0 0" ], + other_values: [ "10px", "10", "10 10", "10 10 10", "10 10 10 10", "10px 10 10 10px" ], + invalid_values: [ "-10", "-10px", "-10%", "10%", "10 10 10 10 10", "10px calc(nonsense)", "1px red" ], + unbalanced_values: [ "10px calc(" ] + }, + "border-image-repeat": { + domProp: "borderImageRepeat", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "stretch", "stretch stretch" ], + other_values: [ "round", "repeat", "stretch round", "repeat round", "stretch repeat", "round round", "repeat repeat", + "space", "stretch space", "repeat space", "round space", "space space" ], + invalid_values: [ "none", "stretch stretch stretch", "0", "10", "0%", "0px" ] + }, + "-moz-border-left-colors": { + domProp: "MozBorderLeftColors", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "red green", "red #fc3", "#ff00cc", "currentColor", "blue currentColor orange currentColor" ], + invalid_values: [ "red none", "red inherit", "red, green", "none red", "inherit red", "ff00cc" ] + }, + "border-radius": { + domProp: "borderRadius", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + subproperties: [ "border-bottom-left-radius", "border-bottom-right-radius", "border-top-left-radius", "border-top-right-radius" ], + initial_values: [ "0", "0px", "0px 0 0 0px", "calc(-2px)", "calc(0px) calc(0pt)", "calc(0px) calc(0pt) calc(0px) calc(0em)" ], + other_values: [ "0%", "3%", "1px", "2em", "3em 2px", "2pt 3% 4em", "2px 2px 2px 2px", // circular + "3% / 2%", "1px / 4px", "2em / 1em", "3em 2px / 2px 3em", "2pt 3% 4em / 4pt 1% 5em", "2px 2px 2px 2px / 4px 4px 4px 4px", "1pt / 2pt 3pt", "4pt 5pt / 3pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "2px 2px calc(2px + 1%) 2px", + "1px 2px 2px 2px / 2px 2px calc(2px + 1%) 2px", + ], + invalid_values: [ "2px -2px", "inherit 2px", "inherit / 2px", "2px inherit", "2px / inherit", "2px 2px 2px 2px 2px", "1px / 2px 2px 2px 2px 2px", "2", "2 2", "2px 2px 2px 2px / 2px 2px 2 2px", "2px calc(0px + rubbish)" ] + }, + "border-bottom-left-radius": { + domProp: "borderBottomLeftRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)" ] + }, + "border-bottom-right-radius": { + domProp: "borderBottomRightRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)" ] + }, + "border-top-left-radius": { + domProp: "borderTopLeftRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)" ] + }, + "border-top-right-radius": { + domProp: "borderTopRightRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)" ] + }, + "-moz-border-right-colors": { + domProp: "MozBorderRightColors", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "red green", "red #fc3", "#ff00cc", "currentColor", "blue currentColor orange currentColor" ], + invalid_values: [ "red none", "red inherit", "red, green", "none red", "inherit red", "ff00cc" ] + }, + "border-inline-start": { + domProp: "borderInlineStart", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-inline-start-color", "border-inline-start-style", "border-inline-start-width" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], + other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ], + invalid_values: [ "5%", "5", "5 green solid" ] + }, + "border-inline-start-color": { + domProp: "borderInlineStartColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000" ] + }, + "border-inline-start-style": { + domProp: "borderInlineStartStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge" ], + invalid_values: [] + }, + "border-inline-start-width": { + domProp: "borderInlineStartWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + prerequisites: { "border-inline-start-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0em)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%", "5" ] + }, + "-moz-border-top-colors": { + domProp: "MozBorderTopColors", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "red green", "red #fc3", "#ff00cc", "currentColor", "blue currentColor orange currentColor" ], + invalid_values: [ "red none", "red inherit", "red, green", "none red", "inherit red", "ff00cc" ] + }, + "-moz-box-align": { + domProp: "MozBoxAlign", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "stretch" ], + other_values: [ "start", "center", "baseline", "end" ], + invalid_values: [] + }, + "-moz-box-direction": { + domProp: "MozBoxDirection", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "reverse" ], + invalid_values: [] + }, + "-moz-box-flex": { + domProp: "MozBoxFlex", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0.0", "-0.0" ], + other_values: [ "1", "100", "0.1" ], + invalid_values: [ "10px", "-1" ] + }, + "-moz-box-ordinal-group": { + domProp: "MozBoxOrdinalGroup", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1" ], + other_values: [ "2", "100", "0" ], + invalid_values: [ "1.0", "-1", "-1000" ] + }, + "-moz-box-orient": { + domProp: "MozBoxOrient", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "horizontal", "inline-axis" ], + other_values: [ "vertical", "block-axis" ], + invalid_values: [] + }, + "-moz-box-pack": { + domProp: "MozBoxPack", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "start" ], + other_values: [ "center", "end", "justify" ], + invalid_values: [] + }, + "box-sizing": { + domProp: "boxSizing", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "content-box" ], + other_values: [ "border-box" ], + invalid_values: [ "padding-box", "margin-box", "content", "padding", "border", "margin" ] + }, + "-moz-box-sizing": { + domProp: "MozBoxSizing", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "box-sizing", + subproperties: [ "box-sizing" ], + }, + "color-adjust": { + domProp: "colorAdjust", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "economy" ], + other_values: [ "exact" ], + invalid_values: [] + }, + "columns": { + domProp: "columns", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "column-count", "column-width" ], + initial_values: [ "auto", "auto auto" ], + other_values: [ "3", "20px", "2 10px", "10px 2", "2 auto", "auto 2", "auto 50px", "50px auto" ], + invalid_values: [ "5%", "-1px", "-1", "3 5", "10px 4px", "10 2px 5in", "30px -1", + "auto 3 5px", "5 auto 20px", "auto auto auto", "calc(50px + rubbish) 2" ] + }, + "-moz-columns": { + domProp: "MozColumns", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "columns", + subproperties: [ "column-count", "column-width" ] + }, + "column-count": { + domProp: "columnCount", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "1", "17" ], + // negative and zero invalid per editor's draft + invalid_values: [ "-1", "0", "3px" ] + }, + "-moz-column-count": { + domProp: "MozColumnCount", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-count", + subproperties: [ "column-count" ] + }, + "column-fill": { + domProp: "columnFill", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "balance" ], + other_values: [ "auto" ], + invalid_values: [ "2px", "dotted", "5em" ] + }, + "-moz-column-fill": { + domProp: "MozColumnFill", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-fill", + subproperties: [ "column-fill" ] + }, + "column-gap": { + domProp: "columnGap", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal", "1em", "calc(-2em + 3em)" ], + other_values: [ "2px", "4em", + "calc(2px)", + "calc(-2px)", + "calc(0px)", + "calc(0pt)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "3%", "-1px", "4" ] + }, + "-moz-column-gap": { + domProp: "MozColumnGap", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-gap", + subproperties: [ "column-gap" ] + }, + "column-rule": { + domProp: "columnRule", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + prerequisites: { "color": "green" }, + subproperties: [ "column-rule-width", "column-rule-style", "column-rule-color" ], + initial_values: [ "medium none currentColor", "none", "medium", "currentColor" ], + other_values: [ "2px blue solid", "red dotted 1px", "ridge 4px orange", "5px solid" ], + invalid_values: [ "2px 3px 4px red", "dotted dashed", "5px dashed green 3px", "5 solid", "5 green solid" ] + }, + "-moz-column-rule": { + domProp: "MozColumnRule", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "column-rule", + subproperties: [ "column-rule-width", "column-rule-style", "column-rule-color" ] + }, + "column-rule-width": { + domProp: "columnRuleWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "-moz-column-rule-style": "solid" }, + initial_values: [ + "medium", + "3px", + "-moz-calc(3px)", + "-moz-calc(5em + 3px - 5em)", + "calc(3px)", + "calc(5em + 3px - 5em)", + ], + other_values: [ "thin", "15px", + /* valid -moz-calc() values */ + "-moz-calc(-2px)", + "-moz-calc(2px)", + "-moz-calc(3em)", + "-moz-calc(3em + 2px)", + "-moz-calc( 3em + 2px)", + "-moz-calc(3em + 2px )", + "-moz-calc( 3em + 2px )", + "-moz-calc(3*25px)", + "-moz-calc(3 *25px)", + "-moz-calc(3 * 25px)", + "-moz-calc(3* 25px)", + "-moz-calc(25px*3)", + "-moz-calc(25px *3)", + "-moz-calc(25px* 3)", + "-moz-calc(25px * 3)", + "-moz-calc(25px * 3 / 4)", + "-moz-calc((25px * 3) / 4)", + "-moz-calc(25px * (3 / 4))", + "-moz-calc(3 * 25px / 4)", + "-moz-calc((3 * 25px) / 4)", + "-moz-calc(3 * (25px / 4))", + "-moz-calc(3em + 25px * 3 / 4)", + "-moz-calc(3em + (25px * 3) / 4)", + "-moz-calc(3em + 25px * (3 / 4))", + "-moz-calc(25px * 3 / 4 + 3em)", + "-moz-calc((25px * 3) / 4 + 3em)", + "-moz-calc(25px * (3 / 4) + 3em)", + "-moz-calc(3em + (25px * 3 / 4))", + "-moz-calc(3em + ((25px * 3) / 4))", + "-moz-calc(3em + (25px * (3 / 4)))", + "-moz-calc((25px * 3 / 4) + 3em)", + "-moz-calc(((25px * 3) / 4) + 3em)", + "-moz-calc((25px * (3 / 4)) + 3em)", + "-moz-calc(3*25px + 1in)", + "-moz-calc(1in - 3em + 2px)", + "-moz-calc(1in - (3em + 2px))", + "-moz-calc((1in - 3em) + 2px)", + "-moz-calc(50px/2)", + "-moz-calc(50px/(2 - 1))", + "-moz-calc(-3px)", + /* numeric reduction cases */ + "-moz-calc(5 * 3 * 2em)", + "-moz-calc(2em * 5 * 3)", + "-moz-calc((5 * 3) * 2em)", + "-moz-calc(2em * (5 * 3))", + "-moz-calc((5 + 3) * 2em)", + "-moz-calc(2em * (5 + 3))", + "-moz-calc(2em / (5 + 3))", + "-moz-calc(2em * (5*2 + 3))", + "-moz-calc(2em * ((5*2) + 3))", + "-moz-calc(2em * (5*(2 + 3)))", + + "-moz-calc((5 + 7) * 3em)", + "-moz-calc((5em + 3em) - 2em)", + "-moz-calc((5em - 3em) + 2em)", + "-moz-calc(2em - (5em - 3em))", + "-moz-calc(2em + (5em - 3em))", + "-moz-calc(2em - (5em + 3em))", + "-moz-calc(2em + (5em + 3em))", + "-moz-calc(2em + 5em - 3em)", + "-moz-calc(2em - 5em - 3em)", + "-moz-calc(2em + 5em + 3em)", + "-moz-calc(2em - 5em + 3em)", + + "-moz-calc(2em / 4 * 3)", + "-moz-calc(2em * 4 / 3)", + "-moz-calc(2em * 4 * 3)", + "-moz-calc(2em / 4 / 3)", + "-moz-calc(4 * 2em / 3)", + "-moz-calc(4 / 3 * 2em)", + + "-moz-calc((2em / 4) * 3)", + "-moz-calc((2em * 4) / 3)", + "-moz-calc((2em * 4) * 3)", + "-moz-calc((2em / 4) / 3)", + "-moz-calc((4 * 2em) / 3)", + "-moz-calc((4 / 3) * 2em)", + + "-moz-calc(2em / (4 * 3))", + "-moz-calc(2em * (4 / 3))", + "-moz-calc(2em * (4 * 3))", + "-moz-calc(2em / (4 / 3))", + "-moz-calc(4 * (2em / 3))", + + // Valid cases with unitless zero (which is never + // a length). + "-moz-calc(0 * 2em)", + "-moz-calc(2em * 0)", + "-moz-calc(3em + 0 * 2em)", + "-moz-calc(3em + 2em * 0)", + "-moz-calc((0 + 2) * 2em)", + "-moz-calc((2 + 0) * 2em)", + // And test zero lengths while we're here. + "-moz-calc(2 * 0px)", + "-moz-calc(0 * 0px)", + "-moz-calc(2 * 0em)", + "-moz-calc(0 * 0em)", + "-moz-calc(0px * 0)", + "-moz-calc(0px * 2)", + + /* valid calc() values */ + "calc(-2px)", + "calc(2px)", + "calc(3em)", + "calc(3em + 2px)", + "calc( 3em + 2px)", + "calc(3em + 2px )", + "calc( 3em + 2px )", + "calc(3*25px)", + "calc(3 *25px)", + "calc(3 * 25px)", + "calc(3* 25px)", + "calc(25px*3)", + "calc(25px *3)", + "calc(25px* 3)", + "calc(25px * 3)", + "calc(25px * 3 / 4)", + "calc((25px * 3) / 4)", + "calc(25px * (3 / 4))", + "calc(3 * 25px / 4)", + "calc((3 * 25px) / 4)", + "calc(3 * (25px / 4))", + "calc(3em + 25px * 3 / 4)", + "calc(3em + (25px * 3) / 4)", + "calc(3em + 25px * (3 / 4))", + "calc(25px * 3 / 4 + 3em)", + "calc((25px * 3) / 4 + 3em)", + "calc(25px * (3 / 4) + 3em)", + "calc(3em + (25px * 3 / 4))", + "calc(3em + ((25px * 3) / 4))", + "calc(3em + (25px * (3 / 4)))", + "calc((25px * 3 / 4) + 3em)", + "calc(((25px * 3) / 4) + 3em)", + "calc((25px * (3 / 4)) + 3em)", + "calc(3*25px + 1in)", + "calc(1in - 3em + 2px)", + "calc(1in - (3em + 2px))", + "calc((1in - 3em) + 2px)", + "calc(50px/2)", + "calc(50px/(2 - 1))", + "calc(-3px)", + /* numeric reduction cases */ + "calc(5 * 3 * 2em)", + "calc(2em * 5 * 3)", + "calc((5 * 3) * 2em)", + "calc(2em * (5 * 3))", + "calc((5 + 3) * 2em)", + "calc(2em * (5 + 3))", + "calc(2em / (5 + 3))", + "calc(2em * (5*2 + 3))", + "calc(2em * ((5*2) + 3))", + "calc(2em * (5*(2 + 3)))", + + "calc((5 + 7) * 3em)", + "calc((5em + 3em) - 2em)", + "calc((5em - 3em) + 2em)", + "calc(2em - (5em - 3em))", + "calc(2em + (5em - 3em))", + "calc(2em - (5em + 3em))", + "calc(2em + (5em + 3em))", + "calc(2em + 5em - 3em)", + "calc(2em - 5em - 3em)", + "calc(2em + 5em + 3em)", + "calc(2em - 5em + 3em)", + + "calc(2em / 4 * 3)", + "calc(2em * 4 / 3)", + "calc(2em * 4 * 3)", + "calc(2em / 4 / 3)", + "calc(4 * 2em / 3)", + "calc(4 / 3 * 2em)", + + "calc((2em / 4) * 3)", + "calc((2em * 4) / 3)", + "calc((2em * 4) * 3)", + "calc((2em / 4) / 3)", + "calc((4 * 2em) / 3)", + "calc((4 / 3) * 2em)", + + "calc(2em / (4 * 3))", + "calc(2em * (4 / 3))", + "calc(2em * (4 * 3))", + "calc(2em / (4 / 3))", + "calc(4 * (2em / 3))", + + // Valid cases with unitless zero (which is never + // a length). + "calc(0 * 2em)", + "calc(2em * 0)", + "calc(3em + 0 * 2em)", + "calc(3em + 2em * 0)", + "calc((0 + 2) * 2em)", + "calc((2 + 0) * 2em)", + // And test zero lengths while we're here. + "calc(2 * 0px)", + "calc(0 * 0px)", + "calc(2 * 0em)", + "calc(0 * 0em)", + "calc(0px * 0)", + "calc(0px * 2)", + + ], + invalid_values: [ "20", "-1px", "red", "50%", + /* invalid -moz-calc() values */ + "-moz-calc(2em+ 2px)", + "-moz-calc(2em +2px)", + "-moz-calc(2em+2px)", + "-moz-calc(2em- 2px)", + "-moz-calc(2em -2px)", + "-moz-calc(2em-2px)", + /* invalid calc() values */ + "calc(2em+ 2px)", + "calc(2em +2px)", + "calc(2em+2px)", + "calc(2em- 2px)", + "calc(2em -2px)", + "calc(2em-2px)", + "-moz-min()", + "calc(min())", + "-moz-max()", + "calc(max())", + "-moz-min(5px)", + "calc(min(5px))", + "-moz-max(5px)", + "calc(max(5px))", + "-moz-min(5px,2em)", + "calc(min(5px,2em))", + "-moz-max(5px,2em)", + "calc(max(5px,2em))", + "calc(50px/(2 - 2))", + "calc(5 + 5)", + "calc(5 * 5)", + "calc(5em * 5em)", + "calc(5em / 5em * 5em)", + + "calc(4 * 3 / 2em)", + "calc((4 * 3) / 2em)", + "calc(4 * (3 / 2em))", + "calc(4 / (3 * 2em))", + + // Tests for handling of unitless zero, which cannot + // be a length inside calc(). + "calc(0)", + "calc(0 + 2em)", + "calc(2em + 0)", + "calc(0 * 2)", + "calc(2 * 0)", + "calc(1 * (2em + 0))", + "calc((2em + 0))", + "calc((2em + 0) * 1)", + "calc(1 * (0 + 2em))", + "calc((0 + 2em))", + "calc((0 + 2em) * 1)", + ] + }, + "-moz-column-rule-width": { + domProp: "MozColumnRuleWidth", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-rule-width", + subproperties: [ "column-rule-width" ] + }, + "column-rule-style": { + domProp: "columnRuleStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "solid", "hidden", "ridge", "groove", "inset", "outset", "double", "dotted", "dashed" ], + invalid_values: [ "20", "foo" ] + }, + "-moz-column-rule-style": { + domProp: "MozColumnRuleStyle", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-rule-style", + subproperties: [ "column-rule-style" ] + }, + "column-rule-color": { + domProp: "columnRuleColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "green" }, + initial_values: [ "currentColor" ], + other_values: [ "red", "blue", "#ffff00" ], + invalid_values: [ "ffff00" ] + }, + "-moz-column-rule-color": { + domProp: "MozColumnRuleColor", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-rule-color", + subproperties: [ "column-rule-color" ] + }, + "column-width": { + domProp: "columnWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ + "15px", + "calc(15px)", + "calc(30px - 3em)", + "calc(-15px)", + "0px", + "calc(0px)" + ], + invalid_values: [ "20", "-1px", "50%" ] + }, + "-moz-column-width": { + domProp: "MozColumnWidth", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-width", + subproperties: [ "column-width" ] + }, + "-moz-float-edge": { + domProp: "MozFloatEdge", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "content-box" ], + other_values: [ "margin-box" ], + invalid_values: [ "content", "padding", "border", "margin" ] + }, + "-moz-force-broken-image-icon": { + domProp: "MozForceBrokenImageIcon", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0" ], + other_values: [ "1" ], + invalid_values: [] + }, + "-moz-image-region": { + domProp: "MozImageRegion", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "rect(3px 20px 15px 4px)", "rect(17px, 21px, 33px, 2px)" ], + invalid_values: [ "rect(17px, 21px, 33, 2px)" ] + }, + "margin-inline-end": { + domProp: "marginInlineEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* no subproperties */ + /* auto may or may not be initial */ + initial_values: [ "0", "0px", "0%", "0em", "0ex", "calc(0pt)", "calc(0% + 0px)" ], + other_values: [ "1px", "3em", "5%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "5", "..25px", ".+5px", ".px", "-.px", "++5px", "-+4px", "+-3px", "--7px", "+-.6px", "-+.5px", "++.7px", "--.4px" ], + }, + "margin-inline-start": { + domProp: "marginInlineStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* no subproperties */ + /* auto may or may not be initial */ + initial_values: [ "0", "0px", "0%", "0em", "0ex", "calc(0pt)", "calc(0% + 0px)" ], + other_values: [ "1px", "3em", "5%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "5", "..25px", ".+5px", ".px", "-.px", "++5px", "-+4px", "+-3px", "--7px", "+-.6px", "-+.5px", "++.7px", "--.4px" ], + }, + "mask-type": { + domProp: "maskType", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "luminance" ], + other_values: [ "alpha" ], + invalid_values: [], + }, + "-moz-outline-radius": { + domProp: "MozOutlineRadius", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + subproperties: [ "-moz-outline-radius-bottomleft", "-moz-outline-radius-bottomright", "-moz-outline-radius-topleft", "-moz-outline-radius-topright" ], + initial_values: [ "0", "0px", "calc(-2px)", "calc(0px) calc(0pt)", "calc(0px) calc(0em)" ], + other_values: [ "0%", "3%", "1px", "2em", "3em 2px", "2pt 3% 4em", "2px 2px 2px 2px", // circular + "3% / 2%", "1px / 4px", "2em / 1em", "3em 2px / 2px 3em", "2pt 3% 4em / 4pt 1% 5em", "2px 2px 2px 2px / 4px 4px 4px 4px", "1pt / 2pt 3pt", "4pt 5pt / 3pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "2px 2px calc(2px + 1%) 2px", + "1px 2px 2px 2px / 2px 2px calc(2px + 1%) 2px", + ], + invalid_values: [ "2px -2px", "inherit 2px", "inherit / 2px", "2px inherit", "2px / inherit", "2px 2px 2px 2px 2px", "1px / 2px 2px 2px 2px 2px", "2", "2 2", "2px 2px 2px 2px / 2px 2px 2 2px" ] + }, + "-moz-outline-radius-bottomleft": { + domProp: "MozOutlineRadiusBottomleft", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)", "calc(0px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px" ] + }, + "-moz-outline-radius-bottomright": { + domProp: "MozOutlineRadiusBottomright", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)", "calc(0px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px" ] + }, + "-moz-outline-radius-topleft": { + domProp: "MozOutlineRadiusTopleft", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)", "calc(0px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px" ] + }, + "-moz-outline-radius-topright": { + domProp: "MozOutlineRadiusTopright", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "200px", "height": "100px", "display": "inline-block"}, + initial_values: [ "0", "0px", "calc(-2px)", "calc(0px)" ], + other_values: [ "0%", "3%", "1px", "2em", // circular + "3% 2%", "1px 4px", "2em 2pt", // elliptical + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px" ] + }, + "padding-inline-end": { + domProp: "paddingInlineEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* no subproperties */ + initial_values: [ "0", "0px", "0%", "0em", "0ex", "calc(0pt)", "calc(0% + 0px)", "calc(-3px)", "calc(-1%)" ], + other_values: [ "1px", "3em", "5%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "5" ] + }, + "padding-inline-start": { + domProp: "paddingInlineStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* no subproperties */ + initial_values: [ "0", "0px", "0%", "0em", "0ex", "calc(0pt)", "calc(0% + 0px)", "calc(-3px)", "calc(-1%)" ], + other_values: [ "1px", "3em", "5%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "5" ] + }, + "resize": { + domProp: "resize", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "display": "block", "overflow": "auto" }, + initial_values: [ "none" ], + other_values: [ "both", "horizontal", "vertical" ], + invalid_values: [] + }, + "-moz-stack-sizing": { + domProp: "MozStackSizing", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "stretch-to-fit" ], + other_values: [ "ignore" ], + invalid_values: [] + }, + "-moz-tab-size": { + domProp: "MozTabSize", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "8" ], + other_values: [ "0", "3", "99", "12000" ], + invalid_values: [ "-1", "-808", "3.0", "17.5" ] + }, + "-moz-text-size-adjust": { + domProp: "MozTextSizeAdjust", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "none" ], + invalid_values: [ "-5%", "0", "100", "0%", "50%", "100%", "220.3%" ] + }, + "transform": { + domProp: "transform", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "width": "300px", "height": "50px" }, + initial_values: [ "none" ], + other_values: [ "translatex(1px)", "translatex(4em)", + "translatex(-4px)", "translatex(3px)", + "translatex(0px) translatex(1px) translatex(2px) translatex(3px) translatex(4px)", + "translatey(4em)", "translate(3px)", "translate(10px, -3px)", + "rotate(45deg)", "rotate(45grad)", "rotate(45rad)", + "rotate(0.25turn)", "rotate(0)", "scalex(10)", "scaley(10)", + "scale(10)", "scale(10, 20)", "skewx(30deg)", "skewx(0)", + "skewy(0)", "skewx(30grad)", "skewx(30rad)", "skewx(0.08turn)", + "skewy(30deg)", "skewy(30grad)", "skewy(30rad)", "skewy(0.08turn)", + "rotate(45deg) scale(2, 1)", "skewx(45deg) skewx(-50grad)", + "translate(0, 0) scale(1, 1) skewx(0) skewy(0) matrix(1, 0, 0, 1, 0, 0)", + "translatex(50%)", "translatey(50%)", "translate(50%)", + "translate(3%, 5px)", "translate(5px, 3%)", + "matrix(1, 2, 3, 4, 5, 6)", + /* valid calc() values */ + "translatex(calc(5px + 10%))", + "translatey(calc(0.25 * 5px + 10% / 3))", + "translate(calc(5px - 10% * 3))", + "translate(calc(5px - 3 * 10%), 50px)", + "translate(-50px, calc(5px - 10% * 3))", + "translatez(1px)", "translatez(4em)", "translatez(-4px)", + "translatez(0px)", "translatez(2px) translatez(5px)", + "translate3d(3px, 4px, 5px)", "translate3d(2em, 3px, 1em)", + "translatex(2px) translate3d(4px, 5px, 6px) translatey(1px)", + "scale3d(4, 4, 4)", "scale3d(-2, 3, -7)", "scalez(4)", + "scalez(-6)", "rotate3d(2, 3, 4, 45deg)", + "rotate3d(-3, 7, 0, 12rad)", "rotatex(15deg)", "rotatey(-12grad)", + "rotatez(72rad)", "rotatex(0.125turn)", + "perspective(0px)", "perspective(1000px)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)", + ], + invalid_values: ["1px", "#0000ff", "red", "auto", + "translatex(1)", "translatey(1)", "translate(2)", + "translate(-3, -4)", + "translatex(1px 1px)", "translatex(translatex(1px))", + "translatex(#0000ff)", "translatex(red)", "translatey()", + "matrix(1px, 2px, 3px, 4px, 5px, 6px)", "scale(150%)", + "skewx(red)", "matrix(1%, 0, 0, 0, 0px, 0px)", + "matrix(0, 1%, 2, 3, 4px,5px)", "matrix(0, 1, 2%, 3, 4px, 5px)", + "matrix(0, 1, 2, 3%, 4%, 5%)", "matrix(1, 2, 3, 4, 5px, 6%)", + "matrix(1, 2, 3, 4, 5%, 6px)", "matrix(1, 2, 3, 4, 5%, 6%)", + "matrix(1, 2, 3, 4, 5px, 6em)", + /* invalid calc() values */ + "translatey(-moz-min(5px,10%))", + "translatex(-moz-max(5px,10%))", + "translate(10px, calc(min(5px,10%)))", + "translate(calc(max(5px,10%)), 10%)", + "matrix(1, 0, 0, 1, max(5px * 3), calc(10% - 3px))", + "perspective(-10px)", "matrix3d(dinosaur)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15%, 16)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16px)", + "rotatey(words)", "rotatex(7)", "translate3d(3px, 4px, 1px, 7px)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13px, 14em, 15px, 16)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20%, 10%, 15, 16)" + ], + }, + "transform-origin": { + domProp: "transformOrigin", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* no subproperties */ + prerequisites: { "width": "10px", "height": "10px", "display": "block"}, + initial_values: [ "50% 50%", "center", "center center" ], + other_values: [ "25% 25%", "6px 5px", "20% 3em", "0 0", "0in 1in", + "top", "bottom","top left", "top right", + "top center", "center left", "center right", + "bottom left", "bottom right", "bottom center", + "20% center", "6px center", "13in bottom", + "left 50px", "right 13%", "center 40px", + "calc(20px)", + "calc(20px) 10px", + "10px calc(20px)", + "calc(20px) 25%", + "25% calc(20px)", + "calc(20px) calc(20px)", + "calc(20px + 1em) calc(20px / 2)", + "calc(20px + 50%) calc(50% - 10px)", + "calc(-20px) calc(-50%)", + "calc(-20%) calc(-50%)", + "6px 5px 5px", + "top center 10px" + ], + invalid_values: ["red", "auto", "none", "0.5 0.5", "40px #0000ff", + "border", "center red", "right diagonal", + "#00ffff bottom", "0px calc(0px + rubbish)", + "0px 0px calc(0px + rubbish)"] + }, + "perspective-origin": { + domProp: "perspectiveOrigin", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* no subproperties */ + prerequisites: { "width": "10px", "height": "10px", "display": "block"}, + initial_values: [ "50% 50%", "center", "center center" ], + other_values: [ "25% 25%", "6px 5px", "20% 3em", "0 0", "0in 1in", + "top", "bottom","top left", "top right", + "top center", "center left", "center right", + "bottom left", "bottom right", "bottom center", + "20% center", "6px center", "13in bottom", + "left 50px", "right 13%", "center 40px", + "calc(20px)", + "calc(20px) 10px", + "10px calc(20px)", + "calc(20px) 25%", + "25% calc(20px)", + "calc(20px) calc(20px)", + "calc(20px + 1em) calc(20px / 2)", + "calc(20px + 50%) calc(50% - 10px)", + "calc(-20px) calc(-50%)", + "calc(-20%) calc(-50%)" ], + invalid_values: [ "red", "auto", "none", "0.5 0.5", "40px #0000ff", + "border", "center red", "right diagonal", + "#00ffff bottom"] + }, + "perspective": { + domProp: "perspective", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "1000px", "500.2px", "0", "0px" ], + invalid_values: [ "pants", "200", "-100px", "-27.2em" ] + }, + "backface-visibility": { + domProp: "backfaceVisibility", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "visible" ], + other_values: [ "hidden" ], + invalid_values: [ "collapse" ] + }, + "transform-style": { + domProp: "transformStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "flat" ], + other_values: [ "preserve-3d" ], + invalid_values: [] + }, + "-moz-user-focus": { + domProp: "MozUserFocus", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "normal", "ignore", "select-all", "select-before", "select-after", "select-same", "select-menu" ], + invalid_values: [] + }, + "-moz-user-input": { + domProp: "MozUserInput", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "none", "enabled", "disabled" ], + invalid_values: [] + }, + "-moz-user-modify": { + domProp: "MozUserModify", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "read-only" ], + other_values: [ "read-write", "write-only" ], + invalid_values: [] + }, + "-moz-user-select": { + domProp: "MozUserSelect", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "none", "text", "element", "elements", "all", "toggle", "tri-state", "-moz-all", "-moz-none" ], + invalid_values: [] + }, + "background": { + domProp: "background", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "background-attachment", "background-color", "background-image", "background-position-x", "background-position-y", "background-repeat", "background-clip", "background-origin", "background-size" ], + initial_values: [ "transparent", "none", "repeat", "scroll", "0% 0%", "top left", "left top", "0% 0% / auto", "top left / auto", "left top / auto", "0% 0% / auto auto", + "transparent none", "top left none", "left top none", "none left top", "none top left", "none 0% 0%", "left top / auto none", "left top / auto auto none", + "transparent none repeat scroll top left", "left top repeat none scroll transparent", "transparent none repeat scroll top left / auto", "left top / auto repeat none scroll transparent", "none repeat scroll 0% 0% / auto auto transparent", + "padding-box border-box" ], + other_values: [ + /* without multiple backgrounds */ + "green", + "none green repeat scroll left top", + "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", + "repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') transparent left top scroll", + "repeat-x", + "repeat-y", + "no-repeat", + "none repeat-y transparent scroll 0% 0%", + "fixed", + "0% top transparent fixed repeat none", + "top", + "left", + "50% 50%", + "center", + "top / 100px", + "left / contain", + "left / cover", + "10px / 10%", + "10em / calc(20px)", + "top left / 100px 100px", + "top left / 100px auto", + "top left / 100px 10%", + "top left / 100px calc(20px)", + "bottom right scroll none transparent repeat", + "50% transparent", + "transparent 50%", + "50%", + "-moz-radial-gradient(10% bottom, #ffffff, black) scroll no-repeat", + "-moz-linear-gradient(10px 10px -45deg, red, blue) repeat", + "-moz-linear-gradient(10px 10px -0.125turn, red, blue) repeat", + "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) scroll no-repeat", + "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) repeat", + "-moz-element(#test) lime", + /* multiple backgrounds */ + "url(404.png), url(404.png)", + "url(404.png), url(404.png) transparent", + "url(404.png), url(404.png) red", + "repeat-x, fixed, none", + "0% top url(404.png), url(404.png) 0% top", + "fixed repeat-y top left url(404.png), repeat-x green", + "url(404.png), -moz-linear-gradient(20px 20px -45deg, blue, green), -moz-element(#a) black", + "top left / contain, bottom right / cover", + /* test cases with clip+origin in the shorthand */ + "url(404.png) green padding-box", + "url(404.png) border-box transparent", + "content-box url(404.png) blue", + "url(404.png) green padding-box padding-box", + "url(404.png) green padding-box border-box", + "content-box border-box url(404.png) blue", + ], + invalid_values: [ + /* mixes with keywords have to be in correct order */ + "50% left", "top 50%", + /* no quirks mode colors */ + "-moz-radial-gradient(10% bottom, ffffff, black) scroll no-repeat", + /* no quirks mode lengths */ + "-moz-linear-gradient(10 10px -45deg, red, blue) repeat", + "-moz-linear-gradient(10px 10 -45deg, red, blue) repeat", + "linear-gradient(red -99, yellow, green, blue 120%)", + /* bug 258080: don't accept background-position separated */ + "left url(404.png) top", "top url(404.png) left", + /* not allowed to have color in non-bottom layer */ + "url(404.png) transparent, url(404.png)", + "url(404.png) red, url(404.png)", + "url(404.png) transparent, url(404.png) transparent", + "url(404.png) transparent red, url(404.png) transparent red", + "url(404.png) red, url(404.png) red", + "url(404.png) rgba(0, 0, 0, 0), url(404.png)", + "url(404.png) rgb(255, 0, 0), url(404.png)", + "url(404.png) rgba(0, 0, 0, 0), url(404.png) rgba(0, 0, 0, 0)", + "url(404.png) rgba(0, 0, 0, 0) rgb(255, 0, 0), url(404.png) rgba(0, 0, 0, 0) rgb(255, 0, 0)", + "url(404.png) rgb(255, 0, 0), url(404.png) rgb(255, 0, 0)", + /* bug 513395: old syntax for gradients */ + "-moz-radial-gradient(10% bottom, 30px, 20px 20px, 10px, from(#ffffff), to(black)) scroll no-repeat", + "-moz-linear-gradient(10px 10px, 20px 20px, from(red), to(blue)) repeat", + /* clip and origin separated in the shorthand */ + "url(404.png) padding-box green border-box", + "url(404.png) padding-box green padding-box", + "transparent padding-box url(404.png) border-box", + "transparent padding-box url(404.png) padding-box", + /* error inside functions */ + "-moz-image-rect(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), rubbish, 50%, 30%, 0) transparent", + "-moz-element(#a rubbish) black", + "text", + "text border-box", + "content-box text text", + "padding-box text url(404.png) text", + ] + }, + "background-attachment": { + domProp: "backgroundAttachment", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "scroll" ], + other_values: [ "fixed", "local", "scroll,scroll", "fixed, scroll", "scroll, fixed, local, scroll", "fixed, fixed" ], + invalid_values: [] + }, + "background-clip": { + /* + * When we rename this to 'background-clip', we also + * need to rename the values to match the spec. + */ + domProp: "backgroundClip", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "border-box" ], + other_values: [ "content-box", "padding-box", "border-box, padding-box", "padding-box, padding-box, padding-box", "border-box, border-box" ], + invalid_values: [ "margin-box", "border-box border-box" ] + }, + "background-color": { + domProp: "backgroundColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "transparent", "rgba(255, 127, 15, 0)", "hsla(240, 97%, 50%, 0.0)", "rgba(0, 0, 0, 0)", "rgba(255,255,255,-3.7)" ], + other_values: [ "green", "rgb(255, 0, 128)", "#fc2", "#96ed2a", "black", "rgba(255,255,0,3)", "hsl(240, 50%, 50%)", "rgb(50%, 50%, 50%)", "-moz-default-background-color", "rgb(100, 100.0, 100)" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "rgb(100, 100%, 100)" ], + quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" }, + }, + "background-image": { + domProp: "backgroundImage", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')", 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', + "none, none", + "none, none, none, none, none", + "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none", + "none, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none", + "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", + ].concat(validGradientAndElementValues), + invalid_values: [ + ].concat(invalidGradientAndElementValues), + unbalanced_values: [ + ].concat(unbalancedGradientAndElementValues) + }, + "background-origin": { + domProp: "backgroundOrigin", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "padding-box" ], + other_values: [ "border-box", "content-box", "border-box, padding-box", "padding-box, padding-box, padding-box", "border-box, border-box" ], + invalid_values: [ "margin-box", "padding-box padding-box" ] + }, + "background-position": { + domProp: "backgroundPosition", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + initial_values: [ "top 0% left 0%", "top 0% left", "top left", "left top", "0% 0%", "0% top", "left 0%" ], + other_values: [ "top", "left", "right", "bottom", "center", "center bottom", "bottom center", "center right", "right center", "center top", "top center", "center left", "left center", "right bottom", "bottom right", "50%", "top left, top left", "top left, top right", "top right, top left", "left top, 0% 0%", "10% 20%, 30%, 40%", "top left, bottom right", "right bottom, left top", "0%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top", + "calc(20px)", + "calc(20px) 10px", + "10px calc(20px)", + "calc(20px) 25%", + "25% calc(20px)", + "calc(20px) calc(20px)", + "calc(20px + 1em) calc(20px / 2)", + "calc(20px + 50%) calc(50% - 10px)", + "calc(-20px) calc(-50%)", + "calc(-20%) calc(-50%)", + "0px 0px", + "right 20px top 60px", + "right 20px bottom 60px", + "left 20px top 60px", + "left 20px bottom 60px", + "right -50px top -50px", + "left -50px bottom -50px", + "right 20px top -50px", + "right -20px top 50px", + "right 3em bottom 10px", + "bottom 3em right 10px", + "top 3em right 10px", + "left 15px", + "10px top", + "left top 15px", + "left 10px top", + "left 20%", + "right 20%" + ], + subproperties: [ "background-position-x", "background-position-y" ], + invalid_values: [ "center 10px center 4px", "center 10px center", + "top 20%", "bottom 20%", "50% left", "top 50%", + "50% bottom 10%", "right 10% 50%", "left right", + "top bottom", "left 10% right", + "top 20px bottom 20px", "left left", + "0px calc(0px + rubbish)"], + quirks_values: { + "20 20": "20px 20px", + "10 5px": "10px 5px", + "7px 2": "7px 2px", + }, + }, + "background-position-x": { + domProp: "backgroundPositionX", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "left 0%", "left", "0%" ], + other_values: [ "right", "center", "50%", "left, left", "left, right", "right, left", "left, 0%", "10%, 20%, 40%", "0px", "30px", "0%, 10%, 20%, 30%", "left, left, left, left, left", + "calc(20px)", + "calc(20px + 1em)", + "calc(20px / 2)", + "calc(20px + 50%)", + "calc(50% - 10px)", + "calc(-20px)", + "calc(-50%)", + "calc(-20%)", + "right 20px", + "left 20px", + "right -50px", + "left -50px", + "right 20px", + "right 3em", + ], + invalid_values: [ "center 10px", "right 10% 50%", "left right", "left left", + "bottom 20px", "top 10%", "bottom 3em", + "top", "bottom", "top, top", "top, bottom", "bottom, top", "top, 0%", "top, top, top, top, top", + "calc(0px + rubbish)"], + }, + "background-position-y": { + domProp: "backgroundPositionY", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "top 0%", "top", "0%" ], + other_values: [ "bottom", "center", "50%", "top, top", "top, bottom", "bottom, top", "top, 0%", "10%, 20%, 40%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top", + "calc(20px)", + "calc(20px + 1em)", + "calc(20px / 2)", + "calc(20px + 50%)", + "calc(50% - 10px)", + "calc(-20px)", + "calc(-50%)", + "calc(-20%)", + "bottom 20px", + "top 20px", + "bottom -50px", + "top -50px", + "bottom 20px", + "bottom 3em", + ], + invalid_values: [ "center 10px", "bottom 10% 50%", "top bottom", "top top", + "right 20px", "left 10%", "right 3em", + "left", "right", "left, left", "left, right", "right, left", "left, 0%", "left, left, left, left, left", + "calc(0px + rubbish)"], + }, + "background-repeat": { + domProp: "backgroundRepeat", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "repeat", "repeat repeat" ], + other_values: [ "repeat-x", "repeat-y", "no-repeat", + "repeat-x, repeat-x", + "repeat, no-repeat", + "repeat-y, no-repeat, repeat-y", + "repeat, repeat, repeat", + "repeat no-repeat", + "no-repeat repeat", + "no-repeat no-repeat", + "repeat repeat, repeat repeat", + "round, repeat", + "round repeat, repeat-x", + "round no-repeat, repeat-y", + "round round", + "space, repeat", + "space repeat, repeat-x", + "space no-repeat, repeat-y", + "space space", + "space round" + ], + invalid_values: [ "repeat repeat repeat", + "repeat-x repeat-y", + "repeat repeat-x", + "repeat repeat-y", + "repeat-x repeat", + "repeat-y repeat", + "round round round", + "repeat-x round", + "round repeat-x", + "repeat-y round", + "round repeat-y", + "space space space", + "repeat-x space", + "space repeat-x", + "repeat-y space", + "space repeat-y" ] + }, + "background-size": { + domProp: "backgroundSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto", "auto auto" ], + other_values: [ "contain", "cover", "100px auto", "auto 100px", "100% auto", "auto 100%", "25% 50px", "3em 40%", + "calc(20px)", + "calc(20px) 10px", + "10px calc(20px)", + "calc(20px) 25%", + "25% calc(20px)", + "calc(20px) calc(20px)", + "calc(20px + 1em) calc(20px / 2)", + "calc(20px + 50%) calc(50% - 10px)", + "calc(-20px) calc(-50%)", + "calc(-20%) calc(-50%)" + ], + invalid_values: [ "contain contain", "cover cover", "cover auto", "auto cover", "contain cover", "cover contain", "-5px 3px", "3px -5px", "auto -5px", "-5px auto", "5 3", "10px calc(10px + rubbish)" ] + }, + "border": { + domProp: "border", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-bottom-color", "border-bottom-style", "border-bottom-width", "border-left-color", "border-left-style", "border-left-width", "border-right-color", "border-right-style", "border-right-width", "border-top-color", "border-top-style", "border-top-width", "-moz-border-top-colors", "-moz-border-right-colors", "-moz-border-bottom-colors", "-moz-border-left-colors", "border-image-source", "border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor", "calc(4px - 1px) none" ], + other_values: [ "solid", "medium solid", "green solid", "10px solid", "thick solid", "calc(2px) solid blue" ], + invalid_values: [ "5%", "medium solid ff00ff", "5 solid green" ] + }, + "border-bottom": { + domProp: "borderBottom", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-bottom-color", "border-bottom-style", "border-bottom-width" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], + other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ], + invalid_values: [ "5%", "5", "5 solid green" ] + }, + "border-bottom-color": { + domProp: "borderBottomColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000" ], + quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" }, + }, + "border-bottom-style": { + domProp: "borderBottomStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge" ], + invalid_values: [] + }, + "border-bottom-width": { + domProp: "borderBottomWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "border-bottom-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0em)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%" ], + quirks_values: { "5": "5px" }, + }, + "border-collapse": { + domProp: "borderCollapse", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "separate" ], + other_values: [ "collapse" ], + invalid_values: [] + }, + "border-color": { + domProp: "borderColor", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-top-color", "border-right-color", "border-bottom-color", "border-left-color" ], + initial_values: [ "currentColor", "currentColor currentColor", "currentColor currentColor currentColor", "currentColor currentColor currentcolor CURRENTcolor" ], + other_values: [ "green", "currentColor green", "currentColor currentColor green", "currentColor currentColor currentColor green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "red rgb(nonsense)", "red 1px" ], + unbalanced_values: [ "red rgb(" ], + quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" }, + }, + "border-left": { + domProp: "borderLeft", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-left-color", "border-left-style", "border-left-width" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], + other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ], + invalid_values: [ "5%", "5", "5 solid green", "calc(5px + rubbish) green solid", "5px rgb(0, rubbish, 0) solid" ] + }, + "border-left-color": { + domProp: "borderLeftColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000" ], + quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" }, + }, + "border-left-style": { + domProp: "borderLeftStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge" ], + invalid_values: [] + }, + "border-left-width": { + domProp: "borderLeftWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "border-left-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0em)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%" ], + quirks_values: { "5": "5px" }, + }, + "border-right": { + domProp: "borderRight", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-right-color", "border-right-style", "border-right-width" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], + other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ], + invalid_values: [ "5%", "5", "5 solid green" ] + }, + "border-right-color": { + domProp: "borderRightColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000" ], + quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" }, + }, + "border-right-style": { + domProp: "borderRightStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge" ], + invalid_values: [] + }, + "border-right-width": { + domProp: "borderRightWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "border-right-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0em)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%" ], + quirks_values: { "5": "5px" }, + }, + "border-spacing": { + domProp: "borderSpacing", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0 0", "0px", "0 0px", "calc(0px)", "calc(0px) calc(0em)", "calc(2em - 2em) calc(3px + 7px - 10px)", "calc(-5px)", "calc(-5px) calc(-5px)" ], + other_values: [ "3px", "4em 2px", "4em 0", "0px 2px", "calc(7px)", "0 calc(7px)", "calc(7px) 0", "calc(0px) calc(7px)", "calc(7px) calc(0px)", "7px calc(0px)", "calc(0px) 7px", "7px calc(0px)", "3px calc(2em)" ], + invalid_values: [ "0%", "0 0%", "-5px", "-5px -5px", "0 -5px", "-5px 0", "0 calc(0px + rubbish)" ], + quirks_values: { + "2px 5": "2px 5px", + "7": "7px", + "3 4px": "3px 4px", + }, + }, + "border-style": { + domProp: "borderStyle", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-top-style", "border-right-style", "border-bottom-style", "border-left-style" ], + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none", "none none", "none none none", "none none none none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge", "none solid", "none none solid", "none none none solid", "groove none none none", "none ridge none none", "none none double none", "none none none dotted" ], + invalid_values: [] + }, + "border-top": { + domProp: "borderTop", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-top-color", "border-top-style", "border-top-width" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], + other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ], + invalid_values: [ "5%", "5", "5 solid green" ] + }, + "border-top-color": { + domProp: "borderTopColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000" ], + quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a" }, + }, + "border-top-style": { + domProp: "borderTopStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge" ], + invalid_values: [] + }, + "border-top-width": { + domProp: "borderTopWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "border-top-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0em)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%" ], + quirks_values: { "5": "5px" }, + }, + "border-width": { + domProp: "borderWidth", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-top-width", "border-right-width", "border-bottom-width", "border-left-width" ], + prerequisites: { "border-style": "solid" }, + initial_values: [ "medium", "3px", "medium medium", "3px medium medium", "medium 3px medium medium", "calc(3px) 3px calc(5px - 2px) calc(2px - -1px)" ], + other_values: [ "thin", "thick", "1px", "2em", "2px 0 0px 1em", "calc(2em)" ], + invalid_values: [ "5%", "1px calc(nonsense)", "1px red" ], + unbalanced_values: [ "1px calc(" ], + quirks_values: { "5": "5px" }, + }, + "bottom": { + domProp: "bottom", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { "position": "relative" }, + /* XXX 0 may or may not be equal to auto */ + initial_values: [ "auto" ], + other_values: [ "32px", "-3em", "12%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [], + quirks_values: { "5": "5px" }, + }, + "box-shadow": { + domProp: "boxShadow", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + prerequisites: { "color": "blue" }, + other_values: [ "2px 2px", "2px 2px 1px", "2px 2px 2px 2px", "blue 3px 2px", "2px 2px 1px 5px green", "2px 2px red", "green 2px 2px 1px", "green 2px 2px, blue 1px 3px 4px", "currentColor 3px 3px", "blue 2px 2px, currentColor 1px 2px, 1px 2px 3px 2px orange", "3px 0 0 0", "inset 2px 2px 3px 4px black", "2px -2px green inset, 4px 4px 3px blue, inset 2px 2px", + /* calc() values */ + "2px 2px calc(-5px)", /* clamped */ + "calc(3em - 2px) 2px green", + "green calc(3em - 2px) 2px", + "2px calc(2px + 0.2em)", + "blue 2px calc(2px + 0.2em)", + "2px calc(2px + 0.2em) blue", + "calc(-2px) calc(-2px)", + "-2px -2px", + "calc(2px) calc(2px)", + "calc(2px) calc(2px) calc(2px)", + "calc(2px) calc(2px) calc(2px) calc(2px)" + ], + invalid_values: [ "3% 3%", "1px 1px 1px 1px 1px", "2px 2px, none", "red 2px 2px blue", "inherit, 2px 2px", "2px 2px, inherit", "2px 2px -5px", "inset 4px 4px black inset", "inset inherit", "inset none", "3 3", "3px 3", "3 3px", "3px 3px 3", "3px 3px 3px 3", "3px calc(3px + rubbish)", "3px 3px calc(3px + rubbish)", "3px 3px 3px calc(3px + rubbish)", "3px 3px 3px 3px rgb(0, rubbish, 0)" ] + }, + "caption-side": { + domProp: "captionSide", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "top" ], + other_values: [ "bottom", "left", "right", "top-outside", "bottom-outside" ], + invalid_values: [] + }, + "clear": { + domProp: "clear", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "left", "right", "both" ], + invalid_values: [] + }, + "clip": { + domProp: "clip", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "rect(0 0 0 0)", "rect(auto,auto,auto,auto)", "rect(3px, 4px, 4em, 0)", "rect(auto, 3em, 4pt, 2px)", "rect(2px 3px 4px 5px)" ], + invalid_values: [ "rect(auto, 3em, 2%, 5px)" ], + quirks_values: { "rect(1, 2, 3, 4)": "rect(1px, 2px, 3px, 4px)" }, + }, + "color": { + domProp: "color", + inherited: true, + type: CSS_TYPE_LONGHAND, + /* XXX should test currentColor, but may or may not be initial */ + initial_values: [ "black", "#000", "#000f", "#000000ff", "-moz-default-color", "rgb(0, 0, 0)", "rgb(0%, 0%, 0%)", + /* css-color-4: */ + /* rgb() and rgba() are aliases of each other. */ + "rgb(0, 0, 0)", "rgba(0, 0, 0)", "rgb(0, 0, 0, 1)", "rgba(0, 0, 0, 1)", + /* hsl() and hsla() are aliases of each other. */ + "hsl(0, 0%, 0%)", "hsla(0, 0%, 0%)", "hsl(0, 0%, 0%, 1)", "hsla(0, 0%, 0%, 1)", + /* rgb() and rgba() functions now accept <number> rather than <integer>. */ + "rgb(0.0, 0.0, 0.0)", "rgba(0.0, 0.0, 0.0)", "rgb(0.0, 0.0, 0.0, 1)", "rgba(0.0, 0.0, 0.0, 1)", + /* <alpha-value> now accepts <percentage> as well as <number> in rgba() and hsla(). */ + "rgb(0.0, 0.0, 0.0, 100%)", "hsl(0, 0%, 0%, 100%)", + /* rgb() and hsl() now support comma-less expression. */ + "rgb(0 0 0)", "rgb(0 0 0 / 1)", "rgb(0/* comment */0/* comment */0)", "rgb(0/* comment */0/* comment*/0/1.0)", + "hsl(0 0% 0%)", "hsl(0 0% 0% / 1)", "hsl(0/* comment */0%/* comment */0%)", "hsl(0/* comment */0%/* comment */0%/1)", + /* Support <angle> for hsl() hue component. */ + "hsl(0deg, 0%, 0%)", "hsl(360deg, 0%, 0%)", "hsl(0grad, 0%, 0%)", "hsl(400grad, 0%, 0%)", "hsl(0rad, 0%, 0%)", "hsl(0turn, 0%, 0%)", "hsl(1turn, 0%, 0%)", + ], + other_values: [ "green", "#f3c", "#fed292", "rgba(45,300,12,2)", "transparent", "-moz-nativehyperlinktext", "rgba(255,128,0,0.5)", "#e0fc", "#10fcee72", + /* css-color-4: */ + "rgb(100, 100.0, 100)", "rgb(300 300 300 / 200%)", "rgb(300.0 300.0 300.0 / 2.0)", "hsl(720, 200%, 200%, 2.0)", "hsla(720 200% 200% / 200%)", + "hsl(480deg, 20%, 30%, 0.3)", "hsl(55grad, 400%, 30%)", "hsl(0.5grad 400% 500% / 9.0)", "hsl(33rad 100% 90% / 4)", "hsl(0.33turn, 40%, 40%, 10%)", + ], + invalid_values: [ "#f", "#ff", "#fffff", "#fffffff", "#fffffffff", + "rgb(100%, 0, 100%)", "rgba(100, 0, 100%, 30%)", + "hsl(0, 0, 0%)", "hsla(0%, 0%, 0%, 0.1)", + /* trailing commas */ + "rgb(0, 0, 0,)", "rgba(0, 0, 0, 0,)", + "hsl(0, 0%, 0%,)", "hsla(0, 0%, 0%, 1,)", + /* css-color-4: */ + /* comma and comma-less expressions should not mix together. */ + "rgb(0, 0, 0 / 1)", "rgb(0 0 0, 1)", "rgb(0, 0 0, 1)", "rgb(0 0, 0 / 1)", + "hsl(0, 0%, 0% / 1)", "hsl(0 0% 0%, 1)", "hsl(0 0% 0%, 1)", "hsl(0 0%, 0% / 1)", + /* trailing slash */ + "rgb(0 0 0 /)", "rgb(0, 0, 0 /)", + "hsl(0 0% 0% /)", "hsl(0, 0%, 0% /)", + ], + quirks_values: { "000000": "#000000", "96ed2a": "#96ed2a", "fff": "#ffffff", "ffffff": "#ffffff", }, + }, + "content": { + domProp: "content", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX needs to be on pseudo-elements */ + initial_values: [ "normal", "none" ], + other_values: [ '""', "''", '"hello"', "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')", 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', 'counter(foo)', 'counter(bar, upper-roman)', 'counters(foo, ".")', "counters(bar, '-', lower-greek)", "'-' counter(foo) '.'", "attr(title)", "open-quote", "close-quote", "no-open-quote", "no-close-quote", "close-quote attr(title) counters(foo, '.', upper-alpha)", "counter(foo, none)", "counters(bar, '.', none)", "attr(\\32)", "attr(\\2)", "attr(-\\2)", "attr(-\\32)", "counter(\\2)", "counters(\\32, '.')", "counter(-\\32, upper-roman)", "counters(-\\2, '-', lower-greek)", "counter(\\()", "counters(a\\+b, '.')", "counter(\\}, upper-alpha)", "-moz-alt-content", "counter(foo, symbols('*'))", "counter(foo, symbols(numeric '0' '1'))", "counters(foo, '.', symbols('*'))", "counters(foo, '.', symbols(numeric '0' '1'))" ], + invalid_values: [ 'counters(foo)', 'counter(foo, ".")', 'attr("title")', "attr('title')", "attr(2)", "attr(-2)", "counter(2)", "counters(-2, '.')", "-moz-alt-content 'foo'", "'foo' -moz-alt-content", "counter(one, two, three) 'foo'" ] + }, + "counter-increment": { + domProp: "counterIncrement", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "foo 1", "bar", "foo 3 bar baz 2", "\\32 1", "-\\32 1", "-c 1", "\\32 1", "-\\32 1", "\\2 1", "-\\2 1", "-c 1", "\\2 1", "-\\2 1", "-\\7f \\9e 1" ], + invalid_values: [ "none foo", "none foo 3", "foo none", "foo 3 none" ], + unbalanced_values: [ "foo 1 (" ] + }, + "counter-reset": { + domProp: "counterReset", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "foo 1", "bar", "foo 3 bar baz 2", "\\32 1", "-\\32 1", "-c 1", "\\32 1", "-\\32 1", "\\2 1", "-\\2 1", "-c 1", "\\2 1", "-\\2 1", "-\\7f \\9e 1" ], + invalid_values: [ "none foo", "none foo 3", "foo none", "foo 3 none" ] + }, + "cursor": { + domProp: "cursor", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "crosshair", "default", "pointer", "move", "e-resize", "ne-resize", "nw-resize", "n-resize", "se-resize", "sw-resize", "s-resize", "w-resize", "text", "wait", "help", "progress", "copy", "alias", "context-menu", "cell", "not-allowed", "col-resize", "row-resize", "no-drop", "vertical-text", "all-scroll", "nesw-resize", "nwse-resize", "ns-resize", "ew-resize", "none", "grab", "grabbing", "zoom-in", "zoom-out", "-moz-grab", "-moz-grabbing", "-moz-zoom-in", "-moz-zoom-out", "url(foo.png), move", "url(foo.png) 5 7, move", "url(foo.png) 12 3, url(bar.png), no-drop", "url(foo.png), url(bar.png) 7 2, wait", "url(foo.png) 3 2, url(bar.png) 7 9, pointer" ], + invalid_values: [ "url(foo.png)", "url(foo.png) 5 5" ] + }, + "direction": { + domProp: "direction", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "ltr" ], + other_values: [ "rtl" ], + invalid_values: [] + }, + "display": { + domProp: "display", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "inline" ], + /* XXX none will really mess with other properties */ + prerequisites: { "float": "none", "position": "static", "contain": "none" }, + other_values: [ + "block", + "flex", + "inline-flex", + "list-item", + "inline-block", + "table", + "inline-table", + "table-row-group", + "table-header-group", + "table-footer-group", + "table-row", + "table-column-group", + "table-column", + "table-cell", + "table-caption", + "ruby", + "ruby-base", + "ruby-base-container", + "ruby-text", + "ruby-text-container", + "none" + ], + invalid_values: [] + }, + "empty-cells": { + domProp: "emptyCells", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "show" ], + other_values: [ "hide" ], + invalid_values: [] + }, + "float": { + domProp: "cssFloat", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "left", "right" ], + invalid_values: [] + }, + "font": { + domProp: "font", + inherited: true, + type: CSS_TYPE_TRUE_SHORTHAND, + prerequisites: { "writing-mode": "initial" }, + subproperties: [ "font-style", "font-variant", "font-weight", "font-size", "line-height", "font-family", "font-stretch", + "font-size-adjust", "font-feature-settings", "font-language-override", + "font-kerning", "font-synthesis", "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", + "font-variant-ligatures", "font-variant-numeric", "font-variant-position" ], + initial_values: [ (gInitialFontFamilyIsSansSerif ? "medium sans-serif" : "medium serif") ], + other_values: [ "large serif", "9px fantasy", "condensed bold italic small-caps 24px/1.4 Times New Roman, serif", "small inherit roman", "small roman inherit", + // system fonts + "caption", "icon", "menu", "message-box", "small-caption", "status-bar", + // Gecko-specific system fonts + "-moz-window", "-moz-document", "-moz-desktop", "-moz-info", "-moz-dialog", "-moz-button", "-moz-pull-down-menu", "-moz-list", "-moz-field", "-moz-workspace", + // line-height with calc() + "condensed bold italic small-caps 24px/calc(2px) Times New Roman, serif", + "condensed bold italic small-caps 24px/calc(50%) Times New Roman, serif", + "condensed bold italic small-caps 24px/calc(3*25px) Times New Roman, serif", + "condensed bold italic small-caps 24px/calc(25px*3) Times New Roman, serif", + "condensed bold italic small-caps 24px/calc(3*25px + 50%) Times New Roman, serif", + "condensed bold italic small-caps 24px/calc(1 + 2*3/4) Times New Roman, serif", + ], + invalid_values: [ "9 fantasy", "-2px fantasy", + // line-height with calc() + "condensed bold italic small-caps 24px/calc(1 + 2px) Times New Roman, serif", + "condensed bold italic small-caps 24px/calc(100% + 0.1) Times New Roman, serif", + ] + }, + "font-family": { + domProp: "fontFamily", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ (gInitialFontFamilyIsSansSerif ? "sans-serif" : "serif") ], + other_values: [ (gInitialFontFamilyIsSansSerif ? "serif" : "sans-serif"), "Times New Roman, serif", "'Times New Roman', serif", "cursive", "fantasy", "\\\"Times New Roman", "\"Times New Roman\"", "Times, \\\"Times New Roman", "Times, \"Times New Roman\"", "-no-such-font-installed", "inherit roman", "roman inherit", "Times, inherit roman", "inherit roman, Times", "roman inherit, Times", "Times, roman inherit" ], + invalid_values: [ "\"Times New\" Roman", "\"Times New Roman\n", "Times, \"Times New Roman\n" ] + }, + "font-feature-settings": { + domProp: "fontFeatureSettings", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ + "'liga' on", "'liga'", "\"liga\" 1", "'liga', 'clig' 1", + "\"liga\" off", "\"liga\" 0", '"cv01" 3, "cv02" 4', + '"cswh", "smcp" off, "salt" 4', '"cswh" 1, "smcp" off, "salt" 4', + '"cswh" 0, \'blah\', "liga", "smcp" off, "salt" 4', + '"liga" ,"smcp" 0 , "blah"' + ], + invalid_values: [ + 'liga', 'liga 1', 'liga normal', '"liga" normal', 'normal liga', + 'normal "liga"', 'normal, "liga"', '"liga=1"', "'foobar' on", + '"blahblah" 0', '"liga" 3.14', '"liga" 1 3.14', '"liga" 1 normal', + '"liga" 1 off', '"liga" on off', '"liga" , 0 "smcp"', '"liga" "smcp"' + ] + }, + "font-kerning": { + domProp: "fontKerning", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "normal", "none" ], + invalid_values: [ "on" ] + }, + "font-language-override": { + domProp: "fontLanguageOverride", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "'ENG'", "'TRK'", "\"TRK\"", "'N\\'Ko'" ], + invalid_values: [ "TRK", "ja" ] + }, + "font-size": { + domProp: "fontSize", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "medium", + "1rem", + "calc(1rem)", + "calc(0.75rem + 200% - 125% + 0.25rem - 75%)" + ], + other_values: [ "large", "2em", "50%", "xx-small", "36pt", "8px", "larger", "smaller", + "0px", + "0%", + "calc(2em)", + "calc(36pt + 75% + (30% + 2em + 2px))", + "calc(-2em)", + "calc(-50%)", + "calc(-1px)" + ], + invalid_values: [ "-2em", "-50%", "-1px" ], + quirks_values: { "5": "5px" }, + }, + "font-size-adjust": { + domProp: "fontSizeAdjust", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "0.3", "0.5", "0.7", "0.0", "0", "3" ], + invalid_values: [ "-0.3", "-1" ] + }, + "font-stretch": { + domProp: "fontStretch", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" ], + invalid_values: [ "narrower", "wider" ] + }, + "font-style": { + domProp: "fontStyle", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "italic", "oblique" ], + invalid_values: [] + }, + "font-synthesis": { + domProp: "fontSynthesis", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "weight style" ], + other_values: [ "none", "weight", "style" ], + invalid_values: [ "weight none", "style none", "none style", "weight 10px", "weight weight", "style style" ] + }, + "font-variant": { + domProp: "fontVariant", + inherited: true, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric", "font-variant-position" ], + initial_values: [ "normal" ], + other_values: [ "small-caps", "none", "traditional oldstyle-nums", "all-small-caps", "common-ligatures no-discretionary-ligatures", + "proportional-nums oldstyle-nums", "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal", + "traditional historical-forms styleset(ok-alt-a, ok-alt-b)", "styleset(potato)" ], + invalid_values: [ "small-caps normal", "small-caps small-caps", "none common-ligatures", "common-ligatures none", "small-caps potato", + "small-caps jis83 all-small-caps", "super historical-ligatures sub", "stacked-fractions diagonal-fractions historical-ligatures", + "common-ligatures traditional common-ligatures", "lining-nums traditional slashed-zero ordinal normal", + "traditional historical-forms styleset(ok-alt-a, ok-alt-b) historical-forms", + "historical-forms styleset(ok-alt-a, ok-alt-b) traditional styleset(potato)", "annotation(a,b,c)" ] + }, + "font-variant-alternates": { + domProp: "fontVariantAlternates", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "historical-forms", + "styleset(alt-a, alt-b)", "character-variant(a, b, c)", "annotation(circled)", + "swash(squishy)", "styleset(complex\\ blob, a)", "annotation(\\62 lah)" ], + invalid_values: [ "historical-forms normal", "historical-forms historical-forms", + "swash", "swash(3)", "annotation(a, b)", "ornaments(a,b)", + "styleset(1234blah)", "annotation(a), annotation(b)", "annotation(a) normal" ] + }, + "font-variant-caps": { + domProp: "fontVariantCaps", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "small-caps", "all-small-caps", "petite-caps", "all-petite-caps", "titling-caps", "unicase" ], + invalid_values: [ "normal small-caps", "petite-caps normal", "unicase unicase" ] + }, + "font-variant-east-asian": { + domProp: "fontVariantEastAsian", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "jis78", "jis83", "jis90", "jis04", "simplified", "traditional", "full-width", "proportional-width", "ruby", + "jis78 full-width", "jis78 full-width ruby", "simplified proportional-width", "ruby simplified" ], + invalid_values: [ "jis78 normal", "jis90 jis04", "simplified traditional", "full-width proportional-width", + "ruby simplified ruby", "jis78 ruby simplified" ] + }, + "font-variant-ligatures": { + domProp: "fontVariantLigatures", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "none", "common-ligatures", "no-common-ligatures", "discretionary-ligatures", "no-discretionary-ligatures", + "historical-ligatures", "no-historical-ligatures", "contextual", "no-contextual", + "common-ligatures no-discretionary-ligatures", "contextual no-discretionary-ligatures", + "historical-ligatures no-common-ligatures", "no-historical-ligatures discretionary-ligatures", + "common-ligatures no-discretionary-ligatures historical-ligatures no-contextual" ], + invalid_values: [ "common-ligatures normal", "common-ligatures no-common-ligatures", "common-ligatures common-ligatures", + "no-historical-ligatures historical-ligatures", "no-discretionary-ligatures discretionary-ligatures", + "no-contextual contextual", "common-ligatures no-discretionary-ligatures no-common-ligatures", + "common-ligatures none", "no-discretionary-ligatures none", "none common-ligatures" ] + }, + "font-variant-numeric": { + domProp: "fontVariantNumeric", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "lining-nums", "oldstyle-nums", "proportional-nums", "tabular-nums", "diagonal-fractions", + "stacked-fractions", "slashed-zero", "ordinal", "lining-nums diagonal-fractions", + "tabular-nums stacked-fractions", "tabular-nums slashed-zero stacked-fractions", + "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal" ], + invalid_values: [ "lining-nums normal", "lining-nums oldstyle-nums", "lining-nums normal slashed-zero ordinal", + "proportional-nums tabular-nums", "diagonal-fractions stacked-fractions", "slashed-zero diagonal-fractions slashed-zero", + "lining-nums slashed-zero diagonal-fractions oldstyle-nums", "diagonal-fractions diagonal-fractions" ] + }, + "font-variant-position": { + domProp: "fontVariantPosition", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "super", "sub" ], + invalid_values: [ "normal sub", "super sub" ] + }, + "font-weight": { + domProp: "fontWeight", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal", "400" ], + other_values: [ "bold", "100", "200", "300", "500", "600", "700", "800", "900", "bolder", "lighter" ], + invalid_values: [ "0", "100.0", "107", "399", "401", "699", "710", "1000" ] + }, + "height": { + domProp: "height", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* FIXME: test zero, and test calc clamping */ + initial_values: [ " auto", + // these four keywords compute to the initial value when the + // writing mode is horizontal, and that's the context we're testing in + "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available", + ], + /* computed value tests for height test more with display:block */ + prerequisites: { "display": "block" }, + other_values: [ "15px", "3em", "15%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "none" ], + quirks_values: { "5": "5px" }, + }, + "ime-mode": { + domProp: "imeMode", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "normal", "disabled", "active", "inactive" ], + invalid_values: [ "none", "enabled", "1px" ] + }, + "left": { + domProp: "left", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { "position": "relative" }, + /* XXX 0 may or may not be equal to auto */ + initial_values: [ "auto" ], + other_values: [ "32px", "-3em", "12%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [], + quirks_values: { "5": "5px" }, + }, + "letter-spacing": { + domProp: "letterSpacing", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "0", "0px", "1em", "2px", "-3px", + "calc(0px)", "calc(1em)", "calc(1em + 3px)", + "calc(15px / 2)", "calc(15px/2)", "calc(-3px)" + ], + invalid_values: [], + quirks_values: { "5": "5px" }, + }, + "line-height": { + domProp: "lineHeight", + inherited: true, + type: CSS_TYPE_LONGHAND, + /* + * Inheritance tests require consistent font size, since + * getComputedStyle (which uses the CSS2 computed value, or + * CSS2.1 used value) doesn't match what the CSS2.1 computed + * value is. And they even require consistent font metrics for + * computation of 'normal'. -moz-block-height requires height + * on a block. + */ + prerequisites: { "font-size": "19px", "font-size-adjust": "none", "font-family": "serif", "font-weight": "normal", "font-style": "normal", "height": "18px", "display": "block", "writing-mode": "initial" }, + initial_values: [ "normal" ], + other_values: [ "1.0", "1", "1em", "47px", "-moz-block-height", "calc(2px)", "calc(50%)", "calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", "calc(1 + 2*3/4)" ], + invalid_values: [ "calc(1 + 2px)", "calc(100% + 0.1)" ] + }, + "list-style": { + domProp: "listStyle", + inherited: true, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "list-style-type", "list-style-position", "list-style-image" ], + initial_values: [ "outside", "disc", "disc outside", "outside disc", "disc none", "none disc", "none disc outside", "none outside disc", "disc none outside", "disc outside none", "outside none disc", "outside disc none" ], + other_values: [ "inside none", "none inside", "none none inside", "square", "none", "none none", "outside none none", "none outside none", "none none outside", "none outside", "outside none", "outside outside", "outside inside", "\\32 style", "\\32 style inside", + '"-"', "'-'", "inside '-'", "'-' outside", "none '-'", "inside none '-'", + "symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")", + "symbols(cyclic \"*\" \"\\2020\" \"\\2021\" \"\\A7\")", + "inside symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")", + "symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\") outside", + "none symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")", + "inside none symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")", + 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', + 'none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', + 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none', + 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside', + 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', + 'outside none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', + 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none', + 'none url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside', + 'none outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', + 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside none', + 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") none outside' + ], + invalid_values: [ "disc disc", "unknown value", "none none none", "none disc url(404.png)", "none url(404.png) disc", "disc none url(404.png)", "disc url(404.png) none", "url(404.png) none disc", "url(404.png) disc none", "none disc outside url(404.png)" ] + }, + "list-style-image": { + domProp: "listStyleImage", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', + // Add some tests for interesting url() values here to test serialization, etc. + "url(\'data:text/plain,\"\')", + "url(\"data:text/plain,\'\")", + "url(\'data:text/plain,\\\'\')", + "url(\"data:text/plain,\\\"\")", + "url(\'data:text/plain,\\\"\')", + "url(\"data:text/plain,\\\'\")", + "url(data:text/plain,\\\\)", + ], + invalid_values: [] + }, + "list-style-position": { + domProp: "listStylePosition", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "outside" ], + other_values: [ "inside" ], + invalid_values: [] + }, + "list-style-type": { + domProp: "listStyleType", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "disc" ], + other_values: [ "none", "circle", "square", + "disclosure-closed", "disclosure-open", + "decimal", "decimal-leading-zero", + "lower-roman", "upper-roman", "lower-greek", + "lower-alpha", "lower-latin", "upper-alpha", "upper-latin", + "hebrew", "armenian", "georgian", + "cjk-decimal", "cjk-ideographic", + "hiragana", "katakana", "hiragana-iroha", "katakana-iroha", + "japanese-informal", "japanese-formal", "korean-hangul-formal", + "korean-hanja-informal", "korean-hanja-formal", + "simp-chinese-informal", "simp-chinese-formal", + "trad-chinese-informal", "trad-chinese-formal", + "ethiopic-numeric", + "-moz-cjk-heavenly-stem", "-moz-cjk-earthly-branch", + "-moz-trad-chinese-informal", "-moz-trad-chinese-formal", + "-moz-simp-chinese-informal", "-moz-simp-chinese-formal", + "-moz-japanese-informal", "-moz-japanese-formal", + "-moz-arabic-indic", "-moz-persian", "-moz-urdu", + "-moz-devanagari", "-moz-gurmukhi", "-moz-gujarati", + "-moz-oriya", "-moz-kannada", "-moz-malayalam", "-moz-bengali", + "-moz-tamil", "-moz-telugu", "-moz-thai", "-moz-lao", + "-moz-myanmar", "-moz-khmer", + "-moz-hangul", "-moz-hangul-consonant", + "-moz-ethiopic-halehame", "-moz-ethiopic-numeric", + "-moz-ethiopic-halehame-am", + "-moz-ethiopic-halehame-ti-er", "-moz-ethiopic-halehame-ti-et", + "other-style", "inside", "outside", "\\32 style", + '"-"', "'-'", + "symbols(\"*\" \"\\2020\" \"\\2021\" \"\\A7\")", + "symbols(cyclic '*' '\\2020' '\\2021' '\\A7')" + ], + invalid_values: [] + }, + "margin": { + domProp: "margin", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "margin-top", "margin-right", "margin-bottom", "margin-left" ], + initial_values: [ "0", "0px 0 0em", "0% 0px 0em 0pt" ], + other_values: [ "3px 0", "2em 4px 2pt", "1em 2em 3px 4px", "1em calc(2em + 3px) 4ex 5cm" ], + invalid_values: [ "1px calc(nonsense)", "1px red" ], + unbalanced_values: [ "1px calc(" ], + quirks_values: { "5": "5px", "3px 6px 2 5px": "3px 6px 2px 5px" }, + }, + "margin-bottom": { + domProp: "marginBottom", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX testing auto has prerequisites */ + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "margin-left": { + domProp: "marginLeft", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX testing auto has prerequisites */ + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)" ], + other_values: [ "1px", "2em", "5%", ".5px", "+32px", "+.789px", "-.328px", "+0.56px", "-0.974px", "237px", "-289px", "-056px", "1987.45px", "-84.32px", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "..25px", ".+5px", ".px", "-.px", "++5px", "-+4px", "+-3px", "--7px", "+-.6px", "-+.5px", "++.7px", "--.4px" ], + quirks_values: { "5": "5px" }, + }, + "margin-right": { + domProp: "marginRight", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX testing auto has prerequisites */ + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "margin-top": { + domProp: "marginTop", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX testing auto has prerequisites */ + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "max-height": { + domProp: "maxHeight", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "display": "block" }, + initial_values: [ "none", + // these four keywords compute to the initial value when the + // writing mode is horizontal, and that's the context we're testing in + "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available", + ], + other_values: [ "30px", "50%", "0", + "calc(2px)", + "calc(-2px)", + "calc(0px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "auto" ], + quirks_values: { "5": "5px" }, + }, + "max-width": { + domProp: "maxWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "display": "block" }, + initial_values: [ "none" ], + other_values: [ "30px", "50%", "0", + // these four keywords compute to the initial value only when the + // writing mode is vertical, and we're testing with a horizontal + // writing mode + "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available", + "calc(2px)", + "calc(-2px)", + "calc(0px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "auto" ], + quirks_values: { "5": "5px" }, + }, + "min-height": { + domProp: "minHeight", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "display": "block" }, + initial_values: [ "auto", "0", "calc(0em)", "calc(-2px)", + // these four keywords compute to the initial value when the + // writing mode is horizontal, and that's the context we're testing in + "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available", + ], + other_values: [ "30px", "50%", + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: ["none"], + quirks_values: { "5": "5px" }, + }, + "min-width": { + domProp: "minWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "display": "block" }, + initial_values: [ "auto", "0", "calc(0em)", "calc(-2px)" ], + other_values: [ "30px", "50%", + // these four keywords compute to the initial value only when the + // writing mode is vertical, and we're testing with a horizontal + // writing mode + "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available", + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "none" ], + quirks_values: { "5": "5px" }, + }, + + "opacity": { + domProp: "opacity", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1", "17", "397.376", "3e1", "3e+1", "3e0", "3e+0", "3e-0" ], + other_values: [ "0", "0.4", "0.0000", "-3", "3e-1" ], + invalid_values: [ "0px", "1px" ] + }, + "-moz-orient": { + domProp: "MozOrient", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "inline" ], + other_values: [ "horizontal", "vertical", "block" ], + invalid_values: [ "none" ] + }, + "outline": { + domProp: "outline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "outline-color", "outline-style", "outline-width" ], + initial_values: [ + "none", "medium", "thin", + // XXX Should be invert, but currently currentcolor. + //"invert", "none medium invert" + "currentColor", "none medium currentcolor" + ], + other_values: [ "solid", "medium solid", "green solid", "10px solid", "thick solid" ], + invalid_values: [ "5%", "5", "5 solid green" ] + }, + "outline-color": { + domProp: "outlineColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor" ], // XXX should be invert + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000", "cc00ff" ] + }, + "outline-offset": { + domProp: "outlineOffset", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0px", "-0", "calc(0px)", "calc(3em + 2px - 2px - 3em)", "calc(-0em)" ], + other_values: [ "-3px", "1em", "calc(3em)", "calc(7pt + 3 * 2em)", "calc(-3px)" ], + invalid_values: [ "5%" ] + }, + "outline-style": { + domProp: "outlineStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + // XXX Should 'hidden' be the same as initial? + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge", "auto" ], + invalid_values: [] + }, + "outline-width": { + domProp: "outlineWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "outline-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0px)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%", "5" ] + }, + "overflow": { + domProp: "overflow", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + prerequisites: { "display": "block", "contain": "none" }, + subproperties: [ "overflow-x", "overflow-y" ], + initial_values: [ "visible" ], + other_values: [ "auto", "scroll", "hidden", "-moz-hidden-unscrollable", "-moz-scrollbars-none" ], + invalid_values: [] + }, + "overflow-x": { + domProp: "overflowX", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "display": "block", "overflow-y": "visible", "contain": "none" }, + initial_values: [ "visible" ], + other_values: [ "auto", "scroll", "hidden", "-moz-hidden-unscrollable" ], + invalid_values: [] + }, + "overflow-y": { + domProp: "overflowY", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "display": "block", "overflow-x": "visible", "contain": "none" }, + initial_values: [ "visible" ], + other_values: [ "auto", "scroll", "hidden", "-moz-hidden-unscrollable" ], + invalid_values: [] + }, + "padding": { + domProp: "padding", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "padding-top", "padding-right", "padding-bottom", "padding-left" ], + initial_values: [ "0", "0px 0 0em", "0% 0px 0em 0pt", "calc(0px) calc(0em) calc(-2px) calc(-1%)" ], + other_values: [ "3px 0", "2em 4px 2pt", "1em 2em 3px 4px" ], + invalid_values: [ "1px calc(nonsense)", "1px red" ], + unbalanced_values: [ "1px calc(" ], + quirks_values: { "5": "5px", "3px 6px 2 5px": "3px 6px 2px 5px" }, + }, + "padding-bottom": { + domProp: "paddingBottom", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)", "calc(-3px)", "calc(-1%)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "padding-left": { + domProp: "paddingLeft", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)", "calc(-3px)", "calc(-1%)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "padding-right": { + domProp: "paddingRight", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)", "calc(-3px)", "calc(-1%)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "padding-top": { + domProp: "paddingTop", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)", "calc(-3px)", "calc(-1%)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "page-break-after": { + domProp: "pageBreakAfter", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "always", "avoid", "left", "right" ], + invalid_values: [] + }, + "page-break-before": { + domProp: "pageBreakBefore", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "always", "avoid", "left", "right" ], + invalid_values: [] + }, + "page-break-inside": { + domProp: "pageBreakInside", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "avoid" ], + invalid_values: [ "left", "right" ] + }, + "pointer-events": { + domProp: "pointerEvents", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "visiblePainted", "visibleFill", "visibleStroke", "visible", + "painted", "fill", "stroke", "all", "none" ], + invalid_values: [] + }, + "position": { + domProp: "position", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "static" ], + other_values: [ "relative", "absolute", "fixed", "sticky" ], + invalid_values: [] + }, + "quotes": { + domProp: "quotes", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ '"\u201C" "\u201D" "\u2018" "\u2019"', + '"\\201C" "\\201D" "\\2018" "\\2019"' ], + other_values: [ "none", "'\"' '\"'" ], + invalid_values: [] + }, + "right": { + domProp: "right", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { "position": "relative" }, + /* XXX 0 may or may not be equal to auto */ + initial_values: [ "auto" ], + other_values: [ "32px", "-3em", "12%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [], + quirks_values: { "5": "5px" }, + }, + "ruby-align": { + domProp: "rubyAlign", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "space-around" ], + other_values: [ "start", "center", "space-between" ], + invalid_values: [ + "end", "1", "10px", "50%", "start center" + ] + }, + "ruby-position": { + domProp: "rubyPosition", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "over" ], + other_values: [ "under" ], + invalid_values: [ + "left", "right", "auto", "none", "not_a_position", + "over left", "right under", "0", "100px", "50%" + ] + }, + "table-layout": { + domProp: "tableLayout", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "fixed" ], + invalid_values: [] + }, + "text-align": { + domProp: "textAlign", + inherited: true, + type: CSS_TYPE_LONGHAND, + // don't know whether left and right are same as start + initial_values: [ "start" ], + other_values: [ "center", "justify", "end", "match-parent" ], + invalid_values: [ "true", "true true" ] + }, + "text-align-last": { + domProp: "textAlignLast", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "center", "justify", "start", "end", "left", "right" ], + invalid_values: [] + }, + "text-decoration": { + domProp: "textDecoration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + subproperties: [ "text-decoration-color", "text-decoration-line", "text-decoration-style" ], + initial_values: [ "none" ], + other_values: [ "underline", "overline", "line-through", "blink", "blink line-through underline", "underline overline line-through blink", "-moz-anchor-decoration", "blink -moz-anchor-decoration", + "underline red solid", "underline #ff0000", "solid underline", "red underline", "#ff0000 underline", "dotted underline" ], + invalid_values: [ "none none", "underline none", "none underline", "blink none", "none blink", "line-through blink line-through", "underline overline line-through blink none", "underline overline line-throuh blink blink", "rgb(0, rubbish, 0) underline" ] + }, + "text-decoration-color": { + domProp: "textDecorationColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000", "ff00ff" ] + }, + "text-decoration-line": { + domProp: "textDecorationLine", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "underline", "overline", "line-through", "blink", "blink line-through underline", "underline overline line-through blink", "-moz-anchor-decoration", "blink -moz-anchor-decoration" ], + invalid_values: [ "none none", "underline none", "none underline", "line-through blink line-through", "underline overline line-through blink none", "underline overline line-throuh blink blink" ] + }, + "text-decoration-style": { + domProp: "textDecorationStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "solid" ], + other_values: [ "double", "dotted", "dashed", "wavy", "-moz-none" ], + invalid_values: [ "none", "groove", "ridge", "inset", "outset", "solid dashed", "wave" ] + }, + "text-emphasis": { + domProp: "textEmphasis", + inherited: true, + type: CSS_TYPE_TRUE_SHORTHAND, + prerequisites: { "color": "black" }, + subproperties: [ "text-emphasis-style", "text-emphasis-color" ], + initial_values: [ "none currentColor", "currentColor none", "none", "currentColor", "none black" ], + other_values: [ "filled dot black", "#f00 circle open", "sesame filled rgba(0,0,255,0.5)", "red", "green none", "currentColor filled", "currentColor open" ], + invalid_values: [ "filled black dot", "filled filled red", "open open circle #000", "circle dot #f00", "rubbish" ] + }, + "text-emphasis-color": { + domProp: "textEmphasisColor", + inherited: true, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor", "black", "rgb(0,0,0)" ], + other_values: [ "red", "rgba(255,255,255,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000", "ff00ff", "rgb(255,xxx,255)" ] + }, + "text-emphasis-position": { + domProp: "textEmphasisPosition", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "over right", "right over" ], + other_values: [ "over left", "left over", "under left", "left under", "under right", "right under" ], + invalid_values: [ "over over", "left left", "over right left", "rubbish left", "over rubbish" ] + }, + "text-emphasis-style": { + domProp: "textEmphasisStyle", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "filled", "open", "dot", "circle", "double-circle", "triangle", "sesame", "'#'", + "filled dot", "filled circle", "filled double-circle", "filled triangle", "filled sesame", + "dot filled", "circle filled", "double-circle filled", "triangle filled", "sesame filled", + "dot open", "circle open", "double-circle open", "triangle open", "sesame open" ], + invalid_values: [ "rubbish", "dot rubbish", "rubbish dot", "open rubbish", "rubbish open", "open filled", "dot circle", + "open '#'", "'#' filled", "dot '#'", "'#' circle", "1", "1 open", "open 1" ] + }, + "text-indent": { + domProp: "textIndent", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "calc(3em - 5em + 2px + 2em - 2px)" ], + other_values: [ "2em", "5%", "-10px", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "text-overflow": { + domProp: "textOverflow", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "clip" ], + other_values: [ "ellipsis", '""', "''", '"hello"', 'clip clip', 'ellipsis ellipsis', 'clip ellipsis', 'clip ""', '"hello" ""', '"" ellipsis' ], + invalid_values: [ "none", "auto", '"hello" inherit', 'inherit "hello"', 'clip initial', 'initial clip', 'initial inherit', 'inherit initial', 'inherit none'] + }, + "text-shadow": { + domProp: "textShadow", + inherited: true, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "blue" }, + initial_values: [ "none" ], + other_values: [ "2px 2px", "2px 2px 1px", "2px 2px green", "2px 2px 1px green", "green 2px 2px", "green 2px 2px 1px", "green 2px 2px, blue 1px 3px 4px", "currentColor 3px 3px", "blue 2px 2px, currentColor 1px 2px", + /* calc() values */ + "2px 2px calc(-5px)", /* clamped */ + "calc(3em - 2px) 2px green", + "green calc(3em - 2px) 2px", + "2px calc(2px + 0.2em)", + "blue 2px calc(2px + 0.2em)", + "2px calc(2px + 0.2em) blue", + "calc(-2px) calc(-2px)", + "-2px -2px", + "calc(2px) calc(2px)", + "calc(2px) calc(2px) calc(2px)", + ], + invalid_values: [ "3% 3%", "2px 2px -5px", "2px 2px 2px 2px", "2px 2px, none", "none, 2px 2px", "inherit, 2px 2px", "2px 2px, inherit", "2 2px", "2px 2", "2px 2px 2", "2px 2px 2px 2", + "calc(2px) calc(2px) calc(2px) calc(2px)", "3px 3px calc(3px + rubbish)" + ] + }, + "text-transform": { + domProp: "textTransform", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "capitalize", "uppercase", "lowercase", "full-width" ], + invalid_values: [] + }, + "top": { + domProp: "top", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { "position": "relative" }, + /* XXX 0 may or may not be equal to auto */ + initial_values: [ "auto" ], + other_values: [ "32px", "-3em", "12%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [], + quirks_values: { "5": "5px" }, + }, + "transition": { + domProp: "transition", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "transition-property", "transition-duration", "transition-timing-function", "transition-delay" ], + initial_values: [ "all 0s ease 0s", "all", "0s", "0s 0s", "ease" ], + other_values: [ "all 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) 0s", "width 1s linear 2s", "width 1s 2s linear", "width linear 1s 2s", "linear width 1s 2s", "linear 1s width 2s", "linear 1s 2s width", "1s width linear 2s", "1s width 2s linear", "1s 2s width linear", "1s linear width 2s", "1s linear 2s width", "1s 2s linear width", "width linear 1s", "width 1s linear", "linear width 1s", "linear 1s width", "1s width linear", "1s linear width", "1s 2s width", "1s width 2s", "width 1s 2s", "1s 2s linear", "1s linear 2s", "linear 1s 2s", "width 1s", "1s width", "linear 1s", "1s linear", "1s 2s", "2s 1s", "width", "linear", "1s", "height", "2s", "ease-in-out", "2s ease-in", "opacity linear", "ease-out 2s", "2s color, 1s width, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)", "1s \\32width linear 2s", "1s -width linear 2s", "1s -\\32width linear 2s", "1s \\32 0width linear 2s", "1s -\\32 0width linear 2s", "1s \\2width linear 2s", "1s -\\2width linear 2s", "2s, 1s width", "1s width, 2s", "2s all, 1s width", "1s width, 2s all", "2s all, 1s width", "2s width, 1s all", "3s --my-color" ], + invalid_values: [ "1s width, 2s none", "2s none, 1s width", "2s inherit", "inherit 2s", "2s width, 1s inherit", "2s inherit, 1s width", "2s initial", "1s width,,2s color", "1s width, ,2s color", "bounce 1s cubic-bezier(0, rubbish) 2s", "bounce 1s steps(rubbish) 2s" ] + }, + "transition-delay": { + domProp: "transitionDelay", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0s", "0ms" ], + other_values: [ "1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s"], + invalid_values: [ "0", "0px" ] + }, + "transition-duration": { + domProp: "transitionDuration", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0s", "0ms" ], + other_values: [ "1s", "250ms", "1s, 250ms, 2.3s"], + invalid_values: [ "0", "0px", "-1ms", "-2s" ] + }, + "transition-property": { + domProp: "transitionProperty", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "all" ], + other_values: [ "none", "left", "top", "color", "width, height, opacity", "foobar", "auto", "\\32width", "-width", "-\\32width", "\\32 0width", "-\\32 0width", "\\2width", "-\\2width", "all, all", "all, color", "color, all", "--my-color" ], + invalid_values: [ "none, none", "color, none", "none, color", "inherit, color", "color, inherit", "initial, color", "color, initial", "none, color", "color, none" ] + }, + "transition-timing-function": { + domProp: "transitionTimingFunction", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "ease" ], + other_values: [ "cubic-bezier(0.25, 0.1, 0.25, 1.0)", "linear", "ease-in", "ease-out", "ease-in-out", "linear, ease-in, cubic-bezier(0.1, 0.2, 0.8, 0.9)", "cubic-bezier(0.5, 0.5, 0.5, 0.5)", "cubic-bezier(0.25, 1.5, 0.75, -0.5)", "step-start", "step-end", "steps(1)", "steps(2, start)", "steps(386)", "steps(3, end)" ], + invalid_values: [ "none", "auto", "cubic-bezier(0.25, 0.1, 0.25)", "cubic-bezier(0.25, 0.1, 0.25, 0.25, 1.0)", "cubic-bezier(-0.5, 0.5, 0.5, 0.5)", "cubic-bezier(1.5, 0.5, 0.5, 0.5)", "cubic-bezier(0.5, 0.5, -0.5, 0.5)", "cubic-bezier(0.5, 0.5, 1.5, 0.5)", "steps(2, step-end)", "steps(0)", "steps(-2)", "steps(0, step-end, 1)" ] + }, + "unicode-bidi": { + domProp: "unicodeBidi", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "embed", "bidi-override", "isolate", "plaintext", "isolate-override", "-moz-isolate", "-moz-plaintext", "-moz-isolate-override" ], + invalid_values: [ "auto", "none" ] + }, + "vertical-align": { + domProp: "verticalAlign", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "baseline" ], + other_values: [ "sub", "super", "top", "text-top", "middle", "bottom", "text-bottom", "-moz-middle-with-baseline", "15%", "3px", "0.2em", "-5px", "-3%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + quirks_values: { "5": "5px" }, + }, + "visibility": { + domProp: "visibility", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "visible" ], + other_values: [ "hidden", "collapse" ], + invalid_values: [] + }, + "white-space": { + domProp: "whiteSpace", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "pre", "nowrap", "pre-wrap", "pre-line", "-moz-pre-space" ], + invalid_values: [] + }, + "width": { + domProp: "width", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* computed value tests for width test more with display:block */ + prerequisites: { "display": "block" }, + initial_values: [ " auto" ], + /* XXX these have prerequisites */ + other_values: [ "15px", "3em", "15%", + // these three keywords compute to the initial value only when the + // writing mode is vertical, and we're testing with a horizontal + // writing mode + "-moz-max-content", "-moz-min-content", "-moz-fit-content", + // whether -moz-available computes to the initial value depends on + // the container size, and for the container size we're testing + // with, it does + // "-moz-available", + "3e1px", "3e+1px", "3e0px", "3e+0px", "3e-0px", "3e-1px", + "3.2e1px", "3.2e+1px", "3.2e0px", "3.2e+0px", "3.2e-0px", "3.2e-1px", + "3e1%", "3e+1%", "3e0%", "3e+0%", "3e-0%", "3e-1%", + "3.2e1%", "3.2e+1%", "3.2e0%", "3.2e+0%", "3.2e-0%", "3.2e-1%", + /* valid -moz-calc() values */ + "-moz-calc(-2px)", + "-moz-calc(2px)", + "-moz-calc(50%)", + "-moz-calc(50% + 2px)", + "-moz-calc( 50% + 2px)", + "-moz-calc(50% + 2px )", + "-moz-calc( 50% + 2px )", + "-moz-calc(50% - -2px)", + "-moz-calc(2px - -50%)", + "-moz-calc(3*25px)", + "-moz-calc(3 *25px)", + "-moz-calc(3 * 25px)", + "-moz-calc(3* 25px)", + "-moz-calc(25px*3)", + "-moz-calc(25px *3)", + "-moz-calc(25px* 3)", + "-moz-calc(25px * 3)", + "-moz-calc(3*25px + 50%)", + "-moz-calc(50% - 3em + 2px)", + "-moz-calc(50% - (3em + 2px))", + "-moz-calc((50% - 3em) + 2px)", + "-moz-calc(2em)", + "-moz-calc(50%)", + "-moz-calc(50px/2)", + "-moz-calc(50px/(2 - 1))", + /* valid calc() values */ + "calc(-2px)", + "calc(2px)", + "calc(50%)", + "calc(50% + 2px)", + "calc( 50% + 2px)", + "calc(50% + 2px )", + "calc( 50% + 2px )", + "calc(50% - -2px)", + "calc(2px - -50%)", + "calc(3*25px)", + "calc(3 *25px)", + "calc(3 * 25px)", + "calc(3* 25px)", + "calc(25px*3)", + "calc(25px *3)", + "calc(25px* 3)", + "calc(25px * 3)", + "calc(3*25px + 50%)", + "calc(50% - 3em + 2px)", + "calc(50% - (3em + 2px))", + "calc((50% - 3em) + 2px)", + "calc(2em)", + "calc(50%)", + "calc(50px/2)", + "calc(50px/(2 - 1))", + ], + invalid_values: [ "none", "-2px", + /* invalid -moz-calc() values */ + "-moz-calc(50%+ 2px)", + "-moz-calc(50% +2px)", + "-moz-calc(50%+2px)", + /* invalid calc() values */ + "calc(50%+ 2px)", + "calc(50% +2px)", + "calc(50%+2px)", + "-moz-min()", + "calc(min())", + "-moz-max()", + "calc(max())", + "-moz-min(5px)", + "calc(min(5px))", + "-moz-max(5px)", + "calc(max(5px))", + "-moz-min(5px,2em)", + "calc(min(5px,2em))", + "-moz-max(5px,2em)", + "calc(max(5px,2em))", + "calc(50px/(2 - 2))", + /* If we ever support division by values, which is + * complicated for the reasons described in + * http://lists.w3.org/Archives/Public/www-style/2010Jan/0007.html + * , we should support all 4 of these as described in + * http://lists.w3.org/Archives/Public/www-style/2009Dec/0296.html + */ + "calc((3em / 100%) * 3em)", + "calc(3em / 100% * 3em)", + "calc(3em * (3em / 100%))", + "calc(3em * 3em / 100%)", + ], + quirks_values: { "5": "5px" }, + }, + "will-change": { + domProp: "willChange", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "scroll-position", "contents", "transform", "opacity", "scroll-position, transform", "transform, opacity", "contents, transform", "property-that-doesnt-exist-yet" ], + invalid_values: [ "none", "all", "default", "auto, scroll-position", "scroll-position, auto", "transform scroll-position", ",", "trailing,", "will-change", "transform, will-change" ] + }, + "word-break": { + domProp: "wordBreak", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "break-all", "keep-all" ], + invalid_values: [] + }, + "word-spacing": { + domProp: "wordSpacing", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal", "0", "0px", "-0em", + "calc(-0px)", "calc(0em)" + ], + other_values: [ "1em", "2px", "-3px", "0%", "50%", "-120%", + "calc(1em)", "calc(1em + 3px)", + "calc(15px / 2)", "calc(15px/2)", + "calc(-2em)", "calc(0% + 0px)", + "calc(-10%/2 - 1em)" + ], + invalid_values: [], + quirks_values: { "5": "5px" }, + }, + "overflow-wrap": { + domProp: "overflowWrap", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "break-word" ], + invalid_values: [] + }, + "hyphens": { + domProp: "hyphens", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "manual" ], + other_values: [ "none", "auto" ], + invalid_values: [] + }, + "z-index": { + domProp: "zIndex", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* XXX requires position */ + initial_values: [ "auto" ], + other_values: [ "0", "3", "-7000", "12000" ], + invalid_values: [ "3.0", "17.5", "3e1" ] + } + , + "clip-path": { + domProp: "clipPath", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "url(#mypath)", "url('404.svg#mypath')" ], + invalid_values: [] + }, + "clip-rule": { + domProp: "clipRule", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "nonzero" ], + other_values: [ "evenodd" ], + invalid_values: [] + }, + "color-interpolation": { + domProp: "colorInterpolation", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "sRGB" ], + other_values: [ "auto", "linearRGB" ], + invalid_values: [] + }, + "color-interpolation-filters": { + domProp: "colorInterpolationFilters", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "linearRGB" ], + other_values: [ "sRGB", "auto" ], + invalid_values: [] + }, + "dominant-baseline": { + domProp: "dominantBaseline", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "use-script", "no-change", "reset-size", "ideographic", "alphabetic", "hanging", "mathematical", "central", "middle", "text-after-edge", "text-before-edge" ], + invalid_values: [] + }, + "fill": { + domProp: "fill", + inherited: true, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "blue" }, + initial_values: [ "black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)" ], + other_values: [ "green", "#fc3", "url('#myserver')", "url(foo.svg#myserver)", 'url("#myserver") green', "none", "currentColor", "context-fill", "context-stroke" ], + invalid_values: [ "000000", "ff00ff", "url('#myserver') rgb(0, rubbish, 0)" ] + }, + "fill-opacity": { + domProp: "fillOpacity", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1", "2.8", "1.000", "context-fill-opacity", "context-stroke-opacity" ], + other_values: [ "0", "0.3", "-7.3" ], + invalid_values: [] + }, + "fill-rule": { + domProp: "fillRule", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "nonzero" ], + other_values: [ "evenodd" ], + invalid_values: [] + }, + "filter": { + domProp: "filter", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "url(#myfilt)" ], + invalid_values: [ "url(#myfilt) none" ] + }, + "flood-color": { + domProp: "floodColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "blue" }, + initial_values: [ "black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)" ], + other_values: [ "green", "#fc3", "currentColor" ], + invalid_values: [ "url('#myserver')", "url(foo.svg#myserver)", 'url("#myserver") green', "000000", "ff00ff" ] + }, + "flood-opacity": { + domProp: "floodOpacity", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1", "2.8", "1.000" ], + other_values: [ "0", "0.3", "-7.3" ], + invalid_values: [] + }, + "image-rendering": { + domProp: "imageRendering", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "optimizeSpeed", "optimizeQuality", "-moz-crisp-edges" ], + invalid_values: [] + }, + "lighting-color": { + domProp: "lightingColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "blue" }, + initial_values: [ "white", "#fff", "#ffffff", "rgb(255,255,255)", "rgba(255,255,255,1.0)", "rgba(255,255,255,42.0)" ], + other_values: [ "green", "#fc3", "currentColor" ], + invalid_values: [ "url('#myserver')", "url(foo.svg#myserver)", 'url("#myserver") green', "000000", "ff00ff" ] + }, + "marker": { + domProp: "marker", + inherited: true, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "marker-start", "marker-mid", "marker-end" ], + initial_values: [ "none" ], + other_values: [ "url(#mysym)" ], + invalid_values: [ "none none", "url(#mysym) url(#mysym)", "none url(#mysym)", "url(#mysym) none" ] + }, + "marker-end": { + domProp: "markerEnd", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "url(#mysym)" ], + invalid_values: [] + }, + "marker-mid": { + domProp: "markerMid", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "url(#mysym)" ], + invalid_values: [] + }, + "marker-start": { + domProp: "markerStart", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "url(#mysym)" ], + invalid_values: [] + }, + "shape-rendering": { + domProp: "shapeRendering", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "optimizeSpeed", "crispEdges", "geometricPrecision" ], + invalid_values: [] + }, + "stop-color": { + domProp: "stopColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "blue" }, + initial_values: [ "black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)" ], + other_values: [ "green", "#fc3", "currentColor" ], + invalid_values: [ "url('#myserver')", "url(foo.svg#myserver)", 'url("#myserver") green', "000000", "ff00ff" ] + }, + "stop-opacity": { + domProp: "stopOpacity", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1", "2.8", "1.000" ], + other_values: [ "0", "0.3", "-7.3" ], + invalid_values: [] + }, + "stroke": { + domProp: "stroke", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "black", "#000", "#000000", "rgb(0,0,0)", "rgba(0,0,0,1)", "green", "#fc3", "url('#myserver')", "url(foo.svg#myserver)", 'url("#myserver") green', "currentColor", "context-fill", "context-stroke" ], + invalid_values: [ "000000", "ff00ff" ] + }, + "stroke-dasharray": { + domProp: "strokeDasharray", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none", "context-value" ], + other_values: [ "5px,3px,2px", "5px 3px 2px", " 5px ,3px\t, 2px ", "1px", "5%", "3em" ], + invalid_values: [ "-5px,3px,2px", "5px,3px,-2px" ] + }, + "stroke-dashoffset": { + domProp: "strokeDashoffset", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "-0px", "0em", "context-value" ], + other_values: [ "3px", "3%", "1em" ], + invalid_values: [] + }, + "stroke-linecap": { + domProp: "strokeLinecap", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "butt" ], + other_values: [ "round", "square" ], + invalid_values: [] + }, + "stroke-linejoin": { + domProp: "strokeLinejoin", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "miter" ], + other_values: [ "round", "bevel" ], + invalid_values: [] + }, + "stroke-miterlimit": { + domProp: "strokeMiterlimit", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "4" ], + other_values: [ "1", "7", "5000", "1.1" ], + invalid_values: [ "0.9", "0", "-1", "3px", "-0.3" ] + }, + "stroke-opacity": { + domProp: "strokeOpacity", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1", "2.8", "1.000", "context-fill-opacity", "context-stroke-opacity" ], + other_values: [ "0", "0.3", "-7.3" ], + invalid_values: [] + }, + "stroke-width": { + domProp: "strokeWidth", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1px", "context-value" ], + other_values: [ "0", "0px", "-0em", "17px", "0.2em" ], + invalid_values: [ "-0.1px", "-3px" ] + }, + "text-anchor": { + domProp: "textAnchor", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "start" ], + other_values: [ "middle", "end" ], + invalid_values: [] + }, + "text-rendering": { + domProp: "textRendering", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "optimizeSpeed", "optimizeLegibility", "geometricPrecision" ], + invalid_values: [] + }, + "vector-effect": { + domProp: "vectorEffect", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "non-scaling-stroke" ], + invalid_values: [] + }, + "-moz-window-dragging": { + domProp: "MozWindowDragging", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "default" ], + other_values: [ "drag", "no-drag" ], + invalid_values: [ "none" ] + }, + "align-content": { + domProp: "alignContent", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "start", "end", "flex-start", "flex-end", "center", "left", + "right", "space-between", "space-around", "space-evenly", + "first baseline", "last baseline", "baseline", "stretch", "start safe", + "unsafe end", "unsafe end stretch", "end safe space-evenly" ], + invalid_values: [ "none", "5", "self-end", "safe", "normal unsafe", "unsafe safe", + "safe baseline", "baseline unsafe", "baseline end", "end normal", + "safe end unsafe start", "safe end unsafe", "normal safe start", + "unsafe end start", "end start safe", "space-between unsafe", + "stretch safe", "auto", "first", "last" ] + }, + "align-items": { + domProp: "alignItems", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + // Can't test 'left'/'right' here since that computes to 'start' for blocks. + other_values: [ "end", "flex-start", "flex-end", "self-start", "self-end", + "center", "stretch", "first baseline", "last baseline", "baseline", + "unsafe left", "start", "center unsafe", "safe right", "center safe" ], + invalid_values: [ "space-between", "abc", "5%", "legacy", "legacy end", + "end legacy", "unsafe", "unsafe baseline", "normal unsafe", + "safe left unsafe", "safe stretch", "end end", "auto" ] + }, + "align-self": { + domProp: "alignSelf", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "normal", "start", "flex-start", "flex-end", "center", "stretch", + "first baseline", "last baseline", "baseline", "right safe", + "unsafe center", "self-start", "self-end safe" ], + invalid_values: [ "space-between", "abc", "30px", "stretch safe", "safe" ] + }, + "justify-content": { + domProp: "justifyContent", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "start", "end", "flex-start", "flex-end", "center", "left", + "right", "space-between", "space-around", "space-evenly", + "first baseline", "last baseline", "baseline", "stretch", "start safe", + "unsafe end", "unsafe end stretch", "end safe space-evenly" ], + invalid_values: [ "30px", "5%", "self-end", "safe", "normal unsafe", "unsafe safe", + "safe baseline", "baseline unsafe", "baseline end", "normal end", + "safe end unsafe start", "safe end unsafe", "normal safe start", + "unsafe end start", "end start safe", "space-around unsafe", + "safe stretch", "auto", "first", "last" ] + }, + "justify-items": { + domProp: "justifyItems", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto", "normal" ], + other_values: [ "end", "flex-start", "flex-end", "self-start", "self-end", + "center", "left", "right", "first baseline", "last baseline", + "baseline", "stretch", "start", "legacy left", "right legacy", + "legacy center", "unsafe right", "left unsafe", "safe right", + "center safe" ], + invalid_values: [ "space-between", "abc", "30px", "legacy", "legacy start", + "end legacy", "legacy baseline", "legacy legacy", "unsafe", + "safe legacy left", "legacy left safe", "legacy safe left", + "safe left legacy", "legacy left legacy", "baseline unsafe", + "safe unsafe", "safe left unsafe", "safe stretch", "last" ] + }, + "justify-self": { + domProp: "justifySelf", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "normal", "start", "end", "flex-start", "flex-end", "self-start", + "self-end", "center", "left", "right", "baseline", "first baseline", + "last baseline", "stretch", "left unsafe", "unsafe right", + "safe right", "center safe" ], + invalid_values: [ "space-between", "abc", "30px", "none", "first", "last", + "legacy left", "right legacy", "baseline first", "baseline last" ] + }, + "place-content": { + domProp: "placeContent", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "align-content", "justify-content" ], + initial_values: [ "normal" ], + other_values: [ "normal start", "end baseline", "end end", + "space-between flex-end", "last baseline start", + "space-evenly", "flex-start", "end", "left" ], + invalid_values: [ "none", "center safe", "unsafe start", "right / end" ] + }, + "place-items": { + domProp: "placeItems", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "align-items", "justify-items" ], + initial_values: [ "normal" ], + other_values: [ "normal center", "end baseline", "end auto", + "end", "right", "baseline", "start last baseline", + "left flex-end", "last baseline start", "stretch" ], + invalid_values: [ "space-between", "start space-evenly", "none", "end/end", + "center safe", "auto start", "end legacy left" ] + }, + "place-self": { + domProp: "placeSelf", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "align-self", "justify-self" ], + initial_values: [ "auto" ], + other_values: [ "normal start", "end first baseline", "end auto", + "end", "right", "normal", "baseline", "start baseline", + "left self-end", "last baseline start", "stretch" ], + invalid_values: [ "space-between", "start space-evenly", "none", "end safe", + "auto legacy left", "legacy left", "auto/auto" ] + }, + "flex": { + domProp: "flex", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "flex-grow", + "flex-shrink", + "flex-basis" + ], + initial_values: [ "0 1 auto", "auto 0 1", "0 auto", "auto 0" ], + other_values: [ + "none", + "1", + "0", + "0 1", + "0.5", + "1.2 3.4", + "0 0 0", + "0 0 0px", + "0px 0 0", + "5px 0 0", + "2 auto", + "auto 4", + "auto 5.6 7.8", + "-moz-max-content", + "1 -moz-max-content", + "1 2 -moz-max-content", + "-moz-max-content 1", + "-moz-max-content 1 2", + "-0" + ], + invalid_values: [ + "1 2px 3", + "1 auto 3", + "1px 2 3px", + "1px 2 3 4px", + "-1", + "1 -1", + "0 1 calc(0px + rubbish)", + ] + }, + "flex-basis": { + domProp: "flexBasis", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ " auto" ], + // NOTE: This is cribbed directly from the "width" chunk, since this + // property takes the exact same values as width (albeit with + // different semantics on 'auto'). + // XXXdholbert (Maybe these should get separated out into + // a reusable array defined at the top of this file?) + other_values: [ "15px", "3em", "15%", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available", + // valid calc() values + "calc(-2px)", + "calc(2px)", + "calc(50%)", + "calc(50% + 2px)", + "calc( 50% + 2px)", + "calc(50% + 2px )", + "calc( 50% + 2px )", + "calc(50% - -2px)", + "calc(2px - -50%)", + "calc(3*25px)", + "calc(3 *25px)", + "calc(3 * 25px)", + "calc(3* 25px)", + "calc(25px*3)", + "calc(25px *3)", + "calc(25px* 3)", + "calc(25px * 3)", + "calc(3*25px + 50%)", + "calc(50% - 3em + 2px)", + "calc(50% - (3em + 2px))", + "calc((50% - 3em) + 2px)", + "calc(2em)", + "calc(50%)", + "calc(50px/2)", + "calc(50px/(2 - 1))" + ], + invalid_values: [ "none", "-2px", + // invalid calc() values + "calc(50%+ 2px)", + "calc(50% +2px)", + "calc(50%+2px)", + "-moz-min()", + "calc(min())", + "-moz-max()", + "calc(max())", + "-moz-min(5px)", + "calc(min(5px))", + "-moz-max(5px)", + "calc(max(5px))", + "-moz-min(5px,2em)", + "calc(min(5px,2em))", + "-moz-max(5px,2em)", + "calc(max(5px,2em))", + "calc(50px/(2 - 2))", + // If we ever support division by values, which is + // complicated for the reasons described in + // http://lists.w3.org/Archives/Public/www-style/2010Jan/0007.html + // , we should support all 4 of these as described in + // http://lists.w3.org/Archives/Public/www-style/2009Dec/0296.html + "calc((3em / 100%) * 3em)", + "calc(3em / 100% * 3em)", + "calc(3em * (3em / 100%))", + "calc(3em * 3em / 100%)" + ] + }, + "flex-direction": { + domProp: "flexDirection", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "row" ], + other_values: [ "row-reverse", "column", "column-reverse" ], + invalid_values: [ "10px", "30%", "justify", "column wrap" ] + }, + "flex-flow": { + domProp: "flexFlow", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "flex-direction", + "flex-wrap" + ], + initial_values: [ "row nowrap", "nowrap row", "row", "nowrap" ], + other_values: [ + // only specifying one property: + "column", + "wrap", + "wrap-reverse", + // specifying both properties, 'flex-direction' first: + "row wrap", + "row wrap-reverse", + "column wrap", + "column wrap-reverse", + // specifying both properties, 'flex-wrap' first: + "wrap row", + "wrap column", + "wrap-reverse row", + "wrap-reverse column", + ], + invalid_values: [ + // specifying flex-direction twice (invalid): + "row column", + "row column nowrap", + "row nowrap column", + "nowrap row column", + // specifying flex-wrap twice (invalid): + "nowrap wrap-reverse", + "nowrap wrap-reverse row", + "nowrap row wrap-reverse", + "row nowrap wrap-reverse", + // Invalid data-type / invalid keyword type: + "1px", "5%", "justify", "none" + ] + }, + "flex-grow": { + domProp: "flexGrow", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0" ], + other_values: [ "3", "1", "1.0", "2.5", "123" ], + invalid_values: [ "0px", "-5", "1%", "3em", "stretch", "auto" ] + }, + "flex-shrink": { + domProp: "flexShrink", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "1" ], + other_values: [ "3", "0", "0.0", "2.5", "123" ], + invalid_values: [ "0px", "-5", "1%", "3em", "stretch", "auto" ] + }, + "flex-wrap": { + domProp: "flexWrap", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "nowrap" ], + other_values: [ "wrap", "wrap-reverse" ], + invalid_values: [ "10px", "30%", "justify", "column wrap", "auto" ] + }, + "order": { + domProp: "order", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0" ], + other_values: [ "1", "99999", "-1", "-50" ], + invalid_values: [ "0px", "1.0", "1.", "1%", "0.2", "3em", "stretch" ] + }, + + // Aliases + "word-wrap": { + domProp: "wordWrap", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "overflow-wrap", + subproperties: [ "overflow-wrap" ], + }, + "-moz-transform": { + domProp: "MozTransform", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform", + subproperties: [ "transform" ], + // NOTE: We specify other_values & invalid_values explicitly here (instead + // of deferring to "transform") because we accept some legacy syntax as + // valid for "-moz-transform" but not for "transform". + other_values: [ "translatex(1px)", "translatex(4em)", + "translatex(-4px)", "translatex(3px)", + "translatex(0px) translatex(1px) translatex(2px) translatex(3px) translatex(4px)", + "translatey(4em)", "translate(3px)", "translate(10px, -3px)", + "rotate(45deg)", "rotate(45grad)", "rotate(45rad)", + "rotate(0.25turn)", "rotate(0)", "scalex(10)", "scaley(10)", + "scale(10)", "scale(10, 20)", "skewx(30deg)", "skewx(0)", + "skewy(0)", "skewx(30grad)", "skewx(30rad)", "skewx(0.08turn)", + "skewy(30deg)", "skewy(30grad)", "skewy(30rad)", "skewy(0.08turn)", + "rotate(45deg) scale(2, 1)", "skewx(45deg) skewx(-50grad)", + "translate(0, 0) scale(1, 1) skewx(0) skewy(0) matrix(1, 0, 0, 1, 0, 0)", + "translatex(50%)", "translatey(50%)", "translate(50%)", + "translate(3%, 5px)", "translate(5px, 3%)", + "matrix(1, 2, 3, 4, 5, 6)", + /* valid calc() values */ + "translatex(calc(5px + 10%))", + "translatey(calc(0.25 * 5px + 10% / 3))", + "translate(calc(5px - 10% * 3))", + "translate(calc(5px - 3 * 10%), 50px)", + "translate(-50px, calc(5px - 10% * 3))", + /* valid only when prefixed */ + "matrix(1, 2, 3, 4, 5px, 6%)", + "matrix(1, 2, 3, 4, 5%, 6px)", + "matrix(1, 2, 3, 4, 5%, 6%)", + "matrix(1, 2, 3, 4, 5px, 6em)", + "matrix(1, 0, 0, 1, calc(5px * 3), calc(10% - 3px))", + "translatez(1px)", "translatez(4em)", "translatez(-4px)", + "translatez(0px)", "translatez(2px) translatez(5px)", + "translate3d(3px, 4px, 5px)", "translate3d(2em, 3px, 1em)", + "translatex(2px) translate3d(4px, 5px, 6px) translatey(1px)", + "scale3d(4, 4, 4)", "scale3d(-2, 3, -7)", "scalez(4)", + "scalez(-6)", "rotate3d(2, 3, 4, 45deg)", + "rotate3d(-3, 7, 0, 12rad)", "rotatex(15deg)", "rotatey(-12grad)", + "rotatez(72rad)", "rotatex(0.125turn)", + "perspective(0px)", "perspective(1000px)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)", + /* valid only when prefixed */ + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13px, 14em, 15px, 16)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20%, 10%, 15, 16)", + ], + invalid_values: ["1px", "#0000ff", "red", "auto", + "translatex(1)", "translatey(1)", "translate(2)", + "translate(-3, -4)", + "translatex(1px 1px)", "translatex(translatex(1px))", + "translatex(#0000ff)", "translatex(red)", "translatey()", + "matrix(1px, 2px, 3px, 4px, 5px, 6px)", "scale(150%)", + "skewx(red)", "matrix(1%, 0, 0, 0, 0px, 0px)", + "matrix(0, 1%, 2, 3, 4px,5px)", "matrix(0, 1, 2%, 3, 4px, 5px)", + "matrix(0, 1, 2, 3%, 4%, 5%)", + /* invalid calc() values */ + "translatey(-moz-min(5px,10%))", + "translatex(-moz-max(5px,10%))", + "translate(10px, calc(min(5px,10%)))", + "translate(calc(max(5px,10%)), 10%)", + "matrix(1, 0, 0, 1, max(5px * 3), calc(10% - 3px))", + "perspective(-10px)", "matrix3d(dinosaur)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15%, 16)", + "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16px)", + "rotatey(words)", "rotatex(7)", "translate3d(3px, 4px, 1px, 7px)", + ], + }, + "-moz-transform-origin": { + domProp: "MozTransformOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform-origin", + subproperties: [ "transform-origin" ], + }, + "-moz-perspective-origin": { + domProp: "MozPerspectiveOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "perspective-origin", + subproperties: [ "perspective-origin" ], + }, + "-moz-perspective": { + domProp: "MozPerspective", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "perspective", + subproperties: [ "perspective" ], + }, + "-moz-backface-visibility": { + domProp: "MozBackfaceVisibility", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "backface-visibility", + subproperties: [ "backface-visibility" ], + }, + "-moz-transform-style": { + domProp: "MozTransformStyle", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform-style", + subproperties: [ "transform-style" ], + }, + "-moz-border-image": { + domProp: "MozBorderImage", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "border-image", + subproperties: [ "border-image-source", "border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat" ], + }, + "-moz-transition": { + domProp: "MozTransition", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "transition", + subproperties: [ "transition-property", "transition-duration", "transition-timing-function", "transition-delay" ], + }, + "-moz-transition-delay": { + domProp: "MozTransitionDelay", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transition-delay", + subproperties: [ "transition-delay" ], + }, + "-moz-transition-duration": { + domProp: "MozTransitionDuration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transition-duration", + subproperties: [ "transition-duration" ], + }, + "-moz-transition-property": { + domProp: "MozTransitionProperty", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transition-property", + subproperties: [ "transition-property" ], + }, + "-moz-transition-timing-function": { + domProp: "MozTransitionTimingFunction", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transition-timing-function", + subproperties: [ "transition-timing-function" ], + }, + "-moz-animation": { + domProp: "MozAnimation", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "animation", + subproperties: [ "animation-name", "animation-duration", "animation-timing-function", "animation-delay", "animation-direction", "animation-fill-mode", "animation-iteration-count", "animation-play-state" ], + }, + "-moz-animation-delay": { + domProp: "MozAnimationDelay", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-delay", + subproperties: [ "animation-delay" ], + }, + "-moz-animation-direction": { + domProp: "MozAnimationDirection", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-direction", + subproperties: [ "animation-direction" ], + }, + "-moz-animation-duration": { + domProp: "MozAnimationDuration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-duration", + subproperties: [ "animation-duration" ], + }, + "-moz-animation-fill-mode": { + domProp: "MozAnimationFillMode", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-fill-mode", + subproperties: [ "animation-fill-mode" ], + }, + "-moz-animation-iteration-count": { + domProp: "MozAnimationIterationCount", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-iteration-count", + subproperties: [ "animation-iteration-count" ], + }, + "-moz-animation-name": { + domProp: "MozAnimationName", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-name", + subproperties: [ "animation-name" ], + }, + "-moz-animation-play-state": { + domProp: "MozAnimationPlayState", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-play-state", + subproperties: [ "animation-play-state" ], + }, + "-moz-animation-timing-function": { + domProp: "MozAnimationTimingFunction", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-timing-function", + subproperties: [ "animation-timing-function" ], + }, + "-moz-font-feature-settings": { + domProp: "MozFontFeatureSettings", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "font-feature-settings", + subproperties: [ "font-feature-settings" ], + }, + "-moz-font-language-override": { + domProp: "MozFontLanguageOverride", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "font-language-override", + subproperties: [ "font-language-override" ], + }, + "-moz-hyphens": { + domProp: "MozHyphens", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "hyphens", + subproperties: [ "hyphens" ], + }, + "-moz-text-align-last": { + domProp: "MozTextAlignLast", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "text-align-last", + subproperties: [ "text-align-last" ], + }, + // vertical text properties + "writing-mode": { + domProp: "writingMode", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "horizontal-tb", "lr", "lr-tb", "rl", "rl-tb" ], + other_values: [ "vertical-lr", "vertical-rl", "sideways-rl", "sideways-lr", "tb", "tb-rl" ], + invalid_values: [ "10px", "30%", "justify", "auto", "1em" ] + }, + "text-orientation": { + domProp: "textOrientation", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "mixed" ], + other_values: [ "upright", "sideways", "sideways-right" ], /* sideways-right alias for backward compatibility */ + invalid_values: [ "none", "3em", "sideways-left" ] /* sideways-left removed from CSS Writing Modes */ + }, + "border-block-end": { + domProp: "borderBlockEnd", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-block-end-color", "border-block-end-style", "border-block-end-width" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], + other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ], + invalid_values: [ "5%", "5", "5 solid green" ] + }, + "block-size": { + domProp: "blockSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + get_computed: logical_axis_prop_get_computed, + /* XXX testing auto has prerequisites */ + initial_values: [ "auto" ], + prerequisites: { "display": "block" }, + other_values: [ "15px", "3em", "15%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "none", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ], + }, + "border-block-end-color": { + domProp: "borderBlockEndColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000" ] + }, + "border-block-end-style": { + domProp: "borderBlockEndStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge" ], + invalid_values: [] + }, + "border-block-end-width": { + domProp: "borderBlockEndWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + prerequisites: { "border-block-end-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0em)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%", "5" ] + }, + "border-block-start": { + domProp: "borderBlockStart", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "border-block-start-color", "border-block-start-style", "border-block-start-width" ], + initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], + other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ], + invalid_values: [ "5%", "5", "5 solid green" ] + }, + "border-block-start-color": { + domProp: "borderBlockStartColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + initial_values: [ "currentColor" ], + other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000" ] + }, + "border-block-start-style": { + domProp: "borderBlockStartStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* XXX hidden is sometimes the same as initial */ + initial_values: [ "none" ], + other_values: [ "solid", "dashed", "dotted", "double", "outset", "inset", "groove", "ridge" ], + invalid_values: [] + }, + "border-block-start-width": { + domProp: "borderBlockStartWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + prerequisites: { "border-block-start-style": "solid" }, + initial_values: [ "medium", "3px", "calc(4px - 1px)" ], + other_values: [ "thin", "thick", "1px", "2em", + "calc(2px)", + "calc(-2px)", + "calc(0em)", + "calc(0px)", + "calc(5em)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 5em)", + ], + invalid_values: [ "5%", "5" ] + }, + "-moz-border-end": { + domProp: "MozBorderEnd", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "border-inline-end", + subproperties: [ "-moz-border-end-color", "-moz-border-end-style", "-moz-border-end-width" ], + }, + "-moz-border-end-color": { + domProp: "MozBorderEndColor", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-inline-end-color", + subproperties: [ "border-inline-end-color" ], + get_computed: logical_box_prop_get_computed, + }, + "-moz-border-end-style": { + domProp: "MozBorderEndStyle", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-inline-end-style", + subproperties: [ "border-inline-end-style" ], + get_computed: logical_box_prop_get_computed, + }, + "-moz-border-end-width": { + domProp: "MozBorderEndWidth", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-inline-end-width", + subproperties: [ "border-inline-end-width" ], + get_computed: logical_box_prop_get_computed, + }, + "-moz-border-start": { + domProp: "MozBorderStart", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "border-inline-start", + subproperties: [ "-moz-border-start-color", "-moz-border-start-style", "-moz-border-start-width" ], + }, + "-moz-border-start-color": { + domProp: "MozBorderStartColor", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-inline-start-color", + subproperties: [ "border-inline-start-color" ], + get_computed: logical_box_prop_get_computed, + }, + "-moz-border-start-style": { + domProp: "MozBorderStartStyle", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-inline-start-style", + subproperties: [ "border-inline-start-style" ], + get_computed: logical_box_prop_get_computed, + }, + "-moz-border-start-width": { + domProp: "MozBorderStartWidth", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-inline-start-width", + subproperties: [ "border-inline-start-width" ], + get_computed: logical_box_prop_get_computed, + }, + "inline-size": { + domProp: "inlineSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + get_computed: logical_axis_prop_get_computed, + /* XXX testing auto has prerequisites */ + initial_values: [ "auto" ], + prerequisites: { "display": "block" }, + other_values: [ "15px", "3em", "15%", + // these three keywords compute to the initial value only when the + // writing mode is vertical, and we're testing with a horizontal + // writing mode + "-moz-max-content", "-moz-min-content", "-moz-fit-content", + // whether -moz-available computes to the initial value depends on + // the container size, and for the container size we're testing + // with, it does + // "-moz-available", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "none" ], + }, + "margin-block-end": { + domProp: "marginBlockEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* XXX testing auto has prerequisites */ + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "..25px", ".+5px", ".px", "-.px", "++5px", "-+4px", "+-3px", "--7px", "+-.6px", "-+.5px", "++.7px", "--.4px" ], + }, + "margin-block-start": { + domProp: "marginBlockStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* XXX testing auto has prerequisites */ + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "..25px", ".+5px", ".px", "-.px", "++5px", "-+4px", "+-3px", "--7px", "+-.6px", "-+.5px", "++.7px", "--.4px" ], + }, + "-moz-margin-end": { + domProp: "MozMarginEnd", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "margin-inline-end", + subproperties: [ "margin-inline-end" ], + get_computed: logical_box_prop_get_computed, + }, + "-moz-margin-start": { + domProp: "MozMarginStart", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "margin-inline-start", + subproperties: [ "margin-inline-start" ], + get_computed: logical_box_prop_get_computed, + }, + "max-block-size": { + domProp: "maxBlockSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + get_computed: logical_axis_prop_get_computed, + prerequisites: { "display": "block" }, + initial_values: [ "none" ], + other_values: [ "30px", "50%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "auto", "5", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ] + }, + "max-inline-size": { + domProp: "maxInlineSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + get_computed: logical_axis_prop_get_computed, + prerequisites: { "display": "block" }, + initial_values: [ "none" ], + other_values: [ "30px", "50%", + // these four keywords compute to the initial value only when the + // writing mode is vertical, and we're testing with a horizontal + // writing mode + "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "auto", "5" ] + }, + "min-block-size": { + domProp: "minBlockSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + get_computed: logical_axis_prop_get_computed, + prerequisites: { "display": "block" }, + initial_values: [ "auto", "0", "calc(0em)", "calc(-2px)" ], + other_values: [ "30px", "50%", + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "none", "5", "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available" ] + }, + "min-inline-size": { + domProp: "minInlineSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + get_computed: logical_axis_prop_get_computed, + prerequisites: { "display": "block" }, + initial_values: [ "auto", "0", "calc(0em)", "calc(-2px)" ], + other_values: [ "30px", "50%", + // these four keywords compute to the initial value only when the + // writing mode is vertical, and we're testing with a horizontal + // writing mode + "-moz-max-content", "-moz-min-content", "-moz-fit-content", "-moz-available", + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ "none", "5" ] + }, + "offset-block-end": { + domProp: "offsetBlockEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { "position": "relative" }, + /* XXX 0 may or may not be equal to auto */ + initial_values: [ "auto" ], + other_values: [ "32px", "-3em", "12%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [] + }, + "offset-block-start": { + domProp: "offsetBlockStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { "position": "relative" }, + /* XXX 0 may or may not be equal to auto */ + initial_values: [ "auto" ], + other_values: [ "32px", "-3em", "12%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [] + }, + "offset-inline-end": { + domProp: "offsetInlineEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { "position": "relative" }, + /* XXX 0 may or may not be equal to auto */ + initial_values: [ "auto" ], + other_values: [ "32px", "-3em", "12%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [] + }, + "offset-inline-start": { + domProp: "offsetInlineStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { "position": "relative" }, + /* XXX 0 may or may not be equal to auto */ + initial_values: [ "auto" ], + other_values: [ "32px", "-3em", "12%", + "calc(2px)", + "calc(-2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [] + }, + "padding-block-end": { + domProp: "paddingBlockEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)", "calc(-3px)", "calc(-1%)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + }, + "padding-block-start": { + domProp: "paddingBlockStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + get_computed: logical_box_prop_get_computed, + initial_values: [ "0", "0px", "0%", "calc(0pt)", "calc(0% + 0px)", "calc(-3px)", "calc(-1%)" ], + other_values: [ "1px", "2em", "5%", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + ], + invalid_values: [ ], + }, + "-moz-padding-end": { + domProp: "MozPaddingEnd", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "padding-inline-end", + subproperties: [ "padding-inline-end" ], + get_computed: logical_box_prop_get_computed, + }, + "-moz-padding-start": { + domProp: "MozPaddingStart", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "padding-inline-start", + subproperties: [ "padding-inline-start" ], + get_computed: logical_box_prop_get_computed, + }, +} // end of gCSSProperties + +function logical_axis_prop_get_computed(cs, property) +{ + // Use defaults for these two properties in case the vertical text + // pref (which they live behind) is turned off. + var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb"; + var orientation = writingMode.substring(0, writingMode.indexOf("-")); + + var mappings = { + "block-size": { horizontal: "height", + vertical: "width", + sideways: "width" }, + "inline-size": { horizontal: "width", + vertical: "height", + sideways: "height" }, + "max-block-size": { horizontal: "max-height", + vertical: "max-width", + sideways: "max-width" }, + "max-inline-size": { horizontal: "max-width", + vertical: "max-height", + sideways: "max-height" }, + "min-block-size": { horizontal: "min-height", + vertical: "min-width", + sideways: "min-width" }, + "min-inline-size": { horizontal: "min-width", + vertical: "min-height", + sideways: "min-height" }, + }; + + if (!mappings[property]) { + throw "unexpected property " + property; + } + + var prop = mappings[property][orientation]; + if (!prop) { + throw "unexpected writing mode " + writingMode; + } + + return cs.getPropertyValue(prop); +} + +function logical_box_prop_get_computed(cs, property) +{ + // http://dev.w3.org/csswg/css-writing-modes-3/#logical-to-physical + + // Use default for writing-mode in case the vertical text + // pref (which it lives behind) is turned off. + var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb"; + + var direction = cs.getPropertyValue("direction"); + + // keys in blockMappings are writing-mode values + var blockMappings = { + "horizontal-tb": { "start": "top", "end": "bottom" }, + "vertical-rl": { "start": "right", "end": "left" }, + "vertical-lr": { "start": "left", "end": "right" }, + "sideways-rl": { "start": "right", "end": "left" }, + "sideways-lr": { "start": "left", "end": "right" }, + }; + + // keys in inlineMappings are regular expressions that match against + // a {writing-mode,direction} pair as a space-separated string + var inlineMappings = { + "horizontal-tb ltr": { "start": "left", "end": "right" }, + "horizontal-tb rtl": { "start": "right", "end": "left" }, + "vertical-.. ltr": { "start": "bottom", "end": "top" }, + "vertical-.. rtl": { "start": "top", "end": "bottom" }, + "vertical-.. ltr": { "start": "top", "end": "bottom" }, + "vertical-.. rtl": { "start": "bottom", "end": "top" }, + "sideways-lr ltr": { "start": "bottom", "end": "top" }, + "sideways-lr rtl": { "start": "top", "end": "bottom" }, + "sideways-rl ltr": { "start": "top", "end": "bottom" }, + "sideways-rl rtl": { "start": "bottom", "end": "top" }, + }; + + var blockMapping = blockMappings[writingMode]; + var inlineMapping; + + // test each regular expression in inlineMappings against the + // {writing-mode,direction} pair + var key = `${writingMode} ${direction}`; + for (var k in inlineMappings) { + if (new RegExp(k).test(key)) { + inlineMapping = inlineMappings[k]; + break; + } + } + + if (!blockMapping || !inlineMapping) { + throw "Unexpected writing mode property values"; + } + + function physicalize(aProperty, aMapping, aLogicalPrefix) { + for (var logicalSide in aMapping) { + var physicalSide = aMapping[logicalSide]; + logicalSide = aLogicalPrefix + logicalSide; + aProperty = aProperty.replace(logicalSide, physicalSide); + } + return aProperty; + } + + if (/^-moz-/.test(property)) { + property = physicalize(property.substring(5), inlineMapping, ""); + } else if (/^offset-(block|inline)-(start|end)/.test(property)) { + property = property.substring(7); // we want "top" not "offset-top", e.g. + property = physicalize(property, blockMapping, "block-"); + property = physicalize(property, inlineMapping, "inline-"); + } else if (/-(block|inline)-(start|end)/.test(property)) { + property = physicalize(property, blockMapping, "block-"); + property = physicalize(property, inlineMapping, "inline-"); + } else { + throw "Unexpected property"; + } + return cs.getPropertyValue(property); +} + +// Get the computed value for a property. For shorthands, return the +// computed values of all the subproperties, delimited by " ; ". +function get_computed_value(cs, property) +{ + var info = gCSSProperties[property]; + if (info.type == CSS_TYPE_TRUE_SHORTHAND || + (info.type == CSS_TYPE_SHORTHAND_AND_LONGHAND && + (property == "text-decoration" || property == "mask"))) { + var results = []; + for (var idx in info.subproperties) { + var subprop = info.subproperties[idx]; + results.push(get_computed_value(cs, subprop)); + } + return results.join(" ; "); + } + if (info.get_computed) + return info.get_computed(cs, property); + return cs.getPropertyValue(property); +} + +if (IsCSSPropertyPrefEnabled("layout.css.touch_action.enabled")) { + gCSSProperties["touch-action"] = { + domProp: "touchAction", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["none", "pan-x", "pan-y", "pan-x pan-y", "pan-y pan-x", "manipulation"], + invalid_values: ["zoom", "pinch", "tap", "10px", "2", "auto pan-x", "pan-x auto", "none pan-x", "pan-x none", + "auto pan-y", "pan-y auto", "none pan-y", "pan-y none", "pan-x pan-x", "pan-y pan-y", + "pan-x pan-y none", "pan-x none pan-y", "none pan-x pan-y", "pan-y pan-x none", "pan-y none pan-x", "none pan-y pan-x", + "pan-x pan-y auto", "pan-x auto pan-y", "auto pan-x pan-y", "pan-y pan-x auto", "pan-y auto pan-x", "auto pan-y pan-x", + "pan-x pan-y zoom", "pan-x zoom pan-y", "zoom pan-x pan-y", "pan-y pan-x zoom", "pan-y zoom pan-x", "zoom pan-y pan-x", + "pan-x pan-y pan-x", "pan-x pan-x pan-y", "pan-y pan-x pan-x", "pan-y pan-x pan-y", "pan-y pan-y pan-x", "pan-x pan-y pan-y", + "manipulation none", "none manipulation", "manipulation auto", "auto manipulation", "manipulation zoom", "zoom manipulation", + "manipulation manipulation", "manipulation pan-x", "pan-x manipulation", "manipulation pan-y", "pan-y manipulation", + "manipulation pan-x pan-y", "pan-x manipulation pan-y", "pan-x pan-y manipulation", + "manipulation pan-y pan-x", "pan-y manipulation pan-x", "pan-y pan-x manipulation"] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.text-combine-upright.enabled")) { + gCSSProperties["text-combine-upright"] = { + domProp: "textCombineUpright", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "all" ], + invalid_values: [ "auto", "all 2", "none all", "digits -3", "digits 0", + "digits 12", "none 3", "digits 3.1415", "digits3", "digits 1", + "digits 3 all", "digits foo", "digits all", "digits 3.0" ] + }; + if (IsCSSPropertyPrefEnabled("layout.css.text-combine-upright-digits.enabled")) { + gCSSProperties["text-combine-upright"].other_values.push( + "digits", "digits 2", "digits 3", "digits 4", "digits 3"); + } +} + +if (IsCSSPropertyPrefEnabled("svg.paint-order.enabled")) { + gCSSProperties["paint-order"] = { + domProp: "paintOrder", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "fill", "fill stroke", "fill stroke markers", "stroke markers fill" ], + invalid_values: [ "fill stroke markers fill", "fill normal" ] + }; +} + +if (IsCSSPropertyPrefEnabled("svg.transform-box.enabled")) { + gCSSProperties["transform-box"] = { + domProp: "transformBox", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "border-box" ], + other_values: [ "fill-box", "view-box" ], + invalid_values: [] + }; +} + +var basicShapeOtherValues = [ + "polygon(20px 20px)", + "polygon(20px 20%)", + "polygon(20% 20%)", + "polygon(20rem 20em)", + "polygon(20cm 20mm)", + "polygon(20px 20px, 30px 30px)", + "polygon(20px 20px, 30% 30%, 30px 30px)", + "polygon(nonzero, 20px 20px, 30% 30%, 30px 30px)", + "polygon(evenodd, 20px 20px, 30% 30%, 30px 30px)", + + "content-box", + "padding-box", + "border-box", + "margin-box", + + "polygon(0 0) content-box", + "border-box polygon(0 0)", + "padding-box polygon( 0 20px , 30px 20% ) ", + "polygon(evenodd, 20% 20em) content-box", + "polygon(evenodd, 20vh 20em) padding-box", + "polygon(evenodd, 20vh calc(20% + 20em)) border-box", + "polygon(evenodd, 20vh 20vw) margin-box", + + "circle()", + "circle(at center)", + "circle(at top left 20px)", + "circle(at bottom right)", + "circle(20%)", + "circle(300px)", + "circle(calc(20px + 30px))", + "circle(farthest-side)", + "circle(closest-side)", + "circle(closest-side at center)", + "circle(farthest-side at top)", + "circle(20px at top right)", + "circle(40% at 50% 100%)", + "circle(calc(20% + 20%) at right bottom)", + "circle() padding-box", + + "ellipse()", + "ellipse(at center)", + "ellipse(at top left 20px)", + "ellipse(at bottom right)", + "ellipse(20% 20%)", + "ellipse(300px 50%)", + "ellipse(calc(20px + 30px) 10%)", + "ellipse(farthest-side closest-side)", + "ellipse(closest-side farthest-side)", + "ellipse(farthest-side farthest-side)", + "ellipse(closest-side closest-side)", + "ellipse(closest-side closest-side at center)", + "ellipse(20% farthest-side at top)", + "ellipse(20px 50% at top right)", + "ellipse(closest-side 40% at 50% 100%)", + "ellipse(calc(20% + 20%) calc(20px + 20cm) at right bottom)", + + "inset(1px)", + "inset(20% -20px)", + "inset(20em 4rem calc(20% + 20px))", + "inset(20vh 20vw 20pt 3%)", + "inset(5px round 3px)", + "inset(1px 2px round 3px / 3px)", + "inset(1px 2px 3px round 3px 2em / 20%)", + "inset(1px 2px 3px 4px round 3px 2vw 20% / 20px 3em 2vh 20%)", +]; + +var basicShapeInvalidValues = [ + "url(#test) url(#tes2)", + "polygon (0 0)", + "polygon(20px, 40px)", + "border-box content-box", + "polygon(0 0) polygon(0 0)", + "polygon(nonzero 0 0)", + "polygon(evenodd 20px 20px)", + "polygon(20px 20px, evenodd)", + "polygon(20px 20px, nonzero)", + "polygon(0 0) conten-box content-box", + "content-box polygon(0 0) conten-box", + "padding-box polygon(0 0) conten-box", + "polygon(0 0) polygon(0 0) content-box", + "polygon(0 0) content-box polygon(0 0)", + "polygon(0 0), content-box", + "polygon(0 0), polygon(0 0)", + "content-box polygon(0 0) polygon(0 0)", + "content-box polygon(0 0) none", + "none content-box polygon(0 0)", + "inherit content-box polygon(0 0)", + "initial polygon(0 0)", + "polygon(0 0) farthest-side", + "farthest-corner polygon(0 0)", + "polygon(0 0) farthest-corner", + "polygon(0 0) conten-box", + "polygon(0 0) polygon(0 0) farthest-corner", + "polygon(0 0) polygon(0 0) polygon(0 0)", + "border-box polygon(0, 0)", + "border-box padding-box", + "margin-box farthest-side", + "nonsense() border-box", + "border-box nonsense()", + + "circle(at)", + "circle(at 20% 20% 30%)", + "circle(20px 2px at center)", + "circle(2at center)", + "circle(closest-corner)", + "circle(at center top closest-side)", + "circle(-20px)", + "circle(farthest-side closest-side)", + "circle(20% 20%)", + "circle(at farthest-side)", + "circle(calc(20px + rubbish))", + + "ellipse(at)", + "ellipse(at 20% 20% 30%)", + "ellipse(20px at center)", + "ellipse(-20px 20px)", + "ellipse(closest-corner farthest-corner)", + "ellipse(20px -20px)", + "ellipse(-20px -20px)", + "ellipse(farthest-side)", + "ellipse(20%)", + "ellipse(at farthest-side farthest-side)", + "ellipse(at top left calc(20px + rubbish))", + + "polygon(at)", + "polygon(at 20% 20% 30%)", + "polygon(20px at center)", + "polygon(2px 2at center)", + "polygon(closest-corner farthest-corner)", + "polygon(at center top closest-side closest-side)", + "polygon(40% at 50% 100%)", + "polygon(40% farthest-side 20px at 50% 100%)", + + "inset()", + "inset(round)", + "inset(round 3px)", + "inset(1px round 1px 2px 3px 4px 5px)", + "inset(1px 2px 3px 4px 5px)", + "inset(1px, round 3px)", + "inset(1px, 2px)", + "inset(1px 2px, 3px)", + "inset(1px at 3px)", + "inset(1px round 1px // 2px)", + "inset(1px round)", + "inset(1px calc(2px + rubbish))", + "inset(1px round 2px calc(3px + rubbish))", +]; + +var basicShapeUnbalancedValues = [ + "polygon(30% 30%", + "polygon(nonzero, 20% 20px", + "polygon(evenodd, 20px 20px", + + "circle(", + "circle(40% at 50% 100%", + "ellipse(", + "ellipse(40% at 50% 100%", + + "inset(1px", + "inset(1px 2px", + "inset(1px 2px 3px", + "inset(1px 2px 3px 4px", + "inset(1px 2px 3px 4px round 5px", + "inset(1px 2px 3px 4px round 5px / 6px", +]; + +if (IsCSSPropertyPrefEnabled("layout.css.clip-path-shapes.enabled")) { + gCSSProperties["clip-path"] = { + domProp: "clipPath", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + // SVG reference clip-path + "url(#my-clip-path)", + + "fill-box", + "stroke-box", + "view-box", + + "polygon(evenodd, 20pt 20cm) fill-box", + "polygon(evenodd, 20ex 20pc) stroke-box", + "polygon(evenodd, 20rem 20in) view-box", + ].concat(basicShapeOtherValues), + invalid_values: basicShapeInvalidValues, + unbalanced_values: basicShapeUnbalancedValues, + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.shape-outside.enabled")) { + gCSSProperties["shape-outside"] = { + domProp: "shapeOutside", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + "url(#my-shape-outside)", + ].concat(basicShapeOtherValues), + invalid_values: basicShapeInvalidValues, + unbalanced_values: basicShapeUnbalancedValues, + }; +} + + +if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) { + gCSSProperties["filter"] = { + domProp: "filter", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + // SVG reference filters + "url(#my-filter)", + "url(#my-filter-1) url(#my-filter-2)", + + // Filter functions + "opacity(50%) saturate(1.0)", + "invert(50%) sepia(0.1) brightness(90%)", + + // Mixed SVG reference filters and filter functions + "grayscale(1) url(#my-filter-1)", + "url(#my-filter-1) brightness(50%) contrast(0.9)", + + // Bad URLs + "url('badscheme:badurl')", + "blur(3px) url('badscheme:badurl') grayscale(50%)", + + "blur(0)", + "blur(0px)", + "blur(0.5px)", + "blur(3px)", + "blur(100px)", + "blur(0.1em)", + "blur(calc(-1px))", // Parses and becomes blur(0px). + "blur(calc(0px))", + "blur(calc(5px))", + "blur(calc(2 * 5px))", + + "brightness(0)", + "brightness(50%)", + "brightness(1)", + "brightness(1.0)", + "brightness(2)", + "brightness(350%)", + "brightness(4.567)", + + "contrast(0)", + "contrast(50%)", + "contrast(1)", + "contrast(1.0)", + "contrast(2)", + "contrast(350%)", + "contrast(4.567)", + + "drop-shadow(2px 2px)", + "drop-shadow(2px 2px 1px)", + "drop-shadow(2px 2px green)", + "drop-shadow(2px 2px 1px green)", + "drop-shadow(green 2px 2px)", + "drop-shadow(green 2px 2px 1px)", + "drop-shadow(currentColor 3px 3px)", + "drop-shadow(2px 2px calc(-5px))", /* clamped */ + "drop-shadow(calc(3em - 2px) 2px green)", + "drop-shadow(green calc(3em - 2px) 2px)", + "drop-shadow(2px calc(2px + 0.2em))", + "drop-shadow(blue 2px calc(2px + 0.2em))", + "drop-shadow(2px calc(2px + 0.2em) blue)", + "drop-shadow(calc(-2px) calc(-2px))", + "drop-shadow(-2px -2px)", + "drop-shadow(calc(2px) calc(2px))", + "drop-shadow(calc(2px) calc(2px) calc(2px))", + + "grayscale(0)", + "grayscale(50%)", + "grayscale(1)", + "grayscale(1.0)", + "grayscale(2)", + "grayscale(350%)", + "grayscale(4.567)", + + "hue-rotate(0deg)", + "hue-rotate(90deg)", + "hue-rotate(540deg)", + "hue-rotate(-90deg)", + "hue-rotate(10grad)", + "hue-rotate(1.6rad)", + "hue-rotate(-1.6rad)", + "hue-rotate(0.5turn)", + "hue-rotate(-2turn)", + + "invert(0)", + "invert(50%)", + "invert(1)", + "invert(1.0)", + "invert(2)", + "invert(350%)", + "invert(4.567)", + + "opacity(0)", + "opacity(50%)", + "opacity(1)", + "opacity(1.0)", + "opacity(2)", + "opacity(350%)", + "opacity(4.567)", + + "saturate(0)", + "saturate(50%)", + "saturate(1)", + "saturate(1.0)", + "saturate(2)", + "saturate(350%)", + "saturate(4.567)", + + "sepia(0)", + "sepia(50%)", + "sepia(1)", + "sepia(1.0)", + "sepia(2)", + "sepia(350%)", + "sepia(4.567)", + ], + invalid_values: [ + // none + "none none", + "url(#my-filter) none", + "none url(#my-filter)", + "blur(2px) none url(#my-filter)", + + // Nested filters + "grayscale(invert(1.0))", + + // Comma delimited filters + "url(#my-filter),", + "invert(50%), url(#my-filter), brightness(90%)", + + // Test the following situations for each filter function: + // - Invalid number of arguments + // - Comma delimited arguments + // - Wrong argument type + // - Argument value out of range + "blur()", + "blur(3px 5px)", + "blur(3px,)", + "blur(3px, 5px)", + "blur(#my-filter)", + "blur(0.5)", + "blur(50%)", + "blur(calc(0))", // Unitless zero in calc is not a valid length. + "blur(calc(0.1))", + "blur(calc(10%))", + "blur(calc(20px - 5%))", + "blur(-3px)", + + "brightness()", + "brightness(0.5 0.5)", + "brightness(0.5,)", + "brightness(0.5, 0.5)", + "brightness(#my-filter)", + "brightness(10px)", + "brightness(-1)", + + "contrast()", + "contrast(0.5 0.5)", + "contrast(0.5,)", + "contrast(0.5, 0.5)", + "contrast(#my-filter)", + "contrast(10px)", + "contrast(-1)", + + "drop-shadow()", + "drop-shadow(3% 3%)", + "drop-shadow(2px 2px -5px)", + "drop-shadow(2px 2px 2px 2px)", + "drop-shadow(2px 2px, none)", + "drop-shadow(none, 2px 2px)", + "drop-shadow(inherit, 2px 2px)", + "drop-shadow(2px 2px, inherit)", + "drop-shadow(2 2px)", + "drop-shadow(2px 2)", + "drop-shadow(2px 2px 2)", + "drop-shadow(2px 2px 2px 2)", + "drop-shadow(calc(2px) calc(2px) calc(2px) calc(2px))", + "drop-shadow(green 2px 2px, blue 1px 3px 4px)", + "drop-shadow(blue 2px 2px, currentColor 1px 2px)", + + "grayscale()", + "grayscale(0.5 0.5)", + "grayscale(0.5,)", + "grayscale(0.5, 0.5)", + "grayscale(#my-filter)", + "grayscale(10px)", + "grayscale(-1)", + + "hue-rotate()", + "hue-rotate(0)", + "hue-rotate(0.5 0.5)", + "hue-rotate(0.5,)", + "hue-rotate(0.5, 0.5)", + "hue-rotate(#my-filter)", + "hue-rotate(10px)", + "hue-rotate(-1)", + "hue-rotate(45deg,)", + + "invert()", + "invert(0.5 0.5)", + "invert(0.5,)", + "invert(0.5, 0.5)", + "invert(#my-filter)", + "invert(10px)", + "invert(-1)", + + "opacity()", + "opacity(0.5 0.5)", + "opacity(0.5,)", + "opacity(0.5, 0.5)", + "opacity(#my-filter)", + "opacity(10px)", + "opacity(-1)", + + "saturate()", + "saturate(0.5 0.5)", + "saturate(0.5,)", + "saturate(0.5, 0.5)", + "saturate(#my-filter)", + "saturate(10px)", + "saturate(-1)", + + "sepia()", + "sepia(0.5 0.5)", + "sepia(0.5,)", + "sepia(0.5, 0.5)", + "sepia(#my-filter)", + "sepia(10px)", + "sepia(-1)", + ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.grid.enabled")) { + var isGridTemplateSubgridValueEnabled = + IsCSSPropertyPrefEnabled("layout.css.grid-template-subgrid-value.enabled"); + + gCSSProperties["display"].other_values.push("grid", "inline-grid"); + gCSSProperties["grid-auto-flow"] = { + domProp: "gridAutoFlow", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "row" ], + other_values: [ + "column", + "column dense", + "row dense", + "dense column", + "dense row", + "dense", + ], + invalid_values: [ + "", + "auto", + "none", + "10px", + "column row", + "dense row dense", + ] + }; + + gCSSProperties["grid-auto-columns"] = { + domProp: "gridAutoColumns", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ + "40px", + "2em", + "2.5fr", + "12%", + "min-content", + "max-content", + "calc(2px - 99%)", + "minmax(20px, max-content)", + "minmax(min-content, auto)", + "minmax(auto, max-content)", + "m\\69nmax(20px, 4Fr)", + "MinMax(min-content, calc(20px + 10%))", + "fit-content(1px)", + "fit-content(calc(1px - 99%))", + "fit-content(10%)", + ], + invalid_values: [ + "", + "normal", + "40ms", + "-40px", + "-12%", + "-2em", + "-2.5fr", + "minmax()", + "minmax(20px)", + "mİnmax(20px, 100px)", + "minmax(20px, 100px, 200px)", + "maxmin(100px, 20px)", + "minmax(min-content, minmax(30px, max-content))", + "fit-content(-1px)", + "fit-content(auto)", + "fit-content(min-content)", + ] + }; + gCSSProperties["grid-auto-rows"] = { + domProp: "gridAutoRows", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: gCSSProperties["grid-auto-columns"].initial_values, + other_values: gCSSProperties["grid-auto-columns"].other_values, + invalid_values: gCSSProperties["grid-auto-columns"].invalid_values + }; + + gCSSProperties["grid-template-columns"] = { + domProp: "gridTemplateColumns", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + "auto", + "40px", + "2.5fr", + "[normal] 40px [] auto [ ] 12%", + "[foo] 40px min-content [ bar ] calc(2px - 99%) max-content", + "40px min-content calc(20px + 10%) max-content", + "minmax(min-content, auto)", + "minmax(auto, max-content)", + "m\\69nmax(20px, 4Fr)", + "40px MinMax(min-content, calc(20px + 10%)) max-content", + "40px 2em", + "[] 40px [-foo] 2em [bar baz This\ is\ one\ ident]", + // TODO bug 978478: "[a] repeat(3, [b] 20px [c] 40px [d]) [e]", + "repeat(1, 20px)", + "repeat(1, [a] 20px)", + "[a] Repeat(4, [a] 20px [] auto [b c]) [d]", + "[a] 2.5fr Repeat(4, [a] 20px [] auto [b c]) [d]", + "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto [b c]) [d]", + "[a] 2.5fr [z] Repeat(4, [a] 20px [] auto) [d]", + "[a] 2.5fr [z] Repeat(4, 20px [b c] auto [b c]) [d]", + "[a] 2.5fr [z] Repeat(4, 20px auto) [d]", + "repeat(auto-fill, 0)", + "[a] repeat( Auto-fill,1%)", + "minmax(auto,0) [a] repeat(Auto-fit, 0) minmax(0,auto)", + "minmax(calc(1% + 1px),auto) repeat(Auto-fit,[] 1%) minmax(auto,1%)", + "[a] repeat( auto-fit,[a b] minmax(0,0) )", + "[a] 40px repeat(auto-fit,[a b] minmax(1px, 0) [])", + "[a] calc(1px - 99%) [b] repeat(auto-fit,[a b] minmax(1mm, 1%) [c]) [c]", + "repeat(auto-fill,minmax(1%,auto))", + "repeat(auto-fill,minmax(1em,min-content)) minmax(min-content,0)", + "repeat(auto-fill,minmax(max-content,1mm))", + "fit-content(1px) 1fr", + "[a] fit-content(calc(1px - 99%)) [b]", + "[a] fit-content(10%) [b c] fit-content(1em)", + ], + invalid_values: [ + "", + "normal", + "40ms", + "-40px", + "-12%", + "-2fr", + "[foo]", + "[inherit] 40px", + "[initial] 40px", + "[unset] 40px", + "[default] 40px", + "[span] 40px", + "[6%] 40px", + "[5th] 40px", + "[foo[] bar] 40px", + "[foo]] 40px", + "(foo) 40px", + "[foo] [bar] 40px", + "40px [foo] [bar]", + "minmax()", + "minmax(20px)", + "mİnmax(20px, 100px)", + "minmax(20px, 100px, 200px)", + "maxmin(100px, 20px)", + "minmax(min-content, minmax(30px, max-content))", + "repeat(0, 20px)", + "repeat(-3, 20px)", + "rêpeat(1, 20px)", + "repeat(1)", + "repeat(1, )", + "repeat(3px, 20px)", + "repeat(2.0, 20px)", + "repeat(2.5, 20px)", + "repeat(2, (foo))", + "repeat(2, foo)", + "40px calc(0px + rubbish)", + "repeat(1, repeat(1, 20px))", + "repeat(auto-fill, auto)", + "repeat(auto-fit,auto)", + "repeat(auto-fit,[])", + "repeat(auto-fill, 0) repeat(auto-fit, 0) ", + "repeat(auto-fit, 0) repeat(auto-fill, 0) ", + "[a] repeat(auto-fit, 0) repeat(auto-fit, 0) ", + "[a] repeat(auto-fill, 0) [a] repeat(auto-fill, 0) ", + "repeat(auto-fill, 0 0)", + "repeat(auto-fill, 0 [] 0)", + "repeat(auto-fill, min-content)", + "repeat(auto-fit,max-content)", + "repeat(auto-fit,1fr)", + "repeat(auto-fit,minmax(auto,auto))", + "repeat(auto-fit,minmax(min-content,1fr))", + "repeat(auto-fit,minmax(1fr,auto))", + "repeat(auto-fill,minmax(1fr,1em))", + "repeat(auto-fill, 10px) auto", + "auto repeat(auto-fit, 10px)", + "minmax(min-content,max-content) repeat(auto-fit, 0)", + "10px [a] 10px [b a] 1fr [b] repeat(auto-fill, 0)", + "fit-content(-1px)", + "fit-content(auto)", + "fit-content(min-content)", + ], + unbalanced_values: [ + "(foo] 40px", + ] + }; + if (isGridTemplateSubgridValueEnabled) { + gCSSProperties["grid-template-columns"].other_values.push( + // See https://bugzilla.mozilla.org/show_bug.cgi?id=981300 + "[none auto subgrid min-content max-content foo] 40px", + + "subgrid", + "subgrid [] [foo bar]", + "subgrid repeat(1, [])", + "subgrid Repeat(4, [a] [b c] [] [d])", + "subgrid repeat(auto-fill, [])", + "subgrid [x] repeat( Auto-fill, [a b c]) []", + "subgrid [x] repeat(auto-fill, []) [y z]" + ); + gCSSProperties["grid-template-columns"].invalid_values.push( + "subgrid [inherit]", + "subgrid [initial]", + "subgrid [unset]", + "subgrid [default]", + "subgrid [span]", + "subgrid [foo] 40px", + "subgrid [foo 40px]", + "[foo] subgrid", + "subgrid rêpeat(1, [])", + "subgrid repeat(0, [])", + "subgrid repeat(-3, [])", + "subgrid repeat(2.0, [])", + "subgrid repeat(2.5, [])", + "subgrid repeat(3px, [])", + "subgrid repeat(1)", + "subgrid repeat(1, )", + "subgrid repeat(2, [40px])", + "subgrid repeat(2, foo)", + "subgrid repeat(1, repeat(1, []))", + "subgrid repeat(auto-fit,[])", + "subgrid [] repeat(auto-fit,[])", + "subgrid [a] repeat(auto-fit,[])", + "subgrid repeat(auto-fill, 1px)", + "subgrid repeat(auto-fill, 1px [])", + "subgrid repeat(Auto-fill, [a] [b c] [] [d])", + "subgrid repeat(auto-fill, []) repeat(auto-fill, [])" + ); + } + gCSSProperties["grid-template-rows"] = { + domProp: "gridTemplateRows", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: gCSSProperties["grid-template-columns"].initial_values, + other_values: gCSSProperties["grid-template-columns"].other_values, + invalid_values: gCSSProperties["grid-template-columns"].invalid_values + }; + gCSSProperties["grid-template-areas"] = { + domProp: "gridTemplateAreas", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + "''", + "'' ''", + "'1a-é_ .' \"b .\"", + "' Z\t\\aZ' 'Z Z'", + " '. . a b' '. .a b' ", + "'a.b' '. . .'", + "'.' '..'", + "'...' '.'", + "'...-blah' '. .'", + "'.. ..' '.. ...'", + ], + invalid_values: [ + "'a b' 'a/b'", + "'a . a'", + "'. a a' 'a a a'", + "'a a .' 'a a a'", + "'a a' 'a .'", + "'a a'\n'..'\n'a a'", + ] + }; + + gCSSProperties["grid-template"] = { + domProp: "gridTemplate", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "grid-template-areas", + "grid-template-rows", + "grid-template-columns", + ], + initial_values: [ + "none", + "none / none", + ], + other_values: [ + // <'grid-template-rows'> / <'grid-template-columns'> + "40px / 100px", + "[foo] 40px [bar] / [baz] repeat(auto-fill,100px) [fizz]", + " none/100px", + "40px/none", + // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list> ]? + "'fizz'", + "[bar] 'fizz'", + "'fizz' / [foo] 40px", + "[bar] 'fizz' / [foo] 40px", + "'fizz' 100px / [foo] 40px", + "[bar] 'fizz' 100px / [foo] 40px", + "[bar] 'fizz' 100px [buzz] / [foo] 40px", + "[bar] 'fizz' 100px [buzz] \n [a] '.' 200px [b] / [foo] 40px", + ], + invalid_values: [ + "'fizz' / repeat(1, 100px)", + "'fizz' repeat(1, 100px) / 0px", + "[foo] [bar] 40px / 100px", + "[fizz] [buzz] 100px / 40px", + "[fizz] [buzz] 'foo' / 40px", + "'foo' / none" + ] + }; + if (isGridTemplateSubgridValueEnabled) { + gCSSProperties["grid-template"].other_values.push( + "subgrid", + "subgrid/40px 20px", + "subgrid [foo] [] [bar baz] / 40px 20px", + "40px 20px/subgrid", + "40px 20px/subgrid [foo] [] repeat(3, [a] [b]) [bar baz]", + "subgrid/subgrid", + "subgrid [foo] [] [bar baz]/subgrid [foo] [] [bar baz]" + ); + gCSSProperties["grid-template"].invalid_values.push( + "subgrid []", + "subgrid [] / 'fizz'", + "subgrid / 'fizz'" + ); + } + + gCSSProperties["grid"] = { + domProp: "grid", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "grid-template-areas", + "grid-template-rows", + "grid-template-columns", + "grid-auto-flow", + "grid-auto-rows", + "grid-auto-columns", + "grid-column-gap", + "grid-row-gap", + ], + initial_values: [ + "none", + "none / none", + ], + other_values: [ + "auto-flow 40px / none", + "auto-flow / 40px", + "auto-flow dense auto / auto", + "dense auto-flow minmax(min-content, 2fr) / auto", + "dense auto-flow / 100px", + "none / auto-flow 40px", + "40px / auto-flow", + "none / dense auto-flow auto", + ].concat( + gCSSProperties["grid-template"].other_values + ), + invalid_values: [ + "auto-flow", + " / auto-flow", + "dense 0 / 0", + "dense dense 40px / 0", + "auto-flow / auto-flow", + "auto-flow / dense", + "auto-flow [a] 0 / 0", + "0 / auto-flow [a] 0", + "auto-flow -20px / 0", + "auto-flow 200ms / 0", + "auto-flow 40px 100px / 0", + ].concat( + gCSSProperties["grid-template"].invalid_values, + gCSSProperties["grid-auto-flow"].other_values, + gCSSProperties["grid-auto-flow"].invalid_values + .filter((v) => v != 'none') + ) + }; + + var gridLineOtherValues = [ + "foo", + "2", + "2 foo", + "foo 2", + "-3", + "-3 bar", + "bar -3", + "span 2", + "2 span", + "span foo", + "foo span", + "span 2 foo", + "span foo 2", + "2 foo span", + "foo 2 span", + ]; + var gridLineInvalidValues = [ + "", + "4th", + "span", + "inherit 2", + "2 inherit", + "20px", + "2 3", + "2.5", + "2.0", + "0", + "0 foo", + "span 0", + "2 foo 3", + "foo 2 foo", + "2 span foo", + "foo span 2", + "span -3", + "span -3 bar", + "span 2 span", + "span foo span", + "span 2 foo span", + ]; + + gCSSProperties["grid-column-start"] = { + domProp: "gridColumnStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: gridLineOtherValues, + invalid_values: gridLineInvalidValues + }; + gCSSProperties["grid-column-end"] = { + domProp: "gridColumnEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: gridLineOtherValues, + invalid_values: gridLineInvalidValues + }; + gCSSProperties["grid-row-start"] = { + domProp: "gridRowStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: gridLineOtherValues, + invalid_values: gridLineInvalidValues + }; + gCSSProperties["grid-row-end"] = { + domProp: "gridRowEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: gridLineOtherValues, + invalid_values: gridLineInvalidValues + }; + + // The grid-column and grid-row shorthands take values of the form + // <grid-line> [ / <grid-line> ]? + var gridColumnRowOtherValues = [].concat(gridLineOtherValues); + gridLineOtherValues.concat([ "auto" ]).forEach(function(val) { + gridColumnRowOtherValues.push(" foo / " + val); + gridColumnRowOtherValues.push(val + "/2"); + }); + var gridColumnRowInvalidValues = [ + "foo, bar", + "foo / bar / baz", + ].concat(gridLineInvalidValues); + gridLineInvalidValues.forEach(function(val) { + gridColumnRowInvalidValues.push("span 3 / " + val); + gridColumnRowInvalidValues.push(val + " / foo"); + }); + gCSSProperties["grid-column"] = { + domProp: "gridColumn", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "grid-column-start", + "grid-column-end" + ], + initial_values: [ "auto", "auto / auto" ], + other_values: gridColumnRowOtherValues, + invalid_values: gridColumnRowInvalidValues + }; + gCSSProperties["grid-row"] = { + domProp: "gridRow", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "grid-row-start", + "grid-row-end" + ], + initial_values: [ "auto", "auto / auto" ], + other_values: gridColumnRowOtherValues, + invalid_values: gridColumnRowInvalidValues + }; + + var gridAreaOtherValues = gridLineOtherValues.slice(); + gridLineOtherValues.forEach(function(val) { + gridAreaOtherValues.push("foo / " + val); + gridAreaOtherValues.push(val + "/2/3"); + gridAreaOtherValues.push("foo / bar / " + val + " / baz"); + }); + var gridAreaInvalidValues = [ + "foo, bar", + "foo / bar / baz / fizz / buzz", + "default / foo / bar / baz", + "foo / initial / bar / baz", + "foo / bar / inherit / baz", + "foo / bar / baz / unset", + ].concat(gridLineInvalidValues); + gridLineInvalidValues.forEach(function(val) { + gridAreaInvalidValues.push("foo / " + val); + gridAreaInvalidValues.push("foo / bar / " + val); + gridAreaInvalidValues.push("foo / 4 / bar / " + val); + }); + + gCSSProperties["grid-area"] = { + domProp: "gridArea", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "grid-row-start", + "grid-column-start", + "grid-row-end", + "grid-column-end" + ], + initial_values: [ + "auto", + "auto / auto", + "auto / auto / auto", + "auto / auto / auto / auto" + ], + other_values: gridAreaOtherValues, + invalid_values: gridAreaInvalidValues + }; + + gCSSProperties["grid-column-gap"] = { + domProp: "gridColumnGap", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0" ], + other_values: [ "2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)", + "calc(1% + 1ch)" , "calc(1px - 99%)" ], + invalid_values: [ "-1px", "auto", "none", "1px 1px", "-1%", "fit-content(1px)" ], + }; + gCSSProperties["grid-row-gap"] = { + domProp: "gridRowGap", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0" ], + other_values: [ "2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)", + "calc(1% + 1ch)" , "calc(1px - 99%)" ], + invalid_values: [ "-1px", "auto", "none", "1px 1px", "-1%", "min-content" ], + }; + gCSSProperties["grid-gap"] = { + domProp: "gridGap", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "grid-column-gap", "grid-row-gap" ], + initial_values: [ "0", "0 0" ], + other_values: [ "1ch 0", "1px 1%", "1em 1px", "calc(1px) calc(1%)" ], + invalid_values: [ "-1px", "1px -1px", "1px 1px 1px", "inherit 1px", + "1px auto" ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.display-contents.enabled")) { + gCSSProperties["display"].other_values.push("contents"); +} + +if (IsCSSPropertyPrefEnabled("layout.css.contain.enabled")) { + gCSSProperties["contain"] = { + domProp: "contain", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + "strict", + "layout", + "style", + "layout style", + "style layout", + "paint", + "layout paint", + "paint layout", + "style paint", + "paint style", + "layout style paint", + "layout paint style", + "style paint layout", + "paint style layout", + ], + invalid_values: [ + "none strict", + "strict layout", + "strict layout style", + "layout strict", + "layout style strict", + "layout style paint strict", + "paint strict", + "style strict", + "paint paint", + "strict strict", + "auto", + "10px", + "0", + ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.image-orientation.enabled")) { + gCSSProperties["image-orientation"] = { + domProp: "imageOrientation", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ + "0deg", + "0grad", + "0rad", + "0turn", + + // Rounded initial values. + "-90deg", + "15deg", + "360deg", + ], + other_values: [ + "0deg flip", + "90deg", + "90deg flip", + "180deg", + "180deg flip", + "270deg", + "270deg flip", + "flip", + "from-image", + + // Grad units. + "0grad flip", + "100grad", + "100grad flip", + "200grad", + "200grad flip", + "300grad", + "300grad flip", + + // Radian units. + "0rad flip", + "1.57079633rad", + "1.57079633rad flip", + "3.14159265rad", + "3.14159265rad flip", + "4.71238898rad", + "4.71238898rad flip", + + // Turn units. + "0turn flip", + "0.25turn", + "0.25turn flip", + "0.5turn", + "0.5turn flip", + "0.75turn", + "0.75turn flip", + + // Rounded values. + "-45deg flip", + "65deg flip", + "400deg flip", + ], + invalid_values: [ + "none", + "0deg none", + "flip 0deg", + "flip 0deg", + "0", + "0 flip", + "flip 0", + "0deg from-image", + "from-image 0deg", + "flip from-image", + "from-image flip", + ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.initial-letter.enabled")) { + gCSSProperties["initial-letter"] = { + domProp: "initialLetter", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "2", "2.5", "3.7 2", "4 3" ], + invalid_values: [ "-3", "3.7 -2", "25%", "16px", "1 0", "0", "0 1" ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.osx-font-smoothing.enabled")) { + gCSSProperties["-moz-osx-font-smoothing"] = { + domProp: "MozOsxFontSmoothing", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "grayscale" ], + invalid_values: [ "none", "subpixel-antialiased", "antialiased" ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.mix-blend-mode.enabled")) { + gCSSProperties["mix-blend-mode"] = { + domProp: "mixBlendMode", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: ["multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", + "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"], + invalid_values: [] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.isolation.enabled")) { + gCSSProperties["isolation"] = { + domProp: "isolation", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: ["isolate"], + invalid_values: [] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.background-blend-mode.enabled")) { + gCSSProperties["background-blend-mode"] = { + domProp: "backgroundBlendMode", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "normal" ], + other_values: [ "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", + "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity" ], + invalid_values: ["none", "10px", "multiply multiply"] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.object-fit-and-position.enabled")) { + gCSSProperties["object-fit"] = { + domProp: "objectFit", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "fill" ], + other_values: [ "contain", "cover", "none", "scale-down" ], + invalid_values: [ "auto", "5px", "100%" ] + }; + gCSSProperties["object-position"] = { + domProp: "objectPosition", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "50% 50%", "50%", "center", "center center" ], + other_values: [ + "calc(20px)", + "calc(20px) 10px", + "10px calc(20px)", + "calc(20px) 25%", + "25% calc(20px)", + "calc(20px) calc(20px)", + "calc(20px + 1em) calc(20px / 2)", + "calc(20px + 50%) calc(50% - 10px)", + "calc(-20px) calc(-50%)", + "calc(-20%) calc(-50%)", + "0px 0px", + "right 20px top 60px", + "right 20px bottom 60px", + "left 20px top 60px", + "left 20px bottom 60px", + "right -50px top -50px", + "left -50px bottom -50px", + "right 20px top -50px", + "right -20px top 50px", + "right 3em bottom 10px", + "bottom 3em right 10px", + "top 3em right 10px", + "left 15px", + "10px top", + "left top 15px", + "left 10px top", + "left 20%", + "right 20%" + ], + invalid_values: [ "center 10px center 4px", "center 10px center", + "top 20%", "bottom 20%", "50% left", "top 50%", + "50% bottom 10%", "right 10% 50%", "left right", + "top bottom", "left 10% right", + "top 20px bottom 20px", "left left", "20 20" ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.overflow-clip-box.enabled")) { + gCSSProperties["overflow-clip-box"] = { + domProp: "overflowClipBox", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "padding-box" ], + other_values: [ "content-box" ], + invalid_values: [ "none", "auto", "border-box", "0" ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.box-decoration-break.enabled")) { + gCSSProperties["box-decoration-break"] = { + domProp: "boxDecorationBreak", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "slice" ], + other_values: [ "clone" ], + invalid_values: [ "auto", "none", "1px" ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.scroll-behavior.property-enabled")) { + gCSSProperties["scroll-behavior"] = { + domProp: "scrollBehavior", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto" ], + other_values: [ "smooth" ], + invalid_values: [ "none", "1px" ] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.scroll-snap.enabled")) { + gCSSProperties["scroll-snap-coordinate"] = { + domProp: "scrollSnapCoordinate", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "25% 25%", "top", "0px 100px, 10em 50%", + "top left, top right, bottom left, bottom right, center", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "calc(20%) calc(3*25px), center"], + invalid_values: [ "auto", "default" ] + } + gCSSProperties["scroll-snap-destination"] = { + domProp: "scrollSnapDestination", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0px 0px" ], + other_values: [ "25% 25%", "6px 5px", "20% 3em", "0in 1in", + "top", "right", "top left", "top right", "center", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(3*25px) 5px", + "5px calc(3*25px)", + "calc(20%) calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)"], + invalid_values: [ "auto", "none", "default" ] + } + gCSSProperties["scroll-snap-points-x"] = { + domProp: "scrollSnapPointsX", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "repeat(100%)", "repeat(120px)", "repeat(calc(3*25px))" ], + invalid_values: [ "auto", "1px", "left", "rgb(1,2,3)" ] + } + gCSSProperties["scroll-snap-points-y"] = { + domProp: "scrollSnapPointsY", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "repeat(100%)", "repeat(120px)", "repeat(calc(3*25px))" ], + invalid_values: [ "auto", "1px", "top", "rgb(1,2,3)" ] + } + gCSSProperties["scroll-snap-type"] = { + domProp: "scrollSnapType", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ "scroll-snap-type-x", "scroll-snap-type-y" ], + initial_values: [ "none" ], + other_values: [ "mandatory", "proximity" ], + invalid_values: [ "auto", "1px" ] + }; + gCSSProperties["scroll-snap-type-x"] = { + domProp: "scrollSnapTypeX", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: ["mandatory", "proximity"], + invalid_values: [ "auto", "1px" ] + }; + gCSSProperties["scroll-snap-type-y"] = { + domProp: "scrollSnapTypeY", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: ["mandatory", "proximity"], + invalid_values: [ "auto", "1px" ] + }; +} + +function SupportsMaskShorthand() { + return "maskImage" in document.documentElement.style; +} + +if (SupportsMaskShorthand()) { + gCSSProperties["mask"] = { + domProp: "mask", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + /* FIXME: All mask-border-* should be added when we implement them. */ + subproperties: ["mask-clip", "mask-image", "mask-mode", "mask-origin", "mask-position-x", "mask-position-y", "mask-repeat", "mask-size" , "mask-composite"], + initial_values: [ "match-source", "none", "repeat", "add", "0% 0%", "top left", "0% 0% / auto", "top left / auto", "left top / auto", "0% 0% / auto auto", + "top left none", "left top none", "none left top", "none top left", "none 0% 0%", "top left / auto none", "left top / auto none", + "top left / auto auto none", + "match-source none repeat add top left", "top left repeat none add", "none repeat add top left / auto", "top left / auto repeat none add match-source", "none repeat add 0% 0% / auto auto match-source", + "border-box", "border-box border-box" ], + other_values: [ + "none alpha repeat add left top", + "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", + "no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') alpha left top add", + "repeat-x", + "repeat-y", + "no-repeat", + "none repeat-y alpha add 0% 0%", + "subtract", + "0% top subtract alpha repeat none", + "top", + "left", + "50% 50%", + "center", + "top / 100px", + "left / contain", + "left / cover", + "10px / 10%", + "10em / calc(20px)", + "top left / 100px 100px", + "top left / 100px auto", + "top left / 100px 10%", + "top left / 100px calc(20px)", + "bottom right add none alpha repeat", + "50% alpha", + "alpha 50%", + "50%", + "url(#mymask)", + "-moz-radial-gradient(10% bottom, #ffffff, black) add no-repeat", + "-moz-linear-gradient(10px 10px -45deg, red, blue) repeat", + "-moz-linear-gradient(10px 10px -0.125turn, red, blue) repeat", + "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) add no-repeat", + "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) repeat", + "-moz-element(#test) alpha", + /* multiple mask-image */ + "url(404.png), url(404.png)", + "repeat-x, subtract, none", + "0% top url(404.png), url(404.png) 50% top", + "subtract repeat-y top left url(404.png), repeat-x alpha", + "url(404.png), -moz-linear-gradient(20px 20px -45deg, blue, green), -moz-element(#a) alpha", + "top left / contain, bottom right / cover", + /* test cases with clip+origin in the shorthand */ + "url(404.png) alpha padding-box", + "url(404.png) border-box alpha", + "content-box url(404.png)", + "url(404.png) alpha padding-box padding-box", + "url(404.png) alpha padding-box border-box", + "content-box border-box url(404.png)", + ], + invalid_values: [ + /* mixes with keywords have to be in correct order */ + "50% left", "top 50%", + /* no quirks mode colors */ + "-moz-radial-gradient(10% bottom, ffffff, black) add no-repeat", + /* no quirks mode lengths */ + "-moz-linear-gradient(10 10px -45deg, red, blue) repeat", + "-moz-linear-gradient(10px 10 -45deg, red, blue) repeat", + "linear-gradient(red -99, yellow, green, blue 120%)", + /* bug 258080: don't accept background-position separated */ + "left url(404.png) top", "top url(404.png) left", + "alpha padding-box url(404.png) border-box", + "alpha padding-box url(404.png) padding-box", + "-moz-element(#a rubbish)", + "left top / match-source" + ] + }; + gCSSProperties["mask-clip"] = { + domProp: "maskClip", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "border-box" ], + other_values: [ "content-box", "padding-box", "border-box, padding-box", "padding-box, padding-box, padding-box", "border-box, border-box" ], + invalid_values: [ "margin-box", "content-box content-box" ] + }; + gCSSProperties["mask-image"] = { + domProp: "maskImage", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ + "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')", 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', + "none, none", + "none, none, none, none, none", + "url(#mymask)", + "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none", + "none, url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), none", + "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==), url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", + ].concat(validGradientAndElementValues), + invalid_values: [ + ].concat(invalidGradientAndElementValues), + unbalanced_values: [ + ].concat(unbalancedGradientAndElementValues) + }; + gCSSProperties["mask-mode"] = { + domProp: "maskMode", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "match-source" ], + other_values: [ "alpha", "luminance", "match-source, match-source", "match-source, alpha", "alpha, luminance, match-source"], + invalid_values: [ "match-source match-source", "alpha match-source" ] + }; + gCSSProperties["mask-composite"] = { + domProp: "maskComposite", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "add" ], + other_values: [ "subtract", "intersect", "exclude", "add, add", "subtract, intersect", "subtract, subtract, add"], + invalid_values: [ "add subtract", "intersect exclude" ] + }; + gCSSProperties["mask-origin"] = { + domProp: "maskOrigin", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "border-box" ], + other_values: [ "padding-box", "content-box", "border-box, padding-box", "padding-box, padding-box, padding-box", "border-box, border-box" ], + invalid_values: [ "margin-box", "padding-box padding-box" ] + }; + gCSSProperties["mask-position"] = { + domProp: "maskPosition", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + initial_values: [ "top 0% left 0%", "top 0% left", "top left", "left top", "0% 0%", "0% top", "left 0%" ], + other_values: [ "top", "left", "right", "bottom", "center", "center bottom", "bottom center", "center right", "right center", "center top", "top center", "center left", "left center", "right bottom", "bottom right", "50%", "top left, top left", "top left, top right", "top right, top left", "left top, 0% 0%", "10% 20%, 30%, 40%", "top left, bottom right", "right bottom, left top", "0%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top", + "calc(20px)", + "calc(20px) 10px", + "10px calc(20px)", + "calc(20px) 25%", + "25% calc(20px)", + "calc(20px) calc(20px)", + "calc(20px + 1em) calc(20px / 2)", + "calc(20px + 50%) calc(50% - 10px)", + "calc(-20px) calc(-50%)", + "calc(-20%) calc(-50%)", + "0px 0px", + "right 20px top 60px", + "right 20px bottom 60px", + "left 20px top 60px", + "left 20px bottom 60px", + "right -50px top -50px", + "left -50px bottom -50px", + "right 20px top -50px", + "right -20px top 50px", + "right 3em bottom 10px", + "bottom 3em right 10px", + "top 3em right 10px", + "left 15px", + "10px top", + "left top 15px", + "left 10px top", + "left 20%", + "right 20%" + ], + subproperties: [ "mask-position-x", "mask-position-y" ], + invalid_values: [ "center 10px center 4px", "center 10px center", + "top 20%", "bottom 20%", "50% left", "top 50%", + "50% bottom 10%", "right 10% 50%", "left right", + "top bottom", "left 10% right", + "top 20px bottom 20px", "left left", + "0px calc(0px + rubbish)"], + }; + gCSSProperties["mask-position-x"] = { + domProp: "maskPositionX", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "left", "0%" ], + other_values: [ "right", "center", "50%", "center, center", "center, right", "right, center", "center, 50%", "10%, 20%, 40%", "1px", "30px", "50%, 10%, 20%, 30%", "center, center, center, center, center", + "calc(20px)", + "calc(20px + 1em)", + "calc(20px / 2)", + "calc(20px + 50%)", + "calc(50% - 10px)", + "calc(-20px)", + "calc(-50%)", + "calc(-20%)", + "right 20px", + "left 20px", + "right -50px", + "left -50px", + "right 20px", + "right 3em", + ], + invalid_values: [ "center 10px", "right 10% 50%", "left right", "left left", + "bottom 20px", "top 10%", "bottom 3em", + "top", "bottom", "top, top", "top, bottom", "bottom, top", "top, 0%", "top, top, top, top, top", + "calc(0px + rubbish)", "center 0%"], + }; + gCSSProperties["mask-position-y"] = { + domProp: "maskPositionY", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "top", "0%" ], + other_values: [ "bottom", "center", "50%", "center, center", "center, bottom", "bottom, center", "center, 0%", "10%, 20%, 40%", "1px", "30px", "50%, 10%, 20%, 30%", "center, center, center, center, center", + "calc(20px)", + "calc(20px + 1em)", + "calc(20px / 2)", + "calc(20px + 50%)", + "calc(50% - 10px)", + "calc(-20px)", + "calc(-50%)", + "calc(-20%)", + "bottom 20px", + "top 20px", + "bottom -50px", + "top -50px", + "bottom 20px", + "bottom 3em", + ], + invalid_values: [ "center 10px", "bottom 10% 50%", "top bottom", "top top", + "right 20px", "left 10%", "right 3em", + "left", "right", "left, left", "left, right", "right, left", "left, 0%", "left, left, left, left, left", + "calc(0px + rubbish)", "center 0%"], + }; + gCSSProperties["mask-repeat"] = { + domProp: "maskRepeat", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "repeat", "repeat repeat" ], + other_values: [ "repeat-x", "repeat-y", "no-repeat", + "repeat-x, repeat-x", + "repeat, no-repeat", + "repeat-y, no-repeat, repeat-y", + "repeat, repeat, repeat", + "repeat no-repeat", + "no-repeat repeat", + "no-repeat no-repeat", + "repeat no-repeat", + "no-repeat no-repeat, no-repeat no-repeat", + ], + invalid_values: [ "repeat repeat repeat", + "repeat-x repeat-y", + "repeat repeat-x", + "repeat repeat-y", + "repeat-x repeat", + "repeat-y repeat" ] + }; + gCSSProperties["mask-size"] = { + domProp: "maskSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "auto", "auto auto" ], + other_values: [ "contain", "cover", "100px auto", "auto 100px", "100% auto", "auto 100%", "25% 50px", "3em 40%", + "calc(20px)", + "calc(20px) 10px", + "10px calc(20px)", + "calc(20px) 25%", + "25% calc(20px)", + "calc(20px) calc(20px)", + "calc(20px + 1em) calc(20px / 2)", + "calc(20px + 50%) calc(50% - 10px)", + "calc(-20px) calc(-50%)", + "calc(-20%) calc(-50%)" + ], + invalid_values: [ "contain contain", "cover cover", "cover auto", "auto cover", "contain cover", "cover contain", "-5px 3px", "3px -5px", "auto -5px", "-5px auto", "5 3", "10px calc(10px + rubbish)" ] + }; +} else { + gCSSProperties["mask"] = { + domProp: "mask", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "none" ], + other_values: [ "url(#mymask)" ], + invalid_values: [] + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.prefixes.webkit")) { + gCSSProperties["-webkit-animation"] = { + domProp: "webkitAnimation", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "animation", + subproperties: [ "animation-name", "animation-duration", "animation-timing-function", "animation-delay", "animation-direction", "animation-fill-mode", "animation-iteration-count", "animation-play-state" ], + }; + gCSSProperties["-webkit-animation-delay"] = { + domProp: "webkitAnimationDelay", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-delay", + subproperties: [ "animation-delay" ], + }; + gCSSProperties["-webkit-animation-direction"] = { + domProp: "webkitAnimationDirection", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-direction", + subproperties: [ "animation-direction" ], + }; + gCSSProperties["-webkit-animation-duration"] = { + domProp: "webkitAnimationDuration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-duration", + subproperties: [ "animation-duration" ], + }; + gCSSProperties["-webkit-animation-fill-mode"] = { + domProp: "webkitAnimationFillMode", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-fill-mode", + subproperties: [ "animation-fill-mode" ], + }; + gCSSProperties["-webkit-animation-iteration-count"] = { + domProp: "webkitAnimationIterationCount", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-iteration-count", + subproperties: [ "animation-iteration-count" ], + }; + gCSSProperties["-webkit-animation-name"] = { + domProp: "webkitAnimationName", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-name", + subproperties: [ "animation-name" ], + }; + gCSSProperties["-webkit-animation-play-state"] = { + domProp: "webkitAnimationPlayState", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-play-state", + subproperties: [ "animation-play-state" ], + }; + gCSSProperties["-webkit-animation-timing-function"] = { + domProp: "webkitAnimationTimingFunction", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "animation-timing-function", + subproperties: [ "animation-timing-function" ], + }; + gCSSProperties["-webkit-filter"] = { + domProp: "webkitFilter", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "filter", + subproperties: [ "filter" ], + }; + gCSSProperties["-webkit-text-fill-color"] = { + domProp: "webkitTextFillColor", + inherited: true, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor", "black", "#000", "#000000", "rgb(0,0,0)" ], + other_values: [ "red", "rgba(255,255,255,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000", "ff00ff", "rgb(255,xxx,255)" ] + }; + gCSSProperties["-webkit-text-stroke"] = { + domProp: "webkitTextStroke", + inherited: true, + type: CSS_TYPE_TRUE_SHORTHAND, + prerequisites: { "color": "black" }, + subproperties: [ "-webkit-text-stroke-width", "-webkit-text-stroke-color" ], + initial_values: [ "0 currentColor", "currentColor 0px", "0", "currentColor", "0px black" ], + other_values: [ "thin black", "#f00 medium", "thick rgba(0,0,255,0.5)", "calc(4px - 8px) green", "2px", "green 0", "currentColor 4em", "currentColor calc(5px - 1px)" ], + invalid_values: [ "-3px black", "calc(50%+ 2px) #000", "30% #f00" ] + }; + gCSSProperties["-webkit-text-stroke-color"] = { + domProp: "webkitTextStrokeColor", + inherited: true, + type: CSS_TYPE_LONGHAND, + prerequisites: { "color": "black" }, + initial_values: [ "currentColor", "black", "#000", "#000000", "rgb(0,0,0)" ], + other_values: [ "red", "rgba(255,255,255,0.5)", "transparent" ], + invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000", "ff00ff", "rgb(255,xxx,255)" ] + }; + gCSSProperties["-webkit-text-stroke-width"] = { + domProp: "webkitTextStrokeWidth", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: [ "0", "0px", "0em", "0ex", "calc(0pt)", "calc(4px - 8px)" ], + other_values: [ "thin", "medium", "thick", "17px", "0.2em", "calc(3*25px + 5em)", "calc(5px - 1px)" ], + invalid_values: [ "5%", "1px calc(nonsense)", "1px red", "-0.1px", "-3px", "30%" ] + }, + gCSSProperties["-webkit-text-size-adjust"] = { + domProp: "webkitTextSizeAdjust", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-text-size-adjust", + subproperties: [ "-moz-text-size-adjust" ], + }; + gCSSProperties["-webkit-transform"] = { + domProp: "webkitTransform", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform", + subproperties: [ "transform" ], + }; + gCSSProperties["-webkit-transform-origin"] = { + domProp: "webkitTransformOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform-origin", + subproperties: [ "transform-origin" ], + }; + gCSSProperties["-webkit-transform-style"] = { + domProp: "webkitTransformStyle", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform-style", + subproperties: [ "transform-style" ], + }; + gCSSProperties["-webkit-backface-visibility"] = { + domProp: "webkitBackfaceVisibility", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "backface-visibility", + subproperties: [ "backface-visibility" ], + }; + gCSSProperties["-webkit-perspective"] = { + domProp: "webkitPerspective", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "perspective", + subproperties: [ "perspective" ], + }; + gCSSProperties["-webkit-perspective-origin"] = { + domProp: "webkitPerspectiveOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "perspective-origin", + subproperties: [ "perspective-origin" ], + }; + gCSSProperties["-webkit-transition"] = { + domProp: "webkitTransition", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "transition", + subproperties: [ "transition-property", "transition-duration", "transition-timing-function", "transition-delay" ], + }; + gCSSProperties["-webkit-transition-delay"] = { + domProp: "webkitTransitionDelay", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transition-delay", + subproperties: [ "transition-delay" ], + }; + gCSSProperties["-webkit-transition-duration"] = { + domProp: "webkitTransitionDuration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transition-duration", + subproperties: [ "transition-duration" ], + }; + gCSSProperties["-webkit-transition-property"] = { + domProp: "webkitTransitionProperty", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transition-property", + subproperties: [ "transition-property" ], + }; + gCSSProperties["-webkit-transition-timing-function"] = { + domProp: "webkitTransitionTimingFunction", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transition-timing-function", + subproperties: [ "transition-timing-function" ], + }; + gCSSProperties["-webkit-border-radius"] = { + domProp: "webkitBorderRadius", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "border-radius", + subproperties: [ "border-bottom-left-radius", "border-bottom-right-radius", "border-top-left-radius", "border-top-right-radius" ], + }; + gCSSProperties["-webkit-border-top-left-radius"] = { + domProp: "webkitBorderTopLeftRadius", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-top-left-radius", + subproperties: [ "border-top-left-radius" ], + }; + gCSSProperties["-webkit-border-top-right-radius"] = { + domProp: "webkitBorderTopRightRadius", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-top-right-radius", + subproperties: [ "border-top-right-radius" ], + }; + gCSSProperties["-webkit-border-bottom-left-radius"] = { + domProp: "webkitBorderBottomLeftRadius", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-bottom-left-radius", + subproperties: [ "border-bottom-left-radius" ], + }; + gCSSProperties["-webkit-border-bottom-right-radius"] = { + domProp: "webkitBorderBottomRightRadius", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "border-bottom-right-radius", + subproperties: [ "border-bottom-right-radius" ], + }; + gCSSProperties["-webkit-background-clip"] = { + domProp: "webkitBackgroundClip", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "background-clip", + subproperties: [ "background-clip" ], + }; + gCSSProperties["-webkit-background-origin"] = { + domProp: "webkitBackgroundOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "background-origin", + subproperties: [ "background-origin" ], + }; + gCSSProperties["-webkit-background-size"] = { + domProp: "webkitBackgroundSize", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "background-size", + subproperties: [ "background-size" ], + }; + gCSSProperties["-webkit-border-image"] = { + domProp: "webkitBorderImage", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "border-image", + subproperties: [ "border-image-source", "border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat" ], + }; + gCSSProperties["-webkit-box-shadow"] = { + domProp: "webkitBoxShadow", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "box-shadow", + subproperties: [ "box-shadow" ], + }; + gCSSProperties["-webkit-box-sizing"] = { + domProp: "webkitBoxSizing", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "box-sizing", + subproperties: [ "box-sizing" ], + }; + gCSSProperties["-webkit-box-flex"] = { + domProp: "webkitBoxFlex", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-flex", + subproperties: [ "-moz-box-flex" ], + }; + gCSSProperties["-webkit-box-ordinal-group"] = { + domProp: "webkitBoxOrdinalGroup", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-ordinal-group", + subproperties: [ "-moz-box-ordinal-group" ], + }; + gCSSProperties["-webkit-box-orient"] = { + domProp: "webkitBoxOrient", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-orient", + subproperties: [ "-moz-box-orient" ], + }; + gCSSProperties["-webkit-box-direction"] = { + domProp: "webkitBoxDirection", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-direction", + subproperties: [ "-moz-box-direction" ], + }; + gCSSProperties["-webkit-box-align"] = { + domProp: "webkitBoxAlign", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-align", + subproperties: [ "-moz-box-align" ], + }; + gCSSProperties["-webkit-box-pack"] = { + domProp: "webkitBoxPack", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-pack", + subproperties: [ "-moz-box-pack" ], + }; + gCSSProperties["-webkit-flex-direction"] = { + domProp: "webkitFlexDirection", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-direction", + subproperties: [ "flex-direction" ], + }; + gCSSProperties["-webkit-flex-wrap"] = { + domProp: "webkitFlexWrap", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-wrap", + subproperties: [ "flex-wrap" ], + }; + gCSSProperties["-webkit-flex-flow"] = { + domProp: "webkitFlexFlow", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "flex-flow", + subproperties: [ "flex-direction", "flex-wrap" ], + }; + gCSSProperties["-webkit-order"] = { + domProp: "webkitOrder", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "order", + subproperties: [ "order" ], + }; + gCSSProperties["-webkit-flex"] = { + domProp: "webkitFlex", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "flex", + subproperties: [ "flex-grow", "flex-shrink", "flex-basis" ], + }; + gCSSProperties["-webkit-flex-grow"] = { + domProp: "webkitFlexGrow", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-grow", + subproperties: [ "flex-grow" ], + }; + gCSSProperties["-webkit-flex-shrink"] = { + domProp: "webkitFlexShrink", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-shrink", + subproperties: [ "flex-shrink" ], + }; + gCSSProperties["-webkit-flex-basis"] = { + domProp: "webkitFlexBasis", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-basis", + subproperties: [ "flex-basis" ], + }; + gCSSProperties["-webkit-justify-content"] = { + domProp: "webkitJustifyContent", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "justify-content", + subproperties: [ "justify-content" ], + }; + gCSSProperties["-webkit-align-items"] = { + domProp: "webkitAlignItems", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "align-items", + subproperties: [ "align-items" ], + }; + gCSSProperties["-webkit-align-self"] = { + domProp: "webkitAlignSelf", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "align-self", + subproperties: [ "align-self" ], + }; + gCSSProperties["-webkit-align-content"] = { + domProp: "webkitAlignContent", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "align-content", + subproperties: [ "align-content" ], + }; + gCSSProperties["-webkit-user-select"] = { + domProp: "webkitUserSelect", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-user-select", + subproperties: [ "-moz-user-select" ], + }; + + if (SupportsMaskShorthand()) { + gCSSProperties["-webkit-mask"] = { + domProp: "webkitMask", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "mask", + subproperties: [ "mask-clip", "mask-image", "mask-mode", "mask-origin", "mask-position", "mask-repeat", "mask-size" , "mask-composite" ], + }; + gCSSProperties["-webkit-mask-clip"] = { + domProp: "webkitMaskClip", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-clip", + subproperties: [ "mask-clip" ], + }; + + gCSSProperties["-webkit-mask-composite"] = { + domProp: "webkitMaskComposite", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-composite", + subproperties: [ "mask-composite" ], + }; + + gCSSProperties["-webkit-mask-image"] = { + domProp: "webkitMaskImage", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-image", + subproperties: [ "mask-image" ], + }; + gCSSProperties["-webkit-mask-origin"] = { + domProp: "webkitMaskOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-origin", + subproperties: [ "mask-origin" ], + }; + gCSSProperties["-webkit-mask-position"] = { + domProp: "webkitMaskPosition", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-position", + subproperties: [ "mask-position" ], + }; + gCSSProperties["-webkit-mask-position-x"] = { + domProp: "webkitMaskPositionX", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-position-x", + subproperties: [ "mask-position-x" ], + }; + gCSSProperties["-webkit-mask-position-y"] = { + domProp: "webkitMaskPositionY", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-position-y", + subproperties: [ "mask-position-y" ], + }; + gCSSProperties["-webkit-mask-repeat"] = { + domProp: "webkitMaskRepeat", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-repeat", + subproperties: [ "mask-repeat" ], + }; + gCSSProperties["-webkit-mask-size"] = { + domProp: "webkitMaskSize", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-size", + subproperties: [ "mask-size" ], + }; + } +} + +if (IsCSSPropertyPrefEnabled("layout.css.unset-value.enabled")) { + gCSSProperties["animation"].invalid_values.push("2s unset"); + gCSSProperties["animation-direction"].invalid_values.push("normal, unset", "unset, normal"); + gCSSProperties["animation-name"].invalid_values.push("bounce, unset", "unset, bounce"); + gCSSProperties["-moz-border-bottom-colors"].invalid_values.push("red unset", "unset red"); + gCSSProperties["-moz-border-left-colors"].invalid_values.push("red unset", "unset red"); + gCSSProperties["border-radius"].invalid_values.push("unset 2px", "unset / 2px", "2px unset", "2px / unset"); + gCSSProperties["border-bottom-left-radius"].invalid_values.push("unset 2px", "2px unset"); + gCSSProperties["border-bottom-right-radius"].invalid_values.push("unset 2px", "2px unset"); + gCSSProperties["border-top-left-radius"].invalid_values.push("unset 2px", "2px unset"); + gCSSProperties["border-top-right-radius"].invalid_values.push("unset 2px", "2px unset"); + gCSSProperties["-moz-border-right-colors"].invalid_values.push("red unset", "unset red"); + gCSSProperties["-moz-border-top-colors"].invalid_values.push("red unset", "unset red"); + gCSSProperties["-moz-outline-radius"].invalid_values.push("unset 2px", "unset / 2px", "2px unset", "2px / unset"); + gCSSProperties["-moz-outline-radius-bottomleft"].invalid_values.push("unset 2px", "2px unset"); + gCSSProperties["-moz-outline-radius-bottomright"].invalid_values.push("unset 2px", "2px unset"); + gCSSProperties["-moz-outline-radius-topleft"].invalid_values.push("unset 2px", "2px unset"); + gCSSProperties["-moz-outline-radius-topright"].invalid_values.push("unset 2px", "2px unset"); + gCSSProperties["background-image"].invalid_values.push("-moz-linear-gradient(unset, 10px 10px, from(blue))", "-moz-linear-gradient(unset, 10px 10px, blue 0)", "-moz-repeating-linear-gradient(unset, 10px 10px, blue 0)"); + gCSSProperties["box-shadow"].invalid_values.push("unset, 2px 2px", "2px 2px, unset", "inset unset"); + gCSSProperties["text-overflow"].invalid_values.push('"hello" unset', 'unset "hello"', 'clip unset', 'unset clip', 'unset inherit', 'unset none', 'initial unset'); + gCSSProperties["text-shadow"].invalid_values.push("unset, 2px 2px", "2px 2px, unset"); + gCSSProperties["transition"].invalid_values.push("2s unset"); + gCSSProperties["transition-property"].invalid_values.push("unset, color", "color, unset"); + if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) { + gCSSProperties["filter"].invalid_values.push("drop-shadow(unset, 2px 2px)", "drop-shadow(2px 2px, unset)"); + } + if (IsCSSPropertyPrefEnabled("layout.css.text-align-unsafe-value.enabled")) { + gCSSProperties["text-align"].other_values.push("true left"); + } else { + gCSSProperties["text-align"].invalid_values.push("true left"); + } +} + +if (IsCSSPropertyPrefEnabled("layout.css.float-logical-values.enabled")) { + gCSSProperties["float"].other_values.push("inline-start"); + gCSSProperties["float"].other_values.push("inline-end"); + gCSSProperties["clear"].other_values.push("inline-start"); + gCSSProperties["clear"].other_values.push("inline-end"); +} else { + gCSSProperties["float"].invalid_values.push("inline-start"); + gCSSProperties["float"].invalid_values.push("inline-end"); + gCSSProperties["clear"].invalid_values.push("inline-start"); + gCSSProperties["clear"].invalid_values.push("inline-end"); +} + +if (IsCSSPropertyPrefEnabled("layout.css.background-clip-text.enabled")) { + gCSSProperties["background-clip"].other_values.push( + "text", + "content-box, text", + "text, border-box", + "text, text" + ); + gCSSProperties["background"].other_values.push( + "url(404.png) green padding-box text", + "content-box text url(404.png) blue" + ); +} else { + gCSSProperties["background-clip"].invalid_values.push( + "text", + "content-box, text", + "text, border-box", + "text, text" + ); + gCSSProperties["background"].invalid_values.push( + "url(404.png) green padding-box text", + "content-box text url(404.png) blue" + ); +} + +// Copy aliased properties' fields from their alias targets. +for (var prop in gCSSProperties) { + var entry = gCSSProperties[prop]; + if (entry.alias_for) { + var aliasTargetEntry = gCSSProperties[entry.alias_for]; + if (!aliasTargetEntry) { + ok(false, + "Alias '" + prop + "' alias_for field, '" + entry.alias_for + "', " + + "must be set to a recognized CSS property in gCSSProperties"); + } else { + // Copy 'values' fields & 'prerequisites' field from aliasTargetEntry: + var fieldsToCopy = + ["initial_values", "other_values", "invalid_values", + "quirks_values", "unbalanced_values", + "prerequisites"]; + + fieldsToCopy.forEach(function(fieldName) { + // (Don't copy the field if the alias already has something there, + // or if the aliased property doesn't have anything to copy.) + if (!(fieldName in entry) && + fieldName in aliasTargetEntry) { + entry[fieldName] = aliasTargetEntry[fieldName] + } + }); + } + } +} + +if (false) { + // TODO These properties are chrome-only, and are not exposed via CSSOM. + // We may still want to find a way to test them. See bug 1206999. + gCSSProperties["-moz-window-shadow"] = { + //domProp: "MozWindowShadow", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ "default" ], + other_values: [ "none", "menu", "tooltip", "sheet" ], + invalid_values: [] + }; +} diff --git a/layout/style/test/redirect.sjs b/layout/style/test/redirect.sjs new file mode 100644 index 000000000..b6249cadf --- /dev/null +++ b/layout/style/test/redirect.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) +{ + response.setStatusLine(request.httpVersion, 301, "Moved Permanently"); + response.setHeader("Location", request.queryString, false); +} diff --git a/layout/style/test/redundant_font_download.sjs b/layout/style/test/redundant_font_download.sjs new file mode 100644 index 000000000..7eb7d8f8d --- /dev/null +++ b/layout/style/test/redundant_font_download.sjs @@ -0,0 +1,60 @@ +const BinaryOutputStream = + Components.Constructor("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream"); + +// this is simply a hex dump of a red square .PNG image +const RED_SQUARE = + [ + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, + 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x20, 0x08, 0x02, 0x00, 0x00, 0x00, 0xFC, + 0x18, 0xED, 0xA3, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, + 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x28, + 0x49, 0x44, 0x41, 0x54, 0x48, 0xC7, 0xED, 0xCD, 0x41, 0x0D, + 0x00, 0x00, 0x08, 0x04, 0xA0, 0xD3, 0xFE, 0x9D, 0x35, 0x85, + 0x0F, 0x37, 0x28, 0x40, 0x4D, 0x6E, 0x75, 0x04, 0x02, 0x81, + 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0xC1, 0x93, 0x60, 0x01, + 0xA3, 0xC4, 0x01, 0x3F, 0x58, 0x1D, 0xEF, 0x27, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 + ]; + +function handleRequest(request, response) +{ + var query = {}; + request.queryString.split('&').forEach(function (val) { + var [name, value] = val.split('='); + query[name] = unescape(value); + }); + + response.setHeader("Cache-Control", "no-cache"); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/plain", false); + + var log = getState("bug-879963-request-log") || ""; + + var stream = new BinaryOutputStream(response.bodyOutputStream); + + if (query["q"] == "init") { + log = "init"; // initialize the log, and return a PNG image + response.setHeader("Content-Type", "image/png", false); + stream.writeByteArray(RED_SQUARE, RED_SQUARE.length); + } else if (query["q"] == "image") { + log = log + ";" + query["q"]; + response.setHeader("Content-Type", "image/png", false); + stream.writeByteArray(RED_SQUARE, RED_SQUARE.length); + } else if (query["q"] == "font") { + log = log + ";" + query["q"]; + // we don't provide a real font; that's ok, OTS will just reject it + response.write("Junk"); + } else if (query["q"] == "report") { + // don't include the actual "report" request in the log we return + response.write(log); + } else { + log = log + ";" + query["q"]; + response.setStatusLine(request.httpVersion, 404, "Not Found"); + } + + setState("bug-879963-request-log", log); +} diff --git a/layout/style/test/style_attribute_tests.js b/layout/style/test/style_attribute_tests.js new file mode 100644 index 000000000..be24baf95 --- /dev/null +++ b/layout/style/test/style_attribute_tests.js @@ -0,0 +1,27 @@ + +SimpleTest.waitForExplicitFinish(); + +window.addEventListener("load", runTests, false); + +function runTests(event) +{ + if (event.target != document) { + return; + } + + var elt = document.getElementById("content"); + + elt.setAttribute("style", "color: blue; background-color: fuchsia"); + is(elt.style.color, "blue", + "setting correct style attribute (color)"); + is(elt.style.backgroundColor, "fuchsia", + "setting correct style attribute (color)"); + + elt.setAttribute("style", "{color: blue; background-color: fuchsia}"); + is(elt.style.color, "", + "setting braced style attribute (color)"); + is(elt.style.backgroundColor, "", + "setting braced style attribute (color)"); + + SimpleTest.finish(); +} diff --git a/layout/style/test/support/external-variable-url.css b/layout/style/test/support/external-variable-url.css new file mode 100644 index 000000000..b13150428 --- /dev/null +++ b/layout/style/test/support/external-variable-url.css @@ -0,0 +1,3 @@ +div { + --a: url('image.png'); +} diff --git a/layout/style/test/test_acid3_test46.html b/layout/style/test/test_acid3_test46.html new file mode 100644 index 000000000..89850f0c5 --- /dev/null +++ b/layout/style/test/test_acid3_test46.html @@ -0,0 +1,141 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=156716 +--> +<!-- + +This is test 46 from the Acid3 test, http://acid3.acidtests.org/ +extracted from the test framework there and put into Mochitest. + +(from irc.mozilla.org, developers) +[2008-05-14 18:07:38] <Hixie> dbaron: I hereby grant all files available from the server http://acid3.acidtests.org/ under the following license: (c) copyright 2008 Ian Hickson. These documents may be used under the terms of any of the following licenses: MPL. GPL. LGPL. BSD. + +--> +<head> + <title>Test for Bug 156716</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <style type="text/css"> + iframe#selectors { width: 0; height: 0; } + </style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=156716">Mozilla Bug 156716</a> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 156716 **/ +SimpleTest.waitForExplicitFinish(); +function runTest() { + + function getTestDocument() { + var iframe = document.getElementById("selectors"); + var doc = iframe.contentDocument; + for (var i = doc.documentElement.childNodes.length-1; i >= 0; i -= 1) + doc.documentElement.removeChild(doc.documentElement.childNodes[i]); + doc.documentElement.appendChild(doc.createElement('head')); + doc.documentElement.firstChild.appendChild(doc.createElement('title')); + doc.documentElement.appendChild(doc.createElement('body')); + return doc; + } + + // test 46: media queries + var doc = getTestDocument(); + var style = doc.createElement('style'); + style.setAttribute('type', 'text/css'); + style.appendChild(doc.createTextNode('@media all and (min-color: 0) { #a { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media not all and (min-color: 0) { #b { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media only all and (min-color: 0) { #c { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media (bogus) { #d { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media all and (bogus) { #e { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media not all and (bogus) { #f { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media only all and (bogus) { #g { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media (bogus), all { #h { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media all and (bogus), all { #i { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media not all and (bogus), all { #j { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media only all and (bogus), all { #k { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media all, (bogus) { #l { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media all, all and (bogus) { #m { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media all, not all and (bogus) { #n { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media all, only all and (bogus) { #o { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media all and color { #p { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media all and min-color: 0 { #q { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media all, all and color { #r { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media all, all and min-color: 0 { #s { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media all and min-color: 0, all { #t { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media (max-color: 0) and (max-monochrome: 0) { #u { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media (min-color: 1), (min-monochrome: 1) { #v { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media all and (min-color: 0) and (min-monochrome: 0) { #w { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media not all and (min-color: 1), not all and (min-monochrome: 1) { #x { text-transform: uppercase; } }')); // matches + style.appendChild(doc.createTextNode('@media all and (min-height: 1em) and (min-width: 1em) { #y1 { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media all and (max-height: 1em) and (min-width: 1em) { #y2 { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media all and (min-height: 1em) and (max-width: 1em) { #y3 { text-transform: uppercase; } }')); + style.appendChild(doc.createTextNode('@media all and (max-height: 1em) and (max-width: 1em) { #y4 { text-transform: uppercase; } }')); // matches + doc.getElementsByTagName('head')[0].appendChild(style); + var names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y1', 'y2', 'y3', 'y4']; + for (var i in names) { + var p = doc.createElement('p'); + p.id = names[i]; + doc.body.appendChild(p); + } + var count = 0; + var check = function (c, e) { + count += 1; + var p = doc.getElementById(c); + is(doc.defaultView.getComputedStyle(p, '').textTransform, e ? 'uppercase' : 'none', "case " + c + " failed (index " + count + ")"); + } + check('a', true); // 1 + check('b', false); + check('c', true); + check('d', false); + check('e', false); + check('f', false); // true in old spec; commented out in real Acid3 + check('g', false); + check('h', true); + check('i', true); + check('j', true); // 10 + check('k', true); + check('l', true); + check('m', true); + check('n', true); + check('o', true); + check('p', false); + check('q', false); + check('r', true); // false in old spec + check('s', true); // false in old spec + check('t', true); // 20 - false in old spec + check('u', false); + check('v', true); + check('w', true); + check('x', true); + // here the viewport is 0x0 + check('y1', false); // 25 + check('y2', false); + check('y3', false); + check('y4', true); + document.getElementById("selectors").setAttribute("style", "height: 100px; width: 100px"); + // now the viewport is more than 1em by 1em + check('y1', true); // 29 + check('y2', false); + check('y3', false); + check('y4', false); + document.getElementById("selectors").removeAttribute("style"); + // here the viewport is 0x0 again + check('y1', false); // 33 + check('y2', false); + check('y3', false); + check('y4', true); + SimpleTest.finish(); +} +</script> +</pre> +<p id="display"> + <iframe src="empty.html" id="selectors" onload="runTest()"></iframe> +</p> +</body> +</html> + diff --git a/layout/style/test/test_addSheet.html b/layout/style/test/test_addSheet.html new file mode 100644 index 000000000..e112378a9 --- /dev/null +++ b/layout/style/test/test_addSheet.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for addSheet</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1024707">Mozilla Bug 1024707</a> + +<iframe id="iframe1" src="additional_sheets_helper.html"></iframe> +<iframe id="iframe2" src="additional_sheets_helper.html"></iframe> + +<pre id="test"> +<script type="application/javascript; version=1.8"> + +let gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"] + .getService(SpecialPowers.Ci.nsIIOService); + +let gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"] + .getService(SpecialPowers.Ci.nsIStyleSheetService); + +function test(win, sheet) { + let cs = win.getComputedStyle(win.document.body, null); + is(cs.getPropertyValue('color'), "rgb(0, 0, 0)", "should have default color"); + var windowUtils = SpecialPowers.wrap(win) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils); + windowUtils.addSheet(sheet, SpecialPowers.Ci.nsIDOMWindowUtils.USER_SHEET); + is(cs.getPropertyValue('color'), "rgb(255, 0, 0)", "should have changed color to red"); +} + +function run() { + var uri = gIOService.newURI("data:text/css,body{color:red;}", null, null); + let sheet = gSSService.preloadSheet(uri, SpecialPowers.Ci.nsIStyleSheetService.USER_SHEET); + + test(document.getElementById("iframe1").contentWindow, sheet); + test(document.getElementById("iframe2").contentWindow, sheet); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +</script> +</body> +</html> diff --git a/layout/style/test/test_additional_sheets.html b/layout/style/test/test_additional_sheets.html new file mode 100644 index 000000000..f1b8f6ed8 --- /dev/null +++ b/layout/style/test/test_additional_sheets.html @@ -0,0 +1,314 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for additional sheets</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=737003">Mozilla Bug 737003</a> +<iframe id="iframe" src="additional_sheets_helper.html"></iframe> +<pre id="test"> +<script type="application/javascript; version=1.8"> + +var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"] + .getService(SpecialPowers.Ci.nsIIOService) + +var gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"] + .getService(SpecialPowers.Ci.nsIStyleSheetService); + +function getUri(style) +{ + return "data:text/css," + style; +} + +function getStyle(color, swapped) +{ + return "body {color: " + color + (swapped ? " !important" : "") + + "; background-color: " + color + (swapped ? "" : " !important;") + ";}"; +} + +function loadUserSheet(win, style) +{ + loadSheet(win, style, "USER_SHEET"); +} + +function loadAgentSheet(win, style) +{ + loadSheet(win, style, "AGENT_SHEET"); +} + +function loadAuthorSheet(win, style) +{ + loadSheet(win, style, "AUTHOR_SHEET"); +} + +function removeUserSheet(win, style) +{ + removeSheet(win, style, "USER_SHEET"); +} + +function removeAgentSheet(win, style) +{ + removeSheet(win, style, "AGENT_SHEET"); +} + +function removeAuthorSheet(win, style) +{ + removeSheet(win, style, "AUTHOR_SHEET"); +} + +function loadSheet(win, style, type) +{ + var uri = gIOService.newURI(getUri(style), null, null); + var windowUtils = SpecialPowers.wrap(win) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils); + windowUtils.loadSheet(uri, windowUtils[type]); +} + +function removeSheet(win, style, type) +{ + var uri = gIOService.newURI(getUri(style), null, null); + var windowUtils = SpecialPowers.wrap(win) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils); + windowUtils.removeSheet(uri, windowUtils[type]); +} + +function loadAndRegisterUserSheet(win, style) +{ + loadAndRegisterSheet(win, style, "USER_SHEET"); +} + +function loadAndRegisterAgentSheet(win, style) +{ + loadAndRegisterSheet(win, style, "AGENT_SHEET"); +} + +function loadAndRegisterAuthorSheet(win, style) +{ + loadAndRegisterSheet(win, style, "AUTHOR_SHEET"); +} + +function unregisterUserSheet(win, style) +{ + unregisterSheet(win, style, "USER_SHEET"); +} + +function unregisterAgentSheet(win, style) +{ + unregisterSheet(win, style, "AGENT_SHEET"); +} + +function unregisterAuthorSheet(win, style) +{ + unregisterSheet(win, style, "AUTHOR_SHEET"); +} + +function loadAndRegisterSheet(win, style, type) +{ + uri = gIOService.newURI(getUri(style), null, null); + gSSService.loadAndRegisterSheet(uri, gSSService[type]); + is(gSSService.sheetRegistered(uri, gSSService[type]), true); +} + +function unregisterSheet(win, style, type) +{ + var uri = gIOService.newURI(getUri(style), null, null); + gSSService.unregisterSheet(uri, gSSService[type]); + is(gSSService.sheetRegistered(uri, gSSService[type]), false); +} + +function setDocSheet(win, style) +{ + var subdoc = win.document; + var headID = subdoc.getElementsByTagName("head")[0]; + var cssNode = subdoc.createElement('style'); + cssNode.type = 'text/css'; + cssNode.innerHTML = style; + cssNode.id = 'docsheet'; + headID.appendChild(cssNode); +} + +function removeDocSheet(win) +{ + var subdoc = win.document; + var node = subdoc.getElementById('docsheet'); + node.parentNode.removeChild(node); +} + +var agent = { + type: 'agent', + color: 'rgb(255, 0, 0)', + addRules: loadAndRegisterAgentSheet, + removeRules: unregisterAgentSheet +}; + +var user = { + type: 'user', + color: 'rgb(0, 255, 0)', + addRules: loadAndRegisterUserSheet, + removeRules: unregisterUserSheet +}; + +var additionalAgent = { + type: 'additionalAgent', + color: 'rgb(0, 0, 255)', + addRules: loadAgentSheet, + removeRules: removeAgentSheet +}; + +var additionalUser = { + type: 'additionalUser', + color: 'rgb(255, 255, 0)', + addRules: loadUserSheet, + removeRules: removeUserSheet +}; + +var additionalAuthor = { + type: 'additionalAuthor', + color: 'rgb(255, 255, 0)', + addRules: loadAuthorSheet, + removeRules: removeAuthorSheet +}; + +var doc = { + type: 'doc', + color: 'rgb(0, 255, 255)', + addRules: setDocSheet, + removeRules: removeDocSheet +}; + +var author = { + type: 'author', + color: 'rgb(255, 0, 255)', + addRules: loadAndRegisterAuthorSheet, + removeRules: unregisterAuthorSheet +}; + +function loadAndCheck(win, firstType, secondType, swap, result1, result2) +{ + var firstStyle = getStyle(firstType.color, false); + var secondStyle = getStyle(secondType.color, swap); + + firstType.addRules(win, firstStyle); + secondType.addRules(win, secondStyle); + + var cs = win.getComputedStyle(win.document.body, null); + is(cs.getPropertyValue('color'), result1, + firstType.type + "(normal)" + " vs " + secondType.type + (swap ? "(important)" : "(normal)" ) + " 1"); + is(cs.getPropertyValue('background-color'), result2, + firstType.type + "(important)" + " vs " + secondType.type + (swap ? "(normal)" : "(important)" ) + " 2"); + + firstType.removeRules(win, firstStyle); + secondType.removeRules(win, secondStyle); + + is(cs.getPropertyValue('color'), 'rgb(0, 0, 0)', firstType.type + " vs " + secondType.type + " 3"); + is(cs.getPropertyValue('background-color'), 'transparent', firstType.type + " vs " + secondType.type + " 4"); +} + +// There are 8 cases. Regular against regular, regular against important, important +// against regular, important against important. We can load style from typeA first +// then typeB or the other way around so that's 4*2=8 cases. + +function testStyleVsStyle(win, typeA, typeB, results) +{ + function color(res) + { + return res ? typeB.color : typeA.color; + } + + loadAndCheck(win, typeA, typeB, false, color(results.AB.rr), color(results.AB.ii)); + loadAndCheck(win, typeB, typeA, false, color(results.BA.rr), color(results.BA.ii)); + + loadAndCheck(win, typeA, typeB, true, color(results.AB.ri), color(results.AB.ir)); + loadAndCheck(win, typeB, typeA, true, color(results.BA.ir), color(results.BA.ri)); +} + +// 5 user agent normal declarations +// 4 user normal declarations +// 3 author normal declarations +// 2 author important declarations +// 1 user important declarations +// 0 user agent important declarations + +function run() +{ + var iframe = document.getElementById("iframe"); + var win = iframe.contentWindow; + +// Some explanation how to interpret this result table... +// in case of loading the agent style first and the user style later (AB) +// if there is an important rule in both for let's say color (ii) +// the rule specified in the agent style will lead (AB.ii == 0) +// If both rules would be just regular rules the one specified in the user style +// would lead. (AB.rr == 1). If we would load/add the rules in reverse order that +// would not change that (BA.rr == 1) + testStyleVsStyle(win, agent, user, + {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}}); + + testStyleVsStyle(win, agent, doc, + {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}}); + + + testStyleVsStyle(win, additionalUser, agent, + {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}}); + + testStyleVsStyle(win, additionalUser, doc, + {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}}); + + testStyleVsStyle(win, additionalAgent, user, + {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}}); + + testStyleVsStyle(win, additionalAgent, doc, + {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}}); + + + testStyleVsStyle(win, additionalAgent, additionalUser, + {AB:{rr:1, ii:0, ri:1, ir:0}, BA:{rr:1, ii:0, ri:1, ir:0}}); + + testStyleVsStyle(win, author, doc, + {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}}); + + testStyleVsStyle(win, author, user, + {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}}); + + testStyleVsStyle(win, author, agent, + {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}}); + + testStyleVsStyle(win, author, additionalUser, + {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}}); + + testStyleVsStyle(win, additionalAuthor, doc, + {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}}); + + testStyleVsStyle(win, additionalAuthor, author, + {AB:{rr:0, ii:0, ri:1, ir:0}, BA:{rr:0, ii:0, ri:1, ir:0}}); + + testStyleVsStyle(win, additionalAuthor, user, + {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}}); + + testStyleVsStyle(win, additionalAuthor, agent, + {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}}); + + testStyleVsStyle(win, additionalAuthor, additionalUser, + {AB:{rr:0, ii:1, ri:1, ir:0}, BA:{rr:0, ii:1, ri:1, ir:0}}); + + // Bug 1228542 + var url = getStyle('rgb(255, 0, 0)'); + loadAndRegisterAuthorSheet(win, url); + // Avoiding security exception... + (new win.Function("document.open()"))(); + (new win.Function("document.close()"))(); + unregisterAuthorSheet(win, url); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_align_justify_computed_values.html b/layout/style/test/test_align_justify_computed_values.html new file mode 100644 index 000000000..3cd4b8b0e --- /dev/null +++ b/layout/style/test/test_align_justify_computed_values.html @@ -0,0 +1,529 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=696253 +--> +<head> + <meta charset="utf-8"> + <title>Test align/justify-items/self/content computed values</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body style="position:relative"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a> +<style> +#flexContainer, #flexContainerGrid { display: flex; position:relative; } +#gridContainer, #gridContainerFlex { display: grid; position:relative; } +#display b, #absChild { position:absolute; } +</style> +<div id="display"> + <div id="myDiv"></div> + <div id="flexContainer"><a></a><b></b></div> + <div id="gridContainer"><a></a><b></b></div> + <div id="flexContainerGrid"><a style="diplay:grid"></a><b style="diplay:grid"></b></div> + <div id="gridContainerFlex"><a style="diplay:flex"></a><b style="diplay:flex"></b></div> +</div> +<div id="absChild"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +/* + * Utility function for getting computed style of "align-self": + */ +function getComputedAlignSelf(elem) { + return window.getComputedStyle(elem, "").alignSelf; +} +function getComputedAlignItems(elem) { + return window.getComputedStyle(elem, "").alignItems; +} +function getComputedAlignContent(elem) { + return window.getComputedStyle(elem, "").alignContent; +} +function getComputedJustifySelf(elem) { + return window.getComputedStyle(elem, "").justifySelf; +} +function getComputedJustifyItems(elem) { + return window.getComputedStyle(elem, "").justifyItems; +} +function getComputedJustifyContent(elem) { + return window.getComputedStyle(elem, "").justifyContent; +} + +/** + * Test behavior of 'align-self:auto' (Bug 696253 and Bug 1304012) + * =============================================== + * + * In a previous revision of the CSS Alignment spec, align-self:auto + * was required to actually *compute* to the parent's align-items value -- + * but now, the spec says it simply computes to itself, and it should + * only get converted into the parent's align-items value when it's used + * in layout. This test verifies that we do indeed have it compute to + * itself, regardless of the parent's align-items value. + */ + +/* + * Tests for a block node with a parent node: + */ +function testGeneralNode(elem) { + // Test initial computed style + // (Initial value should be 'auto', which should compute to itself) + is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " + + "initial computed value of 'align-self' should be 'auto'"); + + // Test value after setting align-self explicitly to "auto" + elem.style.alignSelf = "auto"; + is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " + + "computed value of 'align-self: auto' should be 'auto'"); + elem.style.alignSelf = ""; // clean up + + // Test value after setting align-self explicitly to "inherit" + elem.style.alignSelf = "inherit"; + if (elem.parentNode && elem.parentNode.style) { + is(getComputedAlignSelf(elem), getComputedAlignSelf(elem.parentNode), + elem.tagName + ": computed value of 'align-self: inherit' " + + "should match the value on the parent"); + } else { + is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " + + "computed value of 'align-self: inherit' should be 'auto', " + + "when there is no parent"); + } + elem.style.alignSelf = ""; // clean up +} + +/* + * Tests that depend on us having a parent node: + */ +function testNodeThatHasParent(elem) { + // Sanity-check that we actually do have a styleable parent: + ok(elem.parentNode && elem.parentNode.style, elem.tagName + ": " + + "bug in test -- expecting caller to pass us a node with a parent"); + + // Test initial computed style when "align-items" has been set on our parent. + // (elem's initial "align-self" value should be "auto", which should compute + // to its parent's "align-items" value, which in this case is "center".) + elem.parentNode.style.alignItems = "center"; + is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " + + "initial computed value of 'align-self' should be 'auto', even " + + "after changing parent's 'align-items' value"); + + // ...and now test computed style after setting "align-self" explicitly to + // "auto" (with parent "align-items" still at "center") + elem.style.alignSelf = "auto"; + is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " + + "computed value of 'align-self: auto' should remain 'auto', after " + + "being explicitly set"); + + elem.style.alignSelf = ""; // clean up + elem.parentNode.style.alignItems = ""; // clean up + + // Finally: test computed style after setting "align-self" to "inherit" + // and leaving parent at its initial value which should be "auto". + elem.style.alignSelf = "inherit"; + is(getComputedAlignSelf(elem), "auto", elem.tagName + ": " + + "computed value of 'align-self: inherit' should take parent's " + + "computed 'align-self' value (which should be 'auto', " + + "if we haven't explicitly set any other style"); + elem.style.alignSelf = ""; // clean up + } + +/* + * Main test function + */ +function main() { + // Test the root node + // ================== + // (It's special because it has no parent style context.) + + var rootNode = document.documentElement; + + // Sanity-check that we actually have the root node, as far as CSS is concerned. + // (Note: rootNode.parentNode is a HTMLDocument object -- not an element that + // we inherit style from.) + ok(!rootNode.parentNode.style, + "expecting root node to have no node to inherit style from"); + + testGeneralNode(rootNode); + + // Test the body node + // ================== + // (It's special because it has no grandparent style context.) + + var body = document.getElementsByTagName("body")[0]; + is(body.parentNode, document.documentElement, + "expecting body element's parent to be the root node"); + + testGeneralNode(body); + testNodeThatHasParent(body); + + // + // align-items/self tests: + // + //// Block tests + var elem = document.body; + var child = document.getElementById("display"); + var abs = document.getElementById("absChild"); + is(getComputedAlignItems(elem), 'normal', "default align-items value for block container"); + is(getComputedAlignSelf(child), 'auto', "default align-self value for block child"); + is(getComputedAlignSelf(abs), 'auto', "default align-self value for block container abs.pos. child"); + elem.style.alignItems = "end"; + is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child"); + is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for block container abs.pos. child"); + elem.style.alignItems = "left"; + is(getComputedAlignItems(elem), 'left', "align-items:left computes to itself for a block"); + is(getComputedAlignSelf(child), 'auto', "align-self:auto persists for block child"); + is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for block container abs.pos. child"); + elem.style.alignItems = "right"; + is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child"); + is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for block container abs.pos. child"); + elem.style.alignItems = "right safe"; + is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child"); + elem.style.alignItems = ""; + child.style.alignSelf = "left"; + is(getComputedAlignSelf(child), 'left', "align-self:left computes to left for block child"); + child.style.alignSelf = "right"; + is(getComputedAlignSelf(child), 'right', "align-self:right computes to right for block child"); + child.style.alignSelf = ""; + abs.style.alignSelf = "right"; + is(getComputedAlignSelf(abs), 'right', "align-self:right computes to right for block container abs.pos. child"); + + //// Flexbox tests + function testFlexAlignItemsSelf(elem) { + var item = elem.firstChild; + var abs = elem.children[1]; + is(getComputedAlignItems(elem), 'normal', "default align-items value for flex container"); + is(getComputedAlignSelf(item), 'auto', "default align-self value for flex item"); + is(getComputedAlignSelf(abs), 'auto', "default align-self value for flex container abs.pos. child"); + elem.style.alignItems = "flex-end"; + is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for flex container child"); + is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for flex container abs.pos. child"); + elem.style.alignItems = "left"; + is(getComputedAlignItems(elem), 'left', "align-items:left computes to itself for flex container"); + // XXX TODO: add left/right tests (bug 1221565) + elem.style.alignItems = ""; + } + testFlexAlignItemsSelf(document.getElementById("flexContainer")); + testFlexAlignItemsSelf(document.getElementById("flexContainerGrid")); + + //// Grid tests + function testGridAlignItemsSelf(elem) { + var item = elem.firstChild; + var abs = elem.children[1]; + is(getComputedAlignItems(elem), 'normal', "default align-items value for grid container"); + is(getComputedAlignSelf(item), 'auto', "default align-self value for grid item"); + is(getComputedAlignSelf(abs), 'auto', "default align-self value for grid container abs.pos. child"); + elem.style.alignItems = "end"; + is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child"); + is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child"); + elem.style.alignItems = "left"; + is(getComputedAlignItems(elem), 'left', "align-items:left computes to itself for grid container"); + is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child"); + is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child"); + elem.style.alignItems = "right"; + is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child"); + is(getComputedAlignSelf(abs), 'auto', "align-self:auto value persists for grid container abs.pos. child"); + elem.style.alignItems = "right safe"; + is(getComputedAlignSelf(item), 'auto', "align-self:auto value persists for grid container child"); + item.style.alignSelf = "left"; + is(getComputedAlignSelf(item), 'left', "align-self:left computes to left on grid item"); + item.style.alignSelf = "right"; + is(getComputedAlignSelf(item), 'right', "align-self:right computes to right on grid item"); + item.style.alignSelf = "right safe"; + is(getComputedAlignSelf(item), 'right safe', "align-self:'right safe' computes to 'right safe' on grid item"); + item.style.alignSelf = ""; + abs.style.alignSelf = "right"; + is(getComputedAlignSelf(abs), 'right', "align-self:right computes to right on grid container abs.pos. child"); + abs.style.alignSelf = ""; + elem.style.alignItems = ""; + item.style.alignSelf = ""; + } + testGridAlignItemsSelf(document.getElementById("gridContainer")); + testGridAlignItemsSelf(document.getElementById("gridContainerFlex")); + + // + // justify-items/self tests: + // + //// Block tests + var elem = document.body; + var child = document.getElementById("display"); + var abs = document.getElementById("absChild"); + is(getComputedJustifyItems(elem), 'normal', "default justify-items value for block container"); + is(getComputedJustifySelf(child), 'auto', "default justify-self value for block container child"); + is(getComputedJustifySelf(abs), 'auto', "default justify-self value for block container abs.pos. child"); + elem.style.justifyItems = "end"; + is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for block container abs.pos. child"); + elem.style.justifyItems = "left"; + is(getComputedJustifyItems(elem), 'left', "justify-items:left computes to itself on a block"); + is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for block container abs.pos. child"); + elem.style.justifyItems = "right"; + is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for block container abs.pos. child"); + elem.style.justifyItems = "right safe"; + is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child"); + elem.style.justifyItems = ""; + child.style.justifySelf = "left"; + is(getComputedJustifySelf(child), 'left', "justify-self:left computes to left on block child"); + child.style.justifySelf = "right"; + is(getComputedJustifySelf(child), 'right', "justify-self:right computes to right on block child"); + child.style.justifySelf = ""; + abs.style.justifySelf = "right"; + is(getComputedJustifySelf(abs), 'right', "justify-self:right computes to right on block container abs.pos. child"); + + //// Flexbox tests + function testFlexJustifyItemsSelf(elem) { + var item = elem.firstChild; + var abs = elem.children[1]; + is(getComputedJustifyItems(elem), 'normal', "default justify-items value for flex container"); + is(getComputedJustifySelf(item), 'auto', "default justify-self value for flex item"); + is(getComputedJustifySelf(abs), 'auto', "default justify-self value for flex container abs.pos. child"); + elem.style.justifyItems = "flex-end"; + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for flex container child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for flex container abs.pos. child"); + elem.style.justifyItems = "left"; + is(getComputedJustifyItems(elem), 'left', "justify-items:left computes to itself for flex container"); + elem.style.justifyItems = "right safe"; + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for flex container child"); + // XXX TODO: add left/right tests (bug 1221565) + elem.style.justifyItems = ""; + } + testFlexJustifyItemsSelf(document.getElementById("flexContainer")); + testFlexJustifyItemsSelf(document.getElementById("flexContainerGrid")); + + //// Grid tests + function testGridJustifyItemsSelf(elem) { + var item = elem.firstChild; + var abs = elem.children[1]; + is(getComputedJustifyItems(elem), 'normal', "default justify-items value for grid container"); + is(getComputedJustifySelf(item), 'auto', "default justify-self value for grid item"); + is(getComputedJustifySelf(abs), 'auto', "default justify-self value for grid container abs.pos. child"); + elem.style.justifyItems = "end"; + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child"); + elem.style.justifyItems = "left"; + is(getComputedJustifyItems(elem), 'left', "justify-items:left computes to itself for grid container"); + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child"); + elem.style.justifyItems = "legacy left"; + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child"); + elem.style.justifyItems = "right"; + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child"); + elem.style.justifyItems = "right safe"; + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child"); + elem.style.justifyItems = "legacy right"; + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child"); + elem.style.justifyItems = "legacy center"; + item.style.justifyItems = "inherit"; + abs.style.justifyItems = "inherit"; + is(getComputedJustifySelf(item), 'auto', "justify-self:auto value persists for grid container child"); + is(getComputedJustifySelf(abs), 'auto', "justify-self:auto value persists for grid container abs.pos. child"); + is(getComputedJustifyItems(elem), 'legacy center', "justify-items computes to itself grid container"); + is(getComputedJustifyItems(item), 'legacy center', "justify-items inherits including legacy keyword to grid item"); + is(getComputedJustifyItems(abs), 'legacy center', "justify-items inherits including legacy keyword to grid container abs.pos. child"); + elem.style.justifyItems = ""; + item.style.justifySelf = "left"; + is(getComputedJustifySelf(item), 'left', "justify-self:left computes to left on grid item"); + item.style.justifySelf = "right"; + is(getComputedJustifySelf(item), 'right', "justify-self:right computes to right on grid item"); + item.style.justifySelf = "right safe"; + is(getComputedJustifySelf(item), 'right safe', "justify-self:'right safe' computes to 'right safe' on grid item"); + item.style.justifySelf = ""; + abs.style.justifySelf = "right"; + is(getComputedJustifySelf(abs), 'right', "justify-self:right computes to right on grid container abs.pos. child"); + abs.style.justifySelf = ""; + elem.style.justifyItems = ""; + item.style.justifySelf = ""; + } + testGridJustifyItemsSelf(document.getElementById("gridContainer")); + testGridJustifyItemsSelf(document.getElementById("gridContainerFlex")); + + // + // align-content tests: + // + //// Block tests + var elem = document.body; + var child = document.getElementById("display"); + var abs = document.getElementById("absChild"); + is(getComputedAlignContent(elem), 'normal', "default align-content value for block container"); + is(getComputedAlignContent(child), 'normal', "default align-content value for block child"); + is(getComputedAlignContent(abs), 'normal', "default align-content value for block container abs.pos. child"); + elem.style.alignContent = "end"; + is(getComputedAlignContent(child), 'normal', "default align-content isn't affected by parent align-content value for in-flow child"); + is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child"); + elem.style.alignContent = "left"; + is(getComputedAlignContent(elem), 'left', "align-content:left computes to left on block child"); + is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child"); + elem.style.alignContent = "right"; + is(getComputedAlignContent(elem), 'right', "align-content:right computes to right on block child"); + is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child"); + elem.style.alignContent = "right safe"; + is(getComputedAlignContent(elem), 'right safe', "align-content:'right safe' computes to 'align-content:right safe'"); + elem.style.alignContent = ""; + child.style.alignContent = "left"; + is(getComputedAlignContent(child), 'left', "align-content:left computes to left on block child"); + child.style.alignContent = "right"; + is(getComputedAlignContent(child), 'right', "align-content:right computes to right on block child"); + child.style.alignContent = "left safe"; + is(getComputedAlignContent(child), 'left safe', "align-content:left computes to 'left safe' on block child"); + child.style.alignContent = ""; + abs.style.alignContent = "right"; + is(getComputedAlignContent(abs), 'right', "align-content:right computes to right on block container abs.pos. child"); + abs.style.alignContent = ""; + + //// Flexbox tests + function testFlexAlignContent(elem) { + var item = elem.firstChild; + var abs = elem.children[1]; + is(getComputedAlignContent(elem), 'normal', "default align-content value for flex container"); + is(getComputedAlignContent(item), 'normal', "default align-content value for flex item"); + is(getComputedAlignContent(abs), 'normal', "default align-content value for flex container abs.pos. child"); + elem.style.alignContent = "flex-end safe"; + is(getComputedAlignContent(elem), 'flex-end safe', "align-content:'flex-end safe' computes to itself for flex container"); + is(getComputedAlignContent(item), 'normal', "default align-content isn't affected by parent align-content value for flex item"); + is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for flex container abs.pos. child"); + // XXX TODO: add left/right tests (bug 1221565) + elem.style.alignContent = ""; + } + testFlexAlignContent(document.getElementById("flexContainer")); + testFlexAlignContent(document.getElementById("flexContainerGrid")); + + //// Grid tests + function testGridAlignContent(elem) { + var item = elem.firstChild; + var abs = elem.children[1]; + is(getComputedAlignContent(elem), 'normal', "default align-content value for grid container"); + is(getComputedAlignContent(item), 'normal', "default align-content value for grid item"); + is(getComputedAlignContent(abs), 'normal', "default align-content value for grid container abs.pos. child"); + elem.style.alignContent = "end safe"; + is(getComputedAlignContent(elem), 'end safe', "align-content:'end safe' computes to itself on grid container"); + is(getComputedAlignContent(item), 'normal', "default align-content isn't affected by parent align-content value for grid item"); + is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for grid container abs.pos. child"); + elem.style.alignContent = "left"; + is(getComputedAlignContent(elem), 'left', "align-content:left computes to left on grid container"); + is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for grid container abs.pos. child"); + elem.style.alignContent = "right"; + is(getComputedAlignContent(elem), 'right', "align-content:right computes to right on grid container"); + is(getComputedAlignContent(abs), 'normal', "default align-content isn't affected by parent align-content value for grid container abs.pos. child"); + elem.style.alignContent = "right safe"; + item.style.alignContent = "inherit"; + abs.style.alignContent = "inherit"; + is(getComputedAlignContent(elem), 'right safe', "align-content:'right safe' computes to 'align-content:right safe' on grid container"); + is(getComputedAlignContent(item), 'right safe', "align-content:'right safe' inherits as 'align-content:right safe' to grid item"); + is(getComputedAlignContent(abs), 'right safe', "align-content:'right safe' inherits as 'align-content:right safe' to grid container abs.pos. child"); + item.style.alignContent = "left"; + is(getComputedAlignContent(item), 'left', "align-content:left computes to left on grid item"); + item.style.alignContent = "right"; + is(getComputedAlignContent(item), 'right', "align-content:right computes to right on grid item"); + item.style.alignContent = "right safe"; + is(getComputedAlignContent(item), 'right safe', "align-content:'right safe' computes to 'right safe' on grid item"); + item.style.alignContent = ""; + abs.style.alignContent = "right"; + is(getComputedAlignContent(abs), 'right', "align-content:right computes to right on grid container abs.pos. child"); + abs.style.alignContent = ""; + elem.style.alignContent = ""; + item.style.alignContent = ""; + } + testGridAlignContent(document.getElementById("gridContainer")); + testGridAlignContent(document.getElementById("gridContainerFlex")); + + + // + // justify-content tests: + // + //// Block tests + var elem = document.body; + var child = document.getElementById("display"); + var abs = document.getElementById("absChild"); + is(getComputedJustifyContent(elem), 'normal', "default justify-content value for block container"); + is(getComputedJustifyContent(child), 'normal', "default justify-content value for block child"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content value for block container abs.pos. child"); + elem.style.justifyContent = "end"; + is(getComputedJustifyContent(child), 'normal', "default justify-content isn't affected by parent justify-content value for in-flow child"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child"); + elem.style.justifyContent = "left"; + is(getComputedJustifyContent(elem), 'left', "justify-content:left computes to left on block child"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child"); + elem.style.justifyContent = "right"; + is(getComputedJustifyContent(elem), 'right', "justify-content:right computes to right on block child"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child"); + elem.style.justifyContent = "right safe"; + is(getComputedJustifyContent(elem), 'right safe', "justify-content:'right safe' computes to 'justify-content:right safe'"); + elem.style.justifyContent = ""; + child.style.justifyContent = "left"; + is(getComputedJustifyContent(child), 'left', "justify-content:left computes to left on block child"); + child.style.justifyContent = "right"; + is(getComputedJustifyContent(child), 'right', "justify-content:right computes to right on block child"); + child.style.justifyContent = "left safe"; + is(getComputedJustifyContent(child), 'left safe', "justify-content:left computes to 'left safe' on block child"); + child.style.justifyContent = ""; + abs.style.justifyContent = "right"; + is(getComputedJustifyContent(abs), 'right', "justify-content:right computes to right on block container abs.pos. child"); + abs.style.justifyContent = ""; + + //// Flexbox tests + function testFlexJustifyContent(elem) { + var item = elem.firstChild; + var abs = elem.children[1]; + is(getComputedJustifyContent(elem), 'normal', "default justify-content value for flex container"); + is(getComputedJustifyContent(item), 'normal', "default justify-content value for flex item"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content value for flex container abs.pos. child"); + elem.style.justifyContent = "flex-end safe"; + is(getComputedJustifyContent(elem), 'flex-end safe', "justify-content:'flex-end safe' computes to itself for flex container"); + is(getComputedJustifyContent(item), 'normal', "default justify-content isn't affected by parent justify-content value for flex item"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for flex container abs.pos. child"); + // XXX TODO: add left/right tests (bug 1221565) + elem.style.justifyContent = ""; + } + testFlexJustifyContent(document.getElementById("flexContainer")); + testFlexJustifyContent(document.getElementById("flexContainerGrid")); + + //// Grid tests + function testGridJustifyContent(elem) { + var item = elem.firstChild; + var abs = elem.children[1]; + is(getComputedJustifyContent(elem), 'normal', "default justify-content value for grid container"); + is(getComputedJustifyContent(item), 'normal', "default justify-content value for grid item"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content value for grid container abs.pos. child"); + elem.style.justifyContent = "end safe"; + is(getComputedJustifyContent(elem), 'end safe', "justify-content:'end safe' computes to itself on grid container"); + is(getComputedJustifyContent(item), 'normal', "default justify-content isn't affected by parent justify-content value for grid item"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child"); + elem.style.justifyContent = "left"; + is(getComputedJustifyContent(elem), 'left', "justify-content:left computes to left on grid container"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child"); + elem.style.justifyContent = "right"; + is(getComputedJustifyContent(elem), 'right', "justify-content:right computes to right on grid container"); + is(getComputedJustifyContent(abs), 'normal', "default justify-content isn't affected by parent justify-content value for grid container abs.pos. child"); + elem.style.justifyContent = "right safe"; + item.style.justifyContent = "inherit"; + abs.style.justifyContent = "inherit"; + is(getComputedJustifyContent(elem), 'right safe', "justify-content:'right safe' computes to 'justify-content:right safe' on grid container"); + is(getComputedJustifyContent(item), 'right safe', "justify-content:'right safe' inherits as 'justify-content:right safe' to grid item"); + is(getComputedJustifyContent(abs), 'right safe', "justify-content:'right safe' inherits as 'justify-content:right safe' to grid container abs.pos. child"); + item.style.justifyContent = "left"; + is(getComputedJustifyContent(item), 'left', "justify-content:left computes to left on grid item"); + item.style.justifyContent = "right"; + is(getComputedJustifyContent(item), 'right', "justify-content:right computes to right on grid item"); + item.style.justifyContent = "right safe"; + is(getComputedJustifyContent(item), 'right safe', "justify-content:'right safe' computes to 'right safe' on grid item"); + item.style.justifyContent = ""; + abs.style.justifyContent = "right"; + is(getComputedJustifyContent(abs), 'right', "justify-content:right computes to right on grid container abs.pos. child"); + abs.style.justifyContent = ""; + elem.style.justifyContent = ""; + item.style.justifyContent = ""; + } + testGridJustifyContent(document.getElementById("gridContainer")); + testGridJustifyContent(document.getElementById("gridContainerFlex")); +} + +main(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_align_shorthand_serialization.html b/layout/style/test/test_align_shorthand_serialization.html new file mode 100644 index 000000000..95a3f4814 --- /dev/null +++ b/layout/style/test/test_align_shorthand_serialization.html @@ -0,0 +1,123 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset=utf-8> + <title>Test serialization of CSS Align shorthand properties</title> + <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel='stylesheet' href='/resources/testharness.css'> +</head> +<body> + +<script> + +var initial_values = { + alignContent: "normal", + alignItems: "normal", + alignSelf: "auto", + justifyContent: "normal", + justifyItems: "auto", + justifySelf: "auto", +}; + +var place_content_test_cases = [ + { + alignContent: "center", + shorthand: "center normal", + }, + { + alignContent: "baseline right safe", + shorthand: "", + }, + { + justifyContent: "start safe", + shorthand: "", + }, + { + justifyContent: "space-evenly start", + shorthand: "", + }, + { + alignContent: "start", + justifyContent: "end", + shorthand: "start end", + }, +]; + +var place_items_test_cases = [ + { + alignItems: "center", + shorthand: "center auto", + }, + { + alignItems: "baseline", + shorthand: "baseline auto", + }, + { + justifyItems: "start safe", + shorthand: "", + }, + { + justifyItems: "stretch", + shorthand: "normal stretch", + }, + { + justifyItems: "left legacy", + shorthand: "", + }, + { + alignItems: "stretch", + justifyItems: "end", + shorthand: "stretch end", + }, +]; + +var place_self_test_cases = [ + { + alignSelf: "right", + shorthand: "right auto", + }, + { + alignSelf: "self-end safe", + shorthand: "", + }, + { + justifySelf: "unsafe start", + shorthand: "", + }, + { + justifySelf: "last baseline start", + shorthand: "", + }, + { + alignSelf: "baseline", + justifySelf: "last baseline", + shorthand: "baseline last baseline", + }, +]; + +function run_tests(test_cases, shorthand, subproperties) { + test_cases.forEach(function(test_case) { + test(function() { + var element = document.createElement('div'); + document.body.appendChild(element); + subproperties.forEach(function(longhand) { + element.style[longhand] = test_case[longhand] || + initial_values[longhand]; + }); + assert_equals(element.style[shorthand], test_case.shorthand); + }, "test shorthand serialization " + JSON.stringify(test_case)); + }); +} + +run_tests(place_content_test_cases, "placeContent", [ + "alignContent", "justifyContent"]); +run_tests(place_items_test_cases, "placeItems", [ + "alignItems", "justifyItems"]); +run_tests(place_self_test_cases, "placeSelf", [ + "alignSelf", "justifySelf"]); + +</script> +</body> +</html> diff --git a/layout/style/test/test_all_shorthand.html b/layout/style/test/test_all_shorthand.html new file mode 100644 index 000000000..6185778cc --- /dev/null +++ b/layout/style/test/test_all_shorthand.html @@ -0,0 +1,159 @@ +<!DOCTYPE html> +<title>Test the 'all' shorthand property</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="property_database.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +<body> + +<style id="stylesheet"> +#parent { } +#child { } +#child { } +</style> + +<div style="display: none"> + <div id="parent"> + <div id="child"></div> + </div> +</div> + +<script> +function runTest() { + var sheet = document.getElementById("stylesheet").sheet; + var parentRule = sheet.cssRules[0]; + var childRule1 = sheet.cssRules[1]; + var childRule2 = sheet.cssRules[2]; + var parent = document.getElementById("parent"); + var child = document.getElementById("child"); + + // Longhand properties that are NOT considered to be subproperties of the 'all' + // shorthand. + var excludedSubproperties = ["direction", "unicode-bidi"]; + var excludedSubpropertiesSet = new Set(excludedSubproperties); + + // Longhand properties that are considered to be subproperties of the 'all' + // shorthand. + var includedSubproperties = Object.keys(gCSSProperties).filter(function(prop) { + var info = gCSSProperties[prop]; + return info.type == CSS_TYPE_LONGHAND && + !excludedSubpropertiesSet.has(prop); + }); + + // All longhand properties to be tested. + var allSubproperties = includedSubproperties.concat(excludedSubproperties); + + + // First, get the computed value for the initial value and one other value of + // each property. + var initialComputedValues = new Map(); + var otherComputedValues = new Map(); + + allSubproperties.forEach(function(prop) { + parentRule.style.setProperty(prop, "initial", ""); + initialComputedValues.set(prop, getComputedStyle(parent, "").getPropertyValue(prop)); + parentRule.style.cssText = ""; + }); + + allSubproperties.forEach(function(prop) { + var info = gCSSProperties[prop]; + parentRule.style.setProperty(prop, info.other_values[0], ""); + otherComputedValues.set(prop, getComputedStyle(parent, "").getPropertyValue(prop)); + parentRule.style.cssText = ""; + }); + + + // Test setting all:inherit through setProperty. + includedSubproperties.forEach(function(prop) { + var info = gCSSProperties[prop]; + parentRule.style.setProperty(prop, info.other_values[0], ""); + childRule1.style.setProperty(prop, "initial"); + childRule2.style.setProperty("all", "inherit"); + is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop), + "computed value for " + prop + " when 'all:inherit' set with setProperty"); + parentRule.style.cssText = ""; + childRule1.style.cssText = ""; + childRule2.style.cssText = ""; + }); + excludedSubproperties.forEach(function(prop) { + var info = gCSSProperties[prop]; + parentRule.style.setProperty(prop, info.other_values[0], ""); + childRule1.style.setProperty(prop, "initial"); + childRule2.style.setProperty("all", "inherit"); + is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop), + "computed value for excluded subproperty " + prop + " when 'all:inherit' set with setProperty"); + parentRule.style.cssText = ""; + childRule1.style.cssText = ""; + childRule2.style.cssText = ""; + }); + + // Test setting all:initial through setProperty. + includedSubproperties.forEach(function(prop) { + var info = gCSSProperties[prop]; + parentRule.style.setProperty(prop, info.other_values[0], ""); + childRule1.style.setProperty(prop, "inherit"); + childRule2.style.setProperty("all", "initial"); + is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop), + "computed value for " + prop + " when 'all:initial' set with setProperty"); + parentRule.style.cssText = ""; + childRule1.style.cssText = ""; + childRule2.style.cssText = ""; + }); + excludedSubproperties.forEach(function(prop) { + var info = gCSSProperties[prop]; + parentRule.style.setProperty(prop, info.other_values[0], ""); + childRule1.style.setProperty(prop, info.other_values[0], ""); + childRule2.style.setProperty("all", "initial"); + is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop), + "computed value for excluded subproperty " + prop + " when 'all:initial' set with setProperty"); + parentRule.style.cssText = ""; + childRule1.style.cssText = ""; + childRule2.style.cssText = ""; + }); + + // Test setting all:unset through setProperty. + includedSubproperties.forEach(function(prop) { + var info = gCSSProperties[prop]; + if (info.inherited) { + parentRule.style.setProperty(prop, info.other_values[0], ""); + childRule1.style.setProperty(prop, "initial", ""); + childRule2.style.setProperty("all", "unset"); + is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop), + "computed value for " + prop + " when 'all:unset' set with setProperty"); + } else { + parentRule.style.setProperty(prop, info.other_values[0], ""); + childRule1.style.setProperty(prop, info.other_values[0], ""); + childRule2.style.setProperty("all", "unset"); + is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop), + "computed value for " + prop + " when 'all:unset' set with setProperty"); + } + parentRule.style.cssText = ""; + childRule1.style.cssText = ""; + childRule2.style.cssText = ""; + }); + excludedSubproperties.forEach(function(prop) { + var info = gCSSProperties[prop]; + if (info.inherited) { + parentRule.style.setProperty(prop, info.other_values[0], ""); + childRule1.style.setProperty(prop, "initial", ""); + childRule2.style.setProperty("all", "unset"); + is(getComputedStyle(child, "").getPropertyValue(prop), initialComputedValues.get(prop), + "computed value for excluded subproperty " + prop + " when 'all:unset' set with setProperty"); + } else { + parentRule.style.setProperty(prop, info.other_values[0], ""); + childRule1.style.setProperty(prop, info.other_values[0], ""); + childRule2.style.setProperty("all", "unset"); + is(getComputedStyle(child, "").getPropertyValue(prop), otherComputedValues.get(prop), + "computed value for excluded subproperty " + prop + " when 'all:unset' set with setProperty"); + } + parentRule.style.cssText = ""; + childRule1.style.cssText = ""; + childRule2.style.cssText = ""; + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ "set": [["layout.css.all-shorthand.enabled", true], + ["layout.css.unset-value.enabled", true]] }, runTest); +</script> diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html new file mode 100644 index 000000000..eaccba122 --- /dev/null +++ b/layout/style/test/test_animations.html @@ -0,0 +1,2047 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=435442 +--> +<!-- + + ====== PLEASE KEEP THIS IN SYNC WITH test_animations_omta.html ======= + + test_animations_omta.html mimicks the content of this file but with + extra machinery for testing animation values on the compositor thread. + + If you are making changes to this file or to test_animations_omta.html, please + try to keep them consistent where appropriate. + +--> +<head> + <title>Test for css3-animations (Bug 435442)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + @keyframes anim1 { + 0% { margin-left: 0px } + 50% { margin-left: 80px } + 100% { margin-left: 100px } + } + @keyframes anim2 { + from { margin-right: 0 } to { margin-right: 100px } + } + @keyframes anim3 { + from { margin-top: 0 } to { margin-top: 100px } + } + @keyframes anim4 { + from { margin-bottom: 0 } to { margin-bottom: 100px } + } + @keyframes anim5 { + from { margin-left: 0 } to { margin-left: 100px } + } + + @keyframes kf1 { + 50% { margin-top: 50px } + to { margin-top: 150px } + } + @keyframes kf2 { + from { margin-top: 150px } + 50% { margin-top: 50px } + } + @keyframes kf3 { + 25% { margin-top: 100px } + } + @keyframes kf4 { + to, from { display: none; margin-top: 37px } + } + @keyframes kf_cascade1 { + from { padding-top: 50px } + 50%, from { padding-top: 30px } /* wins: 0% */ + 75%, 85%, 50% { padding-top: 20px } /* wins: 75%, 50% */ + 100%, 85% { padding-top: 70px } /* wins: 100% */ + 85.1% { padding-top: 60px } /* wins: 85.1% */ + 85% { padding-top: 30px } /* wins: 85% */ + } + @keyframes kf_cascade2 { from, to { margin-top: 100px } } + @keyframes kf_cascade2 { from, to { margin-left: 200px } } + @keyframes kf_cascade2 { from, to { margin-left: 300px } } + @keyframes kf_tf1 { + 0% { padding-bottom: 20px; animation-timing-function: ease } + 25% { padding-bottom: 60px; } + 50% { padding-bottom: 160px; animation-timing-function: steps(5) } + 75% { padding-bottom: 120px; animation-timing-function: linear } + 100% { padding-bottom: 20px; animation-timing-function: ease-out } + } + + @keyframes always_fifty { + from, to { margin-left: 50px } + } + + #withbefore::before, #withafter::after { + content: ""; + animation: anim2 1s linear alternate 3; + } + + @keyframes multiprop { + 0% { + padding-top: 10px; padding-left: 30px; + animation-timing-function: ease; + } + 25% { + padding-left: 50px; + animation-timing-function: ease-out; + } + 50% { + padding-top: 40px; + } + 75% { + padding-top: 80px; padding-left: 60px; + animation-timing-function: ease-in; + } + } + + @keyframes uaoverride { + 0%, 100% { line-height: 3; margin-top: 20px } + 50% { margin-top: 120px } + } + + @keyframes cascade { + 0%, 25%, 100% { top: 0 } + 50%, 75% { top: 100px } + 0%, 75%, 100% { left: 0 } + 25%, 50% { left: 100px } + } + @keyframes cascade2 { + 0% { text-indent: 0 } + 25% { text-indent: 30px; animation-timing-function: ease-in } /* beaten by rule below */ + 50% { text-indent: 0 } + 25% { text-indent: 50px } + 100% { text-indent: 100px } + } + + @keyframes primitives1 { + from { -moz-transform: rotate(0deg) translateX(0px) scaleX(1) + translate(0px) scale3d(1, 1, 1); } + to { -moz-transform: rotate(270deg) translate3d(0px, 0px, 0px) scale(1) + translateY(0px) scaleY(1); } + } + + @keyframes important1 { + from { margin-top: 50px; } + 50% { margin-top: 150px !important; } /* ignored */ + to { margin-top: 100px; } + } + + @keyframes important2 { + from { margin-top: 50px; + margin-bottom: 100px; } + to { margin-top: 150px !important; /* ignored */ + margin-bottom: 50px; } + } + + @keyframes empty { } + @keyframes nearlyempty { to { margin-left: 100px; } } + + @keyframes lowerpriority { + 0% { + top: 0px; + left: 0px; + } + 100% { + top: 100px; + left: 100px; + } + } + + @keyframes overrideleft { + 0%, 100% { left: 0px } + } + + @keyframes overridetop { + 0%, 100% { top: 0px } + } + + @keyframes opacitymid { + 0% { opacity: 0.2 } + 100% { opacity: 0.8 } + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435442">Mozilla Bug 435442</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +/** Test for css3-animations (Bug 435442) **/ + +var e = new AnimationEvent("foo", + { + bubbles: true, + cancelable: true, + animationName: "name", + elapsedTime: 0.5, + pseudoElement: "pseudo" + }); +is(e.bubbles, true); +is(e.cancelable, true); +is(e.animationName, "name"); +is(e.elapsedTime, 0.5); +is(e.pseudoElement, "pseudo"); +is(e.isTrusted, false) + +// Shortcut new_div to update div, cs +var div, cs; +var originalNewDiv = window.new_div; +window.new_div = function(style) { + [ div, cs ] = originalNewDiv(style); +}; + +// take over the refresh driver right from the start. +advance_clock(0); + +/* + * css3-animations: 2. Animations + * http://dev.w3.org/csswg/css3-animations/#animations + */ + +// Test that animations don't affect the computed value before the +// start of the animation or after its end. Test without +// animation-fill-mode, but then repeat the test with all the values of +// animation-fill-mode. +function test_fill_mode(fill_mode, fills_backwards, fills_forwards) +{ + var style = "margin-left: 30px; animation: 10s 3s anim1 linear"; + var desc; + if (fill_mode.length > 0) { + style += " " + fill_mode; + desc = "fill mode " + fill_mode + ": "; + } else { + desc = "default fill mode: "; + } + new_div(style); + listen(); + if (fills_backwards) + is(cs.marginLeft, "0px", desc + "does affect value during delay (0s)"); + else + is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (0s)"); + advance_clock(2000); + if (fills_backwards) + is(cs.marginLeft, "0px", desc + "does affect value during delay (2s)"); + else + is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (2s)"); + check_events([], "before start in test_fill_mode"); + advance_clock(1000); + check_events([{ type: 'animationstart', target: div, + bubbles: true, cancelable: false, + animationName: 'anim1', elapsedTime: 0.0, + pseudoElement: "" }], + "right after start in test_fill_mode"); + if (fills_backwards) + is(cs.marginLeft, "0px", desc + "affects value at start of animation"); + advance_clock(125); + is(cs.marginLeft, "2px", desc + "affects value during animation"); + advance_clock(2375); + is(cs.marginLeft, "40px", desc + "affects value during animation"); + advance_clock(2500); + is(cs.marginLeft, "80px", desc + "affects value during animation"); + advance_clock(2500); + is(cs.marginLeft, "90px", desc + "affects value during animation"); + advance_clock(2375); + is(cs.marginLeft, "99.5px", desc + "affects value during animation"); + check_events([], "before end in test_fill_mode"); + advance_clock(125); + check_events([{ type: 'animationend', target: div, + bubbles: true, cancelable: false, + animationName: 'anim1', elapsedTime: 10.0, + pseudoElement: "" }], + "right after end in test_fill_mode"); + if (fills_forwards) + is(cs.marginLeft, "100px", desc + "affects value at end of animation"); + advance_clock(10); + if (fills_forwards) + is(cs.marginLeft, "100px", desc + "does affect value after animation"); + else + is(cs.marginLeft, "30px", desc + "does not affect value after animation"); + done_div(); +} +test_fill_mode("", false, false); +test_fill_mode("none", false, false); +test_fill_mode("forwards", false, true); +test_fill_mode("backwards", true, false); +test_fill_mode("both", true, true); + +// Test that animations continue running when the animation name +// list is changed. +new_div("animation: anim1 linear 10s"); + is(cs.getPropertyValue("margin-top"), "0px", + "just anim1, margin-top at start"); + is(cs.getPropertyValue("margin-right"), "0px", + "just anim1, margin-right at start"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "just anim1, margin-bottom at start"); + is(cs.getPropertyValue("margin-left"), "0px", + "just anim1, margin-left at start"); +advance_clock(1000); + is(cs.getPropertyValue("margin-top"), "0px", + "just anim1, margin-top at 1s"); + is(cs.getPropertyValue("margin-right"), "0px", + "just anim1, margin-right at 1s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "just anim1, margin-bottom at 1s"); + is(cs.getPropertyValue("margin-left"), "16px", + "just anim1, margin-left at 1s"); +// append anim2 +div.style.animation = "anim1 linear 10s, anim2 linear 10s"; + is(cs.getPropertyValue("margin-top"), "0px", + "anim1 + anim2, margin-top at 1s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim1 + anim2, margin-right at 1s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim1 + anim2, margin-bottom at 1s"); + is(cs.getPropertyValue("margin-left"), "16px", + "anim1 + anim2, margin-left at 1s"); +advance_clock(1000); + is(cs.getPropertyValue("margin-top"), "0px", + "anim1 + anim2, margin-top at 2s"); + is(cs.getPropertyValue("margin-right"), "10px", + "anim1 + anim2, margin-right at 2s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim1 + anim2, margin-bottom at 2s"); + is(cs.getPropertyValue("margin-left"), "32px", + "anim1 + anim2, margin-left at 2s"); +// prepend anim3 +div.style.animation = "anim3 linear 10s, anim1 linear 10s, anim2 linear 10s"; + is(cs.getPropertyValue("margin-top"), "0px", + "anim3 + anim1 + anim2, margin-top at 2s"); + is(cs.getPropertyValue("margin-right"), "10px", + "anim3 + anim1 + anim2, margin-right at 2s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1 + anim2, margin-bottom at 2s"); + is(cs.getPropertyValue("margin-left"), "32px", + "anim3 + anim1 + anim2, margin-left at 2s"); +advance_clock(1000); + is(cs.getPropertyValue("margin-top"), "10px", + "anim3 + anim1 + anim2, margin-top at 3s"); + is(cs.getPropertyValue("margin-right"), "20px", + "anim3 + anim1 + anim2, margin-right at 3s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1 + anim2, margin-bottom at 3s"); + is(cs.getPropertyValue("margin-left"), "48px", + "anim3 + anim1 + anim2, margin-left at 3s"); +// remove anim2 from end +div.style.animation = "anim3 linear 10s, anim1 linear 10s"; + is(cs.getPropertyValue("margin-top"), "10px", + "anim3 + anim1, margin-top at 3s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim3 + anim1, margin-right at 3s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1, margin-bottom at 3s"); + is(cs.getPropertyValue("margin-left"), "48px", + "anim3 + anim1, margin-left at 3s"); +advance_clock(1000); + is(cs.getPropertyValue("margin-top"), "20px", + "anim3 + anim1, margin-top at 4s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim3 + anim1, margin-right at 4s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1, margin-bottom at 4s"); + is(cs.getPropertyValue("margin-left"), "64px", + "anim3 + anim1, margin-left at 4s"); +// swap anim1 and anim3, change duration of anim3 +div.style.animation = "anim1 linear 10s, anim3 linear 5s"; + is(cs.getPropertyValue("margin-top"), "40px", + "anim1 + anim3, margin-top at 4s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim1 + anim3, margin-right at 4s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim1 + anim3, margin-bottom at 4s"); + is(cs.getPropertyValue("margin-left"), "64px", + "anim1 + anim3, margin-left at 4s"); +advance_clock(1000); + is(cs.getPropertyValue("margin-top"), "60px", + "anim1 + anim3, margin-top at 5s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim1 + anim3, margin-right at 5s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim1 + anim3, margin-bottom at 5s"); + is(cs.getPropertyValue("margin-left"), "80px", + "anim1 + anim3, margin-left at 5s"); +// list anim1 twice, last duration wins, original start time still applies +div.style.animation = "anim1 linear 10s, anim3 linear 5s, anim1 linear 20s"; + is(cs.getPropertyValue("margin-top"), "60px", + "anim1 + anim3 + anim1, margin-top at 5s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim1 + anim3 + anim1, margin-right at 5s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim1 + anim3 + anim1, margin-bottom at 5s"); + is(cs.getPropertyValue("margin-left"), "40px", + "anim1 + anim3 + anim1, margin-left at 5s"); +// drop one of the anim1, and list anim5 as well, which animates +// the same property as anim1 +div.style.animation = "anim3 linear 5s, anim1 linear 20s, anim5 linear 10s"; + is(cs.getPropertyValue("margin-top"), "60px", + "anim3 + anim1 + anim5, margin-top at 5s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim3 + anim1 + anim5, margin-right at 5s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1 + anim5, margin-bottom at 5s"); + is(cs.getPropertyValue("margin-left"), "0px", + "anim3 + anim1 + anim5, margin-left at 5s"); +advance_clock(1000); + is(cs.getPropertyValue("margin-top"), "80px", + "anim3 + anim1 + anim5, margin-top at 6s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim3 + anim1 + anim5, margin-right at 6s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1 + anim5, margin-bottom at 6s"); + is(cs.getPropertyValue("margin-left"), "10px", + "anim3 + anim1 + anim5, margin-left at 6s"); +// now swap the anim5 and anim1 order +div.style.animation = "anim3 linear 5s, anim5 linear 10s, anim1 linear 20s"; + is(cs.getPropertyValue("margin-top"), "80px", + "anim3 + anim1 + anim5, margin-top at 6s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim3 + anim1 + anim5, margin-right at 6s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1 + anim5, margin-bottom at 6s"); + is(cs.getPropertyValue("margin-left"), "48px", + "anim3 + anim1 + anim5, margin-left at 6s"); +advance_clock(1000); + is(cs.getPropertyValue("margin-top"), "0px", + "anim3 + anim1 + anim5, margin-top at 7s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim3 + anim1 + anim5, margin-right at 7s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1 + anim5, margin-bottom at 7s"); + is(cs.getPropertyValue("margin-left"), "56px", + "anim3 + anim1 + anim5, margin-left at 7s"); +// swap anim1 and anim5 back +div.style.animation = "anim3 linear 5s, anim1 linear 20s, anim5 linear 10s"; + is(cs.getPropertyValue("margin-top"), "0px", + "anim3 + anim1 + anim5, margin-top at 7s"); + is(cs.getPropertyValue("margin-right"), "0px", + "anim3 + anim1 + anim5, margin-right at 7s"); + is(cs.getPropertyValue("margin-bottom"), "0px", + "anim3 + anim1 + anim5, margin-bottom at 7s"); + is(cs.getPropertyValue("margin-left"), "20px", + "anim3 + anim1 + anim5, margin-left at 7s"); +advance_clock(100); + is(cs.getPropertyValue("margin-top"), "0px", + "anim3 + anim1 + anim5, margin-top at 7.1s"); +// Change the animation fill mode on the completed animation. +div.style.animation = "anim3 linear 5s forwards, anim1 linear 20s, anim5 linear 10s"; + is(cs.getPropertyValue("margin-top"), "100px", + "anim3 + anim1 + anim5, margin-top at 7.1s, with fill mode"); +advance_clock(900); + is(cs.getPropertyValue("margin-top"), "100px", + "anim3 + anim1 + anim5, margin-top at 8s, with fill mode"); +// Change the animation duration on the completed animation, so it is +// no longer completed. +div.style.animation = "anim3 linear 10s, anim1 linear 20s, anim5 linear 10s"; + is(cs.getPropertyValue("margin-top"), "60px", + "anim3 + anim1 + anim5, margin-top at 8s, with fill mode"); + is(cs.getPropertyValue("margin-left"), "30px", + "anim3 + anim1 + anim5, margin-left at 8s"); +done_div(); + +/* + * css3-animations: 3. Keyframes + * http://dev.w3.org/csswg/css3-animations/#keyframes + * + * Also see test_keyframes_rules.html . + */ + +// Test the rules on keyframes that lack a 0% or 100% rule: +// (simultaneously, test that reverse animations have their keyframes +// run backwards) + +// 100px at 0%, 50px at 50%, 150px at 100% +new_div("margin-top: 100px; animation: kf1 ease 1s alternate infinite"); +is(cs.marginTop, "100px", "no-0% at 0.0s"); +advance_clock(100); +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.2), 0.01, + "no-0% at 0.1s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.6), 0.01, + "no-0% at 0.3s"); +advance_clock(200); +is(cs.marginTop, "50px", "no-0% at 0.5s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.4), 0.01, + "no-0% at 0.7s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.8), 0.01, + "no-0% at 0.9s"); +advance_clock(100); +is(cs.marginTop, "150px", "no-0% at 1.0s"); +advance_clock(100); +is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.8), 0.01, + "no-0% at 1.1s"); +advance_clock(300); +is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.2), 0.01, + "no-0% at 1.4s"); +advance_clock(300); +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.6), 0.01, + "no-0% at 1.7s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.2), 0.01, + "no-0% at 1.9s"); +advance_clock(100); +is(cs.marginTop, "100px", "no-0% at 2.0s"); +done_div(); + +// 150px at 0%, 50px at 50%, 100px at 100% +new_div("margin-top: 100px; animation: kf2 ease-in 1s alternate infinite"); +is(cs.marginTop, "150px", "no-100% at 0.0s"); +advance_clock(100); +is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.2), 0.01, + "no-100% at 0.1s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.6), 0.01, + "no-100% at 0.3s"); +advance_clock(200); +is(cs.marginTop, "50px", "no-100% at 0.5s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.4), 0.01, + "no-100% at 0.7s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.8), 0.01, + "no-100% at 0.9s"); +advance_clock(100); +is(cs.marginTop, "100px", "no-100% at 1.0s"); +advance_clock(100); +is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.8), 0.01, + "no-100% at 1.1s"); +advance_clock(300); +is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.2), 0.01, + "no-100% at 1.4s"); +advance_clock(300); +is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.6), 0.01, + "no-100% at 1.7s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.2), 0.01, + "no-100% at 1.9s"); +advance_clock(100); +is(cs.marginTop, "150px", "no-100% at 2.0s"); +done_div(); + + +// 50px at 0%, 100px at 25%, 50px at 100% +new_div("margin-top: 50px; animation: kf3 ease-out 1s alternate infinite"); +is(cs.marginTop, "50px", "no-0%-no-100% at 0.0s"); +advance_clock(50); +is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.2), 0.01, + "no-0%-no-100% at 0.05s"); +advance_clock(100); +is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.6), 0.01, + "no-0%-no-100% at 0.15s"); +advance_clock(100); +is(cs.marginTop, "100px", "no-0%-no-100% at 0.25s"); +advance_clock(300); +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.4), 0.01, + "no-0%-no-100% at 0.55s"); +advance_clock(300); +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.8), 0.01, + "no-0%-no-100% at 0.85s"); +advance_clock(150); +is(cs.marginTop, "50px", "no-0%-no-100% at 1.0s"); +advance_clock(150); +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.8), 0.01, + "no-0%-no-100% at 1.15s"); +advance_clock(450); +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.2), 0.01, + "no-0%-no-100% at 1.6s"); +advance_clock(250); +is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.6), 0.01, + "no-0%-no-100% at 1.85s"); +advance_clock(100); +is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.2), 0.01, + "no-0%-no-100% at 1.95s"); +advance_clock(50); +is(cs.marginTop, "50px", "no-0%-no-100% at 2.0s"); +done_div(); + +// Test that non-animatable properties are ignored. +// Simultaneously, test that the block is still honored, and that +// we still override the value when two consecutive keyframes have +// the same value. +new_div("animation: kf4 ease 10s"); +is(cs.display, "block", + "non-animatable properties should be ignored (linear, 0s)"); +is(cs.marginTop, "37px", + "animatable properties should still apply (linear, 0s)"); +advance_clock(1000); +is(cs.display, "block", + "non-animatable properties should be ignored (linear, 1s)"); +is(cs.marginTop, "37px", + "animatable properties should still apply (linear, 1s)"); +done_div(); +new_div("animation: kf4 step-start 10s"); +is(cs.display, "block", + "non-animatable properties should be ignored (step-start, 0s)"); +is(cs.marginTop, "37px", + "animatable properties should still apply (step-start, 0s)"); +advance_clock(1000); +is(cs.display, "block", + "non-animatable properties should be ignored (step-start, 1s)"); +is(cs.marginTop, "37px", + "animatable properties should still apply (step-start, 1s)"); +done_div(); + +// Test cascading of the keyframes within an @keyframes rule. +new_div("animation: kf_cascade1 linear 10s"); +// 0%: 30px +// 50%: 20px +// 75%: 20px +// 85%: 30px +// 85.1%: 60px +// 100%: 70px +is(cs.paddingTop, "30px", "kf_cascade1 at 0s"); +advance_clock(2500); +is(cs.paddingTop, "25px", "kf_cascade1 at 2.5s"); +advance_clock(2500); +is(cs.paddingTop, "20px", "kf_cascade1 at 5s"); +advance_clock(2000); +is(cs.paddingTop, "20px", "kf_cascade1 at 7s"); +advance_clock(500); +is(cs.paddingTop, "20px", "kf_cascade1 at 7.5s"); +advance_clock(500); +is(cs.paddingTop, "25px", "kf_cascade1 at 8s"); +advance_clock(500); +is(cs.paddingTop, "30px", "kf_cascade1 at 8.5s"); +advance_clock(10); +is(cs.paddingTop, "60px", "kf_cascade1 at 8.51s"); +advance_clock(745); +is(cs.paddingTop, "65px", "kf_cascade1 at 9.2505s"); +done_div(); + +// Test cascading of the @keyframes rules themselves. +new_div("animation: kf_cascade2 linear 10s"); +is(cs.marginTop, "0px", "@keyframes rule with margin-top should be ignored"); +is(cs.marginLeft, "300px", "last @keyframes rule with margin-left should win"); +done_div(); + +/* + * css3-animations: 3.1. Timing functions for keyframes + * http://dev.w3.org/csswg/css3-animations/#timing-functions-for-keyframes- + */ +new_div("animation: kf_tf1 ease-in 10s alternate infinite"); +is(cs.paddingBottom, "20px", + "keyframe timing functions test at 0s (test needed for flush)"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01, + "keyframe timing functions test at 1s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.8), 0.01, + "keyframe timing functions test at 2s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01, + "keyframe timing functions test at 3s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01, + "keyframe timing functions test at 4s"); +advance_clock(1000); +is(cs.paddingBottom, "160px", + "keyframe timing functions test at 5s"); +advance_clock(1001); // avoid floating-point error +is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01, + "keyframe timing functions test at 6s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01, + "keyframe timing functions test at 7s"); +advance_clock(999); +is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01, + "keyframe timing functions test at 8s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01, + "keyframe timing functions test at 9s"); +advance_clock(1000); +is(cs.paddingBottom, "20px", + "keyframe timing functions test at 10s"); +advance_clock(20000); +is(cs.paddingBottom, "20px", + "keyframe timing functions test at 30s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01, + "keyframe timing functions test at 31s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01, + "keyframe timing functions test at 32s"); +advance_clock(999); // avoid floating-point error +is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01, + "keyframe timing functions test at 33s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01, + "keyframe timing functions test at 34s"); +advance_clock(1001); +is(cs.paddingBottom, "160px", + "keyframe timing functions test at 35s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01, + "keyframe timing functions test at 36s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01, + "keyframe timing functions test at 37s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.8), 0.01, + "keyframe timing functions test at 38s"); +advance_clock(1000); +is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01, + "keyframe timing functions test at 39s"); +advance_clock(1000); +is(cs.paddingBottom, "20px", + "keyframe timing functions test at 40s"); +done_div(); + +// spot-check the same thing without alternate +new_div("animation: kf_tf1 ease-in 10s infinite"); +is(cs.paddingBottom, "20px", + "keyframe timing functions test at 0s (test needed for flush)"); +advance_clock(11000); +is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01, + "keyframe timing functions test at 11s"); +advance_clock(3000); +is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01, + "keyframe timing functions test at 14s"); +advance_clock(2001); // avoid floating-point error +is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01, + "keyframe timing functions test at 16s"); +advance_clock(1999); +is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01, + "keyframe timing functions test at 18s"); +done_div(); + +/* + * css3-animations: 3.2. The 'animation-name' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property- + */ + +// animation-name is reasonably well-tested up in the tests for Section +// 2, particularly the tests that "Test that animations continue running +// when the animation name list is changed." + +// Test that 'animation-name: none' steps the animation, and setting +// it again starts a new one. + +new_div(""); +div.style.animation = "anim2 ease-in-out 10s"; +is(cs.marginRight, "0px", "after setting animation-name to anim2"); +advance_clock(1000); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in_out(0.1), 0.01, + "before changing animation-name to none"); +div.style.animationName = "none"; +is(cs.marginRight, "0px", "after changing animation-name to none"); +advance_clock(1000); +is(cs.marginRight, "0px", "after changing animation-name to none plus 1s"); +div.style.animationName = "anim2"; +is(cs.marginRight, "0px", "after changing animation-name to anim2"); +advance_clock(1000); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in_out(0.1), 0.01, + "at 1s in animation when animation-name no longer none again"); +div.style.animationName = "none"; +is(cs.marginRight, "0px", "after changing animation-name to none"); +advance_clock(1000); +is(cs.marginRight, "0px", "after changing animation-name to none plus 1s"); +done_div(); + +/* + * css3-animations: 3.3. The 'animation-duration' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-duration-property- + */ + +// FIXME: test animation-duration of 0 (quite a bit, including interaction +// with fill-mode, count, and reversing), once I know what the right +// behavior is. + +/* + * css3-animations: 3.4. The 'animation-timing-function' Property + * http://dev.w3.org/csswg/css3-animations/#animation-timing-function_tag + */ + +// tested in tests for section 3.1 + +/* + * css3-animations: 3.5. The 'animation-iteration-count' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-iteration-count-property- + */ +new_div("animation: anim2 ease-in 10s 0.3 forwards"); +is(cs.marginRight, "0px", "animation-iteration-count test 1 at 0s"); +advance_clock(2000); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-iteration-count test 1 at 2s"); +advance_clock(900); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.29), 0.01, + "animation-iteration-count test 1 at 2.9s"); +advance_clock(100); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01, + "animation-iteration-count test 1 at 3s"); +advance_clock(100); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01, + "animation-iteration-count test 1 at 3.1s"); +advance_clock(5000); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01, + "animation-iteration-count test 1 at 8.1s"); +done_div(); + +new_div("animation: anim2 ease-in 10s 0.3" + + ", anim3 ease-out 20s 1.2 alternate forwards" + + ", anim4 ease-in-out 5s 1.6 forwards"); +is(cs.marginRight, "0px", "animation-iteration-count test 2 at 0s"); +is(cs.marginTop, "0px", "animation-iteration-count test 3 at 0s"); +is(cs.marginBottom, "0px", "animation-iteration-count test 4 at 0s"); +advance_clock(2000); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-iteration-count test 2 at 2s"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.1), 0.01, + "animation-iteration-count test 3 at 2s"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.4), 0.01, + "animation-iteration-count test 4 at 2s"); +advance_clock(900); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.29), 0.01, + "animation-iteration-count test 2 at 2.9s"); +advance_clock(200); +is(cs.marginRight, "0px", "animation-iteration-count test 2 at 3.1s"); +advance_clock(1800); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.98), 0.01, + "animation-iteration-count test 4 at 4.9s"); +advance_clock(200); +is(cs.marginRight, "0px", "animation-iteration-count test 2 at 5.1s"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.02), 0.01, + "animation-iteration-count test 4 at 5.1s"); +advance_clock(2800); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.58), 0.01, + "animation-iteration-count test 4 at 7.9s"); +advance_clock(100); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01, + "animation-iteration-count test 4 at 8s"); +advance_clock(100); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01, + "animation-iteration-count test 4 at 8.1s"); +advance_clock(11700); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.99), 0.01, + "animation-iteration-count test 3 at 19.8s"); +advance_clock(200); +is(cs.marginTop, "100px", "animation-iteration-count test 3 at 20s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.99), 0.01, + "animation-iteration-count test 3 at 20.2s"); +advance_clock(3600); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.81), 0.01, + "animation-iteration-count test 3 at 23.8s"); +advance_clock(200); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.8), 0.01, + "animation-iteration-count test 3 at 24s"); +advance_clock(200); +is(cs.marginRight, "0px", "animation-iteration-count test 2 at 25s"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.8), 0.01, + "animation-iteration-count test 3 at 25s"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01, + "animation-iteration-count test 4 at 25s"); +done_div(); + +/* + * css3-animations: 3.6. The 'animation-direction' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-direction-property- + */ + +// Tested in tests for sections 3.1 and 3.5. + +new_div("animation: anim2 ease-in 10s infinite"); +div.style.animationDirection = "normal"; +is(cs.marginRight, "0px", "animation-direction test 1 (normal) at 0s"); +div.style.animationDirection = "reverse"; +is(cs.marginRight, "100px", "animation-direction test 1 (reverse) at 0s"); +div.style.animationDirection = "alternate"; +is(cs.marginRight, "0px", "animation-direction test 1 (alternate) at 0s"); +div.style.animationDirection = "alternate-reverse"; +is(cs.marginRight, "100px", "animation-direction test 1 (alternate-reverse) at 0s"); +advance_clock(2000); +div.style.animationDirection = "normal"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-direction test 1 (normal) at 2s"); +div.style.animationDirection = "reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "animation-direction test 1 (reverse) at 2s"); +div.style.animationDirection = "alternate"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-direction test 1 (alternate) at 2s"); +div.style.animationDirection = "alternate-reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "animation-direction test 1 (alternate-reverse) at 2s"); +advance_clock(5000); +div.style.animationDirection = "normal"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.7), 0.01, + "animation-direction test 1 (normal) at 7s"); +div.style.animationDirection = "reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01, + "animation-direction test 1 (reverse) at 7s"); +div.style.animationDirection = "alternate"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.7), 0.01, + "animation-direction test 1 (alternate) at 7s"); +div.style.animationDirection = "alternate-reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01, + "animation-direction test 1 (alternate-reverse) at 7s"); +advance_clock(5000); +div.style.animationDirection = "normal"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-direction test 1 (normal) at 12s"); +div.style.animationDirection = "reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "animation-direction test 1 (reverse) at 12s"); +div.style.animationDirection = "alternate"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "animation-direction test 1 (alternate) at 12s"); +div.style.animationDirection = "alternate-reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-direction test 1 (alternate-reverse) at 12s"); +advance_clock(10000); +div.style.animationDirection = "normal"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-direction test 1 (normal) at 22s"); +div.style.animationDirection = "reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "animation-direction test 1 (reverse) at 22s"); +div.style.animationDirection = "alternate"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-direction test 1 (alternate) at 22s"); +div.style.animationDirection = "alternate-reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "animation-direction test 1 (alternate-reverse) at 22s"); +advance_clock(30000); +div.style.animationDirection = "normal"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-direction test 1 (normal) at 52s"); +div.style.animationDirection = "reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "animation-direction test 1 (reverse) at 52s"); +div.style.animationDirection = "alternate"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "animation-direction test 1 (alternate) at 52s"); +div.style.animationDirection = "alternate-reverse"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01, + "animation-direction test 1 (alternate-reverse) at 52s"); +done_div(); + +/* + * css3-animations: 3.7. The 'animation-play-state' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-play-state-property- + */ + +// simple test with just one animation +new_div(""); +div.style.animationTimingFunction = "ease"; +div.style.animationName = "anim1"; +div.style.animationDuration = "1s"; +div.style.animationDirection = "alternate"; +div.style.animationIterationCount = "2"; +is(cs.marginLeft, "0px", "animation-play-state test 1, at 0s"); +advance_clock(250); +is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 250ms"); +div.style.animationPlayState = "paused"; +is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 250ms"); +advance_clock(250); +is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 still at 500ms"); +div.style.animationPlayState = "running"; +is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 still at 500ms"); +advance_clock(500); +is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 1000ms"); +advance_clock(250); +is(cs.marginLeft, "100px", "animation-play-state test 1 at 1250ms"); +advance_clock(250); +is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 1500ms"); +div.style.animationPlayState = "paused"; +is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 1500ms"); +advance_clock(2000); +is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 3500ms"); +advance_clock(500); +is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 4000ms"); +div.style.animationPlayState = ""; +is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 4000ms"); +advance_clock(500); +is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01, + "animation-play-state test 1 at 4500ms"); +advance_clock(250); +is(cs.marginLeft, "0px", "animation-play-state test 1, at 4750ms"); +advance_clock(250); +is(cs.marginLeft, "0px", "animation-play-state test 1, at 5000ms"); +done_div(); + +// more complicated test with multiple animations (and different directions +// and iteration counts) +new_div(""); +div.style.animationTimingFunction = "ease-out, ease-in, ease-in-out"; +div.style.animationName = "anim2, anim3, anim4"; +div.style.animationDuration = "1s, 2s, 1s"; +div.style.animationDirection = "alternate, normal, normal"; +div.style.animationIterationCount = "4, 2, infinite"; +is(cs.marginRight, "0px", "animation-play-state test 2, at 0s"); +is(cs.marginTop, "0px", "animation-play-state test 3, at 0s"); +is(cs.marginBottom, "0px", "animation-play-state test 4, at 0s"); +advance_clock(250); +div.style.animationPlayState = "paused, running"; // pause 1 and 3 +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01, + "animation-play-state test 2 at 250ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.125), 0.01, + "animation-play-state test 3 at 250ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01, + "animation-play-state test 4 at 250ms"); +advance_clock(250); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01, + "animation-play-state test 2 at 500ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.25), 0.01, + "animation-play-state test 3 at 500ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01, + "animation-play-state test 4 at 500ms"); +div.style.animationPlayState = "paused, running, running"; // unpause 3 +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01, + "animation-play-state test 2 at 500ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.25), 0.01, + "animation-play-state test 3 at 500ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01, + "animation-play-state test 4 at 500ms"); +advance_clock(250); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01, + "animation-play-state test 2 at 750ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01, + "animation-play-state test 3 at 750ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.5), 0.01, + "animation-play-state test 4 at 750ms"); +div.style.animationPlayState = "running, paused"; // unpause 1, pause 2 +advance_clock(0); // notify refresh observers +advance_clock(250); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.5), 0.01, + "animation-play-state test 2 at 1000ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01, + "animation-play-state test 3 at 1000ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.75), 0.01, + "animation-play-state test 4 at 1000ms"); +div.style.animationPlayState = "paused"; // pause all +advance_clock(0); // notify refresh observers +advance_clock(3000); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.5), 0.01, + "animation-play-state test 2 at 4000ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01, + "animation-play-state test 3 at 4000ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.75), 0.01, + "animation-play-state test 4 at 4000ms"); +div.style.animationPlayState = "running, paused"; // pause 2 +advance_clock(0); // notify refresh observers +advance_clock(850); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.65), 0.01, + "animation-play-state test 2 at 4850ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01, + "animation-play-state test 3 at 4850ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01, + "animation-play-state test 4 at 4850ms"); +advance_clock(300); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.35), 0.01, + "animation-play-state test 2 at 5150ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01, + "animation-play-state test 3 at 5150ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.9), 0.01, + "animation-play-state test 4 at 5150ms"); +advance_clock(2300); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.05), 0.01, + "animation-play-state test 2 at 7450ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01, + "animation-play-state test 3 at 7450ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.2), 0.01, + "animation-play-state test 4 at 7450ms"); +advance_clock(100); +is(cs.marginRight, "0px", "animation-play-state test 2 at 7550ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01, + "animation-play-state test 3 at 7550ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01, + "animation-play-state test 4 at 7550ms"); +div.style.animationPlayState = "running"; // unpause 2 +advance_clock(0); // notify refresh observers +advance_clock(1000); +is(cs.marginRight, "0px", "animation-play-state test 2 at 7550ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.875), 0.01, + "animation-play-state test 3 at 7550ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01, + "animation-play-state test 4 at 7550ms"); +advance_clock(500); +is(cs.marginRight, "0px", "animation-play-state test 2 at 8050ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.125), 0.01, + "animation-play-state test 3 at 8050ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01, + "animation-play-state test 4 at 8050ms"); +advance_clock(1000); +is(cs.marginRight, "0px", "animation-play-state test 2 at 9050ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.625), 0.01, + "animation-play-state test 3 at 9050ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01, + "animation-play-state test 4 at 9050ms"); +advance_clock(500); +is(cs.marginRight, "0px", "animation-play-state test 2 at 9550ms"); +is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.875), 0.01, + "animation-play-state test 3 at 9550ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01, + "animation-play-state test 4 at 9550ms"); +advance_clock(500); +is(cs.marginRight, "0px", "animation-play-state test 2 at 10050ms"); +is(cs.marginTop, "0px", "animation-play-state test 3 at 10050ms"); +is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01, + "animation-play-state test 4 at 10050ms"); +done_div(); + +// an initially paused animation (bug 1063992) +new_div("animation: anim1 1s paused both"); +is(cs.marginLeft, "0px", "animation-play-state test 5, at 0s"); +advance_clock(500); +is(cs.marginLeft, "0px", "animation-play-state test 5, at 0.5s"); +div.style.animationPlayState = "running"; +is(cs.marginLeft, "0px", + "animation-play-state test 5, at 0.5s after unpausing"); +advance_clock(500); +is(cs.marginLeft, "80px", + "animation-play-state test 5, at 1s after unpaused"); +done_div(); + +/* + * css3-animations: 3.8. The 'animation-delay' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-delay-property- + */ + +// test positive delay +new_div("animation: anim2 1s 0.5s ease-out"); +is(cs.marginRight, "0px", "positive delay test at 0ms"); +advance_clock(400); +is(cs.marginRight, "0px", "positive delay test at 400ms"); +advance_clock(100); +is(cs.marginRight, "0px", "positive delay test at 500ms"); +advance_clock(100); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01, + "positive delay test at 500ms"); +done_div(); + +// test dynamic changes to delay (i.e., that we preserve the start time +// that's before the delay) +new_div("animation: anim2 1s 0.5s ease-out both"); +is(cs.marginRight, "0px", "dynamic delay delay test at 0ms"); +advance_clock(400); +is(cs.marginRight, "0px", "dynamic delay delay test at 400ms (1)"); +div.style.animationDelay = "0.2s"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.2), 0.01, + "dynamic delay delay test at 400ms (2)"); +div.style.animationDelay = "0.6s"; +advance_clock(0); +advance_clock(200); +is(cs.marginRight, "0px", "dynamic delay delay test at 600ms"); +advance_clock(200); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.2), 0.01, + "dynamic delay delay test at 800ms"); +advance_clock(1000); +is(cs.marginRight, "100px", "dynamic delay delay test at 1800ms (1)"); +div.style.animationDelay = "1.5s"; +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.3), 0.01, + "dynamic delay delay test at 1800ms (2)"); +div.style.animationDelay = "2s"; +is(cs.marginRight, "0px", "dynamic delay delay test at 1800ms (3)"); +done_div(); + +// test delay and play-state interaction +new_div("animation: anim2 1s 0.5s ease-out"); +is(cs.marginRight, "0px", "delay and play-state delay test at 0ms"); +advance_clock(400); +is(cs.marginRight, "0px", "delay and play-state delay test at 400ms"); +div.style.animationPlayState = "paused"; +advance_clock(0); +advance_clock(100); +is(cs.marginRight, "0px", "delay and play-state delay test at 500ms"); +advance_clock(500); +is(cs.marginRight, "0px", "delay and play-state delay test at 1000ms"); +div.style.animationPlayState = "running"; +advance_clock(0); +advance_clock(100); +is(cs.marginRight, "0px", "delay and play-state delay test at 1100ms"); +advance_clock(100); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01, + "delay and play-state delay test at 1200ms"); +div.style.animationPlayState = "paused"; +advance_clock(0); +advance_clock(100); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01, + "delay and play-state delay test at 1300ms"); +done_div(); + +// test negative delay and implicit starting values +new_div("margin-top: 1000px"); +advance_clock(300); +div.style.marginTop = "100px"; +div.style.animation = "kf1 1s -0.1s ease-in"; +is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_in(0.2), 0.01, + "delay and implicit starting values test"); +done_div(); + +// test large negative delay that causes the animation to start +// in the fourth iteration +new_div("animation: anim2 1s -3.6s ease-in 5 alternate forwards"); +listen(); // rely on no flush having happened yet +cs.animationName; // flush styles so animation is created +advance_clock(0); // complete pending animation start +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01, + "large negative delay test at 0ms"); +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 3.6, + pseudoElement: "" }, + { type: 'animationiteration', target: div, + animationName: 'anim2', elapsedTime: 3.6, + pseudoElement: "" }], + "right after start in large negative delay test"); +advance_clock(380); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.02), 0.01, + "large negative delay test at 380ms"); +check_events([]); +advance_clock(20); +is(cs.marginRight, "0px", "large negative delay test at 400ms"); +check_events([{ type: 'animationiteration', target: div, + animationName: 'anim2', elapsedTime: 4.0, + pseudoElement: "" }], + "large negative delay test at 400ms"); +advance_clock(800); +is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01, + "large negative delay test at 1200ms"); +check_events([]); +advance_clock(200); +is(cs.marginRight, "100px", "large negative delay test at 1400ms"); +check_events([{ type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 5.0, + pseudoElement: "" }], + "large negative delay test at 1400ms"); +done_div(); + +/* + * css3-animations: 3.9. The 'animation-fill-mode' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property- + */ + +// animation-fill-mode is tested in the tests for section (2). + +/* + * css3-animations: 3.10. The 'animation' Shorthand Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property- + */ + +// shorthand vs. longhand is adequately tested by the +// property_database.js-based tests. + +/** + * Basic tests of animations on pseudo-elements + */ +new_div(""); +listen(); +div.id = "withbefore"; +var cs_before = getComputedStyle(div, ":before"); +is(cs_before.marginRight, "0px", ":before test at 0ms"); +advance_clock(400); +is(cs_before.marginRight, "40px", ":before test at 400ms"); +advance_clock(800); +is(cs_before.marginRight, "80px", ":before test at 1200ms"); +is(cs.marginRight, "0px", ":before animation should not affect element"); +advance_clock(800); +is(cs_before.marginRight, "0px", ":before test at 2000ms"); +advance_clock(300); +is(cs_before.marginRight, "30px", ":before test at 2300ms"); +advance_clock(700); +check_events([ { type: "animationstart", animationName: "anim2", elapsedTime: 0, pseudoElement: "::before" }, + { type: "animationiteration", animationName: "anim2", elapsedTime: 1, pseudoElement: "::before" }, + { type: "animationiteration", animationName: "anim2", elapsedTime: 2, pseudoElement: "::before" }, + { type: "animationend", animationName: "anim2", elapsedTime: 3, pseudoElement: "::before" }]); +done_div(); + +new_div(""); +listen(); +div.id = "withafter"; +var cs_after = getComputedStyle(div, ":after"); +is(cs_after.marginRight, "0px", ":after test at 0ms"); +advance_clock(400); +is(cs_after.marginRight, "40px", ":after test at 400ms"); +advance_clock(800); +is(cs_after.marginRight, "80px", ":after test at 1200ms"); +is(cs.marginRight, "0px", ":after animation should not affect element"); +advance_clock(800); +is(cs_after.marginRight, "0px", ":after test at 2000ms"); +advance_clock(300); +is(cs_after.marginRight, "30px", ":after test at 2300ms"); +advance_clock(700); +check_events([ { type: "animationstart", animationName: "anim2", elapsedTime: 0, pseudoElement: "::after" }, + { type: "animationiteration", animationName: "anim2", elapsedTime: 1, pseudoElement: "::after" }, + { type: "animationiteration", animationName: "anim2", elapsedTime: 2, pseudoElement: "::after" }, + { type: "animationend", animationName: "anim2", elapsedTime: 3, pseudoElement: "::after" }]); +done_div(); + +/** + * Test handling of properties that are present in only some of the + * keyframes. + */ +new_div("animation: multiprop 1s ease-in-out alternate infinite"); +is(cs.paddingTop, "10px", "multiprop top at 0ms"); +is(cs.paddingLeft, "30px", "multiprop top at 0ms"); +advance_clock(100); +is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.2), 0.01, + "multiprop top at 100ms"); +is_approx(px_to_num(cs.paddingLeft), 30 + 20 * gTF.ease(0.4), 0.01, + "multiprop left at 100ms"); +advance_clock(200); +is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.6), 0.01, + "multiprop top at 300ms"); +is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.1), 0.01, + "multiprop left at 300ms"); +advance_clock(300); +is_approx(px_to_num(cs.paddingTop), 40 + 40 * gTF.ease_in_out(0.4), 0.01, + "multiprop top at 600ms"); +is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.7), 0.01, + "multiprop left at 600ms"); +advance_clock(200); +is_approx(px_to_num(cs.paddingTop), 80 - 80 * gTF.ease_in(0.2), 0.01, + "multiprop top at 800ms"); +is_approx(px_to_num(cs.paddingLeft), 60 - 60 * gTF.ease_in(0.2), 0.01, + "multiprop left at 800ms"); +advance_clock(400); +is_approx(px_to_num(cs.paddingTop), 80 - 80 * gTF.ease_in(0.2), 0.01, + "multiprop top at 1200ms"); +is_approx(px_to_num(cs.paddingLeft), 60 - 60 * gTF.ease_in(0.2), 0.01, + "multiprop left at 1200ms"); +advance_clock(200); +is_approx(px_to_num(cs.paddingTop), 40 + 40 * gTF.ease_in_out(0.4), 0.01, + "multiprop top at 1400ms"); +is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.7), 0.01, + "multiprop left at 1400ms"); +advance_clock(300); +is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.6), 0.01, + "multiprop top at 1700ms"); +is_approx(px_to_num(cs.paddingLeft), 50 + 10 * gTF.ease_out(0.1), 0.01, + "multiprop left at 1700ms"); +advance_clock(200); +is_approx(px_to_num(cs.paddingTop), 10 + 30 * gTF.ease(0.2), 0.01, + "multiprop top at 1900ms"); +is_approx(px_to_num(cs.paddingLeft), 30 + 20 * gTF.ease(0.4), 0.01, + "multiprop left at 1900ms"); +done_div(); + +// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=651456 -- make +// sure that refreshing of animations doesn't break when we get two +// refreshes with the same timestamp. +new_div("animation: anim2 1s linear"); +is(cs.marginRight, "0px", "bug 651456 at 0ms"); +advance_clock(100); +is(cs.marginRight, "10px", "bug 651456 at 100ms (1)"); +advance_clock(0); // still forces a refresh +is(cs.marginRight, "10px", "bug 651456 at 100ms (2)"); +advance_clock(100); +is(cs.marginRight, "20px", "bug 651456 at 200ms"); +done_div(); + +// Test that UA !important rules override animations. +// This test depends on forms.css having a rule +// select { line-height: !important } +// If that rule changes, we should rewrite it to depend on a different rule. +var select; +[ select, cs ] = new_element("select", ""); +var default_line_height = cs.lineHeight; +done_element(); +[ select, cs ] = new_element("select", + "animation: uaoverride 2s linear infinite"); +is(cs.lineHeight, default_line_height, + "animations should not override UA !important at 0ms"); +is(cs.marginTop, "20px", + "rest of animation should still work when UA !important present at 0ms"); +advance_clock(200); +is(cs.lineHeight, default_line_height, + "animations should not override UA !important at 200ms"); +is(cs.marginTop, "40px", + "rest of animation should still work when UA !important present at 200ms"); +done_element(); + +// Test that author !important rules override animations, but +// that animations override regular author rules. +new_div("animation: always_fifty 1s linear infinite; margin-left: 200px"); +is(cs.marginLeft, "50px", "animations override regular author rules"); +done_div(); +new_div("animation: always_fifty 1s linear infinite;" + + " margin-left: 200px ! important;"); +is(cs.marginLeft, "200px", "important author rules override animations"); +done_div(); + +// Test interaction of animations and restyling (Bug 686656). +// This test depends on kf3 getting its 0% and 100% values from the +// rules below it in the cascade; we're checking that the animation +// isn't rebuilt when the restyles happen. +new_div("animation: kf3 1s linear forwards"); +is(cs.marginTop, "0px", "bug 686656 test 1 at 0ms"); +advance_clock(250); +display.style.color = "blue"; +is(cs.marginTop, "100px", "bug 686656 test 1 at 250ms"); +advance_clock(375); +is(cs.marginTop, "50px", "bug 686656 test 1 at 625ms"); +advance_clock(375); +is(cs.marginTop, "0px", "bug 686656 test 1 at 1000ms"); +done_div(); +display.style.color = ""; + +// Test interaction of animations and restyling (Bug 686656), +// with reframing. +// This test depends on kf3 getting its 0% and 100% values from the +// rules below it in the cascade; we're checking that the animation +// isn't rebuilt when the restyles happen. +new_div("animation: kf3 1s linear forwards"); +is(cs.marginTop, "0px", "bug 686656 test 2 at 0ms"); +advance_clock(250); +display.style.overflow = "scroll"; +is(cs.marginTop, "100px", "bug 686656 test 2 at 250ms"); +advance_clock(375); +is(cs.marginTop, "50px", "bug 686656 test 2 at 625ms"); +advance_clock(375); +is(cs.marginTop, "0px", "bug 686656 test 2 at 1000ms"); +done_div(); +display.style.overflow = ""; + +// Test that cascading between keyframes rules is per-property rather +// than per-rule (bug ), and that the timing function isn't taken from a +// rule that's skipped. (Bug 738003) +new_div("animation: cascade 1s linear forwards; position: relative"); +is(cs.top, "0px", "cascade test (top) at 0ms"); +is(cs.left, "0px", "cascade test (top) at 0ms"); +advance_clock(125); +is(cs.top, "0px", "cascade test (top) at 125ms"); +is(cs.left, "50px", "cascade test (top) at 125ms"); +advance_clock(125); +is(cs.top, "0px", "cascade test (top) at 250ms"); +is(cs.left, "100px", "cascade test (top) at 250ms"); +advance_clock(125); +is(cs.top, "50px", "cascade test (top) at 375ms"); +is(cs.left, "100px", "cascade test (top) at 375ms"); +advance_clock(125); +is(cs.top, "100px", "cascade test (top) at 500ms"); +is(cs.left, "100px", "cascade test (top) at 500ms"); +advance_clock(125); +is(cs.top, "100px", "cascade test (top) at 625ms"); +is(cs.left, "50px", "cascade test (top) at 625ms"); +advance_clock(125); +is(cs.top, "100px", "cascade test (top) at 750ms"); +is(cs.left, "0px", "cascade test (top) at 750ms"); +advance_clock(125); +is(cs.top, "50px", "cascade test (top) at 875ms"); +is(cs.left, "0px", "cascade test (top) at 875ms"); +advance_clock(125); +is(cs.top, "0px", "cascade test (top) at 1000ms"); +is(cs.left, "0px", "cascade test (top) at 1000ms"); +done_div(); + +new_div("animation: cascade2 8s linear forwards"); +is(cs.textIndent, "0px", "cascade2 test at 0s"); +advance_clock(1000); +is(cs.textIndent, "25px", "cascade2 test at 1s"); +advance_clock(1000); +is(cs.textIndent, "50px", "cascade2 test at 2s"); +advance_clock(1000); +is(cs.textIndent, "25px", "cascade2 test at 3s"); +advance_clock(1000); +is(cs.textIndent, "0px", "cascade2 test at 4s"); +advance_clock(3000); +is(cs.textIndent, "75px", "cascade2 test at 7s"); +advance_clock(1000); +is(cs.textIndent, "100px", "cascade2 test at 8s"); +done_div(); + +new_div("-moz-animation: primitives1 2s linear forwards"); +is(cs.getPropertyValue("-moz-transform"), "matrix(1, 0, 0, 1, 0, 0)", + "primitives1 at 0s"); +advance_clock(1000); +is(cs.getPropertyValue("-moz-transform"), + "matrix(-0.707107, 0.707107, -0.707107, -0.707107, 0, 0)", + "primitives1 at 1s"); +advance_clock(1000); +is(cs.getPropertyValue("-moz-transform"), "matrix(0, -1, 1, 0, 0, 0)", + "primitives1 at 0s"); +done_div(); + +new_div("animation: important1 1s linear forwards"); +is(cs.marginTop, "50px", "important1 test at 0s"); +advance_clock(500); +is(cs.marginTop, "75px", "important1 test at 0.5s"); +advance_clock(500); +is(cs.marginTop, "100px", "important1 test at 1s"); +done_div(); + +new_div("animation: important2 1s linear forwards"); +is(cs.marginTop, "50px", "important2 (margin-top) test at 0s"); +is(cs.marginBottom, "100px", "important2 (margin-bottom) test at 0s"); +advance_clock(1000); +is(cs.marginTop, "0px", "important2 (margin-top) test at 1s"); +is(cs.marginBottom, "50px", "important2 (margin-bottom) test at 1s"); +done_div(); + +// Test that it's the length of the 'animation-name' list that's used to +// start animations. +// note: anim2 animates margin-right from 0 to 100px +// note: anim3 animates margin-top from 0 to 100px +new_div("animation-name: anim2, anim3;" + + " animation-duration: 1s;" + + " animation-timing-function: linear;" + + " animation-delay: -250ms, -250ms, -750ms, -500ms;"); +is(cs.marginRight, "25px", "animation-name list length is the length that matters"); +is(cs.marginTop, "25px", "animation-name list length is the length that matters"); +done_div(); +new_div("animation-name: anim2, anim3, anim2;" + + " animation-duration: 1s;" + + " animation-timing-function: linear;" + + " animation-delay: -250ms, -250ms, -750ms, -500ms;"); +is(cs.marginRight, "75px", "animation-name list length is the length that matters, and the last occurrence of a name wins"); +is(cs.marginTop, "25px", "animation-name list length is the length that matters"); +done_div(); + +var dyn_sheet_elt = document.createElement("style"); +document.head.appendChild(dyn_sheet_elt); +var dyn_sheet = dyn_sheet_elt.sheet; +dyn_sheet.insertRule("@keyframes dyn1 { from { margin-left: 0 } 50% { margin-left: 50px } to { margin-left: 100px } }", 0); +dyn_sheet.insertRule("@keyframes dyn2 { from { margin-left: 100px } to { margin-left: 200px } }", 1); +var dyn1 = dyn_sheet.cssRules[0]; +var dyn2 = dyn_sheet.cssRules[1]; +new_div("animation: dyn1 1s linear"); +is(cs.marginLeft, "0px", "dynamic rule change test, initial state"); +advance_clock(250); +is(cs.marginLeft, "25px", "dynamic rule change test, 250ms"); +dyn2.name = "dyn1"; +is(cs.marginLeft, "125px", "dynamic rule change test, change in @keyframes name applies"); +dyn2.appendRule("50% { margin-left: 0px }"); +is(cs.marginLeft, "50px", "dynamic rule change test, @keyframes appendRule"); +var dyn2_kf1 = dyn2.cssRules[0]; // currently 0% { margin-left: 100px } +dyn2_kf1.style.marginLeft = "-100px"; +is(cs.marginLeft, "-50px", "dynamic rule change test, keyframe style set"); +dyn2.name = "dyn2"; +is(cs.marginLeft, "25px", "dynamic rule change test, change in @keyframes name applies (second time)"); +var dyn1_kf2 = dyn1.cssRules[1]; // currently 50% { margin-left: 50px } +dyn1_kf2.keyText = "25%"; +is(cs.marginLeft, "50px", "dynamic rule change test, change in keyframe keyText"); +dyn1.deleteRule("25%"); +is(cs.marginLeft, "25px", "dynamic rule change test, @keyframes deleteRule"); +done_div(); +dyn_sheet_elt.parentNode.removeChild(dyn_sheet_elt); +dyn_sheet_elt = null; +dyn_sheet = null; + +/* + * Bug 1004361 - CSS animations with short duration sometimes don't dispatch + * a start event + */ +new_div("animation: anim2 1s 0.1s"); +listen(); +advance_clock(0); // Trigger animation +advance_clock(1200); // Skip past end of animation's entire active duration +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 1, + pseudoElement: "" }], + "events after skipping over animation interval"); +done_div(); + +/* + * Bug 1007513 - AnimationEvent.elapsedTime should be animation time + */ +new_div("animation: anim2 1s 2"); +listen(); +advance_clock(0); // Trigger animation +advance_clock(500); // Jump to middle of first interval +advance_clock(1000); // Jump to middle of second interval +advance_clock(1000); // Jump past end of last interval +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationiteration', target: div, + animationName: 'anim2', elapsedTime: 1, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 2, + pseudoElement: "" }], + "events after skipping past event moments"); +done_div(); + +new_div("animation: anim2 1s -2s"); +listen(); +cs.animationName; // build animation +advance_clock(0); // finish pending +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 1, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 1, + pseudoElement: "" }], + "events after skipping over animation with negative delay"); +done_div(); + +/* + * Bug 1004365 - zero-duration animations + */ + +new_div("margin-right: 200px; animation: anim2 0s 1s both"); +listen(); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during backwards fill of zero-duration animation"); +advance_clock(2000); // Skip over animation +is(cs.getPropertyValue("margin-right"), "100px", + "margin-right during forwards fill of zero-duration animation"); +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }], + "events after skipping over zero-duration animation"); +done_div(); + +new_div("margin-right: 200px; animation: anim2 0s 1s both"); +listen(); +advance_clock(0); +// Seek to just before the animation starts and stops +advance_clock(999); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right at exact end of zero-duration animation"); +check_events([]); +// Seek to exactly the point where the animation starts and stops +advance_clock(1); +is(cs.getPropertyValue("margin-right"), "100px", + "margin-right at exact end of zero-duration animation"); +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }], + "events after seeking to end of zero-duration animation"); +// Check no further events are dispatched +advance_clock(0); +advance_clock(100); +check_events([]); +done_div(); + +// Test with animation-direction reverse +new_div("margin-right: 200px;" + + " animation: anim2 0s 1s both reverse"); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "100px", + "margin-right during backwards fill of reversed zero-duration animation"); +advance_clock(2000); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during forwards fill of reversed zero-duration animation"); +done_div(); + +// Test with animation-direction alternate +new_div("margin-right: 200px; animation: anim2 0s 1s both alternate 2"); +listen(); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during backwards fill of alternating zero-duration animation"); +advance_clock(2000); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during forwards fill of alternating zero-duration animation"); +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }], + "events after seeking to end of zero-duration animation" + + " that repeats twice"); +done_div(); + +// Test with animation-direction alternate and odd number of iterations +new_div("margin-right: 200px; animation: anim2 0s 1s both alternate 3"); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during backwards fill of alternating zero-duration " + + "animation with odd number of iterations"); +advance_clock(2000); +is(cs.getPropertyValue("margin-right"), "100px", + "margin-right during forwards fill of alternating zero-duration " + + "animation with odd number of iterations"); +done_div(); + +// Test with animation-direction alternate and non-integral number of iterations +new_div("margin-right: 200px;" + + " animation: anim2 0s 1s both alternate 7.3 linear"); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during backwards fill of alternating zero-duration " + + "animation with non-integral number of iterations"); +advance_clock(2000); +is(cs.getPropertyValue("margin-right"), "70px", + "margin-right during forwards fill of alternating zero-duration " + + "animation with non-integral number of iterations"); +done_div(); + +// Test with infinite iteration count +// CSS Animations doesn't actually define what the behavior is in this case +// (and many many other similar cases) so we follow the behavior defined in Web +// Animations which is that the zero-duration "wins". +new_div("margin-right: 200px; animation: anim2 0s 1s both infinite"); +listen(); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during backwards fill of infinitely repeating " + + "zero-duration animation"); +advance_clock(2000); +is(cs.getPropertyValue("margin-right"), "100px", + "margin-right during forwards fill of infinitely repeating " + + "zero-duration animation"); +// Check we don't get infinite iteration events :) +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }], + "events after seeking to end of infinitely repeating " + + "zero-duration animation"); +done_div(); + +// Test with infinite iteration count and alternating direction +new_div("margin-right: 200px; animation: anim2 0s 1s alternate both infinite"); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during backwards fill of infinitely repeating and " + + "alternating zero-duration animation"); +advance_clock(2000); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during forwards fill of infinitely repeating and " + + "alternating zero-duration animation"); +done_div(); + +// Test with infinite iteration count and alternate-reverse direction +new_div("margin-right: 200px;" + + " animation: anim2 0s 1s alternate-reverse infinite both"); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "100px", + "margin-right during backwards fill of infinitely repeating and " + + "alternate-reverse zero-duration animation"); +advance_clock(2000); +is(cs.getPropertyValue("margin-right"), "100px", + "margin-right during forwards fill of infinitely repeating and " + + "alternate-reverse zero-duration animation"); +done_div(); + +// Test with negative delay +new_div("margin-right: 200px;" + + " animation: anim2 0s -1s both reverse 12.7 linear"); +listen(); +cs.animationName; // build animation +advance_clock(0); // finish pending +is(cs.getPropertyValue("margin-right"), "30px", + "margin-right during forwards fill of reversed and repeated " + + "zero-duration animation with negative delay"); +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }], + "events after skipping over zero-duration animation " + + "with negative delay"); +done_div(); + +// Test zero duration with zero iteration count +new_div("margin-right: 200px; animation: anim2 0s 1s both 0"); +listen(); +advance_clock(0); +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during backwards fill of zero-duration animation"); +advance_clock(2000); // Skip over animation +is(cs.getPropertyValue("margin-right"), "0px", + "margin-right during forwards fill of zero-duration animation"); +check_events([{ type: 'animationstart', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }], + "events after skipping over zero-duration, zero iteration count" + + " animation"); +done_div(); + +/* + * Bug 1004377 - Animations with empty keyframes rule + */ + +new_div("margin-right: 200px; animation: empty 2s 1s both"); +listen(); +advance_clock(0); +check_events([], "events during delay"); +advance_clock(2000); // Skip to middle of animation +div.clientTop; // Trigger events +check_events([{ type: 'animationstart', target: div, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "middle of animation with empty keyframes rule"); +advance_clock(1000); // Skip to end of animation +div.clientTop; // Trigger events +check_events([{ type: 'animationend', target: div, + animationName: 'empty', elapsedTime: 2, + pseudoElement: "" }], + "end of animation with empty keyframes rule"); +done_div(); + +// Test with a zero-duration animation and empty @keyframes rule +new_div("margin-right: 200px; animation: empty 0s 1s both"); +listen(); +advance_clock(0); +advance_clock(1000); +div.clientTop; // Trigger events +check_events([{ type: 'animationstart', target: div, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: div, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "end of zero-duration animation with empty keyframes rule"); +done_div(); + +// Test with a keyframes rule that becomes empty +new_div("animation: nearlyempty 1s both linear"); +advance_clock(0); +advance_clock(500); +is(cs.getPropertyValue("margin-left"), "50px", + "margin-left for animation that is about to be emptied"); +listen(); +findKeyframesRule("nearlyempty").deleteRule("to"); +is(cs.getPropertyValue("margin-left"), "0px", + "margin-left for animation with (now) empty keyframes rule"); +check_events([], "events after emptying keyframes rule"); +advance_clock(500); +div.clientTop; // Trigger events +check_events([{ type: 'animationend', target: div, + animationName: 'nearlyempty', elapsedTime: 1, + pseudoElement: "" }], + "events at end of animation with newly " + + "empty keyframes rule"); +done_div(); + +// Test when we update to point to an empty animation +new_div("animation: always_fifty 1s both linear"); +advance_clock(0); +advance_clock(500); +is(cs.getPropertyValue("margin-left"), "50px", + "margin-left for animation that will soon point to an empty keyframes rule"); +listen(); +div.style.animationName = "empty"; +is(cs.getPropertyValue("margin-left"), "0px", + "margin-left for animation now points to empty keyframes rule"); +advance_clock(500); +div.clientTop; // Trigger events +check_events([{ type: 'animationstart', target: div, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "events at start of animation updated to use " + + "empty keyframes rule"); +done_div(); + +/* + * Bug 1031319 - 'none' animations + * + * The code under test here is run entirely on the main thread so there is no + * OMTA version of these tests in test_animations_omta.html. + */ + +// Setting "animation: none" after animations have finished should not trigger +// animation events +new_div("animation: always_fifty 1s"); +listen(); +advance_clock(0); +advance_clock(1000); +check_events([{ type: 'animationstart', target: div, + animationName: 'always_fifty', elapsedTime: 0, + pseudoElement: '' }, + { type: 'animationend', target: div, + animationName: 'always_fifty', elapsedTime: 1, + pseudoElement: '' }], + "events after running initial animation"); +div.style.animation = "none"; +div.clientTop; // Trigger events +check_events([], "events after setting animation to 'none'"); +done_div(); + +// Setting "animation: " after animations have finished should not trigger +// animation events +new_div("animation: always_fifty 1s"); +listen(); +advance_clock(0); +advance_clock(1000); +check_events([{ type: 'animationstart', target: div, + animationName: 'always_fifty', elapsedTime: 0, + pseudoElement: '' }, + { type: 'animationend', target: div, + animationName: 'always_fifty', elapsedTime: 1, + pseudoElement: '' }], + "events after running initial animation"); +div.style.animation = ""; +div.clientTop; // Trigger events +check_events([], "events after setting animation to ''"); +done_div(); + +// Setting "animation: none 1s" should not trigger events +new_div("animation: none 1s"); +listen(); +advance_clock(0); +advance_clock(1000); +check_events([], "events after setting animation to 'none 1s'"); +done_div(); + +// Setting "animation: 1s" should not trigger events +new_div("animation: 1s"); +listen(); +advance_clock(0); +advance_clock(1000); +check_events([], "events after setting animation to '1s'"); +done_div(); + +// Setting animation-name: none among other animations should cause only that +// animation to be skipped +new_div("animation-name: always_fifty, none, always_fifty;" + + " animation-duration: 1s"); +listen(); +advance_clock(0); +advance_clock(500); +advance_clock(500); +check_events([{ type: 'animationstart', target: div, + animationName: 'always_fifty', elapsedTime: 0, + pseudoElement: '' }, + { type: 'animationstart', target: div, + animationName: 'always_fifty', elapsedTime: 0, + pseudoElement: '' }, + { type: 'animationend', target: div, + animationName: 'always_fifty', elapsedTime: 1, + pseudoElement: '' }, + { type: 'animationend', target: div, + animationName: 'always_fifty', elapsedTime: 1, + pseudoElement: '' }], + "events for animation-name: a, none, a"); +done_div(); + +/* + * Bug 1033881 - Non-matching animation-name + * + * The code under test here is run entirely on the main thread so there is no + * OMTA version of these tests in test_animations_omta.html. + */ + +new_div("animation-name: non_existent, always_fifty; animation-duration: 1s"); +listen(); +advance_clock(0); +advance_clock(500); +advance_clock(500); +check_events([{ type: 'animationstart', target: div, + animationName: 'always_fifty', elapsedTime: 0, + pseudoElement: '' }, + { type: 'animationend', target: div, + animationName: 'always_fifty', elapsedTime: 1, + pseudoElement: '' }], + "events for animation-name: non_existent, always_fifty"); +done_div(); + +/* + * Bug 1038032 - Infinite repetition and delay causes overflow + */ +new_div("animation: always_fifty 10s 1s infinite"); +advance_clock(0); +advance_clock(2000); +is(cs.marginLeft, "50px", + "infinitely repeating animation with positive delay takes effect" + + " (does not overflow)"); +done_div(); + +/* + * Bug 1140134 - A property in a CSS animation being overridden by later + * animation causes later properties in that animation to be skipped + */ +new_div("position: relative; animation: lowerpriority 1s linear infinite alternate, overridetop 1s linear infinite alternate"); +advance_clock(0); +advance_clock(500); +is(cs.getPropertyValue("left"), "50px", "left is animating"); +is(cs.getPropertyValue("top"), "0px", "top is not animating"); +done_div(); + +new_div("position: relative; animation: lowerpriority 1s linear infinite alternate, overrideleft 1s linear infinite alternate"); +advance_clock(0); +advance_clock(500); +is(cs.getPropertyValue("left"), "0px", "left is not animating"); +is(cs.getPropertyValue("top"), "50px", "top is animating"); +done_div(); + +/* + * Bug 962594 - Turn off CSS animations when the element is display:none, or + * is in a display:none subtree. + */ + +// Helper function for the two tests below +function testDisplayNoneTurnsOffAnimations(aTestName, aElementToDisplayNone) { + is(cs.getPropertyValue("margin-right"), "0px", + aTestName + "margin-right at 0s"); + advance_clock(1000); + is(cs.getPropertyValue("margin-right"), "10px", + aTestName + "margin-right at 1s"); + aElementToDisplayNone.style.display = "none"; + is(cs.getPropertyValue("margin-right"), "0px", + aTestName + "margin-right after display:none"); + advance_clock(1000); + is(cs.getPropertyValue("margin-right"), "0px", + aTestName + "margin-right 1s after display:none"); + aElementToDisplayNone.style.display = ""; + is(cs.getPropertyValue("margin-right"), "0px", + aTestName + "margin-right after display:block"); + advance_clock(1000); + is(cs.getPropertyValue("margin-right"), "10px", + aTestName + "margin-right 1s after display:block"); +} + +// Check that it works if the animated element itself becomes display:none +new_div("animation: anim2 linear 10s"); +testDisplayNoneTurnsOffAnimations("AnimatedElement ", div); +done_div(); + +// Check that it works if an ancestor of the animated element becomes display:none +new_div("animation: anim2 linear 10s"); +var ancestor = document.createElement("div"); +div.parentNode.insertBefore(ancestor, div); +ancestor.appendChild(div); +testDisplayNoneTurnsOffAnimations("AncestorElement ", ancestor); +ancestor.parentNode.insertBefore(div, ancestor); +ancestor.parentNode.removeChild(ancestor); +done_div(); + + +/* + * Bug 1125455 - Transitions should not run when animations are running. + */ +new_div("transition: opacity 2s linear; opacity: 0.8"); +advance_clock(0); +is(cs.getPropertyValue("opacity"), "0.8", "initial opacity"); +div.style.opacity = "0.2"; +is(cs.getPropertyValue("opacity"), "0.8", "opacity transition at 0s"); +advance_clock(500); +is(cs.getPropertyValue("opacity"), "0.65", "opacity transition at 0.5s"); +div.style.animation = "opacitymid 2s linear"; +is(cs.getPropertyValue("opacity"), "0.2", "opacity animation overriding transition at 0s"); +advance_clock(500); +is(cs.getPropertyValue("opacity"), "0.35", "opacity animation overriding transition at 0.5s"); +done_div(); + +SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_async_tests.html b/layout/style/test/test_animations_async_tests.html new file mode 100644 index 000000000..5d328c7aa --- /dev/null +++ b/layout/style/test/test_animations_async_tests.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1086937</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + + function run() { + SpecialPowers.pushPrefEnv( + {"set": [['layout.css.font-loading-api.enabled', true]]}, + function() { window.open("file_animations_async_tests.html"); }); + } + </script> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a> +<div id="display"></div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_dynamic_changes.html b/layout/style/test/test_animations_dynamic_changes.html new file mode 100644 index 000000000..a68d734dc --- /dev/null +++ b/layout/style/test/test_animations_dynamic_changes.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=978833 +--> +<head> + <title>Test for Bug 978833</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="style"> + @keyframes a { + from, to { + /* a non-inherited property, so it's cached in the rule tree */ + margin-left: 50px; + } + } + .alwaysa { + animation: a linear 1s infinite; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978833">Mozilla Bug 978833</a> +<p id="display"></p> +<pre id="test"> +<script type="application/javascript"> + +var p = document.getElementById("display"); +var cs = getComputedStyle(p, ""); +var style = document.getElementById("style").sheet; + +/** Test for Bug 978833 **/ +function test_bug978833() { + var kfs = style.cssRules[0]; + var kf = kfs.cssRules[0]; + is(kf.style.marginLeft, "50px", "we found the right keyframe rule"); + + p.classList.add("alwaysa"); + is(cs.marginLeft, "50px", "p margin-left should be 50px"); + + // Temporarily remove the animation style, since we resolve keyframes + // on top of current animation styles (although maybe we shouldn't), + // so we need to remove those styles to hit the rule tree cache. + p.classList.remove("alwaysa"); + is(cs.marginLeft, "0px", "p margin-left should be 0px without animation"); + + p.classList.add("alwaysa"); + kf.style.marginLeft = "100px"; + is(cs.marginLeft, "100px", "p margin-left should be 100px after change"); + + // Try the same thing a second time, just to make sure it works again. + p.classList.remove("alwaysa"); + is(cs.marginLeft, "0px", "p margin-left should be 0px without animation"); + p.classList.add("alwaysa"); + kf.style.marginLeft = "150px"; + is(cs.marginLeft, "150px", "p margin-left should be 150px after second change"); + + p.style.animation = ""; +} +test_bug978833(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_effect_timing_duration.html b/layout/style/test/test_animations_effect_timing_duration.html new file mode 100644 index 000000000..30d77f20a --- /dev/null +++ b/layout/style/test/test_animations_effect_timing_duration.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for animation.effect.timing on compositor</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + { "set": [[ "dom.animations-api.core.enabled", true]] }, + function() { + window.open("file_animations_effect_timing_duration.html"); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_effect_timing_enddelay.html b/layout/style/test/test_animations_effect_timing_enddelay.html new file mode 100644 index 000000000..d4ad918dd --- /dev/null +++ b/layout/style/test/test_animations_effect_timing_enddelay.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for animation.effect.timing.endDelay on compositor</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + { "set": [[ "dom.animations-api.core.enabled", true]] }, + function() { + window.open("file_animations_effect_timing_enddelay.html"); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_effect_timing_iterations.html b/layout/style/test/test_animations_effect_timing_iterations.html new file mode 100644 index 000000000..625c52867 --- /dev/null +++ b/layout/style/test/test_animations_effect_timing_iterations.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for animation.effect.timing.iterations on compositor</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + { "set": [[ "dom.animations-api.core.enabled", true]] }, + function() { + window.open("file_animations_effect_timing_iterations.html"); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_event_handler_attribute.html b/layout/style/test/test_animations_event_handler_attribute.html new file mode 100644 index 000000000..e5def2b34 --- /dev/null +++ b/layout/style/test/test_animations_event_handler_attribute.html @@ -0,0 +1,147 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=911987 +--> +<head> + <meta charset=utf-8> + <title>Test for CSS Animation and Transition event handler + attributes. (Bug 911987)</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + @keyframes anim { to { margin-left: 100px } } + </style> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=911987">Mozilla Bug + 911987</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +'use strict'; + +// Create the div element with event handlers. +// We need two elements: one with the content attribute speficied and one +// with the IDL attribute specified since we can't set these independently. +function createAndRegisterTargets(eventAttributes) { + var displayElement = document.getElementById('display'); + var contentAttributeElement = document.createElement("div"); + var idlAttributeElement = document.createElement("div"); + displayElement.appendChild(contentAttributeElement); + displayElement.appendChild(idlAttributeElement); + + // Add handlers + eventAttributes.forEach(event => { + contentAttributeElement.setAttribute(event, 'handleEvent(event);'); + contentAttributeElement.handlerType = 'content attribute'; + idlAttributeElement[event] = handleEvent; + idlAttributeElement.handlerType = 'IDL attribute'; + }); + + return [contentAttributeElement, idlAttributeElement]; +} + +function handleEvent(event) { + if (event.target.receivedEventType) { + ok(false, `Received ${event.type} event, but this element have previous ` + `received event '${event.target.receivedEventType}'.`); + return; + } + event.target.receivedEventType = event.type; +} + +function checkReceivedEvents(eventType, elements) { + elements.forEach(element => { + is(element.receivedEventType, eventType, + `Expected to receive '${eventType}', got + '${element.receivedEventType}', for event handler registered + using ${element.handlerType}`); + element.receivedEventType = undefined; + }); +} + +// Take over the refresh driver right from the start. +advance_clock(0); + +// 1. Test CSS Animation event handlers. + +var targets = createAndRegisterTargets([ 'onanimationstart', + 'onanimationiteration', + 'onanimationend' ]); +targets.forEach(div => { + div.setAttribute('style', 'animation: anim 100ms 2'); + getComputedStyle(div).animationName; // flush +}); + +advance_clock(0); +checkReceivedEvents("animationstart", targets); + +advance_clock(100); +checkReceivedEvents("animationiteration", targets); + +advance_clock(200); +checkReceivedEvents("animationend", targets); + +targets.forEach(div => { div.remove(); }); + +// 2. Test CSS Transition event handlers. + +var targets = createAndRegisterTargets([ 'ontransitionend' ]); +targets.forEach(div => { + div.style.transition = 'margin-left 100ms'; + getComputedStyle(div).marginLeft; // flush + div.style.marginLeft = "200px"; + getComputedStyle(div).marginLeft; // flush +}); + +advance_clock(100); +checkReceivedEvents("transitionend", targets); + +targets.forEach(div => { div.remove(); }); + +// 3. Test prefixed CSS Animation event handlers. + +var targets = createAndRegisterTargets([ 'onwebkitanimationstart', + 'onwebkitanimationiteration', + 'onwebkitanimationend' ]); +targets.forEach(div => { + div.setAttribute('style', 'animation: anim 100ms 2'); + getComputedStyle(div).animationName; // flush +}); + +advance_clock(0); +checkReceivedEvents("webkitAnimationStart", targets); + +advance_clock(100); +checkReceivedEvents("webkitAnimationIteration", targets); + +advance_clock(200); +checkReceivedEvents("webkitAnimationEnd", targets); + +targets.forEach(div => { div.remove(); }); + +// 4. Test prefixed CSS Transition event handlers. + +advance_clock(0); +var targets = createAndRegisterTargets([ 'onwebkittransitionend' ]); +targets.forEach(div => { + div.style.transition = 'margin-left 100ms'; + getComputedStyle(div).marginLeft; // flush + div.style.marginLeft = "200px"; + getComputedStyle(div).marginLeft; // flush +}); + +advance_clock(100); +checkReceivedEvents("webkitTransitionEnd", targets); + +targets.forEach(div => { div.remove(); }); + +SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + +</script> +</body> +</html> diff --git a/layout/style/test/test_animations_event_order.html b/layout/style/test/test_animations_event_order.html new file mode 100644 index 000000000..5af7639cc --- /dev/null +++ b/layout/style/test/test_animations_event_order.html @@ -0,0 +1,585 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1183461 +--> +<!-- + This test is similar to those in test_animations.html with the exception + that those tests interact with a single element at a time. The tests in this + file are specifically concerned with testing the ordering of events between + elements for which most of the utilities in animation_utils.js are not + suited. +--> +<head> + <meta charset=utf-8> + <title>Test for CSS Animation and Transition event ordering + (Bug 1183461)</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <!-- We still need animation_utils.js for advance_clock --> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + @keyframes anim { to { margin-left: 100px } } + @keyframes animA { to { margin-left: 100px } } + @keyframes animB { to { margin-left: 100px } } + @keyframes animC { to { margin-left: 100px } } + </style> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1183461">Mozilla Bug + 1183461</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +'use strict'; + +// Take over the refresh driver right from the start. +advance_clock(0); + +// Common test scaffolding + +var gEventsReceived = []; +var gDisplay = document.getElementById('display'); + +[ 'animationstart', + 'animationiteration', + 'animationend', + 'transitionend' ] + .forEach(event => + gDisplay.addEventListener(event, + event => gEventsReceived.push(event), + false)); + +function checkEventOrder(...args) { + // Argument format: + // Arguments = ExpectedEvent*, desc + // ExpectedEvent = + // [ target|animationName|transitionProperty, (pseudo,) message ] + var expectedEvents = args.slice(0, -1); + var desc = args[args.length - 1]; + var isTestingNameOrProperty = expectedEvents.length && + typeof expectedEvents[0][0] == 'string'; + + var formatEvent = (target, nameOrProperty, pseudo, message) => + isTestingNameOrProperty ? + `${nameOrProperty}${pseudo}:${message}` : + `${target.id}${pseudo}:${message}`; + + var actual = gEventsReceived.map( + event => formatEvent(event.target, + event.animationName || event.propertyName, + event.pseudoElement, event.type) + ).join(';'); + var expected = expectedEvents.map( + event => event.length == 3 ? + formatEvent(event[0], event[0], event[1], event[2]) : + formatEvent(event[0], event[0], '', event[1]) + ).join(';'); + is(actual, expected, desc); + gEventsReceived = []; +} + +// 1. TESTS FOR SORTING BY TREE ORDER + +// 1a. Test that simultaneous events are sorted by tree order (siblings) + +var divs = [ document.createElement('div'), + document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.setAttribute('style', 'animation: anim 10s'); + div.setAttribute('id', 'div' + i); + getComputedStyle(div).animationName; // trigger building of animation +}); + +advance_clock(0); +checkEventOrder([ divs[0], 'animationstart' ], + [ divs[1], 'animationstart' ], + [ divs[2], 'animationstart' ], + 'Simultaneous start on siblings'); + +divs.forEach(div => div.remove()); +divs = []; + +// 1b. Test that simultaneous events are sorted by tree order (children) + +divs = [ document.createElement('div'), + document.createElement('div'), + document.createElement('div') ]; + +// Create the following arrangement: +// +// display +// / \ +// div[0] div[1] +// / +// div[2] + +gDisplay.appendChild(divs[0]); +gDisplay.appendChild(divs[1]); +divs[0].appendChild(divs[2]); + +divs.forEach((div, i) => { + div.setAttribute('style', 'animation: anim 10s'); + div.setAttribute('id', 'div' + i); + getComputedStyle(div).animationName; // trigger building of animation +}); + +advance_clock(0); +checkEventOrder([ divs[0], 'animationstart' ], + [ divs[2], 'animationstart' ], + [ divs[1], 'animationstart' ], + 'Simultaneous start on children'); + +divs.forEach(div => div.remove()); +divs = []; + +// 1c. Test that simultaneous events are sorted by tree order (pseudos) + +divs = [ document.createElement('div'), + document.createElement('div') ]; + +// Create the following arrangement: +// +// display +// | +// div[0] +// ::before, +// ::after +// | +// div[1] + +gDisplay.appendChild(divs[0]); +divs[0].appendChild(divs[1]); + +divs.forEach((div, i) => { + div.setAttribute('style', 'animation: anim 10s'); + div.setAttribute('id', 'div' + i); +}); + +var extraStyle = document.createElement('style'); +document.head.appendChild(extraStyle); +var sheet = extraStyle.sheet; +sheet.insertRule('#div0::after { animation: anim 10s }', 0); +sheet.insertRule('#div0::before { animation: anim 10s }', 1); +getComputedStyle(divs[0]).animationName; // build animation +getComputedStyle(divs[1]).animationName; // build animation + +advance_clock(0); +checkEventOrder([ divs[0], 'animationstart' ], + [ divs[0], '::before', 'animationstart' ], + [ divs[0], '::after', 'animationstart' ], + [ divs[1], 'animationstart' ], + 'Simultaneous start on pseudo-elements'); + +divs.forEach(div => div.remove()); +divs = []; + +sheet = undefined; +extraStyle.remove(); +extraStyle = undefined; + +// 2. TESTS FOR SORTING BY TIME + +// 2a. Test that events are sorted by time + +divs = [ document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.setAttribute('style', 'animation: anim 10s'); + div.setAttribute('id', 'div' + i); +}); + +divs[0].style.animationDelay = '5s'; + +advance_clock(0); +advance_clock(20000); + +checkEventOrder([ divs[1], 'animationstart' ], + [ divs[0], 'animationstart' ], + [ divs[1], 'animationend' ], + [ divs[0], 'animationend' ], + 'Sorting of start and end events by time'); + +divs.forEach(div => div.remove()); +divs = []; + +// 2b. Test different events within the one element + +var div = document.createElement('div'); +gDisplay.appendChild(div); +div.style.animation = 'anim 4s 2, ' + // Repeat at t=4s + 'anim 10s 5s, ' + // Start at t=5s + 'anim 3s'; // End at t=3s +div.setAttribute('id', 'div'); +getComputedStyle(div).animationName; // build animation + +advance_clock(0); +advance_clock(5000); + +checkEventOrder([ div, 'animationstart' ], + [ div, 'animationstart' ], + [ div, 'animationend' ], + [ div, 'animationiteration' ], + [ div, 'animationstart' ], + 'Sorting of different events by time within an element'); + +div.remove(); +div = undefined; + +// 2c. Test negative delay is sorted equal to zero delay but before +// positive delay + +divs = [ document.createElement('div'), + document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.setAttribute('id', 'div' + i); +}); + +divs[0].style.animation = 'anim 20s 5s'; // Positive delay, sorts last +divs[1].style.animation = 'anim 20s'; // 0s delay +divs[2].style.animation = 'anim 20s -5s'; // Negative delay, sorts same as + // 0s delay, i.e. use document + // position + +advance_clock(0); +advance_clock(5000); +checkEventOrder([ divs[1], 'animationstart' ], + [ divs[2], 'animationstart' ], + [ divs[0], 'animationstart' ], + 'Sorting of events including negative delay'); + +divs.forEach(div => div.remove()); +divs = []; + +// 3. TESTS FOR SORTING BY animation-name POSITION + +// 3a. Test animation-name position + +div = document.createElement('div'); +gDisplay.appendChild(div); +div.style.animation = 'animA 10s, animB 5s, animC 5s 2'; +div.setAttribute('id', 'div'); +getComputedStyle(div).animationName; // build animation + +advance_clock(0); + +checkEventOrder([ 'animA', 'animationstart' ], + [ 'animB', 'animationstart' ], + [ 'animC', 'animationstart' ], + 'Sorting of simultaneous animationstart events by ' + + 'animation-name'); + +advance_clock(5000); + +checkEventOrder([ 'animB', 'animationend' ], + [ 'animC', 'animationiteration' ], + 'Sorting of different types of events by animation-name'); + +div.remove(); +div = undefined; + +// 3b. Test time trumps animation-name position + +div = document.createElement('div'); +gDisplay.appendChild(div); +div.style.animation = 'animA 10s 2s, animB 10s 1s'; +div.setAttribute('id', 'div'); + +advance_clock(0); +advance_clock(3000); + +checkEventOrder([ 'animB', 'animationstart' ], + [ 'animA', 'animationstart' ], + 'Events are sorted by time first, before animation-position'); + +div.remove(); +div = undefined; + +// 4. TESTS FOR TRANSITIONS + +// 4a. Test sorting transitions by document position + +divs = [ document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.style.marginLeft = '0px'; + div.style.transition = 'margin-left 10s'; + div.setAttribute('id', 'div' + i); +}); + +getComputedStyle(divs[0]).marginLeft; +divs.forEach(div => div.style.marginLeft = '100px'); +getComputedStyle(divs[0]).marginLeft; + +advance_clock(0); +advance_clock(10000); + +checkEventOrder([ divs[0], 'transitionend' ], + [ divs[1], 'transitionend' ], + 'Simultaneous transitionend on siblings'); + +divs.forEach(div => div.remove()); +divs = []; + +// 4b. Test sorting transitions by document position (children) + +divs = [ document.createElement('div'), + document.createElement('div'), + document.createElement('div') ]; + +// Create the following arrangement: +// +// display +// / \ +// div[0] div[1] +// / +// div[2] + +gDisplay.appendChild(divs[0]); +gDisplay.appendChild(divs[1]); +divs[0].appendChild(divs[2]); + +divs.forEach((div, i) => { + div.style.marginLeft = '0px'; + div.style.transition = 'margin-left 10s'; + div.setAttribute('id', 'div' + i); +}); + +getComputedStyle(divs[0]).marginLeft; +divs.forEach(div => div.style.marginLeft = '100px'); +getComputedStyle(divs[0]).marginLeft; + +advance_clock(0); +advance_clock(10000); + +checkEventOrder([ divs[0], 'transitionend' ], + [ divs[2], 'transitionend' ], + [ divs[1], 'transitionend' ], + 'Simultaneous transitionend on children'); + +divs.forEach(div => div.remove()); +divs = []; + +// 4c. Test sorting transitions by document position (pseudos) + +divs = [ document.createElement('div'), + document.createElement('div') ]; + +// Create the following arrangement: +// +// display +// | +// div[0] +// ::before, +// ::after +// | +// div[1] + +gDisplay.appendChild(divs[0]); +divs[0].appendChild(divs[1]); + +divs.forEach((div, i) => { + div.setAttribute('id', 'div' + i); +}); + +extraStyle = document.createElement('style'); +document.head.appendChild(extraStyle); +sheet = extraStyle.sheet; +sheet.insertRule('div, #div0::after, #div0::before { ' + + ' transition: margin-left 10s; ' + + ' margin-left: 0px }', 0); +sheet.insertRule('div.active, #div0.active::after, #div0.active::before { ' + + ' margin-left: 100px }', 1); +sheet.insertRule('#div0::after, #div0::before { ' + + ' content: " " }', 2); + +getComputedStyle(divs[0]).marginLeft; +divs.forEach(div => div.classList.add('active')); +getComputedStyle(divs[0]).marginLeft; + +advance_clock(0); +advance_clock(10000); + +checkEventOrder([ divs[0], 'transitionend' ], + [ divs[0], '::before', 'transitionend' ], + [ divs[0], '::after', 'transitionend' ], + [ divs[1], 'transitionend' ], + 'Simultaneous transitionend on pseudo-elements'); + +divs.forEach(div => div.remove()); +divs = []; + +sheet = undefined; +extraStyle.remove(); +extraStyle = undefined; + +// 4d. Test sorting transitions by time + +divs = [ document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.style.marginLeft = '0px'; + div.setAttribute('id', 'div' + i); +}); + +divs[0].style.transition = 'margin-left 10s'; +divs[1].style.transition = 'margin-left 5s'; + +getComputedStyle(divs[0]).marginLeft; +divs.forEach(div => div.style.marginLeft = '100px'); +getComputedStyle(divs[0]).marginLeft; + +advance_clock(0); +advance_clock(10000); + +checkEventOrder([ divs[1], 'transitionend' ], + [ divs[0], 'transitionend' ], + 'Sorting of transitionend events by time'); + +divs.forEach(div => div.remove()); +divs = []; + +// 4e. Test sorting transitions by time (with delay) + +divs = [ document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.style.marginLeft = '0px'; + div.setAttribute('id', 'div' + i); +}); + +divs[0].style.transition = 'margin-left 5s 5s'; +divs[1].style.transition = 'margin-left 5s'; + +getComputedStyle(divs[0]).marginLeft; +divs.forEach(div => div.style.marginLeft = '100px'); +getComputedStyle(divs[0]).marginLeft; + +advance_clock(0); +advance_clock(10 * 1000); + +checkEventOrder([ divs[1], 'transitionend' ], + [ divs[0], 'transitionend' ], + 'Sorting of transitionend events by time' + + '(including delay)'); + +divs.forEach(div => div.remove()); +divs = []; + +// 4f. Test sorting transitions by transition-property + +div = document.createElement('div'); +gDisplay.appendChild(div); +div.style.opacity = '0'; +div.style.marginLeft = '0px'; +div.style.transition = 'all 5s'; + +getComputedStyle(div).marginLeft; +div.style.opacity = '1'; +div.style.marginLeft = '100px'; +getComputedStyle(div).marginLeft; + +advance_clock(0); +advance_clock(10000); + +checkEventOrder([ 'margin-left', 'transitionend' ], + [ 'opacity', 'transitionend' ], + 'Sorting of transitionend events by transition-property') + +div.remove(); +div = undefined; + +// 4g. Test document position beats transition-property + +divs = [ document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.style.marginLeft = '0px'; + div.style.opacity = '0'; + div.style.transition = 'all 10s'; + div.setAttribute('id', 'div' + i); +}); + +getComputedStyle(divs[0]).marginLeft; +divs[0].style.opacity = '1'; +divs[1].style.marginLeft = '100px'; +getComputedStyle(divs[0]).marginLeft; + +advance_clock(0); +advance_clock(10000); + +checkEventOrder([ divs[0], 'transitionend' ], + [ divs[1], 'transitionend' ], + 'Transition events are sorted by document position first, ' + + 'before transition-property'); + +divs.forEach(div => div.remove()); +divs = []; + +// 4h. Test time beats transition-property + +div = document.createElement('div'); +gDisplay.appendChild(div); +div.style.opacity = '0'; +div.style.marginLeft = '0px'; +div.style.transition = 'margin-left 10s, opacity 5s'; + +getComputedStyle(div).marginLeft; +div.style.opacity = '1'; +div.style.marginLeft = '100px'; +getComputedStyle(div).marginLeft; + +advance_clock(0); +advance_clock(10000); + +checkEventOrder([ 'opacity', 'transitionend' ], + [ 'margin-left', 'transitionend' ], + 'Transition events are sorted by time first, before ' + + 'transition-property'); + +div.remove(); +div = undefined; + +// 4i. Test sorting transitions by document position (negative delay) + +divs = [ document.createElement('div'), + document.createElement('div') ]; +divs.forEach((div, i) => { + gDisplay.appendChild(div); + div.style.marginLeft = '0px'; + div.setAttribute('id', 'div' + i); +}); + +divs[0].style.transition = 'margin-left 10s 5s'; +divs[1].style.transition = 'margin-left 10s'; + +getComputedStyle(divs[0]).marginLeft; +divs.forEach(div => div.style.marginLeft = '100px'); +getComputedStyle(divs[0]).marginLeft; + +advance_clock(0); +advance_clock(15 * 1000); + +checkEventOrder([ divs[1], 'transitionend' ], + [ divs[0], 'transitionend' ], + 'Simultaneous transitionend on siblings'); + +divs.forEach(div => div.remove()); +divs = []; + +SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + +</script> +</body> +</html> diff --git a/layout/style/test/test_animations_iterationstart.html b/layout/style/test/test_animations_iterationstart.html new file mode 100644 index 000000000..d6a54f3b2 --- /dev/null +++ b/layout/style/test/test_animations_iterationstart.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1248338 +--> +<head> + <title>Test for iterationStart on compositor animations (Bug 1248338)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1248338">Mozilla Bug 1248338</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + { "set": [[ "dom.animations-api.core.enabled", true]] }, + function() { + window.open("file_animations_iterationstart.html"); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html new file mode 100644 index 000000000..4b276c896 --- /dev/null +++ b/layout/style/test/test_animations_omta.html @@ -0,0 +1,2392 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=964646 +--> +<!-- + + ========= PLEASE KEEP THIS IN SYNC WITH test_animations.html ========= + + This test mimicks the content of test_animations.html but performs tests + specific to animations that run on the compositor thread since they require + special (asynchronous) handling. Furthermore, these tests check that + animations that are expected to run on the compositor thread, are actually + doing so. + + If you are making changes to this file or to test_animations.html, please + try to keep them consistent where appropriate. + +--> +<head> + <meta charset="utf-8"> + <title>Test for css3-animations running on the compositor thread (Bug + 964646)</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + @keyframes transform-anim { + to { + transform: translate(100px); + } + } + @keyframes anim1 { + 0% { transform: translate(0px) } + 50% { transform: translate(80px) } + 100% { transform: translate(100px) } + } + @keyframes anim2 { + from { opacity: 0 } to { opacity: 1 } + } + @keyframes anim3 { + from { opacity: 0 } to { opacity: 1 } + } + @keyframes anim4 { + from { transform: translate(0px, 0px) } + to { transform: translate(0px, 100px) } + } + + @keyframes kf1 { + 50% { transform: translate(50px) } + to { transform: translate(150px) } + } + @keyframes kf2 { + from { transform: translate(150px) } + 50% { transform: translate(50px) } + } + @keyframes kf3 { + 25% { transform: translate(100px) } + } + @keyframes kf4 { + to, from { display: none; transform: translate(37px) } + } + @keyframes kf_cascade1 { + from { transform: translate(50px) } + 50%, from { transform: translate(30px) } /* wins: 0% */ + 75%, 85%, 50% { transform: translate(20px) } /* wins: 75%, 50% */ + 100%, 85% { transform: translate(70px) } /* wins: 100% */ + 85.1% { transform: translate(60px) } /* wins: 85.1% */ + 85% { transform: translate(30px) } /* wins: 85% */ + } + @keyframes kf_cascade2 { from, to { opacity: 0.3 } } + @keyframes kf_cascade2 { from, to { transform: translate(50px) } } + @keyframes kf_cascade2 { from, to { transform: translate(100px) } } + @keyframes kf_tf1 { + 0% { transform: translate(20px); animation-timing-function: ease } + 25% { transform: translate(60px); } + 50% { transform: translate(160px); animation-timing-function: steps(5) } + 75% { transform: translate(120px); animation-timing-function: linear } + 100% { transform: translate(20px); animation-timing-function: ease-out } + } + + @keyframes always_fifty { + from, to { transform: translate(50px) } + } + + #withbefore::before, #withafter::after { + content: "test"; + animation: anim4 1s linear alternate 3; + display:block; + } + + @keyframes multiprop { + 0% { + transform: translate(10px); opacity: 0.3; + animation-timing-function: ease; + } + 25% { + opacity: 0.5; + animation-timing-function: ease-out; + } + 50% { + transform: translate(40px); + } + 75% { + transform: translate(80px); opacity: 0.6; + animation-timing-function: ease-in; + } + } + + @keyframes cascade { + 0%, 25%, 100% { transform: translate(0px) } + 50%, 75% { transform: translate(100px) } + 0%, 75%, 100% { opacity: 0 } + 25%, 50% { opacity: 1 } + } + @keyframes cascade2 { + 0% { transform: translate(0px) } + 25% { transform: translate(30px); + animation-timing-function: ease-in } /* beaten by rule below */ + 50% { transform: translate(0px) } + 25% { transform: translate(50px) } + 100% { transform: translate(100px) } + } + + @keyframes primitives1 { + from { transform: rotate(0deg) translateX(0px) scaleX(1) + translate(0px) scale3d(1, 1, 1); } + to { transform: rotate(270deg) translate3d(0px, 0px, 0px) scale(1) + translateY(0px) scaleY(1); } + } + + @keyframes important1 { + from { opacity: 0.5; } + 50% { opacity: 1 !important; } /* ignored */ + to { opacity: 0.8; } + } + @keyframes important2 { + from { opacity: 0.5; + transform: translate(100px); } + to { opacity: 0.2 !important; /* ignored */ + transform: translate(50px); } + } + + @keyframes empty { } + @keyframes nearlyempty { + to { + transform: translate(100px); + } + } + + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + + .visitedLink:link { background-color: yellow } + .visitedLink:visited { background-color: blue } + + @keyframes opacitymid { + 0% { opacity: 0.2 } + 100% { opacity: 0.8 } + } + + @keyframes transformnone { + 0%, 100% { transform: translateX(50px) } + 25%, 75% { transform: none } + } + </style> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=964646">Mozilla Bug + 964646</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +/** Test for css3-animations running on the compositor thread (Bug 964646) **/ + +// Global state +var gDisplay = document.getElementById("display") + , gDiv = null; + +// Shortcut omta_is and friends by filling in the initial 'elem' argument +// with gDiv. +[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) { + var origFn = window[fn]; + window[fn] = function() { + var args = Array.slice(arguments); + if (!(args[0] instanceof Element)) { + args.unshift(gDiv); + } + return origFn.apply(window, args); + }; +}); + +// Shortcut new_div and done_div to update gDiv +var originalNewDiv = window.new_div; +window.new_div = function(style) { + [ gDiv ] = originalNewDiv(style); +}; +var originalDoneDiv = window.done_div; +window.done_div = function() { + originalDoneDiv(); + gDiv = null; +}; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(2); +runOMTATest(function() { + var onAbort = function() { + if (gDiv) { + done_div(); + } + }; + runAllAsyncAnimTests(onAbort).then(function() { + SimpleTest.finish(); + }); +}, SimpleTest.finish); + +//---------------------------------------------------------------------- +// +// Test cases +// +//---------------------------------------------------------------------- + +// This test is not in test_animations.html but is here to test that +// transform animations are actually run on the compositor thread as expected. +addAsyncAnimTest(function *() { + new_div("animation: transform-anim linear 300s"); + + yield waitForPaintsFlushed(); + + advance_clock(200000); + omta_is("transform", { tx: 100 * 2 / 3 }, RunningOn.Compositor, + "OMTA animation is animating as expected"); + done_div(); +}); + +function *testFillMode(fillMode, fillsBackwards, fillsForwards) +{ + var style = "transform: translate(30px); animation: 10s 3s anim1 linear"; + var desc; + if (fillMode.length > 0) { + style += " " + fillMode; + desc = "fill mode " + fillMode + ": "; + } else { + desc = "default fill mode: "; + } + new_div(style); + listen(); + + yield waitForPaintsFlushed(); + + if (fillsBackwards) + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + desc + "does affect value during delay (0s)"); + else + omta_is("transform", { tx: 30 }, RunningOn.MainThread, + desc + "doesn't affect value during delay (0s)"); + + advance_clock(2000); + if (fillsBackwards) + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + desc + "does affect value during delay (0s)"); + else + omta_is("transform", { tx: 30 }, RunningOn.MainThread, + desc + "does affect value during delay (0s)"); + + check_events([], "before start in testFillMode"); + advance_clock(1000); + check_events([{ type: "animationstart", target: gDiv, + bubbles: true, cancelable: false, + animationName: "anim1", elapsedTime: 0.0, + pseudoElement: "" }], + "right after start in testFillMode"); + + // If we have a backwards fill then at the start of the animation we will end + // up applying the same value as the fill value. Various optimizations in + // RestyleManager may filter out this meaning that the animation doesn't get + // added to the compositor thread until the first time the value changes. + // + // As a result we look for this first sample on either the compositor or the + // computed style + yield waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.Either, + desc + "affects value at start of animation"); + advance_clock(125); + // We might not add the animation to compositor until the second sample (due + // to the optimizations mentioned above) so we should wait for paints before + // proceeding + yield waitForPaints(); + omta_is("transform", { tx: 2 }, RunningOn.Compositor, + desc + "affects value during animation"); + advance_clock(2375); + omta_is("transform", { tx: 40 }, RunningOn.Compositor, + desc + "affects value during animation"); + advance_clock(2500); + omta_is("transform", { tx: 80 }, RunningOn.Compositor, + desc + "affects value during animation"); + advance_clock(2500); + omta_is("transform", { tx: 90 }, RunningOn.Compositor, + desc + "affects value during animation"); + advance_clock(2375); + omta_is("transform", { tx: 99.5 }, RunningOn.Compositor, + desc + "affects value during animation"); + check_events([], "before end in testFillMode"); + advance_clock(125); + check_events([{ type: "animationend", target: gDiv, + bubbles: true, cancelable: false, + animationName: "anim1", elapsedTime: 10.0, + pseudoElement: "" }], + "right after end in testFillMode"); + + // Currently the compositor will apply a forwards fill until it gets told by + // the main thread to clear the animation. As a result we should wait for + // paints to be flushed before checking that the animated value does *not* + // appear on the compositor thread. + yield waitForPaints(); + if (fillsForwards) + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + desc + "affects value at end of animation"); + advance_clock(10); + if (fillsForwards) + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + desc + "affects value after animation"); + else + omta_is("transform", { tx: 30 }, RunningOn.MainThread, + desc + "does not affect value after animation"); + + done_div(); +} + +addAsyncAnimTest(function() { return testFillMode("", false, false); }); +addAsyncAnimTest(function() { return testFillMode("none", false, false); }); +addAsyncAnimTest(function() { return testFillMode("forwards", false, true); }); +addAsyncAnimTest(function() { return testFillMode("backwards", true, false); }); +addAsyncAnimTest(function() { return testFillMode("both", true, true); }); + +// Test that animations continue running when the animation name +// list is changed. +// +// test_animations.html combines all these tests into one block but this is +// difficult for OMTA because currently there are only two properties to which +// we apply OMTA. Instead we break the test down into a few independent pieces +// in order to exercise the same functionality. + +// Append to list +addAsyncAnimTest(function *() { + new_div("animation: anim1 linear 10s"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Either, + "just anim1, translate at start"); + advance_clock(1000); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "just anim1, translate at 1s"); + // append anim2 + gDiv.style.animation = "anim1 linear 10s, anim2 linear 10s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "anim1 + anim2, translate at 1s"); + omta_is("opacity", 0, RunningOn.Compositor, + "anim1 + anim2, opacity at 1s"); + advance_clock(1000); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim1 + anim2, translate at 2s"); + omta_is("opacity", 0.1, RunningOn.Compositor, + "anim1 + anim2, opacity at 2s"); + done_div(); +}); + +// Prepend to list; delete from list +addAsyncAnimTest(function *() { + new_div("animation: anim1 linear 10s"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Either, + "just anim1, translate at start"); + advance_clock(1000); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "just anim1, translate at 1s"); + // prepend anim2 + gDiv.style.animation = "anim2 linear 10s, anim1 linear 10s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "anim2 + anim1, translate at 1s"); + omta_is("opacity", 0, RunningOn.Compositor, + "anim2 + anim1, opacity at 1s"); + advance_clock(1000); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim2 + anim1, translate at 2s"); + omta_is("opacity", 0.1, RunningOn.Compositor, + "anim2 + anim1, opacity at 2s"); + // remove anim2 from list + gDiv.style.animation = "anim1 linear 10s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "just anim1, translate at 2s"); + omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 2s"); + advance_clock(1000); + omta_is("transform", { tx: 48 }, RunningOn.Compositor, + "just anim1, translate at 3s"); + omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 3s"); + done_div(); +}); + +// Swap elements +addAsyncAnimTest(function *() { + new_div("animation: anim1 linear 10s, anim2 linear 10s"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Either, + "anim1 + anim2, translate at start"); + omta_is("opacity", 0, RunningOn.Compositor, + "anim1 + anim2, opacity at start"); + advance_clock(1000); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "anim1 + anim2, translate at 1s"); + omta_is("opacity", 0.1, RunningOn.Compositor, + "anim1 + anim2, opacity at 1s"); + // swap anim1 and anim2, change duration of anim2 + gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 16 }, RunningOn.Compositor, + "anim2 + anim1, translate at 1s"); + omta_is("opacity", 0.2, RunningOn.Compositor, + "anim2 + anim1, opacity at 1s"); + advance_clock(1000); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim2 + anim1, translate at 2s"); + omta_is("opacity", 0.4, RunningOn.Compositor, + "anim2 + anim1, opacity at 2s"); + // list anim2 twice, last duration wins, original start time still applies + gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s, anim2 linear 20s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim2 + anim1 + anim2, translate at 2s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1", + "anim2 + anim1 + anim2, opacity at 2s"); + // drop one of the anim2, and list anim3 as well, which animates + // the same property as anim2 + gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 32 }, RunningOn.Compositor, + "anim1 + anim2 + anim3, translate at 2s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0", + "anim1 + anim2 + anim3, opacity at 2s"); + advance_clock(1000); + omta_is("transform", { tx: 48 }, RunningOn.Compositor, + "anim1 + anim2 + anim3, translate at 3s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1", + "anim1 + anim2 + anim3, opacity at 3s"); + // now swap the anim3 and anim2 order + gDiv.style.animation = "anim1 linear 10s, anim3 linear 10s, anim2 linear 20s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 48 }, RunningOn.Compositor, + "anim1 + anim3 + anim2, translate at 3s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.15", + "anim1 + anim3 + anim2, opacity at 3s"); + advance_clock(2000); // (unlike test_animations.html, we seek 2s forwards here + // since at 4s anim2 and anim3 produce the same result so + // we can't tell which won.) + omta_is("transform", { tx: 80 }, RunningOn.Compositor, + "anim1 + anim3 + anim2, translate at 5s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.25", + "anim1 + anim3 + anim2, opacity at 5s"); + // swap anim3 and anim2 back + gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 80 }, RunningOn.Compositor, + "anim1 + anim2 + anim3, translate at 5s"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.3", + "anim1 + anim2 + anim3, opacity at 5s"); + // seek past end of anim1 + advance_clock(5100); + yield waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "anim1 + anim2 + anim3, translate at 10.1s"); + // Change the animation fill mode on the completed animation. + gDiv.style.animation = + "anim1 linear 10s forwards, anim2 linear 20s, anim3 linear 10s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + "anim1 + anim2 + anim3, translate at 10.1s with fill mode"); + advance_clock(900); + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + "anim1 + anim2 + anim3, translate at 11s with fill mode"); + // Change the animation duration on the completed animation, so it is + // no longer completed. + // XXX Not sure about this---there seems to be a bug in test_animations.html + // in that it drops the fill mode but the test comment says it has a fill mode + gDiv.style.animation = "anim1 linear 20s, anim2 linear 20s, anim3 linear 10s"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 82 }, RunningOn.Compositor, + "anim1 + anim2 + anim3, translate at 11s with fill mode"); + is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.9", + "anim1 + anim2 + anim3, opacity at 11s"); + done_div(); +}); + +/* + * css3-animations: 3. Keyframes + * http://dev.w3.org/csswg/css3-animations/#keyframes + */ + +// Test the rules on keyframes that lack a 0% or 100% rule: +// (simultaneously, test that reverse animations have their keyframes +// run backwards) + +addAsyncAnimTest(function *() { + // 100px at 0%, 50px at 50%, 150px at 100% + new_div("transform: translate(100px); " + + "animation: kf1 ease 1s alternate infinite"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 0.0s"); + advance_clock(100); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "no-0% at 0.1s"); + advance_clock(200); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 0.01, + RunningOn.Compositor, "no-0% at 0.3s"); + advance_clock(200); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-0% at 0.5s"); + advance_clock(200); + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.4) }, 0.01, + RunningOn.Compositor, "no-0% at 0.7s"); + advance_clock(200); + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 0.01, + RunningOn.Compositor, "no-0% at 0.9s"); + advance_clock(100); + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-0% at 1.0s"); + advance_clock(100); + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 0.01, + RunningOn.Compositor, "no-0% at 1.1s"); + advance_clock(300); + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "no-0% at 1.4s"); + advance_clock(300); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 0.01, + RunningOn.Compositor, "no-0% at 1.7s"); + advance_clock(200); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "no-0% at 1.9s"); + advance_clock(100); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 2.0s"); + done_div(); + + // 150px at 0%, 50px at 50%, 100px at 100% + new_div("transform: translate(100px); " + + "animation: kf2 ease-in 1s alternate infinite"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 0.0s"); + advance_clock(100); + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "no-100% at 0.1s"); + advance_clock(200); + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, "no-100% at 0.3s"); + advance_clock(200); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-100% at 0.5s"); + advance_clock(200); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.4) }, 0.01, + RunningOn.Compositor, "no-100% at 0.7s"); + advance_clock(200); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 0.01, + RunningOn.Compositor, "no-100% at 0.9s"); + advance_clock(100); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-100% at 1.0s"); + advance_clock(100); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 0.01, + RunningOn.Compositor, "no-100% at 1.1s"); + advance_clock(300); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "no-100% at 1.4s"); + advance_clock(300); + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, "no-100% at 1.7s"); + advance_clock(200); + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "no-100% at 1.9s"); + advance_clock(100); + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 2.0s"); + done_div(); + + // 50px at 0%, 100px at 25%, 50px at 100% + new_div("transform: translate(50px); " + + "animation: kf3 ease-out 1s alternate infinite"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "no-0%-no-100% at 0.0s"); + advance_clock(50); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 0.05s"); + advance_clock(100); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 0.15s"); + advance_clock(100); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "no-0%-no-100% at 0.25s"); + advance_clock(300); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.4) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 0.55s"); + advance_clock(300); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 0.85s"); + advance_clock(150); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "no-0%-no-100% at 1.0s"); + advance_clock(150); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 1.15s"); + advance_clock(450); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.2) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 1.6s"); + advance_clock(250); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 1.85s"); + advance_clock(100); + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 0.01, + RunningOn.Compositor, "no-0%-no-100% at 1.95s"); + advance_clock(50); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "no-0%-no-100% at 2.0s"); + done_div(); + + // Test that non-animatable properties are ignored. + // Simultaneously, test that the block is still honored, and that + // we still override the value when two consecutive keyframes have + // the same value. + new_div("animation: kf4 ease 10s"); + yield waitForPaintsFlushed(); + var cs = window.getComputedStyle(gDiv); + is(cs.display, "block", + "non-animatable properties should be ignored (linear, 0s)"); + omta_is("transform", { tx: 37 }, RunningOn.Compositor, + "animatable properties should still apply (linear, 0s)"); + advance_clock(1000); + is(cs.display, "block", + "non-animatable properties should be ignored (linear, 1s)"); + omta_is("transform", { tx: 37 }, RunningOn.Compositor, + "animatable properties should still apply (linear, 1s)"); + done_div(); + new_div("animation: kf4 step-start 10s"); + yield waitForPaintsFlushed(); + cs = window.getComputedStyle(gDiv); + is(cs.display, "block", + "non-animatable properties should be ignored (step-start, 0s)"); + omta_is("transform", { tx: 37 }, RunningOn.Compositor, + "animatable properties should still apply (step-start, 0s)"); + advance_clock(1000); + is(cs.display, "block", + "non-animatable properties should be ignored (step-start, 1s)"); + omta_is("transform", { tx: 37 }, RunningOn.Compositor, + "animatable properties should still apply (step-start, 1s)"); + done_div(); + + // Test cascading of the keyframes within an @keyframes rule. + new_div("animation: kf_cascade1 linear 10s"); + yield waitForPaintsFlushed(); + // 0%: 30px + // 50%: 20px + // 75%: 20px + // 85%: 30px + // 85.1%: 60px + // 100%: 70px + omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 0s"); + advance_clock(2500); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 2.5s"); + advance_clock(2500); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 5s"); + advance_clock(2000); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7s"); + advance_clock(500); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7.5s"); + advance_clock(500); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 8s"); + advance_clock(500); + omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 8.5s"); + advance_clock(10); + // For some reason we get an error of 0.0003 for this test only + omta_is_approx("transform", { tx: 60 }, 0.001, RunningOn.Compositor, + "kf_cascade1 at 8.51s"); + advance_clock(745); + omta_is("transform", { tx: 65 }, RunningOn.Compositor, + "kf_cascade1 at 9.2505s"); + done_div(); + + // Test cascading of the @keyframes rules themselves. + new_div("animation: kf_cascade2 linear 10s"); + yield waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.MainThread, + "last @keyframes rule with transform should win"); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "last @keyframes rule with transform should win"); + done_div(); +}); + +/* + * css3-animations: 3.1. Timing functions for keyframes + * http://dev.w3.org/csswg/css3-animations/#timing-functions-for-keyframes- + */ + +addAsyncAnimTest(function *() { + new_div("animation: kf_tf1 ease-in 10s alternate infinite"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 0s (test needed for flush)"); + advance_clock(1000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 1s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.8) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 2s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 3s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 4s"); + advance_clock(1000); + omta_is("transform", { tx: 160 }, RunningOn.Compositor, + "keyframe timing functions test at 5s"); + advance_clock(1010); // avoid floating-point error + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 6s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 7s"); + advance_clock(990); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 8s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) }, 0.01, + RunningOn.Compositor, "keyframe timing functions test at 9s"); + advance_clock(1000); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 10s"); + advance_clock(20000); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 30s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.6) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 31s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 32s"); + advance_clock(990); // avoid floating-point error + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.8) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 33s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 34s"); + advance_clock(1010); + omta_is("transform", { tx: 160 }, RunningOn.Compositor, + "keyframe timing functions test at 35s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 36s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 37s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.8) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 38s"); + advance_clock(1000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 39s"); + advance_clock(1000); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 40s"); + done_div(); + + // spot-check the same thing without alternate + new_div("animation: kf_tf1 ease-in 10s infinite"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 20 }, RunningOn.Compositor, + "keyframe timing functions test at 0s (test needed for flush)"); + advance_clock(11000); + omta_is_approx("transform", { tx: 20 + 40 * gTF.ease(0.4) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 11s"); + advance_clock(3000); + omta_is_approx("transform", { tx: 60 + 100 * gTF.ease_in(0.6) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 14s"); + advance_clock(2010); // avoid floating-point error + omta_is_approx("transform", { tx: 160 - 40 * step_end(5)(0.4) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 16s"); + advance_clock(1990); + omta_is_approx("transform", { tx: 120 - 100 * gTF.linear(0.2) }, 0.01, + RunningOn.Compositor, + "keyframe timing functions test at 18s"); + done_div(); +}); + +/* + * css3-animations: 3.2. The 'animation-name' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property- + */ + +// animation-name is reasonably well-tested up in the tests for Section +// 2, particularly the tests that "Test that animations continue running +// when the animation name list is changed." + +// Test that 'animation-name: none' stops the animation, and setting +// it again starts a new one. + +addAsyncAnimTest(function *() { + new_div("animation: anim2 ease-in-out 10s"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "after setting animation-name to anim2"); + advance_clock(1000); + omta_is_approx("opacity", gTF.ease_in_out(0.1), 0.01, RunningOn.Compositor, + "before changing animation-name to none"); + gDiv.style.animationName = "none"; + yield waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.MainThread, + "after changing animation-name to none"); + advance_clock(1000); + omta_is("opacity", 1, RunningOn.MainThread, + "after changing animation-name to none plus 1s"); + gDiv.style.animationName = "anim2"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "after changing animation-name to anim2"); + advance_clock(1000); + omta_is_approx("opacity", gTF.ease_in_out(0.1), 0.01, RunningOn.Compositor, + "at 1s in animation when animation-name no longer none again"); + gDiv.style.animationName = "none"; + yield waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.MainThread, + "after changing animation-name to none"); + advance_clock(1000); + omta_is("opacity", 1, RunningOn.MainThread, + "after changing animation-name to none plus 1s"); + done_div(); +}); + +/* + * css3-animations: 3.3. The 'animation-duration' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-duration-property- + */ + +// FIXME: test animation-duration of 0 (quite a bit, including interaction +// with fill-mode, count, and reversing), once I know what the right +// behavior is. + +/* + * css3-animations: 3.4. The 'animation-timing-function' Property + * http://dev.w3.org/csswg/css3-animations/#animation-timing-function_tag + */ + +// tested in tests for section 3.1 + +/* + * css3-animations: 3.5. The 'animation-iteration-count' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-iteration-count-property- + */ +addAsyncAnimTest(function *() { + new_div("animation: anim2 ease-in 10s 0.3 forwards"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-iteration-count test 1 at 0s"); + advance_clock(2000); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-iteration-count test 1 at 2s"); + advance_clock(900); + omta_is_approx("opacity", gTF.ease_in(0.29), 0.01, RunningOn.Compositor, + "animation-iteration-count test 1 at 2.9s"); + advance_clock(100); + // Animation has reached the end so allow it to be cleared from the compositor + yield waitForPaints(); + // For transform animations we can tell whether a transform on the compositor + // thread was set by animation or not since there is a special flag for it. + // + // For opacity animations, however, there is no such flag so we'll get an + // "OMTA" opacity even when it wasn't set by animation. When we pause an + // opacity animation we don't worry about where it is reported to be running + // (main thread or compositor) so long as the result is correct, hence we + // check for "either" below. + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either, + "animation-iteration-count test 1 at 3s"); + advance_clock(100); + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either, + "animation-iteration-count test 1 at 3.1s"); + advance_clock(5000); + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Either, + "animation-iteration-count test 1 at 8.1s"); + done_div(); + + // The corresponding test in test_animations.html runs three animations in + // parallel but since we only have two properties that are OMTA-enabled at + // this time and no additive animation we split this test into two parts. + new_div("animation: anim2 ease-in 10s 0.3, " + + "anim4 ease-out 20s 1.2 alternate forwards"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-iteration-count test 2 at 0s"); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "animation-iteration-count test 3 at 0s"); + advance_clock(2000); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-iteration-count test 2 at 2s"); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.1) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 2s"); + advance_clock(900); + omta_is_approx("opacity", gTF.ease_in(0.29), 0.01, RunningOn.Compositor, + "animation-iteration-count test 2 at 2.9s"); + advance_clock(200); + yield waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "animation-iteration-count test 2 at 3.1s"); + advance_clock(2000); + omta_is("opacity", 1, RunningOn.Either, + "animation-iteration-count test 2 at 5.1s"); + advance_clock(14700); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.99) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 19.8s"); + advance_clock(200); + omta_is("transform", { ty: 100 }, RunningOn.Compositor, + "animation-iteration-count test 3 at 20s"); + advance_clock(200); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.99) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 20.2s"); + advance_clock(3600); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.81) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 23.8s"); + advance_clock(200); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.8) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 3 at 24s"); + advance_clock(200); + yield waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "animation-iteration-count test 2 at 25s"); + omta_is_approx("transform", { ty: 100 * gTF.ease_out(0.8) }, 0.01, + RunningOn.MainThread, + "animation-iteration-count test 3 at 25s"); + done_div(); + + new_div("animation: anim4 ease-in-out 5s 1.6 forwards"); + yield waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "animation-iteration-count test 4 at 0s"); + advance_clock(2000); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.4) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 2s"); + advance_clock(2900); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.98) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 4.9s"); + advance_clock(200); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.02) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 5.1s"); + advance_clock(2800); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.58) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 7.9s"); + advance_clock(100); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01, + RunningOn.Compositor, + "animation-iteration-count test 4 at 8s"); + advance_clock(100); + yield waitForPaints(); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01, + RunningOn.Either, + "animation-iteration-count test 4 at 8.1s"); + advance_clock(16100); + omta_is_approx("transform", { ty: 100 * gTF.ease_in_out(0.6) }, 0.01, + RunningOn.Either, + "animation-iteration-count test 4 at 25s"); + done_div(); +}); + +/* + * css3-animations: 3.6. The 'animation-direction' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-direction-property- + */ + +// Tested in tests for sections 3.1 and 3.5. + +addAsyncAnimTest(function *() { + new_div("animation: anim2 ease-in 10s infinite"); + gDiv.style.animationDirection = "normal"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-direction test 1 (normal) at 0s"); + gDiv.style.animationDirection = "reverse"; + yield waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 0s"); + gDiv.style.animationDirection = "alternate"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 0s"); + gDiv.style.animationDirection = "alternate-reverse"; + yield waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 0s"); + advance_clock(2000); + gDiv.style.animationDirection = "normal"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 2s"); + gDiv.style.animationDirection = "reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 2s"); + gDiv.style.animationDirection = "alternate"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 2s"); + gDiv.style.animationDirection = "alternate-reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 2s"); + advance_clock(5000); + gDiv.style.animationDirection = "normal"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.7), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 7s"); + gDiv.style.animationDirection = "reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 7s"); + gDiv.style.animationDirection = "alternate"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.7), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 7s"); + gDiv.style.animationDirection = "alternate-reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.3), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 7s"); + advance_clock(5000); + gDiv.style.animationDirection = "normal"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 12s"); + gDiv.style.animationDirection = "reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 12s"); + gDiv.style.animationDirection = "alternate"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 12s"); + gDiv.style.animationDirection = "alternate-reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 12s"); + advance_clock(10000); + gDiv.style.animationDirection = "normal"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 22s"); + gDiv.style.animationDirection = "reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 22s"); + gDiv.style.animationDirection = "alternate"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 22s"); + gDiv.style.animationDirection = "alternate-reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 22s"); + advance_clock(30000); + gDiv.style.animationDirection = "normal"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (normal) at 52s"); + gDiv.style.animationDirection = "reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 52s"); + gDiv.style.animationDirection = "alternate"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 52s"); + gDiv.style.animationDirection = "alternate-reverse"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.2), 0.01, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 52s"); + done_div(); +}); + +/* + * css3-animations: 3.7. The 'animation-play-state' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-play-state-property- + */ + +addAsyncAnimTest(function *() { + // simple test with just one animation + new_div(""); + gDiv.style.animationTimingFunction = "ease"; + gDiv.style.animationName = "anim1"; + gDiv.style.animationDuration = "1s"; + gDiv.style.animationDirection = "alternate"; + gDiv.style.animationIterationCount = "2"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "animation-play-state test 1, at 0s"); + advance_clock(250); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 250ms"); + gDiv.style.animationPlayState = "paused"; + yield waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 at 250ms"); + advance_clock(250); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 still at 500ms"); + gDiv.style.animationPlayState = "running"; + yield waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 still at 500ms"); + advance_clock(500); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 1000ms"); + advance_clock(250); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "animation-play-state test 1 at 1250ms"); + advance_clock(250); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 1500ms"); + gDiv.style.animationPlayState = "paused"; + yield waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 at 1500ms"); + advance_clock(2000); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 at 3500ms"); + advance_clock(500); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 1 at 4000ms"); + gDiv.style.animationPlayState = ""; + yield waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 80 + 20 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 4000ms"); + advance_clock(500); + omta_is_approx("transform", { tx: 80 * gTF.ease(0.5) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 1 at 4500ms"); + advance_clock(250); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "animation-play-state test 1, at 4750ms"); + advance_clock(250); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "animation-play-state test 1, at 5000ms"); + done_div(); + + // The corresponding test in test_animations.html tests various cases of + // pausing individual animations in a list of three different animations + // but since there are only two OMTA properties we can animate + // independently this test is substantially simpler. + new_div(""); + gDiv.style.animationTimingFunction = "ease-out, ease-in"; + gDiv.style.animationName = "anim2, anim4"; + gDiv.style.animationDuration = "1s, 2s"; + gDiv.style.animationDirection = "alternate, normal"; + gDiv.style.animationIterationCount = "4, 2"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-play-state test 2, at 0s"); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "animation-play-state test 3, at 0s"); + advance_clock(250); + gDiv.style.animationPlayState = "paused, running"; // pause 1 + yield waitForPaintsFlushed(); + // As noted with the tests for animation-iteration-count, for opacity + // animations we don't strictly check the finished animation is being animated + // on the main thread, but simply that it is producing the correct result. + omta_is_approx("opacity", gTF.ease_out(0.25), 0.01, RunningOn.MainThread, + "animation-play-state test 2 at 250ms"); // paused + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.125) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 250ms"); + advance_clock(250); + omta_is_approx("opacity", gTF.ease_out(0.25), 0.01, RunningOn.MainThread, + "animation-play-state test 2 at 500ms"); // paused + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.25) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 500ms"); + advance_clock(250); + gDiv.style.animationPlayState = "running, paused"; // unpause 1, pause 2 + yield waitForPaintsFlushed(); + advance_clock(250); + omta_is_approx("opacity", gTF.ease_out(0.5), 0.01, RunningOn.Compositor, + "animation-play-state test 2 at 1000ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 1000ms"); // paused + gDiv.style.animationPlayState = "paused"; // pause all + yield waitForPaintsFlushed(); + advance_clock(3000); + omta_is_approx("opacity", gTF.ease_out(0.5), 0.01, RunningOn.MainThread, + "animation-play-state test 2 at 4000ms"); // paused + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 4000ms"); // paused + gDiv.style.animationPlayState = "running, paused"; // pause 2 + yield waitForPaintsFlushed(); + advance_clock(850); + omta_is_approx("opacity", gTF.ease_out(0.65), 0.01, RunningOn.Compositor, + "animation-play-state test 2 at 4850ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 4850ms"); + advance_clock(300); + omta_is_approx("opacity", gTF.ease_out(0.35), 0.01, RunningOn.Compositor, + "animation-play-state test 2 at 5150ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 5150ms"); + advance_clock(2300); + omta_is_approx("opacity", gTF.ease_out(0.05), 0.01, RunningOn.Compositor, + "animation-play-state test 2 at 7450ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 7450ms"); + advance_clock(100); + // test 2 has finished so wait for it to be removed from the + // compositor (otherwise it will fill forwards) + yield waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 7550ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.375) }, 0.01, + RunningOn.MainThread, + "animation-play-state test 3 at 7550ms"); + gDiv.style.animationPlayState = "running"; // unpause 2 + yield waitForPaintsFlushed(); + advance_clock(1000); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 7550ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.875) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 7550ms"); + advance_clock(500); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 8050ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.125) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 8050ms"); + advance_clock(1000); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 9050ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.625) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 9050ms"); + advance_clock(500); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 9550ms"); + omta_is_approx("transform", { ty: 100 * gTF.ease_in(0.875) }, 0.01, + RunningOn.Compositor, + "animation-play-state test 3 at 9550ms"); + advance_clock(500); + yield waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "animation-play-state test 2 at 10050ms"); + omta_is("transform", { ty: 0 }, RunningOn.MainThread, + "animation-play-state test 3 at 10050ms"); + done_div(); +}); + +/* + * css3-animations: 3.8. The 'animation-delay' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-delay-property- + */ + +addAsyncAnimTest(function *() { + // test positive delay + new_div("animation: anim2 1s 0.5s ease-out"); + yield waitForPaintsFlushed(); + // NOTE: getOMTAStyle() can't detect the animation is running on the + // compositor or not during the delay phase, since no opacity style is + // applied during the delay phase. + omta_is("opacity", 1, RunningOn.Either, "positive delay test at 0ms"); + advance_clock(400); + omta_is("opacity", 1, RunningOn.Either, "positive delay test at 400ms"); + advance_clock(100); + yield waitForPaints(); + omta_is("opacity", 0, RunningOn.Compositor, "positive delay test at 500ms"); + advance_clock(100); + omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Compositor, + "positive delay test at 500ms"); + done_div(); + + // test dynamic changes to delay (i.e., that we preserve the start time + // that's before the delay) + new_div("animation: anim2 1s 0.5s ease-out both"); + yield waitForPaintsFlushed(); + // NOTE: As noted above, getOMTAStyle() can't detect the animation is running + // on the compositor during the delay phase. + omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 0ms"); + advance_clock(400); + omta_is("opacity", 0, RunningOn.Either, + "dynamic delay delay test at 400ms (1)"); + gDiv.style.animationDelay = "0.2s"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor, + "dynamic delay delay test at 400ms (2)"); + gDiv.style.animationDelay = "0.6s"; + yield waitForPaintsFlushed(); + advance_clock(200); + omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 600ms"); + advance_clock(200); + yield waitForPaints(); + omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor, + "dynamic delay delay test at 800ms"); + advance_clock(1000); + yield waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "dynamic delay delay test at 1800ms (1)"); + gDiv.style.animationDelay = "1.5s"; + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_out(0.3), 0.01, RunningOn.Compositor, + "dynamic delay delay test at 1800ms (2)"); + gDiv.style.animationDelay = "2s"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Either, + "dynamic delay delay test at 1800ms (3)"); + done_div(); + + // test delay and play-state interaction + new_div("animation: anim2 1s 0.5s ease-out"); + yield waitForPaintsFlushed(); + // NOTE: As noted above, getOMTAStyle() can't detect the animation is running + // on the compositor during the delay phase. + omta_is("opacity", 1, RunningOn.Either, + "delay and play-state delay test at 0ms"); + advance_clock(400); + omta_is("opacity", 1, RunningOn.Either, + "delay and play-state delay test at 400ms"); + gDiv.style.animationPlayState = "paused"; + yield waitForPaintsFlushed(); + advance_clock(100); + omta_is("opacity", 1, RunningOn.MainThread, // paused + "delay and play-state delay test at 500ms"); + advance_clock(500); + omta_is("opacity", 1, RunningOn.MainThread, // paused + "delay and play-state delay test at 1000ms"); + gDiv.style.animationPlayState = "running"; + yield waitForPaintsFlushed(); + advance_clock(100); + yield waitForPaints(); + omta_is("opacity", 0, RunningOn.Compositor, + "delay and play-state delay test at 1100ms"); + advance_clock(100); + omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Compositor, + "delay and play-state delay test at 1200ms"); + gDiv.style.animationPlayState = "paused"; + yield waitForPaintsFlushed(); + advance_clock(100); + omta_is_approx("opacity", gTF.ease_out(0.1), 0.01, RunningOn.Either, + "delay and play-state delay test at 1300ms"); + done_div(); + + // test negative delay and implicit starting values + new_div("transform: translate(1000px)"); + yield waitForPaintsFlushed(); + advance_clock(300); + gDiv.style.transform = "translate(100px)"; + gDiv.style.animation = "kf1 1s -0.1s ease-in"; + yield waitForPaintsFlushed(); + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_in(0.2) }, + 0.01, RunningOn.Compositor, + "delay and implicit starting values test"); + done_div(); + + // test large negative delay that causes the animation to start + // in the fourth iteration + new_div("animation: anim2 1s -3.6s ease-in 5 alternate forwards"); + listen(); + yield waitForPaintsFlushed(); + omta_is_approx("opacity", gTF.ease_in(0.4), 0.01, RunningOn.Compositor, + "large negative delay test at 0ms"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim2', elapsedTime: 3.6, + pseudoElement: "" }, + { type: 'animationiteration', target: gDiv, + animationName: 'anim2', elapsedTime: 3.6, + pseudoElement: "" }], + "right after start in large negative delay test"); + advance_clock(380); + omta_is_approx("opacity", gTF.ease_in(0.02), 0.01, RunningOn.Compositor, + "large negative delay test at 380ms"); + check_events([]); + advance_clock(20); + omta_is("opacity", 0, RunningOn.Compositor, + "large negative delay test at 400ms"); + check_events([{ type: 'animationiteration', target: gDiv, + animationName: 'anim2', elapsedTime: 4.0, + pseudoElement: "" }], + "right after start in large negative delay test"); + advance_clock(800); + omta_is_approx("opacity", gTF.ease_in(0.8), 0.01, RunningOn.Compositor, + "large negative delay test at 1200ms"); + check_events([]); + advance_clock(200); + omta_is("opacity", 1, RunningOn.Either, + "large negative delay test at 1400ms"); + check_events([{ type: 'animationend', target: gDiv, + animationName: 'anim2', elapsedTime: 5.0, + pseudoElement: "" }], + "right after start in large negative delay test"); + done_div(); +}); + +/* + * css3-animations: 3.9. The 'animation-fill-mode' Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property- + */ + +// animation-fill-mode is tested in the tests for section (2). + +/* + * css3-animations: 3.10. The 'animation' Shorthand Property + * http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property- + */ + +/** + * Basic tests of animations on pseudo-elements + */ +addAsyncAnimTest(function *() { + new_div(""); + listen(); + gDiv.id = "withbefore"; + yield waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + ":before test at 0ms", "::before"); + advance_clock(400); + omta_is("transform", { ty: 40 }, RunningOn.Compositor, + ":before test at 400ms", "::before"); + advance_clock(800); + omta_is("transform", { ty: 80 }, RunningOn.Compositor, + ":before test at 1200ms", "::before"); + omta_is("transform", { ty: 0 }, RunningOn.MainThread, + ":before animation should not affect element"); + advance_clock(800); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + ":before test at 2000ms", "::before"); + advance_clock(300); + omta_is("transform", { ty: 30 }, RunningOn.Compositor, + ":before test at 2300ms", "::before"); + advance_clock(700); + check_events([ { type: "animationstart", animationName: "anim4", + elapsedTime: 0, pseudoElement: "::before" }, + { type: "animationiteration", animationName: "anim4", + elapsedTime: 1, pseudoElement: "::before" }, + { type: "animationiteration", animationName: "anim4", + elapsedTime: 2, pseudoElement: "::before" }, + { type: "animationend", animationName: "anim4", + elapsedTime: 3, pseudoElement: "::before" }]); + done_div(); + + new_div(""); + listen(); + gDiv.id = "withafter"; + yield waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + ":after test at 0ms", "::after"); + advance_clock(400); + omta_is("transform", { ty: 40 }, RunningOn.Compositor, + ":after test at 400ms", "::after"); + advance_clock(800); + omta_is("transform", { ty: 80 }, RunningOn.Compositor, + ":after test at 1200ms", "::after"); + omta_is("transform", { ty: 0 }, RunningOn.MainThread, + ":before animation should not affect element"); + advance_clock(800); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + ":after test at 2000ms", "::after"); + advance_clock(300); + omta_is("transform", { ty: 30 }, RunningOn.Compositor, + ":after test at 2300ms", "::after"); + advance_clock(700); + check_events([ { type: "animationstart", animationName: "anim4", + elapsedTime: 0, pseudoElement: "::after" }, + { type: "animationiteration", animationName: "anim4", + elapsedTime: 1, pseudoElement: "::after" }, + { type: "animationiteration", animationName: "anim4", + elapsedTime: 2, pseudoElement: "::after" }, + { type: "animationend", animationName: "anim4", + elapsedTime: 3, pseudoElement: "::after" }]); + done_div(); +}); + +/** + * Test handling of properties that are present in only some of the + * keyframes. + */ +addAsyncAnimTest(function *() { + new_div("animation: multiprop 1s ease-in-out alternate infinite"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 10 }, RunningOn.Compositor, + "multiprop transform at 0ms"); + omta_is("opacity", 0.3, RunningOn.Compositor, "multiprop opacity at 0ms"); + advance_clock(100); + omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "multiprop transform at 100ms"); + omta_is_approx("opacity", 0.3 + 0.2 * gTF.ease(0.4), 0.01, + RunningOn.Compositor, "multiprop opacity at 100ms"); + advance_clock(200); + omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.6) }, 0.01, + RunningOn.Compositor, "multiprop transform at 300ms"); + omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.1), 0.01, + RunningOn.Compositor, "multiprop opacity at 300ms"); + advance_clock(300); + omta_is_approx("transform", { tx: 40 + 40 * gTF.ease_in_out(0.4) }, 0.01, + RunningOn.Compositor, "multiprop transform at 600ms"); + omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.7), 0.01, + RunningOn.Compositor, "multiprop opacity at 600ms"); + advance_clock(200); + omta_is_approx("transform", { tx: 80 - 80 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "multiprop transform at 800ms"); + omta_is_approx("opacity", 0.6 + 0.4 * gTF.ease_in(0.2), 0.01, + RunningOn.Compositor, "multiprop opacity at 800ms"); + advance_clock(400); + omta_is_approx("transform", { tx: 80 - 80 * gTF.ease_in(0.2) }, 0.01, + RunningOn.Compositor, "multiprop transform at 1200ms"); + omta_is_approx("opacity", 0.6 + 0.4 * gTF.ease_in(0.2), 0.01, + RunningOn.Compositor, "multiprop opacity at 1200ms"); + advance_clock(200); + omta_is_approx("transform", { tx: 40 + 40 * gTF.ease_in_out(0.4) }, 0.01, + RunningOn.Compositor, "multiprop transform at 1400ms"); + omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.7), 0.01, + RunningOn.Compositor, "multiprop opacity at 1400ms"); + advance_clock(300); + omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.6) }, 0.01, + RunningOn.Compositor, "multiprop transform at 1700ms"); + omta_is_approx("opacity", 0.5 + 0.1 * gTF.ease_out(0.1), 0.01, + RunningOn.Compositor, "multiprop opacity at 1700ms"); + advance_clock(200); + omta_is_approx("transform", { tx: 10 + 30 * gTF.ease(0.2) }, 0.01, + RunningOn.Compositor, "multiprop transform at 1900ms"); + omta_is_approx("opacity", 0.3 + 0.2 * gTF.ease(0.4), 0.01, + RunningOn.Compositor, "multiprop opacity at 1900ms"); + done_div(); +}); + +// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=651456 -- make +// sure that refreshing of animations doesn't break when we get two +// refreshes with the same timestamp. +addAsyncAnimTest(function *() { + new_div("animation: anim2 1s linear"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, "bug 651456 at 0ms"); + advance_clock(100); + omta_is("opacity", 0.1, RunningOn.Compositor, "bug 651456 at 100ms (1)"); + advance_clock(0); // still forces a refresh + omta_is("opacity", 0.1, RunningOn.Compositor, "bug 651456 at 100ms (2)"); + advance_clock(100); + omta_is("opacity", 0.2, RunningOn.Compositor, "bug 651456 at 200ms"); + done_div(); +}); + +// test_animations.html includes a test that UA !important rules override +// animations. Unfortunately, there do not appear to be any UA !important rules +// for opacity or transform except for one targetting a pseudo-element and +// pseudo elements are not animated on the compositor. As a result we cannot +// currently test this behavior. + +// Test that author !important rules override animations, but +// that animations override regular author rules. +addAsyncAnimTest(function *() { + new_div("animation: always_fifty 1s linear infinite; " + + "transform: translate(200px)"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "animations override regular author rules"); + done_div(); + new_div("animation: always_fifty 1s linear infinite; " + + "transform: translate(200px) ! important;"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 200 }, RunningOn.MainThread, + "important author rules override animations"); + done_div(); +}); + +// Test interaction of animations and restyling (Bug 686656). +// This test depends on kf3 getting its 0% and 100% values from the +// rules below it in the cascade; we're checking that the animation +// isn't rebuilt when the restyles happen. +addAsyncAnimTest(function *() { + new_div("animation: kf3 1s linear forwards"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "bug 686656 test 1 at 0ms"); + advance_clock(250); + gDisplay.style.color = "blue"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "bug 686656 test 1 at 250ms"); + advance_clock(375); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "bug 686656 test 1 at 625ms"); + advance_clock(375); + yield waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "bug 686656 test 1 at 1000ms"); + done_div(); + gDisplay.style.color = ""; +}); + +// Test interaction of animations and restyling (Bug 686656), +// with reframing. +// This test depends on kf3 getting its 0% and 100% values from the +// rules below it in the cascade; we're checking that the animation +// isn't rebuilt when the restyles happen. +addAsyncAnimTest(function *() { + new_div("animation: kf3 1s linear forwards"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "bug 686656 test 2 at 0ms"); + advance_clock(250); + gDisplay.style.overflow = "scroll"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "bug 686656 test 2 at 250ms"); + advance_clock(375); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "bug 686656 test 2 at 625ms"); + advance_clock(375); + yield waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "bug 686656 test 2 at 1000ms"); + done_div(); + gDisplay.style.overflow = ""; +}); + +// Test that cascading between keyframes rules is per-property rather +// than per-rule (bug ), and that the timing function isn't taken from a +// rule that's skipped. (Bug 738003) +addAsyncAnimTest(function *() { + new_div("animation: cascade 1s linear forwards; position: relative"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "cascade test (transform) at 0ms"); + omta_is("opacity", 0, RunningOn.Compositor, + "cascade test (opacity) at 0ms"); + advance_clock(125); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "cascade test (transform) at 125ms"); + omta_is("opacity", 0.5, RunningOn.Compositor, + "cascade test (opacity) at 125ms"); + advance_clock(125); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "cascade test (transform) at 250ms"); + omta_is("opacity", 1, RunningOn.Compositor, + "cascade test (opacity) at 250ms"); + advance_clock(125); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "cascade test (transform) at 375ms"); + omta_is("opacity", 1, RunningOn.Compositor, + "cascade test (opacity) at 375ms"); + advance_clock(125); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "cascade test (transform) at 500ms"); + omta_is("opacity", 1, RunningOn.Compositor, + "cascade test (opacity) at 500ms"); + advance_clock(125); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "cascade test (transform) at 625ms"); + omta_is("opacity", 0.5, RunningOn.Compositor, + "cascade test (opacity) at 625ms"); + advance_clock(125); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "cascade test (transform) at 750ms"); + omta_is("opacity", 0, RunningOn.Compositor, + "cascade test (opacity) at 750ms"); + advance_clock(125); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "cascade test (transform) at 875ms"); + omta_is("opacity", 0, RunningOn.Compositor, + "cascade test (opacity) at 875ms"); + advance_clock(125); + yield waitForPaints(); + omta_is("transform", { tx: 0 }, RunningOn.MainThread, + "cascade test (transform) at 1000ms"); + omta_is("opacity", 0, RunningOn.Either, + "cascade test (opacity) at 1000ms"); + done_div(); +}); + +addAsyncAnimTest(function *() { + new_div("animation: cascade2 8s linear forwards"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, "cascade2 test at 0s"); + advance_clock(1000); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "cascade2 test at 1s"); + advance_clock(1000); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, "cascade2 test at 2s"); + advance_clock(1000); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "cascade2 test at 3s"); + advance_clock(1000); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, "cascade2 test at 4s"); + advance_clock(3000); + omta_is("transform", { tx: 75 }, RunningOn.Compositor, "cascade2 test at 7s"); + advance_clock(1000); + yield waitForPaints(); + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + "cascade2 test at 8s"); + done_div(); +}); + +addAsyncAnimTest(function *() { + new_div("animation: primitives1 2s linear forwards"); + yield waitForPaintsFlushed(); + omta_is("transform", { }, RunningOn.Compositor, "primitives1 at 0s"); + advance_clock(1000); + omta_is("transform", [ -0.707107, 0.707107, -0.707107, -0.707107, 0, 0 ], + RunningOn.Compositor, "primitives1 at 1s"); + advance_clock(1000); + yield waitForPaints(); + omta_is("transform", [ 0, -1, 1, 0, 0, 0 ], RunningOn.MainThread, + "primitives1 at 0s"); + done_div(); +}); + +addAsyncAnimTest(function *() { + new_div("animation: important1 1s linear forwards"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0.5, RunningOn.Compositor, "important1 test at 0s"); + advance_clock(500); + omta_is("opacity", 0.65, RunningOn.Compositor, "important1 test at 0.5s"); + advance_clock(500); + yield waitForPaints(); + omta_is("opacity", 0.8, RunningOn.Either, "important1 test at 1s"); + done_div(); +}); + +addAsyncAnimTest(function *() { + new_div("animation: important2 1s linear forwards"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0.5, RunningOn.Compositor, + "important2 (opacity) test at 0s"); + omta_is("transform", { tx: 100 }, RunningOn.Compositor, + "important2 (transform) test at 0s"); + advance_clock(1000); + yield waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "important2 (opacity) test at 1s"); + omta_is("transform", { tx: 50 }, RunningOn.MainThread, + "important2 (transform) test at 1s"); + done_div(); +}); + +addAsyncAnimTest(function *() { + // Test that it's the length of the 'animation-name' list that's used to + // start animations. + // note: anim2 animates opacity from 0 to 1 + // note: anim4 animates transform's y translation component from 0 to 100px + new_div("animation-name: anim2, anim4; " + + "animation-duration: 1s; " + + "animation-timing-function: linear; " + + "animation-delay: -250ms, -250ms, -750ms, -500ms;"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0.25, RunningOn.Compositor, + "animation-name list length is the length that matters"); + omta_is("transform", { ty: 25 }, RunningOn.Compositor, + "animation-name list length is the length that matters"); + done_div(); + new_div("animation-name: anim2, anim4, anim2; " + + "animation-duration: 1s; " + + "animation-timing-function: linear; " + + "animation-delay: -250ms, -250ms, -750ms, -500ms;"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0.75, RunningOn.Compositor, + "animation-name list length is the length that matters, " + + "and the last occurrence of a name wins"); + omta_is("transform", { ty: 25 }, RunningOn.Compositor, + "animation-name list length is the length that matters"); + done_div(); +}); + +addAsyncAnimTest(function *() { + var dyn_sheet_elt = document.createElement("style"); + document.head.appendChild(dyn_sheet_elt); + var dyn_sheet = dyn_sheet_elt.sheet; + dyn_sheet.insertRule( + "@keyframes dyn1 { from { transform: translate(0px) } " + + "50% { transform: translate(50px) } " + + "to { transform: translate(100px) } }", 0); + dyn_sheet.insertRule( + "@keyframes dyn2 { from { transform: translate(100px) } " + + "to { transform: translate(200px) } }", 1); + var dyn1 = dyn_sheet.cssRules[0]; + var dyn2 = dyn_sheet.cssRules[1]; + new_div("animation: dyn1 1s linear"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "dynamic rule change test, initial state"); + advance_clock(250); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, + "dynamic rule change test, 250ms"); + dyn2.name = "dyn1"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 125 }, RunningOn.Compositor, + "dynamic rule change test, change in @keyframes name applies"); + dyn2.appendRule("50% { transform: translate(0px) }"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "dynamic rule change test, @keyframes appendRule"); + // currently 0% { transform: translate(100px) } + var dyn2_kf1 = dyn2.cssRules[0]; + dyn2_kf1.style.transform = "translate(-100px)"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: -50 }, RunningOn.Compositor, + "dynamic rule change test, keyframe style set"); + dyn2.name = "dyn2"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, + "dynamic rule change test, " + + "change in @keyframes name applies (second time)"); + // currently 50% { transform: translate(50px) } + var dyn1_kf2 = dyn1.cssRules[1]; + dyn1_kf2.keyText = "25%"; + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "dynamic rule change test, change in keyframe keyText"); + dyn1.deleteRule("25%"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, + "dynamic rule change test, @keyframes deleteRule"); + done_div(); + dyn_sheet_elt.parentNode.removeChild(dyn_sheet_elt); + dyn_sheet_elt = null; + dyn_sheet = null; +}); + +/* + * Bug 1004361 - CSS animations with short duration sometimes don't dispatch + * a start event + */ +addAsyncAnimTest(function *() { + new_div("animation: anim2 1s 0.1s"); + listen(); + yield waitForPaintsFlushed(); + advance_clock(1200); // Skip past end of animation's entire active duration + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim2', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim2', elapsedTime: 1, + pseudoElement: "" }], + "events after skipping over animation interval"); + done_div(); +}); + +/* + * Bug 1007513 - AnimationEvent.elapsedTime should be animation time + * + * There is no OMTA-version of this test since it is specific to the + * contents of animation events which are dispatched on the main thread. + * + * We *do* provide an OMTA-version of some tests regarding the *dispatch* of + * events to catch possible regressions if in future event dispatch is tied + * to animation throttling. + */ + +/* + * Bug 1004365 - zero-duration animations + */ + +addAsyncAnimTest(function *() { + new_div("transform: translate(0, 200px); animation: anim4 0s 1s both"); + listen(); + yield waitForPaintsFlushed(); + advance_clock(0); + omta_is("transform", { ty: 0 }, RunningOn.MainThread, + "transform during backwards fill of zero-duration animation"); + advance_clock(2000); // Skip over animation + yield waitForPaints(); + omta_is("transform", { ty: 100 }, RunningOn.MainThread, + "transform during backwards fill of zero-duration animation"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }], + "events after skipping over zero-duration animation"); + done_div(); +}); + +addAsyncAnimTest(function *() { + new_div("transform: translate(0, 200px); animation: anim4 0s 1s both"); + listen(); + yield waitForPaintsFlushed(); + advance_clock(0); + // Seek to exactly the point where the animation starts and stops + advance_clock(1000); + yield waitForPaints(); + omta_is("transform", { ty: 100 }, RunningOn.MainThread, + "transform during backwards fill of zero-duration animation"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }], + "events after seeking to end of zero-duration animation"); + // Check no further events are dispatched + advance_clock(0); + advance_clock(100); + check_events([]); + done_div(); +}); + +// We don't need to include all the animation-direction related tests +// found in test_animations.html. We have already asserted above that +// these zero-length animations do in fact run on the main thread and +// we have checked that they dispatch events correctly. +// The actual calculation of values on the main thread is covered by +// test_animations.html + +// We do however still want to test with an infinite repeat count and zero +// duration to ensure this does not confuse the screening of OMTA animations. +addAsyncAnimTest(function *() { + new_div("transform: translate(0, 200px); " + + "animation: anim4 0s 1s both infinite"); + listen(); + yield waitForPaintsFlushed(); + advance_clock(0); + omta_is("transform", { ty: 0 }, RunningOn.MainThread, + "transform during backwards fill of infinitely repeating " + + "zero-duration animation"); + advance_clock(2000); + yield waitForPaints(); + omta_is("transform", { ty: 100 }, RunningOn.MainThread, + "transform during forwards fill of infinitely repeating " + + "zero-duration animation"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }], + "events after seeking to end of infinitely repeating " + + "zero-duration animation"); + done_div(); +}); + +// Test with negative delay +addAsyncAnimTest(function *() { + new_div("transform: translate(0, 200px); " + + "animation: anim4 0s -1s both reverse 12.7 linear"); + listen(); + yield waitForPaintsFlushed(); + advance_clock(0); + omta_is("transform", { ty: 30 }, RunningOn.MainThread, + "transform during forwards fill of reversed and repeated " + + "zero-duration animation with negative delay"); + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'anim4', elapsedTime: 0, + pseudoElement: "" }], + "events after skipping over zero-duration animation " + + "with negative delay"); + done_div(); +}); + +/* + * Bug 1004377 - Animations with empty keyframes rule + */ + +addAsyncAnimTest(function *() { + new_div("margin-right: 200px; animation: empty 2s 1s both"); + listen(); + advance_clock(0); + yield waitForPaintsFlushed(); + check_events([], "events during delay"); + advance_clock(2000); // Skip to middle of animation + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "events during middle of animation with empty keyframes rule"); + advance_clock(1000); // Skip to end of animation + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationend', target: gDiv, + animationName: 'empty', elapsedTime: 2, + pseudoElement: "" }], + "events at end of animation with empty keyframes rule"); + done_div(); +}); + +// Test with a zero-duration animation and empty @keyframes rule +addAsyncAnimTest(function *() { + new_div("margin-right: 200px; animation: empty 0s 1s both"); + listen(); + yield waitForPaintsFlushed(); + advance_clock(1000); + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }, + { type: 'animationend', target: gDiv, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "events at end of zero-duration animation with " + + "empty keyframes rule"); + done_div(); +}); + +// Test with a keyframes rule that becomes empty +addAsyncAnimTest(function *() { + new_div("animation: nearlyempty 1s both linear"); + yield waitForPaintsFlushed(); + advance_clock(500); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "Animation is animating on compositor"); + + // Update keyframes rule and check the result gets removed + listen(); + findKeyframesRule("nearlyempty").deleteRule("to"); + yield waitForPaintsFlushed(); + omta_is("transform", { }, RunningOn.MainThread, + "Animation with (now) empty keyframes rule is cleared " + + "from compositor"); + + // Check we still dispatch the end event however + advance_clock(500); + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationend', target: gDiv, + animationName: 'nearlyempty', elapsedTime: 1, + pseudoElement: "" }], + "events at end of animation with newly " + + "empty keyframes rule"); + + done_div(); +}); + +// Test when we update to point to an empty animation +addAsyncAnimTest(function *() { + new_div("animation: always_fifty 1s both linear"); + yield waitForPaintsFlushed(); + advance_clock(500); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "Animation is animating on compositor"); + + // Update animation name + listen(); + gDiv.style.animationName = "empty"; + yield waitForPaintsFlushed(); + omta_is("transform", { }, RunningOn.MainThread, + "Animation updated to use empty keyframes rule is cleared " + + "from compositor"); + + // Check events + advance_clock(500); + gDiv.clientTop; // Trigger events + check_events([{ type: 'animationstart', target: gDiv, + animationName: 'empty', elapsedTime: 0, + pseudoElement: "" }], + "events at start of animation updated to use " + + "empty keyframes rule"); + + done_div(); +}); + +// Bug 996796 patch 12 - test for correct visited styles during +// animation-only style flush. +addAsyncAnimTest(function *() { + var isb2g = SpecialPowers.Services.appinfo.name == "B2G"; + if (isb2g) { + todo(false, "no global history on B2G; can't run test"); + return; + } + + var div1 = document.createElement("div"); + div1.classList.add("target"); + div1.style.height = "10px"; + div1.style.animation = "anim2 linear 1s"; + + var visitedLink = document.createElement("a"); + visitedLink.setAttribute("href", window.top.location.href); + visitedLink.classList.add("visitedLink"); + visitedLink.classList.add("target"); + visitedLink.style.display = "block"; + visitedLink.style.height = "10px"; + visitedLink.style.animation = "anim2 linear 1s"; + + var refVisitedLink = document.createElement("a"); + refVisitedLink.setAttribute("href", window.top.location.href); + refVisitedLink.classList.add("visitedLink"); + + gDisplay.appendChild(div1); + gDisplay.appendChild(visitedLink); + gDisplay.appendChild(refVisitedLink); + + // Wait for visited link coloring. + yield waitForVisitedLinkColoring(refVisitedLink, + "background-color", "rgb(0, 0, 255)"); + + // Wait for animations to start. + yield waitForPaintsFlushed(); + + var bgColor = SpecialPowers.DOMWindowUtils + .getVisitedDependentComputedStyle(visitedLink, "", "background-color"); + is(bgColor, "rgb(0, 0, 255)", "initial visited link background color"); + + advance_clock(250); + + // Trigger a style change on div1 that will force us to do a miniflush, + // but which will not trigger a style change on visitedLink. + div1.style.color = "blue"; + advance_clock(250); + + bgColor = SpecialPowers.DOMWindowUtils + .getVisitedDependentComputedStyle(visitedLink, "", "background-color"); + + is(bgColor, "rgb(0, 0, 255)", + "visited link background color after animation-only flush"); + + div1.remove(); + visitedLink.remove(); + refVisitedLink.remove(); +}); + +/* + * Bug 962594 - Turn off CSS animations when the element is display:none, or + * is in a display:none subtree. + */ + +// Check that it works if the animated element itself becomes display:none +addAsyncAnimTest(function *() { + new_div("animation: anim4 linear 10s"); + yield waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform animation is running on compositor"); + advance_clock(1000); + omta_is("transform", { ty: 10 }, RunningOn.Compositor, + "transform animation is at 1s on compositor"); + gDiv.style.display = "none"; + yield waitForPaintsFlushed(); + omta_is("transform", "none", RunningOn.MainThread, + "transform animation stopped on compositor"); + advance_clock(1000); + omta_is("transform", "none", RunningOn.MainThread, + "transform animation 1s after display:none"); + gDiv.style.display = ""; + yield waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform animation after display:block"); + advance_clock(1000); + omta_is("transform", { ty: 10 }, RunningOn.Compositor, + "transform animation 1s after display:block"); + done_div(); +}); + +// Check that it works if an ancestor of the animated element becomes display:none +addAsyncAnimTest(function *() { + new_div("animation: anim4 linear 10s"); + var ancestor = document.createElement("div"); + gDiv.parentNode.insertBefore(ancestor, gDiv); + ancestor.appendChild(gDiv); + yield waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform animation is running on compositor"); + advance_clock(1000); + omta_is("transform", { ty: 10 }, RunningOn.Compositor, + "transform animation is at 1s on compositor"); + gDiv.style.display = "none"; + yield waitForPaintsFlushed(); + omta_is("transform", "none", RunningOn.MainThread, + "transform animation stopped on compositor"); + advance_clock(1000); + omta_is("transform", "none", RunningOn.MainThread, + "transform animation 1s after display:none"); + gDiv.style.display = ""; + yield waitForPaintsFlushed(); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform animation after display:block"); + advance_clock(1000); + omta_is("transform", { ty: 10 }, RunningOn.Compositor, + "transform animation 1s after display:block"); + ancestor.parentNode.insertBefore(gDiv, ancestor); + ancestor.parentNode.removeChild(ancestor); + done_div(); +}); + +// Bug 1125455 - Transitions should not run when animations are running. +addAsyncAnimTest(function *() { + new_div("transition: opacity 2s linear; opacity: 0.8"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0.8, RunningOn.MainThread, + "initial opacity"); + gDiv.style.opacity = "0.2"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0.8, RunningOn.Compositor, + "opacity transition at 0s"); + advance_clock(500); + omta_is("opacity", 0.65, RunningOn.Compositor, + "opacity transition at 0.5s"); + gDiv.style.animation = "opacitymid 2s linear"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0.2, RunningOn.Compositor, + "opacity animation overriding transition at 0s"); + advance_clock(500); + omta_is("opacity", 0.35, RunningOn.Compositor, + "opacity animation overriding transition at 0.5s"); + done_div(); +}); + +// Bug 847287 - Test that changes of when an animation is dynamically +// overridden work correctly. +addAsyncAnimTest(function *() { + // anim2 and anim3 are both animations from opacity 0 to 1 + + new_div("animation: anim2 1s linear forwards; opacity: 0.5 ! important"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation at start (0s)"); + advance_clock(750); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation while running (750ms)"); + advance_clock(1000); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation while filling (1750ms)"); + done_div(); + + new_div("animation: anim2 1s linear; opacity: 0.5 ! important"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation at start (0s)"); + advance_clock(750); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation while running (750ms)"); + advance_clock(1000); + omta_is("opacity", 0.5, RunningOn.MainThread, + "opacity overriding animation after complete (1750ms)"); + done_div(); + + // One animation overriding another, and then not. + new_div("animation: anim2 1s linear, anim3 500ms linear reverse"); + yield waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.Compositor, + "anim3 overriding anim2 at start (0s)"); + advance_clock(400); + omta_is("opacity", 0.2, RunningOn.Compositor, + "anim3 overriding anim2 at 400ms"); + advance_clock(200); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + yield waitForPaints(); + omta_is("opacity", 0.6, RunningOn.Compositor, + "anim2 at 600ms"); + done_div(); + + // One animation overriding another, and then not, but without a + // restyle when the overriding one ends. + new_div("animation: anim2 1s steps(8, end)"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim2 at start (0s)"); + advance_clock(300); + omta_is("opacity", 0.25, RunningOn.Compositor, + "anim2 at 300ms"); + gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim3 overriding anim2 at 300ms"); + advance_clock(475); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim3 the same as anim2 at 775ms"); + advance_clock(50); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + yield waitForPaints(); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim2 at 825ms"); + advance_clock(75); + omta_is("opacity", 0.875, RunningOn.Compositor, + "anim2 at 900ms"); + done_div(); + + // Exactly the same as the previous test, except with an extra + // waitForPaintsFlushed(), since that extra one exposes other bugs. + new_div("animation: anim2 1s steps(8, end)"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim2 at start (0s)"); + advance_clock(300); + omta_is("opacity", 0.25, RunningOn.Compositor, + "anim2 at 300ms"); + gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)"; + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim3 overriding anim2 at 300ms"); + advance_clock(475); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim3 the same as anim2 at 775ms"); + // Extra waitForPaintsFlushed to expose bugs. + yield waitForPaintsFlushed(); + advance_clock(50); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + yield waitForPaints(); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim2 at 825ms"); + advance_clock(75); + omta_is("opacity", 0.875, RunningOn.Compositor, + "anim2 at 900ms"); + done_div(); + + // Test that an interpolation that produces transform: none doesn't + // crash. + new_div("animation: transformnone 1s linear"); + yield waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "transformnone animation at 0ms"); + advance_clock(500); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "transformnone animation at 500ms, interpolating none values"); + done_div(); +}); + +addAsyncAnimTest(function *() { + new_div("transform: translate(100px); transition: transform 10s 5s linear"); + yield waitForPaintsFlushed(); + gDiv.style.transform = "translate(200px)"; + yield waitForPaintsFlushed(); + // NOTE: As noted above, getOMTAStyle() can't detect the animation is running + // on the compositor during the delay phase. + omta_is("transform", { tx: 100 }, RunningOn.Either, + "transition runs on compositor thread during delay"); + // At the *very* start of the transition the start value of the transition + // will match the underlying transform value. Various optimizations in + // RestyleManager may recognize this a "no change" and filter out the + // transition meaning that the animation doesn't get added to the compositor + // thread until the first time the value changes. As a result, we fast-forward + // a little past the beginning and then wait for the animation to be sent + // to the compositor. + advance_clock(5100); + yield waitForPaints(); + omta_is("transform", { tx: 101 }, RunningOn.Compositor, + "transition runs on compositor at start of active interval"); + advance_clock(4900); + omta_is("transform", { tx: 150 }, RunningOn.Compositor, + "transition runs on compositor at during active interval"); + advance_clock(5000); + // Currently the compositor will apply a forwards fill until it gets told by + // the main thread to clear the animation. As a result we should wait for + // paints before checking that the animated value does *not* appear on the + // compositor thread. + yield waitForPaints(); + omta_is("transform", { tx: 200 }, RunningOn.MainThread, + "transition runs on main thread at end of active interval"); + + done_div(); +}); + +</script> +</html> diff --git a/layout/style/test/test_animations_omta_start.html b/layout/style/test/test_animations_omta_start.html new file mode 100644 index 000000000..235c32342 --- /dev/null +++ b/layout/style/test/test_animations_omta_start.html @@ -0,0 +1,189 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=975261 +--> +<head> + <meta charset="utf-8"> + <title>Test OMTA animations start correctly (Bug 975261)</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + @keyframes anim-opacity { + 0% { opacity: 0.5 } + 100% { opacity: 0.5 } + } + @keyframes anim-opacity-2 { + 0% { opacity: 0.0 } + 100% { opacity: 1.0 } + } + @keyframes anim-transform { + 0% { transform: translate(50px); } + 100% { transform: translate(50px); } + } + @keyframes anim-transform-2 { + 0% { transform: translate(0px); } + 100% { transform: translate(100px); } + } + .target { + /* These two lines are needed so that an opacity/transform layer + * already exists when the animation is applied. */ + opacity: 0.99; + transform: translate(99px); + + /* Element needs geometry in order to be animated on the + * compositor. */ + width: 100px; + height: 100px; + background-color: white; + } + </style> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=975261">Mozilla Bug + 975261</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +var gUtils = SpecialPowers.DOMWindowUtils; + +SimpleTest.waitForExplicitFinish(); +runOMTATest(testDelay, SimpleTest.finish); + +function newTarget() { + var target = document.createElement("div"); + target.classList.add("target"); + document.getElementById("display").appendChild(target); + return target; +} + +function testDelay() { + gUtils.advanceTimeAndRefresh(0); + + var target = newTarget(); + target.setAttribute("style", "animation: 10s 10s anim-opacity linear"); + gUtils.advanceTimeAndRefresh(0); + + waitForAllPaints(function() { + gUtils.advanceTimeAndRefresh(10100); + waitForAllPaints(function() { + var opacity = gUtils.getOMTAStyle(target, "opacity"); + is(opacity, "0.5", + "opacity is set on compositor thread after delayed start"); + target.removeAttribute("style"); + gUtils.restoreNormalRefresh(); + testTransform(); + }); + }); +} + +function testTransform() { + gUtils.advanceTimeAndRefresh(0); + + var target = newTarget(); + target.setAttribute("style", "animation: 10s 10s anim-transform linear"); + gUtils.advanceTimeAndRefresh(0); + + waitForAllPaints(function() { + gUtils.advanceTimeAndRefresh(10100); + waitForAllPaints(function() { + var transform = gUtils.getOMTAStyle(target, "transform"); + ok(matricesRoughlyEqual(convertTo3dMatrix(transform), + convertTo3dMatrix("matrix(1, 0, 0, 1, 50, 0)")), + "transform is set on compositor thread after delayed start"); + target.remove(); + gUtils.restoreNormalRefresh(); + testBackwardsFill(); + }); + }); +} + +function testBackwardsFill() { + gUtils.advanceTimeAndRefresh(0); + + var target = newTarget(); + target.setAttribute("style", + "transform: translate(30px); " + + "animation: 10s 10s anim-transform-2 linear backwards"); + + gUtils.advanceTimeAndRefresh(0); + waitForAllPaints(function() { + gUtils.advanceTimeAndRefresh(10000); + waitForAllPaints(function() { + gUtils.advanceTimeAndRefresh(100); + waitForAllPaints(function() { + var transform = gUtils.getOMTAStyle(target, "transform"); + ok(matricesRoughlyEqual(convertTo3dMatrix(transform), + convertTo3dMatrix("matrix(1, 0, 0, 1, 1, 0)")), + "transform is set on compositor thread after delayed start " + + "with backwards fill"); + target.remove(); + gUtils.restoreNormalRefresh(); + testTransitionTakingOver(); + }); + }); + }); +} + +function testTransitionTakingOver() { + gUtils.advanceTimeAndRefresh(0); + + var parent = newTarget(); + var child = newTarget(); + parent.appendChild(child); + parent.style.opacity = "0.0"; + parent.style.animation = "10s anim-opacity-2 linear"; + child.style.opacity = "inherit"; + child.style.transition = "10s opacity linear"; + + var childCS = getComputedStyle(child, ""); + + gUtils.advanceTimeAndRefresh(0); + waitForAllPaints(function() { + gUtils.advanceTimeAndRefresh(4000); + waitForAllPaints(function() { + child.style.opacity = "1.0"; + var opacity = gUtils.getOMTAStyle(child, "opacity"); + // FIXME Bug 1039799 (or lower priority followup): Animations + // inherited from an animating parent element don't get shipped to + // the compositor thread. + todo_is(opacity, "0.4", + "transition that interrupted animation is correct"); + + // Trigger to start the transition, without this the transition will + // be pending in advanceTimeAndRefresh(0) so the transition will not + // be sent to the compositor until we call advanceTimeAndRefresh with + // a positive time value. + getComputedStyle(child).opacity; + gUtils.advanceTimeAndRefresh(0); + waitForAllPaints(function() { + var opacity = gUtils.getOMTAStyle(child, "opacity"); + is(opacity, "0.4", + "transition that interrupted animation is correct"); + gUtils.advanceTimeAndRefresh(5000); + waitForAllPaints(function() { + opacity = gUtils.getOMTAStyle(child, "opacity"); + is(opacity, "0.7", + "transition that interrupted animation is correct"); + is(childCS.opacity, "0.7", + "transition that interrupted animation is correct"); + parent.remove(); + gUtils.restoreNormalRefresh(); + SimpleTest.finish(); + }); + }); + }); + }); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_pausing.html b/layout/style/test/test_animations_pausing.html new file mode 100644 index 000000000..65dee5b29 --- /dev/null +++ b/layout/style/test/test_animations_pausing.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1070745 +--> +<head> + <title>Test for play() and pause() on animations (Bug 1070745)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1070745">Mozilla Bug 1070745</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + { "set": [[ "dom.animations-api.core.enabled", true]] }, + function() { + window.open("file_animations_pausing.html"); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_playbackrate.html b/layout/style/test/test_animations_playbackrate.html new file mode 100644 index 000000000..16deca02c --- /dev/null +++ b/layout/style/test/test_animations_playbackrate.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1175751 +--> +<head> + <title>Test for Animation.playbackRate on compositor animations (Bug 1175751)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175751">Mozilla Bug 1175751</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + { "set": [[ "dom.animations-api.core.enabled", true]] }, + function() { + window.open("file_animations_playbackrate.html"); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_styles_on_event.html b/layout/style/test/test_animations_styles_on_event.html new file mode 100644 index 000000000..da7680727 --- /dev/null +++ b/layout/style/test/test_animations_styles_on_event.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1228137 +--> +<head> + <title>Test that mouse movement immediately after finish() should involve restyling for finished state(Bug 1228137)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1219236">Mozilla Bug 1228137</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + { "set": [[ "dom.animations-api.core.enabled", true]] }, + function() { + window.open("file_animations_styles_on_event.html"); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_with_disabled_properties.html b/layout/style/test/test_animations_with_disabled_properties.html new file mode 100644 index 000000000..2ac631169 --- /dev/null +++ b/layout/style/test/test_animations_with_disabled_properties.html @@ -0,0 +1,34 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1265611 +--> +<head> + <title>Test CSS animations ignore disabled properties (Bug 1265611)</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265611">Mozilla Bug + 1265611</a> +<pre id="test"> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); + +/* + * This test relies on the fact that the -webkit-text-fill-color property + * is disabled by the layout.css.prefixes.webkit pref. If we ever remove that + * pref we will need to substitute some other pref:property combination. + */ +SpecialPowers.pushPrefEnv( + { 'set': [[ 'dom.animations-api.core.enabled', true ], + [ 'layout.css.prefixes.webkit', false ]] }, + () => window.open('file_animations_with_disabled_properties.html')); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_any_dynamic.html b/layout/style/test/test_any_dynamic.html new file mode 100644 index 000000000..ae3276d86 --- /dev/null +++ b/layout/style/test/test_any_dynamic.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=544834 +--> +<head> + <title>Test for Bug 544834</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + :-moz-any(#display, #display2) { text-decoration: underline } + p:-moz-any([foo], [bar]) { z-index: 17 } + + </style> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=544834">Mozilla Bug 544834</a> +<p id="display" style="position:absolute"></p> +<pre id="test"> +<script type="application/javascript"> + +/** + * Test for Bug 544834 + * + * In particular, test that we go through :-moz-any() in AddRule. + */ + +function run() +{ + var p = document.getElementById("display"); + var cs = getComputedStyle(p, ""); + is(cs.textDecoration, "underline", "should match first rule"); + is(cs.zIndex, "auto", "should not match second rule"); + p.removeAttribute("id"); + is(cs.textDecoration, "none", "should not match first rule"); + is(cs.zIndex, "auto", "should not match second rule"); + p.setAttribute("foo", "v"); + is(cs.textDecoration, "none", "should not match first rule"); + is(cs.zIndex, "17", "should match second rule"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_asyncopen2.html b/layout/style/test/test_asyncopen2.html new file mode 100644 index 000000000..6dda6848a --- /dev/null +++ b/layout/style/test/test_asyncopen2.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1195173 +--> +<head> + <title>Bug 1195173 - Test asyncOpen2 security exception</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <!-- Note: the following stylesheet does not exist --> + <link rel="stylesheet" id="myCSS" type="text/css" href="file:///home/foo/bar.css"> + +</head> +<body onload="checkCSS()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1195173">Mozilla Bug 1195173</a> +<p id="display"></p> +<div id="content" style="display: none"></div> + +<script type="application/javascript"> +/* + * Description of the test: + * Accessing a stylesheet that got blocked by asyncOpen2 should + * throw an exception. + */ + +SimpleTest.waitForExplicitFinish(); + +function checkCSS() +{ + try { + // accessing tests/SimpleTest/test.css should not throw + var goodCSS = document.styleSheets[0].cssRules + ok(true, "accessing test.css should be allowed"); + } + catch(e) { + ok(false, "accessing test.css should be allowed"); + } + + try { + // accessing file:///home/foo/bar.css should throw + var badCSS = document.styleSheets[1].cssRules + ok(false, "accessing bar.css should throw"); + } + catch(e) { + ok(true, "accessing bar.css should throw"); + } + + SimpleTest.finish(); +} + +</script> +</body> +</html> diff --git a/layout/style/test/test_at_rule_parse_serialize.html b/layout/style/test/test_at_rule_parse_serialize.html new file mode 100644 index 000000000..3723e335b --- /dev/null +++ b/layout/style/test/test_at_rule_parse_serialize.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=478160 +--> +<head> + <title>Test for Bug 478160</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="style" type="text/css"></style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478160">Mozilla Bug 478160</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 478160 **/ + +var style_element = document.getElementById("style"); +var style_text = document.createTextNode(""); +style_element.appendChild(style_text); + +function test_at_rule(str) { + style_text.data = str; + is(style_element.sheet.cssRules.length, 1, + "should have one rule from " + str); + var ser1 = style_element.sheet.cssRules[0].cssText; + isnot(ser1, "", "should have non-empty rule from " + str); + style_text.data = ser1; + var ser2 = style_element.sheet.cssRules[0].cssText; + is(ser2, ser1, "parse+serialize should be idempotent for " + str); +} + +test_at_rule("@namespace 'a b'"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_attribute_selector_eof_behavior.html b/layout/style/test/test_attribute_selector_eof_behavior.html new file mode 100644 index 000000000..76635f9ed --- /dev/null +++ b/layout/style/test/test_attribute_selector_eof_behavior.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for EOF behavior of attribute selectors in selectors API</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + assert_equals(document.querySelector("[id"), + document.getElementById("log"), + "We only have one element with an id"); +}, "']' should be implied if EOF after attribute name"); +test(function() { + assert_equals(document.querySelector('[id="log"'), + document.getElementById("log"), + "We should find the element with id=log"); +}, "']' should be implied if EOF after attribute value"); +</script> diff --git a/layout/style/test/test_background_blend_mode.html b/layout/style/test/test_background_blend_mode.html new file mode 100644 index 000000000..443fe1940 --- /dev/null +++ b/layout/style/test/test_background_blend_mode.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for miscellaneous computed style issues</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for miscellaneous computed style issues **/ + +var frame_container = document.getElementById("display"); +var noframe_container = document.getElementById("content"); + +function test_bug_841601() { + // Test handling of background-blend-mode + var p = document.createElement("p"); + var cs = getComputedStyle(p, ""); + + frame_container.appendChild(p); + is(cs.backgroundBlendMode, "normal", + "default value of background-blend-mode"); + + p.setAttribute("style", "background-blend-mode: normal, invalid"); + cs = getComputedStyle(p, ""); + is(cs.backgroundBlendMode, "normal", + "set invalid blendmode"); + + p.setAttribute("style", "background-blend-mode: normal, normal"); + cs = getComputedStyle(p, ""); + is(cs.backgroundBlendMode, "normal, normal", + "set normal blendmode twice"); + + p.setAttribute("style", "background-blend-mode: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity"); + cs = getComputedStyle(p, ""); + is(cs.backgroundBlendMode, "normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity", + "set all blendmodes"); + + p.parentNode.removeChild(p); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ "set": [["layout.css.background-blend-mode.enabled", true]] }, + test_bug_841601); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_box_size_keywords.html b/layout/style/test/test_box_size_keywords.html new file mode 100644 index 000000000..c8bd5e86c --- /dev/null +++ b/layout/style/test/test_box_size_keywords.html @@ -0,0 +1,172 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1122253 +--> +<head> + <title>Test for Bug 1122253</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1122253">Mozilla Bug 1122253</a> + +<style> +#outer { + position: absolute; + width: 200px; + height: 200px; +} +#horizontal, #vertical { + background-color: #ccc; + line-height: 1px; +} +#vertical { + writing-mode: vertical-rl; + position: relative; + top: 10px; +} +.small, .big { + display: inline-block; + block-size: 10px; +} +.small { + background-image: linear-gradient(to bottom right, black, fuchsia); + inline-size: 10px; +} +.big { + background-image: linear-gradient(to bottom right, black, cyan); + inline-size: 90px; +} +</style> + +<div id=outer> + <div id=horizontal><span class=small></span><span class=big></span><span class=big></span><span class=big></span></div> + <div id=vertical><span class=small></span><span class=big></span><span class=big></span><span class=big></span></div> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1122253 **/ + +// Test that the -moz-available, -moz-min-content, -moz-max-content, and +// -moz-fit-content keywords are usable only on width, when the writing +// mode is horizontal, or height, when the writing mode is vertical, +// and that they are always available on inline-size and never on +// block-size. When used on the wrong properties, they should be +// equivalent to unset. +// +// Also test the corresponding min-* and max-* properties. + +var gTests = [ + { orientation: "horizontal", property: "width", specified_value: "-moz-available", computed_value: "200px", }, + { orientation: "horizontal", property: "width", specified_value: "-moz-min-content", computed_value: "90px", }, + { orientation: "horizontal", property: "width", specified_value: "-moz-max-content", computed_value: "280px", }, + { orientation: "horizontal", property: "width", specified_value: "-moz-fit-content", computed_value: "200px", }, + { orientation: "horizontal", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", }, + { orientation: "horizontal", property: "inline-size", specified_value: "-moz-min-content", computed_value: "90px", }, + { orientation: "horizontal", property: "inline-size", specified_value: "-moz-max-content", computed_value: "280px", }, + { orientation: "horizontal", property: "inline-size", specified_value: "-moz-fit-content", computed_value: "200px", }, + { orientation: "horizontal", property: "min-width", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", property: "min-width", specified_value: "-moz-min-content", computed_value: "-moz-min-content", }, + { orientation: "horizontal", property: "min-width", specified_value: "-moz-max-content", computed_value: "-moz-max-content", }, + { orientation: "horizontal", property: "min-width", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", }, + { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-min-content", computed_value: "-moz-min-content", }, + { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-max-content", computed_value: "-moz-max-content", }, + { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", }, + { orientation: "horizontal", property: "max-width", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", property: "max-width", specified_value: "-moz-min-content", computed_value: "-moz-min-content", }, + { orientation: "horizontal", property: "max-width", specified_value: "-moz-max-content", computed_value: "-moz-max-content", }, + { orientation: "horizontal", property: "max-width", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", }, + { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-min-content", computed_value: "-moz-min-content", }, + { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-max-content", computed_value: "-moz-max-content", }, + { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", }, + { orientation: "vertical", property: "height", specified_value: "-moz-available", computed_value: "200px", }, + { orientation: "vertical", property: "height", specified_value: "-moz-min-content", computed_value: "90px", }, + { orientation: "vertical", property: "height", specified_value: "-moz-max-content", computed_value: "280px", }, + { orientation: "vertical", property: "height", specified_value: "-moz-fit-content", computed_value: "200px", }, + { orientation: "vertical", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", }, + { orientation: "vertical", property: "inline-size", specified_value: "-moz-min-content", computed_value: "90px", }, + { orientation: "vertical", property: "inline-size", specified_value: "-moz-max-content", computed_value: "280px", }, + { orientation: "vertical", property: "inline-size", specified_value: "-moz-fit-content", computed_value: "200px", }, + { orientation: "vertical", property: "min-height", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", property: "min-height", specified_value: "-moz-min-content", computed_value: "-moz-min-content", }, + { orientation: "vertical", property: "min-height", specified_value: "-moz-max-content", computed_value: "-moz-max-content", }, + { orientation: "vertical", property: "min-height", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", }, + { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-min-content", computed_value: "-moz-min-content", }, + { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-max-content", computed_value: "-moz-max-content", }, + { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", }, + { orientation: "vertical", property: "max-height", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", property: "max-height", specified_value: "-moz-min-content", computed_value: "-moz-min-content", }, + { orientation: "vertical", property: "max-height", specified_value: "-moz-max-content", computed_value: "-moz-max-content", }, + { orientation: "vertical", property: "max-height", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", }, + { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-min-content", computed_value: "-moz-min-content", }, + { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-max-content", computed_value: "-moz-max-content", }, + { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-fit-content", computed_value: "-moz-fit-content", }, + { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "-moz-available", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "-moz-min-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "-moz-max-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "-moz-fit-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-min-content", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-max-content", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-fit-content", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-available", computed_value: "0px", }, + { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-min-content", computed_value: "0px", }, + { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-max-content", computed_value: "0px", }, + { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-fit-content", computed_value: "0px", }, + { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-min-content", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-max-content", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-fit-content", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-available", computed_value: "none", }, + { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-min-content", computed_value: "none", }, + { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-max-content", computed_value: "none", }, + { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-fit-content", computed_value: "none", }, + { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-min-content", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-max-content", computed_value: "30px", }, + { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-fit-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-available", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-min-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-max-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-fit-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-min-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-max-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-fit-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-available", computed_value: "0px", }, + { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-min-content", computed_value: "0px", }, + { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-max-content", computed_value: "0px", }, + { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-fit-content", computed_value: "0px", }, + { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-min-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-max-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-fit-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-available", computed_value: "none", }, + { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-min-content", computed_value: "none", }, + { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-max-content", computed_value: "none", }, + { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-fit-content", computed_value: "none", }, + { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-min-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-max-content", computed_value: "30px", }, + { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-fit-content", computed_value: "30px", }, +]; + +gTests.forEach(function(t) { + var e = document.getElementById(t.orientation); + e.style = (t.prerequisites || "") + t.property + ": " + t.specified_value; + is(get_computed_value(getComputedStyle(e), t.property), t.computed_value, + `${t.orientation} ${t.property}:${t.specified_value}`); + e.style = ""; +}); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1055933.html b/layout/style/test/test_bug1055933.html new file mode 100644 index 000000000..bce171682 --- /dev/null +++ b/layout/style/test/test_bug1055933.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1055933
+-->
+<head>
+ <title>Test for Bug 1055933</title>
+ <script type="text/javascript" src="/MochiKit/packed.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1055933">Mozilla Bug 1055933</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 1055933 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var element = document.getElementById('area');
+ is(getComputedStyle(element).cursor, "pointer", "Does the <area> element return the correct cursor?");
+ //Force mPrimaryFrame to be set
+ requestAnimationFrame( function() {
+ synthesizeMouseAtCenter(document.getElementById('image'), {}, window);
+ is(getComputedStyle(element).cursor, "pointer", "Does the <area> element still return the correct cursor after mPrimaryFrame is set?");
+ SimpleTest.finish();
+ });
+});
+</script>
+</pre>
+ <div style="cursor: crosshair">
+ <img usemap="#map" border ="0" id="image" src="file_bug1055933_circle-xxl.png">
+ <map id="map" name="map">
+ <area id="area" onmousedown="this.setCapture();" onmouseup="this.releaseCapture();" shape="circle" coords="128,129,103" style="cursor: pointer">
+ </map>
+ </div>
+</body>
+</html>
diff --git a/layout/style/test/test_bug1089417.html b/layout/style/test/test_bug1089417.html new file mode 100644 index 000000000..3b5a217e4 --- /dev/null +++ b/layout/style/test/test_bug1089417.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1089417 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1089417</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1089417 **/ + + SimpleTest.waitForExplicitFinish(); + + function run() { + var f = document.getElementById("f"); + var fwin = f.contentWindow; + var fdoc = f.contentDocument; + + f.height = "400"; + fdoc.getElementById("s").disabled = false; + is(fwin.getComputedStyle(fdoc.documentElement).backgroundColor, + "rgb(0, 128, 0)", + "media query change should have restyled"); + + f.height = "200"; + fdoc.getElementById("s").disabled = true; + fdoc.getElementById("s").disabled = false; + is(fwin.getComputedStyle(fdoc.documentElement).backgroundColor, + "rgb(255, 0, 0)", + "media query change should have restyled"); + SimpleTest.finish(); + } + + </script> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1089417">Mozilla Bug 1089417</a> +<div id="display"> + <iframe id="f" src="file_bug1089417_iframe.html" width="300" height="200"></iframe> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1112014.html b/layout/style/test/test_bug1112014.html new file mode 100644 index 000000000..d0f66aa46 --- /dev/null +++ b/layout/style/test/test_bug1112014.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1112014 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1112014</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="property_database.js"></script> + <script type="application/javascript;version=1.7"> + + let utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + + SimpleTest.requestLongerTimeout(2); + + // This holds a canonical test value for each TYPE_ constant. + let testValues = { + TYPE_LENGTH: "10px", + TYPE_PERCENTAGE: "50%", + TYPE_COLOR: "rgb(3,3,3)", + TYPE_URL: "url(mozilla.org)", + TYPE_ANGLE: "90deg", + TYPE_FREQUENCY: "10kHz", + TYPE_TIME: "1000ms", + TYPE_GRADIENT: "linear-gradient( 45deg, blue, red )", + TYPE_TIMING_FUNCTION: "cubic-bezier(0.1, 0.7, 1.0, 0.1)", + TYPE_IMAGE_RECT: "-moz-image-rect(url(firefox.jpg), 5%, 5%, 10%, 10%)", + TYPE_NUMBER: "42" + }; + + // The canonical test values don't work for all properties, in + // particular some shorthand properties. For these cases we have + // override values. + let overrideValues = { + "font": { + TYPE_LENGTH: "10px san-serif", + TYPE_PERCENTAGE: "50% san-serif", + TYPE_NUMBER: "24px/1.5 san-serif" + }, + "border-image": { + TYPE_LENGTH: "url(/somewhere) 30% / 30px stretch", + TYPE_IMAGE_RECT: testValues.TYPE_IMAGE_RECT + " 30 30 stretch" + }, + "-moz-border-image": { + TYPE_LENGTH: "url(/somewhere) 30% / 30px stretch", + TYPE_IMAGE_RECT: testValues.TYPE_IMAGE_RECT + " 30 30 stretch" + }, + "-webkit-border-image": { + TYPE_LENGTH: "url(/somewhere) 30% / 30px stretch", + TYPE_IMAGE_RECT: testValues.TYPE_IMAGE_RECT + " 30 30 stretch" + }, + "box-shadow": { + TYPE_LENGTH: "2px 2px", + TYPE_COLOR: testValues.TYPE_COLOR + " 2px 2px" + }, + "-webkit-box-shadow": { + TYPE_LENGTH: "2px 2px", + TYPE_COLOR: testValues.TYPE_COLOR + " 2px 2px" + }, + "text-shadow": { + TYPE_LENGTH: "2px 2px", + TYPE_COLOR: testValues.TYPE_COLOR + " 2px 2px" + }, + "font-weight": { + TYPE_NUMBER: "400" + }, + "grid-template": { + TYPE_LENGTH: "'something' 23px", + TYPE_PERCENTAGE: "'something' 23%" + }, + "grid": { + TYPE_LENGTH: "'something' 23px", + TYPE_PERCENTAGE: "'something' 23%" + }, + }; + + + // Ensure that all the TYPE_ constants have a representative + // test value, to try to ensure that this test is updated + // whenever a new type is added. + let reps = []; + for (let tc in utils) { + if (/TYPE_/.test(tc)) { + if (!(tc in testValues)) { + reps.push(tc); + } + } + } + is(reps.join(","), "", "all types have representative test value"); + + for (let propertyName in gCSSProperties) { + let prop = gCSSProperties[propertyName]; + + for (let iter in testValues) { + let testValue = testValues[iter]; + if (propertyName in overrideValues && + iter in overrideValues[propertyName]) { + testValue = overrideValues[propertyName][iter]; + } + + let supported = + utils.cssPropertySupportsType(propertyName, utils[iter]); + let parsed = utils.cssPropertyIsValid(propertyName, testValue); + is(supported, parsed, propertyName + " supports " + iter); + } + } + + // Regression test for an assertion failure in an earlier version of + // the code. Note that cssPropertySupportsType returns false for + // all types for a variable. + ok(!utils.cssPropertySupportsType("--variable", utils.TYPE_COLOR), + "cssPropertySupportsType returns false for variable"); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1112014">Mozilla Bug 1112014</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1203766.html b/layout/style/test/test_bug1203766.html new file mode 100644 index 000000000..5d8152440 --- /dev/null +++ b/layout/style/test/test_bug1203766.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for bug 1203766</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +<style> +.x { color: red; } +body > .x { color: green; } +.y { color: green; } +body > .y { display: none; color: red; } +div > .z { color: red; } +.z { color: green; } +.a { color: red; } +body > .a { display: none; color: green; } +.b { display: none; } +.c { color: red; } +.b > .c { color: green; } +.e { color: red; } +.d > .e { color: green; } +.f { color: red; } +.g { color: green; } +.h > .i { color: red; } +.j > .i { color: green; } +</style> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1203766">Mozilla Bug 1203766</a> +<p id="display"></p> +<div class=y></div> +<div class=b></div> +<pre id="test"> +<script class="testbody"> +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + + // Element that goes from being out of the document to in the document. + var e = document.createElement("div"); + e.className = "x"; + var cs = getComputedStyle(e); + is(cs.color, "rgb(255, 0, 0)"); + document.body.appendChild(e); + is(cs.color, "rgb(0, 128, 0)"); + + // Element that goes from in the document (and display:none) to out of + // the document. + e = document.querySelector(".y"); + cs = getComputedStyle(e); + is(cs.color, "rgb(255, 0, 0)"); + e.remove(); + is(cs.color, "rgb(0, 128, 0)"); + + // Element that is removed from an out-of-document tree. + e = document.createElement("div"); + f = document.createElement("span"); + f.className = "z"; + e.appendChild(f); + cs = getComputedStyle(f); + is(cs.color, "rgb(255, 0, 0)"); + f.remove(); + is(cs.color, "rgb(0, 128, 0)"); + + // Element going from not in document to in document and display:none. + e = document.createElement("div"); + e.className = "a"; + cs = getComputedStyle(e); + is(cs.color, "rgb(255, 0, 0)"); + document.body.appendChild(e); + is(cs.color, "rgb(0, 128, 0)"); + + // Element going from not in document to in document and child of + // display:none element. + e = document.createElement("div"); + e.className = "c"; + cs = getComputedStyle(e); + is(cs.color, "rgb(255, 0, 0)"); + document.querySelector(".b").appendChild(e); + is(cs.color, "rgb(0, 128, 0)"); + + // Element that is added to an out-of-document tree. + e = document.createElement("div"); + e.className = "d"; + f = document.createElement("span"); + f.className = "e"; + cs = getComputedStyle(f); + is(cs.color, "rgb(255, 0, 0)"); + e.appendChild(f); + is(cs.color, "rgb(0, 128, 0)"); + + // Element that is outside the document when an attribute is modified to + // cause a different rule to match. + e = document.createElement("div"); + e.className = "f"; + cs = getComputedStyle(e); + is(cs.color, "rgb(255, 0, 0)"); + e.className = "g"; + is(cs.color, "rgb(0, 128, 0)"); + + // Element that is outside the document when an ancestor is modified to + // cause a different rule to match. + e = document.createElement("div"); + e.className = "h"; + f = document.createElement("span"); + f.className = "i"; + e.appendChild(f); + cs = getComputedStyle(f); + is(cs.color, "rgb(255, 0, 0)"); + e.className = "j"; + is(cs.color, "rgb(0, 128, 0)"); + + SimpleTest.finish(); +}); +</script> +</pre> diff --git a/layout/style/test/test_bug1232829.html b/layout/style/test/test_bug1232829.html new file mode 100644 index 000000000..8981d56e0 --- /dev/null +++ b/layout/style/test/test_bug1232829.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1232829 +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 1232829</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script> + +/** Test for Bug 1232829 **/ + +// This should be a crashtest but it relies on using a pop-up window which +// isn't supported in crashtests. +function boom() { + var popup = window.open("data:text/html,2"); + setTimeout(function() { + var frameDoc = document.querySelector("iframe").contentDocument; + frameDoc.write("3"); + frameDoc.defaultView.history.back(); + requestAnimationFrame(function() { + popup.close(); + ok(true, "Didn't crash"); + SimpleTest.finish(); + }); + }, 0); +} + +SimpleTest.waitForExplicitFinish(); +</script> +</head> +<body onload="boom()"> + <iframe srcdoc="<style>@keyframes a { to { opacity: 0.5 } }</style> + <div style='animation: a 1ms'></div>"></iframe> +</body> +</html> diff --git a/layout/style/test/test_bug1292447.html b/layout/style/test/test_bug1292447.html new file mode 100644 index 000000000..9636780f4 --- /dev/null +++ b/layout/style/test/test_bug1292447.html @@ -0,0 +1,377 @@ +<!DOCTYPE HTML> +<html> +<!-- + Was for: https://bugzilla.mozilla.org/show_bug.cgi?id=365932 + Updated for: https://bugzilla.mozilla.org/show_bug.cgi?id=1292447 +--> +<head> + <title>Test for Bug 1292447</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style> + #content { + width: 800px; + height: 800px; + padding: 0 200px; + border-width: 0 200px; + border-style: solid; + border-color: transparent + } + #content2 { + display: none; + } + #content > div, #content2 > div { + width: 400px; + height: 400px; + padding: 0 100px; + border-width: 0 100px; + border-style: solid; + border-color: transparent + } + #content > div.auto, #content2 > div.auto { + width: auto; height: auto; + padding: 0 100px; + border-width: 0 80px; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1292447">Mozilla Bug 1292447</a> +<p id="display"></p> +<div id="content"> + <div id="indent1" style="text-indent: 400px"></div> + <div id="indent2" style="text-indent: 50%"></div> + + <div id="widthheight-1" class="auto"></div> + + <div id="minwidth1-1" style="min-width: 200px"></div> + <div id="minwidth1-2" style="min-width: 25%"></div> + <div id="minwidth2-1" style="min-width: 600px"></div> + <div id="minwidth2-2" style="min-width: 75%"></div> + <div id="minwidth3-1" class="auto" style="min-width: 200px"></div> + <div id="minwidth3-2" class="auto" style="min-width: 25%"></div> + <div id="minwidth4-1" class="auto" style="min-width: 600px"></div> + <div id="minwidth4-2" class="auto" style="min-width: 75%"></div> + + <div id="maxwidth1-1" style="max-width: 320px"></div> + <div id="maxwidth1-2" style="max-width: 40%"></div> + <div id="maxwidth2-1" style="max-width: 480px"></div> + <div id="maxwidth2-2" style="max-width: 60%"></div> + <div id="maxwidth3-1" class="auto" style="max-width: 320px"></div> + <div id="maxwidth3-2" class="auto" style="max-width: 40%"></div> + <div id="maxwidth4-1" class="auto" style="max-width: 480px"></div> + <div id="maxwidth4-2" class="auto" style="max-width: 60%"></div> + + <div id="minmaxwidth1-1" style="min-width: 200px; max-width: 320px"></div> + <div id="minmaxwidth1-2" style="min-width: 200px; max-width: 40%"></div> + <div id="minmaxwidth2-1" style="min-width: 25%; max-width: 320px"></div> + <div id="minmaxwidth2-2" style="min-width: 25%; max-width: 40%"></div> + <div id="minmaxwidth3-1" style="min-width: 600px; max-width: 320px"></div> + <div id="minmaxwidth3-2" style="min-width: 600px; max-width: 40%"></div> + <div id="minmaxwidth4-1" style="min-width: 75%; max-width: 320px"></div> + <div id="minmaxwidth4-2" style="min-width: 75%; max-width: 40%"></div> + <div id="minmaxwidth5-1" + style="display:none; min-width: 200px; max-width: 320px"></div> + <div id="minmaxwidth6-1" + style="display: none; min-width: 25%; max-width: 320px"></div> + <div id="minmaxwidth7-1" + style="display: none; min-width: 600px; max-width: 320px"></div> + <div id="minmaxwidth7-2" + style="display: none; min-width: 600px; max-width: 40%"></div> + <div id="minmaxwidth8-1" class="auto" + style="min-width: 200px; max-width: 320px"></div> + <div id="minmaxwidth8-2" class="auto" + style="min-width: 200px; max-width: 40%"></div> + <div id="minmaxwidth9-1" class="auto" + style="min-width: 25%; max-width: 320px"></div> + <div id="minmaxwidth9-2" class="auto" + style="min-width: 25%; max-width: 40%"></div> + <div id="minmaxwidth10-1" class="auto" + style="min-width: 600px; max-width: 320px"></div> + <div id="minmaxwidth10-2" class="auto" + style="min-width: 600px; max-width: 40%"></div> + <div id="minmaxwidth11-1" class="auto" + style="min-width: 75%; max-width: 320px"></div> + <div id="minmaxwidth11-2" class="auto" + style="min-width: 75%; max-width: 40%"></div> + + <div id="minheight1-1" style="min-height: 200px"></div> + <div id="minheight1-2" style="min-height: 25%"></div> + <div id="minheight2-1" style="min-height: 600px"></div> + <div id="minheight2-2" style="min-height: 75%"></div> + <div id="minheight3-1" class="auto" style="min-height: 200px"></div> + <div id="minheight3-2" class="auto" style="min-height: 25%"></div> + <div id="minheight4-1" class="auto" style="min-height: 600px"></div> + <div id="minheight4-2" class="auto" style="min-height: 75%"></div> + + <div id="maxheight1-1" style="max-height: 320px"></div> + <div id="maxheight1-2" style="max-height: 40%"></div> + <div id="maxheight2-1" style="max-height: 480px"></div> + <div id="maxheight2-2" style="max-height: 60%"></div> + <div id="maxheight3-1" class="auto" style="max-height: 320px"></div> + <div id="maxheight3-2" class="auto" style="max-height: 40%"></div> + <div id="maxheight4-1" class="auto" style="max-height: 480px"></div> + <div id="maxheight4-2" class="auto" style="max-height: 60%"></div> + + <div id="minmaxheight1-1" style="min-height: 200px; max-height: 320px"></div> + <div id="minmaxheight1-2" style="min-height: 200px; max-height: 40%"></div> + <div id="minmaxheight2-1" style="min-height: 25%; max-height: 320px"></div> + <div id="minmaxheight2-2" style="min-height: 25%; max-height: 40%"></div> + <div id="minmaxheight3-1" style="min-height: 600px; max-height: 320px"></div> + <div id="minmaxheight3-2" style="min-height: 600px; max-height: 40%"></div> + <div id="minmaxheight4-1" style="min-height: 75%; max-height: 320px"></div> + <div id="minmaxheight4-2" style="min-height: 75%; max-height: 40%"></div> + <div id="minmaxheight5-1" + style="display:none; min-height: 200px; max-height: 320px"></div> + <div id="minmaxheight6-1" + style="display: none; min-height: 25%; max-height: 320px"></div> + <div id="minmaxheight7-1" + style="display: none; min-height: 600px; max-height: 320px"></div> + <div id="minmaxheight7-2" + style="display: none; min-height: 600px; max-height: 40%"></div> + <div id="minmaxheight8-1" class="auto" + style="min-height: 200px; max-height: 320px"></div> + <div id="minmaxheight8-2" class="auto" + style="min-height: 200px; max-height: 40%"></div> + <div id="minmaxheight9-1" class="auto" + style="min-height: 25%; max-height: 320px"></div> + <div id="minmaxheight9-2" class="auto" + style="min-height: 25%; max-height: 40%"></div> + <div id="minmaxheight10-1" class="auto" + style="min-height: 600px; max-height: 320px"></div> + <div id="minmaxheight10-2" class="auto" + style="min-height: 600px; max-height: 40%"></div> + <div id="minmaxheight11-1" class="auto" + style="min-height: 75%; max-height: 320px"></div> + <div id="minmaxheight11-2" class="auto" + style="min-height: 75%; max-height: 40%"></div> + + <div id="radius1" style="border-radius: 80px"></div> + <div id="radius2" style="border-radius: 20% / 20%"></div> + <div id="outlineradius1" style="-moz-outline-radius: 160px"></div> + <div id="outlineradius2" style="-moz-outline-radius: 20% / 20%"></div> +</div> +<div id="content2" style="display: none"> + <div id="indent3" style="text-indent: 400px"></div> + <div id="indent4" style="text-indent: 50%"></div> + + <div id="minwidth1-3" style="min-width: 200px"></div> + <div id="minwidth1-4" style="min-width: 25%"></div> + <div id="minwidth2-3" style="min-width: 600px"></div> + <div id="minwidth2-4" style="min-width: 75%"></div> + + <div id="maxwidth1-3" style="max-width: 320px"></div> + <div id="maxwidth1-4" style="max-width: 40%"></div> + <div id="maxwidth2-3" style="max-width: 480px"></div> + <div id="maxwidth2-4" style="max-width: 60%"></div> + + <div id="minmaxwidth1-3" style="min-width: 200px; max-width: 320px"></div> + <div id="minmaxwidth1-4" style="min-width: 200px; max-width: 40%"></div> + <div id="minmaxwidth2-3" style="min-width: 25%; max-width: 320px"></div> + <div id="minmaxwidth2-4" style="min-width: 25%; max-width: 40%"></div> + <div id="minmaxwidth3-3" style="min-width: 600px; max-width: 320px"></div> + <div id="minmaxwidth3-4" style="min-width: 600px; max-width: 40%"></div> + <div id="minmaxwidth4-3" style="min-width: 75%; max-width: 320px"></div> + <div id="minmaxwidth4-4" style="min-width: 75%; max-width: 40%"></div> + + <div id="minheight1-3" style="min-height: 200px"></div> + <div id="minheight1-4" style="min-height: 25%"></div> + <div id="minheight2-3" style="min-height: 600px"></div> + <div id="minheight2-4" style="min-height: 75%"></div> + + <div id="maxheight1-3" style="max-height: 320px"></div> + <div id="maxheight1-4" style="max-height: 40%"></div> + <div id="maxheight2-3" style="max-height: 480px"></div> + <div id="maxheight2-4" style="max-height: 60%"></div> + + <div id="minmaxheight1-3" style="min-height: 200px; max-height: 320px"></div> + <div id="minmaxheight1-4" style="min-height: 200px; max-height: 40%"></div> + <div id="minmaxheight2-3" style="min-height: 25%; max-height: 320px"></div> + <div id="minmaxheight2-4" style="min-height: 25%; max-height: 40%"></div> + <div id="minmaxheight3-3" style="min-height: 600px; max-height: 320px"></div> + <div id="minmaxheight3-4" style="min-height: 600px; max-height: 40%"></div> + <div id="minmaxheight4-3" style="min-height: 75%; max-height: 320px"></div> + <div id="minmaxheight4-4" style="min-height: 75%; max-height: 40%"></div> + + <div id="radius3" style="border-radius: 80px"></div> + <div id="radius4" style="border-radius: 20%"></div> + <div id="outlineradius3" style="-moz-outline-radius: 160px"></div> + <div id="outlineradius4" style="-moz-outline-radius: 20%"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1292447 **/ + +document.body.offsetWidth; + +doATest("text-indent", "indent", 400, 50); +doATest("border-top-left-radius", "radius", 80, 20); +doATest("-moz-outline-radius-topleft", "outlineradius", 160, 20); + +doATest("width", "widthheight-", 440, 0, true); +doATest("height", "widthheight-", 0, 0, true); + +doATest("min-width", "minwidth1-", 200, 25); +doATest("min-width", "minwidth2-", 600, 75); +doATest("max-width", "maxwidth1-", 320, 40); +doATest("max-width", "maxwidth2-", 480, 60); + +// Test that min-width doesn't affect computed max-width +doATest("max-width", "minmaxwidth1-", 320, 40); +doATest("max-width", "minmaxwidth2-", 320, 40); +doATest("max-width", "minmaxwidth3-", 320, 40); +doATest("max-width", "minmaxwidth4-", 320, 40); + +// Test that max and min-width affect computed width correctly +doATest("width", "minwidth1-", 400, 0, true); +doATest("width", "minwidth2-", 600, 0, true); +doATest("width", "minwidth3-", 440, 0, true); +doATest("width", "minwidth4-", 600, 0, true); +doATest("width", "maxwidth1-", 320, 0, true); +doATest("width", "maxwidth2-", 400, 0, true); +doATest("width", "maxwidth3-", 320, 0, true); +doATest("width", "maxwidth4-", 440, 0, true); +doATest("width", "minmaxwidth1-", 320, 0, true); +doATest("width", "minmaxwidth2-", 320, 0, true); +doATest("width", "minmaxwidth3-", 600, 0, true); +doATest("width", "minmaxwidth4-", 600, 0, true); +doATest("width", "minmaxwidth5-", 320, 0, true); +doATest("width", "minmaxwidth6-", 320, 0, true); +doATest("width", "minmaxwidth7-", 600, 0, true); +doATest("width", "minmaxwidth8-", 320, 0, true); +doATest("width", "minmaxwidth9-", 320, 0, true); +doATest("width", "minmaxwidth10-", 600, 0, true); +doATest("width", "minmaxwidth11-", 600, 0, true); + +doATest("min-height", "minheight1-", 200, 25); +doATest("min-height", "minheight2-", 600, 75); +doATest("max-height", "maxheight1-", 320, 40); +doATest("max-height", "maxheight2-", 480, 60); + +// Test that min-height doesn't affect computed max-height +doATest("max-height", "minmaxheight1-", 320, 40); +doATest("max-height", "minmaxheight2-", 320, 40); +doATest("max-height", "minmaxheight3-", 320, 40); +doATest("max-height", "minmaxheight4-", 320, 40); + +// Test that max and min-height affect computed height correctly +doATest("height", "minheight1-", 400, 0, true); +doATest("height", "minheight2-", 600, 0, true); +doATest("height", "minheight3-", 200, 0, true); +doATest("height", "minheight4-", 600, 0, true); +doATest("height", "maxheight1-", 320, 0, true); +doATest("height", "maxheight2-", 400, 0, true); +doATest("height", "maxheight3-", 0, 0, true); +doATest("height", "maxheight4-", 0, 0, true); +doATest("height", "minmaxheight1-", 320, 0, true); +doATest("height", "minmaxheight2-", 320, 0, true); +doATest("height", "minmaxheight3-", 600, 0, true); +doATest("height", "minmaxheight4-", 600, 0, true); +doATest("height", "minmaxheight5-", 320, 0, true); +doATest("height", "minmaxheight6-", 320, 0, true); +doATest("height", "minmaxheight7-", 600, 0, true); +doATest("height", "minmaxheight8-", 200, 0, true); +doATest("height", "minmaxheight9-", 200, 0, true); +doATest("height", "minmaxheight10-", 600, 0, true); +doATest("height", "minmaxheight11-", 600, 0, true); + +function style(id) { + return document.defaultView.getComputedStyle($(id), ""); +} + +function round(num, decimals) { + return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals); +} + +function coordValueTest(camelProp, cssProp, decl, coordVal, prettyName) { + is(decl[camelProp], coordVal + "px", prettyName); + is(decl.getPropertyCSSValue(cssProp).cssValueType, + CSSValue.CSS_PRIMITIVE_VALUE, prettyName + " is primitive value"); + is(decl.getPropertyCSSValue(cssProp).primitiveType, + CSSPrimitiveValue.CSS_PX, prettyName + " is px"); + is(decl.getPropertyCSSValue(cssProp).cssText, coordVal + "px", + prettyName + " cssText"); + /* Since floats are only accurate to like 6 decimal places, round to 3 decimal + places here. */ + is(round(decl.getPropertyCSSValue(cssProp) + .getFloatValue(CSSPrimitiveValue.CSS_PX), + 3), coordVal, prettyName + " as float value"); +} + +function percentValueTest(camelProp, cssProp, decl, percentVal, prettyName) { + is(decl[camelProp], percentVal + "%", prettyName); + is(decl.getPropertyCSSValue(cssProp).cssValueType, + CSSValue.CSS_PRIMITIVE_VALUE, prettyName + " is primitive value"); + is(decl.getPropertyCSSValue(cssProp).primitiveType, + CSSPrimitiveValue.CSS_PERCENTAGE, prettyName + " is percent"); + is(decl.getPropertyCSSValue(cssProp).cssText, percentVal + "%", + prettyName + " cssText"); + /* Since floats are only accurate to like 6 decimal places, round to 3 decimal + places here. */ + is(round(decl.getPropertyCSSValue(cssProp) + .getFloatValue(CSSPrimitiveValue.CSS_PERCENTAGE), + 3), percentVal, prettyName + " as float value"); +} + +function doATest(propName, idBase, coordVal, percentVal, resolveToUsedVal = false) { + var cssCamelPropName = ""; + var parts = propName.split("-"); + ok(parts.length > 0, "prop name", "Empty css prop name"); + var i; + if (parts[0]) { + i = 0; + } else { + is(parts[1], "moz", "Testing an extension property that's not -moz"); + ok(parts.length > 2, "prop name 2", "Bogus -moz prop name"); + cssCamelPropName = "Moz"; + i = 2; + } + for (; i < parts.length; ++i) { + var part = parts[i]; + isnot(part, "", "Must have a nonempty part"); + if (cssCamelPropName) { + cssCamelPropName += part.charAt(0).toUpperCase() + + part.substring(1, part.length); + } else { + cssCamelPropName += part; + } + } + + /* Test $(id)-1 */ + coordValueTest(cssCamelPropName, propName, + style(idBase + "1"), coordVal, + propName + " of " + idBase + "1"); + + if (!$(idBase + "2")) { + // Nothing else to do here + return + } + + /* Test $(id)-2 */ + if (resolveToUsedVal) { + coordValueTest(cssCamelPropName, propName, + style(idBase + "2"), coordVal, + propName + " of " + idBase + "2"); + } else { + percentValueTest(cssCamelPropName, propName, + style(idBase + "2"), percentVal, + propName + " of " + idBase + "2"); + } + + if (percentVal) { + /* Test $(id)-3 */ + coordValueTest(cssCamelPropName, propName, + style(idBase + "3"), coordVal, + propName + " of " + idBase + "3"); + + /* Test $(id)-4 */ + percentValueTest(cssCamelPropName, propName, + style(idBase + "4"), percentVal, + propName + " of " + idBase + "4"); + } +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug160403.html b/layout/style/test/test_bug160403.html new file mode 100644 index 000000000..18ad66aa9 --- /dev/null +++ b/layout/style/test/test_bug160403.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=160403 +--> +<head> + <title>Test for Bug 160403</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=160403">Mozilla Bug 160403</a> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 160403 **/ + +var element = document.getElementById("content"); +var style = element.style; + +element.setAttribute("style", "border-top-style: dotted"); +is(style.getPropertyValue("border-top-style"), "dotted"); +is(style.getPropertyPriority("border-top-style"), ""); +is(style.getPropertyValue("border-style"), ""); +is(style.getPropertyPriority("border-style"), ""); + +element.setAttribute("style", "border-top-style: dotted ! important"); +is(style.getPropertyValue("border-top-style"), "dotted"); +is(style.getPropertyPriority("border-top-style"), "important"); +is(style.getPropertyValue("border-style"), ""); +is(style.getPropertyPriority("border-style"), ""); + +element.setAttribute("style", "border-top-style: dotted ! important; border-bottom-style: dotted ! important; border-left-style: dotted ! important"); +is(style.getPropertyValue("border-top-style"), "dotted"); +is(style.getPropertyPriority("border-top-style"), "important"); +is(style.getPropertyValue("border-style"), ""); +is(style.getPropertyPriority("border-style"), ""); + +element.setAttribute("style", "border-top-style: dotted ! important; border-right-style: dotted; border-bottom-style: dotted ! important; border-left-style: dotted ! important"); +is(style.getPropertyValue("border-top-style"), "dotted"); +is(style.getPropertyPriority("border-top-style"), "important"); +is(style.getPropertyValue("border-right-style"), "dotted"); +is(style.getPropertyPriority("border-right-style"), ""); +is(style.getPropertyValue("border-style"), ""); +is(style.getPropertyPriority("border-style"), ""); + +element.setAttribute("style", "border-top-style: dotted ! important; border-right-style: dotted ! important; border-bottom-style: dotted ! important; border-left-style: dotted ! important"); +is(style.getPropertyValue("border-top-style"), "dotted"); +is(style.getPropertyPriority("border-top-style"), "important"); +is(style.getPropertyValue("border-right-style"), "dotted"); +is(style.getPropertyPriority("border-right-style"), "important"); +isnot(style.getPropertyValue("border-style"), ""); +is(style.getPropertyPriority("border-style"), "important"); + +// Also test that we check consistency of inherit and initial. +element.setAttribute("style", "border-top-style: dotted; border-right-style: dotted; border-bottom-style: dotted; border-left-style: dotted"); +isnot(style.getPropertyValue("border-style"), "", "serialize shorthand when all values not inherit/initial"); +element.setAttribute("style", "border-top-style: inherit; border-right-style: inherit; border-bottom-style: inherit; border-left-style: inherit"); +is(style.getPropertyValue("border-style"), "inherit", "serialize shorthand as inherit"); +element.setAttribute("style", "border-top-style: initial; border-right-style: initial; border-bottom-style: initial; border-left-style: initial"); +is(style.getPropertyValue("border-style"), "initial", "serialize shorthand as initial"); +element.setAttribute("style", "border-top-style: dotted; border-right-style: dotted; border-bottom-style: dotted; border-left-style: inherit"); +is(style.getPropertyValue("border-style"), "", "don't serialize shorthand when partly inherit"); +element.setAttribute("style", "border-top-style: initial; border-right-style: dotted; border-bottom-style: initial; border-left-style: initial"); +is(style.getPropertyValue("border-style"), "", "don't serialize shorthand when partly initial"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug200089.html b/layout/style/test/test_bug200089.html new file mode 100644 index 000000000..39ab78d59 --- /dev/null +++ b/layout/style/test/test_bug200089.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=200089 +--> +<head> + <title>Test for Bug 200089</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=200089">Mozilla Bug 200089</a> +<div id="display" style="width: 600px"> + <table border="0" id="t" style="width: 300px; margin-left: auto; margin-right: auto"> + <tr><td>Cell</td></tr> + </table> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 200089 **/ +is(getComputedStyle($("t"), "").width, "300px", + "Used width should match specified width in this case"); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug221428.html b/layout/style/test/test_bug221428.html new file mode 100644 index 000000000..25965b567 --- /dev/null +++ b/layout/style/test/test_bug221428.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=221428 +--> +<head> + <title>Test for Bug 221428</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <link rel="stylesheet" href="data:text/css,body { color: green; }"> + <style> + @import url("data:text/css,body { border: 1px solid transparent; }"); + body { color: black; } + </style> + <script> + var executed = false; + </script> + <link rel="stylesheet" href="javascript:executed = true;"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=221428">Mozilla Bug 221428</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 221428 **/ + +var exceptionThrown = false; +try { + is(document.styleSheets[1].cssRules[0].cssText, "body { color: green; }", + "Should get the color: green rule back"); +} catch (e) { + exceptionThrown = true; +} + +ok(!exceptionThrown, "Should be able to access data: <link> stylesheet"); + +exceptionThrown = false; +try { + is(document.styleSheets[2].cssRules[1].cssText, "body { color: black; }", + "Should get the color: black rule back"); +} catch (e) { + exceptionThrown = true; +} +ok(!exceptionThrown, "Should be able to access <style> stylesheet"); + +exceptionThrown = false; +try { + is(document.styleSheets[2].cssRules[0].styleSheet.cssRules[0].cssText, + "body { border: 1px solid transparent; }", + "Should get the 'border: 1px solid transparent' rule back"); +} catch (e) { + exceptionThrown = true; +} +ok(!exceptionThrown, "Should be able to access data: @import stylesheet"); + +ok(!executed, + "Shouldn't be executing stylesheet-link javascript: URIs against " + + "the page context"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug229915.html b/layout/style/test/test_bug229915.html new file mode 100644 index 000000000..e3d1f6280 --- /dev/null +++ b/layout/style/test/test_bug229915.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=229915 +--> +<head> + <title>Test for Bug 229915</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + p { color: black; background: transparent; } + p.prev + p { color: green; } + p.prev ~ p { background: white; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=229915">Mozilla Bug 229915</a> +<div id="display"> + +<div> + <p id="toinsertbefore">After testing, this should turn green.</p> +</div> + +<div> + <p id="toreplace">To be replaced.</p> + <p id="replacecolor">After testing, this should turn green.</p> +</div> + +<div> + <p class="prev">Previous paragraph.</p> + <p id="toremove">To be removed.</p> + <p id="removecolor">After testing, this should turn green.</p> +</div> + +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 229915 **/ + +const GREEN = "rgb(0, 128, 0)"; +const BLACK = "rgb(0, 0, 0)"; +const TRANSPARENT = "transparent"; +const WHITE = "rgb(255, 255, 255)"; + +function make_prev() { + var result = document.createElement("p"); + result.setAttribute("class", "prev"); + var t = document.createTextNode("Dynamically created previous paragraph."); + result.appendChild(t); + return result; +} + +function color(id) { + return getComputedStyle(document.getElementById(id), "").color; +} +function bg(id) { + return getComputedStyle(document.getElementById(id), "").backgroundColor; +} + +var node; + +// test insert +is(color("toinsertbefore"), BLACK, "initial state (insertion test)"); +is(bg("toinsertbefore"), TRANSPARENT, "initial state (insertion test)"); +node = document.getElementById("toinsertbefore"); +node.parentNode.insertBefore(make_prev(), node); +is(color("toinsertbefore"), GREEN, "inserting should turn node green"); +is(bg("toinsertbefore"), WHITE, "inserting should turn background white"); + +// test replace +is(color("replacecolor"), BLACK, "initial state (replacement test)"); +is(bg("replacecolor"), TRANSPARENT, "initial state (replacement test)"); +node = document.getElementById("toreplace"); +node.parentNode.replaceChild(make_prev(), node); +is(color("replacecolor"), GREEN, "replacing should turn node green"); +is(bg("replacecolor"), WHITE, "replacing should turn background white"); + +// test remove +is(color("removecolor"), BLACK, "initial state (removal test)"); +is(bg("removecolor"), WHITE, "initial state (removal test; no change)"); +node = document.getElementById("toremove"); +node.parentNode.removeChild(node); +is(color("removecolor"), GREEN, "removing should turn node green"); +is(bg("removecolor"), WHITE, "removing should leave background"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug302186.html b/layout/style/test/test_bug302186.html new file mode 100644 index 000000000..746e7da0a --- /dev/null +++ b/layout/style/test/test_bug302186.html @@ -0,0 +1,508 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=302186 +--> +<head> + <title>Test for Bug 302186</title> + <script type="text/javascript" src="/MochiKit/Base.js"></script> + <script type="text/javascript" src="/MochiKit/DOM.js"></script> + <script type="text/javascript" src="/MochiKit/Style.js"></script> + <script type="text/javascript" src="/MochiKit/Color.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + +<style> + + +span { color: red } +:default + span { color: green } + +span.reverse { color: green } +:default + span.reverse { color: red } + +button { display: none } +input { display: none } +</style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=302186">Mozilla Bug 302186</a> +<p id="display"></p> +<div id="content" style="display: block"> + + <!-- static default 1 --> + <form> + <div> + <input type="submit" checked="checked"><span id="s1a">There should be no red.</span> + </div> + <div> + <input type="submit"><span id="s1b" class="reverse">There should be no red.</span> + </div> + </form> + + <!-- static default 2 --> + <form> + <div> + <button type="submit" checked="checked" id="foo"></button> + <span id="s2a">There should be no red.</span> + </div> + <div> + <button type="submit"></button> + <span class="reverse" id="s2b">There should be no red.</span> + </div> + </form> + + <!-- static default 3 --> + <form> + <div> + <input type="checkbox" checked="checked" id="foo"> + <span id="s3a">There should be no red.</span> + </div> + <div> + <input checked="checked"> + <span class="reverse" id="s3b">There should be no red.</span> + </div> + </form> + + <!-- static default 3 --> + <form> + <div> + <input type="radio" checked="checked" id="foo"> + <span id="s4a">There should be no red.</span> + </div> + <div> + <input checked="checked"> + <span class="reverse" id="s4b">There should be no red.</span> + </div> + </form> + + <!-- static default 5 --> + <form> + <div> + <input type="image"><span id="s5a">There should be no red.</span> + </div> + <div> + <input type="image"><span id="s5b" class="reverse">There should be no red.</span> + + </div> + </form> + + <!-- dynamic default 1 --> + <form> + <div> + <input type="submit" checked="checked" id="foo1"> + <span class="reverse" id="1a">There should be no red.</span> + </div> + <div> + <input type="submit"> + <span id="1b">There should be no red.</span> + + </div> + </form> + + <!-- dynamic default 2 --> + <form> + <div> + <button type="submit" checked="checked" id="foo2"></button> + <span class="reverse" id="2a">There should be no red.</span> + </div> + <div> + <button type="submit"></button> + <span id="2b">There should be no red.</span> + </div> + </form> + + <!-- dynamic default 3 --> + <form> + <div> + <input type="checkbox" checked="checked" id="foo3"> + <span class="reverse" id="3a">There should be no red.</span> + </div> + <div> + <input checked="checked" id="bar3"> + <span id="3b">There should be no red.</span> + </div> + </form> + + <!-- dynamic default 4 --> + <form> + <div> + <input type="radio" checked="checked" id="foo4"> + <span class="reverse" id="4a" >There should be no red.</span> + </div> + <div> + <input checked="checked" id="bar4"> + <span id="4b">There should be no red.</span> + </div> + </form> + + <!-- dynamic default 5 --> + <form> + <div> + <input type="submit"> + <input type="radio" checked="checked" id="foo5"> + <span id="5" class="reverse">There should be no red.</span> + </div> + </form> + + <!-- dynamic default 6 --> + <form> + <div id="div6"> + <span id="6a">There should be no red.</span> +</div> +<div> + <input type="submit"><span id="6b" class="reverse">There should be no red.</span> +</div> + </form> + + <!-- dynamic default 7 --> + <form> +<div> + <input type="submit"><span id="7a">There should be no red.</span> +</div> +<div id="div7"> + <span class="reverse" id="7b">There should be no red.</span> + +</div> +</form> + + <!-- dynamic default 8 --> +<form> +<div id="div8"><span id="8a">There should be no red.</span> +</div> +<div> + <input type="image" id="foo"><span class="reverse" id="8b">There should be no red.</span> + +</div> +</form> + + <!-- dynamic default 9 --> +<form> +<div> + <input type="image"><span id="9a">There should be no red.</span> +</div> +<div id="div9"> + <span class="reverse" id="9b">There should be no red.</span> + +</div> +</form> + + <!-- dynamic default 10 --> +<form> +<div id="div10"> + <input type="submit"><span id="10a" class="reverse">There should be no red.</span> +</div> +<div> + <input type="submit"><span id="10b" >There should be no red.</span> + +</div> +</form> + +<!-- dynamic default 11 --> +<form> +<div id="div11a"> + <input type="submit"><span id="11a">There should be no red.</span> +</div> +<div id="div11"> + <input type="submit"><span id="11b" class="reverse">There should be no red.</span> + +</div> +</form> + +<!-- dynamic default 12 --> +<form> +<div id="div12"> + <input type="image"><span id="12a" class="reverse">There should be no red.</span> +</div> +<div> + <input type="image"><span id="12b">There should be no red.</span> + +</div> +</form> + +<!-- dynamic default 13 --> +<form> +<div id="div13a"> + <input type="image"><span id="13a">There should be no red.</span> +</div> +<div id="div13"> + <input type="image"><span id="13b" class="reverse">There should be no red.</span> + +</div> +</form> + +<!-- dynamic default 14 --> +<form> +<div id="div14a"> + <input type="submit" id="foo14"><span id="14a">There should be no red.</span> +</div> +<div id="div14b"> + <input type="submit" id="foo14b"><span id="14b" class="reverse">There should be no red.</span> + +</div> +</form> + +<!-- dynamic default 15 --> +<form> +<div id="div15a"> + <input type="image" id="foo15a"><span id="15a">There should be no red.</span> +</div> +<div id="div15b"> + <input type="image" id="foo15b"><span id="15b" class="reverse">There should be no red.</span> + +</div> +</form> + +<!-- dynamic default 16 --> +<form> +<div> + <input type="image" checked="checked" id="foo16"></button> + <span class="reverse" id="16a">There should be no red.</span> +</div> +<div> + <input type="image"></button><span id="16b">There should be no red.</span> + +</div> +</form> + +<!-- dynamic default 17 --> +<form> +<div> + <button type="button" id="foo17"></button> + <span id="17a">There should be no red.</span> +</div> +<div> + <button type="submit"></button><span class="reverse" id="17b">There should be no red.</span> +</div> +</form> + +<!-- dynamic default 18 --> +<form> +<div> + <input type="button" id="foo18"></button> + <span id="18a">There should be no red.</span> +</div> +<div> + <input type="submit"></button><span id="18b" class="reverse">There should be no red.</span> + +</div> +</form> + +<!-- dynamic default 19 --> +<form> +<div id="div19"> + <span id="19a">There should be no red.</span> +</div> +</form> + +<!-- dynamic default 20 --> +<form> +<div id="div20"> + <span id="20a">There should be no red.</span> +</div> +</form> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 302186 **/ + +SimpleTest.waitForExplicitFinish(); + +function idColor(anId) { + var color = Color.fromComputedStyle(anId, "color"); + return color.toRGBString(); +} + +is(idColor("s1a"),"rgb(0,128,0)", "CSS static-default 1a"); +is(idColor("s1b"),"rgb(0,128,0)", "CSS static-default 1b"); +is(idColor("s2a"),"rgb(0,128,0)", "CSS static-default 2a"); +is(idColor("s2b"),"rgb(0,128,0)", "CSS static-default 2b"); +is(idColor("s3a"),"rgb(0,128,0)", "CSS static-default 3a"); +is(idColor("s3b"),"rgb(0,128,0)", "CSS static-default 3b"); +is(idColor("s4a"),"rgb(0,128,0)", "CSS static-default 4a"); +is(idColor("s4b"),"rgb(0,128,0)", "CSS static-default 4b"); +is(idColor("s5a"),"rgb(0,128,0)", "CSS static-default 5a"); +is(idColor("s5b"),"rgb(0,128,0)", "CSS static-default 5b"); + +function dynamicDefault1() { + $('foo1').removeAttribute("type"); + is(idColor("1a"),"rgb(0,128,0)", "CSS dynamic-default 1a"); + is(idColor("1b"),"rgb(0,128,0)", "CSS dynamic-default 1b"); +} + +function dynamicDefault2() { + $('foo2').setAttribute("type", "button"); + is(idColor("2a"),"rgb(0,128,0)", "CSS dynamic-default 2a"); + is(idColor("2b"),"rgb(0,128,0)", "CSS dynamic-default 2b"); +} + +function dynamicDefault3() { + $('foo3').removeAttribute("type"); + $('bar3').setAttribute("type", "checkbox"); + is(idColor("3a"),"rgb(0,128,0)", "CSS dynamic-default 3a"); + is(idColor("3b"),"rgb(0,128,0)", "CSS dynamic-default 3b"); +} + +function dynamicDefault4() { + $('foo4').removeAttribute("type"); + $('bar4').setAttribute("type", "radio"); + is(idColor("4a"),"rgb(0,128,0)", "CSS dynamic-default 4a"); + is(idColor("4b"),"rgb(0,128,0)", "CSS dynamic-default 4b"); +} + +function dynamicDefault5() { + $('foo5').setAttribute("type", "submit") + is(idColor("5"),"rgb(0,128,0)", "CSS dynamic-default 5"); +} + +function dynamicDefault6() { + var but = document.createElement("input"); + but.setAttribute("type", "submit"); + $('div6').insertBefore(but, $('div6').firstChild); + is(idColor("6a"),"rgb(0,128,0)", "CSS dynamic-default 6a"); + is(idColor("6b"),"rgb(0,128,0)", "CSS dynamic-default 6b"); +} + +function dynamicDefault7() { + var but = document.createElement("input"); + but.setAttribute("type", "submit"); + $('div7').insertBefore(but, $('div7').firstChild); + is(idColor("7a"),"rgb(0,128,0)", "CSS dynamic-default 7a"); + is(idColor("7b"),"rgb(0,128,0)", "CSS dynamic-default 7b"); +} + +function dynamicDefault8() { + var but = document.createElement("input"); + but.setAttribute("type", "image"); + $('div8').insertBefore(but, $('div8').firstChild); + is(idColor("8a"),"rgb(0,128,0)", "CSS dynamic-default 8a"); + is(idColor("8b"),"rgb(0,128,0)", "CSS dynamic-default 8b"); +} + +function dynamicDefault9() { + var but = document.createElement("input"); + but.setAttribute("type", "image"); + $('div9').insertBefore(but, $('div9').firstChild); + is(idColor("9a"),"rgb(0,128,0)", "CSS dynamic-default 9a"); + is(idColor("9b"),"rgb(0,128,0)", "CSS dynamic-default 9b"); +} + +function dynamicDefault10() { + var inputs = $('div10').getElementsByTagName("input"); + $('div10').removeChild(inputs[0]); + is(idColor("10a"),"rgb(0,128,0)", "CSS dynamic-default 10a"); + is(idColor("10b"),"rgb(0,128,0)", "CSS dynamic-default 10b"); +} + +function dynamicDefault11() { + var inputs = $('div11').getElementsByTagName("input"); + $('div11').removeChild(inputs[0]); + is(idColor("11a"),"rgb(0,128,0)", "CSS dynamic-default 11a"); + is(idColor("11b"),"rgb(0,128,0)", "CSS dynamic-default 11b"); +} + +function dynamicDefault12() { + var inputs = $('div12').getElementsByTagName("input"); + $('div12').removeChild(inputs[0]); + is(idColor("12a"),"rgb(0,128,0)", "CSS dynamic-default 12a"); + is(idColor("12b"),"rgb(0,128,0)", "CSS dynamic-default 12b"); +} + +function dynamicDefault13() { + var inputs = $('div13').getElementsByTagName("input"); + $('div13').removeChild(inputs[0]); + is(idColor("13a"),"rgb(0,128,0)", "CSS dynamic-default 13a"); + is(idColor("13b"),"rgb(0,128,0)", "CSS dynamic-default 13b"); +} + +function dynamicDefault14() { + var div1 = document.getElementById("div14a"); + var inputs = div1.getElementsByTagName("input"); + var firstElement = div1.removeChild(inputs[0]); + var div2 = document.getElementById("div14b"); + inputs = div2.getElementsByTagName("input"); + var secondElement = div2.removeChild(inputs[0]); + div1.insertBefore(secondElement, div1.firstChild); + div2.insertBefore(firstElement, div2.firstChild); + is(idColor("14a"),"rgb(0,128,0)", "CSS dynamic-default 14a"); + is(idColor("14b"),"rgb(0,128,0)", "CSS dynamic-default 14b"); +} + +function dynamicDefault15() { + var div1 = document.getElementById("div15a"); + var inputs = div1.getElementsByTagName("input"); + var firstElement = div1.removeChild(inputs[0]); + var div2 = document.getElementById("div15b"); + inputs = div2.getElementsByTagName("input"); + var secondElement = div2.removeChild(inputs[0]); + div1.insertBefore(secondElement, div1.firstChild); + div2.insertBefore(firstElement, div2.firstChild); + is(idColor("15a"),"rgb(0,128,0)", "CSS dynamic-default 15a"); + is(idColor("15b"),"rgb(0,128,0)", "CSS dynamic-default 15b"); +} + +function dynamicDefault16() { + $("foo16").setAttribute("type", "button"); + is(idColor("16a"),"rgb(0,128,0)", "CSS dynamic-default 16a"); + is(idColor("16b"),"rgb(0,128,0)", "CSS dynamic-default 16b"); +} + +function dynamicDefault17() { + $("foo17").setAttribute("type", "submit"); + is(idColor("17a"),"rgb(0,128,0)", "CSS dynamic-default 17a"); + is(idColor("17b"),"rgb(0,128,0)", "CSS dynamic-default 17b"); +} + +function dynamicDefault18() { + $("foo18").setAttribute("type", "submit"); + is(idColor("18a"),"rgb(0,128,0)", "CSS dynamic-default 18a"); + is(idColor("18b"),"rgb(0,128,0)", "CSS dynamic-default 18b"); +} + +function dynamicDefault19() { + var newSubmit = document.createElement("input"); + newSubmit.setAttribute("type", "submit"); + var div1 = document.getElementById("div19"); + div1.insertBefore(newSubmit, div1.firstChild); + is(idColor("19a"),"rgb(0,128,0)", "CSS dynamic-default 19a"); +} + +function dynamicDefault20() { + var newSubmit = document.createElement("input"); + newSubmit.setAttribute("type", "image"); + var div1 = document.getElementById("div20"); + div1.insertBefore(newSubmit, div1.firstChild); + is(idColor("20a"),"rgb(0,128,0)", "CSS dynamic-default 20a"); +} + +addLoadEvent(dynamicDefault1); +addLoadEvent(dynamicDefault2); +addLoadEvent(dynamicDefault3); +addLoadEvent(dynamicDefault4); +addLoadEvent(dynamicDefault5); +addLoadEvent(dynamicDefault6); +addLoadEvent(dynamicDefault7); +addLoadEvent(dynamicDefault8); +addLoadEvent(dynamicDefault9); +addLoadEvent(dynamicDefault10); +addLoadEvent(dynamicDefault11); +addLoadEvent(dynamicDefault12); +addLoadEvent(dynamicDefault13); +addLoadEvent(dynamicDefault14); +addLoadEvent(dynamicDefault15); +addLoadEvent(dynamicDefault16); +addLoadEvent(dynamicDefault17); +addLoadEvent(dynamicDefault18); +addLoadEvent(dynamicDefault19); +addLoadEvent(dynamicDefault20); + +addLoadEvent(SimpleTest.finish); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug319381.html b/layout/style/test/test_bug319381.html new file mode 100644 index 000000000..f387428e8 --- /dev/null +++ b/layout/style/test/test_bug319381.html @@ -0,0 +1,89 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=319381 +--> +<head> + <title>Test for Bug 319381</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=319381">Mozilla Bug 319381</a> +<p id="display"></p> +<div id="content" style="display: none"> + <div id="t"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 319381 **/ + +function c() { + return document.defaultView.getComputedStyle($('t'), ""). + getPropertyValue("overflow"); +} + +function cval() { + return document.defaultView.getComputedStyle($('t'), ""). + getPropertyCSSValue("overflow"); +} + +var vals = ["visible", "hidden", "auto", "scroll"]; +var mozVals = ["-moz-scrollbars-vertical", "-moz-scrollbars-horizontal"]; +var i, j; + +for (i = 0; i < vals.length; ++i) { + $('t').style.overflow = vals[i]; + is($('t').style.overflow, vals[i], "Roundtrip"); + is(c(), vals[i], "Simple property set"); + isnot(cval(), null, "Simple property as CSSValue"); +} + +$('t').style.overflow = mozVals[0]; +is($('t').style.getPropertyValue("overflow-y"), "scroll", "Roundtrip"); +is($('t').style.getPropertyValue("overflow-x"), "hidden", "Roundtrip"); +is($('t').style.overflow, "", "Shorthand read directly"); +is(c(), "", "Shorthand computed"); +is(cval(), null, "Shorthand as CSSValue"); + +$('t').style.overflow = mozVals[1]; +is($('t').style.getPropertyValue("overflow-x"), "scroll", "Roundtrip"); +is($('t').style.getPropertyValue("overflow-y"), "hidden", "Roundtrip"); +is($('t').style.overflow, "", "Shorthand read directly"); +is(c(), "", "Shorthand computed"); +is(cval(), null, "Shorthand as CSSValue"); + +for (i = 0; i < vals.length; ++i) { + for (j = 0; j < vals.length; ++j) { + $('t').setAttribute("style", + "overflow-x: " + vals[i] + "; overflow-y: " + vals[j]); + is($('t').style.getPropertyValue("overflow-x"), vals[i], "Roundtrip"); + is($('t').style.getPropertyValue("overflow-y"), vals[j], "Roundtrip"); + + if (i == j) { + is($('t').style.overflow, vals[i], "Shorthand serialization"); + } else { + is($('t').style.overflow, "", "Shorthand serialization"); + } + + // "visible" overflow-x and overflow-y become "auto" in computed style if + // the other direction is not also "visible". + if (i == j || (vals[i] == "visible" && vals[j] == "auto")) { + is(c(), vals[j], "Shorthand computation"); + isnot(cval(), null, "Shorthand computation as CSSValue"); + } else if (vals[j] == "visible" && vals[i] == "auto") { + is(c(), vals[i], "Shorthand computation"); + isnot(cval(), null, "Shorthand computation as CSSValue"); + } else { + is(c(), "", "Shorthand computation"); + is(cval(), null, "Shorthand computation as CSSValue"); + } + } +} + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug357614.html b/layout/style/test/test_bug357614.html new file mode 100644 index 000000000..2cfb63044 --- /dev/null +++ b/layout/style/test/test_bug357614.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=357614 +--> +<head> + <title>Test for Bug 357614</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <style type="text/css" id="style"> + a { color: red; } + a { color: green; } + </style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=357614">Mozilla Bug 357614</a> +<p id="display"><a href="http://www.FOO.com/" rel="next" rev="PREV" foo="bar">a link</a></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 357614 **/ + +var sheet = document.getElementById("style").sheet; +var rule1 = sheet.cssRules[0]; +var rule2 = sheet.cssRules[1]; + +var a = document.getElementById("display").firstChild; +var cs = getComputedStyle(a, ""); + +function change_selector_text(selector) { + // rule2.selectorText = selector; // NOT IMPLEMENTED + + sheet.deleteRule(1); + sheet.insertRule(selector + " { color: green; }", 1); +} + +var cs_green = cs.getPropertyValue("color"); +change_selector_text('p'); +var cs_red = cs.getPropertyValue("color"); +isnot(cs_green, cs_red, "computed values for green and red are different"); + +change_selector_text('a[href="http://www.FOO.com/"]'); +is(cs.getPropertyValue("color"), cs_green, "selector on href value matches case-sensitively"); + +change_selector_text('a[href="http://www.foo.com/"]'); +is(cs.getPropertyValue("color"), cs_red, "selector on href value does not match case-insensitively"); + +change_selector_text('a[rel="next"]'); +is(cs.getPropertyValue("color"), cs_green, "selector on rel value matches case-sensitively"); + +change_selector_text('a[rel="NEXT"]'); +is(cs.getPropertyValue("color"), cs_green, "selector on rel value matches case-insensitively"); + +change_selector_text('a[rev="PREV"]'); +is(cs.getPropertyValue("color"), cs_green, "selector on rev value matches case-sensitively"); + +change_selector_text('a[rev="prev"]'); +is(cs.getPropertyValue("color"), cs_green, "selector on rev value matches case-insensitively"); + +change_selector_text('a[foo="bar"]'); +is(cs.getPropertyValue("color"), cs_green, "selector on foo value matches case-sensitively"); + +change_selector_text('a[foo="Bar"]'); +is(cs.getPropertyValue("color"), cs_red, "selector on foo value does not match case-insensitively"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug363146.html b/layout/style/test/test_bug363146.html new file mode 100644 index 000000000..dfdc4028b --- /dev/null +++ b/layout/style/test/test_bug363146.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=363146 +--> +<head> + <title>Test for Bug 363146</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=363146">Mozilla Bug 363146</a> +<div style="width:100px; height:400px; position:relative;"> + <table id="t1" border="0" cellspacing="0" cellpadding="0" + style="border:10px solid black; margin:20px; position:absolute; left:50px; top:35px;"> + <caption align="top" style="height:100px;">Caption</caption> + <tr> + <td><div style="width:400px; height:100px;">Cell</div></td> + </tr> + </table> +</div> +<div style="width:100px; height:400px; position:relative;"> + <table id="t2" border="0" cellspacing="0" cellpadding="0" + style="margin:20%;"> + <caption align="top" style="height:100px;">Caption</caption> + <tr> + <td><div style="width:400px; height:100px;">Cell</div></td> + </tr> + </table> +</div> +<p id="display"></p> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var c = window.getComputedStyle(document.getElementById("t1"), ""); +is(c.width, "420px"); +is(c.height, "120px"); +is(c.left, "50px"); +is(c.top, "35px"); +is(c.borderLeftWidth, "10px"); +is(c.borderRightWidth, "10px"); +is(c.borderTopWidth, "10px"); +is(c.borderBottomWidth, "10px"); +is(c.marginLeft, "20px"); +is(c.marginRight, "20px"); +is(c.marginTop, "20px"); +is(c.marginBottom, "20px"); + +var c2 = window.getComputedStyle(document.getElementById("t2"), ""); +is(c2.marginLeft, "20px"); +is(c2.marginRight, "20px"); +is(c2.marginTop, "20px"); +is(c2.marginBottom, "20px"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug372770.html b/layout/style/test/test_bug372770.html new file mode 100644 index 000000000..208a79e3e --- /dev/null +++ b/layout/style/test/test_bug372770.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=372770 +--> +<head> + <title>Test for Bug 372770</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style id="testStyle"> + #content {} + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372770">Mozilla Bug 372770</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 372770 **/ +var style1 = $("content").style; +var style2 = $("testStyle").sheet.cssRules[0].style; + +var colors = [ "rgb(128, 128, 128)", "transparent" ] +var i; + +for (i = 0; i < colors.length; ++i) { + var color = colors[i]; + style1.color = color; + style2.color = color; + is(style1.color, color, "Inline style color roundtripping failed at color " + i); + is(style2.color, color, "Rule style color roundtripping failed at color " + i); +} + +// This code is only here because of bug 372783. Once that's fixed, this test +// for "rgba(0, 0, 0, 0)" will fail. +style1.color = "rgba(0, 0, 0, 0)"; +style2.color = "rgba(0, 0, 0, 0)"; +is(style1.color, "transparent", + "Inline style should give transparent for rgba(0,0,0,0)"); +is(style2.color, "transparent", + "Rule style should give transparent for rgba(0,0,0,0)"); +todo(style1.color == "rgba(0, 0, 0, 0)", + "Inline style should round-trip black transparent color correctly"); +todo(style2.color == "rgba(0, 0, 0, 0)", + "Rule style should round-trip black transparent color correctly"); + +for (var i = 0; i <= 100; ++i) { + if (i == 70 || i == 90) { + // Tinderbox unhappy for some reason... just skip these for now? + continue; + } + var color1 = "rgba(128, 128, 128, " + i/100 + ")"; + var color2 = "rgba(175, 63, 27, " + i/100 + ")"; + style1.color = color1; + style1.backgroundColor = color2; + style2.color = color2; + style2.background = color1; + + if (i == 100) { + // Bug 372783 means this doesn't round-trip quite right + todo(style1.color == color1, + "Inline style color roundtripping failed at opacity " + i); + todo(style1.backgroundColor == color2, + "Inline style background roundtripping failed at opacity " + i); + todo(style2.color == color2, + "Rule style color roundtripping failed at opacity " + i); + todo(style2.backgroundColor == color1, + "Rule style background roundtripping failed at opacity " + i); + color1 = "rgb(128, 128, 128)"; + color2 = "rgb(175, 63, 27)"; + } + + is(style1.color, color1, + "Inline style color roundtripping failed at opacity " + i); + is(style1.backgroundColor, color2, + "Inline style background roundtripping failed at opacity " + i); + is(style2.color, color2, + "Rule style color roundtripping failed at opacity " + i); + is(style2.backgroundColor, color1, + "Rule style background roundtripping failed at opacity " + i); + +} +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug373293.html b/layout/style/test/test_bug373293.html new file mode 100644 index 000000000..07053303c --- /dev/null +++ b/layout/style/test/test_bug373293.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=373293 +--> +<head> + <title>Test for Bug 373293</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=373293">Mozilla Bug 373293</a> +<p id="display"></p> +<div id="content" style="display: none"> + <div id="t" style="color: transparent;"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 373293**/ + +var actual = $("t").getAttribute("style"); +var expected = "color: transparent;"; +is(actual, expected, "Expected style content did not match the actual style content"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug377947.html b/layout/style/test/test_bug377947.html new file mode 100644 index 000000000..43f60577c --- /dev/null +++ b/layout/style/test/test_bug377947.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=377947 +--> +<head> + <title>Test for Bug 377947</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377947">Mozilla Bug 377947</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 377947 **/ + +/* + * In particular, test that CSSStyleDeclaration.getPropertyValue doesn't + * return values for shorthands when some of the subproperties are not + * specified (a change that wasn't all that related to the main point of + * the bug). And also test that the internal system-font property added + * in bug 377947 doesn't interfere with that. + */ + +var s = document.getElementById("display").style; + +is(s.getPropertyValue("list-style"), "", + "list-style shorthand should start off empty"); +s.listStyleType="disc"; +s.listStyleImage="none"; +is(s.getPropertyValue("list-style"), "", + "list-style shorthand should be empty when some subproperties specified"); +s.listStylePosition="inside"; +isnot(s.getPropertyValue("list-style"), "", + "list-style shorthand should produce value when all subproperties set"); +s.removeProperty("list-style"); +is(s.getPropertyValue("list-style"), "", + "list-style shorthand be empty after removal"); +s.listStyle="none"; +isnot(s.getPropertyValue("list-style"), "", + "list-style shorthand should produce value when shorthand set"); +s.removeProperty("list-style"); +is(s.getPropertyValue("list-style"), "", + "list-style shorthand be empty after removal"); + +is(s.getPropertyValue("font"), "", + "font shorthand should start off empty"); +var all_but_one = { + "font-family": "serif", + "font-style": "normal", + "font-variant": "normal", + "font-weight": "bold", + "font-size": "small", + "font-stretch": "normal", + "font-size-adjust": "none", // has to be default value + "font-feature-settings": "normal", // has to be default value + "font-language-override": "normal", // has to be default value + "font-kerning": "auto", // has to be default value + "font-synthesis": "weight style", // has to be default value + "font-variant-alternates": "normal", // has to be default value + "font-variant-caps": "normal", // has to be default value + "font-variant-east-asian": "normal", // has to be default value + "font-variant-ligatures": "normal", // has to be default value + "font-variant-numeric": "normal", // has to be default value + "font-variant-position": "normal" // has to be default value +}; + +for (var prop in all_but_one) { + s.setProperty(prop, all_but_one[prop], ""); +} +is(s.getPropertyValue("font"), "", + "font shorthand should be empty when some subproperties specified"); +s.setProperty("line-height", "1.5", ""); +isnot(s.getPropertyValue("font"), "", + "font shorthand should produce value when all subproperties set"); +s.setProperty("font-size-adjust", "0.5", ""); +is(s.getPropertyValue("font"), "", + "font shorthand should be empty when font-size-adjust is non-default"); +s.setProperty("font-size-adjust", "none", ""); +isnot(s.getPropertyValue("font"), "", + "font shorthand should produce value when all subproperties set"); +s.removeProperty("font"); +is(s.getPropertyValue("font"), "", + "font shorthand be empty after removal"); +s.font="medium serif"; +isnot(s.getPropertyValue("font"), "", + "font shorthand should produce value when shorthand set"); +s.removeProperty("font"); +is(s.getPropertyValue("font"), "", + "font shorthand be empty after removal"); +s.font="menu"; +isnot(s.getPropertyValue("font"), "", + "font shorthand should produce value when shorthand (system font) set"); +s.removeProperty("font"); +is(s.getPropertyValue("font"), "", + "font shorthand be empty after removal"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug379440.html b/layout/style/test/test_bug379440.html new file mode 100644 index 000000000..2a6c392ea --- /dev/null +++ b/layout/style/test/test_bug379440.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=379440 +--> +<head> + <title>Test for Bug 379440</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + #display > * { cursor: auto } + #t1 { + cursor: url(file:///tmp/foo), url(file:///c|/), + url(http://example.com/), crosshair; + } + #t2 { + cursor: url(file:///tmp/foo), url(file:///c|/), crosshair; + } + #t3 { + cursor: url(http://example.com/), crosshair; + } + #t4 { + cursor: url(http://example.com/); + } + #t5 { + cursor: url(http://example.com/), no-such-cursor-exists; + } + #t6 { + cursor: crosshair; + } + #t7 { + cursor: no-such-cursor-exists; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=379440">Mozilla Bug 379440</a> +<p id="display"> + <div id="t1"> </div> + <div id="t2"></div> + <div id="t3"></div> + <div id="t4"></div> + <div id="t5"></div> + <div id="t6"></div> + <div id="t7"></div> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 379440 **/ + +function cur(id) { + return document.defaultView.getComputedStyle($(id), "").cursor; +} + +is(cur("t1"), "url(\"http://example.com/\"), crosshair", + "Drop unloadable URIs"); +is(cur("t2"), "crosshair", "Drop unloadable URIs again"); +is(cur("t3"), "url(\"http://example.com/\"), crosshair", "URI + fallback"); +is(cur("t4"), "auto", "Must have a fallback"); +is(cur("t5"), "auto", "Fallback must be recognized"); +is(cur("t6"), "crosshair", "Just a fallback"); +is(cur("t7"), "auto", "Invalid fallback means ignore"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug379741.html b/layout/style/test/test_bug379741.html new file mode 100644 index 000000000..fc99be1ff --- /dev/null +++ b/layout/style/test/test_bug379741.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=379741 +--> +<head> + <title>Test for Bug 379741</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=379741">Mozilla Bug 379741</a> +<div id="content" style="display: none"> +<div id="noframe"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 379741 **/ + +var cs = getComputedStyle(document.getElementById("noframe"), ""); +ok(cs.marginTop == "0" || cs.marginTop == "0px", + "computed margin-top is not none"); +ok(cs.marginRight == "0" || cs.marginRight == "0px", + "computed margin-right is not none"); +ok(cs.marginBottom == "0" || cs.marginBottom == "0px", + "computed margin-bottom is not none"); +ok(cs.marginLeft == "0" || cs.marginLeft == "0px", + "computed margin-left is not none"); +ok(cs.paddingTop == "0" || cs.paddingTop == "0px", + "computed padding-top is not none"); +ok(cs.paddingRight == "0" || cs.paddingRight == "0px", + "computed padding-right is not none"); +ok(cs.paddingBottom == "0" || cs.paddingBottom == "0px", + "computed padding-bottom is not none"); +ok(cs.paddingLeft == "0" || cs.paddingLeft == "0px", + "computed padding-left is not none"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug382027.html b/layout/style/test/test_bug382027.html new file mode 100644 index 000000000..f9a88b17f --- /dev/null +++ b/layout/style/test/test_bug382027.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=382027 +--> +<head> + <title>Test for Bug 382027</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=382027">Mozilla Bug 382027</a> +<div id="content" style="display: none;" + ><div style="border-style: groove none none;" + ><div style="border-style: none none double" + ></div + ></div +></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/* + * Test that the regression in the original patch checked in by bug + * 382027 is caught by something other than an unexpected pass. + */ + +var e = document.createElement("div"); +e.style.setProperty("border-style", "groove none none none", ""); +is(e.getAttribute("style"), "border-style: groove none none;"); +e.style.setProperty("border-style", "none none double none", ""); +is(e.getAttribute("style"), "border-style: none none double;"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug383075.html b/layout/style/test/test_bug383075.html new file mode 100644 index 000000000..c970a802f --- /dev/null +++ b/layout/style/test/test_bug383075.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=383075 +--> +<head> + <title>Test for bug 383075</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<style type="text/css"> + + html,body { + color:black; background-color:white; font-size:16px; font-family: Arial; + } + + +</style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=383075">Mozilla bug 383075</a> +<p id="display"> + +The X'es below should have the same size:<br> + +<span id="a1" style="font-size:72px;">X</span> +<span id="a2" style="font-size:72px;">X</span> +<span id="a3" style="font-size:72px;">X</span> +<span id="a4" style="font-size:72px;">X</span> +<span id="a5" style="font-size:72px;">X</span> +<span id="a6" style="font-size:72px;">X</span> +<span id="a7" style="font-size:24px;">X</span> +<span id="a8" style="font-size:72px;">X</span> +<span id="a9" style="font:24px Arial;">X</span> + +<br> + +<span id="b1" style="font-size:72px;">X</span> +<span id="b2" style="font-size:72px;">X</span> +<span id="b3" style="font-size:72px;">X</span> +<span id="b4" style="font-size:72px;">X</span> +<span id="b5" style="font-size:72px;">X</span> +<span id="b6" style="font-size:72px;">X</span> +<span id="b7" style="font-size:24px;">X</span> +<span id="b8" style="font-size:72px;">X</span> +<span id="b9" style="font:24px Arial;">X</span> +</p> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + + document.getElementById("a1").style.fontSize = "illegal"; + document.getElementById("a2").style.fontSize = "24px;"; + document.getElementById("a3").style.fontSize = "24px; font-size-adjust:2"; + document.getElementById("a4").style.fontSize = ";"; + document.getElementById("a5").style.font = "24px Arial;"; + document.getElementById("a6").style.font = "24px;"; + document.getElementById("a7").style.fontSize = " 72px "; // correct + document.getElementById("a8").style.font = ";"; + document.getElementById("a9").style.font = " 72px Arial "; // correct + + document.getElementById("b1").style.setProperty("font-size", "illegal", null); + document.getElementById("b2").style.setProperty("font-size", "24px;", null); + document.getElementById("b3").style.setProperty("font-size", "24px; font-size-adjust:2", null); + document.getElementById("b4").style.setProperty("font-size", ";", null); + document.getElementById("b5").style.setProperty("font", "24px Arial;", null); + document.getElementById("b6").style.setProperty("font", "24px;", null); + document.getElementById("b7").style.setProperty("font-size", " 72px ", null); // correct + document.getElementById("b8").style.setProperty("font", ";", null); + document.getElementById("b9").style.setProperty("font", " 72px Arial ", null); // correct + + +for (i=1; i <= 9; ++i) + is($('a'+i).style.fontSize, '72px', "font size"); + +for (i=1; i <= 9; ++i) + is($('b'+i).style.fontSize, '72px', "font size"); + + +</script> +</pre> + +</body> +</html> + diff --git a/layout/style/test/test_bug387615.html b/layout/style/test/test_bug387615.html new file mode 100644 index 000000000..daa61f342 --- /dev/null +++ b/layout/style/test/test_bug387615.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=387615 +--> +<head> + <title>Test for Bug 387615</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + @namespace html url(http://www.w3.org/1999/xhtml); + a { color: red; } + a[rel="next"] { color: green; } + a[html|rel="next"] { color: green; } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=387615">Mozilla Bug 387615</a> +<p id="display"><a>link</a></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 387615 **/ + +var htmlns = "http://www.w3.org/1999/xhtml"; + +var a = document.getElementById("display").firstChild; + +function col(elt) { return getComputedStyle(elt, "").color; } + +var red_cs = col(a); +a.setAttribute("rel", "next"); +var green_cs = col(a); +isnot(green_cs, red_cs, "computed values for red and green are different"); + +a.setAttribute("rel", "NEXT"); +is(col(a), green_cs, "rel attribute should match case insensitively"); + +a.removeAttribute("rel"); +a.setAttributeNS(htmlns, "html:rel", "next"); +is(col(a), green_cs, "html:rel attribute should match case-sensitively"); + +a.setAttributeNS(htmlns, "html:rel", "NEXT"); +is(col(a), red_cs, "html:rel attribute should not match case-insensitively"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug389464.html b/layout/style/test/test_bug389464.html new file mode 100644 index 000000000..05e2faf63 --- /dev/null +++ b/layout/style/test/test_bug389464.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- + +--> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <!-- above is to force x-western language group --> + <title>Test for preference not to use document colors</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=58048">Mozilla Bug 58048</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=255411">Mozilla Bug 255411</a> +<div id="display"> + +<pre><font id="one" size="-1">text</font></pre> +<p><font id="two" size="-1">text</font></p> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var cs1 = getComputedStyle(document.getElementById("one"), ""); +var cs2 = getComputedStyle(document.getElementById("two"), ""); + +SpecialPowers.pushPrefEnv({'set': [['variable.x-western', 25], ['fixed.x-western', 20]]}, part1); + +function part1() +{ + var fs1 = cs1.fontSize.match(/(.*)px/)[1]; + var fs2 = cs2.fontSize.match(/(.*)px/)[1]; + ok(fs1 < fs2, "<font size=-1> shrinks relative to font-family: -moz-fixed"); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug391034.html b/layout/style/test/test_bug391034.html new file mode 100644 index 000000000..e98f68f7a --- /dev/null +++ b/layout/style/test/test_bug391034.html @@ -0,0 +1,71 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=391034 +--> +<head> + <title>Test for Bug 391034</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391034">Mozilla Bug 391034</a> +<div id="display" style="width: 90px; height: 80px"> + <div id="width-ref" style="width: 2ch"></div> + <div id="width-ref2" style="width: 5ch"></div> + <div id="one" style="position: relative; left: 2ch; bottom: 5ch"></div> + <div id="two" style="position: relative; left: 10%; bottom: 20%"></div> + <div id="three" style="position: relative; left: 10px; bottom: 6px"></div> +</div> +<div id="content" style="display: none"> + <div id="four" style="position: relative; left: 10%; bottom: 20%"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 391034 **/ +function getComp(id) { + return document.defaultView.getComputedStyle($(id), ""); +} + +is(getComp("one").top, "-" + getComp("width-ref2").width, + "Incorrect computed top offset if specified in ch") +is(getComp("one").right, "-" + getComp("width-ref").width, + "Incorrect computed right offset if specified in ch") +is(getComp("one").bottom, getComp("width-ref2").width, + "Incorrect computed bottom offset if specified in ch") +is(getComp("one").left, getComp("width-ref").width, + "Incorrect computed left offset if specified in ch") + +is(getComp("two").top, "-16px", + "Incorrect computed top offset if specified in %") +is(getComp("two").right, "-9px", + "Incorrect computed right offset if specified in %") +is(getComp("two").bottom, "16px", + "Incorrect computed bottom offset if specified in %") +is(getComp("two").left, "9px", + "Incorrect computed left offset if specified in %") + +is(getComp("three").top, "-6px", + "Incorrect computed top offset if specified in %") +is(getComp("three").right, "-10px", + "Incorrect computed right offset if specified in %") +is(getComp("three").bottom, "6px", + "Incorrect computed bottom offset if specified in %") +is(getComp("three").left, "10px", + "Incorrect computed left offset if specified in %") + +is(getComp("four").top, "auto", + "Incorrect undisplayed computed top offset if specified in %") +is(getComp("four").right, "auto", + "Incorrect undisplayed computed right offset if specified in %") +is(getComp("four").bottom, "20%", + "Incorrect undisplayed computed bottom offset if specified in %") +is(getComp("four").left, "10%", + "Incorrect undisplayed computed left offset if specified in %") + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug391221.html b/layout/style/test/test_bug391221.html new file mode 100644 index 000000000..f5cb9ce3b --- /dev/null +++ b/layout/style/test/test_bug391221.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=391221 +--> +<head> + <title>Test for Bug 391221</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=391221">Mozilla Bug 391221</a> +<p id="display"> + <div id="width-ref" style="width: 2ch"></div> +</p> +<div id="content" style="display: none"> + +<div id="one" style="width: 1000px; max-width: 2ch"></div> +<div id="two" style="width: 0px; min-width: 2ch"></div> +<div id="three" style="width: 1000ch; max-width: 2px"></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 391221 **/ +function getComp(id) { + return document.defaultView.getComputedStyle($(id), ""); +} + +is(getComp("one").width, getComp("width-ref").width, + "max-width in ch units not working?"); + +is(getComp("two").width, getComp("width-ref").width, + "min-width in ch units not working?"); + +is(getComp("three").width, "2px", "max-width not applied to width in chars?"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug397427.html b/layout/style/test/test_bug397427.html new file mode 100644 index 000000000..3175d0e92 --- /dev/null +++ b/layout/style/test/test_bug397427.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=397427 +--> +<head> + <title>Test for Bug 397427</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style id="a"> + @import url("redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-1.css"); + @import url("redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-2.css"); + .test { color: red } + </style> + <link id="b" rel="stylesheet" href="http://example.com"> + <link id="c" rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-2.css"> + <link id="d" rel="stylesheet" href="redirect.sjs?http://example.org/tests/layout/style/test/post-redirect-3.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=397427">Mozilla Bug 397427</a> +<p id="display"> +<span id="one" class="test"></span> +<span id="two" class="test"></span> +<span id="three" class="test"></span> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 397427 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + is($("a").sheet.href, null, "href should be null"); + is(typeof($("a").sheet.href), "object", "should be actual null"); + + // Make sure the redirected sheets are loaded and have the right base URI + is(document.defaultView.getComputedStyle($("one"), "").color, + "rgb(0, 128, 0)", "Redirect 1 did not work"); + is(document.defaultView.getComputedStyle($("one"), "").backgroundImage, + "url(\"http://example.org/tests/layout/style/test/post-redirect-1.css#\")", + "Redirect 1 did not get right base URI"); + is(document.defaultView.getComputedStyle($("two"), "").color, + "rgb(0, 128, 0)", "Redirect 2 did not work"); + is(document.defaultView.getComputedStyle($("two"), "").backgroundImage, + "url(\"http://example.org/tests/layout/style/test/post-redirect-2.css#\")", + "Redirect 2 did not get right base URI"); + is(document.defaultView.getComputedStyle($("three"), "").color, + "rgb(0, 128, 0)", "Redirect 3 did not work"); + is(document.defaultView.getComputedStyle($("three"), "").backgroundImage, + "url(\"http://example.org/tests/layout/style/test/post-redirect-3.css#\")", + "Redirect 3 did not get right base URI"); + + var ruleList = $("a").sheet.cssRules; + + var redirHrefBase = + window.location.href.replace(/test_bug397427.html$/, + "redirect.sjs?http://example.org/tests/layout/style/test/post-"); + + is(ruleList[0].styleSheet.href, redirHrefBase + "redirect-1.css", + "Unexpected href for imported sheet"); + todo_is(ruleList[0].href, redirHrefBase + "redirect-1.css", + "Rule href should be absolute"); + is(ruleList[1].styleSheet.href, redirHrefBase + "redirect-2.css", + "Unexpected href for imported sheet"); + todo_is(ruleList[1].href, redirHrefBase + "redirect-2.css", + "Rule href should be absolute"); + + is($("b").href, "http://example.com/", "Unexpected href one"); + is($("b").href, $("b").sheet.href, + "Should have the same href when not redirecting"); + + is($("c").href, redirHrefBase + "redirect-2.css", + "Unexpected href two"); + is($("c").href, $("c").sheet.href, + "Should have the same href when redirecting"); + + is($("d").href, redirHrefBase + "redirect-3.css", + "Unexpected href three"); + is($("d").href, $("d").sheet.href, + "Should have the same href when redirecting again"); +}) + +addLoadEvent(SimpleTest.finish); +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug399349.html b/layout/style/test/test_bug399349.html new file mode 100644 index 000000000..8e61f7d3b --- /dev/null +++ b/layout/style/test/test_bug399349.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=363146 +--> +<head> + <title>Test for Bug 363146</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=399349">Mozilla Bug 399349</a> + +<!-- Test parsing of integer numbers --> +<div id="Aone" style="width:100px; height:400px; top:-100px; left: -200px;position:relative;"></div> + +<!-- Test parsing of float numbers --> +<div id="Atwo" style="width:150.2px; height:450.25px; top:-150.2px; left: -450.25px;position:relative;"></div> +<div id="Athree" style="width:.1px; height:0.3px; top:-0.1px; left:-0.3px;position:relative;"></div> +<div id="Afour" style="width:+100.017px; height:+400.017px; top:-.117px; left: -.217px;position:relative;"></div> + +<!-- Test parsing of long fractions --> +<div id="Afive" style="width:+2345.0000000000000000000000000000000000001px; height:+456.000000000000000000000000000001px; + top:-2123.000000000000000000000000000000000001px; left:-6543.99999999999999999999999999999999px; + position:relative;"></div> + +<!-- Force parsing of long numbers (>9 digits), if they are zero's. Note css itself can't handle large numers --> +<div id="Asix" style="width:+000000000012px; height:+000000000037.456788px; + top:-000000000023px; left:-000000000044.456788px; + position:relative;"></div> + +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var a1 = window.getComputedStyle(document.getElementById("Aone"), ""); +is(a1.width, "100px"); +is(a1.height, "400px"); +is(a1.top, "-100px"); +is(a1.left, "-200px"); + +var a2 = window.getComputedStyle(document.getElementById("Atwo"), ""); +is(a2.width, "150.2px"); +is(a2.height, "450.25px"); +is(a2.top, "-150.2px"); +is(a2.left, "-450.25px"); + +var a3 = window.getComputedStyle(document.getElementById("Athree"), ""); +is(a3.width, "0.1px"); +is(a3.height, "0.3px"); +is(a3.top, "-0.1px"); +is(a3.left, "-0.3px"); + +var a4 = window.getComputedStyle(document.getElementById("Afour"), ""); +is(a4.width, "100.017px"); +is(a4.height, "400.017px"); +is(a4.top, "-0.116667px"); +is(a4.left, "-0.216667px"); + +var a5 = window.getComputedStyle(document.getElementById("Afive"), ""); +is(a5.width, "2345px"); +is(a5.height, "456px"); +is(a5.top, "-2123px"); +is(a5.left, "-6544px"); + +var a6 = window.getComputedStyle(document.getElementById("Asix"), ""); +is(a6.width, "12px"); +is(a6.height, "37.45px"); +is(a6.top, "-23px"); +is(a6.left, "-44.45px"); + +</script> + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug401046.html b/layout/style/test/test_bug401046.html new file mode 100644 index 000000000..34491eaa8 --- /dev/null +++ b/layout/style/test/test_bug401046.html @@ -0,0 +1,82 @@ +<!DOCTYPE HTML> +<html lang="en"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=401046 +--> +<head> + <title>Test for Bug 401046</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + #display span { margin-bottom: 1em; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=401046">Mozilla Bug 401046</a> +<p id="display" lang="zh-Hans"> + <span id="s0" style="font-size: 0">汉字</span> + <span id="s4" style="font-size: 4px">汉字</span> + <span id="s12" style="font-size: 12px">汉字</span> + <span id="s28" style="font-size: 28px">汉字</span> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 401046 **/ + +SimpleTest.waitForExplicitFinish(); + +var elts = [ + document.getElementById("s0"), + document.getElementById("s4"), + document.getElementById("s12"), + document.getElementById("s28") +]; + +function fs(idx) { + // The computed font size actually *doesn't* currently reflect the + // minimum font size preference, but things in em units do. Not sure + // if this is how it ought to be... + return getComputedStyle(elts[idx], "").marginBottom; +} + +SpecialPowers.pushPrefEnv({'clear': [['font.minimum-size.zh-CN']]}, step1); + +function step1() { + is(fs(0), "0px", "at min font size 0, 0px should compute to 0px"); + is(fs(1), "4px", "at min font size 0, 4px should compute to 4px"); + is(fs(2), "12px", "at min font size 0, 12px should compute to 12px"); + is(fs(3), "28px", "at min font size 0, 28px should compute to 28px"); + + SpecialPowers.pushPrefEnv({'set': [['font.minimum-size.zh-CN', 7]]}, step2); +} + +function step2() { + is(fs(0), "0px", "at min font size 7, 0px should compute to 0px"); + is(fs(1), "7px", "at min font size 7, 4px should compute to 7px"); + is(fs(2), "12px", "at min font size 7, 12px should compute to 12px"); + is(fs(3), "28px", "at min font size 7, 28px should compute to 28px"); + + SpecialPowers.pushPrefEnv({'set': [['font.minimum-size.zh-CN', 18]]}, step3); +} + +function step3() { + is(fs(0), "0px", "at min font size 18, 0px should compute to 0px"); + is(fs(1), "18px", "at min font size 18, 4px should compute to 18px"); + is(fs(2), "18px", "at min font size 18, 12px should compute to 18px"); + is(fs(3), "28px", "at min font size 18, 28px should compute to 28px"); + + SpecialPowers.pushPrefEnv({'clear': [['font.minimum-size.zh-CN']]}, SimpleTest.finish); +} + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug405818.html b/layout/style/test/test_bug405818.html new file mode 100644 index 000000000..fb1c98708 --- /dev/null +++ b/layout/style/test/test_bug405818.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=405818 +--> +<head> + <title>Test for Bug 405818</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <link rel="stylesheet" type="text/css" href="data:text/css,%23myDiv{color:green;}"> + <link rel="stylesheet" type="text/css" href="chrome://global/skin/global.css"> + <!-- Script to make sure sheets gets a chance to load fully in Gecko 1.8 and earlier --> + <script type="text/javascript" src="data:text/javascript,"></script> + <link rel="stylesheet" type="text/css" href="data:text/css,%23myDiv{color:green;}"> + <link rel="stylesheet" type="text/css" href="chrome://global/skin/global.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=405818">Mozilla Bug 405818</a> +<p id="display"></p> +<div id="content" style="display: none"> + <div id="myDiv"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 405818 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + is(document.styleSheets[1].href, + "data:text/css,%23myDiv{color:green;}", + "Unexpected href for linked sheet before cloning"); + is(document.styleSheets[3].href, + "data:text/css,%23myDiv{color:green;}", + "Unexpected href for later linked sheet before cloning"); + + is(document.styleSheets[2].href, + "chrome://global/skin/global.css", + "Unexpected href for linked chrome sheet before cloning"); + is(document.styleSheets[4].href, + "chrome://global/skin/global.css", + "Unexpected href for later linked chrome sheet before cloning"); + + // Force cloning of inners + document.styleSheets[1].cssRules[0]; + SpecialPowers.wrap(document.styleSheets[2]).cssRules[0]; + + is(document.styleSheets[1].href, + "data:text/css,%23myDiv{color:green;}", + "Unexpected href for linked sheet after cloning"); + is(document.styleSheets[3].href, + "data:text/css,%23myDiv{color:green;}", + "Unexpected href for later linked sheet after cloning"); + + is(document.styleSheets[2].href, + "chrome://global/skin/global.css", + "Unexpected href for linked chrome sheet after cloning"); + is(document.styleSheets[4].href, + "chrome://global/skin/global.css", + "Unexpected href for later linked chrome sheet after cloning"); + + var myDiv = document.getElementById("myDiv"); + is(getComputedStyle(myDiv, "").color, "rgb(0, 128, 0)", + "Unexpected color for div (data URI stylesheet not being honored?)"); + + SimpleTest.finish(); +}); +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug412901.html b/layout/style/test/test_bug412901.html new file mode 100644 index 000000000..6443d25c3 --- /dev/null +++ b/layout/style/test/test_bug412901.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=412901 +--> +<head> + <title>Test for Bug 412901</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=412901">Mozilla Bug 412901</a> +<div id="testDiv" style="width:20px; height:20px; border:solid silver; border-left-width:0.2px; border-right-width:0px; border-top-width:2.3px; border-bottom-width:5.5px;"> +<div id="testDiv2" style="width:20px; height:20px; border:hidden solid silver; border-left-width:0.2px; border-right-width:0px; border-top-width:2.3px; border-bottom-width:5.5px;"> +<p id="display"></p> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 412901 **/ + +var div = document.getElementById("testDiv"); +var computedStyle = document.defaultView.getComputedStyle(div, ""); +// we never round down to 0px, very small widths are rounded up to 1px +is(computedStyle.borderLeftWidth, "1px"); +is(computedStyle.borderRightWidth, "0px"); +is(computedStyle.borderTopWidth, "2px"); +is(computedStyle.borderBottomWidth, "5px"); + +var div2 = document.getElementById("testDiv2"); +var computedStyle2 = document.defaultView.getComputedStyle(div2, ""); +is(computedStyle2.borderLeftWidth, "0px"); +is(computedStyle2.borderRightWidth, "0px"); +is(computedStyle2.borderTopWidth, "0px"); +is(computedStyle2.borderBottomWidth, "0px"); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug413958.html b/layout/style/test/test_bug413958.html new file mode 100644 index 000000000..273e4466e --- /dev/null +++ b/layout/style/test/test_bug413958.html @@ -0,0 +1,78 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=413958 +--> +<head> + <title>Test for Bug 413958</title> + <meta charset="UTF-8"> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<style>span { color: red }</style><!-- backstop --> +<p><a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=413958" + >Mozilla Bug 413958</a>. All text below should be black on white.</p> +<p>Sheet: <span id="s1">1</span> + <span id="s2">2</span> + <span id="s3">3</span>. + Style attr: <span id="setStyle">4</span>. + Properties: <span id="setStyleProp" style="">5</span>.</p> +<script> +var tests = [ + function() { + var s = document.createTextNode( +"#s1{nosuchprop:auto; color:black}\n"+ +"#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}"), + e = document.createElement("style"); + e.appendChild(s); + document.body.appendChild(e); + }, + function() { + document.getElementById("setStyle") + .setAttribute("style", "width:200;color:black"); + }, + function() { + var s = document.getElementById("setStyleProp").style; + s.width = "200"; + s.color = "black"; + }, +]; +var results = [ + [ { errorMessage: /Unknown property \u2018nosuchprop\u2019/, + lineNumber: 1, columnNumber: 14, + sourceLine: "#s1{nosuchprop:auto; color:black}" }, + { errorMessage: /Unknown property \u2018nosuchprop\u2019/, + lineNumber: 2, columnNumber: 14, sourceLine: + "#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}" }, + { errorMessage: /Ruleset ignored due to bad selector/, + lineNumber: 2, columnNumber: 40, sourceLine: + "#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}" } ], + [ { errorMessage: /parsing value for \u2018width\u2019/, + lineNumber: 0, columnNumber: 6, + sourceLine: "width:200;color:black" } ], + [ { errorMessage: /parsing value for \u2018width\u2019/, + lineNumber: 0, columnNumber: 0, + sourceLine: "200" } ], +]; +var curTest = -1; + +function doTest() { + if (++curTest == tests.length) { + var ss = document.getElementsByTagName("span"); + for (var i = 0; i < ss.length; i++) { + is(window.getComputedStyle(ss[i], "").color, "rgb(0, 0, 0)", + "recovery | " + ss[i].id); + } + SimpleTest.finish(); + } else { + SimpleTest.expectConsoleMessages(tests[curTest], results[curTest], doTest); + } +} + +SimpleTest.waitForExplicitFinish(); +doTest(); +</script> +</body> +</html> diff --git a/layout/style/test/test_bug418986-2.html b/layout/style/test/test_bug418986-2.html new file mode 100644 index 000000000..aa9ff8af1 --- /dev/null +++ b/layout/style/test/test_bug418986-2.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=418986 +--> +<head> + <meta charset="utf-8"> + <title>Test 2/3 for Bug #418986: Resist fingerprinting by preventing exposure of screen and system info</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="test-css"></style> + <script type="text/javascript;version=1.7" src="chrome/bug418986-2.js"></script> + <script type="text/javascript;version=1.7"> + // Run all tests now. + window.onload = function () { + add_task(function* () { + yield test(true); + }); + }; + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986">Bug 418986</a> +<p id="display">TEST</p> +<div id="content" style="display: none"> + +</div> +<p id="pictures"></p> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug437915.html b/layout/style/test/test_bug437915.html new file mode 100644 index 000000000..595c0a643 --- /dev/null +++ b/layout/style/test/test_bug437915.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=437915 +--> +<head> + <title>Test for Bug 437915</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + div.classvalue { text-decoration: underline; } + div[title~="titlevalue"] { visibility: hidden; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437915">Mozilla Bug 437915</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 437915 **/ + +var div = document.getElementById("content"); +var cs = document.defaultView.getComputedStyle(div, ""); + +var chars = { + 0x09: true, // tab + 0x0a: true, // newline + 0x0b: false, // vertical tab (MAY CHANGE IN FUTURE!) + 0x0c: true, // form feed + 0x0d: true, // carriage return + 0x0e: false, + 0x20: true, // space + 0x2003: false, + 0x200b: false, + 0x2028: false, + 0x2029: false, + 0x3000: false +}; + +var wsmap = { + false: { str: " NOT", "text-decoration": "none", "visibility": "visible" }, + true: { str: "", "text-decoration": "underline", "visibility": "hidden" } +}; + +for (var char in chars) { + var is_whitespace = chars[char]; + var mapent = wsmap[is_whitespace]; + div.setAttribute("class", "classvalue" + String.fromCharCode(char) + "b") + div.setAttribute("title", "a" + String.fromCharCode(char) + "titlevalue") + for (var prop of ["text-decoration", "visibility"]) { + is(cs.getPropertyValue(prop), mapent[prop], + "Character " + char + " should" + mapent.str + + " be treated as whitespace (" + + prop + " should be " + mapent[prop] + ")"); + } +} + + + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug450191.html b/layout/style/test/test_bug450191.html new file mode 100644 index 000000000..6471bcc73 --- /dev/null +++ b/layout/style/test/test_bug450191.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=450191 +--> +<head> + <title>Test for Bug 450191</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450191">Mozilla Bug 450191</a> +<iframe id="display" src="about:blank"></iframe> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 450191 **/ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var iframe = document.getElementById("display"); + var subdoc = iframe.contentDocument; + var subwin = iframe.contentWindow; + + var doctext = "<div style='font-size: 2em'>div text <table><tr><td id='t'>table text</td></tr></table></div>"; + + function subdoc_body_font() { + return subwin.getComputedStyle(subdoc.body, "").fontSize; + } + + function subdoc_cell_font() { + return subwin.getComputedStyle(subdoc.getElementById("t"), "").fontSize; + } + + subdoc.open(); + subdoc.write(doctext); + subdoc.close(); + + is(subdoc_cell_font(), subdoc_body_font(), + "Quirks style sheet should be applied."); + + subdoc.open(); + subdoc.write("<!DOCTYPE HTML>" + doctext); + subdoc.close(); + + isnot(subdoc_cell_font(), subdoc_body_font(), + "Quirks style sheet should NOT be applied."); + + subdoc.open(); + subdoc.write(doctext); + subdoc.close(); + + is(subdoc_cell_font(), subdoc_body_font(), + "Quirks style sheet should be applied."); + + SimpleTest.finish(); +} + + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug453896_deck.html b/layout/style/test/test_bug453896_deck.html new file mode 100644 index 000000000..603063241 --- /dev/null +++ b/layout/style/test/test_bug453896_deck.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=453896 +--> +<head> + <title>Test for Bug 453896</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=453896">Mozilla Bug 453896</a> +<div id="display"> + +<div style="display:-moz-deck; height: 300px; width: 300px;"> + <iframe src="about:blank"></iframe> + <iframe id="subdoc" src="bug453896_iframe.html"></iframe> + <iframe src="about:blank"></iframe> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 453896 **/ + +function run() +{ + var iframe = document.getElementById("subdoc"); + var subdoc = iframe.contentDocument; + var subwin = iframe.contentWindow; + + subwin.run(window); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug470769.html b/layout/style/test/test_bug470769.html new file mode 100644 index 000000000..cb32f47ec --- /dev/null +++ b/layout/style/test/test_bug470769.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=470769 +--> +<head> + <title>Test for Bug 470769</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=470769">Mozilla Bug 470769</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 470769 **/ + +var e = document.getElementById("display"); +e.setAttribute("style", "z-index: 2147483647"); // maximum signed 32-bit +is(e.style.zIndex, "2147483647", "element.style should roundtrip correctly"); +is(window.getComputedStyle(e, "").zIndex, "2147483647", + "getComputedStyle should roundtrip correctly"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug499655.html b/layout/style/test/test_bug499655.html new file mode 100644 index 000000000..385ee43fa --- /dev/null +++ b/layout/style/test/test_bug499655.html @@ -0,0 +1,45 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=499655 +--> +<head> + <title>Test for Bug 499655</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499655">Mozilla Bug 499655</a> +<p id="display"></p> +<div id="content"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 499655 **/ + +test1 = document.createElementNS("http://www.w3.org/1999/xhtml","test"); +test2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst"); +test3 = document.createElementNS("test","test"); +test4 = document.createElementNS("test","TEst"); + +content = document.getElementById("content"); + +content.appendChild(test1); +content.appendChild(test2); +content.appendChild(test3); +content.appendChild(test4); + +list = document.querySelectorAll('test'); +is(list.length, 2, "Number of elements found"); +is(list[0], test1, "First element didn't match"); +is(list[1], test3, "Third element didn't match"); + +list = document.querySelectorAll('TEst'); +is(list.length, 2, "Wrong number of elements found"); +is(list[0], test1, "First element didn't match"); +is(list[1], test4, "Fourth element didn't match"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug499655.xhtml b/layout/style/test/test_bug499655.xhtml new file mode 100644 index 000000000..539c3fa36 --- /dev/null +++ b/layout/style/test/test_bug499655.xhtml @@ -0,0 +1,48 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=499655 +--> +<head> + <title>Test for Bug 499655</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499655">Mozilla Bug 499655</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +<![CDATA[ + +test1 = document.createElementNS("http://www.w3.org/1999/xhtml","test"); +test2 = document.createElementNS("http://www.w3.org/1999/xhtml","TEst"); +test3 = document.createElementNS("test","test"); +test4 = document.createElementNS("test","TEst"); + +content = document.getElementById("content"); + +content.appendChild(test1); +content.appendChild(test2); +content.appendChild(test3); +content.appendChild(test4); + + +list = document.querySelectorAll('test'); +is(list.length, 2, "Number of elements found"); +is(list[0], test1, "First element didn't match"); +is(list[1] , test3, "Third element didn't match"); + +list = document.querySelectorAll('TEst'); +is(list.length, 2, "Number of elements found"); +is(list[0], test2, "Second element didn't match"); +is(list[1], test4, "Fourth element didn't match"); + + +]]> +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug511909.html b/layout/style/test/test_bug511909.html new file mode 100644 index 000000000..060636a9f --- /dev/null +++ b/layout/style/test/test_bug511909.html @@ -0,0 +1,205 @@ +<html><!-- + https://bugzilla.mozilla.org/show_bug.cgi?id=511909 + --><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> +<title>@media and @-moz-document testcases</title> + +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + +<style type="text/css"> +a { + font-weight: bold; +} + #pink { + color: pink; + } + + #green { + color: green; + } + + #blue { + color: blue; + } + + +pre { + border: 1px solid black; +} +</style> + +<style type="text/css"> +@-moz-document regexp(".*test_bug511909.*"){ + #d { + color: pink; + } +} + +</style> + +<style type="text/css"> + +@media screen { + #m { + color: green; + } +} + +</style> + +<style type="text/css"> + +@-moz-document regexp(".*test_bug511909.*"){ + @media screen { + #dm { + color: blue; + } + } +} + +</style> + +<!-- should parse --> +<style type="text/css"> + +@media print { + @-moz-document regexp("not_this_url"),} + #mx { + color: pink; + } + } +} + +</style> + +<!-- should parse --> +<style type="text/css"> + +@-moz-document regexp("not_this_url"){ + @media print ,} + #mxx { + color: blue; + } + } +} + +</style> + +<style type="text/css"> + +@media screen { + @-moz-document regexp(".*test_bug511909.*"){ + #md { + color: green; + } + } +} + +</style> + +<style type="text/css"> + +@media screen { + @-moz-document regexp(".*test_bug511909.*"){ + @media screen { + @-moz-document regexp(".*test_bug511909.*"){ + @media screen { + #me { + color: blue; + } + } + } + } + } +} + +</style> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511909">Mozilla Bug 511909</a> + <p id="display"></p> + <div id="content" style="display: none"> + + </div> + + <script class="testbody" type="text/javascript"> + + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + var pink = getComputedStyle(document.getElementById("pink"), ""); + var green = getComputedStyle(document.getElementById("green"), ""); + var blue = getComputedStyle(document.getElementById("blue"), ""); + + var cs1 = getComputedStyle(document.getElementById("d"), ""); + var cs2 = getComputedStyle(document.getElementById("m"), ""); + var cs3 = getComputedStyle(document.getElementById("dm"), ""); + var cs4 = getComputedStyle(document.getElementById("md"), ""); + var cs5 = getComputedStyle(document.getElementById("mx"), ""); + var cs6 = getComputedStyle(document.getElementById("mxx"), ""); + var cs7 = getComputedStyle(document.getElementById("me"), ""); + + is(cs1.color, pink.color, "@-moz-document applies"); + is(cs2.color, green.color, "@media applies"); + is(cs3.color, blue.color, "@media nested in @-moz-document applies"); + is(cs4.color, green.color, "@-moz-document nested in @media applies"); + is(cs5.color, pink.color, "broken @media nested in @-moz-document correctly handled"); + is(cs6.color, blue.color, "broken @-moz-document nested in @media correctly handled"); + is(cs7.color, blue.color, "@media nested in @-moz-document nested in @media applies"); + SimpleTest.finish(); + }); + </script> +<div> +<pre>default style +</pre> +<a id="pink">This line should be pink</a><br> + +<a id="green">This line should be green</a><br> + +<a id="blue">This line should be blue</a><br> + +<pre>@-moz-document {...} +</pre> +<a id="d">This line should be pink</a><br> +<pre>@media screen {...} +</pre> +<a id="m">This line should be green</a><br> +<pre>@-moz-document { + @media screen {...} +} +</pre> +<a id="dm">This line should be blue</a><br> +<pre>@media print { + @-moz-document regexp("not_this_url"),} + #mx { + color: pink; + } + } +} +</pre> +<a id="mx">This line should be pink</a><br></div> +<pre>@-moz-document regexp("not_this_url"){ + @media print ,} + #mxx { + color: blue; + } + } +} +</pre> +<a id="mxx">This line should be blue</a><br> +<pre>@media screen { + @-moz-documen {...} +} +</pre> +<a id="md">This line should be green</a><br> +<pre>@media screen { + @-moz-document { + @media screen {...} + } +} +</pre> +<a id="me">This line should be blue</a><br> + + +</body></html> diff --git a/layout/style/test/test_bug517224.html b/layout/style/test/test_bug517224.html new file mode 100644 index 000000000..e748f9e20 --- /dev/null +++ b/layout/style/test/test_bug517224.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=517224 +--> +<head> + <title>Test for Bug 517224</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/ecmascript" src="bug517224.sjs?reset"></script> + <style type="text/css"> + + p#display { background: url(bug517224.sjs?image); } + p#display { background-image: none; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=517224">Mozilla Bug 517224</a> +<p id="display">Element with overridden background</p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 517224 **/ + +// Test that we don't load background images for style rules that are +// always overridden. + +// Make sure the style has been computed +var bi = + getComputedStyle(document.getElementById('display'), "").backgroundImage; +SimpleTest.waitForExplicitFinish(); +window.onload = run; + +function run() +{ + var script = document.createElement("script"); + script.setAttribute("src", "bug517224.sjs?result"); + document.body.appendChild(script); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug524175.html b/layout/style/test/test_bug524175.html new file mode 100644 index 000000000..a86b3ed0b --- /dev/null +++ b/layout/style/test/test_bug524175.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=524175 +--> +<head> + <title>Test for Bug 524175</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + span::before ::before {} + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=524175">Mozilla Bug 524175</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 524175 **/ +is(document.styleSheets[1].cssRules.length, 0, "Shouldn't have parsed that rule"); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug525952.html b/layout/style/test/test_bug525952.html new file mode 100644 index 000000000..66768a742 --- /dev/null +++ b/layout/style/test/test_bug525952.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=525952 +--> +<head> + <title>Test for Bug 525952</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525952">Mozilla Bug 525952</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 525952 **/ +var bodies = document.querySelectorAll("::before, div::before, body"); +is(bodies.length, 1, "Unexpected length"); +is(bodies[0], document.body, "Unexpected element"); + +is(document.querySelector("div > ::after, body"), document.body, + "Unexpected return value"); + +var emptyList = document.querySelectorAll("::before, div::before"); +is(emptyList.length, 0, "Unexpected empty list length"); + +is(document.querySelectorAll("div > ::after").length, 0, + "Pseudo-element matched something?"); + +is(document.body.matches("::first-line"), false, + "body shouldn't match ::first-line"); + +is(document.body.matches("::first-line, body"), true, + "body should match 'body'"); + +is(document.body.matches("::first-line, body, ::first-letter"), true, + "body should match 'body' here too"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug534804.html b/layout/style/test/test_bug534804.html new file mode 100644 index 000000000..ac04c3563 --- /dev/null +++ b/layout/style/test/test_bug534804.html @@ -0,0 +1,89 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=534804 +--> +<head> + <title>Test for Bug 534804</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css" id="styleone"> </style> + <style type="text/css" id="styletwo"> </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=534804">Mozilla Bug 534804</a> +<p id="display"></p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 534804 **/ + +var styleone = document.getElementById("styleone"); +var styletwo = document.getElementById("styletwo"); +var display = document.getElementById("display"); + +run1(); +styletwo.firstChild.data = "#e > span:nth-child(2n+1) { color: green }"; +run1(); +styletwo.firstChild.data = "#e > span:first-child { color: green }"; +run1(); +styletwo.firstChild.data = "#e > span:nth-last-child(2n+1) { color: green }"; +run1(); +styletwo.firstChild.data = "#e > span:last-child { color: green }"; +run1(); + +function run1() +{ + function identity(bool) { return bool; } + function inverse(bool) { return !bool; } + function always_false(bool) { return false; } + run2("#e:empty + span", identity, always_false); + run2("#e:empty ~ span", identity, identity); + run2("#e:not(:empty) + span", inverse, always_false); + run2("#e:not(:empty) ~ span", inverse, inverse); +} + +function run2(sel, next_sibling_rule, later_sibling_rule) +{ + styleone.firstChild.data = sel + " { text-decoration: underline }"; + + // Rebuild the subtree every time. + var span1 = document.createElement("span"); + span1.id = "e"; + var span2 = document.createElement("span"); + var span3 = document.createElement("span"); + display.appendChild(span1); + display.appendChild(span2); + display.appendChild(span3); + + function td(e) { return getComputedStyle(e, "").textDecoration; } + + function check(desc, isempty) { + is(td(span2), next_sibling_rule(isempty) ? "underline" : "none", + "match of next sibling in state " + desc); + is(td(span3), later_sibling_rule(isempty) ? "underline" : "none", + "match of next sibling in state " + desc); + } + + check("initially empty", true); + var kid = document.createElement("span"); + span1.appendChild(kid); + check("after append", false); + span1.removeChild(kid); + check("after remove", true); + span1.appendChild(document.createTextNode("")); + span1.appendChild(document.createComment("a comment")); + span1.appendChild(document.createTextNode("")); + check("after append of insignificant children", true); + span1.insertBefore(kid, span1.childNodes[1]); + check("after insert", false); + + display.removeChild(span1); + display.removeChild(span2); + display.removeChild(span3); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug573255.html b/layout/style/test/test_bug573255.html new file mode 100644 index 000000000..5ea1d826f --- /dev/null +++ b/layout/style/test/test_bug573255.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=573255 +--> +<head> + <title>Test for Bug 573255</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + #display:not(.hasSummary) { visibility: hidden } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=573255">Mozilla Bug 573255</a> +<p id="display"></p> +<pre id="test"> +<script type="application/javascript"> + +var p = document.getElementById("display"); +var cs = getComputedStyle(p, ""); + +is(cs.visibility, "hidden", "should be visibility:none since it has no class"); +p.className = "hasSummary"; +is(cs.visibility, "visible", "changing class attribute should remove visibility:hidden"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug580685.html b/layout/style/test/test_bug580685.html new file mode 100644 index 000000000..795b2ce0c --- /dev/null +++ b/layout/style/test/test_bug580685.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=580685 +--> +<head> + <title>Test for Bug 580685</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=580685">Mozilla Bug 580685</a> +<p id="display"> + <iframe id="f" src="data:text/html,<body style='outline-offset: 1rem'>"> + </iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 580685 **/ +SimpleTest.waitForExplicitFinish() +addLoadEvent(function() { + var doc = $("f").contentDocument; + var b = doc.body; + doc.removeChild(doc.documentElement); + is(doc.defaultView.getComputedStyle(b, "").outlineOffset, + doc.defaultView.getComputedStyle(b, "").fontSize, + "1rem did not compute correctly"); + SimpleTest.finish(); +}); + + + + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug621351.html b/layout/style/test/test_bug621351.html new file mode 100644 index 000000000..6a6d373af --- /dev/null +++ b/layout/style/test/test_bug621351.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang=en> +<title>Test for Bug 160403</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +<span></span> +<style> +span { + border-inline-start: 0px solid rgb(0, 0, 0); + border-inline-end: 0px solid rgb(0, 0, 0); + transition: border 100s linear -50s; +} +span.transitioned { + border-inline-start: 100px solid rgb(100, 100, 100); + border-inline-end: 10px solid rgb(10, 10, 10); +} +</style> +<script> +// Test that transitioning each of border-{left,right}-{color,width} +// works when the values are set through the -moz-border-{start,end} +// shorthands. + +var e = document.querySelector("span"); +var cs = getComputedStyle(e); +is(cs.borderLeftColor, "rgb(0, 0, 0)", "value of border-left-color before transition"); +is(cs.borderLeftWidth, "0px", "value of border-left-width before transition"); +is(cs.borderRightColor, "rgb(0, 0, 0)", "value of border-right-color before transition"); +is(cs.borderRightWidth, "0px", "value of border-right-width before transition"); +e.className = "transitioned"; +is(cs.borderLeftWidth, "50px", "value of border-left-width during transition"); +is(cs.borderLeftColor, "rgb(50, 50, 50)", "value of border-left-color during transition"); +is(cs.borderRightWidth, "5px", "value of border-right-width during transition"); +is(cs.borderRightColor, "rgb(5, 5, 5)", "value of border-right-color during transition"); +e.remove(); +</script> diff --git a/layout/style/test/test_bug635286.html b/layout/style/test/test_bug635286.html new file mode 100644 index 000000000..c6c9d6ad4 --- /dev/null +++ b/layout/style/test/test_bug635286.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635286
+-->
+<head>
+ <title>Test for Bug 635286</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ div { background: transparent; }
+ :-moz-any(#case1.before) { background: gray; }
+ #case2:not(.after) { background: gray; }
+ :-moz-any(#case3:not(.after)) { background: gray; }
+ #case4:not(:-moz-any(.after)) { background: gray; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=635286">Mozilla Bug 635286</a>
+<div id="case1" class="before">case1, :-moz-any()</div>
+<div id="case2" class="before">case2, :not()</div>
+<div id="case3" class="before">case3, :not() in :-moz-any()</div>
+<div id="case4" class="before">case4, :-moz-any() in :not()</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 635286 **/
+
+window.addEventListener("load", function() {
+ var cases = Array.slice(document.getElementsByTagName("div"));
+ cases.forEach(function(aCase, aIndex) {
+ aCase.className = "after";
+ });
+ window.setTimeout(function() {
+ cases.forEach(function(aCase, aIndex) {
+ is(window.getComputedStyle(aCase, null)
+ .getPropertyValue("background-color"),
+ "transparent",
+ aCase.textContent);
+ });
+ SimpleTest.finish();
+ }, 1);
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/style/test/test_bug645998.html b/layout/style/test/test_bug645998.html new file mode 100644 index 000000000..f511185ed --- /dev/null +++ b/layout/style/test/test_bug645998.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=645998 +--> +<head> + <title>Test for Bug 645998</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <!-- This is the real test: will these stylesheets ever finish loading? --> + <link rel="stylesheet" href="file_bug645998-1.css"> + <link rel="stylesheet" href="file_bug645998-2.css"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=645998">Mozilla Bug 645998</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 645998 **/ +ok(true, "Hey, we got here! That's a good sign"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug652486.html b/layout/style/test/test_bug652486.html new file mode 100644 index 000000000..9abb7041c --- /dev/null +++ b/layout/style/test/test_bug652486.html @@ -0,0 +1,212 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=652486 +https://bugzilla.mozilla.org/show_bug.cgi?id=1039488 +--> +<head> + <title>Test for Bug 652486 and Bug 1039488</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=652486">Mozilla Bug 652486</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1039488">Mozilla Bug 1039488</a> + +<p id="display"></p> +<div id="content" style="display: none"> + <div id="t"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 652486 and Bug 1039488 **/ + +function c() { + return document.defaultView.getComputedStyle($('t'), ""). + getPropertyValue("text-decoration"); +} + +var tests = [ + // When only text-decoration was specified, text-decoration should look like + // a longhand property. + { decoration: "none", + line: null, color: null, style: null, + expectedValue: "none", expectedCSSValue: "none" }, + { decoration: "underline", + line: null, color: null, style: null, + expectedValue: "underline", expectedCSSValue: "underline" }, + { decoration: "overline", + line: null, color: null, style: null, + expectedValue: "overline", expectedCSSValue: "overline" }, + { decoration: "line-through", + line: null, color: null, style: null, + expectedValue: "line-through", expectedCSSValue: "line-through" }, + { decoration: "blink", + line: null, color: null, style: null, + expectedValue: "blink", expectedCSSValue: "blink" }, + { decoration: "underline overline", + line: null, color: null, style: null, + expectedValue: "underline overline", + expectedCSSValue: "underline overline" }, + { decoration: "underline line-through", + line: null, color: null, style: null, + expectedValue: "underline line-through", + expectedCSSValue: "underline line-through" }, + { decoration: "blink underline", + line: null, color: null, style: null, + expectedValue: "underline blink", + expectedCSSValue: "underline blink" }, + { decoration: "underline blink", + line: null, color: null, style: null, + expectedValue: "underline blink", + expectedCSSValue: "underline blink" }, + + // When only text-decoration-line or text-blink was specified, + // text-decoration should look like a longhand property. + { decoration: null, + line: "blink", color: null, style: null, + expectedValue: "blink", expectedCSSValue: "blink" }, + { decoration: null, + line: "underline", color: null, style: null, + expectedValue: "underline", expectedCSSValue: "underline" }, + { decoration: null, + line: "overline", color: null, style: null, + expectedValue: "overline", expectedCSSValue: "overline" }, + { decoration: null, + line: "line-through", color: null, style: null, + expectedValue: "line-through", expectedCSSValue: "line-through" }, + { decoration: null, + line: "blink underline", color: null, style: null, + expectedValue: "underline blink", expectedCSSValue: "underline blink" }, + + // When text-decoration-color isn't its initial value, + // text-decoration should be a shorthand property. + { decoration: "blink", + line: null, color: "rgb(0, 0, 0)", style: null, + expectedValue: "blink rgb(0, 0, 0)", expectedCSSValue: [ "blink", [0, 0, 0] ] }, + { decoration: "underline", + line: null, color: "black", style: null, + expectedValue: "underline rgb(0, 0, 0)", expectedCSSValue: [ "underline", [0, 0, 0] ] }, + { decoration: "overline", + line: null, color: "#ff0000", style: null, + expectedValue: "overline rgb(255, 0, 0)", expectedCSSValue: [ "overline", [255, 0, 0] ] }, + { decoration: "line-through", + line: null, color: "initial", style: null, + expectedValue: "line-through", expectedCSSValue: "line-through" }, + { decoration: "blink underline", + line: null, color: "currentColor", style: null, + expectedValue: "underline blink", expectedCSSValue: "underline blink" }, + { decoration: "underline line-through", + line: null, color: "currentcolor", style: null, + expectedValue: "underline line-through", + expectedCSSValue: "underline line-through" }, + + // When text-decoration-style isn't its initial value, + // text-decoration should be a shorthand property. + { decoration: "blink", + line: null, color: null, style: "-moz-none", + expectedValue: "blink -moz-none", expectedCSSValue: [ "blink", "-moz-none" ] }, + { decoration: "underline", + line: null, color: null, style: "dotted", + expectedValue: "underline dotted", expectedCSSValue: [ "underline", "dotted" ] }, + { decoration: "overline", + line: null, color: null, style: "dashed", + expectedValue: "overline dashed", expectedCSSValue: [ "overline", "dashed" ] }, + { decoration: "line-through", + line: null, color: null, style: "double", + expectedValue: "line-through double", expectedCSSValue: [ "line-through", "double" ] }, + { decoration: "blink underline", + line: null, color: null, style: "wavy", + expectedValue: "underline blink wavy", expectedCSSValue: [ "underline blink", "wavy" ] }, + { decoration: "underline blink overline line-through", + line: null, color: null, style: "solid", + expectedValue: "underline overline line-through blink", + expectedCSSValue: "underline overline line-through blink" }, + { decoration: "line-through overline underline", + line: null, color: null, style: "initial", + expectedValue: "underline overline line-through", + expectedCSSValue: "underline overline line-through" } +]; + +function makeDeclaration(aTest) +{ + var str = ""; + if (aTest.decoration) { + str += "text-decoration: " + aTest.decoration + "; "; + } + if (aTest.color) { + str += "text-decoration-color: " + aTest.color + "; "; + } + if (aTest.line) { + str += "text-decoration-line: " + aTest.line + "; "; + } + if (aTest.style) { + str += "text-decoration-style: " + aTest.style + "; "; + } + return str; +} + +function clearStyleObject() +{ + $('t').style.textDecoration = null; +} + +function testCSSValue(testname, test, dec) { + var val = document.defaultView.getComputedStyle($('t'), ""). + getPropertyCSSValue("text-decoration"); + isnot(val, null, testname + " (CSS value): " + dec); + + if (typeof test.expectedCSSValue == "string") { + is(val.getStringValue(), test.expectedCSSValue, testname + " (CSS value): " + dec); + return; + } + + is(val.length, test.expectedCSSValue.length, testname + " (CSS value length): " + dec); + for (var i = 0; i < val.length; i ++) { + var actual = val[i]; + var expected = test.expectedCSSValue[i]; + if (typeof expected == "string") { + is(actual.getStringValue(), expected, testname + " (CSS value [" + i + "] value): " + dec); + } else if (typeof expected == "object") { + var rgb = actual.getRGBColorValue(); + is(rgb.red.getFloatValue(CSSPrimitiveValue.CSS_NUMBER), expected[0], testname + " (CSS value [" + i + "] red): " + dec); + is(rgb.green.getFloatValue(CSSPrimitiveValue.CSS_NUMBER), expected[1], testname + " (CSS value [" + i + "] green): " + dec); + is(rgb.blue.getFloatValue(CSSPrimitiveValue.CSS_NUMBER), expected[2], testname + " (CSS value [" + i + "] blue): " + dec); + } + } +} + +for (var i = 0; i < tests.length; ++i) { + var test = tests[i]; + if (test.decoration) { + $('t').style.textDecoration = test.decoration; + } + if (test.color) { + $('t').style.textDecorationColor = test.color; + } + if (test.line) { + $('t').style.textDecorationLine = test.line; + } + if (test.style) { + $('t').style.textDecorationStyle = test.style; + } + + var dec = makeDeclaration(test); + is(c(), test.expectedValue, "Test1 (computed value): " + dec); + testCSSValue("Test1", test, dec); + + clearStyleObject(); + + $('t').setAttribute("style", dec); + + is(c(), test.expectedValue, "Test2 (computed value): " + dec); + testCSSValue("Test2", test, dec); + + $('t').removeAttribute("style"); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug657143.html b/layout/style/test/test_bug657143.html new file mode 100644 index 000000000..4b9980eb7 --- /dev/null +++ b/layout/style/test/test_bug657143.html @@ -0,0 +1,132 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=657143 +--> +<head> + <title>Test for Bug 657143</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657143">Mozilla Bug 657143</a> +<p id="display"></p> +<style> +/* Ensure that there is at least one custom property on the root element's + computed style */ +:root { --test: some value; } +</style> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 657143 **/ + +// Check ordering of CSS properties in nsComputedDOMStylePropertyList.h +// by splitting the getComputedStyle() into five sublists +// then cloning and sort()ing the lists and comparing with originals. + +function isMozPrefixed(aProp) { + return aProp.startsWith("-moz"); +} + +function isNotMozPrefixed(aProp) { + return !isMozPrefixed(aProp); +} + +function isWebkitPrefixed(aProp) { + return aProp.startsWith("-webkit"); +} + +function isNotWebkitPrefixed(aProp) { + return !isWebkitPrefixed(aProp); +} + +function isCustom(aProp) { + return aProp.startsWith("--"); +} + +function isNotCustom(aProp) { + return !isCustom(aProp); +} + +var styleList = window.getComputedStyle(document.documentElement); + cssA = [], mozA = [], webkitA = [], svgA = [], customA = []; + +// Partition the list of property names into four lists: +// +// cssA: regular, non-SVG properties +// mozA: '-moz-' prefixed properties +// svgA: SVG properties +// customA: '--' prefixed custom properties + +var list = cssA; +for (var i = 0, j = styleList.length; i < j; i++) { + var prop = styleList.item(i); + + switch (list) { + case cssA: + if (isMozPrefixed(prop)) { + list = mozA; + } + // fall through + case mozA: + if (isWebkitPrefixed(prop)) { + list = webkitA; + } + // fall through + case webkitA: + // Assume that the first SVG property is 'clip-path'. + if (prop == "clip-path") { + list = svgA; + } + // fall through + case svgA: + if (isCustom(prop)) { + list = customA; + } + // fall through + } + + list.push(prop); +} + +var cssB = cssA.slice(0).sort(), + mozB = mozA.slice(0).sort(), + webkitB = webkitA.slice(0).sort(), + svgB = svgA.slice(0).sort(); + +is(cssA.toString(), cssB.toString(), 'CSS property list should be alphabetical'); +is(mozA.toString(), mozB.toString(), 'Experimental -moz- CSS property list should be alphabetical'); +is(webkitA.toString(), webkitB.toString(), 'Compatible -webkit- CSS property list should be alphabetical'); +is(svgA.toString(), svgB.toString(), 'SVG property list should be alphabetical'); + +// We don't test that the custom property list is sorted, as the CSSOM +// specification does not yet require it, and we don't return them +// in sorted order. + +ok(!cssA.find(isWebkitPrefixed), 'Compatible -webkit- CSS properties should not be in the mature CSS property list'); +ok(!cssA.find(isMozPrefixed), 'Experimental -moz- CSS properties should not be in the mature CSS property list'); +ok(!cssA.find(isCustom), 'Custom CSS properties should not be in the mature CSS property list'); +ok(!mozA.find(isWebkitPrefixed), 'Compatible -webkit- CSS properties should not be in the experimental -moz- CSS ' + + 'property list'); +ok(!mozA.find(isNotMozPrefixed), 'Experimental -moz- CSS property list should not contain non -moz- prefixed ' + + 'CSS properties'); +ok(!mozA.find(isCustom), 'Custom CSS properties should not be in the experimental -moz- CSS property list'); +ok(!webkitA.find(isNotWebkitPrefixed), 'Compatible -webkit- CSS properties should not contain non -webkit- prefixed ' + + 'CSS properties'); +ok(!webkitA.find(isMozPrefixed), 'Experimental -moz- CSS properties should not be in the compatible -webkit- CSS ' + + 'property list'); +ok(!webkitA.find(isCustom), 'Custom CSS properties should not be in the compatible -webkit- CSS property list'); +ok(!svgA.find(isWebkitPrefixed), 'Compatible -webkit- CSS properties should not be in the SVG property list'); +ok(!svgA.find(isMozPrefixed), 'Experimental -moz- CSS properties should not be in the SVG property list'); +ok(!svgA.find(isCustom), 'Custom CSS properties should not be in the SVG property list'); +ok(!customA.find(isNotCustom), 'Non-custom CSS properties should not be in the custom property list'); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug664955.html b/layout/style/test/test_bug664955.html new file mode 100644 index 000000000..a7e31c557 --- /dev/null +++ b/layout/style/test/test_bug664955.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664955 +--> +<head> + <title>Test for Bug 664955</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=664955">Mozilla Bug 664955</a> +<p id="display" style="font-size: 10000000000px"> <!-- must be superior to nscoord_MAX * 60 --> +<span id="larger" style="font-size: larger"> +<span id="larger-again" style="font-size: larger"> +</span> +</span> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +/** Test for Bug 664955 **/ +var parentSize = document.defaultView.getComputedStyle($('display'), "").fontSize; +var largerSize = document.defaultView.getComputedStyle($('larger'), "").fontSize; +var largerAgainSize = document.defaultView.getComputedStyle($('larger-again'), "").fontSize; + +is(parentSize, largerSize, "font size is larger than max font size"); +is(parentSize, largerAgainSize, "font size is larger than max font size"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug667520.html b/layout/style/test/test_bug667520.html new file mode 100644 index 000000000..b6909eedb --- /dev/null +++ b/layout/style/test/test_bug667520.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=667520 +--> +<head> + <title>Test for Bug 667520</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=667520">Mozilla Bug 667520</a> +<p id="display"> + <!-- important: we need a <span> as the second element child and then + non-span elements between that and the next </span> --> + <i></i> + <span id="2"></span> + <i></i> + <i></i> + <i></i> + <i></i> + <span id="7"></span> + <span id="8"></span> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 667520 **/ +var spans = $("display").querySelectorAll("span"); +is(spans.length, 3, "Should have 3 span kids"); + +is($("display").querySelector("span:nth-child(3)"), null, "Third child is not span"); +is($("display").querySelector("span:nth-child(4)"), null, "Fourth child is not span"); + +for (var i = 0; i < spans.length; ++i) { + var id = spans[i].id; + /* Important: need to include 'span' in that selector so we only match + nth-child against spans. */ + var target = $("display").querySelector("span:nth-child("+id+")"); + is(target, spans[i], "Unexpected element"); + is(target.id, spans[i].id, "Unexpected id"); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug716226.html b/layout/style/test/test_bug716226.html new file mode 100644 index 000000000..25df9ffd8 --- /dev/null +++ b/layout/style/test/test_bug716226.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=716226 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 716226</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="s"> + @keyframes foo { } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=716226">Mozilla Bug 716226</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 716226 **/ +var sheet = $("s").sheet; +var rules = sheet.cssRules; +is(rules.length, 1, "Should have one keyframes rule"); +var keyframesRule = rules[0]; +var keyframeRules = keyframesRule.cssRules; +is(keyframeRules.length, 0, "Should have no keyframe rules yet"); + +keyframesRule.appendRule('0% { }'); +is(keyframeRules.length, 1, "Should have a keyframe rule now"); +var keyframeRule = keyframeRules[0]; +is(keyframeRule.parentRule, keyframesRule, + "Parent of keyframe should be keyframes"); +is(keyframeRule.parentStyleSheet, sheet, + "Parent stylesheet of keyframe should be our sheet"); + +is(keyframeRule.style.cssText, "", "Should have no declarations yet"); +// Note: purposefully non-canonical cssText string so we can make sure we +// really invoked the CSS parser and serializer. +keyframeRule.style.cssText = "color:green"; +is(keyframeRule.style.cssText, "color: green;", + "Should have the declarations we set now"); + + + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug732153.html b/layout/style/test/test_bug732153.html new file mode 100644 index 000000000..892029886 --- /dev/null +++ b/layout/style/test/test_bug732153.html @@ -0,0 +1,22 @@ +<!doctype html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=732153 +--> +<title>Test for Bug 732153</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732153">Mozilla Bug 732153</a> +<div></div> +<script> +var div = document.querySelector("div"); +[ + "", + "backface-visibility: hidden", + "transform-style: preserve-3d", + "backface-visibility: hidden; transform-style: preserve-3d", +].forEach(function(style) { + div.setAttribute("style", style); + is(getComputedStyle(div).transform, "none", + "Computed 'transform' with style=\"" + style + '"'); +}); +</script> diff --git a/layout/style/test/test_bug732209.html b/layout/style/test/test_bug732209.html new file mode 100644 index 000000000..0a6c4353d --- /dev/null +++ b/layout/style/test/test_bug732209.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=732209 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 732209</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + #content span { color: red; } + #content span.reverse { color: green; } + #content { display: block !important; } + #content span::before { content: attr(id); } + </style> + <link rel="stylesheet" href="bug732209-css.sjs?one"> + <link rel="stylesheet" href="bug732209-css.sjs?two" crossorigin> + <link rel="stylesheet" href="bug732209-css.sjs?three" crossorigin="use-credentials"> + <link rel="stylesheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?four"> + <link rel="stylesheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?five" + crossorigin> + <link rel="stylesheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?six" + crossorigin="use-credentials"> + <link rel="stylesheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?seven&cors-anonymous"> + <link rel="stylesheet" id="cross-origin-sheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?eight&cors-anonymous" + crossorigin> + <link rel="stylesheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?nine&cors-anonymous" + crossorigin="use-credentials"> + <link rel="stylesheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?ten&cors-credentials"> + <link rel="stylesheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?eleven&cors-credentials" + crossorigin> + <link rel="stylesheet" + href="http://example.com/tests/layout/style/test/bug732209-css.sjs?twelve&cors-credentials" + crossorigin="use-credentials"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=732209">Mozilla Bug 732209</a> +<p id="display"></p> +<div id="content" style="display: none"> + <span id="one"></span> + <span id="two"></span> + <span id="three"></span> + <span id="four"></span> + <span id="five" class="reverse"></span> + <span id="six" class="reverse"></span> + <span id="seven"></span> + <span id="eight"></span> + <span id="nine" class="reverse"></span> + <span id="ten"></span> + <span id="eleven"></span> + <span id="twelve"></span> +</div> +<pre id="test" style="color: red"> +<script type="application/javascript"> + +/** Test for Bug 732209 **/ + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var spans = $("content").querySelectorAll("span"); + for (var i = 0; i < spans.length; ++i) { + is(getComputedStyle(spans[i], "").color, "rgb(0, 128, 0)", + "Span " + spans[i].id + " should be green"); + } + + try { + var sheet = $("cross-origin-sheet").sheet; + dump('aaa\n'); + is(sheet.cssRules.length, 2, + "Should be able to get length of list of rules"); + is(sheet.cssRules[0].style.color, "green", + "Should be able to read individual rules"); + } catch (e) { + ok(false, + "Should be allowed to access cross-origin sheet that opted in with CORS: " + e); + } + + SimpleTest.finish(); +}); + + + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug73586.html b/layout/style/test/test_bug73586.html new file mode 100644 index 000000000..a88ae0a30 --- /dev/null +++ b/layout/style/test/test_bug73586.html @@ -0,0 +1,192 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=73586 +--> +<head> + <title>Test for Bug 73586</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + span { background: white; color: black; border: medium solid black; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=73586">Mozilla Bug 73586</a> +<div id="display"></div> + +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 73586 **/ + +const GREEN = "rgb(0, 128, 0)"; +const LIME = "rgb(0, 255, 0)"; +const BLACK = "rgb(0, 0, 0)"; +const WHITE = "rgb(255, 255, 255)"; + +function cs(elt) { return getComputedStyle(elt, ""); } + +function check_children(p, check_cb) { + var len = p.childNodes.length; + var elts = 0; + var i, elt, child; + for (i = 0; i < len; ++i) { + if (p.childNodes[i].nodeType == Node.ELEMENT_NODE) + ++elts; + } + + elt = 0; + for (i = 0; i < len; ++i) { + child = p.childNodes[i]; + if (child.nodeType != Node.ELEMENT_NODE) + continue; + check_cb(child, elt, elts, i, len); + ++elt; + } +} + +var display = document.getElementById("display"); + +function run_series(check_cb) { + var display = document.getElementById("display"); + // Use a new parent node every time since the optimizations cause + // bits to be set (permanently) on the parent. + var p = document.createElement("p"); + display.appendChild(p); + p.innerHTML = "x<span></span><span></span>"; + + check_children(p, check_cb); + var text = p.removeChild(p.childNodes[0]); + check_children(p, check_cb); + var span = p.removeChild(p.childNodes[0]); + check_children(p, check_cb); + p.appendChild(span); + check_children(p, check_cb); + p.removeChild(span); + check_children(p, check_cb); + p.insertBefore(span, p.childNodes[0]); + check_children(p, check_cb); + p.removeChild(span); + check_children(p, check_cb); + p.insertBefore(span, null); + check_children(p, check_cb); + p.appendChild(document.createElement("span")); + check_children(p, check_cb); + p.insertBefore(document.createElement("span"), p.childNodes[2]); + check_children(p, check_cb); + p.appendChild(text); + check_children(p, check_cb); + + display.removeChild(p); +} + +var style = document.createElement("style"); +style.setAttribute("type", "text/css"); +var styleText = document.createTextNode(""); +style.appendChild(styleText); +document.getElementsByTagName("head")[0].appendChild(style); + +styleText.data = "span:first-child { background: lime; }"; +run_series(function(child, elt, elts, node, nodes) { + is(cs(child).backgroundColor, (elt == 0) ? LIME : WHITE, + "child " + node + " should " + ((elt == 0) ? "" : "NOT ") + + " match :first-child"); + }); + +styleText.data = "span:last-child { color: green; }"; +run_series(function(child, elt, elts, node, nodes) { + is(cs(child).color, (elt == elts - 1) ? GREEN : BLACK, + "child " + node + " should " + ((elt == elts - 1) ? "" : "NOT ") + + " match :last-child"); + }); + +styleText.data = "span:only-child { border: medium solid green; }"; +run_series(function(child, elt, elts, node, nodes) { + is(cs(child).borderTopColor, (elts == 1) ? GREEN : BLACK, + "child " + node + " should " + ((elts == 1) ? "" : "NOT ") + + " match :only-child"); + }); + +styleText.data = "span:-moz-first-node { text-decoration: underline; }"; +run_series(function(child, elt, elts, node, nodes) { + is(cs(child).textDecoration, (node == 0) ? "underline" : "none", + "child " + node + " should " + ((node == 0) ? "" : "NOT ") + + " match :-moz-first-node"); + }); + +styleText.data = "span:-moz-last-node { visibility: hidden; }"; +run_series(function(child, elt, elts, node, nodes) { + is(cs(child).visibility, (node == nodes - 1) ? "hidden" : "visible", + "child " + node + " should " + ((node == nodes - 1) ? "" : "NOT ") + + " match :-moz-last-node"); + }); + +styleText.data = "span:nth-child(1) { background: lime; }"; +run_series(function(child, elt, elts, node, nodes) { + var matches = elt == 0; + is(cs(child).backgroundColor, matches ? LIME : WHITE, + "child " + node + " should " + (matches ? "" : "NOT ") + + " match " + styleText.data); + }); + +styleText.data = "span:nth-last-child(0n+2) { color: green; }"; +run_series(function(child, elt, elts, node, nodes) { + var matches = (elt == elts - 2); + is(cs(child).color, matches ? GREEN : BLACK, + "child " + node + " should " + (matches ? "" : "NOT ") + + " match " + styleText.data); + }); + +styleText.data = "span:nth-of-type(2n+3) { color: green; }"; +run_series(function(child, elt, elts, node, nodes) { + var nidx = elt + 1; + var matches = nidx % 2 == 1 && nidx >= 3; + is(cs(child).color, matches ? GREEN : BLACK, + "child " + node + " should " + (matches ? "" : "NOT ") + + " match " + styleText.data); + }); + +styleText.data = "span:nth-last-of-type(-2n+5) { color: green; }"; +run_series(function(child, elt, elts, node, nodes) { + var nlidx = elts - elt; + var matches = nlidx % 2 == 1 && nlidx <= 5; + is(cs(child).color, matches ? GREEN : BLACK, + "child " + node + " should " + (matches ? "" : "NOT ") + + " match " + styleText.data); + }); + +styleText.data = "span:first-of-type { color: green; }"; +run_series(function(child, elt, elts, node, nodes) { + var matches = (elt == 0); + is(cs(child).color, matches ? GREEN : BLACK, + "child " + node + " should " + (matches ? "" : "NOT ") + + " match " + styleText.data); + }); + +styleText.data = "span:last-of-type { color: green; }"; +run_series(function(child, elt, elts, node, nodes) { + var matches = (elt == elts - 1); + is(cs(child).color, matches ? GREEN : BLACK, + "child " + node + " should " + (matches ? "" : "NOT ") + + " match " + styleText.data); + }); + +styleText.data = "span:only-of-type { color: green; }"; +run_series(function(child, elt, elts, node, nodes) { + var matches = elts == 1; + is(cs(child).color, matches ? GREEN : BLACK, + "child " + node + " should " + (matches ? "" : "NOT ") + + " match " + styleText.data); + }); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug74880.html b/layout/style/test/test_bug74880.html new file mode 100644 index 000000000..9641f5944 --- /dev/null +++ b/layout/style/test/test_bug74880.html @@ -0,0 +1,125 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=74880 +--> +<head> + <title>Test for Bug 74880</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + /* so that computed values for other border properties work right */ + #display { border-style: solid; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=74880">Mozilla Bug 74880</a> +<div style="margin: 1px 2px 3px 4px; border-width: 5px 6px 7px 8px; border-style: dotted dashed solid double; border-color: blue fuchsia green orange; padding: 9px 10px 11px 12px"> +<p id="display"></p> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 74880 **/ + +var gProps = [ + [ "margin-left", "margin-right", "margin-inline-start", "margin-inline-end" ], + [ "padding-left", "padding-right", "padding-inline-start", "padding-inline-end" ], + [ "border-left-color", "border-right-color", "border-inline-start-color", "border-inline-end-color" ], + [ "border-left-style", "border-right-style", "border-inline-start-style", "border-inline-end-style" ], + [ "border-left-width", "border-right-width", "border-inline-start-width", "border-inline-end-width" ], +]; + +var gLengthValues = [ "inherit", "initial", "2px", "1em" ]; +var gColorValues = [ "inherit", "initial", "currentColor", "green" ]; +var gStyleValues = [ "inherit", "initial", "double", "dashed" ]; + +if (SpecialPowers.getBoolPref("layout.css.unset-value.enabled")) { + gLengthValues.push("unset"); + gColorValues.push("unset"); + gStyleValues.push("unset"); +} + +function values_for(set) { + var values; + if (set[0].match(/style$/)) + values = gStyleValues; + else if (set[0].match(/color$/)) + values = gColorValues; + else + values = gLengthValues; + return values; +} + +var e = document.getElementById("display"); +var s = e.style; +var c = window.getComputedStyle(e, ""); + +for (var set of gProps) { + var values = values_for(set); + for (var val of values) { + function check(dir, plogical, pvisual) { + var v0 = c.getPropertyValue(pvisual); + s.setProperty("direction", dir, ""); + s.setProperty(pvisual, val, ""); + var v1 = c.getPropertyValue(pvisual); + if (val != "initial" && val != "unset" && val != "currentColor") + isnot(v1, v0, "setProperty set the property " + pvisual + ": " + val); + s.removeProperty(pvisual); + is(c.getPropertyValue(pvisual), v0, "removeProperty worked for " + pvisual); + s.setProperty(plogical, val, "") + var v2 = c.getPropertyValue(pvisual); + is(v2, v1, "the logical property " + plogical + ": " + val + " showed up in the right place"); + s.removeProperty(plogical); + is(c.getPropertyValue(pvisual), v0, "removeProperty worked for " + plogical); + + s.removeProperty("direction"); + } + + check("ltr", set[2], set[0]); + check("ltr", set[3], set[1]); + check("rtl", set[2], set[1]); + check("rtl", set[3], set[0]); + } + + function check_cascading(dir, plogical, pvisual) { + var dirstr = "direction: " + dir + ";"; + e.setAttribute("style", dirstr + pvisual + ":" + values[2]); + var v2 = c.getPropertyValue(pvisual); + e.setAttribute("style", dirstr + pvisual + ":" + values[3]); + var v3 = c.getPropertyValue(pvisual); + isnot(v2, v3, "values should produce different computed values"); + + var desc = ["cascading for", pvisual, "and", plogical, "with direction", dir].join(" "); + e.setAttribute("style", dirstr + pvisual + ":" + values[3] + ";" + + plogical + ":" + values[2]); + is(c.getPropertyValue(pvisual), v2, desc); + e.setAttribute("style", dirstr + plogical + ":" + values[3] + ";" + + pvisual + ":" + values[2]); + is(c.getPropertyValue(pvisual), v2, desc); + e.setAttribute("style", dirstr + pvisual + ":" + values[2] + ";" + + plogical + ":" + values[3]); + is(c.getPropertyValue(pvisual), v3, desc); + e.setAttribute("style", dirstr + plogical + ":" + values[2] + ";" + + pvisual + ":" + values[3]); + is(c.getPropertyValue(pvisual), v3, desc); + e.removeAttribute("style"); + } + + check_cascading("ltr", set[2], set[0]); + check_cascading("ltr", set[3], set[1]); + check_cascading("rtl", set[2], set[1]); + check_cascading("rtl", set[3], set[0]); +} + + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_bug765590.html b/layout/style/test/test_bug765590.html new file mode 100644 index 000000000..1e2445804 --- /dev/null +++ b/layout/style/test/test_bug765590.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=765590 +--> +<head> + <title>Test for Bug 765590</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + @namespace svg "http://www.w3.org/2000/svg"; + </style> +</head> +<body> + <script> + var styleElement = document.getElementsByTagName("style")[0] + var rule = styleElement.sheet.cssRules[0]; + is(rule.type, 10, "rule type should be equal 10") + is(CSSRule.NAMESPACE_RULE, 10, "NAMESPACE_RULE should be equal to 10") + </script> +</body> diff --git a/layout/style/test/test_bug771043.html b/layout/style/test/test_bug771043.html new file mode 100644 index 000000000..ca9d0cfb6 --- /dev/null +++ b/layout/style/test/test_bug771043.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=771043 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 771043</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 771043 **/ + var expectedValue; + var callCount = 0; + var storedHeight; + function callback(arg) { + ++callCount; + is(arg.matches, expectedValue, + "Should have the right value on call #" + callCount + " to the callback"); + SimpleTest.executeSoon(tests.shift()); + } + + function flushLayout() { + storedHeight = document.querySelector("iframe").offsetHeight; + } + + function setHeight(height) { + var ifr = document.querySelector("iframe"); + ifr.style.height = height + "px"; + flushLayout(); + } + + SimpleTest.waitForExplicitFinish(); + + var tests = [ + () => { expectedValue = true; setHeight(50); }, + () => { expectedValue = false; setHeight(200); }, + () => { + var ifr = document.querySelector("iframe"); + ifr.style.display = "none"; + flushLayout(); + ifr.style.display = ""; + expectedValue = true; + setHeight(50); + }, + () => { expectedValue = false; setHeight(200); }, + SimpleTest.finish.bind(SimpleTest) + ]; + + addLoadEvent(function() { + var mql = frames[0].matchMedia("(orientation: landscape)"); + mql.addListener(callback); + + tests.shift()(); + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=771043">Mozilla Bug 771043</a> +<!-- Important: the iframe needs to be displayed --> +<p id="display"><iframe style="width: 100px; height: 200px"</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug795520.html b/layout/style/test/test_bug795520.html new file mode 100644 index 000000000..98e9b7120 --- /dev/null +++ b/layout/style/test/test_bug795520.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=795520 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 795520</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795520">Mozilla Bug 795520</a> +<p id="display"> + <iframe id="f" style="display:none"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 795520 **/ +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + doc = $("f").contentDocument; + $("f").style.display = ""; + isnot(doc.defaultView.getComputedStyle(doc.body), null, + "Should have computed style here"); + SimpleTest.finish(); +}); + + + + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug798567.html b/layout/style/test/test_bug798567.html new file mode 100644 index 000000000..b8e5dd0ea --- /dev/null +++ b/layout/style/test/test_bug798567.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=798567 +--> +<head> + <title>Test for Bug 798567</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <div id="some_div" style="opacity: 0.5">bar</div> + <div style="cursor: crosshair" id="div_cursor">foobar</div> + <script type="application/javascript"> + var elm = document.getElementById("some_div"); + var cs = getComputedStyle(elm); + var cssValue = cs.getPropertyCSSValue("opacity"); + is(cssValue.getFloatValue(CSSPrimitiveValue.CSS_NUMBER), 0.5, "wrong opacity of some_div"); + + elm = document.getElementById("div_cursor"); + cs = getComputedStyle(elm); + cssValue = cs.getPropertyCSSValue("cursor"); + ok(cssValue[0], "element with set cursor should have a a computed style"); + </script> +</body> +</html> diff --git a/layout/style/test/test_bug798843_pref.html b/layout/style/test/test_bug798843_pref.html new file mode 100644 index 000000000..f403741c1 --- /dev/null +++ b/layout/style/test/test_bug798843_pref.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<!-- + Make sure that the SVG glyph context-* values are not considered real values + when gfx.font_rendering.opentype_svg.enabled is pref'ed off. +--> +<head> + <title>Test that SVG glyph context-* values can be pref'ed off</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<body> + +<script> + +var props = { + "fill" : "context-stroke none", + "stroke" : "context-fill none", + "fillOpacity" : "context-stroke-opacity", + "strokeOpacity" : "context-fill-opacity", + "strokeDasharray" : "context-value", + "strokeDashoffset" : "context-value", + "strokeWidth" : "context-value" +}; + +function testDisabled() { + for (var p in props) { + document.body.style[p] = props[p]; + is(document.body.style[p], "", p + " not settable to " + props[p]); + document.body.style[p] = ""; + } + SimpleTest.finish(); +} + +function testEnabled() { + for (var p in props) { + document.body.style[p] = props[p]; + is(document.body.style[p], props[p], p + " settable to " + props[p]); + document.body.style[p] = ""; + } + + SpecialPowers.pushPrefEnv( + {'set': [['gfx.font_rendering.opentype_svg.enabled', false]]}, + testDisabled + ); +} + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + {'set': [['gfx.font_rendering.opentype_svg.enabled', true]]}, + testEnabled +); + +</script> + +</body> +</html> diff --git a/layout/style/test/test_bug829816.html b/layout/style/test/test_bug829816.html new file mode 100644 index 000000000..df6100e29 --- /dev/null +++ b/layout/style/test/test_bug829816.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=829816 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 829816</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <style type="text/css"> + b { content: "\0"; counter-reset: \0 } + b { content: "\00"; counter-reset: \00 } + b { content: "\000"; counter-reset: \000 } + b { content: "\0000"; counter-reset: \0000 } + b { content: "\00000"; counter-reset: \00000 } + b { content: "\000000"; counter-reset: \000000 } + </style> + + <!-- U+0000 characters in <style> would be replaced by the HTML parser --> + <link rel="stylesheet" type="text/css" href="file_bug829816.css"/> + + <script type="application/javascript"> + + /** Test for Bug 829816 **/ + var ss = document.styleSheets[1]; + + for (var i = 0; i < 6; i++) { + is(ss.cssRules[i].style.content, "\"\uFFFD\"", + "\\0 in strings should be converted to U+FFFD"); + is(ss.cssRules[i].style.counterReset, "\uFFFD", + "\\0 in identifiers should be converted to U+FFFD"); + } + + is(document.styleSheets[2].cssRules[0].style.content, "\"\uFFFD\"", + "U+0000 in strings should be converted to U+FFFD"); + is(document.styleSheets[2].cssRules[0].style.counterReset, "\uFFFD", + "U+0000 in identifiers should be converted to U+FFFD"); + is(document.styleSheets[2].cssRules[1].style.content, "\"\uFFFD\"", + "U+0000 in strings should be converted to U+FFFD"); + is(document.styleSheets[2].cssRules[1].style.counterReset, "\uFFFD", + "U+0000 in identifiers should be converted to U+FFFD"); + + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=829816">Mozilla Bug 829816</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug874919.html b/layout/style/test/test_bug874919.html new file mode 100644 index 000000000..df9facd74 --- /dev/null +++ b/layout/style/test/test_bug874919.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=874919 +--> +<head> + <title>Test for Bug 874919</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=874919">Mozilla Bug 874919</a> +<p id="display"></p> +<div id="content" style="width: 150px"> + <svg id="outer_SVG" style="display: inline; width: 100%"> + <circle cx="120" cy="120" r="120" fill="blue"></circle> + <svg id="inner_SVG"> + <circle id="circle" cx="120" cy="120" r="120" fill="red"></circle> + </svg> + </svg> +</div> +<pre id="test"> + +<script type="text/javascript"> + + var shouldUseComputed = ["inner_SVG"] + var shouldUseUsed = ["outer_SVG"] + + shouldUseUsed.forEach(function(elemId) { + + var style = window.getComputedStyle(document.getElementById(elemId)); + + ok(style.width.match(/^\d+px$/), + "Inline Outer SVG element's getComputedStyle.width should be used value. "); + + ok(style.height.match(/^\d+px$/), + "Inline Outer SVG element's getComputedStyle.height should be used value."); + }); + + shouldUseComputed.forEach(function(elemId) { + var style = window.getComputedStyle(document.getElementById(elemId)); + + // Computed value should match either the percentage used, or "auto" in the case of the inner SVG element. + ok(style.width.match(/^\d+%$|^auto$/), + "Inline inner SVG element's getComputedStyle.width should be computed value. " + style.width); + + ok(style.height.match(/^\d+%$|^auto$/), + "Inline inner SVG element's getComputedStyle.height should be computed value. " + style.height); + }); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html b/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html new file mode 100644 index 000000000..b8896999e --- /dev/null +++ b/layout/style/test/test_bug887741_at-rules_in_declaration_lists.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=887741 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 887741: at-rules in declaration lists</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <style> + #foo { + color: red; + @invalid-rule { + ignored: ignored; + } + /* No semicolon */ + color: green; + } + @page { + margin-top: 0; + @bottom-center { + content: counter(page); + } + /* No semicolon */ + margin-top: 5cm; + } + @keyframes dummy-animation { + 12% { + color: red; + @invalid-rule {} + /* No semicolon */ + color: green; + } + } + /* TODO: other at-rules that use declaration syntax? */ + </style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887741">Mozilla Bug 887741</a> +<p id="display"></p> +<div id="content" style="display: none; color: red; + @invalid-rule{} /* No semicolon */ color: green;"> + +</div> +<pre id="test"> + <script type="application/javascript"> + + /** Test for Bug 887741 **/ + + var style = document.getElementById('content').style; + is(style.display, 'none', 'Sanity check: we have the right element'); + is(style.color, 'green', 'Support at-rules in style attributes'); + + style.cssText = 'display: none; color: red; @invalid-rule{} /* No semicolon */ color: lime;'; + is(style.color, 'lime', 'Support at-rules in CSSStyleDeclaration.cssText'); + + var rules = document.styleSheets[0].cssRules; + var style_rule = rules[0]; + is(style_rule.selectorText, '#foo', 'Sanity check: we have the right style rule'); + is(style_rule.style.color, 'green', 'Support at-rules in style rules'); + + var page_rule = rules[1]; + is(page_rule.type, page_rule.PAGE_RULE, 'Sanity check: we have the right style rule'); + is(page_rule.style.marginTop, '5cm', 'Support at-rules in @page rules'); + + var keyframe_rule = rules[2].cssRules[0]; + is(keyframe_rule.keyText, '12%', 'Sanity check: we have the right keyframe rule'); + is(keyframe_rule.style.color, 'green', 'Support at-rules in keyframe rules') + + </script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug892929.html b/layout/style/test/test_bug892929.html new file mode 100644 index 000000000..a67db56ee --- /dev/null +++ b/layout/style/test/test_bug892929.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset=utf-8> + <title>Bug 892929 test</title> + <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"> + <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#om-fontfeaturevalues" /> + <meta name="assert" content="window.CSSFontFeatureValuesRule should appear by default" /> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <style type="text/css"> + </style> +</head> +<body> +<div id="log"></div> +<pre id="display"></pre> + +<script type="text/javascript"> + +function testCSSFontFeatureValuesRuleOM() { + var s = document.documentElement.style; + var cs = window.getComputedStyle(document.documentElement); + + var hasFFVRule = "CSSFontFeatureValuesRule" in window; + var hasStyleAlternates = "fontVariantAlternates" in s; + var hasCompStyleAlternates = "fontVariantAlternates" in cs; + + test(function() { + assert_equals(hasFFVRule, + hasStyleAlternates, + "style.fontVariantAlternates " + + (hasStyleAlternates ? "available" : "not available") + + " but " + + "window.CSSFontFeatureValuesRule " + + (hasFFVRule ? "available" : "not available") + + " - "); + }, "style.fontVariantAlternates availability matches window.CSSFontFeatureValuesRule availability"); + + test(function() { + assert_equals(hasFFVRule, + hasCompStyleAlternates, + "computedStyle.fontVariantAlternates " + + (hasCompStyleAlternates ? "available" : "not available") + + " but " + + "window.CSSFontFeatureValuesRule " + + (hasFFVRule ? "available" : "not available") + + " - "); + }, "computedStyle.fontVariantAlternates availability matches window.CSSFontFeatureValuesRule availability"); + + // if window.CSSFontFeatureValuesRule isn't around, neither should any of the font feature props + fontFeatureProps = [ "fontKerning", "fontVariantAlternates", "fontVariantCaps", "fontVariantEastAsian", + "fontVariantLigatures", "fontVariantNumeric", "fontVariantPosition", "fontSynthesis", + "fontFeatureSettings", "fontLanguageOverride" ]; + + if (!hasFFVRule) { + var i; + for (i = 0; i < fontFeatureProps.length; i++) { + var prop = fontFeatureProps[i]; + test(function() { + assert_true(!(prop in s), "window.CSSFontFeatureValuesRule not available but style." + prop + " is available - "); + }, "style." + prop + " availability"); + test(function() { + assert_true(!(prop in cs), "window.CSSFontFeatureValuesRule not available but computedStyle." + prop + " is available - "); + }, "computedStyle." + prop + " availability"); + } + } + +} + +testCSSFontFeatureValuesRuleOM(); + +</script> +</body> +</html> diff --git a/layout/style/test/test_bug98997.html b/layout/style/test/test_bug98997.html new file mode 100644 index 000000000..c7104c7c4 --- /dev/null +++ b/layout/style/test/test_bug98997.html @@ -0,0 +1,144 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=98997 +--> +<head> + <title>Test for Bug 98997</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + /* + * This test does NOT test any of the cases where :empty and + * :-moz-only-whitespace differ. We should probably have some tests + * for that as well. + */ + div.test { width: 200px; height: 30px; margin: 5px 0; } + div.test.to, div.test.from:empty { background: orange; } + div.test.to:empty, div.test.from { background: green; } + div.test.to, div.test.from:-moz-only-whitespace { color: maroon; } + div.test.to:-moz-only-whitespace, div.test.from { color: navy; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=98997">Mozilla Bug 98997</a> +<div id="display"> +<div class="test to" onclick="testReplaceChild(this, '')">x</div> +<div class="test to" onclick="testReplaceChild(this, '')"><span>x</span></div> +<div class="test to" onclick="testReplaceChild(this, '')">x<!-- comment --></div> +<div class="test to" onclick="testRemoveChild(this)">x</div> +<div class="test to" onclick="testRemoveChild(this)"><span>x</span></div> +<div class="test to" onclick="testRemoveChild(this)">x<!-- comment --></div> +<div class="test to" onclick="testChangeData(this, '')">x</div> +<div class="test to" onclick="testChangeData(this, '')">x<!-- comment --></div> +<div class="test to" onclick="testDeleteData(this)">x</div> +<div class="test to" onclick="testDeleteData(this)">x<!-- comment --></div> +<div class="test to" onclick="testReplaceData(this, '')">x</div> +<div class="test to" onclick="testReplaceData(this, '')">x<!-- comment --></div> + +<div class="test from makeemptytext" onclick="testReplaceChild(this, 'x')"></div> +<div class="test from makeemptytext" onclick="testReplaceChild(this, 'x')"><!-- comment --></div> +<div class="test from" onclick="testReplaceChild(this, 'x')"><!-- comment --></div> +<div class="test from" onclick="testInsertBefore(this, 'x')"></div> +<div class="test from" onclick="testInsertBefore(this, 'x')"><!-- comment --></div> +<div class="test from" onclick="testAppendChild(this, 'x')"></div> +<div class="test from" onclick="testAppendChild(this, 'x')"><!-- comment --></div> +<div class="test from makeemptytext" onclick="testChangeData(this, 'x')"></div> +<div class="test from makeemptytext" onclick="testChangeData(this, 'x')"><!-- comment --></div> +<div class="test from makeemptytext" onclick="testAppendData(this, 'x')"></div> +<div class="test from makeemptytext" onclick="testAppendData(this, 'x')"><!-- comment --></div> +<div class="test from makeemptytext" onclick="testReplaceData(this, 'x')"></div> +<div class="test from makeemptytext" onclick="testReplaceData(this, 'x')"><!-- comment --></div> +</div> + +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 98997 **/ + +function testInsertBefore(elt, text) { + elt.insertBefore(document.createTextNode(text), elt.firstChild); +} + +function testAppendChild(elt, text) { + elt.appendChild(document.createTextNode(text)); +} + +function testReplaceChild(elt, text) { + elt.replaceChild(document.createTextNode(text), elt.firstChild); +} + +function testRemoveChild(elt) { + elt.removeChild(elt.firstChild); +} + +function testChangeData(elt, text) { + elt.firstChild.data = text; +} + +function testAppendData(elt, text) { + elt.firstChild.appendData(text); +} + +function testDeleteData(elt) { + elt.firstChild.deleteData(0, elt.firstChild.length); +} + +function testReplaceData(elt, text) { + elt.firstChild.replaceData(0, elt.firstChild.length, text); +} + +var cnodes = document.getElementById("display").childNodes; +var divs = []; +var i; +for (i = 0; i < cnodes.length; ++i) { + if (cnodes[i].nodeName == "DIV") + divs.push(cnodes[i]); +} + +for (i in divs) { + var div = divs[i]; + if (div.className.match(/makeemptytext/)) + div.insertBefore(document.createTextNode(""), div.firstChild); +} + +const ORANGE = "rgb(255, 165, 0)"; +const MAROON = "rgb(128, 0, 0)"; +const GREEN = "rgb(0, 128, 0)"; +const NAVY = "rgb(0, 0, 128)"; + +function color(div) { + return getComputedStyle(div, "").color; +} +function bg(div) { + return getComputedStyle(div, "").backgroundColor; +} + +for (i in divs) { + var div = divs[i]; + is(bg(div), ORANGE, "should be orange"); + is(color(div), MAROON, "should be maroon"); +} + +for (i in divs) { + var div = divs[i]; + var e = document.createEvent("MouseEvents"); + e.initEvent("click", true, true); + div.dispatchEvent(e); +} + +for (i in divs) { + var div = divs[i]; + is(bg(div), GREEN, "should be green"); + is(color(div), NAVY, "should be navy"); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_cascade.html b/layout/style/test/test_cascade.html new file mode 100644 index 000000000..0a5d27a8b --- /dev/null +++ b/layout/style/test/test_cascade.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<!-- vim: set shiftwidth=4 tabstop=8 autoindent expandtab: --> +<!-- 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/. --> +<html> +<head> + <title>Test for Author style sheet aspects of CSS cascading</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + </style> +</head> +<body id="thebody"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<div class="content_class" id="content" style="position:relative"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Author style sheet aspects of CSS cascading **/ + +var style_element = document.createElement("style"); +var style_contents = document.createTextNode(""); +style_element.appendChild(style_contents); +document.getElementsByTagName("head")[0].appendChild(style_element); + +var div = document.getElementById("content"); +var cs = window.getComputedStyle(div, ""); +var zindex = 0; + +/** + * Given the selectors |sel1| and |sel2|, in that order (the "order" + * aspect of the cascade), with declarations that are !important if + * |imp1|/|imp2| are true, assert that the one that wins in the + * cascading order is given by |winning| (which must be either 1 or 2). + */ +function do_test(sel1, imp1, sel2, imp2, winning) { + var ind1 = ++zindex; + var ind2 = ++zindex; + style_contents.data = + sel1 + " { z-index: " + ind1 + (imp1 ? "!important" :"") + " } " + + sel2 + " { z-index: " + ind2 + (imp2 ? "!important" :"") + " } "; + var result = cs.zIndex; + is(result, String((winning == 1) ? ind1 : ind2), + "cascading of " + style_contents.data); +} + +// Test order, and order combined with !important +do_test("div", false, "div", false, 2); +do_test("div", false, "div", true, 2); +do_test("div", true, "div", false, 1); +do_test("div", true, "div", true, 2); + +// Test specificity on a single element +do_test("div", false, "div.content_class", false, 2); +do_test("div.content_class", false, "div", false, 1); + +// Test specificity across elements +do_test("body#thebody div", false, "body div.content_class", false, 1); +do_test("body div.content_class", false, "body#thebody div", false, 2); + +// Test specificity combined with !important +do_test("div.content_class", false, "div", false, 1); +do_test("div.content_class", true, "div", false, 1); +do_test("div.content_class", false, "div", true, 2); +do_test("div.content_class", true, "div", true, 1); + +function do_test_greater(sel1, sel2) { + do_test(sel1, false, sel2, false, 1); + do_test(sel2, false, sel1, false, 2); +} + +function do_test_equal(sel1, sel2) { + do_test(sel1, false, sel2, false, 2); + do_test(sel2, false, sel1, false, 2); +} + +// Test specificity of contents of :not() +do_test_equal("div.content_class", "div:not(.wrong_class)"); +do_test_greater("div.content_class.content_class", "div.content_class"); +do_test_greater("div.content_class", "div"); +do_test_greater("div:not(.wrong_class)", "div"); +do_test_greater("div:not(.wrong_class):not(.wrong_class)", + "div:not(.wrong_class)"); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_ch_ex_no_infloops.html b/layout/style/test/test_ch_ex_no_infloops.html new file mode 100644 index 000000000..e8684e935 --- /dev/null +++ b/layout/style/test/test_ch_ex_no_infloops.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=678671 +--> +<head> + <title>Test for Bug 678671</title> + <script type="application/javascript" src="/MochiKit/packed.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=678671">Mozilla Bug 678671</a> +<p id="display"></p> +<div id="content"></div> +<script type="application/javascript"> + +/** Test for Bug 678671 **/ + +/** + * Test 'ex' and 'ch' units in every place we possible can to make + * sure they don't cause an infinite loop. + */ + +var content = document.getElementById("content"); +var cs = getComputedStyle(content, ""); + +for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + function test_val(v) { + content.style.setProperty(prop, v, ""); + isnot(get_computed_value(cs, prop), "", + "Setting '" + prop + "' to '" + v + "' should not cause infinite loop"); + } + test_val('3ex'); + test_val('2ch'); + function test_replaced_values(value_list) { + // For each item in value_list, if it looks like it has a dimension + // in it, replace those dimensions with 3ex and 2ch and test it. + for (var i = 0; i < value_list.length; ++i) { + var value = value_list[i]; + function try_replace(withval) { + var rep = value.replace(/[0-9.]+[a-zA-Z]+/g, withval) + if (rep != value) { + test_val(rep); + } + } + try_replace('3ex'); + try_replace('2ch'); + } + } + test_replaced_values(info.initial_values); + test_replaced_values(info.other_values); + content.style.removeProperty(prop); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_change_hint_optimizations.html b/layout/style/test/test_change_hint_optimizations.html new file mode 100644 index 000000000..7de8d2431 --- /dev/null +++ b/layout/style/test/test_change_hint_optimizations.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for style change hint optimizations</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + + function runTests() { + + /** Test for Bug 1251075 **/ + function test_bug1251075_div(id) { + var utils = SpecialPowers.DOMWindowUtils; + + var div = document.getElementById(id); + div.style.display = ""; + + var description = div.style.cssText; + + div.firstElementChild.offsetTop; + var constructedBefore = utils.framesConstructed; + + div.style.transform = "translateX(10px)"; + div.firstElementChild.offsetTop; + is(utils.framesConstructed, constructedBefore, + "adding a transform style to an element with " + description + + " should not cause frame reconstruction even when the element " + + "has absolutely positioned descendants"); + + div.style.display = "none"; + } + + test_bug1251075_div("bug1251075_a"); + test_bug1251075_div("bug1251075_b"); + + + SimpleTest.finish(); + } + + </script> +</head> +<body onload="runTests()"> +<div id="bug1251075_a" style="will-change: transform; display:none"> + <div style="position: absolute; top: 0; left: 0;"></div> + <div style="position: fixed; top: 0; left: 0;"></div> +</div> +<div id="bug1251075_b" style="filter: blur(3px); display:none"> + <div style="position: absolute; top: 0; left: 0;"></div> + <div style="position: fixed; top: 0; left: 0;"></div> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_clip-path_polygon.html b/layout/style/test/test_clip-path_polygon.html new file mode 100644 index 000000000..9f25accd1 --- /dev/null +++ b/layout/style/test/test_clip-path_polygon.html @@ -0,0 +1,41 @@ +<html> +<head> +<style> +body {padding: 0;margin:0;} +div { + width: 200px; + height: 200px; + position: fixed; + top: 50px; + left: 50px; + margin: 50; + padding: 50; + border: 50px solid red; + transform-origin: 0 0; + transform: translate(50px, 50px) scale(0.5); + background-color: green; + clip-path: polygon(0 0, 200px 0, 0 200px) content-box;*/ +} +</style> +<title>clip-path with polygon() hit test</title> +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<div id="a"></div> +<p style="margin-top: 110px"> +<script> +var a = document.getElementById("a"); +if (SpecialPowers.getBoolPref("layout.css.clip-path-shapes.enabled")) { + isnot(a, document.elementFromPoint(199, 199), "a shouldn't be found"); + isnot(a, document.elementFromPoint(199, 250), "a shouldn't be found"); + isnot(a, document.elementFromPoint(250, 199), "a shouldn't be found"); + isnot(a, document.elementFromPoint(255, 255), "a shouldn't be found"); + isnot(a, document.elementFromPoint(301, 200), "a shouldn't be found"); + isnot(a, document.elementFromPoint(200, 301), "a shouldn't be found"); +} +is(a, document.elementFromPoint(200, 200), "a should be found"); +is(a, document.elementFromPoint(299, 200), "a should be found"); +is(a, document.elementFromPoint(200, 299), "a should be found"); +is(a, document.elementFromPoint(250, 250), "a should be found"); +</script> +</html>
\ No newline at end of file diff --git a/layout/style/test/test_compute_data_with_start_struct.html b/layout/style/test/test_compute_data_with_start_struct.html new file mode 100644 index 000000000..fab111e34 --- /dev/null +++ b/layout/style/test/test_compute_data_with_start_struct.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for correct handling of aStartStruct parameter to nsRuleNode::Compute*Data</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <style type="text/css" id="stylesheet"></style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=216456">Mozilla Bug 216456</a> +<p id="display"> + <span id="base"></span> + <span id="test"></span> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** + * The purpose of this test is to test that nsRuleNode::Compute*Data + * functions are written correctly. In particular, in these functions, + * when the specified value of a property has unit eCSSUnit_Null, + * touching the computed data is forbidden. This is because we + * sometimes stop walking up the rule tree when we find computed data + * for an initial subsequence of our rules (i.e., an ancestor rule node) + * that we can use as a starting point (aStartStruct) for the + * computation for the current rule node. + * + * If one of these tests fails, you should look for a case where the + * property's code in nsRuleNode::Compute*Data touches the computed + * value when the specified value has eCSSUnit_Null, and fix it. + * + * The test works by maintaining one style rule that has every CSS + * property specified, and a second style rule that has different values + * for every property, an element that matches only the first rule (so + * that we'll have a cached struct), and *later* an element that matches + * both rules (for whose computation we'll find the struct cached from + * the first element). (It does this twice, once with the overriding in + * each direction, because one of the cases might match the incorrect + * overwriting.) Then, in the second style rule, it unsets each + * property, one at a time, and checks that the computation works + * correctly. (The reason to want every property set is to hit a case + * where we can store the data in the rule tree... though this isn't + * guaranteed.) + */ + +function xfail_computecheck(prop, roundnum) { + return false; +} + +function xfail_test(prop, roundnum) { + return false; +} + +var gStyleSheet = document.getElementById("stylesheet").sheet; +var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#base, #test {}", gStyleSheet.cssRules.length)]; +var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#test {}", gStyleSheet.cssRules.length)]; + +var gBase = getComputedStyle(document.getElementById("base"), ""); +var gTest = getComputedStyle(document.getElementById("test"), ""); + +function round(lower_set, higher_set, roundnum) { + + for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (info.subproperties || info.get_computed) + continue; + gRule1.style.setProperty(prop, info[lower_set][0], ""); + gRule2.style.setProperty(prop, info[higher_set][0], ""); + } + + for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (info.subproperties || info.get_computed) + continue; + + if ("prerequisites" in info) { + for (var prereq in info.prerequisites) { + gRule2.style.setProperty(prereq, info.prerequisites[prereq], ""); + } + } + + gBase.getPropertyValue(prop); + var higher_set_val = gTest.getPropertyValue(prop); + gRule2.style.setProperty(prop, info[lower_set][0], ""); + var lower_set_val = gTest.getPropertyValue(prop); + (xfail_computecheck(prop, roundnum) ? todo_isnot : isnot)(higher_set_val, lower_set_val, "initial and other values of " + prop + " are different"); + gRule2.style.removeProperty(prop); + (xfail_test(prop, roundnum) ? todo_is : is)(gTest.getPropertyValue(prop), lower_set_val, prop + " is not touched when its value comes from aStartStruct"); + + gRule2.style.setProperty(prop, info[higher_set][0], ""); + if ("prerequisites" in info) { + for (var prereq in info.prerequisites) { + gRule2.style.setProperty(prereq, gCSSProperties[prereq][higher_set][0], ""); + } + } + } +} + +round("other_values", "initial_values", 1); +round("initial_values", "other_values", 2); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_computed_style.html b/layout/style/test/test_computed_style.html new file mode 100644 index 000000000..938a3fcf5 --- /dev/null +++ b/layout/style/test/test_computed_style.html @@ -0,0 +1,413 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for miscellaneous computed style issues</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for miscellaneous computed style issues **/ + +var frame_container = document.getElementById("display"); +var noframe_container = document.getElementById("content"); + +(function test_bug_595650() { + // Test handling of horizontal and vertical percentages for border-radius + // and -moz-outline-radius. + var p = document.createElement("p"); + p.setAttribute("style", "width: 256px; height: 128px"); + p.style.borderTopLeftRadius = "1.5625%"; /* 1/64 == 4px 2px */ + p.style.borderTopRightRadius = "5px"; + p.style.borderBottomRightRadius = "5px 3px"; + p.style.borderBottomLeftRadius = "1.5625% 3.125%" /* 1/64 1/32 == 4px 4px */ + p.style.MozOutlineRadiusTopleft = "1.5625%"; /* 1/64 == 4px 2px */ + p.style.MozOutlineRadiusTopright = "5px"; + p.style.MozOutlineRadiusBottomright = "5px 3px"; + p.style.MozOutlineRadiusBottomleft = "1.5625% 3.125%" /* 1/64 1/32 == 4px 4px */ + var cs = getComputedStyle(p, ""); + + frame_container.appendChild(p); + is(cs.borderTopLeftRadius, "1.5625%", + "computed value of % border-radius, with frame"); + is(cs.borderTopRightRadius, "5px", + "computed value of px border-radius, with frame"); + is(cs.borderBottomRightRadius, "5px 3px", + "computed value of px border-radius, with frame"); + is(cs.borderBottomLeftRadius, "1.5625% 3.125%", + "computed value of % border-radius, with frame"); + is(cs.MozOutlineRadiusTopleft, "1.5625%", + "computed value of % outline-radius, with frame"); + is(cs.MozOutlineRadiusTopright, "5px", + "computed value of px outline-radius, with frame"); + is(cs.MozOutlineRadiusBottomright, "5px 3px", + "computed value of px outline-radius, with frame"); + is(cs.MozOutlineRadiusBottomleft, "1.5625% 3.125%", + "computed value of % outline-radius, with frame"); + + noframe_container.appendChild(p); + is(cs.borderTopLeftRadius, "1.5625%", + "computed value of % border-radius, without frame"); + is(cs.borderTopRightRadius, "5px", + "computed value of px border-radius, without frame"); + is(cs.borderBottomRightRadius, "5px 3px", + "computed value of px border-radius, without frame"); + is(cs.borderBottomLeftRadius, "1.5625% 3.125%", + "computed value of % border-radius, without frame"); + is(cs.MozOutlineRadiusTopleft, "1.5625%", + "computed value of % outline-radius, without frame"); + is(cs.MozOutlineRadiusTopright, "5px", + "computed value of px outline-radius, without frame"); + is(cs.MozOutlineRadiusBottomright, "5px 3px", + "computed value of px outline-radius, without frame"); + is(cs.MozOutlineRadiusBottomleft, "1.5625% 3.125%", + "computed value of % outline-radius, without frame"); + + p.parentNode.removeChild(p); +})(); + +(function test_bug_1292447() { + // Was for bug 595651 which tests that clamping of border-radius + // is reflected in computed style. + // For compatibility issue, resolved value is computed value now. + var p = document.createElement("p"); + p.setAttribute("style", "width: 190px; height: 90px; border: 5px solid;"); + p.style.borderRadius = "1000px"; + var cs = getComputedStyle(p, ""); + + frame_container.appendChild(p); + is(cs.borderTopLeftRadius, "1000px", + "computed value of clamped border radius (top left)"); + is(cs.borderTopRightRadius, "1000px", + "computed value of clamped border radius (top right)"); + is(cs.borderBottomRightRadius, "1000px", + "computed value of clamped border radius (bottom right)"); + is(cs.borderBottomLeftRadius, "1000px", + "computed value of clamped border radius (bottom left)"); + + p.style.overflowY = "scroll"; + is(cs.borderTopLeftRadius, "1000px", + "computed value of clamped border radius (top left, overflow-y)"); + // Fennec doesn't have scrollbars for overflow:scroll content + if (p.clientWidth == p.offsetWidth - 10) { + is(cs.borderTopRightRadius, "1000px", + "computed value of border radius (top right, overflow-y)"); + is(cs.borderBottomRightRadius, "1000px", + "computed value of border radius (bottom right, overflow-y)"); + } else { + is(cs.borderTopRightRadius, "1000px", + "computed value of clamped border radius (top right, overflow-y)"); + is(cs.borderBottomRightRadius, "1000px", + "computed value of clamped border radius (bottom right, overflow-y)"); + } + is(cs.borderBottomLeftRadius, "1000px", + "computed value of clamped border radius (bottom left, overflow-y)"); + + p.style.overflowY = "hidden"; + p.style.overflowX = "scroll"; + is(cs.borderTopLeftRadius, "1000px", + "computed value of clamped border radius (top left, overflow-x)"); + is(cs.borderTopRightRadius, "1000px", + "computed value of clamped border radius (top right, overflow-x)"); + // Fennec doesn't have scrollbars for overflow:scroll content + if (p.clientHeight == p.offsetHeight - 10) { + is(cs.borderBottomRightRadius, "1000px", + "computed value of border radius (bottom right, overflow-x)"); + is(cs.borderBottomLeftRadius, "1000px", + "computed value of border radius (bottom left, overflow-x)"); + } else { + is(cs.borderBottomRightRadius, "1000px", + "computed value of clamped border radius (bottom right, overflow-x)"); + is(cs.borderBottomLeftRadius, "1000px", + "computed value of clamped border radius (bottom left, overflow-x)"); + } + + p.parentNode.removeChild(p); +})(); + +(function test_bug_647885_1() { + // Test that various background-position styles round-trip correctly + var backgroundPositions = [ + [ "0 0", "0px 0px", "unitless 0" ], + [ "0px 0px", "0px 0px", "0 with units" ], + [ "0% 0%", "0% 0%", "0%" ], + [ "calc(0px) 0", "0px 0px", "0 calc with units x" ], + [ "0 calc(0px)", "0px 0px", "0 calc with units y" ], + [ "calc(3px - 3px) 0", "0px 0px", "computed 0 calc with units x" ], + [ "0 calc(3px - 3px)", "0px 0px", "computed 0 calc with units y" ], + [ "calc(0%) 0", "0% 0px", "0% calc x"], + [ "0 calc(0%)", "0px 0%", "0% calc y"], + [ "calc(3px + 2% - 2%) 0", "calc(3px + 0%) 0px", + "computed 0% calc x"], + [ "0 calc(3px + 2% - 2%)", "0px calc(3px + 0%)", + "computed 0% calc y"], + [ "calc(3px - 5px) calc(6px - 7px)", "-2px -1px", + "negative pixel width"], + [ "", "0% 0%", "initial value" ], + ]; + + var p = document.createElement("p"); + var cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (var i = 0; i < backgroundPositions.length; ++i) { + var test = backgroundPositions[i]; + p.style.backgroundPosition = test[0]; + is(cs.backgroundPosition, test[1], "computed value of " + test[2] + " background-position"); + } + + p.parentNode.removeChild(p); +})(); + +(function test_bug_647885_2() { + // Test that various background-size styles round-trip correctly + var backgroundSizes = [ + [ "0 0", "0px 0px", "unitless 0" ], + [ "0px 0px", "0px 0px", "0 with units" ], + [ "0% 0%", "0% 0%", "0%" ], + [ "calc(0px) 0", "0px 0px", "0 calc with units horizontal" ], + [ "0 calc(0px)", "0px 0px", "0 calc with units vertical" ], + [ "calc(3px - 3px) 0", "0px 0px", "computed 0 calc with units horizontal" ], + [ "0 calc(3px - 3px)", "0px 0px", "computed 0 calc with units vertical" ], + [ "calc(0%) 0", "0% 0px", "0% calc horizontal"], + [ "0 calc(0%)", "0px 0%", "0% calc vertical"], + [ "calc(3px + 2% - 2%) 0", "calc(3px + 0%) 0px", + "computed 0% calc horizontal"], + [ "0 calc(3px + 2% - 2%)", "0px calc(3px + 0%)", + "computed 0% calc vertical"], + [ "calc(3px - 5px) calc(6px - 9px)", + "calc(-2px) calc(-3px)", "negative pixel width" ], + [ "", "auto auto", "initial value" ], + ]; + + var p = document.createElement("p"); + var cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (var i = 0; i < backgroundSizes.length; ++i) { + var test = backgroundSizes[i]; + p.style.backgroundSize = test[0]; + is(cs.backgroundSize, test[1], "computed value of " + test[2] + " background-size"); + } + + p.parentNode.removeChild(p); +})(); + +(function test_bug_716628() { + // Test that various gradient styles round-trip correctly + var backgroundImages = [ + [ "-moz-radial-gradient(10% bottom, #ffffff, black)", + "radial-gradient(at 10% 100%, rgb(255, 255, 255), rgb(0, 0, 0))", + "radial gradient 1" ], + [ "-moz-radial-gradient(#ffffff, black)", + "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))", + "radial gradient 2" ], + [ "-moz-radial-gradient(cover, #ffffff, black)", + "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))", + "radial gradient 3" ], + [ "-moz-radial-gradient(top left -45deg, #ffffff, black)", + "-moz-radial-gradient(0% 0% -45deg, rgb(255, 255, 255), rgb(0, 0, 0))", + "radial gradient with angle in degrees" ], + [ "-moz-linear-gradient(red, blue)", + "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient 1" ], + [ "-moz-linear-gradient(to bottom, red, blue)", + "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient 2" ], + [ "-moz-linear-gradient(to right, red, blue)", + "linear-gradient(to right, rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient 3" ], + [ "-moz-linear-gradient(10px 10px -45deg, red, blue)", + "-moz-linear-gradient(10px 10px -45deg, rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient with angle in degrees" ], + [ "-moz-linear-gradient(10px 10px -0.125turn, red, blue)", + "-moz-linear-gradient(10px 10px -0.125turn, rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient with angle in turns" ], + ]; + + var p = document.createElement("p"); + var cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (var i = 0; i < backgroundImages.length; ++i) { + var test = backgroundImages[i]; + p.style.backgroundImage = test[0]; + is(cs.backgroundImage, test[1], "computed value of " + test[2] + " background-image"); + } + + p.parentNode.removeChild(p); +})(); + +(function test_bug_1235015() { + if (!("maskImage" in document.documentElement.style)) { + return; + } + + // "masks" object contains non-initial mask longhand values. + var emptyMasks = { + // More then one <mask-reference>, or any mask-image value other then + // <mask-source>, + "mask-image": [ + "url(#mask1), url(#mask2)", + "linear-gradient(red, yellow)", + "-moz-element(#test)" + ], + // any mask-clip value other than "border-box". + "mask-clip": [ + "content-box", "padding-box", "margin-box", "fill-box", "stroke-box", + "view-box", "no-clip" + ], + // any mask-origin value other than "border-box". + "mask-origin": [ + "content-box", "padding-box", "margin-box", "fill-box", "stroke-box", + "view-box" + ], + // any mask-composite value other than "add". + "mask-composite": [ + "subtract", "intersect", "exclude" + ], + // any mask-mode value other than "match-source". + "mask-mode": [ + "alpha", "luminance" + ], + // any mask-position value other then "0%" "top" "left" + // "center center". + "mask-position": [ + "0%", "center", "right", "bottom", "50%", "100%" + ], + // any mask-repeat value other then "repeat" "repeat repeat". + "mask-repeat": [ + "repeat-x", "repeat-y", "no-repeat", "space", "round" + ], + // any mask-size value other then "auto" "auto auto". + "mask-size": [ + "10px", "100%", "cover", "contain", "auto 5px" + ], + }; + + // "masks" object contains initial mask longhand values. + var nonEmptyMasks = { + "mask-image": [ + "url(#mask1)", "none" + ], + "mask-clip": [ + "border-box" + ], + "mask-origin": [ + "border-box" + ], + "mask-composite": [ + "add" + ], + "mask-mode": [ + "match-source" + ], + "mask-position": [ + "0% 0%", "left top" + ], + "mask-repeat": [ + "repeat", "repeat repeat" + ], + "mask-size": [ + "auto", "auto auto" + ], + }; + + var p = document.createElement("p"); + var cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (var prop in emptyMasks) { + var subProp = emptyMasks[prop]; + for (var i = 0; i < subProp.length; i++) { + p.style.mask = subProp[i]; + is(cs.mask, "", "computed value of " + subProp[i] + " mask"); + } + } + + for (var prop in nonEmptyMasks) { + var subProp = nonEmptyMasks[prop]; + for (var i = 0; i < subProp.length; i++) { + p.style.mask = subProp[i]; + isnot(cs.mask, "", "computed value of " + subProp[i] + " mask"); + } + } + p.parentNode.removeChild(p); +})(); + +(function test_bug_1293164() { + + var p = document.createElement("p"); + var cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + var docPath = document.URL.substring(0, document.URL.lastIndexOf("/") + 1); + + var localURL = "url(\"#foo\")"; + var nonLocalURL = "url(\"foo.svg#foo\")"; + var resolvedNonLocalURL = "url(\"" + docPath + "foo.svg#foo\")"; + + var testStyles = { + "mask" : "", + "markerStart" : "", + "markerMid" : "", + "markerEnd" : "", + "clipPath" : "", + "filter" : "", + "fill" : " transparent", + "stroke" : " transparent", + }; + + for (var prop in testStyles) { + p.style[prop] = localURL; + is(cs[prop], localURL + testStyles[prop], "computed value of " + prop); + p.style[prop] = nonLocalURL; + is(cs[prop], resolvedNonLocalURL + testStyles[prop], "computed value of " + prop); + } + + p.parentNode.removeChild(p); +})(); + +(function test_bug_1347164() { + // Test that computed color values are serialized as "rgb()" + // IFF they're fully-opaque (and otherwise as "rgba()"). + var color = [ + ["rgba(0, 0, 0, 1)", "rgb(0, 0, 0)"], + ["rgba(0, 0, 0, 0.5)", "rgba(0, 0, 0, 0.5)"], + ["hsla(0, 0%, 0%, 1)", "rgb(0, 0, 0)"], + ["hsla(0, 0%, 0%, 0.5)", "rgba(0, 0, 0, 0.5)"], + // css-color-4 + ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["rgba(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"], + ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["rgb(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"], + ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"], + ["hsla(0deg 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"], + ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"], + ["hsl(0 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"], + ]; + + var p = document.createElement("p"); + var cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (var i = 0; i < color.length; ++i) { + var test = color[i]; + p.style.color = test[0]; + is(cs.color, test[1], "computed value of " + test[0]); + } + + p.remove(); +})(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_computed_style_min_size_auto.html b/layout/style/test/test_computed_style_min_size_auto.html new file mode 100644 index 000000000..762172ee3 --- /dev/null +++ b/layout/style/test/test_computed_style_min_size_auto.html @@ -0,0 +1,133 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=763689 +--> +<head> + <meta charset="utf-8"> + <title>Test behavior of 'min-height:auto' and 'min-width:auto' (Bug 763689 and Bug 1304636)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=763689">Mozilla Bug 763689</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1304636">Mozilla Bug 1304636</a> +<body> +<div id="display"> + <div id="block-item">abc</div> + + <div style="display: flex"> + <div id="horizontal-flex-item">abc</div> + <div id="horizontal-flex-item-OH" style="overflow: hidden">def</div> + </div> + + <div style="display: flex; flex-direction: column"> + <div id="vertical-flex-item">abc</div> + <div id="vertical-flex-item-OH" style="overflow: hidden">def</div> + </div> + + <div style="display: grid"> + <div id="grid-item"></div> + <div id="grid-item-OH" style="overflow: hidden"></div> + </div> +</div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +/** + * Test 'min-height:auto' and 'min-width:auto' (Bug 763689 and Bug 1304636) + * ======================================================== + * This test checks the computed-style value of the "auto" keyword introduced + * for the "min-height" and "min-width" properties in CSS3 Flexbox Section 4.5 + * and CSS3 Grid Section 6.2. + * https://www.w3.org/TR/css-flexbox-1/#min-size-auto + * https://www.w3.org/TR/css-grid-1/#grid-item-sizing + * + * Quoting that chunk of spec: + * # auto + * # On a flex item whose overflow is visible in the main axis, + * # when specified on the flex item’s main-axis min-size property, + * # specifies an automatic minimum size. It otherwise computes to 0 + * # (unless otherwise defined by a future specification). + * + */ + +// Given an element ID, this function sets the corresponding +// element's inline-style min-width and min-height explicitly to "auto". +function setElemMinSizesToAuto(aElemId) { + var elem = document.getElementById(aElemId); + + is(elem.style.minWidth, "", "min-width should be initially unset"); + elem.style.minWidth = "auto"; + is(elem.style.minWidth, "auto", "min-width should accept 'auto' value"); + + is(elem.style.minHeight, "", "min-height should be initially unset"); + elem.style.minHeight = "auto"; + is(elem.style.minHeight, "auto", "min-height should accept 'auto' value"); +} + +// Given an element ID, this function compares the corresponding element's +// computed min-width and min-height against expected values. +function checkElemMinSizes(aElemId, + aExpectedMinWidth, + aExpectedMinHeight) +{ + var elem = document.getElementById(aElemId); + is(window.getComputedStyle(elem, "").minWidth, aExpectedMinWidth, + "checking min-width of " + aElemId); + + is(window.getComputedStyle(elem, "").minHeight, aExpectedMinHeight, + "checking min-height of " + aElemId); +} + +// This function goes through all the elements we're interested in +// and checks their computed min-sizes against expected values, +// farming out each per-element job to checkElemMinSizes. +function checkAllTheMinSizes() { + // This is the normal part -- generally, the default value of "min-width" + // and "min-height" (auto) computes to "0px". + checkElemMinSizes("block-item", "0px", "0px"); + + // ...but for a flex item in a horizontal flex container, "min-width: auto" + // computes to "auto". + checkElemMinSizes("horizontal-flex-item", "auto", "0px"); + checkElemMinSizes("horizontal-flex-item-OH", "0px", "0px"); + + // ...and for a flex item in a vertical flex container, "min-height: auto" + // computes to "auto". + checkElemMinSizes("vertical-flex-item", "0px", "auto"); + checkElemMinSizes("vertical-flex-item-OH", "0px", "0px"); + + // ...and for a grid item, "min-width: auto" and min-height both + // compute to "auto". + checkElemMinSizes("grid-item", "auto", "auto"); + checkElemMinSizes("grid-item-OH", "0px", "0px"); +} + +// Main test function +function main() { + // First: check that min-sizes are what we expect, with min-size properties + // at their initial value. + checkAllTheMinSizes(); + + // Now, we *explicitly* set min-size properties to "auto"... + var elemIds = [ "block-item", + "horizontal-flex-item", + "horizontal-flex-item-OH", + "vertical-flex-item", + "vertical-flex-item-OH", + "grid-item", + "grid-item-OH"]; + elemIds.forEach(setElemMinSizesToAuto); + + // ...and try again (should have the same result): + checkAllTheMinSizes(); +} + +main(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_computed_style_no_pseudo.html b/layout/style/test/test_computed_style_no_pseudo.html new file mode 100644 index 000000000..11ae16c75 --- /dev/null +++ b/layout/style/test/test_computed_style_no_pseudo.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=505515 +--> +<head> + <title>Test for Bug 505515</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + #display { color: black; background: white; } + #display:first-line { color: blue; } + + </style> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=505515">Mozilla Bug 505515</a> +<p id="display" style="width: 30em">This <span id="sp">is</span> some text in which the first line is in a different color.</p> +<pre id="test"> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +/** Test for Bug 505515 **/ + +function run() { + var p = document.getElementById("display"); + var span = document.getElementById("sp"); + + isnot(span.offsetWidth, 0, + "span should have width (and we flushed layout)"); + is(getComputedStyle(span, "").color, "rgb(0, 0, 0)", + "span should be black"); + is(getComputedStyle(p, "").color, "rgb(0, 0, 0)", + "p should be black too"); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_computed_style_prefs.html b/layout/style/test/test_computed_style_prefs.html new file mode 100644 index 000000000..163176237 --- /dev/null +++ b/layout/style/test/test_computed_style_prefs.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that preffed off properties do not appear in computed style</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=919594">Mozilla Bug 919594</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript; version=1.7"> + +/** Test that preffed off properties do not appear in computed style **/ + +function testWithAllPrefsDisabled() { + let exposedProperties = Object.keys(gCS).map(i => gCS[i]); + + // Store the number of properties for later tests to use. + gLengthWithAllPrefsDisabled = gCS.length; + + // Check that all of the properties behind the prefs are not exposed. + for (let pref in gProps) { + for (let prop of gProps[pref]) { + ok(exposedProperties.indexOf(prop) == -1, prop + " not exposed when prefs are false"); + } + } +} + +function testWithOnePrefEnabled(aPref) { + let exposedProperties = Object.keys(gCS).map(i => gCS[i]); + + // Check that the number of properties on the object is as expected. + is(gCS.length, gLengthWithAllPrefsDisabled + gProps[aPref].length, "length when " + aPref + " is true"); + + // Check that the properties corresponding to aPref are exposed. + for (let prop of gProps[aPref]) { + ok(exposedProperties.indexOf(prop) != -1, prop + " exposed when " + aPref + " is true"); + } +} + +function step() { + if (gTestIndex == gTests.length) { + // Reached the end of the tests. + SimpleTest.finish(); + return; + } + + if (gPrefsPushed) { + // We've just finished running one tests. Pop the prefs and go on to + // the next test. + gTestIndex++; + gPrefsPushed = false; + SpecialPowers.popPrefEnv(step); + return; + } + + // About to run one test. Push the prefs and run it. + let fn = gTests[gTestIndex].fn; + gPrefsPushed = true; + SpecialPowers.pushPrefEnv(gTests[gTestIndex].settings, + function() { fn(); SimpleTest.executeSoon(step); }); +} + +// ---- + +var gProps = { + "layout.css.text-combine-upright.enabled": ["text-combine-upright"], + "layout.css.image-orientation.enabled": ["image-orientation"], + "layout.css.mix-blend-mode.enabled": ["mix-blend-mode"], + "layout.css.isolation.enabled": [ "isolation"], + "layout.css.touch_action.enabled": ["touch-action"], + "svg.transform-box.enabled": ["transform-box"] +}; + +var gCS = getComputedStyle(document.body, ""); +var gLengthWithAllPrefsDisabled; + +var gTestIndex = 0; +var gPrefsPushed = false; +var gTests = [ + // First, test when all of the prefs are disabled. + { settings: { set: Object.keys(gProps).map(x => [x, false]) }, + fn: testWithAllPrefsDisabled }, + // Then, test each pref enabled individually. + ...Object.keys(gProps).map(p => + ({ settings: { set: Object.keys(gProps).map(x => [x, x == p]) }, + fn: testWithOnePrefEnabled.bind(null, p) })) +]; + +SimpleTest.waitForExplicitFinish(); +step(); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_condition_text.html b/layout/style/test/test_condition_text.html new file mode 100644 index 000000000..9ab60758d --- /dev/null +++ b/layout/style/test/test_condition_text.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=814907 +--> +<head> + <title>Test for Bug 814907</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="style"> + @-moz-document url(http://www.example.com/) {} + @-moz-document url('http://www.example.com/') {} + @-moz-document url("http://www.example.com/") {} + @-moz-document url-prefix('http://www.example.com/') {} + @-moz-document url-prefix("http://www.example.com/") {} + @-moz-document domain('example.com') {} + @-moz-document domain("example.com") {} + @-moz-document regexp('http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/') {} + @-moz-document regexp("http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/") {} + + @media all {} + @media only color {} + @media (color ) {} + @media color \0061ND ( monochrome ) {} + @media (max-width: 200px), (color) {} + + @supports(color: green){} + @supports (color: green) {} + @supports ((color: green)) {} + @supports (color: green) and (color: blue) {} + @supports ( Font: 20px serif ! Important) {} + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=814907">Mozilla Bug 814907</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 814907 **/ + +function runTest() +{ + // re-parse the style sheet with the pref turned on + var style = document.getElementById("style"); + style.textContent += " "; + + var sheet = style.sheet; + + var conditions = [ + "url(\"http://www.example.com/\")", + "url(\"http://www.example.com/\")", + "url(\"http://www.example.com/\")", + "url-prefix(\"http://www.example.com/\")", + "url-prefix(\"http://www.example.com/\")", + "domain(\"example.com\")", + "domain(\"example.com\")", + "regexp(\"http://www.w3.org/TR/\\\\d{4}/[^/]*-CSS2-\\\\d{8}/\")", + "regexp(\"http://www.w3.org/TR/\\\\d{4}/[^/]*-CSS2-\\\\d{8}/\")", + "all", + "only color", + "(color)", + "color and (monochrome)", + "(max-width: 200px), (color)", + "(color: green)", + "(color: green)", + "((color: green))", + "(color: green) and (color: blue)", + "( Font: 20px serif ! Important)" + ]; + + is(sheet.cssRules.length, conditions.length); + + for (var i = 0; i < sheet.cssRules.length; i++) { + var rule = sheet.cssRules[i]; + is(rule.conditionText, conditions[i], "rule " + i + " has expected conditionText"); + if (rule.type == CSSRule.MEDIA_RULE) { + is(rule.conditionText, rule.media.mediaText, "rule " + i + " conditionText matches media.mediaText"); + } + } + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_condition_text_assignment.html b/layout/style/test/test_condition_text_assignment.html new file mode 100644 index 000000000..dc1ef923d --- /dev/null +++ b/layout/style/test/test_condition_text_assignment.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=815021 +--> +<head> + <title>Test for Bug 815021</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="style"> + #a { text-transform: none } + @media all { + #a { text-transform: lowercase } + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815021">Mozilla Bug 815021</a> +<p id="display"><span id=a></span></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 815021 **/ + +var sheet = document.getElementById("style").sheet; +var rule = sheet.cssRules[1]; +var a = document.getElementById("a"); + +function stylesApplied() { + return window.getComputedStyle(a, "").textTransform == "lowercase"; +} + +is(rule.type, CSSRule.MEDIA_RULE, "initial @media rule type"); +is(rule.conditionText, "all", "initial @media rule conditionText"); +ok(stylesApplied(), "initial @media rule applied"); + +// [value to set, value to check, whether styles should be applied] +var media = [ + ["not all", "not all", false], + ["ALL ", "all", true], + ["unknown", "unknown", false], + ["(min-width:1px)", "(min-width: 1px)", true], + ["(bad syntax", "not all", false], + ["(max-width: 1px), (color)", "(max-width: 1px), (color)", true] +]; + +for (var i = 0; i < media.length; i++) { + rule.conditionText = media[i][0]; + is(rule.conditionText, media[i][1], "value of conditionText #" + i); + ok(rule.cssText.startsWith("@media " + media[i][1]), "value of cssText #" + i); + ok(stylesApplied() == media[i][2], "styles applied #" + i); +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_contain_formatting_context.html b/layout/style/test/test_contain_formatting_context.html new file mode 100644 index 000000000..928cc35f5 --- /dev/null +++ b/layout/style/test/test_contain_formatting_context.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1170781 +--> +<head> + <meta charset="utf-8"> + <title>Test that 'contain: paint' updates 'display' correctly</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <div id="test" style="contain: paint"></div> + <script> + // This mapping is currently mostly based off of a bugzilla comment by Tab + // Atkins Jr. in bug 1179349. Ultimately, it should be based on a + // specification. + + // XXX 'ruby[-*]' and 'run-in' might need to be added here. + var expectedChanges = { + 'inline' : 'inline-block', + }; + var displayInfo = gCSSProperties["display"]; + var test = document.getElementById('test'); + var displayVals = displayInfo.initial_values.concat(displayInfo.other_values); + for (dispVal of displayVals) { + test.style.display = dispVal; + if (expectedChanges.hasOwnProperty(dispVal)) { + is(getComputedStyle(test).display, expectedChanges[dispVal], + `'contain: paint' should change 'display: ${dispVal}' to ` + + `'display: ${expectedChanges[dispVal]}'`); + } else { + is(getComputedStyle(test).display, dispVal, + `'contain: paint' should not change 'display: ${dispVal}'`); + } + } + </script> +</body> +</html> diff --git a/layout/style/test/test_counter_descriptor_storage.html b/layout/style/test/test_counter_descriptor_storage.html new file mode 100644 index 000000000..8262d8017 --- /dev/null +++ b/layout/style/test/test_counter_descriptor_storage.html @@ -0,0 +1,267 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test for parsing, storage and serialization of CSS @counter-style descriptor values</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166">Mozilla Bug 966166</a> +<div id="display"></div> +<pre id="test"> +<script type="application/javascript"> +var gStyleElement = document.createElement("style"); +gStyleElement.setAttribute("type", "text/css"); +document.getElementsByTagName("head")[0].appendChild(gStyleElement); +var gSheet = gStyleElement.sheet; +gSheet.insertRule( + "@counter-style test { system: extends decimal }", 0); +var gRule = gSheet.cssRules[0]; + +function set_rule(ruleText) { + gSheet.deleteRule(0); + gSheet.insertRule("@counter-style test { " + ruleText + " }", 0); + gRule = gSheet.cssRules[0]; +} + +function run_tests(tests) { + for (var desc in tests) { + var items = tests[desc]; + for (var i in items) { + var item = items[i]; + var ref = item[0]; + if (ref === null) { + ref = gRule[desc]; + } + for (var j in item) { + if (item[j] !== null) { + gRule[desc] = item[j]; + is(gRule[desc], ref, + "setting '" + item[j] + "' on '" + desc + "'"); + } + } + } + } +} + +function test_system_dep_desc() { + // for system requires at least one symbol + var oneSymbolTests = [ + [null, "", "0"], + ["x y", "x y"], + ["\"x\"", "'x'"], + ["\\-", "\\2D"], + ["\\*", "\\2A"], + ]; + // for system requires at least two symbols + var twoSymbolsTests = [ + [null, "", "0", "x", "\"x\""], + ["x y", "x y"], + ["\"x\" \"y\"", "'x' 'y'"], + ]; + var info = [ + { + system: "cyclic", + base: "symbols: x", + base_tests: { + system: "cyclic", + symbols: "x" + }, + tests: { + system: [ + [null, "", "symbolic"], + ["cyclic", "Cyclic"], + ], + symbols: oneSymbolTests + } + }, + { + system: "fixed", + base: "symbols: x", + base_tests: { + system: "fixed 1", + symbols: "x" + }, + tests: { + system: [ + [null, "", "symbolic"], + ["fixed 0"], + ["fixed 1", "fixed", "FixeD"], + ["fixed -1"], + [null, "fixed a", "fixed \"0\"", "fixed 0 1"], + ], + symbols: oneSymbolTests + } + }, + { + system: "symbolic", + base: "symbols: x", + base_tests: { + system: "symbolic", + symbols: "x" + }, + tests: { + system: [ + [null, "", "cyclic"], + ["symbolic", "SymBolic"], + ], + symbols: oneSymbolTests + } + }, + { + system: "alphabetic", + base: "symbols: x y", + base_tests: { + system: "alphabetic", + symbols: "x y" + }, + tests: { + system: [ + [null, "", "cyclic"], + ["alphabetic", "AlphaBetic"], + ], + symbols: twoSymbolsTests + } + }, + { + system: "numeric", + base: "symbols: x y", + base_tests: { + system: "numeric", + symbols: "x y" + }, + tests: { + system: [ + [null, "", "cyclic"], + ["numeric", "NumEric"], + ], + symbols: twoSymbolsTests + } + }, + { + system: "additive", + base: "additive-symbols: 0 x", + base_tests: { + system: "additive", + additiveSymbols: "0 x" + }, + tests: { + system: [ + [null, "", "cyclic"], + ], + additiveSymbols: [ + [null, "", "x", "0", "\"x\"", "1 x, 0", "0 x, 1 y"], + ["0 x", "x 0"], + ["1 y, 0 x", "y 1, 0 x", "1 y, x 0", "y 1, x 0"], + ["1 \"0\"", "\"0\" 1", "1 '0'"], + ] + } + }, + { + system: "extends decimal", + base: "", + base_tests: { + system: "extends decimal", + symbols: "", + additiveSymbols: "" + }, + tests: { + system: [ + [null, "extends", "fixed", "cyclic", "extends symbols('*')"], + ["extends cjk-decimal", "ExTends cjk-decimal", "extends CJK-decimal"], + ], + symbols: [ + [null, "x", "x y"], + ], + additiveSymbols: [ + [null, "0 x", "1 y, 0 x"], + ] + } + } + ]; + for (var i = 0; i < info.length; i++) { + var item = info[i]; + set_rule("system: " + item.system + "; " + item.base); + for (var desc in item.base_tests) { + is(gRule[desc], item.base_tests[desc], + "checking base value of '" + desc + "' " + + "for system '" + item.system + "'"); + } + run_tests(item.tests); + } +} + +function test_system_indep_desc() { + var tests = { + name: [ + [null, "", "-", " ", "a b"], + [null, "decimal", "none", "Decimal", "NONE"], + ["cjk-decimal", "CJK-Decimal", "cjk-Decimal"], + ["X"], + ["x", "\\78"], + ["\\-", "\\2D"], + ], + negative: [ + [null, "-", "", "0", "a b c"], + ["\"-\"", "'-'", "\"\\2D\""], + ["\\-", "\\2D"], + ["a b"], + ["\"(\" \")\"", "'(' ')'"], + ], + prefix: [ + [null, "0", "-", " ", "a b"], + ["a"], + ["\"a\""], + ], + suffix: [ + [null, "0", "-", " ", "a b"], + ["a"], + ["\"a\""], + ], + range: [ + ["auto", "auTO"], + ["infinite infinite", "INFinite inFinite"], + ["0 infinite", "0 INFINITE"], + ["infinite 100"], + ["1 1"], + ["0 100", "0 100"], + ["0 100, 2 300, -1 1, infinite -100"], + [null, "0", "0 a", "a 0"], + [null, "1 -1", "1 -1, 0 100", "-1 1, 100 0"], + ], + pad: [ + ["0 \"\"", "\"\" 0"], + ["1 a", "a 1", "1 a", "\\61 1"], + [null, "0", "\"\"", "0 0", "a a", "0 a a"], + ], + fallback: [ + [null, "", "-", "0", "a b", "symbols('*')"], + ["a"], + ["A"], + ["decimal", "Decimal"], + ], + speakAs: [ + [null, "", "-", "0", "a b", "symbols('*')"], + ["auto", "AuTo"], + ["bullets", "BULLETs"], + ["numbers", "NumBers"], + ["words", "WordS"], + // Currently spell-out is not supported, so it should be treated + // as an invalid value. + [null, "spell-out", "Spell-Out"], + ["a"], + ["A"], + ["decimal", "Decimal"], + ], + }; + set_rule("system: extends decimal"); + run_tests(tests); +} + +test_system_dep_desc(); +test_system_indep_desc(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_counter_style.html b/layout/style/test/test_counter_style.html new file mode 100644 index 000000000..c248494f5 --- /dev/null +++ b/layout/style/test/test_counter_style.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Test for css3-counter-style (Bug 966166)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + #ol_test, #ol_ref { + display: inline-block; + list-style-position: inside; + } + #ol_test { list-style-type: test; } + #ol_ref { list-style-type: ref; } + #div_test, #div_ref { + display: inline-block; + counter-reset: a -1; + } + #div_test::before { content: counter(a, test); } + #div_ref::before { content: counter(a, ref); } + </style> + <style type="text/css" id="counter"> + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166">Mozilla Bug 966166</a> +<div id="display"></div> +<ol id="ol_test" start="-1"><li></li></ol><br> +<ol id="ol_ref" start="-1"><li></li></ol><br> +<div id="div_test"></div><br> +<div id="div_ref"></div><br> +<pre id="test"> +<script type="application/javascript"> +var gOlTest = document.getElementById("ol_test"), + gOlRef = document.getElementById("ol_ref"), + gDivTest = document.getElementById("div_test"), + gDivRef = document.getElementById("div_ref"), + gCounterSheet = document.getElementById("counter").sheet; + +var testRule, refRule; + +var basicStyle = "system: extends decimal; range: infinite infinite; "; +var info = [ + ["system", + "system: fixed -1; symbols: xxx;", + "system: fixed; symbols: xxx;"], + ["system", + "system: extends decimal;", + "system: extends cjk-ideographic;"], + ["negative", "", "negative: '((' '))';"], + ["negative", "", "negative: '---';"], + ["prefix", "", "prefix: '###';"], + ["suffix", "", "suffix: '###';"], + ["range", + "fallback: cjk-ideographic;", + "fallback: cjk-ideographic; range: 10 infinite;"], + ["pad", "", "pad: 10 '0';"], + ["fallback", + "range: 0 infinite;", + "range: 0 infinite; fallback: cjk-ideographic;"], + ["symbols", + "system: symbolic; symbols: '1';", + "system: symbolic; symbols: '111';"], + ["additiveSymbols", + "system: additive; additive-symbols: 1 '1';", + "system: additive; additive-symbols: 1 '111';"], +]; + +// force a reflow before test to eliminate bug 994418 +gOlTest.getBoundingClientRect().width; + +for (var i in info) { + var item = info[i]; + var desc = item[0], + testStyle = item[1], + refStyle = item[2]; + var isFix = (desc == "prefix" || desc == "suffix"); + + while (gCounterSheet.cssRules.length > 0) { + gCounterSheet.deleteRule(0); + } + gCounterSheet.insertRule("@counter-style test { " + + basicStyle + testStyle + "}", 0); + gCounterSheet.insertRule("@counter-style ref { " + + basicStyle + refStyle + "}", 1); + testRule = gCounterSheet.cssRules[0]; + refRule = gCounterSheet.cssRules[1]; + + var olTestWidth = gOlTest.getBoundingClientRect().width; + var olRefWidth = gOlRef.getBoundingClientRect().width; + ok(olTestWidth > 0, "test ol has width"); + ok(olRefWidth > 0, "ref ol has width"); + ok(olTestWidth != olRefWidth, + "OLs have different width " + + "for rule '" + testStyle + "' and '" + refStyle + "'"); + + var divTestWidth = gDivTest.getBoundingClientRect().width; + var divRefWidth = gDivRef.getBoundingClientRect().width; + if (!isFix) { + ok(divTestWidth > 0, "test div has width"); + ok(divRefWidth > 0, "ref div has width"); + ok(divTestWidth != divRefWidth, + "DIVs have different width" + + "for rule '" + testStyle + "' and '" + refStyle + "'"); + } + + ok(testRule[desc] != refRule[desc], + "rules have different values for desciptor '" + desc + "'"); + testRule[desc] = refRule[desc]; + + var olNewWidth = gOlTest.getBoundingClientRect().width; + var divNewWidth = gDivTest.getBoundingClientRect().width; + is(olNewWidth, olRefWidth); + if (!isFix) { + is(divNewWidth, divRefWidth); + } +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_css_cross_domain.html b/layout/style/test/test_css_cross_domain.html new file mode 100644 index 000000000..055398dec --- /dev/null +++ b/layout/style/test/test_css_cross_domain.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=524223 --> +<head> + <title>Test cross-domain CSS loading</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + hr { border: none; clear: both } + .column { + margin: 10px; + float: left; + } + iframe { + width: 40px; + height: 680px; + border: none; + margin: 0; + padding: 0; + } + h2 { font-weight: normal; padding: 0 } + ol, h2 { font-size: 13px; line-height: 20px; } + ol { padding-left: 1em; + list-style-type: upper-roman } + ol ol { list-style-type: upper-alpha } + ol ol ol { list-style-type: decimal } + </style> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=524223">Mozilla + Bug 524223</a> + +<hr/> + +<div class="column"> +<h2> </h2> +<ol><li>text/css<ol><li>same origin<ol><li>valid</li> + <li>malformed</li> + <li>http error</li></ol></li> + <li>cross origin<ol><li>valid</li> + <li>malformed</li> + <li>http error</li></ol></li> + <li>same to cross<ol><li>valid</li> + <li>malformed</li> + <li>http error</li></ol></li> + <li>cross to same<ol><li>valid</li> + <li>malformed</li> + <li>http error</li></ol></li></ol></li> + <li>text/html<ol><li>same origin<ol><li>valid</li> + <li>malformed</li> + <li>http error</li></ol></li> + <li>cross origin<ol><li>valid</li> + <li>malformed</li> + <li>http error</li></ol></li> + <li>same to cross<ol><li>valid</li> + <li>malformed</li> + <li>http error</li></ol></li> + <li>cross to same<ol><li>valid</li> + <li>malformed</li> + <li>http error</li></ol></li></ol></li> +</ol> +</div> + +<div class="column"> +<h2>Quirks</h2> +<iframe id="quirks" src="ccd-quirks.html"></iframe> +</div> + +<div class="column"> +<h2>Standards</h2> +<iframe id="standards" src="ccd-standards.html"></iframe> +</div> + +<script type="application/javascript"> + +/** Test for Bug 524223 **/ +function check_iframe(ifr) { + var doc = ifr.contentDocument; + var cases = doc.getElementsByTagName("p"); + for (var i = 0; i < cases.length; i++) { + var color = doc.defaultView.getComputedStyle(cases[i], "") + .getPropertyValue("background-color"); + + is(color, "rgb(0, 255, 0)", ifr.id + " " + cases[i].id); + } +} + +SimpleTest.waitForExplicitFinish(); +window.onload = function() { + check_iframe(document.getElementById("quirks")); + check_iframe(document.getElementById("standards")); + SimpleTest.finish(); +}; +</script> +</body> +</html> diff --git a/layout/style/test/test_css_eof_handling.html b/layout/style/test/test_css_eof_handling.html new file mode 100644 index 000000000..b54b031da --- /dev/null +++ b/layout/style/test/test_css_eof_handling.html @@ -0,0 +1,278 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for CSS EOF handling</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p><a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=311616">bug 311616</a>, +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=325064">bug 325064</a></p> +<iframe id="display"></iframe> +<p id="log"></p> +<pre id="test"> +<script class="testbody" type="text/javascript"> +const tests = [ + { + name: "basic rule", + ref: "#r {background-color : orange}", + tst: "#t {background-color : orange", + prop: "background-color", pseudo: "" + }, + { + name: "function", + ref: "#r {background-color: rgb(0,255,0)}", + tst: "#t {background-color: rgb(0,255,0", + prop: "background-color", pseudo: "" + }, + { + name: "comment", + ref: "#r {background-color: aqua/*marine*/}", + tst: "#t {background-color: aqua/*marine", + prop: "background-color", pseudo: "" + }, + { + name: "@media 1", + ref: "@media all { #r { background-color: yellow } }", + tst: "@media all { #t { background-color: yellow }", + prop: "background-color", pseudo: "" + }, + { + name: "@media 2", + ref: "@media all { #r { background-color: magenta } }", + tst: "@media all { #t { background-color: magenta", + prop: "background-color", pseudo: "" + }, + { + name: "@import 1", + ref: "@import 'data:text/css,%23r%7Bbackground-color%3Agray%7D';", + tst: "@import 'data:text/css,%23t%7Bbackground-color%3Agray%7D", + prop: "background-color", pseudo: "" + }, + { + name: "@import 2", + ref: "@import 'data:text/css,%23r%7Bbackground-color%3Ablack%7D' all;", + tst: "@import 'data:text/css,%23t%7Bbackground-color%3Ablack%7D' all", + prop: "background-color", pseudo: "" + }, + { + name: "url-token 1", + ref: "#r { background-image: url(data:image/png;base64," + + "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAADklEQVQI12NI" + + "YJgAhAkAB4gB4Ry+pcoAAAAASUVORK5CYII=) }", + tst: "#t { background-image: url(data:image/png;base64," + + "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAADklEQVQI12NI" + + "YJgAhAkAB4gB4Ry+pcoAAAAASUVORK5CYII=", + prop: "background-image", pseudo: "" + }, + { + name: "url-token 2", + ref: "#r { background-image: url('data:image/png;base64," + + "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12Mo" + + "YNjAcIHhAQAJ2ALR4kRk1gAAAABJRU5ErkJggg==') }", + tst: "#t { background-image: url('data:image/png;base64," + + "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12Mo" + + "YNjAcIHhAQAJ2ALR4kRk1gAAAABJRU5ErkJggg==", + prop: "background-image", pseudo: "" + }, + { + name: "url-token 3", + ref: "#r { background-image: url('data:image/png;base64," + + "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12N4" + + "wHCBYQNDAQAMuALRrGb97AAAAABJRU5ErkJggg==') }", + tst: "#t { background-image: url('data:image/png;base64," + + "iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQAAAACBiqPTAAAAEElEQVQI12N4" + + "wHCBYQNDAQAMuALRrGb97AAAAABJRU5ErkJggg=='", + prop: "background-image", pseudo: "" + }, + { + name: "url-token 4", /*Bug 751939*/ + ref: "#r { background-image: url( )}", + tst: "#t { background-image: url(" , + prop: "background-image", pseudo: "" + }, + { + name: "counter", + ref: "#r::before { content: counter(tr, upper-alpha) }", + tst: "#t::before { content: counter(tr, upper-alpha", + prop: "content", pseudo: "::before" + }, + { + name: "string", + ref: "#r::before { content: 'B' }", + tst: "#t::before { content: 'B", + prop: "content", pseudo: "::before" + }, + + /* For these tests, there is no visible effect on computed style; + instead we have to audit the DOM stylesheet object. */ + + { + todo: 1, /* bug 446226 */ + name: "selector 1", + ref: "td[colspan='3'] {}", + tst: "td[colspan='3" + }, + { + todo: 1, /* bug 446226 */ + name: "selector 2", + ref: "td[colspan='3'] {}", + tst: "td[colspan='3'" + }, + { + todo: 1, /* bug 446226 */ + name: "selector 3", + ref: "td:lang(en) {}", + tst: "td:lang(en" + }, + + { + name: "@media 3", + ref: "@media all {}", + tst: "@media all {", + }, + { + name: "@namespace 1a", + ref: "@namespace foo url('http://foo.example.com/');", + tst: "@namespace foo url('http://foo.example.com/')" + }, + { + name: "@namespace 1b", + ref: "@namespace foo url(http://foo.example.com/);", + tst: "@namespace foo url(http://foo.example.com/" + }, + { + name: "@namespace 1c", + ref: "@namespace foo url('http://foo.example.com/');", + tst: "@namespace foo url('http://foo.example.com/" + }, + { + name: "@namespace 1d", + ref: "@namespace foo 'http://foo.example.com/';", + tst: "@namespace foo 'http://foo.example.com/'" + }, + { + name: "@namespace 1e", + ref: "@namespace foo 'http://foo.example.com/';", + tst: "@namespace foo 'http://foo.example.com/" + }, + { + name: "@namespace 2a", + ref: "@namespace url('http://foo.example.com/');", + tst: "@namespace url('http://foo.example.com/')" + }, + { + name: "@namespace 2b", + ref: "@namespace url('http://foo.example.com/');", + tst: "@namespace url('http://foo.example.com/'" + }, + { + name: "@namespace 2c", + ref: "@namespace url('http://foo.example.com/');", + tst: "@namespace url('http://foo.example.com/" + }, + { + name: "@namespace 2d", + ref: "@namespace 'http://foo.example.com/';", + tst: "@namespace 'http://foo.example.com/'" + }, + { + name: "@namespace 2e", + ref: "@namespace 'http://foo.example.com/';", + tst: "@namespace 'http://foo.example.com/" + }, + { + name: "@-moz-document 1", + ref: "@-moz-document domain('example.com') {}", + tst: "@-moz-document domain('example.com') {" + }, + { + name: "@-moz-document 2", + ref: "@-moz-document domain('example.com') { p {} }", + tst: "@-moz-document domain('example.com') { p {" + } +]; + +const basestyle = ("table {\n"+ + " border-collapse: collapse;\n"+ + "}\n"+ + "td {\n"+ + " width: 1.5em;\n"+ + " height: 1.5em;\n"+ + " border: 1px solid black;\n"+ + " text-align: center;\n"+ + " margin: 0;\n"+ + "}\n"+ + "tr { counter-increment: tr }\n"); + +/* This is more complicated than it might look like it needs to be, + because for each subtest we have to splat stuff into the iframe, + allow the renderer to run, and only then interrogate the computed + styles. */ + +SimpleTest.waitForExplicitFinish(); + +window.onload = function() { + const frame = document.getElementById("display"); + var curTest = 0; + + const prepareTest = function() { + var cd = frame.contentDocument; + cd.open(); + cd.write('<!DOCTYPE HTML><html><head>' + + '<style>\n' + basestyle + '</style>\n' + + '<style>\n' + tests[curTest].ref + '</style>\n' + + '<style>\n' + tests[curTest].tst + '</style>\n' + + '</head><body>\n' + + '<table><tr><td id="r"><td id="t"></table>' + + '</body></html>'); + cd.close(); + }; + + const checkTest = function() { + var cd = frame.contentDocument; + var _is = tests[curTest].todo ? todo_is : is; + var _ok = tests[curTest].todo ? todo : ok; + + if (cd.styleSheets[1].cssRules.length == 1 && + cd.styleSheets[2].cssRules.length == 1) { + // If we have a .prop for this test, the .cssText of the reference + // and test rules will differ in the selector. Change #t to #r + // in the test rule. + var ref_canon = cd.styleSheets[1].cssRules[0].cssText; + var tst_canon = cd.styleSheets[2].cssRules[0].cssText; + tst_canon = tst_canon.replace(/(#|%23)t\b/, "$1r"); + _is(tst_canon, ref_canon, + tests[curTest].name + " (canonicalized rule)"); + } else { + _ok(false, tests[curTest].name + " (rule missing)"); + } + if (tests[curTest].prop) { + var prop = tests[curTest].prop; + var pseudo = tests[curTest].pseudo; + + var refElt = cd.getElementById("r"); + var tstElt = cd.getElementById("t"); + var refStyle = cd.defaultView.getComputedStyle(refElt, pseudo); + var tstStyle = cd.defaultView.getComputedStyle(tstElt, pseudo); + _is(tstStyle.getPropertyValue(prop), + refStyle.getPropertyValue(prop), + tests[curTest].name + " (computed style)"); + } + curTest++; + if (curTest < tests.length) { + prepareTest(); + } else { + SimpleTest.finish(); + } + }; + + frame.onload = function(){setTimeout(checkTest, 0);}; + prepareTest(); +}; +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_css_escape_api.html b/layout/style/test/test_css_escape_api.html new file mode 100644 index 000000000..00ec240c7 --- /dev/null +++ b/layout/style/test/test_css_escape_api.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=955860 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 955860</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=955860">Mozilla Bug 955860</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script> +// Tests taken from: +// https://github.com/mathiasbynens/CSS.escape/blob/master/tests/tests.js + +SimpleTest.doesThrow(() => CSS.escape(), 'undefined'); + +is(CSS.escape('\0'), '\uFFFD', "escaping for 0 char (1)"); +is(CSS.escape('a\0'), 'a\uFFFD', "escaping for 0 char (2)"); +is(CSS.escape('\0b'), '\uFFFDb', "escaping for 0 char (3)"); +is(CSS.escape('a\0b'), 'a\uFFFDb', "escaping for 0 char (4)"); + +is(CSS.escape('\uFFFD'), '\uFFFD', "escaping for replacement char (1)"); +is(CSS.escape('a\uFFFD'), 'a\uFFFD', "escaping replacement char (2)"); +is(CSS.escape('\uFFFDb'), '\uFFFDb', "escaping replacement char (3)"); +is(CSS.escape('a\uFFFDb'), 'a\uFFFDb', "escaping replacement char (4)"); + +is(CSS.escape(true), 'true', "escapingFailed Character : true(bool)"); +is(CSS.escape(false), 'false', "escapingFailed Character : false(bool)"); +is(CSS.escape(null), 'null', "escapingFailed Character : null"); +is(CSS.escape(''), '', "escapingFailed Character : '' "); + +is(CSS.escape('\x01\x02\x1E\x1F'), '\\1 \\2 \\1e \\1f ',"escapingFailed Char: \\x01\\x02\\x1E\\x1F"); + +is(CSS.escape('0a'), '\\30 a', "escapingFailed Char: 0a"); +is(CSS.escape('1a'), '\\31 a', "escapingFailed Char: 1a"); +is(CSS.escape('2a'), '\\32 a', "escapingFailed Char: 2a"); +is(CSS.escape('3a'), '\\33 a', "escapingFailed Char: 3a"); +is(CSS.escape('4a'), '\\34 a', "escapingFailed Char: 4a"); +is(CSS.escape('5a'), '\\35 a', "escapingFailed Char: 5a"); +is(CSS.escape('6a'), '\\36 a', "escapingFailed Char: 6a"); +is(CSS.escape('7a'), '\\37 a', "escapingFailed Char: 7a"); +is(CSS.escape('8a'), '\\38 a', "escapingFailed Char: 8a"); +is(CSS.escape('9a'), '\\39 a', "escapingFailed Char: 9a"); + +is(CSS.escape('a0b'), 'a0b', "escapingFailed Char: a0b"); +is(CSS.escape('a1b'), 'a1b', "escapingFailed Char: a1b"); +is(CSS.escape('a2b'), 'a2b', "escapingFailed Char: a2b"); +is(CSS.escape('a3b'), 'a3b', "escapingFailed Char: a3b"); +is(CSS.escape('a4b'), 'a4b', "escapingFailed Char: a4b"); +is(CSS.escape('a5b'), 'a5b', "escapingFailed Char: a5b"); +is(CSS.escape('a6b'), 'a6b', "escapingFailed Char: a6b"); +is(CSS.escape('a7b'), 'a7b', "escapingFailed Char: a7b"); +is(CSS.escape('a8b'), 'a8b', "escapingFailed Char: a8b"); +is(CSS.escape('a9b'), 'a9b', "escapingFailed Char: a9b"); + +is(CSS.escape('-0a'), '-\\30 a', "escapingFailed Char: -0a"); +is(CSS.escape('-1a'), '-\\31 a', "escapingFailed Char: -1a"); +is(CSS.escape('-2a'), '-\\32 a', "escapingFailed Char: -2a"); +is(CSS.escape('-3a'), '-\\33 a', "escapingFailed Char: -3a"); +is(CSS.escape('-4a'), '-\\34 a', "escapingFailed Char: -4a"); +is(CSS.escape('-5a'), '-\\35 a', "escapingFailed Char: -5a"); +is(CSS.escape('-6a'), '-\\36 a', "escapingFailed Char: -6a"); +is(CSS.escape('-7a'), '-\\37 a', "escapingFailed Char: -7a"); +is(CSS.escape('-8a'), '-\\38 a', "escapingFailed Char: -8a"); +is(CSS.escape('-9a'), '-\\39 a', "escapingFailed Char: -9a"); + +is(CSS.escape('--a'), '--a', 'Should not need to escape leading "--"'); + +is(CSS.escape('\x80\x2D\x5F\xA9'), '\\80 \x2D\x5F\xA9', "escapingFailed Char: \\x80\\x2D\\x5F\\xA9"); +is(CSS.escape('\xA0\xA1\xA2'), '\xA0\xA1\xA2', "escapingFailed Char: \\xA0\\xA1\\xA2"); +is(CSS.escape('a0123456789b'), 'a0123456789b', "escapingFailed Char: a0123465789"); +is(CSS.escape('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz', "escapingFailed Char: abcdefghijklmnopqrstuvwxyz"); +is(CSS.escape('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', "escapingFailed Char: ABCDEFGHIJKLMNOPQRSTUVWXYZBCDEFGHIJKLMNOPQRSTUVWXYZ"); + +is(CSS.escape('\x20\x21\x78\x79'), '\\ \\!xy', "escapingFailed Char: \\x20\\x21\\x78\\x79"); + +// astral symbol (U+1D306 TETRAGRAM FOR CENTRE) +is(CSS.escape('\uD834\uDF06'), '\uD834\uDF06', "escapingFailed Char:\\uD834\\uDF06"); +// lone surrogates +is(CSS.escape('\uDF06'), '\uDF06', "escapingFailed Char: \\uDF06"); +is(CSS.escape('\uD834'), '\uD834', "escapingFailed Char: \\uD834"); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_css_function_mismatched_parenthesis.html b/layout/style/test/test_css_function_mismatched_parenthesis.html new file mode 100644 index 000000000..b28c88c86 --- /dev/null +++ b/layout/style/test/test_css_function_mismatched_parenthesis.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=897094 + +This test verifies that: +(1) Mismatched parentheses in a CSS function prevent parsing of subsequent CSS +properties. +(2) Properly matched parentheses do not prevent parsing of subsequent CSS +properties. +--> +<head> + <title>Test for Bug 897094</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=897094">Mozilla Bug 897094</a> +<p id="display"></p> +<div id="content" style="display: none"> + <div id="target"></div> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 897094 **/ +function check_parens(declaration, parens_are_balanced) +{ + var element = document.getElementById("target"); + element.setAttribute("style", + "background-color: " + (parens_are_balanced ? "red" : "green") + "; " + + declaration + "; " + + "background-color: " + (parens_are_balanced ? "green" : "red") + "; "); + var resultColor = element.style.getPropertyValue("background-color"); + is(resultColor, "green", "parenthesis balancing within " + declaration); +} + +check_parens("transform: scale()", true); +check_parens("transform: scale(", false); +check_parens("transform: scale(,)", true); +check_parens("transform: scale(,", false); +check_parens("transform: scale(1)", true); +check_parens("transform: scale(1", false); +check_parens("transform: scale(1,)", true); +check_parens("transform: scale(1,", false); +check_parens("transform: scale(1,1)", true); +check_parens("transform: scale(1,1", false); +check_parens("transform: scale(1,1,)", true); +check_parens("transform: scale(1,1,", false); +check_parens("transform: scale(1,1,1)", true); +check_parens("transform: scale(1,1,1", false); +check_parens("transform: scale(1,1,1,)", true); +check_parens("transform: scale(1,1,1,", false); +check_parens("transform: scale(1px)", true); +check_parens("transform: scale(1px", false); +check_parens("transform: scale(1px,)", true); +check_parens("transform: scale(1px,", false); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_css_loader_crossorigin_data_url.html b/layout/style/test/test_css_loader_crossorigin_data_url.html new file mode 100644 index 000000000..67105d61f --- /dev/null +++ b/layout/style/test/test_css_loader_crossorigin_data_url.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for handling of 'crossorigin' attribute on CSS link with data: URL</title> +<link id="testlink" crossorigin rel="stylesheet" href="data:text/css,%23someuniqueidhere{display:none}"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<div id="someuniqueidhere"></div> +<script> + var t = async_test("link@crossorigin with data: href"); + window.addEventListener("load", t.step_func_done(function() { + assert_equals(getComputedStyle(document.getElementById("someuniqueidhere")).display, + "none", "sheet should be applied"); + assert_equals(document.getElementById("testlink").sheet.cssRules[0].style.display, + "none", "should be able to read data from the sheet"); + })); +</script> diff --git a/layout/style/test/test_css_supports.html b/layout/style/test/test_css_supports.html new file mode 100644 index 000000000..fa5b3fdcb --- /dev/null +++ b/layout/style/test/test_css_supports.html @@ -0,0 +1,134 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=779917 +--> +<head> + <title>Test for Bug 779917</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=779917">Mozilla Bug 779917</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 779917 **/ + +function runTest() +{ + var passingConditions = [ + "(color: green)", + "((color: green))", + "(color: green !important)", + "(color: rainbow) or (color: green)", + "(color: green) or (color: rainbow)", + "(color: green) and (color: blue)", + "(color: rainbow) or (color: iridescent) or (color: green)", + "(color: red) and (color: green) and (color: blue)", + "(color:green)", + "not (color: rainbow)", + "not (not (color: green))", + "(unknown:) or (color: green)", + "(unknown) or (color: green)", + "(font: 16px serif)", + "(color:) or (color: green)", + "not (@page)", + "not ({ something @with [ balanced ] brackets })", + "an-extension(of some kind) or (color: green)", + "not ()", + "( Font: 20px serif ! Important) ", + "(color: /* comment */ green)", + "(/* comment */ color: green)", + "(color: green /* comment */)", + "(color: green) /* comment */", + "/* comment */ (color: green)", + "(color /* comment */: green)", + "(color: green) /* unclosed comment", + "(color: green", + "(((((((color: green", + "(font-family: 'Helvetica" + ]; + + var failingConditions = [ + "(color: rainbow)", + "(color: rainbow) and (color: green)", + "(color: blue) and (color: rainbow)", + "(color: green) and (color: green) or (color: green)", + "(color: green) or (color: green) and (color: green)", + "not not (color: green)", + "not (color: rainbow) and not (color: iridescent)", + "not (color: rainbow) or (color: green)", + "(not (color: rainbow) or (color: green))", + "(unknown: green)", + "not ({ something @with (unbalanced brackets })", + "(color: green) or an-extension(that is [unbalanced)", + "not(unknown: unknown)", + "(color: green) or(color: blue)", + "color: green", + "(color: green;)", + "(font-family: 'Helvetica\n", + "(font-family: 'Helvetica\n')", + "()", + "" + ]; + + var passingDeclarations = [ + ["color", "green"], + ["color", " green "], + ["Color", "Green"], + ["color", "green /* comment */"], + ["color", "/* comment */ green"], + ["color", "green /* unclosed comment"], + ["font", "16px serif"], + ["font", "16px /* comment */ serif"], + ["font", "16px\nserif"], + ["color", "\\0067reen"] + ]; + + var failingDeclarations = [ + ["color ", "green"], + ["color", "rainbow"], + ["color", "green green"], + ["color", "green !important"], + ["\\0063olor", "green"], + ["/* comment */color", "green"], + ["color/* comment */", "green"], + ["font-family", "'Helvetica\n"], + ["font-family", "'Helvetica\n'"], + ["color", "green;"], + ["color", ""], + ["unknown", "unknown"], + ["", "green"], + ["", ""] + ]; + + passingConditions.forEach(function(aCondition) { + is(CSS.supports(aCondition), true, "CSS.supports returns true for passing condition \"" + aCondition + "\""); + }); + + failingConditions.forEach(function(aCondition) { + is(CSS.supports(aCondition), false, "CSS.supports returns false for failing condition \"" + aCondition + "\""); + }); + + passingDeclarations.forEach(function(aDeclaration) { + is(CSS.supports(aDeclaration[0], aDeclaration[1]), true, "CSS.supports returns true for supported declaration \"" + aDeclaration.join(":") + "\""); + }); + + failingDeclarations.forEach(function(aDeclaration) { + is(CSS.supports(aDeclaration[0], aDeclaration[1]), false, "CSS.supports returns false for unsupported declaration \"" + aDeclaration.join(":") + "\""); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_css_supports_variables.html b/layout/style/test/test_css_supports_variables.html new file mode 100644 index 000000000..25618bdf6 --- /dev/null +++ b/layout/style/test/test_css_supports_variables.html @@ -0,0 +1,247 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=773296 +--> +<head> + <title>Test for Bug 773296</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=773296">Mozilla Bug 773296</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 773296 **/ + +function runTest() +{ + var passingConditions = [ + "(color:var(--a))", + "(color: var(--a))", + "(color: var(--a) )", + "(color: var( --a ) )", + "(color: var(--a, ))", + "(color: var(--))", + "(color: var(--a,/**/a))", + "(color: 1px var(--a))", + "(color: var(--a) 1px)", + "(color: something 3px url(whereever) calc(var(--a) + 1px))", + "(color: var(--a) !important)", + "(color: var(--a)var(--b))", + "(color: var(--a, var(--b, var(--c, black))))", + "(color: var(--a) <!--)", + "(color: --> var(--a))", + "(color: { [ var(--a) ] })", + "(color: [;] var(--a))", + "(color: var(--a,(;)))", + "(color: VAR(--a))", + "(color: var(--0))", + "(color: var(--\\30))", + "(color: var(--\\d800))", + "(color: var(--\\ffffff))", + "(color: var(--", + "(color: var(--a", + "(color: var(--a , ", + "(color: var(--a, ", + "(color: var(--a, var(--b", + "(color: var(--a /* unclosed comment", + "(color: var(--a, '", + "(color: var(--a, '\\", + "(color: var(--a, \\", + + "(--a:var(--b))", + "(--a: var(--b))", + "(--a: var(--b) )", + "(--a: var( --b ) )", + "(--a: var(--b, ))", + "(--a: var(--b,/**/a))", + "(--a: 1px var(--b))", + "(--a: var(--b) 1px)", + "(--a: something 3px url(whereever) calc(var(--b) + 1px))", + "(--a: var(--b) !important)", + "(--a: var(--b)var(--b))", + "(--a: var(--b, var(--c, var(--d, black))))", + "(--a: var(--b) <!--)", + "(--a: --> var(--b))", + "(--a: { [ var(--b) ] })", + "(--a: [;] var(--b))", + "(--a: )", + "(--a:var(--a))", + "(--0: a)", + "(--\\30: a)", + "(--\\61: a)", + "(--\\d800: a)", + "(--\\ffffff: a)", + "(--\0: 1)", + "(--a: ", + "(--a: /* unclosed comment", + "(--a: var(--b", + "(--a: var(--b, ", + "(--a: var(--b, var(--c", + "(--a: [{(((", + "(--a: '", + "(--a: '\\", + "(--a: \\", + "(--: a)", + ]; + + var failingConditions = [ + "(color: var(--a,))", + "(color: var(--a,/**/))", + "(color: var(--a,!))", + "(color: var(--a,!important))", + "(color: var(--a) !important !important)", + "(color: var(--a,;))", + "(color: var(--a);)", + "(color: var(1px))", + "(color: var(--a)))", + "(color: var(--a) \"\n", + "(color: var(--a) url(\"\n", + "(color: var(a))", + + "(--a: var(--b,))", + "(--a: var(--b,/**/))", + "(--a: var(--b,!))", + "(--a: var(--b,!important))", + "((--a: var(--b) !important !important))", + "(--a: var(--b,;))", + "(--a: var(--b);)", + "(--a:)", + "(--a: var(1px))", + "(--a: a))", + "(--a: \"\n", + "(--a: url(\"\n", + "(--a: var(a))", + ]; + + var passingDeclarations = [ + ["color", "var(--a)"], + ["color", " var(--a)"], + ["color", "var(--a) "], + ["color", "var( --a ) "], + ["color", "var(--a, )"], + ["color", "var(--a,/**/a)"], + ["color", "1px var(--a)"], + ["color", "var(--a) 1px"], + ["color", "something 3px url(whereever) calc(var(--a) + 1px)"], + ["color", "var(--a)var(--b)"], + ["color", "var(--a, var(--b, var(--c, black)))"], + ["color", "var(--a) <!--"], + ["color", "--> var(--a)"], + ["color", "{ [ var(--a) ] }"], + ["color", "[;] var(--a)"], + ["color", "var(--a,(;))"], + ["color", "VAR(--a)"], + ["color", "var(--0)"], + ["color", "var(--\\30)"], + ["color", "var(--\\d800)"], + ["color", "var(--\\ffffff)"], + ["color", "var(--a"], + ["color", "var(--a , "], + ["color", "var(--a, "], + ["color", "var(--a, var(--b"], + ["color", "var(--a /* unclosed comment"], + ["color", "var(--a, '"], + ["color", "var(--a, '\\"], + ["color", "var(--a, \\"], + ["color", "var(--"], + + ["--a", " var(--b)"], + ["--a", "var(--b)"], + ["--a", "var(--b) "], + ["--a", "var( --b ) "], + ["--a", "var(--b, )"], + ["--a", "var(--b,/**/a)"], + ["--a", "1px var(--b)"], + ["--a", "var(--b) 1px"], + ["--a", "something 3px url(whereever) calc(var(--b) + 1px)"], + ["--a", "var(--b)var(--b)"], + ["--a", "var(--b, var(--c, var(--d, black)))"], + ["--a", "var(--b) <!--"], + ["--a", "--> var(--b)"], + ["--a", "{ [ var(--b) ] }"], + ["--a", "[;] var(--b)"], + ["--a", " "], + ["--a", "var(--a)"], + ["--0", "a"], + ["--\\30", "a"], + ["--\\61", "a"], + ["--\\d800", "a"], + ["--\\ffffff", "a"], + ["--\0", "a"], + ["--\ud800", "a"], + ["--a", "a /* unclosed comment"], + ["--a", "var(--b"], + ["--a", "var(--b, "], + ["--a", "var(--b, var(--c"], + ["--a", "[{((("], + ["--a ", "a"], + ["--a ", "'"], + ["--a ", "'\\"], + ["--a ", "\\"], + ["--", "a"], + ]; + + var failingDeclarations = [ + ["color", "var(--a,)"], + ["color", "var(--a,/**/)"], + ["color", "var(--a,!)"], + ["color", "var(--a,!important)"], + ["color", "var(--a,;)"], + ["color", "var(--a);"], + ["color", "var(1px)"], + ["color", "var(--a))"], + ["color", "var(--a) \"\n"], + ["color", "var(--a) url(\"\n"], + ["color", "var(--a) !important"], + ["color", "var(--a) !important !important"], + ["color", "var(a)"], + + ["--a", "var(--b,)"], + ["--a", "var(--b,/**/)"], + ["--a", "var(--b,!)"], + ["--a", "var(--b,!important)"], + ["--a", "var(--b) !important !important"], + ["--a", "var(--b,;)"], + ["--a", "var(--b);"], + ["--a", ""], + ["--a", "var(1px)"], + ["(VAR-a", "a"], + ["--a", "a)"], + ["--a", "\"\n"], + ["--a", "url(\"\n"], + ["--a", "var(--b))"], + ["--a", "var(b)"], + ]; + + passingConditions.forEach(function(aCondition) { + is(CSS.supports(aCondition), true, "CSS.supports returns true for passing condition \"" + aCondition + "\""); + }); + + failingConditions.forEach(function(aCondition) { + is(CSS.supports(aCondition), false, "CSS.supports returns false for failing condition \"" + aCondition + "\""); + }); + + passingDeclarations.forEach(function(aDeclaration) { + is(CSS.supports(aDeclaration[0], aDeclaration[1]), true, "CSS.supports returns true for supported declaration \"" + aDeclaration.join(":") + "\""); + }); + + failingDeclarations.forEach(function(aDeclaration) { + is(CSS.supports(aDeclaration[0], aDeclaration[1]), false, "CSS.supports returns false for unsupported declaration \"" + aDeclaration.join(":") + "\""); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ "set": [["layout.css.variables.enabled", true]] }, runTest); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_csslexer.js b/layout/style/test/test_csslexer.js new file mode 100644 index 000000000..a71c02d8f --- /dev/null +++ b/layout/style/test/test_csslexer.js @@ -0,0 +1,171 @@ +/* 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/. + */ + +function test_lexer(domutils, cssText, tokenTypes) { + let lexer = domutils.getCSSLexer(cssText); + let reconstructed = ''; + let lastTokenEnd = 0; + let i = 0; + while (true) { + let token = lexer.nextToken(); + if (!token) { + break; + } + let combined = token.tokenType; + if (token.text) + combined += ":" + token.text; + equal(combined, tokenTypes[i]); + ok(token.endOffset > token.startOffset); + equal(token.startOffset, lastTokenEnd); + lastTokenEnd = token.endOffset; + reconstructed += cssText.substring(token.startOffset, token.endOffset); + ++i; + } + // Ensure that we saw the correct number of tokens. + equal(i, tokenTypes.length); + // Ensure that the reported offsets cover all the text. + equal(reconstructed, cssText); +} + +var LEX_TESTS = [ + ["simple", ["ident:simple"]], + ["simple: { hi; }", + ["ident:simple", "symbol::", + "whitespace", "symbol:{", + "whitespace", "ident:hi", + "symbol:;", "whitespace", + "symbol:}"]], + ["/* whatever */", ["comment"]], + ["'string'", ["string:string"]], + ['"string"', ["string:string"]], + ["rgb(1,2,3)", ["function:rgb", "number", + "symbol:,", "number", + "symbol:,", "number", + "symbol:)"]], + ["@media", ["at:media"]], + ["#hibob", ["id:hibob"]], + ["#123", ["hash:123"]], + ["23px", ["dimension:px"]], + ["23%", ["percentage"]], + ["url(http://example.com)", ["url:http://example.com"]], + ["url('http://example.com')", ["url:http://example.com"]], + ["url( 'http://example.com' )", + ["url:http://example.com"]], + // In CSS Level 3, this is an ordinary URL, not a BAD_URL. + ["url(http://example.com", ["url:http://example.com"]], + // See bug 1153981 to understand why this gets a SYMBOL token. + ["url(http://example.com @", ["bad_url:http://example.com", "symbol:@"]], + ["quo\\ting", ["ident:quoting"]], + ["'bad string\n", ["bad_string:bad string", "whitespace"]], + ["~=", ["includes"]], + ["|=", ["dashmatch"]], + ["^=", ["beginsmatch"]], + ["$=", ["endsmatch"]], + ["*=", ["containsmatch"]], + + // URANGE may be on the way out, and it isn't used by devutils, so + // let's skip it. + + ["<!-- html comment -->", ["htmlcomment", "whitespace", "ident:html", + "whitespace", "ident:comment", "whitespace", + "htmlcomment"]], + + // earlier versions of CSS had "bad comment" tokens, but in level 3, + // unterminated comments are just comments. + ["/* bad comment", ["comment"]] +]; + +function test_lexer_linecol(domutils, cssText, locations) { + let lexer = domutils.getCSSLexer(cssText); + let i = 0; + while (true) { + let token = lexer.nextToken(); + let startLine = lexer.lineNumber; + let startColumn = lexer.columnNumber; + + // We do this in a bit of a funny way so that we can also test the + // location of the EOF. + let combined = ":" + startLine + ":" + startColumn; + if (token) + combined = token.tokenType + combined; + + equal(combined, locations[i]); + ++i; + + if (!token) { + break; + } + } + // Ensure that we saw the correct number of tokens. + equal(i, locations.length); +} + +function test_lexer_eofchar(domutils, cssText, argText, expectedAppend, + expectedNoAppend) { + let lexer = domutils.getCSSLexer(cssText); + while (lexer.nextToken()) { + // Nothing. + } + + do_print("EOF char test, input = " + cssText); + + let result = lexer.performEOFFixup(argText, true); + equal(result, expectedAppend); + + result = lexer.performEOFFixup(argText, false); + equal(result, expectedNoAppend); +} + +var LINECOL_TESTS = [ + ["simple", ["ident:0:0", ":0:6"]], + ["\n stuff", ["whitespace:0:0", "ident:1:4", ":1:9"]], + ['"string with \\\nnewline" \r\n', ["string:0:0", "whitespace:1:8", + ":2:0"]] +]; + +var EOFCHAR_TESTS = [ + ["hello", "hello"], + ["hello \\", "hello \\\\", "hello \\\uFFFD"], + ["'hello", "'hello'"], + ["\"hello", "\"hello\""], + ["'hello\\", "'hello\\\\'", "'hello'"], + ["\"hello\\", "\"hello\\\\\"", "\"hello\""], + ["/*hello", "/*hello*/"], + ["/*hello*", "/*hello*/"], + ["/*hello\\", "/*hello\\*/"], + ["url(hello", "url(hello)"], + ["url('hello", "url('hello')"], + ["url(\"hello", "url(\"hello\")"], + ["url(hello\\", "url(hello\\\\)", "url(hello\\\uFFFD)"], + ["url('hello\\", "url('hello\\\\')", "url('hello')"], + ["url(\"hello\\", "url(\"hello\\\\\")", "url(\"hello\")"], +]; + +function run_test() +{ + let domutils = Components.classes["@mozilla.org/inspector/dom-utils;1"] + .getService(Components.interfaces.inIDOMUtils); + + let text, result; + for ([text, result] of LEX_TESTS) { + test_lexer(domutils, text, result); + } + + for ([text, result] of LINECOL_TESTS) { + test_lexer_linecol(domutils, text, result); + } + + for ([text, expectedAppend, expectedNoAppend] of EOFCHAR_TESTS) { + if (!expectedNoAppend) { + expectedNoAppend = expectedAppend; + } + test_lexer_eofchar(domutils, text, text, expectedAppend, expectedNoAppend); + } + + // Ensure that passing a different inputString to performEOFFixup + // doesn't cause an assertion trying to strip a backslash from the + // end of an empty string. + test_lexer_eofchar(domutils, "'\\", "", "\\'", "'"); +} diff --git a/layout/style/test/test_default_bidi_css.html b/layout/style/test/test_default_bidi_css.html new file mode 100644 index 000000000..bb4db8653 --- /dev/null +++ b/layout/style/test/test_default_bidi_css.html @@ -0,0 +1,79 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for default bidi css **/ +function styleOf(name, attributes) { + var element = document.createElement(name); + for (var name in attributes) { + var value = attributes[name]; + element.setAttribute(name, value); + } + return getComputedStyle(element); +} + +var tests = [ + ['div', {}, 'ltr', 'isolate'], + ['div', {'dir': 'ltr'}, 'ltr', 'isolate'], + ['div', {'dir': 'rtl'}, 'rtl', 'isolate'], + ['div', {'dir': 'auto'}, 'ltr', 'isolate'], + ['div', {'dir': ''}, 'ltr', 'isolate'], + + ['span', {}, 'ltr', 'normal'], + ['span', {'dir': 'ltr'}, 'ltr', 'isolate'], + ['span', {'dir': 'rtl'}, 'rtl', 'isolate'], + ['span', {'dir': 'auto'}, 'ltr', 'isolate'], + ['span', {'dir': ''}, 'ltr', 'isolate'], + + ['bdi', {}, 'ltr', 'isolate'], + ['bdi', {'dir': 'ltr'}, 'ltr', 'isolate'], + ['bdi', {'dir': 'rtl'}, 'rtl', 'isolate'], + ['bdi', {'dir': 'auto'}, 'ltr', 'isolate'], + ['bdi', {'dir': ''}, 'ltr', 'isolate'], + + ['output', {}, 'ltr', 'isolate'], + ['output', {'dir': 'ltr'}, 'ltr', 'isolate'], + ['output', {'dir': 'rtl'}, 'rtl', 'isolate'], + ['output', {'dir': 'auto'}, 'ltr', 'isolate'], + ['output', {'dir': ''}, 'ltr', 'isolate'], + + ['bdo', {}, 'ltr', 'isolate-override'], + ['bdo', {'dir': 'ltr'}, 'ltr', 'isolate-override'], + ['bdo', {'dir': 'rtl'}, 'rtl', 'isolate-override'], + ['bdo', {'dir': 'auto'}, 'ltr', 'isolate-override'], + ['bdo', {'dir': ''}, 'ltr', 'isolate-override'], + + ['textarea', {}, 'ltr', 'normal'], + ['textarea', {'dir': 'ltr'}, 'ltr', 'isolate'], + ['textarea', {'dir': 'rtl'}, 'rtl', 'isolate'], + ['textarea', {'dir': 'auto'}, 'ltr', 'plaintext'], + ['textarea', {'dir': ''}, 'ltr', 'isolate'], + + ['pre', {}, 'ltr', 'isolate'], + ['pre', {'dir': 'ltr'}, 'ltr', 'isolate'], + ['pre', {'dir': 'rtl'}, 'rtl', 'isolate'], + ['pre', {'dir': 'auto'}, 'ltr', 'plaintext'], + ['pre', {'dir': ''}, 'ltr', 'isolate'], +].forEach(function (test) { + var style = styleOf(test[0], test[1]); + is(style.direction, test[2], "default value for direction"); + is(style.unicodeBidi, test[3], "default value for unicode-bidi"); +}); + + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_default_computed_style.html b/layout/style/test/test_default_computed_style.html new file mode 100644 index 000000000..60f3dcab0 --- /dev/null +++ b/layout/style/test/test_default_computed_style.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=800983 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 800983</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + #display::before { content: "Visible"; display: block } + #display { + display: inline; + margin-top: 0; + background: yellow; + color: blue; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=800983">Mozilla Bug 800983</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 800983 **/ +var cs = getComputedStyle($("display")); +var cs_pseudo = getComputedStyle($("display"), "::before") + +var cs_default = getDefaultComputedStyle($("display")); +var cs_default_pseudo = getDefaultComputedStyle($("display"), "::before"); + +// Sanity checks for normal computed style +is(cs.display, "inline", "We have inline display"); +is(cs.marginTop, "0px", "We have 0 margin"); +is(cs.backgroundColor, "rgb(255, 255, 0)", "We have yellow background"); +is(cs.color, "rgb(0, 0, 255)", "We have blue text"); +is(cs_pseudo.content, '"Visible"', "We have some content"); +is(cs_pseudo.display, "block", "Our ::before is block"); + +// And now our actual tests +is(cs_default.display, "block", "We have block display by default"); +is(cs_default.marginTop, "16px", "We have 16px margin by default"); +is(cs_default.backgroundColor, "transparent", + "We have transparent background by default"); +is(cs_default.color, "rgb(0, 0, 0)", "We have black text by default"); +is(cs_default_pseudo.content, "none", "We have no content by default"); +is(cs_default_pseudo.display, "inline", "Our ::before is inline by default"); + + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_descriptor_storage.html b/layout/style/test/test_descriptor_storage.html new file mode 100644 index 000000000..50017f642 --- /dev/null +++ b/layout/style/test/test_descriptor_storage.html @@ -0,0 +1,119 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for parsing, storage, and serialization of CSS @font-face descriptor values</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="descriptor_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for parsing, storage, and serialization of CSS @font-face descriptor values **/ + +/* + * For explanation of some of the more interesting tests here, see the comment + * in test_value_storage.html . + */ + +var gStyleElement = document.createElement("style"); +gStyleElement.setAttribute("type", "text/css"); +document.getElementsByTagName("head")[0].appendChild(gStyleElement); +var gSheet = gStyleElement.sheet; +gSheet.insertRule("@font-face { }", 0); +var gRule = gSheet.cssRules[0]; +var gDeclaration = gRule.style; + +function fake_set_property(descriptor, value) { + gSheet.deleteRule(0); + gSheet.insertRule("@font-face { " + descriptor + ": " + value + "}", 0); + gRule = gSheet.cssRules[0]; + gDeclaration = gRule.style; +} + +function xfail_parse(descriptor, value) { + switch (descriptor) { + case "src": + // not clear whether this is an error or not, so mark todo for now + return value == "local(serif)"; + } + return false; +} + +function test_descriptor(descriptor) +{ + var info = gCSSFontFaceDescriptors[descriptor]; + + function test_value(value) { +// // We don't implement SetProperty yet (bug 443978). +// gDeclaration.setProperty(descriptor, value, ""); + fake_set_property(descriptor, value); + + var idx; + + var step1val = gDeclaration.getPropertyValue(descriptor); + var step1ser = gDeclaration.cssText; + + var func = xfail_parse(descriptor, value) ? todo_isnot : isnot; + func(step1val, "", "setting '" + value + "' on '" + descriptor + "'"); + + // We don't care particularly about the whitespace or the placement of + // semicolons, but for simplicity we'll test the current behavior. + var expected_serialization = ""; + if (step1val != "") + expected_serialization = " " + descriptor + ": " + step1val + ";\n"; + is(step1ser, expected_serialization, + "serialization should match descriptor value"); + + gDeclaration.removeProperty(descriptor); +// // We don't implement SetProperty yet (bug 443978). +// gDeclaration.setProperty(descriptor, step1val, ""); + fake_set_property(descriptor, step1val); + + is(gDeclaration.getPropertyValue(descriptor), step1val, + "parse+serialize should be idempotent for '" + + descriptor + ": " + value + "'"); + + gDeclaration.removeProperty(descriptor); + } + + var idx; + for (idx in info.values) + test_value(info.values[idx]); +} + +// To avoid triggering the slow script dialog, we have to test one +// descriptor at a time. +SimpleTest.waitForExplicitFinish(); +function runTest() { + var descs = []; + for (var desc in gCSSFontFaceDescriptors) + descs.push(desc); + descs = descs.reverse(); + function do_one() { + if (descs.length == 0) { + SimpleTest.finish(); + return; + } + test_descriptor(descs.pop()); + SimpleTest.executeSoon(do_one); + } + SimpleTest.executeSoon(do_one); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(5); + +SpecialPowers.pushPrefEnv({ set: [["layout.css.font-display.enabled", true]] }, + runTest); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_descriptor_syntax_errors.html b/layout/style/test/test_descriptor_syntax_errors.html new file mode 100644 index 000000000..952625c92 --- /dev/null +++ b/layout/style/test/test_descriptor_syntax_errors.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test that we reject syntax errors listed in descriptor_database.js</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="descriptor_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gStyleElement = document.createElement("style"); +gStyleElement.setAttribute("type", "text/css"); +document.getElementsByTagName("head")[0].appendChild(gStyleElement); +var gSheet = gStyleElement.sheet; +gSheet.insertRule("@font-face { }", 0); +var gRule = gSheet.cssRules[0]; +var gDeclaration = gRule.style; + +function fake_set_property(descriptor, value) { + gSheet.deleteRule(0); + gSheet.insertRule("@font-face { " + descriptor + ": " + value + "}", 0); + gRule = gSheet.cssRules[0]; + gDeclaration = gRule.style; +} + +for (var descriptor in gCSSFontFaceDescriptors) { + var info = gCSSFontFaceDescriptors[descriptor]; + for (var idx in info.invalid_values) { + var badval = info.invalid_values[idx]; + +// // We don't implement SetProperty yet (bug 443978). +// gDeclaration.setProperty(descriptor, badval, ""); + fake_set_property(descriptor, badval); + + is(gDeclaration.getPropertyValue(descriptor), "", + "invalid value '" + badval + "' not accepted for '" + descriptor + + "' descriptor"); + + gDeclaration.removeProperty(descriptor); + } +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_dont_use_document_colors.html b/layout/style/test/test_dont_use_document_colors.html new file mode 100644 index 000000000..4ea47c88b --- /dev/null +++ b/layout/style/test/test_dont_use_document_colors.html @@ -0,0 +1,186 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for preference not to use document colors</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + #one, #three { background: blue; color: yellow; border: thin solid red; -moz-column-rule: 2px solid green; text-shadow: 2px 2px green; box-shadow: 3px 7px blue; } + #two { background: transparent; border: thin solid; } + #five, #six {border: thick solid red; border-inline-start-color:green; border-inline-end-color:blue} + #seven { + border: 3px solid; + -moz-border-top-colors: blue aqua fuchsia; + -moz-border-right-colors: aqua blue fuchsia; + -moz-border-bottom-colors: blue fuchsia aqua; + -moz-border-left-colors: fuchsia blue blue; + } + + /* XXX also test rgba() */ + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=58048">Mozilla Bug 58048</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=255411">Mozilla Bug 255411</a> +<div id="display"> + +<div id="one">Hello</div> +<div id="two">Hello</div> +<input id="three" type="button" value="Hello"> +<input id="four" type="button" value="Hello"> +<div id="five" dir="ltr">Hello</div> +<div id="six" dir="rtl">Hello</div> +<div id="seven">Hello</div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var cs1 = getComputedStyle(document.getElementById("one"), ""); +var cs2 = getComputedStyle(document.getElementById("two"), ""); +var cs3 = getComputedStyle(document.getElementById("three"), ""); +var cs4 = getComputedStyle(document.getElementById("four"), ""); +var cs5 = getComputedStyle(document.getElementById("five"), ""); +var cs6 = getComputedStyle(document.getElementById("six"), ""); +var cs7 = getComputedStyle(document.getElementById("seven"), ""); + +SpecialPowers.pushPrefEnv({'set': [['browser.display.document_color_use', 1]]}, part1); + +var transparentBackgroundColor; +var inputBackgroundColor, inputColor, inputBorderTopColor; +var inputBorderRightColor, inputBorderLeftColor, inputBorderBottomColor; + +function part1() +{ + + isnot(cs1.backgroundColor, cs2.backgroundColor, "background-color applies"); + isnot(cs1.color, cs2.color, "color applies"); + isnot(cs1.borderTopColor, cs2.borderTopColor, "border-top-color applies"); + isnot(cs1.borderRightColor, cs2.borderRightColor, + "border-right-color applies"); + isnot(cs1.borderLeftColor, cs2.borderLeftColor, + "border-left-color applies"); + isnot(cs1.borderBottomColor, cs2.borderBottomColor, + "border-top-color applies"); + isnot(cs1.MozColumnRuleColor, cs2.MozColumnRuleColor, + "-moz-column-rule-color applies"); + isnot(cs1.textShadow, cs2.textShadow, + "text-shadow applies"); + isnot(cs1.boxShadow, cs2.boxShadow, + "box-shadow applies"); + is(cs1.borderTopColor, cs3.borderTopColor, "border-top-color applies"); + is(cs1.borderRightColor, cs3.borderRightColor, + "border-right-color applies"); + is(cs1.borderLeftColor, cs3.borderLeftColor, + "border-left-color applies"); + is(cs1.borderBottomColor, cs3.borderBottomColor, + "border-top-color applies"); + is(cs1.MozColumnRuleColor, cs3.MozColumnRuleColor, + "-moz-column-rule-color applies"); + is(cs1.textShadow, cs3.textShadow, + "text-shadow applies"); + is(cs1.boxShadow, cs3.boxShadow, + "box-shadow applies"); + isnot(cs5.borderRightColor, cs2.borderRightColor, + "border-inline-end-color applies"); + isnot(cs5.borderLeftColor, cs2.borderLeftColor, + "border-inline-start-color applies"); + isnot(cs6.borderRightColor, cs2.borderRightColor, + "border-inline-start-color applies"); + isnot(cs6.borderLeftColor, cs2.borderLeftColor, + "border-inline-end-color applies"); + isnot(cs7.MozBorderTopColors, cs2.MozBorderTopColors, + "-moz-border-top-colors applies"); + isnot(cs7.MozBorderRightColors, cs2.MozBorderRightColors, + "-moz-border-right-colors applies"); + isnot(cs7.MozBorderBottomColors, cs2.MozBorderBottomColors, + "-moz-border-bottom-colors applies"); + isnot(cs7.MozBorderLeftColors, cs2.MozBorderLeftColors, + "-moz-border-left-colors applies"); + is(cs1.color, cs3.color, "color applies"); + is(cs1.backgroundColor, cs3.backgroundColor, "background-color applies"); + isnot(cs3.backgroundColor, cs4.backgroundColor, "background-color applies"); + isnot(cs3.color, cs4.color, "color applies"); + isnot(cs3.borderTopColor, cs4.borderTopColor, "border-top-color applies"); + isnot(cs3.borderRightColor, cs4.borderRightColor, + "border-right-color applies"); + isnot(cs3.borderLeftColor, cs4.borderLeftColor, + "border-left-color applies"); + isnot(cs3.borderBottomColor, cs4.borderBottomColor, + "border-bottom-color applies"); + transparentBackgroundColor = cs2.backgroundColor; + inputBackgroundColor = cs4.backgroundColor; + inputColor = cs4.color; + inputBorderTopColor = cs4.borderTopColor; + inputBorderRightColor = cs4.borderRightColor; + inputBorderLeftColor = cs4.borderLeftColor; + inputBorderBottomColor = cs4.borderBottomColor; + SpecialPowers.pushPrefEnv({'set': [['browser.display.document_color_use', 2]]}, part2); +} + +function part2() +{ + isnot(cs1.backgroundColor, cs2.backgroundColor, "background-color transparency preserved (opaque)"); + is(cs2.backgroundColor, transparentBackgroundColor, "background-color transparency is preserved (transparent)"); + is(cs1.color, cs2.color, "color is blocked"); + is(cs1.borderTopColor, cs2.borderTopColor, "border-top-color is blocked"); + is(cs1.borderRightColor, cs2.borderRightColor, + "border-right-color is blocked"); + is(cs1.borderLeftColor, cs2.borderLeftColor, + "border-left-color is blocked"); + is(cs5.borderRightColor, cs2.borderRightColor, + "border-inline-end-color is blocked"); + is(cs5.borderLeftColor, cs2.borderLeftColor, + "border-inline-start-color is blocked"); + is(cs6.borderRightColor, cs2.borderRightColor, + "border-inline-start-color is blocked"); + is(cs6.borderLeftColor, cs2.borderLeftColor, + "border-inline-end-color is blocked"); + is(cs7.MozBorderTopColors, cs2.MozBorderTopColors, + "-moz-border-top-colors is blocked"); + is(cs7.MozBorderRightColors, cs2.MozBorderRightColors, + "-moz-border-right-colors is blocked"); + is(cs7.MozBorderBottomColors, cs2.MozBorderBottomColors, + "-moz-border-bottom-colors is blocked"); + is(cs7.MozBorderLeftColors, cs2.MozBorderLeftColors, + "-moz-border-left-colors is blocked"); + is(cs1.borderBottomColor, cs2.borderBottomColor, + "border-bottom-color is blocked"); + is(cs1.MozColumnRuleColor, cs2.MozColumnRuleColor, + "-moz-column-rule-color is blocked"); + is(cs1.textShadow, cs2.textShadow, + "text-shadow is blocked"); + is(cs1.boxShadow, cs2.boxShadow, + "box-shadow is blocked"); + is(cs3.backgroundColor, cs1.backgroundColor, "background-color transparency preserved (opaque)"); + is(cs3.color, cs4.color, "color is blocked"); + is(cs3.borderTopColor, cs4.borderTopColor, "border-top-color is blocked"); + is(cs3.borderRightColor, cs4.borderRightColor, + "border-right-color is blocked"); + is(cs3.borderLeftColor, cs4.borderLeftColor, + "border-left-color is blocked"); + is(cs3.borderBottomColor, cs4.borderBottomColor, + "border-bottom-color is blocked"); + is(cs4.backgroundColor, inputBackgroundColor, "background-color not broken on inputs"); + is(cs4.color, inputColor, "color not broken on inputs"); + is(cs4.borderTopColor, inputBorderTopColor, "border-top-color not broken on inputs"); + is(cs4.borderRightColor, inputBorderRightColor, + "border-right-color not broken on inputs"); + is(cs4.borderLeftColor, inputBorderLeftColor, + "border-left-color not broken on inputs"); + is(cs4.borderBottomColor, inputBorderBottomColor, + "border-bottom-color not broken on inputs"); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_dynamic_change_causing_reflow.html b/layout/style/test/test_dynamic_change_causing_reflow.html new file mode 100644 index 000000000..a941191f6 --- /dev/null +++ b/layout/style/test/test_dynamic_change_causing_reflow.html @@ -0,0 +1,173 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1131371 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1131371</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1131371">Mozilla Bug 1131371</a> +<div id="display"> + <div id="content"> + </div> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; + +/** Test for Bug 1131371 **/ + +/** + * This test verifies that certain style changes do or don't cause reflow + * and/or frame construction. We do this by checking the framesReflowed & + * framesConstructed counts, before & after a style-change, and verifying + * that any change to these counts is in line with our expectations. + * + * Each entry in gTestcases contains these member-values: + * - beforeStyle (optional): initial value to use for "style" attribute. + * - afterStyle: value to change the "style" attribute to. + * + * Testcases may also include two optional member-values to express that reflow + * and/or frame construction *are* in fact expected: + * - expectConstruction (optional): if set to something truthy, then we expect + * frame construction to occur when afterStyle is set. Otherwise, we + * expect that frame construction should *not* occur. + * - expectReflow (optional): if set to something truthy, then we expect + * reflow to occur when afterStyle is set. Otherwise, we expect that + * reflow should *not* occur. + */ +const gTestcases = [ + // Things that shouldn't cause reflow: + // ----------------------------------- + // * Adding an outline (e.g. for focus ring). + { + afterStyle: "outline: 1px dotted black", + }, + + // * Changing between completely different outlines. + { + beforeStyle: "outline: 2px solid black", + afterStyle: "outline: 6px dashed yellow", + }, + + // * Adding a box-shadow. + { + afterStyle: "box-shadow: inset 3px 3px gray", + }, + { + afterStyle: "box-shadow: 0px 0px 10px 30px blue" + }, + + // * Changing between completely different box-shadow values, + // e.g. from an upper-left shadow to a bottom-right shadow: + { + beforeStyle: "box-shadow: -15px -20px teal", + afterStyle: "box-shadow: 30px 40px yellow", + }, + + // * Adding a text-shadow. + { + afterStyle: "text-shadow: 3px 3px gray", + }, + { + afterStyle: "text-shadow: 0px 0px 10px blue" + }, + + // * Changing between completely different text-shadow values, + // e.g. from an upper-left shadow to a bottom-right shadow: + { + beforeStyle: "text-shadow: -15px -20px teal", + afterStyle: "text-shadow: 30px 40px yellow", + }, + + // Things that *should* cause reflow: + // ---------------------------------- + // (e.g. to make sure our counts are actually measuring something) + + // * Changing 'height' should cause reflow, but not frame construction. + { + beforeStyle: "height: 10px", + afterStyle: "height: 15px", + expectReflow: true, + }, + + // * Changing 'display' should cause frame construction and reflow. + { + beforeStyle: "display: inline", + afterStyle: "display: table", + expectConstruction: true, + expectReflow: true, + }, + +]; + +// Helper function to let us call either "is" or "isnot" & assemble +// the failure message, based on the provided parameters. +function checkFinalCount(aFinalCount, aExpectedCount, + aExpectChange, aMsgPrefix, aCountDescription) +{ + let compareFunc; + let msg = aMsgPrefix; + if (aExpectChange) { + compareFunc = isnot; + msg += "should cause " + aCountDescription; + } else { + compareFunc = is; + msg += "should not cause " + aCountDescription; + } + + compareFunc(aFinalCount, aExpectedCount, msg); +} + +// Vars used in runOneTest that we really only have to look up once: +const gUtils = SpecialPowers.getDOMWindowUtils(window); +const gElem = document.getElementById("content"); + +function runOneTest(aTestcase) +{ + // sanity-check that we have the one main thing we need: + if (!aTestcase.afterStyle) { + ok(false, "testcase is missing an 'afterStyle' to change to"); + return; + } + + // Set the "before" style, and compose the first part of the message + // to be used in our "is"/"isnot" invocations: + let msgPrefix = "Changing style "; + if (aTestcase.beforeStyle) { + gElem.setAttribute("style", aTestcase.beforeStyle); + msgPrefix += "from '" + aTestcase.beforeStyle + "' "; + } + msgPrefix += "to '" + aTestcase.afterStyle + "' "; + + // Establish initial counts: + let unusedVal = gElem.offsetHeight; // flush layout + let origFramesConstructed = gUtils.framesConstructed; + let origFramesReflowed = gUtils.framesReflowed; + + // Make the change and flush: + gElem.setAttribute("style", aTestcase.afterStyle); + unusedVal = gElem.offsetHeight; // flush layout + + // Make our is/isnot assertions about whether things should have changed: + checkFinalCount(gUtils.framesConstructed, origFramesConstructed, + aTestcase.expectConstruction, msgPrefix, + "frame construction"); + checkFinalCount(gUtils.framesReflowed, origFramesReflowed, + aTestcase.expectReflow, msgPrefix, + "reflow"); + + // Clean up! + gElem.removeAttribute("style"); +} + +gTestcases.forEach(runOneTest); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_exposed_prop_accessors.html b/layout/style/test/test_exposed_prop_accessors.html new file mode 100644 index 000000000..937afa7b8 --- /dev/null +++ b/layout/style/test/test_exposed_prop_accessors.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=375363 +--> +<head> + <title>Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset')</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** + * Test that makes sure that we have exposed getters/setters for all the + * various variants of our CSS property names that the spec calls for. + */ +for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + + var s = document.createElement("div").style; + + is(s[info.domProp], "", prop + " should not be set yet"); + s[info.domProp] = info.initial_values[0]; + isnot(s[info.domProp], "", prop + " should now be set"); + is(s[prop], s[info.domProp], + "Getting " + prop + " via name should work") + s = document.createElement("div").style; + is(s[info.domProp], "", prop + " should not be set here either"); + s[prop] = info.initial_values[0]; + isnot(s[info.prop], "", prop + " should now be set again"); + is(s[info.domProp], s[prop], + "Setting " + prop + " via name should work"); +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_extra_inherit_initial.html b/layout/style/test/test_extra_inherit_initial.html new file mode 100644 index 000000000..8a94a0515 --- /dev/null +++ b/layout/style/test/test_extra_inherit_initial.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=940229 +--> +<head> + <title>Test handling extra inherit/initial/unset in CSS declarations (Bug 940229)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=940229">Mozilla Bug 940229</a> +<p id="display"></p> +<div id="content" style="display: none"> + +<div id="testnode"></div> + +</div> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + +/* + * Inspired by mistake in quotes noticed while reviewing bug 189519. + */ + +let gPropsNeedComma = { + "font": true, + "font-family": true, + "voice-family": true, +}; + +let gElement = document.getElementById("testnode"); +let gDeclaration = gElement.style; + +let kValuesToTestThoroughly = 3; + +function test_property(property) +{ + let info = gCSSProperties[property]; + + let delim = (property in gPropsNeedComma) ? ", " : " "; + + function test_value_pair(relation, val1, val2, extraval) { + let decl = property + ": " + val1 + delim + val2; + gElement.setAttribute("style", decl); + if ("subproperties" in info) { + // Shorthand property; inspect each subproperty value. + for (let subprop of info.subproperties) { + is(gDeclaration.getPropertyValue(subprop), "", + ["expected", extraval, "ignored", relation, "value in", + "'" + decl + "'", "when looking at subproperty", + "'" + subprop + "'"].join(" ")); + } + } else { + // Longhand property. + is(gDeclaration.getPropertyValue(property), "", + ["expected", extraval, "ignored", relation, "value in", + "'" + decl + "'"].join(" ")); + } + } + + function test_value(value, valueIdx) { + let specialKeywords = [ "inherit", "initial", "unset" ]; + + if (valueIdx < kValuesToTestThoroughly) { + // For the first few values, we test each special-keyword both before + // and after the value. + for (let keyword of specialKeywords) { + test_value_pair("before", keyword, value, keyword); + test_value_pair("after", value, keyword, keyword); + } + } else { + // For later values, only test one keyword before & after it. + let keywordIdx = + (valueIdx - kValuesToTestThoroughly) % specialKeywords.length; + keyword = specialKeywords[keywordIdx]; + test_value_pair("before", keyword, value, keyword); + test_value_pair("after", value, keyword, keyword); + } + } + + for (let idx in info.initial_values) { + test_value(info.initial_values[idx], idx); + } + for (let idx in info.other_values) { + test_value(info.initial_values[idx], idx); + } +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(4); + +function start_test() { + for (let prop in gCSSProperties) { + test_property(prop); + } + SimpleTest.finish(); +} + +// Turn off CSS error reporting for this test, since it's a bit expensive, +// and we're expecting to generate tons and tons of parse errors here. +SpecialPowers.pushPrefEnv({ "set": [["layout.css.report_errors", false]] }, + start_test); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_flexbox_child_display_values.xhtml b/layout/style/test/test_flexbox_child_display_values.xhtml new file mode 100644 index 000000000..08308d38e --- /dev/null +++ b/layout/style/test/test_flexbox_child_display_values.xhtml @@ -0,0 +1,189 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=783415 +--> +<head> + <meta charset="utf-8"/> + <title>Test "display" values of content in a flex container (Bug 783415)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783415">Mozilla Bug 783415</a> +<div id="display"> + <div id="wrapper"></div> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +<![CDATA[ +"use strict"; + +/** + * Test "display" values of content in a flex container (Bug 783415) + * ================================================================ + * + * This test creates content with a variety of specified "display" values + * and checks what the computed "display" is when we put that content + * in a flex container. (Generally, it'll be the "blockified" form of the + * specified display-value.) + */ + +/* + * Utility function for getting computed style of "display". + * + * @arg aElem The element to query for its computed "display" value. + * @return The computed display value + */ +function getComputedDisplay(aElem) { + return window.getComputedStyle(aElem, "").display; +} + +/* + * Function used for testing a given specified "display" value and checking + * its computed value against expectations. + * + * @arg aSpecifiedDisplay + * The specified value of "display" that we should test. + * + * @arg aExpectedDisplayAsFlexContainerChild + * (optional) The expected computed "display" when an element with + * aSpecifiedDisplay is a child of a flex container. If omitted, + * this argument defaults to aSpecifiedDisplay. + * + * @arg aExpectedDisplayAsOutOfFlowFlexContainerChild + * (optional) The expected computed "display" when an element with + * aSpecifiedDisplay is a child of a flex container *and* has + * position:[fixed|absolute] or float: [left|right] set. If omitted, + * this argument defaults to aExpectedDisplayAsFlexContainerChild. + */ +function testDisplayValue(aSpecifiedDisplay, + aExpectedDisplayAsFlexContainerChild, + aExpectedDisplayAsOutOfFlowFlexContainerChild) { + // DEFAULT-ARGUMENT-VALUES MAGIC: Make 2nd and 3rd args each default to + // the preceding arg, if they're unspecified. + if (typeof aExpectedDisplayAsFlexContainerChild == "undefined") { + aExpectedDisplayAsFlexContainerChild = aSpecifiedDisplay; + } + if (typeof aExpectedDisplayAsOutOfFlowFlexContainerChild == "undefined") { + aExpectedDisplayAsOutOfFlowFlexContainerChild = + aExpectedDisplayAsFlexContainerChild; + } + + // FIRST: Create a node with display:aSpecifiedDisplay, and make sure that + // this original display-type is honored in a non-flex-container context. + let wrapper = document.getElementById("wrapper"); + let node = document.createElement("div"); + wrapper.appendChild(node); + + node.style.display = aSpecifiedDisplay; + is(getComputedDisplay(node), aSpecifiedDisplay, + "checking computed value of 'display: " + aSpecifiedDisplay + "' " + + "should be the same as specified value, when parent is a block"); + + + // SECOND: We make our node's parent into a flex container, and make sure + // that this produces the correct computed "display" value. + wrapper.style.display = "flex"; + is(getComputedDisplay(node), aExpectedDisplayAsFlexContainerChild, + "checking computed value of 'display: " + aSpecifiedDisplay + "' " + + "when parent is a flex container"); + + + // THIRD: We set "float" and "position" on our node (still inside of a + // flex container), and make sure that this produces the correct computed + // "display" value. + node.style.cssFloat = "left"; + is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild, + "checking computed value of 'display: " + aSpecifiedDisplay + "' " + + "when parent is a flex container and we're floated left"); + node.style.cssFloat = ""; + + node.style.cssFloat = "right"; + is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild, + "checking computed value of 'display: " + aSpecifiedDisplay + "' " + + "when parent is a flex container and we're floated right"); + node.style.cssFloat = ""; + + node.style.position = "absolute"; + is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild, + "checking computed value of 'display: " + aSpecifiedDisplay + "' " + + "when parent is a flex container and we're abs-pos"); + node.style.position = ""; + + node.style.position = "fixed"; + is(getComputedDisplay(node), aExpectedDisplayAsOutOfFlowFlexContainerChild, + "checking computed value of 'display: " + aSpecifiedDisplay + "' " + + "when parent is a flex container and we're fixed-pos"); + node.style.position = ""; + + // FINALLY: Clean up -- remove the node we created, and turn the wrapper + // back into its original display type (a block). + wrapper.removeChild(node); + wrapper.style.display = ""; +} + +/* + * Main test function + */ +function main() { + testDisplayValue("none"); + testDisplayValue("block"); + testDisplayValue("flex"); + testDisplayValue("inline-flex", "flex"); + testDisplayValue("list-item"); + testDisplayValue("table"); + testDisplayValue("inline-table", "table"); + + // These values all compute to "block" in a flex container. Do them in a + // loop, so that I don't have to type "block" a zillion times. + var dispValsThatComputeToBlockInAFlexContainer = [ + "inline", + "inline-block", + "-moz-box", + "-moz-inline-box", + "-moz-grid", + "-moz-inline-grid", + "-moz-grid-group", + "-moz-grid-line", + "-moz-stack", + "-moz-inline-stack", + "-moz-deck", + "-moz-popup", + "-moz-groupbox", + ]; + + dispValsThatComputeToBlockInAFlexContainer.forEach( + function(aSpecifiedDisplay) { + testDisplayValue(aSpecifiedDisplay, "block"); + }); + + // Table-parts are special. When they're a child of a flex container, + // they normally don't get blockified -- instead, they trigger table-fixup + // and get wrapped in a table. So, their expected display as the child of + // a flex container is the same as their specified display. BUT, if + // we apply out-of-flow styling, then *that* blockifies them before + // we get to the table-fixup stage -- so then, their computed display + // is "block". + let tablePartsDispVals = [ + "table-row-group", + "table-column", + "table-column-group", + "table-header-group", + "table-footer-group", + "table-row", + "table-cell", + "table-caption" + ]; + + tablePartsDispVals.forEach( + function(aSpecifiedDisplay) { + testDisplayValue(aSpecifiedDisplay, "block", "block"); + }); +} + +main(); +]]> +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_flexbox_flex_grow_and_shrink.html b/layout/style/test/test_flexbox_flex_grow_and_shrink.html new file mode 100644 index 000000000..ef6fd901d --- /dev/null +++ b/layout/style/test/test_flexbox_flex_grow_and_shrink.html @@ -0,0 +1,154 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=696253 +--> +<head> + <meta charset="utf-8"> + <title>Test for flex-grow and flex-shrink animation (Bug 696253)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + /* Set flex-grow and flex-shrink to nonzero values, + when no animations are applied. */ + + * { flex-grow: 10; flex-shrink: 20 } + + /* Animations that we'll test (individually) in the script below: */ + @keyframes flexGrowTwoToThree { + 0% { flex-grow: 2 } + 100% { flex-grow: 3 } + } + @keyframes flexShrinkTwoToThree { + 0% { flex-shrink: 2 } + 100% { flex-shrink: 3 } + } + @keyframes flexGrowZeroToZero { + 0% { flex-grow: 0 } + 100% { flex-grow: 0 } + } + @keyframes flexShrinkZeroToZero { + 0% { flex-shrink: 0 } + 100% { flex-shrink: 0 } + } + @keyframes flexGrowZeroToOne { + 0% { flex-grow: 0 } + 100% { flex-grow: 1 } + } + @keyframes flexShrinkZeroToOne { + 0% { flex-shrink: 0 } + 100% { flex-shrink: 1 } + } + @keyframes flexGrowOneToZero { + 0% { flex-grow: 1 } + 100% { flex-grow: 0 } + } + @keyframes flexShrinkOneToZero { + 0% { flex-shrink: 1 } + 100% { flex-shrink: 0 } + } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a> +<div id="display"> + <div id="myDiv"></div> +</div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +/** Test for flex-grow and flex-shrink animation (Bug 696253) **/ + +// take over the refresh driver +advance_clock(0); + +// ANIMATIONS THAT SHOULD AFFECT COMPUTED STYLE +// -------------------------------------------- + +// flexGrowTwoToThree: 2.0 at 0%, 2.5 at 50%, 10 after animation is over +var [ div, cs ] = new_div("animation: flexGrowTwoToThree linear 1s"); +is_approx(+cs.flexGrow, 2, 0.01, "flexGrowTwoToThree at 0.0s"); +advance_clock(500); +is_approx(+cs.flexGrow, 2.5, 0.01, "flexGrowTwoToThree at 0.5s"); +advance_clock(1000); +is(cs.flexGrow, "10", "flexGrowTwoToThree at 1.5s"); +done_div(); + +// flexShrinkTwoToThree: 2.0 at 0%, 2.5 at 50%, 20 after animation is over +[ div, cs ] = new_div("animation: flexShrinkTwoToThree linear 1s"); +is_approx(cs.flexShrink, 2, 0.01, "flexShrinkTwoToThree at 0.0s"); +advance_clock(500); +is_approx(cs.flexShrink, 2.5, 0.01, "flexShrinkTwoToThree at 0.5s"); +advance_clock(1000); +is(cs.flexShrink, "20", "flexShrinkTwoToThree at 1.5s"); +done_div(); + +// flexGrowZeroToZero: 0 at 0%, 0 at 50%, 10 after animation is over +[ div, cs ] = new_div("animation: flexGrowZeroToZero linear 1s"); +is(cs.flexGrow, "0", "flexGrowZeroToZero at 0.0s"); +advance_clock(500); +is(cs.flexGrow, "0", "flexGrowZeroToZero at 0.5s"); +advance_clock(1000); +is(cs.flexGrow, "10", "flexGrowZeroToZero at 1.5s"); +done_div(); + +// flexShrinkZeroToZero: 0 at 0%, 0 at 50%, 20 after animation is over +[ div, cs ] = new_div("animation: flexShrinkZeroToZero linear 1s"); +is(cs.flexShrink, "0", "flexShrinkZeroToZero at 0.0s"); +advance_clock(500); +is(cs.flexShrink, "0", "flexShrinkZeroToZero at 0.5s"); +advance_clock(1000); +is(cs.flexShrink, "20", "flexShrinkZeroToZero at 1.5s"); +done_div(); + +// ANIMATIONS THAT DIDN'T USED TO AFFECT COMPUTED STYLE, BUT NOW DO +// ---------------------------------------------------------------- +// (In an older version of the flexbox spec, flex-grow & flex-shrink were not +// allowed to animate between 0 and other values. But now that's allowed.) + +// flexGrowZeroToOne: 0 at 0%, 0.5 at 50%, 10 after animation is over. +[ div, cs ] = new_div("animation: flexGrowZeroToOne linear 1s"); +is(cs.flexGrow, "0", "flexGrowZeroToOne at 0.0s"); +advance_clock(500); +is(cs.flexGrow, "0.5", "flexGrowZeroToOne at 0.5s"); +advance_clock(1000); +is(cs.flexGrow, "10", "flexGrowZeroToOne at 1.5s"); +done_div(); + +// flexShrinkZeroToOne: 0 at 0%, 0.5 at 50%, 20 after animation is over. +[ div, cs ] = new_div("animation: flexShrinkZeroToOne linear 1s"); +is(cs.flexShrink, "0", "flexShrinkZeroToOne at 0.0s"); +advance_clock(500); +is(cs.flexShrink, "0.5", "flexShrinkZeroToOne at 0.5s"); +advance_clock(1000); +is(cs.flexShrink, "20", "flexShrinkZeroToOne at 1.5s"); +done_div(); + +// flexGrowOneToZero: 1 at 0%, 0.5 at 50%, 10 after animation is over. +[ div, cs ] = new_div("animation: flexGrowOneToZero linear 1s"); +is(cs.flexGrow, "1", "flexGrowOneToZero at 0.0s"); +advance_clock(500); +is(cs.flexGrow, "0.5", "flexGrowOneToZero at 0.5s"); +advance_clock(1000); +is(cs.flexGrow, "10", "flexGrowOneToZero at 1.5s"); +done_div(); + +// flexShrinkOneToZero: 1 at 0%, 0.5 at 50%, 20 after animation is over. +[ div, cs ] = new_div("animation: flexShrinkOneToZero linear 1s"); +is(cs.flexShrink, "1", "flexShrinkOneToZero at 0.0s"); +advance_clock(500); +is(cs.flexShrink, "0.5", "flexShrinkOneToZero at 0.5s"); +advance_clock(1000); +is(cs.flexShrink, "20", "flexShrinkOneToZero at 1.5s"); +done_div(); + +SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_flexbox_flex_shorthand.html b/layout/style/test/test_flexbox_flex_shorthand.html new file mode 100644 index 000000000..24bffed94 --- /dev/null +++ b/layout/style/test/test_flexbox_flex_shorthand.html @@ -0,0 +1,280 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=696253 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 696253</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696253">Mozilla Bug 696253</a> +<div id="display"> + <div id="content"> + </div> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; + +/** Test for Bug 696253 **/ +/* (Testing the 'flex' CSS shorthand property) */ + +// The CSS property name for the shorthand we're testing: +const gFlexPropName = "flex"; + +// Info from property_database.js on this property: +const gFlexPropInfo = gCSSProperties[gFlexPropName]; + +// The name of the property in the DOM (i.e. in elem.style): +// (NOTE: In this case it's actually the same as the CSS property name -- +// "flex" -- but that's not guaranteed in general.) +const gFlexDOMName = gFlexPropInfo.domProp; + +// Default values for shorthand subproperties, when they're not specified +// explicitly in a testcase. This lets the testcases be more concise. +// +// The values here are from the flexbox spec on the 'flex' shorthand: +// "When omitted, [flex-grow and flex-shrink are] set to '1'." +// "When omitted [..., flex-basis's] specified value is '0%'." +let gFlexShorthandDefaults = { + "flex-grow": "1", + "flex-shrink": "1", + "flex-basis": "0%" +}; + +let gFlexShorthandTestcases = [ +/* + { + "flex": "SPECIFIED value for flex shorthand", + + // Expected Computed Values of Subproperties + // Semi-optional -- if unspecified, the expected value is taken + // from gFlexShorthandDefaults. + "flex-grow": "EXPECTED computed value for flex-grow property", + "flex-shrink": "EXPECTED computed value for flex-shrink property", + "flex-basis": "EXPECTED computed value for flex-basis property", + }, +*/ + + // Initial values of subproperties: + // -------------------------------- + // (checked by another test that uses property_database.js, too, but + // might as well check here, too, for thoroughness). + { + "flex": "", + "flex-grow": "0", + "flex-shrink": "1", + "flex-basis": "auto", + }, + { + "flex": "initial", + "flex-grow": "0", + "flex-shrink": "1", + "flex-basis": "auto", + }, + + // Special keyword "none" --> "0 0 auto" + // ------------------------------------- + { + "flex": "none", + "flex-grow": "0", + "flex-shrink": "0", + "flex-basis": "auto", + }, + + // One Value (numeric) --> sets flex-grow + // -------------------------------------- + { + "flex": "0", + "flex-grow": "0", + }, + { + "flex": "5", + "flex-grow": "5", + }, + { + "flex": "1000", + "flex-grow": "1000", + }, + { + "flex": "0.0000001", + "flex-grow": "1e-7" + }, + { + "flex": "20000000", + "flex-grow": "2e+7" + }, + + // One Value (length or other nonnumeric) --> sets flex-basis + // ---------------------------------------------------------- + { + "flex": "0px", + "flex-basis": "0px", + }, + { + "flex": "0%", + "flex-basis": "0%", + }, + { + "flex": "25px", + "flex-basis": "25px", + }, + { + "flex": "5%", + "flex-basis": "5%", + }, + { + "flex": "auto", + "flex-basis": "auto", + }, + { + "flex": "-moz-fit-content", + "flex-basis": "-moz-fit-content", + }, + { + "flex": "calc(5px + 6px)", + "flex-basis": "11px", + }, + { + "flex": "calc(15% + 30px)", + "flex-basis": "calc(30px + 15%)", + }, + + // Two Values (numeric) --> sets flex-grow, flex-shrink + // ---------------------------------------------------- + { + "flex": "0 0", + "flex-grow": "0", + "flex-shrink": "0", + }, + { + "flex": "0 2", + "flex-grow": "0", + "flex-shrink": "2", + }, + { + "flex": "3 0", + "flex-grow": "3", + "flex-shrink": "0", + }, + { + "flex": "0.5000 2.03", + "flex-grow": "0.5", + "flex-shrink": "2.03", + }, + { + "flex": "300.0 500.0", + "flex-grow": "300", + "flex-shrink": "500", + }, + + // Two Values (numeric & length-ish) --> sets flex-grow, flex-basis + // ---------------------------------------------------------------- + { + "flex": "0 0px", + "flex-grow": "0", + "flex-basis": "0px", + }, + { + "flex": "0 0%", + "flex-grow": "0", + "flex-basis": "0%", + }, + { + "flex": "10 30px", + "flex-grow": "10", + "flex-basis": "30px", + }, + { + "flex": "99px 2.3", + "flex-grow": "2.3", + "flex-basis": "99px", + }, + { + "flex": "99% 6", + "flex-grow": "6", + "flex-basis": "99%", + }, + { + "flex": "auto 5", + "flex-grow": "5", + "flex-basis": "auto", + }, + { + "flex": "5 -moz-fit-content", + "flex-grow": "5", + "flex-basis": "-moz-fit-content", + }, + { + "flex": "calc(5% + 10px) 3", + "flex-grow": "3", + "flex-basis": "calc(10px + 5%)", + }, + + // Three Values --> Sets all three subproperties + // --------------------------------------------- + { + "flex": "0 0 0", + "flex-grow": "0", + "flex-shrink": "0", + "flex-basis": "0px", + }, + { + "flex": "0.0 0.00 0px", + "flex-grow": "0", + "flex-shrink": "0", + "flex-basis": "0px", + }, + { + "flex": "0% 0 0", + "flex-grow": "0", + "flex-shrink": "0", + "flex-basis": "0%", + }, + { + "flex": "10px 3 2", + "flex-grow": "3", + "flex-shrink": "2", + "flex-basis": "10px", + }, +]; + +function runFlexShorthandTest(aFlexShorthandTestcase) +{ + let content = document.getElementById("content"); + + let elem = document.createElement("div"); + + elem.style[gFlexDOMName] = aFlexShorthandTestcase[gFlexPropName]; + content.appendChild(elem); + + gFlexPropInfo.subproperties.forEach(function(aSubPropName) { + var expectedVal = aSubPropName in aFlexShorthandTestcase ? + aFlexShorthandTestcase[aSubPropName] : + gFlexShorthandDefaults[aSubPropName]; + + // Compare computed value against expected computed value (from testcase) + is(window.getComputedStyle(elem, null).getPropertyValue(aSubPropName), + expectedVal, + "Computed value of subproperty \"" + aSubPropName + "\" when we set \"" + + gFlexPropName + ": " + aFlexShorthandTestcase[gFlexPropName] + "\""); + }); + + // Clean up + content.removeChild(elem); +} + +function main() { + gFlexShorthandTestcases.forEach(runFlexShorthandTest); +} + +main(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_flexbox_layout.html b/layout/style/test/test_flexbox_layout.html new file mode 100644 index 000000000..c484030be --- /dev/null +++ b/layout/style/test/test_flexbox_layout.html @@ -0,0 +1,184 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=666041 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 666041</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="flexbox_layout_testcases.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666041">Mozilla Bug 666041</a> +<div id="display"> + <div id="content"> + </div> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; + +/** Test for Bug 666041 **/ + +/* Flexbox Layout Tests + * -------------------- + * This mochitest exercises our implementation of the flexbox layout algorithm + * by creating a flex container, inserting some flexible children, and then + * verifying that the computed width of those children is what we expect. + * + * See flexbox_layout_testcases.js for the actual testcases & testcase format. + */ + +function getComputedStyleWrapper(elem, prop) +{ + return window.getComputedStyle(elem, null).getPropertyValue(prop); +} + +function setPossiblyAliasedProperty(aElem, aPropertyName, aPropertyValue, + aPropertyMapping) +{ + let actualPropertyName = (aPropertyName in aPropertyMapping ? + aPropertyMapping[aPropertyName] : aPropertyName); + + if (!gCSSProperties[actualPropertyName]) { + ok(false, "Bug in test: property '" + actualPropertyName + + "' doesn't exist in gCSSProperties"); + } else { + let domPropertyName = gCSSProperties[actualPropertyName].domProp; + aElem.style[domPropertyName] = aPropertyValue; + } +} + +// Helper function to strip "px" off the end of a string +// (so that we can compare two lengths using "isfuzzy()" with an epsilon) +function stripPx(aLengthInPx) +{ + let pxOffset = aLengthInPx.length - 2; // subtract off length of "px" + + // Sanity-check the arg: + ok(pxOffset > 0 && aLengthInPx.substr(pxOffset) == "px", + "expecting value with 'px' units"); + + return aLengthInPx.substr(0, pxOffset); +} + +// The main test function. +// aFlexboxTestcase is an entry from the list in flexbox_layout_testcases.js +function testFlexboxTestcase(aFlexboxTestcase, aFlexDirection, aPropertyMapping) +{ + let content = document.getElementById("content"); + + // Create flex container + let flexContainer = document.createElement("div"); + flexContainer.style.display = "flex"; + flexContainer.style.flexDirection = aFlexDirection; + setPossiblyAliasedProperty(flexContainer, "_main-size", + gDefaultFlexContainerSize, + aPropertyMapping); + + // Apply testcase's customizations for flex container (if any). + if (aFlexboxTestcase.container_properties) { + for (let propName in aFlexboxTestcase.container_properties) { + let propValue = aFlexboxTestcase.container_properties[propName]; + setPossiblyAliasedProperty(flexContainer, propName, propValue, + aPropertyMapping); + } + } + + // Create & append flex items + aFlexboxTestcase.items.forEach(function(aChildSpec) { + // Create an element for our item + let child = document.createElement("div"); + + // Set all the specified properties on our item + for (let propName in aChildSpec) { + // aChildSpec[propName] is either a specified value, + // or an array of [specifiedValue, computedValue] + let specifiedValue = Array.isArray(aChildSpec[propName]) ? + aChildSpec[propName][0] : + aChildSpec[propName]; + + // SANITY CHECK: + if (Array.isArray(aChildSpec[propName])) { + ok(aChildSpec[propName].length >= 2 && + aChildSpec[propName].length <= 3, + "unexpected number of elements in array within child spec"); + } + + if (specifiedValue !== null) { + setPossiblyAliasedProperty(child, propName, specifiedValue, + aPropertyMapping); + } + } + + // Append the item to the flex container + flexContainer.appendChild(child); + }); + + // Append the flex container + content.appendChild(flexContainer); + + // NOW: Test the computed style on the flex items + let child = flexContainer.firstChild; + for (let i = 0; i < aFlexboxTestcase.items.length; i++) { + if (!child) { // sanity + ok(false, "should have created a child for each child-spec"); + } + + let childSpec = aFlexboxTestcase.items[i]; + for (let propName in childSpec) { + if (Array.isArray(childSpec[propName])) { + let expectedVal = childSpec[propName][1]; + let actualPropName = (propName in aPropertyMapping ? + aPropertyMapping[propName] : propName); + let actualVal = getComputedStyleWrapper(child, actualPropName); + let message = "computed value of '" + actualPropName + + "' should match expected"; + + if (childSpec[propName].length > 2) { + // 3rd entry in array is epsilon + // Need to strip off "px" units in order to use epsilon: + let actualValNoPx = stripPx(actualVal); + let expectedValNoPx = stripPx(expectedVal); + isfuzzy(actualValNoPx, expectedValNoPx, + childSpec[propName][2], message); + } else { + is(actualVal, expectedVal, message); + } + } + } + + child = child.nextSibling; + } + + // Clean up: drop the flex container. + content.removeChild(flexContainer); +} + +function main() +{ + gFlexboxTestcases.forEach( + function(aTestcase) { + testFlexboxTestcase(aTestcase, "", + gRowPropertyMapping); + testFlexboxTestcase(aTestcase, "row", + gRowPropertyMapping); + testFlexboxTestcase(aTestcase, "row-reverse", + gRowReversePropertyMapping); + testFlexboxTestcase(aTestcase, "column", + gColumnPropertyMapping); + testFlexboxTestcase(aTestcase, "column-reverse", + gColumnReversePropertyMapping); + } + ); +} + +main(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_flexbox_order.html b/layout/style/test/test_flexbox_order.html new file mode 100644 index 000000000..49552c645 --- /dev/null +++ b/layout/style/test/test_flexbox_order.html @@ -0,0 +1,194 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=666041 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 666041</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + div.ref { + display: none; + height: 30px; + } + + refA, refB, refC { + display: block; + float: left; + } + + div#a, refA { + background: lightgreen; + width: 20px; + height: 30px; + } + div#b, refB { + background: orange; + width: 30px; + height: 30px; + } + div#c, refC { + background: blue; + width: 50px; + height: 30px; + } + div#flexContainer { + display: flex; + width: 100px; + height: 30px; + } + div#flexContainerParent { + display: none; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666041">Mozilla Bug 666041</a> +<div id="display"> + + <!-- Reference cases (display:none; only shown during initRefSnapshots) --> + <div id="references"> + <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div> + <div class="ref" id="acb"><refA></refA><refC></refC><refB></refB></div> + <div class="ref" id="bac"><refB></refB><refA></refA><refC></refC></div> + <div class="ref" id="bca"><refB></refB><refC></refC><refA></refA></div> + <div class="ref" id="cab"><refC></refC><refA></refA><refB></refB></div> + <div class="ref" id="cba"><refC></refC><refB></refB><refA></refA></div> + </div> + + <div id="flexContainerParent"> + <!-- The flex container that we'll be testing + (its parent is display:none initially) --> + <div id="flexContainer"> + <div id="a"></div> + <div id="b"></div> + <div id="c"></div> + </div> + </div> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; + +/** Test for Bug 666041 **/ + +/* This testcase ensures that we honor the "order" property when ordering + * flex items within a flex container. + * + * Note: The items in this testcase don't overlap, so this testcase does _not_ + * test paint ordering. It only tests horizontal ordering in a flex container. + */ + +// DATA +// ---- + +// This will store snapshots of our reference divs +let gRefSnapshots = {}; + +// These are the sets of 'order' values that we'll test. +// The first three values in each array are the 'order' values that we'll +// assign to elements a, b, and c (respectively). The final value in each +// array is the ID of the expected reference rendering. +let gOrderTestcases = [ + // The 6 basic permutations: + [ 1, 2, 3, "abc"], + [ 1, 3, 2, "acb"], + [ 2, 1, 3, "bac"], + [ 2, 3, 1, "cab"], + [ 3, 1, 2, "bca"], + [ 3, 2, 1, "cba"], + + // Test negative values + [ 1, -5, -2, "bca"], + [ -50, 0, -2, "acb"], + + // Non-integers should be ignored. + // (So, they'll leave their div with the initial 'order' value, which is 0.) + [ 1, 1.5, 2, "bac"], + [ 2.5, 3.4, 1, "abc"], + [ 0.5, 1, 1.5, "acb"], + + // Decimal values that happen to be equal to integers (e.g. "3.0") are still + // <numbers>, and are _not_ <integers>. + // Source: http://www.w3.org/TR/CSS21/syndata.html#value-def-integer + // (So, they'll leave their div with the initial 'order' value, which is 0.) + // (NOTE: We have to use quotes around "3.0" and "2.0" to be sure JS doesn't + // coerce them into integers before we get a chance to set them in CSS.) + [ "3.0", "2.0", "1.0", "abc"], + [ 3, "2.0", 1, "bca"], +]; + +// FUNCTIONS +// --------- + +function initRefSnapshots() { + let refIds = ["abc", "acb", "bac", "bca", "cab", "cba"]; + for (let id of refIds) { + let elem = document.getElementById(id); + elem.style.display = "block"; + gRefSnapshots[id] = snapshotWindow(window, false); + elem.style.display = ""; + } +} + +function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) { + let compareResult = compareSnapshots(aSnap1, aSnap2, true); + ok(compareResult[0], + "flex container rendering should match expected (" + aMsg +")"); + if (!compareResult[0]) { + todo(false, "TESTCASE: " + compareResult[1]); + todo(false, "REFERENCE: "+ compareResult[2]); + } +} + +function runOrderTestcase(aOrderTestcase) { + // Sanity-check + ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array"); + is(aOrderTestcase.length, 4, "expecting testcase to have 4 elements"); + + document.getElementById("a").style.order = aOrderTestcase[0]; + document.getElementById("b").style.order = aOrderTestcase[1]; + document.getElementById("c").style.order = aOrderTestcase[2]; + + let snapshot = snapshotWindow(window, false); + complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[3]], + aOrderTestcase); + + // Clean up + for (let id of ["a", "b", "c"]) { + document.getElementById(id).style.order = ""; + } +} + +// Main Function +function main() { + initRefSnapshots(); + + // un-hide the flex container's parent + let flexContainerParent = document.getElementById("flexContainerParent"); + flexContainerParent.style.display = "block"; + + // Initial sanity-check: should be in expected document order + let initialSnapshot = snapshotWindow(window, false); + complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots["abc"], + "initial flex container rendering, " + + "no 'order' value yet"); + + // OK, now we run our tests + gOrderTestcases.forEach(runOrderTestcase); + + // Re-hide the flex container at the end + flexContainerParent.style.display = ""; +} + +main(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_flexbox_order_abspos.html b/layout/style/test/test_flexbox_order_abspos.html new file mode 100644 index 000000000..f838fed0e --- /dev/null +++ b/layout/style/test/test_flexbox_order_abspos.html @@ -0,0 +1,217 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1345873 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1345873</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + div.ref { + display: none; + height: 30px; + } + + refA, refB, refC { + display: block; + float: left; + } + + div#a, refA { + background: lightgreen; + width: 20px; + height: 30px; + } + div#b, refB { + background: orange; + width: 30px; + height: 30px; + } + div#c, refC { + background: blue; + width: 50px; + height: 30px; + } + div#flexContainer { + display: flex; + width: 100px; + height: 30px; + } + div#flexContainerParent { + display: none; + } + .abs { + position: absolute !important; + width: 15px !important; + height: 15px !important; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1345873">Mozilla Bug 1345873</a> +<div id="display"> + + <!-- Reference cases (display:none; only shown during initRefSnapshots) --> + <div id="references"> + <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div> + <div class="ref" id="Abc"> + <refA class="abs"></refA><refB></refB><refC></refC></div> + <div class="ref" id="Bac"> + <refB class="abs"></refB><refA></refA><refC></refC></div> + <div class="ref" id="Bca"> + <refB class="abs"></refB><refC></refC><refA></refA></div> + <div class="ref" id="Cab"> + <refC class="abs"></refC><refA></refA><refB></refB></div> + <div class="ref" id="ABc"> + <refA class="abs"></refA><refB class="abs"></refB><refC></refC></div> + <div class="ref" id="ACb"> + <refA class="abs"></refA><refC class="abs"></refC><refB></refB></div> + <div class="ref" id="BCa"> + <refB class="abs"></refB><refC class="abs"></refC><refA></refA></div> + <div class="ref" id="ABC"> + <refA class="abs"></refA><refB class="abs"></refB><refC class="abs"></refC></div> + </div> + + <div id="flexContainerParent"> + <!-- The flex container that we'll be testing + (its parent is display:none initially) --> + <div id="flexContainer"> + <div id="a"></div> + <div id="b"></div> + <div id="c"></div> + </div> + </div> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; + +/** Test for Bug 1345873 **/ + +/* This testcase ensures that we honor the "order" property when ordering + * flex items within a flex container. + * + * Note: The items in this testcase don't overlap, so this testcase does _not_ + * test paint ordering. It only tests horizontal ordering in a flex container. + */ + +// DATA +// ---- + +// This will store snapshots of our reference divs +let gRefSnapshots = {}; + +// These are the sets of 'order' values that we'll test. +// * The first three values in each array are the 'order' values that we'll +// assign to elements a, b, and c (respectively). +// * The next value is a string containing the concatenated IDs of any +// elements that should be absolutely positioned. +// * The final value in each array is the ID of the expected reference +// rendering. (By convention, in those IDs, capital = abspos) +var gOrderTestcases = [ + // Just one child is abspos: + [ 1, 2, 3, "a", "Abc"], + [ 1, 2, 3, "b", "Bac"], + [ 1, 2, 3, "c", "Cab"], + [ 2, 3, 1, "b", "Bca"], + [ 3, 1, 1, "b", "Bca"], + + // Two children are abspos: + // (Note: "order" doesn't influence position or paint order for abspos + // children - only for (in-flow) flex items.) + [ 1, 2, 3, "ab", "ABc"], + [ 2, 1, 3, "ab", "ABc"], + [ 1, 2, 3, "ac", "ACb"], + [ 3, 2, 1, "ac", "ACb"], + [ 3, 2, 1, "bc", "BCa"], + + // All three children are abspos: + // (Rendering always the same regardless of "order" values) + [ 1, 2, 3, "abc", "ABC"], + [ 3, 1, 2, "abc", "ABC"], + [ 3, 2, 1, "abc", "ABC"], +]; + +// FUNCTIONS +// --------- + +function initRefSnapshots() { + let refIds = ["abc", + "Abc", "Bac", "Bca", "Cab", + "ABc", "ACb", "BCa", + "ABC"]; + for (let id of refIds) { + let elem = document.getElementById(id); + elem.style.display = "block"; + gRefSnapshots[id] = snapshotWindow(window, false); + elem.style.display = ""; + } +} + +function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) { + let compareResult = compareSnapshots(aSnap1, aSnap2, true); + ok(compareResult[0], + "flex container rendering should match expected (" + aMsg +")"); + if (!compareResult[0]) { + todo(false, "TESTCASE: " + compareResult[1]); + todo(false, "REFERENCE: "+ compareResult[2]); + } +} + +function runOrderTestcase(aOrderTestcase) { + // Sanity-check + ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array"); + is(aOrderTestcase.length, 5, "expecting testcase to have 5 elements"); + + document.getElementById("a").style.order = aOrderTestcase[0]; + document.getElementById("b").style.order = aOrderTestcase[1]; + document.getElementById("c").style.order = aOrderTestcase[2]; + + let idsToMakeAbspos = aOrderTestcase[3].split(""); + for (let absPosId of idsToMakeAbspos) { + document.getElementById(absPosId).classList.add("abs"); + } + + let snapshot = snapshotWindow(window, false); + complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[4]], + aOrderTestcase); + + // Clean up + for (let id of ["a", "b", "c"]) { + document.getElementById(id).style.order = ""; + document.getElementById(id).classList.remove("abs"); + } +} + +// Main Function +function main() { + initRefSnapshots(); + + // un-hide the flex container's parent + let flexContainerParent = document.getElementById("flexContainerParent"); + flexContainerParent.style.display = "block"; + + // Initial sanity-check: should be in expected document order + let initialSnapshot = snapshotWindow(window, false); + complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots["abc"], + "initial flex container rendering, " + + "no 'order' value yet"); + + // OK, now we run our tests + gOrderTestcases.forEach(runOrderTestcase); + + // Re-hide the flex container at the end + flexContainerParent.style.display = ""; +} + +main(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_flexbox_order_table.html b/layout/style/test/test_flexbox_order_table.html new file mode 100644 index 000000000..36d1b8560 --- /dev/null +++ b/layout/style/test/test_flexbox_order_table.html @@ -0,0 +1,198 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=799775 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 799775</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + div.ref { + display: none; + height: 30px; + } + + refA, refB, refC { + display: block; + float: left; + } + + div#a, div#b, div#c { + display: table; + } + + div#a, refA { + background: lightgreen; + width: 20px; + height: 30px; + } + div#b, refB { + background: orange; + width: 30px; + height: 30px; + } + div#c, refC { + background: blue; + width: 50px; + height: 30px; + } + div#flexContainer { + display: flex; + width: 100px; + height: 30px; + } + div#flexContainerParent { + display: none; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=799775">Mozilla Bug 799775</a> +<div id="display"> + + <!-- Reference cases (display:none; only shown during initRefSnapshots) --> + <div id="references"> + <div class="ref" id="abc"><refA></refA><refB></refB><refC></refC></div> + <div class="ref" id="acb"><refA></refA><refC></refC><refB></refB></div> + <div class="ref" id="bac"><refB></refB><refA></refA><refC></refC></div> + <div class="ref" id="bca"><refB></refB><refC></refC><refA></refA></div> + <div class="ref" id="cab"><refC></refC><refA></refA><refB></refB></div> + <div class="ref" id="cba"><refC></refC><refB></refB><refA></refA></div> + </div> + + <div id="flexContainerParent"> + <!-- The flex container that we'll be testing + (its parent is display:none initially) --> + <div id="flexContainer"> + <div id="a"></div> + <div id="b"></div> + <div id="c"></div> + </div> + </div> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; + +/** Test for Bug 799775 **/ + +/* This testcase ensures that we honor the "order" property when ordering + * tables as flex items within a flex container. + * + * Note: The items in this testcase don't overlap, so this testcase does _not_ + * test paint ordering. It only tests horizontal ordering in a flex container. + */ + +// DATA +// ---- + +// This will store snapshots of our reference divs +let gRefSnapshots = {}; + +// These are the sets of 'order' values that we'll test. +// The first three values in each array are the 'order' values that we'll +// assign to elements a, b, and c (respectively). The final value in each +// array is the ID of the expected reference rendering. +let gOrderTestcases = [ + // The 6 basic permutations: + [ 1, 2, 3, "abc"], + [ 1, 3, 2, "acb"], + [ 2, 1, 3, "bac"], + [ 2, 3, 1, "cab"], + [ 3, 1, 2, "bca"], + [ 3, 2, 1, "cba"], + + // Test negative values + [ 1, -5, -2, "bca"], + [ -50, 0, -2, "acb"], + + // Non-integers should be ignored. + // (So, they'll leave their div with the initial 'order' value, which is 0.) + [ 1, 1.5, 2, "bac"], + [ 2.5, 3.4, 1, "abc"], + [ 0.5, 1, 1.5, "acb"], + + // Decimal values that happen to be equal to integers (e.g. "3.0") are still + // <numbers>, and are _not_ <integers>. + // Source: http://www.w3.org/TR/CSS21/syndata.html#value-def-integer + // (So, they'll leave their div with the initial 'order' value, which is 0.) + // (NOTE: We have to use quotes around "3.0" and "2.0" to be sure JS doesn't + // coerce them into integers before we get a chance to set them in CSS.) + [ "3.0", "2.0", "1.0", "abc"], + [ 3, "2.0", 1, "bca"], +]; + +// FUNCTIONS +// --------- + +function initRefSnapshots() { + let refIds = ["abc", "acb", "bac", "bca", "cab", "cba"]; + for (let id of refIds) { + let elem = document.getElementById(id); + elem.style.display = "block"; + gRefSnapshots[id] = snapshotWindow(window, false); + elem.style.display = ""; + } +} + +function complainIfSnapshotsDiffer(aSnap1, aSnap2, aMsg) { + let compareResult = compareSnapshots(aSnap1, aSnap2, true); + ok(compareResult[0], + "flex container rendering should match expected (" + aMsg +")"); + if (!compareResult[0]) { + todo(false, "TESTCASE: " + compareResult[1]); + todo(false, "REFERENCE: "+ compareResult[2]); + } +} + +function runOrderTestcase(aOrderTestcase) { + // Sanity-check + ok(Array.isArray(aOrderTestcase), "expecting testcase to be an array"); + is(aOrderTestcase.length, 4, "expecting testcase to have 4 elements"); + + document.getElementById("a").style.order = aOrderTestcase[0]; + document.getElementById("b").style.order = aOrderTestcase[1]; + document.getElementById("c").style.order = aOrderTestcase[2]; + + let snapshot = snapshotWindow(window, false); + complainIfSnapshotsDiffer(snapshot, gRefSnapshots[aOrderTestcase[3]], + aOrderTestcase); + + // Clean up + for (let id of ["a", "b", "c"]) { + document.getElementById(id).style.order = ""; + } +} + +// Main Function +function main() { + initRefSnapshots(); + + // un-hide the flex container's parent + let flexContainerParent = document.getElementById("flexContainerParent"); + flexContainerParent.style.display = "block"; + + // Initial sanity-check: should be in expected document order + let initialSnapshot = snapshotWindow(window, false); + complainIfSnapshotsDiffer(initialSnapshot, gRefSnapshots["abc"], + "initial flex container rendering, " + + "no 'order' value yet"); + + // OK, now we run our tests + gOrderTestcases.forEach(runOrderTestcase); + + // Re-hide the flex container at the end + flexContainerParent.style.display = ""; +} + +main(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_flexbox_reflow_counts.html b/layout/style/test/test_flexbox_reflow_counts.html new file mode 100644 index 000000000..f00983b31 --- /dev/null +++ b/layout/style/test/test_flexbox_reflow_counts.html @@ -0,0 +1,137 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1142686 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1142686</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + .flex { + display: flex; + } + #outerFlex { + border: 1px solid black; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142686">Mozilla Bug 1142686</a> +<div id="display"> + <div id="content"> + <div class="flex" id="outerFlex"> + <div class="flex" id="midFlex"> + <div id="innerBlock"> + </div> + </div> + </div> + </div> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; + +/** Test for Bug 1142686 **/ + +/** + * This test checks how many reflows are required, when we make a change inside + * a set of two nested flex containers, with various styles applied to + * the containers & innermost child. Some flex layout operations require two + * passes (which can cause exponential blowup). This test is intended to verify + * that certain configurations do *not* require two-pass layout, by comparing + * the reflow-count for a more-complex scenario against a less-complex scenario. + * + * We have two nested flex containers around an initially-empty block. For each + * measurement, we put some text in the block, and we see how many frame-reflow + * operations occur as a result. + */ + +const gUtils = SpecialPowers.getDOMWindowUtils(window); + +// The elements: +const gOuterFlex = document.getElementById("outerFlex"); +const gMidFlex = document.getElementById("midFlex"); +const gInnerBlock = document.getElementById("innerBlock"); + +// Remove contents of inner block, and remove manual styling: +function cleanup() +{ + outerFlex.style = midFlex.style = innerBlock.style = ""; + while (innerBlock.firstChild) { + innerBlock.removeChild(innerBlock.firstChild); + } +} + +// Each testcase here has a label (used in test output), a function to set up +// the testcase, and (optionally) a function to set up the reference case. +let gTestcases = [ + { + label : "border on flex items", + addTestStyle : function() { + midFlex.style.border = innerBlock.style.border = "3px solid black"; + }, + }, + { + label : "padding on flex items", + addTestStyle : function() { + midFlex.style.padding = innerBlock.style.padding = "5px"; + }, + }, + { + label : "margin on flex items", + addTestStyle : function() { + midFlex.style.margin = innerBlock.style.margin = "2px"; + }, + }, +]; + +// Flush layout & return the global frame-reflow-count +function getReflowCount() +{ + let unusedVal = gOuterFlex.offsetHeight; // flush layout + return gUtils.framesReflowed; +} + +// This function adds some text inside of gInnerBlock, and returns the number +// of frames that need to be reflowed as a result. +function makeTweakAndCountReflows() +{ + let beforeCount = getReflowCount(); + gInnerBlock.appendChild(document.createTextNode("hello")); + let afterCount = getReflowCount(); + + let numReflows = afterCount - beforeCount; + if (numReflows <= 0) { + ok(false, "something's wrong -- we should've reflowed *something*"); + } + return numReflows; +} + +// Given a testcase (from gTestcases), this function verifies that the +// testcase scenario requires the same number of reflows as the reference +// scenario. +function runOneTest(aTestcase) +{ + aTestcase.addTestStyle(); + let numTestcaseReflows = makeTweakAndCountReflows(); + cleanup(); + + if (aTestcase.addReferenceStyle) { + aTestcase.addReferenceStyle(); + } + let numReferenceReflows = makeTweakAndCountReflows(); + cleanup(); + + is(numTestcaseReflows, numReferenceReflows, + "Testcase & reference case should require same number of reflows" + + " (testcase label: '" + aTestcase.label + "')"); +} + +gTestcases.forEach(runOneTest); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_font_face_parser.html b/layout/style/test/test_font_face_parser.html new file mode 100644 index 000000000..75eba7293 --- /dev/null +++ b/layout/style/test/test_font_face_parser.html @@ -0,0 +1,382 @@ +<!DOCTYPE HTML><html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=441469 --> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Test of @font-face parser</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<p>@font-face parsing (<a + target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=441469" +>bug 441469</a>)</p> +<pre id="display"></pre> +<style type="text/css" id="testbox"></style> +<script class="testbody" type="text/javascript"> +function runTest() { + function _(b) { return "@font-face { " + b + " }"; }; + var testset = [ + // Complete nonsense - shouldn't make a font-face rule at all. + { rule: "@font-face;" }, + { rule: "font-face { }" }, + { rule: "@fontface { }" }, + { rule: "@namespace foo url(http://example.com/foo);" }, + + // Empty rule. + { rule: "@font-face { }", d: {} }, + { rule: "@font-face {", d: {} }, + { rule: "@font-face { ; }", d: {}, noncanonical: true }, + + // Correct font-family. + { rule: _("font-family: \"Mouse\";"), d: {"font-family" : "\"Mouse\""} }, + { rule: _("font-family: \"Mouse\""), d: {"font-family" : "\"Mouse\""}, + noncanonical: true }, + { rule: _("font-family: Mouse;"), d: {"font-family" : "\"Mouse\"" }, + noncanonical: true }, + { rule: _("font-family: Mouse"), d: {"font-family" : "\"Mouse\"" }, + noncanonical: true }, + + // Correct but unusual font-family. + { rule: _("font-family: Hoefler Text;"), + d: {"font-family" : "\"Hoefler Text\""}, + noncanonical: true }, + + // Incorrect font-family. + { rule: _("font-family:"), d: {} }, + { rule: _("font-family \"Mouse\""), d: {} }, + { rule: _("font-family: *"), d: {} }, + { rule: _("font-family: Mouse, Rat"), d: {} }, + { rule: _("font-family: sans-serif"), d: {} }, + + // Correct font-style. + { rule: _("font-style: normal;"), d: {"font-style" : "normal"} }, + { rule: _("font-style: italic;"), d: {"font-style" : "italic"} }, + { rule: _("font-style: oblique;"), d: {"font-style" : "oblique"} }, + + // Correct font-weight. + { rule: _("font-weight: 100;"), d: {"font-weight" : "100"} }, + { rule: _("font-weight: 200;"), d: {"font-weight" : "200"} }, + { rule: _("font-weight: 300;"), d: {"font-weight" : "300"} }, + { rule: _("font-weight: 400;"), d: {"font-weight" : "400"} }, + { rule: _("font-weight: 500;"), d: {"font-weight" : "500"} }, + { rule: _("font-weight: 600;"), d: {"font-weight" : "600"} }, + { rule: _("font-weight: 700;"), d: {"font-weight" : "700"} }, + { rule: _("font-weight: 800;"), d: {"font-weight" : "800"} }, + { rule: _("font-weight: 900;"), d: {"font-weight" : "900"} }, + { rule: _("font-weight: normal;"), d: {"font-weight" : "normal"} }, + { rule: _("font-weight: bold;"), d: {"font-weight" : "bold"} }, + + // Incorrect font-weight. + { rule: _("font-weight: bolder;"), d: {} }, + { rule: _("font-weight: lighter;"), d: {} }, + + // Correct font-stretch. + { rule: _("font-stretch: ultra-condensed;"), + d: {"font-stretch" : "ultra-condensed"} }, + { rule: _("font-stretch: extra-condensed;"), + d: {"font-stretch" : "extra-condensed"} }, + { rule: _("font-stretch: condensed;"), + d: {"font-stretch" : "condensed"} }, + { rule: _("font-stretch: semi-condensed;"), + d: {"font-stretch" : "semi-condensed"} }, + { rule: _("font-stretch: normal;"), + d: {"font-stretch" : "normal"} }, + { rule: _("font-stretch: semi-expanded;"), + d: {"font-stretch" : "semi-expanded"} }, + { rule: _("font-stretch: expanded;"), + d: {"font-stretch" : "expanded"} }, + { rule: _("font-stretch: extra-expanded;"), + d: {"font-stretch" : "extra-expanded"} }, + { rule: _("font-stretch: ultra-expanded;"), + d: {"font-stretch" : "ultra-expanded"} }, + + // Incorrect font-stretch. + { rule: _("font-stretch: wider;"), d: {} }, + { rule: _("font-stretch: narrower;"), d: {} }, + + // Correct src: + { rule: _("src: url(\"/fonts/Mouse\");"), + d: { "src" : "url(\"/fonts/Mouse\")" } }, + { rule: _("src: url(/fonts/Mouse);"), + d: { "src" : "url(\"/fonts/Mouse\")" }, noncanonical: true }, + + { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\");"), + d: { "src" : "url(\"/fonts/Mouse\") format(\"truetype\")" } }, + { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\", \"opentype\");"), + d: { "src" : "url(\"/fonts/Mouse\") format(\"truetype\", \"opentype\")" } }, + + { rule: _("src: url(\"/fonts/Mouse\"), url(\"/fonts/Rat\");"), + d: { "src" : "url(\"/fonts/Mouse\"), url(\"/fonts/Rat\")" } }, + + { rule: _("src: local(Mouse), url(\"/fonts/Mouse\");"), + d: { "src" : "local(\"Mouse\"), url(\"/fonts/Mouse\")" }, + noncanonical: true }, + + { rule: _("src: local(\"老鼠\"), url(\"/fonts/Mouse\");"), + d: { "src" : "local(\"老鼠\"), url(\"/fonts/Mouse\")" } }, + + { rule: _("src: local(\"老鼠\"), url(\"/fonts/Mouse\") format(\"truetype\");"), + d: { "src" : "local(\"老鼠\"), url(\"/fonts/Mouse\") format(\"truetype\")" } }, + + // Correct but unusual src: + { rule: _("src: local(Hoefler Text);"), + d: {"src" : "local(\"Hoefler Text\")"}, noncanonical: true }, + + // Incorrect src: + { rule: _("src:"), d: {} }, + { rule: _("src: \"/fonts/Mouse\";"), d: {} }, + { rule: _("src: /fonts/Mouse;"), d: {} }, + { rule: _("src: url(\"/fonts/Mouse\") format(truetype);"), d: {} }, + { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\",opentype);"), d: {} }, + { rule: _("src: local(*);"), d: {} }, + { rule: _("src: format(\"truetype\");"), d: {} }, + { rule: _("src: local(Mouse) format(\"truetype\");"), d: {} }, + { rule: _("src: local(Mouse, Rat);"), d: {} }, + { rule: _("src: local(sans-serif);"), d: {} }, + + // Repeated descriptors + { rule: _("font-weight: 700; font-weight: 200;"), + d: {"font-weight" : "200"}, + noncanonical: true }, + { rule: _("src: url(\"/fonts/Cat\"); src: url(\"/fonts/Mouse\");"), + d: { "src" : "url(\"/fonts/Mouse\")" }, + noncanonical: true }, + { rule: _("src: local(Cat); src: local(Mouse)"), + d: { "src" : "local(\"Mouse\")" }, + noncanonical: true }, + + // Correct parenthesis matching for local() + { rule: _("src: local(Mouse); src: local(Cat(); src: local(Rat); )"), + d: { "src" : "local(\"Mouse\")" }, + noncanonical: true }, + { rule: _("src: local(Mouse); src: local(\"Cat\"; src: local(Rat); )"), + d: { "src" : "local(\"Mouse\")" }, + noncanonical: true }, + + // Correct parenthesis matching for format() + { rule: _("src: url(\"/fonts/Mouse\"); " + + "src: url(\"/fonts/Cat\") format(Cat(); src: local(Rat); )"), + d: { "src" : "url(\"/fonts/Mouse\")" }, + noncanonical: true }, + { rule: _("src: url(\"/fonts/Mouse\"); " + + "src: url(\"/fonts/Cat\") format(\"Cat\"; src: local(Rat); )"), + d: { "src" : "url(\"/fonts/Mouse\")" }, + noncanonical: true }, + { rule: _("src: url(\"/fonts/Mouse\"); " + + "src: url(\"/fonts/Cat\") format((); src: local(Rat); )"), + d: { "src" : "url(\"/fonts/Mouse\")" }, + noncanonical: true }, + + // Correct unicode-range: + { rule: _("unicode-range: U+A5;"), d: { "unicode-range" : "U+A5" } }, + { rule: _("unicode-range: U+00A5;"), + d: { "unicode-range" : "U+A5" }, noncanonical: true }, + { rule: _("unicode-range: U+00a5;"), + d: { "unicode-range" : "U+A5" }, noncanonical: true }, + { rule: _("unicode-range: u+00a5;"), + d: { "unicode-range" : "U+A5" }, noncanonical: true }, + { rule: _("unicode-range: U+0-FF;"), + d: { "unicode-range" : "U+0-FF" } }, + { rule: _("unicode-range: U+00??;"), + d: { "unicode-range" : "U+0-FF" }, noncanonical: true }, + { rule: _("unicode-range: U+?"), + d: { "unicode-range" : "U+0-F" }, noncanonical: true }, + { rule: _("unicode-range: U+??????"), + d: { "unicode-range" : "U+0-10FFFF" }, noncanonical: true }, + { rule: _("unicode-range: U+590-5ff;"), + d: { "unicode-range" : "U+590-5FF" }, noncanonical: true }, + { rule: _("unicode-range: U+A0000-12FFFF"), + d: { "unicode-range" : "U+A0000-10FFFF" }, noncanonical: true }, + + { rule: _("unicode-range: U+A5, U+4E00-9FFF, U+30??, U+FF00-FF9F;"), + d: { "unicode-range" : "U+A5, U+4E00-9FFF, U+3000-30FF, U+FF00-FF9F" }, + noncanonical: true }, + + { rule: _("unicode-range: U+104??;"), + d: { "unicode-range" : "U+10400-104FF" }, noncanonical: true }, + { rule: _("unicode-range: U+320??, U+321??, U+322??, U+323??, U+324??, U+325??;"), + d: { "unicode-range" : "U+32000-320FF, U+32100-321FF, U+32200-322FF, U+32300-323FF, U+32400-324FF, U+32500-325FF" }, + noncanonical: true }, + { rule: _("unicode-range: U+100000-10ABCD;"), + d: { "unicode-range" : "U+100000-10ABCD" } }, + { rule: _("unicode-range: U+0121 , U+1023"), + d: { "unicode-range" : "U+121, U+1023" }, noncanonical: true }, + { rule: _("unicode-range: U+0121/**/, U+1023"), + d: { "unicode-range" : "U+121, U+1023" }, noncanonical: true }, + + // Incorrect unicode-range: + { rule: _("unicode-range:"), d: {} }, + { rule: _("unicode-range: U+"), d: {} }, + { rule: _("unicode-range: U+8FFFFFFF"), d: {} }, + { rule: _("unicode-range: U+8FFF-7000"), d: {} }, + { rule: _("unicode-range: U+8F??-9000"), d: {} }, + { rule: _("unicode-range: U+9000-9???"), d: {} }, + { rule: _("unicode-range: U+??00"), d: {} }, + { rule: _("unicode-range: U+12345678?"), d: {} }, + { rule: _("unicode-range: U+1????????"), d: {} }, + { rule: _("unicode-range: twelve"), d: {} }, + { rule: _("unicode-range: 1000"), d: {} }, + { rule: _("unicode-range: 13??"), d: {} }, + { rule: _("unicode-range: 1300-1377"), d: {} }, + { rule: _("unicode-range: U-1000"), d: {} }, + { rule: _("unicode-range: U+nnnn"), d: {} }, + { rule: _("unicode-range: U+0121 U+1023"), d: {} }, + { rule: _("unicode-range: U+ 0121"), d: {} }, + { rule: _("unicode-range: U +0121"), d: {} }, + { rule: _("unicode-range: U+0121-"), d: {} }, + { rule: _("unicode-range: U+0121- 1023"), d: {} }, + { rule: _("unicode-range: U+0121 -1023"), d: {} }, + { rule: _("unicode-range: U+012 ?"), d: {} }, + { rule: _("unicode-range: U+01 2?"), d: {} }, + + // Thorough test of seven-digit rejection: all these are syntax errors + { rule: _("unicode-range: U+1034560, U+A5"), d: {} }, + { rule: _("unicode-range: U+1034569, U+A5"), d: {} }, + { rule: _("unicode-range: U+103456a, U+A5"), d: {} }, + { rule: _("unicode-range: U+103456f, U+A5"), d: {} }, + { rule: _("unicode-range: U+103456?, U+A5"), d: {} }, + { rule: _("unicode-range: U+103456-1034560, U+A5"), d: {} }, + { rule: _("unicode-range: U+103456-1034569, U+A5"), d: {} }, + { rule: _("unicode-range: U+103456-103456a, U+A5"), d: {} }, + { rule: _("unicode-range: U+103456-103456f, U+A5"), d: {} }, + + // Syntactically invalid unicode-range tokens invalidate the + // entire descriptor + { rule: _("unicode-range: U+1, U+2, U+X"), d: {} }, + { rule: _("unicode-range: U+A5, U+0?F"), d: {} }, + { rule: _("unicode-range: U+A5, U+0F?-E00"), d: {} }, + + // Descending ranges and ranges outside 0-10FFFF are ignored + // but do not invalidate the descriptor + { rule: _("unicode-range: U+A5, U+90-30"), + d: { "unicode-range" : "U+A5" }, noncanonical: true }, + { rule: _("unicode-range: U+A5, U+220043"), + d: { "unicode-range" : "U+A5" }, noncanonical: true }, + + // font-feature-settings + { rule: _("font-feature-settings: normal;"), + d: { "font-feature-settings" : "normal" } }, + { rule: _("font-feature-settings: \"dlig\";"), + d: { "font-feature-settings" : "\"dlig\"" } }, + { rule: _("font-feature-settings: \"dlig\" 1;"), + d: { "font-feature-settings" : "\"dlig\"" }, noncanonical: true }, + { rule: _("font-feature-settings: 'dlig' 1"), + d: { "font-feature-settings" : "\"dlig\"" }, noncanonical: true }, + + // incorrect font-feature-settings + { rule: _("font-feature-settings: dlig 1"), d: {} }, + { rule: _("font-feature-settings: none;"), d: {} }, + { rule: _("font-feature-settings: 0;"), d: {} }, + { rule: _("font-feature-settings: 3.14;"), d: {} }, + { rule: _("font-feature-settings: 'blah' 3.14;"), d: {} }, + { rule: _("font-feature-settings: 'dlig' 1 'hist' 0;"), d: {} }, + { rule: _("font-feature-settings: 'dlig=1,hist=1'"), d: {} }, + + // font-language-override: + { rule: _("font-language-override: normal;"), + d: { "font-language-override" : "normal" } }, + { rule: _("font-language-override: \"TRK\";"), + d: { "font-language-override" : "\"TRK\"" } }, + { rule: _("font-language-override: 'TRK'"), + d: { "font-language-override" : "\"TRK\"" }, noncanonical: true }, + + // incorrect font-language-override + { rule: _("font-language-override: TRK"), d: {} }, + { rule: _("font-language-override: none;"), d: {} }, + { rule: _("font-language-override: 0;"), d: {} }, + { rule: _("font-language-override: #999;"), d: {} }, + { rule: _("font-language-override: 'TRK' 'SRB'"), d: {} }, + { rule: _("font-language-override: 'TRK', 'SRB'"), d: {} }, + + // font-display: + { rule: _("font-display: auto;"), + d: { "font-display" : "auto" } }, + { rule: _("font-display: block;"), + d: { "font-display" : "block" } }, + { rule: _("font-display: swap;"), + d: { "font-display" : "swap" } }, + { rule: _("font-display: fallback;"), + d: { "font-display" : "fallback" } }, + { rule: _("font-display: optional;"), + d: { "font-display" : "optional" } }, + + // incorrect font-display + { rule: _("font-display: hidden"), d: {} }, + { rule: _("font-display: swap 3"), d: {} }, + { rule: _("font-display: block 2 swap 0"), d: {} }, + { rule: _("font-display: all"), d: {} }, + ]; + + var display = document.getElementById("display"); + var sheet = document.styleSheets[1]; + + for (var curTest = 0; curTest < testset.length; curTest++) { + try { + while(sheet.cssRules.length > 0) + sheet.deleteRule(0); + sheet.insertRule(testset[curTest].rule, 0); + } catch (e) { + ok(e.name == "SyntaxError" + && e instanceof DOMException + && e.code == DOMException.SYNTAX_ERR + && !('d' in testset[curTest]), + testset[curTest].rule + " syntax error thrown", e); + } + + try { + if (testset[curTest].d) { + is(sheet.cssRules.length, 1, + testset[curTest].rule + " rule count"); + is(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/, + testset[curTest].rule + " rule type"); + + var d = testset[curTest].d; + var s = sheet.cssRules[0].style; + var n = 0; + + // everything is set that should be + for (var name in d) { + is(s.getPropertyValue(name), d[name], + testset[curTest].rule + " (prop " + name + ")"); + n++; + } + // nothing else is set + is(s.length, n, testset[curTest].rule + "prop count"); + for (var i = 0; i < s.length; i++) { + ok(s[i] in d, testset[curTest].rule, + "Unexpected item #" + i + ": " + s[i]); + } + + // round-tripping of cssText + // this is a strong test; it's okay if the exact serialization + // changes in the future + if (n && !testset[curTest].noncanonical) { + is(sheet.cssRules[0].cssText.replace(/[ \n]+/g, " "), + testset[curTest].rule, + testset[curTest].rule + " rule text"); + } + } else { + if (sheet.cssRules.length == 0) { + is(sheet.cssRules.length, 0, + testset[curTest].rule + " rule count (0)"); + } else { + is(sheet.cssRules.length, 1, + testset[curTest].rule + " rule count (1 non-fontface)"); + isnot(sheet.cssRules[0].type, 5 /*FONT_FACE_RULE*/, + testset[curTest].rule + " rule type (1 non-fontface)"); + } + } + } catch (e) { + ok(false, testset[curTest].rule, "During test: " + e); + } + } + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ set: [["layout.css.font-display.enabled", true]] }, + runTest); +</script> +</body> +</html> diff --git a/layout/style/test/test_font_family_parsing.html b/layout/style/test/test_font_family_parsing.html new file mode 100644 index 000000000..526491ebc --- /dev/null +++ b/layout/style/test/test_font_family_parsing.html @@ -0,0 +1,276 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset=utf-8> + <title>Font family name parsing tests</title> + <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"> + <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-family-prop" /> + <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-prop" /> + <meta name="assert" content="tests that valid font family names parse and invalid ones don't" /> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <style type="text/css"> + </style> +</head> +<body> +<div id="log"></div> +<pre id="display"></pre> +<style type="text/css" id="testbox"></style> + +<script type="text/javascript"> + +function fontProp(n, size, s1, s2) { return (s1 ? s1 + " " : "") + (s2 ? s2 + " " : "") + size + " " + n; } +function font(n, size, s1, s2) { return "font: " + fontProp(n, size, s1, s2); } + +// testrules +// namelist - font family list +// invalid - true if declarations won't parse in either font-family or font +// fontonly - only test with the 'font' property +// single - namelist includes only a single name (@font-face rules only allow a single name) + +var testFontFamilyLists = [ + + /* basic syntax */ + { namelist: "simple", single: true }, + { namelist: "'simple'", single: true }, + { namelist: '"simple"', single: true }, + { namelist: "-simple", single: true }, + { namelist: "_simple", single: true }, + { namelist: "quite simple", single: true }, + { namelist: "quite _simple", single: true }, + { namelist: "quite -simple", single: true }, + { namelist: "0simple", invalid: true, single: true }, + { namelist: "simple!", invalid: true, single: true }, + { namelist: "simple()", invalid: true, single: true }, + { namelist: "quite@simple", invalid: true, single: true }, + { namelist: "#simple", invalid: true, single: true }, + { namelist: "quite 0simple", invalid: true, single: true }, + { namelist: "納豆嫌い", single: true }, + { namelist: "納豆嫌い, ick, patooey" }, + { namelist: "ick, patooey, 納豆嫌い" }, + { namelist: "納豆嫌い, 納豆大嫌い" }, + { namelist: "納豆嫌い, 納豆大嫌い, 納豆本当に嫌い" }, + { namelist: "納豆嫌い, 納豆大嫌い, 納豆本当に嫌い, 納豆は好みではない" }, + { namelist: "arial, helvetica, sans-serif" }, + { namelist: "arial, helvetica, 'times' new roman, sans-serif", invalid: true }, + { namelist: "arial, helvetica, \"times\" new roman, sans-serif", invalid: true }, + + { namelist: "arial, helvetica, \"\\\"times new roman\", sans-serif" }, + { namelist: "arial, helvetica, '\\\"times new roman', sans-serif" }, + { namelist: "arial, helvetica, times 'new' roman, sans-serif", invalid: true }, + { namelist: "arial, helvetica, times \"new\" roman, sans-serif", invalid: true }, + { namelist: "\"simple", single: true }, + { namelist: "\\\"simple", single: true }, + { namelist: "\"\\\"simple\"", single: true }, + { namelist: "İsimple", single: true }, + { namelist: "ßsimple", single: true }, + { namelist: "ẙsimple", single: true }, + + /* escapes */ + { namelist: "\\s imple", single: true }, + { namelist: "\\073 imple", single: true }, + + { namelist: "\\035 simple", single: true }, + { namelist: "sim\\035 ple", single: true }, + { namelist: "simple\\02cinitial", single: true }, + { namelist: "simple, \\02cinitial" }, + { namelist: "sim\\020 \\035 ple", single: true }, + { namelist: "sim\\020 5ple", single: true }, + { namelist: "\\@simple", single: true }, + { namelist: "\\@simple\\;", single: true }, + { namelist: "\\@font-face", single: true }, + { namelist: "\\@font-face\\;", single: true }, + { namelist: "\\031 \\036 px", single: true }, + { namelist: "\\031 \\036 px", single: true }, + { namelist: "\\1f4a9", single: true }, + { namelist: "\\01f4a9", single: true }, + { namelist: "\\0001f4a9", single: true }, + { namelist: "\\AbAb", single: true }, + + /* keywords */ + { namelist: "italic", single: true }, + { namelist: "bold", single: true }, + { namelist: "bold italic", single: true }, + { namelist: "italic bold", single: true }, + { namelist: "larger", single: true }, + { namelist: "smaller", single: true }, + { namelist: "bolder", single: true }, + { namelist: "lighter", single: true }, + { namelist: "default", invalid: true, fontonly: true, single: true }, + { namelist: "initial", invalid: true, fontonly: true, single: true }, + { namelist: "inherit", invalid: true, fontonly: true, single: true }, + { namelist: "normal", single: true }, + { namelist: "default, simple", invalid: true }, + { namelist: "initial, simple", invalid: true }, + { namelist: "inherit, simple", invalid: true }, + { namelist: "normal, simple" }, + { namelist: "simple, default", invalid: true }, + { namelist: "simple, initial", invalid: true }, + { namelist: "simple, inherit", invalid: true }, + { namelist: "simple, default bongo" }, + { namelist: "simple, initial bongo" }, + { namelist: "simple, inherit bongo" }, + { namelist: "simple, bongo default" }, + { namelist: "simple, bongo initial" }, + { namelist: "simple, bongo inherit" }, + { namelist: "simple, normal" }, + { namelist: "simple default", single: true }, + { namelist: "simple initial", single: true }, + { namelist: "simple inherit", single: true }, + { namelist: "simple normal", single: true }, + { namelist: "default simple", single: true }, + { namelist: "initial simple", single: true }, + { namelist: "inherit simple", single: true }, + { namelist: "normal simple", single: true }, + { namelist: "caption", single: true }, // these are keywords for the 'font' property but only when in the first position + { namelist: "icon", single: true }, + { namelist: "menu", single: true } + +]; + +if (window.SpecialPowers && SpecialPowers.getBoolPref("layout.css.unset-value.enabled")) { + testFontFamilyLists.push( + { namelist: "unset", invalid: true, fontonly: true, single: true }, + { namelist: "unset, simple", invalid: true }, + { namelist: "simple, unset", invalid: true }, + { namelist: "simple, unset bongo" }, + { namelist: "simple, bongo unset" }, + { namelist: "simple unset", single: true }, + { namelist: "unset simple", single: true }); +} + +var gTest = 0; + +/* strip out just values */ +function extractDecl(rule) +{ + var t = rule.replace(/[ \n]+/g, " "); + t = t.replace(/.*{[ \n]*/, ""); + t = t.replace(/[ \n]*}.*/, ""); + return t; +} + + +function testStyleRuleParse(decl, invalid) { + var sheet = document.styleSheets[1]; + var rule = ".test" + gTest++ + " { " + decl + "; }"; + + while(sheet.cssRules.length > 0) { + sheet.deleteRule(0); + } + + // shouldn't throw but improper handling of punctuation may cause some parsers to throw + try { + sheet.insertRule(rule, 0); + } catch (e) { + assert_unreached("unexpected error with " + decl + " ==> " + e.name); + } + + assert_equals(sheet.cssRules.length, 1, + "strange number of rules (" + sheet.cssRules.length + ") with " + decl); + + var s = extractDecl(sheet.cssRules[0].cssText); + + if (invalid) { + assert_equals(s, "", "rule declaration shouldn't parse - " + rule + " ==> " + s); + } else { + assert_not_equals(s, "", "rule declaration should parse - " + rule); + + // check that the serialization also parses + var r = ".test" + gTest++ + " { " + s + " }"; + while(sheet.cssRules.length > 0) { + sheet.deleteRule(0); + } + try { + sheet.insertRule(r, 0); + } catch (e) { + assert_unreached("exception occurred parsing serialized form of rule - " + rule + " ==> " + r + " " + e.name); + } + var s2 = extractDecl(sheet.cssRules[0].cssText); + assert_not_equals(s2, "", "serialized form of rule should also parse - " + rule + " ==> " + r); + } +} + +var kDefaultFamilySetting = "onelittlepiggywenttomarket"; + +function testFontFamilySetterParse(namelist, invalid) { + var el = document.getElementById("display"); + + el.style.fontFamily = kDefaultFamilySetting; + var def = el.style.fontFamily; + el.style.fontFamily = namelist; + if (!invalid) { + assert_not_equals(el.style.fontFamily, def, "fontFamily setter should parse - " + namelist); + var parsed = el.style.fontFamily; + el.style.fontFamily = kDefaultFamilySetting; + el.style.fontFamily = parsed; + assert_equals(el.style.fontFamily, parsed, "fontFamily setter should parse serialized form to identical serialization - " + parsed + " ==> " + el.style.fontFamily); + } else { + assert_equals(el.style.fontFamily, def, "fontFamily setter shouldn't parse - " + namelist); + } +} + +var kDefaultFontSetting = "16px onelittlepiggywenttomarket"; + +function testFontSetterParse(n, size, s1, s2, invalid) { + var el = document.getElementById("display"); + + el.style.font = kDefaultFontSetting; + var def = el.style.font; + var fp = fontProp(n, size, s1, s2); + el.style.font = fp; + if (!invalid) { + assert_not_equals(el.style.font, def, "font setter should parse - " + fp); + var parsed = el.style.font; + el.style.font = kDefaultFontSetting; + el.style.font = parsed; + assert_equals(el.style.font, parsed, "font setter should parse serialized form to identical serialization - " + parsed + " ==> " + el.style.font); + } else { + assert_equals(el.style.font, def, "font setter shouldn't parse - " + fp); + } +} + +var testFontVariations = [ + { size: "16px" }, + { size: "900px" }, + { size: "900em" }, + { size: "35%" }, + { size: "7832.3%" }, + { size: "xx-large" }, + { size: "larger", s1: "lighter" }, + { size: "16px", s1: "italic" }, + { size: "16px", s1: "italic", s2: "bold" }, + { size: "smaller", s1: "normal" }, + { size: "16px", s1: "normal", s2: "normal" }, + { size: "16px", s1: "400", s2: "normal" }, + { size: "16px", s1: "bolder", s2: "oblique" } +]; + +function testFamilyNameParsing() { + var i; + for (i = 0; i < testFontFamilyLists.length; i++) { + var tst = testFontFamilyLists[i]; + var n = tst.namelist; + var t; + + if (!tst.fontonly) { + t = "font-family: " + n; + test(function() { testStyleRuleParse(t, tst.invalid); }, t); + test(function() { testFontFamilySetterParse(n, tst.invalid); }, t + " (setter)"); + } + + var v; + for (v = 0; v < testFontVariations.length; v++) { + var f = testFontVariations[v]; + t = font(n, f.size, f.s1, f.s2); + test(function() { testStyleRuleParse(t, tst.invalid); }, t); + test(function() { testFontSetterParse(n, f.size, f.s1, f.s2, tst.invalid); }, t + " (setter)"); + } + } +} + +testFamilyNameParsing(); + +</script> +</body> +</html> diff --git a/layout/style/test/test_font_feature_values_parsing.html b/layout/style/test/test_font_feature_values_parsing.html new file mode 100644 index 000000000..a5d4a6834 --- /dev/null +++ b/layout/style/test/test_font_feature_values_parsing.html @@ -0,0 +1,356 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset=utf-8> + <title>@font-feature-values rule parsing tests</title> + <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"> + <link rel="help" href="http://www.w3.org/TR/css3-fonts/#font-feature-values" /> + <meta name="assert" content="tests that valid @font-feature-values rules parse and invalid ones don't" /> + <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=549861 --> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <style type="text/css"> + </style> +</head> +<body> +<div id="log"></div> +<pre id="display"></pre> +<style type="text/css" id="testbox"></style> + +<script type="text/javascript"> +var gPrefix = ""; +var kFontFeatureValuesRuleType = 14; + +function ruleName() { return "@" + gPrefix + "font-feature-values"; } +function makeRule(f, v) { + return ruleName() + " " + f + " { " + v + " }"; +} + +function _() +{ + var i, decl = []; + for (i = 0; i < arguments.length; i++) { + decl.push(arguments[i]); + } + return makeRule("bongo", decl.join(" ")); +} + +// note: because of bugs in the way family names are serialized, +// 'serializationSame' only implies that the value definition block +// is the same (i.e. not including the family name list) + +var testrules = [ + + /* basic syntax */ + { rule: ruleName() + ";", invalid: true }, + { rule: ruleName() + " bongo;", invalid: true }, + { rule: ruleName().replace("values", "value") + " {;}", invalid: true }, + { rule: ruleName().replace("feature", "features") + " {;}", invalid: true }, + { rule: makeRule("bongo", ""), serializationNoValueDefn: true }, + { rule: makeRule("bongo", ";"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", ",;"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", ";,"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", ",;,"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset;"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset,;"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset abc;"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { ;;abc }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc;; }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc: }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc,: }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc:, }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc:,; }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { a,b }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { a;b }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { a:;b: }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { a:,;b: }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { a:1,;b: }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc 1 2 3 }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc:, 1 2 3 }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc:; 1 2 3 }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc: 1 2 3a }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@styleset { abc: 1 2 3, def: 1; }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@blah @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@blah } @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, + { rule: makeRule("bongo", "@blah , @styleset { abc: 1 2 3; }"), serializationNoValueDefn: true }, + { rule: ruleName() + " bongo { @styleset { abc: 1 2 3; }", serialization: _("@styleset { abc: 1 2 3; }") }, + { rule: ruleName() + " bongo { @styleset { abc: 1 2 3 }", serialization: _("@styleset { abc: 1 2 3; }") }, + { rule: ruleName() + " bongo { @styleset { abc: 1 2 3;", serialization: _("@styleset { abc: 1 2 3; }") }, + { rule: ruleName() + " bongo { @styleset { abc: 1 2 3", serialization: _("@styleset { abc: 1 2 3; }") }, + { rule: _("@styleset { ok-1: 1; }"), serializationSame: true }, + { rule: _("@annotation { ok-1: 3; }"), serializationSame: true }, + { rule: _("@stylistic { blah: 3; }"), serializationSame: true }, + { rule: makeRule("bongo", "\n@styleset\n { blah: 3; super-blah: 4 5;\n more-blah: 5 6 7;\n }"), serializationSame: true }, + { rule: makeRule("bongo", "\n@styleset\n {\n blah:\n 3\n;\n super-blah:\n 4\n 5\n;\n more-blah:\n 5 6\n 7;\n }"), serializationSame: true }, + + /* limits on number of values */ + { rule: _("@stylistic { blah: 1; }"), serializationSame: true }, + { rule: _("@styleset { blah: 1 2 3 4; }"), serializationSame: true }, + { rule: _("@character-variant { blah: 1 2; }"), serializationSame: true }, + { rule: _("@swash { blah: 1; }"), serializationSame: true }, + { rule: _("@ornaments { blah: 1; }"), serializationSame: true }, + { rule: _("@annotation { blah: 1; }"), serializationSame: true }, + + /* values ignored when used */ + { rule: _("@styleset { blah: 0; }"), serializationSame: true }, + { rule: _("@styleset { blah: 120 124; }"), serializationSame: true }, + { rule: _("@character-variant { blah: 0; }"), serializationSame: true }, + { rule: _("@character-variant { blah: 111; }"), serializationSame: true }, + { rule: _("@character-variant { blah: 111 13; }"), serializationSame: true }, + + /* invalid value name */ + { rulesrc: ["styleset { blah: 1 }"], serializationNoValueDefn: true }, + { rulesrc: ["stylistic { blah: 1 }"], serializationNoValueDefn: true }, + { rulesrc: ["character-variant { blah: 1 }"], serializationNoValueDefn: true }, + { rulesrc: ["swash { blah: 1 }"], serializationNoValueDefn: true }, + { rulesrc: ["ornaments { blah: 1 }"], serializationNoValueDefn: true }, + { rulesrc: ["annotation { blah: 1 }"], serializationNoValueDefn: true }, + { rulesrc: ["@bongo { blah: 1 }"], serializationNoValueDefn: true }, + { rulesrc: ["@bongo { blah: 1 2 3 }"], serializationNoValueDefn: true }, + { rulesrc: ["@bongo { blah: 1 2 3; burp: 1;;; }"], serializationNoValueDefn: true }, + + /* values */ + { rulesrc: ["@styleset { blah: -1 }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: 1 -1 }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: 1.5 }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: 15px }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: red }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: (1) }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah:(1) }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah:, 1 }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: <1> }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: 1! }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: 1,, }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: 1 1 1 1; }"], serializationSame: true }, + + /* limits on number of values */ + { rulesrc: ["@stylistic { blah: 1 2 }"], serializationNoValueDefn: true }, + { rulesrc: ["@character-variant { blah: 1 2 3 }"], serializationNoValueDefn: true }, + { rulesrc: ["@swash { blah: 1 2 }"], serializationNoValueDefn: true }, + { rulesrc: ["@ornaments { blah: 1 2 }"], serializationNoValueDefn: true }, + { rulesrc: ["@annotation { blah: 1 2 }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { blah: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19; }"], serializationSame: true }, + + /* family names */ + { rule: makeRule("bongo", "@styleset { blah: 1; }"), serializationSame: true }, + { rule: makeRule("\"bongo\"", "@styleset { blah: 1; }"), serializationSame: true }, + { rule: makeRule("'bongo'", "@styleset { blah: 1; }"), serializationSame: true }, + { rule: makeRule("\\62 ongo", "@styleset { blah: 1; }"), serializationSame: true }, + { rule: makeRule("bongo, super bongo, bongo the supreme", "@styleset { blah: 1; }"), serializationSame: true }, + { rule: makeRule("bongo,, super bongo", "@styleset { blah: 1; }"), invalid: true }, + { rule: makeRule("bongo,*", "@styleset { blah: 1; }"), invalid: true }, + { rule: makeRule("bongo, sans-serif", "@styleset { blah: 1; }"), invalid: true }, + { rule: makeRule("serif, sans-serif", "@styleset { blah: 1; }"), invalid: true }, + { rule: makeRule("'serif', 'sans-serif'", "@styleset { blah: 1; }"), serializationSame: true }, + { rule: makeRule("bongo, \"super bongo\", 'bongo the supreme'", "@styleset { blah: 1; }"), serializationSame: true }, + { rule: makeRule("毎日カレーを食べたい!", "@styleset { blah: 1; }"), serializationSame: true }, + { rule: makeRule("毎日カレーを食べたい!, 納豆嫌い", "@styleset { blah: 1; }"), serializationSame: true }, + + { rule: makeRule("bongo, \"super\" bongo, bongo the supreme", "@styleset { blah: 1; }"), invalid: true }, + { rule: makeRule("--bongo", "@styleset { blah: 1; }"), invalid: true }, + + /* ident tests */ + { rule: _("@styleset { blah: 1; blah: 1; }"), serializationSame: true }, + { rule: _("@styleset { blah: 1; de-blah: 1; blah: 2; }"), serializationSame: true }, + { rule: _("@styleset { \\tra-la: 1; }"), serialization: _("@styleset { tra-la: 1; }") }, + { rule: _("@styleset { b\\lah: 1; }"), serialization: _("@styleset { blah: 1; }") }, + { rule: _("@styleset { \\62 lah: 1; }"), serialization: _("@styleset { blah: 1; }") }, + { rule: _("@styleset { \\:blah: 1; }"), serialization: _("@styleset { \\:blah: 1; }") }, + { rule: _("@styleset { \\;blah: 1; }"), serialization: _("@styleset { \\;blah: 1; }") }, + { rule: _("@styleset { complex\\20 blah: 1; }"), serialization: _("@styleset { complex\\ blah: 1; }") }, + { rule: _("@styleset { complex\\ blah: 1; }"), serializationSame: true }, + { rule: _("@styleset { Håkon: 1; }"), serializationSame: true }, + { rule: _("@styleset { Åквариум: 1; }"), serializationSame: true }, + { rule: _("@styleset { \\1f449\\1f4a9\\1f448: 1; }"), serialization: _("@styleset { 👉💩👈: 1; }") }, + { rule: _("@styleset { 魅力: 1; }"), serializationSame: true }, + { rule: _("@styleset { 毎日カレーを食べたい!: 1; }"), serializationSame: true }, + /* from http://en.wikipedia.org/wiki/Metal_umlaut */ + { rule: _("@styleset { TECHNICIÄNS\\ ÖF\\ SPÅCE\\ SHIP\\ EÅRTH\\ THIS\\ IS\\ YÖÜR\\ CÄPTÅIN\\ SPEÄKING\\ YÖÜR\\ ØÅPTÅIN\\ IS\\ DEA̋D: 1; }"), serializationSame: true }, + + { rulesrc: ["@styleset { 123blah: 1; }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { :123blah 1; }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { :123blah: 1; }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { ?123blah: 1; }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { \"blah\": 1; }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { complex blah: 1; }"], serializationNoValueDefn: true }, + { rulesrc: ["@styleset { complex\\ blah: 1; }"], serializationNoValueDefn: true } + +]; + +// test that invalid value declarations don't affect the parsing of surrounding +// declarations. So before + invalid + after should match the serialization +// given in s. + +var gSurroundingTests = [ + // -- invalid, valid ==> valid + { before: "", after: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }") }, + + // -- valid, invalid ==> valid + { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }", after: "", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 7; }") }, + + // -- valid, invalid, valid ==> valid, valid + { before: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; }", after: "@character-variant { whatchamacallit-2: 23 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 3; } @character-variant { whatchamacallit-2: 23 4; }") }, + + // -- invalid, valid, invalid ==> valid + { between: "@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }", s: _("@ornaments { whatchamacallit-1: 23; thingy-dingy: 4; }") } +]; + +/* strip out just values, along with empty value blocks (e.g. @swash { })*/ +function valuesText(ruletext) +{ + var t = ruletext.replace(/@[a-zA-Z0-9\-]+[ \n]*{[ \n]*}/g, ""); + t = t.replace(/[ \n]+/g, " "); + t = t.replace(/^[^{]+{[ \n]*/, ""); + t = t.replace(/[ \n]*}[^}]*$/, ""); + t = t.replace(/[ \n]*;/g, ";"); + return t; +} + +function testParse(rulesrc) +{ + var sheet = document.styleSheets[1]; + var rule = _.apply(this, rulesrc); + + while(sheet.cssRules.length > 0) + sheet.deleteRule(0); + try { + sheet.insertRule(rule, 0); + } catch (e) { + return e.toString(); + } + + if (sheet.cssRules.length == 1 && sheet.cssRules[0].type == kFontFeatureValuesRuleType) { + return sheet.cssRules[0].cssText.replace(/[ \n]+/g, " "); + } + + return ""; +} + +function testOneRule(testrule) { + var sheet = document.styleSheets[1]; + var rule; + + if ("rulesrc" in testrule) { + rule = _.apply(this, testrule.rulesrc); + } else { + rule = testrule.rule; + } + + var parseErr = false; + var expectedErr = false; + var invalid = false; + if ("invalid" in testrule && testrule.invalid) invalid = true; + + while(sheet.cssRules.length > 0) + sheet.deleteRule(0); + try { + sheet.insertRule(rule, 0); + } catch (e) { + expectedErr = (e.name == "SyntaxError" + && e instanceof DOMException + && e.code == DOMException.SYNTAX_ERR + && invalid); + parseErr = true; + } + + test(function() { + assert_true(!parseErr || expectedErr, "unexpected syntax error"); + if (!parseErr) { + assert_equals(sheet.cssRules.length, 1, "bad rule count"); + assert_equals(sheet.cssRules[0].type, kFontFeatureValuesRuleType, "bad rule type"); + } + }, "basic parse tests - " + rule); + + var sanitizedRule = rule.replace(/[ \n]+/g, " "); + if (parseErr) { + return; + } + + // should result in one @font-feature-values rule constructed + + // serialization matches expectation + // -- note: due to inconsistent font family serialization problems, + // only the serialization of the values is tested currently + + var ruleValues = valuesText(rule); + var serialized = sheet.cssRules[0].cssText; + var serializedValues = valuesText(serialized); + var haveSerialization = true; + + if (testrule.serializationSame) { + test(function() { + assert_equals(serializedValues, ruleValues, "canonical cssText serialization doesn't match"); + }, "serialization check - " + rule); + } else if ("serialization" in testrule) { + var s = valuesText(testrule.serialization); + test(function() { + assert_equals(serializedValues, s, "non-canonical cssText serialization doesn't match - "); + }, "serialization check - " + rule); + } else if (testrule.serializationNoValueDefn) { + test(function() { + assert_equals(serializedValues, "", "cssText serialization should have no value defintions - "); + }, "no value definitions in serialization - " + rule); + + haveSerialization = false; + + if ("rulesrc" in testrule) { + test(function() { + var j, rulesrc = testrule.rulesrc; + + // invalid value definitions shouldn't affect the parsing of valid + // definitions before or after an invalid one + for (var j = 0; j < gSurroundingTests.length; j++) { + var t = gSurroundingTests[j]; + var srulesrc = []; + + if ("between" in t) { + srulesrc = srulesrc.concat(rulesrc); + srulesrc = srulesrc.concat(t.between); + srulesrc = srulesrc.concat(rulesrc); + } else { + if (t.before != "") + srulesrc = srulesrc.concat(t.before); + srulesrc = srulesrc.concat(rulesrc); + if (t.after != "") + srulesrc = srulesrc.concat(t.after); + } + + var result = testParse(srulesrc); + assert_equals(valuesText(result), valuesText(t.s), "invalid declarations should not affect valid ones - "); + } + }, "invalid declarations don't affect valid ones - " + rule); + } + } + + // if serialization non-empty, serialization should round-trip to itself + if (haveSerialization) { + var roundTripText = testParse([serializedValues]); + test(function() { + assert_equals(valuesText(roundTripText), serializedValues, + "serialization should round-trip to itself - "); + }, "serialization round-trip - " + rule); + } +} + +function testFontFeatureValuesRuleParsing() { + var i; + for (i = 0; i < testrules.length; i++) { + var testrule = testrules[i]; + var rule; + + if ("rulesrc" in testrule) { + rule = _.apply(this, testrule.rulesrc); + } else { + rule = testrule.rule; + } + + testOneRule(testrule); + //test(function() { testOneRule(testrule); }, "parsing " + rule); + } +} + +testFontFeatureValuesRuleParsing(); +</script> +</body></html> diff --git a/layout/style/test/test_font_loading_api.html b/layout/style/test/test_font_loading_api.html new file mode 100644 index 000000000..26c5a2a85 --- /dev/null +++ b/layout/style/test/test_font_loading_api.html @@ -0,0 +1,1897 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for the CSS Font Loading API</title> +<script src=/tests/SimpleTest/SimpleTest.js></script> +<link rel=stylesheet type=text/css href=/tests/SimpleTest/test.css> + +<script src=descriptor_database.js></script> + +<body onload="start()"> +<iframe id=v src="file_font_loading_api_vframe.html"></iframe> +<iframe id=n style="display: none"></iframe> + +<script> +// Map of FontFace descriptor attribute names to @font-face rule descriptor +// names. +var descriptorNames = { + style: "font-style", + weight: "font-weight", + stretch: "font-stretch", + unicodeRange: "unicode-range", + variant: "font-variant", + featureSettings: "font-feature-settings", + display: "font-display" +}; + +// Default values for the FontFace descriptor attributes other than family, as +// Gecko currently serializes them. +var defaultValues = { + style: "normal", + weight: "normal", + stretch: "normal", + unicodeRange: "U+0-10FFFF", + variant: "normal", + featureSettings: "normal", + display: "auto" +}; + +// Non-default values for the FontFace descriptor attributes other than family +// along with how Gecko currently serializes them. Each value is chosen to be +// different from the default value and also has a different serialized form. +var nonDefaultValues = { + style: ["Italic", "italic"], + weight: ["Bold", "bold"], + stretch: ["Ultra-Condensed", "ultra-condensed"], + unicodeRange: ["U+3??", "U+300-3FF"], + variant: ["Small-Caps", "small-caps"], + featureSettings: ["'dlig' on", "\"dlig\""], + display: ["Block", "block"] +}; + +// Invalid values for the FontFace descriptor attributes other than family. +var invalidValues = { + style: "initial", + weight: "bolder", + stretch: "wider", + unicodeRange: "U+1????-2????", + variant: "inherit", + featureSettings: "dlig", + display: "normal" +}; + +// Invalid font family names. +var invalidFontFamilyNames = [ + "", "\"\"", "sans-serif", "A, B", "inherit", "a 1" +]; + +// Font family list where at least one is likely to be available on +// platforms we care about. +var likelyPlatformFonts = "Helvetica Neue, Bitstream Vera Sans, Bitstream Vera Sans Roman, FreeSans, Free Sans, SwissA, DejaVu Sans, Arial"; + +// Will hold an ArrayBuffer containing a valid font. +var fontData; + +var queue = Promise.resolve(); + +function is_resolved_with(aPromise, aExpectedValue, aDescription, aTestID) { + // This assumes that all Promise tasks come from the task source. + var handled = false; + return new Promise(function(aResolve, aReject) { + aPromise.then(function(aValue) { + if (!handled) { + handled = true; + is(aValue, aExpectedValue, aDescription + " should be resolved with the expected value " + aTestID); + aResolve(); + } + }, function(aError) { + if (!handled) { + handled = true; + ok(false, aDescription + " should be resolved; instead it was rejected with " + aError + " " + aTestID); + aResolve(); + } + }); + Promise.resolve().then(function() { + if (!handled) { + handled = true; + ok(false, aDescription + " should be resolved; instead it is pending " + aTestID); + aResolve(); + } + }); + }); +} + +function is_pending(aPromise, aDescription, aTestID) { + // This assumes that all Promise tasks come from the task source. + var handled = false; + return new Promise(function(aResolve, aReject) { + aPromise.then(function(aValue) { + if (!handled) { + handled = true; + ok(false, aDescription + " should be pending; instead it was resolved with " + aValue + " " + aTestID); + aResolve(); + } + }, function(aError) { + if (!handled) { + handled = true; + ok(false, aDescription + " should be pending; instead it was rejected with " + aError + " " + aTestID); + aResolve(); + } + }); + Promise.resolve().then(function() { + if (!handled) { + handled = true; + ok(true, aDescription + " should be pending " + aTestID); + aResolve(); + } + }); + }); +} + +function fetchAsArrayBuffer(aURL) { + var xhr; + return new Promise(function(aResolve, aReject) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", aURL); + xhr.responseType = "arraybuffer"; + xhr.onreadystatechange = function(evt) { + if (xhr.readyState == 4) { + if (xhr.status >= 200 && xhr.status <= 299) { + aResolve(xhr.response); + } else { + aReject(new Error("Error fetching file " + aURL + ", status " + xhr.status)); + } + } + }; + xhr.send(); + }); +} + +function setTimeoutZero() { + return new Promise(function(aResolve, aReject) { + setTimeout(aResolve, 0); + }); +} + +function awaitRefresh() { + function awaitOneRefresh() { + return new Promise(function(aResolve, aReject) { + requestAnimationFrame(aResolve); + }); + } + + return awaitOneRefresh().then(awaitOneRefresh); +} + +function runTest() { + // Document and window from inside the display:none iframe. + var nframe = document.getElementById("n"); + var ndocument = nframe.contentDocument; + var nwindow = nframe.contentWindow; + + // Document and window from inside the visible iframe. + var vframe = document.getElementById("v"); + var vdocument = vframe.contentDocument; + var vwindow = vframe.contentWindow; + + // For iterating over different combinations of documents and windows + // to test with. + var sources = [ + { win: window, doc: document, what: "window/document" }, + { win: vwindow, doc: vdocument, what: "vwindow/vdocument" }, + { win: nwindow, doc: ndocument, what: "nwindow/ndocument" }, + { win: window, doc: vdocument, what: "window/vdocument" }, + { win: window, doc: ndocument, what: "window/ndocument" }, + { win: vwindow, doc: document, what: "vwindow/document" }, + { win: vwindow, doc: ndocument, what: "vwindow/ndocument" }, + { win: nwindow, doc: document, what: "nwindow/document" }, + { win: nwindow, doc: vdocument, what: "nwindow/vdocument" }, + ]; + + var sourceDocuments = [ + { doc: document, what: "document" }, + { doc: vdocument, what: "vdocument" }, + { doc: ndocument, what: "ndocument" }, + ]; + + var sourceWindows = [ + { win: window, what: "window" }, + { win: vwindow, what: "vwindow" }, + { win: nwindow, what: "nwindow" }, + ]; + + queue = queue.then(function() { + + // First, initialize fontData. + return fetchAsArrayBuffer("BitPattern.woff") + .then(function(aResult) { fontData = aResult; }); + + }).then(function() { + + // (TEST 1) Some miscellaneous tests for FontFaceSet and FontFace. + ok(window.FontFaceSet, "FontFaceSet interface object should be present (TEST 1)"); + is(Object.getPrototypeOf(FontFaceSet.prototype), EventTarget.prototype, "FontFaceSet should inherit from EventTarget (TEST 1)"); + ok(document.fonts instanceof FontFaceSet, "document.fonts should be a a FontFaceSet (TEST 1)"); + ok(window.FontFace, "FontFace interface object should be present (TEST 1)"); + is(Object.getPrototypeOf(FontFace.prototype), Object.prototype, "FontFace should inherit from Object (TEST 1)"); + + // (TEST 2) Some miscellaneous tests for FontFaceSetLoadEvent. + ok(window.FontFaceSetLoadEvent, "FontFaceSetLoadEvent interface object should be present (TEST 2)"); + is(Object.getPrototypeOf(FontFaceSetLoadEvent.prototype), Event.prototype, "FontFaceSetLoadEvent should inherit from Event (TEST 2)"); + + }).then(function() { + + // (TEST 3) Test that document.fonts.ready starts out resolved with the + // FontFaceSet. + var p = Promise.resolve(); + sourceDocuments.forEach(function({ doc, what }) { + p = p.then(function() { + return is_resolved_with(doc.fonts.ready, doc.fonts, "initial value of document.fonts", "(TEST 3) (" + what + ")"); + }); + }); + return p; + + }).then(function() { + + // (TEST 4) Test that document.fonts in this test document starts out with no + // FontFace objects in it. + sourceDocuments.forEach(function({ doc, what }) { + is(Array.from(doc.fonts).length, 0, "initial number of FontFace objects in document.fonts (TEST 4) (" + what + ")"); + }); + + // (TEST 5) Test that document.fonts.status starts off as loaded. + sourceDocuments.forEach(function({ doc, what }) { + is(doc.fonts.status, "loaded", "initial value of document.fonts.status (TEST 5) (" + what + ")"); + }); + + // (TEST 6) Test initial value of FontFace.status when a url() source is + // used. + sourceWindows.forEach(function({ win, what }) { + is(new win.FontFace("test", "url(x)").status, "unloaded", "initial value of FontFace.status when a url() source is used (TEST 6) (" + what + ")"); + }); + + // (TEST 7) Test initial value of FontFace.status when an invalid + // ArrayBuffer source is used. Because it has an implicit initial + // load() call, it should either be "loading" if the browser is + // asynchronously parsing the font data, or "error" if it parsed + // it immediately. + sourceWindows.forEach(function({ win, what }) { + var status = new win.FontFace("test", new ArrayBuffer(0)).status; + ok(status == "loading" || status == "error", "initial value of FontFace.status when an invalid ArrayBuffer source is used (TEST 7) (" + what + ")"); + }); + + // (TEST 8) Test initial value of FontFace.status when a valid ArrayBuffer + // source is used. Because it has an implicit initial load() call, it + // should either be "loading" if the browser is asynchronously parsing the + // font data, or "loaded" if it parsed it immediately. + sourceWindows.forEach(function({ win, what }) { + status = new win.FontFace("test", fontData).status; + ok(status == "loading" || status == "loaded", "initial value of FontFace.status when a valid ArrayBuffer source is used (TEST 8) (" + what + ")"); + }); + + // (TEST 9) (old test became redundant with TEST 19) + + }).then(function() { + + // (TEST 10) Test initial value of FontFace.loaded when a valid url() + // source is used. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + return is_pending(new win.FontFace("test", "url(x)").loaded, "initial value of FontFace.loaded when a valid url() source is used", "(TEST 10) (" + what + ")"); + }); + }); + return p; + + }).then(function() { + + // (TEST 11) (old test became redundant with TEST 21) + + }).then(function() { + + // (TEST 12) (old test became redundant with TEST 20) + + }).then(function() { + + // (TEST 13) Test initial values of the descriptor attributes on FontFace + // objects. + sourceWindows.forEach(function({ win, what }) { + var face = new win.FontFace("test", fontData); + // XXX Spec issue: what values do the descriptor attributes have before the + // constructor's dictionary argument is parsed? + for (var desc in defaultValues) { + is(face[desc], defaultValues[desc], "initial value of FontFace." + desc + " (TEST 13) (" + what + ")"); + } + }); + + // (TEST 14) Test default values of the FontFaceDescriptors dictionary. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var face = new win.FontFace("test", fontData); + return face.loaded.then(function() { + for (var desc in defaultValues) { + is(face[desc], defaultValues[desc], "default value of FontFace." + desc + " (TEST 14) (" + what + ")"); + } + }, function(aError) { + ok(false, "FontFace should have loaded succesfully (TEST 14) (" + what + ")"); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 15) Test passing non-default descriptor values to the FontFace + // constructor. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var descriptorTests = Promise.resolve(); + Object.keys(nonDefaultValues).forEach(function(aDesc) { + descriptorTests = descriptorTests.then(function() { + var init = {}; + init[aDesc] = nonDefaultValues[aDesc][0]; + var face = new win.FontFace("test", fontData, init); + var ok_todo = aDesc == "variant" ? todo : ok; + ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " immediately after construction (TEST 15) (" + what + ")"); + return face.loaded.then(function() { + ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "specified valid non-default value of FontFace." + aDesc + " (TEST 15) (" + what + ")"); + }, function(aError) { + ok(false, "FontFace should have loaded succesfully (TEST 15) (" + what + ")"); + }); + }); + }); + return descriptorTests; + }); + }); + return p; + + }).then(function() { + + // (TEST 16) Test passing invalid descriptor values to the FontFace + // constructor. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var descriptorTests = Promise.resolve(); + Object.keys(invalidValues).forEach(function(aDesc) { + descriptorTests = descriptorTests.then(function() { + var init = {}; + init[aDesc] = invalidValues[aDesc]; + var face = new win.FontFace("test", fontData, init); + var ok_todo = aDesc == "variant" ? todo : ok; + ok_todo(face.status == "error", "FontFace should be error immediately after construction with invalid value of FontFace." + aDesc + " (TEST 16) (" + what + ")"); + return face.loaded.then(function() { + ok_todo(false, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")"); + }, function(aError) { + ok(true, "FontFace should not load after invalid value of FontFace." + aDesc + " specified (TEST 16) (" + what + ")"); + is(aError.name, "SyntaxError", "FontFace.loaded with invalid value of FontFace." + aDesc + " should be rejected with a SyntaxError (TEST 16) (" + what + ")"); + }); + }); + }); + return descriptorTests; + }); + }); + return p; + + }).then(function() { + + // (TEST 17) Test passing an invalid font family name to the FontFace + // constructor. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var familyTests = Promise.resolve(); + invalidFontFamilyNames.forEach(function(aFamilyName) { + familyTests = familyTests.then(function() { + var face = new win.FontFace(aFamilyName, fontData); + is(face.status, "error", "FontFace should be error immediately after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")"); + is(face.family, "", "FontFace.family should be the empty string after construction with invalid family name " + aFamilyName + " (TEST 17) (" + what + ")"); + return face.loaded.then(function() { + ok(false, "FontFace should not load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")"); + }, function(aError) { + ok(true, "FontFace should not load after invalid family name " + aFamilyName + " specified (TEST 17) (" + what + ")"); + is(aError.name, "SyntaxError", "FontFace.loaded with invalid family name " + aFamilyName + " should be rejected with a SyntaxError (TEST 17) (" + what + ")"); + }); + }); + }); + return familyTests; + }); + }); + return p; + + }).then(function() { + + // XXX Disabled this sub-test due to intermittent failures (bug 1076803). + return; + + // (TEST 18) Test passing valid url() source strings to the FontFace + // constructor. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var srcTests = Promise.resolve(); + gCSSFontFaceDescriptors.src.values.forEach(function(aSrc) { + srcTests = srcTests.then(function() { + var face = new win.FontFace("test", aSrc); + return face.load().then(function() { + ok(true, "FontFace should load with valid url() src " + aSrc + " (TEST 18) (" + what + ")"); + }, function(aError) { + is(aError.name, "NetworkError", "FontFace had NetworkError when loading with valid url() src " + aSrc + " (TEST 18) (" + what + ")"); + }); + }); + }); + return srcTests; + }); + }); + return p; + + }).then(function() { + + // (TEST 19) Test passing invalid url() source strings to the FontFace + // constructor. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var srcTests = Promise.resolve(); + gCSSFontFaceDescriptors.src.invalid_values.forEach(function(aSrc) { + srcTests = srcTests.then(function() { + var face = new win.FontFace("test", aSrc); + is(face.status, "error", "FontFace.status should be \"error\" when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")"); + return face.loaded.then(function() { + ok(false, "FontFace should not load with invalid url() src " + aSrc + " (TEST 19) (" + what + ")"); + }, function(aError) { + is(aError.name, "SyntaxError", "FontFace.ready should have been rejected with a SyntaxError when constructed with an invalid url() src " + aSrc + " (TEST 19) (" + what + ")"); + }); + }); + }); + return srcTests; + }); + }); + return p; + + }).then(function() { + + // (TEST 20) Test that the status of a FontFace constructed with a valid + // ArrayBuffer source eventually becomes "loaded". + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var face = new win.FontFace("test", fontData); + return face.loaded.then(function(aFace) { + is(face.status, "loaded", "status of FontFace constructed with a valid ArrayBuffer source should eventually be \"loaded\" (TEST 20) (" + what + ")"); + is(face, aFace, "FontFace.loaded was resolved with the FontFace object once loaded (TEST 20) (" + what + ")"); + }, function(aError) { + ok(false, "FontFace constructed with a valid ArrayBuffer should eventually load (TEST 20) (" + what + ")"); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 21) Test that the status of a FontFace constructed with an invalid + // ArrayBuffer source eventually becomes "error". + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var face = new win.FontFace("test", new ArrayBuffer(0)); + return face.loaded.then(function() { + ok(false, "FontFace constructed with an invalid ArrayBuffer should not load (TEST 21) (" + what + ")"); + }, function(aError) { + is(aError.name, "SyntaxError", "loaded of FontFace constructed with an invalid ArrayBuffer source should be rejected with TypeError (TEST 21) (" + what + ")"); + is(face.status, "error", "status of FontFace constructed with an invalid ArrayBuffer source should eventually be \"error\" (TEST 21) (" + what + ")"); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 22) Test assigning non-default descriptor values on the FontFace. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var descriptorTests = Promise.resolve(); + Object.keys(nonDefaultValues).forEach(function(aDesc) { + descriptorTests = descriptorTests.then(function() { + var face = new win.FontFace("test", fontData); + return face.loaded.then(function() { + var ok_todo = aDesc == "variant" ? todo : ok; + face[aDesc] = nonDefaultValues[aDesc][0]; + ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "assigned valid non-default value to FontFace." + aDesc + " (TEST 22) (" + what + ")"); + }, function(aError) { + ok(false, "FontFace should have loaded succesfully (TEST 22) (" + what + ")"); + }); + }); + }); + return descriptorTests; + }); + }); + return p; + + }).then(function() { + + // (TEST 23) Test assigning invalid descriptor values on the FontFace. + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var descriptorTests = Promise.resolve(); + Object.keys(invalidValues).forEach(function(aDesc) { + descriptorTests = descriptorTests.then(function() { + var face = new win.FontFace("test", fontData); + return face.loaded.then(function() { + var ok_todo = aDesc == "variant" ? todo : ok; + var exceptionName = ""; + try { + face[aDesc] = invalidValues[aDesc]; + } catch (ex) { + exceptionName = ex.name; + } + ok_todo(exceptionName == "SyntaxError", "assigning invalid value to FontFace." + aDesc + " should throw a SyntaxError (TEST 23) (" + what + ")"); + }, function(aError) { + ok(false, "FontFace should have loaded succesfully (TEST 23) (" + what + ")"); + }); + }); + }); + return descriptorTests; + }); + }); + return p; + + }).then(function() { + + // (TEST 24) Test that the status of a FontFace with a non-existing url() + // source is set to "loading" right after load() is called, that its .loaded + // Promise is returned, and that the Promise is eventually rejected with a + // NetworkError and its status is set to "error". + var p = Promise.resolve(); + sourceWindows.forEach(function({ win, what }) { + p = p.then(function() { + var face = new win.FontFace("test", "url(x)"); + var result = face.load(); + is(face.status, "loading", "FontFace.status should be \"loading\" right after load() is called (TEST 24) (" + what + ")"); + is(result, face.loaded, "FontFace.load() should return the .loaded Promise (TEST 24) (" + what + ")"); + + return result.then(function() { + ok(false, "FontFace with a non-existing url() source should not load (TEST 24) (" + what + ")"); + }, function(aError) { + is(aError.name, "NetworkError", "FontFace with a non-existing url() source should result in its .loaded Promise being rejected with a NetworkError (TEST 24) (" + what + ")"); + is(face.status, "error", "FontFace with a non-existing url() source should result in its .status being set to \"error\" (TEST 24) (" + what + ")"); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 25) Test simple manipulation of the FontFaceSet. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + p = p.then(function() { + var face, face2, all; + face = new win.FontFace("test", "url(x)"); + face2 = new win.FontFace("test2", "url(x)"); + ok(!doc.fonts.has(face), "newly created FontFace should not be in document.fonts (TEST 25) (" + what + ")"); + doc.fonts.add(face); + ok(doc.fonts.has(face), "should be able to add a FontFace to document.fonts (TEST 25) (" + what + ")"); + doc.fonts.add(face); + ok(doc.fonts.has(face), "should be able to repeatedly add a FontFace to document.fonts (TEST 25) (" + what + ")"); + ok(doc.fonts.delete(face), "FontFaceSet.delete should return true when it succeeds (TEST 25) (" + what + ")"); + ok(!doc.fonts.has(face), "FontFace should be gone from document.fonts after delete is called (TEST 25) (" + what + ")"); + ok(!doc.fonts.delete(face), "FontFaceSet.delete should return false when it fails (TEST 25) (" + what + ")"); + doc.fonts.add(face); + doc.fonts.add(face2); + ok(doc.fonts.has(face2), "should be able to add a second FontFace to document.fonts (TEST 25) (" + what + ")"); + doc.fonts.clear(); + ok(!doc.fonts.has(face) && !doc.fonts.has(face2), "FontFaces should be gone from document.fonts after clear is called (TEST 25) (" + what + ")"); + doc.fonts.add(face); + doc.fonts.add(face2); + all = Array.from(doc.fonts); + is(all[0], face, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")"); + is(all[1], face2, "FontFaces should be returned in the same order as insertion (TEST 25) (" + what + ")"); + doc.fonts.add(face); + all = Array.from(doc.fonts); + is(all[0], face, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")"); + is(all[1], face2, "FontFaces should be not be reordered when a duplicate entry is added (TEST 25) (" + what + ")"); + doc.fonts.clear(); + return doc.fonts.ready; + }); + }); + return p; + + }).then(function() { + + // (TEST 26) Test that FontFaceSet.ready is replaced, .status is set to + // "loading", and a loading event is dispatched when a loading FontFace is + // added to it. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + p = p.then(function() { + var awaitEvents = new Promise(function(aResolve, aReject) { + + var onloadingTriggered = false, loadingDispatched = false; + + function check() { + if (onloadingTriggered && loadingDispatched) { + doc.fonts.onloading = null; + doc.fonts.removeEventListener("loading", listener); + aResolve(); + } + } + + var listener = function(aEvent) { + is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")"); + loadingDispatched = true; + check(); + }; + doc.fonts.addEventListener("loading", listener); + doc.fonts.onloading = function(aEvent) { + is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 26) (" + what + ")"); + onloadingTriggered = true; + check(); + }; + }); + + is(doc.fonts.status, "loaded", "FontFaceSet.status initially (TEST 26) (" + what + ")"); + + var oldReady = doc.fonts.ready; + var face = new win.FontFace("test", "url(neverending_font_load.sjs)"); + face.load(); + doc.fonts.add(face); + + var newReady = doc.fonts.ready; + isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when a loading FontFace is added to it (TEST 26) (" + what + ")"); + is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when a loading FontFace is added to it (TEST 26) (" + what + ")"); + + return awaitEvents + .then(function() { + return is_pending(newReady, "FontFaceSet.ready should be replaced with a fresh pending Promise when a loading FontFace is added to it", "(TEST 26) (" + what + ")"); + }) + .then(function() { + doc.fonts.clear(); + return doc.fonts.ready; + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 27) Test that FontFaceSet.ready is resolved, .status is set to + // "loaded", and a loadingdone event (but no loadingerror event) is + // dispatched when the only loading FontFace in it is removed. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + p = p.then(function() { + var awaitEvents = new Promise(function(aResolve, aReject) { + + var onloadingdoneTriggered = false, loadingdoneDispatched = false; + var onloadingerrorTriggered = false, loadingerrorDispatched = false; + + function check() { + doc.fonts.onloadingdone = null; + doc.fonts.onloadingerror = null; + doc.fonts.removeEventListener("loadingdone", doneListener); + doc.fonts.removeEventListener("loadingerror", errorListener); + aResolve(); + } + + var doneListener = function(aEvent) { + is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")"); + is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")"); + loadingdoneDispatched = true; + check(); + }; + doc.fonts.addEventListener("loadingdone", doneListener); + doc.fonts.onloadingdone = function(aEvent) { + is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 27) (" + what + ")"); + is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 27) (" + what + ")"); + onloadingdoneTriggered = true; + check(); + }; + var errorListener = function(aEvent) { + loadingerrorDispatched = true; + check(); + } + doc.fonts.addEventListener("loadingerror", errorListener); + doc.fonts.onloadingerror = function(aEvent) { + onloadingdoneTriggered = true; + check(); + }; + }); + + is(doc.fonts.status, "loaded", "FontFaceSet.status should be \"loaded\" initially (TEST 27) (" + what + ")"); + + var f = new win.FontFace("test", "url(neverending_font_load.sjs)"); + f.load(); + doc.fonts.add(f); + + is(doc.fonts.status, "loading", "FontFaceSet.status should be \"loading\" when a loading FontFace is in it (TEST 27) (" + what + ")"); + + doc.fonts.clear(); + + return awaitEvents + .then(function() { + return is_resolved_with(doc.fonts.ready, doc.fonts, "FontFaceSet.ready when the FontFaceSet is cleared", "(TEST 27) (" + what + ")"); + }) + .then(function() { + is(doc.fonts.status, "loaded", "FontFaceSet.status should be set to \"loaded\" when it is cleared (TEST 27) (" + what + ")"); + return doc.fonts.ready; + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 28) Test that FontFaceSet.ready is replaced, .status is set to + // "loading", and a loading event is dispatched when a FontFace in it + // starts loading. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + p = p.then(function() { + var awaitEvents = new Promise(function(aResolve, aReject) { + + var onloadingTriggered = false, loadingDispatched = false; + + function check() { + if (onloadingTriggered && loadingDispatched) { + doc.fonts.onloading = null; + doc.fonts.removeEventListener("loading", listener); + aResolve(); + } + } + + var listener = function(aEvent) { + is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")"); + loadingDispatched = true; + check(); + }; + doc.fonts.addEventListener("loading", listener); + doc.fonts.onloading = function(aEvent) { + is(Object.getPrototypeOf(aEvent), doc.defaultView.Event.prototype, "loading event should be a plain Event object (TEST 28) (" + what + ")"); + onloadingTriggered = true; + check(); + }; + }); + + var oldReady = doc.fonts.ready; + var face = new win.FontFace("test", "url(neverending_font_load.sjs)"); + doc.fonts.add(face); + face.load(); + + var newReady = doc.fonts.ready; + isnot(newReady, oldReady, "FontFaceSet.ready should be replaced when its only FontFace starts loading (TEST 28) (" + what + ")"); + is(doc.fonts.status, "loading", "FontFaceSet.status should be set to \"loading\" when its only FontFace starts loading (TEST 28) (" + what + ")"); + + return awaitEvents + .then(function() { + return is_pending(newReady, "FontFaceSet.ready when the FontFaceSet's only FontFace starts loading", "(TEST 28) (" + what + ")"); + }) + .then(function() { + doc.fonts.clear(); + return doc.fonts.ready; + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 29) Test that a loadingdone and a loadingerror event is dispatched + // when a FontFace that eventually becomes status "error" is added to the + // FontFaceSet. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + p = p.then(function() { + var face; + var awaitEvents = new Promise(function(aResolve, aReject) { + + var onloadingdoneTriggered = false, loadingdoneDispatched = false; + var onloadingerrorTriggered = false, loadingerrorDispatched = false; + + function check() { + if (onloadingdoneTriggered && loadingdoneDispatched && + onloadingerrorTriggered && loadingerrorDispatched) { + doc.fonts.onloadingdone = null; + doc.fonts.onloadingerror = null; + doc.fonts.removeEventListener("loadingdone", doneListener); + doc.fonts.removeEventListener("loadingerror", errorListener); + aResolve(); + } + } + + var doneListener = function(aEvent) { + loadingdoneDispatched = true; + check(); + }; + doc.fonts.addEventListener("loadingdone", doneListener); + doc.fonts.onloadingdone = function(aEvent) { + is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingdone event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")"); + is(aEvent.fontfaces.length, 0, "the FontFaceSetLoadEvent should have an empty fontfaces array (TEST 29) (" + what + ")"); + onloadingdoneTriggered = true; + check(); + }; + var errorListener = function(aEvent) { + is(Object.getPrototypeOf(aEvent), doc.defaultView.FontFaceSetLoadEvent.prototype, "loadingerror event should be a FontFaceSetLoadEvent object (TEST 29) (" + what + ")"); + is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 29) (" + what + ")"); + loadingerrorDispatched = true; + check(); + } + doc.fonts.addEventListener("loadingerror", errorListener); + doc.fonts.onloadingerror = function(aEvent) { + onloadingerrorTriggered = true; + check(); + }; + }); + + face = new win.FontFace("test", "url(x)"); + face.load(); + is(face.status, "loading", "FontFace should have status \"loading\" (TEST 29) (" + what + ")"); + doc.fonts.add(face); + + return face.loaded + .then(function() { + ok(false, "the FontFace should not load (TEST 29) (" + what + ")"); + }, function(aError) { + is(face.status, "error", "FontFace should have status \"error\" (TEST 29) (" + what + ")"); + return awaitEvents; + }) + .then(function() { + doc.fonts.clear(); + return doc.fonts.ready; + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 30) Test that a loadingdone event is dispatched when a FontFace + // that eventually becomes status "loaded" is added to the FontFaceSet. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }, i) { + p = p.then(function() { + var face; + var awaitEvents = new Promise(function(aResolve, aReject) { + + var onloadingdoneTriggered = false, loadingdoneDispatched = false; + + function check() { + if (onloadingdoneTriggered && loadingdoneDispatched) { + doc.fonts.onloadingdone = null; + doc.fonts.removeEventListener("loadingdone", doneListener); + aResolve(); + } + } + + var doneListener = function(aEvent) { + loadingdoneDispatched = true; + check(); + }; + doc.fonts.addEventListener("loadingdone", doneListener); + doc.fonts.onloadingdone = function(aEvent) { + is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 30) (" + what + ")"); + onloadingdoneTriggered = true; + check(); + }; + }); + + face = new win.FontFace("test", "url(BitPattern.woff?test30." + i + ")"); + face.load(); + is(face.status, "loading", "FontFace should have status \"loading\" (TEST 30) (" + what + ")"); + doc.fonts.add(face); + + return face.loaded + .then(function() { + is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 30) (" + what + ")"); + return awaitEvents; + }) + .then(function() { + doc.fonts.clear(); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 31) Test that a loadingdone event is dispatched when a FontFace + // with status "unloaded" is added to the FontFaceSet and load() is called + // on it. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }, i) { + p = p.then(function() { + var face; + var awaitEvents = new Promise(function(aResolve, aReject) { + + var onloadingdoneTriggered = false, loadingdoneDispatched = false; + + function check() { + if (onloadingdoneTriggered && loadingdoneDispatched) { + doc.fonts.onloadingdone = null; + doc.fonts.removeEventListener("loadingdone", doneListener); + aResolve(); + } + } + + var doneListener = function(aEvent) { + loadingdoneDispatched = true; + check(); + }; + doc.fonts.addEventListener("loadingdone", doneListener); + doc.fonts.onloadingdone = function(aEvent) { + is(aEvent.fontfaces[0], face, "the FontFaceSetLoadEvent should have a fontfaces array with the FontFace in it (TEST 31) (" + what + ")"); + onloadingdoneTriggered = true; + check(); + }; + }); + + face = new win.FontFace("test", "url(BitPattern.woff?test31." + i + ")"); + is(face.status, "unloaded", "FontFace should have status \"unloaded\" (TEST 31) (" + what + ")"); + doc.fonts.add(face); + + return face.load() + .then(function() { + return awaitEvents; + }) + .then(function() { + is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 31) (" + what + ")"); + doc.fonts.clear(); + return doc.fonts.ready; + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 32) Test that pending restyles prevent document.fonts.status + // from becoming loaded. + var face = new FontFace("test", "url(neverending_font_load.sjs)"); + face.load(); + document.fonts.add(face); + + is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace (TEST 32)"); + + document.fonts.clear(); + + is(document.fonts.status, "loaded", "FontFaceSet.status after clearing (TEST 32)"); + + document.fonts.add(face); + + is(document.fonts.status, "loading", "FontFaceSet.status after adding a loading FontFace again (TEST 32)"); + + var div = document.querySelector("div"); + div.style.color = "blue"; + + document.fonts.clear(); + is(document.fonts.status, "loading", "FontFaceSet.status after clearing but when there is a pending restyle (TEST 32)"); + + return awaitRefresh() // wait for a refresh driver tick + .then(function() { + is(document.fonts.status, "loaded", "FontFaceSet.status after clearing and the restyle has been flushed (TEST 32)"); + return document.fonts.ready; + }); + + }).then(function() { + + // (TEST 33) Test that CSS-connected FontFace objects are created + // for @font-face rules in the document. + + is(document.fonts.status, "loaded", "document.fonts.status should initially be loaded (TEST 33)"); + + var style = document.querySelector("style"); + var ruleText = "@font-face { font-family: something; src: url(x); "; + Object.keys(nonDefaultValues).forEach(function(aDesc) { + ruleText += descriptorNames[aDesc] + ": " + nonDefaultValues[aDesc][0] + "; "; + }); + ruleText += "}"; + + style.textContent = ruleText; + + var rule = style.sheet.cssRules[0]; + + var all = Array.from(document.fonts); + is(all.length, 1, "document.fonts should contain one FontFace (TEST 33)"); + + var face = all[0]; + is(face.family, "\"something\"", "FontFace should have correct family value (TEST 33)"); + Object.keys(nonDefaultValues).forEach(function(aDesc) { + var ok_todo = aDesc == "variant" ? todo : ok; + ok_todo(face[aDesc] == nonDefaultValues[aDesc][1], "FontFace should have correct " + aDesc + " value (TEST 33)"); + }); + + is(document.fonts.status, "loaded", "document.fonts.status should still be loaded (TEST 33)"); + is(face.status, "unloaded", "FontFace.status should be unloaded (TEST 33)"); + + document.fonts.clear(); + ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when clear is called (TEST 33)"); + + is(document.fonts.delete(face), false, "attempting to remove CSS-connected FontFace from document.fonts should return false (TEST 33)"); + ok(document.fonts.has(face), "CSS-connected FontFace should not be removed from document.fonts when delete is called (TEST 33)"); + + style.textContent = ""; + + ok(!document.fonts.has(face), "CSS-connected FontFace should be removed from document.fonts once the rule has been removed (TEST 33)"); + + is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after rule is removed (TEST 33)"); + is(face.status, "unloaded", "FontFace.status should still be unloaded after rule is removed (TEST 33)"); + + document.fonts.add(face); + ok(document.fonts.has(face), "previously CSS-connected FontFace should be able to be added to document.fonts (TEST 33)"); + + is(document.fonts.status, "loaded", "document.fonts.status should still be loaded after now disconnected FontFace is added (TEST 33)"); + is(face.status, "unloaded", "FontFace.status should still be unloaded after now disconnected FontFace is added (TEST 33)"); + + document.fonts.delete(face); + ok(!document.fonts.has(face), "previously CSS-connected FontFace should be able to be removed from document.fonts (TEST 33)"); + + }).then(function() { + + // (TEST 34) Test that descriptor getters for unspecified descriptors on + // CSS-connected FontFace objects return their default values. + var style = document.querySelector("style"); + var ruleText = "@font-face { font-family: something; src: url(x); }"; + + style.textContent = ruleText; + + var all = Array.from(document.fonts); + var face = all[0]; + + Object.keys(defaultValues).forEach(function(aDesc) { + is(face[aDesc], defaultValues[aDesc], "FontFace should return default value for " + aDesc + " (TEST 34)"); + }); + + style.textContent = ""; + + }).then(function() { + + // (TEST 35) Test that no loadingdone event is dispatched when a FontFace + // with "loaded" status is added to a "loaded" FontFaceSet. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + p = p.then(function() { + var gotLoadingDone = false; + doc.fonts.onloadingdone = function(aEvent) { + gotLoadingDone = true; + }; + + is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 35) (" + what + ")"); + var face = new win.FontFace("test", fontData); + + return face.loaded + .then(function() { + is(face.status, "loaded", "FontFace should have status \"loaded\" (TEST 35) (" + what + ")"); + doc.fonts.add(face); + is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 35) (" + what + ")"); + return doc.fonts.ready; + }) + .then(function() { + ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 35) (" + what + ")"); + doc.fonts.onloadingdone = null; + doc.fonts.clear(); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 36) Test that no loadingdone or loadingerror event is dispatched + // when a FontFace with "error" status is added to a "loaded" FontFaceSet. + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + var doc = win.document; + p = p.then(function() { + var gotLoadingDone = false, gotLoadingError = false; + doc.fonts.onloadingdone = function(aEvent) { + gotLoadingDone = true; + }; + doc.fonts.onloadingerror = function(aEvent) { + gotLoadingError = true; + }; + + is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 36) (" + what + ")"); + var face = new win.FontFace("test", new ArrayBuffer(0)); + + return face.loaded + .then(function() { + ok(false, "FontFace should not have loaded (TEST 36) (" + what + ")"); + }, function() { + is(face.status, "error", "FontFace should have status \"error\" (TEST 36) (" + what + ")"); + doc.fonts.add(face); + is(doc.fonts.status, "loaded", "document.fonts.status should still have status \"loaded\" (TEST 36) (" + what + ")"); + return doc.fonts.ready; + }) + .then(function() { + ok(!gotLoadingDone, "loadingdone event should not be dispatched (TEST 36) (" + what + ")"); + ok(!gotLoadingError, "loadingerror event should not be dispatched (TEST 36) (" + what + ")"); + doc.fonts.onloadingdone = null; + doc.fonts.onloadingerror = null; + doc.fonts.clear(); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 37) Test that a FontFace only has one loadingdone event dispatched + // at the FontFaceSet containing it. + + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what}, i) { + p = p.then(function() { + return setTimeoutZero(); // wait for any previous events to be dispatched + }).then(function() { + var events = [], face, face2; + + var awaitEvents = new Promise(function(aResolve, aReject) { + doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) { + events.push(e); + if (events.length == 2) { + aResolve(); + } + }; + }); + + is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 37) (" + what + ")"); + + face = new win.FontFace("test", "url(BitPattern.woff?test37." + i + "a)"); + face.load(); + doc.fonts.add(face); + is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 37) (" + what + ")"); + + return doc.fonts.ready + .then(function() { + is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 37) (" + what + ")"); + is(face.status, "loaded", "first FontFace should have status \"loaded\" (TEST 37) (" + what + ")"); + + face2 = new win.FontFace("test2", "url(BitPattern.woff?test37." + i + "b)"); + face2.load(); + doc.fonts.add(face2); + is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 37) (" + what + ")"); + + return doc.fonts.ready; + }).then(function() { + return awaitEvents; + }).then(function() { + is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 37) (" + what + ")"); + is(face2.status, "loaded", "second FontFace should have status \"loaded\" (TEST 37) (" + what + ")"); + + is(events.length, 2, "should receive two events (TEST 37) (" + what + ")"); + + is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 37) (" + what + ")"); + is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 37) (" + what + ")"); + is(events[0].fontfaces[0], face, "first event should have the first FontFace"); + + is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 37) (" + what + ")"); + is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 37) (" + what + ")"); + is(events[1].fontfaces[0], face2, "second event should have the second FontFace (TEST 37) (" + what + ")"); + + doc.fonts.onloadingdone = null; + doc.fonts.onloadingerror = null; + doc.fonts.clear(); + return doc.fonts.ready; + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 38) Test that a FontFace only has one loadingerror event dispatched + // at the FontFaceSet containing it. + + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + p = p.then(function() { + return setTimeoutZero(); // wait for any previous events to be dispatched + }).then(function() { + var events = [], face, face2; + + var awaitEvents = new Promise(function(aResolve, aReject) { + doc.fonts.onloadingdone = doc.fonts.onloadingerror = function(e) { + events.push(e); + if (events.length == 4) { + aResolve(); + } + }; + }); + + is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 38) (" + what + ")"); + + face = new win.FontFace("test", "url(x)"); + face.load(); + doc.fonts.add(face); + is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font added (TEST 38) (" + what + ")"); + + return doc.fonts.ready + .then(function() { + is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font failed to load (TEST 38) (" + what + ")"); + is(face.status, "error", "first FontFace should have status \"error\" (TEST 38) (" + what + ")"); + + face2 = new win.FontFace("test2", "url(x)"); + face2.load(); + doc.fonts.add(face2); + is(doc.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font added (TEST 38) (" + what + ")"); + + return doc.fonts.ready; + }).then(function() { + return awaitEvents; + }).then(function() { + is(doc.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font failed to load (TEST 38) (" + what + ")"); + is(face2.status, "error", "second FontFace should have status \"error\" (TEST 38) (" + what + ")"); + + is(events.length, 4, "should receive four events (TEST 38) (" + what + ")"); + + is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 38) (" + what + ")"); + is(events[0].fontfaces.length, 0, "first event should have no FontFaces (TEST 38) (" + what + ")"); + + is(events[1].type, "loadingerror", "second event should be \"loadingerror\" (TEST 38) (" + what + ")"); + is(events[1].fontfaces.length, 1, "second event should have 1 FontFace (TEST 38) (" + what + ")"); + is(events[1].fontfaces[0], face, "second event should have the first FontFace"); + + is(events[2].type, "loadingdone", "third event should be \"loadingdone\" (TEST 38) (" + what + ")"); + is(events[2].fontfaces.length, 0, "third event should have no FontFaces (TEST 38) (" + what + ")"); + + is(events[3].type, "loadingerror", "third event should be \"loadingerror\" (TEST 38) (" + what + ")"); + is(events[3].fontfaces.length, 1, "third event should only have 1 FontFace (TEST 38) (" + what + ")"); + is(events[3].fontfaces[0], face2, "third event should have the second FontFace"); + + doc.fonts.onloadingdone = null; + doc.fonts.onloadingerror = null; + doc.fonts.clear(); + return doc.fonts.ready; + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 39) Test that a FontFace for an @font-face rule only has one + // loadingdone event dispatched at the FontFaceSet containing it. + + var style, all, events, awaitEvents; + + return setTimeoutZero() // wait for any previous events to be dispatched + .then(function() { + style = document.querySelector("style"); + var ruleText = "@font-face { font-family: test; src: url(BitPattern.woff?test39a); } " + + "@font-face { font-family: test2; src: url(BitPattern.woff?test39b); }"; + + style.textContent = ruleText; + + all = Array.from(document.fonts); + events = []; + + awaitEvents = new Promise(function(aResolve, aReject) { + document.fonts.onloadingdone = document.fonts.onloadingerror = function(e) { + events.push(e); + if (events.length == 2) { + aResolve(); + } + }; + }); + + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" (TEST 39)"); + + all[0].load(); + is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after first font loading (TEST 39)"); + + return document.fonts.ready + }).then(function() { + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after first font loaded (TEST 39)"); + is(all[0].status, "loaded", "first FontFace should have status \"loaded\" (TEST 39)"); + is(all[1].status, "unloaded", "second FontFace should have status \"unloaded\" (TEST 39)"); + + all[1].load(); + is(document.fonts.status, "loading", "document.fonts.status should have status \"loading\" after second font loading (TEST 39)"); + + return document.fonts.ready; + }).then(function() { + return awaitEvents; + }).then(function() { + is(document.fonts.status, "loaded", "document.fonts.status should have status \"loaded\" after second font loaded (TEST 39)"); + is(all[1].status, "loaded", "second FontFace should have status \"loaded\" (TEST 39)"); + + is(events.length, 2, "should receive two events (TEST 39)"); + + is(events[0].type, "loadingdone", "first event should be \"loadingdone\" (TEST 39)"); + is(events[0].fontfaces.length, 1, "first event should have 1 FontFace (TEST 39)"); + is(events[0].fontfaces[0], all[0], "first event should have the first FontFace"); + + is(events[1].type, "loadingdone", "second event should be \"loadingdone\" (TEST 39)"); + is(events[1].fontfaces.length, 1, "second event should only have 1 FontFace (TEST 39)"); + is(events[1].fontfaces[0], all[1], "second event should have the second FontFace (TEST 39)"); + + style.textContent = ""; + + document.fonts.onloadingdone = null; + document.fonts.onloadingerror = null; + document.fonts.clear(); + return document.fonts.ready; + }); + + }).then(function() { + + // (TEST 40) Test that an attempt to add the same FontFace object a second + // time to a FontFaceSet (where one of the FontFace objects is reflecting + // an @font-face rule) will be ignored. + + // First set up a @font-face rule. + var style = document.querySelector("style"); + style.textContent = "@font-face { font-family: something; src: url(x); }"; + + // Then add a couple of non-connected FontFace objects. + var f1 = new FontFace("test1", "url(x)"); + var f2 = new FontFace("test2", "url(x)"); + + document.fonts.add(f1); + document.fonts.add(f2); + + var all = Array.from(document.fonts); + var ruleFontFace = all[0]; + + is(all.length, 3, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 40)"); + is(all[1], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)"); + is(all[2], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 40)"); + + document.fonts.add(f1); + + all = Array.from(document.fonts); + is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #1 (TEST 40)"); + is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #1 (TEST 40)"); + is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)"); + is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #1 (TEST 40)"); + + document.fonts.add(ruleFontFace); + + all = Array.from(document.fonts); + is(all.length, 3, "number of FontFace objects in the FontFaceSet after duplicate add #2 (TEST 40)"); + is(all[0], ruleFontFace, "rule-based FontFace object in the FontFaceSEt after duplicate add #2 (TEST 40)"); + is(all[1], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)"); + is(all[2], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add #2 (TEST 40)"); + + style.textContent = ""; + + document.fonts.clear(); + + }).then(function() { + + // (TEST 41) Test that an attempt to add the same FontFace object a second + // time to a FontFaceSet (where none of the FontFace objects are reflecting + // an @font-face rule) will be ignored. + + sources.forEach(function({ win, doc, what }) { + // Add a couple of non-connected FontFace objects. + var f1 = new win.FontFace("test1", "url(x)"); + var f2 = new win.FontFace("test2", "url(x)"); + + doc.fonts.add(f1); + doc.fonts.add(f2); + + var all = Array.from(doc.fonts); + + is(all.length, 2, "number of FontFace objects in the FontFaceSet before duplicate add (TEST 41) (" + what + ")"); + is(all[0], f1, "first non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")"); + is(all[1], f2, "second non-connected FontFace object in the FontFaceSet before duplicate add (TEST 41) (" + what + ")"); + + doc.fonts.add(f1); + + all = Array.from(doc.fonts); + is(all.length, 2, "number of FontFace objects in the FontFaceSet after duplicate add (TEST 41) (" + what + ")"); + is(all[0], f1, "first non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")"); + is(all[1], f2, "second non-connected FontFace object in the FontFaceSet after duplicate add (TEST 41) (" + what + ")"); + + doc.fonts.clear(); + }); + + }).then(function() { + + // (TEST 42) Test that adding a FontFace to multiple FontFaceSets and then + // loading it updates the status of all FontFaceSets. + + var face = new FontFace("test", "url(x)"); + + sourceDocuments.forEach(function({ doc, what }) { + doc.fonts.add(face); + }); + + sourceDocuments.forEach(function({ doc, what }) { + is(doc.fonts.status, "loaded", what + ".fonts.status before loading (TEST 42)"); + }); + + face.load(); + + sourceDocuments.forEach(function({ doc, what }) { + is(doc.fonts.status, "loading", what + ".fonts.status after loading started (TEST 42)"); + }); + + return Promise.all(sourceDocuments.map(function({ doc }) { return doc.fonts.ready; })) + .then(function() { + is(face.status, "error", "FontFace.status after loading finished (TEST 42)"); + sourceDocuments.forEach(function({ doc, what }) { + is(doc.fonts.status, "loaded", what + ".fonts.status after loading finished (TEST 42)"); + }); + + sourceDocuments.forEach(function({ doc, what }) { + doc.fonts.clear(); + }); + }); + + }).then(function() { + + // (TEST 43) Test the check method with platform fonts and some + // degenerate cases. + + sourceDocuments.forEach(function({ doc, what }) { + // Invalid font shorthands should throw a SyntaxError. + try { + doc.fonts.check("Helvetica"); + ok(false, "check should throw when a syntactically invalid font shorthand is given (TEST 43) (" + what + ")"); + } catch (ex) { + is(ex.name, "SyntaxError", "exception name when check is called with a syntactically invalid font shorthand (TEST 43) (" + what + ")"); + } + + // System fonts should throw a SyntaxError. + try { + doc.fonts.check("caption"); + ok(false, "check should throw when a system font value is given (TEST 43) (" + what + ")"); + } catch (ex) { + is(ex.name, "SyntaxError", "exception name when check is called with a system font value (TEST 43) (" + what + ")"); + } + + // CSS-wide keywords should throw a SyntaxError. + try { + doc.fonts.check("inherit"); + ok(false, "check should throw when a CSS-wide keyword is given (TEST 43) (" + what + ")"); + } catch (ex) { + is(ex.name, "SyntaxError", "exception name when check is called with a CSS-wide keyword (TEST 43) (" + what + ")"); + } + + // CSS variables should throw a SyntaxError. + try { + doc.fonts.check("16px var(--family)"); + ok(false, "check should throw when CSS variables are used (TEST 43) (" + what + ")"); + } catch (ex) { + is(ex.name, "SyntaxError", "exception name when check is called with CSS variables (TEST 43) (" + what + ")"); + } + + // No matching font family names => return true. + is(doc.fonts.check("16px NonExistentFont1, NonExistentFont2"), true, "check return value when no matching font family names are used (TEST 43) (" + what + ")"); + + // Matching platform font family name => return true. + is(doc.fonts.check("16px NonExistentFont, " + likelyPlatformFonts), true, "check return value when a matching platform font family name is used (TEST 43) (" + what + ")"); + + // Matching platform font family name, but using a different test + // strings. (Platform fonts always return true from check, regardless + // of the actual glyphs present.) + [ + { test: "\0", desc: "a single non-matching glyph" }, + { test: "A\0", desc: "a matching and a non-matching glyph" }, + { test: "A", desc: "a matching glyph" }, + { test: "AB", desc: "multiple matching glyphs" } + ].forEach(function({ test, desc }) { + is(doc.fonts.check("16px " + likelyPlatformFonts, test), true, "check return value when a matching platform font family name is used but with " + desc + " (TEST 43) (" + what + ")"); + }); + + // No matching font family name, but an empty test string. + is(doc.fonts.check("16px NonExistentFont", ""), true, "check return value with a non-matching font family name and an empty test string (TEST 43) (" + what + ")"); + + // Matching platform font family name, but empty test string. + is(doc.fonts.check("16px " + likelyPlatformFonts, ""), true, "check return value with an empty test string (TEST 43) (" + what + ")"); + }); + + }).then(function() { + + // (TEST 44) Test the check method with script-created FontFaces. + + var tests = [ + // at least one matching FontFace is not loaded ==> false + { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }] }, + { result: false, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded" }] }, + { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "loaded" }] }, + { result: false, font: "16px Test", faces: [{ family: "Test", status: "loading" }] }, + { result: false, font: "16px Test", faces: [{ family: "Test", status: "error" }] }, + { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic" }] }, + { result: false, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold" }] }, + { result: false, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] }, + { result: false, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "unloaded" }] }, + + // all matching FontFaces are loaded ==> true + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "loaded" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded" }, { family: "Irrelevant", status: "unloaded" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed" }] }, + { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold" }, { family: "Test", status: "unloaded", weight: "600" }] }, + { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded" }, { family: "SecondTest", status: "loaded" }] }, + + // no matching FontFaces at all ==> true + { result: true, font: "16px Test", faces: [] }, + { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] }, + { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] }, + + // matching FontFace for one sample text character is loaded but + // not the other ==> false + { result: false, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "unloaded", unicodeRange: "U+62" }] }, + + // matching FontFaces for separate sample text characters are all + // loaded ==> true + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61" }, { family: "Test", status: "loaded", unicodeRange: "U+62" }] }, + ]; + + sources.forEach(function({ win, doc, what }, i) { + tests.forEach(function({ result, font, faces }, j) { + faces.forEach(function(f, k) { + var fontFace; + if (f.status == "loaded") { + fontFace = new win.FontFace(f.family, fontData, f); + } else if (f.status == "error") { + fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f); + } else { + fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test44." + [i, j, k] + ")", f); + if (f.status == "loading") { + fontFace.load(); + } + } + is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 44) (" + what + ")"); + doc.fonts.add(fontFace); + }); + is(doc.fonts.check(font, "ab"), result, "check return value for subtest " + j + " (TEST 44) (" + what + ")"); + doc.fonts.clear(); + }); + }); + + }).then(function() { + + // (TEST 45) Test the load method with platform fonts and some + // degenerate cases. + + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }) { + p = p.then(function() { + // Invalid font shorthands should reject the promise with a SyntaxError. + return doc.fonts.load("Helvetica").then(function() { + ok(false, "load should reject when a syntactically invalid font shorthand is given (TEST 45) (" + what + ")"); + }, function(ex) { + is(ex.name, "SyntaxError", "exception name when load is called with a syntactically invalid font shorthand (TEST 45) (" + what + ")"); + }); + }); + + p = p.then(function() { + // System fonts should reject with a SyntaxError. + return doc.fonts.load("caption").then(function() { + ok(false, "load should throw when a system font value is given (TEST 45) (" + what + ")"); + }, function(ex) { + is(ex.name, "SyntaxError", "exception name when load is called with a system font value (TEST 45) (" + what + ")"); + }); + }); + + p = p.then(function() { + // CSS-wide keywords should reject with a SyntaxError. + return doc.fonts.load("inherit").then(function() { + ok(false, "load should throw when a CSS-wide keyword is given (TEST 45) (" + what + ")"); + }, function(ex) { + is(ex.name, "SyntaxError", "exception name when load is called with a CSS-wide keyword (TEST 45) (" + what + ")"); + }); + }); + + p = p.then(function() { + // CSS variables should throw a SyntaxError. + return doc.fonts.load("16px var(--family)").then(function() { + ok(false, "load should throw when CSS variables are used (TEST 45) (" + what + ")"); + }, function(ex) { + is(ex.name, "SyntaxError", "exception name when load is called with CSS variables (TEST 45) (" + what + ")"); + }); + }); + + p = p.then(function() { + // No matching font family names => return true. + return doc.fonts.load("16px NonExistentFont1, NonExistentFont2").then(function(result) { + is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")"); + }); + }); + + p = p.then(function() { + // Matching platform font family name => return true. + return doc.fonts.load("16px NonExistentFont1, " + likelyPlatformFonts).then(function(result) { + is(result.length, 0, "load resolves with an emtpy array when no matching font family names are used (TEST 45) (" + what + ")"); + }); + }); + + // Matching platform font family name, but using a different test + // strings. (Platform fonts always return true from load, regardless + // of the actual glyphs present.) + [ + { sample: "\0", desc: "a single non-matching glyph" }, + { sample: "A\0", desc: "a matching and a non-matching glyph" }, + { sample: "A", desc: "a matching glyph" }, + { sample: "AB", desc: "multiple matching glyphs" } + ].forEach(function({ sample, desc }) { + p = p.then(function() { + return doc.fonts.load("16px " + likelyPlatformFonts, sample).then(function(result) { + is(result.length, 0, "load resolves with an empty array when a matching platform font family name is used but with " + desc + " (TEST 45) (" + what + ")"); + }); + }); + }); + + p = p.then(function() { + // No matching font family name, but an empty test string. + return doc.fonts.load("16px NonExistentFont", "").then(function(result) { + is(result.length, 0, "load resolves with an empty array when a non-matching platform font family name and an empty test string is used (TEST 45) (" + what + ")"); + }); + }); + + p = p.then(function() { + // Matching font family name, but an empty test string. + return doc.fonts.load("16px " + likelyPlatformFonts, "").then(function(result) { + is(result.length, 0, "load resolves with an empty array when a matching platform font family name and an empty test string is used (TEST 45) (" + what + ")"); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 46) Test the load method with script-created FontFaces. + + var tests = [ + // at least one matching FontFace is not yet loaded, but will load ==> resolve + { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }] }, + { result: true, font: "16px Test", faces: [{ family: "SecondTest", status: "loaded" }, { family: "Test", status: "unloaded", included: true }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", included: true }, { family: "Test", status: "loaded", included: true }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loading", included: true }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", style: "italic", included: true }] }, + { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600", included: true }] }, + { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "unloaded", weight: "600" }, { family: "Test", status: "unloaded", weight: "bold", included: true }] }, + { result: true, font: "16px Test, SecondTest", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] }, + { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "unloaded", included: true }] }, + + // at least one matching FontFace is in an error state ==> reject + { result: false, font: "16px Test", faces: [{ family: "Test", status: "unloaded" }, { family: "Test", status: "error" }] }, + + // all matching FontFaces are already loaded ==> resolve + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "loaded", included: true }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Test", status: "error", unicodeRange: "U+4E0A" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", included: true }, { family: "Irrelevant", status: "unloaded" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", style: "italic", included: true }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", stretch: "condensed", included: true }] }, + { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "bold", included: true }, { family: "Test", status: "loaded", weight: "600" }] }, + { result: true, font: "bold 16px Test", faces: [{ family: "Test", status: "loaded", weight: "600" }, { family: "Test", status: "loaded", weight: "bold", included: true }] }, + { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "Test", status: "loaded", included: true }, { family: "SecondTest", status: "loaded", included: true }] }, + + // no matching FontFaces at all ==> resolve + { result: true, font: "16px Test", faces: [] }, + { result: true, font: "16px Test", faces: [{ family: "Irrelevant", status: "unloaded" }] }, + { result: true, font: "16px Test", faces: [{ family: "Test", status: "unloaded", unicodeRange: "U+4E0A" }] }, + { result: true, font: "16px Test, " + likelyPlatformFonts + ", SecondTest, sans-serif", faces: [{ family: "ThirdTest", status: "loaded" }] }, + + // matching FontFace for one sample text character is already loaded but + // the other is not (but will) ==> resolve + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "unloaded", unicodeRange: "U+62", included: true }] }, + + // matching FontFaces for separate sample text characters are all + // loaded ==> resolve + { result: true, font: "16px Test", faces: [{ family: "Test", status: "loaded", unicodeRange: "U+61", included: true }, { family: "Test", status: "loaded", unicodeRange: "U+62", included: true }] }, + ]; + + var p = Promise.resolve(); + sources.forEach(function({ win, doc, what }, i) { + tests.forEach(function({ result, font, faces }, j) { + p = p.then(function() { + var fontFaces = []; + faces.forEach(function(f, k) { + var fontFace; + if (f.status == "loaded") { + fontFace = new win.FontFace(f.family, fontData, f); + } else if (f.status == "error") { + fontFace = new win.FontFace(f.family, new ArrayBuffer(0), f); + } else { + fontFace = new win.FontFace(f.family, "url(BitPattern.woff?test46." + [i, j, k] + ")", f); + if (f.status == "loading") { + fontFace.load(); + } + } + is(fontFace.status, f.status, "status of newly created FontFace " + [j, k] + " (TEST 46) (" + what + ")"); + doc.fonts.add(fontFace); + fontFaces.push(fontFace); + }); + return doc.fonts.load(font, "ab").then(function(array) { + ok(result, "load should resolve for subtest " + j + " (TEST 46) (" + what + ")"); + var expected = []; + for (var k = 0; k < faces.length; k++) { + if (faces[k].included) { + expected.push(fontFaces[k]); + } + } + is(array.length, expected.length, "length of array load resolves with for subtest " + j + " (TEST 46) (" + what + ")"); + for (var k = 0; k < array.length; k++) { + is(array[k], expected[k], "value in array[" + k + "] load resolves with for subtest " + j + " (TEST 46) (" + what + ")"); + } + }, function(ex) { + ok(!result, "load should not resolve for subtest " + j + " (TEST 46) (" + what + ")"); + is(ex.name, "SyntaxError", "exception load's return value is rejected with for subtest " + j + " (TEST 46) (" + what + ")"); + }).then(function() { + doc.fonts.clear(); + }); + }); + }); + }); + return p; + + }).then(function() { + + // (TEST 47) Test that CSS-connected FontFaces can't be added to other + // FontFaceSets. + + var style = document.querySelector("style"); + style.textContent = "@font-face { font-family: something; src: url(x); }"; + + var rule = style.sheet.cssRules[0]; + + var all = Array.from(document.fonts); + is(all.length, 1, "document.fonts should contain one FontFace (TEST 47)"); + + var face = all[0]; + + sourceDocuments.forEach(function({ doc, what }) { + if (doc == document) { + return; + } + + var exceptionName; + try { + doc.fonts.add(face); + ok(false, "add should throw when attempting to add a CSS-connected FontFace to another FontFaceSet (TEST 47) (" + what + ")"); + } catch (ex) { + is(ex.name, "InvalidModificationError", "exception name when add is called with a CSS-connected FontFace from another FontFaceSet (TEST 47) (" + what + ")"); + } + }); + + style.textContent = ""; + document.body.offsetTop; + + sourceDocuments.forEach(function({ doc, what }) { + if (doc == document) { + return; + } + + ok(!doc.fonts.has(face), "FontFaceSet initially doesn't have the FontFace (TEST 47) (" + what + ")"); + doc.fonts.add(face); + ok(doc.fonts.has(face), "add should allow a previously CSS-connected FontFace to be added to another FontFaceSet (TEST 47) (" + what + ")"); + doc.fonts.clear(); + }); + + document.fonts.clear(); + + }).then(function() { + + // (TEST 48) Test that FontFaceSets that hold a combination of FontFaces + // from different documents expose the right set of FontFaces. + + // Expected FontFaceSet contents. + var expected = { + document: [], + vdocument: [], + ndocument: [], + }; + + // Create a CSS-connected FontFace in the top-level document. + var style = document.querySelector("style"); + style.textContent = "@font-face { font-family: something; src: url(x); }"; + + var all = Array.from(document.fonts); + is(all.length, 1, "document.fonts should contain one FontFace (TEST 48)"); + + all[0]._description = "CSS-connected in document"; + expected.document.push(all[0]); + + // Create a CSS-connected FontFace in the visible iframe. + var vstyle = vdocument.querySelector("style"); + vstyle.textContent = "@font-face { font-family: somethingelse; src: url(x); }"; + + all = Array.from(vdocument.fonts); + all[0]._description = "CSS-connected in vdocument"; + is(all.length, 1, "vdocument.fonts should contain one FontFace (TEST 48)"); + + expected.vdocument.push(all[0]); + + // Create a FontFace in each window and add it to each document's FontFaceSet. + var i = 0; + var faces = []; + sourceWindows.forEach(function({ win, what: whatWin }) { + var f = new win.FontFace("test" + ++i, "url(x)"); + sourceDocuments.forEach(function({ doc, what: whatDoc }) { + doc.fonts.add(f); + expected[whatDoc].push(f); + f._description = whatWin + "/" + whatDoc; + }); + }); + + sourceDocuments.forEach(function({ doc, what }) { + var all = Array.from(doc.fonts); + is(expected[what].length, all.length, "expected FontFaceSet size (TEST 48) (" + what + ")"); + for (var i = 0; i < expected[what].length; i++) { + is(expected[what][i], all[i], "expected FontFace (" + expected[what][i]._description + ") at index " + i + " (TEST 48) (" + what + ")"); + } + }); + + vstyle.textContent = ""; + style.textContent = ""; + + sourceDocuments.forEach(function({ doc }) { doc.fonts.clear(); }); + + }).then(function() { + + // (TEST LAST) Test that a pending style sheet load prevents + // document.fonts.status from being set to "loaded". + + // First, add a FontFace to document.fonts that will load soon. + var face = new FontFace("test", "url(BitPattern.woff?testlast)"); + face.load(); + document.fonts.add(face); + + // Next, add a style sheet reference. + var link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = "neverending_stylesheet_load.sjs"; + link.type = "text/css"; + document.head.appendChild(link); + + return setTimeoutZero() // wait for the style sheet to start loading + .then(function() { + document.fonts.clear(); + is(document.fonts.status, "loading", "FontFaceSet.status when the FontFaceSet has been cleared of loading FontFaces but there is a pending style sheet load (TEST LAST)"); + document.head.removeChild(link); + // XXX Removing the <link> element won't cancel the load of the + // style sheet, so we can't do that to test that + // document.fonts.ready is resolved once there are no more + // loading style sheets. + }); + + // NOTE: It is important that this style sheet test comes last in the file, + // as the neverending style sheet load will interfere with subsequent + // sub-tests. + + }).then(function() { + + // End of the tests. + SimpleTest.finish(); + + }, function(aError) { + + // Something failed. + ok(false, "Something failed: " + aError); + SimpleTest.finish(); + + }); +} + +function start() { + if (SpecialPowers.getBoolPref("layout.css.font-loading-api.enabled")) { + SpecialPowers.pushPrefEnv({ set: [["layout.css.font-display.enabled", true]] }, + runTest); + } else { + ok(true, "CSS Font Loading API is not enabled."); + } +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(5); + +</script> + +<style></style> +<div></div> diff --git a/layout/style/test/test_garbage_at_end_of_declarations.html b/layout/style/test/test_garbage_at_end_of_declarations.html new file mode 100644 index 000000000..5be02a295 --- /dev/null +++ b/layout/style/test/test_garbage_at_end_of_declarations.html @@ -0,0 +1,151 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test handling of garbage at the end of CSS declarations</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +<div id="testnode"></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for correct ExpectEndProperty calls in CSS parser **/ + +/* + * Inspired by review comments on bug 378217. + * + * The original idea was to test that ExpectEndProperty calls are made + * in the correct places in the CSS parser so that we don't accept + * garbage at the end of property values. + * + * However, there's actually other code (in ParseDeclaration) that + * ensures that we don't accept garbage. + * + * Despite that, I'm checking it in anyway, since it caught an infinite + * loop in the patch for bug 435441. + */ + +var gElement = document.getElementById("testnode"); +var gDeclaration = gElement.style; +var gUnsetValueEnabled = SpecialPowers.getBoolPref("layout.css.unset-value.enabled"); + +/* + * This lists properties where garbage identifiers are allowed at the + * end, with values in property_database.js that are exceptions that + * should be tested anyway. "inherit", "initial" and "unset" are always + * tested. + */ +var gAllowsExtra = { + "counter-increment": { "none": true }, + "counter-reset": { "none": true }, + "font-family": {}, + "font": { "caption": true, "icon": true, "menu": true, "message-box": true, + "small-caption": true, "status-bar": true }, + "voice-family": {}, + "list-style": { + "inside none": true, "none inside": true, "none": true, + "none outside": true, "outside none": true, + 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")': true, + 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==") outside': true, + 'outside url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")': true + }, +}; + +/* These are the reverse of the above list; they're the unusual values + that do allow extra keywords afterwards */ +var gAllowsExtraUnusual = { + "transition": { "all": true, "0s": true, "0s 0s": true, "ease": true, + "1s 2s linear": true, "1s linear 2s": true, + "linear 1s 2s": true, "linear 1s": true, + "1s linear": true, "1s 2s": true, "2s 1s": true, + "linear": true, "1s": true, "2s": true, + "ease-in-out": true, "2s ease-in": true, + "ease-out 2s": true, "1s width, 2s": true }, + "animation": { "none": true, "0s": true, "ease": true, + "normal": true, "running": true, "1.0": true, + "1s 2s linear": true, "1s linear 2s": true, + "linear 1s 2s": true, "linear 1s": true, + "1s linear": true, "1s 2s": true, "2s 1s": true, + "linear": true, "1s": true, "2s": true, + "ease-in-out": true, "2s ease-in": true, + "ease-out 2s": true, "1s bounce, 2s": true, + "1s bounce, 2s none": true }, + "font-family": { "inherit": true, "initial": true, "unset": true } +}; + +gAllowsExtraUnusual["-moz-transition"] = gAllowsExtraUnusual["transition"]; +gAllowsExtraUnusual["-moz-animation"] = gAllowsExtraUnusual["animation"]; + +function test_property(property) +{ + var info = gCSSProperties[property]; + + function test_value(value) { + if (property in gAllowsExtra && + value != "inherit" && value != "initial" && value != "unset" && + !(value in gAllowsExtra[property])) { + return; + } + if (property in gAllowsExtraUnusual && + value in gAllowsExtraUnusual[property]) { + return; + } + + // Include non-identifier characters in the garbage + // in case |value| would also be valid with a <custom-ident> added. + gElement.setAttribute("style", property + ": " + value + " +blah/"); + if ("subproperties" in info) { + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + is(gDeclaration.getPropertyValue(subprop), "", + ["expected garbage ignored after '", property, ": ", value, + "' when looking at subproperty '", subprop, "'"].join("")); + } + } else { + is(gDeclaration.getPropertyValue(property), "", + ["expected garbage ignored after '", property, ": ", value, + "'"].join("")); + } + } + + var idx; + test_value("inherit"); + test_value("initial"); + if (gUnsetValueEnabled) + test_value("unset"); + for (idx in info.initial_values) + test_value(info.initial_values[idx]); + for (idx in info.other_values) + test_value(info.other_values[idx]); +} + +// To avoid triggering the slow script dialog, we have to test one +// property at a time. +SimpleTest.waitForExplicitFinish(); +var props = []; +for (var prop in gCSSProperties) + props.push(prop); +props = props.reverse(); +function do_one() { + if (props.length == 0) { + SimpleTest.finish(); + return; + } + test_property(props.pop()); + SimpleTest.executeSoon(do_one); +} +SimpleTest.executeSoon(do_one); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_grid_computed_values.html b/layout/style/test/test_grid_computed_values.html new file mode 100644 index 000000000..de77fec34 --- /dev/null +++ b/layout/style/test/test_grid_computed_values.html @@ -0,0 +1,106 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset=utf-8> + <title>Test computed grid values</title> + <link rel="author" title="Tobias Schneider" href="mailto:schneider@jancona.com"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel='stylesheet' href='/resources/testharness.css'> + <style> + + #grid { + display: grid; + width: 500px; + height: 400px; + grid-template-columns: + [a] auto + [b] minmax(min-content, 1fr) + [b c d] repeat(2, [e] 40px) + repeat(5, auto); + grid-template-rows: + [a] minmax(min-content, 1fr) + [b] auto + [b c d e] 30px 30px + auto auto; + grid-auto-columns: 3fr; + grid-auto-rows: 2fr; + } + #grid2 { + display: grid; + width: 500px; + height: 400px; + grid-auto-columns: 10px; + grid-auto-rows: 2fr; + } + + </style> +</head> +<body> + +<div> + <div id="grid"> + <div style="grid-column-start:1; width:50px"></div> + <div style="grid-column-start:9; width:50px"></div> + </div> + <div id="grid2"> + <div style="grid-column: span X / 1"></div> + <div style="grid-column: 1 / span X 2"></div> + </div> +<div> + +<script> + + var gridElement = document.getElementById("grid"); + + function test_grid_template(assert_fn, width, height, desc) { + test(function() { + assert_fn(getComputedStyle(gridElement).gridTemplateColumns, + "[a] 50px [b] " + width + "px [b c d e] 40px [e] 40px 0px 0px 0px 0px 50px"); + assert_fn(getComputedStyle(gridElement).gridTemplateRows, + "[a] " + height + "px [b] 0px [b c d e] 30px 30px 0px 0px"); + }, desc); + } + + test_grid_template(assert_equals, 320, 340, "test computed grid-template-{columns,rows} values"); + + gridElement.style.overflow = 'scroll'; + var v_scrollbar = gridElement.offsetWidth - gridElement.clientWidth; + var h_scrollbar = gridElement.offsetHeight - gridElement.clientHeight; + test_grid_template(assert_equals, 320 - v_scrollbar, 340 - h_scrollbar, + "test computed grid-template-{columns,rows} values, overflow: scroll"); + + gridElement.style.width = '600px'; + gridElement.style.overflow = 'visible'; + test_grid_template(assert_equals, 420, 340, + "test computed grid-template-{columns,rows} values, after reflow"); + + gridElement.style.display = 'none'; + test_grid_template(assert_not_equals, 420, 340, + "test computed grid-template-{columns,rows} values, display: none"); + + gridElement.style.display = 'grid'; + gridElement.parentNode.style.display = 'none'; + test_grid_template(assert_not_equals, 420, 340, + "test computed grid-template-{columns,rows} values, display: none on parent"); + + gridElement.parentNode.style.display = ''; + function test_grid2() { + gridElement = document.getElementById("grid2"); + test(function() { + assert_equals(getComputedStyle(gridElement).gridTemplateColumns, + "10px 10px 10px"); + assert_equals(getComputedStyle(gridElement, "").gridTemplateRows, + "400px"); + }, "test #grid2 computed grid-template-{columns,rows} values"); + } + + test(function() { + assert_equals(getComputedStyle(gridElement).gridAutoColumns, "3fr"); + assert_equals(getComputedStyle(gridElement).gridAutoRows, "2fr"); + test_grid2(); + }, "test computed grid-auto-{columns,rows} values"); + +</script> +</body> +</html> diff --git a/layout/style/test/test_grid_container_shorthands.html b/layout/style/test/test_grid_container_shorthands.html new file mode 100644 index 000000000..741404bc4 --- /dev/null +++ b/layout/style/test/test_grid_container_shorthands.html @@ -0,0 +1,282 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset=utf-8> + <title>Test parsing of grid container shorthands (grid-template, grid)</title> + <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel='stylesheet' href='/resources/testharness.css'> +</head> +<body> + +<script> + +var isGridTemplateSubgridValueEnabled = + SpecialPowers.getBoolPref("layout.css.grid-template-subgrid-value.enabled"); + +var initial_values = { + gridTemplateAreas: "none", + gridTemplateRows: "none", + gridTemplateColumns: "none", + gridAutoFlow: "row", + // Computed value for 'auto' + gridAutoRows: "auto", + gridAutoColumns: "auto", +}; + +// For various specified values of the grid-template shorthand, +// test the computed values of the corresponding longhands. +var grid_template_test_cases = [ + { + specified: "none", + }, + { + specified: "40px / 100px", + gridTemplateRows: "40px", + gridTemplateColumns: "100px", + }, + { + specified: "minmax(auto,1fr) / minmax(auto,1fr)", + gridTemplateRows: "1fr", + gridTemplateColumns: "1fr", + }, + { + specified: "[foo] 40px [bar] / [baz] 100px [fizz]", + gridTemplateRows: "[foo] 40px [bar]", + gridTemplateColumns: "[baz] 100px [fizz]", + }, + { + specified: " none/100px", + gridTemplateRows: "none", + gridTemplateColumns: "100px", + }, + { + specified: "40px/none", + gridTemplateRows: "40px", + gridTemplateColumns: "none", + }, + { + specified: "40px/repeat(1, 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "20px", + }, + { + specified: "40px/[a]repeat(1, 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] 20px", + }, + { + specified: "40px/repeat(1, [a] 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] 20px", + }, + { + specified: "40px/[a]repeat(2, [b]20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a b] 20px [b] 20px", + }, + { + specified: "40px/[a]repeat(2, 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] 20px 20px", + }, + { + specified: "40px/repeat(2, [a] 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] 20px [a] 20px", + }, + { + specified: "40px/[a]repeat(2, [b]20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a b] 20px [b] 20px", + }, + { + specified: "40px/repeat(2, 20px[a])", + gridTemplateRows: "40px", + gridTemplateColumns: "20px [a] 20px [a]", + }, + { + specified: "40px/repeat(2, 20px[a]) [b]", + gridTemplateRows: "40px", + gridTemplateColumns: "20px [a] 20px [a b]", + }, + { + specified: "40px/repeat(2, [a] 20px[b]) [c]", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] 20px [b a] 20px [b c]", + }, + { + specified: "40px/[a] repeat(3, [b c] 20px [d] 100px [e f]) [g]", + gridTemplateRows: "40px", + gridTemplateColumns: "[a b c] 20px [d] 100px [e f b c] 20px [d] 100px [e f b c] 20px [d] 100px [e f g]", + }, + { + specified: "'fizz'", + gridTemplateAreas: "\"fizz\"", + gridTemplateRows: "auto", + }, + { + specified: "[bar] 'fizz'", + gridTemplateAreas: "\"fizz\"", + gridTemplateRows: "[bar] auto", + }, + { + specified: "'fizz' / [foo] 40px", + gridTemplateAreas: "\"fizz\"", + gridTemplateRows: "auto", + gridTemplateColumns: "[foo] 40px", + }, + { + specified: "[bar] 'fizz' / [foo] 40px", + gridTemplateAreas: "\"fizz\"", + gridTemplateRows: "[bar] auto", + gridTemplateColumns: "[foo] 40px", + }, + { + specified: "'fizz' 100px / [foo] 40px", + gridTemplateAreas: "\"fizz\"", + gridTemplateRows: "100px", + gridTemplateColumns: "[foo] 40px", + }, + { + specified: "[bar] 'fizz' 100px [buzz] \n [a] '.' 200px [b] / [foo] 40px", + gridTemplateAreas: "\"fizz\" \".\"", + gridTemplateRows: "[bar] 100px [buzz a] 200px [b]", + gridTemplateColumns: "[foo] 40px", + }, + { + specified: "subgrid", + gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none", + gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid" : "none", + }, + { + specified: "subgrid / subgrid", + gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none", + gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid" : "none", + }, + { + specified: "subgrid [foo] / subgrid", + gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none", + gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid [foo]" : "none", + }, + { + specified: "subgrid [foo] repeat(3, [] [a b] [c]) / subgrid", + gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none", + gridTemplateRows: isGridTemplateSubgridValueEnabled ? + "subgrid [foo] [] [a b] [c] [] [a b] [c] [] [a b] [c]" : "none", + }, + { + // Test that the number of lines is clamped to kMaxLine = 10000. + specified: "subgrid [foo] repeat(999999999, [a]) / subgrid", + gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid" : "none", + // Array(n).join(s) is a hack for the non-standard s.repeat(n - 1) . + // [foo] + 9999 [a] gives us 10000 lines. + gridTemplateRows: isGridTemplateSubgridValueEnabled ? + "subgrid [foo]" + Array(10000).join(" [a]") : "none", + }, + { + specified: "subgrid [bar]/ subgrid [] [foo", + gridTemplateColumns: isGridTemplateSubgridValueEnabled ? "subgrid [] [foo]" : "none", + gridTemplateRows: isGridTemplateSubgridValueEnabled ? "subgrid [bar]" : "none", + }, + { + specified: "'fizz' repeat(1, 100px)", + }, + { + specified: "'fizz' repeat(auto-fill, 100px)", + }, + { + specified: "'fizz' / repeat(1, 100px)", + }, + { + specified: "'fizz' / repeat(auto-fill, 100px)", + }, +]; + +grid_test_cases = grid_template_test_cases.concat([ + { + specified: "auto-flow / 0", + gridAutoFlow: "row", + gridAutoRows: "auto", + gridTemplateColumns: "0px", + }, + { + specified: "auto-flow dense / 0", + gridAutoFlow: "row dense", + gridAutoRows: "auto", + gridTemplateColumns: "0px", + }, + { + specified: "auto-flow minmax(auto,1fr) / none", + gridAutoFlow: "row", + gridAutoRows: "1fr", + }, + { + specified: "auto-flow 40px / none", + gridAutoFlow: "row", + gridAutoRows: "40px", + }, + { + specified: "none / auto-flow 40px", + gridAutoFlow: "column", + gridAutoRows: "auto", + gridAutoColumns: "40px", + }, + { + specified: "none / auto-flow minmax(auto,1fr)", + gridAutoFlow: "column", + gridAutoRows: "auto", + gridAutoColumns: "1fr", + }, + { + specified: "0 / auto-flow dense auto", + gridAutoFlow: "column dense", + gridAutoRows: "auto", + gridAutoColumns: "auto", + gridTemplateRows: "0px", + }, + { + specified: "dense auto-flow minmax(min-content, 2fr) / 0", + gridAutoFlow: "row dense", + gridAutoRows: "minmax(min-content, 2fr)", + gridAutoColumns: "auto", + gridTemplateColumns: "0px", + }, + { + specified: "auto-flow 40px / 100px", + gridAutoFlow: "row", + gridAutoRows: "40px", + gridAutoColumns: "auto", + gridTemplateColumns: "100px", + }, +]); + +function run_tests(test_cases, shorthand, subproperties) { + test_cases.forEach(function(test_case) { + test(function() { + var element = document.createElement('div'); + document.body.appendChild(element); + element.style[shorthand] = test_case.specified; + var computed = window.getComputedStyle(element); + subproperties.forEach(function(longhand) { + assert_equals( + computed[longhand], + test_case[longhand] || initial_values[longhand], + longhand + ); + }); + }, "test parsing of 'grid-template: " + test_case.specified + "'"); + }); +} + +run_tests(grid_template_test_cases, "gridTemplate", [ + "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows"]); + +run_tests(grid_test_cases, "grid", [ + "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows", + "gridAutoFlow", "gridAutoColumns", "gridAutoRows"]); + +</script> +</body> +</html> diff --git a/layout/style/test/test_grid_item_shorthands.html b/layout/style/test/test_grid_item_shorthands.html new file mode 100644 index 000000000..a50be6112 --- /dev/null +++ b/layout/style/test/test_grid_item_shorthands.html @@ -0,0 +1,153 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset=utf-8> + <title>Test parsing of grid item shorthands (grid-column, grid-row, grid-area)</title> + <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel='stylesheet' href='/resources/testharness.css'> +</head> +<body> + +<script> + +// For various specified values of the grid-column and grid-row shorthands, +// test the computed values of the corresponding longhands. +var grid_column_row_test_cases = [ + { + specified: "3 / 4", + expected_start: "3", + expected_end: "4", + }, + { + specified: "foo / span bar", + expected_start: "foo", + expected_end: "span bar", + }, + // http://dev.w3.org/csswg/css-grid/#placement-shorthands + // "When the second value is omitted, + // if the first value is a <custom-ident>, + // the grid-row-end/grid-column-end longhand + // is also set to that <custom-ident>; + // otherwise, it is set to auto." + { + specified: "foo", + expected_start: "foo", + expected_end: "foo", + }, + { + specified: "7", + expected_start: "7", + expected_end: "auto", + }, + { + specified: "foo 7", + expected_start: "7 foo", + expected_end: "auto", + }, + { + specified: "span foo", + expected_start: "span foo", + expected_end: "auto", + }, + { + specified: "foo 7 span", + expected_start: "span 7 foo", + expected_end: "auto", + }, + { + specified: "7 span", + expected_start: "span 7", + expected_end: "auto", + }, +]; + +// For various specified values of the grid-area shorthand, +// test the computed values of the corresponding longhands. +var grid_area_test_cases = [ + { + specified: "10 / 20 / 30 / 40", + gridRowStart: "10", + gridColumnStart: "20", + gridRowEnd: "30", + gridColumnEnd: "40", + }, + { + specified: "foo / bar / baz", + gridRowStart: "foo", + gridColumnStart: "bar", + gridRowEnd: "baz", + gridColumnEnd: "bar", + }, + { + specified: "foo / span bar / baz", + gridRowStart: "foo", + gridColumnStart: "span bar", + gridRowEnd: "baz", + gridColumnEnd: "auto", + }, + { + specified: "foo / bar", + gridRowStart: "foo", + gridColumnStart: "bar", + gridRowEnd: "foo", + gridColumnEnd: "bar", + }, + { + specified: "foo / 4", + gridRowStart: "foo", + gridColumnStart: "4", + gridRowEnd: "foo", + gridColumnEnd: "auto", + }, + { + specified: "foo", + gridRowStart: "foo", + gridColumnStart: "foo", + gridRowEnd: "foo", + gridColumnEnd: "foo", + }, + { + specified: "7", + gridRowStart: "7", + gridColumnStart: "auto", + gridRowEnd: "auto", + gridColumnEnd: "auto", + }, +] + +grid_column_row_test_cases.forEach(function(test_case) { + ["Column", "Row"].forEach(function(axis) { + var shorthand = "grid" + axis; + var start_longhand = "grid" + axis + "Start"; + var end_longhand = "grid" + axis + "End"; + test(function() { + var element = document.createElement('div'); + document.body.appendChild(element); + element.style[shorthand] = test_case.specified; + var computed = window.getComputedStyle(element); + assert_equals(computed[start_longhand], test_case.expected_start); + assert_equals(computed[end_longhand], test_case.expected_end); + }, "test parsing of '" + shorthand + ": " + test_case.specified + "'"); + }); +}); + +grid_area_test_cases.forEach(function(test_case) { + test(function() { + var element = document.createElement('div'); + document.body.appendChild(element); + element.style.gridArea = test_case.specified; + var computed = window.getComputedStyle(element); + [ + "gridRowStart", "gridColumnStart", "gridRowEnd", "gridColumnEnd" + ].forEach(function(longhand) { + assert_equals(computed[longhand], test_case[longhand], longhand); + }); + }, "test parsing of 'grid-area: " + test_case.specified + "'"); +}); + +</script> + +</body> +</html> diff --git a/layout/style/test/test_grid_shorthand_serialization.html b/layout/style/test/test_grid_shorthand_serialization.html new file mode 100644 index 000000000..dcb6f8880 --- /dev/null +++ b/layout/style/test/test_grid_shorthand_serialization.html @@ -0,0 +1,236 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset=utf-8> + <title>Test serialization of CSS 'grid' shorthand property</title> + <link rel="author" title="Simon Sapin" href="mailto:simon.sapin@exyr.org"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel='stylesheet' href='/resources/testharness.css'> +</head> +<body> + +<script> + +var isGridTemplateSubgridValueEnabled = + SpecialPowers.getBoolPref("layout.css.grid-template-subgrid-value.enabled"); + +var initial_values = { + gridTemplateAreas: "none", + gridTemplateRows: "none", + gridTemplateColumns: "none", + gridAutoFlow: "row", + gridAutoRows: "auto", + gridAutoColumns: "auto", + gridRowGap: "0px", + gridColumnGap: "0px", +}; + +// For various specified values of the grid-template subproperties, +// test the serialization of the shorthand. +var grid_template_test_cases = [ + { + gridTemplateColumns: "100px", + shorthand: "none / 100px", + }, + { + gridTemplateRows: "minmax(auto,1fr)", + shorthand: "1fr / none", + }, + { + gridTemplateColumns: "minmax(auto,1fr)", + shorthand: "none / 1fr", + }, + { + gridTemplateRows: "40px", + shorthand: "40px / none", + }, + { + gridTemplateRows: "40px", + gridTemplateColumns: "subgrid", + shorthand: isGridTemplateSubgridValueEnabled ? "40px / subgrid" : "", + }, + { + gridTemplateRows: "[foo] 40px [bar]", + gridTemplateColumns: "[baz] 100px [fizz]", + shorthand: "[foo] 40px [bar] / [baz] 100px [fizz]", + }, + { + gridTemplateAreas: "\"a\"", + gridTemplateRows: "20px", + shorthand: "\"a\" 20px", + }, + { + gridTemplateAreas: "\"a\"", + gridTemplateRows: "[foo] 20px [bar]", + shorthand: "[foo] \"a\" 20px [bar]", + }, + { + gridTemplateAreas: "\"a\"", + gridTemplateRows: "[foo] repeat(1, 20px) [bar]", + shorthand: "[foo] \"a\" 20px [bar]", + }, + { + gridTemplateAreas: "\"a a\"", + gridTemplateColumns: "repeat(2, 100px)", + gridTemplateRows: "auto", + shorthand: "\"a a\" auto / 100px 100px", + }, + // Combinations of longhands that make the shorthand non-serializable: + { + gridTemplateAreas: "\"a\"", + gridTemplateRows: "20px 100px", + shorthand: "", + }, + { + gridTemplateAreas: "\"a\" \"b\"", + gridTemplateRows: "20px", + shorthand: "", + }, + { + gridTemplateAreas: "\"a\"", + gridTemplateRows: "subgrid", + shorthand: "", + }, + { + gridTemplateAreas: "\"a\"", + gridTemplateRows: "subgrid [foo]", + shorthand: "", + }, + { + gridTemplateAreas: "\"a\"", + gridTemplateRows: "20px", + gridTemplateColumns: "subgrid", + shorthand: "", + }, + { + gridTemplateAreas: "\"a\"", + gridTemplateRows: "repeat(auto-fill, 20px)", + shorthand: "", + }, + { + gridTemplateAreas: "\"a\"", + gridTemplateColumns: "repeat(auto-fill, 100px)", + gridTemplateRows: "auto", + shorthand: "", + }, +]; + +grid_test_cases = grid_template_test_cases.concat([ + { + gridAutoFlow: "row", + shorthand: "none / none", + }, + { + gridAutoRows: "40px", + shorthand: "auto-flow 40px / none", + }, + { + gridAutoRows: "minmax(auto,1fr)", + shorthand: "auto-flow 1fr / none", + }, + { + gridAutoFlow: "column dense", + gridAutoRows: "minmax(min-content, max-content)", + shorthand: "", + }, + { + gridAutoFlow: "column dense", + gridAutoColumns: "minmax(min-content, max-content)", + shorthand: "none / auto-flow dense minmax(min-content, max-content)", + }, + { + gridAutoFlow: "column", + gridAutoColumns: "minmax(auto,1fr)", + shorthand: "none / auto-flow 1fr", + }, + { + gridAutoFlow: "row dense", + gridAutoColumns: "minmax(min-content, 2fr)", + shorthand: "", + }, + { + gridAutoFlow: "row dense", + gridAutoRows: "minmax(min-content, 2fr)", + shorthand: "auto-flow dense minmax(min-content, 2fr) / none", + }, + { + gridAutoFlow: "row", + gridAutoRows: "40px", + gridTemplateColumns: "100px", + shorthand: "auto-flow 40px / 100px", + }, + { + gridAutoFlow: "row", + gridRowGap: "0px", + shorthand: "none / none", + }, + { + gridAutoFlow: "row", + gridRowGap: "1px", + shorthand: "", + }, + { + gridAutoFlow: "row", + gridColumnGap: "1px", + shorthand: "", + }, +]); + +var grid_important_test_cases = [ + { + "grid-auto-flow": "row", + "grid-row-gap": "0px", + shorthand: "", + }, + { + "grid-auto-flow": "row", + "grid-column-gap": "1px", + shorthand: "", + }, +]; + + +function run_tests(test_cases, shorthand, subproperties) { + test_cases.forEach(function(test_case) { + test(function() { + var element = document.createElement('div'); + document.body.appendChild(element); + subproperties.forEach(function(longhand) { + element.style[longhand] = test_case[longhand] || + initial_values[longhand]; + }); + assert_equals(element.style[shorthand], test_case.shorthand); + }, "test shorthand serialization " + JSON.stringify(test_case)); + }); +} + +function run_important_tests(test_cases, shorthand, subproperties) { + test_cases.forEach(function(test_case) { + test(function() { + var element = document.createElement('div'); + document.body.appendChild(element); + subproperties.forEach(function(longhand) { + element.style.setProperty(longhand, + test_case[longhand] || initial_values[longhand], + "important"); + }); + assert_equals(element.style[shorthand], test_case.shorthand); + }, "test shorthand serialization " + JSON.stringify(test_case)); + }); +} + +run_tests(grid_template_test_cases, "gridTemplate", [ + "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows"]); + +run_tests(grid_test_cases, "grid", [ + "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows", + "gridAutoFlow", "gridAutoColumns", "gridAutoRows", "gridColumnGap", "gridRowGap"]); + +run_important_tests(grid_important_test_cases, "grid", [ + "grid-template-areas", "grid-template-columns", "grid-template-rows", + "grid-auto-flow", "grid-auto-columns", "grid-auto-rows", "grid-column-gap", "grid-row-gap"]); + +</script> +</body> +</html> diff --git a/layout/style/test/test_group_insertRule.html b/layout/style/test/test_group_insertRule.html new file mode 100644 index 000000000..85edc2a1a --- /dev/null +++ b/layout/style/test/test_group_insertRule.html @@ -0,0 +1,243 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>CSS Variables Allowed Syntax</title> + <link rel="author" title="L. David Baron" href="https://dbaron.org/"> + <link rel="author" title="Mozilla Corporation" href="http://mozilla.com/" /> + <link rel="help" href="http://www.w3.org/TR/css3-conditional/#the-cssgroupingrule-interface"> + <meta name="assert" content="requirements in definition of insertRule"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +<style id="style"> +@media print {} +</style> +<script id="metadata_cache">/* +{ + "rule_type": {}, + "rule_length": {}, + "insert_import_throws": {}, + "insert_index_throws1": {}, + "insert_index_throws2": {}, + "insert_media_succeed": {}, + "insert_style_succeed": {}, + "insert_bad_media_throw": {}, + "insert_empty_throw": {}, + "insert_garbage_after_media_throw": {}, + "insert_garbage_after_style_throw": {}, + "insert_two_media_throw": {}, + "insert_style_media_throw": {}, + "insert_media_style_throw": {}, + "insert_two_style_throw": {}, + "insert_retval": {} +} +*/</script> +</head> +<body onload="run()"> +<div id=log></div> +<div id="test"></div> +<script> + + var sheet = document.getElementById("style").sheet; + + var grouping_rule = sheet.cssRules[0]; + + test(function() { + assert_equals(grouping_rule.type, CSSRule.MEDIA_RULE, + "Rule type of @media rule"); + }, + "rule_type"); + + test(function() { + assert_equals(grouping_rule.cssRules.length, 0, + "Starting cssRules.length of @media rule"); + }, + "rule_length"); + + test(function() { + assert_throws("HIERARCHY_REQUEST_ERR", + function() { + grouping_rule.insertRule("@import url(foo.css);", 0); + }, + "inserting a disallowed rule should throw HIERARCHY_REQUEST_ERR"); + }, + "insert_import_throws"); + + test(function() { + assert_throws("INDEX_SIZE_ERR", + function() { + grouping_rule.insertRule("p { color: green }", 1); + }, + "inserting at a bad index throws INDEX_SIZE_ERR"); + }, + "insert_index_throws1"); + test(function() { + grouping_rule.insertRule("p { color: green }", 0); + assert_equals(grouping_rule.cssRules.length, 1, + "Modified cssRules.length of @media rule"); + grouping_rule.insertRule("p { color: blue }", 1); + assert_equals(grouping_rule.cssRules.length, 2, + "Modified cssRules.length of @media rule"); + grouping_rule.insertRule("p { color: aqua }", 1); + assert_equals(grouping_rule.cssRules.length, 3, + "Modified cssRules.length of @media rule"); + assert_throws("INDEX_SIZE_ERR", + function() { + grouping_rule.insertRule("p { color: green }", 4); + }, + "inserting at a bad index throws INDEX_SIZE_ERR"); + assert_equals(grouping_rule.cssRules.length, 3, + "Modified cssRules.length of @media rule"); + }, + "insert_index_throws2"); + + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + grouping_rule.insertRule("@media print {}", 0); + assert_equals(grouping_rule.cssRules.length, 1, + "Modified cssRules.length of @media rule"); + assert_equals(grouping_rule.cssRules[0].type, CSSRule.MEDIA_RULE, + "inserting syntactically correct media rule succeeds"); + }, + "insert_media_succeed"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + grouping_rule.insertRule("p { color: yellow }", 0); + assert_equals(grouping_rule.cssRules.length, 1, + "Modified cssRules.length of @media rule"); + assert_equals(grouping_rule.cssRules[0].type, CSSRule.STYLE_RULE, + "inserting syntactically correct style rule succeeds"); + }, + "insert_style_succeed"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + assert_throws("SYNTAX_ERR", + function() { + grouping_rule.insertRule("@media bad syntax;", 0); + }, + "inserting syntactically invalid rule throws syntax error"); + assert_equals(grouping_rule.cssRules.length, 0, + "Modified cssRules.length of @media rule"); + }, + "insert_bad_media_throw"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + assert_throws("SYNTAX_ERR", + function() { + grouping_rule.insertRule("", 0); + }, + "inserting empty rule throws syntax error"); + assert_equals(grouping_rule.cssRules.length, 0, + "Modified cssRules.length of @media rule"); + }, + "insert_empty_throw"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + assert_throws("SYNTAX_ERR", + function() { + grouping_rule.insertRule("@media print {} foo", 0); + }, + "inserting rule with garbage afterwards throws syntax error"); + assert_equals(grouping_rule.cssRules.length, 0, + "Modified cssRules.length of @media rule"); + }, + "insert_garbage_after_media_throw"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + assert_throws("SYNTAX_ERR", + function() { + grouping_rule.insertRule("p { color: yellow } foo", 0); + }, + "inserting rule with garbage afterwards throws syntax error"); + assert_equals(grouping_rule.cssRules.length, 0, + "Modified cssRules.length of @media rule"); + }, + "insert_garbage_after_style_throw"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + assert_throws("SYNTAX_ERR", + function() { + grouping_rule.insertRule("@media print {} @media print {}", 0); + }, + "inserting multiple rules throws syntax error"); + assert_equals(grouping_rule.cssRules.length, 0, + "Modified cssRules.length of @media rule"); + }, + "insert_two_media_throw"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + assert_throws("SYNTAX_ERR", + function() { + grouping_rule.insertRule("p { color: yellow } @media print {}", 0); + }, + "inserting multiple rules throws syntax error"); + assert_equals(grouping_rule.cssRules.length, 0, + "Modified cssRules.length of @media rule"); + }, + "insert_style_media_throw"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + assert_throws("SYNTAX_ERR", + function() { + grouping_rule.insertRule("@media print {} p { color: yellow }", 0); + }, + "inserting multiple rules throws syntax error"); + assert_equals(grouping_rule.cssRules.length, 0, + "Modified cssRules.length of @media rule"); + }, + "insert_media_style_throw"); + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + assert_throws("SYNTAX_ERR", + function() { + grouping_rule.insertRule("p { color: yellow } p { color: yellow }", 0); + }, + "inserting multiple rules throws syntax error"); + assert_equals(grouping_rule.cssRules.length, 0, + "Modified cssRules.length of @media rule"); + }, + "insert_two_style_throw"); + + test(function() { + while (grouping_rule.cssRules.length > 0) { + grouping_rule.deleteRule(0); + } + var res = grouping_rule.insertRule("p { color: green }", 0); + assert_equals(res, 0, "return value should be index"); + assert_equals(grouping_rule.cssRules.length, 1, + "Modified cssRules.length of @media rule"); + res = grouping_rule.insertRule("p { color: green }", 0); + assert_equals(res, 0, "return value should be index"); + assert_equals(grouping_rule.cssRules.length, 2, + "Modified cssRules.length of @media rule"); + res = grouping_rule.insertRule("p { color: green }", 2); + assert_equals(res, 2, "return value should be index"); + assert_equals(grouping_rule.cssRules.length, 3, + "Modified cssRules.length of @media rule"); + }, + "insert_retval"); + + +</script> +</body> +</html> + diff --git a/layout/style/test/test_hover_quirk.html b/layout/style/test/test_hover_quirk.html new file mode 100644 index 000000000..c14b741c3 --- /dev/null +++ b/layout/style/test/test_hover_quirk.html @@ -0,0 +1,82 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=783213 +--> +<head> + <meta charset="utf-8"> + <title>Test for the :active and :hover quirk</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <style type="text/css"> + /* Should apply to all elements: */ + #content :hover:first-of-type { + color: rgb(255, 0, 0); + } + #content :-moz-any(:hover) { + text-transform: lowercase; + } + #content :hover::after { + content: "any element"; + } + #content :hover:first-of-type .child::after { + content: "any child"; + } + + /* Should apply only to links: */ + #content :hover { + color: rgb(0, 255, 0) !important; + text-transform: uppercase !important; + } + #content :hover .child::after { + content: "link child" !important; + } + </style> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript"> + /** Test for the :active and :hover quirk **/ + function test(element, isLink) { + if (!isLink) + var styles = {color: "rgb(255, 0, 0)", textTransform: "lowercase", + childContent: '"any child"'}; + else + var styles = {color: "rgb(0, 255, 0)", textTransform: "uppercase", + childContent: '"link child"'}; + + // Trigger the :hover pseudo-class. + synthesizeMouseAtCenter(element, {type: "mousemove"}); + + var computedStyle = getComputedStyle(element); + is(computedStyle.color, styles.color, "Unexpected color value"); + is(computedStyle.textTransform, styles.textTransform, + "Unexpected text-transform value"); + + computedStyle = getComputedStyle(element, "::after"); + is(computedStyle.content, '"any element"', + "Unexpected pseudo-element content"); + + computedStyle = getComputedStyle( + element.getElementsByClassName("child")[0], "::after"); + is(computedStyle.content, styles.childContent, + "Unexpected pseudo-element content for child"); + } + + SimpleTest.waitForExplicitFinish(); + SimpleTest.waitForFocus(function() { + test(document.getElementById("span"), false); + test(document.getElementById("label"), false); + test(document.getElementById("link"), true); + SimpleTest.finish(); + }); + </script> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783213">Mozilla Bug 783213</a> + <p id="display"></p> + <div id="content"> + <span id="span">Span<span class="child"></span></span><br> + <label id="label">Label<span class="child"></span></label><br> + <a id="link" href="#">Link<span class="child"></span></a> + </div> + <pre id="test"></pre> +</body> +</html> diff --git a/layout/style/test/test_html_attribute_computed_values.html b/layout/style/test/test_html_attribute_computed_values.html new file mode 100644 index 000000000..3f7013cc1 --- /dev/null +++ b/layout/style/test/test_html_attribute_computed_values.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <title>Test for Bug </title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<div id="content"></div> +<pre id="test"> +<script type="application/javascript"> + + +var gValues = [ + { + element: "<li type='i'></li>", + property: "list-style-type", + value: "lower-roman" + }, + { + element: "<li type='I'></li>", + property: "list-style-type", + value: "upper-roman" + }, + { + element: "<li type='a'></li>", + property: "list-style-type", + value: "lower-alpha" + }, + { + element: "<li type='A'></li>", + property: "list-style-type", + value: "upper-alpha" + }, + { + element: "<li type='1'></li>", + property: "list-style-type", + value: "decimal" + }, + { + element: "<ol type='i'></ol>", + property: "list-style-type", + value: "lower-roman" + }, + { + element: "<ol type='I'></ol>", + property: "list-style-type", + value: "upper-roman" + }, + { + element: "<ol type='a'></ol>", + property: "list-style-type", + value: "lower-alpha" + }, + { + element: "<ol type='A'></ol>", + property: "list-style-type", + value: "upper-alpha" + }, + { + element: "<ol type='1'></ol>", + property: "list-style-type", + value: "decimal" + }, +]; + +var content = document.getElementById("content"); +for (var i = 0; i < gValues.length; ++i) { + var v = gValues[i]; + + content.innerHTML = v.element; + is(getComputedStyle(content.firstChild, "").getPropertyValue(v.property), + v.value, + v.property + " for " + v.element); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_ident_escaping.html b/layout/style/test/test_ident_escaping.html new file mode 100644 index 000000000..8d7f32ffa --- /dev/null +++ b/layout/style/test/test_ident_escaping.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=543428 +--> +<head> + <title>Test for Bug 543428</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css" id="sheet">p { color: blue; }</style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=543428">Mozilla Bug 543428</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 543428 **/ + +var sheet = document.getElementById("sheet").sheet; +var rule = sheet.cssRules[0]; + +function set_selector_text(selector) + // no cssText or selectorText setter implemented yet +{ + try { + // insertRule might throw on syntax error + sheet.insertRule(selector + " { color : green }", 0); + sheet.deleteRule(1); + } catch(ex) {} + rule = sheet.cssRules[0]; +} + +is(rule.selectorText, "p", "simple identifier not escaped"); +set_selector_text('\\P'); +is(rule.selectorText, "P", "simple identifier not escaped"); +set_selector_text('\\70'); +is(rule.selectorText, "p", "simple identifier not escaped"); +set_selector_text('font-family_72756'); +is(rule.selectorText, "font-family_72756", "simple identifier not escaped"); +set_selector_text('-font-family_72756'); +is(rule.selectorText, "-font-family_72756", "simple identifier not escaped"); +set_selector_text('-0invalid'); +set_selector_text('0invalid'); +is(rule.selectorText, "-font-family_72756", "setting invalid value ignored"); +set_selector_text('Håkon\\ Lie'); +is(rule.selectorText, "Håkon\\ Lie", "escaping done only where needed"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_inherit_computation.html b/layout/style/test/test_inherit_computation.html new file mode 100644 index 000000000..a4407418c --- /dev/null +++ b/layout/style/test/test_inherit_computation.html @@ -0,0 +1,159 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for computation of CSS 'inherit' on all properties and 'unset' on inherited properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <style type="text/css" id="stylesheet"></style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"><span id="fparent"><span id="fchild"></span></span></p> +<div id="content" style="display: none"> + +<div id="testnode"><span id="nparent"><span id="nchild"></span></span></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for computation of CSS 'inherit' on all properties and 'unset' on + inherited properties **/ + +// elements without a frame +var gNParent = document.getElementById("nparent"); +var gNChild = document.getElementById("nchild"); +// elements with a frame +var gFParent = document.getElementById("fparent"); +var gFChild = document.getElementById("fchild"); + +var gStyleSheet = document.getElementById("stylesheet").sheet; +var gChildRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #fchild {}", gStyleSheet.cssRules.length)]; +var gChildRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #fchild {}", gStyleSheet.cssRules.length)]; +var gChildRule3 = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild.allother, #fchild.allother {}", gStyleSheet.cssRules.length)]; +var gChildRuleTop = gStyleSheet.cssRules[gStyleSheet.insertRule("#nchild, #nchild.allother, #fchild, #fchild.allother {}", gStyleSheet.cssRules.length)]; +var gParentRuleTop = gStyleSheet.cssRules[gStyleSheet.insertRule("#nparent, #fparent {}", gStyleSheet.cssRules.length)]; + +var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled"); + +function get_computed_value_node(node, property) +{ + var cs = getComputedStyle(node, ""); + return get_computed_value(cs, property); +} + +function test_property(property) +{ + var info = gCSSProperties[property]; + + var keywords = ["inherit"]; + if (info.inherited && gTestUnset) + keywords.push("unset"); + + keywords.forEach(function(keyword) { + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + gParentRuleTop.style.setProperty(prereq, prereqs[prereq], ""); + gChildRuleTop.style.setProperty(prereq, prereqs[prereq], ""); + } + } + + if (info.inherited) { + gParentRuleTop.style.setProperty(property, info.initial_values[0], ""); + var initial_computed_n = get_computed_value_node(gNChild, property); + var initial_computed_f = get_computed_value_node(gFChild, property); + gChildRule1.style.setProperty(property, info.other_values[0], ""); + var other_computed_n = get_computed_value_node(gNChild, property); + var other_computed_f = get_computed_value_node(gFChild, property); + isnot(other_computed_n, initial_computed_n, + "should be testing with values that compute to different things " + + "for '" + property + "'"); + isnot(other_computed_f, initial_computed_f, + "should be testing with values that compute to different things " + + "for '" + property + "'"); + gChildRule3.style.setProperty(property, keyword, ""); + gFChild.className="allother"; + gNChild.className="allother"; + var inherit_initial_computed_n = get_computed_value_node(gNChild, property); + var inherit_initial_computed_f = get_computed_value_node(gFChild, property); + is(inherit_initial_computed_n, initial_computed_n, + keyword + " should cause inheritance of initial value for '" + + property + "'"); + is(inherit_initial_computed_f, initial_computed_f, + keyword + " should cause inheritance of initial value for '" + + property + "'"); + gParentRuleTop.style.setProperty(property, info.other_values[0], ""); + var inherit_other_computed_n = get_computed_value_node(gNChild, property); + var inherit_other_computed_f = get_computed_value_node(gFChild, property); + is(inherit_other_computed_n, other_computed_n, + keyword + " should cause inheritance of other value for '" + + property + "'"); + is(inherit_other_computed_f, other_computed_f, + keyword + " should cause inheritance of other value for '" + + property + "'"); + gParentRuleTop.style.removeProperty(property); + gChildRule1.style.removeProperty(property); + gChildRule3.style.setProperty(property, info.other_values[0], ""); + gFChild.className=""; + gNChild.className=""; + } else { + gParentRuleTop.style.setProperty(property, info.other_values[0], ""); + var initial_computed_n = get_computed_value_node(gNChild, property); + var initial_computed_f = get_computed_value_node(gFChild, property); + var other_computed_n = get_computed_value_node(gNParent, property); + var other_computed_f = get_computed_value_node(gFParent, property); + isnot(other_computed_n, initial_computed_n, + "should be testing with values that compute to different things " + + "for '" + property + "'"); + isnot(other_computed_f, initial_computed_f, + "should be testing with values that compute to different things " + + "for '" + property + "'"); + gChildRule2.style.setProperty(property, keyword, ""); + var inherit_other_computed_n = get_computed_value_node(gNChild, property); + var inherit_other_computed_f = get_computed_value_node(gFChild, property); + is(inherit_other_computed_n, other_computed_n, + keyword + " should cause inheritance of other value for '" + + property + "'"); + is(inherit_other_computed_f, other_computed_f, + keyword + " should cause inheritance of other value for '" + + property + "'"); + gParentRuleTop.style.removeProperty(property); + gChildRule1.style.setProperty(property, info.other_values[0], ""); + var inherit_initial_computed_n = get_computed_value_node(gNChild, property); + var inherit_initial_computed_f = get_computed_value_node(gFChild, property); + is(inherit_initial_computed_n, initial_computed_n, + keyword + " should cause inheritance of initial value for '" + + property + "'"); + is(inherit_initial_computed_f, initial_computed_f, + keyword + " should cause inheritance of initial value for '" + + property + "'"); + gParentRuleTop.style.removeProperty(property); + gChildRule1.style.removeProperty(property); + gChildRule2.style.removeProperty(property); + } + + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + gParentRuleTop.style.removeProperty(prereq); + gChildRuleTop.style.removeProperty(prereq); + } + } + }); +} + +for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + gChildRule3.style.setProperty(prop, info.other_values[0], ""); +} + +for (var prop in gCSSProperties) + test_property(prop); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_inherit_storage.html b/layout/style/test/test_inherit_storage.html new file mode 100644 index 000000000..f78c70558 --- /dev/null +++ b/layout/style/test/test_inherit_storage.html @@ -0,0 +1,116 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=375363 +--> +<head> + <title>Test for parsing, storage, and serialization of CSS 'inherit' on all properties and 'unset' on inherited properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375363">Mozilla Bug 375363</a> +<p id="display"></p> +<div id="content" style="display: none"> + +<div id="testnode"></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for parsing, storage, and serialization of CSS 'inherit' on all + properties and 'unset' on inherited properties **/ + +var gDeclaration = document.getElementById("testnode").style; + +var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled"); + +function test_property(property) +{ + var info = gCSSProperties[property]; + + var keywords = ["inherit"]; + if (info.inherited && gTestUnset) + keywords.push("unset"); + + keywords.forEach(function(keyword) { + function check_initial(sproperty) { + var sinfo = gCSSProperties[sproperty]; + var val = gDeclaration.getPropertyValue(sproperty); + is(val, "", "value of '" + sproperty + "' before we do anything"); + is(val, gDeclaration[sinfo.domProp], + "consistency between decl.getPropertyValue('" + sproperty + "') and decl." + sinfo.domProp); + } + check_initial(property); + if ("subproperties" in info) + for (var idx in info.subproperties) + check_initial(info.subproperties[idx]); + + gDeclaration.setProperty(property, keyword, ""); + + function check_set(sproperty) { + var sinfo = gCSSProperties[sproperty]; + val = gDeclaration.getPropertyValue(sproperty); + is(val, keyword, + keyword + " reported back for property '" + sproperty + "'"); + is(val, gDeclaration[sinfo.domProp], + "consistency between decl.getPropertyValue('" + sproperty + + "') and decl." + sinfo.domProp); + } + check_set(property); + if ("subproperties" in info) + for (var idx in info.subproperties) + check_set(info.subproperties[idx]); + + // We don't care particularly about the whitespace or the placement of + // semicolons, but for simplicity we'll test the current behavior. + if ("alias_for" in info) { + is(gDeclaration.cssText, info.alias_for + ": " + keyword + ";", + "declaration should serialize to exactly what went in (for " + keyword + ")"); + } else { + is(gDeclaration.cssText, property + ": " + keyword + ";", + "declaration should serialize to exactly what went in (for " + keyword + ")"); + } + + gDeclaration.removeProperty(property); + + function check_final(sproperty) { + var sinfo = gCSSProperties[sproperty]; + var val = gDeclaration.getPropertyValue(sproperty); + is(val, "", "value of '" + sproperty + "' after removal of value"); + is(val, gDeclaration[sinfo.domProp], + "consistency between decl.getPropertyValue('" + sproperty + "') and decl." + sinfo.domProp); + } + check_final(property); + if ("subproperties" in info) + for (var idx in info.subproperties) + check_final(info.subproperties[idx]); + + // can all properties be removed from the style? + function test_remove_all_properties(property, value) { + var i, p = []; + for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]); + for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]); + var errstr = "when setting property " + property + " to " + value; + is(gDeclaration.length, 0, "unremovable properties " + errstr); + is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr); + } + + // sanity check shorthands to make sure disabled props aren't exposed + if (info.type != CSS_TYPE_LONGHAND) { + gDeclaration.setProperty(property, keyword, ""); + test_remove_all_properties(property, keyword); + gDeclaration.removeProperty(property); + } + }); +} + +for (var prop in gCSSProperties) + test_property(prop); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_initial_computation.html b/layout/style/test/test_initial_computation.html new file mode 100644 index 000000000..8da53ed45 --- /dev/null +++ b/layout/style/test/test_initial_computation.html @@ -0,0 +1,163 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for computation of CSS 'initial' on all properties and 'unset' on reset properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <style type="text/css" id="stylesheet"></style> + <style type="text/css"> + /* For 'width', 'height', etc., need a constant size container. */ + #display { width: 500px; height: 200px } + </style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + var load_count = 0; + function load_done() { + if (++load_count == 3) + run_tests(); + } + </script> +</head> +<body> +<p id="display"><span><span id="elementf"></span></span> +<iframe id="unstyledn" src="unstyled.xml" height="10" width="10" onload="load_done()"></iframe> +<iframe id="unstyledf" src="unstyled-frame.xml" height="10" width="10" onload="load_done()"></iframe> +</p> +<div id="content" style="display: none"> + +<div><span id="elementn"></span></div> + + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for computation of CSS 'initial' on all properties and 'unset' on + reset properties **/ + +var gBrokenInitial = { +}; + +function xfail_initial(property) { + return property in gBrokenInitial; +} + +var gElementN = document.getElementById("elementn"); +var gElementF = document.getElementById("elementf"); +var gStyleSheet = document.getElementById("stylesheet").sheet; +var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)]; +var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)]; + +var gInitialValuesN; +var gInitialValuesF; +var gInitialPrereqsRuleN; +var gInitialPrereqsRuleF; + +var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled"); + +function setup_initial_values(id, ivalprop, prereqprop) { + var iframe = document.getElementById(id); + window[ivalprop] = iframe.contentWindow.getComputedStyle( + iframe.contentDocument.documentElement.firstChild, ""); + var sheet = iframe.contentDocument.styleSheets[0]; + // For 'width', 'height', etc., need a constant size container. + sheet.insertRule(":root { height: 200px; width: 500px }", sheet.cssRules.length); + + window[prereqprop] = sheet.cssRules[sheet.insertRule(":root > * {}", sheet.cssRules.length)]; +} + +function test_property(property) +{ + var info = gCSSProperties[property]; + + var keywords = ["initial"]; + if (!info.inherited && gTestUnset) + keywords.push("unset"); + + keywords.forEach(function(keyword) { + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + gRule1.style.setProperty(prereq, prereqs[prereq], ""); + gInitialPrereqsRuleN.style.setProperty(prereq, prereqs[prereq], ""); + gInitialPrereqsRuleF.style.setProperty(prereq, prereqs[prereq], ""); + } + } + if (info.inherited) { + gElementN.parentNode.style.setProperty(property, info.other_values[0], ""); + gElementF.parentNode.style.setProperty(property, info.other_values[0], ""); + } + + var initial_computed_n = get_computed_value(gInitialValuesN, property); + var initial_computed_f = get_computed_value(gInitialValuesF, property); + gRule1.style.setProperty(property, info.other_values[0], ""); + var other_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property); + var other_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property); + isnot(other_computed_n, initial_computed_n, + "should be testing with values that compute to different things " + + "for '" + property + "'"); + isnot(other_computed_f, initial_computed_f, + "should be testing with values that compute to different things " + + "for '" + property + "'"); + // It's important (given the current design of nsRuleNode) that we're + // modifying the most specific rule that matches the element, and that + // we've already requested style while that rule was empty. This + // means we'll have a cached aStartStruct from the parent in the rule + // tree (caching the "other" value), so we'll make sure we don't get + // the initial value from the luck of default-initialization. + // This means that it's important that we set the prereqs on + // gRule1.style rather than on gElement.style. + gRule2.style.setProperty(property, keyword, ""); + var initial_val_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property); + var initial_val_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property); + (xfail_initial(property) ? todo_is : is)( + initial_val_computed_n, initial_computed_n, + keyword + " should cause initial value for '" + property + "'"); + (xfail_initial(property) ? todo_is : is)( + initial_val_computed_f, initial_computed_f, + keyword + " should cause initial value for '" + property + "'"); + gRule1.style.removeProperty(property); + gRule2.style.removeProperty(property); + + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + gRule1.style.removeProperty(prereq); + gInitialPrereqsRuleN.style.removeProperty(prereq); + gInitialPrereqsRuleF.style.removeProperty(prereq); + } + } + if (info.inherited) { + gElementN.parentNode.style.removeProperty(property); + gElementF.parentNode.style.removeProperty(property); + } + + // FIXME: Something (maybe with the -moz-binding values in + // test_value_computation.html, but may as well do it here to match) + // causes gElementF's frame to get lost. Force it to get recreated + // after each property. + gElementF.parentNode.style.display = "none"; + get_computed_value(getComputedStyle(gElementF, ""), "width"); + gElementF.parentNode.style.display = ""; + get_computed_value(getComputedStyle(gElementF, ""), "width"); + }); +} + +function run_tests() { + setup_initial_values("unstyledn", "gInitialValuesN", "gInitialPrereqsRuleN"); + setup_initial_values("unstyledf", "gInitialValuesF", "gInitialPrereqsRuleF"); + for (var prop in gCSSProperties) + test_property(prop); + SimpleTest.finish(); +} + +load_done(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_initial_storage.html b/layout/style/test/test_initial_storage.html new file mode 100644 index 000000000..f75c0d68c --- /dev/null +++ b/layout/style/test/test_initial_storage.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=375363 +--> +<head> + <title>Test for parsing, storage, and serialization of CSS 'initial' on all properties and 'unset' on reset properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375363">Mozilla Bug 375363</a> +<p id="display"></p> +<div id="content" style="display: none"> + +<div id="testnode"></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for parsing, storage, and serialization of CSS 'initial' on all + properties and 'unset' on reset properties **/ + +var gDeclaration = document.getElementById("testnode").style; + +var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled"); + +/** + * Checks that the passed-in property-value (returned by getPropertyValue) is + * consistent with the DOM accessors that we know about for the given sproperty. + */ +function check_consistency(sproperty, valFromGetPropertyValue, messagePrefix) +{ + var sinfo = gCSSProperties[sproperty]; + is(valFromGetPropertyValue, gDeclaration[sinfo.domProp], + `(${messagePrefix}) consistency between ` + + `decl.getPropertyValue(${sproperty}) and decl.${sinfo.domProp}`); + + if (sinfo.domProp.startsWith("webkit")) { + // For webkit-prefixed DOM accessors, test with lowercase and uppercase + // first letter. + var uppercaseDomProp = "W" + sinfo.domProp.substring(1); + is(valFromGetPropertyValue, gDeclaration[uppercaseDomProp], + `(${messagePrefix}) consistency between ` + + `decl.getPropertyValue(${sproperty}) and decl.${uppercaseDomProp}`); + } +} + +function test_property(property) +{ + var info = gCSSProperties[property]; + + var keywords = ["initial"]; + if (!info.inherited && gTestUnset) + keywords.push("unset"); + + keywords.forEach(function(keyword) { + function check_initial(sproperty) { + var val = gDeclaration.getPropertyValue(sproperty); + is(val, "", "value of '" + sproperty + "' before we do anything"); + check_consistency(sproperty, val, "initial"); + } + check_initial(property); + if ("subproperties" in info) + for (var idx in info.subproperties) + check_initial(info.subproperties[idx]); + + gDeclaration.setProperty(property, keyword, ""); + + function check_set(sproperty) { + val = gDeclaration.getPropertyValue(sproperty); + is(val, keyword, + keyword + " reported back for property '" + sproperty + "'"); + check_consistency(sproperty, val, "set"); + } + check_set(property); + if ("subproperties" in info) + for (var idx in info.subproperties) + check_set(info.subproperties[idx]); + + // We don't care particularly about the whitespace or the placement of + // semicolons, but for simplicity we'll test the current behavior. + if ("alias_for" in info) { + is(gDeclaration.cssText, info.alias_for + ": " + keyword + ";", + "declaration should serialize to exactly what went in (for " + keyword + ")"); + } else { + is(gDeclaration.cssText, property + ": " + keyword + ";", + "declaration should serialize to exactly what went in (for " + keyword + ")"); + } + + gDeclaration.removeProperty(property); + + function check_final(sproperty) { + var val = gDeclaration.getPropertyValue(sproperty); + is(val, "", "value of '" + sproperty + "' after removal of value"); + check_consistency(sproperty, val, "final"); + } + check_final(property); + if ("subproperties" in info) + for (var idx in info.subproperties) + check_final(info.subproperties[idx]); + + // can all properties be removed from the style? + function test_remove_all_properties(property, value) { + var i, p = []; + for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]); + for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]); + var errstr = "when setting property " + property + " to " + value; + is(gDeclaration.length, 0, "unremovable properties " + errstr); + is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr); + } + + // sanity check shorthands to make sure disabled props aren't exposed + if (info.type != CSS_TYPE_LONGHAND) { + gDeclaration.setProperty(property, keyword, ""); + test_remove_all_properties(property, keyword); + gDeclaration.removeProperty(property); + } + }); +} + +for (var prop in gCSSProperties) + test_property(prop); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_keyframes_rules.html b/layout/style/test/test_keyframes_rules.html new file mode 100644 index 000000000..8446f22dd --- /dev/null +++ b/layout/style/test/test_keyframes_rules.html @@ -0,0 +1,134 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=577974 +--> +<head> + <title>Test for Bug 577974</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="style"> + + @keyframes bounce { + from { + margin-left: 0 + } + + /* + * These rules should get dropped due to syntax errors. The script + * below tests that the 25% rule following them is at cssRules[1]. + */ + from, { margin-left: 0 } + from , { margin-left: 0 } + , from { margin-left: 0 } + ,from { margin-left: 0 } + from from { margin-left: 0 } + from, 1 { margin-left: 0 } + 1 { margin-left: 0 } + 1, from { margin-left: 0 } + from, 1.0 { margin-left: 0 } + 1.0 { margin-left: 0 } + 1.0, from { margin-left: 0 } + + 25% { + margin-left: 25px; + } + + 75%, 85% { + margin-left: 90px; + } + + 100% { + margin-left: 100px; + } + } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=577974">Mozilla Bug 577974</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 577974 **/ + +var sheet = document.getElementById("style").sheet; + +var bounce = sheet.cssRules[0]; +is(bounce.type, CSSRule.KEYFRAMES_RULE, "bounce.type"); +is(bounce.type, 7, "bounce.type"); +is(bounce.name, "bounce", "bounce.name"); +bounce.name = "bouncier"; +is(bounce.name, "bouncier", "setting bounce.name"); + +is(bounce.cssRules[0].type, CSSRule.KEYFRAME_RULE, "keyframe rule type"); +is(bounce.cssRules[0].type, 8, "keyframe rule type"); +is(bounce.cssRules[0].keyText, "0%", "keyframe rule keyText"); +is(bounce.cssRules[1].keyText, "25%", "keyframe rule keyText"); +is(bounce.cssRules[2].keyText, "75%, 85%", "keyframe rule keyText"); +is(bounce.cssRules[3].keyText, "100%", "keyframe rule keyText"); +is(bounce.cssRules[0].style.marginLeft, "0px", "keyframe rule style"); +is(bounce.cssRules[1].style.marginLeft, "25px", "keyframe rule style"); + +is(bounce.cssRules[0].cssText, "0% { margin-left: 0px; }"); +is(bounce.cssText, "@keyframes bouncier {\n" + + "0% { margin-left: 0px; }\n" + + "25% { margin-left: 25px; }\n" + + "75%, 85% { margin-left: 90px; }\n" + + "100% { margin-left: 100px; }\n" + + "}"); + +bounce.cssRules[1].keyText = "from, 1"; // syntax error +bounce.cssRules[1].keyText = "from, x"; // syntax error +bounce.cssRules[1].keyText = "from,"; // syntax error +bounce.cssRules[1].keyText = "from x"; // syntax error +bounce.cssRules[1].keyText = "x"; // syntax error +is(bounce.cssRules[1].keyText, "25%", "keyframe rule keyText parsing"); +bounce.cssRules[1].keyText = "from, 10%"; +is(bounce.cssRules[1].keyText, "0%, 10%", "keyframe rule keyText parsing"); +bounce.cssRules[1].keyText = "from, 0%"; +is(bounce.cssRules[1].keyText, "0%, 0%", "keyframe rule keyText parsing"); +bounce.cssRules[1].keyText = "from, from, from"; +is(bounce.cssRules[1].keyText, "0%, 0%, 0%", "keyframe rule keyText parsing"); + +is(bounce.findRule("75%"), null, "findRule should match all keys"); +is(bounce.findRule("85%, 75%"), null, + "findRule should match all keys in order"); +is(bounce.findRule("75%,85%"), bounce.cssRules[2], + "findRule should match all keys in order, parsed"); +is(bounce.findRule("to"), bounce.cssRules[3], + "findRule should match keys as parsed"); +is(bounce.findRule("100%"), bounce.cssRules[3], + "findRule should match keys as parsed"); +is(bounce.findRule("100%, 100%"), null, + "findRule should match key list"); +is(bounce.findRule("100%,"), null, + "findRule should fail when given bad selector"); +is(bounce.findRule(",100%"), null, + "findRule should fail when given bad selector"); +is(bounce.cssRules.length, 4, "length of css rules"); +bounce.deleteRule("85%"); +is(bounce.cssRules.length, 4, "deleteRule should match all keys"); +bounce.deleteRule("85%, 75%"); +is(bounce.cssRules.length, 4, "deleteRule should match key list"); +bounce.deleteRule("75% ,85%"); +is(bounce.cssRules.length, 3, "deleteRule should match keys in order, parsed"); +bounce.deleteRule("0%"); +is(bounce.cssRules.length, 2, "deleteRule should match keys in order, parsed"); +bounce.appendRule("from { color: blue }"); +is(bounce.cssRules.length, 3, "appendRule should append"); +is(bounce.cssRules[2].keyText, "0%", "appendRule should append"); +bounce.appendRule("from { color: blue }"); +is(bounce.cssRules.length, 4, "appendRule should append"); +is(bounce.cssRules[3].keyText, "0%", "appendRule should append"); +bounce.appendRule("from { color: blue } to { color: green }"); +is(bounce.cssRules.length, 4, "appendRule should ignore garbage at end"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_load_events_on_stylesheets.html b/layout/style/test/test_load_events_on_stylesheets.html new file mode 100644 index 000000000..c6dedbed1 --- /dev/null +++ b/layout/style/test/test_load_events_on_stylesheets.html @@ -0,0 +1,152 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=185236 +--> +<head> + <title>Test for Bug 185236</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script> + var pendingEventCounter = 0; + var messagePosted = false; + SimpleTest.waitForExplicitFinish(); + addLoadEvent(function() { + is(messagePosted, true, "Should have gotten onmessage event"); + is(pendingEventCounter, 0, + "How did onload for the page fire before onload for all the stylesheets?"); + SimpleTest.finish(); + }); + // Count the link we're about to parse + pendingEventCounter = 1; + </script> + <link rel="stylesheet" href="data:text/css,*{}" + onload="--pendingEventCounter; + ok(true, 'Load event firing on basic stylesheet')" + onerror="--pendingEventCounter; + ok(false, 'Error event firing on basic stylesheet')"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=185236">Mozilla Bug 185236</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +/** Test for Bug 185236 **/ +// Verify that there are no in-flight sheet loads right now; we should have +// waited for them when we hit the script tag +is(pendingEventCounter, 0, "There should be no pending events"); + +// Test sheet that will already be complete when we write it out +++pendingEventCounter; + +// Make sure that a postMessage we do right now fires after the onload handler +// for the stylesheet. If we ever change the timing of sheet onload, we will +// need to change that. +window.onmessage = function() { + messagePosted = true; + // There are 4 pending events: two from the two direct example.com loads, + // and 2 from the two data:text/css loads that import things + is(pendingEventCounter, 4, "Load event for sheet should have fired"); +} +window.postMessage("", "*"); + +document.write('<link rel="stylesheet" href="data:text/css,*{}"\ + onload="--pendingEventCounter;\ + ok(true, \'Load event firing on basic stylesheet\')"\ + onerror="--pendingEventCounter;\ + ok(false, \'Error event firing on basic stylesheet\')">'); + +// Make sure we have that second stylesheet +is(document.styleSheets.length, 3, "Should have three stylesheets"); + +// Make sure that the second stylesheet is all loaded +// If we ever switch away from sync loading of already-complete sheets, this +// test will need adjusting +is(document.styleSheets[2].cssRules.length, 1, "Should have one rule"); + +// Make sure the load event for that stylesheet has not fired yet +is(pendingEventCounter, 1, "There should be one pending event"); + +++pendingEventCounter; +document.write('<style\ + onload="--pendingEventCounter;\ + ok(true, \'Load event firing on inline stylesheet\')"\ + onerror="--pendingEventCounter;\ + ok(false, \'Error event firing on inline stylesheet\')"></style>'); + +// Make sure the load event for that second stylesheet has not fired yet +is(pendingEventCounter, 2, "There should be two pending events"); + +++pendingEventCounter; +document.write('<link rel="stylesheet" href="http://www.example.com"\ + onload="--pendingEventCounter;\ + ok(false, \'Load event firing on broken stylesheet\')"\ + onerror="--pendingEventCounter;\ + ok(true, \'Error event firing on broken stylesheet\')">'); + +++pendingEventCounter; +var link = document.createElement("link"); +link.rel = "stylesheet"; +link.href = "http://www.example.com"; +link.onload = function() { --pendingEventCounter; + ok(false, 'Load event firing on broken stylesheet'); +}; +link.onerror = function() { --pendingEventCounter; + ok(true, 'Error event firing on broken stylesheet'); +} +document.body.appendChild(link); + +++pendingEventCounter; +link = document.createElement("link"); +link.rel = "stylesheet"; +link.href = "data:text/css,*{}"; +link.onload = function() { --pendingEventCounter; + ok(true, 'Load event firing on external stylesheet'); +}; +link.onerror = function() { --pendingEventCounter; + ok(false, 'Error event firing on external stylesheet'); +} +document.body.appendChild(link); + +// Make sure we have that last stylesheet +is(document.styleSheets.length, 7, "Should have seven stylesheets here"); + +// Make sure that the sixth stylesheet is all loaded +// If we ever switch away from sync loading of already-complete sheets, this +// test will need adjusting +is(document.styleSheets[6].cssRules.length, 1, "Should have one rule"); + +++pendingEventCounter; +link = document.createElement("link"); +link.rel = "stylesheet"; +link.href = "data:text/css,@import url('data:text/css,*{}')"; +link.onload = function() { --pendingEventCounter; + ok(true, 'Load event firing on external stylesheet'); +}; +link.onerror = function() { --pendingEventCounter; + ok(false, 'Error event firing on external stylesheet'); +} +document.body.appendChild(link); + +++pendingEventCounter; +link = document.createElement("link"); +link.rel = "stylesheet"; +link.href = "data:text/css,@import url('http://www.example.com')"; +link.onload = function() { --pendingEventCounter; + ok(false, 'Load event firing on broken stylesheet'); +}; +link.onerror = function() { --pendingEventCounter; + ok(true, 'Error event firing on broken stylesheet'); +} +document.body.appendChild(link); + +// Make sure the load events for all those stylesheets have not fired yet +is(pendingEventCounter, 7, "There should be one pending event"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_logical_properties.html b/layout/style/test/test_logical_properties.html new file mode 100644 index 000000000..f1265496d --- /dev/null +++ b/layout/style/test/test_logical_properties.html @@ -0,0 +1,422 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for handling of logical and physical properties</title> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + +<style id="sheet"></style> + +<!-- specify size for <body> to avoid unconstrained-isize warnings + when writing-mode of the test <div> is vertical-* --> +<body style="width:100px; height: 100px;"> + <div id="test" class="test"></div> +</body> + +<script> +var gSheet = document.getElementById("sheet"); +var gTest = document.getElementById("test"); + +// list of groups of physical and logical box properties, such as +// +// { left: "margin-left", right: "margin-right", +// top: "margin-top", bottom: "margin-bottom", +// inlineStart: "margin-inline-start", inlineEnd: "margin-inline-end", +// blockStart: "margin-block-start", blockEnd: "margin-block-end", +// type: "length", prerequisites: "..." } +// +// where the type is a key from the gValues object and the prerequisites +// is a declaration including gCSSProperties' listed prerequisites for +// all four physical properties. +var gBoxPropertyGroups; + +// list of groups of physical and logical axis properties, such as +// +// { horizontal: "width", vertical: "height", +// inline: "inline-size", block: "block-size", +// type: "length", prerequisites: "..." } +var gAxisPropertyGroups; + +// values to use while testing +var gValues = { + "length": ["1px", "2px", "3px", "4px", "5px"], + "color": ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"], + "border-style": ["solid", "dashed", "dotted", "double", "groove"], +}; + +// Six unique overall writing modes for property-mapping purposes. +// Note that text-orientation does not affect these mappings, now that +// the proposed sideways-left value no longer exists (superseded in CSS +// Writing Modes by writing-mode: sideways-lr). +var gWritingModes = [ + { style: [ + "writing-mode: horizontal-tb; direction: ltr; ", + ], + blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right", + block: "vertical", inline: "horizontal" }, + { style: [ + "writing-mode: horizontal-tb; direction: rtl; ", + ], + blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left", + block: "vertical", inline: "horizontal" }, + { style: [ + "writing-mode: vertical-rl; direction: rtl; ", + "writing-mode: sideways-rl; direction: rtl; ", + ], + blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top", + block: "horizontal", inline: "vertical" }, + { style: [ + "writing-mode: vertical-rl; direction: ltr; ", + "writing-mode: sideways-rl; direction: ltr; ", + ], + blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom", + block: "horizontal", inline: "vertical" }, + { style: [ + "writing-mode: vertical-lr; direction: rtl; ", + "writing-mode: sideways-lr; direction: ltr; ", + ], + blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top", + block: "horizontal", inline: "vertical" }, + { style: [ + "writing-mode: vertical-lr; direction: ltr; ", + "writing-mode: sideways-lr; direction: rtl; ", + ], + blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom", + block: "horizontal", inline: "vertical" }, +]; + +function init() { + gBoxPropertyGroups = []; + + for (var p in gCSSProperties) { + var type = gCSSProperties[p].type; + + if ((type == CSS_TYPE_SHORTHAND_AND_LONGHAND || + type == CSS_TYPE_LONGHAND && gCSSProperties[p].logical) && + /-inline-end/.test(p)) { + var valueType; + if (/margin|padding|width|offset/.test(p)) { + valueType = "length"; + } else if (/color/.test(p)) { + valueType = "color"; + } else if (/border.*style/.test(p)) { + valueType = "border-style"; + } else { + throw `unexpected property ${p}`; + } + var group = { + inlineStart: p.replace("-inline-end", "-inline-start"), + inlineEnd: p, + blockStart: p.replace("-inline-end", "-block-start"), + blockEnd: p.replace("-inline-end", "-block-end"), + type: valueType + }; + if (/^offset/.test(p)) { + group.left = "left"; + group.right = "right"; + group.top = "top"; + group.bottom = "bottom"; + } else { + group.left = p.replace("-inline-end", "-left"); + group.right = p.replace("-inline-end", "-right"); + group.top = p.replace("-inline-end", "-top"); + group.bottom = p.replace("-inline-end", "-bottom"); + } + group.prerequisites = + make_declaration(gCSSProperties[group.top].prerequisites) + + make_declaration(gCSSProperties[group.right].prerequisites) + + make_declaration(gCSSProperties[group.bottom].prerequisites) + + make_declaration(gCSSProperties[group.left].prerequisites); + gBoxPropertyGroups.push(group); + } + } + + // We don't populate this automatically since the only entries we have, for + // inline-size etc., don't lend themselves to automatically determining + // the names "width", "height", "min-width", etc. + gAxisPropertyGroups = []; + ["", "max-", "min-"].forEach(function(aPrefix) { + gAxisPropertyGroups.push({ + horizontal: `${aPrefix}width`, vertical: `${aPrefix}height`, + inline: `${aPrefix}inline-size`, block: `${aPrefix}block-size`, + type: "length", + prerequisites: + make_declaration(gCSSProperties[`${aPrefix}height`].prerequisites) + }); + }); +} + +function test_computed_values(aTestName, aRules, aExpectedValues) { + gSheet.textContent = aRules; + var cs = getComputedStyle(gTest); + aExpectedValues.forEach(function(aPair) { + is(cs.getPropertyValue(aPair[0]), aPair[1], `${aTestName}, ${aPair[0]}`); + }); + gSheet.textContent = ""; +} + +function make_declaration(aObject) { + var decl = ""; + if (aObject) { + for (var p in aObject) { + decl += `${p}: ${aObject[p]}; `; + } + } + return decl; +} + +function start() { + var script = document.createElement("script"); + script.src = "property_database.js"; + script.onload = function() { + init(); + run_tests(); + }; + document.body.appendChild(script); +} + +function run_axis_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl) { + var values = gValues[aGroup.type]; + var decl; + + // Test that logical axis properties are converted to their physical + // equivalent correctly when all four are present on a single + // declaration, with no overwriting of previous properties and + // no physical properties present. We put the writing mode properties + // on a separate declaration to test that the computed values of these + // properties are used, rather than those on the same declaration. + + decl = aGroup.prerequisites + + `${aGroup.inline}: ${values[0]}; ` + + `${aGroup.block}: ${values[1]}; `; + test_computed_values('logical properties on one declaration, writing ' + + 'mode properties on another, ' + + `'${aWritingModeDecl}'`, + `.test { ${aWritingModeDecl} } ` + + `.test { ${decl} }`, + [[aGroup[aWritingMode.inline], values[0]], + [aGroup[aWritingMode.block], values[1]]]); + + + // Test that logical and physical axis properties are cascaded together, + // honoring their relative order on a single declaration. + + // (a) with a single logical property after the physical ones + + ["inline", "block"].forEach(function(aLogicalAxis) { + decl = aWritingModeDecl + aGroup.prerequisites + + `${aGroup.horizontal}: ${values[0]}; ` + + `${aGroup.vertical}: ${values[1]}; ` + + `${aGroup[aLogicalAxis]}: ${values[2]}; `; + var expected = ["horizontal", "vertical"].map( + (axis, i) => [aGroup[axis], + values[axis == aWritingMode[aLogicalAxis] ? 2 : i]] + ); + test_computed_values(`${aLogicalAxis} last on single declaration, ` + + `'${aWritingModeDecl}'`, + `.test { ${decl} }`, + expected); + }); + + // (b) with a single physical property after the logical ones + + ["horizontal", "vertical"].forEach(function(aPhysicalAxis) { + decl = aWritingModeDecl + aGroup.prerequisites + + `${aGroup.inline}: ${values[0]}; ` + + `${aGroup.block}: ${values[1]}; ` + + `${aGroup[aPhysicalAxis]}: ${values[2]}; `; + var expected = ["inline", "block"].map( + (axis, i) => [aGroup[aWritingMode[axis]], + values[aWritingMode[axis] == aPhysicalAxis ? 2 : i]] + ); + test_computed_values(`${aPhysicalAxis} last on single declaration, ` + + `'${aWritingModeDecl}'`, + `.test { ${decl} }`, + expected); + }); + + + // Test that logical and physical axis properties are cascaded properly when + // on different declarations. + + var loDecl; // lower specifity + var hiDecl; // higher specificity + + // (a) with a logical property in the high specificity rule + + loDecl = aWritingModeDecl + aGroup.prerequisites + + `${aGroup.horizontal}: ${values[0]}; ` + + `${aGroup.vertical}: ${values[1]}; `; + + ["inline", "block"].forEach(function(aLogicalAxis) { + hiDecl = `${aGroup[aLogicalAxis]}: ${values[2]}; `; + var expected = ["horizontal", "vertical"].map( + (axis, i) => [aGroup[axis], + values[axis == aWritingMode[aLogicalAxis] ? 2 : i]] + ); + test_computed_values(`${aLogicalAxis}, two declarations, ` + + `'${aWritingModeDecl}'`, + `#test { ${hiDecl} } ` + + `.test { ${loDecl} }`, + expected); + }); + + // (b) with a physical property in the high specificity rule + + loDecl = aWritingModeDecl + aGroup.prerequisites + + `${aGroup.inline}: ${values[0]}; ` + + `${aGroup.block}: ${values[1]}; `; + + ["horizontal", "vertical"].forEach(function(aPhysicalAxis) { + hiDecl = `${aGroup[aPhysicalAxis]}: ${values[2]}; `; + var expected = ["inline", "block"].map( + (axis, i) => [aGroup[aWritingMode[axis]], + values[aWritingMode[axis] == aPhysicalAxis ? 2 : i]] + ); + test_computed_values(`${aPhysicalAxis}, two declarations, ` + + `'${aWritingModeDecl}'`, + `#test { ${hiDecl} } ` + + `.test { ${loDecl} }`, + expected); + }); +} + +function run_box_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl) { + var values = gValues[aGroup.type]; + var decl; + + // Test that logical box properties are converted to their physical + // equivalent correctly when all four are present on a single + // declaration, with no overwriting of previous properties and + // no physical properties present. We put the writing mode properties + // on a separate declaration to test that the computed values of these + // properties are used, rather than those on the same declaration. + + decl = aGroup.prerequisites + + `${aGroup.inlineStart}: ${values[0]}; ` + + `${aGroup.inlineEnd}: ${values[1]}; ` + + `${aGroup.blockStart}: ${values[2]}; ` + + `${aGroup.blockEnd}: ${values[3]}; `; + test_computed_values('logical properties on one declaration, writing ' + + 'mode properties on another, ' + + `'${aWritingModeDecl}'`, + `.test { ${aWritingModeDecl} } ` + + `.test { ${decl} }`, + [[aGroup[aWritingMode.inlineStart], values[0]], + [aGroup[aWritingMode.inlineEnd], values[1]], + [aGroup[aWritingMode.blockStart], values[2]], + [aGroup[aWritingMode.blockEnd], values[3]]]); + + // Test that logical and physical box properties are cascaded together, + // honoring their relative order on a single declaration. + + // (a) with a single logical property after the physical ones + + ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].forEach(function(aLogicalSide) { + decl = aWritingModeDecl + aGroup.prerequisites + + `${aGroup.left}: ${values[0]}; ` + + `${aGroup.right}: ${values[1]}; ` + + `${aGroup.top}: ${values[2]}; ` + + `${aGroup.bottom}: ${values[3]}; ` + + `${aGroup[aLogicalSide]}: ${values[4]}; `; + var expected = ["left", "right", "top", "bottom"].map( + (side, i) => [aGroup[side], + values[side == aWritingMode[aLogicalSide] ? 4 : i]] + ); + test_computed_values(`${aLogicalSide} last on single declaration, ` + + `'${aWritingModeDecl}'`, + `.test { ${decl} }`, + expected); + }); + + // (b) with a single physical property after the logical ones + + ["left", "right", "top", "bottom"].forEach(function(aPhysicalSide) { + decl = aWritingModeDecl + aGroup.prerequisites + + `${aGroup.inlineStart}: ${values[0]}; ` + + `${aGroup.inlineEnd}: ${values[1]}; ` + + `${aGroup.blockStart}: ${values[2]}; ` + + `${aGroup.blockEnd}: ${values[3]}; ` + + `${aGroup[aPhysicalSide]}: ${values[4]}; `; + var expected = ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].map( + (side, i) => [aGroup[aWritingMode[side]], + values[aWritingMode[side] == aPhysicalSide ? 4 : i]] + ); + test_computed_values(`${aPhysicalSide} last on single declaration, ` + + `'${aWritingModeDecl}'`, + `.test { ${decl} }`, + expected); + }); + + + // Test that logical and physical box properties are cascaded properly when + // on different declarations. + + var loDecl; // lower specifity + var hiDecl; // higher specificity + + // (a) with a logical property in the high specificity rule + + loDecl = aWritingModeDecl + aGroup.prerequisites + + `${aGroup.left}: ${values[0]}; ` + + `${aGroup.right}: ${values[1]}; ` + + `${aGroup.top}: ${values[2]}; ` + + `${aGroup.bottom}: ${values[3]}; `; + + ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].forEach(function(aLogicalSide) { + hiDecl = `${aGroup[aLogicalSide]}: ${values[4]}; `; + var expected = ["left", "right", "top", "bottom"].map( + (side, i) => [aGroup[side], + values[side == aWritingMode[aLogicalSide] ? 4 : i]] + ); + test_computed_values(`${aLogicalSide}, two declarations, ` + + `'${aWritingModeDecl}'`, + `#test { ${hiDecl} } ` + + `.test { ${loDecl} }`, + expected); + }); + + // (b) with a physical property in the high specificity rule + + loDecl = aWritingModeDecl + aGroup.prerequisites + + `${aGroup.inlineStart}: ${values[0]}; ` + + `${aGroup.inlineEnd}: ${values[1]}; ` + + `${aGroup.blockStart}: ${values[2]}; ` + + `${aGroup.blockEnd}: ${values[3]}; `; + + ["left", "right", "top", "bottom"].forEach(function(aPhysicalSide) { + hiDecl = `${aGroup[aPhysicalSide]}: ${values[4]}; `; + var expected = ["inlineStart", "inlineEnd", "blockStart", "blockEnd"].map( + (side, i) => [aGroup[aWritingMode[side]], + values[aWritingMode[side] == aPhysicalSide ? 4 : i]] + ); + test_computed_values(`${aPhysicalSide}, two declarations, ` + + `'${aWritingModeDecl}'`, + `#test { ${hiDecl} } ` + + `.test { ${loDecl} }`, + expected); + }); +} + +function run_tests() { + gBoxPropertyGroups.forEach(function(aGroup) { + gWritingModes.forEach(function(aWritingMode) { + aWritingMode.style.forEach(function(aWritingModeDecl) { + run_box_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl); + }); + }); + }); + + gAxisPropertyGroups.forEach(function(aGroup) { + gWritingModes.forEach(function(aWritingMode) { + aWritingMode.style.forEach(function(aWritingModeDecl) { + run_axis_test_for_writing_mode(aGroup, aWritingMode, aWritingModeDecl); + }); + }); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +start(); +</script> diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html new file mode 100644 index 000000000..1edac15ae --- /dev/null +++ b/layout/style/test/test_media_queries.html @@ -0,0 +1,845 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=156716 +--> +<head> + <title>Test for Bug 156716</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=156716">Mozilla Bug 156716</a> +<iframe id="subdoc" src="media_queries_iframe.html"></iframe> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="application/javascript"> + +/** Test for Bug 156716 **/ + +// Note that many other tests are in test_acid3_test46.html . + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(2); + +var iframe; + +function getScreenPixelsPerCSSPixel() { + return SpecialPowers.DOMWindowUtils.screenPixelsPerCSSPixel; +} + +function run() { + iframe = document.getElementById("subdoc"); + var subdoc = iframe.contentDocument; + var subwin = iframe.contentWindow; + var style = subdoc.getElementById("style"); + var iframe_style = iframe.style; + var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body, ""); + + function query_applies(q) { + style.setAttribute("media", q); + return body_cs.getPropertyValue("text-decoration") == "underline"; + } + + function should_apply(q) { + ok(query_applies(q), q + " should apply"); + test_serialization(q, true, true); + } + + function should_not_apply(q) { + ok(!query_applies(q), q + " should not apply"); + test_serialization(q, true, false); + } + + /* for queries that are parseable standalone but not within CSS */ + function should_apply_unbalanced(q) { + ok(query_applies(q), q + " should apply"); + } + + /* for queries that are parseable standalone but not within CSS */ + function should_not_apply_unbalanced(q) { + ok(!query_applies(q), q + " should not apply"); + } + + /* + * Functions to test whether a query is parseable at all. (Should not + * be used for parse errors within expressions.) + */ + var parse_test_style_element = document.createElement("style"); + parse_test_style_element.type = "text/css"; + parse_test_style_element.disabled = true; // for performance, hopefully + var parse_test_style_text = document.createTextNode(""); + parse_test_style_element.appendChild(parse_test_style_text); + document.getElementsByTagName("head")[0] + .appendChild(parse_test_style_element); + + function query_is_parseable(q) { + parse_test_style_text.data = "@media screen, " + q + " {}"; + var sheet = parse_test_style_element.sheet; // XXX yikes, not live! + if (sheet.cssRules.length == 1 && + sheet.cssRules[0].type == CSSRule.MEDIA_RULE) + return sheet.cssRules[0].media.mediaText != "screen, not all"; + ok(false, "unexpected result testing whether query " + q + + " is parseable"); + return true; // doesn't matter, we already failed + } + + function query_should_be_parseable(q) { + ok(query_is_parseable(q), "query " + q + " should be parseable"); + test_serialization(q, false, false); + } + + function query_should_not_be_parseable(q) { + ok(!query_is_parseable(q), "query " + q + " should not be parseable"); + } + + /* + * Functions to test whether a single media expression is parseable. + */ + function expression_is_parseable(e) { + style.setAttribute("media", "all and (" + e + ")"); + return style.sheet.media.mediaText != "not all"; + } + + function expression_should_be_parseable(e) { + ok(expression_is_parseable(e), + "expression " + e + " should be parseable"); + test_serialization("all and (" + e + ")", false, false); + } + + function expression_should_not_be_parseable(e) { + ok(!expression_is_parseable(e), + "expression " + e + " should not be parseable"); + } + + // Helper to share code between -moz & -webkit device-pixel-ratio versions: + function test_device_pixel_ratio(equal_name, min_name, max_name) { + var real_dpr = 1.0 * getScreenPixelsPerCSSPixel(); + var high_dpr = 1.1 * getScreenPixelsPerCSSPixel(); + var low_dpr = 0.9 * getScreenPixelsPerCSSPixel(); + should_apply("all and (" + max_name + ": " + real_dpr + ")"); + should_apply("all and (" + min_name + ": " + real_dpr + ")"); + should_not_apply("not all and (" + max_name + ": " + real_dpr + ")"); + should_not_apply("not all and (" + min_name + ": " + real_dpr + ")"); + should_apply("all and (" + min_name + ": " + low_dpr + ")"); + should_apply("all and (" + max_name + ": " + high_dpr + ")"); + should_not_apply("all and (" + max_name + ": " + low_dpr + ")"); + should_not_apply("all and (" + min_name + ": " + high_dpr + ")"); + should_apply("not all and (" + max_name + ": " + low_dpr + ")"); + should_apply("not all and (" + min_name + ": " + high_dpr + ")"); + should_apply("(" + equal_name + ": " + real_dpr + ")"); + should_not_apply("(" + equal_name + ": " + high_dpr + ")"); + should_not_apply("(" + equal_name + ": " + low_dpr + ")"); + should_apply("(" + equal_name + ")"); + expression_should_not_be_parseable(min_name); + expression_should_not_be_parseable(max_name); + } + + function test_serialization(q, test_application, should_apply) { + style.setAttribute("media", q); + var ser1 = style.sheet.media.mediaText; + isnot(ser1, "", "serialization of '" + q + "' should not be empty"); + style.setAttribute("media", ser1); + var ser2 = style.sheet.media.mediaText; + is(ser2, ser1, "parse+serialize of '" + q + "' should be idempotent"); + if (test_application) { + var applies = body_cs.getPropertyValue("text-decoration") == "underline"; + is(applies, should_apply, + "Media query '" + q + "' should " + (should_apply ? "" : "NOT ") + + "apply after serialize + reparse"); + } + + // Test cloning + var sheet = "@media " + q + " { body { text-decoration: underline } }" + var sheeturl = "data:text/css," + escape(sheet); + var link = "<link rel='stylesheet' href='" + sheeturl + "'>"; + var htmldoc = "<!DOCTYPE HTML>" + link + link + "<body>"; + var docurl = "data:text/html," + escape(htmldoc); + post_clone_test(docurl, function() { + var clonedoc = iframe.contentDocument; + var clonewin = iframe.contentWindow; + var links = clonedoc.getElementsByTagName("link"); + // cause a clone + var clonedsheet = links[1].sheet; + clonedsheet.insertRule("#nonexistent { color: purple}", 1); + // remove the uncloned sheet + links[0].parentNode.removeChild(links[0]); + + var ser3 = clonedsheet.cssRules[0].media.mediaText; + is(ser3, ser1, "cloning query '" + q + "' should not change " + + "serialization"); + if (test_application) { + var applies = clonewin.getComputedStyle(clonedoc.body, ""). + textDecoration == "underline"; + is(applies, should_apply, + "Media query '" + q + "' should " + (should_apply ? "" : "NOT ") + + "apply after cloning"); + } + }); + } + + // The no-type syntax doesn't mix with the not and only keywords. + query_should_be_parseable("(orientation)"); + query_should_not_be_parseable("not (orientation)"); + query_should_not_be_parseable("only (orientation)"); + query_should_be_parseable("all and (orientation)"); + query_should_be_parseable("not all and (orientation)"); + query_should_be_parseable("only all and (orientation)"); + + query_should_be_parseable("(-moz-device-orientation)"); + query_should_not_be_parseable("not (-moz-device-orientation)"); + query_should_not_be_parseable("only (-moz-device-orientation)"); + query_should_be_parseable("all and (-moz-device-orientation)"); + query_should_be_parseable("not all and (-moz-device-orientation)"); + query_should_be_parseable("only all and (-moz-device-orientation)"); + + // Test that the 'not', 'only', 'and', and 'or' keywords are not + // allowed as media types. + query_should_not_be_parseable("not"); + query_should_not_be_parseable("and"); + query_should_not_be_parseable("or"); + query_should_not_be_parseable("only"); + query_should_be_parseable("unknowntype"); + query_should_not_be_parseable("not not"); + query_should_not_be_parseable("not and"); + query_should_not_be_parseable("not or"); + query_should_not_be_parseable("not only"); + query_should_be_parseable("not unknowntype"); + query_should_not_be_parseable("only not"); + query_should_not_be_parseable("only and"); + query_should_not_be_parseable("only or"); + query_should_not_be_parseable("only only"); + query_should_be_parseable("only unknowntype"); + query_should_not_be_parseable("not and (width)"); + query_should_not_be_parseable("and and (width)"); + query_should_not_be_parseable("or and (width)"); + query_should_not_be_parseable("only and (width)"); + query_should_be_parseable("unknowntype and (width)"); + query_should_not_be_parseable("not not and (width)"); + query_should_not_be_parseable("not and and (width)"); + query_should_not_be_parseable("not or and (width)"); + query_should_not_be_parseable("not only and (width)"); + query_should_be_parseable("not unknowntype and (width)"); + query_should_not_be_parseable("only not and (width)"); + query_should_not_be_parseable("only and and (width)"); + query_should_not_be_parseable("only or and (width)"); + query_should_not_be_parseable("only only and (width)"); + query_should_be_parseable("only unknowntype and (width)"); + + var features = [ "width", "height", "device-width", "device-height" ]; + var feature; + var i; + for (i in features) { + feature = features[i]; + expression_should_be_parseable(feature); + expression_should_be_parseable(feature + ": 0"); + expression_should_be_parseable(feature + ": 0px"); + expression_should_be_parseable(feature + ": 0em"); + expression_should_be_parseable(feature + ": -0"); + expression_should_be_parseable("min-" + feature + ": -0"); + expression_should_be_parseable("max-" + feature + ": -0"); + expression_should_be_parseable(feature + ": -0cm"); + expression_should_be_parseable(feature + ": 1px"); + expression_should_be_parseable(feature + ": 0.001mm"); + expression_should_be_parseable(feature + ": 100000px"); + expression_should_not_be_parseable(feature + ": -1px"); + expression_should_not_be_parseable("min-" + feature + ": -1px"); + expression_should_not_be_parseable("max-" + feature + ": -1px"); + expression_should_not_be_parseable(feature + ": -0.00001mm"); + expression_should_not_be_parseable(feature + ": -100000em"); + expression_should_not_be_parseable("min-" + feature); + expression_should_not_be_parseable("max-" + feature); + } + + var mediatypes = ["browser", "minimal-ui", "standalone", "fullscreen"]; + + mediatypes.forEach(function(type) { + expression_should_be_parseable("display-mode: " + type); + }); + + expression_should_not_be_parseable("display-mode: invalid") + + var content_div = document.getElementById("content"); + content_div.style.font = "initial"; + var em_size = + getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1]; + + // in this test, assume the common underlying implementation is correct + var width_val = 117; // pick two not-too-round numbers + var height_val = 76; + change_state(function() { + iframe_style.width = width_val + "px"; + iframe_style.height = height_val + "px"; + }); + var device_width = window.screen.width; + var device_height = window.screen.height; + features = { "width": width_val, + "height": height_val, + "device-width": device_width, + "device-height": device_height }; + for (feature in features) { + var value = features[feature]; + should_apply("all and (" + feature + ": " + value + "px)"); + should_not_apply("all and (" + feature + ": " + (value + 1) + "px)"); + should_not_apply("all and (" + feature + ": " + (value - 1) + "px)"); + should_apply("all and (min-" + feature + ": " + value + "px)"); + should_not_apply("all and (min-" + feature + ": " + (value + 1) + "px)"); + should_apply("all and (min-" + feature + ": " + (value - 1) + "px)"); + should_apply("all and (max-" + feature + ": " + value + "px)"); + should_apply("all and (max-" + feature + ": " + (value + 1) + "px)"); + should_not_apply("all and (max-" + feature + ": " + (value - 1) + "px)"); + should_not_apply("all and (min-" + feature + ": " + + (Math.ceil(value/em_size) + 1) + "em)"); + should_apply("all and (min-" + feature + ": " + + (Math.floor(value/em_size) - 1) + "em)"); + should_apply("all and (max-" + feature + ": " + + (Math.ceil(value/em_size) + 1) + "em)"); + should_not_apply("all and (max-" + feature + ": " + + (Math.floor(value/em_size) - 1) + "em)"); + should_not_apply("all and (min-" + feature + ": " + + (Math.ceil(value/em_size) + 1) + "rem)"); + should_apply("all and (min-" + feature + ": " + + (Math.floor(value/em_size) - 1) + "rem)"); + should_apply("all and (max-" + feature + ": " + + (Math.ceil(value/em_size) + 1) + "rem)"); + should_not_apply("all and (max-" + feature + ": " + + (Math.floor(value/em_size) - 1) + "rem)"); + } + + change_state(function() { + iframe_style.width = "0"; + }); + should_apply("all and (height)"); + should_not_apply("all and (width)"); + change_state(function() { + iframe_style.height = "0"; + }); + should_not_apply("all and (height)"); + should_not_apply("all and (width)"); + should_apply("all and (device-height)"); + should_apply("all and (device-width)"); + change_state(function() { + iframe_style.width = width_val + "px"; + }); + should_not_apply("all and (height)"); + should_apply("all and (width)"); + change_state(function() { + iframe_style.height = height_val + "px"; + }); + should_apply("all and (height)"); + should_apply("all and (width)"); + + // ratio that reduces to 59/40 + change_state(function() { + iframe_style.width = "236px"; + iframe_style.height = "160px"; + }); + expression_should_be_parseable("orientation"); + expression_should_be_parseable("orientation: portrait"); + expression_should_be_parseable("orientation: landscape"); + expression_should_not_be_parseable("min-orientation"); + expression_should_not_be_parseable("min-orientation: portrait"); + expression_should_not_be_parseable("min-orientation: landscape"); + expression_should_not_be_parseable("max-orientation"); + expression_should_not_be_parseable("max-orientation: portrait"); + expression_should_not_be_parseable("max-orientation: landscape"); + should_apply("(orientation)"); + should_apply("(orientation: landscape)"); + should_not_apply("(orientation: portrait)"); + should_apply("not all and (orientation: portrait)"); + // ratio that reduces to 59/80 + change_state(function() { + iframe_style.height = "320px"; + }); + should_apply("(orientation)"); + should_not_apply("(orientation: landscape)"); + should_apply("not all and (orientation: landscape)"); + should_apply("(orientation: portrait)"); + + expression_should_be_parseable("-moz-device-orientation"); + expression_should_be_parseable("-moz-device-orientation: portrait"); + expression_should_be_parseable("-moz-device-orientation: landscape"); + expression_should_not_be_parseable("min--moz-device-orientation"); + expression_should_not_be_parseable("min--moz-device-orientation: portrait"); + expression_should_not_be_parseable("min--moz-device-orientation: landscape"); + expression_should_not_be_parseable("max--moz-device-orientation"); + expression_should_not_be_parseable("max--moz-device-orientation: portrait"); + expression_should_not_be_parseable("max--moz-device-orientation: landscape"); + + // determine the actual configuration of the screen and test against it + var device_orientation = (device_width > device_height) ? "landscape" : "portrait"; + var not_device_orientation = (device_orientation == "landscape") ? "portrait" : "landscape"; + should_apply("(-moz-device-orientation)"); + should_apply("(-moz-device-orientation: " + device_orientation + ")"); + should_not_apply("(-moz-device-orientation: " + not_device_orientation + ")"); + should_apply("not all and (-moz-device-orientation: " + not_device_orientation + ")"); + + should_apply("(aspect-ratio: 59/80)"); + should_not_apply("(aspect-ratio: 58/80)"); + should_not_apply("(aspect-ratio: 59/81)"); + should_not_apply("(aspect-ratio: 60/80)"); + should_not_apply("(aspect-ratio: 59/79)"); + should_apply("(aspect-ratio: 177/240)"); + should_apply("(aspect-ratio: 413/560)"); + should_apply("(aspect-ratio: 5900/8000)"); + should_not_apply("(aspect-ratio: 5901/8000)"); + should_not_apply("(aspect-ratio: 5899/8000)"); + should_not_apply("(aspect-ratio: 5900/8001)"); + should_not_apply("(aspect-ratio: 5900/7999)"); + should_apply("(aspect-ratio)"); + + should_apply("(min-aspect-ratio: 59/80)"); + should_apply("(min-aspect-ratio: 58/80)"); + should_apply("(min-aspect-ratio: 59/81)"); + should_not_apply("(min-aspect-ratio: 60/80)"); + should_not_apply("(min-aspect-ratio: 59/79)"); + expression_should_not_be_parseable("min-aspect-ratio"); + + should_apply("(max-aspect-ratio: 59/80)"); + should_not_apply("(max-aspect-ratio: 58/80)"); + should_not_apply("(max-aspect-ratio: 59/81)"); + should_apply("(max-aspect-ratio: 60/80)"); + should_apply("(max-aspect-ratio: 59/79)"); + expression_should_not_be_parseable("max-aspect-ratio"); + + var real_dar = device_width + "/" + device_height; + var high_dar_1 = (device_width + 1) + "/" + device_height; + var high_dar_2 = device_width + "/" + (device_height - 1); + var low_dar_1 = (device_width - 1) + "/" + device_height; + var low_dar_2 = device_width + "/" + (device_height + 1); + should_apply("(device-aspect-ratio: " + real_dar + ")"); + should_apply("not all and (device-aspect-ratio: " + high_dar_1 + ")"); + should_not_apply("all and (device-aspect-ratio: " + high_dar_2 + ")"); + should_not_apply("all and (device-aspect-ratio: " + low_dar_1 + ")"); + should_apply("not all and (device-aspect-ratio: " + low_dar_2 + ")"); + should_apply("(device-aspect-ratio)"); + + should_apply("(min-device-aspect-ratio: " + real_dar + ")"); + should_not_apply("all and (min-device-aspect-ratio: " + high_dar_1 + ")"); + should_apply("not all and (min-device-aspect-ratio: " + high_dar_2 + ")"); + should_not_apply("not all and (min-device-aspect-ratio: " + low_dar_1 + ")"); + should_apply("all and (min-device-aspect-ratio: " + low_dar_2 + ")"); + expression_should_not_be_parseable("min-device-aspect-ratio"); + + should_apply("all and (max-device-aspect-ratio: " + real_dar + ")"); + should_apply("(max-device-aspect-ratio: " + high_dar_1 + ")"); + should_apply("(max-device-aspect-ratio: " + high_dar_2 + ")"); + should_not_apply("all and (max-device-aspect-ratio: " + low_dar_1 + ")"); + should_apply("not all and (max-device-aspect-ratio: " + low_dar_2 + ")"); + expression_should_not_be_parseable("max-device-aspect-ratio"); + + // Tests for -moz- & -webkit versions of "device-pixel-ratio" + // (Note that the vendor prefixes go in different places.) + test_device_pixel_ratio("-moz-device-pixel-ratio", + "min--moz-device-pixel-ratio", + "max--moz-device-pixel-ratio"); + test_device_pixel_ratio("-webkit-device-pixel-ratio", + "-webkit-min-device-pixel-ratio", + "-webkit-max-device-pixel-ratio"); + + // Make sure that we don't accidentally start accepting *unprefixed* + // "device-pixel-ratio" expressions: + expression_should_be_parseable("-webkit-device-pixel-ratio: 1.0"); + expression_should_not_be_parseable("device-pixel-ratio: 1.0"); + expression_should_be_parseable("-webkit-min-device-pixel-ratio: 1.0"); + expression_should_not_be_parseable("min-device-pixel-ratio: 1.0"); + expression_should_be_parseable("-webkit-max-device-pixel-ratio: 1.0"); + expression_should_not_be_parseable("max-device-pixel-ratio: 1.0"); + + should_apply("(-webkit-transform-3d)"); + + features = [ "max-aspect-ratio", "device-aspect-ratio" ]; + for (i in features) { + feature = features[i]; + expression_should_be_parseable(feature + ": 1/1"); + expression_should_be_parseable(feature + ": 1 /1"); + expression_should_be_parseable(feature + ": 1 / \t\n1"); + expression_should_be_parseable(feature + ": 1/\r1"); + expression_should_not_be_parseable(feature + ": 1"); + expression_should_not_be_parseable(feature + ": 0.5"); + expression_should_not_be_parseable(feature + ": 1.0/1"); + expression_should_not_be_parseable(feature + ": 1/1.0"); + expression_should_not_be_parseable(feature + ": 1.0/1.0"); + expression_should_not_be_parseable(feature + ": 0/1"); + expression_should_not_be_parseable(feature + ": 1/0"); + expression_should_not_be_parseable(feature + ": 0/0"); + expression_should_not_be_parseable(feature + ": -1/1"); + expression_should_not_be_parseable(feature + ": 1/-1"); + expression_should_not_be_parseable(feature + ": -1/-1"); + } + + var is_monochrome = query_applies("all and (min-monochrome: 1)"); + test_serialization("all and (min-monochrome: 1)", true, is_monochrome); + var is_color = query_applies("all and (min-color: 1)"); + test_serialization("all and (min-color: 1)", true, is_color); + isnot(is_monochrome, is_color, "should be either monochrome or color"); + + function depth_query(prefix, depth) { + return "all and (" + prefix + (is_color ? "color" : "monochrome") + + ":" + depth + ")"; + } + + var depth = 0; + do { + if (depth > 50) { + ok(false, "breaking from loop, depth > 50"); + break; + } + } while (query_applies(depth_query("min-", ++depth))); + --depth; + + should_apply(depth_query("", depth)); + should_not_apply(depth_query("", depth - 1)); + should_not_apply(depth_query("", depth + 1)); + should_apply(depth_query("max-", depth)); + should_not_apply(depth_query("max-", depth - 1)); + should_apply(depth_query("max-", depth + 1)); + + (is_color ? should_apply : should_not_apply)("all and (color)"); + expression_should_not_be_parseable("max-color"); + expression_should_not_be_parseable("min-color"); + (is_color ? should_not_apply : should_apply)("all and (monochrome)"); + expression_should_not_be_parseable("max-monochrome"); + expression_should_not_be_parseable("min-monochrome"); + (is_color ? should_apply : should_not_apply)("not all and (monochrome)"); + (is_color ? should_not_apply : should_apply)("not all and (color)"); + (is_color ? should_apply : should_not_apply)("only all and (color)"); + (is_color ? should_not_apply : should_apply)("only all and (monochrome)"); + + features = [ "color", "min-monochrome", "max-color-index" ]; + for (i in features) { + feature = features[i]; + expression_should_be_parseable(feature + ": 1"); + expression_should_be_parseable(feature + ": 327"); + expression_should_be_parseable(feature + ": 0"); + expression_should_not_be_parseable(feature + ": 1.0"); + expression_should_not_be_parseable(feature + ": -1"); + expression_should_not_be_parseable(feature + ": 1/1"); + } + + // Presume that we never support indexed color (at least not usefully + // enough to call it indexed color). + should_apply("(color-index: 0)"); + should_not_apply("(color-index: 1)"); + should_apply("(min-color-index: 0)"); + should_not_apply("(min-color-index: 1)"); + should_apply("(max-color-index: 0)"); + should_apply("(max-color-index: 1)"); + should_apply("(max-color-index: 157)"); + + features = [ "resolution", "min-resolution", "max-resolution" ]; + for (i in features) { + feature = features[i]; + expression_should_be_parseable(feature + ": 3dpi"); + expression_should_be_parseable(feature + ":3dpi"); + expression_should_be_parseable(feature + ": 3.0dpi"); + expression_should_be_parseable(feature + ": 3.4dpi"); + expression_should_be_parseable(feature + "\t: 120dpcm"); + expression_should_be_parseable(feature + ": 1dppx"); + expression_should_be_parseable(feature + ": 1.5dppx"); + expression_should_be_parseable(feature + ": 2.0dppx"); + expression_should_not_be_parseable(feature + ": 0dpi"); + expression_should_not_be_parseable(feature + ": -3dpi"); + expression_should_not_be_parseable(feature + ": 0dppx"); + } + + // Find the resolution using max-resolution + var resolution = 0; + do { + ++resolution; + if (resolution > 10000) { + ok(false, "resolution greater than 10000dpi???"); + break; + } + } while (!query_applies("(max-resolution: " + resolution + "dpi)")); + + // resolution should now be Math.ceil() of the actual resolution. + var dpi_high; + var dpi_low = resolution - 1; + if (query_applies("(min-resolution: " + resolution + "dpi)")) { + // It's exact! + should_apply("(resolution: " + resolution + "dpi)"); + should_apply("(resolution: " + Math.floor(resolution/96) + "dppx)"); + should_not_apply("(resolution: " + (resolution + 1) + "dpi)"); + should_not_apply("(resolution: " + (resolution - 1) + "dpi)"); + dpi_high = resolution + 1; + } else { + // We have no way to test resolution applying since it need not be + // an integer. + should_not_apply("(resolution: " + resolution + "dpi)"); + should_not_apply("(resolution: " + (resolution - 1) + "dpi)"); + dpi_high = resolution; + } + + should_apply("(min-resolution: " + dpi_low + "dpi)"); + should_not_apply("not all and (min-resolution: " + dpi_low + "dpi)"); + should_apply("not all and (min-resolution: " + dpi_high + "dpi)"); + should_not_apply("all and (min-resolution: " + dpi_high + "dpi)"); + + // Test dpcm units based on what we computed in dpi. + var dpcm_high = Math.ceil(dpi_high / 2.54); + var dpcm_low = Math.floor(dpi_low / 2.54); + should_apply("(min-resolution: " + dpcm_low + "dpcm)"); + should_apply("(max-resolution: " + dpcm_high + "dpcm)"); + should_not_apply("(max-resolution: " + dpcm_low + "dpcm)"); + should_apply("not all and (min-resolution: " + dpcm_high + "dpcm)"); + + expression_should_be_parseable("scan"); + expression_should_be_parseable("scan: progressive"); + expression_should_be_parseable("scan:interlace"); + expression_should_not_be_parseable("min-scan:interlace"); + expression_should_not_be_parseable("scan: 1"); + expression_should_not_be_parseable("max-scan"); + expression_should_not_be_parseable("max-scan: progressive"); + // Assume we don't support tv devices. + should_not_apply("(scan)"); + should_not_apply("(scan: progressive)"); + should_not_apply("(scan: interlace)"); + should_apply("not all and (scan)"); + should_apply("not all and (scan: progressive)"); + should_apply("not all and (scan: interlace)"); + + expression_should_be_parseable("grid"); + expression_should_be_parseable("grid: 0"); + expression_should_be_parseable("grid: 1"); + expression_should_be_parseable("grid: 1"); + expression_should_not_be_parseable("min-grid"); + expression_should_not_be_parseable("min-grid:0"); + expression_should_not_be_parseable("max-grid: 1"); + expression_should_not_be_parseable("grid: 2"); + expression_should_not_be_parseable("grid: -1"); + + // Assume we don't support grid devices + should_not_apply("(grid)"); + should_apply("(grid: 0)"); + should_not_apply("(grid: 1)"); + should_not_apply("(grid: 2)"); + should_not_apply("(grid: -1)"); + + // System metrics + expression_should_be_parseable("-moz-scrollbar-start-backward"); + expression_should_be_parseable("-moz-scrollbar-start-forward"); + expression_should_be_parseable("-moz-scrollbar-end-backward"); + expression_should_be_parseable("-moz-scrollbar-end-forward"); + expression_should_be_parseable("-moz-scrollbar-thumb-proportional"); + expression_should_be_parseable("-moz-overlay-scrollbars"); + expression_should_be_parseable("-moz-windows-default-theme"); + expression_should_be_parseable("-moz-mac-graphite-theme"); + expression_should_be_parseable("-moz-mac-yosemite-theme"); + expression_should_be_parseable("-moz-windows-compositor"); + expression_should_be_parseable("-moz-windows-classic"); + expression_should_be_parseable("-moz-windows-glass"); + expression_should_be_parseable("-moz-touch-enabled"); + expression_should_be_parseable("-moz-swipe-animation-enabled"); + + expression_should_be_parseable("-moz-scrollbar-start-backward: 0"); + expression_should_be_parseable("-moz-scrollbar-start-forward: 0"); + expression_should_be_parseable("-moz-scrollbar-end-backward: 0"); + expression_should_be_parseable("-moz-scrollbar-end-forward: 0"); + expression_should_be_parseable("-moz-scrollbar-thumb-proportional: 0"); + expression_should_be_parseable("-moz-overlay-scrollbars: 0"); + expression_should_be_parseable("-moz-windows-default-theme: 0"); + expression_should_be_parseable("-moz-mac-graphite-theme: 0"); + expression_should_be_parseable("-moz-mac-yosemite-theme: 0"); + expression_should_be_parseable("-moz-windows-compositor: 0"); + expression_should_be_parseable("-moz-windows-classic: 0"); + expression_should_be_parseable("-moz-windows-glass: 0"); + expression_should_be_parseable("-moz-touch-enabled: 0"); + expression_should_be_parseable("-moz-swipe-animation-enabled: 0"); + + expression_should_be_parseable("-moz-scrollbar-start-backward: 1"); + expression_should_be_parseable("-moz-scrollbar-start-forward: 1"); + expression_should_be_parseable("-moz-scrollbar-end-backward: 1"); + expression_should_be_parseable("-moz-scrollbar-end-forward: 1"); + expression_should_be_parseable("-moz-scrollbar-thumb-proportional: 1"); + expression_should_be_parseable("-moz-overlay-scrollbars: 1"); + expression_should_be_parseable("-moz-windows-default-theme: 1"); + expression_should_be_parseable("-moz-mac-graphite-theme: 1"); + expression_should_be_parseable("-moz-mac-yosemite-theme: 1"); + expression_should_be_parseable("-moz-windows-compositor: 1"); + expression_should_be_parseable("-moz-windows-classic: 1"); + expression_should_be_parseable("-moz-windows-glass: 1"); + expression_should_be_parseable("-moz-touch-enabled: 1"); + expression_should_be_parseable("-moz-swipe-animation-enabled: 1"); + + expression_should_not_be_parseable("-moz-scrollbar-start-backward: -1"); + expression_should_not_be_parseable("-moz-scrollbar-start-forward: -1"); + expression_should_not_be_parseable("-moz-scrollbar-end-backward: -1"); + expression_should_not_be_parseable("-moz-scrollbar-end-forward: -1"); + expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: -1"); + expression_should_not_be_parseable("-moz-overlay-scrollbars: -1"); + expression_should_not_be_parseable("-moz-windows-default-theme: -1"); + expression_should_not_be_parseable("-moz-mac-graphite-theme: -1"); + expression_should_not_be_parseable("-moz-mac-yosemite-theme: -1"); + expression_should_not_be_parseable("-moz-windows-compositor: -1"); + expression_should_not_be_parseable("-moz-windows-classic: -1"); + expression_should_not_be_parseable("-moz-windows-glass: -1"); + expression_should_not_be_parseable("-moz-touch-enabled: -1"); + expression_should_not_be_parseable("-moz-swipe-animation-enabled: -1"); + + expression_should_not_be_parseable("-moz-scrollbar-start-backward: true"); + expression_should_not_be_parseable("-moz-scrollbar-start-forward: true"); + expression_should_not_be_parseable("-moz-scrollbar-end-backward: true"); + expression_should_not_be_parseable("-moz-scrollbar-end-forward: true"); + expression_should_not_be_parseable("-moz-scrollbar-thumb-proportional: true"); + expression_should_not_be_parseable("-moz-overlay-scrollbars: true"); + expression_should_not_be_parseable("-moz-windows-default-theme: true"); + expression_should_not_be_parseable("-moz-mac-graphite-theme: true"); + expression_should_not_be_parseable("-moz-mac-yosemite-theme: true"); + expression_should_not_be_parseable("-moz-windows-compositor: true"); + expression_should_not_be_parseable("-moz-windows-classic: true"); + expression_should_not_be_parseable("-moz-windows-glass: true"); + expression_should_not_be_parseable("-moz-touch-enabled: true"); + expression_should_not_be_parseable("-moz-swipe-animation-enabled: true"); + + // windows theme media queries + expression_should_be_parseable("-moz-windows-theme: aero"); + expression_should_be_parseable("-moz-windows-theme: aero-lite"); + expression_should_be_parseable("-moz-windows-theme: luna-blue"); + expression_should_be_parseable("-moz-windows-theme: luna-olive"); + expression_should_be_parseable("-moz-windows-theme: luna-silver"); + expression_should_be_parseable("-moz-windows-theme: royale"); + expression_should_be_parseable("-moz-windows-theme: generic"); + expression_should_be_parseable("-moz-windows-theme: zune"); + expression_should_be_parseable("-moz-windows-theme: garbage"); + expression_should_not_be_parseable("-moz-windows-theme: ''"); + expression_should_not_be_parseable("-moz-windows-theme: "); + + // os version media queries (currently windows only) + expression_should_be_parseable("-moz-os-version: windows-xp"); + expression_should_be_parseable("-moz-os-version: windows-vista"); + expression_should_be_parseable("-moz-os-version: windows-win7"); + expression_should_be_parseable("-moz-os-version: windows-win8"); + expression_should_be_parseable("-moz-os-version: windows-win10"); + expression_should_not_be_parseable("-moz-os-version: "); + + // OpenType SVG media features + query_should_be_parseable("(-moz-is-glyph)"); + query_should_not_be_parseable("not (-moz-is-glyph)"); + query_should_not_be_parseable("only (-moz-is-glyph)"); + query_should_be_parseable("all and (-moz-is-glyph)"); + query_should_be_parseable("not all and (-moz-is-glyph)"); + query_should_be_parseable("only all and (-moz-is-glyph)"); + + query_should_be_parseable("(-moz-is-glyph:0)"); + query_should_not_be_parseable("not (-moz-is-glyph:0)"); + query_should_not_be_parseable("only (-moz-is-glyph:0)"); + query_should_be_parseable("all and (-moz-is-glyph:0)"); + query_should_be_parseable("not all and (-moz-is-glyph:0)"); + query_should_be_parseable("only all and (-moz-is-glyph:0)"); + + query_should_be_parseable("(-moz-is-glyph:1)"); + query_should_not_be_parseable("not (-moz-is-glyph:1)"); + query_should_not_be_parseable("only (-moz-is-glyph:1)"); + query_should_be_parseable("all and (-moz-is-glyph:1)"); + query_should_be_parseable("not all and (-moz-is-glyph:1)"); + query_should_be_parseable("only all and (-moz-is-glyph:1)"); + + query_should_not_be_parseable("(min--moz-is-glyph:0)"); + query_should_not_be_parseable("(max--moz-is-glyph:0)"); + query_should_not_be_parseable("(min--moz-is-glyph:1)"); + query_should_not_be_parseable("(max--moz-is-glyph:1)"); + + should_apply("not all and (-moz-is-glyph)"); + should_apply("(-moz-is-glyph:0)"); + should_apply("not all and (-moz-is-glyph:1)"); + should_apply("only all and (-moz-is-glyph:0)"); + should_not_apply("(-moz-is-glyph)"); + should_not_apply("(-moz-is-glyph:1)"); + should_not_apply("not all and (-moz-is-glyph:0)"); + should_not_apply("only all and (-moz-is-glyph:1)"); + + // Parsing tests + // bug 454227 + should_apply_unbalanced("(orientation"); + should_not_apply_unbalanced("not all and (orientation"); + should_not_apply_unbalanced("(orientation:"); + should_apply_unbalanced("all,(orientation:"); + should_not_apply_unbalanced("(orientation:,all"); + should_apply_unbalanced("not all and (grid"); + should_not_apply_unbalanced("only all and (grid"); + should_not_apply_unbalanced("(grid"); + should_apply_unbalanced("all,(grid"); + should_not_apply_unbalanced("(grid,all"); + // bug 454226 + should_apply(",all"); + should_apply("all,"); + should_apply(",all,"); + should_apply("all,badmedium"); + should_apply("badmedium,all"); + should_not_apply(",badmedium,"); + should_apply("all,(badexpression)"); + should_apply("(badexpression),all"); + should_not_apply("(badexpression),badmedium"); + should_not_apply("badmedium,(badexpression)"); + should_apply("all,[badsyntax]"); + should_apply("[badsyntax],all"); + should_not_apply("badmedium,[badsyntax]"); + should_not_apply("[badsyntax],badmedium"); + // bug 528096 + should_not_apply_unbalanced("((resolution),all"); + should_not_apply_unbalanced("(resolution(),all"); + should_not_apply_unbalanced("(resolution (),all"); + should_not_apply_unbalanced("(resolution:(),all"); + + handle_posted_items(); +} + +/* + * The cloning tests have to post tests that wait for onload. However, + * we also make a bunch of state changes during the tests above. So we + * always change state using the change_state call, with both makes the + * change immediately and posts an item in the same queue so that we + * make the same state change again later. + */ + +var posted_items = []; + +function change_state(func) +{ + func(); + posted_items.push({state: func}); +} + +function post_clone_test(docurl, testfunc) +{ + posted_items.push({docurl: docurl, testfunc: testfunc}); +} + +function handle_posted_items() +{ + if (posted_items.length == 0) { + SimpleTest.finish(); + return; + } + + if ("state" in posted_items[0]) { + var item = posted_items.shift(); + item.state(); + handle_posted_items(); + return; + } + + var docurl = posted_items[0].docurl; + iframe.onload = handle_iframe_onload; + iframe.src = docurl; +} + +function handle_iframe_onload(event) +{ + if (event.target != iframe) + return; + + var item = posted_items.shift(); + item.testfunc(); + handle_posted_items(); +} + +</script> +</pre> +</body> +</html> + + diff --git a/layout/style/test/test_media_queries_dynamic.html b/layout/style/test/test_media_queries_dynamic.html new file mode 100644 index 000000000..6c9159186 --- /dev/null +++ b/layout/style/test/test_media_queries_dynamic.html @@ -0,0 +1,213 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=473400 +--> +<head> + <title>Test for Bug 473400</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=473400">Mozilla Bug 473400</a> +<iframe id="subdoc" src="about:blank"></iframe> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="application/javascript; version=1.7"> + +/** Test for Bug 473400 **/ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var subdoc = document.getElementById("subdoc").contentDocument; + var subwin = document.getElementById("subdoc").contentWindow; + var style = subdoc.createElement("style"); + style.setAttribute("type", "text/css"); + subdoc.getElementsByTagName("head")[0].appendChild(style); + var sheet = style.sheet; + var iframe_style = document.getElementById("subdoc").style; + + // Create a style rule and an element now based on the given media + // query "q", and return the computed style that should be passed to + // query_applies to see if that query currently applies. + var n = 0; + function make_query(q) { + var i = ++n; + sheet.insertRule("@media " + q + " { #e" + i + " { text-decoration: underline; } }", sheet.cssRules.length); + var e = subdoc.createElement("div"); + e.id = "e" + i; + subdoc.body.appendChild(e); + var cs = subdoc.defaultView.getComputedStyle(e, ""); + cs._originalQueryText = q; + return cs; + } + function query_applies(cs) { + return cs.getPropertyValue("text-decoration") == "underline"; + } + + function should_apply(cs) { + ok(query_applies(cs), cs._originalQueryText + " should apply"); + } + + function should_not_apply(cs) { + ok(!query_applies(cs), cs._originalQueryText + " should not apply"); + } + + var content_div = document.getElementById("content"); + content_div.style.font = "initial"; + var em_size = + getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1]; + + let width_val = 317; // pick two not-too-round numbers + let height_val = 228; + iframe_style.width = width_val + "px"; + iframe_style.height = height_val + "px"; + var wh_queries = [ + make_query("all and (min-width: " + + (Math.ceil(width_val/em_size) + 1) + "em)"), + make_query("all and (min-width: " + + (Math.floor(width_val/em_size) - 1) + "em)"), + make_query("all and (max-width: " + + (Math.ceil(width_val/em_size) + 1) + "em)"), + make_query("all and (max-width: " + + (Math.floor(width_val/em_size) - 1) + "em)"), + make_query("all and (min-width: " + + (Math.ceil(width_val/(em_size*2)) + 1) + "em)"), + make_query("all and (min-width: " + + (Math.floor(width_val/(em_size*2)) - 1) + "em)"), + make_query("all and (max-width: " + + (Math.ceil(width_val/(em_size*2)) + 1) + "em)"), + make_query("all and (max-width: " + + (Math.floor(width_val/(em_size*2)) - 1) + "em)") + ]; + + is(wh_queries[0].fontSize, em_size + "px", "text zoom is 1.0"); + should_not_apply(wh_queries[0]); + should_apply(wh_queries[1]); + should_apply(wh_queries[2]); + should_not_apply(wh_queries[3]); + SpecialPowers.setTextZoom(subwin, 2.0); + isnot(wh_queries[0].fontSize, em_size + "px", "text zoom is not 1.0"); + should_not_apply(wh_queries[4]); + should_apply(wh_queries[5]); + should_apply(wh_queries[6]); + should_not_apply(wh_queries[7]); + SpecialPowers.setTextZoom(subwin, 1.0); + is(wh_queries[0].fontSize, em_size + "px", "text zoom is 1.0"); + is(subwin.innerHeight, 228, "full zoom is 1.0"); + should_not_apply(wh_queries[0]); + should_apply(wh_queries[1]); + should_apply(wh_queries[2]); + should_not_apply(wh_queries[3]); + SpecialPowers.setFullZoom(subwin, 2.0); + isnot(subwin.innerHeight, 228, "full zoom is not 1.0"); + should_not_apply(wh_queries[4]); + should_apply(wh_queries[5]); + should_apply(wh_queries[6]); + should_not_apply(wh_queries[7]); + SpecialPowers.setFullZoom(subwin, 1.0); + is(subwin.innerHeight, 228, "full zoom is 1.0"); + + + // Now test that certain things *don't* happen, i.e., that we're + // making the optimizations we expect. + subdoc.body.textContent = ""; + subdoc.body.appendChild(subdoc.createElement("div")); + for (var ruleIdx = sheet.cssRules.length; ruleIdx-- != 0; ) { + sheet.deleteRule(ruleIdx); + } + + var utils = SpecialPowers.getDOMWindowUtils(subwin); + var elementsRestyled, framesConstructed, framesReflowed; + function reset_change_counters() + { + elementsRestyled = utils.elementsRestyled; + framesConstructed = utils.framesConstructed; + framesReflowed = utils.framesReflowed; + } + + function flush_and_assert_change_counters(desc, expected) { + subdoc.body.offsetHeight; + + if (!("restyle" in expected) || + !("construct" in expected) || + !("reflow" in expected)) { + ok(false, "parameter missing expectation"); + return; + } + + var restyles = utils.elementsRestyled - elementsRestyled; + var constructs = utils.framesConstructed - framesConstructed; + var reflows = utils.framesReflowed - framesReflowed; + + (expected.restyle ? isnot : is)(restyles, 0, "restyle count: " + desc); + (expected.construct ? isnot : is)(constructs, 0, + "frame construct count: " + desc); + (expected.reflow ? isnot : is)(reflows, 0, "reflow count: " + desc); + + reset_change_counters(); + } + + subdoc.body.offsetHeight; + reset_change_counters(); + + iframe_style.width = "103px"; + flush_and_assert_change_counters("change width with no media queries", + { restyle: false, construct: false, reflow: true }); + + flush_and_assert_change_counters("no change", + { restyle: false, construct: false, reflow: false }); + + iframe_style.height = "123px"; + flush_and_assert_change_counters("change height with no media queries", + { restyle: false, construct: false, reflow: true }); + + sheet.insertRule("@media (min-width: 150px) { div { display:flex } }", 0); + flush_and_assert_change_counters("add non-matching media query", + // FIXME: We restyle here because + // nsIPresShell::RestyleForCSSRuleChanges posts a restyle, but it's + // probably avoidable if we wanted to avoid it. + { restyle: true, construct: false, reflow: false }); + + iframe_style.width = "177px"; + flush_and_assert_change_counters("resize width across media query with 'display'", + { restyle: true, construct: true, reflow: true }); + + iframe_style.width = "162px"; + flush_and_assert_change_counters("resize width without crossing media query", + { restyle: false, construct: false, reflow: true }); + + sheet.deleteRule(0); + flush_and_assert_change_counters("remove matching media query with 'display'", + { restyle: true, construct: true, reflow: true }); + + sheet.insertRule("@media (max-height: 150px) { div { display:flex } }", 0); + flush_and_assert_change_counters("add matching media query with 'display'", + { restyle: true, construct: true, reflow: true }); + + iframe_style.height = "111px"; + flush_and_assert_change_counters("resize height without crossing media query", + { restyle: false, construct: false, reflow: true }); + + iframe_style.height = "184px"; + flush_and_assert_change_counters("resize height across media query with 'display'", + { restyle: true, construct: true, reflow: true }); + + sheet.deleteRule(0); + flush_and_assert_change_counters("remove non-matching media query", + // FIXME: We restyle here because + // nsIPresShell::RestyleForCSSRuleChanges posts a restyle, but it's + // probably avoidable if we wanted to avoid it. + { restyle: true, construct: false, reflow: false }); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_media_queries_dynamic_xbl.html b/layout/style/test/test_media_queries_dynamic_xbl.html new file mode 100644 index 000000000..f7bbde18e --- /dev/null +++ b/layout/style/test/test_media_queries_dynamic_xbl.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=156716 +--> +<head> + <title>Test for Bug 156716</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=156716">Mozilla Bug 156716</a> +<iframe id="display" src="media_queries_dynamic_xbl_iframe.html"></iframe> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 156716 **/ + +function run() { + var iframe = document.getElementById("display"); + + var subdoc = iframe.contentDocument; + var subwin = iframe.contentWindow; + var p = subdoc.getElementById("para"); + + iframe.setAttribute("style", "height: 300px; width: 100px"); + is(subwin.getComputedStyle(p, "").color, "rgb(128, 0, 128)", + "should be purple when portait"); + iframe.setAttribute("style", "height: 100px; width: 300px"); + is(subwin.getComputedStyle(p, "").color, "rgb(0, 0, 255)", + "should be blue when landscape"); + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_media_query_list.html b/layout/style/test/test_media_query_list.html new file mode 100644 index 000000000..b50771bf6 --- /dev/null +++ b/layout/style/test/test_media_query_list.html @@ -0,0 +1,367 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=542058 +--> +<head> + <title>Test for MediaQueryList (Bug 542058)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=542058">Mozilla Bug 542058</a> +<iframe id="subdoc" src="about:blank"></iframe> +<div id="content" style="display:none"></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for MediaQueryList (Bug 542058) **/ + +SimpleTest.waitForExplicitFinish(); + +function run() { + var iframe = document.getElementById("subdoc"); + var subdoc = iframe.contentDocument; + var subwin = iframe.contentWindow; + var subroot = subdoc.documentElement; + + var content_div = document.getElementById("content"); + content_div.style.font = "initial"; + var em_size = + getComputedStyle(content_div, "").fontSize.match(/^(\d+)px$/)[1]; + + var w = Math.floor(em_size * 9.3); + var h = Math.floor(em_size * 4.2); + iframe.style.width = w + "px"; + iframe.style.height = h + "px"; + subroot.offsetWidth; // flush layout + + function setup_mql(str) { + var obj = { + str: str, + mql: subwin.matchMedia(str), + notifyCount: 0, + listener: function(mql) { + is(mql, obj.mql, + "correct argument to listener: " + obj.str); + ++obj.notifyCount; + // Test the last match result only on odd + // notifications. + if (obj.notifyCount & 1) { + obj.lastOddMatchResult = mql.matches; + } + } + } + obj.mql.addListener(obj.listener); + return obj; + } + + function finish_mql(obj) { + obj.mql.removeListener(obj.listener); + } + + var w_exact_w = setup_mql("(width: " + w + "px)"); + var w_min_9em = setup_mql("(min-width : 9em)"); + var w_min_10em = setup_mql("( min-width: 10em ) "); + var w_max_9em = setup_mql("(max-width: 9em)"); + var w_max_10em = setup_mql("(max-width: 10em)"); + + is(w_exact_w.mql.media, "(width: " + w + "px)", "serialization"); + is(w_min_9em.mql.media, "(min-width: 9em)", "serialization"); + is(w_min_10em.mql.media, "(min-width: 10em)", "serialization"); + is(w_max_9em.mql.media, "(max-width: 9em)", "serialization"); + is(w_max_10em.mql.media, "(max-width: 10em)", "serialization"); + + function check_match(obj, expected, desc) { + is(obj.mql.matches, expected, + obj.str + " media query list .matches " + desc); + if (obj.notifyCount & 1) { // odd notifications only + is(obj.lastOddMatchResult, expected, + obj.str + " media query list last notify result " + desc); + } + } + function check_notify(obj, expected, desc) { + is(obj.notifyCount, expected, + obj.str + " media query list .notify count " + desc); + } + check_match(w_exact_w, true, "initially"); + check_notify(w_exact_w, 0, "initially"); + check_match(w_min_9em, true, "initially"); + check_notify(w_min_9em, 0, "initially"); + check_match(w_min_10em, false, "initially"); + check_notify(w_min_10em, 0, "initially"); + check_match(w_max_9em, false, "initially"); + check_notify(w_max_9em, 0, "initially"); + check_match(w_max_10em, true, "initially"); + check_notify(w_max_10em, 0, "initially"); + + var w2 = Math.floor(em_size * 10.3); + iframe.style.width = w2 + "px"; + subroot.offsetWidth; // flush layout + + check_match(w_exact_w, false, "after width increase to around 10.3em"); + check_notify(w_exact_w, 1, "after width increase to around 10.3em"); + check_match(w_min_9em, true, "after width increase to around 10.3em"); + check_notify(w_min_9em, 0, "after width increase to around 10.3em"); + check_match(w_min_10em, true, "after width increase to around 10.3em"); + check_notify(w_min_10em, 1, "after width increase to around 10.3em"); + check_match(w_max_9em, false, "after width increase to around 10.3em"); + check_notify(w_max_9em, 0, "after width increase to around 10.3em"); + check_match(w_max_10em, false, "after width increase to around 10.3em"); + check_notify(w_max_10em, 1, "after width increase to around 10.3em"); + + var w3 = w * 2; + iframe.style.width = w3 + "px"; + subroot.offsetWidth; // flush layout + + check_match(w_exact_w, false, "after width double from original"); + check_notify(w_exact_w, 1, "after width double from original"); + check_match(w_min_9em, true, "after width double from original"); + check_notify(w_min_9em, 0, "after width double from original"); + check_match(w_min_10em, true, "after width double from original"); + check_notify(w_min_10em, 1, "after width double from original"); + check_match(w_max_9em, false, "after width double from original"); + check_notify(w_max_9em, 0, "after width double from original"); + check_match(w_max_10em, false, "after width double from original"); + check_notify(w_max_10em, 1, "after width double from original"); + + SpecialPowers.setFullZoom(subwin, 2.0); + subroot.offsetWidth; // flush layout + + check_match(w_exact_w, true, "after zoom"); + check_notify(w_exact_w, 2, "after zoom"); + check_match(w_min_9em, true, "after zoom"); + check_notify(w_min_9em, 0, "after zoom"); + check_match(w_min_10em, false, "after zoom"); + check_notify(w_min_10em, 2, "after zoom"); + check_match(w_max_9em, false, "after zoom"); + check_notify(w_max_9em, 0, "after zoom"); + check_match(w_max_10em, true, "after zoom"); + check_notify(w_max_10em, 2, "after zoom"); + + SpecialPowers.setFullZoom(subwin, 1.0); + + finish_mql(w_exact_w); + finish_mql(w_min_9em); + finish_mql(w_min_10em); + finish_mql(w_max_9em); + finish_mql(w_max_10em); + + // Additional tests of listener mutation. + (function() { + var received = []; + var received_mql = []; + function listener1(mql) { + received.push(1); + received_mql.push(mql); + } + function listener2(mql) { + received.push(2); + received_mql.push(mql); + } + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + var mql = subwin.matchMedia("(min-width: 150px)"); + mql.addListener(listener1); + mql.addListener(listener1); + mql.addListener(listener2); + is(JSON.stringify(received), "[]", "listeners before notification"); + + iframe.style.width = "100px"; + subroot.offsetWidth; // flush layout + + is(JSON.stringify(received), "[1,2]", "duplicate listeners removed"); + received = []; + mql.removeListener(listener1); + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + is(JSON.stringify(received), "[2]", "listener removal"); + received = []; + mql.addListener(listener1); + + iframe.style.width = "100px"; + subroot.offsetWidth; // flush layout + + is(JSON.stringify(received), "[2,1]", "listeners notified in order"); + received = []; + mql.addListener(listener2); + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + is(JSON.stringify(received), "[2,1]", "add of existing listener is no-op"); + received = []; + mql.addListener(listener1); + + iframe.style.width = "100px"; + subroot.offsetWidth; // flush layout + + is(JSON.stringify(received), "[2,1]", "add of existing listener is no-op"); + mql.removeListener(listener2); + received = []; + received_mql = []; + + var mql2 = subwin.matchMedia("(min-width: 160px)"); + mql2.addListener(listener1); + mql.addListener(listener2); + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + // mql (1, 2), mql2 (1) + is(JSON.stringify(received), "[1,2,1]", + "notification of lists in order created"); + is(received_mql[0], mql, + "notification of lists in order created"); + is(received_mql[1], mql, + "notification of lists in order created"); + is(received_mql[2], mql2, + "notification of lists in order created"); + received = []; + received_mql = []; + + function removing_listener(mql) { + received.push(3); + received_mql.push(mql); + mql.removeListener(listener2); + mql2.removeListener(listener1); + } + + mql.addListener(removing_listener); + mql.removeListener(listener2); + mql.addListener(listener2); // after removing_listener (3) + + iframe.style.width = "100px"; + subroot.offsetWidth; // flush layout + + // mql(1, 3, 2) mql2(1) + is(JSON.stringify(received), "[1,3,2,1]", + "listeners still notified after removed if change was before"); + is(received_mql[0], mql, + "notification order (removal tests)"); + is(received_mql[1], mql, + "notification order (removal tests)"); + is(received_mql[2], mql, + "notification order (removal tests)"); + is(received_mql[3], mql2, + "notification order (removal tests)"); + received = []; + received_mql = []; + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + // mql(1, 3) + is(JSON.stringify(received), "[1,3]", + "listeners not notified for changes after their removal"); + is(received_mql[0], mql, + "notification order (removal tests)"); + is(received_mql[1], mql, + "notification order (removal tests)"); + })(); + + /* Bug 716751: null-dereference crash */ + (function() { + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + var mql = subwin.matchMedia("(min-width: 150px)"); + SimpleTest.doesThrow(function() { + mql.addListener(null); + }, "expected an exception"); + + iframe.style.width = "100px"; + subroot.offsetWidth; // flush layout + // With the bug, we crash here. No need for test assertions. + + SimpleTest.doesThrow(function() { + mql.removeListener(null); + }, "expected an exception"); + SimpleTest.doesThrow(function() { + mql.removeListener(null); + }, "expected an exception"); + })(); + + /* Bug 753777: test that things work in a freshly-created iframe */ + (function() { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + + is(iframe.contentWindow.matchMedia("(min-width: 1px)").matches, true, + "(min-width: 1px) should match in newly-created iframe"); + is(iframe.contentWindow.matchMedia("(max-width: 1px)").matches, false, + "(max-width: 1px) should not match in newly-created iframe"); + + document.body.removeChild(iframe); + })(); + + /* Bug 716751: listeners lost due to GC */ + var gc_received = []; + (function() { + var received = []; + var listener1 = function(mql) { + gc_received.push(1); + } + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + var mql = subwin.matchMedia("(min-width: 150px)"); + mql.addListener(listener1); + is(JSON.stringify(gc_received), "[]", "GC test: before notification"); + + iframe.style.width = "100px"; + subroot.offsetWidth; // flush layout + + is(JSON.stringify(gc_received), "[1]", "GC test: after notification 1"); + + // Because of conservative GC, we need to go back to the event loop + // to GC properly. + setTimeout(step2, 0); + })(); + + function step2() { + SpecialPowers.DOMWindowUtils.garbageCollect(); + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + is(JSON.stringify(gc_received), "[1,1]", "GC test: after notification 2"); + + bug1270626(); + } + + /* Bug 1270626: listeners that throw exceptions */ + function bug1270626() { + var throwingListener = function(mql) { + throw "error"; + } + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + + var mql = subwin.matchMedia("(min-width: 150px)"); + mql.addListener(throwingListener); + + SimpleTest.expectUncaughtException(true); + is(SimpleTest.isExpectingUncaughtException(), true, + "should be waiting for an uncaught exception"); + + iframe.style.width = "100px"; + subroot.offsetWidth; // flush layout + + is(SimpleTest.isExpectingUncaughtException(), false, + "should have gotten an uncaught exception"); + + SimpleTest.finish(); + } +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_moz_device_pixel_ratio.html b/layout/style/test/test_moz_device_pixel_ratio.html new file mode 100644 index 000000000..d3a8cc27c --- /dev/null +++ b/layout/style/test/test_moz_device_pixel_ratio.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=474356 +--> +<head> + <title>Test for Bug 474356</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style>.zoom-test { visibility: hidden; }</style> + <style><!-- placeholder for dynamic additions --></style> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=474356">Mozilla Bug 474356</a> +<div id="content" style="display: none"> + +</div> +<script type="text/javascript"> +</script> +<pre id="test"> +<div id="zoom1" class="zoom-test"></div> +<div id="zoom2" class="zoom-test"></div> +<div id="zoom3" class="zoom-test"></div> +<script class="testbody" type="application/javascript"> + +/** Test for Bug 474356 **/ + +SimpleTest.waitForExplicitFinish(); + +function run() { + function zoom(factor) { + var previous = SpecialPowers.getFullZoom(window); + SpecialPowers.setFullZoom(window, factor); + return previous; + } + + function isVisible(divName) { + return window.getComputedStyle(document.getElementById(divName), null).visibility == "visible"; + } + + function getScreenPixelsPerCSSPixel() { + return SpecialPowers.DOMWindowUtils.screenPixelsPerCSSPixel; + } + + var screenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel(); + var baseRatio = 1.0 * screenPixelsPerCSSPixel; + var doubleRatio = 2.0 * screenPixelsPerCSSPixel; + var halfRatio = 0.5 * screenPixelsPerCSSPixel; + var styleElem = document.getElementsByTagName("style")[1]; + styleElem.textContent = + ["@media all and (-moz-device-pixel-ratio: " + baseRatio + ") {", + "#zoom1 { visibility: visible; }", + "}", + "@media all and (-moz-device-pixel-ratio: " + doubleRatio + ") {", + "#zoom2 { visibility: visible; }", + "}", + "@media all and (-moz-device-pixel-ratio: " + halfRatio + ") {", + "#zoom3 { visibility: visible; }", + "}" + ].join("\n"); + + ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level"); + ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply"); + var origZoom = zoom(2); + ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level"); + ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply"); + zoom(0.5); + ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level"); + ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply"); + zoom(origZoom); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_namespace_rule.html b/layout/style/test/test_namespace_rule.html new file mode 100644 index 000000000..2cf4c4fc5 --- /dev/null +++ b/layout/style/test/test_namespace_rule.html @@ -0,0 +1,462 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for CSS Namespace rules</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<p id="display"><iframe id="iframe" src="data:application/xhtml+xml,<html%20xmlns='http://www.w3.org/1999/xhtml'><head/><body/></html>"></iframe></p> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var HTML_NS = "http://www.w3.org/1999/xhtml"; +var style_text; + +function run() { + var iframe = $("iframe"); + var ifwin = iframe.contentWindow; + var ifdoc = iframe.contentDocument; + var ifbody = ifdoc.getElementsByTagName("body")[0]; + + function setup_style_text() { + var style_elem = ifdoc.createElement("style"); + style_elem.setAttribute("type", "text/css"); + ifdoc.getElementsByTagName("head")[0].appendChild(style_elem); + var style_text = ifdoc.createCDATASection(""); + style_elem.appendChild(style_text); + return style_text; + } + + style_text = setup_style_text(); + var gCounter = 0; + + /* + * namespaceRules: the @namespace rules to use + * selector: the selector to test + * body_contents: what to set the body's innerHTML to + * match_fn: a function that, given the document object into which + * body_contents has been inserted, produces an array of nodes that + * should match selector + * notmatch_fn: likewise, but for nodes that should not match + */ + function test_selector_in_html(namespaceRules, selector, body_contents, + match_fn, notmatch_fn) + { + var zi = ++gCounter; + if (typeof(body_contents) == "string") { + ifbody.innerHTML = body_contents; + } else { + // It's a function. + ifbody.innerHTML = ""; + body_contents(ifbody); + } + style_text.data = + namespaceRules + " " + selector + "{ z-index: " + zi + " }"; + var should_match = match_fn(ifdoc); + var should_not_match = notmatch_fn(ifdoc); + if (should_match.length + should_not_match.length == 0) { + ok(false, "nothing to check"); + } + + for (var i = 0; i < should_match.length; ++i) { + var e = should_match[i]; + is(ifwin.getComputedStyle(e, "").zIndex, String(zi), + "element in " + body_contents + " matched " + selector); + } + for (var i = 0; i < should_not_match.length; ++i) { + var e = should_not_match[i]; + is(ifwin.getComputedStyle(e, "").zIndex, "auto", + "element in " + body_contents + " did not match " + selector); + } + + // Now, since we're here, may as well make sure serialization + // works correctly. It need not produce the exact same text, + // but it should produce a selector that matches the same + // elements. + zi = ++gCounter; + var ruleList = style_text.parentNode.sheet.cssRules; + var ser1 = ruleList[ruleList.length-1].selectorText; + style_text.data = + namespaceRules + " " + ser1 + "{ z-index: " + zi + " }"; + for (var i = 0; i < should_match.length; ++i) { + var e = should_match[i]; + is(ifwin.getComputedStyle(e, "").zIndex, String(zi), + "element in " + body_contents + " matched " + ser1 + + " which is the reserialization of " + selector); + } + for (var i = 0; i < should_not_match.length; ++i) { + var e = should_not_match[i]; + is(ifwin.getComputedStyle(e, "").zIndex, "auto", + "element in " + body_contents + " did not match " + ser1 + + " which is the reserialization of " + selector); + } + + // But when we serialize the serialized result, we should get + // the same text. + var ser2 = ruleList[ruleList.length-1].selectorText; + is(ser2, ser1, "parse+serialize of selector \"" + selector + + "\" is idempotent"); + + ifbody.innerHTML = ""; + style_text.data = ""; + } + + // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-001.xml + test_selector_in_html( + '@namespace foo "x"; @namespace Foo "y";', + 'Foo|test', + '<test xmlns="y"/>', + function (doc) { return doc.getElementsByTagName("test"); }, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace foo "x"; @namespace Foo "y";', + 'foo|test', + '<test xmlns="y"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-002.xml + test_selector_in_html( + '@namespace foo "";', + 'test', + '<test xmlns=""/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace foo "";', + 'foo|test', + '<test xmlns=""/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-003.xml + test_selector_in_html( + '@namespace foo "";', + 'test', + '<foo xmlns=""><test/></foo>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace foo "";', + 'foo|test', + '<foo xmlns=""><test/></foo>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + // 4 tests from http://tc.labs.opera.com/css/namespaces/prefix-004.xml + test_selector_in_html( + '@namespace ""; @namespace x "test";', + 'test[x]', + '<foo xmlns=""><test x=""/></foo>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace ""; @namespace x "test";', + '*|test', + '<foo xmlns=""><test x=""/></foo>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace ""; @namespace x "test";', + '*|test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace ""; @namespace x "test";', + 'test', + '<test xmlns="test"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + // 2 tests from http://tc.labs.opera.com/css/namespaces/prefix-005.xml + test_selector_in_html( + '@namespace x "test";', + 'test', + '<test/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace x "test";', + 'test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + // Skipping the scope tests because they involve import, and we have no way + // to know when the import load completes. + + // 1 test from http://tc.labs.opera.com/css/namespaces/syntax-001.xml + test_selector_in_html( + '@NAmespace x "http://www.w3.org/1999/xhtml";', + 'x|test', + '<test/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + // 1 test from http://tc.labs.opera.com/css/namespaces/syntax-002.xml + test_selector_in_html( + '@NAmespac\\65 x "http://www.w3.org/1999/xhtml";', + 'x|test', + '<test/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + // 3 tests from http://tc.labs.opera.com/css/namespaces/syntax-003.xml + test_selector_in_html( + '@namespace url("test");', + '*|test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace url("test");', + 'test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace url("test");', + 'test', + '<test/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + // 3 tests from http://tc.labs.opera.com/css/namespaces/syntax-004.xml + test_selector_in_html( + '@namespace u\\00072l("test");', + '*|test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace u\\00072l("test");', + 'test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace u\\00072l("test");', + 'test', + '<test/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + // Skipping http://tc.labs.opera.com/css/namespaces/syntax-005.xml because it + // involves import, and we have no way // to know when the import load completes. + + // Skipping http://tc.labs.opera.com/css/namespaces/syntax-006.xml because it + // involves import, and we have no way // to know when the import load completes. + + // 2 tests from http://tc.labs.opera.com/css/namespaces/syntax-007.xml + test_selector_in_html( + '@charset "x"; @namespace url("test"); @namespace url("test2");', + '*|test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@charset "x"; @namespace url("test"); @namespace url("test2");', + 'test', + '<test xmlns="test"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + // 2 tests from http://tc.labs.opera.com/css/namespaces/syntax-008.xml + test_selector_in_html( + '@namespace \\72x url("test");', + 'rx|test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace \\72x url("test");', + 'test', + '<test xmlns="test"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + // And now some :not() tests + test_selector_in_html( + '@namespace url("test");', + '*|*:not(test)', + '<test xmlns="test"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*:not(test)', + '<test xmlns="testing"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace x url("test");', + '*|*:not(x|test)', + '<test xmlns="test"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + test_selector_in_html( + '@namespace x url("test");', + '*|*:not(x|test)', + '<test xmlns="testing"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*:not(*)', + '<test xmlns="testing"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*:not(*)', + '<test xmlns="test"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + test_selector_in_html( + '@namespace x url("test");', + '*|*:not(x|*)', + '<test xmlns="testing"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace x url("test");', + '*|*:not(x|*)', + '<test xmlns="test"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*:not([foo])', + '<test xmlns="testing" foo="bar"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*:not([foo])', + '<test xmlns="test" foo="bar"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*[foo]', + '<test foo="bar"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*[|foo]', + '<test foo="bar"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace test url("test");', + '*|*[test|foo]', + '<test foo="bar"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + test_selector_in_html( + '@namespace test url("test");', + '*|*[test|foo]', + '<test xmlns:t="test" t:foo="bar"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*[foo]', + '<test xmlns:t="test" t:foo="bar"/>', + function (doc) { return []; }, + function (doc) { return doc.getElementsByTagName("test");} + ); + + test_selector_in_html( + '@namespace url("test");', + '*|*[*|foo]', + '<test xmlns:t="test" t:foo="bar"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + test_selector_in_html( + '', + '*|*[*|foo]', + '<test xmlns:t="test" t:foo="bar"/>', + function (doc) { return doc.getElementsByTagName("test");}, + function (doc) { return []; } + ); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_of_type_selectors.xhtml b/layout/style/test/test_of_type_selectors.xhtml new file mode 100644 index 000000000..7ab286bde --- /dev/null +++ b/layout/style/test/test_of_type_selectors.xhtml @@ -0,0 +1,98 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=75375 +--> +<head> + <title>Test for *-of-type selectors in Bug 75375</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=75375">Mozilla Bug 75375</a> +<div id="content" style="display: none" + xmlns:html="http://www.w3.org/1999/xhtml"> + +<p>This is a <code>p</code> element in the HTML namespace.</p> +<p>This is a second <code>p</code> element in the HTML namespace.</p> +<html:p>This is an <code>html:p</code> element in the HTML namespace.</html:p> +<p xmlns="http://www.example.com/ns">This is a <code>p</code> element in the <code>http://www.example.com/ns</code> namespace.</p> +<html:address>This is an <code>html:address</code> element in the HTML namespace.</html:address> +<address xmlns="">This is a <code>address</code> element in no namespace.</address> +<address xmlns="">This is a <code>address</code> element in no namespace.</address> +<p xmlns="">This is a <code>p</code> element in no namespace.</p> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +/** Test for *-of-type selectors in Bug 75375 **/ + +var HTML_NS = "http://www.w3.org/1999/xhtml"; + +function setup_style_text() { + var result = document.createCDATASection(""); + var style = document.createElementNS(HTML_NS, "style"); + style.appendChild(result); + document.getElementsByTagName("head")[0].appendChild(style); + return result; +} + +function run() { + var styleText = setup_style_text(); + + var elements = []; + + var div = document.getElementById("content"); + for (var i = 0; i < div.childNodes.length; ++i) { + var child = div.childNodes[i]; + if (child.nodeType == Node.ELEMENT_NODE) + elements.push(child); + } + + var counter = 0; + + function test_selector(selector, match_indices, notmatch_indices) + { + var zi = ++counter; + styleText.data = selector + " { z-index: " + zi + " }"; + var i; + for (i in match_indices) { + var e = elements[match_indices[i]]; + is(getComputedStyle(e, "").zIndex, String(zi), + "element " + match_indices[i] + " matched " + selector); + } + for (i in notmatch_indices) { + var e = elements[notmatch_indices[i]]; + is(getComputedStyle(e, "").zIndex, "auto", + "element " + notmatch_indices[i] + " did not match " + selector); + } + } + + // 0 - html:p + // 1 - html:p + // 2 - html:p + // 3 - example:p + // 4 - html:address + // 5 - :address + // 6 - :address + // 7 - :p + test_selector(":nth-of-type(1)", [0, 3, 4, 5, 7], [1, 2, 6]); + test_selector(":nth-last-of-type(1)", [2, 3, 4, 6, 7], [0, 1, 5]); + test_selector(":nth-last-of-type(-n+1)", [2, 3, 4, 6, 7], [0, 1, 5]); + test_selector(":nth-of-type(even)", [1, 6], [0, 2, 3, 4, 5, 7]); + test_selector(":nth-last-of-type(odd)", [0, 2, 3, 4, 6, 7], [1, 5]); + test_selector(":nth-last-of-type(n+2)", [0, 1, 5], [2, 3, 4, 6, 7]); + test_selector(":first-of-type", [0, 3, 4, 5, 7], [1, 2, 6]); + test_selector(":last-of-type", [2, 3, 4, 6, 7], [0, 1, 5]); + test_selector(":only-of-type", [3, 4, 7], [0, 1, 2, 5, 6]); +} + +run(); + +]]> +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_page_parser.html b/layout/style/test/test_page_parser.html new file mode 100644 index 000000000..8c94be0bf --- /dev/null +++ b/layout/style/test/test_page_parser.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=115199 --> +<head> + <meta charset="UTF-8"> + <title>Test of @page parser</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<p>@page parsing (<a + target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=115199" +>bug 115199</a>)</p> +<pre id="display"></pre> +<style type="text/css" id="testbox"></style> +<script class="testbody" type="text/javascript"> + function _(b) { return "@page { " + b + " }"; }; + + var testset = [ + // CSS 2.1 only allows margin properties in the page rule. + + // Check a bad property. + { rule: "position: absolute;" }, + + // Check good properties with invalid units. + { rule: _("margin: 2in; margin: 2vw;"), expected: { + "margin-top": "2in", + "margin-right": "2in", + "margin-bottom": "2in", + "margin-left": "2in" + }}, + { rule: _("margin-top: 2in; margin-top: 2vw;"), expected: {"margin-top": "2in"}}, + { rule: _("margin-top: 2in; margin-top: 2vh;"), expected: {"margin-top": "2in"}}, + { rule: _("margin-top: 2in; margin-top: 2vmax;"), expected: {"margin-top": "2in"}}, + { rule: _("margin-top: 2in; margin-top: 2vmin;"), expected: {"margin-top": "2in"}}, + + // Check good properties. + { rule: _("margin: 2in;"), expected: { + "margin-top": "2in", + "margin-right": "2in", + "margin-bottom": "2in", + "margin-left": "2in" + }}, + { rule: _("margin-top: 2in;"), expected: {"margin-top": "2in"}}, + { rule: _("margin-left: 2in;"), expected: {"margin-left": "2in"}}, + { rule: _("margin-bottom: 2in;"), expected: {"margin-bottom": "2in"}}, + { rule: _("margin-right: 2in;"), expected: {"margin-right": "2in"}} + ]; + + var display = document.getElementById("display"); + var sheet = document.styleSheets[1]; + + for (var curTest = 0; curTest < testset.length; curTest++) { + try { + while(sheet.cssRules.length > 0) + sheet.deleteRule(0); + sheet.insertRule(testset[curTest].rule, 0); + } catch (e) { + ok(e.name == "SyntaxError" + && e instanceof DOMException + && e.code == DOMException.SYNTAX_ERR + && !('expected' in testset[curTest]), + testset[curTest].rule + " syntax error thrown", e); + } + + try { + if (testset[curTest].expected) { + is(sheet.cssRules.length, 1, + testset[curTest].rule + " rule count"); + is(sheet.cssRules[0].type, CSSRule.PAGE_RULE, + testset[curTest].rule + " rule type"); + + var expected = testset[curTest].expected; + var s = sheet.cssRules[0].style; + var n = 0; + + // everything is set that should be + for (var name in expected) { + is(s.getPropertyValue(name), expected[name], + testset[curTest].rule + " (prop " + name + ")"); + n++; + } + // nothing else is set + is(s.length, n, testset[curTest].rule + "prop count"); + for (var i = 0; i < s.length; i++) { + ok(s[i] in expected, testset[curTest].rule, + "Unexpected item #" + i + ": " + s[i]); + } + } else { + if (sheet.cssRules.length == 0) { + is(sheet.cssRules.length, 0, + testset[curTest].rule + " rule count (0)"); + } else { + is(sheet.cssRules.length, 1, + testset[curTest].rule + " rule count (1 non-page)"); + isnot(sheet.cssRules[0].type, CSSRule.PAGE_RULE, + testset[curTest].rule + " rule type (1 non-page)"); + } + } + } catch (e) { + ok(false, testset[curTest].rule, "During test: " + e); + } + } +</script> +</body> +</html> diff --git a/layout/style/test/test_parse_eof.html b/layout/style/test/test_parse_eof.html new file mode 100644 index 000000000..74737fd5e --- /dev/null +++ b/layout/style/test/test_parse_eof.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset=utf-8> + <title>Test parsing behaviour of backslash just before EOF</title> + <link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au"> + <meta name="flags" content=""> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + +<style>#a::before { content: "ab\</style> +<style>#b { background-image: url("ab\</style> +<style>#c { background-image: url(ab\</style> +<style>#d { counter-reset: ab\</style> + +<style> +#a-ref::before { content: "ab"; } +#b-ref { background-image: url("ab"); } +#c-ref { background-image: url(ab�); } +#d-ref { counter-reset: ab�; } +</style> + +<div style="display: none"> + <div id="a"></div> + <div id="b"></div> + <div id="c"></div> + <div id="d"></div> + + <div id="a-ref"></div> + <div id="b-ref"></div> + <div id="c-ref"></div> + <div id="d-ref"></div> +</div> + +<script> +var a = document.getElementById("a"); +var b = document.getElementById("b"); +var c = document.getElementById("c"); +var d = document.getElementById("d"); +var a_ref = document.getElementById("a-ref"); +var b_ref = document.getElementById("b-ref"); +var c_ref = document.getElementById("c-ref"); +var d_ref = document.getElementById("d-ref"); + +test(function() { + assert_equals(window.getComputedStyle(a, ":before").content, + window.getComputedStyle(a_ref, ":before").content); +}, "test backslash before EOF inside a string"); + +test(function() { + assert_equals(window.getComputedStyle(b, "").backgroundImage, + window.getComputedStyle(b_ref, "").backgroundImage); +}, "test backslash before EOF inside a url(\"\")"); + +test(function() { + assert_equals(window.getComputedStyle(c, "").backgroundImage, + window.getComputedStyle(c_ref, "").backgroundImage); +}, "test backslash before EOF inside a url()"); + +test(function() { + assert_equals(window.getComputedStyle(d, "").counterReset, + window.getComputedStyle(d_ref, "").counterReset); +}, "test backslash before EOF outside a string"); +</script> + +</body> +</html> diff --git a/layout/style/test/test_parse_ident.html b/layout/style/test/test_parse_ident.html new file mode 100644 index 000000000..e083aad6c --- /dev/null +++ b/layout/style/test/test_parse_ident.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for CSS identifier parsing</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> + +var div = document.getElementById("content"); + +function counter_increment_parses(i) +{ + div.style.counterIncrement = ""; + div.style.counterIncrement = i; + return div.style.counterIncrement != ""; +} + +function is_valid_identifier(i) +{ + ok(counter_increment_parses(i), + "'" + i + "' is a valid CSS identifier"); +} + +function is_invalid_identifier(i) +{ + ok(!counter_increment_parses(i), + "'" + i + "' is not a valid CSS identifier"); +} + +for (var i = 0x7B; i < 0x80; ++i) { + is_invalid_identifier(String.fromCharCode(i)); + is_invalid_identifier("a" + String.fromCharCode(i)); + is_invalid_identifier(String.fromCharCode(i) + "a"); +} + +for (var i = 0x80; i < 0xFF; ++i) { + is_valid_identifier(String.fromCharCode(i)); +} + +is_valid_identifier(String.fromCharCode(0x100)); +is_valid_identifier(String.fromCharCode(0x375)); +is_valid_identifier(String.fromCharCode(0xFEFF)); +is_valid_identifier(String.fromCharCode(0xFFFD)); +is_valid_identifier(String.fromCharCode(0xFFFE)); +is_valid_identifier(String.fromCharCode(0xFFFF)); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_parse_rule.html b/layout/style/test/test_parse_rule.html new file mode 100644 index 000000000..ebaf5aa5d --- /dev/null +++ b/layout/style/test/test_parse_rule.html @@ -0,0 +1,256 @@ +<!DOCTYPE html> +<html lang=en> +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<META http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<body> +<iframe></iframe> +<!-- Note that the following style and div elements are duplicates + of the ones written into the iframe; they are here for convienience + in resolving the "standard" computed value for a given specification +--> +<style></style> +<div id=a class='a b c' title='zxcv weeqweqeweasd a '></div> +<script> +SimpleTest.waitForExplicitFinish(); + +window.onload=function(){ + +var base; + +// A short note about escaping: all of the strings in this test go through +// Javascript unescaping before getting passed to CSS. This means that +// sequences like "\n" refer to a newline, a single backslash is written "\\", +// a CSS escape sequence is something like "\\A", and some quotes must be +// escaped. + +var testset = [ + +// Color tests +// Generic property for testing +{ base : base = "div {color:green}", + tests : [ +// My misc tests +"<!--#a {color:green}", +base + "<!-#a {color:red}", +base + "#a<!--{color:red}", +"-->#a{color:green}", +base + "--#a {color:red}", +base + "--aasdf, #a {color:green}", +base + "-0aasdf, #a {color:red}", +"-asdf, #a {color:green}", +base + "#a {color: rgb\n(255, 0, 0)}", +"#a {font: \"Arial\n;color:green}", +"#a {color: @charset{}\"\\\n'\"url(\na\na); color:green}", +"#a\r{color:green}", +"#a\n{color:green}", +"#a\t{color:green}", +"@threedee maroon url('asdf\n) ra('asdf\n); " + base, +"@threedee {maroon url('asdf\n) ra('asdf\n);} " + base, +"div[title='zxcv weeqweqeweasd\\D\\A a']{color:green}", +"div[title~='weeqweqeweasd']{color:green}", +base + "#a\\\n{color:red}", +base + "#a\v{color:red}", + +// CSS1 section 7.1 +"#a {color: green; rotation: 70deg;}", +"#a {color: green;} #a{color:invalidValue;}", +base + "#a {color: \"red\"}", +base + "@three-dee {\n @background-lighting {\n azimuth: 30deg;\n elevation: 190deg;\n }\n #a { color: red }\n }", +"#a {COLOR: GREEN}", +base + "#a:wait {color: red}", +"#a:lang(en) {color: green}", +"#a:lang(\nen\r\t ) {color: green}", +base + "div ! em, #a {color: red}", +base + "//asdf.zxcv,\n#a {color: red}", +"#a {rotation-code: \"}\"; color: green;}", +"#a {rotation-code: \"\\\"}\\\"\"; color: green;}", +"#a {rotation-code: '}'; color: green;}", +"#a {rotation-code: '\\'}\\''; color: green;}", +"#a {\n type-display: @threedee {rotation-code: '}';};\n color: green;\n }", +base + "p {text-indent: 0.5in;} color: maroon #a {color: red;}", +base + "p {text-indent: 0.5in;} color: maroon; #a {color: red;}", + +// string tokenization as error token, not EOF (bug 311566 comment 70) +"#a { color: green; foo: { \"bar\n;color: red}", + +// CSS 2.1 section 4.1.3 +"@MediA All {#a {ColOR :RgB(\t0,\r128,\n0 ) } };", +base + "\\#a{color:red;}", +base + "#a\\{color:red;\\}", +base + "#a{color\\:red;}", +base + "#a{color:red\\;}", +"#a {c\\o\\l\\o\\r:\\g\\ree\\n}", +"#a{ co\\00006Cor: gr\\000065en; }", +"#a{ co\\4C or: gr\\000045en; }", +".IdE6n-3t0_6, #a { color: green }", +"#IdE6n-3t0_6, #a { color: green }", +"._ident, #a { color: green }", +"#_ident, #a { color: green }", +".-ident, .a { color: green; }", // Testsuite has incorrect version +"#怀ident, .a { color: green }", +"#iden怀t怀, .a { color: green }", +"#\\6000ident, .a { color: green }", +"#iden\\6000t\\6000, .a { color: green }", +".怀ident, .a { color: green }", +".iden怀t怀, .a { color: green }", +".\\6000ident, .a { color: green }", +".iden\\6000t\\6000, .a { color: green }", +base + "#6ident, #a {color: red }", +".id4ent6, .a { color: green }", +"#\\ident, .a { color: green; }", +"#ide\\n\\t, .a { color: green; }", +".\\6ident, .a { color: green; }", +".\\--ident, .a { color: green; }", + +// CSS2.1 section 4.1.5 and 4.2 +"@import 'data:text/css,%23a{color:green}';", +"@import \"data:text/css,%23a{color:green}\";", +"@import url(data:text/css,%23a{color:green});", +"@import 'data:text/css,%23a{color:green}' screen;", +base + "@import 'data:text/css,%23a{color:red}' blahblahblah;", +"@import 'data:text/css,%23a{color:green}'", +"@import 'data:text/css,%23a{color:green}", +"@foo {}" + base, +"@foo bar {}" + base, +"@foo; " + base, +"@foo bar baz; " + base, +base + "@foo {}; #a {color: red}", + +// CSS2.1 section 4.1.9 +"/* This is a CSS comment. */" + base, +base + "/* #a {color: red} */", +"/*********** /*/" + base, + +// CSS2.1 section 4.3.6 +base + "#a {color: rgb(255, 0, 0%)}", +base + "#a {color: rgb(100%, 0, 0)}", +"#a {color: rgb(0, 128, 0)}", +"#a {color: rgb(0%, 50%, 0%)}", +"#a {color: rgb(0%, 49.999999999999%, 0%)}", + +// CSS-Color-4 +// https://drafts.csswg.org/css-color/#rgb-functions +"#a {color: rgb(0, 128.0, 0)}", +], prop: "color", pseudo: "" +}, + +// Border tests +// For testing lengths +{ base : base = "#a {border-style:solid}", + tests : [ +// CSS1 section 7.1 +base + "#a {border-width: funny}", +base + "#a {border-width: 50zu}", +base + "#a {border-width: px}", + +// Number/unit parsing +base + "#a {border-width: 0.px}", +base + "#a {border-width: ..0px}", +base + "#a {border-width: 0..0px}", +base + "#a {border-width: 0.}", +base + "#a {border-width: ..0}", +base + "#a {border-width: 0..0}", +base + "#a {border-width: 0; border-width: .0px medium}", +base + "#a {border-width: 0; border-width: .0 medium}", +base + "#a {border-width: 0; border-width: 0.0px medium}", +], prop: "borderRightWidth", pseudo: ""}, + +// Content tests +// Tests for strings and pseudos +{base : base = ".a::before {content: 'This is \\a'}", + tests : [ +// CSS 2.1 section 4.1.3 +"#a::before {content: 'This is \\a '}", +"#a::before {content: 'This is \\A '}", +"#a::before {content: 'This is \\0000a '}", +"#a::before {content: 'This is \\00000a '}", +"#a::before {content: 'This is \\\n\\00000a '}", +"#a::before {content: 'This is \\\015\012\\00000a '}", +"#a::before {content: 'This is \\\015\\00000a '}", +"#a::before {content: 'This is \\\f\\00000a '}", +"#a::before {content: 'This is\\20\f\\a'}", +"#a::before {content: 'This is\\20\r\\a'}", +"#a::before {content: 'This is\\20\n\\a'}", +"#a::before {content: 'This is\\20\r\n\\a'}", +base + "#a::before {content: 'FAIL \f\\a'}", +base + "#a::before {content: 'FAIL \\\n\r\\a'}", +"#a:before {content: 'This is \\a'}", + +base + "#a:: before {content: 'FAIL'}", +base + "#a ::before {content: 'FAIL'}", +"#a::before {content: 'This is \\a", + +], prop: "content", pseudo: "::before" +}, + +// Background color tests +// For basic URL parsing sanity checks +{ base : base = "div {background: blue}", + tests : [ +"#a {background: url() blue}", +"#a {background: url(怀) blue}", +], prop: "backgroundColor", pseudo: "" +}, + +// A one-off test I couldn't come up with a better way to do +{ base : base = "div {border-style: dotted}", + tests : [ +// Sanity-check to make sure this test will work +// This test requires a color name that starts with a "-" +base + "#a {border: dotted 0 -moz-menuhover}", +// The actual test: check that 0-moz-menuhover get parsed as an unknown dimension +// rather than a separate identifier +base + "#a {border: solid 0-moz-menuhover}", +], prop: "borderLeftStyle", pseudo: "" +}, + +]; + +var curTest = -1; +var curSubTest = 0; + +var styleElement = document.getElementsByTagName("style")[0]; +var divElement = document.getElementById("a"); +var frame = document.getElementsByTagName("iframe")[0]; + +var canonical; + +var doTests = function() { + if (curTest >= 0) { + var curElement = frame.contentDocument.getElementsByTagName("div")[0]; + var curStyle = frame.contentDocument.defaultView.getComputedStyle(curElement, testset[curTest].pseudo); + if (testset[curTest].todo && testset[curTest].todo[testset[curTest].tests[curSubTest]]) { + todo_is(curStyle[testset[curTest].prop], canonical, testset[curTest].tests[curSubTest]); + } else { + is(curStyle[testset[curTest].prop], canonical, testset[curTest].tests[curSubTest]); + } + curSubTest++; + } + if (curTest == -1 || curSubTest >= testset[curTest].tests.length) { + curTest++; + curSubTest = 0; + } + if (!(curTest < testset.length)) { + SimpleTest.finish(); + return; + } + if (curSubTest == 0) { + styleElement.textContent = ""; + var base = document.defaultView.getComputedStyle(divElement, testset[curTest].pseudo)[testset[curTest].prop]; + styleElement.textContent = testset[curTest].base; + canonical = document.defaultView.getComputedStyle(divElement, testset[curTest].pseudo)[testset[curTest].prop]; + styleElement.textContent = ""; + isnot(base, canonical, "Sanity check for rule: " + testset[curTest].base); + } + frame.contentDocument.open(); + frame.contentDocument.write("<html lang=en><style>" + testset[curTest].tests[curSubTest] + "</style><div id=a class='a b c' title='zxcv weeqweqeweasd a'></div>"); + frame.contentWindow.onload = function(){setTimeout(doTests, 0);}; + frame.contentDocument.close(); +}; + +doTests(); + +}; + +</script> diff --git a/layout/style/test/test_parse_url.html b/layout/style/test/test_parse_url.html new file mode 100644 index 000000000..aa167398f --- /dev/null +++ b/layout/style/test/test_parse_url.html @@ -0,0 +1,195 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=473914 +--> +<head> + <title>Test for Bug 473914</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=473914">Mozilla Bug 473914</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 473914 **/ + +var div = document.getElementById("content"); + +// This test relies on normalization (insertion of quote marks) that +// we're not really guaranteed to continue doing in the future. +div.style.listStyleImage = 'url(http://example.org/**/)'; +is(div.style.listStyleImage, 'url("http://example.org/**/")', + "not treated as comment"); +div.style.listStyleImage = 'url("http://example.org/**/")'; +is(div.style.listStyleImage, 'url("http://example.org/**/")', + "not treated as comment"); +div.style.listStyleImage = 'url(/**/foo)'; +is(div.style.listStyleImage, 'url("/**/foo")', + "not treated as comment"); +div.style.listStyleImage = 'url("/**/foo")'; +is(div.style.listStyleImage, 'url("/**/foo")', + "not treated as comment"); +div.style.listStyleImage = 'url(/**/)'; +is(div.style.listStyleImage, 'url("/**/")', + "not treated as comment"); +div.style.listStyleImage = 'url("/**/")'; +is(div.style.listStyleImage, 'url("/**/")', + "not treated as comment"); + +// Tests from Alfred Keyser's patch in bug 337287 (modified by dbaron) +div.style.listStyleImage = 'url("bad")'; +div.style.listStyleImage = 'url(good /*bad comment*/)'; +is(div.style.listStyleImage, 'url("bad")', + "comment not allowed inside token"); + +div.style.listStyleImage = 'url(good /*bad comments*/ /*Hello*/)'; +is(div.style.listStyleImage, 'url("bad")', + "comment not allowed inside token"); + +div.style.listStyleImage = 'url(good/*commentaspartofurl*/)'; +is(div.style.listStyleImage, 'url("good/*commentaspartofurl*/")', + "comment-like syntax not comment inside of url"); + +div.style.listStyleImage = 'url("bad")'; +div.style.listStyleImage = 'url(good/**/ /*secondcommentcanbeskipped*/ )'; +is(div.style.listStyleImage, 'url("bad")', + "comment not allowed inside token"); + +div.style.listStyleImage = 'url(/*partofurl*/good)'; +is(div.style.listStyleImage, 'url("/*partofurl*/good")', + "comment not parsed as part of url"); + +div.style.listStyleImage = 'url(good'; +is(div.style.listStyleImage, 'url("good")', + "URL ending with eof not correctly handled"); + +div.style.listStyleImage = 'url("bad")'; +div.style.listStyleImage = 'url(good /*)*/'; +is(div.style.listStyleImage, 'url("bad")', + "comment not allowed inside token"); + +div.style.listStyleImage = 'url("bad")'; +div.style.listStyleImage = 'url(good /*)*/ tokenaftercommentevenwithclosebracketisinvalid'; +is(div.style.listStyleImage, 'url("bad")', + "comment not allowed inside token"); + +div.style.listStyleImage = 'url(bad)'; +div.style.listStyleImage = 'url("good"'; +is(div.style.listStyleImage, 'url("good")', + "URL as string without close bracket"); + +div.style.listStyleImage = 'url(bad)'; +div.style.listStyleImage = 'url("good'; +is(div.style.listStyleImage, 'url("good")', + "URL as string without closing quote"); + +div.style.listStyleImage = 'url("bad")'; +div.style.listStyleImage = 'url(good notgood'; +is(div.style.listStyleImage, 'url("bad")', + "second token should make url invalid"); + +div.style.listStyleImage = 'url("bad")'; +div.style.listStyleImage = 'url(good(notgood'; +is(div.style.listStyleImage, 'url("bad")', + "open bracket in url not recognized as invalid"); + +var longurl = ''; +for (i=0;i<1000;i++) { + longurl = longurl + 'verylongurlindeed_thequickbrownfoxjumpsoverthelazydoq'; +} +div.style.listStyleImage = 'url(' + longurl; +is(div.style.listStyleImage, 'url("' + longurl + '")', + "very long url not correctly parsed"); + + +// Additional tests from +// https://bugzilla.mozilla.org/show_bug.cgi?id=337287#c21 + +div.style.listStyleImage = 'url(good/*)'; +is(div.style.listStyleImage, 'url("good/*")', + "URL containing comment start is valid"); + +div.style.listStyleImage = 'url("bad")'; +div.style.listStyleImage = 'url(good bad)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +div.style.listStyleImage = 'url(\\g b)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +div.style.listStyleImage = 'url( \\g b)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +div.style.listStyleImage = 'url(c\\g b)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +div.style.listStyleImage = 'url(cc\\g b)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +div.style.listStyleImage = 'url(\\f b)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +div.style.listStyleImage = 'url( \\f b)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +div.style.listStyleImage = 'url(c\\f b)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +div.style.listStyleImage = 'url(cc\\f b)'; +is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with spaces not allowed"); + +var chars = [ 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127]; + +for (var i in chars) { + var charcode = chars[i]; + div.style.listStyleImage = 'url(' + String.fromCharCode(charcode) + ')'; + is(div.style.listStyleImage, 'url("bad")', + "unquoted URL with control character " + charcode + " not allowed"); +} + +div.style.listStyleImage = 'url(\u00ff)'; +is(div.style.listStyleImage, 'url("\u00ff")', "U+A0-U+FF allowed in unquoted URL"); + +div.style.listStyleImage = 'url(\\f good)'; +is(div.style.listStyleImage, 'url("\\f good")', "URL allowed"); +div.style.listStyleImage = 'url( \\f good)'; +is(div.style.listStyleImage, 'url("\\f good")', "URL allowed"); +div.style.listStyleImage = 'url(f\\f good)'; +is(div.style.listStyleImage, 'url("f\\f good")', "URL allowed"); +div.style.listStyleImage = 'url(go\\od)'; +is(div.style.listStyleImage, 'url("good")', "URL allowed"); +div.style.listStyleImage = 'url(goo\\d)'; +is(div.style.listStyleImage, 'url("goo\\d ")', "URL allowed"); +div.style.listStyleImage = 'url(go\\o)'; +is(div.style.listStyleImage, 'url("goo")', "URL allowed"); + +div.setAttribute("style", "color: url(/*); color: green"); +is(div.style.color, 'green', + "URL tokenized correctly outside properties taking URLs"); + +div.style.listStyleImage = 'url("foo\\\nbar1")'; +is(div.style.listStyleImage, 'url("foobar1")', + "escaped newline allowed in string form of URL"); +div.style.listStyleImage = 'url(foo\\\nbar2)'; +is(div.style.listStyleImage, 'url("foobar1")', + "escaped newline NOT allowed in NON-string form of URL"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_parser_diagnostics_unprintables.html b/layout/style/test/test_parser_diagnostics_unprintables.html new file mode 100644 index 000000000..384d4dfa6 --- /dev/null +++ b/layout/style/test/test_parser_diagnostics_unprintables.html @@ -0,0 +1,220 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Test for CSS parser diagnostics escaping unprintable + characters correctly</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=229827" +>Mozilla Bug 229827</a> +<style id="testbench"></style> +<script type="application/javascript;version=1.8"> +// This test has intimate knowledge of how to get the CSS parser to +// emit diagnostics that contain text under control of the user. +// That's not the point of the test, though; the point is only that +// *that text* is properly escaped. + +// There is one "pattern" for each code path through the error reporter +// that might need to escape some kind of user-supplied text. +// Each "pattern" is tested once with each of the "substitution"s below: +// <t>, <i>, and <s> are replaced by the t:, i:, and s: fields of +// each substitution object in turn. +const patterns = [ + // REPORT_UNEXPECTED_P (only ever used in contexts where identifier-like + // escaping is appropriate) + { i: "<t>|x{}", o: "prefix \u2018<i>\u2019" }, + // REPORT_UNEXPECTED_TOKEN with: + // _Ident + { i: "@namespace fnord <t>;", o: "within @namespace: \u2018<i>\u2019" }, + // _Ref + { i: "@namespace fnord #<t>;", o: "within @namespace: \u2018#<i>\u2019" }, + // _Function + { i: "@namespace fnord <t>();", o: "within @namespace: \u2018<i>(\u2019" }, + // _Dimension + { i: "@namespace fnord 14<t>;", o: "within @namespace: \u201814<i>\u2019" }, + // _AtKeyword + { i: "x{@<t>: }", o: "declaration but found \u2018@<i>\u2019." }, + // _String + { i: "x{ '<t>'}" , o: "declaration but found \u2018'<s>'\u2019." }, + // _Bad_String + { i: "x{ '<t>\n}", o: "declaration but found \u2018'<s>\u2019." }, + // _URL + { i: "x{ url('<t>')}", o: "declaration but found \u2018url('<s>')\u2019." }, + // _Bad_URL + { i: "x{ url('<t>'.)}" , o: "declaration but found \u2018url('<s>'\u2019." } +]; + +// Blocks of characters to test, and how they should be escaped when +// they appear in identifiers and string constants. +const substitutions = [ + // ASCII printables that _can_ normally appear in identifiers, + // so should of course _not_ be escaped. + { t: "-_0123456789", i: "-_0123456789", + s: "-_0123456789" }, + { t: "abcdefghijklmnopqrstuvwxyz", i: "abcdefghijklmnopqrstuvwxyz", + s: "abcdefghijklmnopqrstuvwxyz" }, + { t: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", i: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + s: "ABCDEFGHIJKLMNOPQRSTUVWXYZ" }, + + // ASCII printables that are not normally valid as the first character + // of an identifier, or the character immediately after a leading dash, + // but can be forced into that position with escapes. + { t: "\\-", i: "\\-", s: "-" }, + { t: "\\30 ", i: "\\30 ", s: "0" }, + { t: "\\31 ", i: "\\31 ", s: "1" }, + { t: "\\32 ", i: "\\32 ", s: "2" }, + { t: "\\33 ", i: "\\33 ", s: "3" }, + { t: "\\34 ", i: "\\34 ", s: "4" }, + { t: "\\35 ", i: "\\35 ", s: "5" }, + { t: "\\36 ", i: "\\36 ", s: "6" }, + { t: "\\37 ", i: "\\37 ", s: "7" }, + { t: "\\38 ", i: "\\38 ", s: "8" }, + { t: "\\39 ", i: "\\39 ", s: "9" }, + { t: "-\\-", i: "--", s: "--" }, + { t: "-\\30 ", i: "-\\30 ", s: "-0" }, + { t: "-\\31 ", i: "-\\31 ", s: "-1" }, + { t: "-\\32 ", i: "-\\32 ", s: "-2" }, + { t: "-\\33 ", i: "-\\33 ", s: "-3" }, + { t: "-\\34 ", i: "-\\34 ", s: "-4" }, + { t: "-\\35 ", i: "-\\35 ", s: "-5" }, + { t: "-\\36 ", i: "-\\36 ", s: "-6" }, + { t: "-\\37 ", i: "-\\37 ", s: "-7" }, + { t: "-\\38 ", i: "-\\38 ", s: "-8" }, + { t: "-\\39 ", i: "-\\39 ", s: "-9" }, + + // ASCII printables that must be escaped in identifiers. + // Most of these should not be escaped in strings. + { t: "\\!\\\"\\#\\$", i: "\\!\\\"\\#\\$", s: "!\\\"#$" }, + { t: "\\%\\&\\'\\(", i: "\\%\\&\\'\\(", s: "%&\\'(" }, + { t: "\\)\\*\\+\\,", i: "\\)\\*\\+\\,", s: ")*+," }, + { t: "\\.\\/\\:\\;", i: "\\.\\/\\:\\;", s: "./:;" }, + { t: "\\<\\=\\>\\?", i: "\\<\\=\\>\\?", s: "<=>?", }, + { t: "\\@\\[\\\\\\]", i: "\\@\\[\\\\\\]", s: "@[\\\\]" }, + { t: "\\^\\`\\{\\}\\~", i: "\\^\\`\\{\\}\\~", s: "^`{}~" }, + + // U+0000 - U+0020 (C0 controls, space) + // U+000A LINE FEED, U+000C FORM FEED, and U+000D CARRIAGE RETURN + // cannot be put into a CSS token as escaped literal characters, so + // we do them with hex escapes instead. + // The parser replaces U+0000 with U+FFFD. + { t: "\\\x00\\\x01\\\x02\\\x03", i: "�\\1 \\2 \\3 ", + s: "�\\1 \\2 \\3 " }, + { t: "\\\x04\\\x05\\\x06\\\x07", i: "\\4 \\5 \\6 \\7 ", + s: "\\4 \\5 \\6 \\7 " }, + { t: "\\\x08\\\x09\\000A\\\x0B", i: "\\8 \\9 \\a \\b ", + s: "\\8 \\9 \\a \\b " }, + { t: "\\000C\\000D\\\x0E\\\x0F", i: "\\c \\d \\e \\f ", + s: "\\c \\d \\e \\f " }, + { t: "\\\x10\\\x11\\\x12\\\x13", i: "\\10 \\11 \\12 \\13 ", + s: "\\10 \\11 \\12 \\13 " }, + { t: "\\\x14\\\x15\\\x16\\\x17", i: "\\14 \\15 \\16 \\17 ", + s: "\\14 \\15 \\16 \\17 " }, + { t: "\\\x18\\\x19\\\x1A\\\x1B", i: "\\18 \\19 \\1a \\1b ", + s: "\\18 \\19 \\1a \\1b " }, + { t: "\\\x1C\\\x1D\\\x1E\\\x1F\\ ", i: "\\1c \\1d \\1e \\1f \\ ", + s: "\\1c \\1d \\1e \\1f " }, + + // U+007F (DELETE) and U+0080 - U+009F (C1 controls) + { t: "\\\x7f\\\x80\\\x81\\\x82", i: "\\7f \\80 \\81 \\82 ", + s: "\\7f \\80 \\81 \\82 " }, + { t: "\\\x83\\\x84\\\x85\\\x86", i: "\\83 \\84 \\85 \\86 ", + s: "\\83 \\84 \\85 \\86 " }, + { t: "\\\x87\\\x88\\\x89\\\x8A", i: "\\87 \\88 \\89 \\8a ", + s: "\\87 \\88 \\89 \\8a " }, + { t: "\\\x8B\\\x8C\\\x8D\\\x8E", i: "\\8b \\8c \\8d \\8e ", + s: "\\8b \\8c \\8d \\8e " }, + { t: "\\\x8F\\\x90\\\x91\\\x92", i: "\\8f \\90 \\91 \\92 ", + s: "\\8f \\90 \\91 \\92 " }, + { t: "\\\x93\\\x94\\\x95\\\x96", i: "\\93 \\94 \\95 \\96 ", + s: "\\93 \\94 \\95 \\96 " }, + { t: "\\\x97\\\x98\\\x99\\\x9A", i: "\\97 \\98 \\99 \\9a ", + s: "\\97 \\98 \\99 \\9a " }, + { t: "\\\x9B\\\x9C\\\x9D\\\x9E\\\x9F", i: "\\9b \\9c \\9d \\9e \\9f ", + s: "\\9b \\9c \\9d \\9e \\9f " }, + + // CSS doesn't bother with the full Unicode rules for identifiers, + // instead declaring that any code point greater than or equal to + // U+00A0 is a valid identifier character. Test a small handful + // of both basic and astral plane characters. + + // Arabic (caution to editors: there is a possibly-invisible U+200E + // LEFT-TO-RIGHT MARK in each string, just before the close quote) + { t: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ", + i: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ", + s: "أبجدهوزحطيكلمنسعفصقرشتثخذضظغ" }, + + // Box drawing + { t: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷", + i: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷", + s: "─│┌┐└┘├┤┬┴┼╭╮╯╰╴╵╶╷" }, + + // CJK Unified Ideographs + { t: "一丁丂七丄丅丆万丈三上下丌不与丏", + i: "一丁丂七丄丅丆万丈三上下丌不与丏", + s: "一丁丂七丄丅丆万丈三上下丌不与丏" }, + + // CJK Unified Ideographs Extension B (astral) + { t: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏", + i: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏", + s: "𠀀𠀁𠀂𠀃𠀄𠀅𠀆𠀇𠀈𠀉𠀊𠀋𠀌𠀍𠀎𠀏" }, + + // Devanagari + { t: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह", + i: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह", + s: "कखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसह" }, + + // Emoticons (astral) + { t: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐", + i: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐", + s: "😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐" }, + + // Greek + { t: "αβγδεζηθικλμνξοπρςστυφχψω", + i: "αβγδεζηθικλμνξοπρςστυφχψω", + s: "αβγδεζηθικλμνξοπρςστυφχψω" } +]; + +const npatterns = patterns.length; +const nsubstitutions = substitutions.length; + +function quotemeta(str) { + return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +} +function subst(str, sub) { + return str.replace("<t>", sub.t) + .replace("<i>", sub.i) + .replace("<s>", sub.s); +} + +var curpat = 0; +var cursubst = -1; +var testbench = document.getElementById("testbench"); + +function nextTest() { + cursubst++; + if (cursubst == nsubstitutions) { + curpat++; + cursubst = 0; + } + if (curpat == npatterns) { + SimpleTest.finish(); + return; + } + + let css = subst(patterns[curpat].i, substitutions[cursubst]); + let msg = quotemeta(subst(patterns[curpat].o, substitutions[cursubst])); + + SimpleTest.expectConsoleMessages(function () { testbench.innerHTML = css }, + [{ errorMessage: new RegExp(msg) }], + nextTest); +} + +SimpleTest.waitForExplicitFinish(); +nextTest(); +</script> +</body> +</html> diff --git a/layout/style/test/test_pixel_lengths.html b/layout/style/test/test_pixel_lengths.html new file mode 100644 index 000000000..37f9ec83f --- /dev/null +++ b/layout/style/test/test_pixel_lengths.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that pixel lengths don't change based on DPI</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<div id="display"> + +<div id="pt" style="width:90pt; height:90pt; background:lime;">pt</div> +<div id="pc" style="width:5pc; height:5pc; background:yellow;">pc</div> +<div id="mm" style="width:25.4mm; height:25.4mm; background:orange;">mm</div> +<div id="cm" style="width:2.54cm; height:2.54cm; background:purple;">cm</div> +<div id="in" style="width:1in; height:1in; background:magenta;">in</div> +<div id="q" style="width:101.6q; height:101.6q; background:blue;">q</div> + +<div id="mozmm" style="width:25.4mozmm; height:25.4mozmm; background:cyan;">mozmm</div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var oldDPI = SpecialPowers.getIntPref("layout.css.dpi"); +var dpi = oldDPI; + +function check(id, val) { + var e = document.getElementById(id); + is(Math.round(e.getBoundingClientRect().width), Math.round(val), + "Checking width in " + id + " at " + dpi + " DPI"); + is(Math.round(e.getBoundingClientRect().height), Math.round(val), + "Checking height in " + id + " at " + dpi + " DPI"); +} + +function checkPixelRelativeUnits() { + check("pt", 120); + check("pc", 80); + check("mm", 96); + check("cm", 96); + check("in", 96); + check("q", 96); +} + +checkPixelRelativeUnits(); + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv({'set': [['layout.css.dpi', dpi=96]]}, test1); + +var mozmmSize; +function test1() { + var mozmm = document.getElementById("mozmm"); + mozmmSize = mozmm.getBoundingClientRect().width; + is(Math.round(mozmmSize), Math.round(mozmm.getBoundingClientRect().height), + "mozmm div should be square"); + + checkPixelRelativeUnits(); + + SpecialPowers.pushPrefEnv({'set': [['layout.css.dpi', dpi=192]]}, test2); +} + +function test2() { + // At 192 dpi, a one-inch box should be twice the number of device pixels, + // and since we haven't changed the device-pixels-per-CSS-pixel ratio, the + // mozmm box should be twice the size in CSS pixels. + check("mozmm", mozmmSize*2); + checkPixelRelativeUnits(); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_pointer-events.html b/layout/style/test/test_pointer-events.html new file mode 100644 index 000000000..73db14351 --- /dev/null +++ b/layout/style/test/test_pointer-events.html @@ -0,0 +1,114 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for pointer-events in HTML</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + div { height: 10px; width: 10px; background: black; } + + </style> +</head> +<!-- need a set timeout because we need things to start after painting suppression ends --> +<body onload="setTimeout(run_test, 0)"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<div id="display" style="position: absolute; top: 0; left: 0; width: 300px; height: 300px"> + + <div id="one"></div> + <div id="two" style="pointer-events: visiblePainted;"></div> + <div id="three" style="height: 20px; pointer-events: none;"> + <div id="four"style="margin-top: 10px;"></div> + </div> + <a id="five" style="pointer-events: none;" href="http://mozilla.org/">link</a> + <input id="six" style="pointer-events: none;" type="button" value="button" /> + <table> + <tr style="pointer-events: none;"> + <td id="seven">no</td> + <td id="eight" style="pointer-events: visiblePainted;">yes</td> + <td id="nine" style="pointer-events: auto;">yes</td> + </td> + <tr style="opacity: 0.5; pointer-events: none;"> + <td id="ten">no</td> + <td id="eleven" style="pointer-events: visiblePainted;">yes</td> + <td id="twelve" style="pointer-events: auto;">yes</td> + </td> + </table> + <iframe id="thirteen" style="pointer-events: none;" src="about:blank" width="100" height="100"></iframe> + <script type="application/javascript"> + var iframe = document.getElementById("thirteen"); + iframe.contentDocument.open(); + iframe.contentDocument.writeln("<script type='application/javascript'>"); + iframe.contentDocument.writeln("document.addEventListener('mousedown', fail, false);"); + iframe.contentDocument.writeln("function fail() { parent.ok(false, 'thirteen: iframe content must not get pointer events with explicit none') }"); + iframe.contentDocument.writeln("<"+"/script>"); + iframe.contentDocument.close(); + </script> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.8"> + +SimpleTest.expectAssertions(0, 1); + +SimpleTest.waitForExplicitFinish(); + +function catches_pointer_events(element_id) +{ + // we just assume the element is on top here. + var element = document.getElementById(element_id); + var bounds = element.getBoundingClientRect(); + var point = { x: bounds.left + bounds.width/2, y: bounds.top + bounds.height/2 }; + return element == document.elementFromPoint(point.x, point.y); +} + +function synthesizeMouseEvent(type, // string + x, // float + y, // float + button, // long + clickCount, // long + modifiers, // long + ignoreWindowBounds) // boolean +{ + var utils = SpecialPowers.getDOMWindowUtils(window); + utils.sendMouseEvent(type, x, y, button, clickCount, + modifiers, ignoreWindowBounds); +} + +function run_test() +{ + ok(catches_pointer_events("one"), "one: div should default to catching pointer events"); + ok(catches_pointer_events("two"), "two: div should catch pointer events with explicit visiblePainted"); + ok(!catches_pointer_events("three"), "three: div should not catch pointer events with explicit none"); + ok(!catches_pointer_events("four"), "four: div should not catch pointer events with inherited none"); + ok(!catches_pointer_events("five"), "five: link should not catch pointer events with explicit none"); + ok(!catches_pointer_events("six"), "six: native-themed form control should not catch pointer events with explicit none"); + ok(!catches_pointer_events("seven"), "seven: td should not catch pointer events with inherited none"); + ok(catches_pointer_events("eight"), "eight: td should catch pointer events with explicit visiblePainted overriding inherited none"); + ok(catches_pointer_events("nine"), "nine: td should catch pointer events with explicit auto overriding inherited none"); + ok(!catches_pointer_events("ten"), "ten: td should not catch pointer events with inherited none"); + ok(catches_pointer_events("eleven"), "eleven: td should catch pointer events with explicit visiblePainted overriding inherited none"); + ok(catches_pointer_events("twelve"), "twelve: td should catch pointer events with explicit auto overriding inherited none"); + + // elementFromPoint can't be used for iframe + var iframe = document.getElementById("thirteen"); + iframe.parentNode.addEventListener('mousedown', handleIFrameClick, false); + var bounds = iframe.getBoundingClientRect(); + var x = bounds.left + bounds.width/2; + var y = bounds.top + bounds.height/2; + synthesizeMouseEvent('mousedown', x, y, 0, 1, 0, true); +} + +function handleIFrameClick() +{ + ok(true, "thirteen: iframe content must not get pointer events with explicit none"); + + document.getElementById("display").style.display = "none"; + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_position_float_display.html b/layout/style/test/test_position_float_display.html new file mode 100644 index 000000000..03d3eb26b --- /dev/null +++ b/layout/style/test/test_position_float_display.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1038929 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1038929</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1038929">Mozilla Bug 1038929</a> +<p id="display"></p> +<div id="content" style="display: none"> + <div id="float-left" style="float: left"></div> + <div id="float-right" style="float: right"></div> + <div id="position-absolute" style="position: absolute"></div> + <div id="position-fixed" style="position: fixed"></div> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 1038929: Test that "display" on a floated or absolutely/fixed + position node is correctly converted to a block display as given in the table + in CSS 2.1 9.7. */ + +// Maps from display value to expected conversion when floated/positioned +// This loosely follows the spec in CSS 2.1 section 9.7. Except for "other" +// values which the spec says should be "same as specified." For these, we do +// whatever the spec for the value itself says. +var mapping = { + "inline": "block", + "table-row-group": "block", + "table-column": "block", + "table-column-group": "block", + "table-header-group": "block", + "table-footer-group": "block", + "table-row": "block", + "table-cell": "block", + "table-caption": "block", + "inline-block": "block", + "ruby": "block", + "ruby-base": "block", + "ruby-base-container": "block", + "ruby-text": "block", + "ruby-text-container": "block", + "flex": "flex", + "grid": "grid", + "none": "none", + "table": "table", + "inline-grid": "grid", + "inline-flex": "flex", + "inline-table": "table", + "block": "block", + "contents": "contents", + // Note: this is sometimes block + "list-item": "list-item" +}; + +function test_display_value(val) +{ + var floatLeftElem = document.getElementById("float-left"); + floatLeftElem.style.display = val; + var floatLeftConversion = window.getComputedStyle(floatLeftElem, null).display; + floatLeftElem.style.display = ""; + + var floatRightElem = document.getElementById("float-right"); + floatRightElem.style.display = val; + var floatRightConversion = window.getComputedStyle(floatRightElem, null).display; + floatRightElem.style.display = ""; + + var posAbsoluteElem = document.getElementById("position-absolute"); + posAbsoluteElem.style.display = val; + var posAbsoluteConversion = window.getComputedStyle(posAbsoluteElem, null).display; + posAbsoluteElem.style.display = ""; + + var posFixedElem = document.getElementById("position-fixed"); + posFixedElem.style.display = val; + var posFixedConversion = window.getComputedStyle(posFixedElem, null).display; + posFixedElem.style.display = ""; + + if (mapping[val]) { + is(floatLeftConversion, mapping[val], + "Element display should be converted when floated left"); + is(floatRightConversion, mapping[val], + "Element display should be converted when floated right"); + is(posAbsoluteConversion, mapping[val], + "Element display should be converted when absolutely positioned"); + is(posFixedConversion, mapping[val], + "Element display should be converted when fixed positioned"); + } else { + ok(false, "missing rules for display value " + val); + } +} + +var displayInfo = gCSSProperties["display"]; +displayInfo.initial_values.forEach(test_display_value); +displayInfo.other_values.forEach(test_display_value); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_position_sticky.html b/layout/style/test/test_position_sticky.html new file mode 100644 index 000000000..9deb92333 --- /dev/null +++ b/layout/style/test/test_position_sticky.html @@ -0,0 +1,89 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=886646 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 886646</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + #scroller { + width: 100px; + height: 100px; + padding: 10px; + border: 10px solid black; + margin: 10px; + overflow: hidden; + } + #container { + width: 50px; + height: 50px; + } + #sticky { + position: sticky; + width: 10px; + height: 10px; + overflow: hidden; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=886646">Mozilla Bug 886646</a> +<div id="display"> + <div id="scroller"> + <div id="container"> + <div id="sticky"></div> + </div> + </div> +</div> +<pre id="test"> +<script type="application/javascript"> + + +/** Test for Bug 886646 - Offsets for sticky positioning, when accessed through + * getComputedStyle(), should be accurately computed. In particular, + * percentage offsets should be computed in terms of the scroll container's + * content box. */ + +// Test that percentage sticky offsets are computed in terms of the +// scroll container's content box +var offsets = { + "top": 10, + "left": 20, + "bottom": 30, + "right": 40, +}; + +var scroller = document.getElementById("scroller"); +var container = document.getElementById("container"); +var sticky = document.getElementById("sticky"); +var cs = getComputedStyle(sticky, ""); + +for (var prop in offsets) { + sticky.style[prop] = offsets[prop] + "%"; + is(cs[prop], offsets[prop] + "px"); +} + +// ... even in the presence of scrollbars +scroller.style.overflow = "scroll"; +container.style.width = "100%"; +container.style.height = "100%"; + +var ccs = getComputedStyle(container, ""); + +function isApproximatelyEqual(a, b) { + return Math.abs(a - b) < 0.001; +} + +for (var prop in offsets) { + sticky.style[prop] = offsets[prop] + "%"; + var basis = parseFloat(ccs[prop == "left" || prop == "right" ? + "width" : "height"]) / 100; + ok(isApproximatelyEqual(parseFloat(cs[prop]), offsets[prop] * basis)); +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_priority_preservation.html b/layout/style/test/test_priority_preservation.html new file mode 100644 index 000000000..080a4651c --- /dev/null +++ b/layout/style/test/test_priority_preservation.html @@ -0,0 +1,141 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for property priority preservation</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** + * Test that priorities are preserved correctly when setProperty is + * called, and during declaration block expansion/compression when other + * properties are manipulated. + */ + +var div = document.getElementById("content"); +var s = div.style; + +s.setProperty("text-decoration", "underline", ""); +is(s.getPropertyValue("text-decoration"), "underline", + "text-decoration stored"); +is(s.getPropertyPriority("text-decoration"), "", + "text-decoration priority stored"); +s.setProperty("z-index", "7", "important"); +is(s.getPropertyValue("z-index"), "7", + "z-index stored"); +is(s.getPropertyPriority("z-index"), "important", + "z-index priority stored"); +s.setProperty("z-index", "3", ""); +is(s.getPropertyValue("z-index"), "3", + "z-index overridden by setting non-important"); +is(s.getPropertyPriority("z-index"), "", + "z-index priority overridden by setting non-important"); +is(s.getPropertyValue("text-decoration"), "underline", + "text-decoration still stored"); +is(s.getPropertyPriority("text-decoration"), "", + "text-decoration priority still stored"); +s.setProperty("text-decoration", "overline", ""); +is(s.getPropertyValue("text-decoration"), "overline", + "text-decoration stored"); +is(s.getPropertyPriority("text-decoration"), "", + "text-decoration priority stored"); +is(s.getPropertyValue("z-index"), "3", + "z-index still stored"); +is(s.getPropertyPriority("z-index"), "", + "z-index priority still stored"); +s.setProperty("text-decoration", "line-through", "important"); +is(s.getPropertyValue("text-decoration"), "line-through", + "text-decoration stored at new priority"); +is(s.getPropertyPriority("text-decoration"), "important", + "text-decoration priority overridden"); +is(s.getPropertyValue("z-index"), "3", + "z-index still stored"); +is(s.getPropertyPriority("z-index"), "", + "z-index priority still stored"); + + // also test setting a shorthand +s.setProperty("font", "italic bold 12px/30px serif", "important"); +is(s.getPropertyValue("font-style"), "italic", "font-style stored"); +is(s.getPropertyPriority("font-style"), "important", + "font-style priority stored"); +is(s.getPropertyValue("font-weight"), "bold", "font-weight stored"); +is(s.getPropertyPriority("font-weight"), "important", + "font-weight priority stored"); +is(s.getPropertyValue("font-size"), "12px", "font-size stored"); +is(s.getPropertyPriority("font-size"), "important", + "font-size priority stored"); +is(s.getPropertyValue("line-height"), "30px", "line-height stored"); +is(s.getPropertyPriority("line-height"), "important", + "line-height priority stored"); +is(s.getPropertyValue("font-family"), "serif", "font-family stored"); +is(s.getPropertyPriority("font-family"), "important", + "font-family priority stored"); + +is(s.getPropertyValue("text-decoration"), "line-through", + "text-decoration still stored"); +is(s.getPropertyPriority("text-decoration"), "important", + "text-decoration priority still stored"); +is(s.getPropertyValue("z-index"), "3", + "z-index still stored"); +is(s.getPropertyPriority("z-index"), "", + "z-index priority still stored"); + + // and overriding one element of that shorthand with some longhand + // test omitting the third argument to setProperty too (bug 655478) +s.setProperty("font-style", "normal"); + +is(s.getPropertyValue("font-style"), "normal", "font-style overridden"); +is(s.getPropertyPriority("font-style"), "", "font-style priority overridden"); + +is(s.getPropertyValue("font-weight"), "bold", "font-weight unchanged"); +is(s.getPropertyPriority("font-weight"), "important", + "font-weight priority unchanged"); +is(s.getPropertyValue("font-size"), "12px", "font-size unchanged"); +is(s.getPropertyPriority("font-size"), "important", + "font-size priority unchanged"); +is(s.getPropertyValue("line-height"), "30px", "line-height unchanged"); +is(s.getPropertyPriority("line-height"), "important", + "line-height priority unchanged"); +is(s.getPropertyValue("font-family"), "serif", "font-family unchanged"); +is(s.getPropertyPriority("font-family"), "important", + "font-family priority unchanged"); + +is(s.getPropertyValue("text-decoration"), "line-through", + "text-decoration still stored"); +is(s.getPropertyPriority("text-decoration"), "important", + "text-decoration priority still stored"); +is(s.getPropertyValue("z-index"), "3", + "z-index still stored"); +is(s.getPropertyPriority("z-index"), "", + "z-index priority still stored"); + +s.setProperty("border-radius", "2em", ""); +is(s.getPropertyValue("border-radius"), "2em", + "border-radius serialization 1") + +s.setProperty("border-top-left-radius", "3em 4em", ""); +is(s.getPropertyValue("border-radius"), + "3em 2em 2em / 4em 2em 2em", + "border-radius serialization 2"); + +s.setProperty("border-radius", "2em / 3em", ""); +is(s.getPropertyValue("border-radius"), + "2em / 3em", + "border-radius serialization 3") + +s.setProperty("border-top-left-radius", "4em", ""); +is(s.getPropertyValue("border-radius"), + "4em 2em 2em / 4em 3em 3em", + "border-radius serialization 3"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_property_database.html b/layout/style/test/test_property_database.html new file mode 100644 index 000000000..30e6618fc --- /dev/null +++ b/layout/style/test/test_property_database.html @@ -0,0 +1,170 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test that property_database.js contains all supported CSS properties</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="css_properties.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +<div id="testnode"></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test that property_database.js contains all supported CSS properties **/ + +/* + * Here we are testing the hand-written property_database.js against + * the autogenerated css_properties.js to make sure that everything in + * css_properties.js is in property_database.js. + * + * This prevents CSS properties from being added to the code without + * also being put under the minimal test coverage provided by the tests + * that use property_database.js. + */ + +for (var idx in gLonghandProperties) { + var prop = gLonghandProperties[idx]; + if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) { + continue; + } + var present = prop.name in gCSSProperties; + ok(present, + "'" + prop.name + "' listed in gCSSProperties"); + if (present) { + is(gCSSProperties[prop.name].type, CSS_TYPE_LONGHAND, + "'" + prop.name + "' listed as CSS_TYPE_LONGHAND"); + is(gCSSProperties[prop.name].domProp, prop.prop, + "'" + prop.name + "' listed with correct DOM property name"); + } +} +for (var idx in gShorthandProperties) { + var prop = gShorthandProperties[idx]; + if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) { + continue; + } + if (prop.name == "all") { + // "all" isn't listed in property_database.js. + continue; + } + var present = prop.name in gCSSProperties; + ok(present, + "'" + prop.name + "' listed in gCSSProperties"); + if (present) { + ok(gCSSProperties[prop.name].type == CSS_TYPE_TRUE_SHORTHAND || + gCSSProperties[prop.name].type == CSS_TYPE_SHORTHAND_AND_LONGHAND, + "'" + prop.name + "' listed as CSS_TYPE_TRUE_SHORTHAND or CSS_TYPE_SHORTHAND_AND_LONGHAND"); + ok(gCSSProperties[prop.name].domProp == prop.prop, + "'" + prop.name + "' listed with correct DOM property name"); + } +} +for (var idx in gShorthandPropertiesLikeLonghand) { + var prop = gShorthandPropertiesLikeLonghand[idx]; + if (prop.pref && !IsCSSPropertyPrefEnabled(prop.pref)) { + continue; + } + var present = prop.name in gCSSProperties; + ok(present, + "'" + prop.name + "' listed in gCSSProperties"); + if (present) { + ok(gCSSProperties[prop.name].type == CSS_TYPE_SHORTHAND_AND_LONGHAND, + "'" + prop.name + "' listed as CSS_TYPE_SHORTHAND_AND_LONGHAND"); + ok(gCSSProperties[prop.name].domProp == prop.prop, + "'" + prop.name + "' listed with correct DOM property name"); + } +} + +/* + * Test that all shorthand properties have a subproperty list and all + * longhand properties do not. + */ +for (var prop in gCSSProperties) { + var entry = gCSSProperties[prop]; + if (entry.pref && !IsCSSPropertyPrefEnabled(entry.pref)) { + continue; + } + if (entry.type == CSS_TYPE_LONGHAND) { + ok(!("subproperties" in entry), + "longhand property '" + prop + "' must not have subproperty list"); + } else if (entry.type == CSS_TYPE_TRUE_SHORTHAND || + entry.type == CSS_TYPE_SHORTHAND_AND_LONGHAND) { + ok("subproperties" in entry, + "shorthand property '" + prop + "' must have subproperty list"); + } + + if ("subproperties" in entry) { + var good = true; + if (entry.subproperties.length < 1) { + info("subproperty list for '" + prop + "' is empty"); + good = false; + } + for (var idx in entry.subproperties) { + var subprop = entry.subproperties[idx]; + if (!(subprop in gCSSProperties)) { + info("subproperty list for '" + prop + "' lists nonexistent subproperty '" + subprop + "'"); + good = false; + } + } + ok(good, "property '" + prop + "' has a good subproperty list"); + } + + ok("initial_values" in entry && entry.initial_values.length >= 1, + "must have initial values for property '" + prop + "'"); + ok("other_values" in entry && entry.other_values.length >= 1, + "must have non-initial values for property '" + prop + "'"); +} + +/* + * Test that only longhand properties are listed as logical properties. + */ +for (var prop in gCSSProperties) { + var entry = gCSSProperties[prop]; + if (entry.logical) { + is(entry.type, CSS_TYPE_LONGHAND, + "property '" + prop + "' is listed as CSS_TYPE_LONGHAND due to its " + + "being a logical property"); + } +} + +/* + * Test that axis is only specified for logical properties. + */ +for (var prop in gCSSProperties) { + var entry = gCSSProperties[prop]; + if (entry.axis) { + ok(entry.logical, + "property '" + prop + "' is listed as an logical property due to its " + + "being listed as an axis-related property"); + } +} + +/* + * Test that DOM properties match the expected rules. + */ +for (var prop in gCSSProperties) { + var entry = gCSSProperties[prop]; + var expectedDOMProp = prop.replace(/-([a-z])/g, + function(m, p1, offset, str) { + return p1.toUpperCase(); + }); + if (expectedDOMProp == "float") { + expectedDOMProp = "cssFloat"; + } else if (prop.startsWith("-webkit")) { + // Our DOM accessors for webkit-prefixed properties start with lowercase w, + // not uppercase like standard DOM accessors. + expectedDOMProp = expectedDOMProp.replace(/^W/, "w"); + } + is(entry.domProp, expectedDOMProp, "DOM property for " + prop); +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_property_syntax_errors.html b/layout/style/test/test_property_syntax_errors.html new file mode 100644 index 000000000..be1127def --- /dev/null +++ b/layout/style/test/test_property_syntax_errors.html @@ -0,0 +1,153 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test that we reject syntax errors listed in property_database.js</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<p id="display"></p> +<iframe id="quirks" src="data:text/html,<div id='testnode'></div>"></iframe> +<div id="content" style="display: none"> + +<div id="testnode"></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.requestLongerTimeout(2); +SimpleTest.waitForExplicitFinish(); + +function check_not_accepted(decl, property, info, badval) +{ + decl.setProperty(property, badval, ""); + + is(decl.getPropertyValue(property), "", + "invalid value '" + badval + "' not accepted for '" + property + + "' property"); + + if ("subproperties" in info) { + for (var sidx in info.subproperties) { + var subprop = info.subproperties[sidx]; + is(decl.getPropertyValue(subprop), "", + "invalid value '" + badval + "' not accepted for '" + property + + "' property when testing subproperty '" + subprop + "'"); + } + } + + decl.removeProperty(property); +} + +function check_value_balanced(decl, property, badval) +{ + var goodProp = + (property == "background-color") ? "color" : "background-color"; + decl.cssText = goodProp + ": red; " + property + ": " + badval + "; " + + goodProp + ": green"; + is(decl.getPropertyValue(goodProp), "green", + "invalid value '" + property + ": " + badval + + "' is balanced and does not lead to parsing errors afterwards"); + decl.cssText = ""; +} + +function check_value_unbalanced(decl, property, badval) +{ + var goodProp = + (property == "background-color") ? "color" : "background-color"; + decl.cssText = goodProp + ": green; " + property + ": " + badval + "; " + + goodProp + ": red"; + is(decl.getPropertyValue(goodProp), "green", + "invalid value '" + property + ": " + badval + + "' is unbalanced and absorbs what follows it"); + decl.cssText = ""; +} + +function check_empty_value_rejected(decl, emptyval, property) +{ + var goodProp = + (property == "background-color") ? "color" : "background-color"; + decl.cssText = goodProp + ": red; " + property + ":" + emptyval + "; " + + goodProp + ": green"; + is(decl.length, 1, + "empty value '" + property + ":" + emptyval + + "' is not accepted"); + is(decl.getPropertyValue(goodProp), "green", + "empty value '" + property + ":" + emptyval + + "' is balanced and does not lead to parsing errors afterwards"); + decl.cssText = ""; +} + +function run() +{ + var gDeclaration = document.getElementById("testnode").style; + var gQuirksDeclaration = document.getElementById("quirks").contentDocument + .getElementById("testnode").style; + + for (var property in gCSSProperties) { + var info = gCSSProperties[property]; + + check_empty_value_rejected(gDeclaration, "", property); + check_empty_value_rejected(gDeclaration, " ", property); + + for (var idx in info.invalid_values) { + check_not_accepted(gDeclaration, property, info, + info.invalid_values[idx]); + check_not_accepted(gQuirksDeclaration, property, info, + info.invalid_values[idx]); + check_value_balanced(gDeclaration, property, + info.invalid_values[idx]); + } + + if ("quirks_values" in info) { + for (var quirkval in info.quirks_values) { + var standardval = info.quirks_values[quirkval]; + check_not_accepted(gDeclaration, property, info, quirkval); + check_value_balanced(gDeclaration, property, quirkval); + + gQuirksDeclaration.setProperty(property, quirkval, ""); + gDeclaration.setProperty(property, standardval, ""); + var quirkret = gQuirksDeclaration.getPropertyValue(property); + var standardret = gDeclaration.getPropertyValue(property); + isnot(quirkret, "", property + ": " + quirkval + + " should be accepted in quirks mode"); + is(quirkret, standardret, property + ": " + quirkval + " result"); + + if ("subproperties" in info) { + for (var sidx in info.subproperties) { + var subprop = info.subproperties[sidx]; + var quirksub = gQuirksDeclaration.getPropertyValue(subprop); + var standardsub = gDeclaration.getPropertyValue(subprop); + isnot(quirksub, "", property + ": " + quirkval + + " should be accepted in quirks mode" + + " when testing subproperty " + subprop); + is(quirksub, standardsub, property + ": " + quirkval + " result" + + " when testing subproperty " + subprop); + } + } + + gQuirksDeclaration.removeProperty(property); + gDeclaration.removeProperty(property); + } + } + + for (var idx in info.unbalanced_values) { + check_not_accepted(gDeclaration, property, info, + info.invalid_values[idx]); + check_not_accepted(gQuirksDeclaration, property, info, + info.invalid_values[idx]); + check_value_unbalanced(gDeclaration, property, + info.unbalanced_values[idx]); + } + } + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_pseudoelement_parsing.html b/layout/style/test/test_pseudoelement_parsing.html new file mode 100644 index 000000000..b6fcf783f --- /dev/null +++ b/layout/style/test/test_pseudoelement_parsing.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>Test for Bug 922669</title> +<script src="/MochiKit/MochiKit.js"></script> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + +<style></style> + +<script> +var style = document.querySelector("style"); + +var gValidTests = [ + "::-moz-progress-bar", + "::-moz-progress-bar:hover", + "::-moz-progress-bar:active", + "::-moz-progress-bar:focus", + "::-moz-progress-bar:hover:focus", + "#a::-moz-progress-bar:hover", + ":hover::-moz-progress-bar" +]; + +var gInvalidTests = [ + "::foo", + "::-moz-progress-bar::-moz-progress-bar", + "::-moz-progress-bar::first-line", + "::-moz-progress-bar#a", + "::-moz-progress-bar:invalid", + "::-moz-focus-inner:active" +]; + +gValidTests.forEach(function(aTest) { + style.textContent = aTest + "{}"; + is(style.sheet.cssRules.length, 1, aTest); + style.textContent = ""; +}); + +gInvalidTests.forEach(function(aTest) { + style.textContent = aTest + "{}"; + is(style.sheet.cssRules.length, 0, aTest); + style.textContent = ""; +}); +</script> diff --git a/layout/style/test/test_pseudoelement_state.html b/layout/style/test/test_pseudoelement_state.html new file mode 100644 index 000000000..ad4bf5242 --- /dev/null +++ b/layout/style/test/test_pseudoelement_state.html @@ -0,0 +1,164 @@ +<!DOCTYPE html> +<title>Test for Bug 922669</title> +<script src="/MochiKit/MochiKit.js"></script> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + +<iframe src="data:text/html,<!DOCTYPE html><style></style><div></div>"></iframe> + +<script> +var gIsAndroid = navigator.appVersion.indexOf("Android") != -1; + +var gTests = [ + // Interact with the ::-moz-progress-bar. + { markup: '<progress value="75" max="100"></progress>', + pseudoelement: '::-moz-progress-bar', + common_style: 'progress { -moz-appearance: none; } progress::-moz-progress-bar { background: black; }', + hover_test_style: 'progress::-moz-progress-bar:hover { background: green; }', + hover_reference_style: 'progress::-moz-progress-bar { background: green; }', + active_test_style: 'progress::-moz-progress-bar:active { background: lime; }', + active_reference_style: 'progress::-moz-progress-bar { background: lime; }' }, + + // Interact with the part of the <progress> not covered by the ::-moz-progress-bar. + { markup: '<progress value="25" max="100"></progress>', + pseudoelement: '::-moz-progress-bar', + common_style: 'progress { -moz-appearance: none; } progress::-moz-progress-bar { background: black; }', + hover_test_style: 'progress::-moz-progress-bar { background: green; } progress::-moz-progress-bar:hover { background: red; }', + hover_reference_style: 'progress::-moz-progress-bar { background: green; }', + active_test_style: 'progress::-moz-progress-bar { background: lime; } progress::-moz-progress-bar:active { background: red; }', + active_reference_style: 'progress::-moz-progress-bar { background: lime; }' }, + + // Interact with the ::-moz-range-thumb. + { markup: '<input type="range" value="50" min="0" max="100">', + pseudoelement: '::-moz-range-thumb', + common_style: 'input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }', + hover_test_style: 'input::-moz-range-thumb:hover { background: green; }', + hover_reference_style: 'input::-moz-range-thumb { background: green; }', + active_test_style: 'input::-moz-range-thumb:active { background: lime; }', + active_reference_style: 'input::-moz-range-thumb { background: lime; }' }, + + // Interact with the part of the <input type="range"> not covered by the ::-moz-range-thumb. + { markup: '<input type="range" value="25" min="0" max="100">', + pseudoelement: '::-moz-range-thumb', + common_style: 'input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }', + hover_test_style: 'input::-moz-range-thumb { background: green; } input::-moz-range-thumb:hover { background: red; }', + hover_reference_style: 'input::-moz-range-thumb { background: green; }', + active_test_style: 'input::-moz-range-thumb { background: lime; } input::-moz-range-thumb:active { background: red; }', + active_reference_style: 'input::-moz-range-thumb { background: lime; }' }, + + // Interact with the ::-moz-meter-bar. + { markup: '<meter value="75" min="0" max="100"></meter>', + pseudoelement: '::-moz-meter-bar', + common_style: 'meter { -moz-appearance: none; } meter::-moz-meter-bar { background: black; }', + hover_test_style: 'meter::-moz-meter-bar:hover { background: green; }', + hover_reference_style: 'meter::-moz-meter-bar { background: green; }', + active_test_style: 'meter::-moz-meter-bar:active { background: lime; }', + active_reference_style: 'meter::-moz-meter-bar { background: lime; }' }, + + // Interact with the part of the <meter> not covered by the ::-moz-meter-bar. + { markup: '<meter value="25" min="0" max="100"></meter>', + pseudoelement: '::-moz-meter-bar', + common_style: 'meter { -moz-appearance: none; } meter::-moz-meter-bar { background: black; }', + hover_test_style: 'meter::-moz-meter-bar { background: green; } meter::-moz-meter-bar:hover { background: red; }', + hover_reference_style: 'meter::-moz-meter-bar { background: green; }', + active_test_style: 'meter::-moz-meter-bar { background: lime; } meter::-moz-meter-bar:active { background: red; }', + active_reference_style: 'meter::-moz-meter-bar { background: lime; }' }, + + // Do the same as the "Interact with the ::-moz-range-thumb" subtest above, + // but with selectors that include descendant operators. + { markup: '<input type="range" value="50" min="0" max="100">', + pseudoelement: '::-moz-range-thumb', + common_style: 'body input { -moz-appearance: none; } input::-moz-range-thumb { background: black; }', + hover_test_style: 'body input::-moz-range-thumb:hover { background: green; }', + hover_reference_style: 'body input::-moz-range-thumb { background: green; }', + active_test_style: 'body input::-moz-range-thumb:active { background: lime; }', + active_reference_style: 'body input::-moz-range-thumb { background: lime; }' }, + + // ::placeholder can't be tested, since the UA style sheet sets it to + // be pointer-events:none. +]; + +function countPixelDifferences(aCanvas1, aCanvas2) { + var ctx1 = aCanvas1.getContext("2d"); + var ctx2 = aCanvas2.getContext("2d"); + var data1 = ctx1.getImageData(0, 0, aCanvas1.width, aCanvas1.height); + var data2 = ctx2.getImageData(0, 0, aCanvas2.width, aCanvas2.height); + var n = 0; + for (var i = 0; i < data1.width * data2.height * 4; i += 4) { + if (data1.data[i] != data2.data[i] || + data1.data[i + 1] != data2.data[i + 1] || + data1.data[i + 2] != data2.data[i + 2] || + data1.data[i + 3] != data2.data[i + 3]) { + n++; + } + } + return n; +} + +function runTests() { + var iframe = document.querySelector("iframe"); + var style = iframe.contentDocument.querySelector("style"); + var div = iframe.contentDocument.querySelector("div"); + var canvas1, canvas2; + + function runTestPart1(aIndex) { + var test = gTests[aIndex]; + div.innerHTML = test.markup; + style.textContent = test.common_style; + is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 0, 0)", "subtest #" + aIndex + ", computed style"); + style.textContent += test.hover_test_style; + synthesizeMouseAtCenter(div.lastChild, { type: 'mouseover' }, iframe.contentWindow); + } + + function runTestPart2(aIndex) { + var test = gTests[aIndex]; + canvas1 = SpecialPowers.snapshotWindow(iframe.contentWindow, false); + style.textContent = test.common_style + test.hover_reference_style; + } + + function runTestPart3(aIndex) { + var test = gTests[aIndex]; + canvas2 = SpecialPowers.snapshotWindow(iframe.contentWindow, false); + ok(canvas1.width == canvas2.width && canvas1.height == canvas2.height, "hover subtest #" + aIndex + ", canvas sizes equal"); + is(countPixelDifferences(canvas1, canvas2), 0, "hover subtest #" + aIndex + ", number of different pixels"); + is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 128, 0)", "hover subtest #" + aIndex + ", computed style"); + + if (!gIsAndroid) { + style.textContent = test.common_style + test.active_test_style; + synthesizeMouseAtCenter(div.lastChild, { type: 'mousedown' }, iframe.contentWindow); + } + } + + function runTestPart4(aIndex) { + if (!gIsAndroid) { + var test = gTests[aIndex]; + canvas1 = SpecialPowers.snapshotWindow(iframe.contentWindow, false); + synthesizeMouseAtCenter(div.lastChild, { type: 'mouseup' }, iframe.contentWindow); + style.textContent = test.common_style + test.active_reference_style; + } + } + + function runTestPart5(aIndex) { + if (!gIsAndroid) { + var test = gTests[aIndex]; + canvas2 = SpecialPowers.snapshotWindow(iframe.contentWindow, false); + ok(canvas1.width == canvas2.width && canvas1.height == canvas2.height, "active subtest #" + aIndex + ", canvas sizes equal"); + is(countPixelDifferences(canvas1, canvas2), 0, "active subtest #" + aIndex + ", number of different pixels"); + is(getComputedStyle(div.firstChild, test.pseudoelement).backgroundColor, "rgb(0, 255, 0)", "active subtest #" + aIndex + ", computed style"); + } + } + + for (var i = 0; i < gTests.length; i++) { + setTimeout(runTestPart1, 0, i); + setTimeout(runTestPart2, 0, i); + setTimeout(runTestPart3, 0, i); + setTimeout(runTestPart4, 0, i); + setTimeout(runTestPart5, 0, i); + } + setTimeout(function() { SimpleTest.finish(); }, 0); +} + +SimpleTest.waitForExplicitFinish(); +window.addEventListener("load", runTests); +</script> diff --git a/layout/style/test/test_redundant_font_download.html b/layout/style/test/test_redundant_font_download.html new file mode 100644 index 000000000..8349d692b --- /dev/null +++ b/layout/style/test/test_redundant_font_download.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=879963 --> +<head> + <meta charset="utf-8"> + <title>Test for bug 879963</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> + +<body> + <!-- Two <style> elements with identical @font-face rules. + Although multiple @font-face at-rules for the same family are legal, + and add faces to the family, we should NOT download the same resource + twice just because we have a duplicate face entry. --> + <style type="text/css"> + @font-face { + font-family: foo; + src: url("redundant_font_download.sjs?q=font"); + } + .test { + font-family: foo; + } + </style> + + <style type="text/css"> + @font-face { + font-family: foo; + src: url("redundant_font_download.sjs?q=font"); + } + .test { + font-family: foo; + } + </style> + + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=879963">Mozilla Bug 879963</a> + + <div> + <!-- the 'init' request returns an image (just so we can see it's working) + and initializes the request logging in our sjs server --> + <img src="redundant_font_download.sjs?q=init"> + </div> + + <div id="test"> + Test + </div> + + <div> + <img id="image2" src=""> + </div> + + <script type="application/javascript"> + // helper to retrieve the server's request log as text + function getRequestLog() { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "redundant_font_download.sjs?q=report", false); + xmlHttp.send(null); + return xmlHttp.responseText; + } + + // retrieve just the most recent request the server has seen + function getLastRequest() { + return getRequestLog().split(";").pop(); + } + + // poll the server at intervals of 'delay' ms until it has seen a specific request, + // or until maxTime ms have passed + function waitForRequest(request, delay, maxTime, func) { + timeLimit = Date.now() + maxTime; + var intervalId = window.setInterval(function() { + var req = getLastRequest(); + if (req == request || Date.now() > timeLimit) { + window.clearInterval(intervalId); + func(); + } + }.bind(this), delay); + } + + // initially disable the second of the <style> elements, + // so we only have a single copy of the font-face + document.getElementsByTagName("style")[1].disabled = true; + + SimpleTest.waitForExplicitFinish(); + + // We perform a series of actions that trigger requests to the .sjs server, + // and poll the server's request log at each stage to check that it has + // seen the request we expected before we proceed to the next step. + function startTest() { + is(getRequestLog(), "init", "request log has been initialized"); + + // this should trigger a font download + document.getElementById("test").className = "test"; + + // wait to confirm that the server has received the request + waitForRequest("font", 10, 5000, continueTest1); + } + + function continueTest1() { + is(getRequestLog(), "init;font", "server received font request"); + + // trigger a request for the second image, to provide an explicit + // delimiter in the log before we enable the second @font-face rule + document.getElementById("image2").src = "redundant_font_download.sjs?q=image"; + + waitForRequest("image", 10, 5000, continueTest2); + } + + function continueTest2() { + is(getRequestLog(), "init;font;image", "server received image request"); + + // enable the second <style> element: we should NOT see a second font request, + // we expect waitForRequest to time out instead + document.getElementsByTagName("style")[1].disabled = false; + + waitForRequest("font", 10, 1000, continueTest3); + } + + function continueTest3() { + is(getRequestLog(), "init;font;image", "should NOT have re-requested the font"); + + SimpleTest.finish(); + } + + waitForRequest("init", 10, 5000, startTest); + + </script> + +</body> + +</html> diff --git a/layout/style/test/test_rem_unit.html b/layout/style/test/test_rem_unit.html new file mode 100644 index 000000000..8d18f9d50 --- /dev/null +++ b/layout/style/test/test_rem_unit.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=478321 +--> +<head> + <title>Test for CSS 'rem' unit</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478321">Mozilla Bug 478321</a> +<p id="display"></p> +<p id="display2"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for CSS 'rem' unit **/ + +function px_to_num(str) +{ + return Number(String(str).match(/^([\d.]+)px$/)[1]); +} + +function fs(elt) +{ + return px_to_num(getComputedStyle(elt, "").fontSize); +} + +var html = document.documentElement; +var body = document.body; +var p = document.getElementById("display"); +var p2 = document.getElementById("display2"); + +html.style.font = "initial"; + +var defaultFontSize = fs(html); + +// NOTE: This test assumes that the default font size is an +// integral number of pixels (which is always the case at present). +// If that ever becomes false, the calls to "is" may need to be replaced by +// calls to "isapprox" that allows errors of up to some small fraction +// of a pixel. + +html.style.fontSize = "3rem"; +is(fs(html), 3 * defaultFontSize, + "3rem on root should triple root's font size"); +body.style.font = "initial"; +is(fs(body), defaultFontSize, + "initial should produce initial font size"); +body.style.fontSize = "1em"; +is(fs(body), 3 * defaultFontSize, "1em should inherit from parent"); +body.style.fontSize = "200%"; +is(fs(body), 6 * defaultFontSize, "200% should double parent"); +body.style.fontSize = "2rem"; +is(fs(body), 6 * defaultFontSize, "2rem should double root"); +p.style.font = "inherit"; +is(fs(p), 6 * defaultFontSize, "inherit should inherit from parent"); +p2.style.fontSize = "2rem"; +is(fs(p2), 6 * defaultFontSize, "2rem should double root"); +body.style.font = "initial"; +is(fs(p), defaultFontSize, "inherit should inherit from parent"); +is(fs(p2), 6 * defaultFontSize, "2rem should double root"); +body.style.fontSize = "5em"; +html.style.fontSize = "200%"; +is(fs(p), 10 * defaultFontSize, "inherit should inherit from parent"); +is(fs(p2), 4 * defaultFontSize, "2rem should double root"); + + +// Make things readable again. +html.style.fontSize = "1em"; +body.style.fontSize = "1em"; + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_restyles_in_smil_animation.html b/layout/style/test/test_restyles_in_smil_animation.html new file mode 100644 index 000000000..8e18da053 --- /dev/null +++ b/layout/style/test/test_restyles_in_smil_animation.html @@ -0,0 +1,113 @@ +<!doctype html> +<head> +<meta charset=utf-8> +<title>Tests restyles in smil animation</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/SpawnTask.js"></script> +<script src="/tests/SimpleTest/paint_listener.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> + +<div id="target-div"> + <svg> + <rect id="svg-rect" width="100%" height="100%" fill="lime"/> + </svg> +</div> + +<script> +"use strict"; + +function waitForAnimationFrames(frameCount) { + return new Promise(function(resolve, reject) { + function handleFrame() { + if (--frameCount <= 0) { + resolve(); + } else { + window.requestAnimationFrame(handleFrame); // wait another frame + } + } + window.requestAnimationFrame(handleFrame); + }); +} + +function observeStyling(frameCount) { + var Ci = SpecialPowers.Ci; + var docShell = + SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + docShell.recordProfileTimelineMarkers = true; + docShell.popProfileTimelineMarkers(); + + return new Promise(function(resolve) { + return waitForAnimationFrames(frameCount).then(function() { + var markers = docShell.popProfileTimelineMarkers(); + docShell.recordProfileTimelineMarkers = false; + var stylingMarkers = markers.filter(function(marker, index) { + return marker.restyleHint == "eRestyle_SVGAttrAnimations"; + }); + resolve(stylingMarkers); + }); + }); +} + +function ensureElementRemoval(aElement) { + return new Promise(function(resolve) { + aElement.remove(); + waitForAllPaintsFlushed(resolve); + }); +} + +function waitForPaintFlushed() { + return new Promise(function(resolve) { + waitForAllPaintsFlushed(resolve); + }); +} + +SimpleTest.waitForExplicitFinish(); + +add_task(function* smil_is_in_display_none_subtree() { + yield waitForPaintFlushed(); + + var animate = + document.createElementNS("http://www.w3.org/2000/svg", "animate"); + animate.setAttribute("attributeType", "XML"); + animate.setAttribute("attributeName", "fill"); + animate.setAttribute("values", "red;lime"); + animate.setAttribute("dur", "1s"); + animate.setAttribute("repeatCount", "indefinite"); + document.getElementById("svg-rect").appendChild(animate); + + yield waitForPaintFlushed(); + + var displayMarkers = yield observeStyling(5); + // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target + // element is newly associated with an nsIFrame. + ok(displayMarkers.length == 5 || displayMarkers.length == 4, + "should restyle in most frames"); + + var div = document.getElementById("target-div"); + + div.style.display = "none"; + getComputedStyle(div).display; + yield waitForPaintFlushed(); + + var displayNoneMarkers = yield observeStyling(5); + is(displayNoneMarkers.length, 0, "should never restyle if display:none"); + + div.style.display = ""; + getComputedStyle(div).display; + yield waitForPaintFlushed(); + + var displayAgainMarkers = yield observeStyling(5); + // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target + // element is newly associated with an nsIFrame. + ok(displayMarkers.length == 5 || displayMarkers.length == 4, + "should restyle again"); + + yield ensureElementRemoval(animate); +}); +</script> +</body> diff --git a/layout/style/test/test_root_node_display.html b/layout/style/test/test_root_node_display.html new file mode 100644 index 000000000..547fb0e48 --- /dev/null +++ b/layout/style/test/test_root_node_display.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=969460 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 969460</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=969460">Mozilla Bug 969460</a> +<p id="display"></p> +<div id="content" style="display: none"> + <div id="float" style="float: left"></div> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 969460: Test that "display" on the root node is computed + using the same conversion that we use for display on floated elements **/ + +function test_display_value(val) +{ + var floatElem = document.getElementById("float"); + floatElem.style.display = val; + var floatConversion = window.getComputedStyle(floatElem, null).display; + floatElem.style.display = ""; + + var rootNode = document.documentElement; + rootNode.style.display = val; + rootNode.offsetHeight; // (Flush layout, to be sure layout can handle 'val') + var rootConversion = window.getComputedStyle(rootNode, null).display; + rootNode.style.display = ""; + + // Special case: "display:list-item" does not get modified by 'float', + // but the spec allows us to convert it to 'block' on the root node + // (and we do convert it, so that we don't have to support documents whose + // root node is a list-item). + if (val == "list-item") { + is(floatConversion, val, "'float' shouldn't affect 'display:list-item'"); + is(rootConversion, "block", + "We traditionally convert 'display:list-item' on the root node to " + + "'display:block' (though if that changes, it's not technically a bug, " + + "as long as we support it properly)."); + } else if (val == "contents") { + is(floatConversion, val, "'float' shouldn't affect 'display:contents'"); + is(rootConversion, "block", + "'display:contents' on the root node computes to block-level per" + + "http://dev.w3.org/csswg/css-display/#transformations"); + } else { + is(rootConversion, floatConversion, + "root node should make 'display:" + val + "' compute to the same " + + "value that it computes to on a floated element"); + } +} + +var displayInfo = gCSSProperties["display"]; +displayInfo.initial_values.forEach(test_display_value); +displayInfo.other_values.forEach(test_display_value); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_rule_insertion.html b/layout/style/test/test_rule_insertion.html new file mode 100644 index 000000000..a55ec08c5 --- /dev/null +++ b/layout/style/test/test_rule_insertion.html @@ -0,0 +1,240 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=816720 +--> +<head> + <title>Test for Bug 816720</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css" id="style"></style> +</head> +<body> + +<pre id="test"></pre> + +<p><span id=control-serif>........</span></p> +<p><span id=control-monospace>........</span></p> +<p><span id=test-font>........</span></p> + +<style id=other-styles> + #test { font-size: 16px; animation: test 1s both } + #control-serif { font: 16px serif } + #test-font { font: 16px UnlikelyFontName, serif } +</style> + +<p><span id=control-decimal></span></p> +<p><span id=control-cjk-decimal></span></p> +<p><span id=test-counter-style></span></p> + +<style> + #control-decimal::before { content: counter(a, decimal); } + #control-cjk-decimal::before { content: counter(a, cjk-decimal); } + #test-counter-style::before { content: counter(a, unlikely-counter-style); } +</style> + +<script type="application/javascript"> + +// Monospace fonts available on all the platforms we're testing on. +// +// XXX Once bug 817220 is fixed we could instead use the value of +// font.name.monospace.x-western as the monospace font to use. +var MONOSPACE_FONTS = [ + "Courier", + "Courier New", + "Monaco", + "DejaVu Sans Mono", + "Droid Sans Mono" +]; + +var test = document.getElementById("test"); +var controlSerif = document.getElementById("control-serif"); +var controlMonospace = document.getElementById("control-monospace"); +var testFont = document.getElementById("test-font"); +var otherStyles = document.getElementById("other-styles"); + +otherStyles.sheet.insertRule("#control-monospace { font: 16px " + + MONOSPACE_FONTS + ", serif }", 0); + +var monospaceWidth = controlMonospace.getBoundingClientRect().width; +var serifWidth = controlSerif.getBoundingClientRect().width; + +var controlDecimal = document.getElementById("control-decimal"); +var controlCJKDecimal = document.getElementById("control-cjk-decimal"); +var testCounterStyle = document.getElementById("test-counter-style"); + +var decimalWidth = controlDecimal.getBoundingClientRect().width; +var cjkDecimalWidth = controlCJKDecimal.getBoundingClientRect().width; + +// [at-rule type, passing condition, failing condition] +var outerRuleInfo = [ + ["@media", "all", "not all"], + ["@-moz-document", "url-prefix('')", "url-prefix('zzz')"], + ["@supports", "(color: green)", "(unknown: unknown)"] +]; + +// [rule, function to test whether the rule was successfully inserted and applied] +var innerRuleInfo = [ + ["#test { text-decoration: underline; }", + function(aApplied, aParent, aException) { + return !aException && + window.getComputedStyle(test, "").textDecoration == + (aApplied ? "underline" : "none"); + }], + ["@page { margin: 4cm; }", + function(aApplied, aParent, aException) { + // just test whether it threw + return !aException; + }], + ["@keyframes test { from { font-size: 100px; } to { font-size: 100px; } }", + function(aApplied, aParent, aException) { + return !aException && + window.getComputedStyle(test, "").fontSize == + (aApplied ? "100px" : "16px") + }], + ["@font-face { font-family: UnlikelyFontName; src: " + + MONOSPACE_FONTS.map(function(s) { return "local('" + s + "')" }).join(", ") + "; }", + function(aApplied, aParent, aException) { + var width = testFont.getBoundingClientRect().width; + if (aException) { + return false; + } + if (navigator.oscpu.match(/Linux/) || + navigator.oscpu.match(/Android/) || + SpecialPowers.Services.appinfo.name == "B2G") { + return true; + } + return Math.abs(width - (aApplied ? monospaceWidth : serifWidth)) <= 1; // bug 769194 prevents local() + // fonts working on Android + }], + ["@import url(nothing.css);", + function(aApplied, aParent, aException) { + // just test whether it threw + return aParent instanceof CSSRule ? aException : !aException; + }], + ["@namespace test url(http://example.org);", + function(aApplied, aParent, aException) { + // just test whether it threw + return aParent instanceof CSSRule ? aException : !aException; + }], + ["@counter-style unlikely-counter-style { system: extends cjk-decimal; }", + function (aApplied, aParent, aException) { + var width = testCounterStyle.getBoundingClientRect().width; + if (aException) { + return false; + } + return width == (aApplied ? cjkDecimalWidth : decimalWidth); + }], +]; + +function runTest() +{ + // First, assert that our assumed available fonts are indeed available + // and have expected metrics. + ok(monospaceWidth > 0, "monospace text has width"); + ok(serifWidth > 0, "serif text has width"); + ok(Math.abs(monospaceWidth - serifWidth) > 1, "monospace and serif text have sufficiently different widths"); + + // And that the #test-font element starts off using the "serif" font. + var initialFontTestWidth = testFont.getBoundingClientRect().width; + is(initialFontTestWidth, serifWidth); + + ok(decimalWidth > 0, "decimal counter has width"); + ok(cjkDecimalWidth > 0, "cjk-decimal counter has width"); + ok(decimalWidth != cjkDecimalWidth, "decimal and cjk-decimal counter have different width") + + var initialCounterStyleWidth = testCounterStyle.getBoundingClientRect().width; + is(initialCounterStyleWidth, decimalWidth); + + // We construct a style sheet with zero, one or two levels of conditional + // grouping rules (taken from outerRuleInfo), with one of the inner rules + // at the deepest level. + var style = document.getElementById("style"); + + // For each of the outer rule types... + for (var outerRule1 = 0; outerRule1 < outerRuleInfo.length; outerRule1++) { + // For each of { 0 = don't create an outer rule, + // 1 = create an outer rule with a passing condition, + // 2 = create an outer rule with a failing condition }... + for (var outerRuleCondition1 = 0; outerRuleCondition1 <= 2; outerRuleCondition1++) { + + // For each of the outer rule types again... + for (var outerRule2 = 0; outerRule2 < outerRuleInfo.length; outerRule2++) { + // For each of { 0 = don't create an outer rule, + // 1 = create an outer rule with a passing condition, + // 2 = create an outer rule with a failing condition } again... + for (var outerRuleCondition2 = 0; outerRuleCondition2 <= 2; outerRuleCondition2++) { + + // For each of the inner rule types... + for (var innerRule = 0; innerRule < innerRuleInfo.length; innerRule++) { + + // Clear rules + var object = style.sheet; + while (object.cssRules.length) { + object.deleteRule(0); + } + + // We'll record whether the inner rule should have been applied, + // according to whether we put passing or failing conditional + // grouping rules around it. + var applied = true; + + if (outerRuleCondition1) { + // Create an outer conditional rule. + object.insertRule([outerRuleInfo[outerRule1][0], + outerRuleInfo[outerRule1][outerRuleCondition1], + "{}"].join(" "), 0); + object = object.cssRules[0]; + + if (outerRuleCondition1 == 2) { + // If we used a failing condition, we don't expect the inner + // rule to be applied. + applied = false; + } + } + + if (outerRuleCondition2) { + // Create another outer conditional rule as a child of the first + // outer conditional rule (or the style sheet, if we didn't create + // a first outer conditional rule). + object.insertRule([outerRuleInfo[outerRule2][0], + outerRuleInfo[outerRule2][outerRuleCondition2], + "{}"].join(" "), 0); + object = object.cssRules[0]; + + if (outerRuleCondition2 == 2) { + // If we used a failing condition, we don't expect the inner + // rule to be applied. + applied = false; + } + } + + var outer = object instanceof CSSRule ? object.cssText : "style sheet"; + var inner = innerRuleInfo[innerRule][0]; + + // Insert the inner rule. + var exception = null; + try { + object.insertRule(inner, 0); + } catch (e) { + exception = e; + } + + ok(innerRuleInfo[innerRule][1](applied, object, exception), + "<" + [outerRule1, outerRuleCondition1, outerRule2, + outerRuleCondition2, innerRule].join(",") + "> " + + "inserting " + inner + " into " + outer.replace(/ *\n */g, ' ')); + } + } + } + } + } + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); +</script> +</body> +</html> diff --git a/layout/style/test/test_rule_serialization.html b/layout/style/test/test_rule_serialization.html new file mode 100644 index 000000000..eba9a3e6f --- /dev/null +++ b/layout/style/test/test_rule_serialization.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <title>Test for Bug </title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css" id="style"></style> +</head> +<body> +<pre id="test"> +<script type="application/javascript"> + +var rules = [ + { rule: "@-moz-document url(http://www.example.com/) {}" }, + { rule: "@-moz-document url('http://www.example.com/') {}" }, + { rule: '@-moz-document url("http://www.example.com/") {}' }, + { rule: "@-moz-document url-prefix('http://www.example.com/') {}" }, + { rule: '@-moz-document url-prefix("http://www.example.com/") {}' }, + { rule: "@-moz-document domain('example.com') {}" }, + { rule: '@-moz-document domain("example.com") {}' }, + { rule: "@-moz-document regexp('http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/') {}" }, + { rule: '@-moz-document regexp("http://www.w3.org/TR/\\d{4}/[^/]*-CSS2-\\d{8}/") {}' }, +]; + +var style = document.getElementById("style"); +var style_text = document.createTextNode(""); +style.appendChild(style_text); + +for (var i in rules) { + var obj = rules[i]; + var rule = obj.rule; + + style_text.data = rule; + is(style.sheet.cssRules.length, 1, "should have one rule"); + var ser1 = style.sheet.cssRules[0].cssText; + if ("is_canonical" in obj) { + is(ser1, rule, "rule '" + rule + "' should serialize to itself"); + } + + style_text.data = ser1; + is(style.sheet.cssRules.length, 1, "should have one rule"); + var ser2 = style.sheet.cssRules[0].cssText; + is(ser2, ser1, + "parse+serialize for rule '" + rule + "' should be idempotent"); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_rules_out_of_sheets.html b/layout/style/test/test_rules_out_of_sheets.html new file mode 100644 index 000000000..ab692239a --- /dev/null +++ b/layout/style/test/test_rules_out_of_sheets.html @@ -0,0 +1,115 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=634373 +--> +<head> + <title>Test for Bug 634373</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=634373">Mozilla Bug 634373</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 634373 **/ + +function make_rule_and_remove_sheet(text, getter) { + var style = document.createElement("style"); + style.setAttribute("type", "text/css"); + style.appendChild(document.createTextNode(text)); + document.head.appendChild(style); + var result = style.sheet.cssRules[0]; + if (getter) { + result = getter(result); + } + document.head.removeChild(style); + style = null; + SpecialPowers.DOMWindowUtils.garbageCollect(); + return result; +} + +var gDisplayCS = getComputedStyle(document.getElementById("display"), ""); + +function keep_rule_alive_by_matching(rule) { + // It's the caller's job to guarantee that the rule matches a p. + // This just causes a style flush, which in turn keeps the rule alive + // until the next style flush. + var color = gDisplayCS.color; + return rule; +} + +function get_rule_and_child(rule) { + return [rule, rule.cssRules[0]]; +} + +function get_only_child(rule) { + return rule.cssRules[0]; +} + +var rule; + +// In this case, the rule goes away immediately, so we're testing +// the DOM wrapper's handling of a null rule, rather than the rule's +// handling of a null sheet. +rule = make_rule_and_remove_sheet("p { color: blue }"); +rule.style.color = ""; +try { + rule.style.color = "fuchsia"; +} catch(ex) {} + +rule = make_rule_and_remove_sheet("p { color: blue }", + keep_rule_alive_by_matching); +try { + rule.style.color = ""; +} catch(ex) {} +try { + rule.style.color = "fuchsia"; +} catch(ex) {} + +rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }", + get_rule_and_child); +rule[1].style.color = ""; +try { + rule[1].style.color = "fuchsia"; +} catch(ex) {} + +// In this case, the rule goes away immediately, so we're testing +// the DOM wrapper's handling of a null rule, rather than the rule's +// handling of a null sheet. +rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }", + get_only_child); +rule.style.color = ""; +try { + rule.style.color = "fuchsia"; +} catch(ex) {} + +rule = make_rule_and_remove_sheet("@media screen { p { color: blue } }", + function(rule) { + return keep_rule_alive_by_matching( + get_only_child(rule)); + }); +try { + rule.style.color = ""; +} catch(ex) {} +try { + rule.style.color = "fuchsia"; +} catch(ex) {} + +rule = make_rule_and_remove_sheet("@keyframes a { from { color: blue } }"); +rule.appendRule("from { color: fuchsia}"); +rule.deleteRule("from"); +rule.name = "b"; +rule.cssRules[0].keyText = "50%"; + +ok(true, "didn't crash"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_selectors.html b/layout/style/test/test_selectors.html new file mode 100644 index 000000000..7294a879f --- /dev/null +++ b/layout/style/test/test_selectors.html @@ -0,0 +1,1297 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for CSS Selectors</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="run()"> +<p id="display"><iframe id="iframe" src="about:blank"></iframe><iframe id="cloneiframe" src="about:blank"></iframe></p> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(2); + +var cloneiframe; + +function run() { + + var iframe = document.getElementById("iframe"); + var ifwin = iframe.contentWindow; + var ifdoc = iframe.contentDocument; + + cloneiframe = document.getElementById("cloneiframe"); + + var style_elem = ifdoc.createElement("style"); + style_elem.setAttribute("type", "text/css"); + ifdoc.getElementsByTagName("head")[0].appendChild(style_elem); + var style_text = ifdoc.createTextNode(""); + style_elem.appendChild(style_text); + + var gCounter = 0; + + /* + * selector: the selector to test + * body_contents: what to set the body's innerHTML to + * match_fn: a function that, given the document object into which + * body_contents has been inserted, produces an array of nodes that + * should match selector + * notmatch_fn: likewise, but for nodes that should not match + * namespaces (optional): @namespace rules to be included in the sheet + */ + function test_selector_in_html(selector, body_contents, match_fn, notmatch_fn, namespaces) + { + var zi = ++gCounter; + if (typeof(body_contents) == "string") { + ifdoc.body.innerHTML = body_contents; + } else { + // It's a function. + ifdoc.body.innerHTML = ""; + body_contents(ifdoc.body); + } + if (!namespaces) { + namespaces = ""; + } + style_text.data = namespaces + selector + "{ z-index: " + zi + " }"; + + var idx = style_text.parentNode.sheet.cssRules.length - 1; + if (namespaces == "") { + is(idx, 0, "unexpected rule index"); + } + if (idx < 0 || + style_text.parentNode.sheet.cssRules[idx].type != + CSSRule.STYLE_RULE) + { + ok(false, "selector " + selector + " could not be parsed"); + return; + } + + var should_match = match_fn(ifdoc); + var should_not_match = notmatch_fn(ifdoc); + if (should_match.length + should_not_match.length == 0) { + ok(false, "nothing to check"); + } + + for (var i = 0; i < should_match.length; ++i) { + var e = should_match[i]; + is(ifwin.getComputedStyle(e, "").zIndex, String(zi), + "element in " + body_contents + " matched " + selector); + } + for (var i = 0; i < should_not_match.length; ++i) { + var e = should_not_match[i]; + is(ifwin.getComputedStyle(e, "").zIndex, "auto", + "element in " + body_contents + " did not match " + selector); + } + + // Now, since we're here, may as well make sure serialization + // works correctly. It need not produce the exact same text, + // but it should produce a selector that matches the same + // elements. + zi = ++gCounter; + var ser1 = style_text.parentNode.sheet.cssRules[idx].selectorText; + style_text.data = namespaces + ser1 + "{ z-index: " + zi + " }"; + for (var i = 0; i < should_match.length; ++i) { + var e = should_match[i]; + is(ifwin.getComputedStyle(e, "").zIndex, String(zi), + "element in " + body_contents + " matched " + ser1 + + " which is the reserialization of " + selector); + } + for (var i = 0; i < should_not_match.length; ++i) { + var e = should_not_match[i]; + is(ifwin.getComputedStyle(e, "").zIndex, "auto", + "element in " + body_contents + " did not match " + ser1 + + " which is the reserialization of " + selector); + } + + // But when we serialize the serialized result, we should get + // the same text. + var ser2 = style_text.parentNode.sheet.cssRules[idx].selectorText; + is(ser2, ser1, "parse+serialize of selector \"" + selector + + "\" is idempotent"); + + ifdoc.body.innerHTML = ""; + style_text.data = ""; + + // And now test that when we clone the style sheet, we end up + // with the same selector (serializes to same string, and + // matches the same things). + zi = ++gCounter; + var style_sheet = "data:text/css," + + escape(namespaces + selector + "{ z-index: " + zi + " }"); + var style_sheet_link = + "<link rel='stylesheet' href='" + style_sheet + "'>"; + var html_doc = "<!DOCTYPE HTML>" + + style_sheet_link + style_sheet_link + + "<body>"; + if (typeof(body_contents) == "string") { + html_doc += body_contents; + } + var docurl = "data:text/html," + escape(html_doc); + defer_clonedoc_tests(docurl, function() { + var clonedoc = cloneiframe.contentDocument; + var clonewin = cloneiframe.contentWindow; + + if (typeof(body_contents) != "string") { + body_contents(clonedoc.body); + } + + var links = clonedoc.getElementsByTagName("link"); + // cause a clone + links[1].sheet.insertRule("#nonexistent { color: purple}", idx + 1); + // remove the uncloned sheet + links[0].parentNode.removeChild(links[0]); + + var should_match = match_fn(clonedoc); + var should_not_match = notmatch_fn(clonedoc); + + if (should_match.length + should_not_match.length == 0) { + ok(false, "nothing to check"); + } + + for (var i = 0; i < should_match.length; ++i) { + var e = should_match[i]; + is(clonewin.getComputedStyle(e, "").zIndex, String(zi), + "element in " + body_contents + " matched clone of " + + selector); + } + for (var i = 0; i < should_not_match.length; ++i) { + var e = should_not_match[i]; + is(clonewin.getComputedStyle(e, "").zIndex, "auto", + "element in " + body_contents + " did not match clone of " + + selector); + } + + var ser3 = links[0].sheet.cssRules[idx].selectorText; + is(ser3, ser1, + "selector " + selector + " serializes correctly after cloning"); + }); + } + + function should_serialize_to(selector, serialization) + { + style_text.data = selector + "{ z-index: 0 }"; + is(style_text.parentNode.sheet.cssRules[0].selectorText, + serialization, + "selector '" + selector + "' should serialize to '" + + serialization + "'."); + } + + function test_parseable(selector) + { + ifdoc.body.innerHTML = "<p></p>"; + + var zi = ++gCounter; + style_text.data = "p, " + selector + "{ z-index: " + zi + " }"; + var should_match = ifdoc.getElementsByTagName("p")[0]; + var parsed = ifwin.getComputedStyle(should_match, "").zIndex == zi; + ok(parsed, "selector " + selector + " was parsed"); + if (!parsed) { + return; + } + + // Test that it serializes to something that is also parseable. + var ser1 = style_elem.sheet.cssRules[0].selectorText; + zi = ++gCounter; + style_text.data = ser1 + "{ z-index: " + zi + " }"; + is(ifwin.getComputedStyle(should_match, "").zIndex, String(zi), + "serialization " + ser1 + " of selector p, " + selector + + " was parsed"); + var ser2 = style_elem.sheet.cssRules[0].selectorText; + is(ser2, ser1, + "parse+serialize of selector " + selector + " is idempotent"); + + ifdoc.body.innerHTML = ""; + style_text.data = ""; + + // Test that it clones to the same thing it serializes to. + zi = ++gCounter; + var style_sheet = "data:text/css," + + escape("p, " + selector + "{ z-index: " + zi + " }"); + var style_sheet_link = + "<link rel='stylesheet' href='" + style_sheet + "'>"; + var html_doc = "<!DOCTYPE HTML>" + + style_sheet_link + style_sheet_link + + "<p></p>"; + var docurl = "data:text/html," + escape(html_doc); + + defer_clonedoc_tests(docurl, function() { + var clonedoc = cloneiframe.contentDocument; + var clonewin = cloneiframe.contentWindow; + var links = clonedoc.getElementsByTagName("link"); + // cause a clone + links[1].sheet.insertRule("#nonexistent { color: purple}", 0); + // remove the uncloned sheet + links[0].parentNode.removeChild(links[0]); + + should_match = clonedoc.getElementsByTagName("p")[0]; + is(clonewin.getComputedStyle(should_match, "").zIndex, String(zi), + "selector " + selector + " was cloned correctly"); + var ser3 = links[0].sheet.cssRules[1].selectorText; + is(ser3, ser1, + "selector " + selector + " serializes correctly after cloning"); + }); + } + + function test_unparseable_via_api(selector) + { + try { + // Test that it is also unparseable when followed by EOF. + ifdoc.body.matches(selector); + ok(false, "selector '" + selector + "' plus EOF is parse error"); + } catch(ex) { + is(ex.name, "SyntaxError", + "selector '" + selector + "' plus EOF is parse error"); + is(ex.code, DOMException.SYNTAX_ERR, + "selector '" + selector + "' plus EOF is parse error"); + } + } + + function test_parseable_via_api(selector) + { + var threw = false; + try { + // Test that a selector is parseable when followed by EOF. + ifdoc.body.matches(selector); + } catch(ex) { + threw = true; + } + ok(!threw, "selector '" + selector + "' was parsed"); + } + + function test_balanced_unparseable(selector) + { + var zi1 = ++gCounter; + var zi2 = ++gCounter; + ifdoc.body.innerHTML = "<p></p><div></div>"; + style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }" + + "div { z-index: " + zi2 + " }"; + var should_not_match = ifdoc.getElementsByTagName("p")[0]; + var should_match = ifdoc.getElementsByTagName("div")[0]; + is(ifwin.getComputedStyle(should_not_match, "").zIndex, "auto", + "selector " + selector + " was a parser error"); + is(ifwin.getComputedStyle(should_match, "").zIndex, String(zi2), + "selector " + selector + " error was recovered from"); + ifdoc.body.innerHTML = ""; + style_text.data = ""; + test_unparseable_via_api(selector); + } + + function test_unbalanced_unparseable(selector) + { + var zi1 = ++gCounter; + var zi2 = ++gCounter; + ifdoc.body.innerHTML = "<p></p>"; + style_text.data = "p, " + selector + "{ z-index: " + zi1 + " }"; + var should_not_match = ifdoc.getElementsByTagName("p")[0]; + is(ifwin.getComputedStyle(should_not_match, "").zIndex, "auto", + "selector " + selector + " was a parser error"); + is(style_text.parentNode.sheet.cssRules.length, 0, + "sheet should have no rules since " + selector + " is parse error"); + ifdoc.body.innerHTML = ""; + style_text.data = ""; + test_unparseable_via_api(selector); + } + + // [attr] selector + test_parseable("[attr]") + test_parseable_via_api("[attr"); + + // [attr= ] selector + test_parseable("[attr=\"x\"]"); + test_parseable("[attr='x']"); + test_parseable("[attr=x]"); + test_parseable("[attr=\"\"]"); + test_parseable("[attr='']"); + test_parseable("[attr=\"foo bar\"]"); + test_parseable_via_api("[attr=x"); + + test_balanced_unparseable("[attr=]"); + test_balanced_unparseable("[attr=foo bar]"); + + test_selector_in_html( + '[title=""]', + '<p title=""></p>' + + '<div lang=" "></div><div lang="\t"></div><div lang="\n"></div>', + function(doc) { return doc.getElementsByTagName("p"); }, + function(doc) { return doc.getElementsByTagName("div"); } + ); + + // [attr~= ] selector + test_parseable("[attr~=\"x\"]"); + test_parseable("[attr~='x']"); + test_parseable("[attr~=x]"); + test_parseable("[attr~=\"\"]"); + test_parseable("[attr~='']"); + test_parseable("[attr~=\"foo bar\"]"); + test_parseable_via_api("[attr~=x"); + + test_balanced_unparseable("[attr~=]"); + test_balanced_unparseable("[attr~=foo bar]"); + + test_selector_in_html( + '[class~="x x"]', + '<div class="x x"></div><div class="x"></div><div class="x\tx"></div>div class="x\nx"></div>', + function(doc) { return []; }, + function(doc) { return doc.getElementsByTagName("div"); } + ); + + // [attr|="x"] + test_parseable('[attr|="x"]'); + test_parseable("[attr|='x']"); + test_parseable('[attr|=x]'); + test_parseable_via_api("[attr|=x"); + + test_parseable('[attr|=""]'); + test_parseable("[attr|='']"); + test_balanced_unparseable('[attr|=]'); + + test_selector_in_html( + '[lang|=""]', + '<p lang=""></p><p lang="-"></p><p lang="-GB"></p>' + + '<div lang="en-GB"></div><div lang="en-"></div>', + function(doc) { return doc.getElementsByTagName("p"); }, + function(doc) { return doc.getElementsByTagName("div"); } + ); + + // [attr$= ] selector + test_parseable("[attr$=\"x\"]"); + test_parseable("[attr$='x']"); + test_parseable("[attr$=x]"); + test_parseable("[attr$=\"\"]"); + test_parseable("[attr$='']"); + test_parseable("[attr$=\"foo bar\"]"); + test_parseable_via_api("[attr$=x"); + + test_balanced_unparseable("[attr$=]"); + test_balanced_unparseable("[attr$=foo bar]"); + + // [attr^= ] selector + test_parseable("[attr^=\"x\"]"); + test_parseable("[attr^='x']"); + test_parseable("[attr^=x]"); + test_parseable("[attr^=\"\"]"); + test_parseable("[attr^='']"); + test_parseable("[attr^=\"foo bar\"]"); + test_parseable_via_api("[attr^=x"); + + test_balanced_unparseable("[attr^=]"); + test_balanced_unparseable("[attr^=foo bar]"); + + // attr[*= ] selector + test_parseable("[attr*=\"x\"]"); + test_parseable("[attr*='x']"); + test_parseable("[attr*=x]"); + test_parseable("[attr*=\"\"]"); + test_parseable("[attr*='']"); + test_parseable("[attr*=\"foo bar\"]"); + test_parseable_via_api("[attr^=x"); + + test_balanced_unparseable("[attr*=]"); + test_balanced_unparseable("[attr*=foo bar]"); + + + // Bug 420814 + test_selector_in_html( + "div ~ div p", + "<div></div><div><div><p>match</p></div></div>", + function(doc) { return doc.getElementsByTagName("p"); }, + function(doc) { return []; } + ); + + // Bug 420245 + test_selector_in_html( + "p[attr$=\"\"]", + "<p attr=\"foo\">This should not match</p>", + function(doc) { return []; }, + function(doc) { return doc.getElementsByTagName("p"); } + ); + test_selector_in_html( + "div + p[attr~=\"\"]", + "<div>Dummy</div><p attr=\"foo\">This should not match</p>", + function(doc) { return []; }, + function(doc) { return doc.getElementsByTagName("p"); } + ); + test_selector_in_html( + "div[attr^=\"\"]", + "<div attr=\"dummy1\">Dummy</div><div attr=\"dummy2\">Dummy</div>", + function(doc) { return []; }, + function(doc) { return doc.getElementsByTagName("div"); } + ); + test_selector_in_html( + "div[attr*=\"\"]", + "<div attr=\"dummy1\">Dummy</div><div attr=\"dummy2\">Dummy</div>", + function(doc) { return []; }, + function(doc) { return doc.getElementsByTagName("div"); } + ); + + // :nth-child(), etc. + // Follow the whitespace rules as proposed in + // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html + test_balanced_unparseable(":nth-child()"); + test_balanced_unparseable(":nth-of-type( )"); + test_parseable(":nth-last-child( odd)"); + test_parseable(":nth-last-of-type(even )"); + test_parseable(":nth-child(n )"); + test_parseable(":nth-of-type( 2n)"); + test_parseable(":nth-last-child( -n)"); + test_parseable(":nth-last-of-type(-2n )"); + test_balanced_unparseable(":nth-child(- n)"); + test_balanced_unparseable(":nth-of-type(-2 n)"); + test_balanced_unparseable(":nth-last-of-type(2n1)"); + test_balanced_unparseable(":nth-child(2n++1)"); + test_balanced_unparseable(":nth-of-type(2n-+1)"); + test_balanced_unparseable(":nth-last-child(2n+-1)"); + test_balanced_unparseable(":nth-last-of-type(2n--1)"); + test_parseable(":nth-child( 3n + 1 )"); + test_parseable(":nth-child( +3n - 2 )"); + test_parseable(":nth-child( -n+ 6)"); + test_parseable(":nth-child( +6 )"); + test_balanced_unparseable(":nth-child(3 n)"); + test_balanced_unparseable(":nth-child(+ 2n)"); + test_balanced_unparseable(":nth-child(+ 2)"); + test_parseable(":nth-child(3)"); + test_parseable(":nth-of-type(-3)"); + test_parseable(":nth-last-child(+3)"); + test_parseable(":nth-last-of-type(0)"); + test_parseable(":nth-child(-0)"); + test_parseable(":nth-of-type(3n)"); + test_parseable(":nth-last-child(-3n)"); + test_parseable(":nth-last-of-type(+3n)"); + test_parseable(":nth-last-of-type(0n)"); + test_parseable(":nth-child(-0n)"); + test_parseable(":nth-of-type(n)"); + test_parseable(":nth-last-child(-n)"); + test_parseable(":nth-last-of-type(2n+1)"); + test_parseable(":nth-child(2n-1)"); + test_parseable(":nth-of-type(2n+0)"); + test_parseable(":nth-last-child(2n-0)"); + test_parseable(":nth-child(-0n+0)"); + test_parseable(":nth-of-type(n+1)"); + test_parseable(":nth-last-child(n-1)"); + test_parseable(":nth-last-of-type(-n+1)"); + test_parseable(":nth-child(-n-1)"); + test_balanced_unparseable(":nth-child(2-n)"); + test_balanced_unparseable(":nth-child(2-n-1)"); + test_balanced_unparseable(":nth-child(n-2-1)"); + // Bug 750388 + test_parseable(":nth-child(+n)"); + test_balanced_unparseable(":nth-child(+ n)"); + test_parseable(":nth-child(+n+2)"); + test_parseable(":nth-child(+n-2)"); + test_parseable(":nth-child(+n + 2)"); + test_parseable(":nth-child(+n - 2)"); + test_balanced_unparseable(":nth-child(+ n+2)"); + test_balanced_unparseable(":nth-child(+ n-2)"); + test_balanced_unparseable(":nth-child(+ n + 2)"); + test_balanced_unparseable(":nth-child(+ n - 2)"); + test_parseable(":nth-child(+n-100)"); + test_parseable(":nth-child(+n - 100)"); + test_balanced_unparseable(":nth-child(+ n-100)"); + test_balanced_unparseable(":nth-child(+-n+2)"); + test_balanced_unparseable(":nth-child(+ -n+2)"); + test_balanced_unparseable(":nth-child(+-n-100)"); + test_balanced_unparseable(":nth-child(+ -n-100)"); + test_balanced_unparseable(":nth-child(++n-100)"); + test_balanced_unparseable(":nth-child(-+n-100)"); + test_balanced_unparseable(":nth-child(++2n - 100)"); + test_balanced_unparseable(":nth-child(+-2n - 100)"); + test_balanced_unparseable(":nth-child(-+2n - 100)"); + test_balanced_unparseable(":nth-child(--2n - 100)"); + test_balanced_unparseable(":nth-child(+/**/+2n - 100)"); + test_balanced_unparseable(":nth-child(+/**/-2n - 100)"); + test_balanced_unparseable(":nth-child(-/**/+2n - 100)"); + test_balanced_unparseable(":nth-child(-/**/-2n - 100)"); + test_balanced_unparseable(":nth-child(+/**/+/**/2n - 100)"); + test_balanced_unparseable(":nth-child(+/**/-/**/2n - 100)"); + test_balanced_unparseable(":nth-child(-/**/+/**/2n - 100)"); + test_balanced_unparseable(":nth-child(-/**/-/**/2n - 100)"); + test_balanced_unparseable(":nth-child(++/**/2n - 100)"); + test_balanced_unparseable(":nth-child(+-/**/2n - 100)"); + test_balanced_unparseable(":nth-child(-+/**/2n - 100)"); + test_balanced_unparseable(":nth-child(--/**/2n - 100)"); + test_balanced_unparseable(":nth-child(-even)"); + test_balanced_unparseable(":nth-child(-odd)"); + test_balanced_unparseable(":nth-child(+even)"); + test_balanced_unparseable(":nth-child(+odd)"); + test_balanced_unparseable(":nth-child(+ even)"); + test_balanced_unparseable(":nth-child(+ odd)"); + test_balanced_unparseable(":nth-child(+-n)"); + test_balanced_unparseable(":nth-child(+-n-)"); + test_balanced_unparseable(":nth-child(-+n)"); + test_balanced_unparseable(":nth-child(+n--)"); + test_parseable(":nth-child(n+2)"); + test_parseable(":nth-child(n/**/+/**/2)"); + test_parseable(":nth-child(n-2)"); + test_parseable(":nth-child(n/**/-/**/2)"); + test_balanced_unparseable(":nth-child(n++2)"); + test_balanced_unparseable(":nth-child(n+-2)"); + test_balanced_unparseable(":nth-child(n-+2)"); + test_balanced_unparseable(":nth-child(n--2)"); + test_balanced_unparseable(":nth-child(n/**/++2)"); + test_balanced_unparseable(":nth-child(n/**/+-2)"); + test_balanced_unparseable(":nth-child(n/**/-+2)"); + test_balanced_unparseable(":nth-child(n/**/--2)"); + test_balanced_unparseable(":nth-child(n/**/+/**/+2)"); + test_balanced_unparseable(":nth-child(n/**/+/**/-2)"); + test_balanced_unparseable(":nth-child(n/**/-/**/+2)"); + test_balanced_unparseable(":nth-child(n/**/-/**/-2)"); + test_balanced_unparseable(":nth-child(n+/**/+2)"); + test_balanced_unparseable(":nth-child(n+/**/-2)"); + test_balanced_unparseable(":nth-child(n-/**/+2)"); + test_balanced_unparseable(":nth-child(n-/**/-2)"); + test_balanced_unparseable(":nth-child(n++/**/2)"); + test_balanced_unparseable(":nth-child(n+-/**/2)"); + test_balanced_unparseable(":nth-child(n-+/**/2)"); + test_balanced_unparseable(":nth-child(n--/**/2)"); + test_balanced_unparseable(":nth-child(n/**/++/**/2)"); + test_balanced_unparseable(":nth-child(n/**/+-/**/2)"); + test_balanced_unparseable(":nth-child(n/**/-+/**/2)"); + test_balanced_unparseable(":nth-child(n/**/--/**/2)"); + test_balanced_unparseable(":nth-child(n/**/+/**/+/**/2)"); + test_balanced_unparseable(":nth-child(n/**/+/**/-/**/2)"); + test_balanced_unparseable(":nth-child(n/**/-/**/+/**/2)"); + test_balanced_unparseable(":nth-child(n/**/-/**/-/**/2)"); + test_balanced_unparseable(":nth-child(n+/**/+/**/2)"); + test_balanced_unparseable(":nth-child(n+/**/-/**/2)"); + test_balanced_unparseable(":nth-child(n-/**/+/**/2)"); + test_balanced_unparseable(":nth-child(n-/**/-/**/2)"); + test_parseable(":nth-child(2n+2)"); + test_parseable(":nth-child(2n/**/+/**/2)"); + test_parseable(":nth-child(2n-2)"); + test_parseable(":nth-child(2n/**/-/**/2)"); + test_balanced_unparseable(":nth-child(2n++2)"); + test_balanced_unparseable(":nth-child(2n+-2)"); + test_balanced_unparseable(":nth-child(2n-+2)"); + test_balanced_unparseable(":nth-child(2n--2)"); + test_balanced_unparseable(":nth-child(2n/**/++2)"); + test_balanced_unparseable(":nth-child(2n/**/+-2)"); + test_balanced_unparseable(":nth-child(2n/**/-+2)"); + test_balanced_unparseable(":nth-child(2n/**/--2)"); + test_balanced_unparseable(":nth-child(2n/**/+/**/+2)"); + test_balanced_unparseable(":nth-child(2n/**/+/**/-2)"); + test_balanced_unparseable(":nth-child(2n/**/-/**/+2)"); + test_balanced_unparseable(":nth-child(2n/**/-/**/-2)"); + test_balanced_unparseable(":nth-child(2n+/**/+2)"); + test_balanced_unparseable(":nth-child(2n+/**/-2)"); + test_balanced_unparseable(":nth-child(2n-/**/+2)"); + test_balanced_unparseable(":nth-child(2n-/**/-2)"); + test_balanced_unparseable(":nth-child(2n++/**/2)"); + test_balanced_unparseable(":nth-child(2n+-/**/2)"); + test_balanced_unparseable(":nth-child(2n-+/**/2)"); + test_balanced_unparseable(":nth-child(2n--/**/2)"); + test_balanced_unparseable(":nth-child(2n/**/++/**/2)"); + test_balanced_unparseable(":nth-child(2n/**/+-/**/2)"); + test_balanced_unparseable(":nth-child(2n/**/-+/**/2)"); + test_balanced_unparseable(":nth-child(2n/**/--/**/2)"); + test_balanced_unparseable(":nth-child(2n/**/+/**/+/**/2)"); + test_balanced_unparseable(":nth-child(2n/**/+/**/-/**/2)"); + test_balanced_unparseable(":nth-child(2n/**/-/**/+/**/2)"); + test_balanced_unparseable(":nth-child(2n/**/-/**/-/**/2)"); + test_balanced_unparseable(":nth-child(2n+/**/+/**/2)"); + test_balanced_unparseable(":nth-child(2n+/**/-/**/2)"); + test_balanced_unparseable(":nth-child(2n-/**/+/**/2)"); + test_balanced_unparseable(":nth-child(2n-/**/-/**/2)"); + test_parseable(":nth-child(+/**/n+2)"); + test_parseable(":nth-child(+n/**/+2)"); + test_parseable(":nth-child(+n/**/+2)"); + test_parseable(":nth-child(+n+/**/2)"); + test_parseable(":nth-child(+n+2/**/)"); + test_parseable(":nth-child(+1/**/n+2)"); + test_parseable(":nth-child(+1n/**/+2)"); + test_parseable(":nth-child(+1n/**/+2)"); + test_parseable(":nth-child(+1n+/**/2)"); + test_parseable(":nth-child(+1n+2/**/)"); + test_parseable(":nth-child(-/**/n+2)"); + test_parseable(":nth-child(-n/**/+2)"); + test_parseable(":nth-child(-n/**/+2)"); + test_parseable(":nth-child(-n+/**/2)"); + test_parseable(":nth-child(-n+2/**/)"); + test_parseable(":nth-child(-1/**/n+2)"); + test_parseable(":nth-child(-1n/**/+2)"); + test_parseable(":nth-child(-1n/**/+2)"); + test_parseable(":nth-child(-1n+/**/2)"); + test_parseable(":nth-child(-1n+2/**/)"); + test_balanced_unparseable(":nth-child(-/**/ n+2)"); + test_balanced_unparseable(":nth-child(- /**/n+2)"); + test_balanced_unparseable(":nth-child(+/**/ n+2)"); + test_balanced_unparseable(":nth-child(+ /**/n+2)"); + test_parseable(":nth-child(+/**/n-2)"); + test_parseable(":nth-child(+n/**/-2)"); + test_parseable(":nth-child(+n/**/-2)"); + test_parseable(":nth-child(+n-/**/2)"); + test_parseable(":nth-child(+n-2/**/)"); + test_parseable(":nth-child(+1/**/n-2)"); + test_parseable(":nth-child(+1n/**/-2)"); + test_parseable(":nth-child(+1n/**/-2)"); + test_parseable(":nth-child(+1n-/**/2)"); + test_parseable(":nth-child(+1n-2/**/)"); + test_parseable(":nth-child(-/**/n-2)"); + test_parseable(":nth-child(-n/**/-2)"); + test_parseable(":nth-child(-n/**/-2)"); + test_parseable(":nth-child(-n-/**/2)"); + test_parseable(":nth-child(-n-2/**/)"); + test_parseable(":nth-child(-1/**/n-2)"); + test_parseable(":nth-child(-1n/**/-2)"); + test_parseable(":nth-child(-1n/**/-2)"); + test_parseable(":nth-child(-1n-/**/2)"); + test_parseable(":nth-child(-1n-2/**/)"); + test_balanced_unparseable(":nth-child(-/**/ n-2)"); + test_balanced_unparseable(":nth-child(- /**/n-2)"); + test_balanced_unparseable(":nth-child(+/**/ n-2)"); + test_balanced_unparseable(":nth-child(+ /**/n-2)"); + test_parseable(":nth-child(+/**/N-2)"); + test_parseable(":nth-child(+N/**/-2)"); + test_parseable(":nth-child(+N/**/-2)"); + test_parseable(":nth-child(+N-/**/2)"); + test_parseable(":nth-child(+N-2/**/)"); + test_parseable(":nth-child(+1/**/N-2)"); + test_parseable(":nth-child(+1N/**/-2)"); + test_parseable(":nth-child(+1N/**/-2)"); + test_parseable(":nth-child(+1N-/**/2)"); + test_parseable(":nth-child(+1N-2/**/)"); + test_parseable(":nth-child(-/**/N-2)"); + test_parseable(":nth-child(-N/**/-2)"); + test_parseable(":nth-child(-N/**/-2)"); + test_parseable(":nth-child(-N-/**/2)"); + test_parseable(":nth-child(-N-2/**/)"); + test_parseable(":nth-child(-1/**/N-2)"); + test_parseable(":nth-child(-1N/**/-2)"); + test_parseable(":nth-child(-1N/**/-2)"); + test_parseable(":nth-child(-1N-/**/2)"); + test_parseable(":nth-child(-1N-2/**/)"); + test_balanced_unparseable(":nth-child(-/**/ N-2)"); + test_balanced_unparseable(":nth-child(- /**/N-2)"); + test_balanced_unparseable(":nth-child(+/**/ N-2)"); + test_balanced_unparseable(":nth-child(+ /**/N-2)"); + test_parseable(":nth-child( +n + 1 )"); + test_parseable(":nth-child( +/**/n + 1 )"); + test_parseable(":nth-child( -/**/2/**/n/**/+/**/4 )"); + test_balanced_unparseable(":nth-child( -/**/ 2/**/n/**/+/**/4 )"); + test_balanced_unparseable(":nth-child( -/**/2 /**/n/**/+/**/4 )"); + test_balanced_unparseable(":nth-child( -/**/2/**/ n/**/+/**/4 )"); + test_parseable(":nth-child( -/**/2/**/n /**/+/**/4 )"); + test_parseable(":nth-child( -/**/2/**/n/**/ +/**/4 )"); + test_parseable(":nth-child(+1/**/n-1)"); + test_parseable(":nth-child(1/**/n-1)"); + // bug 876570 + test_balanced_unparseable(":nth-child(+2n-)"); + test_balanced_unparseable(":nth-child(+n-)"); + test_balanced_unparseable(":nth-child(-2n-)"); + test_balanced_unparseable(":nth-child(-n-)"); + test_balanced_unparseable(":nth-child(2n-)"); + test_balanced_unparseable(":nth-child(n-)"); + test_balanced_unparseable(":nth-child(+2n+)"); + test_balanced_unparseable(":nth-child(+n+)"); + test_balanced_unparseable(":nth-child(-2n+)"); + test_balanced_unparseable(":nth-child(-n+)"); + test_balanced_unparseable(":nth-child(2n+)"); + test_balanced_unparseable(":nth-child(n+)"); + + // exercise the an+b matching logic particularly hard for + // :nth-child() (since we know we use the same code for all 4) + var seven_ps = "<p></p><p></p><p></p><p></p><p></p><p></p><p></p>"; + function pset(indices) { // takes an array of 1-based indices + return function pset_filter(doc) { + var a = doc.getElementsByTagName("p"); + var result = []; + for (var i in indices) + result.push(a[indices[i] - 1]); + return result; + } + } + test_selector_in_html(":nth-child(0)", seven_ps, + pset([]), pset([1, 2, 3, 4, 5, 6, 7])); + test_selector_in_html(":nth-child(-3)", seven_ps, + pset([]), pset([1, 2, 3, 4, 5, 6, 7])); + test_selector_in_html(":nth-child(3)", seven_ps, + pset([3]), pset([1, 2, 4, 5, 6, 7])); + test_selector_in_html(":nth-child(0n+3)", seven_ps, + pset([3]), pset([1, 2, 4, 5, 6, 7])); + test_selector_in_html(":nth-child(-0n+3)", seven_ps, + pset([3]), pset([1, 2, 4, 5, 6, 7])); + test_selector_in_html(":nth-child(8)", seven_ps, + pset([]), pset([1, 2, 3, 4, 5, 6, 7])); + test_selector_in_html(":nth-child(odd)", seven_ps, + pset([1, 3, 5, 7]), pset([2, 4, 6])); + test_selector_in_html(":nth-child(even)", seven_ps, + pset([2, 4, 6]), pset([1, 3, 5, 7])); + test_selector_in_html(":nth-child(2n-1)", seven_ps, + pset([1, 3, 5, 7]), pset([2, 4, 6])); + test_selector_in_html(":nth-child( 2n - 1 )", seven_ps, + pset([1, 3, 5, 7]), pset([2, 4, 6])); + test_selector_in_html(":nth-child(2n+1)", seven_ps, + pset([1, 3, 5, 7]), pset([2, 4, 6])); + test_selector_in_html(":nth-child( 2n + 1 )", seven_ps, + pset([1, 3, 5, 7]), pset([2, 4, 6])); + test_selector_in_html(":nth-child(2n+0)", seven_ps, + pset([2, 4, 6]), pset([1, 3, 5, 7])); + test_selector_in_html(":nth-child(2n-0)", seven_ps, + pset([2, 4, 6]), pset([1, 3, 5, 7])); + test_selector_in_html(":nth-child(-n+3)", seven_ps, + pset([1, 2, 3]), pset([4, 5, 6, 7])); + test_selector_in_html(":nth-child(-n-3)", seven_ps, + pset([]), pset([1, 2, 3, 4, 5, 6, 7])); + test_selector_in_html(":nth-child(n)", seven_ps, + pset([1, 2, 3, 4, 5, 6, 7]), pset([])); + test_selector_in_html(":nth-child(n-3)", seven_ps, + pset([1, 2, 3, 4, 5, 6, 7]), pset([])); + test_selector_in_html(":nth-child(n+3)", seven_ps, + pset([3, 4, 5, 6, 7]), pset([1, 2])); + test_selector_in_html(":nth-child(2n+3)", seven_ps, + pset([3, 5, 7]), pset([1, 2, 4, 6])); + test_selector_in_html(":nth-child(2n)", seven_ps, + pset([2, 4, 6]), pset([1, 3, 5, 7])); + test_selector_in_html(":nth-child(2n-3)", seven_ps, + pset([1, 3, 5, 7]), pset([2, 4, 6])); + test_selector_in_html(":nth-child(-1n+3)", seven_ps, + pset([1, 2, 3]), pset([4, 5, 6, 7])); + test_selector_in_html(":nth-child(-2n+3)", seven_ps, + pset([1, 3]), pset([2, 4, 5, 6, 7])); + // And a few spot-checks for the other :nth-* selectors + test_selector_in_html(":nth-child(4n+1)", seven_ps, + pset([1, 5]), pset([2, 3, 4, 6, 7])); + test_selector_in_html(":nth-last-child(4n+1)", seven_ps, + pset([3, 7]), pset([1, 2, 4, 5, 6])); + test_selector_in_html(":nth-of-type(4n+1)", seven_ps, + pset([1, 5]), pset([2, 3, 4, 6, 7])); + test_selector_in_html(":nth-last-of-type(4n+1)", seven_ps, + pset([3, 7]), pset([1, 2, 4, 5, 6])); + test_selector_in_html(":nth-child(6)", seven_ps, + pset([6]), pset([1, 2, 3, 4, 5, 7])); + test_selector_in_html(":nth-last-child(6)", seven_ps, + pset([2]), pset([1, 3, 4, 5, 6, 7])); + test_selector_in_html(":nth-of-type(6)", seven_ps, + pset([6]), pset([1, 2, 3, 4, 5, 7])); + test_selector_in_html(":nth-last-of-type(6)", seven_ps, + pset([2]), pset([1, 3, 4, 5, 6, 7])); + + // Test [first|last|only]-[child|node|of-type] + var interesting_doc = "<!----> <div id='p1'> <!---->x<p id='s1'></p> <!----><p id='s2'></p> <!----></div> <!----><p id='p2'> <!----><span id='s3'></span> <!----><span id='s4'></span> <!---->x</p> <!----><div id='p3'> <!----><p id='s5'></p> <!----></div> <!---->"; + function idset(ids) { // takes an array of ids + return function idset_filter(doc) { + var result = []; + for (var id of ids) + result.push(doc.getElementById(id)); + return result; + } + } + function classset(classes) { // takes an array of classes + return function classset_filter(doc) { + var i, j, els; + var result = []; + for (i = 0; i < classes.length; i++) { + els = doc.getElementsByClassName(classes[i]); + for (j = 0; j < els.length; j++) { + result.push(els[j]); + } + } + return result; + } + } + function emptyset(doc) { return []; } + test_parseable(":first-child"); + test_parseable(":last-child"); + test_parseable(":only-child"); + test_parseable(":-moz-first-node"); + test_parseable(":-moz-last-node"); + test_parseable(":first-of-type"); + test_parseable(":last-of-type"); + test_parseable(":only-of-type"); + test_selector_in_html(":first-child", seven_ps, + pset([1]), pset([2, 3, 4, 5, 6, 7])); + test_selector_in_html(":first-child", interesting_doc, + idset(["p1", "s1", "s3", "s5"]), + idset(["s2", "p2", "s4", "p3"])); + test_selector_in_html(":-moz-first-node", interesting_doc, + idset(["p1", "s3", "s5"]), + idset(["s1", "s2", "p2", "s4", "p3"])); + test_selector_in_html(":last-child", seven_ps, + pset([7]), pset([1, 2, 3, 4, 5, 6])); + test_selector_in_html(":last-child", interesting_doc, + idset(["s2", "s4", "p3", "s5"]), + idset(["p1", "s1", "p2", "s3"])); + test_selector_in_html(":-moz-last-node", interesting_doc, + idset(["s2", "p3", "s5"]), + idset(["p1", "s1", "p2", "s3", "s4"])); + test_selector_in_html(":only-child", seven_ps, + pset([]), pset([1, 2, 3, 4, 5, 6, 7])); + test_selector_in_html(":only-child", interesting_doc, + idset(["s5"]), + idset(["p1", "s1", "s2", "p2", "s3", "s4", "p3"])); + test_selector_in_html(":first-of-type", seven_ps, + pset([1]), pset([2, 3, 4, 5, 6, 7])); + test_selector_in_html(":first-of-type", interesting_doc, + idset(["p1", "s1", "p2", "s3", "s5"]), + idset(["s2", "s4", "p3"])); + test_selector_in_html(":last-of-type", seven_ps, + pset([7]), pset([1, 2, 3, 4, 5, 6])); + test_selector_in_html(":last-of-type", interesting_doc, + idset(["s2", "p2", "s4", "p3", "s5"]), + idset(["p1", "s1", "s3"])); + test_selector_in_html(":only-of-type", seven_ps, + pset([]), pset([1, 2, 3, 4, 5, 6, 7])); + test_selector_in_html(":only-of-type", interesting_doc, + idset(["p2", "s5"]), + idset(["p1", "s1", "s2", "s3", "s4", "p3"])); + + // And a bunch of tests for the of-type aspect of :nth-of-type() and + // :nth-last-of-type(). Note that the last div here contains two + // children. + var mixed_elements="<p></p><p></p><div></div><p></p><div><p></p><address></address></div><address></address>"; + function pdaset(ps, divs, addresses) { // takes an array of 1-based indices + var l = { p: ps, div: divs, address: addresses }; + return function pdaset_filter(doc) { + var result = []; + for (var tag in l) { + var a = doc.getElementsByTagName(tag); + var indices = l[tag]; + for (var i in indices) + result.push(a[indices[i] - 1]); + } + return result; + } + } + test_selector_in_html(":nth-of-type(odd)", mixed_elements, + pdaset([1, 3, 4], [1], [1, 2]), + pdaset([2], [2], [])); + test_selector_in_html(":nth-of-type(2n-0)", mixed_elements, + pdaset([2], [2], []), + pdaset([1, 3, 4], [1], [1, 2])); + test_selector_in_html(":nth-last-of-type(even)", mixed_elements, + pdaset([2], [1], []), + pdaset([1, 3, 4], [2], [1, 2])); + + // Test greediness of descendant combinators. + var four_children="<div id='a'><div id='b'><div id='c'><div id='d'><\/div><\/div><\/div><\/div>"; + test_selector_in_html("#a > div div", four_children, + idset(["c", "d"]), idset(["a", "b"])); + test_selector_in_html("#a > #b div", four_children, + idset(["c", "d"]), idset(["a", "b"])); + test_selector_in_html("#a div > div", four_children, + idset(["c", "d"]), idset(["a", "b"])); + test_selector_in_html("#a #b > div", four_children, + idset(["c"]), idset(["a", "b", "d"])); + test_selector_in_html("#a > #b div", four_children, + idset(["c", "d"]), idset(["a", "b"])); + test_selector_in_html("#a #c > div", four_children, + idset(["d"]), idset(["a", "b", "c"])); + test_selector_in_html("#a > #c div", four_children, + idset([]), idset(["a", "b", "c", "d"])); + + // More descendant combinator greediness (bug 511147) + test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="match"></div></div>', + classset(["match"]), classset(["a", "b"])); + test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="x"></div><div class="match"></div></div>', + classset(["match"]), classset(["a", "b", "x"])); + test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"><p>filler filler <i>filler</i> filler</p></div><div class="match"></div></div>', + classset(["match"]), classset(["a", "b", "x"])); + test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="x"><p>filler filler <i>filler</i> filler</p></div><div></div><div class="b"></div><div></div><div class="x"><p>filler filler <i>filler</i> filler</p></div><div class="match"></div></div>', + classset(["match"]), classset(["a", "b", "x"])); + test_selector_in_html(".a > .b ~ .match", '<div class="a"><div class="b"></div><div class="match"></div><div class="match"></div></div>', + classset(["match"]), classset(["a", "b"])); + + test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div><div class="b"></div><div class="nomatch"></div></div></div>', + emptyset, classset(["a", "b", "nomatch"])); + test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div><div class="b"></div><div class="nomatch"></div></div><div class="nomatch"></div></div>', + emptyset, classset(["a", "b", "nomatch"])); + test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div class="b"></div><div><div class="nomatch"></div></div><div></div></div>', + emptyset, classset(["a", "b", "nomatch"])); + test_selector_in_html(".a > .b ~ .nomatch", '<div class="a"><div class="b"></div></div><div class="nomatch"></div>', + emptyset, classset(["a", "b", "nomatch"])); + + // Test serialization of pseudo-elements. + should_serialize_to("p::first-letter", "p::first-letter"); + should_serialize_to("p:first-letter", "p::first-letter"); + should_serialize_to("div>p:first-letter", "div > p::first-letter"); + should_serialize_to("span +div:first-line", "span + div::first-line"); + should_serialize_to("input::placeholder", "input::placeholder"); + should_serialize_to("input:placeholder-shown", "input:placeholder-shown"); + + // Test serialization of non CSS2 pseudo-element. + should_serialize_to("input::-moz-placeholder", "input::-moz-placeholder"); + + // Test default namespaces, including inside :not(). + var html_default_ns = "@namespace url(http://www.w3.org/1999/xhtml);"; + var html_ns = "@namespace html url(http://www.w3.org/1999/xhtml);"; + var xul_default_ns = "@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);"; + var single_a = "<a id='a' href='data:text/plain,this_better_be_unvisited'></a>"; + var set_single = idset(['a']); + var empty_set = idset([]); + test_selector_in_html("a", single_a, set_single, empty_set, + html_default_ns); + test_selector_in_html("a", single_a, empty_set, set_single, + xul_default_ns); + test_selector_in_html("*|a", single_a, set_single, empty_set, + xul_default_ns); + test_selector_in_html("html|a", single_a, set_single, empty_set, + xul_default_ns + html_ns); + // Type selectors inside :not() bring in default namespaces, but + // non-type selectors don't. + test_selector_in_html("*|a:not(*)", single_a, set_single, empty_set, + xul_default_ns); + test_selector_in_html("*|a:not(a)", single_a, set_single, empty_set, + xul_default_ns); + test_selector_in_html("*|a:not(*|*)", single_a, empty_set, set_single, + xul_default_ns); + test_selector_in_html("*|a:not(*|a)", single_a, empty_set, set_single, + xul_default_ns); + test_selector_in_html("*|a:not(:link)", single_a + "<a id='b'></a>", + idset(["b"]), set_single, + xul_default_ns); + test_selector_in_html("*|a:not(:visited)", single_a + "<a id='b'></a>", + idset(["a", "b"]), empty_set, + xul_default_ns); + test_selector_in_html("*|a:not(html|*)", single_a, empty_set, set_single, + xul_default_ns + html_ns); + test_selector_in_html("*|a:not(html|a)", single_a, empty_set, set_single, + xul_default_ns + html_ns); + test_selector_in_html("*|a:not(|*)", single_a, set_single, empty_set, + xul_default_ns + html_ns); + test_selector_in_html("*|a:not(|a)", single_a, set_single, empty_set, + xul_default_ns + html_ns); + test_selector_in_html("html|a:not(|*)", single_a, set_single, empty_set, + xul_default_ns + html_ns); + test_selector_in_html("html|a:not(|a)", single_a, set_single, empty_set, + xul_default_ns + html_ns); + test_selector_in_html("html|a:not(*|*)", single_a, empty_set, set_single, + xul_default_ns + html_ns); + test_selector_in_html("html|a:not(*|a)", single_a, empty_set, set_single, + xul_default_ns + html_ns); + + // Test -moz-locale-dir + test_parseable(":-moz-locale-dir(ltr)"); + test_parseable(":-moz-locale-dir(rtl)"); + test_parseable(":-moz-locale-dir(rTl)"); + test_parseable(":-moz-locale-dir(LTR)"); + test_parseable(":-moz-locale-dir(other)"); + if (document.body.matches(":-moz-locale-dir(ltr)")) { + test_selector_in_html("a:-moz-locale-dir(LTr)", single_a, + set_single, empty_set); + test_selector_in_html("a:-moz-locale-dir(ltR)", single_a, + set_single, empty_set); + test_selector_in_html("a:-moz-locale-dir(LTR)", single_a, + set_single, empty_set); + test_selector_in_html("a:-moz-locale-dir(RTl)", single_a, + empty_set, set_single); + } else { + test_selector_in_html("a:-moz-locale-dir(RTl)", single_a, + set_single, empty_set); + test_selector_in_html("a:-moz-locale-dir(rtL)", single_a, + set_single, empty_set); + test_selector_in_html("a:-moz-locale-dir(RTL)", single_a, + set_single, empty_set); + test_selector_in_html("a:-moz-locale-dir(LTr)", single_a, + empty_set, set_single); + } + test_selector_in_html("a:-moz-locale-dir(other)", single_a, + empty_set, set_single); + + test_balanced_unparseable(":-moz-locale-dir()"); + test_balanced_unparseable(":-moz-locale-dir(())"); + test_balanced_unparseable(":-moz-locale-dir(3())"); + test_balanced_unparseable(":-moz-locale-dir(f{})"); + test_balanced_unparseable(":-moz-locale-dir('ltr')"); + test_balanced_unparseable(":-moz-locale-dir(ltr, other)"); + test_balanced_unparseable(":-moz-locale-dir(ltr other)"); + test_balanced_unparseable(":-moz-locale-dir"); + + // Test :dir() + test_parseable(":dir(ltr)"); + test_parseable(":dir(rtl)"); + test_parseable(":dir(rTl)"); + test_parseable(":dir(LTR)"); + test_parseable(":dir(other)"); + if (document.body.matches(":dir(ltr)")) { + test_selector_in_html("a:dir(LTr)", single_a, + set_single, empty_set); + test_selector_in_html("a:dir(ltR)", single_a, + set_single, empty_set); + test_selector_in_html("a:dir(LTR)", single_a, + set_single, empty_set); + test_selector_in_html("a:dir(RTl)", single_a, + empty_set, set_single); + } else { + test_selector_in_html("a:dir(RTl)", single_a, + set_single, empty_set); + test_selector_in_html("a:dir(rtL)", single_a, + set_single, empty_set); + test_selector_in_html("a:dir(RTL)", single_a, + set_single, empty_set); + test_selector_in_html("a:dir(LTr)", single_a, + empty_set, set_single); + } + test_selector_in_html("a:dir(other)", single_a, + empty_set, set_single); + + test_balanced_unparseable(":dir()"); + test_balanced_unparseable(":dir(())"); + test_balanced_unparseable(":dir(3())"); + test_balanced_unparseable(":dir(f{})"); + test_balanced_unparseable(":dir('ltr')"); + test_balanced_unparseable(":dir(ltr, other)"); + test_balanced_unparseable(":dir(ltr other)"); + test_balanced_unparseable(":dir"); + + // Test -moz-lwtheme and -moz-lwtheme-[darktext|brighttext] + test_parseable(":-moz-lwtheme"); + test_parseable(":-moz-lwtheme-brighttext"); + test_parseable(":-moz-lwtheme-darktext"); + + test_parseable(":-moz-tree-row(selected)"); + test_parseable("::-moz-tree-row(selected)"); + test_parseable(":-moz-tree-row(selected focus)"); + test_parseable(":-moz-tree-row(selected , focus)"); + test_parseable("::-moz-tree-row(selected ,focus)"); + test_parseable(":-moz-tree-row(selected, focus)"); + test_parseable("::-moz-tree-row(selected,focus)"); + test_parseable(":-moz-tree-row(selected focus)"); + test_parseable("::-moz-tree-row(selected , focus)"); + test_parseable("::-moz-tree-twisty( hover open )"); + test_balanced_unparseable("::-moz-tree-row(selected {[]} )"); + test_balanced_unparseable(":-moz-tree-twisty(open())"); + test_balanced_unparseable("::-moz-tree-twisty(hover ())"); + + test_parseable(":-moz-window-inactive"); + test_parseable("div p:-moz-window-inactive:hover span"); + + // Chrome-only + test_unbalanced_unparseable(":-moz-browser-frame"); + + // Plugin pseudoclasses are chrome-only: + test_unbalanced_unparseable(":-moz-type-unsupported"); + test_unbalanced_unparseable(":-moz-user-disabled"); + test_unbalanced_unparseable(":-moz-suppressed"); + test_unbalanced_unparseable(":-moz-type-unsupported"); + test_unbalanced_unparseable(":-moz-type-unsupported-platform"); + test_unbalanced_unparseable(":-moz-handler-clicktoplay"); + test_unbalanced_unparseable(":-moz-handler-vulnerable-updatable"); + test_unbalanced_unparseable(":-moz-handler-vulnerable-no-update"); + test_unbalanced_unparseable(":-moz-handler-disabled"); + test_unbalanced_unparseable(":-moz-handler-blocked"); + test_unbalanced_unparseable(":-moz-handler-crashed"); + + // We're not in a UA sheet, so this should be invalid. + test_balanced_unparseable(":-moz-native-anonymous"); + + // Case sensitivity of tag selectors + function setup_cased_spans(body) { + var data = [ + { tag: "span" }, + { tag: "sPaN" }, + { tag: "Span" }, + { tag: "SPAN" }, + { ns: "http://www.w3.org/1999/xhtml", tag: "span" }, + { ns: "http://www.w3.org/1999/xhtml", tag: "sPaN" }, + { ns: "http://www.w3.org/1999/xhtml", tag: "Span" }, + { ns: "http://www.w3.org/1999/xhtml", tag: "SPAN" }, + { ns: "http://example.com/useless", tag: "span" }, + { ns: "http://example.com/useless", tag: "sPaN" }, + { ns: "http://example.com/useless", tag: "Span" }, + { ns: "http://example.com/useless", tag: "SPAN" }, + ] + for (var i in data) { + var ent = data[i]; + var elem; + if ("ns" in ent) { + elem = body.ownerDocument.createElementNS(ent.ns, ent.tag); + } else { + elem = body.ownerDocument.createElement(ent.tag); + } + body.appendChild(elem); + } + } + function bodychildset(indices) { + return function bodychildset_filter(doc) { + var body = doc.body; + var result = []; + for (var i in indices) { + result.push(body.childNodes[indices[i]]); + } + return result; + } + } + test_selector_in_html("span", setup_cased_spans, + bodychildset([0, 1, 2, 3, 4, 8]), + bodychildset([5, 6, 7, 9, 10, 11])); + test_selector_in_html("sPaN", setup_cased_spans, + bodychildset([0, 1, 2, 3, 4, 9]), + bodychildset([5, 6, 7, 8, 10, 11])); + test_selector_in_html("Span", setup_cased_spans, + bodychildset([0, 1, 2, 3, 4, 10]), + bodychildset([5, 6, 7, 8, 9, 11])); + test_selector_in_html("SPAN", setup_cased_spans, + bodychildset([0, 1, 2, 3, 4, 11]), + bodychildset([5, 6, 7, 8, 9, 10])); + + // bug 528096 (tree pseudos) + test_unbalanced_unparseable(":-moz-tree-column((){} a"); + test_unbalanced_unparseable(":-moz-tree-column(x(){} a"); + test_unbalanced_unparseable(":-moz-tree-column(a b (){} a"); + test_unbalanced_unparseable(":-moz-tree-column(a, b (){} a"); + + // Bug 543428 (escaping) + test_selector_in_html("\\32|a", single_a, set_single, empty_set, + "@namespace \\32 url(http://www.w3.org/1999/xhtml);"); + test_selector_in_html("-\\32|a", single_a, set_single, empty_set, + "@namespace -\\32 url(http://www.w3.org/1999/xhtml);"); + test_selector_in_html("\\2|a", single_a, set_single, empty_set, + "@namespace \\0002 url(http://www.w3.org/1999/xhtml);"); + test_selector_in_html("-\\2|a", single_a, set_single, empty_set, + "@namespace -\\000002 url(http://www.w3.org/1999/xhtml);"); + var spans = "<span class='2'></span><span class=''></span>" + + "<span id='2'></span><span id=''></span>" + test_selector_in_html(".\\32", spans, + bodychildset([0]), bodychildset([1, 2, 3])); + test_selector_in_html("[class=\\32]", spans, + bodychildset([0]), bodychildset([1, 2, 3])); + test_selector_in_html(".\\2", spans, + bodychildset([1]), bodychildset([0, 2, 3])); + test_selector_in_html("[class=\\2]", spans, + bodychildset([1]), bodychildset([0, 2, 3])); + test_selector_in_html("#\\32", spans, + bodychildset([2]), bodychildset([0, 1, 3])); + test_selector_in_html("[id=\\32]", spans, + bodychildset([2]), bodychildset([0, 1, 3])); + test_selector_in_html("#\\2", spans, + bodychildset([3]), bodychildset([0, 1, 2])); + test_selector_in_html("[id=\\2]", spans, + bodychildset([3]), bodychildset([0, 1, 2])); + test_balanced_unparseable("#2"); + + // Bug 553805: :not() containing nothing is forbidden + test_balanced_unparseable(":not()"); + test_balanced_unparseable(":not( )"); + test_balanced_unparseable(":not( \t\n )"); + test_balanced_unparseable(":not(/*comment*/)"); + test_balanced_unparseable(":not( /*comment*/ /* comment */ )"); + test_balanced_unparseable("p :not()"); + test_balanced_unparseable("p :not( )"); + test_balanced_unparseable("p :not( \t\n )"); + test_balanced_unparseable("p :not(/*comment*/)"); + test_balanced_unparseable("p :not( /*comment*/ /* comment */ )"); + test_balanced_unparseable("p:not()"); + test_balanced_unparseable("p:not( )"); + test_balanced_unparseable("p:not( \t\n )"); + test_balanced_unparseable("p:not(/*comment*/)"); + test_balanced_unparseable("p:not( /*comment*/ /* comment */ )"); + + test_balanced_unparseable(":not(:nth-child(2k))"); + test_balanced_unparseable(":not(:nth-child(()))"); + + // :-moz-any() + test_balanced_unparseable(":-moz-any()"); + test_balanced_unparseable(":-moz-any(div p)"); + test_balanced_unparseable(":-moz-any(div ~ p)"); + test_balanced_unparseable(":-moz-any(div~p)"); + test_balanced_unparseable(":-moz-any(div + p)"); + test_balanced_unparseable(":-moz-any(div+p)"); + test_balanced_unparseable(":-moz-any(div > p)"); + test_balanced_unparseable(":-moz-any(div>p)"); + test_parseable(":-moz-any(div, p)"); + test_parseable(":-moz-any( div , p )"); + test_parseable(":-moz-any(div,p)"); + test_parseable(":-moz-any(div)"); + test_parseable(":-moz-any(div,p,:link,span:focus)"); + test_parseable(":-moz-any(:active,:focus)"); + test_parseable(":-moz-any(:active,:link:focus)"); + test_balanced_unparseable(":-moz-any(div,:nonexistentpseudo)"); + var any_elts = "<input type='text'><a href='http://www.example.com/'></a><div></div><a name='foo'>"; + test_selector_in_html(":-moz-any(a,input)", any_elts, + bodychildset([0, 1, 3]), bodychildset([2])); + test_selector_in_html(":-moz-any(:link,:not(a))", any_elts, + bodychildset([0, 1, 2]), bodychildset([3])); + test_selector_in_html(":-moz-any([href],input[type],input[name])", any_elts, + bodychildset([0, 1]), bodychildset([2, 3])); + test_selector_in_html(":-moz-any(div,a):-moz-any([type],[href],[name])", + any_elts, + bodychildset([1, 3]), bodychildset([0, 2])); + + test_selector_in_html(":-moz-table-border-nonzero", + "<p></p>" + + "<p border='2'></p>" + + "<table border='2'></table>" + + "<table border></table>" + + "<table></table>" + + "<table frame='border'></table>" + + "<table border='0'></table>" + + "<table border='0pt'></table>" + + "<table border='3pt'></table>", + bodychildset([2, 3, 8]), + bodychildset([0, 1, 4, 5, 6, 7])); + + // Test that we don't tokenize an empty HASH. + test_balanced_unparseable("#"); + test_balanced_unparseable("# "); + test_balanced_unparseable("#, p"); + test_balanced_unparseable("# , p"); + test_balanced_unparseable("p #"); + test_balanced_unparseable("p # "); + test_balanced_unparseable("p #, p"); + test_balanced_unparseable("p # , p"); + + // Test that a backslash alone at EOF outside of a string is treated + // as U+FFFD. + test_parseable_via_api("#a\\"); + test_parseable_via_api("#\\"); + test_parseable_via_api("\\"); + + // Test that newline escapes are only supported in strings. + test_balanced_unparseable("di\\\nv"); + test_balanced_unparseable("div \\\n p"); + test_balanced_unparseable("div\\\n p"); + test_balanced_unparseable("div \\\np"); + test_balanced_unparseable("div\\\np"); + + // Test that :-moz-placeholder is parsable. + test_parseable(":-moz-placeholder"); + + // Test that things other than user-action pseudo-classes are + // rejected after pseudo-elements. Some of these tests rely on + // using a pseudo-element that supports a user-action pseudo-class + // after it, so we need to use the prefixed ::-moz-color-swatch, + // which is one of the ones with + // CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE (none of which are + // unprefixed). + test_parseable("::-moz-color-swatch:hover"); + test_balanced_unparseable("::-moz-color-swatch:not(.foo)"); + test_balanced_unparseable("::-moz-color-swatch:first-child"); + test_balanced_unparseable("::-moz-color-swatch:hover#foo"); + test_balanced_unparseable(".foo::after:not(.bar) ~ h3"); + + run_deferred_tests(); +} + +var deferred_tests = []; + +function defer_clonedoc_tests(docurl, onloadfunc) +{ + deferred_tests.push( { docurl: docurl, onloadfunc: onloadfunc } ); +} + +function run_deferred_tests() +{ + if (deferred_tests.length == 0) { + SimpleTest.finish(); + return; + } + + cloneiframe.onload = deferred_tests_onload; + cloneiframe.src = deferred_tests[0].docurl; +} + +function deferred_tests_onload(event) +{ + if (event.target != cloneiframe) + return; + + deferred_tests[0].onloadfunc(); + deferred_tests.shift(); + + run_deferred_tests(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_selectors_on_anonymous_content.html b/layout/style/test/test_selectors_on_anonymous_content.html new file mode 100644 index 000000000..a4975f6b8 --- /dev/null +++ b/layout/style/test/test_selectors_on_anonymous_content.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for CSS Selectors</title> + <!-- + Separate from test_selectors.html so we don't need to deal with + waiting for the binding document to load. + --> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + + #display { -moz-binding: url(xbl_bindings.xml#onedivchild); } + + </style> +</head> +<body onload="run()"> +<div id="display"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function run() { + + function setup_style() { + var style_elem = document.createElement("style"); + style_elem.setAttribute("type", "text/css"); + document.getElementsByTagName("head")[0].appendChild(style_elem); + var style_text = document.createTextNode(""); + style_elem.appendChild(style_text); + return style_text; + } + + var style_text = setup_style(); + + var gCounter = 0; + + function test_selector(selector, matches_docdiv, matches_anondiv) + { + var zi = ++gCounter; + style_text.data = selector + "{ z-index: " + zi + " }"; + + var doc_div = document.getElementById("display"); + var anon_div = SpecialPowers.wrap(document).getAnonymousNodes(doc_div)[0]; + var should_match = []; + var should_not_match = []; + (matches_docdiv ? should_match : should_not_match).push(doc_div); + (matches_anondiv ? should_match : should_not_match).push(anon_div); + + for (var i = 0; i < should_match.length; ++i) { + var e = should_match[i]; + is(SpecialPowers.wrap(window).getComputedStyle(e, "").zIndex, String(zi), + "element matched " + selector); + } + for (var i = 0; i < should_not_match.length; ++i) { + var e = should_not_match[i]; + is(SpecialPowers.wrap(window).getComputedStyle(e, "").zIndex, "auto", + "element did not match " + selector); + } + + style_text.data = ""; + } + + // Test that the root of an XBL1 anonymous content subtree doesn't + // match :nth-child(). + test_selector("div.anondiv", false, true); + test_selector("div:nth-child(odd)", true, false); + test_selector("div:nth-child(even)", false, false); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> + diff --git a/layout/style/test/test_setPropertyWithNull.html b/layout/style/test/test_setPropertyWithNull.html new file mode 100644 index 000000000..74de52cbe --- /dev/null +++ b/layout/style/test/test_setPropertyWithNull.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=830260 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 830260</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 830260 **/ + var div = document.createElement("div"); + div.style.color = "green"; + div.style.color = "null"; + is(div.style.color, "green", 'Assigning "null" as a color should not parse'); + div.style.setProperty("color", "null"); + is(div.style.color, "green", + 'Passing "null" as a color to setProperty should not parse'); + + div.style.setProperty("color", null); + is(div.style.color, "", + 'Passing null as a color to setProperty should remove the property'); + + div.style.color = "green"; + is(div.style.color, "green", 'Assigning "green" as a color should parse'); + + div.style.color = null; + is(div.style.color, "", + 'Assigning null as a color should remove the property'); + + + + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=830260">Mozilla Bug 830260</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_shorthand_property_getters.html b/layout/style/test/test_shorthand_property_getters.html new file mode 100644 index 000000000..da4d8de3a --- /dev/null +++ b/layout/style/test/test_shorthand_property_getters.html @@ -0,0 +1,268 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=376075 +--> +<head> + <title>Test for Bug 376075</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=376075">Mozilla Bug 376075</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 376075 **/ + +var e = document.getElementById("display"); + +var borderExtras = ";-moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none; -moz-border-left-colors: none; border-image: none"; + +// Test that we only serialize the 'border' shorthand when appropriate. +e.setAttribute("style", "border-left: medium solid blue; border-right: medium solid blue; border-top: medium blue solid; border-bottom: blue medium solid" + borderExtras); +isnot(e.style.border, "", "should be able to serialize border"); +e.setAttribute("style", "border-left: medium solid blue; border-right: medium solid blue; border-top: medium blue solid; border-bottom: green medium solid" + borderExtras); +is(e.style.border, "", "should not be able to serialize border"); +e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: green" + borderExtras); +isnot(e.style.border, "", "should be able to serialize border"); +e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: green blue blue blue" + borderExtras); +is(e.style.border, "", "should not be able to serialize border"); +e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue green blue blue" + borderExtras); +is(e.style.border, "", "should not be able to serialize border"); +e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue blue green blue" + borderExtras); +is(e.style.border, "", "should not be able to serialize border"); +e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid; border-color: blue blue blue green" + borderExtras); +is(e.style.border, "", "should not be able to serialize border"); +e.setAttribute("style", "border-width: 3px 2px 3px 3px; border-style: solid; border-color: green" + borderExtras); +is(e.style.border, "", "should not be able to serialize border"); +e.setAttribute("style", "border-width: 3px 3px 3px 3px; border-style: solid dashed; border-color: green" + borderExtras); +is(e.style.border, "", "should not be able to serialize border"); + +// Test suppression of currentcolor in border shorthands. +e.setAttribute("style", "border: medium solid"); +ok(e.style.border == "medium solid" || + e.style.border == "solid medium", + "implied default color omitted serializing border"); +ok(e.style.borderLeft == "medium solid" || + e.style.borderLeft == "solid medium", + "implied default color omitted serializing border-left"); +ok(e.style.cssText == "border: medium solid;" || + e.style.cssText == "border: solid medium;", + "implied default color omitted serializing declaration"); +e.setAttribute("style", "border-right: medium solid"); +ok(e.style.borderRight == "medium solid" || + e.style.borderRight == "solid medium", + "implied default color omitted serializing border-right"); +ok(e.style.borderRight == "medium solid" || + e.style.borderRight == "solid medium", + "implied default color omitted serializing border-right"); +ok(e.style.cssText == "border-right: medium solid;" || + e.style.cssText == "border-right: solid medium;", + "implied default color omitted serializing declaration"); + +// Test that we shorten box properties to the shortest possible. +e.setAttribute("style", "margin: 7px"); +is(e.style.margin, "7px", "should condense to shortest possible"); +is(e.style.cssText, "margin: 7px;", "should condense to shortest possible"); +e.setAttribute("style", "padding: 7px 7px 7px"); +is(e.style.padding, "7px", "should condense to shortest possible"); +is(e.style.cssText, "padding: 7px;", "should condense to shortest possible"); +e.setAttribute("style", "border-width: 7px 7px 7px 7px"); +is(e.style.borderWidth, "7px", "should condense to shortest possible"); +is(e.style.cssText, "border-width: 7px;", "should condense to shortest possible"); +e.setAttribute("style", "margin: 7px 7px 7px 6px"); +is(e.style.margin, "7px 7px 7px 6px", "should not condense"); +is(e.style.cssText, "margin: 7px 7px 7px 6px;", "should not condense"); +e.setAttribute("style", "border-style: solid dotted none dotted"); +is(e.style.borderStyle, "solid dotted none", "should condense"); +is(e.style.cssText, "border-style: solid dotted none;", "should condense"); +e.setAttribute("style", "border-color: green blue"); +is(e.style.borderColor, "green blue", "should condense"); +is(e.style.cssText, "border-color: green blue;", "should condense"); +e.setAttribute("style", "border-color: green blue green"); +is(e.style.borderColor, "green blue", "should condense"); +is(e.style.cssText, "border-color: green blue;", "should condense"); +e.setAttribute("style", "border-color: green blue green blue"); +is(e.style.borderColor, "green blue", "should condense"); +is(e.style.cssText, "border-color: green blue;", "should condense"); +e.setAttribute("style", "border-color: currentColor currentColor currentcolor CURRENTcolor"); +is(e.style.borderColor, "currentcolor", "should condense to canonical case"); +is(e.style.cssText, "border-color: currentcolor;", "should condense to canonical case"); +e.setAttribute("style", "border-style: ridge none none none"); +is(e.style.borderStyle, "ridge none none", "should condense"); +is(e.style.cssText, "border-style: ridge none none;", "should condense"); +e.setAttribute("style", "border-radius: 1px 1px 1px 1px"); +is(e.style.borderRadius, "1px", "should condense to shortest possible"); +is(e.style.cssText, "border-radius: 1px;", "should condense to shortest possible"); +e.setAttribute("style", "border-radius: 1px 1px 1px 1px / 2px 2px 2px 2px"); +is(e.style.borderRadius, "1px / 2px", "should condense to shortest possible"); +is(e.style.cssText, "border-radius: 1px / 2px;", "should condense to shortest possible"); +e.setAttribute("style", "border-radius: 1px 2px 1px 2px / 1% 2% 3% 2%"); +is(e.style.borderRadius, "1px 2px / 1% 2% 3%", "should condense to shortest possible"); +is(e.style.cssText, "border-radius: 1px 2px / 1% 2% 3%;", "should condense to shortest possible"); + +// Test that we refuse to serialize the 'background' and 'font' +// shorthands when some subproperties that can't be expressed in the +// shorthand syntax are present. +e.setAttribute("style", "font: medium serif"); +isnot(e.style.font, "", "should have font shorthand"); +e.setAttribute("style", "font: medium serif; font-size-adjust: 0.45"); +is(e.style.font, "", "should not have font shorthand"); +e.setAttribute("style", "font: medium serif; font-feature-settings: 'liga' off"); +is(e.style.font, "", "should not have font shorthand"); + +// Test that all combinations of background-clip and background-origin +// can be expressed in the shorthand (which wasn't the case previously). +e.setAttribute("style", "background: red"); +isnot(e.style.background, "", "should have background shorthand"); +e.setAttribute("style", "background: red; background-origin: border-box"); +isnot(e.style.background, "", "should have background shorthand (origin:border-box)"); +e.setAttribute("style", "background: red; background-clip: padding-box"); +isnot(e.style.background, "", "should have background shorthand (clip:padding-box)"); +e.setAttribute("style", "background: red; background-origin: content-box"); +isnot(e.style.background, "", "should have background shorthand (origin:content-box)"); +e.setAttribute("style", "background: red; background-clip: content-box"); +isnot(e.style.background, "", "should have background shorthand (clip:content-box)"); +e.setAttribute("style", "background: red; background-clip: content-box; background-origin: content-box;"); +isnot(e.style.background, "", "should have background shorthand (clip:content-box;origin:content-box)"); + +// Test background-size in the background shorthand. +e.setAttribute("style", "background: red; background-size: 100% 100%"); +isnot(e.style.background, "", "should have background shorthand (size:100% 100%)"); +e.setAttribute("style", "background: red; background-size: 100% auto"); +isnot(e.style.background, "", "should have background shorthand (size:100% auto)"); +e.setAttribute("style", "background: red; background-size: auto 100%"); +isnot(e.style.background, "", "should have background shorthand (size:auto 100%)"); + +// Check that we only serialize background when the lists (of layers) for +// the subproperties are the same length. +e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat"); +isnot(e.style.background, "", "should have background shorthand (all lists length 3)"); +e.setAttribute("style", "background-clip: border-box, padding-box, border-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat"); +is(e.style.background, "", "should not have background shorthand (background-clip too long)"); +e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat"); +is(e.style.background, "", "should not have background shorthand (background-origin too short)"); +e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png), none; background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat"); +is(e.style.background, "", "should not have background shorthand (background-image too long)"); +e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat"); +is(e.style.background, "", "should not have background shorthand (background-attachment too short)"); +e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px, bottom; background-repeat: repeat-x, repeat, no-repeat"); +is(e.style.background, "", "should not have background shorthand (background-position too long)"); +e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat"); +is(e.style.background, "", "should not have background shorthand (background-repeat too short)"); +e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat"); +is(e.style.background, "", "should not have background shorthand (background-size too long)"); + +// Check that we only serialize background-position when the lists (of layers) for +// the -x/-y subproperties are the same length. +e.setAttribute("style", "background-position-x: 10%, left 2em, right; background-position-y: top 2em, bottom, 10%"); +is(e.style.backgroundPosition, "left 10% top 2em, left 2em bottom, right 10%", "should have background-position shorthand (both lists length 3)"); +e.setAttribute("style", "background-position-x: 10%, left 2em; background-position-y: top 2em, bottom, 10%"); +is(e.style.backgroundPosition, "", "should not have background-position shorthand (background-position-x too short)"); +e.setAttribute("style", "background-position-x: 10%, left 2em, right; background-position-y: top 2em"); +is(e.style.backgroundPosition, "", "should not have background-position shorthand (background-position-y too short)"); + +// Check that background-position serialization doesn't produce invalid values. +e.setAttribute("style", "background-position: 0px"); +is(e.style.backgroundPosition, "0px center", "1-value form should be accepted, with implied center value for background-position-y"); +e.setAttribute("style", "background-position: 0px center"); +is(e.style.backgroundPosition, "0px center", "2-value form 'x-offset' 'y-edge' should be accepted, and serialize to 2-value form"); +e.setAttribute("style", "background-position: left 0px center"); +is(e.style.backgroundPosition, "left 0px center", "3-value form 'x-edge' 'x-offset' 'y-edge' should be accepted and serialize to 3-value form"); +e.setAttribute("style", "background-position: left top 0px"); +is(e.style.backgroundPosition, "left top 0px", "3-value form 'x-edge' 'y-edge' 'y-offset' should be accepted and serialize to 3-value form"); +e.setAttribute("style", "background-position: left 0px top 0px"); +is(e.style.backgroundPosition, "left 0px top 0px", "4-value form should be accepted and serialize to 4-value form"); +e.setAttribute("style", "background-position-x: 0px; background-position-y: center"); +is(e.style.backgroundPosition, "0px center", "should always serialize to 2-value form if setting -x and -y with the 1-value form"); +e.setAttribute("style", "background-position-x: 0px; background-position-y: 0px"); +is(e.style.backgroundPosition, "0px 0px", "should always serialize to 2-value form if setting -x and -y with the 1-value form"); +e.setAttribute("style", "background-position-x: center; background-position-y: 0px"); +is(e.style.backgroundPosition, "center 0px", "should always serialize to 2-value form if setting -x and -y with the 1-value form"); +e.setAttribute("style", "background-position-x: left; background-position-y: top"); +is(e.style.backgroundPosition, "left top", "should always serialize to 2-value form if setting -x and -y with the 1-value form"); +e.setAttribute("style", "background-position-x: left 0px; background-position-y: center"); +is(e.style.backgroundPosition, "left 0px center", "should always serialize to 3-value form if both -x and -y specified an edge"); +e.setAttribute("style", "background-position-x: right; background-position-y: top 0px"); +is(e.style.backgroundPosition, "right top 0px", "should always serialize to 3-value form if both -x and -y specified an edge"); +e.setAttribute("style", "background-position-x: left 0px; background-position-y: 0px"); +is(e.style.backgroundPosition, "left 0px top 0px", "should serialize to 4-value form if 3-value form would only have one edge"); +e.setAttribute("style", "background-position-x: 0px; background-position-y: top 0px"); +is(e.style.backgroundPosition, "left 0px top 0px", "should serialize to 4-value form if 3-value form would only have one edge"); + +// Check that we only serialize transition when the lists are the same length. +e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s"); +isnot(e.style.transition, "", "should have transition shorthand (lists same length)"); +e.setAttribute("style", "transition-property: color, width, left; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s"); +is(e.style.transition, "", "should not have transition shorthand (lists different lengths)"); +e.setAttribute("style", "transition-property: all; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s"); +is(e.style.transition, "", "should not have transition shorthand (lists different lengths)"); +e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms, 300ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s"); +is(e.style.transition, "", "should not have transition shorthand (lists different lengths)"); +e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear, ease-out; transition-delay: 0s, 1s"); +is(e.style.transition, "", "should not have transition shorthand (lists different lengths)"); +e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s, 0s"); +is(e.style.transition, "", "should not have transition shorthand (lists different lengths)"); + +e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;"); +isnot(e.style.animation, "", "should have animation shorthand (lists same length)"); +e.setAttribute("style", "animation-name: bounce, roll, left; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); +e.setAttribute("style", "animation-name: bounce; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); +e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms, 300ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); +e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear, ease-out; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); +e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s, 0s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); +e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); +e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards, both; animation-iteration-count: infinite, 2; animation-play-state: paused, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); +e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2, 1; animation-play-state: paused, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); +e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running, running;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); + +// Check that the 'border' shorthand resets 'border-image' and +// '-moz-border-*-colors', but that other 'border-*' shorthands don't +// (bug 482692). +e.setAttribute("style", 'border-image: url("foo.png") 5 5 5 5 / 5 5 5 5 / 5 5 5 5 repeat repeat; border-left: medium solid green'); +is(e.style.cssText, + 'border-image: url("foo.png") 5 5 5 5 / 5 5 5 5 / 5 5 5 5 repeat repeat; border-left: medium solid green;', + "border-left does NOT reset border-image"); +e.setAttribute("style", 'border-image: url("foo.png") 5 5 5 5; border: medium solid green'); +is(e.style.cssText, + 'border: medium solid green;', + "border DOES reset border-image"); +e.setAttribute("style", '-moz-border-left-colors: fuchsia blue; border-left: medium solid green'); +is(e.style.cssText, + '-moz-border-left-colors: fuchsia blue; border-left: medium solid green;', + "border-left does NOT reset -moz-border-left-colors"); +e.setAttribute("style", '-moz-border-left-colors: fuchsia blue; border: medium solid green'); +is(e.style.cssText, + 'border: medium solid green;', + "border DOES reset -moz-border-left-colors"); + +// Test that the color goes at the beginning of the last item of the +// background shorthand. +// FIXME: Really the "repeat scroll 0% 0%" shouldn't be there. +e.setAttribute("style", "background: url(foo.png) blue"); +is(e.style.cssText, + "background: blue url(\"foo.png\") repeat scroll 0% 0%;", + "color should be at start of single-item background"); +e.setAttribute("style", "background: url(bar.png), url(foo.png) blue"); +is(e.style.cssText, + "background: url(\"bar.png\") repeat scroll 0% 0%, blue url(\"foo.png\") repeat scroll 0% 0%;", + "color should be at start of single-item background"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_specified_value_serialization.html b/layout/style/test/test_specified_value_serialization.html new file mode 100644 index 000000000..edf84fe8d --- /dev/null +++ b/layout/style/test/test_specified_value_serialization.html @@ -0,0 +1,105 @@ +<!doctype html> +<html> +<head> + <title>Test for miscellaneous specified value issues</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> + +<pre id="test"> +<script type="application/javascript"> + +(function test_bug_721136() { + // Test for transform property serialization. + [ + [" mAtRiX(1, 2,3,4, 5,6 ) ", "matrix(1, 2, 3, 4, 5, 6)"], + [" mAtRiX3d( 1,2,3,0,4 ,5,6,0,7,8 , 9,0,10, 11,12,1 ) ", + "matrix3d(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 10, 11, 12, 1)"], + [" pErSpEcTiVe( 400Px ) ", "perspective(400px)"], + [" rOtAtE( 90dEg ) ", "rotate(90deg)"], + [" rOtAtE3d( 0,0 , 1 ,180DeG ) ", "rotate3d(0, 0, 1, 180deg)"], + [" rOtAtEx( 100GrAD ) ", "rotateX(100grad)"], + [" rOtAtEy( 1.57RaD ) ", "rotateY(1.57rad)"], + [" rOtAtEz( 0.25TuRn ) ", "rotateZ(0.25turn)"], + [" sCaLe( 2 ) ", "scale(2)"], + [" sCaLe( 2,3 ) ", "scale(2, 3)"], + [" sCaLe3D( 2,4 , -9 ) ", "scale3d(2, 4, -9)"], + [" sCaLeX( 2 ) ", "scaleX(2)"], + [" sCaLeY( 2 ) ", "scaleY(2)"], + [" sCaLeZ( 2 ) ", "scaleZ(2)"], + [" sKeW( 45dEg ) ", "skew(45deg)"], + [" sKeW( 45dEg,45DeG ) ", "skew(45deg, 45deg)"], + [" sKeWx( 45DeG ) ", "skewX(45deg)"], + [" sKeWy( 45DeG ) ", "skewY(45deg)"], + [" tRaNsLaTe( 1Px ) ", "translate(1px)"], + [" tRaNsLaTe( 1Px,3Pt ) ", "translate(1px, 3pt)"], + [" tRaNsLaTe3D( 21pX,-6pX , 4pX ) ", "translate3d(21px, -6px, 4px)"], + [" tRaNsLaTeX( 1pT ) ", "translateX(1pt)"], + [" tRaNsLaTeY( 1iN ) ", "translateY(1in)"], + [" tRaNsLaTeZ( 15.4pX ) ", "translateZ(15.4px)"], + ["tranSlatex( 16px )rotatez(-90deg) rotate(100grad)\ttranslate3d(12pt, 0pc, 0.0em)", + "translateX(16px) rotateZ(-90deg) rotate(100grad) translate3d(12pt, 0pc, 0em)"], + ].forEach(function(arr) { + document.documentElement.style.MozTransform = arr[0]; + is(document.documentElement.style.MozTransform, arr[1], + "incorrect serialization"); + }); + + var elt = document.documentElement; + + elt.setAttribute("style", + "transform: tRANslatEX(5px) TRanslATey(10px) translatez(2px) ROTATEX(30deg) rotateY(30deg) rotatez(5deg) SKEWx(10deg) skewy(10deg) scaleX(2) SCALEY(0.5) scalez(2)"); + is(elt.style.getPropertyValue("transform"), + "translateX(5px) translateY(10px) translateZ(2px) rotateX(30deg) rotateY(30deg) rotateZ(5deg) skewX(10deg) skewY(10deg) scaleX(2) scaleY(0.5) scaleZ(2)", + "expected case canonicalization of transform functions"); + + elt.setAttribute("style", + "font-variant-alternates: SWASH(fOo) stYLIStiC(Bar)"); + is(elt.style.getPropertyValue("font-variant-alternates"), + "swash(fOo) stylistic(Bar)", + "expected case canonicalization of transform functions"); + + elt.setAttribute("style", ""); // leave the page in a useful state +})(); + +(function test_bug_1347164() { + // Test that specified color values are serialized as "rgb()" + // IFF they're fully-opaque (and otherwise as "rgba()"). + var color = [ + ["rgba(0, 0, 0, 1)", "rgb(0, 0, 0)"], + ["rgba(0, 0, 0, 0.5)", "rgba(0, 0, 0, 0.5)"], + ["hsla(0, 0%, 0%, 1)", "rgb(0, 0, 0)"], + ["hsla(0, 0%, 0%, 0.5)", "rgba(0, 0, 0, 0.5)"], + // css-color-4 + ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["rgba(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"], + ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["rgb(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"], + ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"], + ["hsla(0deg 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"], + ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"], + ["hsl(0 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"], + ]; + + var frame_container = document.getElementById("display"); + var p = document.createElement("p"); + frame_container.appendChild(p); + + for (var i = 0; i < color.length; ++i) { + var test = color[i]; + p.style.color = test[0]; + is(p.style.color, test[1], "serialization value of " + test[0]); + } + + p.remove(); +})(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_style_attribute_quirks.html b/layout/style/test/test_style_attribute_quirks.html new file mode 100644 index 000000000..d0941042f --- /dev/null +++ b/layout/style/test/test_style_attribute_quirks.html @@ -0,0 +1,18 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=915093 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 915093</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="style_attribute_tests.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=915093">Mozilla Bug 915093</a> +<div id="content"></div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_style_attribute_standards.html b/layout/style/test/test_style_attribute_standards.html new file mode 100644 index 000000000..d49e7ecd9 --- /dev/null +++ b/layout/style/test/test_style_attribute_standards.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=915093 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 915093</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="style_attribute_tests.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=915093">Mozilla Bug 915093</a> +<div id="content"></div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_style_struct_copy_constructors.html b/layout/style/test/test_style_struct_copy_constructors.html new file mode 100644 index 000000000..b020b0844 --- /dev/null +++ b/layout/style/test/test_style_struct_copy_constructors.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for style struct copy constructors</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <style type="text/css" id="stylesheet"></style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"><span id="one"></span><span id="two"></span><span id="parent"><span id="child"></span></span></p> +<div id="content" style="display: none"> + +<div id="testnode"><span id="element"></span></div> + + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for style struct copy constructors **/ + +/** + * XXX Why doesn't putting a bug in the nsStyleFont copy-constructor for + * font-weight (initializing to normal) trigger a failure of this test? + * It works for leaving -moz-image-region uninitialized (both halves), + * overwriting text-decoration (only the first half, since it's not + * inherited), and leaving visibility uninitialized (only the second + * half; passes the first half ok). + */ + +var gElementOne = document.getElementById("one"); +var gElementTwo = document.getElementById("two"); +var gElementParent = document.getElementById("parent"); +var gElementChild = document.getElementById("child"); +var gStyleSheet = document.getElementById("stylesheet").sheet; +var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#one, #two, #parent {}", gStyleSheet.cssRules.length)]; +var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#two, #child {}", gStyleSheet.cssRules.length)]; + +/** Test using aStartStruct **/ + +for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (!("subproperties" in info)) { + gRule1.style.setProperty(prop, info.other_values[0], ""); + gRule2.style.setProperty(prop, info.other_values[0], ""); + } +} + +for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (!("subproperties" in info)) { + gRule2.style.removeProperty(prop); + + var one = getComputedStyle(gElementOne, "").getPropertyValue(prop); + var two = getComputedStyle(gElementTwo, "").getPropertyValue(prop); + is(two, one, + "property '" + prop + "' was copy-constructed correctly (aStartStruct)"); + + gRule2.style.setProperty(prop, info.other_values[0], ""); + } +} + +/** Test using inheritance **/ +for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (info.inherited && !("subproperties" in info)) { + gRule2.style.removeProperty(prop); + + var parent = getComputedStyle(gElementParent, "").getPropertyValue(prop); + var child = getComputedStyle(gElementChild, "").getPropertyValue(prop); + + is(child, parent, + "property '" + prop + "' was copy-constructed correctly (inheritance)"); + + gRule2.style.setProperty(prop, info.other_values[0], ""); + } +} + +for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (!("subproperties" in info)) { + gRule1.style.removeProperty(prop); + gRule2.style.removeProperty(prop); + } +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_supports_rules.html b/layout/style/test/test_supports_rules.html new file mode 100644 index 000000000..834671375 --- /dev/null +++ b/layout/style/test/test_supports_rules.html @@ -0,0 +1,88 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=649740 +--> +<head> + <title>Test for Bug 649740</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="style"> + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=649740">Mozilla Bug 649740</a> +<p id="display1"></p> +<p id="display2"></p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 649740 **/ + +function condition(s) { + return s.replace(/^@supports\s*/, '').replace(/ \s*{\s*}\s*$/, ''); +} + +var styleSheetText = + "@supports(color: green){ }\n" + + "@supports (color: green) { }\n" + + "@supports ((color: green)) { }\n" + + "@supports (color: green) and (color: blue) { }\n" + + "@supports ( Font: 20px serif ! Important) { }"; + +function runTest() { + var style = document.getElementById("style"); + style.textContent = styleSheetText; + + var sheet = style.sheet; + + is(condition(sheet.cssRules[0].cssText), "(color: green)"); + is(condition(sheet.cssRules[1].cssText), "(color: green)"); + is(condition(sheet.cssRules[2].cssText), "((color: green))"); + is(condition(sheet.cssRules[3].cssText), "(color: green) and (color: blue)"); + is(condition(sheet.cssRules[4].cssText), "( Font: 20px serif ! Important)"); + + var cs1 = getComputedStyle(document.getElementById("display1"), ""); + var cs2 = getComputedStyle(document.getElementById("display2"), ""); + function check_balanced_condition(condition, expected_match) { + style.textContent = "#display1, #display2 { text-decoration: overline }\n" + + "@supports " + condition + "{\n" + + " #display1 { text-decoration: line-through }\n" + + "}\n" + + "#display2 { text-decoration: underline }\n"; + is(cs1.textDecoration, + expected_match ? "line-through" : "overline", + "@supports condition \"" + condition + "\" should " + + (expected_match ? "" : "NOT ") + "match"); + is(cs2.textDecoration, "underline", + "@supports condition \"" + condition + "\" should be balanced"); + } + + check_balanced_condition("not (color: green)", false); + check_balanced_condition("not (colour: green)", true); + check_balanced_condition("not(color: green)", false); + check_balanced_condition("not(colour: green)", false); + check_balanced_condition("not/* */(color: green)", false); + check_balanced_condition("not/* */(colour: green)", false); + check_balanced_condition("not /* */ (color: green)", false); + check_balanced_condition("not /* */ (colour: green)", true); + check_balanced_condition("(color: green) and (color: blue)", true); + check_balanced_condition("(color: green) /* */ /* */ and /* */ /* */ (color: blue)", true); + check_balanced_condition("(color: green) and(color: blue)", false); + check_balanced_condition("(color: green) and/* */(color: blue)", false); + check_balanced_condition("(color: green)and (color: blue)", false); + check_balanced_condition("(color: green) or (color: blue)", true); + check_balanced_condition("(color: green) /* */ /* */ or /* */ /* */ (color: blue)", true); + check_balanced_condition("(color: green) or(color: blue)", false); + check_balanced_condition("(color: green) or/* */(color: blue)", false); + check_balanced_condition("(color: green)or (color: blue)", false); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +runTest(); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_system_font_serialization.html b/layout/style/test/test_system_font_serialization.html new file mode 100644 index 000000000..65b016836 --- /dev/null +++ b/layout/style/test/test_system_font_serialization.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=475214 +--> +<head> + <title>Test for Bug 475214</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=475214">Mozilla Bug 475214</a> +<p id="display"></p> +<div id="content"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 475214 **/ + +var e = document.getElementById("content"); +var s = e.style; + +e.style.font = "menu"; +is(e.style.cssText, "font: menu;", "serialize system font alone"); +is(e.style.font, "menu", "font getter returns value"); + +e.style.fontFamily = "inherit"; +is(e.style.cssText, "font: menu; font-family: inherit;", "serialize system font and font-family"); +is(e.style.font, "", "font getter should be empty"); + +e.style.font = "message-box"; +is(e.style.cssText, "font: message-box;", "serialize system font alone"); +is(e.style.font, "message-box", "font getter returns value"); + +e.setAttribute("style", "font-weight:bold;font:caption;line-height:3;"); +is(e.style.cssText, "font: caption; line-height: 3;", "serialize system font and font-family"); +is(e.style.font, "", "font getter should be empty"); + +e.setAttribute("style", "font: menu; font-weight: -moz-use-system-font"); +is(e.style.cssText, "font: menu;", "serialize system font alone"); +is(e.style.font, "menu", "font getter returns value"); + +e.setAttribute("style", "font: menu; font-weight: -moz-use-system-font ! important"); +is(e.style.cssText, "font: menu; font-weight: -moz-use-system-font ! important;", "serialize system font and subproperty that is important"); +is(e.style.font, "", "font getter returns nothing"); + +e.setAttribute("style", "font: inherit; font-family: Helvetica;"); + +var cssTextStr = "font-style: inherit; font-weight: inherit; font-size: inherit; line-height: inherit; font-size-adjust: inherit; font-stretch: inherit; font-feature-settings: inherit; font-language-override: inherit; font-kerning: inherit; font-synthesis: inherit; font-variant: inherit;"; + +is(e.style.cssText, cssTextStr + " font-family: Helvetica;", "don't serialize system font for font:inherit"); +is(e.style.font, "", "font getter returns nothing"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_text_decoration_shorthands.html b/layout/style/test/test_text_decoration_shorthands.html new file mode 100644 index 000000000..aff21e1d8 --- /dev/null +++ b/layout/style/test/test_text_decoration_shorthands.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset=utf-8> + <title>Test parsing of text-decoration shorthands</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel='stylesheet' href='/resources/testharness.css'> +</head> +<body> + +<script> + +var initial_values = { + textDecorationColor: "rgb(255, 0, 255)", + textDecorationLine: "none", + textDecorationStyle: "solid", +}; + +// For various specified values of the text-decoration shorthand, +// test the computed values of the corresponding longhands. +var text_decoration_test_cases = [ + { + specified: "none", + }, + { + specified: "red", + textDecorationColor: "rgb(255, 0, 0)", + }, + { + specified: "line-through", + textDecorationLine: "line-through", + }, + { + specified: "dotted", + textDecorationStyle: "dotted", + }, + { + specified: "#00ff00 underline", + textDecorationColor: "rgb(0, 255, 0)", + textDecorationLine: "underline", + }, + { + specified: "#ffff00 wavy", + textDecorationColor: "rgb(255, 255, 0)", + textDecorationStyle: "wavy", + }, + { + specified: "overline double", + textDecorationLine: "overline", + textDecorationStyle: "double", + }, + { + specified: "red underline solid", + textDecorationColor: "rgb(255, 0, 0)", + textDecorationLine: "underline", + textDecorationStyle: "solid", + }, + { + specified: "double overline blue", + textDecorationColor: "rgb(0, 0, 255)", + textDecorationLine: "overline", + textDecorationStyle: "double", + }, +]; + +function run_tests(test_cases, shorthand, subproperties) { + test_cases.forEach(function(test_case) { + test(function() { + var element = document.createElement('div'); + document.body.appendChild(element); + // Set text color to test initial value of text-decoration-color + // (currentColor). + element.style.color = "#ff00ff"; + element.style[shorthand] = test_case.specified; + var computed = window.getComputedStyle(element); + subproperties.forEach(function(longhand) { + assert_equals( + computed[longhand], + test_case[longhand] || initial_values[longhand], + longhand + ); + }); + }, "test parsing of 'text-decoration: " + test_case.specified + "'"); + }); +} + +run_tests(text_decoration_test_cases, "textDecoration", [ + "textDecorationColor", "textDecorationLine", "textDecorationStyle"]); + +</script> +</body> +</html> diff --git a/layout/style/test/test_transitions.html b/layout/style/test/test_transitions.html new file mode 100644 index 000000000..de7943a49 --- /dev/null +++ b/layout/style/test/test_transitions.html @@ -0,0 +1,787 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=435441 +--> +<head> + <title>Test for Bug 435441</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + #display p { margin-top: 0; margin-bottom: 0; } + #display .before, #display .after { + width: -moz-fit-content; border: 1px solid black; + } + #display .before::before, #display .after::after { + display: block; + width: 0; + text-indent: 0; + } + #display .before.started::before, #display .after.started::after { + width: 100px; + text-indent: 100px; + transition: 8s width ease-in-out, 8s text-indent ease-in-out; + } + #display .before::before { + content: "Before"; + } + #display .after::after { + content: "After"; + } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a> +<div id="display"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 435441 **/ + +// Run tests simultaneously so we don't have to take up too much time. +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +var gTestsRunning = 0; +function TestStarted() { ++gTestsRunning; } +function TestFinished() { if (--gTestsRunning == 0) SimpleTest.finish(); } + +// An array of arrays of functions to be called at the outer index number +// of seconds after the present. +var gFutureCalls = []; + +function add_future_call(index, func) +{ + if (!(index in gFutureCalls)) { + gFutureCalls[index] = []; + } + gFutureCalls[index].push(func); + TestStarted(); +} +var gStartTime1, gStartTime2; +var gCurrentTime; +var gSetupComplete = false; + +function process_future_calls(index) +{ + var calls = gFutureCalls[index]; + if (!calls) + return; + gCurrentTime = Date.now(); + for (var i = 0; i < calls.length; ++i) { + calls[i](); + TestFinished(); + } +} + +var timingFunctions = { + // a map from the value of 'transition-timing-function' to an array of + // the portions this function yields at 0 (always 0), 1/4, 1/2, and + // 3/4 and all (always 1) of the way through the time of the + // transition. Each portion is represented as a value and an + // acceptable error tolerance (based on a time error of 1%) for that + // value. + + // ease + "ease": bezier(0.25, 0.1, 0.25, 1), + "cubic-bezier(0.25, 0.1, 0.25, 1.0)": bezier(0.25, 0.1, 0.25, 1), + + // linear and various synonyms for it + "linear": function(x) { return x; }, + "cubic-bezier(0.0, 0.0, 1.0, 1.0)": function(x) { return x; }, + "cubic-bezier(0, 0, 1, 1)": function(x) { return x; }, + "cubic-bezier(0, 0, 0, 0.0)": function(x) { return x; }, + "cubic-bezier(1.0, 1, 0, 0)": function(x) { return x; }, + + // ease-in + "ease-in": bezier(0.42, 0, 1, 1), + "cubic-bezier(0.42, 0, 1.0, 1.0)": bezier(0.42, 0, 1, 1), + + // ease-out + "ease-out": bezier(0, 0, 0.58, 1), + "cubic-bezier(0, 0, 0.58, 1.0)": bezier(0, 0, 0.58, 1), + + // ease-in-out + "ease-in-out": bezier(0.42, 0, 0.58, 1), + "cubic-bezier(0.42, 0, 0.58, 1.0)": bezier(0.42, 0, 0.58, 1), + + // other cubic-bezier values + "cubic-bezier(0.4, 0.1, 0.7, 0.95)": bezier(0.4, 0.1, 0.7, 0.95), + "cubic-bezier(1, 0, 0, 1)": bezier(1, 0, 0, 1), + "cubic-bezier(0, 1, 1, 0)": bezier(0, 1, 1, 0), + +}; + +var div = document.getElementById("display"); + +// Set up all the elements on which we are going to initiate transitions. + +// We have two reference elements to check the expected timing range. +// They both have 16s linear transitions from 0 to 1000px. +// This means they move through 62.5 pixels per second. +const REF_PX_PER_SEC = 62.5; +function make_reference_p() { + var p = document.createElement("p"); + p.appendChild(document.createTextNode("reference")); + p.style.textIndent = "0px"; + p.style.transition = "16s text-indent linear"; + div.appendChild(p); + return p; +} +var earlyref = make_reference_p(); +var earlyrefcs = getComputedStyle(earlyref, ""); + +// Test all timing functions using a set of 8-second transitions, which +// we check at times 0, 2s, 4s, 6s, and 8s. +var tftests = []; +for (var tf in timingFunctions) { + var p = document.createElement("p"); + var t = document.createTextNode("transition-timing-function: " + tf); + p.appendChild(t); + p.style.textIndent = "0px"; + p.style.transition = "8s text-indent linear"; + p.style.transitionTimingFunction = tf; + div.appendChild(p); + is(getComputedStyle(p, "").textIndent, "0px", + "should be zero before changing value"); + tftests.push([ p, tf ]); +} + +// Check that the timing function continues even when we restyle in the +// middle. +var interrupt_tests = []; +for (var restyleParent of [true, false]) { + for (var itime = 2; itime < 8; itime += 2) { + var p = document.createElement("p"); + var t = document.createTextNode("interrupt on " + + (restyleParent ? "parent" : "node itself") + + " at " + itime + "s"); + p.appendChild(t); + p.style.textIndent = "0px"; + p.style.transition = "8s text-indent cubic-bezier(0, 1, 1, 0)"; + if (restyleParent) { + var d = document.createElement("div"); + d.appendChild(p); + div.appendChild(d); + } else { + div.appendChild(p); + } + is(getComputedStyle(p, "").textIndent, "0px", + "should be zero before changing value"); + setTimeout("interrupt_tests[" + interrupt_tests.length + "]" + + "[0]" + (restyleParent ? ".parentNode" : "") + + ".style.color = 'blue';" + + "check_interrupt_tests()", itime*1000); + interrupt_tests.push([ p, itime ]); + } +} + +// Test transition-delay values of -4s through 4s on a 4s transition +// with 'ease-out' timing function. +var delay_tests = {}; +for (var d = -4; d <= 4; ++d) { + var p = document.createElement("p"); + var delay = d + "s"; + var t = document.createTextNode("transition-delay: " + delay); + p.appendChild(t); + p.style.marginLeft = "0px"; + p.style.transition = "4s margin-left ease-out " + delay; + div.appendChild(p); + is(getComputedStyle(p, "").marginLeft, "0px", + "should be zero before changing value"); + delay_tests[d] = p; +} + +// Test transition-delay values of -4s through 4s on a 4s transition +// with duration of zero. +var delay_zero_tests = {}; +for (var d = -4; d <= 4; ++d) { + var p = document.createElement("p"); + var delay = d + "s"; + var t = document.createTextNode("transition-delay: " + delay); + p.appendChild(t); + p.style.marginLeft = "0px"; + p.style.transition = "0s margin-left linear " + delay; + div.appendChild(p); + is(getComputedStyle(p, "").marginLeft, "0px", + "should be zero before changing value"); + delay_zero_tests[d] = p; +} + +// Test that changing the value on an already-running transition to the +// value it currently happens to have resets the transition. +function make_reset_test(transition, description) +{ + var p = document.createElement("p"); + var t = document.createTextNode(description); + p.appendChild(t); + p.style.marginLeft = "0px"; + p.style.transition = transition; + div.appendChild(p); + is(getComputedStyle(p, "").marginLeft, "0px", + "should be zero before changing value"); + return p; +} +var reset_test = make_reset_test("4s margin-left ease-out 4s", "transition-delay reset to starting point"); +var reset_test_reference = make_reset_test("4s margin-left linear -3s", "reference for previous test (reset test)"); + +// Test that transitions on descendants start correctly when the +// inherited value is itself transitioning. In other words, when +// ancestor and descendant both have a transition for the same property, +// and the descendant inherits the property from the ancestor, the +// descendant's transition starts as specified, based on the concepts of +// the before-change style, the after-change style, and the +// after-transition style. +var descendant_tests = [ + { parent_transition: "", + child_transition: "4s text-indent" }, + { parent_transition: "4s text-indent", + child_transition: "" }, + { parent_transition: "4s text-indent", + child_transition: "16s text-indent" }, + { parent_transition: "4s text-indent", + child_transition: "1s text-indent" }, + { parent_transition: "8s letter-spacing", + child_transition: "4s text-indent" }, + { parent_transition: "4s text-indent", + child_transition: "8s letter-spacing" }, + { parent_transition: "4s text-indent", + child_transition: "8s all" }, + { parent_transition: "8s text-indent", + child_transition: "4s all" }, + // examples with positive and negative delay + { parent_transition: "4s text-indent 1s", + child_transition: "8s text-indent" }, + { parent_transition: "4s text-indent -1s", + child_transition: "8s text-indent" } +]; + +for (var i in descendant_tests) { + var test = descendant_tests[i]; + test.parentNode = document.createElement("div"); + test.childNode = document.createElement("p"); + test.parentNode.appendChild(test.childNode); + test.childNode.appendChild(document.createTextNode( + "parent with \"" + test.parent_transition + "\" and " + + "child with \"" + test.child_transition + "\"")); + test.parentNode.style.transition = test.parent_transition; + test.childNode.style.transition = test.child_transition; + test.parentNode.style.textIndent = "50px"; // transition from 50 to 150 + test.parentNode.style.letterSpacing = "10px"; // transition from 10 to 5 + div.appendChild(test.parentNode); + var parentCS = getComputedStyle(test.parentNode, ""); + var childCS = getComputedStyle(test.childNode, ""); + is(parentCS.textIndent, "50px", + "parent text-indent should be 50px before changing"); + is(parentCS.letterSpacing, "10px", + "parent letter-spacing should be 10px before changing"); + is(childCS.textIndent, "50px", + "child text-indent should be 50px before changing"); + is(childCS.letterSpacing, "10px", + "child letter-spacing should be 10px before changing"); + test.childCS = childCS; +} + +// For all of these transitions, the transition for margin-left should +// have a duration of 8s, and the default timing function (ease) and +// delay (0). +// This is because we're implementing the proposal in +// http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html +var number_tests = [ + { style: "transition: 4s margin, 8s margin-left" }, + { style: "transition: 4s margin-left, 8s margin" }, + { style: "transition-property: margin-left; " + + "transition-duration: 8s, 2s" }, + { style: "transition-property: margin-left, margin-left; " + + "transition-duration: 2s, 8s" }, + { style: "transition-property: margin-left, margin-left, margin-left; " + + "transition-duration: 8s, 2s" }, + { style: "transition-property: margin-left; " + + "transition-duration: 8s, 16s" }, + { style: "transition-property: margin-left, margin-left; " + + "transition-duration: 16s, 8s" }, + { style: "transition-property: margin-left, margin-left, margin-left; " + + "transition-duration: 8s, 16s" }, + { style: "transition-property: text-indent,word-spacing,margin-left; " + + "transition-duration: 8s; " + + "transition-delay: 0, 8s" }, + { style: "transition-property: text-indent,word-spacing,margin-left; " + + "transition-duration: 8s, 16s; " + + "transition-delay: 8s, 8s, 0, 8s, 8s, 8s" }, +]; + +for (var i in number_tests) { + var test = number_tests[i]; + var p = document.createElement("p"); + p.setAttribute("style", test.style); + var t = document.createTextNode(test.style); + p.appendChild(t); + p.style.marginLeft = "100px"; + div.appendChild(p); + is(getComputedStyle(p, "").marginLeft, "100px", + "should be 100px before changing value"); + test.node = p; +} + +// Test transitions that are also from-display:none, to-display:none, and +// display:none throughout. +var from_none_test, to_none_test, always_none_test; +function make_display_test(initially_none, text) +{ + var p = document.createElement("p"); + p.appendChild(document.createTextNode(text)); + p.style.textIndent = "0px"; + p.style.transition = "8s text-indent ease-in-out"; + if (initially_none) + p.style.display = "none"; + div.appendChild(p); + return p; +} +from_none_test = make_display_test(true, "transition from display:none"); +to_none_test = make_display_test(false, "transition to display:none"); +always_none_test = make_display_test(true, "transition always display:none"); +var display_tests = [ from_none_test, to_none_test, always_none_test ]; + +// Test transitions on pseudo-elements +var before_test, after_test; +function make_pseudo_elem_test(pseudo) +{ + var p = document.createElement("p"); + p.className = pseudo; + div.appendChild(p); + return {"pseudo": pseudo, element: p}; +} +before_test = make_pseudo_elem_test("before"); +after_test = make_pseudo_elem_test("after"); +var pseudo_element_tests = [ before_test, after_test ]; + +// FIXME (Bug 522599): Test a transition that reverses partway through. + +var lateref = make_reference_p(); +var laterefcs = getComputedStyle(lateref, ""); + +// flush style changes +var x = getComputedStyle(div, "").color; + +// Start our timer as close as possible to when we start the first +// transition. +// Do not use setInterval because once it gets off in time, it stays off. +for (var i = 1; i <= 8; ++i) { + setTimeout(process_future_calls, i * 1000, i); +} +gStartTime1 = Date.now(); // set before any transitions have started + +// Start all the transitions. +earlyref.style.textIndent = "1000px"; +for (var test in tftests) { + var p = tftests[test][0]; + p.style.textIndent = "100px"; +} +for (var test in interrupt_tests) { + var p = interrupt_tests[test][0]; + p.style.textIndent = "100px"; +} +for (var d in delay_tests) { + var p = delay_tests[d]; + p.style.marginLeft = "100px"; +} +for (var d in delay_zero_tests) { + var p = delay_zero_tests[d]; + p.style.marginLeft = "100px"; +} +reset_test.style.marginLeft = "100px"; +reset_test_reference.style.marginLeft = "100px"; +for (var i in descendant_tests) { + var test = descendant_tests[i]; + test.parentNode.style.textIndent = "150px"; + test.parentNode.style.letterSpacing = "5px"; +} +for (var i in number_tests) { + var test = number_tests[i]; + test.node.style.marginLeft = "50px"; +} +from_none_test.style.textIndent = "100px"; +from_none_test.style.display = ""; +to_none_test.style.textIndent = "100px"; +to_none_test.style.display = "none"; +always_none_test.style.textIndent = "100px"; +for (var i in pseudo_element_tests) { + var test = pseudo_element_tests[i]; + test.element.classList.add("started"); +} +lateref.style.textIndent = "1000px"; + +// flush style changes +x = getComputedStyle(div, "").color; + +gStartTime2 = Date.now(); // set after all transitions have started +gCurrentTime = gStartTime2; + +/** + * Assert that a transition whose timing function yields the bezier + * |func|, running from |start_time| to |end_time| (both in seconds + * relative to when the transitions were started) should have produced + * computed value |cval| given that the transition was from + * |start_value| to |end_value| (both numbers in CSS pixels). + */ +function check_transition_value(func, start_time, end_time, + start_value, end_value, cval, desc, + xfail) +{ + /** + * Compute the value at a given time |elapsed|, by normalizing the + * input to the timing function using start_time and end_time and + * then turning the output into a value using start_value and + * end_value. + * + * The |error_direction| argument should be either -1, 0, or 1, + * suggesting adding on a little bit of error, to allow for the + * cubic-bezier calculation being an approximation. The amount of + * error is proportional to the slope of the timing function, since + * the error is added to the *input* of the timing function (after + * normalization to 0-1 based on start_time and end_time). + */ + function value_at(elapsed, error_direction) { + var time_portion = (elapsed - start_time) / (end_time - start_time); + if (time_portion < 0) + time_portion = 0; + else if (time_portion > 1) + time_portion = 1; + // Assume a small error since bezier computation can be off slightly. + // (This test's computation is probably more accurate than Mozilla's.) + var value_portion = func(time_portion + error_direction * 0.0005); + if (value_portion < 0) + value_portion = 0; + else if (value_portion > 1) + value_portion = 1; + var value = (1 - value_portion) * start_value + value_portion * end_value; + if (start_value > end_value) + error_direction = -error_direction; + // Computed values get rounded to 1/60th of a pixel. + return value + error_direction * 0.02; + } + + var time_range; // in seconds + var uns_range; // |range| before being sorted (so errors give it + // in the original order + if (!gSetupComplete) { + // No timers involved + time_range = [0, 0]; + if (start_time < 0) { + uns_range = [ value_at(0, -1), value_at(0, 1) ]; + } else { + var val = value_at(0, 0); + uns_range = [val, val]; + } + } else { + time_range = [ px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC, + px_to_num(laterefcs.textIndent) / REF_PX_PER_SEC ]; + // seconds + uns_range = [ value_at(time_range[0], -1), + value_at(time_range[1], 1) ]; + } + var range = uns_range.concat(). /* concat to clone array */ + sort(function compareNumbers(a,b) { return a - b; }); + var actual = px_to_num(cval); + + var fn = ok; + if (xfail && xfail(range)) + fn = todo; + + fn(range[0] <= actual && actual <= range[1], + desc + ": computed value " + cval + " should be between " + + uns_range[0].toFixed(6) + "px and " + uns_range[1].toFixed(6) + + "px at time between " + time_range[0] + "s and " + time_range[1] + "s."); +} + +function check_ref_range() +{ + // This is the only test where we compare the progress of the + // transitions to an actual time; we need considerable tolerance at + // the low end (we are using half a second). + var expected_range = [ (gCurrentTime - gStartTime2 - 40) / 16, + (Date.now() - gStartTime1 + 20) / 16 ]; + if (expected_range[0] > 1000) { + expected_range[0] = 1000; + } + if (expected_range[1] > 1000) { + expected_range[1] = 1000; + } + function check(desc, value) { + // The timing on the unit test VMs is not reliable, so make this + // test report PASS when it succeeds and TODO when it fails. + var passed = expected_range[0] <= value && value <= expected_range[1]; + (passed ? ok : todo)(passed, + desc + ": computed value " + value + "px should be between " + + expected_range[0].toFixed(6) + "px and " + + expected_range[1].toFixed(6) + "px at time between " + + expected_range[0]/REF_PX_PER_SEC + "s and " + + expected_range[1]/REF_PX_PER_SEC + "s."); + } + check("early reference", px_to_num(earlyrefcs.textIndent)); + check("late reference", px_to_num(laterefcs.textIndent)); +} + +for (var i = 1; i <= 8; ++i) { + add_future_call(i, check_ref_range); +} + +function check_tf_test() +{ + for (var test in tftests) { + var p = tftests[test][0]; + var tf = tftests[test][1]; + + check_transition_value(timingFunctions[tf], 0, 8, 0, 100, + getComputedStyle(p, "").textIndent, + "timing function test for timing function " + tf); + + } + + check_interrupt_tests(); +} + +check_tf_test(); +add_future_call(2, check_tf_test); +add_future_call(4, check_tf_test); +add_future_call(6, check_tf_test); +add_future_call(8, check_tf_test); + +function check_interrupt_tests() +{ + for (var test in interrupt_tests) { + var p = interrupt_tests[test][0]; + var itime = interrupt_tests[test][1]; + + check_transition_value(timingFunctions["cubic-bezier(0, 1, 1, 0)"], + 0, 8, 0, 100, + getComputedStyle(p, "").textIndent, + "interrupt " + + (p.parentNode == div ? "" : "on parent ") + + "test for time " + itime + "s"); + } +} + +// check_interrupt_tests is called from check_tf_test and from +// where we reset the interrupts + +function check_delay_test(time) +{ + var tf = timingFunctions["ease-out"]; + for (var d in delay_tests) { + var p = delay_tests[d]; + + check_transition_value(tf, Number(d), Number(d) + 4, 0, 100, + getComputedStyle(p, "").marginLeft, + "delay test for delay " + d + "s"); + } +} + +check_delay_test(0); +for (var i = 1; i <= 8; ++i) { + add_future_call(i, check_delay_test); +} + +function check_delay_zero_test(time) +{ + for (var d in delay_zero_tests) { + var p = delay_zero_tests[d]; + + time_range = [ px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC, + px_to_num(laterefcs.textIndent) / REF_PX_PER_SEC ]; + var m = getComputedStyle(p, "").marginLeft; + var desc = "delay_zero test for delay " + d + "s"; + if (time_range[0] < d && time_range[1] < d) { + is(m, "0px", desc); + } else if ((time_range[0] > d && time_range[1] > d) || + (d == 0 && time == 0)) { + is(m, "100px", desc); + } + } +} + +check_delay_zero_test(0); +for (var i = 1; i <= 8; ++i) { + add_future_call(i, check_delay_zero_test); +} + +function reset_reset_test(time) +{ + reset_test.style.marginLeft = "0px"; +} +function check_reset_test(time) +{ + is(getComputedStyle(reset_test, "").marginLeft, "0px", + "reset test value at time " + time + "s."); +} +check_reset_test(0); +// reset the reset test right now so we don't have to worry about clock skew +// To make sure that this is valid, check that a pretty-much-identical test is +// already transitioning. +is(getComputedStyle(reset_test_reference, "").marginLeft, "75px", + "reset test reference value"); +reset_reset_test(); +check_reset_test(0); +for (var i = 1; i <= 8; ++i) { + (function(j) { + add_future_call(j, function() { check_reset_test(j); }); + })(i); +} + +check_descendant_tests(); +add_future_call(2, check_descendant_tests); +add_future_call(6, check_descendant_tests); + +function check_descendant_tests() { + // text-indent: transition from 50px to 150px + // letter-spacing: transition from 10px to 5px + var values = {}; + values["text-indent"] = [ 50, 150 ]; + values["letter-spacing"] = [ 10, 5 ]; + var tf = timingFunctions["ease"]; + + var time = px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC; + + for (var i in descendant_tests) { + var test = descendant_tests[i]; + + /* ti=text-indent, ls=letter-spacing */ + var child_ti_duration = 0; + var child_ls_duration = 0; + var child_ti_delay = 0; + var child_ls_delay = 0; + + if (test.parent_transition != "") { + var props = test.parent_transition.split(" "); + var duration = parseInt(props[0]); + var delay = (props.length > 2) ? parseInt(props[2]) : 0; + var property = props[1]; + if (property == "text-indent") { + child_ti_duration = duration; + child_ti_delay = delay; + } else if (property == "letter-spacing") { + child_ls_duration = duration; + child_ls_delay = delay; + } else { + ok(false, "fix this test (unexpected transition-property " + + property + " on parent)"); + } + } + + if (test.child_transition != "") { + var props = test.child_transition.split(" "); + var duration = parseInt(props[0]); + var delay = (props.length > 2) ? parseInt(props[2]) : 0; + var property = props[1]; + if (property != "text-indent" && property != "letter-spacing" && + property != "all") { + ok(false, "fix this test (unexpected transition-property " + + property + " on child)"); + } + + // Override the parent's transition with the child's as long + // as the child transition is still running. + if (property != "letter-spacing" && duration + delay > time) { + child_ti_duration = duration; + child_ti_delay = delay; + } + if (property != "text-indent" && duration + delay > time) { + child_ls_duration = duration; + child_ls_delay = delay; + } + } + + var time_portions = { + "text-indent": + { duration: child_ti_duration, delay: child_ti_delay }, + "letter-spacing": + { duration: child_ls_duration, delay: child_ls_delay }, + }; + + for (var prop in {"text-indent": true, "letter-spacing": true}) { + var time_portion = time_portions[prop]; + + if (time_portion.duration == 0) { + time_portion.duration = 0.01; + time_portion.delay = -1; + } + + check_transition_value(tf, time_portion.delay, + time_portion.delay + time_portion.duration, + values[prop][0], values[prop][1], + test.childCS.getPropertyValue(prop), + `descendant test #${Number(i)+1}, property ${prop}`); + } + } +} + +function check_number_tests() +{ + var tf = timingFunctions["ease"]; + for (var d in number_tests) { + var test = number_tests[d]; + var p = test.node; + + check_transition_value(tf, 0, 8, 100, 50, + getComputedStyle(p, "").marginLeft, + "number of transitions test for style " + + test.style); + } +} + +check_number_tests(0); +add_future_call(2, check_number_tests); +add_future_call(4, check_number_tests); +add_future_call(6, check_number_tests); +add_future_call(8, check_number_tests); + +function check_display_tests(time) +{ + for (var i in display_tests) { + var p = display_tests[i]; + + // There is no transition if the old or new style is display:none, so + // the computed value is always the end value. + var computedValue = getComputedStyle(p, "").textIndent; + is(computedValue, "100px", + "display test for test with " + p.childNodes[0].data + + ": computed value " + computedValue + " should be 100px."); + } +} + +check_display_tests(0); +add_future_call(2, function() { check_display_tests(2); }); +add_future_call(4, function() { check_display_tests(4); }); +add_future_call(6, function() { check_display_tests(6); }); +add_future_call(8, function() { check_display_tests(8); }); + +function check_pseudo_element_tests(time) +{ + var tf = timingFunctions["ease-in-out"]; + for (var i in pseudo_element_tests) { + var test = pseudo_element_tests[i]; + + check_transition_value(tf, 0, 8, 0, 100, + getComputedStyle(test.element, "").width, + "::"+test.pseudo+" test"); + check_transition_value(tf, 0, 8, 0, 100, + getComputedStyle(test.element, + "::"+test.pseudo).textIndent, + "::"+test.pseudo+" indent test"); + } +} +check_pseudo_element_tests(0); +add_future_call(2, function() { check_pseudo_element_tests(2); }); +add_future_call(4, function() { check_pseudo_element_tests(4); }); +add_future_call(6, function() { check_pseudo_element_tests(6); }); +add_future_call(8, function() { check_pseudo_element_tests(8); }); + +gSetupComplete = true; +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_and_reframes.html b/layout/style/test/test_transitions_and_reframes.html new file mode 100644 index 000000000..b4213dbdd --- /dev/null +++ b/layout/style/test/test_transitions_and_reframes.html @@ -0,0 +1,298 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=625289 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 625289</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + :root, + #e1, #e2 > div, + #b1::before, #b2 > div::before, + #a1::after, #a2 > div::after { + margin-left: 0; + } + :root.t, + #e1.t, #e2.t > div, + #b1.t::before, #b2.t > div::before, + #a1.t::after, #a2.t > div::after { + transition: margin-left linear 1s; + } + #b1::before, #b2 > div::before, + #a1::after, #a2 > div::after { + content: "x"; + display: block; + } + :root.m, + #e1.m, #e2.m > div, + #b1.m::before, #b2.m > div::before, + #a1.m::after, #a2.m > div::after { + margin-left: 100px; + } + .o { overflow: hidden } + .n { display: none } + + #fline { color: blue; font-size: 20px; width: 800px; } + #fline::first-line { color: yellow } + #fline.narrow { width: 50px } + #fline i { transition: color linear 1s } + + #flexboxtest #flex { display: flex; flex-direction: column } + #flexboxtest #flextransition { color: blue; transition: color 5s linear } + + #flexboxtest #flexkid[newstyle] { resize: both } + #flexboxtest #flextransition[newstyle] { color: yellow } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=625289">Mozilla Bug 625289</a> +<div id="container"></div> +<div id="fline"> + This text has an <i>i element</i> in it. +</div> +<div id="flexboxtest"> + <div id="flex"> + hello + <span id="flexkid">this appears</span> + hello + <div id="flextransition">color transition</div> + </div> +</div> +<pre id="test"> +<script> +"use strict"; + +function advance_clock(milliseconds) { + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds); +} + +var container = document.getElementById("container"); + +function make_elements(idName, child) { + var e = document.createElement("div"); + e.setAttribute("id", idName); + if (child) { + e.appendChild(document.createElement("div")); + } + container.appendChild(e); + return e; +} + +function assert_margin_at_quarter(element, pseudo, passes) +{ + var desc; + var useParent = false; + if (element == document.documentElement) { + desc = "root element"; + } else if (element.id) { + desc = "element " + element.id; + } else { + desc = "child of element " + element.parentNode.id; + useParent = true; + } + var classes = (useParent ? element.parentNode : element).getAttribute("class"); + if (classes) { + desc += " (classes: " + classes + ")"; + } + if (pseudo) { + desc += " " + pseudo + " pseudo-element"; + } + (passes ? is : todo_is)(getComputedStyle(element, pseudo).marginLeft, "25px", + "margin of " + desc); +} + +function do_test(test) +{ + var expected_props = [ "element", "test_child", "pseudo", "passes", + "dynamic_change_transition", "start_from_none" ]; + for (var propidx in expected_props) { + if (! expected_props[propidx] in test) { + ok(false, "expected " + expected_props[propidx] + " on test object"); + } + } + + var e; + if (typeof(test.element) == "string") { + e = make_elements(test.element, test.test_child); + } else { + if (test.test_child) { + ok(false, "test_child unexpected"); + } + e = test.element; + } + + var target = test.test_child ? e.firstChild : e; + + if (!test.dynamic_change_transition) { + e.classList.add("t"); + } + if (test.start_from_none) { + e.classList.add("n"); + } + + advance_clock(100); + e.classList.add("m"); + e.classList.add("o"); + if (test.dynamic_change_transition) { + e.classList.add("t"); + } + if (test.start_from_none) { + e.classList.remove("n"); + } + advance_clock(0); + advance_clock(250); + assert_margin_at_quarter(target, test.pseudo, test.passes); + if (typeof(test.element) == "string") { + e.remove(); + } else { + target.style.transition = ""; + target.removeAttribute("class"); + } +} + +advance_clock(0); + +var tests = [ + { element:"e1", test_child:false, pseudo:"", passes:true, + dynamic_change_transition:false, start_from_none:false }, + { element:"e2", test_child:true, pseudo:"", passes:true, + dynamic_change_transition:false, start_from_none:false }, + { element:"b1", test_child:false, pseudo:"::before", passes:true, + dynamic_change_transition:false, start_from_none:false }, + { element:"b2", test_child:true, pseudo:"::before", passes:true, + dynamic_change_transition:false, start_from_none:false }, + { element:"a1", test_child:false, pseudo:"::after", passes:true, + dynamic_change_transition:false, start_from_none:false }, + { element:"a2", test_child:true, pseudo:"::after", passes:true, + dynamic_change_transition:false, start_from_none:false }, + { element:document.documentElement, test_child:false, pseudo:"", passes:true, + dynamic_change_transition:false, start_from_none:false }, + // Recheck with a dynamic change in transition + { element:"e1", test_child:false, pseudo:"", passes:true, + dynamic_change_transition:true, start_from_none:false }, + { element:"e2", test_child:true, pseudo:"", passes:true, + dynamic_change_transition:true, start_from_none:false }, + { element:"b1", test_child:false, pseudo:"::before", passes:true, + dynamic_change_transition:true, start_from_none:false }, + { element:"b2", test_child:true, pseudo:"::before", passes:true, + dynamic_change_transition:true, start_from_none:false }, + { element:"a1", test_child:false, pseudo:"::after", passes:true, + dynamic_change_transition:true, start_from_none:false }, + { element:"a2", test_child:true, pseudo:"::after", passes:true, + dynamic_change_transition:true, start_from_none:false }, + { element:document.documentElement, test_child:false, pseudo:"", passes:true, + dynamic_change_transition:true, start_from_none:false }, + // Recheck starting from display:none. Note that these tests all fail, + // although we could get *some* of them to pass by calling + // RestyleManager::TryInitiatingTransition from + // ElementRestyler::RestyleUndisplayedChildren. + { element:"e1", test_child:false, pseudo:"", passes:false, + dynamic_change_transition:false, start_from_none:true }, + { element:"e2", test_child:true, pseudo:"", passes:false, + dynamic_change_transition:false, start_from_none:true }, + { element:"b1", test_child:false, pseudo:"::before", passes:false, + dynamic_change_transition:false, start_from_none:true }, + { element:"b2", test_child:true, pseudo:"::before", passes:false, + dynamic_change_transition:false, start_from_none:true }, + { element:"a1", test_child:false, pseudo:"::after", passes:false, + dynamic_change_transition:false, start_from_none:true }, + { element:"a2", test_child:true, pseudo:"::after", passes:false, + dynamic_change_transition:false, start_from_none:true }, + { element:document.documentElement, test_child:false, pseudo:"", passes:false, + dynamic_change_transition:false, start_from_none:true }, + // Recheck with a dynamic change in transition and starting from display:none + { element:"e1", test_child:false, pseudo:"", passes:false, + dynamic_change_transition:true, start_from_none:true }, + { element:"e2", test_child:true, pseudo:"", passes:false, + dynamic_change_transition:true, start_from_none:true }, + { element:"b1", test_child:false, pseudo:"::before", passes:false, + dynamic_change_transition:true, start_from_none:true }, + { element:"b2", test_child:true, pseudo:"::before", passes:false, + dynamic_change_transition:true, start_from_none:true }, + { element:"a1", test_child:false, pseudo:"::after", passes:false, + dynamic_change_transition:true, start_from_none:true }, + { element:"a2", test_child:true, pseudo:"::after", passes:false, + dynamic_change_transition:true, start_from_none:true }, + { element:document.documentElement, test_child:false, pseudo:"", passes:false, + dynamic_change_transition:true, start_from_none:true }, +]; + +for (var testidx in tests) { + do_test(tests[testidx]); +} + +var fline = document.getElementById("fline"); +var fline_i_cs = getComputedStyle(fline.firstElementChild, ""); +// Note that the color in the ::first-line is never used in the test +// since we avoid reporting ::first-line data in getComputedStyle. +// However, if we were to start a transition (incorrectly), that would +// show up in getComputedStyle. +var COLOR_IN_LATER_LINES = "rgb(0, 0, 255)"; + +function do_firstline_test(test) { + if (test.widening) { + fline.classList.add("narrow"); + is (fline_i_cs.color, COLOR_IN_LATER_LINES, "initial color"); + } else { + is (fline_i_cs.color, COLOR_IN_LATER_LINES, "initial color"); + } + + if (test.widening) { + fline.classList.remove("narrow"); + } else { + fline.classList.add("narrow"); + } + + if (test.set_overflow) { + fline.classList.add("o"); + } + + advance_clock(100); + + if (test.widening) { + is (fline_i_cs.color, COLOR_IN_LATER_LINES, + "::first-line changes don't trigger transitions"); + } else { + is (fline_i_cs.color, COLOR_IN_LATER_LINES, + "::first-line changes don't trigger transitions"); + } + + fline.removeAttribute("class"); +} + +var firstline_tests = [ + { widening: true, set_overflow: false }, + { widening: false, set_overflow: false }, + { widening: true, set_overflow: true }, + { widening: false, set_overflow: true }, +]; + +for (var firstline_test_idx in firstline_tests) { + do_firstline_test(firstline_tests[firstline_test_idx]); +} + +function do_flexbox_reframe_test() +{ + var flextransition = document.getElementById("flextransition"); + var cs = getComputedStyle(flextransition, ""); + cs.backgroundColor; + flextransition.setAttribute("newstyle", ""); + document.getElementById("flexkid").setAttribute("newstyle", ""); + is(cs.color, "rgb(0, 0, 255)", + "color at start of wrapped flexbox transition"); + advance_clock(1000); + is(cs.color, "rgb(51, 51, 204)", + "color one second in to wrapped flexbox transition"); +} + +do_flexbox_reframe_test(); + +SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_and_restyles.html b/layout/style/test/test_transitions_and_restyles.html new file mode 100644 index 000000000..68085a712 --- /dev/null +++ b/layout/style/test/test_transitions_and_restyles.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1030993 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1030993</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + #display { + background: blue; height: 10px; width: 0; color: black; + transition: width linear 1s, color linear 1s; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1030993">Mozilla Bug 1030993</a> +<p id="display"></p> +<pre id="test"> +</pre> +<script type="application/javascript"> + +/** Test for Bug 1030993 **/ + +function advance_clock(milliseconds) { + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds); +} + +var p = document.getElementById("display"); +var cs = getComputedStyle(p, ""); +advance_clock(0); +cs.width; // flush +p.style.width = "1000px"; // initiate transition +is(cs.width, "0px", "transition at 0ms"); // flush (and test) +advance_clock(100); +is(cs.width, "100px", "transition at 100ms"); // flush +// restyle *and* trigger new transitions +p.style.color = "blue"; +// flush again, at the same timestamp +is(cs.width, "100px", "transition at 100ms, after restyle"); + +SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + +</script> +</body> +</html> diff --git a/layout/style/test/test_transitions_and_zoom.html b/layout/style/test/test_transitions_and_zoom.html new file mode 100644 index 000000000..265339af4 --- /dev/null +++ b/layout/style/test/test_transitions_and_zoom.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=583219 +--> +<head> + <title>Test for Bug 583219</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + #display { + transition: margin-left 1s linear; + } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=583219">Mozilla Bug 583219</a> +<p id="display"></p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 583219 **/ + +var p = document.getElementById("display"); +var cs = getComputedStyle(p, ""); +cs.marginLeft; + +p.addEventListener("transitionend", TransitionEndHandler, false); +p.style.marginLeft = "100px"; +cs.marginLeft; + +SpecialPowers.setFullZoom(window, 2.0) + +SimpleTest.waitForExplicitFinish(); + +function TransitionEndHandler(event) { + ok(true, "transition has completed"); + is(event.propertyName, "margin-left", "event.propertyName"); + is(cs.marginLeft, "100px", "value of margin-left"); + SpecialPowers.setFullZoom(window, 1.0) + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_bug537151.html b/layout/style/test/test_transitions_bug537151.html new file mode 100644 index 000000000..a3640e393 --- /dev/null +++ b/layout/style/test/test_transitions_bug537151.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=537151 +--> +<head> + <title>Test for Bug 537151</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + #display { + transition: margin-left 200ms; + } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=537151">Mozilla Bug 537151</a> +<p id="display">Paragraph</p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 537151 **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var p = document.getElementById("display"); +p.addEventListener("transitionend", listener, false); +var ignored = getComputedStyle(p, "").marginLeft; +p.style.marginLeft = "150px"; + +var event_count = 0; +function listener(event) +{ + ++event_count; + setTimeout(finish, 400); + p.style.color = "blue"; +} + +function finish() +{ + is(event_count, 1, "should have gotten only 1 transitionend event"); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_cancel_near_end.html b/layout/style/test/test_transitions_cancel_near_end.html new file mode 100644 index 000000000..4fca67ada --- /dev/null +++ b/layout/style/test/test_transitions_cancel_near_end.html @@ -0,0 +1,82 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=613888 +--> +<head> + <title>Test for Bug 613888</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css"> + #animated-elements-container > span { + color: black; + background: white; + transition: + color 10s ease-out, + background 1s ease-out; + } + #animated-elements-container > span.another { + color: white; + background: black; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=613888">Mozilla Bug 613888</a> +<pre id="animated-elements-container"> + <span should-restyle="true">canceled on a half of the animation</span> + <span should-restyle="true">canceled too fast, and restyled on transitionend</span> + <span>canceled too fast, but not restyled on transitionend</span> +</pre> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 613888: that we don't cancel transitions when they're + about to end (current interpolated value rounds to ending value) and + they get an unrelated style change. **/ + +var count_remaining = 6; + +window.addEventListener('load', function() { + var cases = Array.slice(document.querySelectorAll('#animated-elements-container > span')); + + cases.forEach(function(aTarget) { + aTarget.addEventListener('transitionend', function(aEvent) { + if (aTarget.hasAttribute('should-restyle')) + aTarget.style.outline = '1px solid'; + var attr = 'transitionend-' + aEvent.propertyName; + if (aTarget.hasAttribute(attr)) { + // It's possible, given bad timers, that we might get a + // transition that completed before we reversed it, which could + // lead to two transitionend events for the same thing. We + // don't want to decrement count_remaining in this case. + return; + } + aTarget.setAttribute(attr, "true"); + if (--count_remaining == 0) { + cases.forEach(function(aCase, aIndex) { + ok(aCase.hasAttribute('transitionend-color'), + "transitionend for color was fired for case "+aIndex); + ok(aCase.hasAttribute('transitionend-background-color'), + "transitionend for background-color was fired for case "+aIndex); + }); + SimpleTest.finish(); + } + }, false); + }); + + cases.forEach(aCase => aCase.className = 'another' ); + + window.setTimeout(() => cases[0].className = '', 500); + window.setTimeout(() => cases[1].className = cases[2].className = '', 250); + +}, false); + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_computed_value_combinations.html b/layout/style/test/test_transitions_computed_value_combinations.html new file mode 100644 index 000000000..f0421eeb4 --- /dev/null +++ b/layout/style/test/test_transitions_computed_value_combinations.html @@ -0,0 +1,170 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=435441 +--> +<head> + <title>Test for Bug 435441</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 435441 **/ + + +/** + * I want to test a reasonable number of combinations rather than all of + * them, but I also want the test results to be reproducable. So use a + * simple random number generator with my own seed. See + * http://en.wikipedia.org/wiki/Linear_congruential_generator + * (Using the numbers from Numerical Recipes.) + */ +var rand_state = 1938266273; // a randomly (once) generated number in [0,2^32) +var all_integers = true; +function myrand() +{ + rand_state = ((rand_state * 1664525) + 1013904223) % 0x100000000; + all_integers = all_integers && + Math.ceil(rand_state) == Math.floor(rand_state); + return rand_state / 0x100000000; // return value in [0,1) +} + +// We want to test a bunch of values for each property. +// Each of these values will also have a "computed" property filled in +// below, so that we ensure it always computes to the same value. +var values = { + "transition-duration": + [ + { lone: true, specified: "initial" }, + { lone: false, specified: "2s" }, + { lone: false, specified: "0s" }, + { lone: false, specified: "430ms" }, + { lone: false, specified: "1s" }, + ], + "transition-property": + [ + { lone: true, specified: "initial" }, + { lone: true, specified: "none" }, + { lone: true, specified: "all" }, + { lone: false, specified: "color" }, + { lone: false, specified: "border-spacing" }, + // Make sure to test the "unknown property" case. + { lone: false, specified: "unsupported-property" }, + { lone: false, specified: "-other-unsupported-property" }, + ], + "transition-timing-function": + [ + { lone: true, specified: "initial" }, + { lone: false, specified: "linear" }, + { lone: false, specified: "ease" }, + { lone: false, specified: "ease-in-out" }, + { lone: false, specified: "cubic-bezier(0, 0, 0.63, 1.00)" }, + ], + "transition-delay": + [ + { lone: true, specified: "initial" }, + { lone: false, specified: "2s" }, + { lone: false, specified: "0s" }, + { lone: false, specified: "430ms" }, + { lone: false, specified: "-1s" }, + ], +}; + +var elt = document.getElementById("content"); +var cs = getComputedStyle(elt, ""); + +// Add the "computed" property to all of the above values. +for (var prop in values) { + var valueset = values[prop]; + for (var index in valueset) { + var item = valueset[index]; + elt.style.setProperty(prop, item.specified, ""); + item.computed = cs.getPropertyValue(prop); + elt.style.removeProperty(prop); + isnot(item.computed, "", "computed value must not be empty"); + if (index != 0) { + isnot(item.computed, valueset[index-1].computed, + "computed value must not be the same as the last one"); + } + } +} + +var child = document.createElement("div"); +elt.appendChild(child); +var child_cs = getComputedStyle(child, ""); + +// Now test a hundred random combinations of values on the parent and +// child. +for (var iteration = 0; iteration < 100; ++iteration) { + // Figure out values on the parent. + var parent_vals = {}; + for (var prop in values) { + var valueset = values[prop]; + var list_length = Math.ceil(Math.pow(myrand(), 2) * 6); + // 41% chance of length 1 + var specified = []; + var computed = []; + for (var i = 0; i < list_length; ++i) { + var index; + do { + index = Math.floor(myrand() * valueset.length); + } while (list_length != 1 && valueset[index].lone); + specified.push(valueset[index].specified); + computed.push(valueset[index].computed); + } + parent_vals[prop] = { specified: specified.join(", "), + computed: computed.join(", ") }; + elt.style.setProperty(prop, parent_vals[prop].specified, ""); + } + + // Figure out values on the child. + var child_vals = {}; + for (var prop in values) { + var valueset = values[prop]; + // Use 0 as a magic value for "inherit". + var list_length = Math.floor(Math.pow(myrand(), 1.5) * 7); + // 27% chance of inherit + // 16% chance of length 1 + if (list_length == 0) { + child_vals[prop] = { specified: "inherit", + computed: parent_vals[prop].computed }; + } else { + var specified = []; + var computed = []; + for (var i = 0; i < list_length; ++i) { + var index; + do { + index = Math.floor(myrand() * valueset.length); + } while (list_length != 1 && valueset[index].lone); + specified.push(valueset[index].specified); + computed.push(valueset[index].computed); + } + child_vals[prop] = { specified: specified.join(", "), + computed: computed.join(", ") }; + } + child.style.setProperty(prop, child_vals[prop].specified, ""); + } + + // Test computed values + for (var prop in values) { + is(cs.getPropertyValue(prop), parent_vals[prop].computed, + "computed value of " + prop + ": " + parent_vals[prop].specified + + " on parent."); + is(child_cs.getPropertyValue(prop), child_vals[prop].computed, + "computed value of " + prop + ": " + child_vals[prop].specified + + " on child."); + } +} + +ok(all_integers, "pseudo-random number generator kept its numbers " + + "as integers throughout run"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_computed_values.html b/layout/style/test/test_transitions_computed_values.html new file mode 100644 index 000000000..c84b8e6a8 --- /dev/null +++ b/layout/style/test/test_transitions_computed_values.html @@ -0,0 +1,113 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=435441 +--> +<head> + <title>Test for Bug 435441</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 435441 **/ + + +/* + * test that when transition properties are inherited, the length of the + * computed value stays the same + */ + +var p = document.getElementById("content"); +var c = document.createElement("div"); +p.appendChild(c); +var cs = getComputedStyle(c, ""); + +p.style.transitionProperty = "margin-left, margin-right"; +c.style.transitionProperty = "inherit"; +is(cs.transitionProperty, "margin-left, margin-right", + "computed style match with no other properties"); +c.style.transitionDuration = "5s"; +is(cs.transitionProperty, "margin-left, margin-right", + "computed style match with shorter property"); +is(cs.transitionDuration, "5s", + "shorter property not extended"); +c.style.transitionDuration = "5s, 4s, 3s, 2000ms"; +is(cs.transitionProperty, "margin-left, margin-right", + "computed style match with longer property"); +is(cs.transitionDuration, "5s, 4s, 3s, 2s", + "longer property computed correctly"); +p.style.transitionProperty = ""; +c.style.transitionProperty = ""; +c.style.transitionDuration = ""; + +// and repeat the above set of tests with property and duration swapped +p.style.transitionDuration = "5s, 4s"; +c.style.transitionDuration = "inherit"; +is(cs.transitionDuration, "5s, 4s", + "computed style match with no other properties"); +c.style.transitionProperty = "margin-left"; +is(cs.transitionDuration, "5s, 4s", + "computed style match with shorter property"); +is(cs.transitionProperty, "margin-left", + "shorter property not extended"); +c.style.transitionProperty = + "margin-left, margin-right, margin-top, margin-bottom"; +is(cs.transitionDuration, "5s, 4s", + "computed style match with longer property"); +is(cs.transitionProperty, + "margin-left, margin-right, margin-top, margin-bottom", + "longer property computed correctly"); +p.style.transitionDuration = ""; +c.style.transitionDuration = ""; +c.style.transitionProperty = ""; + +// And do the same pair of tests for animations: + +p.style.animationName = "bounce, roll"; +c.style.animationName = "inherit"; +is(cs.animationName, "bounce, roll", + "computed style match with no other properties"); +c.style.animationDuration = "5s"; +is(cs.animationName, "bounce, roll", + "computed style match with shorter property"); +is(cs.animationDuration, "5s", + "shorter property not extended"); +c.style.animationDuration = "5s, 4s, 3s, 2000ms"; +is(cs.animationName, "bounce, roll", + "computed style match with longer property"); +is(cs.animationDuration, "5s, 4s, 3s, 2s", + "longer property computed correctly"); +p.style.animationName = ""; +c.style.animationName = ""; +c.style.animationDuration = ""; + +// and repeat the above set of tests with name and duration swapped +p.style.animationDuration = "5s, 4s"; +c.style.animationDuration = "inherit"; +is(cs.animationDuration, "5s, 4s", + "computed style match with no other properties"); +c.style.animationName = "bounce"; +is(cs.animationDuration, "5s, 4s", + "computed style match with shorter property"); +is(cs.animationName, "bounce", + "shorter property not extended"); +c.style.animationName = + "bounce, roll, wiggle, spin"; +is(cs.animationDuration, "5s, 4s", + "computed style match with longer property"); +is(cs.animationName, + "bounce, roll, wiggle, spin", + "longer property computed correctly"); +p.style.animationDuration = ""; +c.style.animationDuration = ""; +c.style.animationName = ""; + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_dynamic_changes.html b/layout/style/test/test_transitions_dynamic_changes.html new file mode 100644 index 000000000..b74c50a49 --- /dev/null +++ b/layout/style/test/test_transitions_dynamic_changes.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=525530 +--> +<head> + <title>Test for Bug 525530</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525530">Mozilla Bug 525530</a> +<p id="display" style="text-indent: 100px"></p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 525530 **/ + +var p = document.getElementById("display"); +var cs = getComputedStyle(p, ""); +var utils = SpecialPowers.DOMWindowUtils; + +p.style.transitionProperty = "all"; +p.style.transitionDuration = "4s"; +p.style.transitionDelay = "-2s"; +p.style.transitionTimingFunction = "linear"; + +is(cs.textIndent, "100px", "initial value"); + +p.style.textIndent = "0"; +is(cs.textIndent, "50px", "transition is halfway"); +p.style.transitionDuration = "0s"; +is(cs.textIndent, "50px", "changing duration doesn't change transitioning"); +p.style.transitionDelay = "0s"; +is(cs.textIndent, "50px", "changing delay doesn't change transitioning"); +p.style.transitionProperty = "text-indent"; +is(cs.textIndent, "50px", + "irrelevant change to transition property doesn't change transitioning"); +p.style.transitionProperty = "font"; +is(cs.textIndent, "0px", + "relevant change to transition property does change transitioning"); + +/** Test for Bug 522643 */ +p.style.transitionDuration = "4s"; +p.style.transitionDelay = "-2s"; +p.style.transitionProperty = "text-indent"; +p.style.textIndent = "100px"; +is(cs.textIndent, "50px", "transition is halfway"); +p.style.transitionDuration = "0s"; +p.style.transitionDelay = "0s"; +is(cs.textIndent, "50px", + "changing duration and delay doesn't change transitioning"); +p.style.textIndent = "0px"; +is(cs.textIndent, "0px", + "changing property after changing duration and delay stops transition"); + +/** Test for Bug 1133375 */ +p.style.transitionDuration = "1s"; +p.style.transitionDelay = "-1s"; +p.style.transitionProperty = "text-indent"; +var endCount = 0; +function incrementEndCount(event) { ++endCount; } +p.addEventListener("transitionend", incrementEndCount, false); +utils.advanceTimeAndRefresh(0); +p.style.textIndent = "100px"; +is(cs.textIndent, "100px", "value should now be 100px"); +utils.advanceTimeAndRefresh(10); +is(endCount, 0, "should not have started transition when combined duration less than or equal to 0"); +p.style.transitionDelay = "-2s"; +p.style.textIndent = "0"; +is(cs.textIndent, "0px", "value should now be 0px"); +utils.advanceTimeAndRefresh(10); +is(endCount, 0, "should not have started transition when combined duration less than or equal to 0"); +utils.restoreNormalRefresh(); +p.style.textIndent = ""; + +/** Test for bug 1144410 */ +utils.advanceTimeAndRefresh(0); +p.style.transition = "opacity 200ms linear"; +p.style.opacity = "1"; +is(cs.opacity, "1", "bug 1144410 test - initial opacity"); +p.style.opacity = "0"; +is(cs.opacity, "1", "bug 1144410 test - opacity after starting transition"); +utils.advanceTimeAndRefresh(100); +is(cs.opacity, "0.5", "bug 1144410 test - opacity during transition"); +utils.advanceTimeAndRefresh(200); +is(cs.opacity, "0", "bug 1144410 test - opacity after transition"); +document.body.style.display = "none"; +is(cs.opacity, "0", "bug 1144410 test - opacity after display:none"); +p.style.opacity = "1"; +document.body.style.display = ""; +is(cs.opacity, "1", "bug 1144410 test - second transition, initial opacity"); +p.style.opacity = "0"; +is(cs.opacity, "1", "bug 1144410 test - opacity after starting second transition"); +utils.advanceTimeAndRefresh(100); +is(cs.opacity, "0.5", "bug 1144410 test - opacity during second transition"); +utils.advanceTimeAndRefresh(200); +is(cs.opacity, "0", "bug 1144410 test - opacity after second transition"); +utils.restoreNormalRefresh(); +p.style.opacity = ""; +p.style.transition = ""; + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_events.html b/layout/style/test/test_transitions_events.html new file mode 100644 index 000000000..6df8ab30c --- /dev/null +++ b/layout/style/test/test_transitions_events.html @@ -0,0 +1,282 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=531585 +--> +<head> + <title>Test for Bug 531585 (transitionend event)</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<style type="text/css"> + +.bar { margin: 10px; } + +#one { transition-duration: 500ms; transition-property: all; } +#two { transition: margin-left 1s; } +#three { transition: margin 0.5s 0.25s; } + +#four, #five, #six, #seven::before, #seven::after { + transition: 500ms color; + border-color: black; /* don't derive from color */ + -moz-column-rule-color: black; /* don't derive from color */ + text-decoration-color: black; /* don't derive from color */ + outline-color: black; /* don't derive from color */ +} + +#four { + /* give the reversing transition a long duration; the reversing will + still be quick */ + transition-duration: 30s; + transition-timing-function: cubic-bezier(0, 1, 1, 0); +} + +#seven::before, #seven::after { + content: "x"; + transition-duration: 50ms; +} +#seven[foo]::before, #seven[foo]::after { color: lime; } + +</style> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=531585">Mozilla Bug 531585</a> +<p id="display"> + +<span id="one" style="color:blue"></span> +<span id="two"></span> +<span id="three"></span> +<span id="four" style="color: blue"></span> +<span id="five" style="color: blue"></span> +<span id="six" style="color: blue"></span> +<span id="seven" style="color: blue"></span> + +</p> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 531585 (transitionend event) **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +var gTestCount = 0; +function started_test() { ++gTestCount; } +function finished_test() { if (--gTestCount == 0) { SimpleTest.finish(); } } + +function $(id) { return document.getElementById(id); } +function cs(id) { return getComputedStyle($(id), ""); } + +var got_one_root = false; +var got_one_target = false; +var got_two_target = false; +var got_three_top = false; +var got_three_right = false; +var got_three_bottom = false; +var got_three_left = false; +var got_four_root = false; +var got_body = false; +var did_stops = false; +var got_before = false; +var got_after = false; + +document.documentElement.addEventListener("transitionend", + function(event) { + if (event.target == $("one")) { + ok(!got_one_root, "transitionend on one on root"); + is(event.propertyName, "border-right-color", + "propertyName for transitionend on one"); + is(event.elapsedTime, 0.5, + "elapsedTime for transitionend on one"); + is(cs("one").borderRightColor, "rgb(0, 255, 0)", + "computed style for transitionend on one"); + got_one_root = true; + finished_test(); + } else if (event.target == $("four")) { + ok(!got_four_root, "transitionend on four on root"); + is(event.propertyName, "color", + "propertyName for transitionend on four"); + // Reported time should (really?) be shortened by reversing. + ok(event.elapsedTime < 30, + "elapsedTime for transitionend on four"); + is(cs("four").color, "rgb(0, 0, 255)", + "computed style for transitionend on four (end of reverse transition)"); + got_four_root = true; + finished_test(); + } else if (event.target == document.body) { + // A synthesized event. + ok(!got_body, "transitionend on body on root"); + is(event.propertyName, "some-unknown-prop", + "propertyName for transitionend on body"); + // Reported time should (really?) be shortened by reversing. + is(event.elapsedTime, 0.5, + "elapsedTime for transitionend on body"); + got_body = true; + finished_test(); + } else if (event.target == $("seven")) { + if (!got_before) { + got_before = true; + is(event.pseudoElement, "::before"); + } else { + ok(!got_after, "transitionend on #seven::after"); + got_after = true; + is(event.pseudoElement, "::after"); + } + is(event.propertyName, "color"); + is(event.isTrusted, true); + finished_test(); + } else { + if (!did_stops && + (event.target == $("five") || event.target == $("six"))) { + todo(false, + "timeout to stop transitions firing later than it should be"); + return; + } + ok(false, + "unexpected event on " + event.target.nodeName + + " element with id '" + event.target.id + "' " + + "elapsedTime=" + event.elapsedTime + + " propertyName='" + event.propertyName + "'"); + } + }, false); + +$("one").addEventListener("transitionend", + function(event) { + is(event.propertyName, "color", "unexpected " + + "property name for transitionend on one on target"); + ok(!got_one_target, + "transitionend on one on target (color)"); + got_one_target = true; + event.stopPropagation(); + is(event.elapsedTime, 0.5, + "elapsedTime for transitionend on one"); + is(cs("one").getPropertyValue(event.propertyName), "rgb(0, 255, 0)", + "computed style of " + event.propertyName + " for transitionend on one"); + finished_test(); + }, false); + +started_test(); // color on #one +$("one").style.color = "lime"; + + +$("two").addEventListener("transitionend", + function(event) { + event.stopPropagation(); + + ok(!got_two_target, "transitionend on two on target"); + is(event.propertyName, "margin-left", + "propertyName for transitionend on two"); + is(event.elapsedTime, 1, + "elapsedTime for transitionend on two"); + is(event.bubbles, true, + "transitionend events should bubble"); + is(event.cancelable, false, + "transitionend events should not be cancelable"); + is(cs("two").marginLeft, "10px", + "computed style for transitionend on two"); + got_two_target = true; + finished_test(); + }, false); + +started_test(); // #two +$("two").className = "bar"; + +$("three").addEventListener("transitionend", + function(event) { + event.stopPropagation(); + + switch (event.propertyName) { + case "margin-top": + ok(!got_three_top, "should only get margin-top once"); + got_three_top = true; + break; + case "margin-right": + ok(!got_three_right, "should only get margin-right once"); + got_three_right = true; + break; + case "margin-bottom": + ok(!got_three_bottom, "should only get margin-bottom once"); + got_three_bottom = true; + break; + case "margin-left": + ok(!got_three_left, "should only get margin-left once"); + got_three_left = true; + break; + default: + ok(false, "unexpected property name " + event.propertyName + + " for transitionend on three"); + } + is(event.elapsedTime, 0.5, + "elapsedTime for transitionend on three"); + is(cs("three").getPropertyValue(event.propertyName), "10px", + "computed style for transitionend on three"); + finished_test(); + }, true); + +started_test(); // margin-top on #three +started_test(); // margin-right on #three +started_test(); // margin-bottom on #three +started_test(); // margin-left on #three +$("three").className = "bar"; + +// We reverse the transition on four, and we should only get an event +// at the end of the second transition. +started_test(); // #four (listener on root) +$("four").style.color = "lime"; + +// We cancel the transition on five by changing 'transition-property', +// and should thus get no event. +$("five").style.color = "lime"; + +// We cancel the transition on six by changing 'transition-duration' and +// then changing the value, so we should get no event. +$("six").style.color = "lime"; + +started_test(); // #seven::before (listener on root) +started_test(); // #seven::after (listener on root) +$("seven").setAttribute("foo", "bar"); + +setTimeout(function() { + if (cs("five") != "rgb(0, 255, 0)" && + cs("six") != "rgb(0, 255, 0)") { + // The transition hasn't finished already. + did_stops = true; + } + $("five").style.transitionProperty = "margin-left"; + $("six").style.transitionDuration = "0s"; + $("six").style.transitionDelay = "0s"; + $("six").style.color = "blue"; + }, 100); +function poll_start_reversal() { + if (cs("four").color != "rgb(0, 0, 255)") { + // The forward transition has started. + $("four").style.color = "blue"; + } else { + // The forward transition has not started yet. + setTimeout(poll_start_reversal, 20); + } +} +setTimeout(poll_start_reversal, 200); + +// And make our own event to dispatch to the body. +started_test(); // synthesized event to body (listener on root) + +var e = new TransitionEvent("transitionend", + { + bubbles: true, + cancelable: true, + propertyName: "some-unknown-prop", + elapsedTime: 0.5, + pseudoElement: "pseudo" + }); +is(e.bubbles, true); +is(e.cancelable, true); +is(e.propertyName, "some-unknown-prop"); +is(e.elapsedTime, 0.5); +is(e.pseudoElement, "pseudo"); +is(e.isTrusted, false) + +document.body.dispatchEvent(e); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_per_property.html b/layout/style/test/test_transitions_per_property.html new file mode 100644 index 000000000..29e2ae24c --- /dev/null +++ b/layout/style/test/test_transitions_per_property.html @@ -0,0 +1,2565 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=435441 +--> +<head> + <title>Test for Bug 435441</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <script type="text/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + #display > p { margin-top: 0; margin-bottom: 0; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a> + +<!-- + fixed-height container so percentage heights compute to different + (i.e., nonzero) values + fixed-width container so that percentages for margin-top and + margin-bottom are all relative to the same size container (rather than + one that depends on whether we're tall enough to need a scrollbar) + + Use a 20px font size and line-height so that percentage line-height + and vertical-align doesn't accumulate rounding error. + --> +<div style="height: 50px; width: 300px; font-size: 20px; line-height: 20px"> + +<div id="display"> +</div> + +<div id="transformTest" style="height:100px; width:200px; background-color:blue;"> +</div> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 435441 **/ + +SimpleTest.requestLongerTimeout(2); +SimpleTest.waitForExplicitFinish(); + +function has_num(str) +{ + return !!String(str).match(/^([\d.]+)/); +} + +function any_unit_to_num(str) +{ + return Number(String(str).match(/^([\d.]+)/)[1]); +} + +var FUNC_NEGATIVE = "cubic-bezier(0.25, -2, 0.75, 1)"; +var FUNC_OVERONE = "cubic-bezier(0.25, 0, 0.75, 3)"; + +var supported_properties = { + "border-bottom-left-radius": [ test_radius_transition ], + "border-bottom-right-radius": [ test_radius_transition ], + "border-top-left-radius": [ test_radius_transition ], + "border-top-right-radius": [ test_radius_transition ], + "-moz-box-flex": [ test_float_zeroToOne_transition, + test_float_aboveOne_transition, + test_float_zeroToOne_clamped ], + "box-shadow": [ test_shadow_transition ], + "column-count": [ test_pos_integer_or_auto_transition, + test_integer_at_least_one_clamping ], + "column-gap": [ test_length_transition, + test_length_clamped ], + "column-rule-color": [ test_color_transition, + test_true_currentcolor_transition ], + "column-rule-width": [ test_length_transition, + test_length_clamped ], + "column-width": [ test_length_transition, + test_length_clamped ], + "-moz-image-region": [ test_rect_transition ], + "-moz-outline-radius-bottomleft": [ test_radius_transition ], + "-moz-outline-radius-bottomright": [ test_radius_transition ], + "-moz-outline-radius-topleft": [ test_radius_transition ], + "-moz-outline-radius-topright": [ test_radius_transition ], + "background-color": [ test_color_transition, + test_currentcolor_transition ], + "background-position": [ test_background_position_transition, + // FIXME: We don't currently test clamping, + // since background-position uses calc() as + // an intermediate form. + /* test_length_percent_pair_unclamped */ ], + "background-position-x": [ test_background_position_coord_transition, + test_length_transition, + test_percent_transition, + // FIXME: We don't currently test clamping, + // since background-position-x uses calc() as + // an intermediate form. + /* test_length_percent_pair_unclamped */ ], + "background-position-y": [ test_background_position_coord_transition, + test_length_transition, + test_percent_transition, + // FIXME: We don't currently test clamping, + // since background-position-y uses calc() as + // an intermediate form. + /* test_length_percent_pair_unclamped */ ], + "background-size": [ test_background_size_transition, + // FIXME: We don't currently test clamping, + // since background-size uses calc() as an + // intermediate form. + /* test_length_percent_pair_clamped */ ], + "border-bottom-color": [ test_color_transition, + test_true_currentcolor_transition ], + "border-bottom-width": [ test_length_transition, + test_length_clamped ], + "border-left-color": [ test_color_transition, + test_true_currentcolor_transition ], + "border-left-width": [ test_length_transition, + test_length_clamped ], + "border-right-color": [ test_color_transition, + test_true_currentcolor_transition ], + "border-right-width": [ test_length_transition, + test_length_clamped ], + "border-spacing": [ test_length_pair_transition, + test_length_pair_transition_clamped ], + "border-top-color": [ test_color_transition, + test_true_currentcolor_transition ], + "border-top-width": [ test_length_transition, + test_length_clamped ], + "bottom": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_unclamped, test_percent_unclamped ], + "clip": [ test_rect_transition ], + "clip-path": [ test_clip_path_transition ], + "color": [ test_color_transition, + test_currentcolor_transition ], + "fill": [ test_color_transition, + test_currentcolor_transition ], + "fill-opacity" : [ test_float_zeroToOne_transition, + // opacity is clamped in computed style + // (not parsing/interpolation) + test_float_zeroToOne_clamped ], + "filter" : [ test_filter_transition ], + "flex-basis": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "flex-grow": [ test_float_zeroToOne_transition, + test_float_aboveOne_transition ], + "flex-shrink": [ test_float_zeroToOne_transition, + test_float_aboveOne_transition ], + "flood-color": [ test_color_transition, + test_currentcolor_transition ], + "flood-opacity" : [ test_float_zeroToOne_transition, + // opacity is clamped in computed style + // (not parsing/interpolation) + test_float_zeroToOne_clamped ], + "font-size": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_clamped, test_percent_clamped ], + "font-size-adjust": [ test_float_zeroToOne_transition, + test_float_aboveOne_transition, + /* FIXME: font-size-adjust treats zero specially */ + /* test_float_zeroToOne_clamped */ ], + "font-stretch": [ test_font_stretch ], + "font-weight": [ test_font_weight ], + "grid-column-gap": [ test_grid_gap ], + "grid-row-gap": [ test_grid_gap ], + "height": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_clamped, test_percent_clamped ], + "left": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_unclamped, test_percent_unclamped ], + "letter-spacing": [ test_length_transition, test_length_unclamped ], + "lighting-color": [ test_color_transition, + test_currentcolor_transition ], + // NOTE: when calc() is supported on 'line-height', we should add + // test_length_percent_calc_transition. + "line-height": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "margin-bottom": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_unclamped, test_percent_unclamped ], + "margin-left": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_unclamped, test_percent_unclamped ], + "margin-right": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_unclamped, test_percent_unclamped ], + "margin-top": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_unclamped, test_percent_unclamped ], + "max-height": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "max-width": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "min-height": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "min-width": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "object-position": [ test_background_position_transition ], + "opacity" : [ test_float_zeroToOne_transition, + // opacity is clamped in computed style + // (not parsing/interpolation) + test_float_zeroToOne_clamped ], + "order": [ test_integer_transition ], + "outline-color": [ test_color_transition, + test_true_currentcolor_transition ], + "outline-offset": [ test_length_transition, test_length_unclamped ], + "outline-width": [ test_length_transition, test_length_clamped ], + "padding-bottom": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_clamped, test_percent_clamped ], + "padding-left": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_clamped, test_percent_clamped ], + "padding-right": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_clamped, test_percent_clamped ], + "padding-top": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_clamped, test_percent_clamped ], + "perspective": [ test_length_transition ], + "perspective-origin": [ test_length_pair_transition, + test_length_percent_pair_transition, + test_length_percent_pair_unclamped ], + "right": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_unclamped, test_percent_unclamped ], + "stop-color": [ test_color_transition, + test_currentcolor_transition ], + "stop-opacity" : [ test_float_zeroToOne_transition, + // opacity is clamped in computed style + // (not parsing/interpolation) + test_float_zeroToOne_clamped ], + "stroke": [ test_color_transition, + test_currentcolor_transition ], + "stroke-dasharray": [ test_dasharray_transition ], + // NOTE: when calc() is supported on 'stroke-dashoffset', we should + // add test_length_percent_calc_transition. + "stroke-dashoffset": [ test_length_transition_svg, test_percent_transition, + test_length_unclamped_svg, test_percent_unclamped ], + "stroke-miterlimit": [ test_float_aboveOne_transition, + test_float_aboveOne_clamped ], + "stroke-opacity" : [ test_float_zeroToOne_transition, + // opacity is clamped in computed style + // (not parsing/interpolation) + test_float_zeroToOne_clamped ], + // NOTE: when calc() is supported on 'stroke-width', we should add + // test_length_percent_calc_transition. + "stroke-width": [ test_length_transition_svg, test_percent_transition, + test_length_clamped_svg, test_percent_clamped ], + "text-decoration": [ test_color_shorthand_transition, + test_true_currentcolor_shorthand_transition ], + "text-decoration-color": [ test_color_transition, + test_true_currentcolor_transition ], + "text-emphasis-color": [ test_color_transition, + test_true_currentcolor_transition ], + "text-indent": [ test_length_transition, test_percent_transition, + test_length_unclamped, test_percent_unclamped ], + "text-shadow": [ test_shadow_transition ], + "top": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_unclamped, test_percent_unclamped ], + "transform": [ test_transform_transition ], + "transform-origin": [ test_length_pair_transition, + test_length_percent_pair_transition, + test_length_percent_pair_unclamped ], + "vertical-align": [ test_length_transition, test_percent_transition, + test_length_unclamped, test_percent_unclamped ], + "visibility": [ test_visibility_transition ], + "width": [ test_length_transition, test_percent_transition, + test_length_percent_calc_transition, + test_length_clamped, test_percent_clamped ], + "word-spacing": [ test_length_transition, test_length_unclamped ], + "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ], + "-webkit-text-fill-color": [ test_color_transition, + test_true_currentcolor_transition ], + "-webkit-text-stroke-color": [ test_color_transition, + test_true_currentcolor_transition ] +}; + +if (SupportsMaskShorthand()) { + supported_properties["mask-position"] = [ test_background_position_transition, + // FIXME: We don't currently test clamping, + // since mask-position uses calc() as + // an intermediate form. + /* test_length_percent_pair_unclamped */ ]; + supported_properties["mask-position-x"] = [ test_background_position_coord_transition, + test_length_transition, + test_percent_transition, + // FIXME: We don't currently test clamping, + // since background-position-x uses calc() as + // an intermediate form. + /* test_length_percent_pair_unclamped */ ]; + supported_properties["mask-position-y"] = [ test_background_position_coord_transition, + test_length_transition, + test_percent_transition, + // FIXME: We don't currently test clamping, + // since background-position-y uses calc() as + // an intermediate form. + /* test_length_percent_pair_unclamped */ ]; + supported_properties["mask-size"] = [ test_background_size_transition, + // FIXME: We don't currently test clamping, + // since mask-size uses calc() as an + // intermediate form. + /* test_length_percent_pair_clamped */ ]; +} + +var div = document.getElementById("display"); +var OMTAdiv = document.getElementById("transformTest"); +var cs = getComputedStyle(div, ""); +var OMTACs = getComputedStyle(OMTAdiv, ""); +var winUtils = SpecialPowers.getDOMWindowUtils(window); + +function computeMatrix(v) { + div.style.setProperty("transform", v, ""); + var result = cs.getPropertyValue("transform"); + div.style.removeProperty("transform"); + return result; +} +var c_rot_15 = computeMatrix("rotate(15deg)"); +is(c_rot_15.substring(0,6), "matrix", "should compute to matrix value"); +var c_rot_60 = computeMatrix("rotate(60deg)"); +is(c_rot_60.substring(0,6), "matrix", "should compute to matrix value"); + +var transformTests = [ + // rotate + { start: 'none', end: 'rotate(60deg)', + expected_uncomputed: 'rotate(15deg)', + expected: c_rot_15 }, + { start: 'rotate(0)', end: 'rotate(60deg)', + expected_uncomputed: 'rotate(15deg)', + expected: c_rot_15 }, + { start: 'rotate(0deg)', end: 'rotate(60deg)', + expected_uncomputed: 'rotate(15deg)', + expected: c_rot_15 }, + { start: 'none', end: c_rot_60, + expected: c_rot_15 }, + { start: 'none', end: 'rotate(360deg)', + expected_uncomputed: 'rotate(90deg)', + expected: computeMatrix('rotate(90deg)') }, + { start: 'none', end: 'rotatez(360deg)', + expected_uncomputed: 'rotate(90deg)', + expected: computeMatrix('rotate(90deg)') }, + { start: 'none', end: 'rotate(720deg)', + expected_uncomputed: 'rotate(180deg)', + expected: computeMatrix('rotate(180deg)') }, + { start: 'none', end: 'rotate(720deg)', + expected_uncomputed: 'rotatez(180deg)', + expected: computeMatrix('rotate(180deg)') }, + { start: 'none', end: 'rotate(1080deg)', + expected_uncomputed: 'rotate(270deg)', + expected: computeMatrix('rotate(270deg)') }, + { start: 'none', end: 'rotate(1080deg)', + expected_uncomputed: 'rotate(270deg)', + expected: computeMatrix('rotatez(270deg)') }, + { start: 'none', end: 'rotate(1440deg)', + expected_uncomputed: 'rotate(360deg)', + expected: computeMatrix('scale(1)'), + round_error_ok: true }, + { start: 'none', end: 'rotatey(60deg)', + expected_uncomputed: 'rotatey(15deg)', + expected: computeMatrix('rotatey(15deg)') }, + { start: 'none', end: 'rotatey(720deg)', + expected_uncomputed: 'rotatey(180deg)', + expected: computeMatrix('rotatey(180deg)') }, + { start: 'none', end: 'rotatex(60deg)', + expected_uncomputed: 'rotatex(15deg)', + expected: computeMatrix('rotatex(15deg)') }, + { start: 'none', end: 'rotatex(720deg)', + expected_uncomputed: 'rotatex(180deg)', + expected: computeMatrix('rotatex(180deg)') }, + + // translate + { start: 'translate(20px)', end: 'none', + expected_uncomputed: 'translate(15px)', + expected: 'matrix(1, 0, 0, 1, 15, 0)' }, + { start: 'translate(20px, 12px)', end: 'none', + expected_uncomputed: 'translate(15px, 9px)', + expected: 'matrix(1, 0, 0, 1, 15, 9)' }, + { start: 'translateX(-20px)', end: 'none', + expected_uncomputed: 'translateX(-15px)', + expected: 'matrix(1, 0, 0, 1, -15, 0)' }, + { start: 'translateY(-40px)', end: 'none', + expected_uncomputed: 'translateY(-30px)', + expected: 'matrix(1, 0, 0, 1, 0, -30)' }, + { start: 'translateZ(40px)', end: 'none', + expected_uncomputed: 'translateZ(30px)', + expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 30, 1)' }, + { start: 'none', end: 'translate3D(40px, 60px, -40px)', + expected_uncomputed: 'translate3D(10px, 15px, -10px)', + expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 15, -10, 1)' }, + // percentages are relative to 300px (width) and 50px (height) + // per the prerequisites in property_database.js + { start: 'translate(20%)', end: 'none', + expected_uncomputed: 'translate(15%)', + expected: 'matrix(1, 0, 0, 1, 45, 0)', + round_error_ok: true }, + { start: 'translate(20%, 12%)', end: 'none', + expected_uncomputed: 'translate(15%, 9%)', + expected: 'matrix(1, 0, 0, 1, 45, 4.5)', + round_error_ok: true }, + { start: 'translateX(-20%)', end: 'none', + expected_uncomputed: 'translateX(-15%)', + expected: 'matrix(1, 0, 0, 1, -45, 0)', + round_error_ok: true }, + { start: 'translateY(-40%)', end: 'none', + expected_uncomputed: 'translateY(-30%)', + expected: 'matrix(1, 0, 0, 1, 0, -15)', + round_error_ok: true }, + { start: 'none', end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)', + expected_uncomputed: 'rotate(22.5deg) translate(5%, 5%) rotate(-22.5deg)', + round_error_ok: true }, + { start: 'none', end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)', + expected_uncomputed: 'rotate(-22.5deg) translate(5%, 5%) rotate(22.5deg)', + round_error_ok: true }, + // test percent translation using matrix decomposition + { start: 'rotate(45deg) rotate(-45deg)', + end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)', + expected: 'matrix(1, 0, 0, 1, -2.5, 15)', + round_error_ok: true }, + { start: 'rotate(45deg) rotate(-45deg)', + end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)', + expected: 'matrix(1, 0, 0, 1, 2.5, -15)', + round_error_ok: true }, + // test calc() in translate + // Note that font-size: is 20px, and that percentages are relative + // to 300px (width) and 50px (height) per the prerequisites in + // property_database.js + { start: 'translateX(20%)', /* 60px */ + end: 'translateX(calc(10% + 1em))', /* 30px + 20px = 50px */ + expected_uncomputed: 'translateX(calc(17.5% + 0.25em))', + expected: 'matrix(1, 0, 0, 1, 57.5, 0)' }, + { start: 'translate(calc(0.75 * 3em + 1.5 * 10%), calc(0.5 * 5em + 0.5 * 8%))', /* 90px, 52px */ + end: 'rotate(90deg) translateY(20%) rotate(90deg) translateY(calc(10% + 0.5em)) rotate(180deg)', /* -10px, -15px */ + expected: 'matrix(1, 0, 0, 1, 65, 35.25)' }, + + // scale + { start: 'scale(2)', end: 'none', + expected_uncomputed: 'scale(1.75)', + expected: 'matrix(1.75, 0, 0, 1.75, 0, 0)' }, + { start: 'none', end: 'scale(0.4)', + expected_uncomputed: 'scale(0.85)', + expected: 'matrix(0.85, 0, 0, 0.85, 0, 0)', + round_error_ok: true }, + { start: 'scale(2)', end: 'scale(-2)', + expected_uncomputed: 'scale(1)', + expected: 'matrix(1, 0, 0, 1, 0, 0)' }, + { start: 'scale(2)', end: 'scale(-6)', + expected_uncomputed: 'scale(0)', + expected: 'matrix(0, 0, 0, 0, 0, 0)' }, + { start: 'scale(2, 0.4)', end: 'none', + expected_uncomputed: 'scale(1.75, 0.55)', + expected: 'matrix(1.75, 0, 0, 0.55, 0, 0)', + round_error_ok: true }, + { start: 'scaleX(3)', end: 'none', + expected_uncomputed: 'scaleX(2.5)', + expected: 'matrix(2.5, 0, 0, 1, 0, 0)' }, + { start: 'scaleY(5)', end: 'none', + expected_uncomputed: 'scaleY(4)', + expected: 'matrix(1, 0, 0, 4, 0, 0)' }, + { start: 'scaleZ(5)', end: 'none', + expected_uncomputed: 'scaleZ(4)', + expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1)' }, + { start: 'none', end: 'scale3D(5, 5, 5)', + expected_uncomputed: 'scale3D(2, 2, 2)', + expected: 'matrix3d(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1)' }, + + // skew + { start: 'skewX(45deg)', end: 'none', + expected_uncomputed: 'skewX(33.75deg)' }, + { start: 'skewY(45deg)', end: 'none', + expected_uncomputed: 'skewY(33.75deg)' }, + { start: 'skew(45deg)', end: 'none', + expected_uncomputed: 'skew(33.75deg)' }, + { start: 'skew(45deg, 45deg)', end: 'none', + expected_uncomputed: 'skew(33.75deg, 33.75deg)' }, + { start: 'skewX(45deg)', end: 'skewX(-45deg)', + expected_uncomputed: 'skewX(22.5deg)' }, + { start: 'skewX(0)', end: 'skewX(-45deg)', + expected_uncomputed: 'skewX(-11.25deg)' }, + { start: 'skewY(45deg)', end: 'skewY(-45deg)', + expected_uncomputed: 'skewY(22.5deg)' }, + + // matrix : skewX + { start: 'matrix(1, 0, 3, 1, 0, 0)', end: 'none', + expected: 'matrix(1, 0, ' + 3 * 0.75 + ', 1, 0, 0)', + round_error_ok: true }, + { start: 'skewX(0)', end: 'skewX(-45deg) translate(0)', + expected: 'matrix(1, 0, -0.25, 1, 0, 0)', + round_error_ok: true }, + // matrix : rotate + { start: 'rotate(-30deg)', end: 'matrix(0, 1, -1, 0, 0, 0)', + expected: 'matrix(1, 0, 0, 1, 0, 0)', + round_error_ok: true }, + { start: 'rotate(-30deg) translateX(0)', + end: 'translateX(0) rotate(-90deg)', + expected: computeMatrix('rotate(-45deg)'), + round_error_ok: true }, + // matrix decomposition of skewY + { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)', + /* rotate(30deg) skewX(60deg)/2 scale(2, 0.5) */ + expected: computeMatrix('rotate(30deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'), + round_error_ok: true }, + + // matrix decomposition + + // Four pairs of the same matrix expressed different ways. + { start: 'matrix(-1, 0, 0, -1, 0, 0)', /* rotate(180deg) */ + end: 'matrix(1, 0, 0, 1, 0, 0)', + expected: computeMatrix('rotate(135deg)') }, + { start: 'scale(-1)', end: 'none', + expected_uncomputed: 'scale(-0.5)', + expected: 'matrix(-0.5, 0, 0, -0.5, 0, 0)' }, + { start: 'rotate(180deg)', end: 'none', + expected_uncomputed: 'rotate(135deg)' }, + { start: 'rotate(-180deg)', end: 'none', + expected_uncomputed: 'rotate(-135deg)', + expected: computeMatrix('rotate(225deg)') }, + + // matrix followed by scale + { start: 'matrix(2, 0, 0, 2, 10, 20) scale(2)', + end: 'none', + expected: 'matrix(3.0625, 0, 0, 3.0625, 7.5, 15)' }, + + // ... and a bunch of similar possibilities. The spec isn't settled + // here; there are multiple options. See: + // http://lists.w3.org/Archives/Public/www-style/2010Jun/0602.html + { start: 'matrix(-1, 0, 0, 1, 0, 0)', /* scaleX(-1) */ + end: 'matrix(1, 0, 0, 1, 0, 0)', + expected: computeMatrix('scaleX(-0.5)') }, + + { start: 'matrix(1, 0, 0, -1, 0, 0)', /* rotate(-180deg) scaleX(-1) */ + end: 'matrix(1, 0, 0, 1, 0, 0)', + expected: computeMatrix('rotate(-135deg) scaleX(-0.5)') }, + + { start: 'matrix(0, 1, 1, 0, 0, 0)', /* rotate(-90deg) scaleX(-1) */ + end: 'matrix(1, 0, 0, 1, 0, 0)', + expected: computeMatrix('rotate(-67.5deg) scaleX(-0.5)') }, + + { start: 'matrix(0, -1, 1, 0, 0, 0)', /* rotate(-90deg) */ + end: 'matrix(1, 0, 0, 1, 0, 0)', + expected: computeMatrix('rotate(-67.5deg)') }, + + { start: 'matrix(0, 1, -1, 0, 0, 0)', /* rotate(90deg) */ + end: 'matrix(1, 0, 0, 1, 0, 0)', + expected: computeMatrix('rotate(67.5deg)') }, + + { start: 'matrix(0, -1, -1, 0, 0, 0)', /* rotate(90deg) scaleX(-1) */ + end: 'matrix(1, 0, 0, 1, 0, 0)', + expected: computeMatrix('rotate(67.5deg) scaleX(-0.5)') }, + + // Similar decomposition tests, but with skewX. I checked visually + // that the sign of the skew was correct by checking visually that + // the animations in + // https://dbaron.org/css/test/2010/transition-negative-determinant + // don't flip when they finish, and then wrote tests corresponding + // to the current code's behavior. + // ... start with four with positive determinants + { start: 'none', + end: 'matrix(1, 0, 1.5, 1, 0, 0)', + /* skewX(atan(1.5)) */ + expected: 'matrix(1, 0, ' + 1.5 * 0.25 + ', 1, 0, 0)', + round_error_ok: true }, + { start: 'none', + end: 'matrix(-1, 0, 2, -1, 0, 0)', + /* rotate(180deg) skewX(atan(-2)) */ + expected: computeMatrix('rotate(45deg) matrix(1, 0, ' + -2 * 0.25 + ', 1, 0, 0)'), + round_error_ok: true }, + { start: 'none', + end: 'matrix(0, -1, 1, -3, 0, 0)', + /* rotate(-90deg) skewX(atan(3)) */ + expected: computeMatrix('rotate(-22.5deg) matrix(1, 0, ' + 3 * 0.25 + ', 1, 0, 0)'), + round_error_ok: true }, + { start: 'none', + end: 'matrix(0, 1, -1, 4, 0, 0)', + /* rotate(90deg) skewX(atan(4)) */ + expected: computeMatrix('rotate(22.5deg) matrix(1, 0, ' + 4 * 0.25 + ', 1, 0, 0)'), + round_error_ok: true }, + // and then four with negative determinants + { start: 'none', + end: 'matrix(1, 0, 1, -1, 0, 0)', + /* rotate(-180deg) skewX(atan(-1)) scaleX(-1) */ + expected: computeMatrix('rotate(-45deg) matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)'), + round_error_ok: true }, + { start: 'none', + end: 'matrix(-1, 0, -1, 1, 0, 0)', + /* skewX(atan(-1)) scaleX(-1) */ + expected: computeMatrix('matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)') }, + { start: 'none', + end: 'matrix(0, 1, 1, -2, 0, 0)', + /* rotate(-90deg) skewX(atan(2)) scaleX(-1) */ + expected: computeMatrix('rotate(-22.5deg) matrix(1, 0, ' + 2 * 0.25 + ', 1, 0, 0) scaleX(0.5)'), + round_error_ok: true }, + { start: 'none', + end: 'matrix(0, -1, -1, 0.5, 0, 0)', + /* rotate(90deg) skewX(atan(0.5)) scaleX(-1) */ + expected: computeMatrix('rotate(22.5deg) matrix(1, 0, ' + 0.5 * 0.25 + ', 1, 0, 0) scaleX(0.5)'), + round_error_ok: true }, + + // lists vs. matrix decomposition + { start: 'translate(10px) skewY(45deg)', + end: 'translate(30px) skewY(-45deg)', + expected_uncomputed: 'translate(15px) skewY(22.5deg)' }, + { start: 'skewY(45deg) rotate(90deg)', + end: 'skewY(-45deg) rotate(90deg)', + expected_uncomputed: 'skewY(22.5deg) rotate(90deg)' }, + { start: 'skewY(45deg) rotate(90deg) translate(0)', + end: 'skewY(-45deg) rotate(90deg)', + expected: 'matrix(0, 1, -1, -0.5, 0, 0)', + round_error_ok: true }, + { start: 'skewX(45deg) rotate(90deg)', + end: 'skewX(-45deg) rotate(90deg)', + expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' }, + { start: 'skewX(-60deg) rotate(90deg) translate(0)', + end: 'skewX(60deg) rotate(90deg)', + expected: computeMatrix('rotate(120deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'), + round_error_ok: true }, +]; + +var clipPathTests = [ + { start: "none", end: "none", + expected: ["none"] }, + // none to shape + { start: "none", + end: "circle(500px at 500px 500px) border-box", + expected: ["circle", ["500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] + }, + { start: "none", + end: "ellipse(500px 500px at 500px 500px) border-box", + expected: ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] + }, + { start: "none", + end: "polygon(evenodd, 500px 500px, 500px 500px) border-box", + expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "border-box"] + }, + { start: "none", + end: "inset(500px 500px 500px 500px round 500px 500px) border-box", + expected: ["inset", ["500px round 500px"], "border-box"] + }, + // matching functions + { start: "circle(100px)", end: "circle(500px)", + expected: ["circle", ["200px at 50% 50%"]] }, + { start: "ellipse(100px 100px)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["200px 200px at 50% 50%"]] }, + { start: "circle(100px at 100px 100px) border-box", + end: "circle(500px at 500px 500px) border-box", + expected: ["circle", ["200px at calc(200px + 0%) calc(200px + 0%)"], "border-box"] + }, + { start: "ellipse(100px 100px at 100px 100px) border-box", + end: "ellipse(500px 500px at 500px 500px) border-box", + expected: ["ellipse", ["200px 200px at calc(200px + 0%) calc(200px + 0%)"], "border-box"] + }, + { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box", + end: "polygon(evenodd, 500px 500px, 500px 500px) border-box", + expected: ["polygon", ["evenodd, 200px 200px, 200px 200px"], "border-box"] + }, + { start: "inset(100px 100px 100px 100px round 100px 100px) border-box", + end: "inset(500px 500px 500px 500px round 500px 500px) border-box", + expected: ["inset", ["200px round 200px"], "border-box"] + }, + // matching functions percentage + { start: "circle(100%)", end: "circle(500%)", + expected: ["circle", ["200% at 50% 50%"]] }, + { start: "ellipse(100% 100%)", end: "ellipse(500% 500%)", + expected: ["ellipse", ["200% 200% at 50% 50%"]] }, + { start: "circle(100% at 100% 100%) border-box", + end: "circle(500% at 500% 500%) border-box", + expected: ["circle", ["200% at 200% 200%"], "border-box"] + }, + { start: "ellipse(100% 100% at 100% 100%) border-box", + end: "ellipse(500% 500% at 500% 500%) border-box", + expected: ["ellipse", ["200% 200% at 200% 200%"], "border-box"] + }, + { start: "polygon(evenodd, 100% 100%, 100% 100%) border-box", + end: "polygon(evenodd, 500% 500%, 500% 500%) border-box", + expected: ["polygon", ["evenodd, 200% 200%, 200% 200%"], "border-box"] + }, + { start: "inset(100% 100% 100% 100% round 100% 100%) border-box", + end: "inset(500% 500% 500% 500% round 500% 500%) border-box", + expected: ["inset", ["200% round 200%"], "border-box"] }, + // matching functions with calc() values + { start: "circle(calc(80px + 20px))", end: "circle(calc(200px + 300px))", + expected: ["circle", ["200px at 50% 50%"]] }, + { start: "circle(calc(80% + 20%))", end: "circle(calc(200% + 300%))", + expected: ["circle", ["calc(0px + 200%) at 50% 50%"]] }, + { start: "circle(calc(10px + 20%))", end: "circle(calc(50px + 40%))", + expected: ["circle", ["calc(20px + 25%) at 50% 50%"]] }, + // matching functions with interpolation between percentage/pixel values + { start: "circle(20px)", end: "circle(100%)", + expected: ["circle", ["calc(15px + 25%) at 50% 50%"]] }, + { start: "ellipse(100% 100px at 8px 20%) border-box", + end: "ellipse(40px 4% at 80% 60px) border-box", + expected: ["ellipse", ["calc(10px + 75%) calc(75px + 1%) at " + + "calc(6px + 20%) calc(15px + 15%)"], + "border-box"] }, + // no interpolation for keywords + { start: "circle()", end: "circle(50px)", + expected: ["circle", ["50px at 50% 50%"]] }, + { start: "circle(closest-side)", end: "circle(500px)", + expected: ["circle", ["500px at 50% 50%"]] }, + { start: "circle(farthest-side)", end: "circle(500px)", + expected: ["circle", ["500px at 50% 50%"]] }, + { start: "circle(500px)", end: "circle(farthest-side)", + expected: ["circle", ["farthest-side at 50% 50%"]]}, + { start: "circle(500px)", end: "circle(closest-side)", + expected: ["circle", ["closest-side at 50% 50%"]]}, + { start: "ellipse()", end: "ellipse(50px 50px)", + expected: ["ellipse", ["50px 50px at 50% 50%"]] }, + { start: "ellipse(closest-side closest-side)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["500px 500px at 50% 50%"]] }, + { start: "ellipse(farthest-side closest-side)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["500px 500px at 50% 50%"]] }, + { start: "ellipse(farthest-side farthest-side)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["500px 500px at 50% 50%"]] }, + { start: "ellipse(500px 500px)", end: "ellipse(farthest-side farthest-side)", + expected: ["ellipse", ["farthest-side farthest-side at 50% 50%"]] }, + { start: "ellipse(500px 500px)", end: "ellipse(closest-side closest-side)", + expected: ["ellipse", ["closest-side closest-side at 50% 50%"]] }, + // mismatching boxes + { start: "circle(100px at 100px 100px) border-box", + end: "circle(500px at 500px 500px) content-box", + expected: ["circle", ["500px at calc(500px + 0%) calc(500px + 0%)"], "content-box"] + }, + { start: "ellipse(100px 100px at 100px 100px) border-box", + end: "ellipse(500px 500px at 500px 500px) content-box", + expected: ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "content-box"] + }, + { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box", + end: "polygon(evenodd, 500px 500px, 500px 500px) content-box", + expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "content-box"] + }, + { start: "inset(100px 100px 100px 100px round 100px 100px) border-box", + end: "inset(500px 500px 500px 500px round 500px 500px) content-box", + expected: ["inset", ["500px round 500px"], "content-box"] + }, + // mismatching functions + { start: "circle(100px at 100px 100px) border-box", + end: "ellipse(500px 500px at 500px 500px) border-box", + expected: ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] + }, + { start: "inset(0px round 20px)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["500px 500px at 50% 50%"]] + }, + // shape to reference box + { start: "circle(20px)", end: "content-box", expected: ["content-box"] }, + { start: "content-box", end: "circle(20px)", expected: ["circle", ["20px at 50% 50%"]] }, + // url to shape + { start: "circle(20px)", end: "url('#a')", expected: ["url", ["\"#a\""]] }, + { start: "url('#a')", end: "circle(20px)", expected: ["circle", ["20px at 50% 50%"]] }, + // url to none + { start: "none", end: "url('#a')", expected: ["url", ["\"#a\""]] }, + { start: "url('#a')", end: "none", expected: ["none"] }, + +]; + +var filterTests = [ + { start: "none", end: "none", + expected: ["none"] }, + // function from none (number/length) + { start: "none", end: "brightness(0.5)", + expected: ["brightness", 0.875] }, + { start: "none", end: "contrast(0.5)", + expected: ["contrast", 0.875] }, + { start: "none", end: "grayscale(0.5)", + expected: ["grayscale", 0.125] }, + { start: "none", end: "invert(0.5)", + expected: ["invert", 0.125] }, + { start: "none", end: "opacity(0.5)", + expected: ["opacity", 0.875] }, + { start: "none", end: "saturate(0.5)", + expected: ["saturate", 0.875] }, + { start: "none", end: "sepia(0.5)", + expected: ["sepia", 0.125] }, + { start: "none", end: "blur(50px)", + expected: ["blur", 12.5] }, + // function to none (number/length) + { start: "brightness(0.5)", end: "none", + expected: ["brightness", 0.625] }, + { start: "contrast(0.5)", end: "none", + expected: ["contrast", 0.625] }, + { start: "grayscale(0.5)", end: "none", + expected: ["grayscale", 0.375] }, + { start: "invert(0.5)", end: "none", + expected: ["invert", 0.375] }, + { start: "opacity(0.5)", end: "none", + expected: ["opacity", 0.625] }, + { start: "saturate(0.5)", end: "none", + expected: ["saturate", 0.625] }, + { start: "sepia(0.5)", end: "none", + expected: ["sepia", 0.375] }, + { start: "blur(50px)", end: "none", + expected: ["blur", 37.5] }, + // function to same function (number/length) + { start: "brightness(0.25)", end: "brightness(0.75)", + expected: ["brightness", 0.375] }, + { start: "contrast(0.25)", end: "contrast(0.75)", + expected: ["contrast", 0.375] }, + { start: "grayscale(0.25)", end: "grayscale(0.75)", + expected: ["grayscale", 0.375] }, + { start: "invert(0.25)", end: "invert(0.75)", + expected: ["invert", 0.375] }, + { start: "opacity(0.25)", end: "opacity(0.75)", + expected: ["opacity", 0.375] }, + { start: "saturate(0.25)", end: "saturate(0.75)", + expected: ["saturate", 0.375] }, + { start: "sepia(0.25)", end: "sepia(0.75)", + expected: ["sepia", 0.375] }, + { start: "blur(25px)", end: "blur(75px)", + expected: ["blur", 37.5] }, + // function to same function (percent) + { start: "brightness(25%)", end: "brightness(75%)", + expected: ["brightness", 0.375] }, + { start: "contrast(25%)", end: "contrast(75%)", + expected: ["contrast", 0.375] }, + { start: "grayscale(25%)", end: "grayscale(75%)", + expected: ["grayscale", 0.375] }, + { start: "invert(25%)", end: "invert(75%)", + expected: ["invert", 0.375] }, + { start: "opacity(25%)", end: "opacity(75%)", + expected: ["opacity", 0.375] }, + { start: "saturate(25%)", end: "saturate(75%)", + expected: ["saturate", 0.375] }, + { start: "sepia(25%)", end: "sepia(75%)", + expected: ["sepia", 0.375] }, + // function to same function (percent, number/length) + { start: "brightness(0.25)", end: "brightness(75%)", + expected: ["brightness", 0.375] }, + { start: "contrast(25%)", end: "contrast(0.75)", + expected: ["contrast", 0.375] }, + // hue-rotate with different angle values + { start: "hue-rotate(0deg)", end: "hue-rotate(720deg)", + expected: ["hue-rotate", "180deg"] }, + { start: "hue-rotate(0rad)", end: "hue-rotate("+4*Math.PI+"rad)", + expected: ["hue-rotate", Math.PI.toFixed(5)+"rad"] }, + { start: "hue-rotate(0grad)", end: "hue-rotate(800grad)", + expected: ["hue-rotate", "200grad"] }, + { start: "hue-rotate(0turn)", end: "hue-rotate(2turn)", + expected: ["hue-rotate", "0.5turn"] }, + { start: "hue-rotate(0deg)", end: "hue-rotate("+4*Math.PI+"rad)", + expected: ["hue-rotate", Math.PI.toFixed(5)+"rad"] }, + { start: "hue-rotate(0turn)", end: "hue-rotate(800grad)", + expected: ["hue-rotate", Math.PI.toFixed(5)+"rad"] }, + { start: "hue-rotate(0grad)", end: "hue-rotate("+4*Math.PI+"rad)", + expected: ["hue-rotate", Math.PI.toFixed(5)+"rad"] }, + { start: "hue-rotate(0grad)", end: "hue-rotate(0turn)", + expected: ["hue-rotate", "0rad"] }, + // multiple matching functions, same length + { start: "contrast(25%) brightness(0.25) blur(25px) sepia(75%)", + end: "contrast(75%) brightness(0.75) blur(75px) sepia(25%)", + expected: ["contrast", 0.375, "brightness", 0.375, "blur", 37.5, "sepia", 0.625] }, + { start: "invert(25%) brightness(0.25) blur(25px) invert(50%) brightness(0.5) blur(50px)", + end: "invert(75%) brightness(0.75) blur(75px)", + expected: ["invert", 0.375, "brightness", 0.375, "blur", 37.5, "invert", 0.375, "brightness", 0.625, "blur", 37.5] }, + // multiple matching functions, different length + { start: "contrast(25%) brightness(0.5) blur(50px)", + end: "contrast(75%)", + expected: ["contrast", 0.375, "brightness", 0.625, "blur", 37.5] }, + // mismatching filter functions + { start: "contrast(0%)", end: "blur(10px)", + expected: ["blur", 10] }, + // not supported interpolations + { start: "none", end: "url('#b')", + expected: ["url", "\"#b\""] }, + { start: "url('#a')", end: "none", + expected: ["none"] }, + { start: "url('#a')", end: "url('#b')", + expected: ["url", "\"#b\""] }, + { start: "url('#a')", end: "blur(10px)", + expected: ["blur", 10] }, + { start: "blur(10px)", end: "url('#a')", + expected: ["url", "\"#a\""] }, + { start: "blur(0px) url('#a')", end: "blur(20px)", + expected: ["blur", 20] }, + { start: "blur(0px)", end: "blur(20px) url('#a')", + expected: ["blur", 20, "url", "\"#a\""] }, + { start: "contrast(0.25) brightness(0.25) blur(25px)", + end: "contrast(0.75) url('#a')", + expected: ["contrast", 0.75, "url", "\"#a\""] }, + { start: "contrast(0.25) brightness(0.25) blur(75px)", + end: "brightness(0.75) contrast(0.75) blur(25px)", + expected: ["brightness", 0.75, "contrast", 0.75, "blur", 25] }, + { start: "contrast(0.25) brightness(0.25) blur(25px)", + end: "contrast(0.75) brightness(0.75) contrast(0.75)", + expected: ["contrast", 0.75, "brightness", 0.75, "contrast", 0.75] }, + // drop-shadow animation + { start: "none", + end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)", + expected: ["drop-shadow", "rgba(0, 0, 0, 0.25) 1px 1px 0px"] }, + { start: "drop-shadow(rgb(0, 0, 0) 0px 0px 0px)", + end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)", + expected: ["drop-shadow", "rgb(0, 0, 0) 1px 1px 0px"] }, + { start: "drop-shadow(#038000 4px 4px)", + end: "drop-shadow(8px 8px 8px red)", + expected: ["drop-shadow", "rgb(66, 96, 0) 5px 5px 2px"] }, + { start: "blur(25px) drop-shadow(8px 8px)", + end: "blur(75px)", + expected: ["blur", 37.5, "drop-shadow", "rgb(0, 0, 0) 6px 6px 0px"] }, + { start: "blur(75px)", + end: "blur(25px) drop-shadow(8px 8px)", + expected: ["blur", 62.5, "drop-shadow", "rgb(0, 0, 0) 2px 2px 0px"] }, + { start: "drop-shadow(2px 2px blue)", + end: "none", + expected: ["drop-shadow", "rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px"] }, +]; + +var prop; +for (prop in supported_properties) { + // Test that prop is in the property database. + ok(prop in gCSSProperties, "property " + prop + " in gCSSProperties"); + + // Test that the entry has at least one test function. + ok(supported_properties[prop].length > 0, + "property " + prop + " must have at least one test function"); +} + +// Return a consistent sampling of |count| values out of |array|. +function sample_array(array, count) { + if (count <= 0) { + ok(false, "unexpected count"); + return []; + } + var ratio = array.length / count; + if (ratio <= 1) { + return array; + } + var result = new Array(count); + for (var i = 0; i < count; ++i) { + result[i] = array[Math.floor(i * ratio)]; + } + return result; +} + +// Test that transitions don't do anything (i.e., aren't supported) on +// the properties not in our test list above (and not transition +// properties themselves). +for (prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (!(prop in supported_properties) && + info.type != CSS_TYPE_TRUE_SHORTHAND && + !("alias_for" in info) && + !prop.match(/^transition-/) && + // FIXME (Bug 119078): THIS SHOULD REALLY NOT BE NEEDED! + prop != "-moz-binding" && + prop != "mask") { + + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + div.style.setProperty(prereq, prereqs[prereq], ""); + } + } + + var all_values = info.initial_values.concat(info.other_values); + + if (all_values.length > 50) { + // Since we're using an O(N^2) algorithm here, reduce the list of + // values that we want to test. (This test is really only testing + // that somebody didn't make a property animatable without + // modifying this test. The odds of somebody doing that without + // making at least one of the many pairs of values we have left + // animatable seems pretty low, at least relative to the chance + // that any pair of the values listed in property_database.js is + // animatable.) + // + // That said, we still try to use all of the start of the list on + // the assumption that the more basic values are likely to be at + // the beginning of the list. + all_values = [].concat(info.initial_values.slice(0,2), + sample_array(info.initial_values.slice(2), 6), + info.other_values.slice(0, 10), + sample_array(info.other_values.slice(10), 40)); + } + + var all_computed = []; + for (var idx in all_values) { + var val = all_values[idx]; + div.style.setProperty(prop, val, ""); + all_computed.push(cs.getPropertyValue(prop)); + } + div.style.removeProperty(prop); + + div.style.setProperty("transition", prop + " 20s linear", ""); + for (var i = 0; i < all_values.length; ++i) { + for (var j = i + 1; j < all_values.length; ++j) { + div.style.setProperty(prop, all_values[i], ""); + is(cs.getPropertyValue(prop), all_computed[i], + "transitions not supported for property " + prop + + " value " + all_values[i]); + div.style.setProperty(prop, all_values[j], ""); + is(cs.getPropertyValue(prop), all_computed[j], + "transitions not supported for property " + prop + + " value " + all_values[j]); + } + } + + div.style.removeProperty("transition"); + div.style.removeProperty(prop); + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + div.style.removeProperty(prereq); + } + } + } +} + +// Do 4-second linear transitions with -1 second transition delay and +// linear timing function so that we can expect the transition to be +// one quarter of the way through the value space right after changing +// the property. +div.style.setProperty("transition-duration", "4s", ""); +div.style.setProperty("transition-delay", "-1s", ""); +div.style.setProperty("transition-timing-function", "linear", ""); +for (prop in supported_properties) { + var tinfo = supported_properties[prop]; + var info = gCSSProperties[prop]; + + isnot(info.type, CSS_TYPE_TRUE_SHORTHAND, + prop + " must not be a shorthand"); + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + // We don't want the 19px font-size prereq of line-height, since we + // want to leave it 20px. + if (prop != "line-height" || prereq != "font-size") { + div.style.setProperty(prereq, prereqs[prereq], ""); + } + } + } + + for (var idx in tinfo) { + tinfo[idx](prop); + } + + // Make sure to unset the property and stop transitions on it. + div.style.setProperty("transition-property", "none", ""); + div.style.removeProperty(prop); + cs.getPropertyValue(prop); + + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + div.style.removeProperty(prereq); + } + } +} +div.style.removeProperty("transition"); + +function get_distance(prop, v1, v2) +{ + return SpecialPowers.DOMWindowUtils + .computeAnimationDistance(div, prop, v1, v2); +} + +function check_distance(prop, start, quarter, end) +{ + var sq = get_distance(prop, start, quarter); + var se = get_distance(prop, start, end); + var qe = get_distance(prop, quarter, end); + + ok(Math.abs((sq * 4 - se) / se) < 0.0001, "property '" + prop + "': distance " + sq + " from start '" + start + "' to quarter '" + quarter + "' should be quarter distance " + se + " from start '" + start + "' to end '" + end + "'"); + ok(Math.abs((qe * 4 - se * 3) / se) < 0.0001, "property '" + prop + "': distance " + qe + " from quarter '" + quarter + "' to end '" + end + "' should be three quarters distance " + se + " from start '" + start + "' to end '" + end + "'"); +} + +function test_length_transition_svg_or_units(prop, numbers_are_pixels) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "4px", ""); + is(cs.getPropertyValue(prop), "4px", + "length-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "12px", ""); + is(cs.getPropertyValue(prop), numbers_are_pixels ? "6" : "6px", + "length-valued property " + prop + ": interpolation of lengths"); + check_distance(prop, "4px", "6px", "12px"); +} + +function test_length_transition(prop) { + test_length_transition_svg_or_units(prop, false); +} + +function test_length_transition_svg(prop) { + test_length_transition_svg_or_units(prop, true); +} + +function test_length_clamped(prop) { + test_length_clamped_or_unclamped(prop, true, false); +} + +function test_length_unclamped(prop) { + test_length_clamped_or_unclamped(prop, false, false); +} + +function test_length_clamped_svg(prop) { + test_length_clamped_or_unclamped(prop, true, true); +} + +function test_length_unclamped_svg(prop) { + test_length_clamped_or_unclamped(prop, false, true); +} + +function test_length_clamped_or_unclamped(prop, is_clamped, numbers_are_pixels) { + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0px", ""); + is(cs.getPropertyValue(prop), "0px", + "length-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "100px", ""); + (is_clamped ? is : isnot)(cs.getPropertyValue(prop), + numbers_are_pixels ? "0" : "0px", + "length-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +// Test using float values in the range [0, 1] (e.g. opacity) +function test_float_zeroToOne_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0.3", ""); + is(cs.getPropertyValue(prop), "0.3", + "float-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "0.8", ""); + is(cs.getPropertyValue(prop), "0.425", + "float-valued property " + prop + ": interpolation of floats"); + check_distance(prop, "0.3", "0.425", "0.8"); +} + +function test_float_zeroToOne_clamped(prop) { + test_float_zeroToOne_clamped_or_unclamped(prop, true); +} +function test_float_zeroToOne_unclamped(prop) { + test_float_zeroToOne_clamped_or_unclamped(prop, false); +} + +function test_float_zeroToOne_clamped_or_unclamped(prop, is_clamped) { + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0", ""); + is(cs.getPropertyValue(prop), "0", + "float-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "1", ""); + (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0", + "float-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +// Test using float values in the range [1, infinity) (e.g. stroke-miterlimit) +function test_float_aboveOne_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "1", ""); + is(cs.getPropertyValue(prop), "1", + "float-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "2.1", ""); + is(cs.getPropertyValue(prop), "1.275", + "float-valued property " + prop + ": interpolation of floats"); + check_distance(prop, "1", "1.275", "2.1"); +} + +function test_float_aboveOne_clamped(prop) { + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "1", ""); + is(cs.getPropertyValue(prop), "1", + "float-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "5", ""); + is(cs.getPropertyValue(prop), "1", + "float-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_percent_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "25%", ""); + var av = cs.getPropertyValue(prop); + var a = any_unit_to_num(av); + div.style.setProperty(prop, "75%", ""); + var bv = cs.getPropertyValue(prop); + var b = any_unit_to_num(bv); + isnot(b, a, "different percentages (" + av + " and " + bv + + ") should be different for " + prop); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "25%", ""); + var res = cs.getPropertyValue(prop); + is(any_unit_to_num(res) * 4, 3 * b + a, + "percent-valued property " + prop + ": interpolation of percents: " + + res + " should be a quarter of the way between " + bv + " and " + av); + ok(has_num(res), + "percent-valued property " + prop + ": percent computes to number"); + check_distance(prop, "25%", "37.5%", "75%"); +} + +function test_percent_clamped(prop) { + test_percent_clamped_or_unclamped(prop, true); +} + +function test_percent_unclamped(prop) { + test_percent_clamped_or_unclamped(prop, false); +} + +function test_percent_clamped_or_unclamped(prop, is_clamped) { + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0%", ""); + var zero_val = cs.getPropertyValue(prop); // flushes too + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "150%", ""); + (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val, + "percent-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_length_percent_calc_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0%", ""); + var av = cs.getPropertyValue(prop); + var a = any_unit_to_num(av); + div.style.setProperty(prop, "100%", ""); + var bv = cs.getPropertyValue(prop); + var b = any_unit_to_num(bv); + div.style.setProperty(prop, "100px", ""); + var cv = cs.getPropertyValue(prop); + var c = any_unit_to_num(cv); + isnot(b, a, "different percentages (" + av + " and " + bv + + ") should be different for " + prop); + + div.style.setProperty(prop, "50%", ""); + var v1v = cs.getPropertyValue(prop); + is(any_unit_to_num(v1v) * 2, a + b, + "computed value before transition for " + prop + ": '" + + v1v + "' should be halfway " + + "between '" + av + "' + and '" + bv + "'."); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "200px", ""); + var v2v = cs.getPropertyValue(prop); + is(any_unit_to_num(v2v) * 8, 5*a + 3*b + 4*c, + "interpolation between length and percent for " + prop + ": '" + + v2v + "'"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "calc(25% + 100px)", ""); + v1v = cs.getPropertyValue(prop); + is(any_unit_to_num(v1v) * 4, b + 4*c, + "computed value before transition for " + prop + ": '" + v1v + "'"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "75%", ""); + v2v = cs.getPropertyValue(prop); + is(any_unit_to_num(v2v) * 8, 5*a + 3*b + 6*c, + "interpolation between calc() and percent for " + prop + ": '" + + v2v + "'"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "150px", ""); + v1v = cs.getPropertyValue(prop); + is(any_unit_to_num(v1v) * 2, c * 3, + "computed value before transition for " + prop + ": '" + v1v + "'"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "calc(50% + 50px)", ""); + v2v = cs.getPropertyValue(prop); + is(any_unit_to_num(v2v) * 8, 7 * a + b + 10*c, + "interpolation between length and calc() for " + prop + ": '" + + v2v + "'"); + + check_distance(prop, "50%", "calc(37.5% + 50px)", "200px"); + check_distance(prop, "calc(25% + 100px)", "calc(37.5% + 75px)", + "75%"); + check_distance(prop, "150px", "calc(125px + 12.5%)", + "calc(50% + 50px)"); +} + +function test_color_transition(prop, get_color=(x => x), is_shorthand=false) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "rgb(255, 28, 0)", ""); + is(get_color(cs.getPropertyValue(prop)), "rgb(255, 28, 0)", + "color-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "rgb(75, 84, 128)", ""); + is(get_color(cs.getPropertyValue(prop)), "rgb(210, 42, 32)", + "color-valued property " + prop + ": interpolation of colors"); + + if (!is_shorthand) { + check_distance(prop, "rgb(255, 28, 0)", "rgb(210, 42, 32)", + "rgb(75, 84, 128)"); + } + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "rgb(0, 255, 0)", ""); + var vals = cs.getPropertyValue(prop).match(/rgb\(([^, ]*), ([^, ]*), ([^, ]*)\)/); + is(vals.length, 4, + "color-valued property " + prop + ": flush before clamping test (length)"); + is(vals[1], "0", + "color-valued property " + prop + ": flush before clamping test (red)"); + is(vals[2], "255", + "color-valued property " + prop + ": flush before clamping test (green)"); + is(vals[3], "0", + "color-valued property " + prop + ": flush before clamping test (blue)"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "rgb(255, 0, 128)", ""); + // FIXME: Once we support non-sRGB colors, these tests will need fixing. + vals = cs.getPropertyValue(prop).match(/rgb\(([^, ]*), ([^, ]*), ([^, ]*)\)/); + is(vals.length, 4, + "color-valued property " + prop + ": clamping of negatives (length)"); + is(vals[1], "0", + "color-valued property " + prop + ": clamping of negatives (red)"); + is(vals[2], "255", + "color-valued property " + prop + ": clamping of above-range (green)"); + is(vals[3], "0", + "color-valued property " + prop + ": clamping of negatives (blue)"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_currentcolor_transition(prop, get_color=(x => x), is_shorthand=false) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "rgb(128, 64, 0)", ""); + (prop == "color" ? div.parentNode : div).style. + setProperty("color", "rgb(0, 0, 128)", ""); + is(get_color(cs.getPropertyValue(prop)), "rgb(128, 64, 0)", + "color-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "currentColor", ""); + is(get_color(cs.getPropertyValue(prop)), "rgb(96, 48, 32)", + "color-valued property " + prop + ": interpolation of currentColor"); + + if (!is_shorthand) { + check_distance(prop, "rgb(128, 64, 0)", "rgb(96, 48, 32)", + "currentColor"); + } + + (prop == "color" ? div.parentNode : div).style.removeProperty("color"); +} + +function test_true_currentcolor_transition(prop, get_color=(x => x), is_shorthand=false) { + const msg_prefix = `color-valued property ${prop}: `; + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty("color", "rgb(128, 0, 0)", ""); + div.style.setProperty(prop, "rgb(0, 0, 128)", ""); + is(get_color(cs.getPropertyValue(prop)), "rgb(0, 0, 128)", + msg_prefix + "computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "currentcolor", ""); + is(get_color(cs.getPropertyValue(prop)), "rgb(32, 0, 96)", + msg_prefix + "interpolation of rgb color and currentcolor"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty("color", "rgb(128, 0, 0)", ""); + div.style.setProperty(prop, "rgb(0, 0, 128)", ""); + is(get_color(cs.getPropertyValue(prop)), "rgb(0, 0, 128)", + msg_prefix + "computed value before transition"); + div.style.setProperty("transition-property", `color, ${prop}`, ""); + div.style.setProperty("color", "rgb(0, 128, 0)", ""); + div.style.setProperty(prop, "currentcolor", ""); + is(cs.getPropertyValue("color"), "rgb(96, 32, 0)", + "interpolation of rgb color property"); + is(get_color(cs.getPropertyValue(prop)), "rgb(24, 8, 96)", + msg_prefix + "interpolation of rgb color and interpolated currentcolor"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty("color", "rgba(128, 0, 0, 0.6)", ""); + div.style.setProperty(prop, "rgba(0, 0, 128, 0.8)", ""); + is(get_color(cs.getPropertyValue(prop)), "rgba(0, 0, 128, 0.8)", + msg_prefix + "computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "currentcolor", ""); + is(get_color(cs.getPropertyValue(prop)), "rgba(26, 0, 102, 0.75)", + msg_prefix + "interpolation of rgba color and currentcolor"); + + // It is not possible to check distance, because there is a hidden + // dimension for ratio of currentcolor. + + div.style.removeProperty("color"); +} + +function get_color_from_shorthand_value(value) { + var m = value.match(/rgba?\([^, ]*, [^, ]*, [^, ]*(?:, [^, ]*)?\)/); + isnot(m, null, "shorthand property value should contain color"); + return m[0]; +} + +function test_color_shorthand_transition(prop) { + test_color_transition(prop, get_color_from_shorthand_value, true); +} + +function test_currentcolor_shorthand_transition(prop) { + test_currentcolor_transition(prop, get_color_from_shorthand_value, true); +} + +function test_true_currentcolor_shorthand_transition(prop) { + test_true_currentcolor_transition(prop, get_color_from_shorthand_value, true); +} + +function test_clip_path_equals(computedValStr, expectedList) +{ + // Check simple case "none" + if (computedValStr == "none" && computedValStr == expectedList[0]) { + return true; + } + var start = String(computedValStr); + + var regBox = /\s*(content\-box|padding\-box|border\-box|margin\-box|view\-box|stroke\-box|fill\-box)/ + var matches = computedValStr.split(regBox); + var expectRefBox = typeof expectedList[expectedList.length - 1] == "string" && + expectedList[expectedList.length - 1].match(regBox) !== null; + + // Found a reference box? Format: "shape()" or "shape() reference-box" + if (matches.length > 1) { + // Our split() did actually split the string, which means computedValStr + // contains a reference box. That reference box should be at the end, + // which means split() will have produced an empty string as the final + // entry in |matches|. Let's first ditch that empty string. + var trailingJunk = matches.pop(); + is(trailingJunk, "", "reference box shouldn't have anything after it"); + + // Do we expect a reference box? + if (!expectRefBox) { + ok(false, "unexpected reference box found"); + matches.pop(); // Get rid of it, so we can test the rest... + } else { + is(matches.pop(), expectedList.pop(), "Reference boxes should match"); + } + } else { + // No reference box found. Did we expect one? + if (expectRefBox) { + ok(false, "expected reference box"); + return false; + } + } + computedValStr = matches[0]; + if (expectedList.length == 0) { + if (computedValStr == "") { + return true; + } else { + ok(false, "expected clip-path shape"); + return false; + } + } + + // The regular expression does not filter out the last parenthesis. + // Remove last character for now. + is(computedValStr.substring(computedValStr.length - 1, computedValStr.length), + ')', "Function should have close-paren"); + computedValStr = computedValStr.substring(0, computedValStr.length - 1); + + var regShape = /\)*\s*(circle|ellipse|polygon|inset|url)\(/ + matches = computedValStr.split(regShape); + // First item must be empty. All other items are of functionName, functionValue. + if (!matches || matches.shift() != "") { + ok(false, "invalid value or unknown shape function"); + return false; + } + + // Check argument values. + if (matches[1] != expectedList[1]) { + ok(false, "function parameters mismatch"); + return false; + } + + return true; +} + +function filter_function_list_equals(computedValStr, expectedList) +{ + // Check simple case "none" + if (computedValStr == "none" && computedValStr == expectedList[0]) { + return true; + } + + // The regular expression does not filter out the last parenthesis. + // Remove last character for now. + is(computedValStr.substring(computedValStr.length - 1, computedValStr.length), + ')', "Last character should be close-paren"); + computedValStr = computedValStr.substring(0, computedValStr.length - 1); + + var reg = /\)*\s*(blur|brightness|contrast|grayscale|hue\-rotate|invert|opacity|saturate|sepia|drop\-shadow|url)\(/ + var matches = computedValStr.split(reg); + // First item must be empty. All other items are of functionName, functionValue. + if (!matches || matches.shift() != "") { + ok(false, "computed style of 'filter' isn't in the format we expect"); + return false; + } + + // Odd items are the function name, even items the function value. + if (!matches.length || matches.length % 2 || + expectedList.length != matches.length) { + ok(false, "computed style of 'filter' isn't in the format we expect"); + return false; + } + for (var i = 0; i < matches.length; i += 2) { + var functionName = matches[i]; + var functionValue = matches[i+1]; + var expected = expectedList[i+1] + var tolerance = 0; + // Check if we have the expected function. + if (functionName != expectedList[i]) { + return false; + } + if (functionName == "blur") { + // Last two characters must be "px". + if (functionValue.search("px") != functionValue.length - 2) { + return false; + } + functionValue = functionValue.substring(0, functionValue.length - 2); + } else if (functionName == "hue-rotate") { + // Just check for string equality. + return functionValue == expected; + } else if (functionName == "drop-shadow" || functionName == "url") { + if (functionValue != expected) { + return false; + } + continue; + } + // Check if string is not a number or difference is not in tolerance level. + if (isNaN(functionValue) || + Math.abs(parseFloat(functionValue) - expected) > tolerance) { + return false; + } + } + return true; +} + +function test_clip_path_transition(prop) { + if (!SpecialPowers.getBoolPref("layout.css.clip-path-shapes.enabled")) { + return; + } + for (var i in clipPathTests) { + var test = clipPathTests[i]; + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, test.start, ""); + cs.getPropertyValue(prop); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, test.end, ""); + var actual = cs.getPropertyValue(prop); + ok(test_clip_path_equals(actual, test.expected), + "Clip-path property is " + actual + " expected values of " + + test.expected); + } +} + +function test_filter_transition(prop) { + if (!SpecialPowers.getBoolPref("layout.css.filters.enabled")) { + return; + } + for (var i in filterTests) { + var test = filterTests[i]; + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, test.start, ""); + cs.getPropertyValue(prop); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, test.end, ""); + var actual = cs.getPropertyValue(prop); + ok(filter_function_list_equals(actual, test.expected), + "Filter property is " + actual + " expected values of " + + test.expected); + } +} + +function test_shadow_transition(prop) { + var spreadStr = (prop == "box-shadow") ? " 0px" : ""; + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "none", ""); + is(cs.getPropertyValue(prop), "none", + "shadow-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "4px 8px 3px red", ""); + is(cs.getPropertyValue(prop), "rgba(255, 0, 0, 0.25) 1px 2px 0.75px" + spreadStr, + "shadow-valued property " + prop + ": interpolation of shadows"); + check_distance(prop, "none", "rgba(255, 0, 0, 0.25) 1px 2px 0.75px", + "4px 8px 3px red"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "#038000 4px 4px, 2px 2px blue", ""); + is(cs.getPropertyValue(prop), "rgb(3, 128, 0) 4px 4px 0px" + spreadStr + ", rgb(0, 0, 255) 2px 2px 0px" + spreadStr, + "shadow-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "8px 8px 8px red", ""); + is(cs.getPropertyValue(prop), "rgb(66, 96, 0) 5px 5px 2px" + spreadStr + ", rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px" + spreadStr, + "shadow-valued property " + prop + ": interpolation of shadows"); + check_distance(prop, "#038000 4px 4px, 2px 2px blue", + "rgb(66, 96, 0) 5px 5px 2px, rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px", + "8px 8px 8px red"); + + if (prop == "box-shadow") { + div.style.setProperty(prop, "8px 8px 8px red inset", ""); + is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px inset", + "shadow-valued property " + prop + ": non-interpolable cases"); + div.style.setProperty(prop, "8px 8px 8px 8px red inset", ""); + is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 2px inset", + "shadow-valued property " + prop + ": interpolation of spread"); + // Leave in same state whether in the |if| or not. + div.style.setProperty(prop, "8px 8px 8px red", ""); + is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px", + "shadow-valued property " + prop + ": non-interpolable cases"); + check_distance(prop, "8px 8px 8px red inset", + "rgb(255, 0, 0) 8px 8px 8px 2px inset", + "8px 8px 8px 8px red inset"); + } + + var defaultColor = cs.getPropertyValue("color") + " "; + div.style.setProperty(prop, "2px 2px 2px", ""); + is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr, + "shadow-valued property " + prop + ": non-interpolable cases"); + div.style.setProperty(prop, "6px 14px 10px", ""); + is(cs.getPropertyValue(prop), defaultColor + "3px 5px 4px" + spreadStr, + "shadow-valued property " + prop + ": interpolation without color"); + check_distance(prop, "2px 2px 2px", "3px 5px 4px", "6px 14px 10px"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0px 0px 0px black", ""); + is(cs.getPropertyValue(prop), "rgb(0, 0, 0) 0px 0px 0px" + spreadStr, + "shadow-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "10px 10px 10px black", ""); + var vals = cs.getPropertyValue(prop).split(" "); + is(vals.length, 6 + (prop == "box-shadow"), "unexpected number of values"); + is(vals.slice(0, 3).join(" "), "rgb(0, 0, 0)", + "shadow-valued property " + prop + " (color): clamping of negatives"); + isnot(vals[3], "0px", + "shadow-valued property " + prop + " (x): clamping of negatives"); + isnot(vals[4], "0px", + "shadow-valued property " + prop + " (y): clamping of negatives"); + is(vals[5], "0px", + "shadow-valued property " + prop + " (radius): clamping of negatives"); + if (prop == "box-shadow") { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0px 0px 0px 0px black", ""); + is(cs.getPropertyValue(prop), "rgb(0, 0, 0) 0px 0px 0px 0px", + "shadow-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "10px 10px 10px 10px black", ""); + var vals = cs.getPropertyValue(prop).split(" "); + is(vals.length, 7, "unexpected number of values"); + is(vals.slice(0, 3).join(" "), "rgb(0, 0, 0)", + "shadow-valued property " + prop + " (color): clamping of negatives"); + isnot(vals[3], "0px", + "shadow-valued property " + prop + " (x): clamping of negatives"); + isnot(vals[4], "0px", + "shadow-valued property " + prop + " (y): clamping of negatives"); + is(vals[5], "0px", + "shadow-valued property " + prop + " (radius): clamping of negatives"); + isnot(vals[6], "0px", + "shadow-valued property " + prop + " (spread): clamping of negatives"); + } + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_dasharray_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "3", ""); + is(cs.getPropertyValue(prop), "3", + "dasharray-valued property " + prop + + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "15px", ""); + is(cs.getPropertyValue(prop), "6", + "dasharray-valued property " + prop + ": interpolation of dasharray"); + check_distance(prop, "3", "6", "15px"); + div.style.setProperty(prop, "none", ""); + is(cs.getPropertyValue(prop), "none", + "dasharray-valued property " + prop + ": non-interpolability of none"); + div.style.setProperty(prop, "6,8px,4,4", ""); + is(cs.getPropertyValue(prop), "6, 8px, 4, 4", + "dasharray-valued property " + prop + + ": computed value before transition"); + div.style.setProperty(prop, "14, 12,16,16px", ""); + is(cs.getPropertyValue(prop), "8, 9, 7, 7", + "dasharray-valued property " + prop + ": interpolation of dasharray"); + check_distance(prop, "6,8px,4,4", "8,9,7,7", "14, 12,16,16px"); + div.style.setProperty(prop, "none", ""); + is(cs.getPropertyValue(prop), "none", + "dasharray-valued property " + prop + ": non-interpolability of none"); + div.style.setProperty(prop, "8,16,4", ""); + is(cs.getPropertyValue(prop), "8, 16, 4", + "dasharray-valued property " + prop + + ": computed value before transition"); + div.style.setProperty(prop, "4,8,12,16", ""); + is(cs.getPropertyValue(prop), "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7", + "dasharray-valued property " + prop + ": interpolation of dasharray"); + check_distance(prop, "8,16,4", "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7", + "4,8,12,16"); + div.style.setProperty(prop, "2,50%,6,10", ""); + is(cs.getPropertyValue(prop), "2, 50%, 6, 10", + "dasharray-valued property " + prop + ": non-interpolability of mixed units"); + div.style.setProperty(prop, "6,30%,2,2", ""); + is(cs.getPropertyValue(prop), "3, 45%, 5, 8", + "dasharray-valued property " + prop + ": interpolation of dasharray"); + check_distance(prop, "2,50%,6,10", "3, 45%, 5, 8", "6,30%,2,2"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0,0%", ""); + is(cs.getPropertyValue(prop), "0, 0%", + "dasharray-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "5, 25%", ""); + is(cs.getPropertyValue(prop), "0, 0%", + "dasharray-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_radius_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + + // FIXME: Test a square for now, since we haven't updated to the spec + // for vertical components being relative to the height. + // Note: We use powers of two here so the floating-point math comes out + // nicely. + div.style.setProperty("width", "256px", ""); + div.style.setProperty("height", "256px", ""); + div.style.setProperty("border", "none", ""); + div.style.setProperty("padding", "0", ""); + + div.style.setProperty(prop, "3px", ""); + is(cs.getPropertyValue(prop), "3px", + "radius-valued property " + prop + + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "15px", ""); + is(cs.getPropertyValue(prop), "6px", + "radius-valued property " + prop + ": interpolation of radius"); + check_distance(prop, "3px", "6px", "15px"); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "12.5%", ""); + is(cs.getPropertyValue(prop), "12.5%", + "radius-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "25%", ""); + is(cs.getPropertyValue(prop), "15.625%", + "radius-valued property " + prop + ": interpolation of radius"); + check_distance(prop, "12.5%", "15.625%", "25%"); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "3px 8px", ""); + is(cs.getPropertyValue(prop), "3px 8px", + "radius-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "15px 12px", ""); + is(cs.getPropertyValue(prop), "6px 9px", + "radius-valued property " + prop + ": interpolation of radius"); + check_distance(prop, "3px 8px", "6px 9px", "15px 12px"); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "12.5% 6.25%", ""); + is(cs.getPropertyValue(prop), "12.5% 6.25%", + "radius-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "25%", ""); + is(cs.getPropertyValue(prop), "15.625% 10.9375%", + "radius-valued property " + prop + ": interpolation of radius"); + check_distance(prop, "12.5% 6.25%", "15.625% 10.9375%", "25%"); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "6.25% 12.5%", ""); + is(cs.getPropertyValue(prop), "6.25% 12.5%", + "radius-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "64px 16px", ""); + is(cs.getPropertyValue(prop), "calc(16px + 4.6875%) calc(4px + 9.375%)", + "radius-valued property " + prop + ": interpolation of radius with mixed units"); + check_distance(prop, "6.25% 12.5%", + "calc(16px + 4.6875%) calc(4px + 9.375%)", + "64px 16px"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "calc(5px) 10px", ""); + is(cs.getPropertyValue(prop), "5px 10px", + "radius-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "calc(25px) calc(50px)", ""); + is(cs.getPropertyValue(prop), "10px 20px", + "radius-valued property " + prop + ": interpolation of radius with calc() units"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0px 0px", ""); + is(cs.getPropertyValue(prop), "0px", + "radius-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "10px 20px", ""); + is(cs.getPropertyValue(prop), "0px", + "radius-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); + + div.style.removeProperty("width"); + div.style.removeProperty("height"); + div.style.removeProperty("border"); + div.style.removeProperty("padding"); +} + +function test_integer_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "4", ""); + is(cs.getPropertyValue(prop), "4", + "integer-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "-14", ""); + is(cs.getPropertyValue(prop), "-1", + "integer-valued property " + prop + ": interpolation of integers"); + check_distance(prop, "6", "1", "-14"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "-4", ""); + is(cs.getPropertyValue(prop), "-4", + "integer-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "8", ""); + is(cs.getPropertyValue(prop), "-1", + "integer-valued property " + prop + ": interpolation of integers"); + check_distance(prop, "-4", "-1", "8"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0", ""); + is(cs.getPropertyValue(prop), "0", + "integer-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "100", ""); + isnot(cs.getPropertyValue(prop), "0", + "integer-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_font_stretch(prop) { + is(prop, "font-stretch", "only designed for one property"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "normal", ""); + is(cs.getPropertyValue(prop), "normal", + "font-stretch property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "ultra-expanded", ""); + is(cs.getPropertyValue(prop), "semi-expanded", + "font-stretch property " + prop + ": interpolation of font-stretches"); + check_distance(prop, "normal", "semi-expanded", "ultra-expanded"); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "expanded", ""); + is(cs.getPropertyValue(prop), "expanded", + "font-stretch property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "extra-condensed", ""); + is(cs.getPropertyValue(prop), "normal", + "font-stretch property " + prop + ": interpolation of font-stretches"); + check_distance(prop, "expanded", "semi-expanded", "condensed"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "ultra-condensed", ""); + is(cs.getPropertyValue(prop), "ultra-condensed", + "font-stretch property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "ultra-expanded", ""); + is(cs.getPropertyValue(prop), "ultra-condensed", + "font-stretch property " + prop + ": clamping of values"); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "ultra-expanded", ""); + is(cs.getPropertyValue(prop), "ultra-expanded", + "font-stretch property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "ultra-condensed", ""); + is(cs.getPropertyValue(prop), "ultra-expanded", + "font-stretch property " + prop + ": clamping of values"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_font_weight(prop) { + is(prop, "font-weight", "only designed for one property"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "normal", ""); + is(cs.getPropertyValue(prop), "400", + "font-weight property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "900", ""); + is(cs.getPropertyValue(prop), "500", + "font-weight property " + prop + ": interpolation of font-weights"); + check_distance(prop, "400", "500", "800"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "900", ""); + is(cs.getPropertyValue(prop), "900", + "font-weight property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "100", ""); + is(cs.getPropertyValue(prop), "700", + "font-weight property " + prop + ": interpolation of font-weights"); + check_distance(prop, "900", "700", "100"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "100", ""); + is(cs.getPropertyValue(prop), "100", + "font-weight property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "900", ""); + is(cs.getPropertyValue(prop), "100", + "font-weight property " + prop + ": clamping of values"); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "900", ""); + is(cs.getPropertyValue(prop), "900", + "font-weight property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "100", ""); + is(cs.getPropertyValue(prop), "900", + "font-weight property " + prop + ": clamping of values"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_grid_gap(prop) { + if (!SpecialPowers.getBoolPref("layout.css.grid.enabled")) { + return; + } + test_length_transition(prop); + test_length_clamped(prop); + test_percent_transition(prop); + test_percent_clamped(prop); +} + +function test_pos_integer_or_auto_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "4", ""); + is(cs.getPropertyValue(prop), "4", + "integer-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "11", ""); + is(cs.getPropertyValue(prop), "5", + "integer-valued property " + prop + ": interpolation of integers"); + check_distance(prop, "4", "6", "12"); + div.style.setProperty(prop, "auto", ""); + is(cs.getPropertyValue(prop), "auto", + "integer-valued property " + prop + ": auto not interpolable"); + div.style.setProperty(prop, "8", ""); + is(cs.getPropertyValue(prop), "8", + "integer-valued property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "4", ""); + is(cs.getPropertyValue(prop), "7", + "integer-valued property " + prop + ": interpolation of integers"); + check_distance(prop, "8", "7", "4"); +} + +function test_integer_at_least_one_clamping(prop) { + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "1", ""); + is(cs.getPropertyValue(prop), "1", + "integer-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "5", ""); + is(cs.getPropertyValue(prop), "1", + "integer-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_length_pair_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "4px 6px", ""); + is(cs.getPropertyValue(prop), "4px 6px", + "length-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "12px 10px", ""); + is(cs.getPropertyValue(prop), "6px 7px", + "length-valued property " + prop + ": interpolation of lengths"); + check_distance(prop, "4px 6px", "6px 7px", "12px 10px"); +} + +function test_length_pair_transition_clamped(prop) { + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0px 0px", ""); + is(cs.getPropertyValue(prop), "0px 0px", + "length-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "30px 50px", ""); + is(cs.getPropertyValue(prop), "0px 0px", + "length-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_length_percent_pair_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "4px 50%", ""); + is(cs.getPropertyValue(prop), "4px 5px", + "length-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "12px 70%", ""); + is(cs.getPropertyValue(prop), "6px 5.5px", + "length-valued property " + prop + ": interpolation of lengths"); + check_distance(prop, "4px 50%", "6px 55%", "12px 70%"); +} + +function test_length_percent_pair_clamped(prop) { + test_length_percent_pair_clamped_or_unclamped(prop, true); +} + +function test_length_percent_pair_unclamped(prop) { + test_length_percent_pair_clamped_or_unclamped(prop, false); +} + +function test_length_percent_pair_clamped_or_unclamped(prop, is_clamped) { + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0px 0%", ""); + is(cs.getPropertyValue(prop), "0px 0px", + "length+percent-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "30px 25%", ""); + (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0px 0px", + "length+percent-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_rect_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "rect(4px, 16px, 12px, 6px)", ""); + is(cs.getPropertyValue(prop), "rect(4px, 16px, 12px, 6px)", + "rect-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "rect(0px, 4px, 4px, 2px)", ""); + is(cs.getPropertyValue(prop), "rect(3px, 13px, 10px, 5px)", + "rect-valued property " + prop + ": interpolation of rects"); + check_distance(prop, "rect(4px, 16px, 12px, 6px)", + "rect(3px, 13px, 10px, 5px)", + "rect(0px, 4px, 4px, 2px)"); + if (prop == "clip") { + div.style.setProperty(prop, "rect(0px, 6px, 4px, auto)", ""); + is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)", + "rect-valued property " + prop + ": can't interpolate auto components"); + div.style.setProperty(prop, "rect(0px, 6px, 4px, 2px)", ""); + } + div.style.setProperty(prop, "auto", ""); + is(cs.getPropertyValue(prop), "auto", + "rect-valued property " + prop + ": can't interpolate auto components"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "rect(-10px, 30px, 0px, 0px)", ""); + var vals = cs.getPropertyValue(prop).match(/rect\(([^, ]*), ([^, ]*), ([^, ]*), ([^, ]*)\)/); + is(vals.length, 5, + "rect-valued property " + prop + ": flush before clamping test (length)"); + is(vals[1], "-10px", + "rect-valued property " + prop + ": flush before clamping test (top)"); + is(vals[2], "30px", + "rect-valued property " + prop + ": flush before clamping test (right)"); + is(vals[3], "0px", + "rect-valued property " + prop + ": flush before clamping test (bottom)"); + is(vals[4], "0px", + "rect-valued property " + prop + ": flush before clamping test (left)"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "rect(0px, 40px, 10px, 10px)", ""); + vals = cs.getPropertyValue(prop).match(/rect\(([^, ]*), ([^, ]*), ([^, ]*), ([^, ]*)\)/); + is(vals.length, 5, + "rect-valued property " + prop + ": clamping of negatives (length)"); + isnot(vals[1], "-10px", + "rect-valued property " + prop + ": clamping of negatives (top)"); + isnot(vals[2], "30px", + "rect-valued property " + prop + ": clamping of negatives (right)"); + isnot(vals[3], "0px", + "rect-valued property " + prop + ": clamping of negatives (bottom)"); + isnot(vals[4], "0px", + "rect-valued property " + prop + ": clamping of negatives (left)"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_visibility_transition(prop) { + function do_test(from_value, to_value, interp_value) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, from_value, ""); + is(cs.getPropertyValue(prop), from_value, + "visibility property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, to_value, ""); + is(cs.getPropertyValue(prop), interp_value, + "visibility property " + prop + ": interpolation of visibility"); + } + + do_test("visible", "hidden", "visible"); + do_test("hidden", "visible", "visible"); + do_test("hidden", "collapse", "collapse"); /* not interpolable */ + do_test("collapse", "hidden", "hidden"); /* not interpolable */ + do_test("visible", "collapse", "visible"); + do_test("collapse", "visible", "visible"); + + isnot(get_distance(prop, "visible", "hidden"), 0, + "distance between visible and hidden should not be zero"); + isnot(get_distance(prop, "visible", "collapse"), 0, + "distance between visible and collapse should not be zero"); + is(get_distance(prop, "visible", "visible"), 0, + "distance between visible and visible should not be zero"); + is(get_distance(prop, "hidden", "hidden"), 0, + "distance between hidden and hidden should not be zero"); + is(get_distance(prop, "collapse", "collapse"), 0, + "distance between collapse and collapse should not be zero"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + function do_negative_test(from_value, to_value, interpolable) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, from_value, ""); + is(cs.getPropertyValue(prop), from_value, + "visibility property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, to_value, ""); + is(cs.getPropertyValue(prop), interpolable ? from_value : to_value, + "visibility property " + prop + ": clamping of negatives"); + } + do_negative_test("visible", "hidden", true); + do_negative_test("hidden", "visible", true); + do_negative_test("hidden", "collapse", false); + do_negative_test("collapse", "hidden", false); + do_negative_test("visible", "collapse", true); + do_negative_test("collapse", "visible", true); + + div.style.setProperty("transition-delay", "-3s", ""); + div.style.setProperty("transition-timing-function", FUNC_OVERONE, ""); + + function do_overone_test(from_value, to_value) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, from_value, ""); + is(cs.getPropertyValue(prop), from_value, + "visibility property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, to_value, ""); + is(cs.getPropertyValue(prop), to_value, + "visibility property " + prop + ": clamping of over-ones"); + } + do_overone_test("visible", "hidden"); + do_overone_test("hidden", "visible"); + do_overone_test("hidden", "collapse"); + do_overone_test("collapse", "hidden"); + do_overone_test("visible", "collapse"); + do_overone_test("collapse", "visible"); + + div.style.setProperty("transition-delay", "-1s", ""); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_background_size_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "50% 80%", ""); + is(cs.getPropertyValue(prop), "50% 80%", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "100% 100%", ""); + is(cs.getPropertyValue(prop), "62.5% 85%", + "property " + prop + ": interpolation of percents"); + check_distance(prop, "50% 80%", "62.5% 85%", "100% 100%"); + div.style.setProperty(prop, "contain", ""); + is(cs.getPropertyValue(prop), "contain", + "property " + prop + ": can't interpolate 'contain'"); + test_background_position_size_common(prop, true, true); +} + +function test_background_position_transition(prop) { + var doesPropTakeListValues = (prop == "background-position") || + (prop == "mask-position"); + var doesPropHaveDistanceComputation = (prop != "background-position") && + (prop != "mask-position"); + + // Test interpolation between edge keywords, and between edge keyword and a + // percent value. (Note: edge keywords are really aliases for percent vals.) + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "center 80%", ""); + is(cs.getPropertyValue(prop), "50% 80%", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "bottom right", ""); + is(cs.getPropertyValue(prop), "62.5% 85%", + "property " + prop + ": interpolation of edge keywords & percents"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "center 80%", "62.5% 85%", "bottom right"); + } + + // Test interpolation between edge keyword *with an offset* and non-keyword + // values. + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "right 20px bottom 30%", ""); + is(cs.getPropertyValue(prop), "calc(-20px + 100%) 70%", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "calc(40px + 20%) calc(12px + 30%)", ""); + is(cs.getPropertyValue(prop), "calc(-5px + 80%) calc(3px + 60%)", + "property " + prop + ": interpolation of edge keywords w/ offsets & calc"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "right 20px bottom 30%", + "calc(-5px + 80%) calc(3px + 60%)", + "calc(40px + 20%) calc(12px + 30%)"); + } + + test_background_position_size_common(prop, doesPropTakeListValues, + doesPropHaveDistanceComputation); +} + +function test_background_position_coord_transition(prop) { + var endEdge = prop.endsWith("-x") ? "right" : "bottom"; + + // Test interpolation between edge keywords, and between edge keyword and a + // percent value. (Note: edge keywords are really aliases for percent vals.) + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "center", ""); + is(cs.getPropertyValue(prop), "50%", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, endEdge, ""); + is(cs.getPropertyValue(prop), "62.5%", + "property " + prop + ": interpolation of edge keywords & percents"); + check_distance(prop, "center", "62.5%", endEdge); + + // Test interpolation between edge keyword *with an offset* and non-keyword + // values. + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, `${endEdge} 20px`, ""); + is(cs.getPropertyValue(prop), "calc(-20px + 100%)", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "calc(40px + 20%)", ""); + is(cs.getPropertyValue(prop), "calc(-5px + 80%)", + "property " + prop + ": interpolation of edge keywords w/ offsets & calc"); + check_distance(prop, `${endEdge} 20px`, + "calc(-5px + 80%)", + "calc(40px + 20%)"); + + div.style.setProperty(prop, "10px, 50px, 30px", ""); + is(cs.getPropertyValue(prop), "10px, 50px, 30px", + "property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "50px, 70px, 30px", ""); + is(cs.getPropertyValue(prop), "20px, 55px, 30px", + "property " + prop + ": interpolation of lists of lengths"); + check_distance(prop, "10px, 50px, 30px", + "20px, 55px, 30px", + "50px, 70px, 30px"); + div.style.setProperty(prop, "10px, 50%, 30%, 5px", ""); + is(cs.getPropertyValue(prop), "10px, 50%, 30%, 5px", + "property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "50px, 70%, 30%, 25px", ""); + is(cs.getPropertyValue(prop), "20px, 55%, 30%, 10px", + "property " + prop + ": interpolation of lists of lengths and percents"); + check_distance(prop, "10px, 50%, 30%, 5px", + "20px, 55%, 30%, 10px", + "50px, 70%, 30%, 25px"); + div.style.setProperty(prop, "20%, 8px", ""); + is(cs.getPropertyValue(prop), "20%, 8px", + "property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "12px, 40%", ""); + is(cs.getPropertyValue(prop), "calc(3px + 15%), calc(6px + 10%)", + "property " + prop + ": interpolation that computes to calc()"); + check_distance(prop, "20%, 8px", + "calc(3px + 15%), calc(6px + 10%)", + "12px, 40%"); + div.style.setProperty(prop, "calc(20% + 40px), 8px, calc(20px + 12%)", ""); + is(cs.getPropertyValue(prop), "calc(40px + 20%), 8px, calc(20px + 12%)", + "property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "12px, calc(20%), calc(8px + 20%)", ""); + is(cs.getPropertyValue(prop), "calc(33px + 15%), calc(6px + 5%), calc(17px + 14%)", + "property " + prop + ": interpolation that computes to calc()"); + check_distance(prop, "calc(20% + 40px), 8px, calc(20px + 12%)", + "calc(33px + 15%), calc(6px + 5%), calc(17px + 14%)", + "12px, calc(20%), calc(8px + 20%)"); +} + +/** + * Common tests for 'background-position', 'background-size', and other + * properties that take CSS value-type 'position' or 'bg-size'. + * + * @arg prop The name of the property + * @arg doesPropTakeListValues + * If false, the property is assumed to just take a single 'position' or + * 'bg-size' value. If true, the property is assumed to also accept + * comma-separated list of such values. + */ +function test_background_position_size_common(prop, doesPropTakeListValues, + doesPropHaveDistanceComputation) { + // Test non-list values + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "40% 0%", ""); + is(cs.getPropertyValue(prop), "40% 0%", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "0% 0%", ""); + is(cs.getPropertyValue(prop), "30% 0%", + "property " + prop + ": interpolation of percentages"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "40% 0%", "30% 0%", "0% 0%"); + } + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "0% 40%", ""); + is(cs.getPropertyValue(prop), "0% 40%", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "0% 0%", ""); + is(cs.getPropertyValue(prop), "0% 30%", + "property " + prop + ": interpolation of percentages"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "0% 40%", "0% 30%", "0% 0%"); + } + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "10px 40px", ""); + is(cs.getPropertyValue(prop), "10px 40px", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "50px 0", ""); + is(cs.getPropertyValue(prop), "20px 30px", + "property " + prop + ": interpolation of lengths"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "10px 40px", "20px 30px", "50px 0"); + } + + // Test interpolation that computes to to calc() (transition from % to px) + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "20% 40%", ""); + is(cs.getPropertyValue(prop), "20% 40%", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "12px 20px", ""); + is(cs.getPropertyValue(prop), "calc(3px + 15%) calc(5px + 30%)", + "property " + prop + ": interpolation that computes to calc()"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "20% 40%", + "calc(3px + 15%) calc(5px + 30%)", + "12px 20px"); + } + + // Test interpolation that computes to to calc() (transition from px to %) + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "12px 20px", ""); + is(cs.getPropertyValue(prop), "12px 20px", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "20% 40%", ""); + is(cs.getPropertyValue(prop), "calc(9px + 5%) calc(15px + 10%)", + "property " + prop + ": interpolation that computes to calc()"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "12px 20px", + "calc(9px + 5%) calc(15px + 10%)", + "20% 40%"); + } + + // Test interpolation between calc() and non-calc() + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "calc(40px + 10%) 16px", ""); + is(cs.getPropertyValue(prop), "calc(40px + 10%) 16px", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "30% calc(8px + 60%)", ""); + is(cs.getPropertyValue(prop), "calc(30px + 15%) calc(14px + 15%)", + "property " + prop + ": interpolation between calc() and non-calc()"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "calc(40px + 10%) 16px", + "calc(30px + 15%) calc(14px + 15%)", + "30% calc(8px + 60%)"); + } + + // Test list values, if appropriate + if (doesPropTakeListValues) { + div.style.setProperty(prop, "10px 40px, 50px 50px, 30px 20px", ""); + is(cs.getPropertyValue(prop), "10px 40px, 50px 50px, 30px 20px", + "property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "50px 20px, 70px 50px, 30px 40px", ""); + is(cs.getPropertyValue(prop), "20px 35px, 55px 50px, 30px 25px", + "property " + prop + ": interpolation of lists of lengths"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "10px 40px, 50px 50px, 30px 20px", + "20px 35px, 55px 50px, 30px 25px", + "50px 20px, 70px 50px, 30px 40px"); + } + div.style.setProperty(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px", ""); + is(cs.getPropertyValue(prop), "10px 40%, 50% 50px, 30% 20%, 5px 10px", + "property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "50px 20%, 70% 50px, 30% 40%, 25px 50px", ""); + is(cs.getPropertyValue(prop), "20px 35%, 55% 50px, 30% 25%, 10px 20px", + "property " + prop + ": interpolation of lists of lengths and percents"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px", + "20px 35%, 55% 50px, 30% 25%, 10px 20px", + "50px 20%, 70% 50px, 30% 40%, 25px 50px"); + } + div.style.setProperty(prop, "20% 40%, 8px 12px", ""); + is(cs.getPropertyValue(prop), "20% 40%, 8px 12px", + "property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "12px 20px, 40% 16%", ""); + is(cs.getPropertyValue(prop), "calc(3px + 15%) calc(5px + 30%), calc(6px + 10%) calc(9px + 4%)", + "property " + prop + ": interpolation that computes to calc()"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "20% 40%, 8px 12px", + "calc(3px + 15%) calc(5px + 30%), calc(6px + 10%) calc(9px + 4%)", + "12px 20px, 40% 16%"); + } + div.style.setProperty(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)", ""); + is(cs.getPropertyValue(prop), "calc(40px + 20%) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)", + "property " + prop + ": computed value before transition"); + div.style.setProperty(prop, "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)", ""); + is(cs.getPropertyValue(prop), "calc(33px + 15%) calc(30px + 35%), calc(6px + 5%) calc(4px + 24%), calc(17px + 14%) calc(28px + 10%)", + "property " + prop + ": interpolation that computes to calc()"); + if (doesPropHaveDistanceComputation) { + check_distance(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)", + "calc(33px + 15%) calc(30px + 35%), calc(6px + 5%) calc(4px + 24%), calc(17px + 14%) calc(28px + 10%)", + "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)"); + } + } +} + +function test_transform_transition(prop) { + is(prop, "transform", "Unexpected transform property! Test needs to be fixed"); + var matrix_re = /^matrix\(([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*)\)$/; + for (var i in transformTests) { + var test = transformTests[i]; + if (!("expected" in test)) { + var v = test.expected_uncomputed; + if (v.match(matrix_re) && !test.force_compute) { + test.expected = v; + } else { + test.expected = computeMatrix(v); + } + } + } + + for (var i in transformTests) { + var test = transformTests[i]; + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, test.start, ""); + cs.getPropertyValue(prop); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, test.end, ""); + var actual = cs.getPropertyValue(prop); + if (!test.round_error_ok || actual == test.expected) { + // In most cases, we'll get an exact match, but in some cases + // there can be a small amount of rounding error. + is(actual, test.expected, + "interpolation of transitions: " + test.start + " to " + test.end); + } else { + function s(mat) { + return mat.match(matrix_re).slice(1,7); + } + var pass = true; + var actual_split = s(actual); + var expected_split = s(test.expected); + for (var i = 0; i < 6; ++i) { + // Allow differences of 1 at the sixth decimal place, and allow + // a drop extra for floating point error from the subtraction. + if (Math.abs(Number(actual_split[i]) - Number(expected_split[i])) > + 0.0000011) { + pass = false; + } + } + ok(pass, + "interpolation of transitions: " + test.start + " to " + test.end + + ": " + actual + " should approximately equal " + test.expected); + } + } + + // FIXME: should perhaps test that no clamping occurs + + runOMTATest(runAsyncTests, SimpleTest.finish); +} + +function runAsyncTests() { + // These tests check the value on the compositor 2/3rds of the way through + // the transition. + // For the transform tests we simply compare the value on the compositor + // with the computed value, but for the opacity test we check the absolute + // value as well. + OMTAdiv.style.setProperty("transition-duration", "300s", ""); + OMTAdiv.style.setProperty("transition-timing-function", "linear", ""); + addAsyncTransformTests(); + addAsyncOpacityTest(); + addAsyncDelayTest(); + + runAllAsyncAnimTests().then(function() { + OMTAdiv.style.removeProperty("transition"); + SimpleTest.finish(); + }); +} + +function addAsyncTransformTests() { + transformTests.forEach(function(test) { + addAsyncAnimTest(function () { return runTransformTest(test); } ); + }); +} + +function *runTransformTest(test) { + OMTAdiv.style.setProperty("transition-property", "none", ""); + OMTAdiv.style.setProperty("transform", test.start, ""); + OMTACs.getPropertyValue("transform"); + OMTAdiv.style.setProperty("transition-property", "transform", ""); + OMTAdiv.style.setProperty("transform", test.end, ""); + OMTACs.getPropertyValue("transform"); + yield waitForPaints(); + + // If the start value produced a non-invertible matrix the layer won't be + // created yet so we need to force an extra sample. + if (!isTransformInvertible(test.start)) { + winUtils.advanceTimeAndRefresh(100000); + yield waitForPaints(); + winUtils.advanceTimeAndRefresh(100000); + yield waitForPaints(); + } else { + winUtils.advanceTimeAndRefresh(200000); + yield waitForPaints(); + } + + omta_is_approx(OMTAdiv, "transform", OMTACs.getPropertyValue("transform"), + 0.0001, RunningOn.Compositor, + "compositor transform transition " + + "from '" + test.start + "' " + + "to '" + test.end + "' " + + "at 2/3rds duration matches computed style"); +} + +function addAsyncOpacityTest() { + addAsyncAnimTest(function *() { + OMTAdiv.style.setProperty("transition-property", "none", ""); + OMTAdiv.style.setProperty("opacity", 0, ""); + OMTACs.getPropertyValue("opacity"); + OMTAdiv.style.setProperty("transition-property", "opacity", ""); + OMTAdiv.style.setProperty("opacity", 1, ""); + OMTACs.getPropertyValue("opacity"); + + yield waitForPaints(); + + winUtils.advanceTimeAndRefresh(200000); + + omta_is_approx(OMTAdiv, "opacity", 2/3, 0.00001, RunningOn.Compositor, + "compositor opacity transition at 2/3rds duration"); + }); +} + +function addAsyncDelayTest() { + addAsyncAnimTest(function *() { + OMTAdiv.style.setProperty("transition-property", "none", ""); + OMTAdiv.style.setProperty("transition-delay", "100s", ""); + OMTAdiv.style.setProperty("transition-duration", "200s", ""); + OMTAdiv.style.setProperty("transform", "", ""); + OMTACs.getPropertyValue("transform"); + OMTAdiv.style.setProperty("transition-property", "transform", ""); + OMTAdiv.style.setProperty("transform", "translate(100px)", ""); + OMTACs.getPropertyValue("transform"); + + winUtils.advanceTimeAndRefresh(200000); + yield waitForPaints(); + + omta_is_approx(OMTAdiv, "transform", { tx: 50 }, 0.0001, + RunningOn.Compositor, + "compositor transform transition with delay at 1/2" + + " duration"); + }); +} + +function isTransformInvertible(transformStr) { + var computedStr = transformStrToComputedStr(transformStr); + if (!transformStr) + return false; + var matrix = convertTo3dMatrix(computedStr); + if (matrix === null) + return false; + return isInvertible(matrix); +} + +function transformStrToComputedStr(transformStr) { + var div = document.createElement("div"); + div.style.transform = transformStr; + return window.getComputedStyle(div).transform; +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_replacement_on_busy_frame.html b/layout/style/test/test_transitions_replacement_on_busy_frame.html new file mode 100644 index 000000000..a9e197d16 --- /dev/null +++ b/layout/style/test/test_transitions_replacement_on_busy_frame.html @@ -0,0 +1,30 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1167519 +--> +<head> + <title>Test for bug 1167519</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1167519">Mozilla Bug + 1167519</a> +<pre id="test"> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv( + { 'set': [[ 'dom.animations-api.core.enabled', true ]] }, + function() { + window.open('file_transitions_replacement_on_busy_frame.html'); + }); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_step_functions.html b/layout/style/test/test_transitions_step_functions.html new file mode 100644 index 000000000..920b48af3 --- /dev/null +++ b/layout/style/test/test_transitions_step_functions.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=435441 +--> +<head> + <title>Test for Bug 435441</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + p.transition { + transition: margin-top 100s linear; + } + + </style> +</head> +<body> +<div id="display"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for transition step functions **/ + +var display = document.getElementById("display"); + +function run_test(tf, percent, value) +{ + var p = document.createElement("p"); + p.className = "transition"; + p.style.marginTop = "0px"; + // be this percent of the way through a 100s transition + p.style.transitionDelay = (percent * -100) + "s"; + p.style.transitionTimingFunction = tf; + display.appendChild(p); + var cs = getComputedStyle(p, ""); + var flush1 = cs.marginTop; + + p.style.marginTop = "1000px"; + var result = px_to_num(cs.marginTop) / 1000 + + is(result, value, 100 * percent + "% of the way through " + tf); + + display.removeChild(p); +} + +run_test("step-start", 0, 1); +run_test("step-start", 0.001, 1); +run_test("step-start", 0.999, 1); +run_test("step-start", 1, 1); +run_test("step-end", 0, 0); +run_test("step-end", 0.001, 0); +run_test("step-end", 0.999, 0); +run_test("step-end", 1, 1); + +run_test("steps(2)", 0.00, 0.0); +run_test("steps(2)", 0.49, 0.0); +run_test("steps(2)", 0.50, 0.5); +run_test("steps(2)", 0.99, 0.5); +run_test("steps(2)", 1.00, 1.0); + +run_test("steps(2, end)", 0.00, 0.0); +run_test("steps(2, end)", 0.49, 0.0); +run_test("steps(2, end)", 0.50, 0.5); +run_test("steps(2, end)", 0.99, 0.5); +run_test("steps(2, end)", 1.00, 1.0); + +run_test("steps(2, start)", 0.00, 0.5); +run_test("steps(2, start)", 0.49, 0.5); +run_test("steps(2, start)", 0.50, 1.0); +run_test("steps(2, start)", 0.99, 1.0); +run_test("steps(2, start)", 1.00, 1.0); + +run_test("steps(8,end)", 0.00, 0.0); +run_test("steps(8,end)", 0.10, 0.0); +run_test("steps(8,end)", 0.20, 0.125); +run_test("steps(8,end)", 0.30, 0.25); +run_test("steps(8,end)", 0.40, 0.375); +run_test("steps(8,end)", 0.49, 0.375); +run_test("steps(8,end)", 0.50, 0.5); +run_test("steps(8,end)", 0.60, 0.5); +run_test("steps(8,end)", 0.70, 0.625); +run_test("steps(8,end)", 0.80, 0.75); +run_test("steps(8,end)", 0.90, 0.875); +run_test("steps(8,end)", 1.00, 1.0); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_transitions_with_disabled_properties.html b/layout/style/test/test_transitions_with_disabled_properties.html new file mode 100644 index 000000000..fc0965f42 --- /dev/null +++ b/layout/style/test/test_transitions_with_disabled_properties.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1265611 +--> +<head> + <meta charset=utf-8> + <title>Test for bug 1265611</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265611">Mozilla Bug + 1265611</a> + +<pre id="test"> +<script> +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv({'set': [['layout.css.prefixes.webkit', false], + ['dom.animations-api.core.enabled', true]] }, + () => window.open('file_transitions_with_disabled_properties.html')); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_unclosed_parentheses.html b/layout/style/test/test_unclosed_parentheses.html new file mode 100644 index 000000000..d2daae944 --- /dev/null +++ b/layout/style/test/test_unclosed_parentheses.html @@ -0,0 +1,289 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=575672 +--> +<head> + <title>Test for Bug 575672</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <style type="text/css" id="style"></style> + <style type="text/css"> + #display { position: relative } + </style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=575672">Mozilla Bug 575672</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for unclosed parentheses in CSS values. **/ + +// Each of the following semicolon-terminated @-rules should have a +// single missing ')' in the value. +var semirules = [ + "@import (", + "@import url(", + "@import url(foo", + "@import url('foo'", + "@import foo(", +]; + +// Each of the following declarations should have a single missing ')' +// in the value. +var declarations = [ + "content: url(", + "content: url( ", + "content: url(http://www.foo.com", + "content: url('http://www.foo.com'", + "content: foobar(", + "content: foobar( ", + "content: foobar(http://www.foo.com", + "content: foobar('http://www.foo.com'", + "color: url(", + "color: url( ", + "color: url(http://www.foo.com", + "color: url('http://www.foo.com'", + "background-image: linear-gradient(", + "background-image: linear-gradient( ", + "background-image: linear-gradient(to", + "background-image: linear-gradient(to top", + "background-image: linear-gradient(to top left", + "background-image: linear-gradient(to top left,", + "background-image: repeating-linear-gradient(to top left, red, blue", + "background-image: linear-gradient(to top left, red, yellow, blue", + "background-image: linear-gradient(to top left, red 1px, yellow 5px, blue 10px", + "background-image: linear-gradient(to top left, red, yellow, rgb(0, 0, 255)", + "background-image: -moz-linear-gradient(", + "background-image: -moz-linear-gradient( ", + "background-image: -moz-linear-gradient(red, blue", + "background-image: -moz-linear-gradient(red, yellow, blue", + "background-image: -moz-linear-gradient(red 1px, yellow 5px, blue 10px", + "background-image: -moz-linear-gradient(red, yellow, rgb(0, 0, 255)", + "background-image: -moz-linear-gradient(to", + "background-image: -moz-linear-gradient(to top", + "background-image: -moz-linear-gradient(to top left", + "background-image: -moz-linear-gradient(to top left,", + "background-image: -moz-repeating-linear-gradient(to top left, red, blue", + "background-image: -moz-linear-gradient(to top left, red, yellow, blue", + "background-image: -moz-linear-gradient(to top left, red 1px, yellow 5px, blue 10px", + "background-image: -moz-linear-gradient(to top left, red, yellow, rgb(0, 0, 255)", + "background-image: -moz-repeating-linear-gradient(top left, red, blue", + "background-image: -moz-linear-gradient(top left, red, yellow, blue", + "background-image: -moz-linear-gradient(top left, red 1px, yellow 5px, blue 10px", + "background-image: -moz-linear-gradient(top left, red, yellow, rgb(0, 0, 255)", + "background-image: radial-gradient(", + "background-image: radial-gradient( ", + "background-image: radial-gradient(at", + "background-image: radial-gradient(at ", + "background-image: radial-gradient(at center", + "background-image: radial-gradient(at center,", + "background-image: radial-gradient(at center ", + "background-image: radial-gradient(closest-corner", + "background-image: radial-gradient(farthest-side ", + "background-image: radial-gradient(closest-corner ellipse", + "background-image: radial-gradient(farthest-side circle ", + "background-image: radial-gradient(closest-corner ellipse at", + "background-image: radial-gradient(farthest-side circle at ", + "background-image: radial-gradient(closest-corner ellipse at center", + "background-image: radial-gradient(farthest-side circle at center ", + "background-image: radial-gradient(50px", + "background-image: radial-gradient(50px,", + "background-image: radial-gradient(50px ", + "background-image: radial-gradient(50px at", + "background-image: radial-gradient(50px at ", + "background-image: radial-gradient(50px at center", + "background-image: radial-gradient(50px at center ", + "background-image: radial-gradient(50px at center,", + "background-image: radial-gradient(50px 50px", + "background-image: radial-gradient(50px 50px,", + "background-image: radial-gradient(50px 50px ", + "background-image: radial-gradient(50px 50px at", + "background-image: radial-gradient(50px 50px at ", + "background-image: radial-gradient(50px 50px at center", + "background-image: radial-gradient(50px 50px at center ", + "background-image: radial-gradient(50px 50px at center,", + "background-image: radial-gradient(50px 50px at center, red, blue", + "background-image: radial-gradient(ellipse at", + "background-image: radial-gradient(ellipse at ", + "background-image: radial-gradient(circle", + "background-image: radial-gradient(circle ", + "background-image: radial-gradient(circle closest-corner", + "background-image: radial-gradient(circle farthest-side ", + "background-image: radial-gradient(ellipse closest-corner at center", + "background-image: radial-gradient(ellipse farthest-side at center,", + "background-image: radial-gradient(circle at center", + "background-image: radial-gradient(circle at center,", + "background-image: radial-gradient(circle at center ", + "background-image: radial-gradient(circle at 50px center", + "background-image: radial-gradient(circle at 50px center ", + "background-image: radial-gradient(ellipse 50px", + "background-image: radial-gradient(ellipse 50px ", + "background-image: radial-gradient(ellipse 50px 50px", + "background-image: radial-gradient(ellipse 50px 50px,", + "background-image: radial-gradient(ellipse 50px 50px ", + "background-image: radial-gradient(ellipse 50px 50px at", + "background-image: radial-gradient(ellipse 50px 50px at ", + "background-image: radial-gradient(ellipse 50px 50px at center", + "background-image: radial-gradient(ellipse 50px 50px at center ", + "background-image: radial-gradient(ellipse 50px 50px at center,", + "background-image: radial-gradient(ellipse 50px 50px at center, red, blue", + "background-image: repeating-radial-gradient(50%", + "background-image: repeating-radial-gradient(50% ", + "background-image: repeating-radial-gradient(50% 50%", + "background-image: repeating-radial-gradient(50% 50%,", + "background-image: repeating-radial-gradient(50% 50%, red, blue", + "background-image: -moz-radial-gradient(", + "background-image: -moz-radial-gradient( ", + "background-image: -moz-radial-gradient(top left 45deg, red, blue", + "background-image: -moz-radial-gradient(cover, red, blue", + "background-image: -moz-repeating-radial-gradient(circle, red, blue", + "background-image: -moz-radial-gradient(ellipse closest-corner, red, hsl(240, 50%, 50%)", + "background-image: -moz-radial-gradient(farthest-side circle, red, blue", + "background-image: -moz-image-rect(", + "background-image: -moz-image-rect( ", + "background-image: -moz-image-rect(url(foo.jpg)", + "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10", + "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10 ", + "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10,", + "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, ", + "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, 10", + "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, 10 ", + "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, 10,", + "background-image: -moz-image-rect(url(foo.jpg), 2, 10, 10, 10, ", + "color: rgb(", + "color: rgb( ", + "color: rgb(128, 0", + "color: rgb(128, 0, 128", + "color: rgb(128, 0, 128, 128", + "color: rgba(", + "color: rgba( ", + "color: rgba(128, 0", + "color: rgba(128, 0, 128", + "color: rgba(128, 0, 128, 1", + "color: rgba(128, 0, 128, 1, 1", + "color: hsl(", + "color: hsl( ", + "color: hsl(240, 50%", + "color: hsl(240, 50%, 50%", + "color: hsl(240, 50%, 50%, 50%", + "color: hsla(", + "color: hsla( ", + "color: hsla(240, 50%", + "color: hsla(240, 50%, 50%", + "color: hsla(240, 50%, 50%, 1", + "color: hsla(240, 50%, 50%, 1, 1", + "content: counter(", + "content: counter( ", + "content: counter(foo", + "content: counter(foo ", + "content: counter(foo,", + "content: counter(foo, ", + "content: counter(foo, upper-roman", + "content: counter(foo, upper-roman ", + "content: counter(foo, upper-roman,", + "content: counter(foo, upper-roman, ", + "content: counters(", + "content: counters( ", + "content: counters(foo, ','", + "content: counters(foo, ',' ", + "content: counters(foo, ',',", + "content: counters(foo, ',', ", + "content: counters(foo, ',', upper-roman", + "content: counters(foo, ',', upper-roman ", + "content: counters(foo, ',', upper-roman,", + "content: counters(foo, ',', upper-roman, ", + "content: attr(", + "content: attr( ", + "content: attr(href", + "content: attr(href ", + "content: attr(html", + "content: attr(html ", + "content: attr(html|", + "content: attr(html| ", + "content: attr(html|href", + "content: attr(html|href ", + "content: attr(|", + "content: attr(| ", + "content: attr(|href", + "content: attr(|href ", + "transition-timing-function: cubic-bezier(", + "transition-timing-function: cubic-bezier( ", + "transition-timing-function: cubic-bezier(0, 0, 1", + "transition-timing-function: cubic-bezier(0, 0, 1 ", + "transition-timing-function: cubic-bezier(0, 0, 1,", + "transition-timing-function: cubic-bezier(0, 0, 1, ", + "transition-timing-function: cubic-bezier(0, 0, 1, 1", + "transition-timing-function: cubic-bezier(0, 0, 1, 1 ", + "transition-timing-function: cubic-bezier(0, 0, 1, 1,", + "transition-timing-function: cubic-bezier(0, 0, 1, 1, ", + "border-top-width: calc(", + "border-top-width: calc( ", + "border-top-width: calc(2em", + "border-top-width: calc(2em ", + "border-top-width: calc(2em +", + "border-top-width: calc(2em + ", + "border-top-width: calc(2em *", + "border-top-width: calc(2em * ", + "border-top-width: calc((2em)", + "border-top-width: calc((2em) ", +]; + +var selectors = [ + ":not(", + ":not( ", + ":not(-", + ":not(- ", + ":not(>", + ":not(> ", + ":not(div p", + ":not(div p ", + ":not(div >", + ":not(div > ", +]; + +var textNode = document.createTextNode(""); +document.getElementById("style").appendChild(textNode); +var cs = getComputedStyle(document.getElementById("display"), ""); + +for (var i = 0; i < semirules.length; ++i) { + var sheet = semirules[i] + + "p#display { color: red } ) ; p { color: green; z-index: " + (i + 1) + " }"; + textNode.data = sheet; + is(cs.color, "rgb(0, 128, 0)", + "color for rule '" + semirules[i] + "'"); + is(cs.zIndex, String(i + 1), + "z-index for rule '" + semirules[i] + "'"); +} + +for (var i = 0; i < declarations.length; ++i) { + var sheet = "@namespace html url(http://www.w3.org/1999/xhtml);\n" + + "#display { color: green; " + declarations[i] + + " x x x x x x x ; color: red; ) ; z-index: " + (i + 1) + " }"; + textNode.data = sheet; + is(cs.color, "rgb(0, 128, 0)", + "color for declaration '" + declarations[i] + "'"); + is(cs.zIndex, String(i + 1), + "z-index for declaration '" + declarations[i] + "'"); +} + +for (var i = 0; i < selectors.length; ++i) { + var sheet = "@namespace html url(http://www.w3.org/1999/xhtml);\n" + + "#display { color: green } " + + selectors[i] + " x x x x x x x , #display { color: red } #display { color: red } ) , #display { color: red } " + + "#display { z-index: " + (i + 1) + " }"; + textNode.data = sheet; + is(cs.color, "rgb(0, 128, 0)", + "color for selector '" + selectors[i] + "'"); + is(cs.zIndex, String(i + 1), + "z-index for selector '" + selectors[i] + "'"); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_unicode_range_loading.html b/layout/style/test/test_unicode_range_loading.html new file mode 100644 index 000000000..43622e2ae --- /dev/null +++ b/layout/style/test/test_unicode_range_loading.html @@ -0,0 +1,366 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset=utf-8> + <title>unicode-range load tests using font loading api</title> + <link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"> + <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#unicode-range-desc" /> + <link rel="help" href="http://dev.w3.org/csswg/css-font-loading/" /> + <meta name="assert" content="unicode-range descriptor defines precisely which fonts should be loaded" /> + <script type="text/javascript" src="/resources/testharness.js"></script> + <script type="text/javascript" src="/resources/testharnessreport.js"></script> + <style type="text/css"> + </style> +</head> +<body> +<div id="log"></div> +<pre id="display"></pre> +<style id="testfonts"></style> +<style id="teststyle"></style> +<div id="testcontent"></div> + +<script> + +const kSheetFonts = 1; +const kSheetStyles = 2; + +const redSquDataURL = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' width='100%' height='100%'><rect fill='red' x='0' y='0' width='10' height='10'/></svg>"; + +var unicodeRangeTests = [ + { test: "simple load sanity check, unused fonts not loaded", + fonts: [{ family: "a", src: "markA", descriptors: { }, loaded: false}], + content: "AAA", style: { "font-family": "unused" } }, + { test: "simple load sanity check, font for a used character loaded", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}], + content: "AAA" }, + { test: "simple load sanity check, font for an unused character not loaded", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false}], + content: "BBB" }, + { test: "simple load sanity check, with two fonts only font for used character loaded A", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}], + content: "AAA" }, + { test: "simple load sanity check, with two fonts only font for used character loaded B", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}], + content: "BBB" }, + { test: "simple load sanity check, two fonts but neither supports characters used", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}], + content: "CCC" }, + { test: "simple load sanity check, two fonts and both are used", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}], + content: "ABC" }, + { test: "simple load sanity check, one with Han ranges", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+3???,u+5???,u+7???,u+8???" }, loaded: true}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}], + content: "納豆嫌い" }, + { test: "simple load sanity check, two fonts with different styles A", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}, + { family: "a", src: "markB", descriptors: { weight: "bold", unicodeRange: "u+42" }, loaded: false}], + content: "ABC" }, + { test: "simple load sanity check, two fonts with different styles B", + fonts: [{ family: "a", src: "markA", descriptors: { weight: "bold", unicodeRange: "u+41" }, loaded: false}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}], + content: "ABC" }, + { test: "multiple fonts with overlapping ranges, all with default ranges, only last one supports character used", + fonts: [{ family: "a", src: "markC", descriptors: { }, loaded: true}, + { family: "a", src: "markA", descriptors: { }, loaded: true}, + { family: "a", src: "markB", descriptors: { }, loaded: true}], + content: "CCC" }, + { test: "multiple fonts with overlapping ranges, all with default ranges, first one supports character used", + fonts: [{ family: "a", src: "markB", descriptors: { }, loaded: false}, + { family: "a", src: "markA", descriptors: { }, loaded: false}, + { family: "a", src: "markC", descriptors: { }, loaded: true}], + content: "CCC" }, + { test: "multiple fonts with overlapping ranges, one with default value in the fallback position", + fonts: [{ family: "a", src: "markC", descriptors: { }, loaded: true}, + { family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}], + content: "ABC" }, + { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, fallback to one", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}, + { family: "a", src: "markC", descriptors: { }, loaded: true}], + content: "AAA" }, + { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, fallback to two", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: true}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: true}, + { family: "a", src: "markC", descriptors: { }, loaded: true}], + content: "ABC" }, + { test: "multiple fonts with overlapping ranges, one with default value in the primary use position, no fallback", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+41" }, loaded: false}, + { family: "a", src: "markB", descriptors: { unicodeRange: "u+42" }, loaded: false}, + { family: "a", src: "markC", descriptors: { }, loaded: true}], + content: "CCC" }, + { test: "metrics only case, ex-sized image, single font with space in range", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+0??" }, loaded: true}], + content: "<img style='width: 2ex' src=\"" + redSquDataURL + "\">" }, + { test: "metrics only case, ex-sized image, single font with space outside range", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+1??" }, loaded: false}], + content: "<img style='width: 2ex' src=\"" + redSquDataURL + "\">" }, + { test: "metrics only case, ch-sized image, single font with space in range", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+0??" }, loaded: true}], + content: "<img style='width: 2ch' src=\"" + redSquDataURL + "\">" }, + { test: "metrics only case, ch-sized image, single font with space outside range", + fonts: [{ family: "a", src: "markA", descriptors: { unicodeRange: "u+1??" }, loaded: false}], + content: "<img style='width: 2ch' src=\"" + redSquDataURL + "\">" }, +]; + +// map font loading descriptor names to @font-face rule descriptor names +var mapDescriptorNames = { + style: "font-style", + weight: "font-weight", + stretch: "font-stretch", + unicodeRange: "unicode-range", + variant: "font-variant", + featureSettings: "font-feature-settings" +}; + +var kBaseFontURL; +if ("SpecialPowers" in window) { + kBaseFontURL = ""; +} else { + kBaseFontURL = "fonts/"; +} + +var mapFontURLs = { + markA: "url(" + kBaseFontURL + "markA.woff" + ")", + markB: "url(" + kBaseFontURL + "markB.woff" + ")", + markC: "url(" + kBaseFontURL + "markC.woff" + ")", + markD: "url(" + kBaseFontURL + "markD.woff" + ")", + + /* twourl versions include a bogus url followed by a valid url */ + markAtwourl: "url(" + kBaseFontURL + "bogus-markA.woff" + "), url(" + kBaseFontURL + "markA.woff" + ")", + markBtwourl: "url(" + kBaseFontURL + "bogus-markB.woff" + "), url(" + kBaseFontURL + "markB.woff" + ")", + markCtwourl: "url(" + kBaseFontURL + "bogus-markC.woff" + "), url(" + kBaseFontURL + "markC.woff" + ")", + markDtwourl: "url(" + kBaseFontURL + "bogus-markD.woff" + "), url(" + kBaseFontURL + "markD.woff" + ")", + + /* localfont versions include a bogus local ref followed by a valid url */ + markAlocalfirst: "local(bogus-markA), url(" + kBaseFontURL + "markA.woff" + ")", + markBlocalfirst: "local(bogus-markB), url(" + kBaseFontURL + "markB.woff" + ")", + markClocalfirst: "local(bogus-markC), url(" + kBaseFontURL + "markC.woff" + ")", + markDlocalfirst: "local(bogus-markD), url(" + kBaseFontURL + "markD.woff" + ")", +}; + +function familyName(name, i) { + return "test" + i + "-" + name; +} + +function fontFaceRule(name, fontdata, ft) { + var desc = []; + desc.push("font-family: " + name); + var srckey = fontdata.src + ft; + desc.push("src: " + mapFontURLs[srckey]); + for (var d in fontdata.descriptors) { + desc.push(mapDescriptorNames[d] + ": " + fontdata.descriptors[d]); + } + return "@font-face { " + desc.join(";") + " }"; +} + +function clearRules(sheetIndex) { + var sheet = document.styleSheets[sheetIndex]; + while(sheet.cssRules.length > 0) { + sheet.deleteRule(0); + } +} + +function clearAllRulesAndFonts() { + clearRules(kSheetFonts); + clearRules(kSheetStyles); + document.fonts.clear(); +} + +function addStyleRulesAndText(testdata, i) { + // add style rules for testcontent + var sheet = document.styleSheets[kSheetStyles]; + while(sheet.cssRules.length > 0) { + sheet.deleteRule(0); + } + var rule = []; + var family = familyName(testdata.fonts[0].family, i); + rule.push("#testcontent { font-family: " + family); + if ("style" in testdata) { + for (s in testdata.style) { + rule.push(s + ": " + testdata.style[s]); + } + } + rule.push("}"); + sheet.insertRule(rule.join("; "), 0); + + var content = document.getElementById("testcontent"); + content.innerHTML = testdata.content; + content.offsetHeight; +} + +// work arounds +function getFonts() { + if ("forEach" in document.fonts) { + return document.fonts; + } + return Array.from(document.fonts); +} + +function getSize() { + if ("size" in document.fonts) { + return document.fonts.size; + } + return getFonts().length; +} + +function getReady() { + if (typeof(document.fonts.ready) == "function") { + return document.fonts.ready(); + } + return document.fonts.ready; +} + +function setTimeoutPromise(aDelay) { + return new Promise(function(aResolve, aReject) { + setTimeout(aResolve, aDelay); + }); +} + +function addFontFaceRules(testdata, i, ft) { + var sheet = document.styleSheets[kSheetFonts]; + var createdFonts = []; + testdata.fonts.forEach(function(f) { + var n = sheet.cssRules.length; + var fn = familyName(f.family, i); + sheet.insertRule(fontFaceRule(fn, f, ft), n); + var newfont; + var fonts = getFonts(); + try { + fonts.forEach(function(font) { newfont = font; }); + createdFonts.push({family: fn, data: f, font: newfont}); + } catch (e) { + console.log(e); + } + }); + return createdFonts; +} + +function addDocumentFonts(testdata, i, ft) { + var createdFonts = []; + testdata.fonts.forEach(function(fd) { + var fn = familyName(fd.family, i); + var srckey = fd.src + ft; + var f = new FontFace(fn, mapFontURLs[srckey], fd.descriptors); + document.fonts.add(f); + createdFonts.push({family: fn, data: fd, font: f}); + }); + return createdFonts; +} + +var q = Promise.resolve(); + +function runTests() { + function setupTests() { + setup({explicit_done: true}); + } + + function checkFontsBeforeLoad(name, testdata, fd) { + test(function() { + assert_equals(document.fonts.status, "loaded", "before initializing test, no fonts should be loading - found: " + document.fonts.status); + var size = getSize(); + assert_equals(size, testdata.fonts.length, + "fonts where not added to the font set object"); + var i = 0; + fonts = getFonts(); + fonts.forEach(function(ff) { + assert_equals(ff.status, "unloaded", "added fonts should be in unloaded state"); + }); + }, name + " before load"); + } + + function checkFontsAfterLoad(name, testdata, fd, afterTimeout) { + test(function() { + assert_equals(document.fonts.status, "loaded", "after ready promise resolved, no fonts should be loading"); + var i = 0; + fd.forEach(function(f) { + assert_true(f.font instanceof FontFace, "font needs to be an instance of FontFace object"); + if (f.data.loaded) { + assert_equals(f.font.status, "loaded", "font not loaded - font " + i + " " + f.data.src + " " + + JSON.stringify(f.data.descriptors) + " for content " + testdata.content); + } else { + assert_equals(f.font.status, "unloaded", "font loaded - font " + i + " " + f.data.src + " " + + JSON.stringify(f.data.descriptors) + " for content " + testdata.content); + } + i++; + }); + }, name + " after load" + (afterTimeout ? " and timeout" : "")); + } + + function testFontLoads(testdata, i, name, fd) { + checkFontsBeforeLoad(name, testdata, fd); + addStyleRulesAndText(testdata, i); + + var ready = getReady(); + return ready.then(function() { + checkFontsAfterLoad(name, testdata, fd, false); + }).then(function() { + return setTimeoutPromise(0).then(function() { + checkFontsAfterLoad(name, testdata, fd, true); + }); + }).then(function() { + var ar = getReady(); + return ar.then(function() { + test(function() { + assert_equals(document.fonts.status, "loaded", "after ready promise fulfilled once, fontset should not be loading"); + var fonts = getFonts(); + fonts.forEach(function(f) { + assert_not_equals(f.status, "loading", "after ready promise fulfilled once, no font should be loading"); + }); + }, name + " test done check"); + }); + }).then(function() { + clearAllRulesAndFonts(); + }); + } + + function testUnicodeRangeFontFace(testdata, i, ft) { + var name = "TEST " + i + " " + testdata.test + " (@font-face rules)" + (ft != "" ? " " + ft : ft); + + var fd = addFontFaceRules(testdata, i, ft); + return testFontLoads(testdata, i, name, fd); + } + + function testUnicodeRangeDocumentFonts(testdata, i, ft) { + var name = "TEST " + i + " " + testdata.test + " (document.fonts)" + (ft != "" ? " " + ft : ft); + + var fd = addDocumentFonts(testdata, i, ft); + return testFontLoads(testdata, i, name, fd); + } + + q = q.then(function() { + setupTests(); + }); + + var fontTypes = ["", "twourl", "localfirst"]; + + unicodeRangeTests.forEach(function(testdata, i) { + fontTypes.forEach(function(ft) { + q = q.then(function() { + return testUnicodeRangeFontFace(testdata, i, ft); + }).then(function() { + return testUnicodeRangeDocumentFonts(testdata, i, ft); + }); + }); + }); + + q = q.then(function() { + done(); + }); +} + +if ("fonts" in document) { + runTests(); +} else { + test(function() { + assert_true(true, "CSS Font Loading API is not enabled."); + }, "CSS Font Loading API is not enabled"); +} +</script> +</body> +</html> diff --git a/layout/style/test/test_units_angle.html b/layout/style/test/test_units_angle.html new file mode 100644 index 000000000..9ad800f13 --- /dev/null +++ b/layout/style/test/test_units_angle.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for serialization and equivalence of angle units</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for serialization and equivalence of angle units **/ + +/** + * We test that for each of the following: + * + they reserialize to exactly what is given + * + if a mapping is provided, they compute to the same result as the mapping + */ +var tests = { + "45deg": "50grad", + "150grad": "135deg", + "1rad": null +}; + +var p = document.getElementById("display"); + +for (var test in tests) { + p.setAttribute("style", "-moz-transform: rotate(" + test + ")"); + is(p.style.getPropertyValue("-moz-transform"), "rotate(" + test + ")", + test + " serializes to exactly itself"); + // We can't test any equivalence since we don't have any properties + // with angle values that we compute. (-moz-transform doesn't help.) +/* + var equiv = tests[test]; + if (equiv) { + var cm1 = getComputedStyle(p, "").elevation; + p.style.elevation = equiv; + var cm2 = getComputedStyle(p, "").elevation; + is(cm1, cm2, test + " should compute to the same as " + equiv); + } +*/ +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_units_frequency.html b/layout/style/test/test_units_frequency.html new file mode 100644 index 000000000..bb8984726 --- /dev/null +++ b/layout/style/test/test_units_frequency.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for serialization and equivalence of frequency units</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for serialization and equivalence of frequency units **/ + +/** + * We test that for each of the following: + * + they reserialize to exactly what is given + * + if a mapping is provided, they compute to the same result as the mapping + */ +var tests = { + "7kHz": "7000Hz", + "300Hz": "0.3khz" +}; + +var p = document.getElementById("display"); + +for (var test in tests) { + // We can't test this because we no longer support any properties + // with frequency values. + todo(false, "no tests to run, for now"); + /* + p.setAttribute("style", "pitch: " + test); + is(p.style.getPropertyValue("pitch"), test, + test + " serializes to exactly itself"); + */ + // We can't test any equivalence since we don't have any properties + // with frequency values that we compute. +/* + var equiv = tests[test]; + if (equiv) { + var cm1 = getComputedStyle(p, "").pitch; + p.style.pitch = equiv; + var cm2 = getComputedStyle(p, "").pitch; + is(cm1, cm2, test + " should compute to the same as " + equiv); + } +*/ +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_units_length.html b/layout/style/test/test_units_length.html new file mode 100644 index 000000000..6db3f0c7d --- /dev/null +++ b/layout/style/test/test_units_length.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for serialization and equivalence of length units</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for serialization and equivalence of length units **/ + +/** + * We test that for each of the following: + * + they reserialize to exactly what is given + * + if a mapping is provided, they compute to the same result as the mapping + */ +var tests = { + "1in": "72pt", + "20mm": "2cm", + "2.54cm": "1in", + "36pt": "0.5in", + "4pc": "48pt", + "1em": null, + "3ex": null, + "57px": null, + "5rem": null +}; + +var p = document.getElementById("display"); + +for (var test in tests) { + p.setAttribute("style", "margin-left: " + test); + is(p.style.getPropertyValue("margin-left"), test, + test + " serializes to exactly itself"); + var equiv = tests[test]; + if (equiv) { + var cm1 = getComputedStyle(p, "").marginLeft; + p.style.marginLeft = equiv; + var cm2 = getComputedStyle(p, "").marginLeft; + is(cm1, cm2, test + " should compute to the same as " + equiv); + } +} + +// Bug 393910 +p.setAttribute("style", "margin-left: 0"); +is(p.style.getPropertyValue("margin-left"), "0px", + "0 serializes to 0px"); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_units_time.html b/layout/style/test/test_units_time.html new file mode 100644 index 000000000..e9d3e77bd --- /dev/null +++ b/layout/style/test/test_units_time.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for serialization and equivalence of time units</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for serialization and equivalence of time units **/ + +/** + * We test that for each of the following: + * + they reserialize to exactly what is given + * + if a mapping is provided, they compute to the same result as the mapping + */ +var tests = { + "3s": "3000ms", + "500ms": "0.5s" +}; + +var p = document.getElementById("display"); + +for (var test in tests) { + p.setAttribute("style", "transition-duration: " + test); + is(p.style.getPropertyValue("transition-duration"), test, + test + " serializes to exactly itself"); + var equiv = tests[test]; + if (equiv) { + var cm1 = getComputedStyle(p, "").transitionDuration; + p.style.transitionDuration = equiv; + var cm2 = getComputedStyle(p, "").transitionDuration; + is(cm1, cm2, test + " should compute to the same as " + equiv); + } +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_unprefixing_service.html b/layout/style/test/test_unprefixing_service.html new file mode 100644 index 000000000..c489e2ac0 --- /dev/null +++ b/layout/style/test/test_unprefixing_service.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1107378 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1107378</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="unprefixing_service_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1107378">Mozilla Bug 1107378</a> +<div id="display"> + <iframe id="testIframe"></iframe> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; +SimpleTest.waitForExplicitFinish(); + +/** + * This test checks that unprefixing is enabled for whitelisted domains, and + * that it's disabled for non-whitelisted domains. + * + * We do this using an iframe, in which we load a test file at a test domain, + * and we have the iframe report back to us (using postMessage) about + * whether unprefixing is working. + * + * High-level overview of the process here: + * - First, we tweak prefs to enable unprefixing & enable the test-only + * entries in our unprefixing whitelist. + * - The rest of this test is driven by the "startNextTest()" method. + * This method pops a hostname to test and loads a URL from that host + * in the iframe. + * - We then listen for test-results from the iframe, using the postMessage + * handler in unprefixing_service_utils.js. + * - When the iframe indicates that it's done, we call "startNextTest()" + * again to pop the next host & load *that* in the iframe. + * - When nothing remains to be popped, we're done. + */ + +const IFRAME_TESTFILE = "unprefixing_service_iframe.html"; + +// This function gets invoked when our iframe finishes a given round of testing. +function startNextTest() +{ + // Test the next whitelisted host, if any remain. + if (gWhitelistedHosts.length > 0) { + let host = gWhitelistedHosts.pop(); + info("Verifying that CSS Unprefixing Service is active, " + + "at whitelisted test-host '" + host + "'"); + testHost(host, true); + return; + } + + // Test the next not-whitelisted host, if any remain. + if (gNotWhitelistedHosts.length > 0) { + let host = gNotWhitelistedHosts.pop(); + info("Verifying that CSS Unprefixing Service is inactive, " + + "at non-whitelisted test-host '" + host + "'"); + testHost(host, false); + return; + } + + // Both arrays empty --> we're done. + SimpleTest.finish(); +} + +function begin() +{ + // Before we start loading things in iframes, set up postMessage handler. + registerPostMessageListener(startNextTest); + + // Turn on prefs & start the first test! + SpecialPowers.pushPrefEnv( + { set: [[PREF_UNPREFIXING_SERVICE, true], + [PREF_INCLUDE_TEST_DOMAINS, true], + // Make sure *native* -webkit prefix support is turned off. It's + // not whitelist-restricted, so if we left it enabled, it'd prevent + // us from being able to detect CSSUnprefixingService's domain + // whitelisting in this test. + ["layout.css.prefixes.webkit", false]]}, + startNextTest); +} + +begin(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_unprefixing_service_prefs.html b/layout/style/test/test_unprefixing_service_prefs.html new file mode 100644 index 000000000..329dce2a6 --- /dev/null +++ b/layout/style/test/test_unprefixing_service_prefs.html @@ -0,0 +1,132 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1132743 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1132743</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript;version=1.7" src="unprefixing_service_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1132743">Mozilla Bug 1132743</a> +<div id="display"> + <iframe id="testIframe"></iframe> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> +"use strict"; +SimpleTest.waitForExplicitFinish(); + +/** + * This test checks that our CSS unprefixing prefs are effective. + * + * We do this using an iframe, in which we load a test file at a test domain + * (whose whitelist-status depends on a pref), and we have the iframe report + * back to us (using postMessage) about whether unprefixing is working. + * + * High-level overview of the process here (starting with begin()): + * - First, we ensure that the pref... + * "layout.css.unprefixing-service.include-test-domains" + * ...is *unset* by default. (No point exposing it in about:config). + * - Then, we test that (as a result of this pref being unset) the + * unprefixing service is *inactive* at our test-domain, by default. + * - Then, via a series of calls to "startNextTest()"/"testHost()", we re-test + * the same test-domain with a variety of pref configurations, to ensure + * that unprefixing only happens there when we've preffed on the service + * *and* we've enabled the testing entries in the whiteslist. + */ + +const IFRAME_TESTFILE = "unprefixing_service_iframe.html"; + +// Just test the first host in our known-whitelisted-hosts list. +const WHITELISTED_TEST_HOST = gWhitelistedHosts[0]; + +// Configurations of our prefs to test. +// Each is a 3-entry array, whose entries mean: +// (1) should we enable the CSS Unprefixing Service pref? +// (2) should we enable the "include test domains in whitelist" pref? +// (3) in this pref-configuration, should we expect to see unprefixing active +// on our whitelisted test-domain? +// +// As you can see, the only configuration which should produce unprefixing +// activity is when *both* prefs are enabled. +let gTestConfigs = [ + [false, false, false], + [false, true, false], + [true, false, false], + [true, true, true], +]; + +// Test that a particular configuration of prefs will activate or inactivate +// the CSS unprefixing service, for styles loaded from WHITELISTED_TEST_HOST. +// aTestConfig is described above, in documentation for gTestConfigs. +function testConfig(aTestConfig) +{ + if (aTestConfig.length != 3) { + ok(false, "bug in test; need 3 entries. see gTestConfigs documentation"); + } + + info("Verifying that CSS Unprefixing Service is " + + (aTestConfig[2] ? "active" : "inactive") + + " at test host, with prefs: " + + PREF_UNPREFIXING_SERVICE + "=" + aTestConfig[0] + ", " + + PREF_INCLUDE_TEST_DOMAINS + "=" + aTestConfig[1]); + + SpecialPowers.pushPrefEnv( + { set: + [[PREF_UNPREFIXING_SERVICE, aTestConfig[0]], + [PREF_INCLUDE_TEST_DOMAINS, aTestConfig[1]]] + }, + function() { + testHost(WHITELISTED_TEST_HOST, aTestConfig[2]); + }); +} + +// This function gets invoked when our iframe finishes a given round of testing. +function startNextTest() +{ + if (gTestConfigs.length > 0) { + // Grab the next test-config, and kick off a test for it. + testConfig(gTestConfigs.pop()); + return; + } + + // Array empty --> we're done. + SimpleTest.finish(); +} + +function begin() +{ + // First, check that PREF_INCLUDE_TEST_DOMAINS is unset: + try { + let val = SpecialPowers.getBoolPref(PREF_INCLUDE_TEST_DOMAINS); + ok(false, "The test pref '" + PREF_INCLUDE_TEST_DOMAINS + + "' should be unspecified by default"); + } catch(e) { /* Good, we threw; pref is unset. */ } + + // Before we start loading things in iframes, set up postMessage handler. + registerPostMessageListener(startNextTest); + + // To kick things off, we don't set any prefs; we just test the default state + // (which should have the "include test domains" pref implicitly disabled, & + // hence unprefixing should end up being disabled in our iframe). Subsequent + // tests are kicked off via postMessage-triggered calls to startNextTest(), + // which will tweak prefs and re-test. + info("Verifying that CSS Unprefixing Service is inactive at test host, " + + "with default pref configuration"); + testHost(WHITELISTED_TEST_HOST, false); +} + +// Before we start, make sure *native* -webkit prefix support is turned off. +// It's not whitelist-restricted (and behaves slightly differently), so if we +// left it enabled, it'd prevent us from being able to detect +// CSSUnprefixingService's domain whitelisting in this test. +SpecialPowers.pushPrefEnv({ set: [["layout.css.prefixes.webkit", false]]}, + begin); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_value_cloning.html b/layout/style/test/test_value_cloning.html new file mode 100644 index 000000000..5582b8303 --- /dev/null +++ b/layout/style/test/test_value_cloning.html @@ -0,0 +1,182 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=375363 +--> +<head> + <title>Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset')</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"><iframe id="iframe" src="about:blank"></iframe></p> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for cloning of CSS property values (including 'inherit', 'initial' and 'unset') **/ +var gTestUnset = SpecialPowers.getBoolPref("layout.css.unset-value.enabled"); +var test_queue = []; +var iframe = document.getElementById("iframe"); + +SimpleTest.waitForExplicitFinish(); + +for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + + test_queue.push({ prop: prop, value: "inherit", + inherited_value: info.initial_values[0] }); + test_queue.push({ prop: prop, value: "inherit", + inherited_value: info.other_values[0] }); + test_queue.push({ prop: prop, value: "initial" }); + if (gTestUnset) { + if (info.inherited) { + test_queue.push({ prop: prop, value: "unset", + inherited_value: info.initial_values[0] }); + test_queue.push({ prop: prop, value: "unset", + inherited_value: info.other_values[0] }); + } else { + test_queue.push({ prop: prop, value: "unset" }); + } + } + for (var idx in info.initial_values) { + test_queue.push({ prop: prop, value: info.initial_values[idx] }); + } + for (var idx in info.other_values) { + test_queue.push({ prop: prop, value: info.other_values[idx] }); + } +} + +test_queue.reverse(); + +doTest(); + +function doTest() +{ + var sheet_data = ""; + + for (var idx = 0; idx < test_queue.length; ++idx) { + var current_item = test_queue[idx]; + + var info = gCSSProperties[current_item.prop]; + + sheet_data += "#parent"+idx+", #test"+idx+" { "; + for (var prereq in info.prereqs) { + sheet_data += prereq + ": " + info.prereqs[prereq] + ";"; + } + sheet_data += " }"; + + sheet_data += "#parent"+idx+" { "; + if ("inherited_value" in current_item) { + sheet_data += current_item.prop + ": " + current_item.inherited_value; + } + sheet_data += "}"; + + sheet_data += "#test"+idx+" { "; + sheet_data += current_item.prop + ": " + current_item.value; + sheet_data += "}"; + } + + var sheet_url = "data:text/css," + escape(sheet_data); + + var doc_data = + "<!DOCTYPE HTML>\n" + + "<link rel='stylesheet' type='text/css' href='" + sheet_url + "'>\n" + + "<link rel='stylesheet' type='text/css' href='" + sheet_url + "'>\n" + + "<body>\n"; + + + for (var idx = 0; idx < test_queue.length; ++idx) { + var current_item = test_queue[idx]; + + if ("inherited_value" in current_item) { + doc_data += "<span id='parent"+idx+"'>"; + } + doc_data += "<span id='test"+idx+"'></span>"; + if ("inherited_value" in current_item) { + doc_data += "</span>"; + } + } + + var doc_url = "data:text/html," + escape(doc_data); + iframe.onload = iframe_loaded; + iframe.src = doc_url; +} + +function iframe_loaded(event) +{ + if (event.target != iframe) + return; + + var start_ser = []; + var start_compute = []; + var test_cs = []; + var ifdoc = iframe.contentDocument; + + for (var idx = 0; idx < test_queue.length; ++idx) { + var current_item = test_queue[idx]; + var info = gCSSProperties[current_item.prop]; + + var test = ifdoc.getElementById("test" + idx); + var cur_cs = iframe.contentWindow.getComputedStyle(test, ""); + test_cs.push(cur_cs); + var cur_ser = ifdoc.styleSheets[0].cssRules[3*idx+2].style.getPropertyValue(current_item.prop); + if (cur_ser == "") { + isnot(cur_ser, "", + "serialization should be nonempty for " + + current_item.prop + ": " + current_item.value); + } + start_ser.push(cur_ser); + + var cur_compute = get_computed_value(cur_cs, current_item.prop); + if (cur_compute == "") { + isnot(cur_compute, "", + "computed value should be nonempty for " + + current_item.prop + ": " + current_item.value); + } + start_compute.push(cur_compute); + } + + // In case the above access didn't force a clone already (though it + // currently does), clone the second style sheet's inner and then + // remove the first. + ifdoc.styleSheets[1].insertRule("#nonexistent { color: red }", 0); + var firstlink = ifdoc.getElementsByTagName("link")[0]; + firstlink.parentNode.removeChild(firstlink); + + // Force a flush + ifdoc.body.style.display="none"; + var ow = ifdoc.body.offsetWidth; + ifdoc.body.style.display=""; + + for (var idx = 0; idx < test_queue.length; ++idx) { + var current_item = test_queue[idx]; + var info = gCSSProperties[current_item.prop]; + + var end_ser = + ifdoc.styleSheets[0].cssRules[3*idx+3].style.getPropertyValue(current_item.prop); + is(end_ser, start_ser[idx], + "serialization should match when cloning " + + current_item.prop + ": " + current_item.value); + + var end_compute = get_computed_value(test_cs[idx], current_item.prop); + // Output computed values only when the test failed. + // Computed values may be very long. + if (end_compute == start_compute[idx]) { + ok(true, + "computed values should match when cloning " + + current_item.prop + ": " + current_item.value); + } else { + is(end_compute, start_compute[idx], + "computed values should match when cloning " + + current_item.prop + ": " + current_item.value); + } + } + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_value_computation.html b/layout/style/test/test_value_computation.html new file mode 100644 index 000000000..024b26210 --- /dev/null +++ b/layout/style/test/test_value_computation.html @@ -0,0 +1,249 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for computation of values in property database</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <style type="text/css" id="stylesheet"></style> + <style type="text/css"> + /* For 'width', 'height', etc., need a constant size container. */ + #display { width: 500px; height: 200px } + </style> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript"> + var numAssertions = 9; + + // these are from the additional margin-inline-{start,end} tests + numAssertions += 2; + + SimpleTest.expectAssertions(numAssertions); + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestLongerTimeout(2); + + var load_count = 0; + function load_done() { + if (++load_count == 3) + run_tests(); + } + </script> +</head> +<body> +<p id="display"><span><span id="elementf"></span></span> +<iframe id="unstyledn" src="unstyled.xml" height="10" width="10" onload="load_done()"></iframe> +<iframe id="unstyledf" src="unstyled-frame.xml" height="10" width="10" onload="load_done()"></iframe> +</p> +<div id="content" style="display: none"> + +<div><span id="elementn"></span></div> + + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for computation of values in property database **/ + +var gBadComputed = { + // These values are treated as auto. + "page-break-after": [ "avoid" ], + "page-break-before": [ "avoid" ], + + // This is the only SVG-length property (i.e., length allowing + // unitless lengths) whose initial value is zero. + "stroke-dashoffset": [ "0", "-moz-objectValue" ], +}; + +var gBadComputedNoFrame = { + // These are probably bogus tests... + "-moz-margin-end": [ "0%", "calc(0% + 0px)" ], + "-moz-margin-start": [ "0%", "calc(0% + 0px)" ], + "-moz-padding-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "-moz-padding-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "margin": [ "0% 0px 0em 0pt" ], + "margin-block-end": [ "0%", "calc(0% + 0px)" ], + "margin-block-start": [ "0%", "calc(0% + 0px)" ], + "margin-bottom": [ "0%", "calc(0% + 0px)" ], + "margin-inline-end": [ "0%", "calc(0% + 0px)" ], + "margin-inline-start": [ "0%", "calc(0% + 0px)" ], + "margin-left": [ "0%", "calc(0% + 0px)" ], + "margin-right": [ "0%", "calc(0% + 0px)" ], + "margin-top": [ "0%", "calc(0% + 0px)" ], + "padding": [ "0% 0px 0em 0pt", "calc(0px) calc(0em) calc(-2px) calc(-1%)" ], + "padding-block-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "padding-block-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "padding-bottom": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "padding-inline-end": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "padding-inline-start": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "padding-left": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "padding-right": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], + "padding-top": [ "0%", "calc(0% + 0px)", "calc(-1%)" ], +}; + +function xfail_value(property, value, is_initial, has_frame) { + if ((property in gBadComputed) && + gBadComputed[property].indexOf(value) != -1) + return true; + + if (!has_frame && (property in gBadComputedNoFrame) && + gBadComputedNoFrame[property].indexOf(value) != -1) + return true; + + return false; +} + +var gSwapInitialWhenHaveFrame = { + // When there's a frame, '-moz-available' works out to the same as + // 'auto' given the prerequisites of only 'display: block'. + "width": [ "-moz-available" ], +}; + +function swap_when_frame(property, value) { + return (property in gSwapInitialWhenHaveFrame) && + gSwapInitialWhenHaveFrame[property].indexOf(value) != -1; +} + +var gElementN = document.getElementById("elementn"); +var gElementF = document.getElementById("elementf"); +var gStyleSheet = document.getElementById("stylesheet").sheet; +var gRule1 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)]; +var gRule2 = gStyleSheet.cssRules[gStyleSheet.insertRule("#elementn, #elementf {}", gStyleSheet.cssRules.length)]; + +var gInitialValuesN; +var gInitialValuesF; +var gInitialPrereqsRuleN; +var gInitialPrereqsRuleF; + +function setup_initial_values(id, ivalprop, prereqprop) { + var iframe = document.getElementById(id); + window[ivalprop] = iframe.contentWindow.getComputedStyle( + iframe.contentDocument.documentElement.firstChild, ""); + var sheet = iframe.contentDocument.styleSheets[0]; + // For 'width', 'height', etc., need a constant size container. + sheet.insertRule(":root { height: 200px; width: 500px }", sheet.cssRules.length); + + window[prereqprop] = sheet.cssRules[sheet.insertRule(":root > * {}", sheet.cssRules.length)]; +} + +function test_value(property, val, is_initial) +{ + var info = gCSSProperties[property]; + + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + gRule1.style.setProperty(prereq, prereqs[prereq], ""); + gInitialPrereqsRuleN.style.setProperty(prereq, prereqs[prereq], ""); + gInitialPrereqsRuleF.style.setProperty(prereq, prereqs[prereq], ""); + } + } + if (info.inherited && is_initial) { + gElementN.parentNode.style.setProperty(property, info.other_values[0], ""); + gElementF.parentNode.style.setProperty(property, info.other_values[0], ""); + } + + var initial_computed_n = get_computed_value(gInitialValuesN, property); + var initial_computed_f = get_computed_value(gInitialValuesF, property); + if (is_initial) { + gRule1.style.setProperty(property, info.other_values[0], ""); + var other_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property); + var other_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property); + isnot(other_computed_n, initial_computed_n, + "should be testing with values that compute to different things " + + "for '" + property + "'"); + isnot(other_computed_f, initial_computed_f, + "should be testing with values that compute to different things " + + "for '" + property + "'"); + } + // It's important for values that are supposed to compute to the + // initial value (given the current design of nsRuleNode) that we're + // modifying the most specific rule that matches the element, and that + // we've already requested style while that rule was empty. This + // means we'll have a cached aStartStruct from the parent in the rule + // tree (caching the "other" value), so we'll make sure we don't get + // the initial value from the luck of default-initialization. + // This means that it's important that we set the prereqs on + // gRule1.style rather than on gElement.style. + gRule2.style.setProperty(property, val, ""); + var val_computed_n = get_computed_value(getComputedStyle(gElementN, ""), property); + var val_computed_f = get_computed_value(getComputedStyle(gElementF, ""), property); + isnot(val_computed_n, "", + "should not get empty value for '" + property + ":" + val + "'"); + isnot(val_computed_f, "", + "should not get empty value for '" + property + ":" + val + "'"); + if (is_initial) { + (xfail_value(property, val, is_initial, false) ? todo_is : is)( + val_computed_n, initial_computed_n, + "should get initial value for '" + property + ":" + val + "'"); + (xfail_value(property, val, is_initial, true) ? todo_is : is)( + val_computed_f, initial_computed_f, + "should get initial value for '" + property + ":" + val + "'"); + } else { + (xfail_value(property, val, is_initial, false) ? todo_isnot : isnot)( + val_computed_n, initial_computed_n, + "should not get initial value for '" + property + ":" + val + "' on elementn."); + var swap = swap_when_frame(property, val); + (xfail_value(property, val, is_initial, true) ? todo_isnot : (swap ? is : isnot))( + val_computed_f, initial_computed_f, + "should " + (swap ? "" : "not ") + + "get initial value for '" + property + ":" + val + "' on elementf."); + } + if (is_initial) + gRule1.style.removeProperty(property); + gRule2.style.removeProperty(property); + + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + gRule1.style.removeProperty(prereq); + gInitialPrereqsRuleN.style.removeProperty(prereq); + gInitialPrereqsRuleF.style.removeProperty(prereq); + } + } + if (info.inherited && is_initial) { + gElementN.parentNode.style.removeProperty(property); + gElementF.parentNode.style.removeProperty(property); + } + + // FIXME: Something (maybe with the -moz-binding values) causes + // gElementF's frame to get lost. Force it to get recreated after + // each property. + gElementF.parentNode.style.display = "none"; + get_computed_value(getComputedStyle(gElementF, ""), "width"); + gElementF.parentNode.style.display = ""; + get_computed_value(getComputedStyle(gElementF, ""), "width"); +} + +function test_property(prop) { + var info = gCSSProperties[prop]; + for (var idx in info.initial_values) + test_value(prop, info.initial_values[idx], true); + for (var idx in info.other_values) + test_value(prop, info.other_values[idx], false); +} + +function run_tests() { + setup_initial_values("unstyledn", "gInitialValuesN", "gInitialPrereqsRuleN"); + setup_initial_values("unstyledf", "gInitialValuesF", "gInitialPrereqsRuleF"); + var props = []; + for (var prop in gCSSProperties) + props.push(prop); + props = props.reverse(); + function do_one() { + if (props.length == 0) { + SimpleTest.finish(); + return; + } + test_property(props.pop()); + SimpleTest.executeSoon(do_one); + } + SimpleTest.executeSoon(do_one); +} + +load_done(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_value_storage.html b/layout/style/test/test_value_storage.html new file mode 100644 index 000000000..5e7fa6b69 --- /dev/null +++ b/layout/style/test/test_value_storage.html @@ -0,0 +1,352 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for parsing, storage, and serialization of CSS values</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css" id="prereqsheet"> + #testnode {} + </style> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +<div id="testnode"></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for parsing, storage, and serialization of CSS values **/ + +/* + * The idempotence tests here deserve a little bit of explanation. What + * we're testing here are the following operations: + * parse: string -> CSS rule + * serialize: CSS rule -> string (normalization 1) + * (this actually has two variants that go through partly different + * codepaths, which we exercise with getPropertyValue and cssText) + * compute: CSS rule -> computed style + * cserialize: computed style -> string (normalization 2) + * + * Both serialize and cserialize do some normalization, so we can't test + * for pure round-tripping, and we also can't compare their output since + * they could normalize differently. (We might at some point in the + * future want to guarantee that any output of cserialize is + * untouched by going through parse+serialize, though.) + * + * So we test idempotence of parse + serialize by running the whole + * operation twice. Likewise for parse + compute + cserialize. + * + * Slightly more interestingly, we test that serialize + parse is the + * identity transform by comparing the output of parse + compute + + * cserialize to the output of parse + serialize + parse + compute + + * cserialize. + */ + +var gSystemFont = { + "caption": true, + "icon": true, + "menu": true, + "message-box": true, + "small-caption": true, + "status-bar": true, + "-moz-window": true, + "-moz-document": true, + "-moz-desktop": true, + "-moz-info": true, + "-moz-dialog": true, + "-moz-button": true, + "-moz-pull-down-menu": true, + "-moz-list": true, + "-moz-field": true, + "-moz-workspace": true, +}; + +var gBadCompute = { + // output wrapped around to positive, in exponential notation + "-moz-box-ordinal-group": [ "-1", "-1000" ], +}; + +function xfail_compute(property, value) +{ + if (property in gBadCompute && + gBadCompute[property].indexOf(value) != -1) + return true; + + return false; +} + +// constructed to map longhands ==> list of containing shorthands +var gPropertyShorthands = {}; + +var gElement = document.getElementById("testnode"); +var gDeclaration = gElement.style; +var gComputedStyle = window.getComputedStyle(gElement, ""); + +var gPrereqDeclaration = + document.getElementById("prereqsheet").sheet.cssRules[0].style; + +// On Android, avoid most 'TEST-PASS' logging by overriding +// SimpleTest.is/isnot, to improve performance +if (navigator.appVersion.indexOf("Android") >= 0) { + is = function is(a, b, name) + { + var pass = Object.is(a, b); + if (!pass) + SimpleTest.is(a, b, name); + } + + isnot = function isnot(a, b, name) + { + var pass = !Object.is(a, b); + if (!pass) + SimpleTest.isnot(a, b, name); + } +} + +// Returns true if propA and propB are equivalent, considering aliasing. +// (i.e. if one is an alias of the other, or if they're both aliases of +// the same 3rd property) +function are_properties_aliased(propA, propB) +{ + // If either property is an alias, replace it with the property it aliases. + if ("alias_for" in gCSSProperties[propA]) { + propA = gCSSProperties[propA].alias_for; + } + if ("alias_for" in gCSSProperties[propB]) { + propB = gCSSProperties[propB].alias_for; + } + + return propA == propB; +} + +function test_property(property) +{ + var info = gCSSProperties[property]; + + // can all properties be removed from the style? + function test_remove_all_properties(property, value) { + var i, p = []; + for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]); + for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]); + var errstr = "when setting property " + property + " to " + value; + is(gDeclaration.length, 0, "unremovable properties " + errstr); + is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr); + } + + function test_other_shorthands_empty(value, subprop) { + if (!(subprop in gPropertyShorthands)) return; + var shorthands = gPropertyShorthands[subprop]; + for (idx in shorthands) { + var sh = shorthands[idx]; + if (are_properties_aliased(sh, property)) { + continue; + } + is(gDeclaration.getPropertyValue(sh), "", + "setting '" + value + "' on '" + property + "' (for shorthand '" + sh + "')"); + } + } + + function test_value(value, resolved_value) { + var value_has_variable_reference = resolved_value != null; + + gDeclaration.setProperty(property, value, ""); + + var idx; + + var step1val = gDeclaration.getPropertyValue(property); + var step1vals = []; + var step1ser = gDeclaration.cssText; + if ("subproperties" in info) + for (idx in info.subproperties) + step1vals.push(gDeclaration.getPropertyValue(info.subproperties[idx])); + var step1comp; + var step1comps = []; + if (info.type != CSS_TYPE_TRUE_SHORTHAND) + step1comp = gComputedStyle.getPropertyValue(property); + if ("subproperties" in info) + for (idx in info.subproperties) + step1comps.push(gComputedStyle.getPropertyValue(info.subproperties[idx])); + + SimpleTest.isnot(step1val, "", "setting '" + value + "' on '" + property + "'"); + if ("subproperties" in info) + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + if (value_has_variable_reference && + (!info.alias_for || info.type == CSS_TYPE_TRUE_SHORTHAND)) { + is(gDeclaration.getPropertyValue(subprop), "", + "setting '" + value + "' on '" + property + "' (for '" + subprop + "')"); + test_other_shorthands_empty(value, subprop); + } else { + isnot(gDeclaration.getPropertyValue(subprop), "", + "setting '" + value + "' on '" + property + "' (for '" + subprop + "')"); + } + } + + // We don't care particularly about the whitespace or the placement of + // semicolons, but for simplicity we'll test the current behavior. + var expected_serialization = ""; + if (step1val != "") { + if ("alias_for" in info) { + expected_serialization = info.alias_for + ": " + step1val + ";"; + } else { + expected_serialization = property + ": " + step1val + ";"; + } + } + is(step1ser, expected_serialization, + "serialization should match property value"); + + gDeclaration.removeProperty(property); + gDeclaration.setProperty(property, step1val, ""); + + is(gDeclaration.getPropertyValue(property), step1val, + "parse+serialize should be idempotent for '" + + property + ": " + value + "'"); + if (info.type != CSS_TYPE_TRUE_SHORTHAND) { + is(gComputedStyle.getPropertyValue(property), step1comp, + "serialize+parse should be identity transform for '" + + property + ": " + value + "'"); + } + + if ("subproperties" in info && + // Using setProperty over subproperties is not sufficient for + // system fonts, since the shorthand does more than its parts. + (property != "font" || !(value in gSystemFont)) && + // Likewise for special compatibility values of transform + (property != "-moz-transform" || !value.match(/^matrix.*(px|em|%)/)) && + !value_has_variable_reference) { + gDeclaration.removeProperty(property); + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + gDeclaration.setProperty(subprop, step1vals[idx], ""); + } + + // Now that all the subprops are set, check their values. Note that we + // need this in a separate loop, in case parts of the shorthand affect + // the computed values of other parts. + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + is(gComputedStyle.getPropertyValue(subprop), step1comps[idx], + "serialize(" + subprop + ")+parse should be the identity " + + "transform for '" + property + ": " + value + "'"); + } + is(gDeclaration.getPropertyValue(property), step1val, + "parse+split+serialize should be idempotent for '" + + property + ": " + value + "'"); + } + + if (info.type != CSS_TYPE_TRUE_SHORTHAND && + property != "mask") { + gDeclaration.removeProperty(property); + gDeclaration.setProperty(property, step1comp, ""); + var func = xfail_compute(property, value) ? todo_is : is; + func(gComputedStyle.getPropertyValue(property), step1comp, + "parse+compute+serialize should be idempotent for '" + + property + ": " + value + "'"); + } + if ("subproperties" in info) { + gDeclaration.removeProperty(property); + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + gDeclaration.setProperty(subprop, step1comps[idx], ""); + } + + // Now that all the subprops are set, check their values. Note that we + // need this in a separate loop, in case parts of the shorthand affect + // the computed values of other parts. + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + is(gComputedStyle.getPropertyValue(subprop), step1comps[idx], + "parse+compute+serialize(" + subprop + ") should be idempotent for '" + + property + ": " + value + "'"); + } + } + + // sanity check shorthands to make sure disabled props aren't exposed + if (info.type != CSS_TYPE_LONGHAND) { + gDeclaration.setProperty(property, value, ""); + test_remove_all_properties(property, value); + } + + gDeclaration.removeProperty(property); + } + + function test_value_without_variable(value) { + test_value(value, null); + } + + function test_value_with_variable(value) { + gPrereqDeclaration.setProperty("--a", value, ""); + test_value("var(--a)", value); + gPrereqDeclaration.removeProperty("--a"); + } + + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + gPrereqDeclaration.setProperty(prereq, prereqs[prereq], ""); + } + } + + var idx; + for (idx in info.initial_values) { + test_value_without_variable(info.initial_values[idx]); + test_value_with_variable(info.initial_values[idx]); + } + for (idx in info.other_values) { + test_value_without_variable(info.other_values[idx]); + test_value_with_variable(info.other_values[idx]); + } + + if ("prerequisites" in info) { + for (var prereq in info.prerequisites) { + gPrereqDeclaration.removeProperty(prereq); + } + } + +} + +function runTest() { + // To avoid triggering the slow script dialog, we have to test one + // property at a time. + ok(SpecialPowers.getBoolPref("layout.css.variables.enabled"), "pref not set #1"); + var props = []; + for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if ("subproperties" in info) { + for (var idx in info.subproperties) { + var subprop = info.subproperties[idx]; + if (!(subprop in gPropertyShorthands)) { + gPropertyShorthands[subprop] = []; + } + gPropertyShorthands[subprop].push(prop); + } + } + props.push(prop); + } + props = props.reverse(); + function do_one() { + if (props.length == 0) { + SimpleTest.finish(); + return; + } + test_property(props.pop()); + SimpleTest.executeSoon(do_one); + } + SimpleTest.executeSoon(do_one); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(7); + +SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] }, + runTest); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_variable_serialization_computed.html b/layout/style/test/test_variable_serialization_computed.html new file mode 100644 index 000000000..c93ae8938 --- /dev/null +++ b/layout/style/test/test_variable_serialization_computed.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<title>Test serialization of computed CSS variable values</title> +<script src="/MochiKit/MochiKit.js"></script> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css"> + +<div> + <span></span> +</div> + +<script> +// Each entry is an entire declaration followed by the property to check and +// its expected computed value. +var values = [ + ["", "--z", "an-inherited-value"], + ["--a: ", "--a", " "], + ["--a: initial", "--a", ""], + ["--z: initial", "--z", ""], + ["--a: inherit", "--a", ""], + ["--z: inherit", "--z", "an-inherited-value"], + ["--a: unset", "--a", ""], + ["--z: unset", "--z", "an-inherited-value"], + ["--a: 1px", "--a", " 1px"], + ["--a: var(--a)", "--a", ""], + ["--a: var(--b)", "--a", ""], + ["--a: var(--b); --b: 1px", "--a", " 1px"], + ["--a: var(--b, 1px)", "--a", " 1px"], + ["--a: var(--a, 1px)", "--a", ""], + ["--a: something 3px url(whereever) calc(var(--a) + 1px)", "--a", ""], + ["--a: something 3px url(whereever) calc(var(--b,1em) + 1px)", "--a", " something 3px url(whereever) calc(1em + 1px)"], + ["--a: var(--b, var(--c, var(--d, Black)))", "--a", " Black"], + ["--a: a var(--b) c; --b:b", "--a", " a b c"], + ["--a: a var(--b,b var(--c) d) e; --c:c", "--a", " a b c d e"], + ["--a: var(--b)red; --b:orange;", "--a", " orange/**/red"], + ["--a: var(--b)var(--c); --b:orange; --c:red;", "--a", " orange/**/red"], + ["--a: var(--b)var(--c,red); --b:orange;", "--a", " orange/**/red"], + ["--a: var(--b,orange)var(--c); --c:red;", "--a", " orange/**/red"], + ["--a: var(--b)-; --b:-;", "--a", " -/**/-"], + ["--a: var(--b)--; --b:-;", "--a", " -/**/--"], + ["--a: var(--b)--x; --b:-;", "--a", " -/**/--x"], + ["--a: var(--b)var(--c); --b:-; --c:-;", "--a", " -/**/-"], + ["--a: var(--b)var(--c); --b:--; --c:-;", "--a", " --/**/-"], + ["--a: var(--b)var(--c); --b:--x; --c:-;", "--a", " --x/**/-"], + ["counter-reset: var(--a)red; --a:orange;", "counter-reset", "orange 0 red 0"], + ["--a: var(--b)var(--c); --c:[c]; --b:('ab", "--a", " ('ab')[c]"], + ["--a: '", "--a", " ''"], + ["--a: '\\", "--a", " ''"], + ["--a: \\", "--a", " \\\ufffd"], + ["--a: \"", "--a", " \"\""], + ["--a: \"\\", "--a", " \"\""], + ["--a: /* abc ", "--a", " /* abc */"], + ["--a: /* abc *", "--a", " /* abc */"], + ["--a: url(http://example.org/", "--a", " url(http://example.org/)"], + ["--a: url(http://example.org/\\", "--a", " url(http://example.org/\\\ufffd)"], + ["--a: url('http://example.org/", "--a", " url('http://example.org/')"], + ["--a: url('http://example.org/\\", "--a", " url('http://example.org/')"], + ["--a: url(\"http://example.org/", "--a", " url(\"http://example.org/\")"], + ["--a: url(\"http://example.org/\\", "--a", " url(\"http://example.org/\")"] +]; + +function runTest() { + var div = document.querySelector("div"); + var span = document.querySelector("span"); + + div.setAttribute("style", "--z:an-inherited-value"); + + values.forEach(function(entry, i) { + var declaration = entry[0]; + var property = entry[1]; + var expected = entry[2]; + span.setAttribute("style", declaration); + var cs = getComputedStyle(span, ""); + is(cs.getPropertyValue(property), expected, "subtest #" + i); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] }, + runTest); +</script> diff --git a/layout/style/test/test_variable_serialization_specified.html b/layout/style/test/test_variable_serialization_specified.html new file mode 100644 index 000000000..62321eaaf --- /dev/null +++ b/layout/style/test/test_variable_serialization_specified.html @@ -0,0 +1,117 @@ +<!DOCTYPE html> +<title>Test serialization of specified CSS variable values</title> +<script src="/MochiKit/MochiKit.js"></script> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css"> + +<style id=style1>#test { }</style> +<style id=style2></style> + +<script> +// Values that should be serialized back to the same string. +var values_with_unchanged_specified_value_serialization = [ + "var(--a)", + "var(--a)", + "var(--a) ", + "var( --a ) ", + "var(--a, )", + "var(--a,/**/a)", + "1px var(--a)", + "var(--a) 1px", + "something 3px url(whereever) calc(var(--a) + 1px)", + "var(--a)", + "var(--a)var(--b)", + "var(--a, var(--b, var(--c, black)))", + "var(--a) <!--", + "--> var(--a)", + "{ [ var(--a) ] }", + "[;] var(--a)", + "var(--a,(;))", + "VAR(--a)", + "var(--0)", + "var(--\\30)", + "var(--\\d800)", + "var(--\\ffffff)", +]; + +// Values that serialize differently, due to additional implied closing +// characters at EOF. +var values_with_changed_specified_value_serialization = [ + ["var(--a", "var(--a)"], + ["var(--a , ", "var(--a , )"], + ["var(--a, ", "var(--a, )"], + ["var(--a, var(--b", "var(--a, var(--b))"], + ["var(--a /* unclosed comment", "var(--a /* unclosed comment*/)"], + ["var(--a /* unclosed comment *", "var(--a /* unclosed comment */)"], + ["[{(((var(--a", "[{(((var(--a))))}]"], + ["var(--a, \"unclosed string", "var(--a, \"unclosed string\")"], + ["var(--a, 'unclosed string", "var(--a, 'unclosed string')"], + ["var(--a) \"unclosed string\\", "var(--a) \"unclosed string\""], + ["var(--a) 'unclosed string\\", "var(--a) 'unclosed string'"], + ["var(--a) \\", "var(--a) \\\ufffd"], + ["var(--a) url(unclosedurl", "var(--a) url(unclosedurl)"], + ["var(--a) url('unclosedurl", "var(--a) url('unclosedurl')"], + ["var(--a) url(\"unclosedurl", "var(--a) url(\"unclosedurl\")"], + ["var(--a) url(unclosedurl\\", "var(--a) url(unclosedurl\\\ufffd)"], + ["var(--a) url('unclosedurl\\", "var(--a) url('unclosedurl')"], + ["var(--a) url(\"unclosedurl\\", "var(--a) url(\"unclosedurl\")"], +]; + +var style1 = document.getElementById("style1"); +var style2 = document.getElementById("style2"); + +var decl = style1.sheet.cssRules[0].style; + +function test_specified_value_serialization(value, expected) { + // Test setting value on a custom property with setProperty. + decl.setProperty("--test", value, ""); + is(decl.getPropertyValue("--test"), expected, + "value with identical serialization set on custom property with setProperty"); + + // Test setting value on a custom property via style sheet parsing. + style2.textContent = "#test { --test:" + value; + is(style2.sheet.cssRules[0].style.getPropertyValue("--test"), expected, + "value with identical serialization set on custom property via parsing"); + + // Test setting value on a non-custom longhand property with setProperty. + decl.setProperty("color", value, ""); + is(decl.getPropertyValue("color"), expected, + "value with identical serialization set on non-custom longhand property with setProperty"); + + // Test setting value on a non-custom longhand property via style sheet parsing. + style2.textContent = "#test { color:" + value; + is(style2.sheet.cssRules[0].style.getPropertyValue("color"), expected, + "value with identical serialization set on non-custom longhand property via parsing"); + + // Test setting value on a non-custom shorthand property with setProperty. + decl.setProperty("margin", value, ""); + is(decl.getPropertyValue("margin"), expected, + "value with identical serialization set on non-custom shorthand property with setProperty"); + + // Test setting value on a non-custom shorthand property via style sheet parsing. + style2.textContent = "#test { margin:" + value; + is(style2.sheet.cssRules[0].style.getPropertyValue("margin"), expected, + "value with identical serialization set on non-custom shorthand property via parsing"); + + // Clean up. + decl.removeProperty("--test"); + decl.removeProperty("color"); + decl.removeProperty("margin"); +} + +function runTest() { + values_with_unchanged_specified_value_serialization.forEach(function(value) { + test_specified_value_serialization(value, value); + }); + + values_with_changed_specified_value_serialization.forEach(function(pair) { + test_specified_value_serialization(pair[0], pair[1]); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true]] }, + runTest); +</script> diff --git a/layout/style/test/test_variables.html b/layout/style/test/test_variables.html new file mode 100644 index 000000000..a1dd341a7 --- /dev/null +++ b/layout/style/test/test_variables.html @@ -0,0 +1,125 @@ +<!DOCTYPE type> +<title>Assorted CSS variable tests</title> +<script src="/MochiKit/MochiKit.js"></script> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css" type="text/css"> + +<style id="test1"> +</style> + +<style id="test2"> +</style> + +<style id="test3"> +</style> + +<style id="test4"> +</style> + +<div id="t4"></div> + +<style id="test5"> +</style> + +<div id="t5"></div> + +<style id="test6"> +</style> + +<style id="test7"> +</style> + +<script> +var tests = [ + function() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=773296#c121 + var test1 = document.getElementById("test1"); + test1.textContent = "p { --a:123!important; }"; + var declaration = test1.sheet.cssRules[0].style; + declaration.cssText; + declaration.setProperty("color", "black"); + is(declaration.getPropertyValue("--a"), "123"); + }, + + function() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=773296#c121 + var test2 = document.getElementById("test2"); + test2.textContent = "p { --a: a !important; }"; + var declaration = test2.sheet.cssRules[0].style; + is(declaration.getPropertyPriority("--a"), "important"); + }, + + function() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=955913 + var test3 = document.getElementById("test3"); + test3.textContent = "p { border-left-style: inset; padding: 1px; --decoration: line-through; }"; + var declaration = test3.sheet.cssRules[0].style; + is(declaration[declaration.length - 1], "--decoration"); + }, + + function() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=959973 + var test4 = document.getElementById("test4"); + test4.textContent = "#t4 { background-image: var(--a); }"; + + var element = document.getElementById("t4"); + var path = window.location.pathname; + var currentDir = path.substring(0, path.lastIndexOf('/')); + var imageURL = "http://mochi.test:8888" + currentDir + "/image.png"; + + is(window.getComputedStyle(element).getPropertyValue("background-image"), "url(\"" + imageURL +"\")"); + }, + + function() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=1043713 + var test5 = document.getElementById("test5"); + test5.textContent = "#t5 { --SomeVariableName: a; }"; + + var declaration = test5.sheet.cssRules[0].style; + is(declaration.item(0), "--SomeVariableName", "custom property name returned by item() on style declaration"); + is(declaration[0], "--SomeVariableName", "custom property name returned by indexed getter on style declaration"); + + var element = document.getElementById("t5"); + var cs = window.getComputedStyle(element); + + is(cs.item(cs.length - 1), "--SomeVariableName", "custom property name returned by item() on computed style"); + is(cs[cs.length - 1], "--SomeVariableName", "custom property name returned by indexed getter on style declaration"); + }, + + function() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=969756 + var test6 = document.getElementById("test6"); + test6.textContent = "p { font: var(--var6) hangul mongolian; font-size-adjust: none; }"; + var declaration = test6.sheet.cssRules[0].style; + test6.style.color = "white"; + is(declaration.getPropertyValue("-x-system-font"), " var(--var6) hangul mongolian"); + }, + + function() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=1154356 + var test7 = document.getElementById("test7"); + test7.textContent = "p { --weird\\;name: green; }"; + is(test7.sheet.cssRules[0].style.cssText, "--weird\\;name: green;"); + test7.textContent = "p { --0: green; }"; + is(test7.sheet.cssRules[0].style.cssText, "--0: green;"); + }, +]; + +function prepareTest() { + // Load an external style sheet for test 4. + var e = document.createElement("link"); + e.addEventListener("load", runTest); + e.setAttribute("rel", "stylesheet"); + e.setAttribute("href", "support/external-variable-url.css"); + document.head.appendChild(e); +} + +function runTest() { + tests.forEach(function(fn) { fn(); }); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ set: [["layout.css.variables.enabled", true ]] }, + prepareTest); +</script> diff --git a/layout/style/test/test_video_object_fit.html b/layout/style/test/test_video_object_fit.html new file mode 100644 index 000000000..d19a6750c --- /dev/null +++ b/layout/style/test/test_video_object_fit.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1065766 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1065766</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1065766">Mozilla Bug 1065766</a> +<div id="content" style="display: none"> + <video id="myVideo"></video> +</div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +/** + * Test for Bug 1065766 + * + * This test verifies that <video> has 'object-fit:contain' by default, set via + * a UA stylesheet. (This is different from the property's initial value, which + * is "fill".) + * + * Spec reference: + * https://html.spec.whatwg.org/multipage/rendering.html#video-object-fit + */ + +function checkStyle(elem, expectedVal, message) { + is(window.getComputedStyle(elem, "").objectFit, expectedVal, message); +} + +function main() { + const videoElem = document.getElementById("myVideo"); + + checkStyle(videoElem, "contain", + "<video> should have 'object-fit:contain' by default"); + + // Make sure we can override this behavior (i.e. that the UA stylesheet + // doesn't use "!important" to make this style mandatory): + videoElem.style.objectFit = "cover"; + checkStyle(videoElem, "cover", + "<video> should honor 'object-fit:cover' in inline style"); +} + +main(); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_viewport_units.html b/layout/style/test/test_viewport_units.html new file mode 100644 index 000000000..d1d35b964 --- /dev/null +++ b/layout/style/test/test_viewport_units.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=804970 +--> +<head> + <title>Test for dynamic changes to CSS 'vh', 'vw', 'vmin', and 'vmax' units</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=804970">Mozilla Bug 804970</a> +<iframe id="iframe" src="viewport_units_iframe.html"></iframe> +<pre id="test"> +<script type="application/javascript"> + +/** Test for CSS vh/vw/vmin/vmax units **/ + +function px_to_num(str) +{ + return Number(String(str).match(/^([\d.]+)px$/)[1]); +} + +function width(elt) +{ + return px_to_num(elt.ownerDocument.defaultView.getComputedStyle(elt, "").width); +} + +SimpleTest.waitForExplicitFinish(); + +function run() { + var iframe = document.getElementById("iframe"); + var idoc = iframe.contentDocument; + var vh = idoc.getElementById("vh"); + var vw = idoc.getElementById("vw"); + var vmin = idoc.getElementById("vmin"); + var vmax = idoc.getElementById("vmax"); + + iframe.style.width = "100px"; + iframe.style.height = "250px"; + is(width(vh), 250, "vh should be 250px"); + is(width(vw), 100, "vw should be 100px"); + is(width(vmin), 100, "vmin should be 100px"); + is(width(vmax), 250, "vmax should be 250px"); + + iframe.style.width = "300px"; + is(width(vh), 250, "vh should be 250px"); + is(width(vw), 300, "vw should be 300px"); + is(width(vmin), 250, "vmin should be 250px"); + is(width(vmax), 300, "vmax should be 300px"); + + iframe.style.height = "200px"; + is(width(vh), 200, "vh should be 200px"); + is(width(vw), 300, "vw should be 300px"); + is(width(vmin), 200, "vmin should be 200px"); + is(width(vmax), 300, "vmax should be 300px"); + + SimpleTest.finish(); +} + +window.addEventListener("load", run, false); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_visited_image_loading.html b/layout/style/test/test_visited_image_loading.html new file mode 100644 index 000000000..1a18d7ac3 --- /dev/null +++ b/layout/style/test/test_visited_image_loading.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=557287 +--> +<head> + <title>Test for Bug 557287</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557287">Mozilla Bug 147777</a> +<iframe id="display" src="visited_image_loading_frame.html"></iframe> +<pre id="test"> +<script type="application/ecmascript" src="visited_image_loading.sjs?reset"></script> +<script type="application/javascript"> + +/** Test for Bug 557287 **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var subdoc, subwin; + +window.addEventListener("load", run, false); + +function run() +{ + var frame = document.getElementById("display"); + subdoc = frame.contentDocument; + subwin = frame.contentWindow; + setTimeout(check_link_styled, 50); +} + +function visitedDependentComputedStyle(win, elem, property) { + return SpecialPowers.DOMWindowUtils + .getVisitedDependentComputedStyle(elem, "", property); +} + +function check_link_styled() +{ + var vislink = subdoc.getElementById("visited"); + var bgcolor = + visitedDependentComputedStyle(subwin, vislink, "background-color"); + if (bgcolor == "rgb(128, 0, 128)") { + // We've done our async :visited processing and restyled accordingly. + // Make sure that we've actually painted before finishing the test. + subwin.addEventListener("MozAfterPaint", paint_listener, false); + // do something that forces a paint + subdoc.body.appendChild(subdoc.createTextNode("new text node")); + } else { + setTimeout(check_link_styled, 50); + } +} + +function paint_listener(event) +{ + subwin.removeEventListener("MozAfterPaint", paint_listener, false); + var s = document.createElement("script"); + s.src = "visited_image_loading.sjs?waitforresult"; + document.body.appendChild(s); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_visited_image_loading_empty.html b/layout/style/test/test_visited_image_loading_empty.html new file mode 100644 index 000000000..42b7c7aff --- /dev/null +++ b/layout/style/test/test_visited_image_loading_empty.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=557287 +--> +<head> + <title>Test for Bug 557287</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=557287">Mozilla Bug 147777</a> +<iframe id="display" src="visited_image_loading_frame_empty.html"></iframe> +<pre id="test"> +<script type="application/ecmascript" src="visited_image_loading.sjs?reset"></script> +<script type="application/javascript"> + +/** Test for Bug 557287 **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var subdoc, subwin; + +window.addEventListener("load", run, false); + +function run() +{ + var frame = document.getElementById("display"); + subdoc = frame.contentDocument; + subwin = frame.contentWindow; + setTimeout(check_link_styled, 50); +} + +function visitedDependentComputedStyle(win, elem, property) { + return SpecialPowers.DOMWindowUtils + .getVisitedDependentComputedStyle(elem, "", property); +} + +function check_link_styled() +{ + var vislink = subdoc.getElementById("visited"); + var bgcolor = + visitedDependentComputedStyle(subwin, vislink, "background-color"); + if (bgcolor == "rgb(128, 0, 128)") { + // We've done our async :visited processing and restyled accordingly. + // Make sure that we've actually painted before finishing the test. + subwin.addEventListener("MozAfterPaint", paint_listener, false); + // do something that forces a paint + subdoc.body.appendChild(subdoc.createTextNode("new text node")); + } else { + setTimeout(check_link_styled, 50); + } +} + +function paint_listener(event) +{ + subwin.removeEventListener("MozAfterPaint", paint_listener, false); + var s = document.createElement("script"); + s.src = "visited_image_loading.sjs?waitforresult"; + document.body.appendChild(s); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_visited_lying.html b/layout/style/test/test_visited_lying.html new file mode 100644 index 000000000..1d5fe8c7f --- /dev/null +++ b/layout/style/test/test_visited_lying.html @@ -0,0 +1,97 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=147777 +--> +<head> + <title>Test for Bug 147777</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a> +<iframe id="iframe" src="visited-lying-inner.html" style="width: 20em; height: 5em"></iframe> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 147777 **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +window.addEventListener("load", start, false); + +var iframe; +var visitedlink, unvisitedlink; +var snapshot1; + +function start() +{ + // Our load event has fired, so we know our iframe is loaded. + iframe = document.getElementById("iframe"); + visitedlink = iframe.contentDocument.getElementById("visitedlink"); + unvisitedlink = iframe.contentDocument.getElementById("unvisitedlink"); + + // First, take a snapshot of it with both links unvisited. + snapshot1 = snapshotWindow(iframe.contentWindow, false); + + // Then, change one of the links in the iframe to being visited. + visitedlink.href = window.location; + + // Then, start polling to see when the history has updated the display. + setTimeout(poll_for_restyle, 100); +} + +function poll_for_restyle() +{ + var snapshot2 = snapshotWindow(iframe.contentWindow, false); + var equal = compareSnapshots(snapshot1, snapshot2, true)[0]; + if (equal) { + // keep polling + setTimeout(poll_for_restyle, 100); + } else { + // We now know that the link is visited, so we're ready to run + // tests. + run_tests(); + } +} + +function run_tests() +{ + // Test querySelector and querySelectorAll. + var subdoc = iframe.contentDocument; + is(subdoc.querySelector(":link"), unvisitedlink, + "first :link should be the unvisited link"); + is(subdoc.querySelector(":visited"), null, + "querySelector should not find anything :visited"); + var qsr = subdoc.querySelectorAll(":link"); + is(qsr.length, 2, "querySelectorAll(:link) should find 2 results"); + is(qsr[0], unvisitedlink, "querySelectorAll(:link)[0]"); + is(qsr[1], visitedlink, "querySelectorAll(:link)[1]"); + qsr = subdoc.querySelectorAll(":visited"); + is(qsr.length, 0, "querySelectorAll(:visited) should find 0 results"); + + // Test getComputedStyle. + var subwin = iframe.contentWindow; + is(subwin.getComputedStyle(unvisitedlink, "").color, "rgb(0, 0, 255)", + "getComputedStyle on unvisited link should report color is blue"); + is(subwin.getComputedStyle(visitedlink, "").color, "rgb(0, 0, 255)", + "getComputedStyle on visited link should report color is blue"); + + // Test matches. + is(unvisitedlink.matches(":link"), true, + "unvisited link matches :link"); + is(visitedlink.matches(":link"), true, + "visited link matches :link"); + is(unvisitedlink.matches(":visited"), false, + "unvisited link does not match :visited"); + is(visitedlink.matches(":visited"), false, + "visited link does not match :visited"); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_visited_pref.html b/layout/style/test/test_visited_pref.html new file mode 100644 index 000000000..3526b833f --- /dev/null +++ b/layout/style/test/test_visited_pref.html @@ -0,0 +1,112 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=147777 +--> +<head> + <title>Test for visited link coloring pref Bug 147777</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style type="text/css"> + + :link { float: left; } + + :visited { float: right; } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a> +<iframe id="iframe" src="visited-pref-iframe.html" style="width: 10em; height: 5em"></iframe> +<pre id="test"> +<script type="application/javascript"> +/** Test for Bug 147777 **/ + +function reinsert_node(e) { + var sib = e.nextSibling; + var par = e.parentNode; + par.removeChild(e); + par.insertBefore(e, sib); +} + +function get_pref() +{ + return SpecialPowers.getBoolPref("layout.css.visited_links_enabled"); +} + +function snapshotsEqual(snap1, snap2) +{ + return compareSnapshots(snap1, snap2, true)[0]; +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +window.addEventListener("load", step1, false); + +var iframe, subdoc, subwin; +var link; +var start; +var timeout; + +var unvisref; // reference image for unvisited style + +function step1() +{ + is(get_pref(), true, "pref defaults to true"); + + iframe = document.getElementById("iframe"); + subdoc = iframe.contentDocument; + subwin = iframe.contentWindow; + link = subdoc.getElementById("link"); + + unvisref = snapshotWindow(subwin, false); + + // Now set the href of the link to a location that's actually visited. + link.href = window.location; + + start = Date.now(); + + // And wait for the link to get restyled when the history lets us + // know it is (asynchronously). + setTimeout(poll_for_visited_style, 100); +} + +function poll_for_visited_style() +{ + var snapshot = snapshotWindow(subwin, false); + if (snapshotsEqual(unvisref, snapshot)) { + // hasn't been styled yet + setTimeout(poll_for_visited_style, 100); + + // If it never gets styled correctly, this test will fail because + // this loop will never complete. + } else { + var end = Date.now(); + timeout = 3 * Math.max(end - start, 300); + SpecialPowers.pushPrefEnv({"set":[["layout.css.visited_links_enabled", false]]}, step2); + } +} + +function step2() +{ + // we don't handle dynamic changes of this pref; it only takes effect + // when a new page loads + reinsert_node(link); + + setTimeout(step3, timeout); +} + +function step3() +{ + var snapshot = snapshotWindow(subwin, false); + ok(snapshotsEqual(unvisref, snapshot), + ":visited selector does not apply given false preference"); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_visited_reftests.html b/layout/style/test/test_visited_reftests.html new file mode 100644 index 000000000..af6e63b9f --- /dev/null +++ b/layout/style/test/test_visited_reftests.html @@ -0,0 +1,210 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=147777 +--> +<head> + <title>Test for Bug 147777</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=147777">Mozilla Bug 147777</a> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 147777 **/ + +// Because link-coloring for visited links is asynchronous, running +// reftests that involve link coloring requires that we poll for the +// correct result until all links are styled correctly. + +// A requirement of these reftests is that the reference rendering is +// styled correctly when loaded. We only poll for the tests. + +var gTests = [ + // there's also an implicit "load visited-page.html" at the start, + // thanks to the code below. + + // IMPORTANT NOTE: For these tests, the test and reference are not + // snapshotted in the same way. The REFERENCE (second file) is + // assumed to be complete when loaded, but we poll for visited link + // coloring on the TEST (first file) until the test passes. + "== pseudo-classes-02.svg pseudo-classes-02-ref.svg", + "!= color-on-link-1-ref.html color-on-visited-1-ref.html", + "== color-on-link-1.html color-on-link-1-ref.html", + "== color-on-link-before-1.html color-on-link-1-ref.html", + "== color-on-visited-1.html color-on-visited-1-ref.html", + "== color-on-visited-before-1.html color-on-visited-1-ref.html", + "!= content-color-on-link-before-1-ref.html content-color-on-visited-before-1-ref.html", + "== content-color-on-link-before-1.html content-color-on-link-before-1-ref.html", + "== content-color-on-visited-before-1.html content-color-on-visited-before-1-ref.html", + "== content-on-link-before-1.html content-before-1-ref.html", + "== content-on-visited-before-1.html content-before-1-ref.html", + "== color-on-text-decoration-1.html color-on-text-decoration-1-ref.html", + "== color-on-bullets-1.html color-on-bullets-1-ref.html", + // NOTE: background-color is tested by all the selector tests (and + // also color-choice-1) and therefore doesn't have its own tests. + // FIXME: Maybe add a test for selection colors (foreground and + // background), if possible. + "== width-on-link-1.html width-1-ref.html", + "== width-on-visited-1.html width-1-ref.html", + "== border-1.html border-1-ref.html", + "== border-2a.html border-2-ref.html", + "== border-2b.html border-2-ref.html", + // FIXME: Commented out because of dynamic change handling bugs in + // border-collapse tables that mean we get an incorrect rendering when + // the asynchronous restyle-from-history arrives. + //"== border-collapse-1.html border-collapse-1-ref.html", + "== outline-1.html outline-1-ref.html", + "== column-rule-1.html column-rule-1-ref.html", + "!= column-rule-1.html column-rule-1-notref.html", + "== color-choice-1.html color-choice-1-ref.html", + "== selector-descendant-1.html selector-descendant-1-ref.html", + "== selector-descendant-2.xhtml selector-descendant-2-ref.xhtml", + "== selector-child-1.html selector-child-1-ref.html", + "== selector-child-2.xhtml selector-child-2-ref.xhtml", + "== selector-adj-sibling-1.html selector-adj-sibling-1-ref.html", + "== selector-adj-sibling-2.html selector-adj-sibling-2-ref.html", + "== selector-any-sibling-1.html selector-any-sibling-1-ref.html", + "== selector-any-sibling-2.html selector-any-sibling-2-ref.html", + "== subject-of-selector-descendant-1.html subject-of-selector-1-ref.html", + "== subject-of-selector-descendant-2.xhtml subject-of-selector-descendant-2-ref.xhtml", + "== subject-of-selector-child-1.html subject-of-selector-1-ref.html", + "== subject-of-selector-adj-sibling-1.html subject-of-selector-1-ref.html", + "== subject-of-selector-any-sibling-1.html subject-of-selector-1-ref.html", + "== inherit-keyword-1.xhtml inherit-keyword-1-ref.html", + "!= svg-image-visited-1-helper.svg lime100x100.svg", + "!= svg-image-visited-2-helper.svg lime100x100.svg", + // FIXME: commented out because dynamic changes on the non-first-line + // part of the test don't work right when the link becomes visited. + //"== first-line-1.html first-line-1-ref.html", + "== white-to-transparent-1.html white-to-transparent-1-ref.html", + "== link-root-1.xhtml link-root-1-ref.xhtml", + "== mathml-links.html mathml-links-ref.html", +]; + +// Maintain a reference count of how many things we're waiting for until +// we can say the tests are done. +var gDelayCount = 0; +function AddFinishDependency() + { ++gDelayCount; } +function RemoveFinishDependency() + { if (--gDelayCount == 0) SimpleTest.finish(); } + +// We record the maximum number of times we had to look at a test before +// it switched to the passing state (though we assume it's 10 to start +// rather than 0 so that we have a reasonable default). Then we make a +// test "time out" if it takes more than gTimeoutFactor times that +// amount of time. This allows us to report a test failure rather than +// making a test failure just show up as a timeout. +var gMaxPassingTries = 10; +var gTimeoutFactor = 10; + +function loadVisitedPage() +{ + var element = document.createElement("iframe"); + element.addEventListener("load", visitedPageLoad, false); + element.src = "css-visited/visited-page.html"; + document.body.appendChild(element); + AddFinishDependency(); +} + +function visitedPageLoad() +{ + for (var i = 0; i < gTests.length; ++i) { + startTest(i); + } + RemoveFinishDependency(); +} + +function takeSnapshot(iframe_element) +{ + return snapshotWindow(iframe_element.contentWindow, false); +} + +function passes(op, shot1, shot2) +{ + var [correct, s1, s2] = compareSnapshots(shot1, shot2, op == "=="); + return correct; +} + +function startTest(i) +{ + var testLine = gTests[i]; + var splitData = testLine.split(" "); + var testData = + { op: splitData[0], test: splitData[1], reference: splitData[2] }; + var tries = 0; + + // Maintain state specific to this test in the closure exposed to all + // the functions nested inside this one. + + function startIframe(url) + { + var element = document.createElement("iframe"); + element.addEventListener("load", handleLoad, false); + // smaller than normal reftests, but enough for these + element.setAttribute("style", "width: 30em; height: 10em"); + element.src = "css-visited/" + url; + document.body.appendChild(element); + function handleLoad(event) + { + iframe.loaded = true; + if (iframe == reference) { + reference.snapshot = takeSnapshot(element); + } + var other = (iframe == test) ? reference : test; + if (other.loaded) { + // Always wait at least 100ms, so that any test that switches + // from passing to failing when the asynchronous link coloring + // happens should fail at least some of the time. + setTimeout(checkTest, 100); + } + } + function checkTest() + { + var test_snapshot = takeSnapshot(test.element); + if (passes(testData.op, test_snapshot, reference.snapshot)) { + if (tries > gMaxPassingTries) { + gMaxPassingTries = tries; + } + report(true); + } else { + ++tries; + if (tries > gMaxPassingTries * gTimeoutFactor) { + info("Giving up after " + tries + " tries, " + + "maxp=" + gMaxPassingTries + + "fact=" + gTimeoutFactor); + report(false); + } else { + // Links might not have been colored yet. Try again in 100ms. + setTimeout(checkTest, 100); + } + } + } + function report(result) + { + ok(result, "(" + i + ") " + + testData.op + " " + testData.test + " " + testData.reference); + RemoveFinishDependency(); + } + var iframe = { element: element, loaded: false }; + + return iframe; + } + + AddFinishDependency(); + var test = startIframe(testData.test); + var reference = startIframe(testData.reference); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +loadVisitedPage(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_webkit_device_pixel_ratio.html b/layout/style/test/test_webkit_device_pixel_ratio.html new file mode 100644 index 000000000..ed655bd8a --- /dev/null +++ b/layout/style/test/test_webkit_device_pixel_ratio.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1176968 +--> +<head> + <title>Test for Bug 1176968</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style>.zoom-test { visibility: hidden; }</style> + <style><!-- placeholder for dynamic additions --></style> +</head> +<body onload="run()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1176968">Mozilla Bug 1176968</a> +<div id="content" style="display: none"> + +</div> +<script type="text/javascript"> +</script> +<pre id="test"> +<div id="zoom1" class="zoom-test"></div> +<div id="zoom2" class="zoom-test"></div> +<div id="zoom3" class="zoom-test"></div> +<script class="testbody" type="application/javascript"> + +/** Test for Bug 1176968 **/ + +SimpleTest.waitForExplicitFinish(); + +function run() { + function zoom(factor) { + var previous = SpecialPowers.getFullZoom(window); + SpecialPowers.setFullZoom(window, factor); + return previous; + } + + function isVisible(divName) { + return window.getComputedStyle(document.getElementById(divName), null).visibility == "visible"; + } + + function getScreenPixelsPerCSSPixel() { + return SpecialPowers.DOMWindowUtils.screenPixelsPerCSSPixel; + } + + var screenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel(); + var baseRatio = 1.0 * screenPixelsPerCSSPixel; + var doubleRatio = 2.0 * screenPixelsPerCSSPixel; + var halfRatio = 0.5 * screenPixelsPerCSSPixel; + var styleElem = document.getElementsByTagName("style")[1]; + styleElem.textContent = + ["@media all and (-webkit-device-pixel-ratio: " + baseRatio + ") {", + "#zoom1 { visibility: visible; }", + "}", + "@media all and (-webkit-device-pixel-ratio: " + doubleRatio + ") {", + "#zoom2 { visibility: visible; }", + "}", + "@media all and (-webkit-device-pixel-ratio: " + halfRatio + ") {", + "#zoom3 { visibility: visible; }", + "}" + ].join("\n"); + + ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level"); + ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply"); + var origZoom = zoom(2); + ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level"); + ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply"); + zoom(0.5); + ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level"); + ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply"); + zoom(origZoom); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_webkit_flex_display.html b/layout/style/test/test_webkit_flex_display.html new file mode 100644 index 000000000..b84d16608 --- /dev/null +++ b/layout/style/test/test_webkit_flex_display.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1274096 +--> +<head> + <title>Test for Bug 1274096</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1274096">Mozilla Bug 1274096</a> +<div id="content" style="display: none"> + <div id="testElem"></div> +</div> +<script type="text/javascript"> +</script> +<pre id="test"> +<script class="testbody" type="application/javascript"> + +/** Test for Bug 1274096 **/ + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + {"set": [["layout.css.prefixes.webkit", true]]} +).then(runTest); + +function runTest() { + testValue("display", "-webkit-flex", "flex"); + testValue("display", "-webkit-inline-flex", "inline-flex"); + + SimpleTest.finish(); +} + +function testValue(propName, specifiedVal, serializedVal) { + var testElem = document.getElementById("testElem"); + testElem.style[propName] = specifiedVal; + + is(testElem.style[propName], serializedVal, + `CSS '${propName}:${specifiedVal} should serialize as '${serializedVal}'`); + is(window.getComputedStyle(testElem, "")[propName], serializedVal, + `CSS 'display:${specifiedVal} should compute to '${serializedVal}'`); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/unprefixing_service_iframe.html b/layout/style/test/unprefixing_service_iframe.html new file mode 100644 index 000000000..8edeb20dc --- /dev/null +++ b/layout/style/test/unprefixing_service_iframe.html @@ -0,0 +1,394 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Helper file for testing CSS Unprefixing Service</title> + <script type="text/javascript" src="property_database.js"></script> + <style type="text/css"> + #wrapper { + width: 500px; + } + </style> +</head> +<body> +<div id="wrapper"> + <div id="content"></div> +</div> + +<script type="application/javascript;version=1.7"> +"use strict"; + +/** Helper file for testing the CSS Unprefixing Service **/ + +/* Testcases for CSS Unprefixing service. + * + * Each testcase MUST have the following fields: + * - decl: A CSS declaration with prefixed style, to be tested via elem.style. + * - targetPropName: The name of the property whose value should be + * affected by |decl|. + * + * And will have EITHER: + * - isInvalid: If set to something truthy, this implies that |decl| is + * invalid and should have no effect on |targetPropName|'s + * computed or specified style. + * + * ...OR: + * - expectedDOMStyleVal: The value that we expect to find in the specified + * style -- in elem.style.[targetPropName]. + * - expectedCompStyleVal: The value that we expect to find in the computed + * style -- in getComputedStyle(...)[targetPropName] + * If omitted, this is assumed to be the same as + * expectedDOMStyleVal. (Usually they'll be the same.) + */ +const gTestcases = [ + { decl: "-webkit-box-flex:5", + targetPropName: "flex-grow", + expectedDOMStyleVal: "5" }, + + /* If author happens to specify modern flexbox style after prefixed style, + make sure the modern stuff is preserved. */ + { decl: "-webkit-box-flex:4;flex-grow:6", + targetPropName: "flex-grow", + expectedDOMStyleVal: "6" }, + + /* Tests for handling !important: */ + { decl: "-webkit-box-flex:3!important;", + targetPropName: "flex-grow", + expectedDOMStyleVal: "3" }, + { decl: "-webkit-box-flex:2!important;flex-grow:1", + targetPropName: "flex-grow", + expectedDOMStyleVal: "2" }, + + { decl: "-webkit-box-flex:1!important bogusText;", + targetPropName: "flex-grow", + isInvalid: true }, + + // Make sure we handle weird capitalization in property & value, too: + { decl: "-WEBKIT-BoX-aLign: baSELine", + targetPropName: "align-items", + expectedDOMStyleVal: "baseline" }, + + { decl: "display:-webkit-box", + targetPropName: "display", + expectedDOMStyleVal: "flex" }, + + { decl: "display:-webkit-box; display:-moz-box;", + targetPropName: "display", + expectedDOMStyleVal: "flex" }, + + { decl: "display:-webkit-foobar; display:-moz-box;", + targetPropName: "display", + expectedDOMStyleVal: "-moz-box" }, + + // -webkit-box-align: baseline | center | end | start | stretch + // ...maps to: + // align-items: baseline | center | flex-end | flex-start | stretch + { decl: "-webkit-box-align: baseline", + targetPropName: "align-items", + expectedDOMStyleVal: "baseline" }, + { decl: "-webkit-box-align: center", + targetPropName: "align-items", + expectedDOMStyleVal: "center" }, + { decl: "-webkit-box-align: end", + targetPropName: "align-items", + expectedDOMStyleVal: "flex-end" }, + { decl: "-webkit-box-align: start", + targetPropName: "align-items", + expectedDOMStyleVal: "flex-start" }, + { decl: "-webkit-box-align: stretch", + targetPropName: "align-items", + expectedDOMStyleVal: "stretch" }, + + // -webkit-box-direction is not supported, because it's unused & would be + // complicated to support. See note in CSSUnprefixingService.js for more. + + // -webkit-box-ordinal-group: <number> maps directly to "order". + { decl: "-webkit-box-ordinal-group: 2", + targetPropName: "order", + expectedDOMStyleVal: "2" }, + { decl: "-webkit-box-ordinal-group: 6000", + targetPropName: "order", + expectedDOMStyleVal: "6000" }, + + // -webkit-box-orient: horizontal | inline-axis | vertical | block-axis + // ...maps to: + // flex-direction: row | row | column | column + { decl: "-webkit-box-orient: horizontal", + targetPropName: "flex-direction", + expectedDOMStyleVal: "row" }, + { decl: "-webkit-box-orient: inline-axis", + targetPropName: "flex-direction", + expectedDOMStyleVal: "row" }, + { decl: "-webkit-box-orient: vertical", + targetPropName: "flex-direction", + expectedDOMStyleVal: "column" }, + { decl: "-webkit-box-orient: block-axis", + targetPropName: "flex-direction", + expectedDOMStyleVal: "column" }, + + // -webkit-box-pack: start | center | end | justify + // ... maps to: + // justify-content: flex-start | center | flex-end | space-between + { decl: "-webkit-box-pack: start", + targetPropName: "justify-content", + expectedDOMStyleVal: "flex-start" }, + { decl: "-webkit-box-pack: center", + targetPropName: "justify-content", + expectedDOMStyleVal: "center" }, + { decl: "-webkit-box-pack: end", + targetPropName: "justify-content", + expectedDOMStyleVal: "flex-end" }, + { decl: "-webkit-box-pack: justify", + targetPropName: "justify-content", + expectedDOMStyleVal: "space-between" }, + + // -webkit-transform: <transform> maps directly to "transform" + { decl: "-webkit-transform: matrix(1, 2, 3, 4, 5, 6)", + targetPropName: "transform", + expectedDOMStyleVal: "matrix(1, 2, 3, 4, 5, 6)" }, + + // -webkit-transform-origin: <value> maps directly to "transform-origin" + { decl: "-webkit-transform-origin: 0 0", + targetPropName: "transform-origin", + expectedDOMStyleVal: "0px 0px 0px", + expectedCompStyleVal: "0px 0px" }, + + { decl: "-webkit-transform-origin: 100% 0", + targetPropName: "transform-origin", + expectedDOMStyleVal: "100% 0px 0px", + expectedCompStyleVal: "500px 0px" }, + + // -webkit-transition: <property> maps directly to "transition" + { decl: "-webkit-transition: width 1s linear 2s", + targetPropName: "transition", + expectedDOMStyleVal: "width 1s linear 2s" }, + + // -webkit-transition **with** -webkit-prefixed property in value. + { decl: "-webkit-transition: -webkit-transform 1s linear 2s", + targetPropName: "transition", + expectedDOMStyleVal: "transform 1s linear 2s" }, + // (Re-test to check that it sets the "transition-property" subproperty.) + { decl: "-webkit-transition: -webkit-transform 1s linear 2s", + targetPropName: "transition-property", + expectedDOMStyleVal: "transform" }, + + // Same as previous test, except with "-webkit-transform" in the + // middle of the value instead of at the beginning (still valid): + { decl: "-webkit-transition: 1s -webkit-transform linear 2s", + targetPropName: "transition", + expectedDOMStyleVal: "transform 1s linear 2s" }, + { decl: "-webkit-transition: 1s -webkit-transform linear 2s", + targetPropName: "transition-property", + expectedDOMStyleVal: "transform" }, + + // -webkit-gradient(linear, ...) expressions: + { decl: "background-image: -webkit-gradient(linear,0 0,0 100%,from(rgb(1, 2, 3)),to(rgb(104, 105, 106)))", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(180deg, rgb(1, 2, 3) 0%, rgb(104, 105, 106) 100%)"}, + { decl: "background-image: -webkit-gradient(linear, left top, right bottom, from(rgb(1, 2, 3)), to(rgb(201, 202, 203)))", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(135deg, rgb(1, 2, 3) 0%, rgb(201, 202, 203) 100%)"}, + + { decl: "background-image: -webkit-gradient(linear, left center, right center, from(rgb(1, 2, 3)), to(rgb(201, 202, 203)))", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(to right, rgb(1, 2, 3) 0%, rgb(201, 202, 203) 100%)"}, + + { decl: "background-image: -webkit-gradient(linear, left center, right center, from(rgb(0, 0, 0)), color-stop(30%, rgb(255, 0, 0)), color-stop(60%, rgb(0, 255, 0)), to(rgb(0, 0, 255)))", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(to right, rgb(0, 0, 0) 0%, rgb(255, 0, 0) 30%, rgb(0, 255, 0) 60%, rgb(0, 0, 255) 100%)"}, + + // -webkit-gradient(radial, ...) expressions: + { decl: "background-image: -webkit-gradient(radial, center center, 0, center center, 50, from(black), to(white)", + targetPropName: "background-image", + expectedDOMStyleVal: "radial-gradient(50px at center center , black 0%, white 100%)", + // XXXdholbert Note: unnecessary space, see bug 1160063----^ + expectedCompStyleVal: "radial-gradient(50px, rgb(0, 0, 0) 0%, rgb(255, 255, 255) 100%)", }, + + { decl: "background-image: -webkit-gradient(radial, left bottom, 0, center center, 50, from(yellow), color-stop(20%, orange), color-stop(40%, red), color-stop(60%, green), color-stop(80%, blue), to(purple))", + targetPropName: "background-image", + expectedDOMStyleVal: "radial-gradient(50px at left bottom , yellow 0%, orange 20%, red 40%, green 60%, blue 80%, purple 100%)", + // XXXdholbert Note: unnecessary space, see bug 1160063--^ + expectedCompStyleVal: "radial-gradient(50px at 0% 100%, rgb(255, 255, 0) 0%, rgb(255, 165, 0) 20%, rgb(255, 0, 0) 40%, rgb(0, 128, 0) 60%, rgb(0, 0, 255) 80%, rgb(128, 0, 128) 100%)" }, + + // -webkit-linear-gradient(...) expressions: + { decl: "background-image: -webkit-linear-gradient(top, blue, green)", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(to bottom, blue, green)", + expectedCompStyleVal: "linear-gradient(rgb(0, 0, 255), rgb(0, 128, 0))", }, + + { decl: "background-image: -webkit-linear-gradient(left, blue, green)", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(to right, blue, green)", + expectedCompStyleVal: "linear-gradient(to right, rgb(0, 0, 255), rgb(0, 128, 0))", }, + + { decl: "background-image: -webkit-linear-gradient(left bottom, blue, green)", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(to right top, blue, green)", + expectedCompStyleVal: "linear-gradient(to top right, rgb(0, 0, 255), rgb(0, 128, 0))", }, + + { decl: "background-image: -webkit-linear-gradient(130deg, blue, green)", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(320deg, blue, green)", + expectedCompStyleVal: "linear-gradient(320deg, rgb(0, 0, 255), rgb(0, 128, 0))", }, + + // -webkit-radial-gradient(...) expressions: + { decl: "background-image: -webkit-radial-gradient(#000, #fff)", + targetPropName: "background-image", + expectedDOMStyleVal: "radial-gradient(rgb(0, 0, 0), rgb(255, 255, 255))", }, + + { decl: "background-image: -webkit-radial-gradient(bottom right, white, black)", + targetPropName: "background-image", + expectedDOMStyleVal: "radial-gradient(at right bottom , white, black)", + // XXXdholbert Note: unnecessary space---------------^ see bug 1160063 + expectedCompStyleVal: "radial-gradient(at 100% 100%, rgb(255, 255, 255), rgb(0, 0, 0))", }, + + // Combination of unprefixed & prefixed gradient styles in a single 'background-image' expression + { decl: "background-image: -webkit-linear-gradient(black, white), radial-gradient(blue, purple), -webkit-gradient(linear,0 0,0 100%,from(red),to(orange))", + targetPropName: "background-image", + expectedDOMStyleVal: "linear-gradient(black, white), radial-gradient(blue, purple), linear-gradient(180deg, red 0%, orange 100%)", + expectedCompStyleVal: "linear-gradient(rgb(0, 0, 0), rgb(255, 255, 255)), radial-gradient(rgb(0, 0, 255), rgb(128, 0, 128)), linear-gradient(180deg, rgb(255, 0, 0) 0%, rgb(255, 165, 0) 100%)", }, + +]; + +function getComputedStyleWrapper(elem, prop) +{ + return window.getComputedStyle(elem, null).getPropertyValue(prop); +} + +// Shims for "is()" and "ok()", which defer to parent window using postMessage: +function is(aActual, aExpected, aDesc) +{ + // Add URL to description: + aDesc += " (iframe url: '" + window.location + "')"; + + window.parent.postMessage({type: "is", + actual: aActual, + expected: aExpected, + desc: aDesc}, "*"); +} + +function ok(aCondition, aDesc) +{ + // Add URL to description: + aDesc += " (iframe url: '" + window.location + "')"; + + window.parent.postMessage({type: "ok", + condition: aCondition, + desc: aDesc}, "*"); +} + +// Main test function to use, to test a given unprefixed CSS property. +// The argument aTestcase should be an entry from gTestcases above. +function runOneTest(aTestcase) +{ + let elem = document.getElementById("content"); + + // (self-test/sanity-check:) + if (!aTestcase.decl || !aTestcase.targetPropName) { + ok(false, "Bug in test; missing 'decl' or 'targetPropName' field"); + } + + // Populate testcase's implied fields: + if (aTestcase.isInvalid) { + // (self-test/sanity-check:) + if (aTestcase.expectedDOMStyleVal || aTestcase.expectedCompStyleVal) { + ok(false, "Bug in test; testcase w/ 'isInvalid' field also provided " + + "an expected*Val field, but should not have"); + } + aTestcase.expectedDOMStyleVal = ''; + aTestcase.expectedCompStyleVal = // initial computed style: + getComputedStyleWrapper(elem, aTestcase.targetPropName); + } else { + // (self-test/sanity-check:) + if (!aTestcase.expectedDOMStyleVal) { + ok(false, "Bug in test; testcase must provide expectedDOMStyleVal " + + "(or set isInvalid if it's testing an invalid decl)"); + } + // If expected computed style is unspecified, we assume it should match + // expected DOM style: + if (!aTestcase.expectedCompStyleVal) { + aTestcase.expectedCompStyleVal = aTestcase.expectedDOMStyleVal; + } + } + + elem.setAttribute("style", aTestcase.decl); + + // Check that DOM elem.style has the expected value: + is(elem.style[aTestcase.targetPropName], aTestcase.expectedDOMStyleVal, + "Checking if CSS Unprefixing Service produced expected result " + + "in elem.style['" + aTestcase.targetPropName + "'] " + + "when given decl '" + aTestcase.decl + "'"); + + // Check that computed style has the expected value: + // (only for longhand properties; shorthands aren't in computed style) + if (gCSSProperties[aTestcase.targetPropName].type == CSS_TYPE_LONGHAND) { + let computedValue = getComputedStyleWrapper(elem, aTestcase.targetPropName); + is(computedValue, aTestcase.expectedCompStyleVal, + "Checking if CSS Unprefixing Service produced expected result " + + "in computed value of property '" + aTestcase.targetPropName + "' " + + "when given decl '" + aTestcase.decl + "'"); + } + + elem.removeAttribute("style"); +} + +// Function used to quickly test that unprefixing is off: +function testUnprefixingDisabled() +{ + let elem = document.getElementById("content"); + + let initialFlexGrow = getComputedStyleWrapper(elem, "flex-grow"); + elem.setAttribute("style", "-webkit-box-flex:5"); + is(getComputedStyleWrapper(elem, "flex-grow"), initialFlexGrow, + "'-webkit-box-flex' shouldn't affect computed 'flex-grow' " + + "when CSS Unprefixing Service is inactive"); + + let initialDisplay = getComputedStyleWrapper(elem, "display"); + elem.setAttribute("style", "display:-webkit-box"); + is(getComputedStyleWrapper(elem, "display"), initialDisplay, + "'display:-webkit-box' shouldn't affect computed 'display' " + + "when CSS Unprefixing Service is inactive"); + + elem.style.display = "-webkit-box"; + is(getComputedStyleWrapper(elem, "display"), initialDisplay, + "Setting elem.style.display to '-webkit-box' shouldn't affect computed " + + "'display' when CSS Unprefixing Service is inactive"); +} + +// Focused test that CSS Unprefixing Service is functioning properly +// on direct tweaks to elem.style.display: +function testStyleDisplayDirectly() +{ + let elem = document.getElementById("content"); + elem.style.display = "-webkit-box"; + + is(elem.style.display, "flex", + "Setting elem.style.display to '-webkit-box' should produce 'flex' " + + "in elem.style.display, when CSS Unprefixing Service is active"); + is(getComputedStyleWrapper(elem, "display"), "flex", + "Setting elem.style.display to '-webkit-box' should produce 'flex' " + + "in computed style, when CSS Unprefixing Service is active"); + + // clean up: + elem.style.display = ""; +} + +function startTest() +{ + if (window.location.hash === "#expectEnabled") { + testStyleDisplayDirectly(); + gTestcases.forEach(runOneTest); + } else if (window.location.hash === "#expectDisabled") { + testUnprefixingDisabled(); + } else { + ok(false, + "Need a recognized 'window.location.hash' to indicate expectation. " + + "Got: '" + window.location.hash + "'"); + } + window.parent.postMessage({type: "testComplete"}, "*"); +} + +startTest(); +</script> +</body> +</html> diff --git a/layout/style/test/unprefixing_service_utils.js b/layout/style/test/unprefixing_service_utils.js new file mode 100644 index 000000000..cd17d20d0 --- /dev/null +++ b/layout/style/test/unprefixing_service_utils.js @@ -0,0 +1,87 @@ +/* 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/. */ + +// Shared data & functionality used in tests for CSS Unprefixing Service. + +// Whitelisted hosts: +// (per implementation of nsPrincipal::IsOnCSSUnprefixingWhitelist()) +var gWhitelistedHosts = [ + // test1.example.org is on the whitelist. + "test1.example.org", + // test2.example.org is on the "allow all subdomains" whitelist. + "test2.example.org", + "sub1.test2.example.org", + "sub2.test2.example.org" +]; + +// *NOT* whitelisted hosts: +var gNotWhitelistedHosts = [ + // Though test1.example.org is on the whitelist, its subdomains are not. + "sub1.test1.example.org", + // mochi.test is not on the whitelist. + "mochi.test:8888" +]; + +// Names of prefs: +const PREF_UNPREFIXING_SERVICE = + "layout.css.unprefixing-service.enabled"; +const PREF_INCLUDE_TEST_DOMAINS = + "layout.css.unprefixing-service.include-test-domains"; + +// Helper-function to make unique URLs in testHost(): +var gCounter = 0; +function getIncreasingCounter() { + return gCounter++; +} + +// This function tests a particular host in our iframe. +// @param aHost The host to be tested +// @param aExpectEnabled Should we expect unprefixing to be enabled for host? +function testHost(aHost, aExpectEnabled) { + // Build the URL: + let url = window.location.protocol; // "http:" or "https:" + url += "//"; + url += aHost; + + // Append the path-name, up to the actual filename (the final "/"): + const re = /(.*\/).*/; + url += window.location.pathname.replace(re, "$1"); + url += IFRAME_TESTFILE; + // In case this is the same URL as last time, we add "?N" for some unique N, + // to make each URL different, so that the iframe actually (re)loads: + url += "?" + getIncreasingCounter(); + // We give the URL a #suffix to indicate to the test whether it should expect + // that unprefixing is enabled or disabled: + url += (aExpectEnabled ? "#expectEnabled" : "#expectDisabled"); + + let iframe = document.getElementById("testIframe"); + iframe.contentWindow.location = url; + // The iframe will report its results back via postMessage. + // Our caller had better have set up a postMessage listener. +} + +// Register a postMessage() handler, to allow our cross-origin iframe to +// communicate back to the main page's mochitest functionality. +// The handler expects postMessage to be called with an object like: +// { type: ["is"|"ok"|"testComplete"], ... } +// The "is" and "ok" types will trigger the corresponding function to be +// called in the main page, with named arguments provided in the payload. +// The "testComplete" type will trigger the passed-in aTestCompleteCallback +// function to be invoked (e.g. to advance to the next testcase, or to finish +// the overall test, as-appropriate). +function registerPostMessageListener(aTestCompleteCallback) { + let receiveMessage = function(event) { + if (event.data.type === "is") { + is(event.data.actual, event.data.expected, event.data.desc); + } else if (event.data.type === "ok") { + ok(event.data.condition, event.data.desc); + } else if (event.data.type === "testComplete") { + aTestCompleteCallback(); + } else { + ok(false, "unrecognized data in postMessage call"); + } + }; + + window.addEventListener("message", receiveMessage, false); +} diff --git a/layout/style/test/unstyled-frame.css b/layout/style/test/unstyled-frame.css new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/layout/style/test/unstyled-frame.css diff --git a/layout/style/test/unstyled-frame.xml b/layout/style/test/unstyled-frame.xml new file mode 100644 index 000000000..833b4f112 --- /dev/null +++ b/layout/style/test/unstyled-frame.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="unstyled-frame.css" type="text/css"?> +<!-- The root element is forced to display:block, so look at its child --> +<root><child/></root> diff --git a/layout/style/test/unstyled.css b/layout/style/test/unstyled.css new file mode 100644 index 000000000..82767f9b2 --- /dev/null +++ b/layout/style/test/unstyled.css @@ -0,0 +1,2 @@ +/* we're testing computed style on elements without frames */ +root { display: none } diff --git a/layout/style/test/unstyled.xml b/layout/style/test/unstyled.xml new file mode 100644 index 000000000..86b7c54ac --- /dev/null +++ b/layout/style/test/unstyled.xml @@ -0,0 +1,3 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="unstyled.css" type="text/css"?> +<root><child/></root> diff --git a/layout/style/test/viewport_units_iframe.html b/layout/style/test/viewport_units_iframe.html new file mode 100644 index 000000000..fd71a3cd3 --- /dev/null +++ b/layout/style/test/viewport_units_iframe.html @@ -0,0 +1,6 @@ +<!DOCTYPE HTML> +<title>viewport units test</title> +<div id="vh" style="width: 100vh"></div> +<div id="vw" style="width: 100vw"></div> +<div id="vmin" style="width: 100vmin"></div> +<div id="vmax" style="width: 100vmax"></div> diff --git a/layout/style/test/visited-lying-inner.html b/layout/style/test/visited-lying-inner.html new file mode 100644 index 000000000..ad1dac758 --- /dev/null +++ b/layout/style/test/visited-lying-inner.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<title>Test document for test_visited_lying.html</title> +<style> +:link { color: blue } +:visited { color: purple } +</style> +<div><a id="unvisitedlink" href="http://www.example.com/url-that-was-never-visited">unvisited link</a></div> +<div><a id="visitedlink" href="http://www.example.com/url-that-was-never-visited">visited link</a></div> diff --git a/layout/style/test/visited-pref-iframe.html b/layout/style/test/visited-pref-iframe.html new file mode 100644 index 000000000..31da176e4 --- /dev/null +++ b/layout/style/test/visited-pref-iframe.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<title>iframe for test_visited_pref.html</title> +<style> +:link { color: blue } +:visited { color: purple } +</style> +<a href="http://www.example.com/url-that-has-not-been-visited" id="link">link</a> diff --git a/layout/style/test/visited_image_loading.sjs b/layout/style/test/visited_image_loading.sjs new file mode 100644 index 000000000..880305439 --- /dev/null +++ b/layout/style/test/visited_image_loading.sjs @@ -0,0 +1,60 @@ +function handleRequest(request, response) +{ + response.setHeader("Cache-Control", "no-cache", false); + var query = request.queryString; + switch (query) { + case "reset": + response.setHeader("Content-Type", "application/ecmascript", false); + setState("1l", ""); + setState("1v", ""); + setState("2l", ""); + setState("2v", ""); + break; + case "1l": + case "1v": + case "2l": + case "2v": + setState(query, getState(query) + "load"); + response.setStatusLine("1.1", 302, "Found"); + // redirect to a solid blue image + response.setHeader("Location", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12NgYPgPAAEDAQDZqt2zAAAAAElFTkSuQmCC"); + response.setHeader("Content-Type", "text/plain", false); + break; + + case "waitforresult": + response.setHeader("Content-Type", "application/ecmascript", false); + response.write("var start = Date.now();\n"); + // fall through! + + case "waitforresult-internal": + response.setHeader("Content-Type", "application/ecmascript", false); + response.write("if ('" + getState("1l") + "' == 'load' && '" + + getState("1v") + "' == '' && '" + + getState("2l") + "' == 'load' && '" + + getState("2v") + "' == '') { \n"); + response.write("setTimeout(function() {\n"); + response.write("var s = document.createElement('script');\n"); + response.write("s.src = 'visited_image_loading.sjs?result';\n"); + response.write("document.body.appendChild(s);"); + response.write("}, Math.max(100, 2 * (Date.now() - start)));\n"); + response.write("} else setTimeout(function() {\n"); + response.write("var s = document.createElement('script');\n"); + response.write("s.src = 'visited_image_loading.sjs?waitforresult-internal';\n"); + response.write("document.body.appendChild(s);"); + response.write("}, 10);\n"); + break; + + case "result": + response.setHeader("Content-Type", "application/ecmascript", false); + response.write("is('" + getState("1l") + + "', 'load', 'image 1l should have been loaded once')\n"); + response.write("is('" + getState("1v") + + "', '', 'image 1v should not have been loaded')\n"); + response.write("is('" + getState("2l") + + "', 'load', 'image 2l should have been loaded once')\n"); + response.write("is('" + getState("2v") + + "', '', 'image 2v should not have been loaded')\n"); + response.write("SimpleTest.finish()"); + break; + } +} diff --git a/layout/style/test/visited_image_loading_frame.html b/layout/style/test/visited_image_loading_frame.html new file mode 100644 index 000000000..f919f5eb7 --- /dev/null +++ b/layout/style/test/visited_image_loading_frame.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<title>Test for :visited image loading</title> +<style type="text/css"> + +:link { background-color: blue } +:visited { background-color: purple } + +#link:link { background-image: url("visited_image_loading.sjs?1l"); } +#link:visited { background-image: url("visited_image_loading.sjs?1v"); } +#visited:link { background-image: url("visited_image_loading.sjs?2l"); } +#visited:visited { background-image: url("visited_image_loading.sjs?2v"); } + +</style> +<a id="link" href="do-not-visit-this-link.html">unvisited link</a> +<a id="visited" href="visited_image_loading_frame.html">visited link</a> diff --git a/layout/style/test/visited_image_loading_frame_empty.html b/layout/style/test/visited_image_loading_frame_empty.html new file mode 100644 index 000000000..21579bb9c --- /dev/null +++ b/layout/style/test/visited_image_loading_frame_empty.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<title>Test for :visited image loading</title> +<style type="text/css"> + +:link { background-color: blue } +:visited { background-color: purple } + +#link:link { background-image: url("visited_image_loading.sjs?1l"); } +#link:visited { background-image: url("visited_image_loading.sjs?1v"); } +#visited:link { background-image: url("visited_image_loading.sjs?2l"); } +#visited:visited { background-image: url("visited_image_loading.sjs?2v"); } + +</style> +<a id="link" href="do-not-visit-this-link.html"></a> +<a id="visited" href="visited_image_loading_frame_empty.html"></a> diff --git a/layout/style/test/xbl_bindings.xml b/layout/style/test/xbl_bindings.xml new file mode 100644 index 000000000..90d68ae06 --- /dev/null +++ b/layout/style/test/xbl_bindings.xml @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<bindings xmlns="http://www.mozilla.org/xbl" + xmlns:html="http://www.w3.org/1999/xhtml"> + + <binding id="onedivchild"> + <content><html:div class="anondiv" /></content> + </binding> + +</bindings> diff --git a/layout/style/test/xpcshell.ini b/layout/style/test/xpcshell.ini new file mode 100644 index 000000000..5019e0a48 --- /dev/null +++ b/layout/style/test/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = +tail = + +[test_csslexer.js] |