diff options
Diffstat (limited to 'layout/style/test')
410 files changed, 69570 insertions, 0 deletions
diff --git a/layout/style/test/Ahem.ttf b/layout/style/test/Ahem.ttf Binary files differnew file mode 100644 index 0000000000..ac81cb0316 --- /dev/null +++ b/layout/style/test/Ahem.ttf diff --git a/layout/style/test/BitPattern.woff b/layout/style/test/BitPattern.woff Binary files differnew file mode 100644 index 0000000000..e4e8244057 --- /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 0000000000..13ac6ae93d --- /dev/null +++ b/layout/style/test/ListCSSProperties.cpp @@ -0,0 +1,178 @@ +/* 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" + +// Do not consider properties not valid in style rules +#define CSS_PROP_LIST_EXCLUDE_NOT_IN_STYLE + +// Need an extra level of macro nesting to force expansion of method_ +// params before they get pasted. +#define STRINGIFY_METHOD(method_) #method_ + +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_LONGHAND(name_, id_, method_, flags_, pref_, ...) \ + {#name_, STRINGIFY_METHOD(method_), pref_}, + +#include "mozilla/ServoCSSPropList.h" + +#undef CSS_PROP_LONGHAND +#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_LONGHAND(name_, ...) #name_, + +#include "mozilla/ServoCSSPropList.h" + +#undef CSS_PROP_LONGHAND +#undef CSS_PROP_LIST_EXCLUDE_INTERNAL + +}; + +const PropertyInfo gShorthandProperties[] = { + +#define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_ +#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \ + {#name_, STRINGIFY_METHOD(method_), pref_}, +#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, pref_) \ + {#name_, #method_, pref_}, + +#include "mozilla/ServoCSSPropList.h" + +#undef CSS_PROP_ALIAS +#undef CSS_PROP_SHORTHAND +#undef CSS_PROP_PUBLIC_OR_PRIVATE + +}; + +/* see gLonghandPropertiesWithDOMProp */ +const char* gShorthandPropertiesWithDOMProp[] = { + +#define CSS_PROP_LIST_EXCLUDE_INTERNAL +#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) #name_, +#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, flags_, pref_) #name_, + +#include "mozilla/ServoCSSPropList.h" + +#undef CSS_PROP_ALIAS +#undef CSS_PROP_SHORTHAND +#undef CSS_PROP_LIST_EXCLUDE_INTERNAL + +}; + +#undef STRINGIFY_METHOD + +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-text-scale", + "-moz-default-appearance", + "-moz-theme", + "-moz-inert", + "-moz-script-level", // parsed by UA sheets only + "-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-box-collapse", // chrome-only internal properties + "-moz-subtree-hidden-only-visually", // chrome-only internal properties + "-moz-user-focus", // chrome-only internal properties + "-moz-window-input-region-margin", // chrome-only internal properties + "-moz-window-opacity", // chrome-only internal properties + "-moz-window-transform", // chrome-only internal properties + "-moz-window-transform-origin", // chrome-only internal properties + "-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 0000000000..04e37d48e2 --- /dev/null +++ b/layout/style/test/ParseCSS.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// 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_InitXPCOM(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/additional_sheets_helper.html b/layout/style/test/additional_sheets_helper.html new file mode 100644 index 0000000000..306ddbf5bd --- /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 0000000000..6f7ededcd4 --- /dev/null +++ b/layout/style/test/animation_utils.js @@ -0,0 +1,935 @@ +//---------------------------------------------------------------------- +// +// 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); + gElem.addEventListener("animationiteration", listener); + gElem.addEventListener("animationend", listener); + } + + 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.remove(); + style.remove(); + if (utils.isTestControllingRefreshes) { + utils.restoreNormalRefresh(); + } + }; + + return waitForDocumentLoad() + .then(loadPaintListener) + .then(function () { + // Put refresh driver under test control and flush all pending style, + // layout and paint to avoid the situation that waitForPaintsFlush() + // receives unexpected MozAfterpaint event for those pending + // notifications. + utils.advanceTimeAndRefresh(0); + return waitForPaintsFlushed(); + }) + .then(function () { + div.style.animation = animationName + " 10s"; + + return waitForPaintsFlushed(); + }) + .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 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); + } + return Promise.resolve(next.value).then(step, function (err) { + throw err; + }); + } + + // Put refresh driver under test control + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0); + + // Run test + var promise = aTestFunc(); + if (!promise.then) { + generator = promise; + promise = step(); + } + return promise + .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, + TodoCompositor: 4, +}; + +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 + // FIXME: Auto generate this array. + const omtaProperties = [ + "transform", + "translate", + "rotate", + "scale", + "offset-path", + "offset-distance", + "offset-rotate", + "offset-anchor", + "offset-position", + "opacity", + "background-color", + ]; + if (!omtaProperties.includes(property)) { + ok(false, property + " is not an OMTA property"); + return; + } + var normalize; + var compare; + var normalizedToString = JSON.stringify; + switch (property) { + case "offset-path": + case "offset-distance": + case "offset-rotate": + case "offset-anchor": + case "offset-position": + case "translate": + case "rotate": + case "scale": + if (runningOn == RunningOn.MainThread) { + normalize = value => value; + compare = function (a, b, error) { + return a == b; + }; + break; + } + // fall through + case "transform": + normalize = convertTo3dMatrix; + compare = matricesRoughlyEqual; + normalizedToString = convert3dMatrixToString; + break; + case "opacity": + normalize = parseFloat; + compare = function (a, b, error) { + return Math.abs(a - b) <= error; + }; + break; + default: + normalize = value => value; + compare = function (a, b, error) { + return a == b; + }; + break; + } + + if (!!expected.compositorValue) { + const originalNormalize = normalize; + normalize = value => + !!value.compositorValue + ? originalNormalize(value.compositorValue) + : originalNormalize(value); + } + + // 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; + + case RunningOn.TodoCompositor: + todo( + compositorStr !== "", + desc + ": should 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); + // Note: the actualStr should be empty string when using todoCompositor, so + // actualValue is null in this case. However, compare() should handle null + // well. + okOrTodo( + compare(expectedValue, actualValue, tolerance), + desc + + " - got " + + actualStr + + ", expected " + + normalizedToString(expectedValue) + ); + + // For transform-like properties, if we have multiple transform-like + // properties, the OMTA value and getComputedStyle() must be different, + // so use this flag to skip the following tests. + // FIXME: Putting this property on the expected value is a little bit odd. + // It's not really a product of the expected value, but rather the kind of + // test we're running. That said, the omta_is, omta_todo_is etc. methods are + // already pretty complex and adding another parameter would probably + // complicate things too much so this is fine for now. If we extend these + // functions any more, though, we should probably reconsider this API. + if (expected.usesMultipleProperties) { + return; + } + + if (typeof expected.computed !== "undefined") { + // For some tests we specify a separate computed value for comparing + // with getComputedStyle. + // + // In particular, we do this for the individual transform functions since + // the form returned from getComputedStyle() reflects the individual + // properties (e.g. 'translate: 100px') while the form we read back from + // the compositor represents the combined result of all the transform + // properties as a single transform matrix (e.g. [0, 0, 0, 0, 100, 0]). + // + // Despite the fact that we can't directly compare the OMTA value against + // the getComputedStyle value in this case, it is still worth checking the + // result of getComputedStyle since it will help to alert us if some + // discrepancy arises between the way we calculate values on the main + // thread and compositor. + okOrTodo( + computedStr == expected.computed, + desc + ": Computed style should be equal to " + expected.computed + ); + } else if (actualStr === compositorStr) { + // For compositor animations do an additional check that they match + // the value calculated on the main thread + var computedValue = normalize(computedStr); + if (computedValue === null) { + ok( + false, + desc + + ": test framework should parse computed style" + + " - got " + + computedStr + ); + return; + } + okOrTodo( + compare(computedValue, actualValue, 0.0), + desc + + ": OMTA style and computed style should be equal" + + " - OMTA " + + actualStr + + ", computed " + + computedStr + ); + } + }; + + window.matricesRoughlyEqual = function (a, b, tolerance) { + // Error handle if a or b is invalid. + if (!a || !b) { + return false; + } + + 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); + } + 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), + ]; + } + return null; + } + + // Return the first defined value in args. + function defined(...args) { + return args.find(arg => typeof arg !== "undefined"); + } + + // 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 [ + [ + defined(obj.a, obj.sx, obj.m11, 1), + obj.b || obj.m12 || 0, + obj.m13 || 0, + obj.m14 || 0, + ], + [ + obj.c || obj.m21 || 0, + defined(obj.d, obj.sy, obj.m22, 1), + obj.m23 || 0, + obj.m24 || 0, + ], + [obj.m31 || 0, obj.m32 || 0, defined(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, + defined(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(", ") + + ")" + ); + } + 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.toml b/layout/style/test/browser.toml new file mode 100644 index 0000000000..7c16123562 --- /dev/null +++ b/layout/style/test/browser.toml @@ -0,0 +1,19 @@ +[DEFAULT] +support-files = [ + "bug453896_iframe.html", + "media_queries_iframe.html", + "mapped.css", + "mapped.css^headers^", + "mapped2.css", + "mapped2.css^headers^", + "sourcemap_css.html" +] + +["browser_bug453896.js"] + +["browser_sourcemap.js"] + +["browser_sourcemap_comment.js"] + +["browser_sourceurl_comment.js"] + diff --git a/layout/style/test/browser_bug453896.js b/layout/style/test/browser_bug453896.js new file mode 100644 index 0000000000..6b8e180c38 --- /dev/null +++ b/layout/style/test/browser_bug453896.js @@ -0,0 +1,16 @@ +add_task(async function () { + let uri = getRootDirectory(gTestPath) + "bug453896_iframe.html"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: uri, + }, + function (browser) { + return SpecialPowers.spawn(browser, [], async function () { + var fake_window = { ok: ok }; + content.wrappedJSObject.run(fake_window); + }); + } + ); +}); diff --git a/layout/style/test/browser_sourcemap.js b/layout/style/test/browser_sourcemap.js new file mode 100644 index 0000000000..56ad067818 --- /dev/null +++ b/layout/style/test/browser_sourcemap.js @@ -0,0 +1,41 @@ +add_task(async function () { + let uri = "http://example.com/browser/layout/style/test/sourcemap_css.html"; + info(`URI is ${uri}`); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: uri, + }, + async function (browser) { + await SpecialPowers.spawn(browser, [], function () { + let seenSheets = 0; + + for (let i = 0; i < content.document.styleSheets.length; ++i) { + let sheet = content.document.styleSheets[i]; + + info(`Checking ${sheet.href}`); + if (/mapped\.css/.test(sheet.href)) { + is( + sheet.sourceMapURL, + "mapped.css.map", + "X-SourceMap header took effect" + ); + seenSheets |= 1; + } else if (/mapped2\.css/.test(sheet.href)) { + is( + sheet.sourceMapURL, + "mapped2.css.map", + "SourceMap header took effect" + ); + seenSheets |= 2; + } else { + ok(false, "sheet does not have source map URL"); + } + } + + is(seenSheets, 3, "seen all source-mapped sheets"); + }); + } + ); +}); diff --git a/layout/style/test/browser_sourcemap_comment.js b/layout/style/test/browser_sourcemap_comment.js new file mode 100644 index 0000000000..e0bbff8de4 --- /dev/null +++ b/layout/style/test/browser_sourcemap_comment.js @@ -0,0 +1,47 @@ +add_task(async function () { + // Test text and expected results. + let test_cases = [ + ["/*# sourceMappingURL=here*/", "here"], + ["/*# sourceMappingURL=here */", "here"], + ["/*@ sourceMappingURL=here*/", "here"], + ["/*@ sourceMappingURL=there*/ /*# sourceMappingURL=here*/", "here"], + ["/*# sourceMappingURL=here there */", "here"], + + ["/*# sourceMappingURL= here */", ""], + ["/*# sourceMappingURL=*/", ""], + ["/*# sourceMappingUR=here */", ""], + ["/*! sourceMappingURL=here */", ""], + ["/*# sourceMappingURL = here */", ""], + ["/* # sourceMappingURL=here */", ""], + ]; + + let page = "<!DOCTYPE HTML>\n<html>\n<head>\n"; + for (let i = 0; i < test_cases.length; ++i) { + page += `<style type="text/css"> #x${i} { color: red; }${test_cases[i][0]}</style>\n`; + } + page += "</head><body>some text</body></html>"; + + let uri = "data:text/html;base64," + btoa(page); + info(`URI is ${uri}`); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: uri, + }, + async function (browser) { + await SpecialPowers.spawn(browser, [test_cases], function (tests) { + for (let i = 0; i < content.document.styleSheets.length; ++i) { + let sheet = content.document.styleSheets[i]; + + info(`Checking sheet #${i}`); + is( + sheet.sourceMapURL, + tests[i][1], + `correct source map for sheet ${i}` + ); + } + }); + } + ); +}); diff --git a/layout/style/test/browser_sourceurl_comment.js b/layout/style/test/browser_sourceurl_comment.js new file mode 100644 index 0000000000..9baf6c9ce5 --- /dev/null +++ b/layout/style/test/browser_sourceurl_comment.js @@ -0,0 +1,43 @@ +add_task(async function () { + // Test text and expected results. + let test_cases = [ + ["/*# sourceURL=here*/", "here"], + ["/*# sourceURL=here */", "here"], + ["/*@ sourceURL=here*/", "here"], + ["/*@ sourceURL=there*/ /*# sourceURL=here*/", "here"], + ["/*# sourceURL=here there */", "here"], + + ["/*# sourceURL= here */", ""], + ["/*# sourceURL=*/", ""], + ["/*# sourceUR=here */", ""], + ["/*! sourceURL=here */", ""], + ["/*# sourceURL = here */", ""], + ["/* # sourceURL=here */", ""], + ]; + + let page = "<!DOCTYPE HTML>\n<html>\n<head>\n"; + for (let i = 0; i < test_cases.length; ++i) { + page += `<style type="text/css"> #x${i} { color: red; }${test_cases[i][0]}</style>\n`; + } + page += "</head><body>some text</body></html>"; + + let uri = "data:text/html;base64," + btoa(page); + info(`URI is ${uri}`); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: uri, + }, + async function (browser) { + await SpecialPowers.spawn(browser, [test_cases], function (tests) { + for (let i = 0; i < content.document.styleSheets.length; ++i) { + let sheet = content.document.styleSheets[i]; + + info(`Checking sheet #${i}`); + is(sheet.sourceURL, tests[i][1], `correct source URL for sheet ${i}`); + } + }); + } + ); +}); diff --git a/layout/style/test/bug1382568-iframe.html b/layout/style/test/bug1382568-iframe.html new file mode 100644 index 0000000000..b1448703e5 --- /dev/null +++ b/layout/style/test/bug1382568-iframe.html @@ -0,0 +1,8 @@ +<!doctype html> +<iframe src="http://example.com/doesnt-matter-because-it-gets-blocked-due-to-mixed-content"></iframe> +<script> +window.addEventListener('load', function(){ + window[0].document.body.innerText; + window.parent.postMessage({ result: "ok" }, "*"); +}); +</script> diff --git a/layout/style/test/bug1729861.js b/layout/style/test/bug1729861.js new file mode 100644 index 0000000000..dbfa060ab1 --- /dev/null +++ b/layout/style/test/bug1729861.js @@ -0,0 +1,81 @@ +// # Bug 1729861 + +// Expected values. Format: [name, pref_off_value, pref_on_value] +var expected_values = [ + [ + "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"], +]; + +// Create a line containing a CSS media query and a rule to set the bg color. +var mediaQueryCSSLine = function (key, val, color) { + return ( + "@media (" + + key + + ": " + + val + + ") { #" + + key + + " { background-color: " + + color + + "; } }\n" + ); +}; + +var green = "rgb(0, 128, 0)"; +var blue = "rgb(0, 0, 255)"; + +// Set a pref value asynchronously, returning a promise that resolves +// when it succeeds. +var pushPref = function (key, value) { + return SpecialPowers.pushPrefEnv({ set: [[key, value]] }); +}; + +// Set the resistFingerprinting pref to the given value, and then check that the background +// color has been updated properly as a result of re-evaluating the media queries. +var checkColorForPref = async function (setting, testDivs, expectedColor) { + await pushPref("privacy.resistFingerprinting", setting); + for (let div of testDivs) { + let color = window.getComputedStyle(div).backgroundColor; + is(color, expectedColor, "background for '" + div.id + "' is " + color); + } +}; + +var test = async function () { + // If the "off" and "on" expected values are the same, we can't actually + // test anything here. (Might this be the case on mobile?) + let skipTest = false; + for (let [key, offVal, onVal] of expected_values) { + if (offVal == onVal) { + todo(false, "Unable to test because '" + key + "' is invariant"); + return; + } + } + + let css = + ".test { margin: 1em; padding: 1em; color: white; width: max-content; font: 2em monospace }\n"; + + // Create a test element for each of the media queries we're checking, and append the matching + // "on" and "off" media queries to the CSS. + let testDivs = []; + for (let [key, offVal, onVal] of expected_values) { + let div = document.createElement("div"); + div.textContent = key; + div.setAttribute("class", "test"); + div.setAttribute("id", key); + testDivs.push(div); + document.getElementById("display").appendChild(div); + css += mediaQueryCSSLine(key, onVal, "green"); + css += mediaQueryCSSLine(key, offVal, "blue"); + } + document.getElementById("test-css").textContent = css; + + // Check that the test elements change color as expected when we flip the resistFingerprinting pref. + await checkColorForPref(true, testDivs, green); + await checkColorForPref(false, testDivs, blue); + await checkColorForPref(true, testDivs, green); +}; diff --git a/layout/style/test/bug453896_iframe.html b/layout/style/test/bug453896_iframe.html new file mode 100644 index 0000000000..e5414cc0df --- /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"> + +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-line") == "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 0000000000..bd1fe4f336 --- /dev/null +++ b/layout/style/test/bug517224.sjs @@ -0,0 +1,27 @@ +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 0000000000..a572cf3942 --- /dev/null +++ b/layout/style/test/bug732209-css.sjs @@ -0,0 +1,30 @@ +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 0000000000..570b5d5a1b --- /dev/null +++ b/layout/style/test/ccd-quirks.html @@ -0,0 +1,129 @@ +<!-- 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> +<script> + window.onload = function() { + window.parent.quirksLoaded() + } +</script> +</body></html> diff --git a/layout/style/test/ccd-standards.html b/layout/style/test/ccd-standards.html new file mode 100644 index 0000000000..693402df4c --- /dev/null +++ b/layout/style/test/ccd-standards.html @@ -0,0 +1,128 @@ +<!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> +<script> + window.onload = function() { + window.parent.standardsLoaded(); + } +</script> +</body></html> diff --git a/layout/style/test/ccd.sjs b/layout/style/test/ccd.sjs new file mode 100644 index 0000000000..22a4294d59 --- /dev/null +++ b/layout/style/test/ccd.sjs @@ -0,0 +1,71 @@ +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 0000000000..6d2af235c3 --- /dev/null +++ b/layout/style/test/chrome/bug418986-2.js @@ -0,0 +1,318 @@ +// # Bug 418986, part 2. + +const is_chrome_window = window.location.protocol === "chrome:"; + +const HTML_NS = "http://www.w3.org/1999/xhtml"; + +// 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 = [ + // Not available on most OSs. + "-moz-scrollbar-end-backward", + "-moz-scrollbar-end-forward", + "-moz-scrollbar-start-backward", + "-moz-scrollbar-start-forward", + "-moz-gtk-csd-available", + "-moz-gtk-csd-minimize-button", + "-moz-gtk-csd-maximize-button", + "-moz-gtk-csd-close-button", + "-moz-gtk-csd-reversed-placement", +]; + +var toggles_enabled_in_content = []; + +// Read the current OS. +var OS = SpecialPowers.Services.appinfo.OS; + +// __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 (!toggles_enabled_in_content.includes(key) && !is_chrome_window) { + ok(!exists, key + " should not exist."); + } else { + ok(exists, key + " should exist."); + if (resisting) { + ok( + keyValMatches(key, 0) && !keyValMatches(key, 1), + "Should always match as false" + ); + } + } + }); +}; + +// __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 fragment = document.createDocumentFragment(); + expected_values.forEach(function ([key, offVal, onVal]) { + let val = resisting ? onVal : offVal; + if (val) { + let div = document.createElementNS(HTML_NS, "div"); + div.setAttribute("class", "spoof"); + div.setAttribute("id", key); + div.textContent = key; + fragment.appendChild(div); + } + }); + suppressed_toggles.forEach(function (key) { + let div = document.createElementNS(HTML_NS, "div"); + div.setAttribute("class", "suppress"); + div.setAttribute("id", key); + div.textContent = key; + fragment.appendChild(div); + }); + return fragment; +}; + +// __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) { + if ( + !toggles_enabled_in_content.includes(key) && + !resisting && + !is_chrome_window + ) { + lines += "#" + key + " { background-color: green; }\n"; + } else { + lines += suppressedMediaQueryCSSLine(key, "green"); + } + }); + return lines; +}; + +// __green__. +// Returns the computed color style corresponding to green. +var green = "rgb(0, 128, 0)"; + +// __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").appendChild(generateHtmlLines(resisting)); + document.getElementById("test-css").textContent = 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.createElementNS(HTML_NS, "div"); + div.style.MozOsxFontSmoothing = "unset"; + document.documentElement.appendChild(div); + let readBack = window.getComputedStyle(div).MozOsxFontSmoothing; + div.remove(); + 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. +var testMediaQueriesInPictureElements = async function (resisting) { + const MATCH = "/tests/layout/style/test/chrome/match.png"; + let container = document.getElementById("pictures"); + let testImages = []; + for (let [key, offVal, onVal] of expected_values) { + let expected = resisting ? onVal : offVal; + if (expected) { + let picture = document.createElementNS(HTML_NS, "picture"); + let query = constructQuery(key, expected); + ok(matchMedia(query).matches, `${query} should match`); + + let source = document.createElementNS(HTML_NS, "source"); + source.setAttribute("srcset", MATCH); + source.setAttribute("media", query); + + let image = document.createElementNS(HTML_NS, "img"); + image.setAttribute("title", key + ":" + expected); + image.setAttribute("class", "testImage"); + image.setAttribute("src", "/tests/layout/style/test/chrome/mismatch.png"); + image.setAttribute("alt", key); + + testImages.push(image); + + picture.appendChild(source); + picture.appendChild(image); + container.appendChild(picture); + } + } + const matchURI = new URL(MATCH, document.baseURI).href; + await sleep(0); + for (let testImage of testImages) { + is( + testImage.currentSrc, + matchURI, + "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. +var test = async function (isContent) { + for (prefValue of [false, true]) { + await pushPref("privacy.resistFingerprinting", prefValue); + let resisting = prefValue && isContent; + expected_values.forEach(function ([key, offVal, onVal]) { + testMatch(key, resisting ? onVal : offVal); + }); + testToggles(resisting); + testCSS(resisting); + if (OS === "Darwin") { + testOSXFontSmoothing(resisting); + } + await 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 0000000000..bda339f776 --- /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 0000000000..e4395da3f3 --- /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.xhtml b/layout/style/test/chrome/bug535806-xul.xhtml new file mode 100644 index 0000000000..3d9a82b91e --- /dev/null +++ b/layout/style/test/chrome/bug535806-xul.xhtml @@ -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-only-media-queries.js b/layout/style/test/chrome/chrome-only-media-queries.js new file mode 100644 index 0000000000..aaf313a526 --- /dev/null +++ b/layout/style/test/chrome/chrome-only-media-queries.js @@ -0,0 +1,34 @@ +const CHROME_ONLY_TOGGLES = [ + "-moz-is-glyph", + "-moz-print-preview", + "-moz-scrollbar-start-backward", + "-moz-scrollbar-start-forward", + "-moz-scrollbar-end-backward", + "-moz-scrollbar-end-forward", + "-moz-overlay-scrollbars", + "-moz-mac-big-sur-theme", + "-moz-menubar-drag", + "-moz-windows-accent-color-in-titlebar", + "-moz-swipe-animation-enabled", + "-moz-gtk-csd-available", + "-moz-gtk-csd-minimize-button", + "-moz-gtk-csd-maximize-button", + "-moz-gtk-csd-close-button", + "-moz-gtk-csd-reversed-placement", + "-moz-panel-animations", +]; + +// Non-parseable queries can be tested directly in +// `test_chrome_only_media_queries.html`. +const CHROME_ONLY_QUERIES = [ + "(-moz-platform: linux)", + "(-moz-platform: windows)", + "(-moz-platform: macos)", + "(-moz-platform: android)", + "(-moz-content-prefers-color-scheme: dark)", + "(-moz-content-prefers-color-scheme: light)", + "(-moz-gtk-theme-family: unknown)", + "(-moz-gtk-theme-family: adwaita)", + "(-moz-gtk-theme-family: breeze)", + "(-moz-gtk-theme-family: yaru)", +]; diff --git a/layout/style/test/chrome/chrome.toml b/layout/style/test/chrome/chrome.toml new file mode 100644 index 0000000000..8c4c6045d8 --- /dev/null +++ b/layout/style/test/chrome/chrome.toml @@ -0,0 +1,51 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = [ + "bug418986-2.js", + "bug535806-css.css", + "bug535806-html.html", + "bug535806-xul.xhtml", + "hover_helper.html", + "match.png", + "mismatch.png", +] + +["test_bug418986-2.xhtml"] + +["test_bug511909.html"] + +["test_bug535806.xhtml"] + +["test_bug1157097.html"] + +["test_bug1346623.html"] + +["test_bug1371453.html"] + +["test_chrome_only_media_queries.html"] +support-files = ["chrome-only-media-queries.js"] + +["test_constructable_stylesheets_chrome_only_rules.html"] + +["test_display_mode.html"] +support-files = ["display_mode.html"] +tags = "fullscreen" + +["test_display_mode_reflow.html"] +support-files = ["display_mode_reflow.html"] +tags = "fullscreen" + +["test_hover.html"] +skip-if = ["true"] # bug 1346353 + +["test_moz_document_rules.html"] + +["test_moz_document_serialization.html"] + +["test_scrollbar_inline_size.html"] + +["test_stylesheet_clone_import_rule.html"] +support-files = [ + "import_useless1.css", + "import_useless2.css", +] diff --git a/layout/style/test/chrome/display_mode.html b/layout/style/test/chrome/display_mode.html new file mode 100644 index 0000000000..a4a0afb57e --- /dev/null +++ b/layout/style/test/chrome/display_mode.html @@ -0,0 +1,122 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1104916 +--> +<head> + <meta charset="utf-8"> + <title>Test for Display Mode</title> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.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"> + var imports = [ "SimpleTest", "is", "isnot", "ok" ]; + for (var n of imports) { + window[n] = window.opener.wrappedJSObject[n]; + } + + /** Test for Display Mode **/ + + function waitOneEvent(element, name) { + return new Promise(function(resolve, reject) { + element.addEventListener(name, function() { + resolve(); + }, {once: true}); + }); + } + + function promiseNextTick() { + return new Promise(resolve => setTimeout(resolve, 0)); + } + + async function test_task() { + 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-line") == + "underline"; + } + + function shouldApply(q) { + ok(queryApplies(q), q + " should apply"); + } + + function shouldNotApply(q) { + ok(!queryApplies(q), q + " should not apply"); + } + + function setDisplayMode(mode) { + window.browsingContext.top.displayMode = mode; + } + + 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("KEY_F11"); + await fullScreenEntered; + // Wait for the next tick to apply media feature changes. See bug 1430380. + await promiseNextTick(); + shouldApply("all and (display-mode: fullscreen)"); + shouldNotApply("all and (display-mode: browser)"); + var fullScreenExited = waitOneEvent(win, "sizemodechange"); + synthesizeKey("KEY_F11"); + await fullScreenExited; + // Wait for the next tick to apply media feature changes. See bug 1430380. + await promiseNextTick(); + 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(); + await 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(); + await fullScreenExited; + ok(!document.mozFullScreenElement, "window exited fullscreen"); + shouldNotApply("all and (display-mode: fullscreen)"); + shouldApply("all and (display-mode: browser)"); + + // Test entering display mode mode through docshell + setDisplayMode("standalone"); + shouldApply("all and (display-mode: standalone)"); + shouldNotApply("all and (display-mode: fullscreen)"); + shouldNotApply("all and (display-mode: browser)"); + shouldNotApply("all and (display-mode: minimal-ui)"); + + // Test that changes in the display mode are reflected + setDisplayMode("minimal-ui"); + shouldApply("all and (display-mode: minimal-ui)"); + shouldNotApply("all and (display-mode: standalone)"); + + // Set the display mode back. + setDisplayMode("browser"); + + window.close(); + window.SimpleTest.finish(); + } + </script> +</head> +<body onload="test_task()"> +<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/chrome/media_queries_iframe.html" allowfullscreen></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/display_mode_reflow.html b/layout/style/test/chrome/display_mode_reflow.html new file mode 100644 index 0000000000..7b2a118cd6 --- /dev/null +++ b/layout/style/test/chrome/display_mode_reflow.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1256084 +--> +<head> + <meta charset="utf-8"> + <title>Test for Display Mode</title> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.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"> + var imports = [ "SimpleTest", "is", "isnot", "ok" ]; + for (var n of imports) { + window[n] = window.opener.wrappedJSObject[n]; + } + + /** Test for Display Mode **/ + + function waitOneEvent(element, name) { + return new Promise(function(resolve, reject) { + element.addEventListener(name, function() { + resolve(); + }, {once: true}); + }); + } + + function promiseNextTick() { + return new Promise(resolve => setTimeout(resolve, 0)); + } + + async function test_task() { + 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("KEY_F11"); + await fullScreenEntered; + + // Wait for the next tick to apply media feature changes. See bug 1430380. + await promiseNextTick(); + ok(offsetTop !== secondDiv.offsetTop, "offset top changes"); + var fullScreenExited = waitOneEvent(win, "sizemodechange"); + synthesizeKey("KEY_F11"); + await fullScreenExited; + + // Wait for the next tick to apply media feature changes. See bug 1430380. + await promiseNextTick(); + 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(); + await fullScreenEntered + ok(offsetTop !== secondDiv.offsetTop, "offset top changes"); + fullScreenExited = waitOneEvent(document, "mozfullscreenchange"); + document.mozCancelFullScreen(); + await fullScreenExited; + ok(offsetTop === secondDiv.offsetTop, "offset top returns to original value"); + + window.close(); + window.SimpleTest.finish(); + } + </script> +</head> +<body onload="test_task()"> +<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/chrome/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/display_mode_reflow_iframe.html b/layout/style/test/chrome/display_mode_reflow_iframe.html new file mode 100644 index 0000000000..c05880ce7f --- /dev/null +++ b/layout/style/test/chrome/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/chrome/hover_empty.html b/layout/style/test/chrome/hover_empty.html new file mode 100644 index 0000000000..7879e1ce9f --- /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 0000000000..b1ae14e8cc --- /dev/null +++ b/layout/style/test/chrome/hover_helper.html @@ -0,0 +1,270 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for :hover</title> + <script 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); + setTimeout(str, 100); + }; + iframe.contentWindow.addEventListener("resize", handler); +} + +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, "rgba(0, 0, 0, 0)", + ":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, "rgba(0, 0, 0, 0)", + ":hover does not apply"); + is(getComputedStyle(divone.firstChild, "").backgroundColor, "rgba(0, 0, 0, 0)", + ":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, "rgba(0, 0, 0, 0)", + ":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, "rgba(0, 0, 0, 0)", + ":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, "rgba(0, 0, 0, 0)", + ":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, "rgba(0, 0, 0, 0)", + ":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, "rgba(0, 0, 0, 0)", + ":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, "rgba(0, 0, 0, 0)", + ":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/import_useless1.css b/layout/style/test/chrome/import_useless1.css new file mode 100644 index 0000000000..37e1a3d1d9 --- /dev/null +++ b/layout/style/test/chrome/import_useless1.css @@ -0,0 +1,3 @@ +.unlikely_to_match_anything { + color: black; +} diff --git a/layout/style/test/chrome/import_useless2.css b/layout/style/test/chrome/import_useless2.css new file mode 100644 index 0000000000..37e1a3d1d9 --- /dev/null +++ b/layout/style/test/chrome/import_useless2.css @@ -0,0 +1,3 @@ +.unlikely_to_match_anything { + color: black; +} diff --git a/layout/style/test/chrome/match.png b/layout/style/test/chrome/match.png Binary files differnew file mode 100644 index 0000000000..d3f299bf58 --- /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 0000000000..8f9da3f00f --- /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 0000000000..8b331b19e0 --- /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_bug1157097.html b/layout/style/test/chrome/test_bug1157097.html new file mode 100644 index 0000000000..febf4952fb --- /dev/null +++ b/layout/style/test/chrome/test_bug1157097.html @@ -0,0 +1,27 @@ +<!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> +function run() { + window.windowUtils.postRestyleSelfEvent(document.querySelector("p")); + document.querySelectorAll("span")[0].className = ""; + document.querySelectorAll("b")[0].className = "inline-block"; + document.querySelectorAll("span")[1].className = "blue"; + window.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_bug1346623.html b/layout/style/test/chrome/test_bug1346623.html new file mode 100644 index 0000000000..027f839ace --- /dev/null +++ b/layout/style/test/chrome/test_bug1346623.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 1346623</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script 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=1346623">Mozilla Bug 1346623</a> +<div id="display"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +var winUtils = window.windowUtils; + +function startTest() { + // load some styles at the agent level + var css = ` + #ac-parent { color: green; } + #ac-child.abc { } + `; + var sheetURL = "data:text/css," + encodeURIComponent(css); + winUtils.loadSheetUsingURIString(sheetURL, winUtils.AGENT_SHEET); + + // add canvas anonymous content + var bq = document.createElement("blockquote"); + bq.id = "ac-parent"; + bq.textContent = "This blockquote text should be green."; + var div = document.createElement("div"); + div.id = "ac-child"; + div.textContent = " This div text should be green."; + bq.appendChild(div); + var ac = document.insertAnonymousContent(); + ac.root.appendChild(bq); + document.body.offsetWidth; + + is(getComputedStyle(div).color, "rgb(0, 128, 0)", + "color before reframing"); + + // reframe the root + document.documentElement.style.display = "flex"; + document.body.offsetWidth; + + // restyle the div + div.className = "abc"; + document.body.offsetWidth; + + is(getComputedStyle(div).color, "rgb(0, 128, 0)", + "color after reframing"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/chrome/test_bug1371453.html b/layout/style/test/chrome/test_bug1371453.html new file mode 100644 index 0000000000..6b3b4cb6eb --- /dev/null +++ b/layout/style/test/chrome/test_bug1371453.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<head> +<title>Test for Bug 1371453</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"/> +<link rel="stylesheet" href="data:text/css,{}"> +<body> +<script> +SimpleTest.waitForExplicitFinish(); + +const Cu = SpecialPowers.Components.utils; + +document.styleSheetChangeEventsEnabled = true; + +onload = runTest; + +async function runTest() { + const sheet = document.getElementsByTagName("link")[1].sheet; + sheet.insertRule('@import url("blahblah")', 0); + + const rule = sheet.cssRules[0]; + is(rule.type, CSSRule.IMPORT_RULE, "Got expected import rule."); + isnot(rule.styleSheet, null, "Import rule contains a stylesheet."); + isnot(rule.media, null, "Import rule contains a media list."); + is(rule.href, "blahblah", "Import rule contains expected href."); + + SimpleTest.finish(); +} + +</script> +</body> +</html> diff --git a/layout/style/test/chrome/test_bug418986-2.xhtml b/layout/style/test/chrome/test_bug418986-2.xhtml new file mode 100644 index 0000000000..152cac004e --- /dev/null +++ b/layout/style/test/chrome/test_bug418986-2.xhtml @@ -0,0 +1,29 @@ +<?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 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.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" src="bug418986-2.js"></script> + <!-- test code goes here --> + <script type="text/javascript"> + // Run all tests now. + window.onload = function () { + add_task(async function() { + await test(false); + }); + }; + </script> +</window> diff --git a/layout/style/test/chrome/test_bug511909.html b/layout/style/test/chrome/test_bug511909.html new file mode 100644 index 0000000000..fa28bbe854 --- /dev/null +++ b/layout/style/test/chrome/test_bug511909.html @@ -0,0 +1,194 @@ +<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 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/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() { + // Ensure all the sheets are re-parsed, so that the pref applies. + for (const sheet of Array.from(document.querySelectorAll('style'))) { + sheet.textContent += "/* dummy */"; + } + + 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/chrome/test_bug535806.xhtml b/layout/style/test/chrome/test_bug535806.xhtml new file mode 100644 index 0000000000..7f4ec286bc --- /dev/null +++ b/layout/style/test/chrome/test_bug535806.xhtml @@ -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 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"); + }); + + function htmlLoaded() { + $("f").setAttribute("src", "bug535806-xul.xhtml"); + } + + function xulLoaded() { + var doc = $("f").contentDocument; + is(doc.defaultView.getComputedStyle(doc.getElementById("s")).color, + "rgb(0, 128, 0)"); + SimpleTest.finish(); + } + + + ]]> + </script> +</window> diff --git a/layout/style/test/chrome/test_chrome_only_media_queries.html b/layout/style/test/chrome/test_chrome_only_media_queries.html new file mode 100644 index 0000000000..1a2fb098c0 --- /dev/null +++ b/layout/style/test/chrome/test_chrome_only_media_queries.html @@ -0,0 +1,84 @@ +<!doctype html> +<title>Test for parsing of non-content-exposed media-queries.</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<script src="chrome-only-media-queries.js"></script> +<style></style> +<script> +const SHEET = document.querySelector('style'); + +SimpleTest.waitForExplicitFinish(); + +async function testWithPref() { + await new Promise(r => { + SpecialPowers.pushPrefEnv( + { + set: [ + ["layout.css.forced-colors.enabled", false], + ], + }, + r + ); + }); + expectKnown("(forced-colors: none)"); + expectKnown("(forced-colors: active)"); + expectKnown("(forced-colors)"); + SimpleTest.finish(); +} + +function expect(q, shouldBeKnown) { + is(matchMedia(q).media, q, "Serialization should roundtrip"); + is(matchMedia(`${q} or (not ${q})`).matches, shouldBeKnown, `Query should${shouldBeKnown ? "" : " not"} be known`); +} + +function expectKnown(q) { + expect(q, true); +} + +function expectUnkown(q) { + expect(q, false); +} + +// Test a toggle that should always match for `1` or `0`. +function testToggle(toggle) { + expectKnown(`(${toggle})`); + expectKnown(`(${toggle}: 1)`); + expectKnown(`(${toggle}: 0)`); + + expectUnkown(`(${toggle}: foo)`); + expectUnkown(`(${toggle}: true)`); + expectUnkown(`(${toggle}: false)`); + expectUnkown(`(${toggle}: -1)`); + expectUnkown(`(min-${toggle}: 0)`); + expectUnkown(`(max-${toggle}: 0)`); + expectUnkown(`(max-${toggle})`); + expectUnkown(`(min-${toggle})`); + + let matches_1 = matchMedia(`(${toggle}: 1)`).matches; + let matches_0 = matchMedia(`(${toggle}: 0)`).matches; + isnot(matches_0, matches_1, `Should not match both true and false: ${toggle}`); + is(matches_0 || matches_1, true, `Should match at least one: ${toggle}`); +} + +for (let toggle of CHROME_ONLY_TOGGLES) { + testToggle(toggle) +} + +for (let query of CHROME_ONLY_QUERIES) { + expectKnown(query); +} + +// These might be exposed to content by pref, we just want to make sure they're +// always exposed to chrome. +expectKnown("(prefers-contrast: more)") +expectKnown("(prefers-contrast: no-preference)") +expectKnown("(prefers-contrast: less)"); +expectKnown("(prefers-contrast)") + +expectKnown("(forced-colors: none)"); +expectKnown("(forced-colors: active)"); +expectKnown("(forced-colors)"); + +expectUnkown("(-moz-platform: )"); + +testWithPref(); +</script> diff --git a/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html b/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html new file mode 100644 index 0000000000..4d9647ba27 --- /dev/null +++ b/layout/style/test/chrome/test_constructable_stylesheets_chrome_only_rules.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<meta charset="utf-8"> +<title>Test for chrome-only rules in constructable stylesheets</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<script> + add_task(async function chrome_rules_constructable_stylesheets() { + let sheet = new CSSStyleSheet(); + sheet.replaceSync(".foo { -moz-default-appearance: none }"); + is(sheet.cssRules[0].style.length, 1, "Should parse chrome-only property in chrome document"); + }); +</script> 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 0000000000..69e72d5ab8 --- /dev/null +++ b/layout/style/test/chrome/test_display_mode.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1104916 +--> +<head> + <meta charset="utf-8"> + <title>Test for Display Mode</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.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"> + SimpleTest.waitForExplicitFinish(); + async function startTest() { + await new Promise(r => { + SpecialPowers.pushPrefEnv( + { + set: [ + ["dom.security.featurePolicy.header.enabled", true], + ["dom.security.featurePolicy.webidl.enabled", true], + ], + }, + r + ); + }); + // Chrome test run tests in iframe, and fullscreen is disabled by default. + // So run the test in a separate window. + window.open("display_mode.html", "display_mode", "width=500,height=500,resizable"); + } + </script> +</head> +<body onload="startTest();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1104916">Mozilla Bug 1104916</a> +</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 0000000000..01022207f3 --- /dev/null +++ b/layout/style/test/chrome/test_display_mode_reflow.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1256084 +--> +<head> + <meta charset="utf-8"> + <title>Test for Display Mode</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.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"> + SimpleTest.waitForExplicitFinish(); + async function startTest() { + await new Promise(r => { + SpecialPowers.pushPrefEnv( + { + set: [ + ["dom.security.featurePolicy.header.enabled", true], + ["dom.security.featurePolicy.webidl.enabled", true], + ], + }, + r + ); + }); + // Chrome test run tests in iframe, and fullscreen is disabled by default. + // So run the test in a separate window. + window.open("display_mode_reflow.html", "display_mode_reflow", "width=500,height=500,resizable"); + } + </script> +</head> +<body onload="startTest();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1256084">Mozilla Bug 1256084</a> +<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 0000000000..019f537e8c --- /dev/null +++ b/layout/style/test/chrome/test_hover.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for :hover</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script 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 0000000000..c28fc964ed --- /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 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script 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"> + +var [gStyleSheetService, gIOService] = (function() { + return [ + Cc["@mozilla.org/content/style-sheet-service;1"] + .getService(Ci.nsIStyleSheetService), + Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + ]; +})(); +function set_user_sheet(sheeturi) +{ + var uri = gIOService.newURI(sheeturi); + gStyleSheetService.loadAndRegisterSheet(uri, gStyleSheetService.USER_SHEET); +} +function remove_user_sheet(sheeturi) +{ + var uri = gIOService.newURI(sheeturi); + 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/chrome/test_moz_document_serialization.html b/layout/style/test/chrome/test_moz_document_serialization.html new file mode 100644 index 0000000000..0707880507 --- /dev/null +++ b/layout/style/test/chrome/test_moz_document_serialization.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <title>Test for Bug </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 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}/") {}' }, +]; + +SimpleTest.waitForExplicitFinish(); + + 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"); + } + + SimpleTest.finish(); + + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/chrome/test_scrollbar_inline_size.html b/layout/style/test/chrome/test_scrollbar_inline_size.html new file mode 100644 index 0000000000..31161a9caf --- /dev/null +++ b/layout/style/test/chrome/test_scrollbar_inline_size.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<meta charset="utf-8"> +<title>Test for env(scrollbar-inline-size)</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<link rel="stylesheet" href="chrome://global/skin"/> +<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +<div id="scroller" style="width: 100px; height: 100px; overflow: scroll"></div> +<div id="ref" style="width: env(scrollbar-inline-size, 1000px)"></div> +<script> + SimpleTest.waitForExplicitFinish(); + async function runTest() { + // We need to disable overlay scrollbars to measure the real scrollbar + // size. + await SpecialPowers.pushPrefEnv({ + set: [["ui.useOverlayScrollbars", 0]], + }); + runOnce(); + + info("with full zoom"); + SpecialPowers.setFullZoom(window, 2.0); + + runOnce(); + } + + function runOnce() { + let scroller = document.getElementById("scroller"); + let ref = document.getElementById("ref"); + let scrollbarSize = scroller.getBoundingClientRect().width - scroller.clientWidth; + ok(scrollbarSize > 0, "Should have a scrollbar"); + // clientWidth rounds, so we might see a bit of rounding error + isfuzzy(ref.getBoundingClientRect().width, scrollbarSize, 1, "env() should match the scrollbar size"); + } + + runTest().then(SimpleTest.finish); +</script> diff --git a/layout/style/test/chrome/test_stylesheet_clone_import_rule.html b/layout/style/test/chrome/test_stylesheet_clone_import_rule.html new file mode 100644 index 0000000000..37c3b9ccaa --- /dev/null +++ b/layout/style/test/chrome/test_stylesheet_clone_import_rule.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html lang="en-US"> +<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>div { color: green; }</style> + +<link id="theOnlyLink" rel="stylesheet" type="text/css" href="import_useless1.css"> + +<div id="theOnlyDiv">This text will change colors several times.</div> + +<script> + SimpleTest.waitForExplicitFinish(); + + const Cu = SpecialPowers.Components.utils; + + + let theOnlyDiv = document.getElementById("theOnlyDiv"); + let link = document.getElementById("theOnlyLink"); + let stylesheet = link.sheet; + + runTest().catch(function(reason) { + ok(false, "Failed with reason: " + reason); + }).then(function() { + SimpleTest.finish(); + }); + + function cssRulesToString(cssRules) { + return Array.from(cssRules).map(rule => rule.cssText).join(''); + } + + async function runTest() { + // Test that the div is initially red (from base.css) + is(getComputedStyle(theOnlyDiv).color, "rgb(0, 128, 0)", "div begins as green."); + + // Insert some import rules. + stylesheet.insertRule('@import url("import_useless2.css")', 0); + stylesheet.insertRule('@import url("import_useless2.css")', 1); + + // Do some sanity checking of our import rules. + let primaryRules = stylesheet.cssRules; + await SimpleTest.promiseWaitForCondition(function() { + try { + primaryRules[0].styleSheet.cssRules; + primaryRules[1].styleSheet.cssRules; + return true; + } catch (ex) { + return false; + } + }); + + // Make some helper variables for the comparison tests. + let importSheet1 = primaryRules[0].styleSheet; + let rules1 = importSheet1.cssRules; + + let importSheet2 = primaryRules[1].styleSheet; + let rules2 = importSheet2.cssRules; + + // Confirm that these two sheets are meaningfully the same. + is(cssRulesToString(rules1), cssRulesToString(rules2), "Cloned sheet rules are equivalent."); + + // Add a color-changing rule to the first stylesheet. + importSheet1.insertRule('div { color: blue; }'); + rules1 = importSheet1.cssRules; + + // And make sure that it has an effect. + is(getComputedStyle(theOnlyDiv).color, "rgb(0, 0, 255)", "div becomes blue."); + + // Make sure that the two sheets have different rules now. + isnot(cssRulesToString(rules1), cssRulesToString(rules2), "Cloned sheet rules are no longer equivalent."); + + // Add a color-changing rule to the second stylesheet (that will mask the first). + importSheet2.insertRule('div { color: red; }'); + // And make sure that it has an effect. + is(getComputedStyle(theOnlyDiv).color, "rgb(255, 0, 0)", "div becomes red."); + + // Delete the second sheet by removing the import rule, and make sure the color changes back. + stylesheet.deleteRule(1); + is(getComputedStyle(theOnlyDiv).color, "rgb(0, 0, 255)", "div goes back to blue."); + + // Delete the first sheet by removing the import rule, and make sure the color changes back. + stylesheet.deleteRule(0); + is(getComputedStyle(theOnlyDiv).color, "rgb(0, 128, 0)", "div goes back to green."); + } +</script> +</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 0000000000..606de3ad68 --- /dev/null +++ b/layout/style/test/css_properties_like_longhand.js @@ -0,0 +1 @@ +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 0000000000..f5abc8576d --- /dev/null +++ b/layout/style/test/descriptor_database.js @@ -0,0 +1,142 @@ +/* -*- 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", + "107", + "399", + "401", + "699", + "710", + "calc(1001)", + "calc(100 + 1)", + "calc(1)", + "100.6", + "99", + "700 900", + "300.4 500.4", + "calc(200.4) calc(400.4)", + ], + invalid_values: ["bolder", "lighter", "1001", "0", "0 100", "100 1001"], + }, + src: { + domProp: null, + values: [ + "url(404.ttf)", + 'url("404.eot")', + "url('404.otf')", + 'url(404.ttf) format("truetype")', + "local(Times New Roman)", + "local('Times New Roman')", + 'local("Times New Roman")', + 'local("serif")', + "url(404.ttf) format(truetype)", + 'url(404.ttf) format("truetype", "opentype"), url(\'404.eot\')', + 'url(404.ttf) format("truetype", "unknown"), local(Times New Roman), url(\'404.eot\')', + ], + invalid_values: [ + '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", + 'url(404.ttf) format("truetype", "opentype")', + ], + }, + "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+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,U+200-17F", + "U+100-17F,200-27F", + "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/empty.html b/layout/style/test/empty.html new file mode 100644 index 0000000000..734c5a1c09 --- /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 0000000000..9d4dfa1fed --- /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_omta_scroll.html b/layout/style/test/file_animations_omta_scroll.html new file mode 100644 index 0000000000..625b4b6c19 --- /dev/null +++ b/layout/style/test/file_animations_omta_scroll.html @@ -0,0 +1,392 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <title>Test for css-animations running on the compositor thread with scroll-timeline</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> + <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <style type="text/css"> + @keyframes transform_anim { + from { transform: translate(50px); } + to { transform: translate(150px); } + } + + @keyframes always_fifty { + from, to { transform: translate(50px); } + } + + @keyframes geometry { + from { width: 50px; } + to { width: 100px; } + } + + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: green; + } + + .scroller { + width: 100px; + height: 100px; + overflow: scroll; + scroll-timeline-name: scroll_timeline; + } + + .content { + block-size: 100%; + padding-block-end: 100px; + } + </style> +</head> +<body> + <div id="display"></div> + <pre id="test"></pre> +</body> +<script type="application/javascript"> +"use strict"; + +// Global state +var gScroller = null; +var 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.from(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; +}; + +// Bind the ok and todo to the opener, and close this window when we finish. +var ok = opener.ok.bind(opener); +var todo = opener.todo.bind(opener); +function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); +} + +function new_scroller() { + gScroller = document.createElement('div'); + gScroller.className = `scroller`; + + let content = document.createElement('div'); + content.className = 'content'; + + gScroller.appendChild(content); + document.getElementById("display").appendChild(gScroller); + return gScroller; +} + +function done_scroller() { + gScroller.firstChild.remove(); + gScroller.remove(); + gScroller = null; +} + +waitUntilApzStable().then(() => { + runOMTATest(function() { + var onAbort = function() { + if (gDiv) { + done_div(); + } + if (gScroller) { + done_scroller(); + } + }; + runAllAsyncAnimTests(onAbort).then(finish); + }, finish); +}); + +//---------------------------------------------------------------------- +// +// Test cases +// +//---------------------------------------------------------------------- + +// The non-omta property with scroll-timeline together with an omta property +// with document-timeline. +addAsyncAnimTest(async function() { + new_scroller(); + new_div("animation: geometry 10s scroll_timeline, always_fifty 1s infinite;"); + await waitForPaintsFlushed(); + + // Note: width is not a OMTA property, so it must be running on the main + // thread. + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "transform animations should runs on compositor thread"); + + done_div(); + done_scroller(); +}); + +// transform property with scroll-driven animations. +addAsyncAnimTest(async function() { + let scroller = new_scroller(); + new_div("animation: transform_anim 1s linear scroll_timeline;"); + await waitForPaintsFlushed(); + + scroller.scrollTop = 50; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor, + "scroll transform animations should runs on compositor " + + "thread"); + + done_div(); + done_scroller(); +}); + + +// The scroll-driven animation with an underlying value and make it go from the +// active phase to the before phase. +addAsyncAnimTest(async function() { + let scroller = new_scroller(); + new_div("animation: always_fifty 5s linear 5s scroll_timeline; " + + "transform: translate(25px);"); + await waitForPaintsFlushed(); + + // NOTE: getOMTAStyle() can't detect the animation is running on the + // compositor during the delay phase. + omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either, + "The scroll animation is in delay"); + + scroller.scrollTop = 75; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor, + "scroll transform animations should runs on compositor " + + "thread"); + + // Use setAsyncScrollOffset() to update apz (compositor thread only) to make + // sure Bug 1776077 is reproducible. + let utils = SpecialPowers.wrap(window).windowUtils; + utils.setAsyncScrollOffset(scroller, 0, -50); + utils.advanceTimeAndRefresh(16); + utils.restoreNormalRefresh(); + await waitForPaints(); + + // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the + // OMTA style directly. + let compositorStr = + SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); + ok(compositorStr === "matrix(1, 0, 0, 1, 25, 0)", + "scroll animations is in delay phase before calling main thread style " + + "udpate"); + + scroller.scrollTop = 25; + await waitForPaintsFlushed(); + + compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); + ok(compositorStr === "", + "scroll animation in delay phase clears its OMTA style"); + omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either, + "The scroll animation is in delay"); + + done_div(); + done_scroller(); +}); + +// The scroll-driven animation without an underlying value and make it go from +// the active phase to the before phase. +addAsyncAnimTest(async function() { + let scroller = new_scroller(); + new_div("animation: always_fifty 5s linear 5s scroll_timeline; "); + await waitForPaintsFlushed(); + + // NOTE: getOMTAStyle() can't detect the animation is running on the + // compositor during the delay phase. + omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.Either, + "The scroll animation is in delay"); + + scroller.scrollTop = 75; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor, + "scroll transform animations should runs on compositor " + + "thread"); + + // Use setAsyncScrollOffset() to update apz (compositor thread only) to make + // sure Bug 1776077 is reproducible. + let utils = SpecialPowers.wrap(window).windowUtils; + utils.setAsyncScrollOffset(scroller, 0, -50); + utils.advanceTimeAndRefresh(16); + utils.restoreNormalRefresh(); + await waitForPaints(); + + // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the + // OMTA style directly. + let compositorStr = + SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); + ok(compositorStr === "matrix(1, 0, 0, 1, 0, 0)", + "scroll animations is in delay phase before calling main thread style " + + "udpate"); + + done_div(); + done_scroller(); +}); + +// The scroll-driven animation is in delay, together with other runing +// animations. +addAsyncAnimTest(async function() { + let scroller = new_scroller(); + new_div("animation: transform_anim 10s linear -5s paused, " + + " always_fifty 5s linear 5s scroll_timeline;"); + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor, + "The scroll animation is in delay"); + + scroller.scrollTop = 75; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor, + "scroll transform animations should runs on compositor " + + "thread"); + + let utils = SpecialPowers.wrap(window).windowUtils; + utils.setAsyncScrollOffset(scroller, 0, -50); + utils.advanceTimeAndRefresh(16); + utils.restoreNormalRefresh(); + await waitForPaints(); + + // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the + // OMTA style directly. + let compositorStr = + SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); + ok(compositorStr === "matrix(1, 0, 0, 1, 100, 0)", + "scroll animations is in delay phase before calling main thread style " + + "udpate"); + + done_div(); + done_scroller(); +}); + +addAsyncAnimTest(async function() { + let iframe = document.createElement("iframe"); + iframe.setAttribute("style", "width: 200px; height: 200px"); + iframe.setAttribute("scrolling", "no"); + iframe.srcdoc = + "<!DOCTYPE HTML>" + + "<html style='min-height: 100%; padding-bottom: 100px;'>" + + "<style>" + + "@keyframes anim { from, to { transform: translate(50px) } }" + + "</style>" + + "<div id='target_in_iframe' " + + " style='width:50px; height:50px; background:green;" + + " animation: anim 10s linear scroll(root);'>" + + "</div>" + + "</html>"; + + await new Promise(resolve => { + iframe.onload = resolve; + document.body.appendChild(iframe); + }); + + gDiv = iframe.contentDocument.getElementById("target_in_iframe"); + + const root = iframe.contentDocument.scrollingElement; + const maxScroll = root.scrollHeight - root.clientHeight; + root.scrollTop = 0.5 * maxScroll; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 50 }, 0, RunningOn.MainThread, + "scroll transform animations inside an iframe with " + + "scrolling:no should run on the main thread"); + + gDiv = null; + iframe.remove(); +}); + +// FIXME: Bug 1818346. Support OMTA for view-timeline. +addAsyncAnimTest(async function() { + let scroller = document.createElement("div"); + scroller.style.width = "100px"; + scroller.style.height = "100px"; + // Use hidden so we don't have scrollbar, to make sure the scrollport size + // is 100px x 100px, so view progress visibility range is 100px on block axis. + scroller.style.overflow = "hidden"; + + let content1 = document.createElement("div"); + content1.style.height = "150px"; + scroller.appendChild(content1); + + let subject = document.createElement("div"); + subject.style.width = "50px"; + subject.style.height = "50px"; + subject.style.viewTimelineName = "view_timeline"; + scroller.appendChild(subject); + + // Let |target| be the child of |subject|, so view-timeline-name property of + // |subject| is referenceable. + let target = document.createElement("div"); + target.style.width = "10px"; + target.style.height = "10px"; + subject.appendChild(target); + gDiv = target; + + let content2 = document.createElement("div"); + content2.style.height = "150px"; + scroller.appendChild(content2); + + // So the DOM tree looks like this: + // <div class=scroller> <!-- "scroller", height: 100px; --> + // <div></div> <!-- "", height: 150px --> + // <div></div> <!-- "subject", height: 50px; --> + // <div></div> <!-- "", height: 150px; --> + // </div> + // The subject is in view when scroller.scrollTop is [50px, 200px]. + document.getElementById("display").appendChild(scroller); + await waitForPaintsFlushed(); + + scroller.scrollTop = 0; + target.style.animation = "transform_anim 10s linear"; + target.style.animationTimeline = "view_timeline"; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.OnMainThread, + "The scroll animation is out of view"); + + scroller.scrollTop = 50; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.OnMainThread, + "The scroll animation is 0%"); + + scroller.scrollTop = 125; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.OnMainThread, + "The scroll animation is 50%"); + + target.remove(); + content2.remove(); + subject.remove(); + content1.remove(); + scroller.remove(); + gDiv = null; +}); + +</script> +</html> diff --git a/layout/style/test/file_animations_omta_scroll_rtl.html b/layout/style/test/file_animations_omta_scroll_rtl.html new file mode 100644 index 0000000000..771cf6c38f --- /dev/null +++ b/layout/style/test/file_animations_omta_scroll_rtl.html @@ -0,0 +1,112 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> + <title>Test for css-animations running on the compositor thread with + scroll-timeline and right to left writing mode</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> + <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> + <script type="application/javascript" src="animation_utils.js"></script> + <style type="text/css"> + @keyframes transform_anim { + from { transform: translateX(-100px) } + to { transform: translateX(-200px) } + } + + html { + min-width: 100%; + padding-left: 100px; + direction: rtl; + } + + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: green; + } + </style> +</head> +<body> + <div id="display"></div> + <pre id="test"></pre> +</body> +<script type="application/javascript"> +"use strict"; + +// Global state +var 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.from(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; +}; + +// Bind the ok and todo to the opener, and close this window when we finish. +var ok = opener.ok.bind(opener); +var todo = opener.todo.bind(opener); +function finish() { + var o = opener; + self.close(); + o.SimpleTest.finish(); +} + +waitUntilApzStable().then(() => { + runOMTATest(function() { + var onAbort = function() { + if (gDiv) { + done_div(); + } + }; + runAllAsyncAnimTests(onAbort).then(finish); + }, finish); +}); + +//---------------------------------------------------------------------- +// +// Test cases +// +//---------------------------------------------------------------------- + +// transform property with scroll-driven animations. The writing mode is from +// right to left, so we have to use the negative scroll offset. +addAsyncAnimTest(async function() { + new_div("animation: transform_anim 1s linear scroll(horizontal root);"); + await waitForPaintsFlushed(); + + const root = document.scrollingElement; + const maxScroll = root.scrollWidth - root.clientWidth; + root.scrollLeft = -0.5 * maxScroll; + await waitForPaintsFlushed(); + + omta_is_approx("transform", { tx: -150 }, 0.1, RunningOn.Compositor, + "scroll transform animations should runs on compositor " + + "thread"); + + root.scrollLeft = 0; + done_div(); +}); + +</script> +</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 0000000000..5b206df693 --- /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; + overflow-clip-box: padding-box; + } + to { + left: 100px; + overflow-clip-box: padding-box; + } + } + </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('overflowClipBox'), + '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('overflowClipBox'), + '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 0000000000..3223a56900 --- /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 0000000000..95208dbc56 --- /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_bug1375944.html b/layout/style/test/file_bug1375944.html new file mode 100644 index 0000000000..809ea4205b --- /dev/null +++ b/layout/style/test/file_bug1375944.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<style> +@font-face { + font-family: Ahem; + src: url(Ahem.ttf); +} +#test { + display: inline-block; + font: 64px/1 Ahem; + margin-right: 1ex; +} +</style> +<div id="test">X</div> diff --git a/layout/style/test/file_bug1381233.html b/layout/style/test/file_bug1381233.html new file mode 100644 index 0000000000..b82d309128 --- /dev/null +++ b/layout/style/test/file_bug1381233.html @@ -0,0 +1,4 @@ +<link rel="stylesheet" href="somestylesheet"> +<table><td> +<embed src="whatever" type="application/x-shockwave-flash"></embed> +</td></table> diff --git a/layout/style/test/file_bug1443344.css b/layout/style/test/file_bug1443344.css new file mode 100644 index 0000000000..74e81d1823 --- /dev/null +++ b/layout/style/test/file_bug1443344.css @@ -0,0 +1 @@ +#importTarget { color: red ! important } diff --git a/layout/style/test/file_bug645998-1.css b/layout/style/test/file_bug645998-1.css new file mode 100644 index 0000000000..328e6ed797 --- /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 0000000000..2d5edbe217 --- /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 0000000000..8f12ba6f56 --- /dev/null +++ b/layout/style/test/file_bug829816.css diff --git a/layout/style/test/file_computed_style_bfcache_display_none.html b/layout/style/test/file_computed_style_bfcache_display_none.html new file mode 100644 index 0000000000..d366aa68ee --- /dev/null +++ b/layout/style/test/file_computed_style_bfcache_display_none.html @@ -0,0 +1,6 @@ +<div id=div style="display:none">Page 1</div> +<script> + window.onload = function() { + opener.postMessage("loaded", "*"); + } +</script> diff --git a/layout/style/test/file_computed_style_bfcache_display_none2.html b/layout/style/test/file_computed_style_bfcache_display_none2.html new file mode 100644 index 0000000000..c337ca7214 --- /dev/null +++ b/layout/style/test/file_computed_style_bfcache_display_none2.html @@ -0,0 +1,6 @@ +<div>Page 2</div> +<script> + window.onload = function() { + opener.postMessage("loaded", "*"); + } +</script>; 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 0000000000..51dbbbee91 --- /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_shared_sheet_caching.css b/layout/style/test/file_shared_sheet_caching.css new file mode 100644 index 0000000000..2ceb1b7e0b --- /dev/null +++ b/layout/style/test/file_shared_sheet_caching.css @@ -0,0 +1,3 @@ +:root { + background-color: lime; +} diff --git a/layout/style/test/file_shared_sheet_caching.html b/layout/style/test/file_shared_sheet_caching.html new file mode 100644 index 0000000000..1eb9a8ce31 --- /dev/null +++ b/layout/style/test/file_shared_sheet_caching.html @@ -0,0 +1,12 @@ +<!doctype html> +<meta charset="utf-8"> +<link rel="stylesheet" href="file_shared_sheet_caching.css"> +<script> +onload = function() { + if (parent != window) { + parent.childWindowLoaded(window); + } else { + window.opener.childWindowLoaded(window); + } +} +</script> diff --git a/layout/style/test/file_specified_value_serialization_individual_transforms.html b/layout/style/test/file_specified_value_serialization_individual_transforms.html new file mode 100644 index 0000000000..9dcf85f955 --- /dev/null +++ b/layout/style/test/file_specified_value_serialization_individual_transforms.html @@ -0,0 +1,65 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test for Bug 1207734 (individual transforms)</title> +<!-- + FIXME: This is only here in a separate file since it needs the + layout.css.individual-transform.enabled pref to be set when it runs and the + pref= annotation in mochitest.ini doesn't work on Android (bug 1393326). + Once we turn on that pref by default or fix bug 1393326 we can move this back + into test_specified_value_serialization.html. +--> +<script> +const is = opener.is.bind(opener); +function finish() { + const o = opener; + self.close(); + o.SimpleTest.finish(); +} + +function runTest() { + // Test for rotate property serialization. + [ + [" 90deg ", "90deg"], + [" 100grad ", "100grad"], + [" 100gRaD ", "100grad"], + [" 0.25turn ", "0.25turn"], + [" 0.25tUrN ", "0.25turn"], + [" 1.57RaD ", "1.57rad"], + ].forEach(function(arr) { + document.documentElement.style.rotate = arr[0]; + is(document.documentElement.style.rotate, arr[1], + "bug-1207734: incorrect rotate serialization"); + }); + document.documentElement.style.rotate = ""; + + // Test for translate property serialization. + [ + [" 50% 5px 6px ", "50% 5px 6px"], + [" 50% 10px 100px ", "50% 10px 100px"], + [" 4px 5px ", "4px 5px"], + [" 10% 10% 99px ", "10% 10% 99px"], + [" 50px ", "50px"], + ].forEach(function(arr) { + document.documentElement.style.translate = arr[0]; + is(document.documentElement.style.translate, arr[1], + "bug-1207734: incorrect translate serialization"); + }); + document.documentElement.style.translate = ""; + + // Test for scale property serialization. + [ + [" 10 ", "10"], + [" 10 20.5 ", "10 20.5"], + [" 10 20 30 ", "10 20 30"], + ].forEach(function(arr) { + document.documentElement.style.scale = arr[0]; + is(document.documentElement.style.scale, arr[1], + "bug-1207734: incorrect scale serialization"); + }); + + document.documentElement.style.scale = ""; +} + +runTest(); +finish(); +</script> diff --git a/layout/style/test/flexbox_layout_testcases.js b/layout/style/test/flexbox_layout_testcases.js new file mode 100644 index 0000000000..c8990877ab --- /dev/null +++ b/layout/style/test/flexbox_layout_testcases.js @@ -0,0 +1,1317 @@ +/* -*- 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/. */ + +/** + * 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 0000000000..1d6fad226d --- /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/. + +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/gtest/ImportScannerTest.cpp b/layout/style/test/gtest/ImportScannerTest.cpp new file mode 100644 index 0000000000..c0b43221dd --- /dev/null +++ b/layout/style/test/gtest/ImportScannerTest.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "gtest/gtest.h" +#include "mozilla/ImportScanner.h" +#include "mozilla/StaticPrefs_layout.h" + +using namespace mozilla; + +static nsTArray<nsString> Scan(const char* aCssCode) { + nsTArray<nsString> urls; + ImportScanner scanner; + scanner.Start(); + urls.AppendElements(scanner.Scan(NS_ConvertUTF8toUTF16(aCssCode))); + urls.AppendElements(scanner.Stop()); + return urls; +} + +TEST(ImportScanner, Simple) +{ + auto urls = Scan( + "/* Something something */ " + "@charset \"utf-8\";" + "@import url(bar);" + "@import uRL( baz );" + "@import \"bazz)\""); + + ASSERT_EQ(urls.Length(), 3u); + ASSERT_EQ(urls[0], u"bar"_ns); + ASSERT_EQ(urls[1], u"baz"_ns); + ASSERT_EQ(urls[2], u"bazz)"_ns); +} + +TEST(ImportScanner, UrlWithQuotes) +{ + auto urls = Scan( + "/* Something something */ " + "@import url(\"bar\");" + "@import\tuRL( \"baz\" );" + "@imPort\turL( 'bazz' );" + "something else {}" + "@import\turL( 'bazz' ); "); + + ASSERT_EQ(urls.Length(), 3u); + ASSERT_EQ(urls[0], u"bar"_ns); + ASSERT_EQ(urls[1], u"baz"_ns); + ASSERT_EQ(urls[2], u"bazz"_ns); +} + +TEST(ImportScanner, MediaIsIgnored) +{ + auto urls = Scan( + "@chArset \"utf-8\";" + "@import url(\"bar\") print;" + "/* Something something */ " + "@import\tuRL( \"baz\" ) (min-width: 100px);" + "@import\turL( bazz ) (max-width: 100px);"); + + ASSERT_EQ(urls.Length(), 3u); + ASSERT_EQ(urls[0], u"bar"_ns); + ASSERT_EQ(urls[1], u"baz"_ns); + ASSERT_EQ(urls[2], u"bazz"_ns); +} + +TEST(ImportScanner, Layers) +{ + auto urls = Scan( + "@layer foo, bar;\n" + "@import url(\"bar\") layer(foo);" + "@import url(\"baz\");" + "@import url(bazz);" + "@layer block {}" + // This one below is invalid now and shouldn't be scanned. + "@import\turL( 'bazzz' ); "); + + ASSERT_EQ(urls.Length(), 3u); + ASSERT_EQ(urls[0], u"bar"_ns); + ASSERT_EQ(urls[1], u"baz"_ns); + ASSERT_EQ(urls[2], u"bazz"_ns); +} + +TEST(ImportScanner, Supports) +{ + auto urls = Scan( + // Supported feature, should be included. + "@import url(bar) supports(display: block);" + // Unsupported feature, should not be included. + "@import url(baz) supports(foo: bar);" + // Supported condition with operator, should be included. + "@import url(bazz) supports((display: flex) and (display: block));" + // Unsupported condition with function, should be not included. + "@import url(bazzz) supports(selector(foo:bar(baz)));" + // Supported large condition with layer, supports, media list + "@import url(bazzzz) layer(A.B) supports(display: flex) (max-width: " + "100px)"); + + if (StaticPrefs::layout_css_import_supports_enabled()) { + // If the pref is enabled, expect the supports conditions to be evaluated + // and unsupported to not be emitted. + + ASSERT_EQ(urls.Length(), 3u); + ASSERT_EQ(urls[0], u"bar"_ns); + ASSERT_EQ(urls[1], u"bazz"_ns); + ASSERT_EQ(urls[2], u"bazzzz"_ns); + } else { + // If disabled, all imports should be included as the supports conditions + // should be ignored. + + ASSERT_EQ(urls.Length(), 5u); + ASSERT_EQ(urls[0], u"bar"_ns); + ASSERT_EQ(urls[1], u"baz"_ns); + ASSERT_EQ(urls[2], u"bazz"_ns); + ASSERT_EQ(urls[3], u"bazzz"_ns); + ASSERT_EQ(urls[4], u"bazzzz"_ns); + } +} diff --git a/layout/style/test/gtest/StyloParsingBench.cpp b/layout/style/test/gtest/StyloParsingBench.cpp new file mode 100644 index 0000000000..4c14ebed7c --- /dev/null +++ b/layout/style/test/gtest/StyloParsingBench.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "gtest/gtest.h" +#include "gtest/MozGTestBench.h" +#include "nsString.h" +#include "ExampleStylesheet.h" +#include "ServoBindings.h" +#include "mozilla/dom/DOMString.h" +#include "mozilla/Encoding.h" +#include "mozilla/Utf8.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/css/SheetParsingMode.h" +#include "ReferrerInfo.h" +#include "nsCSSValue.h" +#include "ReferrerInfo.h" + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::dom; +using namespace mozilla::net; + +// Bug 1436018 - Disable Stylo microbenchmark on Windows +#if !defined(_WIN32) && !defined(_WIN64) + +# define PARSING_REPETITIONS 20 +# define SETPROPERTY_REPETITIONS (1000 * 1000) +# define GETPROPERTY_REPETITIONS (1000 * 1000) + +static void ServoParsingBench(const StyleUseCounters* aCounters) { + auto css = AsBytes(MakeStringSpan(EXAMPLE_STYLESHEET)); + nsCString cssStr; + cssStr.Append(css); + ASSERT_EQ(Encoding::UTF8ValidUpTo(css), css.Length()); + + RefPtr<nsIURI> uri = NullPrincipal::CreateURI(); + nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr); + RefPtr<URLExtraData> data = + new URLExtraData(uri.forget(), referrerInfo.forget(), + NullPrincipal::CreateWithoutOriginAttributes()); + for (int i = 0; i < PARSING_REPETITIONS; i++) { + RefPtr<StyleStylesheetContents> stylesheet = + Servo_StyleSheet_FromUTF8Bytes( + nullptr, nullptr, nullptr, &cssStr, eAuthorSheetFeatures, data, + eCompatibility_FullStandards, nullptr, aCounters, + StyleAllowImportRules::Yes, StyleSanitizationKind::None, nullptr) + .Consume(); + } +} + +static constexpr auto STYLE_RULE = StyleCssRuleType::Style; + +static void ServoSetPropertyByIdBench(const nsACString& css) { + RefPtr<StyleLockedDeclarationBlock> block = + Servo_DeclarationBlock_CreateEmpty().Consume(); + RefPtr<nsIURI> uri = NullPrincipal::CreateURI(); + nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr); + RefPtr<URLExtraData> data = + new URLExtraData(uri.forget(), referrerInfo.forget(), + NullPrincipal::CreateWithoutOriginAttributes()); + ASSERT_TRUE(IsUtf8(css)); + + for (int i = 0; i < SETPROPERTY_REPETITIONS; i++) { + Servo_DeclarationBlock_SetPropertyById( + block, eCSSProperty_width, &css, + /* is_important = */ false, data, StyleParsingMode::DEFAULT, + eCompatibility_FullStandards, nullptr, STYLE_RULE, {}); + } +} + +static void ServoGetPropertyValueById() { + RefPtr<StyleLockedDeclarationBlock> block = + Servo_DeclarationBlock_CreateEmpty().Consume(); + + RefPtr<nsIURI> uri = NullPrincipal::CreateURI(); + nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(nullptr); + RefPtr<URLExtraData> data = + new URLExtraData(uri.forget(), referrerInfo.forget(), + NullPrincipal::CreateWithoutOriginAttributes()); + constexpr auto css_ = "10px"_ns; + const nsACString& css = css_; + Servo_DeclarationBlock_SetPropertyById( + block, eCSSProperty_width, &css, + /* is_important = */ false, data, StyleParsingMode::DEFAULT, + eCompatibility_FullStandards, nullptr, STYLE_RULE, {}); + + for (int i = 0; i < GETPROPERTY_REPETITIONS; i++) { + nsAutoCString value; + Servo_DeclarationBlock_GetPropertyValueById(block, eCSSProperty_width, + &value); + ASSERT_TRUE(value.EqualsLiteral("10px")); + } +} + +MOZ_GTEST_BENCH(Stylo, Servo_StyleSheet_FromUTF8Bytes_Bench, + [] { ServoParsingBench(nullptr); }); + +MOZ_GTEST_BENCH(Stylo, Servo_StyleSheet_FromUTF8Bytes_Bench_UseCounters, [] { + UniquePtr<StyleUseCounters> counters(Servo_UseCounters_Create()); + ServoParsingBench(counters.get()); +}); + +MOZ_GTEST_BENCH(Stylo, Servo_DeclarationBlock_SetPropertyById_Bench, + [] { ServoSetPropertyByIdBench("10px"_ns); }); + +MOZ_GTEST_BENCH(Stylo, + Servo_DeclarationBlock_SetPropertyById_WithInitialSpace_Bench, + [] { ServoSetPropertyByIdBench(" 10px"_ns); }); + +MOZ_GTEST_BENCH(Stylo, Servo_DeclarationBlock_GetPropertyById_Bench, + ServoGetPropertyValueById); + +#endif diff --git a/layout/style/test/gtest/example.css b/layout/style/test/gtest/example.css new file mode 100644 index 0000000000..12a0fb9a4f --- /dev/null +++ b/layout/style/test/gtest/example.css @@ -0,0 +1,2925 @@ +/* Copied from devtools/client/debugger/debugger.css on 2017-04-03 */ + +.landing-page { + flex: 1; + display: flex; + width: 100vw; + height: 100vh; + flex-direction: row; + align-items: stretch; + /* Customs properties */ + --title-font-size: 24px; + --ui-element-font-size: 16px; + --primary-line-height: 30px; + --secondary-line-height: 25px; + --base-spacing: 20px; + --base-transition: all 0.25s ease; +} + +.landing-popup { + min-width: 0; +} + +.landing-page .panel { + display: flex; + flex: 1; + flex-direction: column; + justify-content: space-between; +} + +.landing-page .panel header { + display: flex; + align-items: baseline; + margin: calc(2 * var(--base-spacing)) 0 0; + padding-bottom: var(--base-spacing); +} + +.landing-page .panel header input[type=search] { + flex: 1; + background-color: var(--theme-tab-toolbar-background); + color: var(--theme-comment); + font-size: var(--ui-element-font-size); + border: 1px solid var(--theme-splitter-color); + padding: calc(var(--base-spacing) / 2); + margin: 0 var(--base-spacing); + transition: var(--base-transition); +} + +.landing-page .panel header input[type=button] { + background-color: var(--theme-tab-toolbar-background); + color: var(--theme-comment); + font-size: var(--ui-element-font-size); + border: 1px solid var(--theme-splitter-color); + padding: calc(var(--base-spacing) / 2); + margin: 0 var(--base-spacing); + transition: var(--base-transition); +} + +.landing-page .panel header h1 { + color: var(--theme-body-color); + font-size: var(--title-font-size); + margin: 0; + line-height: var(--primary-line-height); + font-weight: normal; + padding-left: calc(2 * var(--base-spacing)); +} + +.landing-page .panel h3 { + padding-left: calc(2 * var(--base-spacing)); +} + +.landing-page .panel header input::placeholder { + color: var(--theme-body-color-inactive); +} + +.landing-page .panel header input:focus { + border: 1px solid var(--theme-selection-background); +} + +.landing-page .panel .center-message { + font-size: var(--ui-element-font-size); + line-height: var(--secondary-line-height); + padding: calc(var(--base-spacing) / 2); +} + +.landing-page .center a { + color: var(--theme-highlight-bluegrey); + text-decoration: none; +} + +.landing-page .panel .footer-note { + padding: var(--base-spacing) 0; + text-align: center; + font-size: 14px; + color: var(--theme-comment); +} +.landing-page .tab-group { + flex: 1; + overflow-y: auto; +} + +.landing-page .tab-list { + list-style: none; + padding: 0; + margin: 0; +} + +.landing-page .tab { + border-bottom: 1px solid var(--theme-splitter-color); + padding: calc(var(--base-spacing) / 2) var(--base-spacing); + font-family: sans-serif; +} + +.landing-page .tab-sides { + display: flex; + justify-content: space-between; + margin: 0 calc(var(--base-spacing) * 2); +} + +.landing-page .tab-title { + line-height: var(--secondary-line-height); + font-size: var(--ui-element-font-size); + color: var(--theme-highlight-bluegrey); + word-break: break-all; +} + +.landing-page .tab-url { + color: var(--theme-comment); + word-break: break-all; +} + +.landing-page .tab-value { + color: var(--theme-comment); + line-height: var(--secondary-line-height); + font-size: var(--ui-element-font-size); +} + +.landing-page .tab:focus, +.landing-page .tab.active { + background: var(--theme-selection-background); + color: var(--theme-selection-color); + cursor: pointer; + transition: var(--base-transition); +} + +.landing-page .tab:focus .tab-title, +.landing-page .tab.active .tab-title { + color: inherit; +} + +.landing-page .tab:focus .tab-url, +.landing-page .tab.active .tab-url { + color: var(--theme-highlight-gray); +} +.landing-page .sidebar { + display: flex; + background-color: var(--theme-tab-toolbar-background); + width: 200px; + flex-direction: column; + border-right: 1px solid var(--theme-splitter-color); +} + +.landing-page .sidebar h1 { + color: var(--theme-body-color); + font-size: var(--title-font-size); + margin: 0; + line-height: var(--primary-line-height); + font-weight: normal; + padding: calc(2 * var(--base-spacing)) var(--base-spacing); +} + +.landing-page .sidebar ul { + list-style: none; + padding: 0; + line-height: var(--primary-line-height); + font-size: var(--ui-element-font-size); +} + +.landing-page .sidebar li { + padding: calc(var(--base-spacing) / 4) var(--base-spacing); +} + +.landing-page .sidebar li a { + color: var(--theme-body-color); +} + +.landing-page .sidebar li.selected { + background: var(--theme-highlight-bluegrey); + color: var(--theme-selection-color); + transition: var(--base-transition); +} + +.landing-page .sidebar li.selected a { + color: inherit; +} + +.landing-page .sidebar li:hover, +.landing-page .sidebar li:focus { + background: var(--theme-selection-background); + color: var(--theme-selection-color); + cursor: pointer; +} + +.landing-page .sidebar li:hover a, +.landing-page .sidebar li:focus a { + color: inherit; +} +:root.theme-light, +:root .theme-light { + --theme-search-overlays-semitransparent: rgba(221, 225, 228, 0.66); +} + +* { + box-sizing: border-box; +} + +html, +body { + height: 100%; + margin: 0; + padding: 0; + width: 100%; +} + +#mount { + display: flex; + height: 100%; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; + background: transparent; +} + +::-webkit-scrollbar-track { + border-radius: 8px; + background: transparent; +} + +::-webkit-scrollbar-thumb { + border-radius: 8px; + background: rgba(113, 113, 113, 0.5); +} + +:root.theme-dark .CodeMirror-scrollbar-filler { + background: transparent; +} +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0 !important; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-rulers { + position: absolute; + left: 0; right: 0; top: -50px; bottom: -20px; + overflow: hidden; +} +.CodeMirror-ruler { + border-left: 1px solid #ccc; + top: 0; bottom: 0; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -30px; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } +/* 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/. */ + +:root { + /* --breakpoint-background: url("chrome://devtools/skin/images/breakpoint.svg#light"); */ + /* --breakpoint-hover-background: url("chrome://devtools/skin/images/breakpoint.svg#light-hover"); */ + --breakpoint-active-color: rgba(44,187,15,.2); + --breakpoint-active-color-hover: rgba(44,187,15,.5); + /* --breakpoint-conditional-background: url("chrome://devtools/skin/images/breakpoint.svg#light-conditional"); */ +} + +.theme-dark:root { + /* --breakpoint-background: url("chrome://devtools/skin/images/breakpoint.svg#dark"); */ + /* --breakpoint-hover-background: url("chrome://devtools/skin/images/breakpoint.svg#dark-hover"); */ + --breakpoint-active-color: rgba(0,255,175,.4); + --breakpoint-active-color-hover: rgba(0,255,175,.7); + /* --breakpoint-conditional-background: url("chrome://devtools/skin/images/breakpoint.svg#dark-conditional"); */ +} + +.CodeMirror .errors { + width: 16px; +} + +.CodeMirror .error { + display: inline-block; + margin-left: 5px; + width: 12px; + height: 12px; + background-repeat: no-repeat; + background-position: center; + background-size: contain; + /* background-image: url("chrome://devtools/skin/images/editor-error.png"); */ + opacity: 0.75; +} + +.CodeMirror .hit-counts { + width: 6px; +} + +.CodeMirror .hit-count { + display: inline-block; + height: 12px; + border: solid rgba(0,0,0,0.2); + border-width: 1px 1px 1px 0; + border-radius: 0 3px 3px 0; + padding: 0 3px; + font-size: 10px; + pointer-events: none; +} + +.CodeMirror-linenumber:before { + content: " "; + display: block; + width: calc(100% - 3px); + position: absolute; + top: 1px; + left: 0; + height: 12px; + z-index: -1; + background-size: calc(100% - 2px) 12px; + background-repeat: no-repeat; + background-position: right center; + padding-inline-end: 9px; +} + +.breakpoint .CodeMirror-linenumber { + color: var(--theme-body-background); +} + +.breakpoint .CodeMirror-linenumber:before { + background-image: var(--breakpoint-background) !important; +} + +.conditional .CodeMirror-linenumber:before { + background-image: var(--breakpoint-conditional-background) !important; +} + +.debug-line .CodeMirror-linenumber { + background-color: var(--breakpoint-active-color); +} + +.theme-dark .debug-line .CodeMirror-linenumber { + color: #c0c0c0; +} + +.debug-line .CodeMirror-line { + background-color: var(--breakpoint-active-color) !important; +} + +/* Don't display the highlight color since the debug line + is already highlighted */ +.debug-line .CodeMirror-activeline-background { + display: none; +} + +.CodeMirror { + cursor: text; + height: 100%; +} + +.CodeMirror-gutters { + cursor: default; +} + +/* This is to avoid the fake horizontal scrollbar div of codemirror to go 0 +height when floating scrollbars are active. Make sure that this value is equal +to the maximum of `min-height` specific to the `scrollbar[orient="horizontal"]` +selector in floating-scrollbar-light.css across all platforms. */ +.CodeMirror-hscrollbar { + min-height: 10px; +} + +/* This is to avoid the fake vertical scrollbar div of codemirror to go 0 +width when floating scrollbars are active. Make sure that this value is equal +to the maximum of `min-width` specific to the `scrollbar[orient="vertical"]` +selector in floating-scrollbar-light.css across all platforms. */ +.CodeMirror-vscrollbar { + min-width: 10px; +} + +.cm-trailingspace { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg=="); + opacity: 0.75; + background-position: left bottom; + background-repeat: repeat-x; +} + +.cm-highlight { + position: relative; +} + +.cm-highlight:before { + position: absolute; + border-top-style: solid; + border-bottom-style: solid; + border-top-color: var(--theme-comment-alt); + border-bottom-color: var(--theme-comment-alt); + border-top-width: 1px; + border-bottom-width: 1px; + top: -1px; + bottom: 0; + left: 0; + right: 0; + content: ""; + margin-bottom: -1px; +} + +.cm-highlight-full:before { + border: 1px solid var(--theme-comment-alt); +} + +.cm-highlight-start:before { + border-left-width: 1px; + border-left-style: solid; + border-left-color: var(--theme-comment-alt); + margin: 0 0 -1px -1px; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} + +.cm-highlight-end:before { + border-right-width: 1px; + border-right-style: solid; + border-right-color: var(--theme-comment-alt); + margin: 0 -1px -1px 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +/* CodeMirror dialogs styling */ + +.CodeMirror-dialog { + padding: 4px 3px; +} + +.CodeMirror-dialog, +.CodeMirror-dialog input { + font: message-box; +} + +/* Fold addon */ + +.CodeMirror-foldmarker { + color: blue; + text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; + font-family: sans-serif; + line-height: .3; + cursor: pointer; +} + +.CodeMirror-foldgutter { + width: 16px; /* Same as breakpoints gutter above */ +} + +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded { + color: #555; + cursor: pointer; +} + +.CodeMirror-foldgutter-open:after { + font-size: 120%; + content: "\25BE"; +} + +.CodeMirror-foldgutter-folded:after { + font-size: 120%; + content: "\25B8"; +} + +.CodeMirror-hints { + position: absolute; + z-index: 10; + overflow: hidden; + list-style: none; + margin: 0; + padding: 2px; + border-radius: 3px; + font-size: 90%; + max-height: 20em; + overflow-y: auto; +} + +.CodeMirror-hint { + margin: 0; + padding: 0 4px; + border-radius: 2px; + max-width: 19em; + overflow: hidden; + white-space: pre; + cursor: pointer; +} + +.CodeMirror-Tern-completion { + padding-inline-start: 22px; + position: relative; + line-height: 18px; +} + +.CodeMirror-Tern-completion:before { + position: absolute; + left: 2px; + bottom: 2px; + border-radius: 50%; + font-size: 12px; + font-weight: bold; + height: 15px; + width: 15px; + line-height: 16px; + text-align: center; + color: #ffffff; + box-sizing: border-box; +} + +.CodeMirror-Tern-completion-unknown:before { + content: "?"; +} + +.CodeMirror-Tern-completion-object:before { + content: "O"; +} + +.CodeMirror-Tern-completion-fn:before { + content: "F"; +} + +.CodeMirror-Tern-completion-array:before { + content: "A"; +} + +.CodeMirror-Tern-completion-number:before { + content: "N"; +} + +.CodeMirror-Tern-completion-string:before { + content: "S"; +} + +.CodeMirror-Tern-completion-bool:before { + content: "B"; +} + +.CodeMirror-Tern-completion-guess { + color: #999; +} + +.CodeMirror-Tern-tooltip { + border-radius: 3px; + padding: 2px 5px; + white-space: pre-wrap; + max-width: 40em; + position: absolute; + z-index: 10; +} + +.CodeMirror-Tern-hint-doc { + max-width: 25em; +} + +.CodeMirror-Tern-farg-current { + text-decoration: underline; +} + +.CodeMirror-Tern-fhint-guess { + opacity: .7; +} +:root.theme-light, +:root .theme-light { + --search-overlays-semitransparent: rgba(221, 225, 228, 0.66); +} + +:root.theme-dark, +:root .theme-dark { + --search-overlays-semitransparent: rgba(42, 46, 56, 0.66); +} +.debugger { + display: flex; + flex: 1; + height: 100%; +} + +.editor-pane { + display: flex; + position: relative; + flex: 1; + background-color: var(--theme-tab-toolbar-background); + height: calc(100% - 1px); + overflow: hidden; +} + +.editor-container { + width: 100%; +} + +.subsettings:hover { + cursor: pointer; +} + +.search-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + z-index: 200; + background-color: var(--search-overlays-semitransparent); +} + +.search-container .close-button { + width: 16px; + margin-top: 25px; + margin-right: 20px; +} +menupopup { + position: fixed; + z-index: 10000; + background: white; + border: 1px solid #cccccc; + padding: 5px 0; + background: #f2f2f2; + border-radius: 5px; + color: #585858; + box-shadow: 0 0 4px 0 rgba(190, 190, 190, 0.8); + min-width: 130px; +} + +menuitem { + display: block; + padding: 0 20px; + line-height: 20px; + font-weight: 500; + font-size: 13px; + user-select: none; +} + +menuitem:hover { + background: #3780fb; + color: white; + cursor: pointer; +} + +menuitem[disabled=true] { + color: #cccccc; +} + +menuitem[disabled=true]:hover { + background-color: transparent; + cursor: default; +} + +menuseparator { + border-bottom: 1px solid #cacdd3; + width: 100%; + height: 5px; + display: block; + margin-bottom: 5px; +} + +#contextmenu-mask.show { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 999; +} +/* 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/. */ + +.split-box { + display: flex; + flex: 1; + min-width: 0; + height: 100%; + width: 100%; +} + +.split-box.vert { + flex-direction: row; +} + +.split-box.horz { + flex-direction: column; +} + +.split-box > .uncontrolled { + display: flex; + flex: 1; + min-width: 0; + overflow: auto; +} + +.split-box > .controlled { + display: flex; + overflow: auto; +} + +.split-box > .splitter { + background-image: none; + border: 0; + border-style: solid; + border-color: transparent; + background-color: var(--theme-splitter-color); + background-clip: content-box; + position: relative; + + box-sizing: border-box; + + /* Positive z-index positions the splitter on top of its siblings and makes + it clickable on both sides. */ + z-index: 1; +} + +.split-box.vert > .splitter { + min-width: calc(var(--devtools-splitter-inline-start-width) + + var(--devtools-splitter-inline-end-width) + 1px); + + border-left-width: var(--devtools-splitter-inline-start-width); + border-right-width: var(--devtools-splitter-inline-end-width); + + margin-left: calc(-1 * var(--devtools-splitter-inline-start-width) - 1px); + margin-right: calc(-1 * var(--devtools-splitter-inline-end-width)); + + cursor: ew-resize; +} + +.split-box.horz > .splitter { + min-height: calc(var(--devtools-splitter-top-width) + + var(--devtools-splitter-bottom-width) + 1px); + + border-top-width: var(--devtools-splitter-top-width); + border-bottom-width: var(--devtools-splitter-bottom-width); + + margin-top: calc(-1 * var(--devtools-splitter-top-width) - 1px); + margin-bottom: calc(-1 * var(--devtools-splitter-bottom-width)); + + cursor: ns-resize; +} + +.split-box.disabled { + pointer-events: none; +} + +/** + * Make sure splitter panels are not processing any mouse + * events. This is good for performance during splitter + * bar dragging. + */ +.split-box.dragging > .controlled, +.split-box.dragging > .uncontrolled { + pointer-events: none; +} +/* 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/. */ + +.theme-dark, +.theme-light { + --number-color: var(--theme-highlight-green); + --string-color: var(--theme-highlight-orange); + --null-color: var(--theme-comment); + --object-color: var(--theme-body-color); + --caption-color: var(--theme-highlight-blue); + --location-color: var(--theme-content-color1); + --source-link-color: var(--theme-highlight-blue); + --node-color: var(--theme-highlight-bluegrey); + --reference-color: var(--theme-highlight-purple); +} + +.theme-firebug { + --number-color: #000088; + --string-color: #FF0000; + --null-color: #787878; + --object-color: DarkGreen; + --caption-color: #444444; + --location-color: #555555; + --source-link-color: blue; + --node-color: rgb(0, 0, 136); + --reference-color: rgb(102, 102, 255); +} + +/******************************************************************************/ + +.objectLink:hover { + cursor: pointer; + text-decoration: underline; +} + +.inline { + display: inline; + white-space: normal; +} + +.objectBox-object { + font-weight: bold; + color: var(--object-color); + white-space: pre-wrap; +} + +.objectBox-string, +.objectBox-text, +.objectLink-textNode, +.objectBox-table { + white-space: pre-wrap; +} + +.objectBox-number, +.objectLink-styleRule, +.objectLink-element, +.objectLink-textNode, +.objectBox-array > .length { + color: var(--number-color); +} + +.objectBox-string { + color: var(--string-color); +} + +.objectLink-function, +.objectBox-stackTrace, +.objectLink-profile { + color: var(--object-color); +} + +.objectLink-Location { + font-style: italic; + color: var(--location-color); +} + +.objectBox-null, +.objectBox-undefined, +.objectBox-hint, +.logRowHint { + font-style: italic; + color: var(--null-color); +} + +.objectLink-sourceLink { + position: absolute; + right: 4px; + top: 2px; + padding-left: 8px; + font-weight: bold; + color: var(--source-link-color); +} + +/******************************************************************************/ + +.objectLink-event, +.objectLink-eventLog, +.objectLink-regexp, +.objectLink-object, +.objectLink-Date { + font-weight: bold; + color: var(--object-color); + white-space: pre-wrap; +} + +/******************************************************************************/ + +.objectLink-object .nodeName, +.objectLink-NamedNodeMap .nodeName, +.objectLink-NamedNodeMap .objectEqual, +.objectLink-NamedNodeMap .arrayLeftBracket, +.objectLink-NamedNodeMap .arrayRightBracket, +.objectLink-Attr .attrEqual, +.objectLink-Attr .attrTitle { + color: var(--node-color); +} + +.objectLink-object .nodeName { + font-weight: normal; +} + +/******************************************************************************/ + +.objectLeftBrace, +.objectRightBrace, +.arrayLeftBracket, +.arrayRightBracket { + cursor: pointer; + font-weight: bold; +} + +.objectLeftBrace, +.arrayLeftBracket { + margin-right: 4px; +} + +.objectRightBrace, +.arrayRightBracket { + margin-left: 4px; +} + +/******************************************************************************/ +/* Cycle reference*/ + +.objectLink-Reference { + font-weight: bold; + color: var(--reference-color); +} + +.objectBox-array > .objectTitle { + font-weight: bold; + color: var(--object-color); +} + +.caption { + font-weight: bold; + color: var(--caption-color); +} + +/******************************************************************************/ +/* Themes */ + +.theme-dark .objectBox-null, +.theme-dark .objectBox-undefined, +.theme-light .objectBox-null, +.theme-light .objectBox-undefined { + font-style: normal; +} + +.theme-dark .objectBox-object, +.theme-light .objectBox-object { + font-weight: normal; + white-space: pre-wrap; +} + +.theme-dark .caption, +.theme-light .caption { + font-weight: normal; +} + +.search-container { + position: absolute; + top: 30px; + left: 0; + width: calc(100% - 1px); + height: calc(100% - 31px); + display: flex; + z-index: 200; + background-color: var(--theme-body-background); +} + +.searchinput-container { + display: flex; + border-bottom: 1px solid var(--theme-splitter-color); +} + +.theme-dark .result-list li .subtitle { + color: var(--theme-comment-alt); +} + +.arrow, +.folder, +.domain, +.file, +.worker, +.refresh, +.add-button { + fill: var(--theme-splitter-color); +} + +.worker, +.folder { + position: relative; + top: 2px; +} + +.domain, +.file, +.worker, +.refresh, +.add-button { + position: relative; + top: 1px; +} + +.domain svg, +.folder svg, +.worker svg, +.refresh svg, +.add-button svg { + width: 15px; +} + +.file svg { + width: 13px; +} + +.file svg, +.domain svg, +.folder svg, +.refresh svg, +.worker svg { + margin-inline-end: 5px; +} + +.arrow svg { + fill: var(--theme-splitter-color); + margin-top: 3px; + transition: transform 0.25s ease; + width: 10px; +} + +html:not([dir="rtl"]) .arrow svg { + margin-right: 5px; + transform: rotate(-90deg); +} + +html[dir="rtl"] .arrow svg { + margin-left: 5px; + transform: rotate(90deg); +} + +/* TODO (Amit): html is just for specificity. keep it like this? */ +html .arrow.expanded svg { + transform: rotate(0deg); +} + +.arrow.hidden { + visibility: hidden; +} +.close-btn path { + fill: var(--theme-comment-alt); +} + +.close-btn .close { + cursor: pointer; + width: 14px; + height: 14px; + padding: 2px; + text-align: center; + margin-top: 2px; + line-height: 7px; + transition: all 0.25s easeinout; +} + +.close-btn .close svg { + width: 8px; +} + +.close-btn:hover { + display: block; +} + +.close-btn:hover .close { + background: var(--theme-selection-background); + border-radius: 2px; +} + +.close-btn:hover .close path { + fill: white; +} + +.close-btn-big { + padding: 11px; + margin-right: 7px; + width: 27px; + height: 40px; +} + +.close-btn-big .close { + cursor: pointer; + display: inline-block; + padding: 2px; + text-align: center; + transition: all 0.25s easeinout; + line-height: 100%; + width: 16px; + height: 16px; +} + +.close-btn-big .close svg { + width: 9px; + height: 9px; +} + +.close-btn-big .close:hover { + border-radius: 2px; +} + +.search-field { + width: calc(100% - 1px); + height: 27px; + background-color: var(--theme-toolbar-background); + border-bottom: 1px solid var(--theme-splitter-color); + padding-right: 10px; + display: flex; + flex-shrink: 0; +} + +.search-field.big { + height: 40px; + font-size: 14px; +} + +.search-field.big input { + font-size: 14px; +} + +.search-field i { + display: block; + padding: 0; + width: 16px; +} + +.search-field i svg { + width: 16px; +} + +.search-field.big input { + line-height: 40px; +} + +.search-field input { + border: none; + line-height: 30px; + background-color: var(--theme-toolbar-background); + color: var(--theme-body-color-inactive); + width: calc(100% - 38px); + flex: 1; +} + +.theme-dark .search-field input { + color: var(--theme-body-color-inactive); +} + +.search-field i.magnifying-glass, +.search-field i.sad-face { + padding: 6px; + width: 24px; +} + +.search-field.big i.magnifying-glass, +.search-field.big i.sad-face { + padding: 14px; + width: 40px; +} + +.search-field .magnifying-glass path, +.search-field .magnifying-glass ellipse { + stroke: var(--theme-splitter-color); +} + +.search-field ::-webkit-input-placeholder { + color: var(--theme-body-color-inactive); +} + +.search-field input::placeholder { + color: var(--theme-body-color-inactive); +} + +.search-field input:focus { + outline-width: 0; +} + +.search-field input.empty { + color: var(--theme-highlight-orange); +} + +.search-field.big .summary { + line-height: 40px; +} + +.search-field .summary { + line-height: 27px; + padding-right: 10px; + color: var(--theme-body-color-inactive); +} + +.result-list { + list-style: none; + width: 100%; + background-color: var(--theme-toolbar-background); + margin: 0px; + padding: 0px; + overflow: auto; +} + +.result-list.big { + max-height: calc(100% - 32px); +} + +.result-list * { + user-select: none; +} + +.result-list li { + color: var(--theme-body-color); + background-color: var(--theme-tab-toolbar-background); + padding: 4px 13px; + display: flex; + justify-content: space-between; +} + +.result-list li:first-child { + border-top: none; +} + +.result-list.big li { + padding: 10px; + flex-direction: column; + border-bottom: 1px solid var(--theme-splitter-color); +} + +.result-list li:hover { + background: var(--theme-tab-toolbar-background); + cursor: pointer; +} + +.result-list li.selected { + border: 1px solid var(--theme-selection-background); +} + +.result-list.big li.selected { + padding-left: 9px; + padding-top: 9px; +} + +.search-bar .result-list li.selected { + background-color: var(--theme-selection-background); + color: white; +} + +.result-list li .title { + line-height: 1.5em; + word-break: break-all; +} + +.result-list li.selected .title { + color: white; +} + +.result-list.big li.selected .title { + color: var(--theme-body-color); +} + +.result-list.big li .subtitle { + word-break: break-all; + color: var(--theme-body-color-inactive); +} + +.result-list.big li .subtitle { + line-height: 1.5em; +} + +.search-bar .result-list li.selected .subtitle { + color: white; +} + +.search-bar .result-list { + border-bottom: 1px solid var(--theme-splitter-color); +} + +.theme-dark .result-list { + background-color: var(--theme-body-background); +} + +.autocomplete { + flex: 1; + width: 100%; +} + +.autocomplete .no-result-msg { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + color: var(--theme-graphs-full-red); + font-size: 24px; + padding: 4px; + word-break: break-all; +} + +.autocomplete .no-result-msg .sad-face { + width: 24px; + margin: 0 4px; + line-height: 0; + flex-shrink: 0; +} + +.autocomplete .no-result-msg .sad-face svg { + fill: var(--theme-graphs-full-red); +} +.tree { + -webkit-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + + white-space: nowrap; + overflow: auto; + min-width: 100%; +} + +.tree button { + display: block; +} + +.tree .node { + padding: 2px 5px; + position: relative; + cursor: pointer; +} + +.tree .node.focused { + color: white; + background-color: var(--theme-selection-background); +} + +html:not([dir="rtl"]) .tree .node > div { + margin-left: 10px; +} + +html[dir="rtl"] .tree .node > div { + margin-right: 10px; +} + +.tree .node.focused svg { + fill: white; +} + +.tree-node button { + position: fixed; +} +.sources-panel { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.sources-panel * { + user-select: none; +} + +.sources-header { + height: 30px; + border-bottom: 1px solid var(--theme-splitter-color); + padding-top: 0px; + padding-bottom: 0px; + line-height: 30px; + font-size: 1.2em; + display: flex; + align-items: baseline; + user-select: none; + justify-content: flex-end; +} + +.theme-dark .sources-header { + background-color: var(--theme-tab-toolbar-background); +} + +.sources-header { + padding-inline-start: 10px; +} + +.sources-header-info { + font-size: 12px; + color: var(--theme-comment-alt); + font-weight: lighter; + white-space: nowrap; + padding-inline-end: 10px; + cursor: pointer; +} + +.sources-list { + flex: 1; + display: flex; + overflow: hidden; +} + +.theme-dark .sources-list .tree .node:not(.focused) svg { + fill: var(--theme-comment); +} + +.theme-dark .source-list .tree .node.focused { + background-color: var(--theme-tab-toolbar-background); +} +.toggle-button-start, +.toggle-button-end { + transform: translate(0, 2px); + transition: transform 0.25s ease-in-out; + cursor: pointer; + padding: 4px 2px; +} + +.toggle-button-start svg, +.toggle-button-end svg { + width: 16px; + fill: var(--theme-comment); +} + +.theme-dark .toggle-button-start svg, +.theme-dark .toggle-button-end svg { + fill: var(--theme-comment-alt); +} + +.toggle-button-end { + margin-left: auto; + margin-right: 5px; +} + +.toggle-button-start { + margin-left: 5px; +} + +html:not([dir="rtl"]) .toggle-button-end svg, +html[dir="rtl"] .toggle-button-start svg { + transform: rotate(180deg); +} + +html .toggle-button-end.vertical svg { + transform: rotate(-90deg); +} + +.toggle-button-end.vertical { + margin-bottom: 2px; +} + +.toggle-button-start.collapsed, +.toggle-button-end.collapsed { + transform: rotate(180deg); +} + +.source-footer { + background: var(--theme-toolbar-background); + border-top: 1px solid var(--theme-splitter-color); + position: absolute; + display: flex; + bottom: 0; + left: 0; + right: 1px; + opacity: 1; + z-index: 100; + user-select: none; + height: 27px; + box-sizing: border-box; +} + +.source-footer .commands { + display: flex; +} + +.source-footer .commands * { + user-select: none; +} + +.source-footer > .commands > .action { + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + transition: opacity 200ms; + border: none; + background: transparent; + padding: 8px 0.7em; +} + +.source-footer > .commands > .action i { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; +} + +:root.theme-dark .source-footer > .commands > .action { + fill: var(--theme-body-color); +} + +:root.theme-dark .source-footer > .commands > .action:hover { + fill: var(--theme-selection-color); +} + +.source-footer > .commands > .action svg { + height: 16px; + width: 16px; +} + +.source-footer .commands .coverage { + color: var(--theme-body-color); +} + +.coverage-on .source-footer .commands .coverage { + color: var(--theme-highlight-blue); + border: 1px solid var(--theme-body-color-inactive); + border-radius: 2px; +} + +.search-bar { + display: flex; + flex-direction: column; + max-height: 50%; +} + +.search-bar .search-field { + padding-left: 7px; +} + +.search-bar .close-btn { + padding: 6px; +} + +.search-bottom-bar * { + user-select: none; +} + +.search-bottom-bar { + display: flex; + flex-shrink: 0; + justify-content: space-between; + width: calc(100% - 1px); + height: 27px; + background-color: var(--theme-toolbar-background); + border-bottom: 1px solid var(--theme-splitter-color); + padding: 0 13px; +} + +.search-bottom-bar button:focus { + outline: none; +} + +.search-bottom-bar .search-modifiers { + display: flex; + align-items: center; +} + +.search-bottom-bar .search-modifiers button { + padding: 0 3px; + margin: 0 3px; + border: none; + background: none; + width: 20px; + height: 20px; + border-radius: 3px; +} + +.search-bottom-bar .search-modifiers button i { + display: flex; + justify-content: center; + align-items: center; + padding: 0; + width: 16px; +} + +.search-bottom-bar .search-modifiers button svg { + fill: var(--theme-comment-alt); + height: 16px; + width: 16px; +} + +.search-bottom-bar .search-modifiers button svg:hover { + cursor: pointer; +} + +.search-bottom-bar .search-modifiers button:active { + outline: none; +} + +.search-bottom-bar .search-modifiers button.active svg { + fill: var(--theme-selection-background); +} + +.theme-dark .search-bottom-bar .search-modifiers button.active svg { + fill: white; +} + +.search-bottom-bar .search-modifiers button.disabled svg { + fill: var(--theme-comment-alt); +} + +.search-bottom-bar .search-type-toggles { + display: flex; + align-items: center; +} + +.search-bottom-bar .search-type-toggles .search-toggle-title { + color: var(--theme-body-color-inactive); + font-size: 11px; + font-weight: normal; + margin: 0; +} + +.search-bottom-bar .search-type-toggles .search-type-btn { + margin: 0 6px; + border: none; + background: transparent; + color: var(--theme-comment-alt); +} + +.search-bottom-bar .search-type-toggles .search-type-btn:hover { + cursor: pointer; +} + +.search-bottom-bar .search-type-toggles .search-type-btn:active { + outline: none; +} + +.search-bottom-bar .search-type-toggles .search-type-btn.active { + color: var(--theme-selection-background); +} + +.theme-dark .search-bottom-bar .search-type-toggles .search-type-btn.active { + color: white; +} + +.search-bar .result-list { + max-height: 230px; +} +.popover { + position: fixed; + z-index: 4; +} + +.popover-gap { + height: 10px; + padding-top: 10px; +} + +.popover::before, +.popover::after { + content: ''; + height: 0; + width: 0; + position: absolute; + border: 10px solid transparent; + left: calc(20% - 10px); /* corresponds to calculation in Popover.js */ +} + +.popover::before { + border-bottom-color: var(--theme-comment); + top: -10px; +} + +.popover::after { + border-bottom-color: var(--theme-body-background); + top: -8px; +} +.preview { + background: var(--theme-body-background); + min-width: 200px; + min-height: 80px; + border: 1px solid var(--theme-comment); + padding: 10px; + height: auto; + min-height: inherit; + max-height: 130px; + overflow: auto; + max-width: 400px; +} + +.preview .header { + width: 100%; + line-height: 20px; + border-bottom: 1px solid #cccccc; + display: flex; + flex-direction: column; +} + +.preview .header .link { + align-self: flex-end; + color: var(--theme-highlight-blue); + text-decoration: underline; +} +.preview .header .link:hover { + cursor: pointer; +} + +.selected-token { + background-color: var(--theme-search-overlays-semitransparent); + color: var(--theme-selection-color); +} + +.selected-token:hover { + cursor: pointer; +} + +.preview .function-signature { + padding-top: 10px; +} + +.function-signature { + line-height: 20px; + align-self: center; +} + +.function-signature .function-name { + color: var(--theme-highlight-blue); +} + +.function-signature .param { + color: var(--string-color); +} + +.function-signature .paren { + color: var(--object-color); +} + +.function-signature .comma { + color: var(--object-color); +} + +.theme-dark .preview { + border-color: var(--theme-body-color); +} + +.theme-dark .preview .arrow svg { + fill: var(--theme-comment); +} +.conditional-breakpoint-panel { + cursor: initial; + margin: 1em 0; + position: relative; + display: flex; + align-items: center; + background: var(--theme-toolbar-background); + border-top: 1px solid var(--theme-splitter-color); + border-bottom: 1px solid var(--theme-splitter-color); +} + +.conditional-breakpoint-panel .prompt { + font-size: 1.8em; + color: var(--theme-comment-alt); + padding-left: 3px; +} + +.conditional-breakpoint-panel input { + margin: 5px 10px; + width: calc(100% - 4em); + border: none; + background: var(--theme-toolbar-background); + font-size: 14px; + color: var(--theme-comment); + line-height: 30px; +} + +.conditional-breakpoint-panel input:focus { + outline-width: 0; +} +/* 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/. */ + +/** + * There's a known codemirror flex issue with chrome that this addresses. + * BUG https://github.com/devtools-html/debugger.html/issues/63 + */ +.editor-wrapper { + position: absolute; + height: calc(100% - 31px); + width: 100%; + top: 30px; + left: 0px; +} + +html[dir="rtl"] .editor-mount { + direction: ltr; +} + +.editor-wrapper .breakpoints { + position: absolute; + top: 0; + left: 0; +} + +.function-search { + max-height: 300px; + overflow: hidden; +} + +.function-search .results { + height: auto; +} + +.editor.hit-marker { + height: 14px; +} + +.coverage-on .CodeMirror-code :not(.hit-marker) .CodeMirror-line, +.coverage-on .CodeMirror-code :not(.hit-marker) .CodeMirror-gutter-wrapper { + opacity: 0.5; +} + +.editor.new-breakpoint svg { + fill: var(--theme-selection-background); + width: 60px; + height: 14px; + position: absolute; + top: 0px; + right: -4px; +} + +.new-breakpoint.has-condition svg { + fill: var(--theme-graphs-yellow); +} + +.editor.new-breakpoint.breakpoint-disabled svg { + opacity: 0.3; +} + +.CodeMirror { + width: 100%; + height: 100%; +} + +.editor-wrapper .editor-mount { + width: 100%; + height: calc(100% - 32px); + background-color: var(--theme-body-background); +} + +.search-bar ~ .editor-mount { + height: calc(100% - 72px); +} + +.CodeMirror-linenumber { + font-size: 11px; + line-height: 14px; +} + +/* set the linenumber white when there is a breakpoint */ +.new-breakpoint .CodeMirror-gutter-wrapper .CodeMirror-linenumber { + color: white; +} + +/* move the breakpoint below the linenumber */ +.new-breakpoint .CodeMirror-gutter-elt:last-child { + z-index: 0; +} + +.editor-wrapper .CodeMirror-line { + font-size: 11px; + line-height: 14px; +} + +.theme-dark .editor-wrapper .CodeMirror-line .cm-comment { + color: var(--theme-content-color3); +} + +.debug-line .CodeMirror-line { + background-color: var(--breakpoint-active-color) !important; +} + +/* Don't display the highlight color since the debug line + is already highlighted */ +.debug-line .CodeMirror-activeline-background { + display: none; +} + +.highlight-line .CodeMirror-line { + animation: fade-highlight-out 1.5s normal forwards; +} + +@keyframes fade-highlight-out { + 0% { background-color: var(--theme-highlight-gray); } + 100% { background-color: transparent; } +} + +.welcomebox { + width: calc(100% - 1px); + + /* Offsetting it by 30px for the sources-header area */ + height: calc(100% - 30px); + position: absolute; + top: 30px; + left: 0; + padding: 50px 0; + text-align: center; + font-size: 1.25em; + color: var(--theme-comment-alt); + background-color: var(--theme-tab-toolbar-background); + font-weight: lighter; + z-index: 100; + user-select: none; +} +.input-expression { + width: 100%; + margin: 0px; + border: 1px; + cursor: pointer; + background-color: var(--theme-body-background); + font-size: 12px; + padding: 0px 20px; + color: var(--theme-highlight-blue); +} + +.input-expression::-webkit-input-placeholder { + text-align: center; + font-style: italic; + color: var(--theme-comment-alt); +} + +.input-expression::placeholder { + text-align: center; + font-style: italic; + color: var(--theme-comment-alt); + opacity: 1; +} + +.input-expression:focus { + outline: none; + cursor: text; +} + +.expression-input-container { + padding: 0.5em; + display: flex; +} + +.expression-container { + border: 1px; + padding: 8px 5px 0px 0px; + width: 100%; + color: var(--theme-body-color); + background-color: var(--theme-body-background); + display: flex; + position: relative; +} + +.expression-container > .tree { + width: 100%; + overflow: hidden; +} + +:root.theme-light .expression-container:hover { + background-color: var(--theme-tab-toolbar-background); +} + +:root.theme-dark .expression-container:hover { + background-color: var(--search-overlays-semitransparent); +} + +.expression-container .close-btn { + position: absolute; + inset-inline-end: 6px; + top: 6px; +} + +.expression-container .close { + display: none; +} + +.expression-container:hover .close { + display: block; +} + +.expression-input { + cursor: pointer; + max-width: 50%; +} + +.expression-separator { + padding: 0px 5px; +} + +.expression-value { + overflow-x: scroll; + color: var(--theme-content-color2); + max-width: 50% !important; +} + +.expression-error { + color: var(--theme-highlight-red); +} + +.why-paused { + background-color: var(--breakpoint-active-color); + border: 1.7px solid var(--breakpoint-active-color); + color: var(--theme-highlight-blue); + padding: 10px 10px 10px 20px; + white-space: normal; + opacity: 0.9; + font-size: 12px; + font-weight: bold; + flex: 0 1 auto; +} + +.theme-dark .secondary-panes .why-paused { + color: white; +} +.breakpoints-list * { + user-select: none; +} + +.breakpoints-list .breakpoint { + font-size: 12px; + color: var(--theme-content-color1); + padding: 0.5em 1px; + line-height: 1em; + position: relative; + transition: all 0.25s ease; +} + +html[dir="rtl"] .breakpoints-list .breakpoint { + border-right: 4px solid transparent; +} + +html:not([dir="rtl"]) .breakpoints-list .breakpoint { + border-left: 4px solid transparent; +} + +.breakpoints-list .breakpoint:last-of-type { + padding-bottom: 0.45em; +} + +html:not([dir="rtl"]) .breakpoints-list .breakpoint.is-conditional { + border-left-color: var(--theme-graphs-yellow); +} + +html[dir="rtl"] .breakpoints-list .breakpoint.is-conditional { + border-right-color: var(--theme-graphs-yellow); +} + +html .breakpoints-list .breakpoint.paused { + background-color: var(--theme-toolbar-background-alt); + border-color: var(--breakpoint-active-color); +} + +.breakpoints-list .breakpoint.disabled .breakpoint-label { + color: var(--theme-content-color3); + transition: color 0.5s linear; +} + +.breakpoints-list .breakpoint:hover { + cursor: pointer; + background-color: var(--search-overlays-semitransparent); +} + +.breakpoints-list .breakpoint.paused:hover { + border-color: var(--breakpoint-active-color-hover); +} + +.breakpoints-list .breakpoint-checkbox { + margin-inline-start: 0; +} + +.breakpoints-list .breakpoint-label { + display: inline-block; + padding-inline-start: 2px; + padding-bottom: 4px; +} + +.breakpoints-list .pause-indicator { + flex: 0 1 content; + order: 3; +} + +:root.theme-light .breakpoint-snippet, +:root.theme-firebug .breakpoint-snippet { + color: var(--theme-comment); +} + +:root.theme-dark .breakpoint-snippet { + color: var(--theme-body-color); + opacity: 0.6; +} + +.breakpoint-snippet { + overflow: hidden; + text-overflow: ellipsis; + padding-inline-start: 18px; + padding-inline-end: 18px; +} + +.breakpoint .close-btn { + position: absolute; + inset-inline-end: 6px; + top: 12px; +} + +.breakpoint .close { + display: none; +} + +.breakpoint:hover .close { + display: block; +} + +.object-node.default-property { + opacity: 0.6; +} + +.object-label { + color: var(--theme-highlight-blue); +} + +.objectBox-object, +.objectBox-string, +.objectBox-text, +.objectBox-table, +.objectLink-textNode, +.objectLink-event, +.objectLink-eventLog, +.objectLink-regexp, +.objectLink-object, +.objectLink-Date, +.theme-dark .objectBox-object, +.theme-light .objectBox-object { + white-space: nowrap; +} + +.scopes-list .tree-node { + overflow: hidden; +} +.frames ul { + list-style: none; + margin: 0; + padding: 0; +} + +.frames ul li { + cursor: pointer; + padding: 7px 10px 7px 21px; + overflow: hidden; + display: flex; + justify-content: space-between; +} + +/* Style the focused call frame like so: +.frames ul li:focus { + border: 3px solid red; +} +*/ + +.frames ul li * { + user-select: none; +} + +.frames ul li:nth-of-type(2n) { + background-color: var(--theme-tab-toolbar-background); +} + +.frames .location { + font-weight: lighter; +} + +:root.theme-light .frames .location, +:root.theme-firebug .frames .location { + color: var(--theme-comment); +} + +:root.theme-dark .frames .location { + color: var(--theme-body-color); + opacity: 0.6; +} + +.frames .title { + text-overflow: ellipsis; + overflow: hidden; + margin-right: 1em; +} + +.frames ul li:hover, +.frames ul li:focus { + background-color: var(--theme-toolbar-background-alt); + outline: none; +} + +.frames ul li.selected { + background-color: var(--theme-selection-background); + color: white; +} + +:root.theme-light .frames ul li.selected .location, +:root.theme-firebug .frames ul li.selected .location, +:root.theme-dark .frames ul li.selected .location { + color: white; +} + +:root.theme-dark .frames ul li:hover .location, +:root.theme-dark .frames ul li.selected .location { + opacity: 1; +} + +.show-more { + cursor: pointer; + text-align: center; + padding: 8px 0px; + border-top: 1px solid var(--theme-splitter-color); + background-color: var(--theme-tab-toolbar-background); +} + +.show-more:hover { + background-color: var(--search-overlays-semitransparent); +} +.event-listeners { + list-style: none; + margin: 0; + padding: 0; +} + +.event-listeners .listener { + cursor: pointer; + padding: 7px 10px 7px 21px; + clear: both; + overflow: hidden; +} + +.event-listeners .listener * { + user-select: none; +} + +.event-listeners .listener:nth-of-type(2n) { + background-color: var(--theme-tab-toolbar-background); +} + +.event-listeners .listener .type { + color: var(--theme-highlight-bluegrey); + padding-right: 5px; +} + +.event-listeners .listener .selector { + color: var(--theme-content-color2); +} + +.event-listeners .listener-checkbox { + margin-left: 0; +} + +.event-listeners .listener .close-btn { + float: right; +} + +.event-listeners .listener .close { + display: none; +} + +.event-listeners .listener:hover .close { + display: block; +} +.accordion { + background-color: var(--theme-body-background); + width: 100%; +} + +.accordion ._header { + background-color: var(--theme-toolbar-background); + border-bottom: 1px solid var(--theme-splitter-color); + cursor: pointer; + font-size: 12px; + padding: 5px; + transition: all 0.25s ease; + width: 100%; + + -webkit-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.accordion ._header { + display: flex; +} + +.accordion ._header:hover { + background-color: var(--search-overlays-semitransparent); +} + +.accordion ._header button svg, +.accordion ._header:hover button svg { + fill: currentColor; + height: 16px; +} + +.accordion ._content { + border-bottom: 1px solid var(--theme-splitter-color); + font-size: 12px; +} + +.accordion ._header .header-buttons { + display: flex; + margin-left: auto; + padding-right: 5px; +} + +.accordion .header-buttons .add-button { + font-size: 180%; + text-align: center; + line-height: 16px; +} + +.accordion .header-buttons button { + color: var(--theme-body-color); + border: none; + background: none; + outline: 0; + padding: 0; + width: 16px; + height: 16px; +} + +.accordion .header-buttons button::-moz-focus-inner { + border: none; +} +.command-bar { + flex: 0 0 30px; + border-bottom: 1px solid var(--theme-splitter-color); + display: flex; + height: 30px; + overflow: hidden; + position: sticky; + top: 0; + z-index: 1; + background-color: var(--theme-body-background); +} + +.theme-dark .command-bar { + background-color: var(--theme-tab-toolbar-background); +} + +.command-bar > button { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + cursor: pointer; + display: inline-block; + text-align: center; + transition: all 0.25s ease; + padding: 8px 5px; + position: relative; + fill: currentColor; +} + +:root.theme-dark .command-bar > button { + color: var(--theme-body-color); +} + +.command-bar > button { + margin-inline-end: 0.7em; +} + +html .command-bar > button:disabled { + opacity: 0.3; + cursor: default; +} + +.command-bar > button > i { + height: 100%; + width: 100%; + display: block; +} + +.command-bar > button > i > svg { + width: 16px; + height: 16px; +} + +.command-bar button.pause-exceptions { + margin-inline-start: 0.5em; +} + +.command-bar .subSettings { + float: right; +} + +.command-bar button.pause-exceptions.uncaught { + color: var(--theme-highlight-purple); +} + +.command-bar button.pause-exceptions.all { + color: var(--theme-highlight-blue); +} +.secondary-panes { + display: flex; + flex-direction: column; + flex: 1; + white-space: nowrap; +} + +.secondary-panes * { + user-select: none; +} + +.secondary-panes .accordion { + overflow-y: auto; + overflow-x: hidden; + flex: 1 0 auto; +} + +.pane { + color: var(--theme-body-color); +} + +.pane .pane-info { + font-style: italic; + text-align: center; + padding: 0.5em; + user-select: none; +} + +.theme-dark .secondary-panes .accordion .arrow svg { + fill: var(--theme-comment); +} +.welcomebox { + width: calc(100% - 1px); + + /* Offsetting it by 30px for the sources-header area */ + height: calc(100% - 30px); + position: absolute; + top: 30px; + left: 0; + padding: 50px 0 0 0; + text-align: center; + font-size: 1.25em; + color: var(--theme-comment-alt); + background-color: var(--theme-tab-toolbar-background); + font-weight: lighter; + z-index: 100; +} + +html .welcomebox .toggle-button-end { + bottom: 11px; + position: absolute; + top: auto; +} +.dropdown { + --width: 150px; + background: var(--theme-body-background); + border: 1px solid var(--theme-splitter-color); + box-shadow: 0 4px 4px 0 var(--search-overlays-semitransparent); + max-height: 300px; + position: absolute; + right: 8px; + top: 35px; + width: var(--width); + z-index: 1000; +} + +html[dir="rtl"] .dropdown { + right: calc((var(--width) - 11px) * (-1)); +} + +.dropdown-block { + padding: 0px 2px; + position: relative; + align-self: center; +} + +.dropdown-button { + cursor: pointer; + color: var(--theme-comment); + background: none; + border: none; + padding: 0; + font-weight: 100; + margin-top: 6px; + font-size: 14px; +} + +.dropdown li { + transition: all 0.25s ease; + padding: 2px 10px 10px 5px; + overflow: hidden; + height: 30px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dropdown li:hover { + background-color: var(--search-overlays-semitransparent); + cursor: pointer; +} + +.dropdown ul { + list-style: none; + line-height: 2em; + font-size: 1em; + margin: 0; + padding: 0; +} + +.dropdown-mask { + position: fixed; + width: 100%; + height: 100%; + background: transparent; + z-index: 999; + left: 0; + top: 0; +} +.source-header { + border-bottom: 1px solid var(--theme-splitter-color); + width: 100%; + height: 30px; + display: flex; + align-items: flex-end; +} + +.source-header * { + user-select: none; +} + +.source-header .new-tab-btn { + padding: 0px 4px; + margin-top: 8px; + cursor: pointer; + fill: var(--theme-comment); + transition: 0.1s ease; + align-self: center; +} + +.source-header .new-tab-btn svg { + width: 12px; +} + +.source-tabs { + max-width: calc(100% - 80px); + align-self: flex-start; +} + +.source-tab { + border: 1px solid transparent; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + height: 30px; + display: inline-flex; + align-items: center; + position: relative; + transition: all 0.25s ease; + min-width: 40px; + overflow: hidden; + padding: 6px; + margin-inline-start: 3px; + margin-top: 2px; +} + +.source-tab:hover { + background-color: var(--theme-toolbar-background-alt); + border-color: var(--theme-splitter-color); + cursor: pointer; +} + +.source-tab.active { + color: var(--theme-body-color); + background-color: var(--theme-body-background); + border-color: var(--theme-splitter-color); + border-bottom-color: transparent; +} + +.source-tab.active path, +.source-tab:hover path { + fill: var(--theme-body-color); +} + +.source-tab .prettyPrint { + line-height: 0; +} + +.source-tab .prettyPrint svg { + height: 12px; + width: 12px; +} + +.source-tab .prettyPrint path { + fill: var(--theme-textbox-box-shadow); +} + +.source-tab .filename { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.source-tab.pretty .filename { + padding-inline-start: 8px; +} + +.source-tab .close-btn { + visibility: hidden; + line-height: 0; + margin-inline-start: 6px; +} + +.source-tab.active .close-btn { + visibility: visible; +} + +.source-tab:hover .close-btn { + visibility: visible; +} + +.source-tab .close-btn .close { + padding: 0; + margin-top: 0; + display: inline-flex; + justify-content: center; +} diff --git a/layout/style/test/gtest/generate_example_stylesheet.py b/layout/style/test/gtest/generate_example_stylesheet.py new file mode 100644 index 0000000000..5c69f5c702 --- /dev/null +++ b/layout/style/test/gtest/generate_example_stylesheet.py @@ -0,0 +1,16 @@ +def main(output, stylesheet): + css = open(stylesheet, "r").read() + css = ( + css.replace("\\", "\\\\") + .replace("\r", "\\r") + .replace("\n", "\\n") + .replace('"', '\\"') + ) + + # Work around "error C2026: string too big" + # https://msdn.microsoft.com/en-us/library/dddywwsc.aspx + chunk_size = 10000 + chunks = ('"%s"' % css[i : i + chunk_size] for i in range(0, len(css), chunk_size)) + + header = "#define EXAMPLE_STYLESHEET " + " ".join(chunks) + output.write(header) diff --git a/layout/style/test/gtest/moz.build b/layout/style/test/gtest/moz.build new file mode 100644 index 0000000000..aa3adbc9ab --- /dev/null +++ b/layout/style/test/gtest/moz.build @@ -0,0 +1,24 @@ +# -*- 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/. + +Library("style-gtest") + +UNIFIED_SOURCES = [ + "ImportScannerTest.cpp", + "StyloParsingBench.cpp", +] + +LOCAL_INCLUDES += [ + "/layout/style", +] + +GeneratedFile( + "ExampleStylesheet.h", + script="generate_example_stylesheet.py", + inputs=["example.css"], +) + +FINAL_LIBRARY = "xul-gtest" diff --git a/layout/style/test/mapped.css b/layout/style/test/mapped.css new file mode 100644 index 0000000000..7aebaecc82 --- /dev/null +++ b/layout/style/test/mapped.css @@ -0,0 +1,3 @@ +div { + color: #f06; +} diff --git a/layout/style/test/mapped.css^headers^ b/layout/style/test/mapped.css^headers^ new file mode 100644 index 0000000000..3b74491556 --- /dev/null +++ b/layout/style/test/mapped.css^headers^ @@ -0,0 +1 @@ +X-SourceMap: mapped.css.map diff --git a/layout/style/test/mapped2.css b/layout/style/test/mapped2.css new file mode 100644 index 0000000000..fc42a5abef --- /dev/null +++ b/layout/style/test/mapped2.css @@ -0,0 +1,4 @@ +span { + color: #f06; +} +//# sourceMappingURL: overridden-by-headers diff --git a/layout/style/test/mapped2.css^headers^ b/layout/style/test/mapped2.css^headers^ new file mode 100644 index 0000000000..1656e66589 --- /dev/null +++ b/layout/style/test/mapped2.css^headers^ @@ -0,0 +1,2 @@ +SourceMap: mapped2.css.map +X-SourceMap: ignored.css.map diff --git a/layout/style/test/media_queries_iframe.html b/layout/style/test/media_queries_iframe.html new file mode 100644 index 0000000000..141ecdcd94 --- /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/media_queries_iframe2.html b/layout/style/test/media_queries_iframe2.html new file mode 100644 index 0000000000..8768cd53c6 --- /dev/null +++ b/layout/style/test/media_queries_iframe2.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<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> + html, body { + width: 100%; + height: 100%; + margin: 0; + } + </style> + <style type="text/css" id="style" media="all and (display-mode: browser)"> + body { background: yellow; } + </style> + <style type="text/css" id="style" media="all and (display-mode: standalone)"> + body { background: green; } + </style> + <style type="text/css" id="style" media="all and (display-mode: minimal-ui)"> + body { background: red; } + </style> + <style type="text/css" id="style" media="all and (display-mode: fullscreen)"> + body { background: blue; } + </style> +</head> +<body> +<script> +window.addEventListener("message", e => { + if (e.data == "get-background-color") { + let { backgroundColor } = document.defaultView.getComputedStyle(document.body); + e.source.postMessage({ backgroundColor }, e.origin); + } +}); +</script> +</body> +</html> diff --git a/layout/style/test/mochitest.toml b/layout/style/test/mochitest.toml new file mode 100644 index 0000000000..9df86ea539 --- /dev/null +++ b/layout/style/test/mochitest.toml @@ -0,0 +1,783 @@ +[DEFAULT] +prefs = [ + "dom.animations-api.compositing.enabled=true", + "dom.animations-api.timelines.enabled=true", + "gfx.omta.background-color=true", + "gfx.font_loader.delay=0", + "layout.css.container-queries.enabled=true", + "layout.css.individual-transform.enabled=true", + "layout.css.motion-path-ray.enabled=true", + "layout.css.motion-path-basic-shapes.enabled=true", + "layout.css.motion-path-coord-box.enabled=true", + "layout.css.motion-path-offset-position.enabled=true", + "layout.css.motion-path-url.enabled=true", + "layout.css.backdrop-filter.enabled=true", + "layout.css.fit-content-function.enabled=true", + "layout.css.scroll-driven-animations.enabled=true", + "layout.css.animation-composition.enabled=true", + "layout.css.basic-shape-rect.enabled=true", + "layout.css.basic-shape-xywh.enabled=true", + "layout.css.transform-box-content-stroke.enabled=true", +] +support-files = [ + "animation_utils.js", + "bug1729861.js", + "ccd-quirks.html", + "ccd.sjs", + "ccd-standards.html", + "chrome/bug418986-2.js", + "chrome/match.png", + "chrome/mismatch.png", + "descriptor_database.js", + "!/dom/events/test/event_leak_utils.js", + "empty.html", + "file_computed_style_bfcache_display_none.html", + "file_computed_style_bfcache_display_none2.html", + "media_queries_iframe.html", + "media_queries_iframe2.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", + "support/blue-100x100.png", + "support/1x1-transparent.png", + "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", +] + +["test_acid3_test46.html"] + +["test_addSheet.html"] +support-files = ["additional_sheets_helper.html"] + +["test_additional_sheets.html"] +support-files = ["additional_sheets_helper.html"] + +["test_align_justify_computed_values.html"] + +["test_all_shorthand.html"] + +["test_animations.html"] + +["test_animations_async_tests.html"] +support-files = [ + "Ahem.ttf", + "file_animations_async_tests.html", +] + +["test_animations_dynamic_changes.html"] + +["test_animations_effect_timing_duration.html"] + +["test_animations_effect_timing_enddelay.html"] + +["test_animations_effect_timing_iterations.html"] + +["test_animations_event_handler_attribute.html"] + +["test_animations_event_order.html"] + +["test_animations_iterationstart.html"] + +["test_animations_omta.html"] + +["test_animations_omta_scroll.html"] +support-files = ["file_animations_omta_scroll.html"] + +["test_animations_omta_scroll_rtl.html"] +support-files = ["file_animations_omta_scroll_rtl.html"] + +["test_animations_omta_start.html"] + +["test_animations_pausing.html"] + +["test_animations_playbackrate.html"] + +["test_animations_reverse.html"] + +["test_animations_styles_on_event.html"] + +["test_animations_variable_changes.html"] + +["test_animations_with_disabled_properties.html"] +support-files = ["file_animations_with_disabled_properties.html"] + +["test_any_dynamic.html"] + +["test_area_url_cursor.html"] + +["test_asyncopen.html"] + +["test_at_rule_parse_serialize.html"] + +["test_attribute_selector_eof_behavior.html"] + +["test_backdrop_filter_enabled_state.html"] + +["test_background_blend_mode.html"] + +["test_border_device_pixel_rounding_initial_style.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"] + +["test_bug379741.html"] + +["test_bug382027.html"] + +["test_bug383075.html"] + +["test_bug387615.html"] + +["test_bug389464.html"] + +["test_bug391034.html"] + +["test_bug391221.html"] + +["test_bug397427.html"] +fail-if = ["xorigin"] +skip-if = [ + "http3", + "http2", +] + +["test_bug399349.html"] + +["test_bug401046.html"] +skip-if = ["true"] # Bug 701060 + +["test_bug405818.html"] + +["test_bug412901.html"] + +["test_bug413958.html"] + +["test_bug418986-2.html"] + +["test_bug437915.html"] + +["test_bug450191.html"] + +["test_bug470769.html"] + +["test_bug499655.html"] + +["test_bug499655.xhtml"] + +["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_bug645998.html"] +support-files = [ + "file_bug645998-1.css", + "file_bug645998-2.css", +] + +["test_bug652486.html"] + +["test_bug657143.html"] + +["test_bug667520.html"] + +["test_bug716226.html"] + +["test_bug732153.html"] + +["test_bug732209.html"] +support-files = ["bug732209-css.sjs"] +skip-if = [ + "http3", + "http2", +] + +["test_bug765590.html"] + +["test_bug771043.html"] + +["test_bug795520.html"] + +["test_bug798843_pref.html"] + +["test_bug829816.html"] +support-files = ["file_bug829816.css"] + +["test_bug874919.html"] + +["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_bug1330375.html"] + +["test_bug1371488.html"] + +["test_bug1375944.html"] +support-files = [ + "file_bug1375944.html", + "Ahem.ttf", +] + +["test_bug1382568.html"] +support-files = ["bug1382568-iframe.html"] + +["test_bug1394302.html"] + +["test_bug1443344-1.html"] +scheme = "https" +support-files = ["file_bug1443344.css"] + +["test_bug1443344-2.html"] +scheme = "https" +support-files = ["file_bug1443344.css"] + +["test_bug1451199-1.html"] + +["test_bug1451199-2.html"] + +["test_bug1490890.html"] + +["test_bug1505254.html"] + +["test_bug1729861.html"] + +["test_cascade.html"] + +["test_ch_ex_no_infloops.html"] + +["test_change_hint_optimizations.html"] + +["test_clip-path_polygon.html"] + +["test_color_rounding.html"] + +["test_compute_data_with_start_struct.html"] +skip-if = ["os == 'android'"] + +["test_computed_style.html"] + +["test_computed_style_bfcache_display_none.html"] + +["test_computed_style_difference.html"] + +["test_computed_style_grid_with_pseudo.html"] + +["test_computed_style_in_created_document.html"] + +["test_computed_style_min_size_auto.html"] + +["test_computed_style_no_flush.html"] + +["test_computed_style_no_pseudo.html"] + +["test_computed_style_prefs.html"] + +["test_condition_text.html"] + +["test_constructable_stylesheets_chrome_only_rules_in_content.html"] + +["test_counter_descriptor_storage.html"] + +["test_counter_style.html"] + +["test_crash_with_content_policy.html"] +support-files = ["file_bug1381233.html"] + +["test_css_cross_domain.html"] +skip-if = [ + "http3", + "http2", + "socketprocess_networking", +] + +["test_css_cross_domain_no_orb.html"] +skip-if = [ + "http3", + "http2", +] + +["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_parse_error_smoketest.html"] + +["test_css_supports.html"] + +["test_css_supports_variables.html"] + +["test_cue_restrictions.html"] + +["test_custom_content_inheritance.html"] + +["test_default_bidi_css.html"] + +["test_default_computed_style.html"] + +["test_descriptor_storage.html"] + +["test_descriptor_syntax_errors.html"] + +["test_display_mode.html"] +skip-if = [ + "http3", + "http2", +] + +["test_dont_use_document_colors.html"] + +["test_dont_use_document_fonts.html"] + +["test_dynamic_change_causing_reflow.html"] + +["test_exposed_prop_accessors.html"] + +["test_extra_inherit_initial.html"] + +["test_first_letter_restrictions.html"] + +["test_first_line_restrictions.html"] + +["test_flexbox_child_display_values.xhtml"] + +["test_flexbox_flex_grow_and_shrink.html"] + +["test_flexbox_flex_shorthand.html"] + +["test_flexbox_focus_order.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"] +skip-if = ["verify"] + +["test_flushing_frame.html"] + +["test_font_face_cascade.html"] + +["test_font_face_parser.html"] + +["test_font_family_parsing.html"] + +["test_font_family_serialization.html"] + +["test_font_loading_api.html"] +support-files = [ + "BitPattern.woff", + "file_font_loading_api_vframe.html", +] +# This test checks font loading state. When loaded second time, fonts may be +# loaded synchronously, causing this test to fail in test-verify task. +skip-if = [ + "verify", # Bug 1455824 + "os == 'android'", # Bug 1455824 + "http3", + "http2", +] + +["test_garbage_at_end_of_declarations.html"] + +["test_grid_computed_values.html"] + +["test_grid_container_shorthands.html"] + +["test_grid_item_shorthands.html"] + +["test_grid_shorthand_serialization.html"] + +["test_group_insertRule.html"] + +["test_hover_on_part.html"] + +["test_hover_quirk.html"] + +["test_html_attribute_computed_values.html"] + +["test_ident_escaping.html"] + +["test_img_src_causing_reflow.html"] + +["test_import_preload.html"] +support-files = ["slow_load.sjs"] +# Test is slightly racy and on Android it fails frequently enough to be +# annoying. +skip-if = ["os == 'android'"] + +["test_inherit_computation.html"] + +["test_inherit_storage.html"] + +["test_initial_computation.html"] + +["test_initial_storage.html"] + +["test_invalidation_basic.html"] + +["test_keyframes_rules.html"] + +["test_keyframes_vendor_prefix.html"] + +["test_load_events_on_stylesheets.html"] +support-files = [ + "slow_broken_sheet.sjs", + "slow_ok_sheet.sjs", +] + +["test_logical_properties.html"] + +["test_marker_restrictions.html"] + +["test_mask_image_CORS.html"] + +["test_media_queries.html"] +# times out on verify, see bug 1461033. +skip-if = ["verify"] +support-files = ["chrome/chrome-only-media-queries.js"] + +["test_media_queries_dynamic.html"] +skip-if = ["xorigin"] # Crashes, Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110, [Child][MessageChannel] Error: (msgtype=0xFFF7,name=<unknown IPC msg name>) Channel error: cannot send/recv + +["test_media_query_list.html"] + +["test_media_query_serialization.html"] + +["test_moz_device_pixel_ratio.html"] + +["test_moz_prefixed_cursor.html"] + +["test_mq_any_hover_and_any_pointer.html"] + +["test_mq_changes_in_iframe.html"] +support-files = ["mq_changes_child.html"] +skip-if = [ + "headless", + "os == 'win'", +] + +["test_mq_hover_and_pointer.html"] + +["test_mq_prefers_contrast_dynamic.html"] +skip-if = [ + "headless", + "os == 'win'", +] + +["test_mq_prefers_reduced_motion_dynamic.html"] +skip-if = [ + "headless", + "os == 'win'", +] + +["test_mql_event_listener_leaks.html"] + +["test_namespace_rule.html"] + +["test_non_content_accessible_env_vars.html"] + +["test_non_content_accessible_properties.html"] + +["test_non_content_accessible_pseudos.html"] + +["test_non_content_accessible_values.html"] + +["test_non_matching_sheet_media.html"] + +["test_of_type_selectors.xhtml"] + +["test_overscroll_behavior_pref.html"] + +["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_placeholder_restrictions.html"] + +["test_pointer-events.html"] + +["test_position_float_display.html"] + +["test_position_sticky.html"] + +["test_prefers_contrast_color_pairs.html"] + +["test_priority_preservation.html"] + +["test_property_database.html"] + +["test_property_syntax_errors.html"] + +["test_pseudo_display_fixup.html"] + +["test_pseudoelement_parsing.html"] + +["test_pseudoelement_state.html"] +skip-if = ["verify && debug && os == 'linux'"] + +["test_query_container_for.html"] + +["test_redundant_font_download.html"] +support-files = ["redundant_font_download.sjs"] + +["test_reframe_cb.html"] + +["test_reframe_image_loading.html"] + +["test_reframe_input.html"] + +["test_reframe_pseudo_element.html"] + +["test_rem_unit.html"] + +["test_restyle_table_wrapper.html"] + +["test_restyles_in_smil_animation.html"] + +["test_revert.html"] + +["test_root_node_display.html"] + +["test_rule_insertion.html"] + +["test_rules_out_of_sheets.html"] + +["test_selectors.html"] + +["test_setPropertyWithNull.html"] +skip-if = ["xorigin && debug"] + +["test_shape_outside_CORS.html"] + +["test_shared_sheet_caching.html"] +support-files = [ + "file_shared_sheet_caching.css", + "file_shared_sheet_caching.html", +] +fail-if = ["xorigin"] + +["test_sheet_privilege.html"] + +["test_shorthand_property_getters.html"] + +["test_specified_value_serialization.html"] +support-files = ["file_specified_value_serialization_individual_transforms.html"] + +["test_style_attr_listener.html"] + +["test_style_attribute_quirks.html"] + +["test_style_attribute_standards.html"] + +["test_style_struct_copy_constructors.html"] + +["test_stylesheet_additions.html"] + +["test_stylesheet_clone_font_face.html"] + +["test_supports_rules.html"] + +["test_system_font_serialization.html"] + +["test_text_decoration_shorthands.html"] + +["test_transitions.html"] + +["test_transitions_and_reframes.html"] + +["test_transitions_and_restyles.html"] + +["test_transitions_and_zoom.html"] + +["test_transitions_at_start.html"] + +["test_transitions_bug537151.html"] + +["test_transitions_cancel_near_end.html"] + +["test_transitions_computed_value_combinations.html"] + +["test_transitions_computed_values.html"] + +["test_transitions_dynamic_changes.html"] + +["test_transitions_events.html"] + +["test_transitions_per_property.html"] + +["test_transitions_replacement_on_busy_frame.html"] + +["test_transitions_replacement_with_setKeyframes.html"] + +["test_transitions_step_functions.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_use_counters.html"] +skip-if = ["!nightly_build"] + +["test_user_sheet_shadow_dom.html"] + +["test_value_cloning.html"] +# This test requires too much memory on TSan (bug 1612707) +# See bug 775227 for android +skip-if = [ + "os == 'android'", + "tsan", +] + +["test_value_computation.html"] +# This test requires too much memory on TSan (bug 1612707) +skip-if = ["tsan"] + +["test_value_storage.html"] + +["test_variable_serialization_computed.html"] + +["test_variable_serialization_specified.html"] + +["test_variables.html"] +support-files = ["support/external-variable-url.css"] +skip-if = [ + "http3", + "http2", +] + +["test_variables_loop.html"] + +["test_variables_order.html"] +support-files = ["support/external-variable-url.css"] + +["test_video_object_fit.html"] + +["test_viewport_scrollbar_causing_reflow.html"] +skip-if = ["verify && (os == 'win' || os == 'mac')"] + +["test_viewport_units.html"] + +["test_visited_image_loading.html"] +skip-if = ["os == 'android'"] # TIMED_OUT for android + +["test_visited_image_loading_empty.html"] +skip-if = ["os == 'android'"] # TIMED_OUT for android + +["test_visited_lying.html"] +skip-if = ["os == 'android'"] # TIMED_OUT for android +fail-if = ["xorigin"] + +["test_visited_pref.html"] +skip-if = ["os == 'android'"] # TIMED_OUT for android +fail-if = ["xorigin"] + +["test_visited_reftests.html"] +skip-if = ["os == 'android'"] # TIMED_OUT for android + +["test_webkit_device_pixel_ratio.html"] +skip-if = ["xorigin"] # process crash: Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110 + +["test_webkit_flex_display.html"] +skip-if = ["xorigin"] # Crashes, Assertion failure: mInFlightProcessId == 0, at /builds/worker/checkouts/gecko/docshell/base/CanonicalBrowsingContext.cpp:110 diff --git a/layout/style/test/moz.build b/layout/style/test/moz.build new file mode 100644 index 0000000000..ee826be9ed --- /dev/null +++ b/layout/style/test/moz.build @@ -0,0 +1,152 @@ +# -*- 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. + +DIRS += ["gtest"] + +HostSimplePrograms( + [ + "host_ListCSSProperties", + ] +) + +MOCHITEST_MANIFESTS += [ + "mochitest.toml", +] +BROWSER_CHROME_MANIFESTS += ["browser.toml"] +MOCHITEST_CHROME_MANIFESTS += ["chrome/chrome.toml"] + +TEST_HARNESS_FILES.testing.mochitest.tests.layout.style.test.chrome += [ + "chrome/display_mode_reflow_iframe.html", + "chrome/moz_document_helper.html", + "media_queries_iframe.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/caret-color-on-visited-1-ref.html", + "/layout/reftests/css-visited/caret-color-on-visited-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/color-on-visited-text-1-ref.html", + "/layout/reftests/css-visited/color-on-visited-text-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/logical-box-border-color-visited-link-001.html", + "/layout/reftests/css-visited/logical-box-border-color-visited-link-002.html", + "/layout/reftests/css-visited/logical-box-border-color-visited-link-003.html", + "/layout/reftests/css-visited/logical-box-border-color-visited-link-ref.html", + "/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/placeholder-1-ref.html", + "/layout/reftests/css-visited/placeholder-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-adj-sibling-3-ref.xhtml", + "/layout/reftests/css-visited/selector-adj-sibling-3.xhtml", + "/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/svg-paint-currentcolor-visited-ref.svg", + "/layout/reftests/css-visited/svg-paint-currentcolor-visited.svg", + "/layout/reftests/css-visited/transition-on-visited-ref.html", + "/layout/reftests/css-visited/transition-on-visited.html", + "/layout/reftests/css-visited/variables-visited-ref.html", + "/layout/reftests/css-visited/variables-visited.html", + "/layout/reftests/css-visited/visited-inherit-1-ref.html", + "/layout/reftests/css-visited/visited-inherit-1.html", + "/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/fonts/Ahem.ttf", + "/layout/reftests/svg/as-image/svg-image-visited-1-ref.html", + "/layout/reftests/svg/as-image/svg-image-visited-1a-helper.svg", + "/layout/reftests/svg/as-image/svg-image-visited-1a.html", + "/layout/reftests/svg/as-image/svg-image-visited-1b-helper.svg", + "/layout/reftests/svg/as-image/svg-image-visited-1b.html", + "/layout/reftests/svg/as-image/svg-image-visited-1c-helper.svg", + "/layout/reftests/svg/as-image/svg-image-visited-1c.html", + "/layout/reftests/svg/as-image/svg-image-visited-1d-helper.svg", + "/layout/reftests/svg/as-image/svg-image-visited-1d.html", + "/layout/reftests/svg/pseudo-classes-02-ref.svg", + "/layout/reftests/svg/pseudo-classes-02.svg", +] + +DEFINES["MOZILLA_INTERNAL_API"] = True + +if CONFIG["COMPILE_ENVIRONMENT"]: + GeneratedFile( + "css_properties.js", + script="gen-css-properties.py", + 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/mq_changes_child.html b/layout/style/test/mq_changes_child.html new file mode 100644 index 0000000000..f594e9f00d --- /dev/null +++ b/layout/style/test/mq_changes_child.html @@ -0,0 +1,8 @@ +<script> +const mql = matchMedia("(prefers-reduced-motion: reduce)"); +mql.addEventListener("change", event => { + parent.postMessage({ "matches": event.matches }, "*"); +}); + +window.onload = () => { parent.postMessage("ready", "*"); }; +</script> diff --git a/layout/style/test/neverending_font_load.sjs b/layout/style/test/neverending_font_load.sjs new file mode 100644 index 0000000000..b02fc377a8 --- /dev/null +++ b/layout/style/test/neverending_font_load.sjs @@ -0,0 +1,5 @@ +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 0000000000..048263fe54 --- /dev/null +++ b/layout/style/test/neverending_stylesheet_load.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) { + response.processAsync(); + response.setHeader("Content-Type", "text/css", false); + response.write(""); +} diff --git a/layout/style/test/post-redirect-1.css b/layout/style/test/post-redirect-1.css new file mode 100644 index 0000000000..3620c9f377 --- /dev/null +++ b/layout/style/test/post-redirect-1.css @@ -0,0 +1 @@ +#one { color: green; background: url("?1"); } diff --git a/layout/style/test/post-redirect-2.css b/layout/style/test/post-redirect-2.css new file mode 100644 index 0000000000..3bdf3279d3 --- /dev/null +++ b/layout/style/test/post-redirect-2.css @@ -0,0 +1 @@ +#two { color: green; background: url("?1"); } diff --git a/layout/style/test/post-redirect-3.css b/layout/style/test/post-redirect-3.css new file mode 100644 index 0000000000..dd98be8e66 --- /dev/null +++ b/layout/style/test/post-redirect-3.css @@ -0,0 +1 @@ +#three { color: green; background: url("?1"); } diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js new file mode 100644 index 0000000000..422f2ffe5c --- /dev/null +++ b/layout/style/test/property_database.js @@ -0,0 +1,14128 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* eslint-disable dot-notation */ +/* 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; + +// Legacy shorthand properties, that behave mostly like an alias +// (CSS_TYPE_SHORTHAND_AND_LONGHAND) but not quite because their syntax may not +// match, plus they shouldn't serialize in cssText. +const CSS_TYPE_LEGACY_SHORTHAND = 3; + +// 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.) +// 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'. + const meta = document.createElement("meta"); + meta.setAttribute("style", "font: initial;"); + document.documentElement.appendChild(meta); + const family = getComputedStyle(meta).fontFamily; + meta.remove(); + return family == "sans-serif"; +} + +var gInitialFontFamilyIsSansSerif = initial_font_family_is_sans_serif(); + +// shared by background-image and border-image-source +var validNonUrlImageValues = [ + "-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(180deg, red, 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)", + + "linear-gradient(red 0% 100%)", + "linear-gradient(red 0% 50%, blue 50%)", + "linear-gradient(red 0% 50%, blue 50% 100%)", + "linear-gradient(red 0% 50%, 0%, blue 50%)", + "linear-gradient(red 0% 50%, 0%, blue 50% 100%)", + + /* Unitless 0 is valid as an <angle> */ + "linear-gradient(0, red, blue)", + + "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(farthest-side circle, red, blue)", + + "radial-gradient(at 43px, red, blue)", + "radial-gradient(at 43px 43px, red, blue)", + "radial-gradient(at 50% 50%, red, blue)", + "radial-gradient(at 43px 50%, red, blue)", + "radial-gradient(at 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)", + + "radial-gradient(circle at 15% 20%, red, blue)", + + "radial-gradient(red 0% 100%)", + "radial-gradient(red 0% 50%, blue 50%)", + "radial-gradient(red 0% 50%, blue 50% 100%)", + "radial-gradient(red 0% 50%, 0%, blue 50%)", + "radial-gradient(red 0% 50%, 0%, blue 50% 100%)", + + "repeating-radial-gradient(red, blue)", + "repeating-radial-gradient(red, yellow, blue)", + "repeating-radial-gradient(red 1px, yellow 20%, blue 24em, green)", + "repeating-radial-gradient(red, yellow, green, blue 50%)", + "repeating-radial-gradient(red -50%, yellow -25%, green, blue)", + "repeating-radial-gradient(red -99px, yellow, green, blue 120%)", + "repeating-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + "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)", + + "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)", + + // When that happens this should be moved to the `invalid` list. + "repeating-radial-gradient(circle closest-side at left 0px bottom 7in, hsl(2,2%,5%), rgb(1,6,0))", + + "radial-gradient(at calc(25%) top, red, blue)", + "radial-gradient(at left calc(25%), red, blue)", + "radial-gradient(at calc(25px) top, red, blue)", + "radial-gradient(at left calc(25px), red, blue)", + "radial-gradient(at calc(-25%) top, red, blue)", + "radial-gradient(at left calc(-25%), red, blue)", + "radial-gradient(at calc(-25px) top, red, blue)", + "radial-gradient(at left calc(-25px), red, blue)", + "radial-gradient(at calc(100px + -25%) top, red, blue)", + "radial-gradient(at left calc(100px + -25%), red, blue)", + "radial-gradient(at calc(100px + -25px) top, red, blue)", + "radial-gradient(at left calc(100px + -25px), red, blue)", + + "image-set(linear-gradient(green, green) 1x, url(foobar.png) 2x)", + "image-set(linear-gradient(red, red), url(foobar.png) 2x)", + "image-set(url(foobar.png) 2x)", + "image-set(url(foobar.png) 1x, url(bar.png) 2x, url(baz.png) 3x)", + "image-set('foobar.png', 'bar.png' 2x, url(baz.png) 3x)", + "image-set(url(foobar.png) type('image/png'))", + "image-set(url(foobar.png) 1x type('image/png'))", + "image-set(url(foobar.png) type('image/png') 1x)", + + ...(IsCSSPropertyPrefEnabled("layout.css.cross-fade.enabled") + ? [ + "cross-fade(red, blue)", + "cross-fade(red)", + "cross-fade(red 50%)", + // see: <https://github.com/w3c/csswg-drafts/issues/5333>. This + // may become invalid depending on how discussion on that issue + // goes. + "cross-fade(red -50%, blue 150%)", + "cross-fade(red -50%, url(www.example.com))", + + "cross-fade(url(http://placekitten.com/200/300), 55% linear-gradient(red, blue))", + "cross-fade(cross-fade(red, white), cross-fade(blue))", + "cross-fade(gold 77%, 60% blue)", + + "cross-fade(url(http://placekitten.com/200/300), url(http://placekitten.com/200/300))", + "cross-fade(#F0F8FF, rgb(0, 0, 0), rgba(0, 255, 0, 1) 25%)", + ] + : []), + + // Conic gradient + "conic-gradient(red, blue)", + "conic-gradient(red,blue,yellow)", + "conic-gradient( red , blue, yellow)", + "conic-gradient(red 0, blue 50deg)", + "conic-gradient(red 10%, blue 50%)", + "conic-gradient(red -50deg, blue 50deg)", + "conic-gradient(red 50deg, blue 0.3turn, yellow 200grad, orange 60% 5rad)", + + "conic-gradient(red 0 100%)", + "conic-gradient(red 0 50%, blue 50%)", + "conic-gradient(red 0 50deg, blue 50% 100%)", + "conic-gradient(red 0 50%, 0deg, blue 50%)", + "conic-gradient(red 0deg 50%, 0%, blue 50% 100%)", + + "conic-gradient(from 0, red, blue)", + "conic-gradient(from 40deg, red, blue)", + "conic-gradient(from 0.4turn, red, blue)", + "conic-gradient(from 200grad, red, blue)", + "conic-gradient(from 5rad, red, blue)", + + "conic-gradient(at top, red, blue)", + "conic-gradient(at top left, red, blue)", + "conic-gradient(at left top, red, blue)", + "conic-gradient(at center center, red, blue)", + "conic-gradient(at 20% bottom, red, blue)", + "conic-gradient(at center 20%, red, blue)", + "conic-gradient(at left 35px, red, blue)", + "conic-gradient(at 10% 10em, red, blue)", + "conic-gradient(at 44px top, red, blue)", + "conic-gradient(at 0 0, red, blue)", + "conic-gradient(at 10px, red, blue)", + + "conic-gradient(at calc(25%) top, red, blue)", + "conic-gradient(at left calc(25%), red, blue)", + "conic-gradient(at calc(25px) top, red, blue)", + "conic-gradient(at left calc(25px), red, blue)", + "conic-gradient(at calc(-25%) top, red, blue)", + "conic-gradient(at left calc(-25%), red, blue)", + "conic-gradient(at calc(-25px) top, red, blue)", + "conic-gradient(at left calc(-25px), red, blue)", + "conic-gradient(at calc(100px + -25%) top, red, blue)", + "conic-gradient(at left calc(100px + -25%), red, blue)", + "conic-gradient(at calc(100px + -25px) top, red, blue)", + "conic-gradient(at left calc(100px + -25px), red, blue)", + + "conic-gradient(from 0 at 0 0, red, blue)", + "conic-gradient(from 40deg at 50%, red, blue)", + "conic-gradient(from 0.4turn at left 30%, red, blue)", + "conic-gradient(from 200grad at calc(100px + -25%) top, red, blue)", + "conic-gradient(from 5rad at 10px, red, blue)", + + "repeating-conic-gradient(red, blue)", + "repeating-conic-gradient(red, yellow, blue)", + "repeating-conic-gradient(red 1deg, yellow 20%, blue 5rad, green)", + "repeating-conic-gradient(red, yellow, green, blue 50%)", + "repeating-conic-gradient(red -50%, yellow -25%, green, blue)", + "repeating-conic-gradient(red -99deg, yellow, green, blue 120%)", + "repeating-conic-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))", + "repeating-conic-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + + "repeating-conic-gradient(from 0, red, blue)", + "repeating-conic-gradient(from 40deg, red, blue)", + "repeating-conic-gradient(from 0.4turn, red, blue)", + "repeating-conic-gradient(from 200grad, red, blue)", + "repeating-conic-gradient(from 5rad, red, blue)", + + "repeating-conic-gradient(at top left, red, blue)", + "repeating-conic-gradient(at 0 0, red, blue)", + "repeating-conic-gradient(at 20% bottom, red, blue)", + "repeating-conic-gradient(at center 20%, red, blue)", + "repeating-conic-gradient(at left 35px, red, blue)", + "repeating-conic-gradient(at 10% 10em, red, blue)", + "repeating-conic-gradient(at 44px top, red, blue)", + + "repeating-conic-gradient(from 0 at 0 0, red, blue)", + "repeating-conic-gradient(from 40deg at 50%, red, blue)", + "repeating-conic-gradient(from 0.4turn at left 30%, red, blue)", + "repeating-conic-gradient(from 200grad at calc(100px + -25%) top, red, blue)", + "repeating-conic-gradient(from 5rad at 10px, red, blue)", + + // 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)", + + // 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( 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)", + + // Linear-gradient with calc expression (bug 1363349) + "-webkit-gradient(linear, calc(5 + 5) top, calc(10 + 10) top, from(blue), to(lime))", + "-webkit-gradient(linear, calc(5 - 5) top, calc(10 + 10) top, from(blue), to(lime))", + "-webkit-gradient(linear, calc(5 * 5) top, calc(10 + 10) top, from(blue), to(lime))", + "-webkit-gradient(linear, calc(5 / 5) top, calc(10 + 10) top, from(blue), to(lime))", + "-webkit-gradient(linear, left calc(25% - 10%), right calc(75% + 10%), from(blue), to(lime))", + "-webkit-gradient(linear, calc(1) 2, 3 4)", + + // Radial-gradient with calc expression (bug 1363349) + "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1 + 5), from(blue), to(lime))", + "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5), from(blue), to(lime))", + "-webkit-gradient(radial, 1 2, calc(1 - 2), 3 4, calc(1 + 5), from(blue), to(lime))", + "-webkit-gradient(radial, 1 2, calc(1 * 2), 3 4, calc(1 + 5), from(blue), to(lime))", + "-webkit-gradient(radial, 1 2, calc(1 / 2), 3 4, calc(1 + 5), from(blue), to(lime))", + "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))", + "-webkit-gradient(radial, 1 2, calc(8), 3 4, 9)", + + // 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(top, red, blue)", + "-webkit-linear-gradient(left, red, blue)", + "-webkit-linear-gradient(bottom, 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%)", +]; +var invalidNonUrlImageValues = [ + "-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 */ + "radial-gradient(at 10% bottom, ffffff, black) scroll no-repeat", + /* no quirks mode lengths */ + "linear-gradient(red -99, yellow, green, blue 120%)", + /* Unitless nonzero numbers are valid as an <angle> */ + "linear-gradient(30, red, blue)", + /* There must be a comma between gradient-line (e.g. <angle>) and colors */ + "linear-gradient(30deg red, blue)", + "linear-gradient(to top left red, blue)", + "linear-gradient(to right red, blue)", + /* Invalid color or calc() 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)", + + "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)", + + /* Invalid units */ + "conic-gradient(red, blue 50px, yellow 30px)", + "repeating-conic-gradient(red 1deg, yellow 20%, blue 24em, green)", + "conic-gradient(from 0%, black, white)", + "conic-gradient(from 60%, black, white)", + "conic-gradient(from 40px, black, white)", + "conic-gradient(from 50, black, white)", + "conic-gradient(at 50deg, black, white)", + "conic-gradient(from 40deg at 50deg, black, white)", + "conic-gradient(from 40deg at 50deg 60deg, black, white)", + /* Invalid keywords (or ordering) */ + "conic-gradient(at 40% from 50deg, black, white)", + "conic-gradient(to 50deg, black, white)", + + /* Used to be 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)", + + "linear-gradient(unset, 10px 10px, from(blue))", + "linear-gradient(unset, 10px 10px, blue 0)", + "repeating-linear-gradient(unset, 10px 10px, blue 0)", + + "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)", + "radial-gradient(circle red, blue)", + + /* don't allow more than two positions with multi-position syntax */ + "linear-gradient(red 0% 50% 100%)", + "linear-gradient(red 0% 50% 75%, blue 75%)", + "linear-gradient(to bottom, red 0% 50% 100%)", + "linear-gradient(to bottom, red 0% 50% 75%, blue 75%)", + "radial-gradient(red 0% 50% 100%)", + "radial-gradient(red 0% 50% 75%, blue 75%)", + "radial-gradient(center, red 0% 50% 100%)", + "radial-gradient(center, red 0% 50% 75%, blue 75%)", + "conic-gradient(red 0% 50% 100%)", + "conic-gradient(red 0% 50% 75%, blue 75%)", + "conic-gradient(center, red 0% 50% 100%)", + "conic-gradient(center, red 0% 50% 75%, blue 75%)", + + // missing color in color stop + "conic-gradient(red 50deg, blue 0.3turn, yellow 200grad, orange 60%, 5rad)", + + "-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)", + + // 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, 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 + "-webkit-gradient(radial, 1 2, -1.5, center center, +99999.9999)", // Negative 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, 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()", + + // * missing comma between <legacy-gradient-line> and color list: + "-webkit-linear-gradient(0 red, blue)", + "-webkit-linear-gradient(30deg red, blue)", + "-webkit-linear-gradient(top right red, blue)", + "-webkit-linear-gradient(bottom red, blue)", + + // Linear-gradient with calc expression containing mixed units + // (bug 1363349) + "-webkit-gradient(linear, calc(5 + 5%) top, calc(10 + 10) top, from(blue), to(lime))", + "-webkit-gradient(linear, left calc(25 - 10%), right calc(75% + 10%), from(blue), to(lime))", + + // Radial-gradient with calc expression containing mixed units, or a + // percentage in the radius (bug 1363349) + "-webkit-gradient(radial, 1 2, 0, 3 4, calc(1% + 5%), from(blue), to(lime))", + "-webkit-gradient(radial, 1 2, calc(1 + 2), 3 4, calc(1 + 5%), from(blue), to(lime))", + "-webkit-gradient(radial, calc(0 + 1) calc(1 + 1), calc(1% + 2%), calc(1 + 2) 4, calc(1 + 5), from(blue), to(lime))", + + // 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)", + + // Conic gradients should not support prefixed syntax + "-webkit-gradient(conic, 1 2, 3 4, color-stop(0, lime))", + "-webkit-conic-gradient(red, blue)", + "-moz-conic-gradient(red, blue)", + "-webkit-repeating-conic-gradient(red, blue)", + "-moz-repeating-conic-gradient(red, blue)", + + "image-set(url(foobar.png) 1x, none)", + "image-set(garbage)", + "image-set(image-set('foobar.png', 'bar.png' 2x) 1x, url(baz.png) 3x)", // Nested image-sets should fail to parse + "image-set(image-set(garbage))", + "image-set()", + "image-set(type('image/png') url(foobar.png) 1x)", + "image-set(url(foobar.png) type('image/png') 1x type('image/png'))", + "image-set(url(foobar.png) type('image/png') type('image/png'))", + "image-set(url(foobar.png) type(image/png))", + "image-set(url(foobar.png) epyt('image/png'))", + + ...(IsCSSPropertyPrefEnabled("layout.css.cross-fade.enabled") + ? [ + "cross-fade(red blue)", + "cross-fade()", + "cross-fade(50%, blue 50%)", + // Old syntax + "cross-fade(red, white, 50%)", + // see: <https://github.com/w3c/csswg-drafts/issues/5333>. This + // may become invalid depending on how discussion on that issue + // goes. + "cross-fade(red, 150%, blue)", + "cross-fade(red auto, blue 10%)", + + // nested invalidity should propagate. + "cross-fade(url(http://placekitten.com/200/300), 55% linear-gradient(center, red, blue))", + "cross-fade(cross-fade(red, white, 50%), cross-fade(blue))", + + "cross-fade(url(http://placekitten.com/200/300) url(http://placekitten.com/200/300))", + "cross-fade(#F0F8FF, rgb(0, 0, 0), rgba(0, 255, 0, 1), 25%)", + ] + : []), +]; +var unbalancedGradientAndElementValues = ["-moz-element(#a()"]; + +var basicShapeSVGBoxValues = [ + "fill-box", + "stroke-box", + "view-box", + + "polygon(evenodd, 20pt 20cm) fill-box", + "polygon(evenodd, 20ex 20pc) stroke-box", + "polygon(evenodd, 20rem 20in) view-box", +]; + +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)", + + "content-box", + "padding-box", + "border-box", + + "polygon(0 0) content-box", + "border-box polygon(0 0)", + "padding-box polygon( 0 20px , 30px 20% ) ", + + "circle()", + "circle(at center)", + "circle(at top 0px 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 0px 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 basicShapeOtherValuesWithFillRule = [ + "polygon(nonzero, 20px 20px, 30% 30%, 30px 30px)", + "polygon(evenodd, 20px 20px, 30% 30%, 30px 30px)", + "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", +]; + +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))", + "circle(at top left 20px)", + + "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))", + "ellipse(at top left 20px)", + + "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", +]; + +var basicShapeXywhRectValues = []; +if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-xywh.enabled")) { + basicShapeXywhRectValues.push( + "xywh(1px 2% 3px 4em)", + "xywh(1px 2% 3px 4em round 0px)", + "xywh(1px 2% 3px 4em round 0px 1%)", + "xywh(1px 2% 3px 4em round 0px 1% 2px)", + "xywh(1px 2% 3px 4em round 0px 1% 2px 3em)" + ); +} + +if (IsCSSPropertyPrefEnabled("layout.css.basic-shape-rect.enabled")) { + basicShapeXywhRectValues.push( + "rect(auto auto auto auto)", + "rect(1px 2% auto 4em)", + "rect(1px 2% auto 4em round 0px)", + "rect(1px 2% auto 4em round 0px 1%)", + "rect(1px 2% auto 4em round 0px 1% 2px)", + "rect(1px 2% auto 4em round 0px 1% 2px 3em)" + ); +} + +if (/* mozGradientsEnabled */ true) { + // Maybe one day :( + // Extend gradient lists with valid/invalid moz-prefixed expressions: + validNonUrlImageValues.push( + "-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(top, red, blue)", + + "-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(left, red, blue)", + "-moz-linear-gradient(bottom, red, blue)", + + "-moz-linear-gradient(0, red, blue)", + + "-moz-linear-gradient(-33deg, 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))", + + "-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(0 0, red, blue)", + "-moz-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)", + + "-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-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-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)", + + "-moz-repeating-radial-gradient(farthest-corner, red, blue)", + "-moz-repeating-radial-gradient(circle, red, blue)", + "-moz-repeating-radial-gradient(ellipse closest-corner, 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(-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)" + ); + + invalidNonUrlImageValues.push( + // The entries in this block used to be valid with the older more-complex + // -moz prefixed gradient syntax, but we've since simplified the syntax for + // consistency with -webkit prefixed gradients, in a way that makes these + // invalid now. + "-moz-linear-gradient(center 0%, red, blue)", + "-moz-linear-gradient(50% top, red, blue)", + "-moz-linear-gradient(50% 0%, 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(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(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-radial-gradient(top left 45deg, 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(-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(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(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(30grad left 35px, red, blue)", + "-moz-repeating-linear-gradient(10deg 20px, red, blue)", + "-moz-repeating-linear-gradient(.414rad bottom, red, blue)", + + /* Negative radii */ + "-moz-radial-gradient(40%, -100px -10%, red, blue)", + + /* 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", + /* Unitless 0 is invalid as an <angle> */ + "-moz-linear-gradient(top left 0, red, blue)", + "-moz-linear-gradient(5px 5px 0, red, blue)", + /* There must be a comma between gradient-line (e.g. <angle>) and colors */ + "-moz-linear-gradient(30deg red, blue)", + "-moz-linear-gradient(5px 5px 30deg red, blue)", + "-moz-linear-gradient(5px 5px red, blue)", + "-moz-linear-gradient(top left 30deg 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(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)", + + /* 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)" + ); +} + +const pathValues = { + other_values: [ + "path('M 10 10 20 20 H 90 V 90 Z')", + "path('M10 10 20,20H90V90Z')", + "path('M 10 10 C 20 20, 40 20, 50 10')", + "path('M 10 80 C 40 10, 65 10, 95 80 S 1.5e2 150, 180 80')", + "path('M 10 80 Q 95 10 180 80')", + "path('M 10 80 Q 52.5 10, 95 80 T 180 80')", + "path('M 80 80 A 45 45, 0, 0, 0, 1.25e2 1.25e2 L 125 80 Z')", + "path('M100-200h20z')", + "path('M10,10L20.6.5z')", + ], + invalid_values: [ + "path()", + "path(a)", + "path('M 10 Z')", + "path('M 10-10 20')", + "path('M 10 10 C 20 20 40 20')", + ], +}; + +var gCSSProperties = { + animation: { + domProp: "animation", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + applies_to_marker: true, + 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", + "2s unset", + ], + }, + "animation-delay": { + domProp: "animationDelay", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + initial_values: ["0s", "0ms"], + other_values: [ + "1s", + "250ms", + "-100ms", + "-1s", + "1s, 250ms, 2.3s", + "calc(1s + 2ms)", + ], + invalid_values: ["0", "0px"], + }, + "animation-direction": { + domProp: "animationDirection", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + 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", + "normal, unset", + "unset, normal", + ], + }, + "animation-duration": { + domProp: "animationDuration", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0s", "0ms"], + applies_to_marker: true, + other_values: ["1s", "250ms", "1s, 250ms, 2.3s", "calc(1s + 2ms)"], + invalid_values: ["0", "0px", "-1ms", "-2s"], + }, + "animation-fill-mode": { + domProp: "animationFillMode", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + 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, + applies_to_marker: true, + initial_values: ["1"], + other_values: [ + "infinite", + "0", + "0.5", + "7.75", + "-0.0", + "1, 2, 3", + "infinite, 2", + "1, infinite", + "calc(1 + 2.0)", + ], + // 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, + applies_to_marker: true, + 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", + "bounce, unset", + "unset, bounce", + ], + }, + "animation-play-state": { + domProp: "animationPlayState", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + 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, + applies_to_marker: true, + 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)", + "steps(calc(2 + 1))", + "steps(1, jump-start)", + "steps(1, jump-end)", + "steps(2, jump-none)", + "steps(1, jump-both)", + ], + 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)", + "steps(0, jump-start)", + "steps(0, jump-end)", + "steps(1, jump-none)", + "steps(0, jump-both)", + ], + }, + appearance: { + domProp: "appearance", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["auto", "radio", "menulist"], + invalid_values: [], + }, + "-moz-appearance": { + domProp: "MozAppearance", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "appearance", + subproperties: ["appearance"], + }, + "-webkit-appearance": { + domProp: "webkitAppearance", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "appearance", + subproperties: ["appearance"], + }, + "aspect-ratio": { + domProp: "aspectRatio", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "1", + "1.0", + "1 / 2", + "1/2", + "16.2 / 9.5", + "1/0", + "0/1", + "0 / 0", + "auto 1", + "0 auto", + ], + invalid_values: ["none", "1 test", "1 / auto", "auto / 1"], + }, + "border-inline": { + domProp: "borderInline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "border-inline-start-color", + "border-inline-start-style", + "border-inline-start-width", + "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 solid green"], + }, + "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-color": { + domProp: "borderInlineColor", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["border-inline-start-color", "border-inline-end-color"], + initial_values: ["currentColor"], + other_values: ["green", "rgba(255,128,0,0.5) blue", "blue transparent"], + invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"], + }, + "border-inline-end-color": { + domProp: "borderInlineEndColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + initial_values: ["currentColor"], + other_values: ["green", "rgba(255,128,0,0.5)", "transparent"], + invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"], + }, + "border-inline-style": { + domProp: "borderInlineStyle", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["border-inline-start-style", "border-inline-end-style"], + initial_values: ["none"], + other_values: [ + "solid", + "dashed solid", + "solid dotted", + "double double", + "inset outset", + "inset double", + "none groove", + "ridge none", + ], + invalid_values: [], + }, + "border-inline-end-style": { + domProp: "borderInlineEndStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + /* 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-width": { + domProp: "borderInlineWidth", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["border-inline-start-width", "border-inline-end-width"], + prerequisites: { "border-style": "solid" }, + initial_values: ["medium", "3px", "medium medium"], + other_values: [ + "thin", + "thick", + "1px", + "2em", + "calc(2px)", + "calc(2px) thin", + "calc(-2px)", + "calc(-2px) thick", + "calc(0em)", + "medium calc(0em)", + "calc(0px)", + "1px calc(0px)", + "calc(5em)", + "1em calc(5em)", + ], + invalid_values: ["5%", "5", "5 thin", "thin 5%", "blue", "solid"], + }, + "border-inline-end-width": { + domProp: "borderInlineEndWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + 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, + applies_to_first_letter: true, + initial_values: ["none"], + other_values: ["url('border.png')"].concat(validNonUrlImageValues), + invalid_values: ["url('border.png') url('border.png')"].concat( + invalidNonUrlImageValues + ), + unbalanced_values: [].concat(unbalancedGradientAndElementValues), + }, + "border-image-slice": { + domProp: "borderImageSlice", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + 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", + "calc(10px)", + "calc(10px + 5%)", + ], + 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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + 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"], + }, + "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)", + "unset 2px", + "unset / 2px", + "2px unset", + "2px / unset", + ], + }, + "border-bottom-left-radius": { + domProp: "borderBottomLeftRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + 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)", + "unset 2px", + "2px unset", + ], + }, + "border-bottom-right-radius": { + domProp: "borderBottomRightRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + 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)", + "unset 2px", + "2px unset", + ], + }, + "border-top-left-radius": { + domProp: "borderTopLeftRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + 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)", + "unset 2px", + "2px unset", + ], + }, + "border-top-right-radius": { + domProp: "borderTopRightRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + 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)", + "unset 2px", + "2px unset", + ], + }, + "border-start-start-radius": { + domProp: "borderStartStartRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + 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)", + "unset 2px", + "2px unset", + ], + }, + "border-start-end-radius": { + domProp: "borderStartEndRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + 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)", + "unset 2px", + "2px unset", + ], + }, + "border-end-start-radius": { + domProp: "borderEndStartRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + 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)", + "unset 2px", + "2px unset", + ], + }, + "border-end-end-radius": { + domProp: "borderEndEndRadius", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + 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)", + "unset 2px", + "2px unset", + ], + }, + "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, + applies_to_first_letter: true, + logical: true, + 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, + applies_to_first_letter: true, + logical: true, + /* 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, + applies_to_first_letter: true, + logical: true, + 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-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-decoration-break": { + domProp: "boxDecorationBreak", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["slice"], + other_values: ["clone"], + invalid_values: ["auto", "none", "1px"], + }, + "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"], + }, + "print-color-adjust": { + domProp: "printColorAdjust", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["economy"], + other_values: ["exact"], + invalid_values: [], + }, + "color-adjust": { + domProp: "colorAdjust", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "print-color-adjust", + subproperties: ["print-color-adjust"], + }, + "color-scheme": { + domProp: "colorScheme", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + other_values: [ + "light", + "dark", + "light dark", + "light dark purple", + "light light dark", + "only light", + "only light dark", + "only light dark purple", + "light only", + ], + invalid_values: ["only normal", "normal only", "only light only"], + }, + 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", + ], + }, + "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"], + }, + "column-fill": { + domProp: "columnFill", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["balance"], + other_values: ["auto"], + invalid_values: ["2px", "dotted", "5em"], + }, + "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", + ], + }, + "column-rule-width": { + domProp: "columnRuleWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { "column-rule-style": "solid" }, + initial_values: ["medium", "3px", "calc(3px)", "calc(5em + 3px - 5em)"], + other_values: [ + "thin", + "15px", + /* 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))", + + "min(5px)", + "min(5px,2em)", + + "max(5px)", + "max(5px,2em)", + + "calc(min(5px))", + "calc(min(5px,2em))", + + "calc(max(5px))", + "calc(max(5px,2em))", + + // 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 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)", + "-moz-max(5px)", + "-moz-min(5px,2em)", + "-moz-max(5px,2em)", + "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)", + ], + }, + "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"], + }, + "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"], + }, + "column-span": { + domProp: "columnSpan", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["all"], + invalid_values: ["-1", "0", "auto", "2px"], + }, + "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%"], + }, + d: { + domProp: "d", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["path('')", "path(' ')"].concat(pathValues.other_values), + invalid_values: pathValues.invalid_values, + }, + "-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: [], + }, + "margin-inline": { + domProp: "marginInline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["margin-inline-start", "margin-inline-end"], + initial_values: ["0", "0px 0em"], + other_values: [ + "1px", + "3em 1%", + "5%", + "calc(2px) 1%", + "calc(-2px) 1%", + "calc(50%) 1%", + "calc(3*25px) calc(2px)", + "calc(25px*3) 1em", + "calc(3*25px + 50%) calc(3*25px - 50%)", + ], + invalid_values: [ + "5", + "..25px", + ".+5px", + ".px", + "-.px", + "++5px", + "-+4px", + "+-3px", + "--7px", + "+-.6px", + "-+.5px", + "++.7px", + "--.4px", + ], + }, + "margin-inline-end": { + domProp: "marginInlineEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + /* 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, + applies_to_first_letter: true, + logical: true, + /* 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: { + 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)", + "radial-gradient(at 10% bottom, #ffffff, black) add no-repeat", + "repeating-radial-gradient(at 10% bottom, #ffffff, black) no-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", + "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)", + "alpha padding-box url(404.png) border-box", + "alpha padding-box url(404.png) padding-box", + ], + invalid_values: [ + /* mixes with keywords have to be in correct order */ + "50% left", + "top 50%", + /* no quirks mode colors */ + "radial-gradient(at 10% bottom, ffffff, black) add no-repeat", + /* no quirks mode lengths */ + "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", + "-moz-element(#a rubbish)", + "left top / match-source", + ], + }, + "mask-clip": { + domProp: "maskClip", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["border-box"], + other_values: [ + "content-box", + "fill-box", + "stroke-box", + "view-box", + "no-clip", + "padding-box", + "border-box, padding-box", + "padding-box, padding-box, padding-box", + "border-box, border-box", + ], + invalid_values: ["content-box content-box", "margin-box"], + }, + "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"], + }, + "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(validNonUrlImageValues), + invalid_values: [].concat(invalidNonUrlImageValues), + unbalanced_values: [].concat(unbalancedGradientAndElementValues), + }, + "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"], + }, + "mask-origin": { + domProp: "maskOrigin", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["border-box"], + other_values: [ + "padding-box", + "content-box", + "fill-box", + "stroke-box", + "view-box", + "border-box, padding-box", + "padding-box, padding-box, padding-box", + "border-box, border-box", + ], + invalid_values: ["padding-box padding-box", "no-clip", "margin-box"], + }, + "mask-position": { + domProp: "maskPosition", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + initial_values: [ + "top 0% left 0%", + "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 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)", + "left top 15px", + "left 10px top", + ], + }, + "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%", + ], + }, + "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%", + ], + }, + "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", + ], + }, + "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)", + ], + }, + "mask-type": { + domProp: "maskType", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["luminance"], + other_values: ["alpha"], + invalid_values: [], + }, + "padding-inline-end": { + domProp: "paddingInlineEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + logical: true, + /* 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, + applies_to_first_letter: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + logical: true, + /* 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, + // No applies_to_placeholder because we have a !important rule in forms.css. + prerequisites: { display: "block", overflow: "auto" }, + initial_values: ["none"], + other_values: ["both", "horizontal", "vertical", "inline", "block"], + invalid_values: [], + }, + "tab-size": { + domProp: "tabSize", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["8"], + other_values: [ + "0", + "2.5", + "3", + "99", + "12000", + "0px", + "1em", + "calc(1px + 1em)", + "calc(1px - 2px)", + "calc(1 + 1)", + "calc(-2.5)", + ], + invalid_values: [ + "9%", + "calc(9% + 1px)", + "calc(1 + 1em)", + "-1", + "-808", + "auto", + ], + }, + "-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)", + "scalex(10%)", + "scalex(-10)", + "scalex(-10%)", + "scaley(10)", + "scaley(10%)", + "scaley(-10)", + "scaley(-10%)", + "scale(10)", + "scale(10%)", + "scale(10, 20)", + "scale(10%, 20%)", + "scale(-10)", + "scale(-10%)", + "scale(-10, 20)", + "scale(10%, -20%)", + "scale(10, 20%)", + "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))", + "translate(10px, calc(min(5px,10%)))", + "translate(calc(max(5px,10%)), 10%)", + "translate(max(5px,10%), 10%)", + "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(4%, 4%, 4%)", + "scale3d(-2, 3, -7)", + "scale3d(-2%, 3%, -7%)", + "scalez(4)", + "scalez(4%)", + "scalez(-6)", + "scalez(-6%)", + "rotate3d(2, 3, 4, 45deg)", + "rotate3d(-3, 7, 0, 12rad)", + "rotatex(15deg)", + "rotatey(-12grad)", + "rotatez(72rad)", + "rotatex(0.125turn)", + "rotate3d(0, 0, 0, 0rad)", + "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)", + "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%))", + "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-box": { + domProp: "transformBox", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["view-box"], + other_values: ["fill-box", "border-box"], + invalid_values: ["padding-box", "margin-box"], + }, + "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-input": { + domProp: "MozUserInput", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["none"], + 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_SHORTHAND_AND_LONGHAND, + alias_for: "user-select", + subproperties: ["user-select"], + }, + "user-select": { + domProp: "userSelect", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["none", "text", "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 8px scroll none transparent repeat", + "50% transparent", + "transparent 50%", + "50%", + "radial-gradient(at 10% bottom, #ffffff, black) scroll no-repeat", + "repeating-radial-gradient(at 10% bottom, #ffffff, black) scroll no-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", + "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", + "url(404.png) green padding-box text", + "content-box text url(404.png) blue", + /* 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", + /* text */ + "text", + "text border-box", + ], + invalid_values: [ + /* mixes with keywords have to be in correct order */ + "50% left", + "top 50%", + /* no quirks mode colors */ + "radial-gradient(at 10% bottom, ffffff, black) scroll no-repeat", + /* no quirks mode lengths */ + "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)", + /* error inside functions */ + "-moz-element(#a rubbish) black", + "content-box text text", + "padding-box text url(404.png) text", + ], + }, + "background-attachment": { + domProp: "backgroundAttachment", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["scroll"], + other_values: [ + "fixed", + "local", + "scroll,scroll", + "fixed, scroll", + "scroll, fixed, local, scroll", + "fixed, fixed", + ], + invalid_values: [], + }, + "background-blend-mode": { + domProp: "backgroundBlendMode", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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", "plus-lighter"], + }, + "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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["border-box"], + other_values: [ + "content-box", + "padding-box", + "border-box, padding-box", + "padding-box, padding-box, padding-box", + "border-box, border-box", + "text", + "content-box, text", + "text, border-box", + "text, text", + ], + invalid_values: [ + "margin-box", + "border-box border-box", + "fill-box", + "stroke-box", + "view-box", + "no-clip", + ], + }, + "background-color": { + domProp: "backgroundColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["transparent", "rgba(0, 0, 0, 0)"], + 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)", + "rgba(255, 127, 15, 0)", + "hsla(240, 97%, 50%, 0.0)", + "rgba(255,255,255,-3.7)", + ], + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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(validNonUrlImageValues), + invalid_values: [].concat(invalidNonUrlImageValues), + unbalanced_values: [].concat(unbalancedGradientAndElementValues), + }, + "background-origin": { + domProp: "backgroundOrigin", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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", + "fill-box", + "stroke-box", + "view-box", + "no-clip", + ], + }, + "background-position": { + domProp: "backgroundPosition", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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", + "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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + /* 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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + /* 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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + /* 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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + /* 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, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + 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)", + "unset, 2px 2px", + "2px 2px, unset", + "inset unset", + ], + }, + "caption-side": { + domProp: "captionSide", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["top"], + other_values: ["bottom"], + invalid_values: ["right", "left", "top-outside", "bottom-outside"], + }, + "caret-color": { + domProp: "caretColor", + inherited: true, + type: CSS_TYPE_LONGHAND, + prerequisites: { color: "black" }, + // Though "auto" is an independent computed-value time keyword value, + // it is not distinguishable from currentcolor because getComputedStyle + // always returns used value for <color>. + initial_values: ["auto", "currentcolor", "black", "rgb(0,0,0)"], + other_values: ["green", "transparent", "rgba(128,128,128,.5)", "#123"], + invalid_values: ["#0", "#00", "#00000", "cc00ff"], + }, + clear: { + domProp: "clear", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["left", "right", "both", "inline-start", "inline-end"], + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + /* 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%)", + /* CSS4 System Colors */ + "canvastext", + /* Preserve previously available specially prefixed colors */ + "-moz-default-color", + ], + other_values: [ + "green", + "#f3c", + "#fed292", + "rgba(45,300,12,2)", + "transparent", + "LinkText", + "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%)", + "hsl(63e292, 41%, 34%)", + /* CSS4 System Colors */ + "canvas", + "linktext", + "visitedtext", + "activetext", + "buttonface", + "field", + "highlight", + "graytext", + /* Preserve previously available specially prefixed colors */ + "-moz-activehyperlinktext", + "-moz-default-background-color", + "-moz-hyperlinktext", + "-moz-visitedhyperlinktext", + /* color-mix */ + "color-mix(in srgb, red, blue)", + "color-mix(in srgb, highlight, rgba(0, 0, 0, .5))", + "color-mix(in srgb, color-mix(in srgb, red 10%, blue), green)", + "color-mix(in srgb, blue, red 80%)", + "color-mix(in srgb, rgba(0, 200, 32, .5) 90%, red 50%)", + "color-mix(in srgb, currentColor, red)", + ], + 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% /)", + /* color-mix */ + "color-mix(red, blue)", + "color-mix(red blue)", + "color-mix(in srgb, red blue)", + "color-mix(in srgb, red 10% blue)", + ], + quirks_values: { + "000000": "#000000", + "96ed2a": "#96ed2a", + fff: "#ffffff", + ffffff: "#ffffff", + }, + }, + content: { + domProp: "content", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + // XXX This really depends on pseudo-element-ness. + 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)", + "attr(\\32)", + "attr(\\2)", + "attr(-\\2)", + "attr(-\\32)", + 'attr(title, "fallback")', + 'attr(\\32, "fallback")', + 'attr(-\\32, "fallback")', + "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'))", + "image-set(url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==))", + ].concat(validNonUrlImageValues), + invalid_values: [ + "counter(foo, none)", + "counters(bar, '.', none)", + "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'", + ].concat(invalidNonUrlImageValues), + }, + "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"], + }, + "counter-set": { + domProp: "counterSet", + 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", + "url(foo.png) calc(1 + 2) calc(3), pointer", + "image-set(url(foo.png)), auto", + ], + invalid_values: [ + "url(foo.png)", + "url(foo.png) 5 5", + "image-set(linear-gradient(red, blue)), auto", + // Gradients are supported per spec, but we don't have support for it yet + "linear-gradient(red, blue), auto", + ], + }, + direction: { + domProp: "direction", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + initial_values: ["ltr"], + other_values: ["rtl"], + invalid_values: [], + }, + display: { + domProp: "display", + inherited: false, + type: CSS_TYPE_LONGHAND, + // No applies_to_placeholder because we have a !important rule in forms.css. + 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 list-item", + "inline flow-root 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", + "block ruby", + "ruby", + "ruby-base", + "ruby-base-container", + "ruby-text", + "ruby-text-container", + "contents", + "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, + applies_to_first_letter: true, + initial_values: ["none"], + other_values: ["left", "right", "inline-start", "inline-end"], + 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-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", + // 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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"', + '"ab\\"c"', + '"ab\\\\c"', + "'vert' calc(2)", + ], + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["auto"], + other_values: ["normal", "none"], + invalid_values: ["on"], + }, + "font-language-override": { + domProp: "fontLanguageOverride", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["normal"], + other_values: ["'ENG'", "'TRK'", '"TRK"', "'N\\'Ko'"], + invalid_values: ["TRK", "ja"], + }, + "font-size": { + domProp: "fontSize", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: [ + "medium", + "1rem", + "calc(1rem)", + "calc(0.75rem + 200% - 125% + 0.25rem - 75%)", + ], + other_values: [ + "large", + "2em", + "50%", + "xx-small", + "xxx-large", + "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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["none"], + other_values: [ + "0.7", + "0.0", + "0", + "3", + "from-font", + "cap-height 0.8", + "ch-width 0.4", + "ic-width 0.4", + "ic-height 0.9", + "ch-width from-font", + ], + invalid_values: [ + "-0.3", + "-1", + "normal", + "none none", + "cap-height none", + "none from-font", + "from-font none", + "0.5 from-font", + "0.5 cap-height", + "cap-height, 0.8", + ], + }, + "font-stretch": { + domProp: "fontStretch", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["normal"], + other_values: ["italic", "oblique"], + invalid_values: [], + }, + "font-synthesis": { + domProp: "fontSynthesis", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + subproperties: [ + "font-synthesis-weight", + "font-synthesis-style", + "font-synthesis-small-caps", + "font-synthesis-position", + ], + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: [ + "weight style small-caps position", + "weight small-caps style position", + "small-caps weight position style", + "small-caps style position weight", + "style position weight small-caps", + "style position small-caps weight", + ], + other_values: [ + "none", + "weight", + "style", + "small-caps", + "position", + "weight style", + "style weight", + "weight small-caps", + "small-caps weight", + "weight position", + "position weight", + "style small-caps", + "small-caps style", + "style position", + "position style", + "small-caps position", + "position small-caps", + "weight style small-caps", + "small-caps weight style", + "weight style position", + "position weight style", + "weight small-caps position", + "position weight small-caps", + ], + invalid_values: [ + "10px", + "weight none", + "style none", + "none style", + "none 10px", + "weight 10px", + "weight weight", + "style style", + "small-caps none", + "small-caps small-caps", + "position none", + "position position", + ], + }, + "font-synthesis-weight": { + domProp: "fontSynthesisWeight", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["auto"], + other_values: ["none"], + invalid_values: ["auto none", "weight", "normal", "0"], + }, + "font-synthesis-style": { + domProp: "fontSynthesisStyle", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["auto"], + other_values: ["none"], + invalid_values: ["auto none", "style", "normal", "0"], + }, + "font-synthesis-small-caps": { + domProp: "fontSynthesisSmallCaps", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["auto"], + other_values: ["none"], + invalid_values: ["auto none", "small-caps", "normal", "0"], + }, + "font-synthesis-position": { + domProp: "fontSynthesisPosition", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["auto"], + other_values: ["none"], + invalid_values: ["auto none", "position", "normal", "0"], + }, + "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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["normal"], + other_values: ["super", "sub"], + invalid_values: ["normal sub", "super sub"], + }, + "font-weight": { + domProp: "fontWeight", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["normal", "400"], + other_values: [ + "bold", + "100", + "200", + "300", + "500", + "600", + "700", + "800", + "900", + "bolder", + "lighter", + "10.5", + "calc(10 + 10)", + "calc(10 - 99)", + "100.0", + "107", + "399", + "401", + "699", + "710", + "1000", + ], + invalid_values: ["0", "1001", "calc(10%)"], + }, + height: { + domProp: "height", + inherited: false, + type: CSS_TYPE_LONGHAND, + /* FIXME: test zero, and test calc clamping */ + initial_values: [" auto"], + /* computed value tests for height test more with display:block */ + prerequisites: { display: "block" }, + other_values: [ + "15px", + "3em", + "15%", + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-content", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + initial_values: ["normal", "0", "0px", "calc(0px)"], + other_values: [ + "1em", + "2px", + "-3px", + "calc(1em)", + "calc(1em + 3px)", + "calc(15px / 2)", + "calc(15px/2)", + "calc(-3px)", + ], + invalid_values: [], + quirks_values: { 5: "5px" }, + }, + "line-break": { + domProp: "lineBreak", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["loose", "normal", "strict", "anywhere"], + invalid_values: [], + }, + "line-height": { + domProp: "lineHeight", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + /* + * 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'. + */ + 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", + "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,\\\\)", + ].concat(validNonUrlImageValues), + invalid_values: ["url('border.png') url('border.png')"].concat( + invalidNonUrlImageValues + ), + unbalanced_values: [].concat(unbalancedGradientAndElementValues), + }, + "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, + applies_to_first_letter: true, + /* 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, + applies_to_first_letter: true, + /* 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, + applies_to_first_letter: true, + /* 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, + applies_to_first_letter: true, + /* 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"], + other_values: [ + "30px", + "50%", + "0", + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-content", + "calc(2px)", + "calc(-2px)", + "calc(0px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(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 + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-content", + "calc(2px)", + "calc(-2px)", + "calc(0px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(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)"], + other_values: [ + "30px", + "50%", + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-content", + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(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 + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-content", + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(calc(3*25px + 50%))", + ], + invalid_values: ["none"], + quirks_values: { 5: "5px" }, + }, + "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%"], + }, + "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 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", + "left top 15px", + "left 10px top", + ], + }, + opacity: { + domProp: "opacity", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: [ + "1", + "17", + "397.376", + "3e1", + "3e+1", + "3e0", + "3e+0", + "3e-0", + "300%", + ], + other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"], + 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, + applies_to_cue: true, + 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, + applies_to_cue: true, + // 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, + applies_to_cue: true, + 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", + "clip", + "auto auto", + "auto scroll", + "hidden scroll", + "auto hidden", + "clip clip", + "overlay", + "overlay overlay", + ], + invalid_values: [ + "clip -moz-scrollbars-none", + "-moz-scrollbars-none", + "-moz-scrollbars-horizontal", + "-moz-scrollbars-vertical", + ], + }, + "overflow-x": { + domProp: "overflowX", + inherited: false, + type: CSS_TYPE_LONGHAND, + // No applies_to_placeholder because we have a !important rule in forms.css. + prerequisites: { + display: "block", + "overflow-y": "visible", + contain: "none", + }, + initial_values: ["visible"], + other_values: ["auto", "scroll", "hidden", "clip", "overlay"], + invalid_values: [], + }, + "overflow-y": { + domProp: "overflowY", + inherited: false, + type: CSS_TYPE_LONGHAND, + // No applies_to_placeholder because we have a !important rule in forms.css. + prerequisites: { + display: "block", + "overflow-x": "visible", + contain: "none", + }, + initial_values: ["visible"], + other_values: ["auto", "scroll", "hidden", "clip", "overlay"], + invalid_values: [], + }, + "overflow-inline": { + domProp: "overflowInline", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + prerequisites: { + display: "block", + "overflow-block": "visible", + contain: "none", + }, + initial_values: ["visible"], + other_values: ["auto", "scroll", "hidden", "clip"], + invalid_values: [], + }, + "overflow-block": { + domProp: "overflowBlock", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + prerequisites: { + display: "block", + "overflow-inline": "visible", + contain: "none", + }, + initial_values: ["visible"], + other_values: ["auto", "scroll", "hidden", "clip"], + invalid_values: [], + }, + "overflow-clip-margin": { + domProp: "overflowClipMargin", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0px"], + other_values: ["1px", "2em", "calc(10px + 1vh)"], + invalid_values: ["-10px"], + }, + 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", "-1px"], + unbalanced_values: ["1px calc("], + quirks_values: { 5: "5px", "3px 6px 2 5px": "3px 6px 2px 5px" }, + }, + "padding-block": { + domProp: "paddingBlock", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["padding-block-start", "padding-block-end"], + initial_values: ["0", "0px 0em"], + other_values: ["3px 0", "2% 4px", "1em", "calc(1px) calc(-1%)"], + invalid_values: ["1px calc(nonsense)", "1px red", "-1px", "auto", "none"], + unbalanced_values: ["1px calc("], + }, + "padding-inline": { + domProp: "paddingInline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["padding-inline-start", "padding-inline-end"], + initial_values: ["0", "0px 0em"], + other_values: ["3px 0", "2% 4px", "1em", "calc(1px) calc(-1%)"], + invalid_values: ["1px calc(nonsense)", "1px red", "-1px", "auto", "none"], + unbalanced_values: ["1px calc("], + }, + "padding-bottom": { + domProp: "paddingBottom", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + 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, + applies_to_first_letter: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + 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, + applies_to_first_letter: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + 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, + applies_to_first_letter: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + 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_LEGACY_SHORTHAND, + alias_for: "break-after", + subproperties: ["break-after"], + initial_values: ["auto"], + other_values: ["always", "avoid", "left", "right"], + legacy_mapping: { + always: "page", + }, + invalid_values: ["page", "column"], + }, + "page-break-before": { + domProp: "pageBreakBefore", + inherited: false, + type: CSS_TYPE_LEGACY_SHORTHAND, + alias_for: "break-before", + subproperties: ["break-before"], + initial_values: ["auto"], + other_values: ["always", "avoid", "left", "right"], + legacy_mapping: { + always: "page", + }, + invalid_values: ["page", "column"], + }, + "break-after": { + domProp: "breakAfter", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["always", "page", "avoid", "left", "right"], + invalid_values: [], + }, + "break-before": { + domProp: "breakBefore", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["always", "page", "avoid", "left", "right"], + invalid_values: [], + }, + "break-inside": { + domProp: "breakInside", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["avoid", "avoid-page", "avoid-column"], + invalid_values: ["left", "right", "always"], + }, + "page-break-inside": { + domProp: "pageBreakInside", + inherited: false, + type: CSS_TYPE_LEGACY_SHORTHAND, + alias_for: "break-inside", + subproperties: ["break-inside"], + initial_values: ["auto"], + other_values: ["avoid"], + invalid_values: ["avoid-page", "avoid-column"], + }, + "paint-order": { + domProp: "paintOrder", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["normal"], + other_values: [ + "fill", + "fill stroke", + "fill stroke markers", + "stroke markers fill", + ], + invalid_values: ["fill stroke markers fill", "fill normal"], + }, + "pointer-events": { + domProp: "pointerEvents", + inherited: true, + type: CSS_TYPE_LONGHAND, + // No applies_to_placeholder because we have a !important rule in forms.css. + 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: ["auto"], + other_values: [ + "none", + "'\"' '\"'", + "'' ''", + '"\u201C" "\u201D" "\u2018" "\u2019"', + '"\\201C" "\\201D" "\\2018" "\\2019"', + ], + 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, + applies_to_cue: true, + initial_values: ["alternate", "alternate over", "over alternate"], + other_values: ["over", "under", "alternate under", "under alternate"], + invalid_values: [ + "left", + "right", + "auto", + "none", + "not_a_position", + "over left", + "right under", + "over under", + "alternate alternate", + "0", + "100px", + "50%", + ], + }, + "scroll-behavior": { + domProp: "scrollBehavior", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["smooth"], + invalid_values: ["none", "1px"], + }, + "scroll-snap-stop": { + domProp: "scrollSnapStop", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + other_values: ["always"], + invalid_values: ["auto", "none", "1px"], + }, + "scroll-snap-type": { + domProp: "scrollSnapType", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "both mandatory", + "y mandatory", + "inline proximity", + "both", + "x", + "y", + "block", + "inline", + ], + invalid_values: [ + "auto", + "1px", + "x y", + "block mandatory inline", + "mandatory", + "proximity", + "mandatory inline", + "proximity both", + "mandatory x", + "proximity y", + "mandatory block", + "proximity mandatory", + ], + }, + "scroll-snap-align": { + domProp: "scrollSnapAlign", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "start", + "end", + "center", + "start none", + "center end", + "start start", + ], + invalid_values: ["auto", "start invalid", "start end center"], + }, + "scroll-margin": { + domProp: "scrollMargin", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "scroll-margin-top", + "scroll-margin-right", + "scroll-margin-bottom", + "scroll-margin-left", + ], + initial_values: ["0"], + other_values: [ + "-10px", + "calc(2em + 3ex)", + "1px 2px", + "1px 2px 3px", + "1px 2px 3px 4px", + ], + invalid_values: ["auto", "20%", "-30%", "1px 2px 3px 4px 5px"], + }, + "scroll-margin-top": { + domProp: "scrollMarginTop", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)"], + invalid_values: ["auto", "20%", "-30%", "1px 2px"], + }, + "scroll-margin-right": { + domProp: "scrollMarginRight", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)"], + invalid_values: ["auto", "20%", "-30%", "1px 2px"], + }, + "scroll-margin-bottom": { + domProp: "scrollMarginBottom", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)"], + invalid_values: ["auto", "20%", "-30%", "1px 2px"], + }, + "scroll-margin-left": { + domProp: "scrollMarginLeft", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)"], + invalid_values: ["auto", "20%", "-30%", "1px 2px"], + }, + "scroll-margin-inline": { + domProp: "scrollMarginInline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["scroll-margin-inline-start", "scroll-margin-inline-end"], + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)", "1px 2px"], + invalid_values: ["auto", "20%", "-30%", "1px 2px 3px"], + }, + "scroll-margin-inline-start": { + domProp: "scrollMarginInlineStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)"], + invalid_values: ["auto", "20%", "-30%", "1px 2px"], + }, + "scroll-margin-inline-end": { + domProp: "scrollMarginInlineEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)"], + invalid_values: ["auto", "20%", "-30%", "1px 2px"], + }, + "scroll-margin-block": { + domProp: "scrollMarginBlock", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["scroll-margin-block-start", "scroll-margin-block-end"], + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)", "1px 2px"], + invalid_values: ["auto", "20%", "-30%", "1px 2px 3px"], + }, + "scroll-margin-block-start": { + domProp: "scrollMarginBlockStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)"], + invalid_values: ["auto", "20%", "-30%", "1px 2px"], + }, + "scroll-margin-block-end": { + domProp: "scrollMarginBlockEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + initial_values: ["0"], + other_values: ["-10px", "calc(2em + 3ex)"], + invalid_values: ["auto", "20%", "-30%", "1px 2px"], + }, + "scroll-padding": { + domProp: "scrollPadding", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "scroll-padding-top", + "scroll-padding-right", + "scroll-padding-bottom", + "scroll-padding-left", + ], + initial_values: ["auto"], + other_values: [ + "10px", + "0", + "20%", + "calc(2em + 3ex)", + "1px 2px", + "1px 2px 3%", + "1px 2px 3% 4px", + "1px auto", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-top": { + domProp: "scrollPaddingTop", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "0", + "10px", + "20%", + "calc(2em + 3ex)", + "calc(50% + 60px)", + "calc(-50px)", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-right": { + domProp: "scrollPaddingRight", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "0", + "10px", + "20%", + "calc(2em + 3ex)", + "calc(50% + 60px)", + "calc(-50px)", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-bottom": { + domProp: "scrollPaddingBottom", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "0", + "10px", + "20%", + "calc(2em + 3ex)", + "calc(50% + 60px)", + "calc(-50px)", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-left": { + domProp: "scrollPaddingLeft", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "0", + "10px", + "20%", + "calc(2em + 3ex)", + "calc(50% + 60px)", + "calc(-50px)", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-inline": { + domProp: "scrollPaddingInline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["scroll-padding-inline-start", "scroll-padding-inline-end"], + initial_values: ["auto", "auto auto"], + other_values: [ + "10px", + "0", + "20%", + "calc(2em + 3ex)", + "1px 2px", + "1px auto", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-inline-start": { + domProp: "scrollPaddingInlineStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + initial_values: ["auto"], + other_values: [ + "0", + "10px", + "20%", + "calc(2em + 3ex)", + "calc(50% + 60px)", + "calc(-50px)", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-inline-end": { + domProp: "scrollPaddingInlineEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + initial_values: ["auto"], + other_values: [ + "0", + "10px", + "20%", + "calc(2em + 3ex)", + "calc(50% + 60px)", + "calc(-50px)", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-block": { + domProp: "scrollPaddingBlock", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["scroll-padding-block-start", "scroll-padding-block-end"], + initial_values: ["auto", "auto auto"], + other_values: [ + "10px", + "0", + "20%", + "calc(2em + 3ex)", + "1px 2px", + "1px auto", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-block-start": { + domProp: "scrollPaddingBlockStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + initial_values: ["auto"], + other_values: [ + "0", + "10px", + "20%", + "calc(2em + 3ex)", + "calc(50% + 60px)", + "calc(-50px)", + ], + invalid_values: ["20", "-20px"], + }, + "scroll-padding-block-end": { + domProp: "scrollPaddingBlockEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + initial_values: ["auto"], + other_values: [ + "0", + "10px", + "20%", + "calc(2em + 3ex)", + "calc(50% + 60px)", + "calc(-50px)", + ], + invalid_values: ["20", "-20px"], + }, + "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, + applies_to_placeholder: true, + // 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", + "char", + "-moz-center-or-inherit", + "true left", + "unsafe left", + ], + }, + "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-combine-upright": { + domProp: "textCombineUpright", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_cue: true, + applies_to_marker: true, + 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", + ], + }, + "text-decoration": { + domProp: "textDecoration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + subproperties: [ + "text-decoration-color", + "text-decoration-line", + "text-decoration-style", + "text-decoration-thickness", + ], + initial_values: ["none"], + other_values: [ + "underline", + "overline", + "line-through", + "blink", + "blink line-through underline", + "underline overline line-through blink", + "underline red solid", + "underline #ff0000", + "solid underline", + "red underline", + "#ff0000 underline", + "dotted underline", + "solid underline 50px", + "underline 50px blue", + "50px dotted line-through purple", + "overline 2em", + "underline from-font", + "red from-font overline", + "5% underline blue", + "dotted line-through 25%", + ], + 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", + "from font blue underline", + ], + }, + "text-decoration-color": { + domProp: "textDecorationColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["none"], + other_values: [ + "underline", + "overline", + "line-through", + "blink", + "blink line-through underline", + "underline overline line-through blink", + ], + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["solid"], + other_values: ["double", "dotted", "dashed", "wavy", "-moz-none"], + invalid_values: [ + "none", + "groove", + "ridge", + "inset", + "outset", + "solid dashed", + "wave", + ], + }, + "text-decoration-thickness": { + domProp: "textDecorationThickness", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["auto"], + other_values: [ + "from-font", + "0", + "-14px", + "25px", + "100em", + "-45em", + "43%", + "-10%", + ], + invalid_values: ["13", "-25", "rubbish", ",./!@#$", "from font"], + }, + "text-decoration-skip-ink": { + domProp: "textDecorationSkipInk", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + initial_values: ["auto"], + other_values: ["none", "all"], + invalid_values: [ + "13", + "15%", + "-1", + "0", + "otto", + "trash", + "non", + "nada", + "!@#$%^", + "none auto", + "auto none", + ], + }, + "text-underline-offset": { + domProp: "textUnderlineOffset", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + initial_values: ["auto"], + other_values: ["0", "-14px", "25px", "100em", "-45em", "43%", "-10%"], + invalid_values: [ + "13", + "-25", + "rubbish", + ",./!@#$", + "from-font", + "from font", + ], + }, + "text-underline-position": { + domProp: "textUnderlinePosition", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + initial_values: ["auto"], + other_values: [ + "under", + "left", + "right", + "left under", + "under left", + "right under", + "under right", + "from-font", + "from-font left", + "from-font right", + "left from-font", + "right from-font", + ], + invalid_values: [ + "none", + "auto from-font", + "auto under", + "under from-font", + "left right", + "right auto", + "0", + "1px", + "10%", + "from font", + ], + }, + "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", "over"], + other_values: [ + "over left", + "left over", + "under left", + "left under", + "under right", + "right under", + "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, + applies_to_placeholder: true, + 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", + '"hello" unset', + 'unset "hello"', + "clip unset", + "unset clip", + "unset inherit", + "unset none", + "initial unset", + ], + }, + "text-shadow": { + domProp: "textShadow", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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)", + "unset, 2px 2px", + "2px 2px, unset", + ], + }, + "text-transform": { + domProp: "textTransform", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_marker: true, + initial_values: ["none"], + other_values: [ + "capitalize", + "uppercase", + "lowercase", + "full-width", + "full-size-kana", + "uppercase full-width", + "full-size-kana capitalize", + "full-width lowercase full-size-kana", + ], + invalid_values: [ + "none none", + "none uppercase", + "full-width none", + "uppercase lowercase", + "full-width capitalize full-width", + "uppercase full-width lowercase", + ], + }, + "text-wrap": { + domProp: "textWrap", + inherited: true, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["text-wrap-mode"], + applies_to_placeholder: true, + applies_to_cue: true, + applies_to_marker: true, + initial_values: ["wrap"], + other_values: ["nowrap"], + invalid_values: [], + }, + "text-wrap-mode": { + domProp: "textWrapMode", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_cue: true, + applies_to_placeholder: true, + applies_to_marker: true, + initial_values: ["wrap"], + other_values: ["nowrap"], + invalid_values: ["none", "normal", "on", "off", "wrap nowrap"], + }, + 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, + applies_to_marker: true, + 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", + "none", + "none 2s linear 2s", + ], + 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", + "2s unset", + ], + }, + "transition-delay": { + domProp: "transitionDelay", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + 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, + applies_to_marker: true, + 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, + applies_to_marker: true, + 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", + "unset, color", + "color, unset", + ], + }, + "transition-timing-function": { + domProp: "transitionTimingFunction", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + 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)", + "steps(1, jump-start)", + "steps(1, jump-end)", + "steps(2, jump-none)", + "steps(1, jump-both)", + ], + 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)", + "steps(0, jump-start)", + "steps(0, jump-end)", + "steps(1, jump-none)", + "steps(0, jump-both)", + ], + }, + "unicode-bidi": { + domProp: "unicodeBidi", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + initial_values: ["normal"], + other_values: [ + "embed", + "bidi-override", + "isolate", + "plaintext", + "isolate-override", + ], + invalid_values: [ + "auto", + "none", + "-moz-isolate", + "-moz-plaintext", + "-moz-isolate-override", + ], + }, + "vertical-align": { + domProp: "verticalAlign", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + 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" }, + }, + "baseline-source": { + domProp: "baselineSource", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + initial_values: ["auto"], + other_values: ["first", "last"], + invalid_values: [], + }, + visibility: { + domProp: "visibility", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_cue: true, + initial_values: ["visible"], + other_values: ["hidden", "collapse"], + invalid_values: [], + }, + "white-space": { + domProp: "whiteSpace", + inherited: true, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["white-space-collapse", "text-wrap-mode"], + applies_to_placeholder: true, + applies_to_cue: true, + applies_to_marker: true, + initial_values: ["normal"], + other_values: [ + "pre", + "nowrap", + "pre-wrap", + "pre-line", + "-moz-pre-space", + "break-spaces", + ], + invalid_values: [], + }, + "white-space-collapse": { + domProp: "whiteSpaceCollapse", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_placeholder: true, + applies_to_cue: true, + applies_to_marker: true, + initial_values: ["collapse"], + other_values: [ + "preserve", + "preserve-breaks", + "preserve-spaces", + "break-spaces", + ], + invalid_values: ["normal", "auto"], + }, + width: { + domProp: "width", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { + // computed value tests for width test more with display:block + display: "block", + // add some margin to avoid the initial "auto" value getting + // resolved to the same length as the parent element. + "margin-left": "5px", + }, + 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 + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-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 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))", + "calc(min(5px))", + "calc(min(5px,2em))", + "calc(max(5px))", + "calc(max(5px,2em))", + "min(5px)", + "min(5px,2em)", + "max(5px)", + "max(5px,2em)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(calc(3*25px + 50%))", + ], + invalid_values: [ + "none", + "-2px", + "content" /* (valid for 'flex-basis' but not 'width') */, + /* invalid calc() values */ + "calc(50%+ 2px)", + "calc(50% +2px)", + "calc(50%+2px)", + "-moz-min()", + "calc(min())", + "-moz-max()", + "calc(max())", + "-moz-min(5px)", + "-moz-max(5px)", + "-moz-min(5px,2em)", + "-moz-max(5px,2em)", + /* 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + 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: [ + "path(nonzero, 'M 10 10 h 100 v 100 h-100 v-100 z')", + "path(evenodd, 'M 10 10 h 100 v 100 h-100 v-100 z')", + "path('M10,30A20,20 0,0,1 50,30A20,20 0,0,1 90,30Q90,60 50,90Q10,60 10,30z')", + "url(#mypath)", + "url('404.svg#mypath')", + "url(#my-clip-path)", + "margin-box", + ] + .concat(basicShapeSVGBoxValues) + .concat(basicShapeOtherValues) + .concat(basicShapeOtherValuesWithFillRule) + .concat(basicShapeXywhRectValues), + invalid_values: [ + "path(nonzero)", + "path(abs, 'M 10 10 L 10 10 z')", + "path(evenodd, '')", + "path('')", + ].concat(basicShapeInvalidValues), + unbalanced_values: basicShapeUnbalancedValues, + }, + "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: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "ideographic", + "alphabetic", + "hanging", + "mathematical", + "central", + "middle", + "text-after-edge", + "text-before-edge", + ], + invalid_values: [], + }, + fill: { + domProp: "fill", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["1", "2.8", "1.000", "300%"], + other_values: [ + "0", + "0.3", + "-7.3", + "-100%", + "50%", + "context-fill-opacity", + "context-stroke-opacity", + ], + invalid_values: [], + }, + "fill-rule": { + domProp: "fillRule", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["nonzero"], + other_values: ["evenodd"], + invalid_values: [], + }, + 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()", + "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()", + "brightness(0)", + "brightness(50%)", + "brightness(1)", + "brightness(1.0)", + "brightness(2)", + "brightness(350%)", + "brightness(4.567)", + + "contrast()", + "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()", + "grayscale(0)", + "grayscale(50%)", + "grayscale(1)", + "grayscale(1.0)", + "grayscale(2)", + "grayscale(350%)", + "grayscale(4.567)", + + "hue-rotate()", + "hue-rotate(0)", + "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()", + "invert(0)", + "invert(50%)", + "invert(1)", + "invert(1.0)", + "invert(2)", + "invert(350%)", + "invert(4.567)", + + "opacity()", + "opacity(0)", + "opacity(50%)", + "opacity(1)", + "opacity(1.0)", + "opacity(2)", + "opacity(350%)", + "opacity(4.567)", + + "saturate()", + "saturate(0)", + "saturate(50%)", + "saturate(1)", + "saturate(1.0)", + "saturate(2)", + "saturate(350%)", + "saturate(4.567)", + + "sepia()", + "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(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(0.5 0.5)", + "brightness(0.5,)", + "brightness(0.5, 0.5)", + "brightness(#my-filter)", + "brightness(10px)", + "brightness(-1)", + + "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)", + "drop-shadow(unset, 2px 2px)", + "drop-shadow(2px 2px, unset)", + + "grayscale(0.5 0.5)", + "grayscale(0.5,)", + "grayscale(0.5, 0.5)", + "grayscale(#my-filter)", + "grayscale(10px)", + "grayscale(-1)", + + "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(0.5 0.5)", + "invert(0.5,)", + "invert(0.5, 0.5)", + "invert(#my-filter)", + "invert(10px)", + "invert(-1)", + + "opacity(0.5 0.5)", + "opacity(0.5,)", + "opacity(0.5, 0.5)", + "opacity(#my-filter)", + "opacity(10px)", + "opacity(-1)", + + "saturate(0.5 0.5)", + "saturate(0.5,)", + "saturate(0.5, 0.5)", + "saturate(#my-filter)", + "saturate(10px)", + "saturate(-1)", + + "sepia(0.5 0.5)", + "sepia(0.5,)", + "sepia(0.5, 0.5)", + "sepia(#my-filter)", + "sepia(10px)", + "sepia(-1)", + ], + }, + "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", "300%"], + other_values: ["0", "0.3", "-7.3", "-100%", "50%"], + invalid_values: [], + }, + "image-orientation": { + domProp: "imageOrientation", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["from-image"], + other_values: ["none"], + invalid_values: ["0", "0deg"], + }, + "image-rendering": { + domProp: "imageRendering", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "optimizeSpeed", + "optimizeQuality", + "-moz-crisp-edges", + "crisp-edges", + "smooth", + "pixelated", + ], + invalid_values: [], + }, + isolation: { + domProp: "isolation", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["isolate"], + 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: [], + }, + "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", + "plus-lighter", + ], + invalid_values: [], + }, + "shape-image-threshold": { + domProp: "shapeImageThreshold", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + initial_values: ["0", "0.0000", "-3", "0%", "-100%"], + other_values: [ + "0.4", + "1", + "17", + "397.376", + "3e1", + "3e+1", + "3e-1", + "3e0", + "3e+0", + "3e-0", + "50%", + "300%", + ], + invalid_values: ["0px", "1px", "default", "auto"], + }, + "shape-margin": { + domProp: "shapeMargin", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + initial_values: ["0"], + other_values: ["2px", "2%", "1em", "calc(1px + 1em)", "calc(1%)"], + invalid_values: ["-1px", "auto", "none", "1px 1px", "-1%"], + }, + "shape-outside": { + domProp: "shapeOutside", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + initial_values: ["none"], + other_values: ["url(#my-shape-outside)", "margin-box"].concat( + basicShapeOtherValues, + basicShapeOtherValuesWithFillRule, + validNonUrlImageValues + ), + invalid_values: [].concat( + basicShapeSVGBoxValues, + basicShapeInvalidValues, + invalidNonUrlImageValues + ), + unbalanced_values: [].concat( + basicShapeUnbalancedValues, + unbalancedGradientAndElementValues + ), + }, + "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", "300%"], + other_values: ["0", "0.3", "-7.3", "-100%", "50%"], + invalid_values: [], + }, + stroke: { + domProp: "stroke", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["none"], + other_values: [ + "5px,3px,2px", + "5px 3px 2px", + " 5px ,3px\t, 2px ", + "1px", + "5%", + "3em", + "0.0002", + "context-value", + ], + invalid_values: ["-5px,3px,2px", "5px,3px,-2px"], + }, + "stroke-dashoffset": { + domProp: "strokeDashoffset", + inherited: true, + applies_to_first_letter: true, + applies_to_first_line: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["0", "-0px", "0em"], + other_values: ["3px", "3%", "1em", "0.0002", "context-value"], + invalid_values: [], + }, + "stroke-linecap": { + domProp: "strokeLinecap", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["butt"], + other_values: ["round", "square"], + invalid_values: [], + }, + "stroke-linejoin": { + domProp: "strokeLinejoin", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["miter"], + other_values: ["round", "bevel"], + invalid_values: [], + }, + "stroke-miterlimit": { + domProp: "strokeMiterlimit", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["4"], + other_values: ["0", "0.9", "1", "7", "5000", "1.1"], + invalid_values: ["-1", "3px", "-0.3"], + }, + "stroke-opacity": { + domProp: "strokeOpacity", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["1", "2.8", "1.000", "300%"], + other_values: [ + "0", + "0.3", + "-7.3", + "-100%", + "50%", + "context-fill-opacity", + "context-stroke-opacity", + ], + invalid_values: [], + }, + "stroke-width": { + domProp: "strokeWidth", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["1px"], + other_values: [ + "0", + "0px", + "-0em", + "17px", + "0.2em", + "0.0002", + "context-value", + ], + invalid_values: ["-0.1px", "-3px"], + }, + x: { + domProp: "x", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0px"], + other_values: ["-1em", "17px", "0.2em", "23.4%"], + invalid_values: ["auto", "context-value", "0.0002"], + }, + y: { + domProp: "y", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0px"], + other_values: ["-1em", "17px", "0.2em", "23.4%"], + invalid_values: ["auto", "context-value", "0.0002"], + }, + cx: { + domProp: "cx", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0px"], + other_values: ["-1em", "17px", "0.2em", "23.4%"], + invalid_values: ["auto", "context-value", "0.0002"], + }, + cy: { + domProp: "cy", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0px"], + other_values: ["-1em", "17px", "0.2em", "23.4%"], + invalid_values: ["auto", "context-value", "0.0002"], + }, + r: { + domProp: "r", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0px"], + other_values: ["17px", "0.2em", "23.4%"], + invalid_values: ["auto", "-1", "-1.5px", "0.0002"], + }, + rx: { + domProp: "rx", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["17px", "0.2em", "23.4%"], + invalid_values: ["hello", "-12px", "0.0002"], + }, + ry: { + domProp: "ry", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["17px", "0.2em", "23.4%"], + invalid_values: ["hello", "-1.3px", "0.0002"], + }, + "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, + applies_to_first_letter: true, + applies_to_first_line: true, + initial_values: ["auto"], + other_values: ["optimizeSpeed", "optimizeLegibility", "geometricPrecision"], + invalid_values: [], + }, + "vector-effect": { + domProp: "vectorEffect", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + 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"], + }, + "accent-color": { + domProp: "accentColor", + inherited: true, + type: CSS_TYPE_LONGHAND, + prerequisites: { color: "black" }, + initial_values: ["auto"], + other_values: [ + "currentcolor", + "black", + "green", + "transparent", + "rgba(128,128,128,.5)", + "#123", + ], + invalid_values: ["#0", "#00", "#00000", "cc00ff"], + }, + "align-content": { + domProp: "alignContent", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + other_values: [ + "start", + "end", + "flex-start", + "flex-end", + "center", + "space-between", + "space-around", + "space-evenly", + "first baseline", + "last baseline", + "baseline", + "stretch", + "safe start", + "unsafe end", + "safe end", + ], + 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", + "left", + "right", + ], + }, + "align-items": { + domProp: "alignItems", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + other_values: [ + "end", + "flex-start", + "flex-end", + "self-start", + "self-end", + "center", + "stretch", + "first baseline", + "last baseline", + "baseline", + "start", + "unsafe center", + "safe center", + ], + invalid_values: [ + "space-between", + "abc", + "5%", + "legacy", + "legacy end", + "end legacy", + "unsafe", + "unsafe baseline", + "normal unsafe", + "safe left unsafe", + "safe stretch", + "end end", + "auto", + "left", + "right", + ], + }, + "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", + "unsafe center", + "self-start", + "safe self-end", + ], + invalid_values: [ + "space-between", + "abc", + "30px", + "stretch safe", + "safe", + "left", + "right", + ], + }, + "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", + "stretch", + "safe start", + "unsafe end", + "safe end", + ], + 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: ["legacy", "normal"], + other_values: [ + "end", + "flex-start", + "flex-end", + "self-start", + "self-end", + "center", + "left", + "right", + "stretch", + "start", + "legacy left", + "right legacy", + "legacy center", + "unsafe right", + "unsafe left", + "safe right", + "safe center", + ], + invalid_values: [ + "auto", + "space-between", + "abc", + "30px", + "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", + "stretch", + "unsafe left", + "baseline", + "last baseline", + "first baseline", + "unsafe right", + "safe right", + "safe center", + ], + 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", + "baseline end", + "end end", + "space-between flex-end", + "last baseline start", + "space-evenly", + "flex-start", + "end", + "unsafe start", + "safe center", + "baseline", + "last baseline", + ], + invalid_values: [ + "none", + "center safe", + "right / end", + "left", + "right", + "left left", + "right right", + ], + }, + "place-items": { + domProp: "placeItems", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["align-items", "justify-items"], + initial_values: ["normal"], + other_values: [ + "normal center", + "baseline end", + "end legacy", + "end", + "flex-end left", + "last baseline start", + "stretch", + "safe center", + "end legacy left", + ], + invalid_values: [ + "space-between", + "start space-evenly", + "none", + "end/end", + "center safe", + "auto start", + "left", + "right", + "left left", + "right right", + ], + }, + "place-self": { + domProp: "placeSelf", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["align-self", "justify-self"], + initial_values: ["auto"], + other_values: [ + "normal start", + "first baseline end", + "end auto", + "end", + "normal", + "baseline start", + "baseline", + "start baseline", + "self-end left", + "last baseline start", + "stretch", + ], + invalid_values: [ + "space-between", + "start space-evenly", + "none", + "end safe", + "auto legacy left", + "legacy left", + "auto/auto", + "left", + "right", + "left left", + "right right", + ], + }, + 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", + "max-content", + "1 max-content", + "1 2 max-content", + "max-content 1", + "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: Besides "content", this is cribbed directly from the "width" + // chunk, since this property takes the exact same values as width + // (plus 'content' & 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: [ + "content", + "15px", + "3em", + "15%", + "max-content", + "min-content", + "fit-content", + "-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))", + "calc(min(5px))", + "calc(min(5px,2em))", + "calc(max(5px))", + "calc(max(5px,2em))", + "min(5px)", + "min(5px,2em)", + "max(5px)", + "max(5px,2em)", + ], + 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)", + "-moz-max(5px)", + "-moz-min(5px,2em)", + "-moz-max(5px,2em)", + // 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-tab-size": { + domProp: "MozTabSize", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "tab-size", + subproperties: ["tab-size"], + }, + "-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-font-feature-settings": { + domProp: "MozFontFeatureSettings", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + alias_for: "font-feature-settings", + subproperties: ["font-feature-settings"], + }, + "-moz-font-language-override": { + domProp: "MozFontLanguageOverride", + inherited: true, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + 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"], + }, + // 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 */, + }, + "block-size": { + domProp: "blockSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + /* XXX testing auto has prerequisites */ + initial_values: ["auto"], + prerequisites: { display: "block" }, + other_values: [ + "15px", + "3em", + "15%", + // These keywords are treated as initial value. + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + "-moz-max-content", + "-moz-min-content", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(calc(3*25px + 50%))", + ], + invalid_values: ["none"], + }, + "border-block": { + domProp: "borderBlock", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "border-block-start-color", + "border-block-start-style", + "border-block-start-width", + "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"], + }, + "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"], + }, + "border-block-color": { + domProp: "borderBlockColor", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["border-block-start-color", "border-block-end-color"], + initial_values: ["currentColor"], + other_values: ["green", "rgba(255,128,0,0.5) blue", "blue transparent"], + invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"], + }, + "border-block-end-color": { + domProp: "borderBlockEndColor", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + initial_values: ["currentColor"], + other_values: ["green", "rgba(255,128,0,0.5)", "transparent"], + invalid_values: ["#0", "#00", "#00000", "#0000000", "#000000000", "000000"], + }, + "border-block-style": { + domProp: "borderBlockStyle", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["border-block-start-style", "border-block-end-style"], + initial_values: ["none"], + other_values: [ + "solid", + "dashed solid", + "solid dotted", + "double double", + "inset outset", + "inset double", + "none groove", + "ridge none", + ], + invalid_values: [], + }, + "border-block-end-style": { + domProp: "borderBlockEndStyle", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + /* 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-width": { + domProp: "borderBlockWidth", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["border-block-start-width", "border-block-end-width"], + prerequisites: { "border-style": "solid" }, + initial_values: ["medium", "3px", "medium medium"], + other_values: [ + "thin", + "thick", + "1px", + "2em", + "calc(2px)", + "calc(2px) thin", + "calc(-2px)", + "calc(-2px) thick", + "calc(0em)", + "medium calc(0em)", + "calc(0px)", + "1px calc(0px)", + "calc(5em)", + "1em calc(5em)", + ], + invalid_values: ["5%", "5", "5 thin", "thin 5%", "blue", "solid"], + }, + "border-block-end-width": { + domProp: "borderBlockEndWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + 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, + applies_to_first_letter: true, + logical: true, + 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, + applies_to_first_letter: true, + logical: true, + /* 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, + applies_to_first_letter: true, + logical: true, + 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, + applies_to_first_letter: true, + alias_for: "border-inline-end-color", + subproperties: ["border-inline-end-color"], + }, + "-moz-border-end-style": { + domProp: "MozBorderEndStyle", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "border-inline-end-style", + subproperties: ["border-inline-end-style"], + }, + "-moz-border-end-width": { + domProp: "MozBorderEndWidth", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "border-inline-end-width", + subproperties: ["border-inline-end-width"], + }, + "-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, + applies_to_first_letter: true, + alias_for: "border-inline-start-color", + subproperties: ["border-inline-start-color"], + }, + "-moz-border-start-style": { + domProp: "MozBorderStartStyle", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "border-inline-start-style", + subproperties: ["border-inline-start-style"], + }, + "-moz-border-start-width": { + domProp: "MozBorderStartWidth", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "border-inline-start-width", + subproperties: ["border-inline-start-width"], + }, + "inline-size": { + domProp: "inlineSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + /* XXX testing auto has prerequisites */ + initial_values: ["auto"], + prerequisites: { + display: "block", + // add some margin to avoid the initial "auto" value getting + // resolved to the same length as the parent element. + "margin-left": "5px", + }, + 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 + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-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%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(calc(3*25px + 50%))", + ], + invalid_values: ["none"], + }, + "margin-block": { + domProp: "marginBlock", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["margin-block-start", "margin-block-end"], + initial_values: ["0", "0px 0em"], + other_values: [ + "1px", + "3em 1%", + "5%", + "calc(2px) 1%", + "calc(-2px) 1%", + "calc(50%) 1%", + "calc(3*25px) calc(2px)", + "calc(25px*3) 1em", + "calc(3*25px + 50%) calc(3*25px - 50%)", + ], + invalid_values: [ + "5", + "..25px", + ".+5px", + ".px", + "-.px", + "++5px", + "-+4px", + "+-3px", + "--7px", + "+-.6px", + "-+.5px", + "++.7px", + "--.4px", + ], + }, + "margin-block-end": { + domProp: "marginBlockEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + logical: true, + /* 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, + applies_to_first_letter: true, + logical: true, + /* 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, + applies_to_first_letter: true, + alias_for: "margin-inline-end", + subproperties: ["margin-inline-end"], + }, + "-moz-margin-start": { + domProp: "MozMarginStart", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "margin-inline-start", + subproperties: ["margin-inline-start"], + }, + "max-block-size": { + domProp: "maxBlockSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + prerequisites: { display: "block" }, + initial_values: ["none"], + other_values: [ + "30px", + "50%", + // These keywords are treated as initial value. + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + "-moz-max-content", + "-moz-min-content", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(calc(3*25px + 50%))", + ], + invalid_values: ["auto", "5"], + }, + "max-inline-size": { + domProp: "maxInlineSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + 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 + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-content", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(calc(3*25px + 50%))", + ], + invalid_values: ["auto", "5"], + }, + "min-block-size": { + domProp: "minBlockSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + prerequisites: { display: "block" }, + initial_values: ["auto", "0", "calc(0em)", "calc(-2px)"], + other_values: [ + "30px", + "50%", + // These keywords are treated as initial value. + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + "-moz-max-content", + "-moz-min-content", + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(calc(3*25px + 50%))", + ], + invalid_values: ["none", "5"], + }, + "min-inline-size": { + domProp: "minInlineSize", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + axis: true, + 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 + "max-content", + "min-content", + "fit-content", + "-moz-fit-content", + "-moz-available", + // these two keywords are the aliases of above first two. + "-moz-max-content", + "-moz-min-content", + "calc(-1%)", + "calc(2px)", + "calc(50%)", + "calc(3*25px)", + "calc(25px*3)", + "calc(3*25px + 50%)", + "fit-content(100px)", + "fit-content(10%)", + "fit-content(calc(3*25px + 50%))", + ], + invalid_values: ["none", "5"], + }, + inset: { + domProp: "inset", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["top", "right", "bottom", "left"], + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { position: "relative" }, + initial_values: ["auto"], + other_values: [ + "3px 0", + "2em 4px 2pt", + "1em 2em 3px 4px", + "1em calc(2em + 3px) 4ex 5cm", + ], + invalid_values: ["1px calc(nonsense)", "1px red", "3"], + unbalanced_values: ["1px calc("], + }, + "inset-block": { + domProp: "insetBlock", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["inset-block-start", "inset-block-end"], + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { position: "relative" }, + initial_values: ["auto", "auto auto"], + other_values: [ + "32px", + "-3em", + "12%", + "32px auto", + "auto -3em", + "12% auto", + "calc(2px)", + "calc(2px) auto", + "calc(-2px)", + "auto calc(-2px)", + "calc(50%)", + "auto calc(50%)", + "calc(3*25px)", + "calc(3*25px) auto", + "calc(25px*3)", + "auto calc(25px*3)", + "calc(3*25px + 50%)", + "auto calc(3*25px + 50%)", + ], + invalid_values: ["none"], + }, + "inset-block-end": { + domProp: "insetBlockEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + /* 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: [], + }, + "inset-block-start": { + domProp: "insetBlockStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + /* 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: [], + }, + "inset-inline": { + domProp: "insetInline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["inset-inline-start", "inset-inline-end"], + /* FIXME: run tests with multiple prerequisites */ + prerequisites: { position: "relative" }, + initial_values: ["auto", "auto auto"], + other_values: [ + "32px", + "-3em", + "12%", + "32px auto", + "auto -3em", + "12% auto", + "calc(2px)", + "calc(2px) auto", + "calc(-2px)", + "auto calc(-2px)", + "calc(50%)", + "auto calc(50%)", + "calc(3*25px)", + "calc(3*25px) auto", + "calc(25px*3)", + "auto calc(25px*3)", + "calc(3*25px + 50%)", + "auto calc(3*25px + 50%)", + ], + invalid_values: ["none"], + }, + "inset-inline-end": { + domProp: "insetInlineEnd", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + /* 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: [], + }, + "inset-inline-start": { + domProp: "insetInlineStart", + inherited: false, + type: CSS_TYPE_LONGHAND, + logical: true, + /* 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, + applies_to_first_letter: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + logical: true, + 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, + applies_to_first_letter: true, + // No applies_to_placeholder because we have a !important rule in forms.css. + logical: true, + 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, + applies_to_first_letter: true, + alias_for: "padding-inline-end", + subproperties: ["padding-inline-end"], + }, + "-moz-padding-start": { + domProp: "MozPaddingStart", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "padding-inline-start", + subproperties: ["padding-inline-start"], + }, + "-webkit-animation": { + domProp: "webkitAnimation", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + applies_to_marker: true, + alias_for: "animation", + subproperties: [ + "animation-name", + "animation-duration", + "animation-timing-function", + "animation-delay", + "animation-direction", + "animation-fill-mode", + "animation-iteration-count", + "animation-play-state", + ], + }, + "-webkit-animation-delay": { + domProp: "webkitAnimationDelay", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-delay", + subproperties: ["animation-delay"], + }, + "-webkit-animation-direction": { + domProp: "webkitAnimationDirection", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-direction", + subproperties: ["animation-direction"], + }, + "-webkit-animation-duration": { + domProp: "webkitAnimationDuration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-duration", + subproperties: ["animation-duration"], + }, + "-webkit-animation-fill-mode": { + domProp: "webkitAnimationFillMode", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-fill-mode", + subproperties: ["animation-fill-mode"], + }, + "-webkit-animation-iteration-count": { + domProp: "webkitAnimationIterationCount", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-iteration-count", + subproperties: ["animation-iteration-count"], + }, + "-webkit-animation-name": { + domProp: "webkitAnimationName", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-name", + subproperties: ["animation-name"], + }, + "-webkit-animation-play-state": { + domProp: "webkitAnimationPlayState", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-play-state", + subproperties: ["animation-play-state"], + }, + "-webkit-animation-timing-function": { + domProp: "webkitAnimationTimingFunction", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-timing-function", + subproperties: ["animation-timing-function"], + }, + "-webkit-clip-path": { + domProp: "webkitClipPath", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "clip-path", + subproperties: ["clip-path"], + }, + "-webkit-filter": { + domProp: "webkitFilter", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "filter", + subproperties: ["filter"], + }, + "-webkit-text-security": { + domProp: "webkitTextSecurity", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + initial_values: ["none"], + other_values: ["circle", "disc", "square"], + invalid_values: ["0", "auto", "true", "'*'"], + }, + "-webkit-text-fill-color": { + domProp: "webkitTextFillColor", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + 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)", + ], + }, + "-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"], + }, + "-webkit-text-stroke-color": { + domProp: "webkitTextStrokeColor", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + 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)", + ], + }, + "-webkit-text-stroke-width": { + domProp: "webkitTextStrokeWidth", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + 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%", + ], + }, + "-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"], + }, + "-webkit-transform": { + domProp: "webkitTransform", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform", + subproperties: ["transform"], + }, + "-webkit-transform-origin": { + domProp: "webkitTransformOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform-origin", + subproperties: ["transform-origin"], + }, + "-webkit-transform-style": { + domProp: "webkitTransformStyle", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform-style", + subproperties: ["transform-style"], + }, + "-webkit-backface-visibility": { + domProp: "webkitBackfaceVisibility", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "backface-visibility", + subproperties: ["backface-visibility"], + }, + "-webkit-perspective": { + domProp: "webkitPerspective", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "perspective", + subproperties: ["perspective"], + }, + "-webkit-perspective-origin": { + domProp: "webkitPerspectiveOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "perspective-origin", + subproperties: ["perspective-origin"], + }, + "-webkit-transition": { + domProp: "webkitTransition", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + applies_to_marker: true, + alias_for: "transition", + subproperties: [ + "transition-property", + "transition-duration", + "transition-timing-function", + "transition-delay", + ], + }, + "-webkit-transition-delay": { + domProp: "webkitTransitionDelay", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "transition-delay", + subproperties: ["transition-delay"], + }, + "-webkit-transition-duration": { + domProp: "webkitTransitionDuration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "transition-duration", + subproperties: ["transition-duration"], + }, + "-webkit-transition-property": { + domProp: "webkitTransitionProperty", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "transition-property", + subproperties: ["transition-property"], + }, + "-webkit-transition-timing-function": { + domProp: "webkitTransitionTimingFunction", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "transition-timing-function", + subproperties: ["transition-timing-function"], + }, + "-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", + ], + }, + "-webkit-border-top-left-radius": { + domProp: "webkitBorderTopLeftRadius", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "border-top-left-radius", + subproperties: ["border-top-left-radius"], + }, + "-webkit-border-top-right-radius": { + domProp: "webkitBorderTopRightRadius", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "border-top-right-radius", + subproperties: ["border-top-right-radius"], + }, + "-webkit-border-bottom-left-radius": { + domProp: "webkitBorderBottomLeftRadius", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "border-bottom-left-radius", + subproperties: ["border-bottom-left-radius"], + }, + "-webkit-border-bottom-right-radius": { + domProp: "webkitBorderBottomRightRadius", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "border-bottom-right-radius", + subproperties: ["border-bottom-right-radius"], + }, + "-webkit-background-clip": { + domProp: "webkitBackgroundClip", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + alias_for: "background-clip", + subproperties: ["background-clip"], + }, + "-webkit-background-origin": { + domProp: "webkitBackgroundOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + alias_for: "background-origin", + subproperties: ["background-origin"], + }, + "-webkit-background-size": { + domProp: "webkitBackgroundSize", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + alias_for: "background-size", + subproperties: ["background-size"], + }, + "-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", + ], + }, + "-webkit-box-shadow": { + domProp: "webkitBoxShadow", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_first_letter: true, + alias_for: "box-shadow", + subproperties: ["box-shadow"], + }, + "-webkit-box-sizing": { + domProp: "webkitBoxSizing", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "box-sizing", + subproperties: ["box-sizing"], + }, + "-webkit-box-flex": { + domProp: "webkitBoxFlex", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-flex", + subproperties: ["-moz-box-flex"], + }, + "-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"], + }, + "-webkit-box-orient": { + domProp: "webkitBoxOrient", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-orient", + subproperties: ["-moz-box-orient"], + }, + "-webkit-box-direction": { + domProp: "webkitBoxDirection", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-direction", + subproperties: ["-moz-box-direction"], + }, + "-webkit-box-align": { + domProp: "webkitBoxAlign", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-align", + subproperties: ["-moz-box-align"], + }, + "-webkit-box-pack": { + domProp: "webkitBoxPack", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "-moz-box-pack", + subproperties: ["-moz-box-pack"], + }, + "-webkit-flex-direction": { + domProp: "webkitFlexDirection", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-direction", + subproperties: ["flex-direction"], + }, + "-webkit-flex-wrap": { + domProp: "webkitFlexWrap", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-wrap", + subproperties: ["flex-wrap"], + }, + "-webkit-flex-flow": { + domProp: "webkitFlexFlow", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "flex-flow", + subproperties: ["flex-direction", "flex-wrap"], + }, + "-webkit-line-clamp": { + domProp: "webkitLineClamp", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["1", "2"], + invalid_values: ["auto", "0", "-1"], + }, + "-webkit-order": { + domProp: "webkitOrder", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "order", + subproperties: ["order"], + }, + "-webkit-flex": { + domProp: "webkitFlex", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "flex", + subproperties: ["flex-grow", "flex-shrink", "flex-basis"], + }, + "-webkit-flex-grow": { + domProp: "webkitFlexGrow", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-grow", + subproperties: ["flex-grow"], + }, + "-webkit-flex-shrink": { + domProp: "webkitFlexShrink", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-shrink", + subproperties: ["flex-shrink"], + }, + "-webkit-flex-basis": { + domProp: "webkitFlexBasis", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "flex-basis", + subproperties: ["flex-basis"], + }, + "-webkit-justify-content": { + domProp: "webkitJustifyContent", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "justify-content", + subproperties: ["justify-content"], + }, + "-webkit-align-items": { + domProp: "webkitAlignItems", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "align-items", + subproperties: ["align-items"], + }, + "-webkit-align-self": { + domProp: "webkitAlignSelf", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "align-self", + subproperties: ["align-self"], + }, + "-webkit-align-content": { + domProp: "webkitAlignContent", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "align-content", + subproperties: ["align-content"], + }, + "-webkit-user-select": { + domProp: "webkitUserSelect", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "user-select", + subproperties: ["user-select"], + }, + "-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", + ], + }, + "-webkit-mask-clip": { + domProp: "webkitMaskClip", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-clip", + subproperties: ["mask-clip"], + }, + + "-webkit-mask-composite": { + domProp: "webkitMaskComposite", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-composite", + subproperties: ["mask-composite"], + }, + + "-webkit-mask-image": { + domProp: "webkitMaskImage", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-image", + subproperties: ["mask-image"], + }, + "-webkit-mask-origin": { + domProp: "webkitMaskOrigin", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-origin", + subproperties: ["mask-origin"], + }, + "-webkit-mask-position": { + domProp: "webkitMaskPosition", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-position", + subproperties: ["mask-position"], + }, + "-webkit-mask-position-x": { + domProp: "webkitMaskPositionX", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-position-x", + subproperties: ["mask-position-x"], + }, + "-webkit-mask-position-y": { + domProp: "webkitMaskPositionY", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-position-y", + subproperties: ["mask-position-y"], + }, + "-webkit-mask-repeat": { + domProp: "webkitMaskRepeat", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-repeat", + subproperties: ["mask-repeat"], + }, + "-webkit-mask-size": { + domProp: "webkitMaskSize", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "mask-size", + subproperties: ["mask-size"], + }, +}; // end of gCSSProperties + +// 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_LEGACY_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(" ; "); + } + return cs.getPropertyValue(property); +} + +{ + const mozHiddenUnscrollableEnabled = IsCSSPropertyPrefEnabled( + "layout.css.overflow-moz-hidden-unscrollable.enabled" + ); + for (let p of ["overflow", "overflow-x", "overflow-y"]) { + let prop = gCSSProperties[p]; + let mozHiddenUnscrollableValues = mozHiddenUnscrollableEnabled + ? prop.other_values + : prop.invalid_values; + mozHiddenUnscrollableValues.push("-moz-hidden-unscrollable"); + if (p == "overflow") { + mozHiddenUnscrollableValues.push( + "-moz-hidden-unscrollable -moz-hidden-unscrollable" + ); + } + } +} + +if (IsCSSPropertyPrefEnabled("layout.css.individual-transform.enabled")) { + gCSSProperties.rotate = { + domProp: "rotate", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "45deg", + "45grad", + "72rad", + "0.25turn", + ".57rad", + "0 0 0 0rad", + "0 0 1 45deg", + "0 0 1 0rad", + "0rad 0 0 1", + "10rad 10 20 30", + "x 10rad", + "y 10rad", + "z 10rad", + "10rad x", + "10rad y", + "10rad z", + /* valid calc() values */ + "calc(1) 0 0 calc(45deg + 5rad)", + "0 1 0 calc(400grad + 1rad)", + "calc(0.5turn + 10deg)", + ], + invalid_values: [ + "0", + "7", + "0, 0, 1, 45deg", + "0 0 45deg", + "0 0 20rad", + "0 0 0 0", + "x x 10rad", + "x y 10rad", + "0 0 1 10rad z", + "0 0 1 z 10rad", + "z 0 0 1 10rad", + "0 0 z 1 10rad", + /* invalid calc() values */ + "0.5 1 0 calc(45deg + 10)", + "calc(0.5turn + 10%)", + ], + }; + + gCSSProperties.translate = { + domProp: "translate", + inherited: false, + type: CSS_TYPE_LONGHAND, + prerequisites: { width: "10px", height: "10px", display: "block" }, + initial_values: ["none"], + other_values: [ + "-4px", + "3px", + "4em", + "50%", + "4px 5px 6px", + "4px 5px", + "50% 5px 6px", + "50% 10% 6em", + /* valid calc() values */ + "calc(5px + 10%)", + "calc(0.25 * 5px + 10% / 3)", + "calc(5px - 10% * 3)", + "calc(5px - 3 * 10%) 50px", + "-50px calc(5px - 10% * 3)", + "10px calc(min(5px,10%))", + ], + invalid_values: [ + "1", + "-moz-min(5px,10%)", + "4px, 5px, 6px", + "3px 4px 1px 7px", + "4px 5px 10%", + /* invalid calc() values */ + "calc(max(5px,10%) 10%)", + "calc(nonsense)", + ], + }; + gCSSProperties.scale = { + domProp: "scale", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "10", + "10%", + "10 20", + "10% 20%", + "10 20 30", + "10% 20% 30%", + "10 20% 30", + "-10", + "-10%", + "-10 20", + "-10% 20%", + "-10 20 -30", + "-10% 20% -30%", + "-10 20% -30", + "0 2.0", + /* valid calc() values */ + "calc(1 + 2)", + "calc(10) calc(20) 30", + ], + invalid_values: [ + "10px", + "10deg", + "10, 20, 30", + /* invalid calc() values */ + "calc(1 + 20%)", + "10 calc(1 + 10px)", + ], + }; +} + +if ( + IsCSSPropertyPrefEnabled("layout.css.transform-box-content-stroke.enabled") +) { + gCSSProperties["transform-box"]["other_values"].push( + "content-box", + "stroke-box" + ); +} + +gCSSProperties["touch-action"] = { + domProp: "touchAction", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "none", + "pan-x", + "pan-y", + "pinch-zoom", + "pan-x pan-y", + "pan-y pan-x", + "pinch-zoom pan-x", + "pinch-zoom pan-y", + "pan-x pinch-zoom", + "pan-y pinch-zoom", + "pinch-zoom pan-x pan-y", + "pinch-zoom pan-y pan-x", + "pan-x pinch-zoom pan-y", + "pan-y pinch-zoom pan-x", + "pan-x pan-y pinch-zoom", + "pan-y pan-x pinch-zoom", + "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", + "auto pinch-zoom", + "pinch-zoom auto", + "none pinch-zoom", + "pinch-zoom none", + "pinch-zoom pinch-zoom", + "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 pinch-zoom none", + "pan-x none pinch-zoom", + "none pan-x pinch-zoom", + "pinch-zoom pan-x none", + "pinch-zoom none pan-x", + "none pinch-zoom pan-x", + "pinch-zoom pan-y none", + "pinch-zoom none pan-y", + "none pinch-zoom pan-y", + "pan-y pinch-zoom none", + "pan-y none pinch-zoom", + "none pan-y pinch-zoom", + "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 pinch-zoom auto", + "pan-x auto pinch-zoom", + "auto pan-x pinch-zoom", + "pinch-zoom pan-x auto", + "pinch-zoom auto pan-x", + "auto pinch-zoom pan-x", + "pinch-zoom pan-y auto", + "pinch-zoom auto pan-y", + "auto pinch-zoom pan-y", + "pan-y pinch-zoom auto", + "pan-y auto pinch-zoom", + "auto pan-y pinch-zoom", + "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", + "pinch-zoom pan-y zoom", + "pinch-zoom zoom pan-y", + "zoom pinch-zoom pan-y", + "pan-y pinch-zoom zoom", + "pan-y zoom pinch-zoom", + "zoom pan-y pinch-zoom", + "pan-x pinch-zoom zoom", + "pan-x zoom pinch-zoom", + "zoom pan-x pinch-zoom", + "pinch-zoom pan-x zoom", + "pinch-zoom zoom pan-x", + "zoom pinch-zoom 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", + "pan-x pinch-zoom pan-x", + "pan-x pan-x pinch-zoom", + "pinch-zoom pan-x pan-x", + "pinch-zoom pan-x pinch-zoom", + "pinch-zoom pinch-zoom pan-x", + "pan-x pinch-zoom pinch-zoom", + "pinch-zoom pan-y pinch-zoom", + "pinch-zoom pinch-zoom pan-y", + "pan-y pinch-zoom pinch-zoom", + "pan-y pinch-zoom pan-y", + "pan-y pan-y pinch-zoom", + "pinch-zoom 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 pinch-zoom", + "pinch-zoom 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", + "manipulation pinch-zoom pan-y", + "pinch-zoom manipulation pan-y", + "pinch-zoom pan-y manipulation", + "manipulation pan-y pinch-zoom", + "pan-y manipulation pinch-zoom", + "pan-y pinch-zoom manipulation", + "manipulation pan-x pinch-zoom", + "pan-x manipulation pinch-zoom", + "pan-x pinch-zoom manipulation", + "manipulation pinch-zoom pan-x", + "pinch-zoom manipulation pan-x", + "pinch-zoom pan-x manipulation", + ], +}; + +gCSSProperties["page"] = { + domProp: "page", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["page", "small_page", "large_page", "A4"], + invalid_values: ["page1 page2", "auto page", "1cm"], +}; + +gCSSProperties["text-justify"] = { + domProp: "textJustify", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_placeholder: true, + initial_values: ["auto"], + other_values: ["none", "inter-word", "inter-character", "distribute"], + invalid_values: [], +}; + +if (IsCSSPropertyPrefEnabled("layout.css.text-indent-keywords.enabled")) { + gCSSProperties["text-indent"].other_values.push( + "2em hanging", + "5% each-line", + "-10px hanging each-line", + "hanging calc(2px)", + "each-line calc(-2px)", + "each-line calc(50%) hanging", + "hanging calc(3*25px) each-line", + "each-line hanging calc(25px*3)" + ); + gCSSProperties["text-indent"].invalid_values.push( + "hanging", + "each-line", + "-10px hanging hanging", + "each-line calc(2px) each-line" + ); +} + +if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) { + gCSSProperties["font-variation-settings"] = { + domProp: "fontVariationSettings", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_marker: true, + applies_to_cue: true, + initial_values: ["normal"], + other_values: [ + "'wdth' 0", + "'wdth' -.1", + '"wdth" 1', + "'wdth' 2, 'wght' 3", + '"XXXX" 0', + ], + invalid_values: [ + "wdth", + "wdth 1", // unquoted tags + "'wdth'", + "'wdth' 'wght'", + "'wdth', 'wght'", // missing values + "'' 1", + "'wid' 1", + "'width' 1", // incorrect tag lengths + "'wd\th' 1", // non-graphic character in tag + "'wdth' 1 'wght' 2", // missing comma between pairs + "'wdth' 1,", // trailing comma + "'wdth' 1 , , 'wght' 2", // extra comma + "'wdth', 1", // comma within pair + ], + unbalanced_values: [ + "'wdth\" 1", + "\"wdth' 1", // mismatched quotes + ], + }; + gCSSProperties["font"].subproperties.push("font-variation-settings"); + gCSSProperties["font-optical-sizing"] = { + domProp: "fontOpticalSizing", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_marker: true, + applies_to_cue: true, + initial_values: ["auto"], + other_values: ["none"], + invalid_values: ["on"], + }; + gCSSProperties["font"].subproperties.push("font-optical-sizing"); + gCSSProperties["font-variation-settings"].other_values.push( + "'vert' calc(2.5)" + ); +} + +if (IsCSSPropertyPrefEnabled("layout.css.font-palette.enabled")) { + gCSSProperties["font-palette"] = { + domProp: "fontPalette", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + initial_values: ["normal"], + other_values: ["light", "dark", "--custom"], + invalid_values: ["custom"], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.font-variant-emoji.enabled")) { + gCSSProperties["font"].subproperties.push("font-variant-emoji"); + gCSSProperties["font-variant"].subproperties.push("font-variant-emoji"); + gCSSProperties["font-variant-emoji"] = { + domProp: "fontVariantEmoji", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_marker: true, + applies_to_placeholder: true, + applies_to_cue: true, + initial_values: ["normal"], + other_values: ["text", "emoji", "unicode"], + invalid_values: [ + "none", + "auto", + "text emoji", + "auto text", + "normal, unicode", + ], + }; +} + +var isGridTemplateMasonryValueEnabled = IsCSSPropertyPrefEnabled( + "layout.css.grid-template-masonry-value.enabled" +); + +if (isGridTemplateMasonryValueEnabled) { + gCSSProperties["masonry-auto-flow"] = { + domProp: "masonryAutoFlow", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["pack"], + other_values: ["pack ordered", "ordered next", "next definite-first"], + invalid_values: ["auto", "none", "10px", "row", "dense"], + }; + + let alignTracks = { ...gCSSProperties["align-content"] }; + alignTracks.domProp = "alignTracks"; + gCSSProperties["align-tracks"] = alignTracks; + + let justifyTracks = { ...gCSSProperties["justify-content"] }; + justifyTracks.domProp = "justifyTracks"; + gCSSProperties["justify-tracks"] = justifyTracks; +} + +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%)", + "40px 12%", + "2.5fr min-content fit-content(1px)", + ], + 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)", + "1px [a] 1px", + ], +}; +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, 0 0)", + "repeat(auto-fill, 0 [] 0)", + "repeat(auto-fill,minmax(1%,auto))", + "repeat(auto-fill,minmax(1em,min-content)) minmax(min-content,0)", + "repeat(auto-fill,minmax(max-content,1mm))", + "repeat(2, fit-content(1px))", + "fit-content(1px) 1fr", + "[a] fit-content(calc(1px - 99%)) [b]", + "[a] fit-content(10%) [b c] fit-content(1em)", + // See https://bugzilla.mozilla.org/show_bug.cgi?id=981300 + "[none 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 repeat(Auto-fill, [a b c]) [a] []", + "subgrid [x] repeat( Auto-fill, [a b c]) []", + "subgrid [x] repeat( auto-fill , [a b] [c]) [y]", + "subgrid repeat(auto-fill, [a] [b] [c]) [d]", + "subgrid repeat(Auto-fill, [a] [b c] [] [d])", + "subgrid [x y] [x] repeat(auto-fill, [a b] [c] [d] [d]) [x] [x]", + "subgrid [x] repeat(auto-fill, []) [y z]", + "subgrid [x] repeat(auto-fill, [y]) [z] [] repeat(2, [a] [b]) [y] []", + "subgrid [x] repeat(auto-fill, []) [x y] [z] [] []", + ], + 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-fill, fit-content(1px))", + "repeat(auto-fit, fit-content(1px))", + "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, 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)", + "fit-content(1px) repeat(auto-fit, 1px)", + "fit-content(1px) repeat(auto-fill, 1px)", + "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-fill)", + "subgrid repeat(auto-fill) [a]", + "subgrid repeat(auto-fill) []", + "subgrid [a] repeat(auto-fill)", + "subgrid repeat(auto-fill,)", + "subgrid repeat(auto-fill,)", + "subgrid repeat(auto-fill,) [a]", + "subgrid repeat(auto-fill,) []", + "subgrid [a] repeat(auto-fill,)", + "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, []) repeat(auto-fill, [])", + ], + unbalanced_values: ["(foo] 40px"], +}; +if (isGridTemplateMasonryValueEnabled) { + gCSSProperties["grid-template-columns"].other_values.push("masonry"); + gCSSProperties["grid-template-columns"].invalid_values.push( + "masonry []", + "masonry [foo] 40px", + "masonry 40px", + "[foo] masonry", + "0px masonry", + "masonry masonry", + "subgrid masonry", + "masonry subgrid", + "masonry repeat(1, [])" + ); +} +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", + "subgrid / 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]", + ], + 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", + "subgrid", + "subgrid []", + "subgrid [] / 'fizz'", + "subgrid / 'fizz'", + ], +}; +if (isGridTemplateMasonryValueEnabled) { + gCSSProperties["grid-template"].other_values.push( + "masonry / subgrid", + "subgrid / masonry", + "masonry / masonry" /* valid but behaves as 'masonry / none' */, + "masonry/40px 20px", + "subgrid [foo] [] [bar baz] / masonry", + "40px 20px/masonry", + "masonry/subgrid [foo] [] repeat(3, [a] [b]) [bar baz]", + "subgrid [foo] [] [bar baz]/masonry" + ); + gCSSProperties["grid-template"].invalid_values.push( + "masonry", + "masonry / '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", + ], + initial_values: ["none", "none / none"], + other_values: [ + "auto-flow 40px / none", + "auto-flow 40px 100px / 0", + "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 1px [a] 1px / 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["column-gap"] = { + domProp: "columnGap", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + 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-column-gap"] = { + domProp: "gridColumnGap", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "column-gap", + subproperties: ["column-gap"], +}; +gCSSProperties["row-gap"] = { + domProp: "rowGap", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + 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-row-gap"] = { + domProp: "gridRowGap", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "row-gap", + subproperties: ["row-gap"], +}; +gCSSProperties["gap"] = { + domProp: "gap", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["column-gap", "row-gap"], + initial_values: ["normal", "normal normal"], + other_values: [ + "1ch 0", + "1px 1%", + "1em 1px", + "calc(1px) calc(1%)", + "normal 0", + "1% normal", + ], + invalid_values: [ + "-1px", + "1px -1px", + "1px 1px 1px", + "inherit 1px", + "1px auto", + ], +}; +gCSSProperties["grid-gap"] = { + domProp: "gridGap", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + alias_for: "gap", + subproperties: ["column-gap", "row-gap"], +}; + +gCSSProperties["contain"] = { + domProp: "contain", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "strict", + "layout", + "size", + "content", + "paint", + "layout paint", + "paint layout", + "size layout", + "paint size", + "layout size paint", + "layout paint size", + "size paint layout", + "paint size layout", + ], + invalid_values: [ + "none strict", + "none size", + "strict layout", + "strict layout size", + "layout strict", + "layout content", + "strict content", + "layout size strict", + "layout size paint strict", + "paint strict", + "size strict", + "paint paint", + "content content", + "size content", + "content strict size", + "paint layout content", + "layout size content", + "size size", + "strict strict", + "auto", + "10px", + "0", + ], +}; + +if (IsCSSPropertyPrefEnabled("layout.css.initial-letter.enabled")) { + gCSSProperties["initial-letter"] = { + domProp: "initialLetter", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + 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, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + applies_to_cue: true, + applies_to_marker: true, + initial_values: ["auto"], + other_values: ["grayscale"], + invalid_values: ["none", "subpixel-antialiased", "antialiased"], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.scroll-anchoring.enabled")) { + gCSSProperties["overflow-anchor"] = { + domProp: "overflowAnchor", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["none"], + invalid_values: [], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.overflow-clip-box.enabled")) { + gCSSProperties["overflow-clip-box-block"] = { + domProp: "overflowClipBoxBlock", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_placeholder: true, + initial_values: ["padding-box"], + other_values: ["content-box"], + invalid_values: ["auto", "border-box", "0", "padding-box padding-box"], + }; + gCSSProperties["overflow-clip-box-inline"] = { + domProp: "overflowClipBoxInline", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_placeholder: true, + initial_values: ["padding-box"], + other_values: ["content-box"], + invalid_values: ["none", "border-box", "0", "content-box content-box"], + }; + gCSSProperties["overflow-clip-box"] = { + domProp: "overflowClipBox", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["overflow-clip-box-block", "overflow-clip-box-inline"], + initial_values: ["padding-box"], + other_values: [ + "content-box", + "padding-box content-box", + "content-box padding-box", + "content-box content-box", + ], + invalid_values: [ + "none", + "auto", + "content-box none", + "border-box", + "0", + "content-box, content-box", + ], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.overscroll-behavior.enabled")) { + gCSSProperties["overscroll-behavior-x"] = { + domProp: "overscrollBehaviorX", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["contain", "none"], + invalid_values: ["left", "1px"], + }; + gCSSProperties["overscroll-behavior-y"] = { + domProp: "overscrollBehaviorY", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["contain", "none"], + invalid_values: ["left", "1px"], + }; + gCSSProperties["overscroll-behavior-inline"] = { + domProp: "overscrollBehaviorInline", + inherited: false, + logical: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["contain", "none"], + invalid_values: ["left", "1px"], + }; + gCSSProperties["overscroll-behavior-block"] = { + domProp: "overscrollBehaviorBlock", + inherited: false, + logical: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["contain", "none"], + invalid_values: ["left", "1px"], + }; + gCSSProperties["overscroll-behavior"] = { + domProp: "overscrollBehavior", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["overscroll-behavior-x", "overscroll-behavior-y"], + initial_values: ["auto"], + other_values: [ + "contain", + "none", + "contain contain", + "contain auto", + "none contain", + ], + invalid_values: ["left", "1px", "contain auto none", "contain nonsense"], + }; +} + +{ + const patterns = { + background: [ + "{} scroll no-repeat", + "{} repeat", + "url(404.png), {}, -moz-element(#a) black", + ], + mask: [ + "{} add no-repeat", + "{} repeat", + "url(404.png), {}, -moz-element(#a) alpha", + ], + }; + + for (const prop of ["background", "mask"]) { + let i = 0; + const p = patterns[prop]; + for (const v of invalidNonUrlImageValues) { + gCSSProperties[prop].invalid_values.push( + p[i++ % p.length].replace("{}", v) + ); + } + for (const v of validNonUrlImageValues) { + gCSSProperties[prop].other_values.push( + p[i++ % p.length].replace("{}", v) + ); + } + } +} + +gCSSProperties["display"].other_values.push("flow-root"); + +gCSSProperties["hyphenate-character"] = { + domProp: "hyphenateCharacter", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_first_letter: true, + applies_to_first_line: true, + applies_to_placeholder: true, + initial_values: ["auto"], + other_values: ['"="', '"/-/"', '"\1400"', '""'], + invalid_values: ["none", "auto auto", "1400", "U+1400"], +}; + +if (IsCSSPropertyPrefEnabled("layout.css.content-visibility.enabled")) { + gCSSProperties["content-visibility"] = { + domProp: "contentVisibility", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["visible"], + other_values: ["auto", "hidden"], + invalid_values: [ + "invisible", + "partially-visible", + "auto auto", + "visible hidden", + ], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) { + gCSSProperties["contain-intrinsic-width"] = { + domProp: "containIntrinsicWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["1em", "1px", "auto 1px", "auto none"], + invalid_values: ["auto auto", "auto", "-1px"], + }; + gCSSProperties["contain-intrinsic-height"] = { + domProp: "containIntrinsicHeight", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["1em", "1px", "auto 1px", "auto none"], + invalid_values: ["auto auto", "auto", "-1px"], + }; + gCSSProperties["contain-intrinsic-block-size"] = { + domProp: "containIntrinsicBlockSize", + inherited: false, + logical: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["1em", "1px", "auto 1px", "auto none"], + invalid_values: ["auto auto", "auto", "-1px"], + }; + gCSSProperties["contain-intrinsic-inline-size"] = { + domProp: "containIntrinsicInlineSize", + inherited: false, + logical: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["1em", "1px", "auto 1px", "auto none"], + invalid_values: ["auto auto", "auto", "-1px"], + }; + + gCSSProperties["contain-intrinsic-size"] = { + domProp: "containIntrinsicSize", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["contain-intrinsic-width", "contain-intrinsic-height"], + initial_values: ["none"], + other_values: ["1em 1em", "1px 1px", "auto 1px auto 1px", "1px auto 1px"], + invalid_values: ["auto auto", "-1px -1px", "1px, auto none"], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.container-queries.enabled")) { + gCSSProperties["container-type"] = { + domProp: "containerType", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + other_values: ["inline-size", "size"], + invalid_values: [ + "none style", + "none inline-size", + "inline-size none", + "style none", + "style style", + "inline-size style inline-size", + "inline-size block-size", + "block-size", + "block-size style", + "size inline-size", + "size block-size", + ], + }; + gCSSProperties["container-name"] = { + domProp: "containerName", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: ["foo bar", "foo", "baz bazz", "foo foo"], + invalid_values: ["foo unset", "none bar", "foo initial", "initial foo"], + }; + gCSSProperties["container"] = { + domProp: "container", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["container-type", "container-name"], + initial_values: ["none"], + other_values: ["foo / size", "foo bar / size", "foo / inline-size", "foo"], + invalid_values: ["size / foo", "size / foo bar"], + }; +} + +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", "cliprounded"], + invalid_values: [], + }; + + gCSSProperties["-moz-window-opacity"] = { + // domProp: "MozWindowOpacity", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: [ + "1", + "17", + "397.376", + "3e1", + "3e+1", + "3e0", + "3e+0", + "3e-0", + "300%", + ], + other_values: ["0", "0.4", "0.0000", "-3", "3e-1", "-100%", "50%"], + invalid_values: ["0px", "1px", "20%", "default", "auto"], + }; + + gCSSProperties["-moz-window-transform"] = { + // domProp: "MozWindowTransform", + 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)", + "scalex(10%)", + "scalex(-10)", + "scalex(-10%)", + "scaley(10)", + "scaley(10%)", + "scaley(-10)", + "scaley(-10%)", + "scale(10)", + "scale(10%)", + "scale(10, 20)", + "scale(10%, 20%)", + "scale(-10)", + "scale(-10%)", + "scale(-10, 20)", + "scale(10%, -20%)", + "scale(10, 20%)", + "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(4%, 4%, 4%)", + "scale3d(-2, 3, -7)", + "scale3d(-2%, 3%, -7%)", + "scalez(4)", + "scalez(4%)", + "scalez(-6)", + "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)", + "translate(10px, calc(min(5px,10%)))", + "translate(calc(max(5px,10%)), 10%)", + "translate(max(5px,10%), 10%)", + ], + 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)", + "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%))", + "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)", + ], + }; + + gCSSProperties["-moz-window-transform-origin"] = { + // domProp: "MozWindowTransformOrigin", + 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", + "0px calc(0px + rubbish)", + "0px 0px calc(0px + rubbish)", + "6px 5px 5px", + "top center 10px", + ], + }; + + gCSSProperties["-moz-context-properties"] = { + //domProp: "MozContextProperties", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "fill", + "stroke", + "fill, stroke", + "fill, stroke, fill", + "fill, foo", + "foo", + ], + invalid_values: [ + "default", + "fill, auto", + "all, stroke", + "none, fill", + "fill, none", + "fill, default", + "2px", + ], + }; +} + +gCSSProperties["scrollbar-color"] = { + domProp: "scrollbarColor", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["red green", "blue yellow", "#ffff00 white"], + invalid_values: ["ffff00 red", "auto red", "red auto", "green"], +}; + +gCSSProperties["scrollbar-width"] = { + domProp: "scrollbarWidth", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["none", "thin"], + invalid_values: ["1px"], +}; + +gCSSProperties["offset"] = { + domProp: "offset", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: [ + "offset-path", + "offset-distance", + "offset-rotate", + "offset-anchor", + ], + initial_values: ["none"], + other_values: [ + "none 30deg reverse", + "none 50px reverse 30deg", + "none calc(10px + 20%) auto", + "none reverse", + "none / left center", + "path('M 0 0 H 1') -200% auto", + "path('M 0 0 H 1') -200%", + "path('M 0 0 H 1') 50px", + "path('M 0 0 H 1') auto", + "path('M 0 0 H 1') reverse 30deg 50px", + "path('M 0 0 H 1')", + "path('m 20 0 h 100') -7rad 8px / auto", + "path('m 0 30 v 100') -7rad 8px / left top", + "path('m 0 0 h 100') -7rad 8px", + "path('M 0 0 H 100') 100px 0deg", + ], + invalid_values: [ + "100px 0deg path('m 0 0 h 100')", + "30deg", + "auto 30deg 100px", + "auto / none", + "none /", + "none / 100px 20px 30deg", + "path('M 20 30 A 60 70 80') bottom", + "path('M 20 30 A 60 70 80') bottom top", + "path('M 20 30 A 60 70 80') 100px 200px", + "path('M 20 30 A 60 70 80') reverse auto", + "path('M 20 30 A 60 70 80') reverse 10px 30deg", + "path('M 20 30 A 60 70 80') /", + ], +}; + +gCSSProperties["offset-path"] = { + domProp: "offsetPath", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [...pathValues.other_values], + invalid_values: ["path('')"].concat(pathValues.invalid_values), +}; + +if (IsCSSPropertyPrefEnabled("layout.css.motion-path-ray.enabled")) { + gCSSProperties["offset-path"]["other_values"].push( + "ray(0deg)", + "ray(45deg closest-side)", + "ray(0rad farthest-side)", + "ray(0.5turn closest-corner contain)", + "ray(200grad farthest-corner)", + "ray(sides 180deg)", + "ray(contain farthest-side 180deg)", + "ray(calc(180deg - 45deg) farthest-side)", + "ray(0deg at center center)", + "ray(at 10% 10% 1rad)" + ); + + gCSSProperties["offset-path"]["invalid_values"].push( + "ray(closest-side)", + "ray(0deg, closest-side)", + "ray(contain 0deg closest-side contain)" + ); +} + +if (IsCSSPropertyPrefEnabled("layout.css.motion-path-basic-shapes.enabled")) { + gCSSProperties["offset-path"]["other_values"].push( + ...basicShapeOtherValues, + ...basicShapeXywhRectValues + ); +} + +if (IsCSSPropertyPrefEnabled("layout.css.motion-path-url.enabled")) { + gCSSProperties["offset-path"]["other_values"].push("url(#svgPath)"); +} + +gCSSProperties["offset-distance"] = { + domProp: "offsetDistance", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["0"], + other_values: ["10px", "10%", "190%", "-280%", "calc(30px + 40%)"], + invalid_values: ["none", "45deg"], +}; + +gCSSProperties["offset-rotate"] = { + domProp: "offsetRotate", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["reverse", "0deg", "0rad reverse", "-45deg", "5turn auto"], + invalid_values: ["none", "10px", "reverse 0deg reverse", "reverse auto"], +}; + +gCSSProperties["offset-anchor"] = { + domProp: "offsetAnchor", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: [ + "left bottom", + "center center", + "calc(20% + 10px) center", + "right 30em", + "10px 20%", + "left -10px top -20%", + "right 10% bottom 20em", + ], + invalid_values: ["none", "10deg", "left 10% top"], +}; + +if ( + IsCSSPropertyPrefEnabled("layout.css.motion-path-offset-position.enabled") +) { + gCSSProperties["offset"]["subproperties"].push("offset-position"); + gCSSProperties["offset"]["other_values"].push("top right / top left"); + + if (IsCSSPropertyPrefEnabled("layout.css.motion-path-ray.enabled")) { + gCSSProperties["offset"]["other_values"].push( + "top right ray(45deg closest-side)", + "50% 50% ray(0rad farthest-side)" + ); + } + + gCSSProperties["offset-position"] = { + domProp: "offsetPosition", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + other_values: [ + "auto", + "left bottom", + "center center", + "calc(20% + 10px) center", + "right 30em", + "10px 20%", + "left -10px top -20%", + "right 10% bottom 20em", + ], + invalid_values: ["none", "10deg", "left 10% top"], + }; +} + +{ + let linear_function_other_values = [ + "linear(0, 1)", + "linear(0 0% 50%, 1 50% 100%)", + ]; + + let linear_function_invalid_values = [ + "linear()", + "linear(0.5)", + "linear(0% 0 100%)", + "linear(0,)", + ]; + gCSSProperties["animation-timing-function"].other_values.push( + ...linear_function_other_values + ); + gCSSProperties["animation-timing-function"].invalid_values.push( + ...linear_function_invalid_values + ); + + gCSSProperties["transition-timing-function"].other_values.push( + ...linear_function_other_values + ); + gCSSProperties["transition-timing-function"].invalid_values.push( + ...linear_function_invalid_values + ); + + gCSSProperties["animation"].other_values.push( + "1s 2s linear(0, 1) bounce", + "4s linear(0, 0.5 25% 75%, 1 100% 100%)" + ); +} + +if (IsCSSPropertyPrefEnabled("layout.css.backdrop-filter.enabled")) { + gCSSProperties["backdrop-filter"] = { + domProp: "backdropFilter", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: gCSSProperties["filter"].other_values, + invalid_values: gCSSProperties["filter"].invalid_values, + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.math-depth.enabled")) { + gCSSProperties["math-depth"] = { + domProp: "mathDepth", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["0"], + other_values: [ + // auto-add cannot be tested here because it has no effect when the + // inherited math-style is equal to the default (normal). + "123", + "-123", + "add(123)", + "add(-123)", + "calc(1 + 2*3)", + "add(calc(4 - 2/3))", + ], + invalid_values: ["auto", "1,23", "1.23", "add(1,23)", "add(1.23)"], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.math-style.enabled")) { + gCSSProperties["math-style"] = { + domProp: "mathStyle", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal"], + other_values: ["compact"], + invalid_values: [], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.forced-color-adjust.enabled")) { + gCSSProperties["forced-color-adjust"] = { + domProp: "forcedColorAdjust", + inherited: true, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["none"], + invalid_values: [], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.animation-composition.enabled")) { + gCSSProperties["animation-composition"] = { + domProp: "animationComposition", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + initial_values: ["replace"], + other_values: [ + "add", + "accumulate", + "replace, add", + "add, accumulate", + "replace, add, accumulate", + ], + invalid_values: ["all", "none"], + }; +} + +if (IsCSSPropertyPrefEnabled("layout.css.prefixes.animations")) { + Object.assign(gCSSProperties, { + "-moz-animation": { + domProp: "MozAnimation", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + applies_to_marker: true, + 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, + applies_to_marker: true, + alias_for: "animation-delay", + subproperties: ["animation-delay"], + }, + "-moz-animation-direction": { + domProp: "MozAnimationDirection", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-direction", + subproperties: ["animation-direction"], + }, + "-moz-animation-duration": { + domProp: "MozAnimationDuration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-duration", + subproperties: ["animation-duration"], + }, + "-moz-animation-fill-mode": { + domProp: "MozAnimationFillMode", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-fill-mode", + subproperties: ["animation-fill-mode"], + }, + "-moz-animation-iteration-count": { + domProp: "MozAnimationIterationCount", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-iteration-count", + subproperties: ["animation-iteration-count"], + }, + "-moz-animation-name": { + domProp: "MozAnimationName", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-name", + subproperties: ["animation-name"], + }, + "-moz-animation-play-state": { + domProp: "MozAnimationPlayState", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-play-state", + subproperties: ["animation-play-state"], + }, + "-moz-animation-timing-function": { + domProp: "MozAnimationTimingFunction", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "animation-timing-function", + subproperties: ["animation-timing-function"], + }, + }); +} + +if (IsCSSPropertyPrefEnabled("layout.css.scroll-driven-animations.enabled")) { + // Basically, web-platform-tests should cover most cases, so here we only + // put some basic test cases. + gCSSProperties["animation"].subproperties.push("animation-timeline"); + gCSSProperties["animation"].initial_values.push( + "none none 0s 0s ease normal running 1.0 auto", + "none none auto" + ); + gCSSProperties["animation"].other_values.push( + "none none 0s 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) normal running 1.0 auto", + "bounce 1s linear 2s timeline", + "bounce 1s 2s linear none", + "bounce timeline", + "2s, 1s bounce timeline", + "1s bounce timeline, 2s", + "1s bounce none, 2s none auto" + ); + + gCSSProperties["-moz-animation"].subproperties.push("animation-timeline"); + gCSSProperties["-webkit-animation"].subproperties.push("animation-timeline"); + + gCSSProperties["animation-timeline"] = { + domProp: "animationTimeline", + inherited: false, + type: CSS_TYPE_LONGHAND, + applies_to_marker: true, + initial_values: ["auto"], + other_values: [ + "none", + "all", + "ball", + "mall", + "color", + "bounce, bubble, opacity", + "foobar", + "\\32bounce", + "-bounce", + "-\\32bounce", + "\\32 0bounce", + "-\\32 0bounce", + "\\2bounce", + "-\\2bounce", + "scroll()", + "scroll(block)", + "scroll(inline)", + "scroll(horizontal)", + "scroll(vertical)", + "scroll(root)", + "scroll(nearest)", + "scroll(inline nearest)", + "scroll(vertical root)", + "scroll(root horizontal)", + "view()", + "view(inline)", + "view(auto)", + "view(auto 1px)", + "view(inline auto)", + "view(vertical auto auto)", + "view(horizontal 1px 1%)", + "view(1px 1% block)", + ], + invalid_values: [ + "bounce, initial", + "initial, bounce", + "bounce, inherit", + "inherit, bounce", + "bounce, unset", + "unset, bounce", + ], + }; + + gCSSProperties["scroll-timeline-name"] = { + domProp: "scrollTimelineName", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "all", + "auto", + "ball", + "mall", + "color", + "foobar", + "\\32bounce", + "-bounce", + "-\\32bounce", + "\\32 0bounce", + "-\\32 0bounce", + "\\2bounce", + "-\\2bounce", + ], + invalid_values: ["abc bounce", "10px", "rgb(1, 2, 3)"], + }; + + gCSSProperties["scroll-timeline-axis"] = { + domProp: "scrollTimelineAxis", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["block"], + other_values: ["inline", "vertical", "horizontal"], + invalid_values: ["auto", "none", "abc"], + }; + + gCSSProperties["scroll-timeline"] = { + domProp: "scrollTimeline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["scroll-timeline-name", "scroll-timeline-axis"], + initial_values: ["none block", "none"], + other_values: [ + "auto inline", + "bounce inline", + "bounce vertical", + "\\32bounce inline", + "-bounce block", + "\\32 0bounce vertical", + "-\\32 0bounce horizontal", + "a, b, c", + "a block, b inline, c vertical", + ], + invalid_values: ["", "bounce bounce", "horizontal a", "block abc"], + }; + + gCSSProperties["view-timeline-name"] = { + domProp: "viewTimelineName", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["none"], + other_values: [ + "all", + "auto", + "ball", + "mall", + "color", + "foobar", + "\\32bounce", + "-bounce", + "-\\32bounce", + "\\32 0bounce", + "-\\32 0bounce", + "\\2bounce", + "-\\2bounce", + "bounce, abc", + "none, none", + ], + invalid_values: ["abc bounce", "10px", "rgb(1, 2, 3)"], + }; + + gCSSProperties["view-timeline-axis"] = { + domProp: "viewTimelineAxis", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["block"], + other_values: ["inline", "vertical", "horizontal", "inline, block"], + invalid_values: ["auto", "none", "abc", "inline block"], + }; + + gCSSProperties["view-timeline-inset"] = { + domProp: "viewTimelineInset", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["0px", "1%", "1px 1%", "0px 0%", "calc(0px) auto"], + invalid_values: ["none", "rgb(1, 2, 3)", "foo bar", "1px 2px 3px"], + }; + + gCSSProperties["view-timeline"] = { + domProp: "viewTimeline", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + subproperties: ["view-timeline-name", "view-timeline-axis"], + initial_values: ["none block", "none"], + other_values: [ + "auto inline", + "bounce inline", + "bounce vertical", + "\\32bounce inline", + "-bounce block", + "\\32 0bounce vertical", + "-\\32 0bounce horizontal", + "a, b, c", + "a block, b inline, c vertical", + ], + invalid_values: ["", ",", "abc abc", "horizontal a", "block abc"], + }; +} + +gCSSProperties["scrollbar-gutter"] = { + domProp: "scrollbarGutter", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["auto"], + other_values: ["stable", "stable both-edges", "both-edges stable"], + invalid_values: [ + "auto stable", + "auto both-edges", + "both-edges", + "stable mirror", + // The following values are from scrollbar-gutter extension in CSS + // Overflow 4 https://drafts.csswg.org/css-overflow-4/#sbg-ext. + "always", + "always both-edges", + "always force", + "always both-edges force", + "stable both-edges force", + "match-parent", + ], +}; + +if (IsCSSPropertyPrefEnabled("layout.css.text-wrap-balance.enabled")) { + gCSSProperties["text-wrap-style"] = { + domProp: "textWrapStyle", + inherited: true, + type: CSS_TYPE_LONGHAND, + applies_to_placeholder: true, + applies_to_cue: true, + applies_to_marker: true, + initial_values: ["auto"], + other_values: ["stable", "balance"], + invalid_values: ["wrap", "nowrap", "normal"], + }; + gCSSProperties["text-wrap"].subproperties.push("text-wrap-style"); + gCSSProperties["text-wrap"].other_values.push("stable"); + gCSSProperties["text-wrap"].other_values.push("balance"); + gCSSProperties["text-wrap"].other_values.push("wrap stable"); + gCSSProperties["text-wrap"].other_values.push("nowrap balance"); +} + +if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transforms")) { + Object.assign(gCSSProperties, { + "-moz-transform": { + domProp: "MozTransform", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + alias_for: "transform", + subproperties: ["transform"], + }, + "-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"], + }, + }); +} + +if (IsCSSPropertyPrefEnabled("layout.css.zoom.enabled")) { + Object.assign(gCSSProperties, { + zoom: { + domProp: "zoom", + inherited: false, + type: CSS_TYPE_LONGHAND, + initial_values: ["normal", "1", "100%", "0", "0%"], + other_values: ["1.5", "2", "150%", "200%"], + invalid_values: ["-1", "-40%"], + }, + }); +} + +if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transitions")) { + Object.assign(gCSSProperties, { + "-moz-transition": { + domProp: "MozTransition", + inherited: false, + type: CSS_TYPE_TRUE_SHORTHAND, + applies_to_marker: true, + 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, + applies_to_marker: true, + alias_for: "transition-delay", + subproperties: ["transition-delay"], + }, + "-moz-transition-duration": { + domProp: "MozTransitionDuration", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "transition-duration", + subproperties: ["transition-duration"], + }, + "-moz-transition-property": { + domProp: "MozTransitionProperty", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "transition-property", + subproperties: ["transition-property"], + }, + "-moz-transition-timing-function": { + domProp: "MozTransitionTimingFunction", + inherited: false, + type: CSS_TYPE_SHORTHAND_AND_LONGHAND, + applies_to_marker: true, + alias_for: "transition-timing-function", + subproperties: ["transition-timing-function"], + }, + }); +} + +// Copy aliased properties' fields from their alias targets. Keep this logic +// at the bottom of this file to ensure all the aliased properties are +// processed. +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]; + } + }); + } + } +} diff --git a/layout/style/test/redirect.sjs b/layout/style/test/redirect.sjs new file mode 100644 index 0000000000..43fec90b5a --- /dev/null +++ b/layout/style/test/redirect.sjs @@ -0,0 +1,4 @@ +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 0000000000..09236563de --- /dev/null +++ b/layout/style/test/redundant_font_download.sjs @@ -0,0 +1,63 @@ +"use strict"; + +const BinaryOutputStream = Components.Constructor( + "@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream" +); + +// this is simply a hex dump of a red square .PNG image +// prettier-ignore +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) { + let query = {}; + request.queryString.split("&").forEach(function (val) { + let [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); + + let log = getState("bug-879963-request-log") || ""; + + let 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); + } else if (query.q == "image") { + log = log + ";" + query.q; + response.setHeader("Content-Type", "image/png", false); + stream.writeByteArray(RED_SQUARE); + } 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/slow_broken_sheet.sjs b/layout/style/test/slow_broken_sheet.sjs new file mode 100644 index 0000000000..6af03ee4c6 --- /dev/null +++ b/layout/style/test/slow_broken_sheet.sjs @@ -0,0 +1,19 @@ +// Make sure our timer stays alive. +let gTimer; + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine("1.1", 404, "Not Found"); + response.processAsync(); + + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + // Wait for 1s before responding; this should usually make sure this load comes in last. + gTimer.init( + () => { + response.write("<h1>Hello</h1>"); + response.finish(); + }, + 1000, + Ci.nsITimer.TYPE_ONE_SHOT + ); +} diff --git a/layout/style/test/slow_load.sjs b/layout/style/test/slow_load.sjs new file mode 100644 index 0000000000..3873f466ce --- /dev/null +++ b/layout/style/test/slow_load.sjs @@ -0,0 +1,29 @@ +// Make sure our timer stays alive. +let gTimer; + +function handleRequest(request, response) { + let isCss = request.queryString.indexOf("css") != -1; + + response.setHeader("Content-Type", isCss ? "text/css" : "text/plain", false); + response.setStatusLine("1.1", 200, "OK"); + response.processAsync(); + + let time = Date.now(); + + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + // Wait for 1s before responding; this should usually make sure this load comes in last. + gTimer.init( + () => { + if (isCss) { + // FIXME(emilio): This clamps the date to the 32-bit integer range which + // is what we use to store the z-index... We don't seem to store f64s + // anywhere in the specified values... + time = time % (Math.pow(2, 31) - 1); + response.write(":root { z-index: " + time + "}"); + } + response.finish(); + }, + 1000, + Ci.nsITimer.TYPE_ONE_SHOT + ); +} diff --git a/layout/style/test/slow_ok_sheet.sjs b/layout/style/test/slow_ok_sheet.sjs new file mode 100644 index 0000000000..5673bb2be8 --- /dev/null +++ b/layout/style/test/slow_ok_sheet.sjs @@ -0,0 +1,21 @@ +// Make sure our timer stays alive. +let gTimer; + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/css", false); + response.setStatusLine("1.1", 200, "OK"); + response.processAsync(); + + gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + // Wait for 1s before responding; this should usually make sure this load comes in last. + gTimer.init( + () => { + // This sheet _does_ still get applied even though its importing link + // overall reports failure... + response.write("nosuchelement { background: red }"); + response.finish(); + }, + 1000, + Ci.nsITimer.TYPE_ONE_SHOT + ); +} diff --git a/layout/style/test/sourcemap_css.html b/layout/style/test/sourcemap_css.html new file mode 100644 index 0000000000..1f29a38d5f --- /dev/null +++ b/layout/style/test/sourcemap_css.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test for bug 1306887</title> + <link rel="stylesheet" type="text/css" href="mapped.css"/> + <link rel="stylesheet" type="text/css" href="mapped2.css"/> + </head> + <body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1306887">Mozilla Bug 1306887</a> + </body> +</html> diff --git a/layout/style/test/style_attribute_tests.js b/layout/style/test/style_attribute_tests.js new file mode 100644 index 0000000000..243ec3380d --- /dev/null +++ b/layout/style/test/style_attribute_tests.js @@ -0,0 +1,25 @@ +SimpleTest.waitForExplicitFinish(); + +window.addEventListener("load", runTests); + +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/1x1-transparent.png b/layout/style/test/support/1x1-transparent.png Binary files differnew file mode 100644 index 0000000000..56cd9eb930 --- /dev/null +++ b/layout/style/test/support/1x1-transparent.png diff --git a/layout/style/test/support/blue-100x100.png b/layout/style/test/support/blue-100x100.png Binary files differnew file mode 100644 index 0000000000..3b72d5ce53 --- /dev/null +++ b/layout/style/test/support/blue-100x100.png 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 0000000000..d730ac0cea --- /dev/null +++ b/layout/style/test/support/external-variable-url.css @@ -0,0 +1,3 @@ +#t4 { + --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 0000000000..4ec50cfddc --- /dev/null +++ b/layout/style/test/test_acid3_test46.html @@ -0,0 +1,140 @@ +<!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 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) { + let p = doc.createElement('p'); + p.id = names[i]; + doc.body.appendChild(p); + } + var count = 0; + var check = function (c, e) { + count += 1; + let 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 0000000000..06f9f93fc3 --- /dev/null +++ b/layout/style/test/test_addSheet.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for addSheet</title> + <script 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"> + +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); + is(cs.getPropertyValue('color'), "rgb(0, 0, 0)", "should have default color"); + var windowUtils = SpecialPowers.getDOMWindowUtils(win); + 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;}"); + 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 0000000000..8cd8ffd93a --- /dev/null +++ b/layout/style/test/test_additional_sheets.html @@ -0,0 +1,310 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for additional sheets</title> + <script 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"> + +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)); + var windowUtils = SpecialPowers.getDOMWindowUtils(win); + windowUtils.loadSheet(uri, windowUtils[type]); +} + +function removeSheet(win, style, type) +{ + var uri = gIOService.newURI(getUri(style)); + var windowUtils = SpecialPowers.getDOMWindowUtils(win); + 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)); + gSSService.loadAndRegisterSheet(uri, gSSService[type]); + is(gSSService.sheetRegistered(uri, gSSService[type]), true); +} + +function unregisterSheet(win, style, type) +{ + var uri = gIOService.newURI(getUri(style)); + 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.remove(); +} + +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); + 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'), 'rgba(0, 0, 0, 0)', 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 0000000000..aa13762cb7 --- /dev/null +++ b/layout/style/test/test_align_justify_computed_values.html @@ -0,0 +1,484 @@ +<!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 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 ComputedStyle.) + + 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 ComputedStyle.) + + 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 element = document.body; + var child = document.getElementById("display"); + var absChild = document.getElementById("absChild"); + is(getComputedAlignItems(element), 'normal', "default align-items value for block container"); + is(getComputedAlignSelf(child), 'auto', "default align-self value for block child"); + is(getComputedAlignSelf(absChild), 'auto', "default align-self value for block container abs.pos. child"); + element.style.alignItems = "end"; + is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child"); + is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists for block container abs.pos. child"); + element.style.alignItems = "left"; + is(getComputedAlignItems(element), 'end', "align-items:left is an invalid declaration"); + is(getComputedAlignSelf(child), 'auto', "align-self:auto persists for block child"); + is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists for block container abs.pos. child"); + element.style.alignItems = "right"; + is(getComputedAlignItems(element), 'end', "align-items:right is an invalid declaration"); + is(getComputedAlignSelf(child), 'auto', "align-self:auto value persists for block child"); + is(getComputedAlignSelf(absChild), 'auto', "align-self:auto value persists 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), 'flex-end', "align-items:left is an invalid declaration"); + 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), 'end', "align-items:left is an invalid declaration"); + 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(getComputedAlignItems(elem), 'end', "align-items:right is an invalid declaration"); + 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"); + + item.style.alignSelf = ""; + abs.style.alignSelf = ""; + elem.style.alignItems = ""; + item.style.alignSelf = ""; + } + testGridAlignItemsSelf(document.getElementById("gridContainer")); + testGridAlignItemsSelf(document.getElementById("gridContainerFlex")); + + // + // justify-items/self tests: + // + //// Block tests + element = document.body; + child = document.getElementById("display"); + absChild = document.getElementById("absChild"); + is(getComputedJustifyItems(element), 'normal', "default justify-items value for block container"); + is(getComputedJustifySelf(child), 'auto', "default justify-self value for block container child"); + is(getComputedJustifySelf(absChild), 'auto', "default justify-self value for block container abs.pos. child"); + element.style.justifyItems = "end"; + is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child"); + is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child"); + element.style.justifyItems = "left"; + is(getComputedJustifyItems(element), 'left', "justify-items:left computes to itself on a block"); + is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child"); + is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child"); + element.style.justifyItems = "right"; + is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child"); + is(getComputedJustifySelf(absChild), 'auto', "justify-self:auto value persists for block container abs.pos. child"); + element.style.justifyItems = "safe right"; + is(getComputedJustifySelf(child), 'auto', "justify-self:auto value persists for block child"); + element.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 = ""; + absChild.style.justifySelf = "right"; + is(getComputedJustifySelf(absChild), '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 = "safe right"; + 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 = "safe right"; + 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 = "safe right"; + is(getComputedJustifySelf(item), 'safe right', "justify-self:'safe right' computes to 'safe right' 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 + element = document.body; + child = document.getElementById("display"); + absChild = document.getElementById("absChild"); + is(getComputedAlignContent(element), 'normal', "default align-content value for block container"); + is(getComputedAlignContent(child), 'normal', "default align-content value for block child"); + is(getComputedAlignContent(absChild), 'normal', "default align-content value for block container abs.pos. child"); + element.style.alignContent = "end"; + is(getComputedAlignContent(child), 'normal', "default align-content isn't affected by parent align-content value for in-flow child"); + is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child"); + element.style.alignContent = "left"; + is(getComputedAlignContent(element), 'end', "align-content:left isn't a valid declaration"); + is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child"); + element.style.alignContent = "right"; + is(getComputedAlignContent(element), 'end', "align-content:right isn't a valid declaration"); + is(getComputedAlignContent(absChild), 'normal', "default align-content isn't affected by parent align-content value for block container abs.pos. child"); + element.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 = "safe end"; + is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' 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"); + 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 = "safe end"; + is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' 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 = "safe end"; + item.style.alignContent = "inherit"; + abs.style.alignContent = "inherit"; + is(getComputedAlignContent(elem), 'safe end', "align-content:'safe end' computes to 'align-content:safe end' on grid container"); + is(getComputedAlignContent(item), 'safe end', "align-content:'safe end' inherits as 'align-content:safe end' to grid item"); + is(getComputedAlignContent(abs), 'safe end', "align-content:'safe end' inherits as 'align-content:safe end' to grid container abs.pos. child"); + item.style.alignContent = ""; + abs.style.alignContent = ""; + elem.style.alignContent = ""; + item.style.alignContent = ""; + } + testGridAlignContent(document.getElementById("gridContainer")); + testGridAlignContent(document.getElementById("gridContainerFlex")); + + + // + // justify-content tests: + // + //// Block tests + element = document.body; + child = document.getElementById("display"); + absChild = document.getElementById("absChild"); + is(getComputedJustifyContent(element), 'normal', "default justify-content value for block container"); + is(getComputedJustifyContent(child), 'normal', "default justify-content value for block child"); + is(getComputedJustifyContent(absChild), 'normal', "default justify-content value for block container abs.pos. child"); + element.style.justifyContent = "end"; + is(getComputedJustifyContent(child), 'normal', "default justify-content isn't affected by parent justify-content value for in-flow child"); + is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child"); + element.style.justifyContent = "left"; + is(getComputedJustifyContent(element), 'left', "justify-content:left computes to left on block child"); + is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child"); + element.style.justifyContent = "right"; + is(getComputedJustifyContent(element), 'right', "justify-content:right computes to right on block child"); + is(getComputedJustifyContent(absChild), 'normal', "default justify-content isn't affected by parent justify-content value for block container abs.pos. child"); + element.style.justifyContent = "safe right"; + is(getComputedJustifyContent(element), 'safe right', "justify-content:'safe right' computes to 'justify-content:safe right'"); + element.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 = "safe left"; + is(getComputedJustifyContent(child), 'safe left', "justify-content:safe left computes to 'safe left' on block child"); + child.style.justifyContent = ""; + absChild.style.justifyContent = "right"; + is(getComputedJustifyContent(absChild), 'right', "justify-content:right computes to right on block container abs.pos. child"); + absChild.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 = "safe end"; + is(getComputedJustifyContent(elem), 'safe end', "justify-content:'safe end' 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 = "safe end"; + is(getComputedJustifyContent(elem), 'safe end', "justify-content:'safe end' 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 = "safe right"; + item.style.justifyContent = "inherit"; + abs.style.justifyContent = "inherit"; + is(getComputedJustifyContent(elem), 'safe right', "justify-content:'safe right' computes to 'justify-content:safe right' on grid container"); + is(getComputedJustifyContent(item), 'safe right', "justify-content:'safe right' inherits as 'justify-content:safe right' to grid item"); + is(getComputedJustifyContent(abs), 'safe right', "justify-content:'safe right' inherits as 'justify-content:safe right' 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 = "safe right"; + is(getComputedJustifyContent(item), 'safe right', "justify-content:'safe right' computes to 'safe right' 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_all_shorthand.html b/layout/style/test/test_all_shorthand.html new file mode 100644 index 0000000000..0a3bfd29fd --- /dev/null +++ b/layout/style/test/test_all_shorthand.html @@ -0,0 +1,157 @@ +<!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 onload="runTest()"> + +<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(); +</script> diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html new file mode 100644 index 0000000000..846eb1d2a0 --- /dev/null +++ b/layout/style/test/test_animations.html @@ -0,0 +1,2107 @@ +<!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 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% { white-space: pre; 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 { 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 { 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 } + } + + @keyframes "string name 1" { /* using string for keyframes name */ + 0%, 100% { left: 1px } + } + + @keyframes "string name 2" { + 0%, 100% { left: 2px } + } + + @keyframes custom\ ident\ 1 { + 0%, 100% { left: 3px } + } + + @keyframes custom\ ident\ 2 { + 0%, 100% { left: 4px } + } + + @keyframes "initial" { + 0%, 100% { left: 5px } + } + + @keyframes initial { /* illegal as an identifier, should be dropped */ + 0%, 100% { left: 6px } + } + + @keyframes "none" { + 0%, 100% { left: 7px } + } + + @keyframes none { /* illegal as an identifier, should be dropped */ + 0%, 100% { left: 8px } + } + </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); + +SimpleTest.registerCleanupFunction(() => { + SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); +}); + +/* + * 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_approx(px_to_num(cs.paddingTop), 60, 0.001, "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: "" }], + "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 +// option { white-space: !important } +// If that rule changes, we should rewrite it to depend on a different rule. +var option; +[ option, cs ] = new_element("option", ""); +var default_white_space = cs.whiteSpace; +isnot(default_white_space, "pre", + "default style should not be the same as animation style"); +done_element(); +[ option, cs ] = new_element("option", + "animation: uaoverride 2s linear infinite"); +is(cs.whiteSpace, default_white_space, + "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.whiteSpace, default_white_space, + "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("animation: primitives1 2s linear forwards"); +is(cs.getPropertyValue("transform"), "matrix(1, 0, 0, 1, 0, 0)", + "primitives1 at 0s"); +advance_clock(1000); +is(cs.getPropertyValue("transform"), + "matrix(-0.707107, 0.707107, -0.707107, -0.707107, 0, 0)", + "primitives1 at 1s"); +advance_clock(1000); +is(cs.getPropertyValue("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.remove(); +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.remove(); +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(); + + +/* + * Bug 1320474 - keyframes-name may be a string, allows names that would otherwise be excluded + */ +new_div("position: relative; animation: \"string name 1\" 1s linear"); +advance_clock(0); +is(cs.getPropertyValue("left"), "1px", "animation name as a string"); +div.style.animation = "string\\ name\\ 2 1s linear"; +is(cs.getPropertyValue("left"), "2px", "animation name specified as string, referenced using custom ident"); +div.style.animation = "custom\\ ident\\ 1 1s linear"; +is(cs.getPropertyValue("left"), "3px", "animation name specified as custom-ident"); +div.style.animation = "\"custom ident 2\" 1s linear"; +is(cs.getPropertyValue("left"), "4px", "animation name specified as custom-ident, referenced using string"); +div.style.animation = "unset"; +div.style.animation = "initial 1s linear"; +is(cs.getPropertyValue("left"), "0px", "animation name 'initial' as identifier is ignored"); +div.style.animation = "unset"; +div.style.animation = "\"initial\" 1s linear"; +is(cs.getPropertyValue("left"), "5px", "animation name 'initial' as string is accepted"); +div.style.animation = "unset"; +div.style.animation = "none 1s linear"; +is(cs.getPropertyValue("left"), "0px", "animation name 'none' as identifier is ignored"); +div.style.animation = "unset"; +div.style.animation = "\"none\" 1s linear"; +is(cs.getPropertyValue("left"), "7px", "animation name 'none' as string is accepted"); +done_div(); + +</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 0000000000..7ad1d0d598 --- /dev/null +++ b/layout/style/test/test_animations_async_tests.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1086937</title> + <script 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() { + 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 0000000000..1863d08829 --- /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 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 0000000000..7b3c443669 --- /dev/null +++ b/layout/style/test/test_animations_effect_timing_duration.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<html> +<head> + <title> + Test for animation.effect.updateTiming({ duration }) on compositor + animations + </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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 { + 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> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +runOMTATest(function() { + runAllAsyncAnimTests().then(SimpleTest.finish); +}, SimpleTest.finish, SpecialPowers); + +addAsyncAnimTest(async function() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)', easing: "steps(2, start)" }, + { transform: 'translate(100px)' } ], 4000); + await waitForPaints(); + + advance_clock(500); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, + "Animation is running on compositor"); + animation.effect.updateTiming({ 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); + + await 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(async function() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)', easing: "steps(2, end)" }, + { transform: 'translate(100px)' } ], 4000); + await waitForPaints(); + + advance_clock(1000); + animation.effect.updateTiming({ duration: 2000 }); + advance_clock(0); + await 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/test_animations_effect_timing_enddelay.html b/layout/style/test/test_animations_effect_timing_enddelay.html new file mode 100644 index 0000000000..ad018f6373 --- /dev/null +++ b/layout/style/test/test_animations_effect_timing_enddelay.html @@ -0,0 +1,141 @@ +<!DOCTYPE html> +<html> +<head> + <title> + Test for animation.effect.updateTiming({ endDelay }) on compositor + animations + </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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"> + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +runOMTATest(function() { + runAllAsyncAnimTests().then(SimpleTest.finish); +}, SimpleTest.finish, SpecialPowers); + +addAsyncAnimTest(async function() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ], + { duration: 1000, fill: 'none' }); + await waitForPaints(); + + advance_clock(100); + omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor, + "Animation is running on compositor"); + animation.effect.updateTiming({ endDelay: 1000 }); + + await waitForPaints(); + omta_is(div, "transform", { tx: 10 }, RunningOn.Compositor, + "Animation remains on compositor when endDelay is changed"); + + advance_clock(1000); + await waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread, + "Animation is updated on main thread"); + + done_div(); +}); + +addAsyncAnimTest(async function() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ], + { duration: 1000, endDelay: -500, fill: 'none' }); + await waitForPaints(); + + advance_clock(400); + await 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); + await 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); + await 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); + await 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(async function() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ], + { duration: 1000, endDelay: 1000, fill: 'forwards' }); + await waitForPaints(); + + advance_clock(1500); + await waitForPaints(); + omta_is(div, "transform", { tx: 100 }, RunningOn.Compositor, + "The end delay is performed on the compositor thread"); + + done_div(); +}); + +addAsyncAnimTest(async function() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ], + { duration: 1000, endDelay: -500, fill: 'forwards' }); + await waitForPaints(); + + advance_clock(400); + await 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); + await 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); + await 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); + await 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/test_animations_effect_timing_iterations.html b/layout/style/test/test_animations_effect_timing_iterations.html new file mode 100644 index 0000000000..380cab1763 --- /dev/null +++ b/layout/style/test/test_animations_effect_timing_iterations.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<html> +<head> + <title> + Test for Animation.effect.updateTiming({ iterations }) on compositor + animations + </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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 { + 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> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +runOMTATest(function() { + runAllAsyncAnimTests().then(SimpleTest.finish); +}, SimpleTest.finish, SpecialPowers); + +addAsyncAnimTest(async function() { + var [ div ] = new_div(""); + var animation = div.animate( + [ { transform: 'translate(0px)' }, + { transform: 'translate(100px)' } ], + { duration: 4000, + iterations: 2 + }); + await waitForPaints(); + + advance_clock(6000); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, + "Animation is running on compositor"); + animation.effect.updateTiming({ iterations: 1 }); + advance_clock(0); + + await waitForPaints(); + omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread, + "Animation is on MainThread"); + + animation.effect.updateTiming({ iterations: 3 }); + + advance_clock(0); + await 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/test_animations_event_handler_attribute.html b/layout/style/test/test_animations_event_handler_attribute.html new file mode 100644 index 0000000000..7a9da1809c --- /dev/null +++ b/layout/style/test/test_animations_event_handler_attribute.html @@ -0,0 +1,204 @@ +<!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 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); + +// 1a. Test CSS Animation event handlers (without animationcancel) + +var targets = createAndRegisterTargets([ 'onanimationstart', + 'onanimationiteration', + 'onanimationend', + 'onanimationcancel']); +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(); }); + +// 1b. Test CSS Animation cancel event handler +var targets = createAndRegisterTargets([ 'onanimationcancel' ]); + +targets.forEach(div => { + div.setAttribute('style', 'animation: anim 100ms 2 200ms'); + getComputedStyle(div).animationName; // flush +}); + +advance_clock(200); + +targets.forEach(div => { + div.style.display = "none" + getComputedStyle(div).display; // flush +}); + +advance_clock(0); +checkReceivedEvents("animationcancel", targets); + +advance_clock(200); + +targets.forEach(div => { div.remove(); }); + + +// 2a. Test CSS Transition event handlers (without transitioncancel) + +var targets = createAndRegisterTargets([ 'ontransitionrun', + 'ontransitionstart', + 'ontransitionend', + 'ontransitioncancel' ]); +targets.forEach(div => { + div.style.transition = 'margin-left 100ms 200ms'; + getComputedStyle(div).marginLeft; // flush + div.style.marginLeft = "200px"; + getComputedStyle(div).marginLeft; // flush +}); + +advance_clock(0); +checkReceivedEvents("transitionrun", targets); + +advance_clock(200); +checkReceivedEvents("transitionstart", targets); + +advance_clock(100); +checkReceivedEvents("transitionend", targets); + +targets.forEach(div => { div.remove(); }); + +// 2b. Test CSS Transition cancel event handler. + +var targets = createAndRegisterTargets([ 'ontransitioncancel' ]); +targets.forEach(div => { + div.style.transition = 'margin-left 100ms 200ms'; + getComputedStyle(div).marginLeft; // flush + div.style.marginLeft = "200px"; + getComputedStyle(div).marginLeft; // flush +}); + +advance_clock(200); + +targets.forEach(div => { + div.style.display = "none" +}); +getComputedStyle(targets[0]).display; // flush + +advance_clock(0); +checkReceivedEvents("transitioncancel", targets); + +advance_clock(100); +targets.forEach( div => { is(div.receivedEventType, undefined); }); + +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 0000000000..7caee2bdcb --- /dev/null +++ b/layout/style/test/test_animations_event_order.html @@ -0,0 +1,710 @@ +<!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 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'; + +/* eslint-disable no-shadow */ + +// 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', + 'animationcancel', + 'transitionrun', + 'transitionstart', + 'transitionend', + 'transitioncancel' ] + .forEach(event => + gDisplay.addEventListener(event, + event => gEventsReceived.push(event))); + +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); +sheet.insertRule('#div0::after, #div0::before { ' + + ' content: " " }', 2); +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], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionend' ], + [ divs[1], 'transitionend' ], + 'Simultaneous transitionrun/start/end 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], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[2], 'transitionrun' ], + [ divs[2], 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionend' ], + [ divs[2], 'transitionend' ], + [ divs[1], 'transitionend' ], + 'Simultaneous transitionrun/start/end 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], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[0], '::before', 'transitionrun' ], + [ divs[0], '::before', 'transitionstart' ], + [ divs[0], '::after', 'transitionrun' ], + [ divs[0], '::after', 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionend' ], + [ divs[0], '::before', 'transitionend' ], + [ divs[0], '::after', 'transitionend' ], + [ divs[1], 'transitionend' ], + 'Simultaneous transitionrun/start/end 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[0], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[1], 'transitionend' ], + [ divs[0], 'transitionend' ], + 'Sorting of transitionrun/start/end 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[0], 'transitionrun' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionend' ], + [ divs[0], 'transitionend' ], + 'Sorting of transitionrun/start/end 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', 'transitionrun' ], + [ 'margin-left', 'transitionstart' ], + [ 'opacity', 'transitionrun' ], + [ 'opacity', 'transitionstart' ], + [ 'margin-left', 'transitionend' ], + [ 'opacity', 'transitionend' ], + 'Sorting of transitionrun/start/end 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], 'transitionrun' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ 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([ 'margin-left', 'transitionrun' ], + [ 'margin-left', 'transitionstart' ], + [ 'opacity', 'transitionrun' ], + [ 'opacity', 'transitionstart' ], + [ '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[0], 'transitionrun' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionstart' ], + [ divs[1], 'transitionend' ], + [ divs[0], 'transitionend' ], + 'Simultaneous transitionrun/start/end on siblings'); + +divs.forEach(div => div.remove()); +divs = []; + +// 4j. Test sorting transitions with cancel +// The order of transitioncancel is based on StyleManager. + +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(5 * 1000); +divs.forEach(div => { + div.style.display = 'none'; + // The transitioncancel event order is not absolute when firing siblings + // transitioncancel on same elapsed time. + // Force to flush style for the element so that the transition on the element + // iscancelled and corresponding cancel event is queued respectively. + getComputedStyle(div).display; +}); +advance_clock(10 * 1000); + +checkEventOrder([ divs[0], 'transitionrun' ], + [ divs[1], 'transitionrun' ], + [ divs[1], 'transitionstart' ], + [ divs[0], 'transitionstart' ], + [ divs[0], 'transitioncancel' ], + [ divs[1], 'transitioncancel' ], + 'Simultaneous transitionrun/start/cancel on siblings'); + +divs.forEach(div => div.remove()); +divs = []; + + +// 4k. Test sorting animations with cancel + +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.animation = 'anim 10s 5s'; +divs[1].style.animation = 'anim 10s'; + +getComputedStyle(divs[0]).animation; // flush + +advance_clock(0); // divs[1]'s animation start +advance_clock(5 * 1000); // divs[0]'s animation start +divs.forEach(div => { + div.style.display = 'none'; + // The animationcancel event order is not absolute when firing siblings + // animationcancel on same elapsed time. + // Force to flush style for the element so that the transition on the element + // iscancelled and corresponding cancel event is queued respectively. + getComputedStyle(div).display; +}); +advance_clock(10 * 1000); + +checkEventOrder([ divs[1], 'animationstart' ], + [ divs[0], 'animationstart' ], + [ divs[0], 'animationcancel' ], + [ divs[1], 'animationcancel' ], + 'Simultaneous animationcancel on siblings'); + +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 0000000000..dbca9490e4 --- /dev/null +++ b/layout/style/test/test_animations_iterationstart.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> +<head> + <title> + Test for Animation.effect.timing.iterationStart on compositor animations + </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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"> + .target { + /* The animation target needs geometry in order to qualify for OMTA */ + width: 100px; + height: 100px; + background-color: white; + } + </style> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +runOMTATest(function() { + runAllAsyncAnimTests().then(SimpleTest.finish); +}, SimpleTest.finish, SpecialPowers); + +addAsyncAnimTest(async function() { + var [ div ] = new_div("test"); + var animation = div.animate( + { transform: ["translate(0px)", "translate(100px)"] }, + { iterationStart: 0.5, duration: 10000, fill: "both"} + ); + await waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor, "Start of Animation"); + + advance_clock(4000); + await waitForPaints(); + omta_is(div, "transform", { tx: 90 }, RunningOn.Compositor, "40% of Animation"); + + advance_clock(6000); + await waitForPaints(); + omta_is(div, "transform", { tx: 50 }, RunningOn.MainThread, "End of Animation"); + + done_div(); +}); + +</script> +</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 0000000000..06a409b490 --- /dev/null +++ b/layout/style/test/test_animations_omta.html @@ -0,0 +1,2969 @@ +<!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 src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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 kf_scale { + to { scale: 2.25 2.25; } + } + + @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"; + +const { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +/** 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.from(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(async function() { + new_div("animation: transform-anim linear 300s"); + + await waitForPaintsFlushed(); + + advance_clock(200000); + omta_is("transform", { tx: 100 * 2 / 3 }, RunningOn.Compositor, + "OMTA animation is animating as expected"); + done_div(); +}); + +async 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(); + + await 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 + await 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 + await 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. + await 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(async function() { + new_div("animation: anim1 linear 10s"); + await 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"; + await 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(async function() { + new_div("animation: anim1 linear 10s"); + await 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"; + await 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"; + await 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(async function() { + new_div("animation: anim1 linear 10s, anim2 linear 10s"); + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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); + await 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"; + await 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"; + await 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(async function() { + // 100px at 0%, 50px at 50%, 150px at 100% + new_div("transform: translate(100px); " + + "animation: kf1 ease 1s alternate infinite"); + await 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"); + await 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"); + await 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"); + await 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"); + await 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"); + await 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"); + await 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(async function() { + new_div("animation: kf_tf1 ease-in 10s alternate infinite"); + await 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"); + await 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(async function() { + new_div("animation: anim2 ease-in-out 10s"); + await 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"; + await 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"; + await 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"; + await 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(async function() { + new_div("animation: anim2 ease-in 10s 0.3 forwards"); + await 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 + await 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"); + await 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); + await 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); + await 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"); + await 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); + await 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(async function() { + new_div("animation: anim2 ease-in 10s infinite"); + gDiv.style.animationDirection = "normal"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-direction test 1 (normal) at 0s"); + gDiv.style.animationDirection = "reverse"; + await waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.Compositor, + "animation-direction test 1 (reverse) at 0s"); + gDiv.style.animationDirection = "alternate"; + await waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "animation-direction test 1 (alternate) at 0s"); + gDiv.style.animationDirection = "alternate-reverse"; + await waitForPaintsFlushed(); + omta_is("opacity", 1, RunningOn.Compositor, + "animation-direction test 1 (alternate-reverse) at 0s"); + advance_clock(2000); + gDiv.style.animationDirection = "normal"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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"; + await 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(async 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"; + await 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"; + await 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"; + await 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"; + await 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 = ""; + await 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); + await 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"; + await 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 + await 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 + await 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 + await 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 + await 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) + await 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 + await 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); + await 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(async function() { + // test positive delay + new_div("animation: anim2 1s 0.5s ease-out"); + await 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); + await 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"); + await 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"; + await 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"; + await waitForPaintsFlushed(); + advance_clock(200); + omta_is("opacity", 0, RunningOn.Either, "dynamic delay delay test at 600ms"); + advance_clock(200); + await waitForPaints(); + omta_is_approx("opacity", gTF.ease_out(0.2), 0.01, RunningOn.Compositor, + "dynamic delay delay test at 800ms"); + advance_clock(1000); + await waitForPaints(); + omta_is("opacity", 1, RunningOn.Either, + "dynamic delay delay test at 1800ms (1)"); + gDiv.style.animationDelay = "1.5s"; + await 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"; + await 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"); + await 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"; + await 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"; + await waitForPaintsFlushed(); + advance_clock(100); + await 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"; + await 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)"); + await waitForPaintsFlushed(); + advance_clock(300); + gDiv.style.transform = "translate(100px)"; + gDiv.style.animation = "kf1 1s -0.1s ease-in"; + await 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(); + await 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: "" }], + "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(async function() { + new_div(""); + listen(); + gDiv.id = "withbefore"; + await 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"; + await 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(async function() { + new_div("animation: multiprop 1s ease-in-out alternate infinite"); + await 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(async function() { + new_div("animation: anim2 1s linear"); + await 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(async function() { + new_div("animation: always_fifty 1s linear infinite; " + + "transform: translate(200px)"); + await 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;"); + await 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(async function() { + new_div("animation: kf3 1s linear forwards"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "bug 686656 test 1 at 0ms"); + advance_clock(250); + gDisplay.style.color = "blue"; + await 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); + await 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(async function() { + new_div("animation: kf3 1s linear forwards"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 0 }, RunningOn.Compositor, + "bug 686656 test 2 at 0ms"); + advance_clock(250); + gDisplay.style.overflow = "scroll"; + await 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); + await 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(async function() { + new_div("animation: cascade 1s linear forwards; position: relative"); + await 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); + await 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(async function() { + new_div("animation: cascade2 8s linear forwards"); + await 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); + await waitForPaints(); + omta_is("transform", { tx: 100 }, RunningOn.MainThread, + "cascade2 test at 8s"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("animation: primitives1 2s linear forwards"); + await 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); + await waitForPaints(); + omta_is("transform", [ 0, -1, 1, 0, 0, 0 ], RunningOn.MainThread, + "primitives1 at 0s"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("animation: important1 1s linear forwards"); + await 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); + await waitForPaints(); + omta_is("opacity", 0.8, RunningOn.Either, "important1 test at 1s"); + done_div(); +}); + +addAsyncAnimTest(async function() { + new_div("animation: important2 1s linear forwards"); + await 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); + await 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(async 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;"); + await 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;"); + await 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(async 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"); + await 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"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 125 }, RunningOn.Compositor, + "dynamic rule change test, change in @keyframes name applies"); + dyn2.appendRule("50% { transform: translate(0px) }"); + await 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)"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: -50 }, RunningOn.Compositor, + "dynamic rule change test, keyframe style set"); + dyn2.name = "dyn2"; + await 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%"; + await waitForPaintsFlushed(); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "dynamic rule change test, change in keyframe keyText"); + dyn1.deleteRule("25%"); + await waitForPaintsFlushed(); + omta_is("transform", { tx: 25 }, RunningOn.Compositor, + "dynamic rule change test, @keyframes deleteRule"); + done_div(); + dyn_sheet_elt.remove(); + dyn_sheet_elt = null; + dyn_sheet = null; +}); + +/* + * Bug 1004361 - CSS animations with short duration sometimes don't dispatch + * a start event + */ +addAsyncAnimTest(async function() { + new_div("animation: anim2 1s 0.1s"); + listen(); + await 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(async function() { + new_div("transform: translate(0, 200px); animation: anim4 0s 1s both"); + listen(); + await waitForPaintsFlushed(); + advance_clock(0); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform during backwards fill of zero-duration animation"); + advance_clock(2000); // Skip over animation + await 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(async function() { + new_div("transform: translate(0, 200px); animation: anim4 0s 1s both"); + listen(); + await waitForPaintsFlushed(); + advance_clock(0); + // Seek to exactly the point where the animation starts and stops + advance_clock(1000); + await 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(async function() { + new_div("transform: translate(0, 200px); " + + "animation: anim4 0s 1s both infinite"); + listen(); + await waitForPaintsFlushed(); + advance_clock(0); + omta_is("transform", { ty: 0 }, RunningOn.Compositor, + "transform during backwards fill of infinitely repeating " + + "zero-duration animation"); + advance_clock(2000); + await 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(async function() { + new_div("transform: translate(0, 200px); " + + "animation: anim4 0s -1s both reverse 12.7 linear"); + listen(); + await 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(async function() { + new_div("margin-right: 200px; animation: empty 2s 1s both"); + listen(); + advance_clock(0); + await 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(async function() { + new_div("margin-right: 200px; animation: empty 0s 1s both"); + listen(); + await 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(async function() { + new_div("animation: nearlyempty 1s both linear"); + await 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"); + await 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(async function() { + new_div("animation: always_fifty 1s both linear"); + await waitForPaintsFlushed(); + advance_clock(500); + omta_is("transform", { tx: 50 }, RunningOn.Compositor, + "Animation is animating on compositor"); + + // Update animation name + listen(); + gDiv.style.animationName = "empty"; + await 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(async function() { + if (AppConstants.platform === "android") { + todo(false, "no global history on GeckoView; can't run test"); + return; + } + + var div1 = document.createElement("div"); + div1.classList.add("target"); + div1.style.height = "10px"; + div1.style.animation = "anim2 linear 1s"; + + const topLocation = + await SpecialPowers.spawnChrome([], () => browsingContext.top.currentURI.spec); + + var visitedLink = document.createElement("a"); + visitedLink.setAttribute("href", topLocation); + 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", topLocation); + refVisitedLink.classList.add("visitedLink"); + + gDisplay.appendChild(div1); + gDisplay.appendChild(visitedLink); + gDisplay.appendChild(refVisitedLink); + + // Wait for visited link coloring. + await waitForVisitedLinkColoring(refVisitedLink, + "background-color", "rgb(0, 0, 255)"); + + // Wait for animations to start. + await 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(async function() { + new_div("animation: anim4 linear 10s"); + await 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"; + await 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 = ""; + await 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(async function() { + new_div("animation: anim4 linear 10s"); + var ancestor = document.createElement("div"); + gDiv.parentNode.insertBefore(ancestor, gDiv); + ancestor.appendChild(gDiv); + await 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"; + await 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 = ""; + await 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.remove(); + done_div(); +}); + +// Bug 1125455 - Transitions should not run when animations are running. +addAsyncAnimTest(async function() { + new_div("transition: opacity 2s linear; opacity: 0.8"); + await waitForPaintsFlushed(); + omta_is("opacity", 0.8, RunningOn.MainThread, + "initial opacity"); + gDiv.style.opacity = "0.2"; + await 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"; + await 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 1320474 - keyframes-name may be a string, allows names that would +// otherwise be excluded. +// These tests don't need to be duplicated here as they relate purely to +// the animation setup which is common to both main-thread and compositor +// animations. + +// Bug 847287 - Test that changes of when an animation is dynamically +// overridden work correctly. +addAsyncAnimTest(async function() { + // anim2 and anim3 are both animations from opacity 0 to 1 + + new_div("animation: anim2 1s linear forwards; opacity: 0.5 ! important"); + await 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"); + await 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"); + await 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. + await 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)"); + await 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)"; + await 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. + await 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)"); + await 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)"; + await 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. + await 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. + await 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"); + await 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(async function() { + new_div("transform: translate(100px); transition: transform 10s 5s linear"); + await waitForPaintsFlushed(); + gDiv.style.transform = "translate(200px)"; + await 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); + await 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. + await waitForPaints(); + omta_is("transform", { tx: 200 }, RunningOn.MainThread, + "transition runs on main thread at end of active interval"); + + done_div(); +}); + +// Normal background-color animation. +addAsyncAnimTest(async function() { + new_div("background-color: rgb(255, 0, 0); " + + "transition: background-color 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.backgroundColor = "rgb(0, 255, 0)"; + await waitForPaintsFlushed(); + + omta_is("background-color", "rgb(255, 0, 0)", RunningOn.Compositor, + "background-color transition runs on compositor thread"); + + advance_clock(5000); + omta_is("background-color", "rgb(128, 128, 0)", RunningOn.Compositor, + "background-color on compositor at 5s"); + + done_div(); +}); + +// background-color animation with currentColor. +addAsyncAnimTest(async function() { + new_div("color: rgb(255, 0, 0); " + + "background-color: currentColor; " + + "transition: background-color 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.backgroundColor = "rgb(0, 255, 0)"; + await waitForPaintsFlushed(); + + omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor, + "background-color transition starting with current-color runs on " + + "compositor thread"); + + advance_clock(5000); + omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor, + "background-color on compositor at 5s"); + + done_div(); +}); + +// Tests that a background-color animation from inherited currentColor to +// a normal color on the compositor is updated when the parent color is +// changed. +addAsyncAnimTest(async function() { + new_div(""); + const parent = document.createElement("div"); + gDiv.parentNode.insertBefore(parent, gDiv); + parent.style.color = "rgb(255, 0, 0)"; + parent.appendChild(gDiv); + + gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000); + + await waitForPaintsFlushed(); + + omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor, + "background-color animation starting with current-color runs on " + + "compositor thread"); + + advance_clock(500); + + omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor, + "background-color on compositor at 5s"); + + // Change the parent's color in the middle of the animation. + parent.style.color = "rgb(0, 0, 255)"; + await waitForPaintsFlushed(); + + omta_todo_is("background-color", "rgb(0, 128, 128)", RunningOn.TodoCompositor, + "background-color on compositor is reflected by the parent's " + + "color change"); + + done_div(); + parent.remove(); +}); + +// Tests that a background-color animation from currentColor to a normal color +// on <a> element is updated when the link is visited. +addAsyncAnimTest(async function() { + if (AppConstants.platform === "android") { + todo(false, "no global history on GeckoView; can't run test"); + return; + } + + [ gDiv ] = new_element("a", "display: block"); + gDiv.setAttribute("href", "not-exist.html"); + gDiv.classList.add("visited"); + + const extraStyle = document.createElement('style'); + document.head.appendChild(extraStyle); + extraStyle.sheet.insertRule(".visited:visited { color: rgb(0, 0, 255); }", 0); + extraStyle.sheet.insertRule(".visited:link { color: rgb(255, 0, 0); }", 1); + + gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000); + await waitForPaintsFlushed(); + + omta_todo_is("background-color", "rgb(255, 0, 0)", RunningOn.TodoCompositor, + "background-color animation starting with current-color runs on " + + "compositor thread"); + + advance_clock(500); + + omta_todo_is("background-color", "rgb(128, 128, 0)", RunningOn.TodoCompositor, + "background-color on compositor at 5s"); + + const topLocation = + await SpecialPowers.spawnChrome([], () => browsingContext.top.currentURI.spec); + gDiv.setAttribute("href", topLocation); + await waitForVisitedLinkColoring(gDiv, "color", "rgb(0, 0, 255)"); + await waitForPaintsFlushed(); + + // `omta_is` checks that the result on the compositor equals to the value by + // getComputedValue() but getComputedValue lies for visited link values so + // we use getOMTAStyle directly instead. + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "background-color"), + "rgb(0, 128, 128)", + "background-color on <a> element after the link is visited"); + + extraStyle.remove(); + done_element(); + gDiv = null; +}); + +// Normal translate animation. +addAsyncAnimTest(async function() { + new_div("translate: 100px; " + + "transition: translate 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.translate = "200px"; + await waitForPaintsFlushed(); + + omta_is("translate", { compositorValue: { tx: 100 }, computed: "100px" }, + RunningOn.Compositor, + "translate transition runs on compositor thread"); + + advance_clock(5000); + omta_is("translate", { compositorValue: { tx: 150 }, computed: "150px" }, + RunningOn.Compositor, "translate on compositor at 5s"); + + done_div(); +}); + +// Normal rotate animation. +addAsyncAnimTest(async function() { + new_div("rotate: 0deg; " + + "transition: rotate 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.rotate = "90deg"; + await waitForPaintsFlushed(); + + omta_is("rotate", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "0deg"}, + RunningOn.Compositor, "rotate transition runs on compositor thread"); + + advance_clock(5000); + omta_is("rotate", + { compositorValue: [ Math.cos(Math.PI / 4), Math.sin(Math.PI / 4), + -Math.sin(Math.PI / 4), Math.cos(Math.PI / 4), + 0, 0 ], + computed: "45deg" }, RunningOn.Compositor, + "rotate on compositor at 5s"); + + done_div(); +}); + +// Normal scale animation. +addAsyncAnimTest(async function() { + new_div("scale: 1 1; " + + "transition: scale 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.scale = "2 2"; + await waitForPaintsFlushed(); + + omta_is("scale", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "1" }, + RunningOn.Compositor, "scale transition runs on compositor thread"); + + advance_clock(5000); + omta_is("scale", + { compositorValue: [ 1.5, 0, 0, 1.5, 0, 0 ], computed: "1.5" }, + RunningOn.Compositor, "scale on compositor at 5s"); + + done_div(); +}); + +// Normal multiple transform-like properties animation. +addAsyncAnimTest(async function() { + new_div("translate: 100px; " + + "scale: 1 1; " + + "transform: translate(200px); " + + "transition: all 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.translate = "200px"; + gDiv.style.scale = "2 2"; + gDiv.style.transform = "translate(100px)"; + await waitForPaintsFlushed(); + + omta_is("transform", { compositorValue: { tx: 300 }, + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("transform", + // The order is: translate, scale, transform. + // So the translate() in transform should be multiplied by 1.5. + { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 150*1.5), 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties on compositor at 5s"); + + done_div(); +}); + +// Multiple transform-like properties animation. The non-animating properties +// shouldn't be overridden by animating ones. +addAsyncAnimTest(async function() { + new_div("translate: 100px; " + + "scale: 1 1; " + + "transform: translate(200px); " + + "transition: all 10s linear"); + await waitForPaintsFlushed(); + + // No transition on transform property. + gDiv.style.translate = "200px"; + gDiv.style.scale = "2 2"; + await waitForPaintsFlushed(); + + omta_is("transform", { compositorValue: { tx: 300 }, + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("transform", + // The order is: translate, scale, transform. + // So the translate() in transform should be multiplied by 1.5. + { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 200*1.5), 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties on compositor at 5s"); + + done_div(); +}); + +// Multiple transform-like properties animation with delay. The delayed +// animating properties shouldn't be overridden. +// +// Note: +// In delay phase, the SampleResult should be None, even though there is +// an non-animating property which is also sent to the compositor. +// If the SampleResult is Sampled in this case, we may get an incorrect result +// ("scale" would be "1" because the final matrix is calculated by a default +// scale value which overrides the scale css style). +// That's why we shouldn't take non-animating properties into account on +// SampleResult. +addAsyncAnimTest(async function() { + new_div("translate: 100px; " + + "scale: 1.25 1.25; " + + "animation: kf_scale 10s 2.5s linear"); + await waitForPaintsFlushed(); + + // All transform-like properties use DisplayItemType::TYPE_TRANSFORM, + // so using "transform" is enough. + var compositorStr = + SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); + ok(compositorStr === "", + "transform-like properties should not run on the compositor at 0s " + + "(in delay phase)"); + var computedStr = window.getComputedStyle(gDiv).translate; + ok(computedStr === "100px", + "The computed value of translate property should be equal to 100px " + + "in delay phase, got " + computedStr); + computedStr = window.getComputedStyle(gDiv).scale; + ok(computedStr === "1.25", + "The computed value of scale property should be equal to 1.25 " + + "in delay phase, got " + computedStr); + + advance_clock(2500); + + omta_is("transform", { compositorValue: [ 1.25, 0, 0, 1.25, 100, 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties on compositor at 2.5s"); + + advance_clock(5000); + + omta_is("transform", + { compositorValue: [ 1.75, 0, 0, 1.75, 100, 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties on compositor at 7.5s"); + + done_div(); +}); + +// Normal offset-path animation with path(). +addAsyncAnimTest(async function() { + new_div("offset-path: path('M50 50L100 100'); " + + "offset-distance: 50%; " + + "offset-rotate: 0deg; " + + "transition: offset-path 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetPath = "path('M50 50L200 200')"; + await waitForPaintsFlushed(); + + omta_is("offset-path", + { compositorValue: { tx: 25, ty: 25 }, + computed: 'path("M 50 50 L 100 100")' }, + RunningOn.Compositor, + "offset-path transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-path", + { compositorValue: { tx: 50, ty: 50 }, + computed: 'path("M 50 50 L 150 150")' }, + RunningOn.Compositor, + "offset-path on compositor at 5s"); + + done_div(); +}); + +// Normal offset-path animation with ray(). +addAsyncAnimTest(async function() { + new_div("offset-path: ray(90deg); " + + "offset-distance: 0%; " + + "offset-position: auto; " + + "transition: offset-path 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetPath = "ray(180deg)"; + await waitForPaintsFlushed(); + + // At 0%, it's ray(90deg), so there is no rotation and only movement because + // the default offset-anchor is 50%. + omta_is("offset-path", + { compositorValue: { tx: -50, ty: -50 }, + computed: 'ray(90deg)' }, + RunningOn.Compositor, + "offset-path transition runs on compositor thread"); + + advance_clock(5000); + + // At 50%, ray() is 135deg. so the matrix is kind of rotate -45 deg: + // [cos(-1/4 * pi), -sin(-1/4 * pi), sin(-1/4 * pi), cos(-1/4 * pi), -50, -50] + // Note: the movement of (-50 -50) is from the default offset-anchor. + omta_is("offset-path", + { + compositorValue: [ + Math.cos(-Math.PI * 1/4), -Math.sin(-Math.PI * 1/4), + Math.sin(-Math.PI * 1/4), Math.cos(-Math.PI * 1/4), + -50, -50 + ], + computed: 'ray(135deg)' + }, + RunningOn.Compositor, + "offset-path on compositor at 5s"); + + done_div(); +}); + +// Normal offset-path animation with polygon(). +addAsyncAnimTest(async function() { + new_div("offset-path: polygon(0px 0px, 100px 0px, 50px 100px); " + + "offset-distance: 0%; " + + "offset-rotate: 0deg; " + + "offset-anchor: left top; " + + "transition: offset-path 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetPath = "polygon(50px 0px, 100px 0px, 50px 100px)"; + await waitForPaintsFlushed(); + + omta_is("offset-path", + { compositorValue: { tx: 0 }, + computed: 'polygon(0px 0px, 100px 0px, 50px 100px)' }, + RunningOn.Compositor, + "offset-path transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-path", + { compositorValue: { tx: 25 }, + computed: 'polygon(25px 0px, 100px 0px, 50px 100px)' }, + RunningOn.Compositor, + "offset-path on compositor at 5s"); + + done_div(); +}); + +// Normal offset-distance animation with path(). +addAsyncAnimTest(async function() { + new_div("offset-path: path('M50 50v100'); " + + "offset-distance: 0%; " + + "offset-rotate: 0deg; " + + "transition: offset-distance 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetDistance = "100%"; + await waitForPaintsFlushed(); + + omta_is("offset-distance", + { compositorValue: { ty: 0 }, computed: '0%' }, + RunningOn.Compositor, + "offset-distance transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-distance", + { compositorValue: { ty: 50 }, computed: '50%' }, + RunningOn.Compositor, + "offset-distance on compositor at 5s"); + + done_div(); +}); + +// Normal offset-distance animation with polygon(). +addAsyncAnimTest(async function() { + new_div("offset-path: polygon(0px 0px, 100px 0px, 100px 50px, 0px 50px); " + + "offset-distance: 0%; " + + "offset-rotate: 0deg; " + + "offset-anchor: left top; " + + "transition: offset-distance 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetDistance = "100%"; + await waitForPaintsFlushed(); + + omta_is("offset-distance", + { compositorValue: { tx: 0 }, computed: '0%' }, + RunningOn.Compositor, + "offset-distance transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-distance", + { compositorValue: { tx: 100, ty: 50 }, computed: '50%' }, + RunningOn.Compositor, + "offset-distance on compositor at 5s"); + + done_div(); +}); + +// Normal offset-rotate animation. +addAsyncAnimTest(async function() { + new_div("offset-path: path('M50 50v100'); " + + "offset-rotate: auto; " + + "transition: offset-rotate 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetRotate = "auto 90deg"; + await waitForPaintsFlushed(); + + // The direction vector is 90deg (because the path go from top to bottom), and + // offset-rotate is auto 0deg, so the entire rotation is 90deg. + // The matrix is [cos(pi/2), sin(pi/2), -sin(pi/2), cos(pi/2), 0, 0]. + omta_is("offset-rotate", + { compositorValue: [ 0, 1, -1, 0, 0, 0 ], computed: 'auto' }, + RunningOn.Compositor, + "offset-rotate transition runs on compositor thread"); + + advance_clock(5000); + + // At 50%, offset-rotate is auto 45deg, so the entire rotation is 135deg + // (= 90deg + 45deg = pi * 3/4), so the matrix is + // [cos(pi * 3/4), sin(pi * 3/4), -sin(pi * 3/4), cos(pi * 3/4), 0, 0] + omta_is("offset-rotate", + { + compositorValue: [ + Math.cos(Math.PI * 3/4), Math.sin(Math.PI * 3/4), + -Math.sin(Math.PI * 3/4), Math.cos(Math.PI * 3/4), + 0, 0 + ], + computed: 'auto 45deg', + }, + RunningOn.Compositor, + "offset-rotate on compositor at 5s"); + + done_div(); +}); + +// Normal offset-anchor animation. +addAsyncAnimTest(async function() { + new_div("offset-path: path('M50 50v100'); " + + "offset-rotate: 0deg; " + + "offset-anchor: 0% 0%; " + + "transition: offset-anchor 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetAnchor = "100% 100%"; + await waitForPaintsFlushed(); + + omta_is("offset-anchor", + { compositorValue: { tx: 50, ty: 50 }, computed: '0% 0%' }, + RunningOn.Compositor, + "offset-anchor transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-anchor", + { compositorValue: { tx: 0, ty: 0 }, computed: '50% 50%' }, + RunningOn.Compositor, + "offset-anchor on compositor at 5s"); + + done_div(); +}); + +// Normal offset-position animation. +addAsyncAnimTest(async function() { + new_div("offset-path: ray(0deg sides); " + + "offset-rotate: 0deg; " + + "offset-anchor: 0% 0%; " + + "offset-position: 0px 0px; " + + "transition: offset-position 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.offsetPosition = "100px 100px"; + await waitForPaintsFlushed(); + + omta_is("offset-position", + { compositorValue: { tx: 0, ty: 0 }, computed: '0px 0px' }, + RunningOn.Compositor, + "offset-position transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("offset-position", + { compositorValue: { tx: 50, ty: 50 }, computed: '50px 50px' }, + RunningOn.Compositor, + "offset-position on compositor at 5s"); + + done_div(); +}); + +// Normal multiple transform-like properties animation (including motion-path). +addAsyncAnimTest(async function() { + new_div("translate: 0px; " + + "offset-path: path('M50 50v100');" + + "offset-distance: 0%;" + + "transform: translateX(0px); " + + "transition: all 10s linear"); + await waitForPaintsFlushed(); + + gDiv.style.translate = "0px 100px"; + gDiv.style.transform = "translateX(-100px)"; + gDiv.style.offsetDistance = "100%"; + await waitForPaintsFlushed(); + + omta_is("transform", { compositorValue: [ 0, 1, -1, 0, 0, 0 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties transition runs on compositor thread"); + + advance_clock(5000); + + omta_is("transform", { compositorValue: [ 0, 1, -1, 0, 0, 50 ], + usesMultipleProperties: true }, + RunningOn.Compositor, + "transform-like properties transition runs on compositor thread"); + + done_div(); +}); + +</script> +</html> diff --git a/layout/style/test/test_animations_omta_scroll.html b/layout/style/test/test_animations_omta_scroll.html new file mode 100644 index 0000000000..324264c3e7 --- /dev/null +++ b/layout/style/test/test_animations_omta_scroll.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> +<head> + <title>Test for css-animations running on the compositor thread with + scroll-timeline</title> + <script 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=1676780">Scroll-driven + animations generated by CSS</a> +<pre id="test"></pre> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); + +// Open a new window to make sure we test this with scrolling attribute. +window.open('file_animations_omta_scroll.html'); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_animations_omta_scroll_rtl.html b/layout/style/test/test_animations_omta_scroll_rtl.html new file mode 100644 index 0000000000..73339211c5 --- /dev/null +++ b/layout/style/test/test_animations_omta_scroll_rtl.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> +<head> + <title>Test for css-animations running on the compositor thread with + scroll-timeline with right to left writing mode</title> + <script 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=1676780">Scroll-driven + animations generated by CSS</a> +<pre id="test"></pre> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); + +// Open a new window to make sure we test this with scrolling attribute. +window.open('file_animations_omta_scroll_rtl.html'); + +</script> +</pre> +</body> +</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 0000000000..92423bf67d --- /dev/null +++ b/layout/style/test/test_animations_omta_start.html @@ -0,0 +1,187 @@ +<!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 src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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() { + 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 0000000000..8aba46b5e8 --- /dev/null +++ b/layout/style/test/test_animations_pausing.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html> +<head> + <title> + Test for Animation.play() and Animation.pause() on compositor animations + </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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 { + 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> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +runOMTATest(function() { + runAllAsyncAnimTests().then(SimpleTest.finish); +}, SimpleTest.finish, SpecialPowers); + +addAsyncAnimTest(async function() { + var [ div, cs ] = new_div("animation: anim 10s 2 linear alternate"); + + // Animation is initially running on compositor + await 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); + await 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); + await 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/test_animations_playbackrate.html b/layout/style/test/test_animations_playbackrate.html new file mode 100644 index 0000000000..8ecfdb6f28 --- /dev/null +++ b/layout/style/test/test_animations_playbackrate.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test for Animation.playbackRate on compositor animations</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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 { + 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> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +runOMTATest(function() { + runAllAsyncAnimTests().then(SimpleTest.finish); +}, SimpleTest.finish, SpecialPowers); + +addAsyncAnimTest(async function() { + var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards"); + var animation = div.getAnimations()[0]; + animation.playbackRate = 10; + + advance_clock(300); + + await waitForPaints(); + omta_is(div, "transform", { tx: 30 }, RunningOn.Compositor, + "at 300ms"); + done_div(); +}); + +addAsyncAnimTest(async function() { + var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards"); + var animation = div.getAnimations()[0]; + advance_clock(300); + await waitForPaints(); + + animation.playbackRate = 0; + + await 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(async 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) + + await 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(async 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) + + await 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/test_animations_reverse.html b/layout/style/test/test_animations_reverse.html new file mode 100644 index 0000000000..93ff8c37a9 --- /dev/null +++ b/layout/style/test/test_animations_reverse.html @@ -0,0 +1,64 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>Test for Animation.reverse() on compositor animations</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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 { + 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> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +runOMTATest(function() { + runAllAsyncAnimTests().then(SimpleTest.finish); +}, SimpleTest.finish, SpecialPowers); + +addAsyncAnimTest(async function() { + var [ div, cs ] = new_div("animation: anim 10s linear"); + const animation = div.getAnimations()[0]; + + // Animation is initially running on compositor + await waitForPaintsFlushed(); + advance_clock(5000); + omta_is(div, 'transform', { tx: 50 }, RunningOn.Compositor, + 'Animation is initally animating on compositor'); + + // Reverse animation + animation.reverse(); + + // At this point the playbackRate has changed but the transform will + // not have changed. + await waitForPaints(); + omta_is(div, 'transform', { tx: 50 }, RunningOn.Compositor, + 'Animation value does not change after being reversed'); + + // However, we should still have sent a layer transaction to update the + // playbackRate on the compositor so that on the next tick we advance + // in the right direction. + advance_clock(1000); + omta_is(div, 'transform', { tx: 40 }, RunningOn.Compositor, + 'Animation proceeds in reverse direction'); + + done_div(); +}); +</script> +</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 0000000000..0e96711530 --- /dev/null +++ b/layout/style/test/test_animations_styles_on_event.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<head> + <title> + Test that mouse movement immediately after finish() should involve + restyling for finished state (Bug 1228137) + </title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script 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 { + 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> +</head> +<body> +<div id="display"></div> +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +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(); + SimpleTest.finish(); + }); + + 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/test_animations_variable_changes.html b/layout/style/test/test_animations_variable_changes.html new file mode 100644 index 0000000000..ac254e1136 --- /dev/null +++ b/layout/style/test/test_animations_variable_changes.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Tests that animations respond to changes to variables</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../testcommon.js"></script> +<style> +:root { + --width: 100px; +} +.wider { + --width: 200px; +} +@keyframes widen { + to { margin-left: var(--width) } +} +</style> +<body> +<div id="log"></div> +<script> + +test(() => { + const div = document.createElement('div'); + document.body.append(div); + + div.style.animation = 'widen step-start 100s'; + assert_equals(getComputedStyle(div).marginLeft, '100px', + 'Animation value before updating CSS variable'); + + div.classList.add('wider'); + + assert_equals(getComputedStyle(div).marginLeft, '200px', + 'Animation value after updating CSS variable'); + + div.remove(); +}, 'Animation reflects changes to custom properties'); + +test(() => { + const parent = document.createElement('div'); + const child = document.createElement('div'); + parent.append(child); + document.body.append(parent); + + child.style.animation = 'widen step-start 100s'; + assert_equals(getComputedStyle(child).marginLeft, '100px', + 'Animation value before updating CSS variable'); + + parent.classList.add('wider'); + + assert_equals(getComputedStyle(child).marginLeft, '200px', + 'Animation value after updating CSS variable'); + + parent.remove(); + child.remove(); +}, 'Animation reflect changes to custom properties on parent'); + +</script> +</body> 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 0000000000..9eb395003f --- /dev/null +++ b/layout/style/test/test_animations_with_disabled_properties.html @@ -0,0 +1,33 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1265611 +--> +<head> + <title>Test CSS animations ignore disabled properties (Bug 1265611)</title> + <script 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 overflow-clip-box property + * is disabled by the layout.css.overflow-clip-box.enabled pref. If we ever + * remove that pref we will need to substitute some other pref:property + * combination. + */ +SpecialPowers.pushPrefEnv( + { 'set': [[ 'layout.css.overflow-clip-box.enabled', 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 0000000000..9c9a9e2a3f --- /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 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-line: 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.textDecorationLine, "underline", "should match first rule"); + is(cs.zIndex, "auto", "should not match second rule"); + p.removeAttribute("id"); + is(cs.textDecorationLine, "none", "should not match first rule"); + is(cs.zIndex, "auto", "should not match second rule"); + p.setAttribute("foo", "v"); + is(cs.textDecorationLine, "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_area_url_cursor.html b/layout/style/test/test_area_url_cursor.html new file mode 100644 index 0000000000..cbc5efed74 --- /dev/null +++ b/layout/style/test/test_area_url_cursor.html @@ -0,0 +1,34 @@ +<!doctype html> +<title>cursor: url() doesn't assert for area elements</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<style> +area { + /* Doesn't matter to trigger the assert */ + cursor: url(invalid.cur), auto; +} +</style> +<img width="300" height="98" usemap="#map"> +<map name="map" id="map"> + <area class="url" shape="rect" coords="0,0,300,98" href="https://mozilla.org"></area> +</map> +<div></div> +<script> +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(() => { + + let checked = false; + document.querySelector("area").addEventListener("mousemove", function() { + setTimeout(() => { + if (checked) { + return; + } + checked = true; + ok(true, "Didn't assert"); + SimpleTest.finish() + }, 0); + }); + + synthesizeMouseAtCenter(document.querySelector("img"), { type: "mousemove" }); +}); +</script> diff --git a/layout/style/test/test_asyncopen.html b/layout/style/test/test_asyncopen.html new file mode 100644 index 0000000000..9a52ea37eb --- /dev/null +++ b/layout/style/test/test_asyncopen.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1195173 +--> +<head> + <title>Bug 1195173 - Test asyncOpen security exception</title> + <script 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 asyncOpen 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 0000000000..2c6f2e2d5c --- /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 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 0000000000..76635f9ed0 --- /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_backdrop_filter_enabled_state.html b/layout/style/test/test_backdrop_filter_enabled_state.html new file mode 100644 index 0000000000..f8f2995b28 --- /dev/null +++ b/layout/style/test/test_backdrop_filter_enabled_state.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<head> + <meta charset=utf-8> + <title>Test Backdrop-Filter Enabled State</title> + <link rel="author" title="Erik Nordin" href="mailto:nordzilla@mozilla.com"> + <script src="/tests/SimpleTest/SimpleTest.js"></script> +</head> +<script> +SimpleTest.waitForExplicitFinish(); + +(async function() { + for (let enabled of [true, false]) { + await SpecialPowers.pushPrefEnv({"set": [["layout.css.backdrop-filter.enabled", enabled]]}); + is(enabled, CSS.supports("backdrop-filter: initial"), + "backdrop-filter is available only if backdrop-filter pref is set"); + } + SimpleTest.finish(); +}()); + +</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 0000000000..23551ebda9 --- /dev/null +++ b/layout/style/test/test_background_blend_mode.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for miscellaneous computed style issues</title> + <script 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.remove(); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +test_bug_841601(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_border_device_pixel_rounding_initial_style.html b/layout/style/test/test_border_device_pixel_rounding_initial_style.html new file mode 100644 index 0000000000..a8b5b0546d --- /dev/null +++ b/layout/style/test/test_border_device_pixel_rounding_initial_style.html @@ -0,0 +1,20 @@ +<!doctype html> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<iframe id="frame"></iframe> +<script> +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ "set": [["layout.css.devPixelsPerPx", "1.25"]] }, + function() { + is(window.devicePixelRatio, 1.25, "devPixelsPerPx should work"); + const frame = document.getElementById("frame"); + frame.addEventListener("load", function() { + let doc = frame.contentDocument; + let win = frame.contentWindow; + is(win.devicePixelRatio, 1.25, "devPixelsPerPx should work inside the frame"); + is(win.getComputedStyle(doc.querySelector("div")).borderTopWidth, "0.8px", "Shouldn't incorrectly round with 60 app units after getting the initial style"); + SimpleTest.finish(); + }); + frame.srcdoc = "<div style='border: 1px solid; display: none;'></div>"; + }); +</script> 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 0000000000..c4074c7d8f --- /dev/null +++ b/layout/style/test/test_box_size_keywords.html @@ -0,0 +1,170 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for keywords on box sizing properties</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" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1122253">Mozilla Bug 1122253</a> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1496558">Mozilla Bug 1496558</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 and Bug 1496558 **/ + +// Test that the -moz-available, min-content, max-content, and +// 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: "min-content", computed_value: "90px", }, + { orientation: "horizontal", property: "width", specified_value: "max-content", computed_value: "280px", }, + { orientation: "horizontal", property: "width", specified_value: "fit-content", computed_value: "200px", }, + { orientation: "horizontal", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", }, + { orientation: "horizontal", property: "inline-size", specified_value: "min-content", computed_value: "90px", }, + { orientation: "horizontal", property: "inline-size", specified_value: "max-content", computed_value: "280px", }, + { orientation: "horizontal", property: "inline-size", specified_value: "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: "min-content", computed_value: "min-content", }, + { orientation: "horizontal", property: "min-width", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "horizontal", property: "min-width", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "horizontal", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", property: "min-inline-size", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "horizontal", property: "min-inline-size", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "horizontal", property: "min-inline-size", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "horizontal", property: "max-width", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", property: "max-width", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "horizontal", property: "max-width", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "horizontal", property: "max-width", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "horizontal", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", property: "max-inline-size", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "horizontal", property: "max-inline-size", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "horizontal", property: "max-inline-size", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "vertical", property: "height", specified_value: "-moz-available", computed_value: "200px", }, + { orientation: "vertical", property: "height", specified_value: "min-content", computed_value: "90px", }, + { orientation: "vertical", property: "height", specified_value: "max-content", computed_value: "280px", }, + { orientation: "vertical", property: "height", specified_value: "fit-content", computed_value: "200px", }, + { orientation: "vertical", property: "inline-size", specified_value: "-moz-available", computed_value: "200px", }, + { orientation: "vertical", property: "inline-size", specified_value: "min-content", computed_value: "90px", }, + { orientation: "vertical", property: "inline-size", specified_value: "max-content", computed_value: "280px", }, + { orientation: "vertical", property: "inline-size", specified_value: "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: "min-content", computed_value: "min-content", }, + { orientation: "vertical", property: "min-height", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "vertical", property: "min-height", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "vertical", property: "min-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", property: "min-inline-size", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "vertical", property: "min-inline-size", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "vertical", property: "min-inline-size", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "vertical", property: "max-height", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", property: "max-height", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "vertical", property: "max-height", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "vertical", property: "max-height", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "vertical", property: "max-inline-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", property: "max-inline-size", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "vertical", property: "max-inline-size", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "vertical", property: "max-inline-size", specified_value: "fit-content", computed_value: "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: "min-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "max-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "width: 30px; ", property: "width", specified_value: "fit-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "min-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "max-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "fit-content", computed_value: "20px", }, + { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "vertical", prerequisites: "min-width: 30px; ", property: "min-width", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "vertical", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "vertical", prerequisites: "max-width: 30px; ", property: "max-width", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "vertical", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "-moz-available", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "min-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "max-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "height: 30px; ", property: "height", specified_value: "fit-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "-moz-available", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "min-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "max-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "block-size: 30px; ", property: "block-size", specified_value: "fit-content", computed_value: "20px", }, + { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "horizontal", prerequisites: "min-height: 30px; ", property: "min-height", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "horizontal", prerequisites: "min-block-size: 30px; ", property: "min-block-size", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "horizontal", prerequisites: "max-height: 30px; ", property: "max-height", specified_value: "fit-content", computed_value: "fit-content", }, + { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "-moz-available", computed_value: "-moz-available", }, + { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "min-content", computed_value: "min-content", }, + { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "max-content", computed_value: "max-content", }, + { orientation: "horizontal", prerequisites: "max-block-size: 30px; ", property: "max-block-size", specified_value: "fit-content", computed_value: "fit-content", }, +]; + +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 0000000000..de96d1d441 --- /dev/null +++ b/layout/style/test/test_bug1055933.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1055933
+-->
+<head>
+ <title>Test for Bug 1055933</title>
+ <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" />
+</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 0000000000..d4b09beffd --- /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 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 0000000000..cd4330e50f --- /dev/null +++ b/layout/style/test/test_bug1112014.html @@ -0,0 +1,89 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1112014 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1112014</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="property_database.js"></script> + <script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(2); + +async function test() { + const InspectorUtils = SpecialPowers.InspectorUtils; + + // This holds a canonical test value for each TYPE_ constant. + let testValues = { + "color": "rgb(3,3,3)", + "gradient": "linear-gradient( 45deg, blue, red )", + "timing-function": "cubic-bezier(0.1, 0.7, 1.0, 0.1)", + }; + + // The canonical test values don't work for all properties, in + // particular some shorthand properties. For these cases we have + // override values. + let overrideValues = { + "box-shadow": { + "color": testValues.color + " 2px 2px" + }, + "-webkit-box-shadow": { + "color": testValues.color + " 2px 2px" + }, + "scrollbar-color": { + "color": testValues.color + " " + testValues.color, + }, + "text-shadow": { + "color": testValues.color + " 2px 2px" + }, + }; + + + // 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 = await SpecialPowers.spawn(window, [], () => { + return Object.getOwnPropertyNames(InspectorUtils).filter(tc => /TYPE_/.test(tc)); + }).then(v => v.filter(tc => !(tc in testValues))); + 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 = + InspectorUtils.cssPropertySupportsType(propertyName, iter); + let parsed = CSS.supports(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(!InspectorUtils.cssPropertySupportsType("--variable", "color"), + "cssPropertySupportsType returns false for variable"); + + SimpleTest.finish(); +} + </script> +</head> +<body onload="test()"> +<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 0000000000..42da6520ac --- /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, ""); + 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, ""); + + // 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, ""); + f.remove(); + is(cs.color, ""); + + // 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, ""); + 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, ""); + 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, ""); + e.appendChild(f); + is(cs.color, ""); + + // 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, ""); + e.className = "g"; + is(cs.color, ""); + + // 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, ""); + e.className = "j"; + is(cs.color, ""); + + 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 0000000000..65bea2014d --- /dev/null +++ b/layout/style/test/test_bug1232829.html @@ -0,0 +1,37 @@ +<!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"); + 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 0000000000..6937b648c5 --- /dev/null +++ b/layout/style/test/test_bug1292447.html @@ -0,0 +1,350 @@ +<!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 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> +<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> +<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("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-", 400, 0, true); +doATest("width", "minmaxwidth6-", 400, 0, true); +doATest("width", "minmaxwidth7-", 400, 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-", 400, 0, true); +doATest("height", "minmaxheight6-", 400, 0, true); +doATest("height", "minmaxheight7-", 400, 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, decl, coordVal, prettyName) { + is(decl[camelProp], coordVal + "px", prettyName); +} + +function percentValueTest(camelProp, decl, percentVal, prettyName) { + is(decl[camelProp], percentVal + "%", prettyName); +} + +function doATest(propName, idBase, coordVal, percentVal, resolveToUsedVal = false) { + var cssCamelPropName = ""; + var parts = propName.split("-"); + ok(parts.length > 0, "CSS prop name should not be empty"); + var i; + if (parts[0]) { + i = 0; + } else { + is(parts[1], "moz", "Testing an extension property that's not -moz"); + ok(parts.length > 2, "-moz prop name should not have more than 2 parts"); + 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, + style(idBase + "1"), coordVal, + propName + " of " + idBase + "1"); + + if (!$(idBase + "2")) { + // Nothing else to do here + return + } + + /* Test $(id)-2 */ + if (resolveToUsedVal) { + coordValueTest(cssCamelPropName, + style(idBase + "2"), coordVal, + propName + " of " + idBase + "2"); + } else { + percentValueTest(cssCamelPropName, + style(idBase + "2"), percentVal, + propName + " of " + idBase + "2"); + } + + if (percentVal) { + /* Test $(id)-3 */ + coordValueTest(cssCamelPropName, + style(idBase + "3"), coordVal, + propName + " of " + idBase + "3"); + + /* Test $(id)-4 */ + percentValueTest(cssCamelPropName, + style(idBase + "4"), percentVal, + propName + " of " + idBase + "4"); + } +} +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1330375.html b/layout/style/test/test_bug1330375.html new file mode 100644 index 0000000000..c9bc0f6715 --- /dev/null +++ b/layout/style/test/test_bug1330375.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<!-- https://bugzil.la/1330375 --> +<meta charset="utf-8"> +<title>Test for Bug 1330375</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest.css"/> +<body> + <div id="content"> + <table> + <tbody> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + <tr><td>lorem ipsum</td><td>dolor sit</td><td>amet</td><td>consectetur adipsicing</td><td>elit.</td></tr> + </tbody> + </table> + </div> +</body> +<script> +"use strict"; + +const gUtils = SpecialPowers.getDOMWindowUtils(window); + +function flush_layout(element) { + (element || document.documentElement).offsetHeight; +} + +SimpleTest.waitForExplicitFinish(); + +window.onload = function() { + flush_layout(document.getElementById("content")); + + let before = { + framesConstructed: gUtils.framesConstructed, + framesReflowed: gUtils.framesReflowed, + }; + + // Begin test + let rows = document.getElementsByTagName("tr"); + for (var r = 0; r < rows.length; r++) { + let row = rows[r]; + row.innerText; + // Cause potential invalidation of layout: + row.style.display = "none"; + } + + is(gUtils.framesConstructed, before.framesConstructed, "Frames constructed should be 0"); + is(gUtils.framesReflowed, before.framesReflowed, "Frames reflowed should be 0"); + + SimpleTest.finish(); +} +</script> + diff --git a/layout/style/test/test_bug1371488.html b/layout/style/test/test_bug1371488.html new file mode 100644 index 0000000000..7e32fa0031 --- /dev/null +++ b/layout/style/test/test_bug1371488.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test for bug 1371488</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <style id="test"> + @import url(non-exist-file.css); + </style> +</head> +<body> +<pre id="log"> +<script> + let sheet = document.getElementById("test").sheet; + let rule = sheet.cssRules[0]; + ok(rule, "The import rule should not be null even if the file fails to load"); + is(rule.type, CSSRule.IMPORT_RULE, "It is the import rule"); + ok(rule.styleSheet, "The associated stylesheet should exists as well"); + is(rule.styleSheet.cssRules.length, 0, "The stylesheet should be empty"); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1375944.html b/layout/style/test/test_bug1375944.html new file mode 100644 index 0000000000..c265691170 --- /dev/null +++ b/layout/style/test/test_bug1375944.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test for bug 1375944</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<iframe id="subframe"></iframe> +<pre id="log"> +<script> + +SimpleTest.waitForExplicitFinish(); + +async function runTest() { + let f = new FontFace("Ahem", "url(Ahem.ttf)", {}); + await f.load(); + is(f.status, "loaded", "Loaded Ahem font"); + + let subframe = document.getElementById("subframe"); + subframe.src = "file_bug1375944.html"; + await new Promise(resolve => subframe.onload = resolve); + let elem = subframe.contentDocument.getElementById("test"); + is(elem.getBoundingClientRect().width, 64, + "The font should be loaded properly"); + + SimpleTest.finish(); +} +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1382568.html b/layout/style/test/test_bug1382568.html new file mode 100644 index 0000000000..23d4dfe5b6 --- /dev/null +++ b/layout/style/test/test_bug1382568.html @@ -0,0 +1,14 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for bug 1382568: calling innerText on an uninitialized presshell doesn't crash</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script> + window.onmessage = function(e) { + is(e.data.result, "ok", "Child frame should load properly"); + SimpleTest.finish(); + }; +</script> +<iframe src="https://example.com/tests/layout/style/test/bug1382568-iframe.html"></iframe> +<script> + SimpleTest.waitForExplicitFinish(); +</script> diff --git a/layout/style/test/test_bug1394302.html b/layout/style/test/test_bug1394302.html new file mode 100644 index 0000000000..e21bcc4ea1 --- /dev/null +++ b/layout/style/test/test_bug1394302.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1394302 +--> +<head> + <title>Test for Bug 1394302</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style> + #inner { + animation: setFontSize 0s forwards; + } + @keyframes setFontSize { + to { font-size: calc(110% + 0.1em); } + } + </style> +</head> +<body> +<div id=outer> + <div id=inner></div> +</div> +<script> +var outer = document.getElementById("outer"); +outer.style.fontSize = '10px'; +is(getComputedStyle(inner).fontSize, "12px"); + +outer.style.fontSize = '20px'; +is(getComputedStyle(inner).fontSize, "24px"); +</script> +</body> +</html> diff --git a/layout/style/test/test_bug1443344-1.html b/layout/style/test/test_bug1443344-1.html new file mode 100644 index 0000000000..fbbcb1ecbb --- /dev/null +++ b/layout/style/test/test_bug1443344-1.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1443344 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1443344</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1443344 **/ + SimpleTest.waitForExplicitFinish(); + + var sheetURL = new URL("file_bug1443344.css", location.href); + sheetURL.protocol = "http"; + var link = document.createElement("link"); + link.href = `data:text/css,@import url("${sheetURL}");` + link.rel = "stylesheet"; + var loadFired = false, errorFired = false; + link.onload = () => loadFired = true; + link.onerror = () => errorFired = true; + document.head.appendChild(link); + + addLoadEvent(() => { + is(loadFired, false, "Should not fire onload for erroring @import"); + is(errorFired, true, "Should fire onerror for erroring @import"); + is(getComputedStyle($("importTarget")).color, "rgb(0, 255, 0)", + "Erroring sheet should not load"); + SimpleTest.finish(); + }); + + </script> + <style> + #importTarget { color: rgb(0, 255, 0); } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443344">Mozilla Bug 1443344</a> +<p id="display"><div id="importTarget"></div></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1443344-2.html b/layout/style/test/test_bug1443344-2.html new file mode 100644 index 0000000000..224f575f36 --- /dev/null +++ b/layout/style/test/test_bug1443344-2.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1443344 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1443344</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1443344 **/ + SimpleTest.waitForExplicitFinish(); + + var sheetURL = new URL("file_bug1443344.css", location.href); + sheetURL.protocol = "http"; + var link = document.createElement("link"); + link.href = `data:text/css,@import url("data:text/css,@import url('${sheetURL}');");` + link.rel = "stylesheet"; + var loadFired = false, errorFired = false; + link.onload = () => loadFired = true; + link.onerror = () => errorFired = true; + document.head.appendChild(link); + + addLoadEvent(() => { + is(loadFired, false, "Should not fire onload for erroring @import"); + is(errorFired, true, "Should fire onerror for erroring @import"); + is(getComputedStyle($("importTarget")).color, "rgb(0, 255, 0)", + "Erroring sheet should not load"); + SimpleTest.finish(); + }); + + </script> + <style> + #importTarget { color: rgb(0, 255, 0); } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1443344">Mozilla Bug 1443344</a> +<p id="display"><div id="importTarget"></div></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1451199-1.html b/layout/style/test/test_bug1451199-1.html new file mode 100644 index 0000000000..4deb6aac1b --- /dev/null +++ b/layout/style/test/test_bug1451199-1.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1451199 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1451199</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1451199 **/ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + var iframe = document.querySelector("iframe"); + iframe.width = "50"; + iframe.contentDocument.documentElement.offsetWidth; // Flush layout + + // We have to be careful to not check l.matches until the very end + // of the test. + var l = frames[0].matchMedia("(orientation: portrait)"); + l.onchange = function() { + is(l.matches, false, + "Should not match portrait by the time we get notified"); + SimpleTest.finish(); + }; + iframe.width = "200"; + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1451199">Mozilla Bug 1451199</a> +<p id="display"><iframe height="100" width="200"></iframe></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1451199-2.html b/layout/style/test/test_bug1451199-2.html new file mode 100644 index 0000000000..d29d644c0b --- /dev/null +++ b/layout/style/test/test_bug1451199-2.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1451199 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1451199</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1451199 **/ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(async function() { + // We have to be careful to not check l.matches until the very end + // of the test. + const l = frames[0].matchMedia("(orientation: portrait)"); + const iframe = document.querySelector("iframe"); + iframe.width = "50"; + + await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); + + l.addEventListener("change", function() { + is(l.matches, false, + "Should not match portrait by the time we get notified"); + SimpleTest.finish(); + }, { once: true }); + iframe.width = "200"; + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1451199">Mozilla Bug 1451199</a> +<p id="display"><iframe height="100" width="200"></iframe></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1490890.html b/layout/style/test/test_bug1490890.html new file mode 100644 index 0000000000..00a8176ed6 --- /dev/null +++ b/layout/style/test/test_bug1490890.html @@ -0,0 +1,112 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1490890 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1490890</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + #flex { + display: flex; + flex-direction: column; + height: 100px; + max-height: 100px; + overflow: hidden; + border: 1px solid black; + } + #overflowAuto { + overflow: auto; + white-space: pre-wrap; + } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1490890">Mozilla Bug 1490890</a> +<div id="display"> + <div id="content"> + <div id="flex"> + <div id="overflowAuto"> + <!-- Populated by test JS below: --> + <div id="tall"></div> + </div> + <div id="testNode">abc</div> + </div> + </div> +</div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +/** Test for Bug 1490890 **/ + +/** + * This test checks how many reflows are required, when we make a change inside + * a flex item, with a tall scrollable sibling flex item. + */ + +const gUtils = SpecialPowers.getDOMWindowUtils(window); + +// The elements that we will modify here: +const gTall = document.getElementById("tall"); +const gTestNode = document.getElementById("testNode"); + +// Helper function to undo our modifications: +function cleanup() +{ + gTall.firstChild.remove(); + gTestNode.style = ""; +} + +// Flush layout & return the global frame-reflow-count +function getReflowCount() +{ + let unusedVal = document.getElementById("flex").offsetHeight; // flush layout + return gUtils.framesReflowed; +} + +// This function changes gTestNode to "display:none", and returns the number +// of frames that need to be reflowed as a result of that tweak. +function makeTweakAndCountReflows() +{ + let beforeCount = getReflowCount(); + gTestNode.style.display = "none"; + let afterCount = getReflowCount(); + + let numReflows = afterCount - beforeCount; + if (numReflows <= 0) { + ok(false, "something's wrong -- we should've reflowed *something*"); + } + return numReflows; +} + +// ACTUAL TEST LOGIC STARTS HERE +// ----------------------------- +const testLineCount = 100; +const refLineCount = 5000; + +// "Reference" measurement: put enough lines of text into gTall to trigger +// a vertical scrollbar, and then see how many frames need to be reflowed +// in response to a tweak in gTestNode: +let text = document.createTextNode("a\n".repeat(testLineCount)); +gTall.appendChild(text); +let numReferenceReflows = makeTweakAndCountReflows(); +cleanup(); + +// "Test" measurement: put many more lines of text into gTall (many more than +// for reference case), and then see how many frames need to be reflowed +// in response to a tweak in gTestNode: +text = document.createTextNode("a\n".repeat(refLineCount)); +gTall.appendChild(text); +let numTestReflows = makeTweakAndCountReflows(); +cleanup(); + +is(numTestReflows, numReferenceReflows, + "Tweak should trigger the same number of reflows regardless of " + + "how much content is present in descendant of sibling"); +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_bug1505254.html b/layout/style/test/test_bug1505254.html new file mode 100644 index 0000000000..9cb3d1e316 --- /dev/null +++ b/layout/style/test/test_bug1505254.html @@ -0,0 +1,152 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1505254 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1505254</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style> + /* Note: this CSS/DOM structure is loosely based on WhatsApp Web. */ + #outerFlex { + display: flex; + height: 200px; + border: 3px solid purple; + overflow: hidden; + position: relative; + } + #outerItem { + flex: 0 0 60%; + overflow: hidden; + position: relative; + } + #abspos { + position: absolute; + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + } + #insideAbspos { + position: relative; + flex: 1 1 0; + width: 100%; + height: 100%; + } + #scroller { + display: flex; + flex-direction: column; + position: absolute; + top: 0; + overflow-x: hidden; + overflow-y: scroll; + height: 100%; + width: 100%; + } + #initiallyHidden { + display:none; + } + + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1505254">Mozilla Bug 1505254</a> +<div id="display"> + <div id="content"> + <div id="outerFlex"> + <div id="outerItem"> + <div id="abspos"> + <div id="insideAbspos"> + <div> + <div id="scroller"> + <div style="min-height: 600px">abc</div> + <div id="initiallyHidden">def</div> + </div> + </div> + </div> + <div id="testNode"></div> + </div> + </div> + </div> + </div> +</div> +<pre id="test"> +<script type="application/javascript"> +"use strict"; + +/** Test for Bug 1505254 **/ + +/** + * This test checks how many reflows are required when we make a change inside + * of an abpsos element, which itself is inside of a flex item with cached + * block-size measurements. This test is checking that this sort of change + * doesn't invalidate those cached block-size measurements on the flex item + * ancestor. (We're testing that indirectly by seeing how many frames are + * reflowed.) + */ + +const gUtils = SpecialPowers.getDOMWindowUtils(window); + +// The elements that we will modify here: +const gInitiallyHidden = document.getElementById("initiallyHidden"); +const gTestNode = document.getElementById("testNode"); + +// Helper function to undo our modifications: +function cleanup() +{ + gTestNode.textContent = ""; + gInitiallyHidden.style = ""; +} + +// Helper function to flush layout & return the global frame-reflow-count: +function getReflowCount() +{ + let unusedVal = document.getElementById("scroller").offsetHeight; // flush layout + return gUtils.framesReflowed; +} + +// This function adds some text in gTestNode and returns the number of frames +// that need to be reflowed as a result of that tweak: +function makeTweakAndCountReflows() +{ + let beforeCount = getReflowCount(); + gTestNode.textContent = "def"; + let afterCount = getReflowCount(); + + let numReflows = afterCount - beforeCount; + if (numReflows <= 0) { + ok(false, "something's wrong -- we should've reflowed *something*"); + } + return numReflows; +} + +// ACTUAL TEST LOGIC STARTS HERE +// ----------------------------- + +// "Reference" measurement: see how many frames need to be reflowed +// in response to a tweak in gTestNode, before we've shown +// #initiallyHidden: +let numReferenceReflows = makeTweakAndCountReflows(); +cleanup(); + +// "Test" measurement: see how many frames need to be reflowed +// in response to a tweak in gTestNode, after we've shown #initiallyHidden: +gInitiallyHidden.style.display = "block"; +let numTestReflows = makeTweakAndCountReflows(); +cleanup(); + +// Any difference between our measurements is an indication that we're reflowing +// frames in a non-"dirty" subtree. (The gTestNode tweak has no reason to cause +// #initiallyHidden to be dirty -- and therefore, the presence/absence of +// #initiallyHidden shouldn't affect the number of frames that get reflowed in +// response to the gTestNode tweak). +is(numTestReflows, numReferenceReflows, + "Tweak should trigger the same number of reflows regardless of " + + "content in unmodified sibling"); + +</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 0000000000..79b10462d8 --- /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 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_bug1729861.html b/layout/style/test/test_bug1729861.html new file mode 100644 index 0000000000..247b7c2644 --- /dev/null +++ b/layout/style/test/test_bug1729861.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1729861 +--> +<head> + <meta charset="utf-8"> + <title>Test that toggling the resistFingerprinting pref re-evaluates device media queries</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="test-css"></style> + <script src="bug1729861.js"></script> + <script> + // Run all tests now. + window.onload = function () { + add_task(async function() { + await test(); + }); + }; + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1729861">Bug 1729861</a> +<p id="display">TEST</p> +</body> +</html> diff --git a/layout/style/test/test_bug200089.html b/layout/style/test/test_bug200089.html new file mode 100644 index 0000000000..496de50e98 --- /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 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 0000000000..7e84319462 --- /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 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 0000000000..0a23d8b799 --- /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 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 = "rgba(0, 0, 0, 0)"; +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.remove(); +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 0000000000..28ff676ff0 --- /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 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 0000000000..d29f1bbd39 --- /dev/null +++ b/layout/style/test/test_bug319381.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=319381 +--> +<head> + <title>Test for Bug 319381</title> + <script 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"); +} + +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"); +} + +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, vals[i] + " " + vals[j], "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"); + } else if (vals[j] == "visible" && vals[i] == "auto") { + is(c(), vals[i], "Shorthand computation"); + } else { + let x = vals[i] == "visible" ? "auto" : vals[i]; + let y = vals[j] == "visible" ? "auto" : vals[j]; + is(c(), x + " " + y, "Shorthand computation"); + } + } +} +</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 0000000000..37475512d4 --- /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 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 0000000000..09ed45a8e2 --- /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 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 0000000000..ac3879c9d4 --- /dev/null +++ b/layout/style/test/test_bug372770.html @@ -0,0 +1,85 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=372770 +--> +<head> + <title>Test for Bug 372770</title> + <script 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); +} + +style1.color = "rgba(0, 0, 0, 0)"; +style2.color = "rgba(0, 0, 0, 0)"; +is(style1.color, "rgba(0, 0, 0, 0)", + "Inline style should round-trip black transparent color correctly"); +is(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 0000000000..d23c865b67 --- /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 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 0000000000..88fccd0dc2 --- /dev/null +++ b/layout/style/test/test_bug377947.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=377947 +--> +<head> + <title>Test for Bug 377947</title> + <script 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-variation-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-optical-sizing": "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-emoji": "auto", // 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 0000000000..56644f85cb --- /dev/null +++ b/layout/style/test/test_bug379440.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=379440 +--> +<head> + <title>Test for Bug 379440</title> + <script 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("file:///tmp/foo"), url("file:///c:/"), ' + + 'url("http://example.com/"), crosshair', + "Serialize unloadable URLs using their specified value"); +is(cur("t2"), 'url("file:///tmp/foo"), url("file:///c:/"), crosshair', + "Serialize unloadable URLs using their specified value"); +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 0000000000..7bb2463ce7 --- /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 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 0000000000..795a17048a --- /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 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 0000000000..8d902103e9 --- /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 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 0000000000..eec7109289 --- /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 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 0000000000..2e05ed848f --- /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 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 0000000000..f9544035b1 --- /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 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 0000000000..bf78711197 --- /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 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"> + +<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 0000000000..ff0e71f238 --- /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 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?1\")", + "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?1\")", + "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?1\")", + "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 0000000000..a36451927f --- /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 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.117px"); +is(a4.left, "-0.217px"); + +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.4568px"); + +</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 0000000000..e4492a5ca8 --- /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 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 0000000000..eff4da449b --- /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 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/popup.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/popup.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/popup.css", + "Unexpected href for linked chrome sheet before cloning"); + is(document.styleSheets[4].href, + "chrome://global/skin/popup.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/popup.css", + "Unexpected href for linked chrome sheet after cloning"); + is(document.styleSheets[4].href, + "chrome://global/skin/popup.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 0000000000..fb37be57f7 --- /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 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 0000000000..0b48e3aa68 --- /dev/null +++ b/layout/style/test/test_bug413958.html @@ -0,0 +1,75 @@ +<!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> +SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true; + +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: 16, sourceLine: "", cssSelectors: "#s1" }, + { errorMessage: /Unknown property \u2018nosuchprop\u2019/, + lineNumber: 2, columnNumber: 16, sourceLine: "", cssSelectors: "#s2" }, + { errorMessage: /Ruleset ignored due to bad selector/, + lineNumber: 2, columnNumber: 41, sourceLine: "", cssSelectors: "" } ], + [ { errorMessage: /parsing value for \u2018width\u2019/, + lineNumber: 1, columnNumber: 7, sourceLine: "", cssSelectors: "" } ], + [ { errorMessage: /parsing value for \u2018width\u2019/, + lineNumber: 1, columnNumber: 1, sourceLine: "", cssSelectors: "" } ], +]; +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 0000000000..04443a3553 --- /dev/null +++ b/layout/style/test/test_bug418986-2.html @@ -0,0 +1,32 @@ +<!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 src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="test-css"></style> + <script type="text/javascript" src="chrome/bug418986-2.js"></script> + <script type="text/javascript"> + // Run all tests now. + window.onload = function () { + add_task(async function() { + await 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 0000000000..fb6830dd57 --- /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 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-line": "none", "visibility": "visible" }, + true: { str: "", "text-decoration-line": "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-line", "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 0000000000..91488e9496 --- /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 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_bug470769.html b/layout/style/test/test_bug470769.html new file mode 100644 index 0000000000..589cf790b0 --- /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 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 0000000000..37ad553206 --- /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 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 0000000000..b398d33967 --- /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 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_bug517224.html b/layout/style/test/test_bug517224.html new file mode 100644 index 0000000000..83d2bb8d80 --- /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 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 0000000000..5a57b61ba2 --- /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 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 0000000000..d3ad02419d --- /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 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 0000000000..0b60e6d89c --- /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 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, "").textDecorationLine; } + + 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 0000000000..182087e1ec --- /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 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 0000000000..e54dfc2d7f --- /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 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" srcdoc="<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 0000000000..6a6d373afc --- /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 0000000000..80ea5a98ef --- /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 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.from(document.getElementsByTagName("div"));
+ cases.forEach(function(aCase, aIndex) {
+ aCase.className = "after";
+ });
+ window.setTimeout(function() {
+ cases.forEach(function(aCase, aIndex) {
+ is(window.getComputedStyle(aCase)
+ .getPropertyValue("background-color"),
+ "rgba(0, 0, 0, 0)",
+ aCase.textContent);
+ });
+ SimpleTest.finish();
+ }, 1);
+});
+
+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 0000000000..ed8feccf7d --- /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 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 0000000000..cdee3f33a7 --- /dev/null +++ b/layout/style/test/test_bug652486.html @@ -0,0 +1,192 @@ +<!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, Bug 1039488 and Bug 1574222</title> + <script 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> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1574222">Mozilla Bug 1574222</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, Bug 1039488 and Bug 1574222 **/ + +function c() { + return document.defaultView.getComputedStyle($('t')). + getPropertyValue("text-decoration"); +} + +// The default value of the 'color' property, which in turn establishes the +// default value of 'text-decoration-color' (via the 'currentColor' keyword). +var defaultLineColor = "rgb(0, 0, 0)"; + +var tests = [ + // When only text-decoration was specified, text-decoration should look like + // a longhand property. However, as of Bug 1574222, the getComputedStyle() + // serialization for "text-decoration" will always include the color, + // because we can't tell whether the resolved color value came from the + // initial "currentColor" value (and could safely be omitted) vs. whether it + // came from a custom specified value (and cannot be omitted). + { decoration: "none", + line: null, color: null, style: null, + expectedValue: defaultLineColor }, + { decoration: "underline", + line: null, color: null, style: null, + expectedValue: "underline " + defaultLineColor }, + { decoration: "overline", + line: null, color: null, style: null, + expectedValue: "overline " + defaultLineColor }, + { decoration: "line-through", + line: null, color: null, style: null, + expectedValue: "line-through " + defaultLineColor }, + { decoration: "blink", + line: null, color: null, style: null, + expectedValue: "blink " + defaultLineColor }, + { decoration: "underline overline", + line: null, color: null, style: null, + expectedValue: "underline overline " + defaultLineColor }, + { decoration: "underline line-through", + line: null, color: null, style: null, + expectedValue: "underline line-through " + defaultLineColor }, + { decoration: "blink underline", + line: null, color: null, style: null, + expectedValue: "underline blink " + defaultLineColor }, + { decoration: "underline blink", + line: null, color: null, style: null, + expectedValue: "underline blink " + defaultLineColor }, + + // When only text-decoration-line or text-blink was specified, + // text-decoration should look like a longhand property. + // However, as of Bug 1574222, the getComputedStyle() serialization for + // "text-decoration" will always include the color, because we can't tell + // whether the resolved color value came from the initial "currentColor" + // value (and could safely be omitted) vs. whether it came from a custom + // specified value (and cannot be omitted). + { decoration: null, + line: "blink", color: null, style: null, + expectedValue: "blink " + defaultLineColor }, + { decoration: null, + line: "underline", color: null, style: null, + expectedValue: "underline " + defaultLineColor }, + { decoration: null, + line: "overline", color: null, style: null, + expectedValue: "overline " + defaultLineColor }, + { decoration: null, + line: "line-through", color: null, style: null, + expectedValue: "line-through " + defaultLineColor }, + { decoration: null, + line: "blink underline", color: null, style: null, + expectedValue: "underline blink " + defaultLineColor }, + + // 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)" }, + { decoration: "underline", + line: null, color: "black", style: null, + expectedValue: "underline rgb(0, 0, 0)" }, + { decoration: "overline", + line: null, color: "#ff0000", style: null, + expectedValue: "overline rgb(255, 0, 0)" }, + { decoration: "line-through", + line: null, color: "initial", style: null, + expectedValue: "line-through " + defaultLineColor }, + { decoration: "blink underline", + line: null, color: "currentColor", style: null, + expectedValue: "underline blink " + defaultLineColor }, + { decoration: "underline line-through", + line: null, color: "currentcolor", style: null, + expectedValue: "underline line-through " + defaultLineColor }, + + // 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 " + defaultLineColor }, + { decoration: "underline", + line: null, color: null, style: "dotted", + expectedValue: "underline dotted " + defaultLineColor }, + { decoration: "overline", + line: null, color: null, style: "dashed", + expectedValue: "overline dashed " + defaultLineColor }, + { decoration: "line-through", + line: null, color: null, style: "double", + expectedValue: "line-through double " + defaultLineColor }, + { decoration: "blink underline", + line: null, color: null, style: "wavy", + expectedValue: "underline blink wavy " + defaultLineColor }, + { decoration: "underline blink overline line-through", + line: null, color: null, style: "solid", + expectedValue: "underline overline line-through blink " + defaultLineColor }, + { decoration: "line-through overline underline", + line: null, color: null, style: "initial", + expectedValue: "underline overline line-through " + defaultLineColor } +]; + +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; +} + +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); + + clearStyleObject(); + + $('t').setAttribute("style", dec); + + is(c(), test.expectedValue, "Test2 (computed value): " + 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 0000000000..de8a1961d4 --- /dev/null +++ b/layout/style/test/test_bug657143.html @@ -0,0 +1,120 @@ +<!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 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 = [], customA = []; + +// Partition the list of property names into four lists: +// +// cssA: regular properties +// mozA: '-moz-' prefixed 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: + 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(); + +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'); + +// 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(!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_bug667520.html b/layout/style/test/test_bug667520.html new file mode 100644 index 0000000000..fb46943e8e --- /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 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 0000000000..ed538cd822 --- /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 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 0000000000..03591e3dc6 --- /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 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 0000000000..1bf5825000 --- /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 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 0000000000..6a95703cd7 --- /dev/null +++ b/layout/style/test/test_bug73586.html @@ -0,0 +1,189 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=73586 +--> +<head> + <title>Test for Bug 73586</title> + <script 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; + } +} + +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-line: underline; }"; +run_series(function(child, elt, elts, node, nodes) { + is(cs(child).textDecorationLine, (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 0000000000..054189056b --- /dev/null +++ b/layout/style/test/test_bug74880.html @@ -0,0 +1,119 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=74880 +--> +<head> + <title>Test for Bug 74880</title> + <script 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", "unset" ]; +var gColorValues = [ "inherit", "initial", "currentColor", "green", "unset" ]; +var gStyleValues = [ "inherit", "initial", "double", "dashed", "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 0000000000..acc9f5b5c0 --- /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 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 0000000000..a5073d681e --- /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 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 0000000000..66726c2b8e --- /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 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_bug798843_pref.html b/layout/style/test/test_bug798843_pref.html new file mode 100644 index 0000000000..aea12ccc35 --- /dev/null +++ b/layout/style/test/test_bug798843_pref.html @@ -0,0 +1,53 @@ +<!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 = { + "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 0000000000..a4614cf6eb --- /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 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", + "\\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 0", + "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 0", + "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 0000000000..be480600b9 --- /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 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 0000000000..739ace0f04 --- /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 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 0000000000..a67db56ee3 --- /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 0000000000..5dc325f9ef --- /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 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.firstChild.remove(); +} + +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) { + let 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) { + let div = divs[i]; + is(bg(div), ORANGE, "should be orange"); + is(color(div), MAROON, "should be maroon"); +} + +for (i in divs) { + let div = divs[i]; + var e = document.createEvent("MouseEvents"); + e.initEvent("click", true, true); + div.dispatchEvent(e); +} + +for (i in divs) { + let 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 0000000000..6479d52617 --- /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 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 0000000000..db9eda20df --- /dev/null +++ b/layout/style/test/test_ch_ex_no_infloops.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=678671 +--> +<head> + <title>Test for Bug 678671</title> + <script 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 0000000000..82e149154c --- /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 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 0000000000..3c77103ba2 --- /dev/null +++ b/layout/style/test/test_clip-path_polygon.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> +<style> +body {padding: 0;margin:0;} +div { + width: 200px; + height: 200px; + position: fixed; + top: 50px; + left: 50px; + margin: 50px; + padding: 50px; + 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 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"); +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> diff --git a/layout/style/test/test_color_rounding.html b/layout/style/test/test_color_rounding.html new file mode 100644 index 0000000000..46698ede3d --- /dev/null +++ b/layout/style/test/test_color_rounding.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset=utf-8> + <title>Test rounding of CSS color valus</title> + <link rel="author" title="Manish Goregaokar" href="mailto:mgoregaokar@mozilla.com"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel='stylesheet' href='/resources/testharness.css'> +</head> +<body> +<div id="colordiv"></div> +<script> + +function do_test(color, computed, reason) { + test(function() { + var element = document.getElementById('colordiv'); + // assume this works; this way we clear any previous state + element.style.color = "red"; + element.style.color = color; + assert_equals(getComputedStyle(element).color, computed); + }, `${reason}: ${color}`); +} + +do_test("rgb(10%, 10%, 10%, 10%)", "rgba(26, 26, 26, 0.1)", "rgb percent-to-int should round"); +do_test("rgb(10%, 10%, 10%)", "rgb(26, 26, 26)", "rgb percent-to-int should round"); +do_test("hsl(0, 0%, 90%)", "rgb(230, 230, 230)", "hsl-to rgb should round"); +do_test("hsla(0, 0%, 90%, 10%)", "rgba(230, 230, 230, 0.1)", "hsl-to rgb should round"); +do_test("rgb(100%, 100%, 0%)", "rgb(255, 255, 0)", "handling of extrema"); +do_test("rgba(100%, 100%, 100%, 100%)", "rgb(255, 255, 255)", "handling of extrema"); +do_test("rgba(100%, 100%, 100%, 0%)", "rgba(255, 255, 255, 0)", "handling of extrema"); +do_test("rgb(255.5, 260, 500, 50)", "rgb(255, 255, 255)", "out of bounds should be handled"); +do_test("rgb(254.5, 254.55, 254.45)", "rgb(255, 255, 254)", "number values should be rounded"); +do_test("rgb(99.8%, 99.9%, 99.7%)", "rgb(254, 255, 254)", "percentage values should be rounded"); + +</script> +</body> +</html> 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 0000000000..6970270c95 --- /dev/null +++ b/layout/style/test/test_compute_data_with_start_struct.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for correct handling of aStartStruct parameter to nsRuleNode::Compute*Data</title> + <script 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 was to test that in the old style system + * the nsRuleNode::Compute*Data functions were written correctly. + * In particular, in these functions, when the specified value of a + * property had unit eCSSUnit_Null, touching the computed data is + * forbidden. This is because we sometimes would 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. + * + * However, we don't cache style structs in the rule tree in the current + * style system code, and property cascading no longer relies on hand + * written functions, so this particular failure mode isn't as likely to + * happen. + */ + +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 test_property(prop, lower_set, higher_set) { + var info = gCSSProperties[prop]; + if (info.subproperties || info.logical) + return; + + gRule1.style.setProperty(prop, info[lower_set][0]); + gRule2.style.setProperty(prop, info[higher_set][0]); + + 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); + isnot(higher_set_val, lower_set_val, "initial and other values of " + prop + " are different"); + + gRule2.style.removeProperty(prop); + is(gTest.getPropertyValue(prop), lower_set_val, prop + " is not touched when its value comes from aStartStruct"); + + gRule1.style.removeProperty(prop); + if ("prerequisites" in info) { + for (var prereq in info.prerequisites) { + gRule2.style.removeProperty(prereq); + } + } +} + +function round(lower_set, higher_set) { + for (var prop in gCSSProperties) + test_property(prop, lower_set, higher_set); +} + +round("other_values", "initial_values"); +round("initial_values", "other_values"); + +</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 0000000000..c2dc667f2f --- /dev/null +++ b/layout/style/test/test_computed_style.html @@ -0,0 +1,664 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for miscellaneous computed style issues</title> + <script 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=">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. + 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 */ + 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"); + + 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"); + + p.remove(); +})(); + +(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.remove(); +})(); + +(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(0% + 3px) 0px", + "computed 0% calc x"], + [ "0 calc(3px + 2% - 2%)", "0px calc(0% + 3px)", + "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.remove(); +})(); + +(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(0% + 3px) 0px", + "computed 0% calc horizontal"], + [ "0 calc(3px + 2% - 2%)", "0px calc(0% + 3px)", + "computed 0% calc vertical"], + [ "calc(3px - 5px) calc(6px - 9px)", "0px 0px", "negative pixel width" ], + [ "", "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.remove(); +})(); + +(function test_bug_716628() { + // Test that various gradient styles round-trip correctly + var backgroundImages = [ + [ "radial-gradient(at 10% bottom, #ffffff, black)", + "radial-gradient(at 10% 100%, rgb(255, 255, 255), rgb(0, 0, 0))", + "radial gradient 1" ], + [ "radial-gradient(#ffffff, black)", + "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))", + "radial gradient 2" ], + [ "radial-gradient(farthest-corner, #ffffff, black)", + "radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))", + "radial gradient 3" ], + [ "linear-gradient(red, blue)", + "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient 1" ], + [ "linear-gradient(to bottom, red, blue)", + "linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient 2" ], + [ "linear-gradient(to right, red, blue)", + "linear-gradient(to right, rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient 3" ], + [ "linear-gradient(-45deg, red, blue)", + "linear-gradient(-45deg, rgb(255, 0, 0), rgb(0, 0, 255))", + "linear gradient with angle in degrees" ], + [ "linear-gradient(-0.125turn, red, blue)", + "linear-gradient(-45deg, 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.remove(); +})(); + +(function test_bug_1363349_linear() { + const specPrefix = "-webkit-gradient(linear, "; + const specSuffix = ", from(blue), to(lime))"; + + const expPrefix = "linear-gradient("; + const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)"; + + let testcases = [ + [ "calc(5 + 5) top, calc(10 + 10) top", + "to right", + "calc(num+num) in position" + ], + [ "left calc(25% - 10%), right calc(75% + 10%)", + "to right bottom", + "calc(pct+pct) in position " + ] + ]; + + let p = document.createElement("p"); + let cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (let test of testcases) { + let specifiedStyle = specPrefix + test[0] + specSuffix; + let expectedStyle = expPrefix; + if (test[1] != "") { + expectedStyle += test[1] + ", "; + } + expectedStyle += expSuffix; + + p.style.backgroundImage = specifiedStyle; + is(cs.backgroundImage, expectedStyle, + "computed value of -webkit-gradient expression (" + test[2] + ")"); + p.style.backgroundImage = ""; + } + + p.remove(); +})(); + +(function test_bug_1363349_radial() { + const specPrefix = "-webkit-gradient(radial, "; + const specSuffix = ", from(blue), to(lime))"; + + const expPrefix = "radial-gradient("; + const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)"; + + let testcases = [ + [ "1 2, 0, 3 4, calc(1 + 5)", + "6px at 3px 4px", + "calc(num+num) in radius" + ], + [ "1 2, calc(1 + 2), 3 4, calc(1 + 5)", + "6px at 3px 4px", + "calc(num+num) in radius" + ], + [ "calc(0 + 1) calc(1 + 1), calc(1 + 2), calc(1 + 2) 4, calc(1 + 5)", + "6px at 3px 4px", + "calc(num+num) in position and radius" + ] + ]; + + let p = document.createElement("p"); + let cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (let test of testcases) { + let specifiedStyle = specPrefix + test[0] + specSuffix; + let expectedStyle = expPrefix; + if (test[1] != "") { + expectedStyle += test[1] + ", "; + } + expectedStyle += expSuffix; + + p.style.backgroundImage = specifiedStyle; + is(cs.backgroundImage, expectedStyle, + "computed value of -webkit-gradient expression (" + test[2] + ")"); + p.style.backgroundImage = ""; + } + + p.remove(); +})(); + +(function test_bug_1241623() { + // Test that -webkit-gradient() styles are approximated the way we expect: + + // For compactness, we'll pull out the common prefix & suffix from all of the + // specified & expected styles, and construct the full expression on the fly: + const specPrefix = "-webkit-gradient(linear, "; + const specSuffix = ", from(blue), to(lime))"; + + const expPrefix = "linear-gradient("; + const expSuffix = "rgb(0, 0, 255) 0%, rgb(0, 255, 0) 100%)"; + + let testcases = [ + // + // [ legacyDirection, + // modernDirection, // (empty string means use default direction) + // descriptionOfTestcase ], + + // If start & end are at same point, we just produce a gradient with + // the default direction. + [ "left top, left top", + "", + "start & end point are the same" ], + [ "40 40, 40 40", + "", + "start & end point are the same" ], + [ "center center, center center", + "", + "start & end point are the same" ], + + // If start & end use different units in the same coordinate, we generally + // can't extract a direction (because we can't know whether arbitrary + // percent values are larger or smaller than arbitrary pixel values). So + // we produce a gradient in the default direction. + [ "left top, 30 100%", // (Note: keywords like "left" are really % vals.) + "", + "start & end point have different units" ], + [ "100% 15, right bottom", + "", + "start & end point have different units" ], + [ "0 0%, 20 20", + "", + "start & end point have different units" ], + [ "0 0, 100% 20", + "", + "start & end point have different units" ], + [ "5% 30, 20 50%", + "", + "start & end point have different units" ], + [ "5% 6%, 20 30", + "", + "start & end point have different units" ], + + // Gradient starting/ending somewhere arbitrary in middle: + [ "center center, right top", + "to right top", + "from center to right top" ], + [ "left top, center center", + "to right bottom", + "from left top to center" ], + [ "10 15, 5 20", + "to left bottom", + "from arbitrary point to another point in lower-left direction" ], + + // Gradient using negative coordinates: + [ "-10 -15, 0 0", + "to right bottom", + "from negative point to origin" ], + [ "-100 10, 20 30", + "to right bottom", + "from negative-x point to another point in lower-right direction" ], + [ "10 -100, 5 10", + "to left bottom", + "from negative-y point to another point in lower-left direction" ], + + // Diagonal gradient between sides/corners: + [ "center top, left center", + "to left bottom", + "left/bottom-wards, using edge keywords" ], + [ "left center, center top", + "to right top", + "top/right-wards, using edge keywords" ], + [ "right center, center top", + "to left top", + "top/left-wards, using edge keywords" ], + [ "right top, center bottom", + "to left bottom", + "bottom/left-wards, using edge keywords" ], + [ "left top, right bottom", + "to right bottom", + "bottom/right-wards, using edge keywords" ], + [ "left bottom, right top", + "to right top", + "top/right-wards, using edge keywords" ], + ]; + + let p = document.createElement("p"); + let cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (let test of testcases) { + let specifiedStyle = specPrefix + test[0] + specSuffix; + let expectedStyle = expPrefix; + if (test[1] != "") { + expectedStyle += test[1] + ", "; + } + expectedStyle += expSuffix; + + p.style.backgroundImage = specifiedStyle; + is(cs.backgroundImage, expectedStyle, + "computed value of -webkit-gradient expression (" + test[2] + ")"); + p.style.backgroundImage = ""; + } + + p.remove(); +})(); + +(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 = [ + "maskImage", + "backgroundImage", + "markerStart", + "markerMid", + "markerEnd", + "clipPath", + "filter", + "fill", + "stroke", + ]; + + for (var prop of testStyles) { + p.style[prop] = localURL; + is(cs[prop], localURL, "computed value of " + prop); + p.style[prop] = nonLocalURL; + is(cs[prop], resolvedNonLocalURL, "computed value of " + prop); + } + + p.remove(); +})(); + +(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)"], + ]; + + var css_color_4 = [ + ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["rgba(0 0 0 / 0.1)", "rgba(0, 0, 0, 0.1)"], + ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["rgb(0 0 0 / 0.2)", "rgba(0, 0, 0, 0.2)"], + ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"], + ["hsla(0deg 0% 0% / 0.3)", "rgba(0, 0, 0, 0.3)"], + ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"], + ["hsl(0 0% 0% / 0.4)", "rgba(0, 0, 0, 0.4)"], + ]; + + 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]); + } + for (var i = 0; i < css_color_4.length; ++i) { + var test = css_color_4[i]; + p.style.color = test[0]; + is(cs.color, test[1], "css-color-4 computed value of " + test[0]); + } + + p.remove(); +})(); + +(function test_bug_1357117() { + // Test that vendor-prefixed gradient styles round-trip with the same prefix, + // or with no prefix. + var backgroundImages = [ + // [ specified style, + // expected computed style, + // descriptionOfTestcase ], + // Linear gradient with legacy-gradient-line (needs prefixed syntax): + [ "-webkit-linear-gradient(10deg, red, blue)", + "-webkit-linear-gradient(10deg, rgb(255, 0, 0), rgb(0, 0, 255))", + "-webkit-linear-gradient with angled legacy-gradient-line" ], + + // Linear gradient with box corner (needs prefixed syntax): + [ "-webkit-linear-gradient(top left, red, blue)", + "-webkit-linear-gradient(left top, rgb(255, 0, 0), rgb(0, 0, 255))", + "-webkit-linear-gradient with box corner" ], + + // Linear gradient with default keyword (should be serialized without keyword): + [ "-webkit-linear-gradient(top, red, blue)", + "-webkit-linear-gradient(rgb(255, 0, 0), rgb(0, 0, 255))", + "-webkit-linear-gradient with legacy default direction keyword" ], + + // Radial gradients (should be serialized using modern unprefixed style): + [ "-webkit-radial-gradient(contain, red, blue)", + "-webkit-radial-gradient(closest-side, rgb(255, 0, 0), rgb(0, 0, 255))", + "-webkit-radial-gradient with legacy 'contain' keyword" ], + ]; + + 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 prefixed gradient expression (" + test[2] + ")"); + } + + p.remove(); +})(); + +(function test_bug_1367028() { + const borderImageSubprops = [ + "border-image-slice", + "border-image-outset", + "border-image-width" + ]; + const rectValues = [ + { + values: ["5 5 5 5", "5 5 5", "5 5", "5"], + expected: "5", + desc: "identical four sides", + }, + { + values: ["5 6 5 6", "5 6 5", "5 6"], + expected: "5 6", + desc: "identical values on each axis", + }, + { + values: ["5 6 7 6", "5 6 7"], + expected: "5 6 7", + desc: "identical values on left and right", + }, + { + values: ["5 6 5 7"], + desc: "identical values on top and bottom", + }, + { + values: ["5 5 6 6", "5 6 6 5"], + desc: "identical values on unrelated sides", + }, + { + values: ["5 6 7 8"], + desc: "different values on all sides", + }, + ]; + + let frameContainer = document.getElementById("display"); + let p = document.createElement("p"); + frameContainer.appendChild(p); + let cs = getComputedStyle(p); + + for (let prop of borderImageSubprops) { + for (let {values, expected, desc} of rectValues) { + for (let value of values) { + p.style.setProperty(prop, value); + is(cs.getPropertyValue(prop), + expected ? expected : value, `${desc} for ${prop}`); + p.style.removeProperty(prop); + } + } + } + + p.remove(); +})(); + +(function test_bug_1378368() { + // Test that negative results of calc()s in basic-shapes (e.g. polygon()) should + // not be clamped to 0px. + var clipPaths = [ + // [ specified style, + // expected computed style, + // descriptionOfTestcase ], + // polygon: + [ "polygon(calc(10px - 20px) 0px, 100px 100px, 0px 100px)", + "polygon(-10px 0px, 100px 100px, 0px 100px)", + "polygon with negative calc() coordinates" ], + // inset: + [ "inset(calc(10px - 20px))", + "inset(-10px)", + "inset with negative calc() coordinates" ], + ]; + + var p = document.createElement("p"); + var cs = getComputedStyle(p, ""); + frame_container.appendChild(p); + + for (let test of clipPaths) { + p.style.clipPath = test[0]; + is(cs.clipPath, test[1], + "computed value of clip-path for basic-shapes (" + test[2] + ")"); + } + + p.remove(); +})(); + +(function test_bug_1418433() { + // Test that the style data read through getComputedStyle is always up-to-date, + // even for non-displayed elements. + + var d = document.createElement("div"); + d.setAttribute("id", "nonDisplayedDiv"); + var cs = getComputedStyle(d, null); + noframe_container.appendChild(d); + + // Test for stylesheet change, i.e., added/changed/removed + var style = document.getElementById("style"); + is(cs.height, "auto", + "computed value of display none element (before testing)"); + style.textContent = "#nonDisplayedDiv { height: 100px; }"; + is(cs.height, "100px", + "computed value of display none element (sheet added)"); + style.textContent = "#nonDisplayedDiv { height: 10px; }"; + is(cs.height, "10px", + "computed value of display none element (sheet changed)"); + style.textContent = ""; + is(cs.height, "auto", + "computed value of display none element (sheet removed)"); + + // Test for rule change, i.e., added/changed/removed + var styleSheet = style.sheet; + is(cs.width, "auto", + "computed value of display none element (before testing)"); + styleSheet.insertRule("#nonDisplayedDiv { width: 100px; }", 0); + is(cs.width, "100px", + "computed value of display none element (rule added)"); + styleSheet.deleteRule(0); + styleSheet.insertRule("#nonDisplayedDiv { width: 10px; }", 0); + is(cs.width, "10px", + "computed value of display none element (rule changed)"); + styleSheet.deleteRule(0); + is(cs.width, "auto", + "computed value of display none element (rule removed)"); + + d.remove(); +})(); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_computed_style_bfcache_display_none.html b/layout/style/test/test_computed_style_bfcache_display_none.html new file mode 100644 index 0000000000..8322e4977a --- /dev/null +++ b/layout/style/test/test_computed_style_bfcache_display_none.html @@ -0,0 +1,60 @@ +<!doctype html> +<title>Test for getting the computed style on the root node of a display:none subtree in a document in the bfcache</title> +<script 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=1377010">Mozilla Bug 1377010</a> +<p id="display"></p> +<script> +SimpleTest.waitForExplicitFinish(); + +let testDiv; +let loadedPromiseResolve; + +const TEST_PATH = "http://mochi.test:8888/tests/layout/style/test/"; +const TEST_FILE1 = TEST_PATH + "file_computed_style_bfcache_display_none.html"; +const TEST_FILE2 = TEST_PATH + "file_computed_style_bfcache_display_none2.html"; + +// Open a new window. +const w = window.open(TEST_FILE1); +waitForLoadMessage().then(() => { + // Take a reference to a node in the new window. + testDiv = w.document.getElementById('div'); + + // Open a new document so that the test div now refers to a node in a + // document in the bfcache. + w.location = TEST_FILE2; + return waitForLoadMessage(); +}).then(() => { + // Compute styles for the node in the bfcache document. + is(w.getComputedStyle(testDiv).opacity, '1'); + + // Restore the bfcache document. + return goBack(w); +}).then(() => { + // Fetch the style once again. + is(w.getComputedStyle(testDiv).opacity, '1'); + + w.close(); + SimpleTest.finish(); +}); + +window.addEventListener('message', e => { + if (e.data === 'loaded' && loadedPromiseResolve) { + loadedPromiseResolve(); + loadedPromiseResolve = undefined; + } +}); + +function waitForLoadMessage() { + return new Promise(resolve => { + loadedPromiseResolve = resolve; + }); +} + +function goBack(win) { + return new Promise(resolve => { + win.onpagehide = e => resolve(win); + win.history.back(); + }); +} +</script> diff --git a/layout/style/test/test_computed_style_difference.html b/layout/style/test/test_computed_style_difference.html new file mode 100644 index 0000000000..f4008ff476 --- /dev/null +++ b/layout/style/test/test_computed_style_difference.html @@ -0,0 +1,104 @@ +<!doctype html> +<title>Test that the difference of the computed style of an element is always correctly propagated</title> +<!-- + There are CSS property changes that don't have an effect in computed style. + + It's relatively easy to return `nsChangeHint(0)` for the case where the + property changes but it should have no rendering difference. + + That's however incorrect, since if it's an inherited property, or a + descendant explicitly inherits it, we should still propagate the change + downwards. + + This test tests that computed style diffing is correct. +--> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="property_database.js"></script> +<div id="outer"> + <div id="inner"></div> +</div> +<script> +// We need to skip checking for properties for which the value returned by +// getComputedStyle depends on the parent. +// +// TODO(emilio): We could test a subset of these, see below. +const kWhitelist = [ + // Could test display values that don't force blockification of children. + "display", + + // Could avoid testing only the ones that have percentages. + "transform", + "transform-origin", + "perspective-origin", + + "padding-bottom", + "padding-left", + "padding-right", + "padding-top", + "padding-inline-end", + "padding-inline-start", + "padding-block-end", + "padding-block-start", + + "margin-bottom", + "margin-left", + "margin-right", + "margin-top", + "margin-inline-end", + "margin-inline-start", + "margin-block-end", + "margin-block-start", + + "width", + "height", + "block-size", + "inline-size", + + "min-height", + "min-width", + "min-block-size", + "min-inline-size", +]; + +const outer = document.getElementById("outer"); +const inner = document.getElementById("inner"); + +function testValue(prop, value) { + outer.style.setProperty(prop, value); + const computed = getComputedStyle(outer).getPropertyValue(prop); + assert_equals( + getComputedStyle(inner).getPropertyValue(prop), computed, + "Didn't handle the inherited change correctly?" + ) +} + +// Note that we intentionally ignore the "prerequisites" here, since that's +// the most likely place where the diffing could potentially go wrong. +function testProperty(prop, info) { + // We only care about longhands, changing shorthands is not that interesting, + // since we're interested of changing as little as possible, and changing + // them would be equivalent to changing all the longhands at the same time. + if (info.type !== CSS_TYPE_LONGHAND) + return; + if (kWhitelist.includes(prop)) + return; + + inner.style.setProperty(prop, "inherit"); + for (const v of info.initial_values) + testValue(prop, v); + for (const v of info.other_values) + testValue(prop, v); + // Test again the first value so that we test changing to it, not just from + // it. + // + // TODO(emilio): We could test every value against every-value if we wanted, + // might be worth it. + testValue(prop, info.initial_values[0]); + + inner.style.removeProperty(prop); +} + +for (let prop in gCSSProperties) + test(() => testProperty(prop, gCSSProperties[prop]), "Diffing for " + prop); +</script> diff --git a/layout/style/test/test_computed_style_grid_with_pseudo.html b/layout/style/test/test_computed_style_grid_with_pseudo.html new file mode 100644 index 0000000000..24eb520776 --- /dev/null +++ b/layout/style/test/test_computed_style_grid_with_pseudo.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1350780 +--> +<head> +<title>Test for Bug 1350780</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + +<style> +#container { + width: 100px; +} + +.gridBefore::before { + content: ""; + display: grid; + grid-template-columns: auto; +} + +.gridBeforeNoContent::before { + display: grid; + grid-template-columns: 40px; +} +</style> + +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function checkTemplateWithData(data) { + let obj = document.createElement("div"); + + // We need either a template or an additionalClass. + if (typeof(data.template != "undefined")) { + obj.style.display = "grid"; + obj.style.gridTemplateColumns = data.template; + } + + if (typeof(data.additionalClass != "undefined")) { + obj.className = data.additionalClass; + } + + let container = document.getElementById("container"); + container.appendChild(obj); + + let computedStyle = getComputedStyle(obj, data.pseudo); + let computedTemplate = computedStyle.getPropertyValue("grid-template-columns"); + + let message = "Got expected template with pseudo " + data.pseudo; + if (typeof(data.additionalClass != "undefined")) { + message += " with class " + data.additionalClass; + } + message += "."; + + is(computedTemplate, data.expected, message); + + container.removeChild(obj); +} + +function runTest() { + let dataToTest = [ + { template: "40px", + pseudo: "::selection", + expected: "none"}, + { template: "40px", + pseudo: "::before", + expected: "none" }, + { additionalClass: "gridBefore", + pseudo: "::before", + expected: "100px" }, + { additionalClass: "gridBeforeNoContent", + pseudo: "::before", + expected: "40px" }, + ]; + + for (let i = 0; i < dataToTest.length; ++i) { + checkTemplateWithData(dataToTest[i]); + } + + SimpleTest.finish(); +} + +</script> +</head> +<body onload="runTest()"> +<div id="container"></div> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1350780">Mozilla Bug 1350780</a> +</body> +</html> diff --git a/layout/style/test/test_computed_style_in_created_document.html b/layout/style/test/test_computed_style_in_created_document.html new file mode 100644 index 0000000000..72ff0f5921 --- /dev/null +++ b/layout/style/test/test_computed_style_in_created_document.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test for bug 1398619</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<script> +SimpleTest.waitForExplicitFinish(); +let referenceFontSize = getComputedStyle(document.body).fontSize; + +function checkComputedStyle(desc, doc) { + try { + let fontSize = getComputedStyle(doc.body).fontSize; + is(fontSize, referenceFontSize, `${desc}: get computed font-size`); + } catch (e) { + ok(false, `${desc}: fail to get computed font-size, ${e}`); + } +} + +async function runTest() { + // DOMParser + { + let parser = new DOMParser(); + let doc = parser.parseFromString("<body>", "text/html"); + checkComputedStyle("DOMParser", doc); + } + // DOMImplementation + { + let doc = document.implementation.createHTMLDocument(""); + checkComputedStyle("DOMImplementation", doc); + } + // XMLHttpRequest + { + let xhr = new XMLHttpRequest(); + xhr.open("GET", "empty.html"); + xhr.responseType = "document"; + let promise = new Promise(resolve => { + xhr.onload = resolve; + }); + xhr.send(); + await promise; + checkComputedStyle("XMLHttpRequest", xhr.responseXML); + } +} +runTest() + .catch(e => ok(false, `Exception: ${e}`)) + .then(() => SimpleTest.finish()); +</script> +</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 0000000000..12b4e48b46 --- /dev/null +++ b/layout/style/test/test_computed_style_min_size_auto.html @@ -0,0 +1,129 @@ +<!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 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 or grid item, "min-width: auto" and + // "min-height: auto" both compute to "auto" (even in cases where + // we know it'll actually resolve to 0 in layout, like for example + // when the item has "overflow:hidden"). + checkElemMinSizes("horizontal-flex-item", "auto", "auto"); + checkElemMinSizes("horizontal-flex-item-OH", "auto", "auto"); + checkElemMinSizes("vertical-flex-item", "auto", "auto"); + checkElemMinSizes("vertical-flex-item-OH", "auto", "auto"); + checkElemMinSizes("grid-item", "auto", "auto"); + checkElemMinSizes("grid-item-OH", "auto", "auto"); +} + +// 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_flush.html b/layout/style/test/test_computed_style_no_flush.html new file mode 100644 index 0000000000..2caea9d294 --- /dev/null +++ b/layout/style/test/test_computed_style_no_flush.html @@ -0,0 +1,63 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + Test for bug 1363805: We only restyle as little as needed +</title> +<link rel="author" href="mailto:wpan@mozilla.com" title="Wei-Cheng Pan"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<style> +.black { + background-color: black; +} +.black + div { + background-color: gray; +} +</style> +<div id="container"> + <div> + <div id="foo"> + </div> + <div id="bar"> + </div> + </div> +</div> +<script> +function flushStyle () { + getComputedStyle(document.body).width; +} + +SimpleTest.waitForExplicitFinish(); +const utils = SpecialPowers.getDOMWindowUtils(window); +const container = document.querySelector('#container'); +const foo = document.querySelector('#foo'); +const bar = document.querySelector('#bar'); + +flushStyle(); +let currentRestyleGeneration = utils.restyleGeneration; + +// No style changed, so we should not restyle. +getComputedStyle(foo).backgroundColor; +is(utils.restyleGeneration, currentRestyleGeneration, + "Shouldn't restyle anything if no style changed"); + +// foo's parent has changed, must restyle. +container.classList.toggle('black'); +getComputedStyle(foo).backgroundColor; +isnot(utils.restyleGeneration, currentRestyleGeneration, + "Should have restyled something"); + +currentRestyleGeneration = utils.restyleGeneration; + +// The change of foo should not affect its parent. +foo.classList.toggle('black'); +getComputedStyle(container).backgroundColor; +is(utils.restyleGeneration, currentRestyleGeneration, + "Shouldn't restyle anything if no style changed"); + +// It should restyle for foo's later sibling. +getComputedStyle(bar).backgroundColor; +isnot(utils.restyleGeneration, currentRestyleGeneration, + "Should have restyled something"); + +SimpleTest.finish(); +</script> 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 0000000000..efb0dda7b4 --- /dev/null +++ b/layout/style/test/test_computed_style_no_pseudo.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=505515 +--> +<head> + <title>Test for Bug 505515</title> + <script 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 span { position: relative; display: inline-block; } + #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(p, "").color, "rgb(0, 0, 0)", + "p should be black too"); + + let spanStyle = getComputedStyle(span, ""); + let width = spanStyle.width; + + isnot(width.indexOf("px"), -1, + "should be able to get the used value") + is(width, spanStyle.width, + "shouldn't lose track of the frame"); + is(spanStyle.color, "rgb(0, 0, 0)", + "span should be black"); + + 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 0000000000..0f297477d6 --- /dev/null +++ b/layout/style/test/test_computed_style_prefs.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that preffed off properties do not appear in computed style</title> + <script 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"> + +/** 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.includes(prop), 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.includes(prop), 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.backdrop-filter.enabled": ["backdrop-filter"], +}; + +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 0000000000..e2462979c2 --- /dev/null +++ b/layout/style/test/test_condition_text.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=814907 +--> +<head> + <title>Test for Bug 814907</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <style id="style"> + @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 = [ + "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_constructable_stylesheets_chrome_only_rules_in_content.html b/layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html new file mode 100644 index 0000000000..6d80b2bb7b --- /dev/null +++ b/layout/style/test/test_constructable_stylesheets_chrome_only_rules_in_content.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<meta charset="utf-8"> +<title>Test for chrome-only rules in constructable stylesheets (in content)</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script> + add_task(async function chrome_rules_constructable_stylesheets_in_content() { + let sheet = new CSSStyleSheet(); + sheet.replaceSync(".foo { -moz-default-appearance: none }"); + is(sheet.cssRules[0].style.length, 0, "Should not parse chrome-only property in content document"); + }); + + add_task(async function chrome_rules_constructable_stylesheets_in_content() { + let sheet = new CSSStyleSheet({ baseURL: "chrome://browser/content/browser.xhtml" }) + sheet.replaceSync(".foo { -moz-default-appearance: none }"); + is(sheet.cssRules[0].style.length, 0, "Should not parse chrome-only property in content document, even with chrome baseURL"); + }); +</script> 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 0000000000..bb91b6d12c --- /dev/null +++ b/layout/style/test/test_counter_descriptor_storage.html @@ -0,0 +1,268 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test for parsing, storage and serialization of CSS @counter-style descriptor values</title> + <script 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", + symbols: "x" + }, + tests: { + system: [ + [null, "", "symbolic"], + ["fixed 0"], + ["fixed", "FixeD"], + ["fixed 1", "FixeD 1"], + ["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 0000000000..58f5763451 --- /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 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_crash_with_content_policy.html b/layout/style/test/test_crash_with_content_policy.html new file mode 100644 index 0000000000..9acec58243 --- /dev/null +++ b/layout/style/test/test_crash_with_content_policy.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtests for style system with content policy</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> +<iframe id="iframe"></iframe> +<script> +const TESTS = [ + "file_bug1381233.html", +]; + +const Cc = SpecialPowers.Cc; +const Ci = SpecialPowers.Ci; + +var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{b80e19d0-878f-d41b-2654-194714a4115c}"); +var policyName = "@mozilla.org/testpolicy;1"; +var policy = { + // nsISupports implementation + QueryInterface: function(iid) { + + iid = SpecialPowers.wrap(iid); + if (iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsIFactory) || + iid.equals(Ci.nsIContentPolicy)) + return this; + + throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE; + }, + + // nsIFactory implementation + createInstance: function(outer, iid) { + return this.QueryInterface(iid); + }, + + // nsIContentPolicy implementation + shouldLoad: function(contentLocation, loadInfo) { + info(`shouldLoad is invoked for ${SpecialPowers.wrap(contentLocation).spec}`); + return Ci.nsIContentPolicy.ACCEPT; + }, + shouldProcess: function(contentLocation, loadInfo) { + return Ci.nsIContentPolicy.ACCEPT; + } +} +policy = SpecialPowers.wrapCallbackObject(policy); + +// Register content policy +var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager + .QueryInterface(Ci.nsIComponentRegistrar); +componentManager.registerFactory(policyID, "Test content policy", policyName, policy); +var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); +categoryManager.addCategoryEntry("content-policy", policyName, policyName, false, true); + + +SimpleTest.waitForExplicitFinish(); + +async function runTests() { + let iframe = document.getElementById("iframe"); + for (let test of TESTS) { + iframe.src = test; + await new Promise(resolve => { + iframe.onload = resolve; + }); + ok(true, `${test} doesn't crash`); + } + categoryManager.deleteCategoryEntry("content-policy", policyName, false); + componentManager.unregisterFactory(policyID, policy); + SimpleTest.finish(); +} +runTests(); +</script> +</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 0000000000..8541c9c5ce --- /dev/null +++ b/layout/style/test/test_css_cross_domain.html @@ -0,0 +1,158 @@ +<!DOCTYPE HTML> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=524223 --> +<head> + <title>Test cross-domain CSS loading</title> + <script 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> +<div id="quirks-placeholder"></div> +</div> + +<div class="column"> +<h2>Standards</h2> +<div id="standards-placeholder"></div> +</div> + +<script type="application/javascript"> + +const COLOR = {red: "rgb(255, 0, 0)", lime: "rgb(0, 255, 0)"}; + +// Cross origin requests with text/html as the contentType. +// These requests will be blocked by ORB (when ORB is enabled), +// thus the color of the element is not going to be changed. +const BLOCKED_BY_ORB = ["JD1i", "JD1l", "JD2i", "JD2l"]; + +/** 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"); + + var id = cases[i].id; + // only 'quirks' can have requests that are blocked by ORB. + if (BLOCKED_BY_ORB.includes(id) && ifr.id === "quirks") { + is(color, COLOR.red, ifr.id + " " + id); + } else { + is(color, COLOR.lime, ifr.id + " " + id); + } + } +} + +SimpleTest.waitForExplicitFinish(); + +function insertIFrames(src, id) { + const quirks = document.createElement("iframe"); + quirks.src = "ccd-quirks.html"; + quirks.id = "quirks"; + document.getElementById("quirks-placeholder").replaceWith(quirks); + + const standards = document.createElement("iframe"); + standards.src = "ccd-standards.html"; + standards.id = "standards"; + document.getElementById("standards-placeholder").replaceWith(standards); +} + +var hasQuirksLoaded = false; +var hasStandardsLoaded = false; + +function quirksLoaded() { + hasQuirksLoaded = true; + MaybeRunTest(); +} + +function standardsLoaded() { + hasStandardsLoaded = true; + MaybeRunTest(); +} + +function runTest() { + check_iframe(document.getElementById("quirks")); + check_iframe(document.getElementById("standards")); +} + +function MaybeRunTest() { + if (!hasQuirksLoaded || !hasStandardsLoaded) { + return; + } + + runTest(); + SimpleTest.finish(); +} + +window.onload = async function() { + await SpecialPowers.pushPrefEnv( + { + set: [ + ['browser.opaqueResponseBlocking', true], + ['browser.opaqueResponseBlocking.javascriptValidator', true], + ], + } + ); + insertIFrames(); +}; +</script> +</body> +</html> diff --git a/layout/style/test/test_css_cross_domain_no_orb.html b/layout/style/test/test_css_cross_domain_no_orb.html new file mode 100644 index 0000000000..27ede793be --- /dev/null +++ b/layout/style/test/test_css_cross_domain_no_orb.html @@ -0,0 +1,147 @@ +<!DOCTYPE HTML> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=524223 --> +<head> + <title>Test cross-domain CSS loading</title> + <script 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> +<div id="quirks-placeholder"></div> +</div> + +<div class="column"> +<h2>Standards</h2> +<div id="standards-placeholder"></div> +</div> + +<script type="application/javascript"> + +const COLOR = {red: "rgb(255, 0, 0)", lime: "rgb(0, 255, 0)"}; + +/** 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"); + + var id = cases[i].id; + is(color, COLOR.lime, ifr.id + " " + id); + } +} + +SimpleTest.waitForExplicitFinish(); + +function insertIFrames(src, id) { + const quirks = document.createElement("iframe"); + quirks.src = "ccd-quirks.html"; + quirks.id = "quirks"; + document.getElementById("quirks-placeholder").replaceWith(quirks); + + const standards = document.createElement("iframe"); + standards.src = "ccd-standards.html"; + standards.id = "standards"; + document.getElementById("standards-placeholder").replaceWith(standards); +} + +var hasQuirksLoaded = false; +var hasStandardsLoaded = false; + +function quirksLoaded() { + hasQuirksLoaded = true; + MaybeRunTest(); +} + +function standardsLoaded() { + hasStandardsLoaded = true; + MaybeRunTest(); +} + +function runTest() { + check_iframe(document.getElementById("quirks")); + check_iframe(document.getElementById("standards")); +} + +function MaybeRunTest() { + if (!hasQuirksLoaded || !hasStandardsLoaded) { + return; + } + + runTest(); + SimpleTest.finish(); +} + +window.onload = async function() { + await SpecialPowers.pushPrefEnv( + { + set: [ + ['browser.opaqueResponseBlocking', false], + ], + } + ); + insertIFrames(); +}; +</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 0000000000..099ca4c752 --- /dev/null +++ b/layout/style/test/test_css_eof_handling.html @@ -0,0 +1,268 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for CSS EOF handling</title> + <script 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/" + } +]; + +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 0000000000..2bcc094be1 --- /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 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('\x7F\x80\x2D\x5F\xA9'), '\\7f \x80\x2D\x5F\xA9', "escapingFailed Char: \\x7F\\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 0000000000..e7e78cc545 --- /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 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 0000000000..67105d61f0 --- /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_parse_error_smoketest.html b/layout/style/test/test_css_parse_error_smoketest.html new file mode 100644 index 0000000000..96d8edce3a --- /dev/null +++ b/layout/style/test/test_css_parse_error_smoketest.html @@ -0,0 +1,160 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Test for CSS parser reporting parsing errors with expected precision</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +</head> +<body> +<style id="testbench"></style> +<script> + SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true; + // Tests that apply to all types of style sheets + var tests = [ + { + css: "@unknown {}", + error: "Unrecognized at-rule or error parsing at-rule ‘@unknown’.", + }, { + css: "x { color: invalid; }", + error: "Expected color but found ‘invalid’. Error in parsing value for ‘color’. Declaration dropped.", + cssSelectors: "x", + }, { + css: "x { filter: alpha(foo); }", + error: "Expected ‘none’, URL, or filter function but found ‘alpha(’. Error in parsing value for ‘filter’. Declaration dropped.", + cssSelectors: "x", + }, { + css: "x { color: red; abc; }", + error: "Unknown property ‘abc;’. Declaration dropped.", + cssSelectors: "x", + }, { + css: "x { filter: 5; }", + error: "Expected ‘none’, URL, or filter function but found ‘5’. Error in parsing value for ‘filter’. Declaration dropped.", + cssSelectors: "x", + }, { + css: "::unknown {}", + error: "Unknown pseudo-class or pseudo-element ‘unknown’. Ruleset ignored due to bad selector.", + }, { + css: ":unknown {}", + error: "Unknown pseudo-class or pseudo-element ‘unknown’. Ruleset ignored due to bad selector.", + }, { + css: "::5 {}", + error: "Expected identifier for pseudo-class or pseudo-element but found ‘5’. Ruleset ignored due to bad selector.", + }, { + css: ": {}", + error: "Expected identifier for pseudo-class or pseudo-element but found ‘ ’. Ruleset ignored due to bad selector.", + }, { + css: "x[a.]{}", + error: "Unexpected token in attribute selector: ‘.’. Ruleset ignored due to bad selector.", + }, { + css: "x[*a]{}", + error: "Expected ‘|’ but found ‘a’. Ruleset ignored due to bad selector.", + }, { + css: "x[a=5]{}", + error: "Expected identifier or string for value in attribute selector but found ‘5’. Ruleset ignored due to bad selector.", + }, { + css: "x[$] {}", + error: "Expected attribute name or namespace but found ‘$’. Ruleset ignored due to bad selector.", + }, { + css: "a[|5] {}", + error: "Expected identifier for attribute name but found ‘5’. Ruleset ignored due to bad selector.", + }, { + css: "a[x|] {}", + error: "Unknown namespace prefix ‘x’. Ruleset ignored due to bad selector.", + }, { + css: "x| {}", + error: "Unknown namespace prefix ‘x’. Ruleset ignored due to bad selector.", + }, { + css: "a> {}", + error: "Dangling combinator. Ruleset ignored due to bad selector.", + }, { + css: "~ {}", + error: "Selector expected. Ruleset ignored due to bad selector.", + }, { + css: "| {}", + error: "Expected element name or ‘*’ but found ‘ ’. Ruleset ignored due to bad selector.", + }, { + css: ". {}", + error: "Expected identifier for class selector but found ‘ ’. Ruleset ignored due to bad selector.", + }, { + css: ":not() {}", + error: "Selector expected. Ruleset ignored due to bad selector.", + }, { + css: "* { -webkit-text-size-adjust: 100% }", + error: "Error in parsing value for ‘-webkit-text-size-adjust’. Declaration dropped.", + cssSelectors: "*", + }, { + css: "@media (totally-unknown-feature) {}", + error: "Expected media feature name but found ‘totally-unknown-feature’.", + }, { + css: "@media \"foo\" {}", + error: "Unexpected token ‘\"foo\"’ in media list.", + }, { + css: "@media (min-width) {}", + error: "Media features with min- or max- must have a value.", + }, { + css: "@media (min-width >= 3px) {}", + error: "Unexpected operator in media list.", + }, { + css: "@media (device-height: three) {}", + error: "Found invalid value for media feature.", + }, { + css: "@media (min-width: foo) {}", + error: "Found invalid value for media feature.", + }, { + css: "@media (min-resolution: 2) {}", + error: "Found invalid value for media feature.", + }, { + css: "@media (min-monochrome: 1.1) {}", + error: "Found invalid value for media feature.", + }, { + css: "@media (min-aspect-ratio: 1 invalid) {}", + error: "Unexpected token ‘invalid’ in media list.", + }, { + css: "@media (min-aspect-ratio: 1 / invalid) {}", + error: "Found invalid value for media feature.", + }, { + css: "@media (orientation: invalid-orientation-value) {}", + error: "Found invalid value for media feature.", + }, { + css: "a, .b, #c { unknown: invalid; }", + error: "Unknown property ‘unknown’. Declaration dropped.", + cssSelectors: "a, .b, #c" + }, + { + css: ":host:hover { color: red; }", + error: ":host selector in ‘:host:hover’ is not featureless and will never match. Maybe you intended to use :host()?" + }, + ]; + + // Tests that apply only to constructed style sheets + var constructedSheetTests = [ + { + css: '@import url("sheet.css");', + error: "@import rules are not yet valid in constructed stylesheets." + } + ]; + + function assertMessages(messages, action) { + return new Promise(resolve => { + SimpleTest.expectConsoleMessages(action, messages, resolve); + }); + } + + async function runTests() { + for (let {css, cssSelectors = "", error} of tests) { + let messages = [ { cssSelectors, errorMessage: error } ]; + await assertMessages(messages, () => { testbench.innerHTML = css }); + await assertMessages(messages, () => { new CSSStyleSheet().replaceSync(css) }); + await assertMessages(messages, async () => { await new CSSStyleSheet().replace(css) }); + } + for (let {css, cssSelectors = "", error} of constructedSheetTests) { + let messages = [ { cssSelectors, errorMessage: error } ]; + await assertMessages(messages, () => { new CSSStyleSheet().replaceSync(css) }); + await assertMessages(messages, async () => { await new CSSStyleSheet().replace(css) }); + } + } + + add_task(runTests); + +</script> diff --git a/layout/style/test/test_css_supports.html b/layout/style/test/test_css_supports.html new file mode 100644 index 0000000000..a6b1e8d303 --- /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 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))", + "(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;)", + "(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 0000000000..7efc14a4fc --- /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 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(--a,/**/a))", + "(color: var(--a,))", + "(color: var(--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(--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: var(--b,))", + "(--a: var(--b,/**/))", + "(--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,!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))", + "(color: var(--", + "(color: var(--))", + + "(--a: var(--b,!))", + "(--a: var(--b,!important))", + "((--a: var(--b) !important !important))", + "(--a: var(--b,;))", + "(--a: var(--b);)", + "(--a: var(1px))", + "(--a: a))", + "(--a: \"\n", + "(--a: url(\"\n", + "(--a: var(a))", + "(--: 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,)"], + ["color", "var(--a,/**/)"], + + ["--a", " var(--b)"], + ["--a", "var(--b)"], + ["--a", "var(--b) "], + ["--a", "var( --b ) "], + ["--a", "var(--b, )"], + ["--a", "var(--b,/**/a)"], + ["--a", "var(--b,)"], + ["--a", "var(--b,/**/)"], + ["--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", ""], + ["--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 ", "\\"], + ]; + + var failingDeclarations = [ + ["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)"], + ["color", "var(--"], + + ["--a", "var(--b,!)"], + ["--a", "var(--b,!important)"], + ["--a", "var(--b) !important !important"], + ["--a", "var(--b,;)"], + ["--a", "var(--b);"], + ["--a", "var(1px)"], + ["(VAR-a", "a"], + ["--a", "a)"], + ["--a", "\"\n"], + ["--a", "url(\"\n"], + ["--a", "var(--b))"], + ["--a", "var(b)"], + ["--", "a"], + ]; + + 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_cue_restrictions.html b/layout/style/test/test_cue_restrictions.html new file mode 100644 index 0000000000..c5b3cf3bda --- /dev/null +++ b/layout/style/test/test_cue_restrictions.html @@ -0,0 +1,34 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for ::cue property restrictions.</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="property_database.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<style id="s"></style> +<video id="test"></video> +<video id="control"></video> +<script> +const test = getComputedStyle($("test"), "::cue"); +const control = getComputedStyle($("control"), "::cue"); + +for (const prop in gCSSProperties) { + const info = gCSSProperties[prop]; + if (info.type == CSS_TYPE_TRUE_SHORTHAND) + continue; + + let prereqs = ""; + if (info.prerequisites) + for (let name in info.prerequisites) + prereqs += `${name}: ${info.prerequisites[name]}; `; + + $("s").textContent = ` + #control::cue { ${prop}: ${info.initial_values[0]}; ${prereqs} } + #test::cue { ${prop}: ${info.other_values[0]}; ${prereqs} } + `; + + (info.applies_to_cue ? isnot : is)( + get_computed_value(test, prop), + get_computed_value(control, prop), + `${prop} should ${info.applies_to_cue ? "" : "not "}apply to ::cue`); +} +</script> diff --git a/layout/style/test/test_custom_content_inheritance.html b/layout/style/test/test_custom_content_inheritance.html new file mode 100644 index 0000000000..625f1ad9c3 --- /dev/null +++ b/layout/style/test/test_custom_content_inheritance.html @@ -0,0 +1,24 @@ +<!doctype html> +<title>Test for custom content inheritance</title> +<style> + html { color: red !important; } +</style> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script> +onload = function() { + let doc = SpecialPowers.wrap(document); + let div = doc.createElement('div'); + div.id = "test-id"; + ok(!!doc.insertAnonymousContent, + "Must have the insertAnonymousContent API"); + let content = doc.insertAnonymousContent(); + ok(!!content, "Must have anon content"); + content.root.appendChild(div); + let color = SpecialPowers.wrap(window).getComputedStyle(div).color; + ok(!!color, "Should be able to get a color"); + isnot(color, getComputedStyle(document.documentElement).color, + "Custom anon content shouldn't inherit from the root element"); + SimpleTest.finish(); +}; +SimpleTest.waitForExplicitFinish(); +</script> 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 0000000000..9033c9b6a7 --- /dev/null +++ b/layout/style/test/test_default_bidi_css.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + <script 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); + } + document.body.appendChild(element); + 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 0000000000..56b5863935 --- /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 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, "rgba(0, 0, 0, 0)", + "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 0000000000..27750a2bad --- /dev/null +++ b/layout/style/test/test_descriptor_storage.html @@ -0,0 +1,118 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for parsing, storage, and serialization of CSS @font-face descriptor values</title> + <script 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 + "; "; + 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); + +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 0000000000..bf73b15b64 --- /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 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_display_mode.html b/layout/style/test/test_display_mode.html new file mode 100644 index 0000000000..2fbf78bdb4 --- /dev/null +++ b/layout/style/test/test_display_mode.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1648157 +--> +<head> + <title>Test for displayMode</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="property_database.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"> + <iframe id="iframe" src="http://example.org/tests/layout/style/test/media_queries_iframe2.html"></iframe> +</p> +<pre id="test"> +<script class="testbody"> +let iframe = document.getElementById("iframe"); + +// Keep in sync with media_queries_iframe2.html +const DISPLAY_MODES_BACKGROUND_COLOR = { + 'minimal-ui': 'rgb(255, 0, 0)', + 'standalone': 'rgb(0, 128, 0)', + 'fullscreen': 'rgb(0, 0, 255)', + 'browser': 'rgb(255, 255, 0)' +}; +const DISPLAY_MODES = Object.keys(DISPLAY_MODES_BACKGROUND_COLOR); + +SimpleTest.waitForExplicitFinish(); + +window.addEventListener("load", async (event) => { + const wrappedWindow = SpecialPowers.wrap(window); + + function setDisplayMode(mode) { + wrappedWindow.browsingContext.top.displayMode = mode; + } + + async function displayModeApplies(mode) { + let responsePromise = new Promise(resolve => { + window.addEventListener("message", e => { + resolve(e.data.backgroundColor); + }, { once: true }); + }); + iframe.contentWindow.postMessage('get-background-color', '*'); + let response = await responsePromise; + let expected = DISPLAY_MODES_BACKGROUND_COLOR[mode]; + + info(`displayModeApplies: ${response} === ${expected}`); + return response === expected; + } + + async function checkIfApplies(q, shouldApply) { + let message = shouldApply ? "should apply" : "should not apply"; + is((await displayModeApplies(q)), shouldApply, `${q} ${message}`); + } + + for (let currentMode of DISPLAY_MODES) { + setDisplayMode(currentMode); + + for (let mode of DISPLAY_MODES) { + await checkIfApplies(mode, currentMode === mode); + } + } + + SimpleTest.finish(); +}); +</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 0000000000..71fc48278d --- /dev/null +++ b/layout/style/test/test_dont_use_document_colors.html @@ -0,0 +1,201 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for preference not to use document colors</title> + <script 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; 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; + } + #eight { + border: 10px solid transparent; + border-image: repeating-linear-gradient(45deg, blue, blue 1%, red 1%, red 8%) 10; + } + #nine { + border: 10px solid blue; + border-image: none; + } + + #eleven { + background-color: transparent; + } + + /* 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> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430969">Mozilla Bug 1430969</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 id="eight">I have a border-image</div> +<div id="nine">I do not have a border-image</div> + +<input id="ten" type="button" value="Hello"><!-- Nothing should match this --> + +<button id="eleven">Hello</button> +</div> +<pre id="test"> +<script class="testbody"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout('nsPresContext internally delays applying prefs using an nsITimer'); + +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")); +var cs8 = getComputedStyle(document.getElementById("eight")); +var cs9 = getComputedStyle(document.getElementById("nine")); +var cs10 = getComputedStyle(document.getElementById("ten")); +var cs11 = getComputedStyle(document.getElementById("eleven")); + +function pushPrefEnvAndWait(args, cb) { + SpecialPowers.pushPrefEnv(args).then(cb) +} + +pushPrefEnvAndWait({'set': [['browser.display.document_color_use', 1]]}, part1); + +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.columnRuleColor, cs2.columnRuleColor, + "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.columnRuleColor, cs3.columnRuleColor, + "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"); + 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"); + isnot(cs8.borderImageSource, cs9.borderImageSource, "border-image-source applies"); + pushPrefEnvAndWait({'set': [['browser.display.document_color_use', 2]]}, part2); +} + +function toRGBA(c) { + return SpecialPowers.wrap(window).InspectorUtils.colorToRGBA(c, document); +} + +function systemColor(c) { + let {r, g, b, a} = toRGBA(c); + if (a == 1) + return `rgb(${r}, ${g}, ${b})`; + // Match ColorComponentToFloat's max number of decimals (3), and remove trailing zeros. + let alphaString = a.toFixed(3); + if (alphaString.includes(".")) { + while (alphaString[alphaString.length - 1] == "0") + alphaString = alphaString.substring(0, alphaString.length - 1); + } + return `rgba(${r}, ${g}, ${b}, ${alphaString})`; +} + +function part2() +{ + isnot(cs1.backgroundColor, cs2.backgroundColor, "background-color transparency preserved (opaque)"); + is(toRGBA(cs2.backgroundColor).a, 0, "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(cs1.borderBottomColor, cs2.borderBottomColor, + "border-bottom-color is blocked"); + is(cs1.columnRuleColor, cs2.columnRuleColor, + "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, cs10.backgroundColor, "background-color transparency preserved (opaque)"); + is(cs3.color, cs10.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, systemColor("ButtonFace"), "background-color not broken on inputs"); + is(cs4.color, systemColor("ButtonText"), "color not broken on inputs"); + is(cs4.borderTopColor, systemColor("ButtonBorder"), "border-top-color not broken on inputs"); + is(cs4.borderRightColor, systemColor("ButtonBorder"), + "border-right-color not broken on inputs"); + is(cs4.borderLeftColor, systemColor("ButtonBorder"), + "border-left-color not broken on inputs"); + is(cs4.borderBottomColor, systemColor("ButtonBorder"), + "border-bottom-color not broken on inputs"); + is(cs8.borderImageSource, cs9.borderImageSource, "border-image-source is blocked"); + is(toRGBA(cs11.backgroundColor).a, 0, "background-color transparency is preserved on buttons"); + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_dont_use_document_fonts.html b/layout/style/test/test_dont_use_document_fonts.html new file mode 100644 index 0000000000..59bc6c6d62 --- /dev/null +++ b/layout/style/test/test_dont_use_document_fonts.html @@ -0,0 +1,116 @@ +<!doctype html> +<title>Test for preference to not use document fonts</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel='stylesheet' href='/resources/testharness.css'> +<div id="content"></div> +<script> +const content = document.getElementById("content"); + +// This is just a subset of browser.display.use_document_fonts.icon_font_allowlist +// that we feel are worth double-checking via this test. In particular: +// * Chromium Bug Tracker and https://developers.google.com use "Material Icons" +// * Google Translate and Google Timeline use "Material Icons Extended" +// * https://fonts.google.com/icons uses "Material Symbols Outlined" +// * Google Calendar and Google Contacts use "Google Material Icons" +const kKnownLigatureIconFonts = "Material Icons, Material Icons Extended, " + + "Material Symbols Outlined, Google Material Icons"; + +setup({explicit_done: true }) + +content.style.fontFamily = "initial"; +const kInitialFamily = getComputedStyle(content).fontFamily; +content.style.fontFamily = ""; + +const kTests = [ + { + specified: "monospace", + computed: "monospace", + description: "Single generic family should not be changed", + }, + { + specified: "monospace, sans-serif", + computed: "monospace, sans-serif", + description: "Generic families should not be changed", + }, + { + specified: "Courier, monospace", + computed: "monospace, Courier", + description: "Generics are preferred, but may still fall back to document fonts", + }, + { + specified: "system-ui, sans-serif", + computed: "sans-serif, system-ui", + description: "system-ui is not prioritized", + }, + { + specified: "Courier, something-else", + computed: `${kInitialFamily}, Courier, something-else`, + description: "Generic is prepended to the font-family if none is found", + }, + { + specified: kKnownLigatureIconFonts + ", something-else, sans-serif", + computed: kKnownLigatureIconFonts + ", sans-serif, something-else", + description: "Known ligature-icon fonts remain ahead of the generic", + }, + { + specified: "Material Icons, something-else, Material Symbols Outlined, sans-serif", + computed: "Material Icons, sans-serif, something-else, Material Symbols Outlined", + description: "Generic is moved ahead of the first non-allowlisted font", + }, + { + specified: "Material Icons, something-else, Material Symbols Outlined", + computed: `Material Icons, ${kInitialFamily}, something-else, Material Symbols Outlined`, + description: "Default generic is inserted ahead of the first non-allowlisted font", + }, + { + specified: "Material Icons, cursive, Material Symbols Outlined, serif", + computed: "Material Icons, serif, cursive, Material Symbols Outlined", + description: "cursive is not treated as a generic to be prioritized", + }, + { + specified: "Material Icons, fantasy, Material Symbols Outlined", + computed: `Material Icons, ${kInitialFamily}, fantasy, Material Symbols Outlined`, + description: "fantasy is not treated as a generic to be prioritized", + }, +]; + +let systemFont; + +// compute expectations while the pref is not active yet. +test(function() { + for (const test of kTests) { + content.style.fontFamily = ""; + content.style.fontFamily = test.computed; + assert_not_equals(content.style.fontFamily, "", `computed font ${test.computed} was invalid`); + test.expected = getComputedStyle(content).fontFamily; + } + + content.style.font = "menu"; + systemFont = getComputedStyle(content).fontFamily; + assert_not_equals(systemFont, "", `computed menu system font was invalid`); + + content.style.font = ""; +}, "Sanity"); + +function runTest({ specified, computed, description, expected }) { + test(function() { + content.style.fontFamily = ""; + content.style.fontFamily = specified; + assert_equals(getComputedStyle(content).fontFamily, expected); + }, description); +} + +(async function() { + await SpecialPowers.pushPrefEnv({'set': [['browser.display.use_document_fonts', 0]]}); + for (const test of kTests) + runTest(test); + + test(function() { + content.style.font = "menu"; + assert_equals(getComputedStyle(content).fontFamily, systemFont); + }, "System font should be honored"); + + done(); +})(); +</script> 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 0000000000..7a000c87a6 --- /dev/null +++ b/layout/style/test/test_dynamic_change_causing_reflow.html @@ -0,0 +1,1014 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1131371 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1131371</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=1131371">Mozilla Bug 1131371</a> +<style> + #elemWithScrollbars { overflow: scroll } + .flexScrollerWithTransformedItems { + display: flex; + overflow: hidden; + width: 200px; + position: relative; + } + .flexScrollerWithTransformedItems div { + min-width: 50px; + min-height: 50px; + margin: 10px; + will-change: transform; + } + .absposFlexItem { + left: 250px; + top: 0; + position: absolute; + } + #tableCell, + #tableCellWithAbsPosChild { + display: table-cell; + } + .blockmargin { + margin: 25px 0; + } +</style> +<div id="display"> + <div id="content"> + </div> + <div id="elemWithAbsPosChild"><div style="position:absolute"></div></div> + <div id="elemWithFixedPosChild"><div style="position:fixed"></div></div> + <div id="elemWithScrollbars"></div> + <div id="elemWithoutScrollbars"></div> + <div class="flexScrollerWithTransformedItems"> + <div></div> + <div></div> + <div></div> + <div></div> + <div id="flexItemMovementTarget"></div> + </div> + <div class="flexScrollerWithTransformedItems"> + <div></div> + <div></div> + <div></div> + <div id="flexItemMovementTarget2"></div> + <div class="absposFlexItem"></div> + </div> + <input id="inputElem" type="image"> + <textarea id="textareaElem"> + Some + lines + of + text + </textarea> + <select id="selectElem"> + <option>A</option> + <option>B</option> + <option>C</option> + </select> + <button id="buttonElem"> + Something + </button> + <button id="buttonElemWithAbsPosChild"><div style="position:absolute"></div></button> + <div id="tableCell"></div> + <div id="tableCellWithAbsPosChild"> + <div style="position: absolute"></div> + </div> + <div id="containNode"> + <div></div> + </div> + <div id="containNodeWithAbsPosChild"> + <div style="position: absolute"></div> + </div> + <div id="containNodeWithFixedPosChild"> + <div style="position: fixed"></div> + </div> + <div id="containNodeWithFloatingChild"> + <div style="float: left"></div> + </div> + <div id="containNodeWithMarginCollapsing" class="blockmargin"> + <div class="blockmargin"></div> + </div> + <div id="contentVisibilityNode"> + <div></div> + </div> +</div> +<pre id="test"> +<script> +"use strict"; + +/** Test for Bug 1131371 **/ + +const contentVisibilityEnabled = SpecialPowers.getBoolPref("layout.css.content-visibility.enabled"); +const elemWithAbsPosChild = document.getElementById("elemWithAbsPosChild"); +const elemWithFixedPosChild = document.getElementById("elemWithFixedPosChild"); +const elemWithScrollbars = document.getElementById("elemWithScrollbars"); +const elemWithoutScrollbars = document.getElementById("elemWithoutScrollbars"); +const inputElem = document.getElementById("inputElem"); +const textareaElem = document.getElementById("textareaElem"); +const selectElem = document.getElementById("selectElem"); +const buttonElem = document.getElementById("buttonElem"); +const buttonElemWithAbsPosChild = document.getElementById("buttonElemWithAbsPosChild"); +const tableCell = document.getElementById("tableCell"); +const tableCellWithAbsPosChild = document.getElementById("tableCellWithAbsPosChild"); + +for (let scroller of document.querySelectorAll(".flexScrollerWithTransformedItems")) { + scroller.scrollLeft = 10000; +} + +const flexItemMovementTarget = document.getElementById("flexItemMovementTarget"); +const flexItemMovementTarget2 = document.getElementById("flexItemMovementTarget2"); + +/** + * 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", + }, + + // * switching overflow between things that shouldn't create scrollframes. + { + beforeStyle: "overflow: visible", + afterStyle: "overflow: clip", + }, + + // 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 'shape-outside' on a non-floating box should not cause anything to happen. + { + beforeStyle: "shape-outside: none", + afterStyle: "shape-outside: circle()", + }, + + // * Changing 'shape-outside' should cause reflow, but not frame construction. + { + beforeStyle: "float: left; shape-outside: none", + afterStyle: "float: left; shape-outside: circle()", + expectReflow: true, + }, + + // * Changing 'overflow' on <body> should cause reflow, + // but not frame reconstruction + { + elem: document.body, + /* beforeStyle: implicitly 'overflow:visible' */ + afterStyle: "overflow: hidden", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.body, + /* beforeStyle: implicitly 'overflow:visible' */ + afterStyle: "overflow: scroll", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.body, + beforeStyle: "overflow: hidden", + afterStyle: "overflow: auto", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.body, + beforeStyle: "overflow: hidden", + afterStyle: "overflow: scroll", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.body, + beforeStyle: "overflow: hidden", + afterStyle: "overflow: visible", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.body, + beforeStyle: "overflow: auto", + afterStyle: "overflow: hidden", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.body, + beforeStyle: "overflow: visible", + afterStyle: "overflow: hidden", + expectConstruction: false, + expectReflow: true, + }, + + // * Changing 'overflow' on <html> should cause reflow, + // but not frame reconstruction + { + elem: document.documentElement, + /* beforeStyle: implicitly 'overflow:visible' */ + afterStyle: "overflow: auto", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.documentElement, + beforeStyle: "overflow: visible", + afterStyle: "overflow: auto", + expectConstruction: false, + expectReflow: true, + }, + + // * Setting 'overflow' on arbitrary node should cause reflow as well as + // frame reconstruction + { + /* beforeStyle: implicitly 'overflow:visible' */ + afterStyle: "overflow: auto", + expectConstruction: true, + expectReflow: true, + }, + { + beforeStyle: "overflow: auto", + afterStyle: "overflow: visible", + expectConstruction: true, + expectReflow: true, + }, + + // * but only reflow if we don't need to construct / unconstruct a new frame. + { + beforeStyle: "overflow: scroll", + afterStyle: "overflow: auto", + expectConstruction: false, + expectReflow: true, + }, + { + beforeStyle: "overflow: auto", + afterStyle: "overflow: scroll", + expectConstruction: false, + expectReflow: true, + }, + + { + beforeStyle: "overflow: hidden", + afterStyle: "overflow: auto", + expectConstruction: true, + expectReflow: true, + }, + { + beforeStyle: "overflow: auto", + afterStyle: "overflow: hidden", + expectConstruction: false, + expectReflow: true, + }, + { + beforeStyle: "overflow: hidden", + afterStyle: "overflow: scroll", + expectConstruction: true, + expectReflow: true, + }, + { + beforeStyle: "overflow: scroll", + afterStyle: "overflow: hidden", + expectConstruction: false, + expectReflow: true, + }, + + { + elem: elemWithoutScrollbars, + beforeStyle: "scrollbar-width: auto", + afterStyle: "scrollbar-width: none", + expectConstruction: false, + expectReflow: false, + }, + { + elem: elemWithScrollbars, + beforeStyle: "scrollbar-width: auto", + afterStyle: "scrollbar-width: none", + expectConstruction: false, + expectReflow: true, + }, + { + // Not the scrolling element, so nothing should happen. + elem: document.body, + beforeStyle: "scrollbar-width: none", + afterStyle: "scrollbar-width: auto", + expectConstruction: false, + expectReflow: false, + }, + { + // Not the scrolling element, so nothing should happen. + elem: document.body, + beforeStyle: "scrollbar-width: auto", + afterStyle: "scrollbar-width: none", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.documentElement, + beforeStyle: "scrollbar-width: none;", + afterStyle: "scrollbar-width: auto", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.documentElement, + beforeStyle: "overflow: scroll; scrollbar-width: none;", + afterStyle: "overflow: scroll; scrollbar-width: auto", + expectConstruction: false, + expectReflow: true, + }, + { + elem: document.documentElement, + beforeStyle: "overflow: scroll; scrollbar-width: auto;", + afterStyle: "overflow: scroll; scrollbar-width: none", + expectConstruction: false, + expectReflow: true, + }, + + // * Changing 'display' should cause frame construction and reflow. + { + beforeStyle: "display: inline", + afterStyle: "display: table", + expectConstruction: true, + expectReflow: true, + }, + + + // * Position changes trigger a reframe, unless whether we're a containing + // block doesn't change, in which case we just need to reflow. + { + beforeStyle: "position: static", + afterStyle: "position: absolute", + expectConstruction: true, + expectReflow: true, + }, + { + beforeStyle: "position: absolute", + afterStyle: "position: fixed", + expectConstruction: true, + expectReflow: true, + }, + { + beforeStyle: "position: relative", + afterStyle: "position: fixed", + expectConstruction: true, + expectReflow: true, + }, + + // This doesn't change whether we're a containing block because there are no + // abspos descendants. + { + afterStyle: "position: static", + beforeStyle: "position: relative", + expectReflow: true, + }, + + // This doesn't change whether we're a containing block, shouldn't reframe. + { + afterStyle: "position: sticky", + beforeStyle: "position: relative", + expectReflow: true, + }, + + // These don't change whether we're a containing block for our + // absolutely-positioned child, so shouldn't reframe. + { + elem: elemWithAbsPosChild, + afterStyle: "position: sticky", + beforeStyle: "position: relative", + expectReflow: true, + }, + { + elem: elemWithFixedPosChild, + afterStyle: "position: sticky", + beforeStyle: "position: relative", + expectReflow: true, + }, + { + elem: elemWithFixedPosChild, + afterStyle: "position: static", + beforeStyle: "position: relative", + expectReflow: true, + }, + { + elem: elemWithFixedPosChild, + afterStyle: "position: static", + beforeStyle: "position: sticky", + expectReflow: true, + }, + { + // Even if we're a scroll frame. + elem: elemWithFixedPosChild, + afterStyle: "position: static; overflow: auto;", + beforeStyle: "position: relative; overflow: auto;", + expectReflow: true, + }, + { + elem: tableCell, + afterStyle: "position: static;", + beforeStyle: "position: relative;", + expectReflow: true, + }, + { + elem: tableCell, + afterStyle: "filter: none", + beforeStyle: "filter: saturate(1)", + expectReflow: false, + }, + + // These ones do though. + { + elem: elemWithAbsPosChild, + afterStyle: "position: static", + beforeStyle: "position: relative", + expectConstruction: true, + expectReflow: true, + }, + { + elem: elemWithAbsPosChild, + afterStyle: "position: static", + beforeStyle: "position: sticky", + expectConstruction: true, + expectReflow: true, + }, + { + elem: elemWithAbsPosChild, + afterStyle: "position: static; overflow: auto;", + beforeStyle: "position: relative; overflow: auto;", + expectConstruction: true, + expectReflow: true, + }, + { + elem: tableCellWithAbsPosChild, + afterStyle: "position: static;", + beforeStyle: "position: relative;", + expectConstruction: true, + expectReflow: true, + }, + + // Adding transform to a scrollframe without abspos / fixedpos children shouldn't reframe. + { + elem: elemWithScrollbars, + afterStyle: "transform: translateX(1px)", + expectConstruction: false, + expectReflow: false, + }, + + // <select> can't contain abspos / floating children so shouldn't reframe + // when changing containing block-ness. + { + elem: selectElem, + afterStyle: "transform: translateX(1px)", + expectConstruction: false, + expectReflow: false, + }, + { + elem: selectElem, + afterStyle: "position: relative", + expectConstruction: false, + expectReflow: true, + }, + + // <button> shouldn't be reframed either in the absence of positioned descendants. + { + elem: buttonElem, + afterStyle: "transform: translateX(1px)", + expectConstruction: false, + expectReflow: false, + }, + { + elem: buttonElem, + afterStyle: "position: relative", + expectConstruction: false, + expectReflow: true, + }, + { + elem: buttonElemWithAbsPosChild, + afterStyle: "position: relative", + expectConstruction: true, + expectReflow: true, + }, + // changing scroll-behavior should not cause reflow or frame construction + { + elem: document.documentElement, + /* beforeStyle: implicitly 'scroll-behavior: auto' */ + afterStyle: "scroll-behavior: smooth", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.documentElement, + beforeStyle: "scroll-behavior: smooth", + afterStyle: "scroll-behavior: auto", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.body, + /* beforeStyle: implicitly 'scroll-behavior: auto' */ + afterStyle: "scroll-behavior: smooth", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.body, + beforeStyle: "scroll-behavior: smooth", + afterStyle: "scroll-behavior: auto", + expectConstruction: false, + expectReflow: false, + }, + // changing scroll-snap-type should not cause reflow or frame construction + { + elem: document.documentElement, + /* beforeStyle: implicitly 'scroll-snap-type: none' */ + afterStyle: "scroll-snap-type: y mandatory", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.documentElement, + /* beforeStyle: implicitly 'scroll-snap-type: none' */ + afterStyle: "scroll-snap-type: x proximity", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.documentElement, + beforeStyle: "scroll-snap-type: y mandatory", + afterStyle: "scroll-snap-type: none", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.body, + /* beforeStyle: implicitly 'scroll-snap-type: none' */ + afterStyle: "scroll-snap-type: y mandatory", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.body, + /* beforeStyle: implicitly 'scroll-snap-type: none' */ + afterStyle: "scroll-snap-type: x proximity", + expectConstruction: false, + expectReflow: false, + }, + { + elem: document.body, + beforeStyle: "scroll-snap-type: y mandatory", + afterStyle: "scroll-snap-type: none", + expectConstruction: false, + expectReflow: false, + }, + { + elem: inputElem, + beforeStyle: "overflow: auto", + afterStyle: "overflow: hidden", + expectConstruction: false, + expectReflow: true, + }, + { + elem: textareaElem, + beforeStyle: "overflow: auto", + afterStyle: "overflow: hidden", + expectConstruction: false, + expectReflow: true, + }, + { + elem: flexItemMovementTarget, + beforeStyle: "transform: translateX(0)", + afterStyle: "transform: translateX(-100px)", + expectConstruction: false, + expectReflow: false, + }, + { + elem: flexItemMovementTarget2, + beforeStyle: "transform: translateX(0)", + afterStyle: "transform: translateX(-100px)", + expectConstruction: false, + expectReflow: false, + }, + // Style containment affects counters so we need to re-frame. + // For other containments, we only need to reflow. + { + elem: containNode, + beforeStyle: "contain: none", + afterStyle: "contain: style", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNode, + beforeStyle: "contain: style", + afterStyle: "contain: none", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNode, + beforeStyle: "contain: none", + afterStyle: "contain: paint", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNode, + beforeStyle: "contain: paint", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNode, + beforeStyle: "contain: none", + afterStyle: "contain: layout", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNode, + beforeStyle: "contain: layout", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNode, + beforeStyle: "contain: none", + afterStyle: "contain: size", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNode, + beforeStyle: "contain: size", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + // paint/layout containment boxes establish an absolute positioning + // containing block, so need a re-frame to handle abs/fixed positioned boxes. + { + elem: containNodeWithAbsPosChild, + beforeStyle: "contain: none", + afterStyle: "contain: paint", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNodeWithAbsPosChild, + beforeStyle: "contain: paint", + afterStyle: "contain: none", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNodeWithAbsPosChild, + beforeStyle: "contain: none", + afterStyle: "contain: layout", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNodeWithAbsPosChild, + beforeStyle: "contain: layout", + afterStyle: "contain: none", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNodeWithAbsPosChild, + beforeStyle: "contain: none", + afterStyle: "contain: size", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithAbsPosChild, + beforeStyle: "contain: size", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithFixedPosChild, + beforeStyle: "contain: none", + afterStyle: "contain: paint", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNodeWithFixedPosChild, + beforeStyle: "contain: paint", + afterStyle: "contain: none", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNodeWithFixedPosChild, + beforeStyle: "contain: none", + afterStyle: "contain: layout", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNodeWithFixedPosChild, + beforeStyle: "contain: layout", + afterStyle: "contain: none", + expectConstruction: true, + expectReflow: true, + }, + { + elem: containNodeWithFixedPosChild, + beforeStyle: "contain: none", + afterStyle: "contain: size", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithFixedPosChild, + beforeStyle: "contain: size", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + // paint/layout containment boxes establish an independent formatting context + // but floats and margin collapsing can be handled without reconstruction. + { + elem: containNodeWithFloatingChild, + beforeStyle: "contain: none", + afterStyle: "contain: paint", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithFloatingChild, + beforeStyle: "contain: paint", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithFloatingChild, + beforeStyle: "contain: none", + afterStyle: "contain: layout", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithFloatingChild, + beforeStyle: "contain: layout", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithFloatingChild, + beforeStyle: "contain: none", + afterStyle: "contain: size", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithFloatingChild, + beforeStyle: "contain: size", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithMarginCollapsing, + beforeStyle: "contain: none", + afterStyle: "contain: paint", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithMarginCollapsing, + beforeStyle: "contain: paint", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithMarginCollapsing, + beforeStyle: "contain: none", + afterStyle: "contain: layout", + expectConstruction: false, + expectReflow: true, + }, + { + elem: containNodeWithMarginCollapsing, + beforeStyle: "contain: layout", + afterStyle: "contain: none", + expectConstruction: false, + expectReflow: true, + }, + // content-visibility: auto/hidden implies style containment contrary to + // content-visibility: visible, so we generally need a re-frame (see above) + // when going from one case to the other. + { + elem: contentVisibilityNode, + beforeStyle: "content-visibility: visible", + afterStyle: "content-visibility: hidden", + expectConstruction: contentVisibilityEnabled, + expectReflow: contentVisibilityEnabled, + }, + { + elem: contentVisibilityNode, + beforeStyle: "content-visibility: hidden", + afterStyle: "content-visibility: visible", + expectConstruction: contentVisibilityEnabled, + expectReflow: contentVisibilityEnabled, + }, + { + elem: contentVisibilityNode, + beforeStyle: "content-visibility: visible", + afterStyle: "content-visibility: auto", + expectConstruction: contentVisibilityEnabled, + expectReflow: contentVisibilityEnabled, + }, + { + elem: contentVisibilityNode, + beforeStyle: "content-visibility: auto", + afterStyle: "content-visibility: visible", + expectConstruction: contentVisibilityEnabled, + expectReflow: contentVisibilityEnabled, + }, + { + elem: contentVisibilityNode, + beforeStyle: "content-visibility: hidden", + afterStyle: "content-visibility: auto", + expectConstruction: false, + expectReflow: contentVisibilityEnabled, + }, + { + elem: contentVisibilityNode, + beforeStyle: "content-visibility: auto", + afterStyle: "content-visibility: hidden", + expectConstruction: false, + expectReflow: contentVisibilityEnabled, + }, + // However that's not the case if we force style containment explicitly. + { + elem: contentVisibilityNode, + beforeStyle: "content-visibility: visible; contain: style", + afterStyle: "content-visibility: hidden; contain: style", + expectConstruction: false, + expectReflow: contentVisibilityEnabled, + }, + { + elem: contentVisibilityNode, + beforeStyle: "content-visibility: hidden; contain: style", + afterStyle: "content-visibility: visible; contain: style", + expectConstruction: false, + expectReflow: contentVisibilityEnabled, + }, +]; + +// 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; + } + + // Figure out which element we'll be tweaking (defaulting to gElem) + let elem = aTestcase.elem ? aTestcase.elem : gElem; + + // Verify that 'style' attribute is unset (avoid causing ourselves trouble): + const oldStyle = elem.getAttribute("style"); + + // 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) { + elem.setAttribute("style", aTestcase.beforeStyle); + msgPrefix += "from '" + aTestcase.beforeStyle + "' "; + } + msgPrefix += "to '" + aTestcase.afterStyle + "' "; + msgPrefix += "on " + elem.nodeName + " "; + + // Establish initial counts: + let unusedVal = elem.offsetHeight; // flush layout + let origFramesConstructed = gUtils.framesConstructed; + let origFramesReflowed = gUtils.framesReflowed; + + // Make the change and flush: + elem.setAttribute("style", aTestcase.afterStyle); + unusedVal = elem.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! + if (oldStyle) { + elem.setAttribute("style", oldStyle); + } else { + elem.removeAttribute("style"); + } + + unusedVal = elem.offsetHeight; // flush layout +} + +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 0000000000..765818bae4 --- /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 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 0000000000..34c63b3626 --- /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 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"> + +/* + * 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_first_letter_restrictions.html b/layout/style/test/test_first_letter_restrictions.html new file mode 100644 index 0000000000..99aaba8b93 --- /dev/null +++ b/layout/style/test/test_first_letter_restrictions.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1382786 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1382786</title> + <script 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 id="s"></style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<div id="test"></div> +<div id="control"></div> +<script type="application/javascript"> + +/** Test for Bug 1382786 **/ +var test = getComputedStyle($("test"), "::first-letter"); +var control = getComputedStyle($("control"), "::first-letter"); +for (let prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (info.type == CSS_TYPE_TRUE_SHORTHAND) { + // Can't get useful info out of getComputedStyle. + continue; + } + let prereqs = ""; + if (info.prerequisites) { + for (let name in info.prerequisites) { + prereqs += `${name}: ${info.prerequisites[name]}; `; + } + } + $("s").textContent = ` + #control::first-letter { ${prop}: ${info.initial_values[0]}; ${prereqs} } + #test::first-letter { ${prop}: ${info.other_values[0]}; ${prereqs} } + `; + if (info.applies_to_first_letter) { + isnot(get_computed_value(test, prop), + get_computed_value(control, prop), + `${prop} should apply to ::first-letter`); + } else { + is(get_computed_value(test, prop), + get_computed_value(control, prop), + `${prop} should not apply to ::first-letter`); + } +} + +</script> +</body> +</html> diff --git a/layout/style/test/test_first_line_restrictions.html b/layout/style/test/test_first_line_restrictions.html new file mode 100644 index 0000000000..bb8e116e8b --- /dev/null +++ b/layout/style/test/test_first_line_restrictions.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1382786 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1382786</title> + <script 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 id="s"></style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<div id="test"></div> +<div id="control"></div> +<script type="application/javascript"> + +/** Test for Bug 1382786 **/ +var test = getComputedStyle($("test"), "::first-line"); +var control = getComputedStyle($("control"), "::first-line"); +for (let prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (info.type == CSS_TYPE_TRUE_SHORTHAND) { + // Can't get useful info out of getComputedStyle. + continue; + } + let prereqs = ""; + if (info.prerequisites) { + for (let name in info.prerequisites) { + prereqs += `${name}: ${info.prerequisites[name]}; `; + } + } + $("s").textContent = ` + #control::first-line { ${prop}: ${info.initial_values[0]}; ${prereqs} } + #test::first-line { ${prop}: ${info.other_values[0]}; ${prereqs} } + `; + if (info.applies_to_first_line) { + isnot(get_computed_value(test, prop), + get_computed_value(control, prop), + `${prop} should apply to ::first-line`); + } else { + is(get_computed_value(test, prop), + get_computed_value(control, prop), + `${prop} should not apply to ::first-line`); + } +} + +</script> +</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 0000000000..7ba870c69f --- /dev/null +++ b/layout/style/test/test_flexbox_child_display_values.xhtml @@ -0,0 +1,178 @@ +<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 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"> +<![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", + ]; + + 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 0000000000..fc5090fd61 --- /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 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 0000000000..b8416403b6 --- /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 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"> +"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": "20000000", + }, + + // 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": "fit-content", + "flex-basis": "fit-content", + }, + { + "flex": "calc(5px + 6px)", + "flex-basis": "11px", + }, + { + "flex": "calc(15% + 30px)", + "flex-basis": "calc(15% + 30px)", + }, + + // 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 fit-content", + "flex-grow": "5", + "flex-basis": "fit-content", + }, + { + "flex": "calc(5% + 10px) 3", + "flex-grow": "3", + "flex-basis": "calc(5% + 10px)", + }, + + // 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).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_focus_order.html b/layout/style/test/test_flexbox_focus_order.html new file mode 100644 index 0000000000..0c1f023e3c --- /dev/null +++ b/layout/style/test/test_flexbox_focus_order.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=812687 +--> +<head> + <title>Test for Bug 812687: focus order of reordered flex items</title> + <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> + .container { display: flex; } + + #focus1 { background: yellow; } + #focus2 { background: lightgray; } + #focus3 { background: orange; } + #focus4 { background: lightblue; } + #focus5 { background: pink; } + </style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=812687">Mozilla Bug 812687</a> +<p id="display"> + <a href="#" id="beforeContainer">Link before container</a> + <!-- This flex container's children are reordered visually with the "order" + CSS property, but their focus order (when tabbing through them) should + match their DOM order. So, #focus1 should receive focus before the other + flex items, even though it isn't visually the first flex item. And so + on, up to #focus5, which is visually first (due to its negative "order" + value) but should be focused last (due to being last in the DOM). --> + <div class="container"> + <a href="#" id="focus1">1</a> + <div><a href="#" id="focus2">2</a></div> + <div style="order: 100"><a href="#" id="focus3">3</a></div> + <div><a href="#" id="focus4">4</a></div> + <a href="#" id="focus5" style="order: -1">5</a> + </div> +</p> +<div id="content" style="display: none"></div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 812687 **/ + +const gExpectedFocusedIds = [ + "focus1", + "focus2", + "focus3", + "focus4", + "focus5" +]; + +function doTest() { + // First, we focus something just before the flex container: + document.getElementById('beforeContainer').focus(); + is(document.activeElement, document.getElementById('beforeContainer'), + "explicitly-focused link should gain focus"); + + // And then we advance focus across the focusable things in the flex container + // and check that we traverse them in the expected order: + for (let expectedId of gExpectedFocusedIds) { + synthesizeKey("KEY_Tab"); + is(document.activeElement, document.getElementById(expectedId), + "expecting element '#" + expectedId + "' to be focused"); + } + + SimpleTest.finish(); +} + +// Before we start, we have to bump Mac to make its 'tab'-key focus behavior +// predicatble: +function begin() { + SpecialPowers.pushPrefEnv({ set: [[ "accessibility.tabfocus", 7 ]] }, doTest); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(begin); + +</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 0000000000..49ee287aa2 --- /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 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"> +"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).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 0000000000..64b5431da8 --- /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 src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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"> +"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 0000000000..bf4c99aa76 --- /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 src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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"> +"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 0000000000..2423d5d6d6 --- /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 src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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"> +"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 0000000000..a8f4913f7d --- /dev/null +++ b/layout/style/test/test_flexbox_reflow_counts.html @@ -0,0 +1,199 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1142686 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1142686</title> + <script 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"> +"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"); + +// This cleanup helper-function removes all children from 'parent' +// except for 'childToPreserve' (if specified) +function removeChildrenExcept(parent, childToPreserve) +{ + if (childToPreserve && childToPreserve.parentNode != parent) { + // This is just a sanity/integrity-check -- if this fails, it's probably a + // bug in this test rather than in the code. I'm not checking this via + // e.g. "is(childToPreserve.parentNode, parent)", because this *should* + // always pass, and each "pass" is not interesting here since it's a + // sanity-check. It's only interesting/noteworthy if it fails. So, to + // avoid bloating this test's passed-subtest-count & output, we only bother + // reporting on this in the case where something's wrong. + ok(false, "bug in test; 'childToPreserve' should be child of 'parent'"); + } + + // For simplicity, we just remove *all children* and then reappend + // childToPreserve as the sole child. + while (parent.firstChild) { + parent.removeChild(parent.firstChild); + } + if (childToPreserve) { + parent.appendChild(childToPreserve); + } +} + +// Appends 'childCount' new children to 'parent' +function addNChildren(parent, childCount) +{ + for (let i = 0; i < childCount; i++) { + let newChild = document.createElement("div"); + // Give the new child some text so it's got a nonzero content-size: + newChild.append("a"); + parent.appendChild(newChild); + } +} + +// Undoes whatever styling customizations and DOM insertions that a given +// testcase has done, to prepare for running the next testcase. +function cleanup() +{ + gOuterFlex.style = gMidFlex.style = gInnerBlock.style = ""; + removeChildrenExcept(gInnerBlock); + removeChildrenExcept(gMidFlex, gInnerBlock); + removeChildrenExcept(gOuterFlex, gMidFlex); +} + +// 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() { + gMidFlex.style.border = gInnerBlock.style.border = "3px solid black"; + }, + }, + { + label : "padding on flex items", + addTestStyle : function() { + gMidFlex.style.padding = gInnerBlock.style.padding = "5px"; + }, + }, + { + label : "margin on flex items", + addTestStyle : function() { + gMidFlex.style.margin = gInnerBlock.style.margin = "2px"; + }, + }, + { + // When we make a change in one flex item, the number of reflows should not + // scale with its number of siblings (as long as those siblings' sizes + // aren't impacted by the change): + label : "additional flex items in outer flex container", + + // Compare 5 bonus flex items vs. 1 bonus flex item: + addTestStyle : function() { + addNChildren(gOuterFlex, 5); + }, + addReferenceStyle : function() { + addNChildren(gOuterFlex, 1); + }, + }, + { + // (As above, but now the bonus flex items are one step deeper in the tree, + // on the nested flex container rather than the outer one) + label : "additional flex items in nested flex container", + addTestStyle : function() { + addNChildren(gMidFlex, 5); + }, + addReferenceStyle : function() { + addNChildren(gMidFlex, 1); + }, + }, +]; + +// 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_flushing_frame.html b/layout/style/test/test_flushing_frame.html new file mode 100644 index 0000000000..fa6d751647 --- /dev/null +++ b/layout/style/test/test_flushing_frame.html @@ -0,0 +1,40 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + Test for bug 1545516: We don't flush layout unnecessarily on the parent + document when the frame is already disconnected. +</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<div id="content"></div> +<script> + SimpleTest.waitForExplicitFinish(); + const iframe = document.createElement("iframe"); + const content = document.querySelector("#content"); + const parentUtils = SpecialPowers.getDOMWindowUtils(window); + iframe.onload = function() { + const win = iframe.contentWindow; + iframe.offsetTop; // flush layout + content.style.display = "inline"; // Dirty style with something that will reframe. + + const previousConstructCount = parentUtils.framesConstructed; + let pagehideRan = false; + win.addEventListener("pagehide", function() { + pagehideRan = true; + win.foo = win.innerWidth; + is(parentUtils.framesConstructed, previousConstructCount, "innerWidth shouldn't have flushed parent document layout") + win.bar = win.document.documentElement.offsetHeight; + is(parentUtils.framesConstructed, previousConstructCount, "offsetHeight shouldn't have flushed parent document layout") + win.baz = win.getComputedStyle(win.document.documentElement).color; + is(parentUtils.framesConstructed, previousConstructCount, "getComputedStyle shouldn't have flushed parent document layout") + }); + + iframe.remove(); // Remove the iframe + is(pagehideRan, true, "pagehide handler should've ran"); + is(parentUtils.framesConstructed, previousConstructCount, "Nothing should've flushed the parent document layout yet"); + content.offsetTop; + isnot(parentUtils.framesConstructed, previousConstructCount, "We should've flushed layout now"); + SimpleTest.finish(); + }; + document.body.appendChild(iframe); +</script> diff --git a/layout/style/test/test_font_face_cascade.html b/layout/style/test/test_font_face_cascade.html new file mode 100644 index 0000000000..0d98f9d606 --- /dev/null +++ b/layout/style/test/test_font_face_cascade.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<title>Test that @font-face rules from different origins cascade correctly</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +<script> +let io = SpecialPowers.Cc["@mozilla.org/network/io-service;1"] + .getService(SpecialPowers.Ci.nsIIOService); + +let utils = SpecialPowers.getDOMWindowUtils(window); + +function load_sheet(sheet_text, level) { + if (level != "AGENT_SHEET" && level != "USER_SHEET" && level != "AUTHOR_SHEET") { + throw "unknown level"; + } + + let uri = io.newURI("data:text/css," + encodeURI(sheet_text)); + utils.loadSheet(uri, utils[level]); +} + +load_sheet( + "@font-face { font-family: TestAgent; src: url(about:invalid); }", + "AGENT_SHEET"); + +load_sheet( + "@font-face { font-family: TestAuthor; src: url(about:invalid); }", + "AUTHOR_SHEET"); + +load_sheet( + "@font-face { font-family: TestUser; src: url(about:invalid); }", + "USER_SHEET"); + +is([...document.fonts].map(f => f.family).join(" "), + 'TestAuthor', + "document.fonts only contains author @font-face rules"); +</script> 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 0000000000..448db4ff14 --- /dev/null +++ b/layout/style/test/test_font_face_parser.html @@ -0,0 +1,386 @@ +<!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 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\"), 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\")" } }, + + { rule: _("src: url(\"/fonts/Mouse\") format(truetype);"), + d: { "src" : "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\",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: {} }, + { rule: _("src: url(\"/fonts/Mouse\") format(\"truetype\", \"opentype\");"), 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+590-5ff;"), + d: { "unicode-range" : "U+590-5FF" }, 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: {} }, + { rule: _("unicode-range: U+A0000-12FFFF"), d: {} }, + { rule: _("unicode-range: U+??????"), 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 invalid + { rule: _("unicode-range: U+A5, U+90-30"), d: {} }, + { rule: _("unicode-range: U+A5, U+220043"), d: {} }, + + // 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; + + is(s.getPropertyValue("pointless"), "", "Unknown descriptors don't assert"); + + // 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(); + +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 0000000000..59bedc0b94 --- /dev/null +++ b/layout/style/test/test_font_family_parsing.html @@ -0,0 +1,272 @@ +<!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 }, + { 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_family_serialization.html b/layout/style/test/test_font_family_serialization.html new file mode 100644 index 0000000000..ad922158f5 --- /dev/null +++ b/layout/style/test/test_font_family_serialization.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<link rel="author" title="Xidorn Quan" href="https://www.upsuper.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<div id="display"></div> +<script> +// This cannot be a web-platform test because this doesn't match what +// the spec says at the moment. Specifically, the spec wants to have +// all font family serialized to string, while in practice, all browsers +// serialize simple them to identifiers in some cases. +// We want to check our current behavior. This can be changed once +// browsers have an agreement on the exact behavior to spec. + +// format: [input, expected serialization] +const tests = [ + // Basic cases + ['simple', 'simple'], + [' simple ', 'simple'], + ['multi idents font', 'multi idents font'], + [' multi idents font ', 'multi idents font'], + ['"wrapped name"', '"wrapped name"'], + ['" wrapped name "', '" wrapped name "'], + + // Special whitespaces + ['\\ leading ws', '" leading ws"'], + [' \\ leading ws', '" leading ws"'], + ['\\ \\ leading ws', '" leading ws"'], + [' \\ \\ leading ws', '" leading ws"'], + ['\\ \\ \\ leading ws', '" leading ws"'], + ['trailing ws\\ ', '"trailing ws "'], + ['trailing ws\\ ', '"trailing ws "'], + ['trailing ws \\ ', '"trailing ws "'], + ['trailing ws\\ \\ ', '"trailing ws "'], + ['escaped\\ ws', '"escaped ws"'], + ['escaped\\ ws', '"escaped ws"'], + ['escaped\\ \\ ws', '"escaped ws"'], + ['escaped \\ ws', '"escaped ws"'], + ['escaped \\ ws', '"escaped ws"'], + ['escaped number\\ 5', '"escaped number 5"'], +]; + +let el = document.getElementById("display"); +for (let [input, expected] of tests) { + test(function() { + el.style.fontFamily = input; + assert_equals(el.style.fontFamily, expected); + }, "Reserialization for " + JSON.stringify(input)); +} +</script> 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 0000000000..7b2a0d9e1b --- /dev/null +++ b/layout/style/test/test_font_loading_api.html @@ -0,0 +1,1895 @@ +<!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="runTest()"> +<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) { + 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 flushStyles() { + getComputedStyle(document.body).width; +} + +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 is resolved with the + // document.fonts FontFaceSet. + var p = Promise.resolve(); + sourceDocuments.forEach(function({ doc, what }) { + p = p.then(_ => { return doc.fonts.ready }).then(function() { + return is_resolved_with(doc.fonts.ready, doc.fonts, "document.fonts.ready resolves with 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() { + + // (TEST 18) Test passing valid url() source strings to the FontFace + // constructor. + var p = Promise.resolve(); + + // The sub-test is very fragile on Android platform, see Bug 1455824, + // especially Comment 34. + if (navigator.appVersion.includes("Android")) { + return p; + } + + 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(); + flushStyles(); + + 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 faces = []; + sourceWindows.forEach(function({ win, what: whatWin }, index) { + var f = new win.FontFace("test" + index, "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 }) { + let allFonts = Array.from(doc.fonts); + is(expected[what].length, allFonts.length, "expected FontFaceSet size (TEST 48) (" + what + ")"); + for (let i = 0; i < expected[what].length; i++) { + is(expected[what][i], allFonts[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(); + + }); +} + +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 0000000000..95882d1591 --- /dev/null +++ b/layout/style/test/test_garbage_at_end_of_declarations.html @@ -0,0 +1,156 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test handling of garbage at the end of CSS declarations</title> + <script 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"> + +/* eslint-disable dot-notation */ +/** 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; + +/* + * 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 } +}; + +if (IsCSSPropertyPrefEnabled("layout.css.prefixes.transitions")) { + gAllowsExtraUnusual["-moz-transition"] = gAllowsExtraUnusual["transition"]; +} +if (IsCSSPropertyPrefEnabled("layout.css.prefixes.animations")) { + 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"); + 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(); +SimpleTest.requestLongerTimeout(2); +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 0000000000..68a183606c --- /dev/null +++ b/layout/style/test/test_grid_computed_values.html @@ -0,0 +1,113 @@ +<!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() { + const expectedCols = SpecialPowers.getBoolPref("layout.css.serialize-grid-implicit-tracks") + ? "10px 10px 10px" + : "none"; + const expectedRows = SpecialPowers.getBoolPref("layout.css.serialize-grid-implicit-tracks") + ? "400px" + : "none"; + + assert_equals(getComputedStyle(gridElement).gridTemplateColumns, + expectedCols); + assert_equals(getComputedStyle(gridElement).gridTemplateRows, + expectedRows); + }, "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 0000000000..1b7a434208 --- /dev/null +++ b/layout/style/test/test_grid_container_shorthands.html @@ -0,0 +1,271 @@ +<!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 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: "repeat(1, 20px)", + }, + { + specified: "40px/[a]repeat(1, 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] repeat(1, 20px)", + }, + { + specified: "40px/repeat(1, [a] 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "repeat(1, [a] 20px)", + }, + { + specified: "40px/[a]repeat(2, [b]20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] repeat(2, [b] 20px)", + }, + { + specified: "40px/[a]repeat(2, 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] repeat(2, 20px)", + }, + { + specified: "40px/repeat(2, [a] 20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "repeat(2, [a] 20px)", + }, + { + specified: "40px/[a]repeat(2, [b]20px)", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] repeat(2, [b] 20px)", + }, + { + specified: "40px/repeat(2, 20px[a])", + gridTemplateRows: "40px", + gridTemplateColumns: "repeat(2, 20px [a])", + }, + { + specified: "40px/repeat(2, 20px[a]) [b]", + gridTemplateRows: "40px", + gridTemplateColumns: "repeat(2, 20px [a]) [b]", + }, + { + specified: "40px/repeat(2, [a] 20px[b]) [c]", + gridTemplateRows: "40px", + gridTemplateColumns: "repeat(2, [a] 20px [b]) [c]", + }, + { + specified: "40px/[a] repeat(3, [b c] 20px [d] 100px [e f]) [g]", + gridTemplateRows: "40px", + gridTemplateColumns: "[a] repeat(3, [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 / subgrid", + gridTemplateColumns: "subgrid", + gridTemplateRows: "subgrid", + }, + { + specified: "subgrid [foo] / subgrid", + gridTemplateColumns: "subgrid", + gridTemplateRows: "subgrid [foo]", + }, + { + specified: "subgrid [foo] repeat(3, [] [a b] [c]) / subgrid", + gridTemplateColumns: "subgrid", + gridTemplateRows: "subgrid [foo] repeat(3, [] [a b] [c])", + }, + { + // Test that the number of lines is clamped to kMaxLine = 10000. + // https://drafts.csswg.org/css-grid/#overlarge-grids + specified: "subgrid [foo] repeat(999999999, [a]) / subgrid", + gridTemplateColumns: "subgrid", + gridTemplateRows: "subgrid [foo] repeat(10000, [a])", + }, + { + specified: "subgrid [bar]/ subgrid [] [foo", + gridTemplateColumns: "subgrid [] [foo]", + gridTemplateRows: "subgrid [bar]", + }, + { + 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: "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: "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 0000000000..a50be6112d --- /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 0000000000..b2d32b9364 --- /dev/null +++ b/layout/style/test/test_grid_shorthand_serialization.html @@ -0,0 +1,221 @@ +<!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 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: "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: "", + }, + { + gridTemplateAreas: "\"a a\"", + gridTemplateColumns: "repeat(2, 100px)", + gridTemplateRows: "auto", + shorthand: "", + }, + // 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", + }, + { + 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", + }, + // Test that grid-gap properties don't affect serialization. + { + gridRowGap: "1px", + shorthand: "none", + }, + { + gridColumnGap: "1px", + shorthand: "none", + }, +]); + +var grid_important_test_cases = [ + { + "grid-auto-flow": "row", + 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"]); + +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"]); + +</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 0000000000..85edc2a1a0 --- /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_on_part.html b/layout/style/test/test_hover_on_part.html new file mode 100644 index 0000000000..fc39dcc307 --- /dev/null +++ b/layout/style/test/test_hover_on_part.html @@ -0,0 +1,52 @@ +<!doctype html> +<title>Shadow parts are invalidated correctly when only a pseudo-class state to the right of the part matches</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<style> + div { + width: 100px; + height: 100px; + } + #host::part(p) { + background-color: red; + } + #host::part(p):hover { + background-color: lime; + } +</style> +<div id="host"></div> +<div id="random-element-to-force-change"></div> +<script> +SimpleTest.waitForExplicitFinish(); + +let host = document.getElementById("host"); +host.attachShadow({ mode: "open" }).innerHTML = ` + <style> + div { + width: 100px; + height: 100px; + } + </style> + <div part=p></div> +`; + +let part = host.shadowRoot.querySelector("div"); +let other = document.getElementById("random-element-to-force-change"); + +SimpleTest.waitForFocus(function() { + synthesizeMouseAtCenter(other, {type: "mousemove"}); + is( + getComputedStyle(part).backgroundColor, + "rgb(255, 0, 0)", + "Part is red" + ); + + synthesizeMouseAtCenter(part, {type: "mousemove"}); + is( + getComputedStyle(part).backgroundColor, + "rgb(0, 255, 0)", + "Part is lime" + ); + SimpleTest.finish(); +}); +</script> diff --git a/layout/style/test/test_hover_quirk.html b/layout/style/test/test_hover_quirk.html new file mode 100644 index 0000000000..61e19f2a60 --- /dev/null +++ b/layout/style/test/test_hover_quirk.html @@ -0,0 +1,118 @@ +<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"; + } + + #content .parent .child::after { + content: "wrong" !important; + } + + #content .parent:hover .child::after { + content: "any child" !important; + } + + /* 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; + } + + #dynamic-test { + width: 100px; + height: 100px; + background: green; + } + + #dynamic-test > * { + width: 100%; + height: 100%; + background: red; + } + + #dynamic-test:hover > * { + background: rgb(0, 255, 0); + } + </style> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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); + test(document.getElementById("div"), false); + // Dynamic change test. + // Trigger the :hover pseudo-class. + synthesizeMouseAtCenter(document.getElementById('dynamic-test'), {type: "mousemove"}); + is(getComputedStyle(document.getElementById('should-be-green-on-hover')).backgroundColor, + "rgb(0, 255, 0)", + "Dynamic change should invalidate properly"); + 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="dynamic-test"> + <div id="should-be-green-on-hover"></div> + </div> + <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><br> + <div id="div" class="parent">Div <span><span class="child"></span></span></div><br> + </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 0000000000..74e5b9754a --- /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 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 0000000000..d727e7f207 --- /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 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_img_src_causing_reflow.html b/layout/style/test/test_img_src_causing_reflow.html new file mode 100644 index 0000000000..e63b39f9fe --- /dev/null +++ b/layout/style/test/test_img_src_causing_reflow.html @@ -0,0 +1,36 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for bug 1787072</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<style> +img { + width: 100px; + height: 100px; + background-color: blue; +} +</style> +<img> <!-- Initially broken --> +<script> +add_task(async function() { + const utils = SpecialPowers.DOMWindowUtils; + const img = document.querySelector("img"); + img.getBoundingClientRect(); + + let origFramesConstructed = utils.framesConstructed; + let origFramesReflowed = utils.framesReflowed; + + let error = new Promise(r => img.addEventListener("error", r, { once: true })); + + // Doesn't need to be an actual image. + img.src = "/some-valid-url"; + + img.getBoundingClientRect(); + is(origFramesReflowed, utils.framesReflowed, "Shouldn't have reflowed when going broken -> loading"); + is(origFramesConstructed, utils.framesConstructed, "Shouldn't have reflowed when going broken -> loading"); + + await error; + + is(origFramesReflowed, utils.framesReflowed, "Shouldn't have reflowed when going loading -> broken"); + is(origFramesConstructed, utils.framesConstructed, "Shouldn't have reflowed when going loading -> broken"); +}); +</script> diff --git a/layout/style/test/test_import_preload.html b/layout/style/test/test_import_preload.html new file mode 100644 index 0000000000..1c095c9768 --- /dev/null +++ b/layout/style/test/test_import_preload.html @@ -0,0 +1,22 @@ +<!doctype html> +<meta charset="utf-8"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<script src="slow_load.sjs"></script> +<script> + let time = Date.now(); + SimpleTest.waitForExplicitFinish(); + + onload = function() { + let styleTime = parseInt(getComputedStyle(document.documentElement).zIndex, 10); + isnot(styleTime, 0, "Should apply the @import sheet"); + ok(!isNaN(styleTime), "Should apply the @import sheet (and not be nan)"); + // This is technically a bit racy... Also see the comment in slow_load.sjs about the clamping. + time = time % (Math.pow(2, 31) - 1); + ok(styleTime < time, "Should try to fetch the import before running the script: " + styleTime + " vs. " + time); + SimpleTest.finish(); + } +</script> +<style> + @import url(slow_load.sjs?css) +</style> diff --git a/layout/style/test/test_inherit_computation.html b/layout/style/test/test_inherit_computation.html new file mode 100644 index 0000000000..9ac056518c --- /dev/null +++ b/layout/style/test/test_inherit_computation.html @@ -0,0 +1,176 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for computation of CSS 'inherit' on all properties and 'unset' on inherited properties</title> + <script 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 **/ + +var gDisplayTree = document.getElementById("display"); +// 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)]; + +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) + 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 + "'"); + // For width and inline-size, getComputedStyle returns used value + // when the element is displayed. Their initial value "auto" makes + // the element fill available space of the parent, so it doesn't + // make sense to compare it with the value we get before. + if (property != "width" && property != "inline-size") { + 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); + } + } + + // FIXME -moz-binding value makes gElementF and its parent loses + // their frame. Force it to get recreated after each property. + // See bug 1331903. + gDisplayTree.style.display = "none"; + get_computed_value(getComputedStyle(gFChild, ""), "width"); + gDisplayTree.style.display = ""; + get_computed_value(getComputedStyle(gFChild, ""), "width"); + }); +} + +for (var prop in gCSSProperties) { + // Skip zoom because it affects other props. + if (prop == "zoom") { + continue; + } + 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 0000000000..a9587d34d9 --- /dev/null +++ b/layout/style/test/test_inherit_storage.html @@ -0,0 +1,120 @@ +<!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 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; + +function test_property(property) +{ + var info = gCSSProperties[property]; + + var keywords = ["inherit"]; + if (info.inherited) + 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 if (info.type == CSS_TYPE_LEGACY_SHORTHAND) { + // We can't assert anything more meaningful here, really. + is(property, "zoom", "Zoom is a bit special because it never " + + "serializes as-is, we always serialize the longhands, " + + "but it doesn't just map to a single property " + + "(and thus we can't use the 'alias_for' mechanism)"); + } 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(propName, 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 " + propName + " 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 0000000000..c8a36af227 --- /dev/null +++ b/layout/style/test/test_initial_computation.html @@ -0,0 +1,159 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for computation of CSS 'initial' on all properties and 'unset' on reset properties</title> + <script 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 gDisplayTree = document.getElementById("display"); +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_property(property) +{ + var info = gCSSProperties[property]; + + var keywords = ["initial"]; + if (!info.inherited) + 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 used to be important for values that are supposed to compute to the + // initial value (given the design of the old rule tree, 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'd have a cached aStartStruct from the parent in the rule + // tree (caching the "other" value), so we'd make sure we don't get the + // initial value from the luck of default-initialization. This means + // that it would've been important that we set the prereqs on + // gRule1.style rather than on gElement.style. + // + // However, the rule tree no longer stores cached structs, and we only + // temporarily cache reset structs during a single restyle. So the + // particular failure mode this was designed to test for isn't as + // likely to eventuate. + 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); + } + }); +} + +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 0000000000..a1a081c5a6 --- /dev/null +++ b/layout/style/test/test_initial_storage.html @@ -0,0 +1,134 @@ +<!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 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; + +/** + * 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) + 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 if (info.type == CSS_TYPE_LEGACY_SHORTHAND) { + // We can't assert anything more meaningful here, really. + is(property, "zoom", "Zoom is a bit special because it never " + + "serializes as-is, we always serialize the longhands, " + + "but it doesn't just map to a single property " + + "(and thus we can't use the 'alias_for' mechanism)"); + } 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(propName, 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 " + propName + " 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_invalidation_basic.html b/layout/style/test/test_invalidation_basic.html new file mode 100644 index 0000000000..b5a6928405 --- /dev/null +++ b/layout/style/test/test_invalidation_basic.html @@ -0,0 +1,45 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + Test for bug 1368240: We only invalidate style as little as needed +</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<style> +.foo .bar { + color: red; +} +#container ~ .bar { + color: green; +} +</style> +<div id="container"> + <div></div> + <div></div> + <div></div> +</div> +<div></div> +<div></div> +<script> +SimpleTest.waitForExplicitFinish(); +const utils = SpecialPowers.getDOMWindowUtils(window); + +// TODO(emilio): Add an API to get the ComputedStyles we've recreated, to make +// more elaborated tests. +document.documentElement.offsetTop; +const initialRestyleGeneration = utils.restyleGeneration; + +// Normally we'd restyle the whole subtree in this case, but we should go down +// the tree invalidating as little as needed (nothing in this case). +container.classList.add("foo"); +document.documentElement.offsetTop; +is(utils.restyleGeneration, initialRestyleGeneration, + "Shouldn't have restyled any descendant"); + +container.setAttribute("id", ""); +document.documentElement.offsetTop; +is(utils.restyleGeneration, initialRestyleGeneration, + "Shouldn't have restyled any sibling"); + +SimpleTest.finish(); +</script> diff --git a/layout/style/test/test_keyframes_rules.html b/layout/style/test/test_keyframes_rules.html new file mode 100644 index 0000000000..027a406009 --- /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 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_keyframes_vendor_prefix.html b/layout/style/test/test_keyframes_vendor_prefix.html new file mode 100644 index 0000000000..4463bd259a --- /dev/null +++ b/layout/style/test/test_keyframes_vendor_prefix.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title> +Test for interaction between prefixed and non-prefixed @keyframes rules with +the same name +</title> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<div id='log'></div> +<script> +/** + * Appends a style element to the document head. + * + * @param t The testharness.js Test object. If provided, this will be used + * to register a cleanup callback to remove the style element + * when the test finishes. + * + * @param rules A dictionary object with selector names and rules to set on + * the style sheet. + */ +function addStyle(t, rules) { + var extraStyle = document.createElement('style'); + document.head.appendChild(extraStyle); + if (rules) { + var sheet = extraStyle.sheet; + for (var selector in rules) { + sheet.insertRule(selector + '{' + rules[selector] + '}', + sheet.cssRules.length); + } + } + + if (t && typeof t.add_cleanup === 'function') { + t.add_cleanup(function() { + extraStyle.remove(); + }); + } +} + +/** + * Appends a div to the document body. + * + * @param t The testharness.js Test object. If provided, this will be used + * to register a cleanup callback to remove the div when the test + * finishes. + * + * @param attrs A dictionary object with attribute names and values to set on + * the div. + */ +function addDiv(t, attrs) { + var div = document.createElement('div'); + if (attrs) { + for (var attrName in attrs) { + div.setAttribute(attrName, attrs[attrName]); + } + } + document.body.appendChild(div); + if (t && typeof t.add_cleanup === 'function') { + t.add_cleanup(function() { + div.remove(); + }); + } + return div; +} + +test(function(t) { + addStyle(t, + { '@-webkit-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, '-webkit- prefix keyframes'); + +test(function(t) { + addStyle(t, + { '@-moz-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, '-moz- prefix keyframes'); + +test(function(t) { + addStyle(t, + { '@-WEBKIT-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, '-WEBKIT- prefix keyframes'); + +test(function(t) { + addStyle(t, + { '@-MOZ-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, '-MOZ- prefix keyframes'); + +test(function(t) { + addStyle(t, + { '@-webkit-KEYFRAMES anim': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, '-webkit- prefix KEYFRAMES'); + +test(function(t) { + addStyle(t, + { '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }', + '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, '-webkit-keyframes should not override earlier non-prefix keyframes'); + +test(function(t) { + addStyle(t, + { '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }', + '@-moz-keyframes anim': 'from,to { color: rgb(255, 0, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, '-moz-keyframes should not override earlier non-prefix keyframes'); + +test(function(t) { + addStyle(t, + { '@-moz-keyframes anim': 'from,to { color: rgb(255, 0, 0); }', + '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, 'non-prefix keyframes should override earlier -moz-keyframes'); + +test(function(t) { + addStyle(t, + { '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }', + '@keyframes anim': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, 'non-prefix keyframes should override earlier -webkit-keyframes'); + +test(function(t) { + addStyle(t, + { '@-webkit-keyframes anim': 'from,to { color: rgb(255, 0, 0); }', + '@-moz-keyframes anim': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); + + addStyle(t, + { '@-moz-keyframes anim2': 'from,to { color: rgb(255, 0, 0); }', + '@-webkit-keyframes anim2': 'from,to { color: rgb(0, 255, 0); }' }); + + var div = addDiv(t, { style: 'animation: anim2 100s' }); + + assert_equals(getComputedStyle(div).color, 'rgb(0, 255, 0)'); +}, 'last prefixed keyframes should override earlier prefixed keyframes'); +</script> 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 0000000000..7344e27b22 --- /dev/null +++ b/layout/style/test/test_load_events_on_stylesheets.html @@ -0,0 +1,176 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=185236 +--> +<head> + <title>Test for Bug 185236</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script> + var pendingEventCounter = 0; + SimpleTest.waitForExplicitFinish(); + addLoadEvent(function() { + 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> +/** Test for Bug 185236 **/ + +function checkSheetComplete(sheet, length) { + try { + is(sheet.cssRules.length, length, `Should be loaded with ${length} rule(s)`); + } catch (e) { + ok(false, "Sheet has not been loaded completely"); + } +} + + +// There should be usually 1 pending event but the load event might have fired +// if the parser yields between the link and starting the script execution. +const pendingEventCounterAtStart = pendingEventCounter; + +ok(pendingEventCounter <= 1, `There should be at most one pending event, got ${pendingEventCounterAtStart}`); + +is(document.styleSheets.length, 2, "Should have two stylesheets"); +checkSheetComplete(document.styleSheets[1], 1); + +// Test sheet that will already be complete when we write it out +++pendingEventCounter; +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 +checkSheetComplete(document.styleSheets[2], 1); + +// Make sure the load event for that stylesheet has not fired yet +is(pendingEventCounter, pendingEventCounterAtStart + 1, "After first document write"); +++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, pendingEventCounterAtStart + 2, "after second document write"); +++pendingEventCounter; + +document.write('<link rel="stylesheet" href="http://www.example.com"\ + onload="--pendingEventCounter;\ + ok(false, \'Load event firing on broken stylesheet 1\')"\ + onerror="--pendingEventCounter;\ + ok(true, \'Error event firing on broken stylesheet 1\')">'); +++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 2'); +}; +link.onerror = function() { --pendingEventCounter; + ok(true, 'Error event firing on broken stylesheet 2'); +} +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); + +// If we ever switch away from sync loading of already-complete sheets, this +// test will need adjusting +checkSheetComplete(link.sheet, 1); + +++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 3'); +}; +link.onerror = function() { --pendingEventCounter; + ok(true, 'Error event firing on broken stylesheet 3'); +} +document.body.appendChild(link); + +function absoluteURL(relativeURL) { + return new URL(relativeURL, location.href).href; +} + +++pendingEventCounter; +link = document.createElement("link"); +link.rel = "stylesheet"; +link.href = `data:text/css,@import url('http://www.example.com'); @import url(${absoluteURL('slow_ok_sheet.sjs')});`; +link.onload = function() { --pendingEventCounter; + ok(false, 'Load event firing on broken stylesheet 4'); +}; +link.onerror = function() { --pendingEventCounter; + ok(true, 'Error event firing on broken stylesheet 4'); +} +document.body.appendChild(link); + +++pendingEventCounter; +link = document.createElement("link"); +link.rel = "stylesheet"; +link.href = `data:text/css,@import url(${absoluteURL('slow_broken_sheet.sjs')}); @import url('data:text/css,');`; +link.onload = function() { --pendingEventCounter; + ok(false, 'Load event firing on broken stylesheet 5'); +}; +link.onerror = function() { --pendingEventCounter; + ok(true, 'Error event firing on broken stylesheet 5'); +} +document.body.appendChild(link); + +// Make sure the load events for all those stylesheets have not fired yet +is(pendingEventCounter, pendingEventCounterAtStart + 9, "There should be nine more pending events"); +</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 0000000000..a6947791cb --- /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|inset|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|inset)/.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_marker_restrictions.html b/layout/style/test/test_marker_restrictions.html new file mode 100644 index 0000000000..f547f928cb --- /dev/null +++ b/layout/style/test/test_marker_restrictions.html @@ -0,0 +1,35 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for ::marker property restrictions.</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="property_database.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<style id="s"></style> +<div id="test"></div> +<div id="control"></div> +<script> +const test = getComputedStyle($("test"), "::marker"); +const control = getComputedStyle($("control"), "::marker"); + +for (const prop in gCSSProperties) { + const info = gCSSProperties[prop]; + if (info.type == CSS_TYPE_TRUE_SHORTHAND) + continue; + + let prereqs = ""; + if (info.prerequisites) + for (let name in info.prerequisites) + prereqs += `${name}: ${info.prerequisites[name]}; `; + + $("s").textContent = ` + #control::marker { ${prop}: ${info.initial_values[0]}; ${prereqs} } + #test::marker { ${prop}: ${info.other_values[0]}; ${prereqs} } + `; + + (info.applies_to_marker ? isnot : is)( + get_computed_value(test, prop), + get_computed_value(control, prop), + `${prop} should ${info.applies_to_marker ? "" : " not"} apply to ::marker`); +} + +</script> diff --git a/layout/style/test/test_mask_image_CORS.html b/layout/style/test/test_mask_image_CORS.html new file mode 100644 index 0000000000..8edd8af48e --- /dev/null +++ b/layout/style/test/test_mask_image_CORS.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>Test mask-image CORS anonymous retrieval</title> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/WindowSnapshot.js"></script> +<style> +.block100 { + width: 100px; + height: 100px; +} +#allow { + /* + * shape-outside is unnecessary for the mask, but using it ensures that the first frame + * of the image is decoded and reflow is called before onload is fired. Since the + * shape-outside uses the same url as the mask, this ensures that the css image resource + * is decoded and available for the repaint triggered by the call to snapshotRect. + */ + shape-outside: url("support/blue-100x100.png"); + mask-image: url("support/blue-100x100.png"); + background-color: #00FF00 +} +#refuse { + shape-outside: url("http://example.com/tests/layout/style/test/support/blue-100x100.png"); + mask-image: url("http://example.com/tests/layout/style/test/support/blue-100x100.png"); + background-color: #FF0000 +} +</style> + +<script> +SimpleTest.waitForExplicitFinish(); + +function checkBothSquares() { + checkIsColor("allow", "0,255,0,255"); + checkIsColor("refuse", "255,255,255,255"); + + SimpleTest.finish(); +} + +function checkIsColor(elementId, color) { + let e = document.getElementById(elementId); + let r = e.getBoundingClientRect(); + info("Element " + elementId + " has rect " + r.top + ", " + r.left + ", " + r.width + ", " + r.height + "."); + + let canvas = snapshotRect(window, r); + let context = canvas.getContext('2d'); + + // Only check the top left pixel. + let image = context.getImageData(0, 0, 1, 1); + let pixel = image.data.toString(); + is(pixel, color, "Element " + elementId + " has expected color."); +} +</script> + +</head> +<body onload="checkBothSquares()"> + <p>There should be a green square, but no red square.</p> + <div id="allow" class="block100"></div> + <div id="refuse" class="block100"></div> +</body> +</html> diff --git a/layout/style/test/test_media_queries.html b/layout/style/test/test_media_queries.html new file mode 100644 index 0000000000..441fc1105a --- /dev/null +++ b/layout/style/test/test_media_queries.html @@ -0,0 +1,867 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=156716 +--> +<head> + <title>Test for Bug 156716</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome/chrome-only-media-queries.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 window.devicePixelRatio; +} + +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-line") == "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"); + } + + function expression_should_be_known(e) { + should_apply(`(${e}) or (not (${e}))`); + } + + function expression_should_not_be_known(e) { + should_not_apply(`(${e}) or (not (${e}))`); + } + + // 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_known(min_name); + expression_should_not_be_known(max_name); + } + + function test_serialization(q, test_application, expected_to_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) { + let applies = body_cs.getPropertyValue("text-decoration-line") == + "underline"; + is(applies, expected_to_apply, + "Media query '" + q + "' should " + (expected_to_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>"; + post_clone_test(htmldoc, 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].remove(); + + var ser3 = clonedsheet.cssRules[0].media.mediaText; + is(ser3, ser1, "cloning query '" + q + "' should not change " + + "serialization"); + if (test_application) { + let applies = clonewin.getComputedStyle(clonedoc.body). + textDecorationLine == "underline"; + is(applies, expected_to_apply, + "Media query '" + q + "' should " + (expected_to_apply ? "" : "NOT ") + + "apply after cloning"); + } + }); + } + + // The no-type syntax doesn't mix with the not and only keywords. + expression_should_be_known("(orientation)"); + expression_should_be_known("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_not_be_parseable("not not (orientation)"); + expression_should_be_known("(orientation) and (orientation)"); + expression_should_be_known("(orientation) or (orientation)"); + expression_should_be_known("(orientation) or ((orientation) and ((orientation) or (orientation) or (not (orientation))))"); + + query_should_not_be_parseable("all and (orientation) or (orientation)"); + query_should_be_parseable("all and (orientation) and (orientation)"); + + query_should_not_be_parseable("(orientation) and (orientation) or (orientation)"); + query_should_not_be_parseable("(orientation) and not (orientation)"); + + query_should_be_parseable("(-moz-device-orientation)"); + query_should_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 separators = [ ":", ">", ">=", "=", "<=", "<" ]; + + var feature; + var i; + for (i in features) { + feature = features[i]; + expression_should_be_known(feature); + expression_should_not_be_known("min-" + feature); + expression_should_not_be_known("max-" + feature); + for (let separator of separators) { + expression_should_be_known(feature + " " + separator + " 0"); + expression_should_be_known(feature + " " + separator + " 0px"); + expression_should_be_known(feature + " " + separator + " 0em"); + expression_should_be_known(feature + " " + separator + " -0"); + expression_should_be_known(feature + " " + separator + " -0cm"); + expression_should_be_known(feature + " " + separator + " 1px"); + expression_should_be_known(feature + " " + separator + " 0.001mm"); + expression_should_be_known(feature + " " + separator + " 100000px"); + expression_should_be_known(feature + " " + separator + " -1px"); + if (separator == ":") { + expression_should_be_known("min-" + feature + " " + separator + " -0"); + expression_should_be_known("max-" + feature + " " + separator + " -0"); + expression_should_be_known("min-" + feature + " " + separator + " -1px"); + expression_should_be_known("max-" + feature + " " + separator + " -1px"); + expression_should_be_known(feature + " " + separator + " -0.00001mm"); + expression_should_be_known(feature + " " + separator + " -100000em"); + } else { + expression_should_not_be_known("min-" + feature + " " + separator + " -0"); + expression_should_not_be_known("max-" + feature + " " + separator + " -0"); + expression_should_not_be_known("min-" + feature + " " + separator + " -1px"); + expression_should_not_be_known("max-" + feature + " " + separator + " -1px"); + let multi_range = "0px " + separator + " " + feature + " " + separator + " 100000px" + if (separator == "=") { + expression_should_not_be_known(multi_range); + } else { + expression_should_be_known(multi_range); + } + } + if (separator == ">=") { + expression_should_not_be_known(feature + " > = 0px"); + } else if (separator == "<=") { + expression_should_not_be_known(feature + " < = 0px"); + } + } + } + + var mediatypes = ["browser", "minimal-ui", "standalone", "fullscreen"]; + + mediatypes.forEach(function(type) { + expression_should_be_known("display-mode: " + type); + }); + + expression_should_not_be_known("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_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_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)"); + + should_apply("(" + feature + " <= " + value + "px)"); + should_apply("(" + feature + " >= " + value + "px)"); + + should_apply("(0px < " + feature + " <= " + value + "px)"); + should_apply("(" + value + "px >= " + feature + " > 0px)"); + + should_not_apply("(0px < " + feature + " < " + value + "px)"); + should_not_apply("(" + value + "px > " + feature + " > 0px)"); + + should_not_apply("(" + feature + " < " + value + "px)"); + should_not_apply("(" + feature + " > " + value + "px)"); + + should_apply("(" + feature + " < " + (value + 1) + "px)"); + should_apply("(" + feature + " <= " + (value + 1) + "px)"); + should_not_apply("(" + feature + " > " + (value + 1) + "px)"); + should_not_apply("(" + feature + " >= " + (value + 1) + "px)"); + + should_apply("(" + feature + " > " + (value - 1) + "px)"); + should_apply("(" + feature + " >= " + (value - 1) + "px)"); + should_not_apply("(" + feature + " < " + (value - 1) + "px)"); + should_not_apply("(" + feature + " <= " + (value - 1) + "px)"); + } + + 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_known("orientation"); + expression_should_be_known("orientation: portrait"); + expression_should_be_known("orientation: landscape"); + expression_should_not_be_known("min-orientation"); + expression_should_not_be_known("min-orientation: portrait"); + expression_should_not_be_known("min-orientation: landscape"); + expression_should_not_be_known("max-orientation"); + expression_should_not_be_known("max-orientation: portrait"); + expression_should_not_be_known("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_known("-moz-device-orientation"); + expression_should_be_known("-moz-device-orientation: portrait"); + expression_should_be_known("-moz-device-orientation: landscape"); + expression_should_not_be_known("min--moz-device-orientation"); + expression_should_not_be_known("min--moz-device-orientation: portrait"); + expression_should_not_be_known("min--moz-device-orientation: landscape"); + expression_should_not_be_known("max--moz-device-orientation"); + expression_should_not_be_known("max--moz-device-orientation: portrait"); + expression_should_not_be_known("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)"); + + // Test "unreasonable", but still valid aspect ratios, such as aspect ratios with negative numbers, + // and zeros, and with numbers near 2^32 and 2^64 (to check overflow). + should_not_apply("(aspect-ratio: 0/1)"); + should_not_apply("(aspect-ratio: 1/0)"); + should_not_apply("(aspect-ratio: -1/1)"); + should_not_apply("(aspect-ratio: 1/-1)"); + should_not_apply("(aspect-ratio: -1/-1)"); + should_not_apply("(aspect-ratio: -59/-80)"); + should_not_apply("(aspect-ratio: 4294967295/4294967295)"); + should_not_apply("(aspect-ratio: 4294967297/4294967297)"); + should_not_apply("(aspect-ratio: 18446744073709560000/18446744073709560000)"); + + // Test min and max aspect ratios. + 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_known("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_known("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_known("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_known("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_known("-webkit-device-pixel-ratio: 1.0"); + expression_should_not_be_known("device-pixel-ratio: 1.0"); + expression_should_be_known("-webkit-min-device-pixel-ratio: 1.0"); + expression_should_not_be_known("min-device-pixel-ratio: 1.0"); + expression_should_be_known("-webkit-max-device-pixel-ratio: 1.0"); + expression_should_not_be_known("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_known(feature + ": 1/1"); + expression_should_be_known(feature + ": 1 /1"); + expression_should_be_known(feature + ": 1 / \t\n1"); + expression_should_be_known(feature + ": 1/\r1"); + expression_should_be_known(feature + ": 1"); + expression_should_be_known(feature + ": 0.5"); + expression_should_be_known(feature + ": 1.0/1"); + expression_should_be_known(feature + ": 1/1.0"); + expression_should_be_known(feature + ": 1.0/1.0"); + expression_should_be_known(feature + ": 1.5/1.2"); + expression_should_be_known(feature + ": 1.5"); + expression_should_be_known(feature + ": calc(1.2 * 1.3)"); + expression_should_be_known(feature + ": 1.1/calc(2.2 * 2.3)"); + expression_should_be_known(feature + ": calc(1.2 * 1.3)/2.2"); + expression_should_be_known(feature + ": calc(1.2 * 1.3)/calc(2.2 * 2.3)"); + expression_should_be_known(feature + ": 0/1"); + expression_should_be_known(feature + ": 1/0"); + expression_should_be_known(feature + ": 0/0"); + expression_should_not_be_known(feature + ": -1/1"); + expression_should_not_be_known(feature + ": 1/-1"); + expression_should_not_be_known(feature + ": -1/-1"); + expression_should_not_be_known(feature + ": -1/-1"); + expression_should_not_be_known(feature + ": -1/-1"); + expression_should_not_be_known(feature + ": invalid"); + expression_should_not_be_known(feature + ": 1 / invalid"); + expression_should_not_be_known(feature + ": 1 invalid"); + } + + 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_known("max-color"); + expression_should_not_be_known("min-color"); + (is_color ? should_not_apply : should_apply)("all and (monochrome)"); + expression_should_not_be_known("max-monochrome"); + expression_should_not_be_known("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_known(feature + ": 1"); + expression_should_be_known(feature + ": 327"); + expression_should_be_known(feature + ": 0"); + expression_should_be_known(feature + ": -1"); + expression_should_not_be_known(feature + ": 1.0"); + expression_should_not_be_known(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_known(feature + ": 3dpi"); + expression_should_be_known(feature + ":3dpi"); + expression_should_be_known(feature + ": 3.0dpi"); + expression_should_be_known(feature + ": 3.4dpi"); + expression_should_be_known(feature + "\t: 120dpcm"); + expression_should_be_known(feature + ": 1dppx"); + expression_should_be_known(feature + ": 1x"); + expression_should_be_known(feature + ": 1.5dppx"); + expression_should_be_known(feature + ": 1.5x"); + expression_should_be_known(feature + ": 2.0dppx"); + expression_should_be_known(feature + ": 0dpi"); + expression_should_be_known(feature + ": 0dppx"); + expression_should_be_known(feature + ": 0x"); + expression_should_not_be_known(feature + ": -3dpi"); + } + + // 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_apply("(resolution: " + Math.floor(resolution/96) + "x)"); + 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_known("scan"); + expression_should_be_known("scan: progressive"); + expression_should_be_known("scan:interlace"); + expression_should_not_be_known("min-scan:interlace"); + expression_should_not_be_known("scan: 1"); + expression_should_not_be_known("max-scan"); + expression_should_not_be_known("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_known("grid"); + expression_should_be_known("grid: 0"); + expression_should_be_known("grid: 1"); + expression_should_be_known("grid: 1"); + expression_should_not_be_known("min-grid"); + expression_should_not_be_known("min-grid:0"); + expression_should_not_be_known("max-grid: 1"); + expression_should_not_be_known("grid: 2"); + expression_should_not_be_known("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)"); + + for (let toggle of CHROME_ONLY_TOGGLES) { + expression_should_not_be_known(toggle); + expression_should_not_be_known(toggle + ": 1"); + expression_should_not_be_known(toggle + ": 0"); + expression_should_not_be_known(toggle + ": -1"); + expression_should_not_be_known(toggle + ": true"); + expression_should_not_be_known(toggle + ": false"); + } + + for (let query of CHROME_ONLY_QUERIES) { + expression_should_not_be_known(query); + } + + { + let should_be_parseable_if_enabled = SpecialPowers.getBoolPref('layout.css.prefers-contrast.enabled') + ? expression_should_be_known + : expression_should_not_be_known; + should_be_parseable_if_enabled("prefers-contrast"); + should_be_parseable_if_enabled("prefers-contrast: more"); + should_be_parseable_if_enabled("prefers-contrast: less"); + should_be_parseable_if_enabled("prefers-contrast: custom"); + should_be_parseable_if_enabled("prefers-contrast: no-preference"); + } + + { + let should_be_parseable_if_enabled = SpecialPowers.getBoolPref('layout.css.forced-colors.enabled') + ? expression_should_be_known + : expression_should_not_be_known; + should_be_parseable_if_enabled("forced-colors"); + should_be_parseable_if_enabled("forced-colors: none"); + should_be_parseable_if_enabled("forced-colors: active"); + } + + // OpenType SVG media features + expression_should_not_be_known("(-moz-is-glyph)"); + expression_should_not_be_known("not (-moz-is-glyph)"); + expression_should_not_be_known("only (-moz-is-glyph)"); + expression_should_not_be_known("all and (-moz-is-glyph)"); + expression_should_not_be_known("not all and (-moz-is-glyph)"); + expression_should_not_be_known("only all and (-moz-is-glyph)"); + + expression_should_not_be_known("(-moz-is-glyph:0)"); + expression_should_not_be_known("not (-moz-is-glyph:0)"); + expression_should_not_be_known("only (-moz-is-glyph:0)"); + expression_should_not_be_known("all and (-moz-is-glyph:0)"); + expression_should_not_be_known("not all and (-moz-is-glyph:0)"); + expression_should_not_be_known("only all and (-moz-is-glyph:0)"); + + expression_should_not_be_known("(-moz-is-glyph:1)"); + expression_should_not_be_known("not (-moz-is-glyph:1)"); + expression_should_not_be_known("only (-moz-is-glyph:1)"); + expression_should_not_be_known("all and (-moz-is-glyph:1)"); + expression_should_not_be_known("not all and (-moz-is-glyph:1)"); + expression_should_not_be_known("only all and (-moz-is-glyph:1)"); + + expression_should_not_be_known("(min--moz-is-glyph:0)"); + expression_should_not_be_known("(max--moz-is-glyph:0)"); + expression_should_not_be_known("(min--moz-is-glyph:1)"); + expression_should_not_be_known("(max--moz-is-glyph:1)"); + + should_not_apply("not all and (-moz-is-glyph)"); + should_not_apply("(-moz-is-glyph:0)"); + should_not_apply("not all and (-moz-is-glyph:1)"); + should_not_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)"); + + // Resource documents (UA-only). + expression_should_not_be_known("(-moz-is-resource-document)"); + + // 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"); + + for (let rangeFeature of ["dynamic-range", "video-dynamic-range"]) { + should_apply("(" + rangeFeature + ": standard)"); + expression_should_be_known("(" + rangeFeature + ": high)"); + expression_should_not_be_known("(" + rangeFeature + ": low)"); + } + + 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(srcdoc, testfunc) +{ + posted_items.push({srcdoc, 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 srcdoc = posted_items[0].srcdoc; + iframe.onload = handle_iframe_onload; + iframe.srcdoc = srcdoc; +} + +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 0000000000..52e5fda9ca --- /dev/null +++ b/layout/style/test/test_media_queries_dynamic.html @@ -0,0 +1,207 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=473400 +--> +<head> + <title>Test for Bug 473400</title> + <script 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"> + +/** 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-line") == "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 restyleGeneration, framesConstructed, framesReflowed; + function reset_change_counters() + { + restyleGeneration = utils.restyleGeneration; + 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 didRestyle = utils.restyleGeneration != restyleGeneration; + var constructs = utils.framesConstructed - framesConstructed; + var reflows = utils.framesReflowed - framesReflowed; + + (expected.restyle ? isnot : is)(didRestyle, false, "restyle: " + 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", + { restyle: false, 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", + { restyle: false, construct: false, reflow: false }); + + SimpleTest.finish(); +} + +</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 0000000000..5f213117e5 --- /dev/null +++ b/layout/style/test/test_media_query_list.html @@ -0,0 +1,373 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=542058 +--> +<head> + <title>Test for MediaQueryList (Bug 542058)</title> + <script 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 tick() { + // MediaQueryList events are guaranteed to run before requestAnimationFrame + // per spec. + return new Promise(r => requestAnimationFrame(r)); +} + +async 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 + await tick(); + + function setup_mql(str) { + var obj = { + str: str, + mql: subwin.matchMedia(str), + notifyCount: 0, + listener: function(event) { + ok(event instanceof subwin.MediaQueryListEvent, + "correct argument to listener: " + obj.str); + is(event.media, obj.mql.media, + "correct media in the event: " + obj.str); + is(event.target, obj.mql, + "correct target in the event: " + obj.str); + ++obj.notifyCount; + // Test the last match result only on odd + // notifications. + if (obj.notifyCount & 1) { + obj.lastOddMatchResult = event.target.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 + await tick(); + + 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 + await tick(); + + 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 + await tick(); + + 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); + + await tick(); + + 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. + { + let received = []; + let received_mql = []; + function listener1(event) { + received.push(1); + received_mql.push(event.target); + } + function listener2(event) { + received.push(2); + received_mql.push(event.target); + } + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + await tick(); + + let 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 + await tick(); + + is(JSON.stringify(received), "[1,2]", "duplicate listeners removed"); + received = []; + mql.removeListener(listener1); + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + await tick(); + + is(JSON.stringify(received), "[2]", "listener removal"); + received = []; + mql.addListener(listener1); + + iframe.style.width = "100px"; + subroot.offsetWidth; // flush layout + await tick(); + + is(JSON.stringify(received), "[2,1]", "listeners notified in order"); + received = []; + mql.addListener(listener2); + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + await tick(); + + 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 + await tick(); + + 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 + await tick(); + + // 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(event) { + received.push(3); + received_mql.push(event.target); + event.target.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 + await tick(); + + // mql(1, 3) + is(JSON.stringify(received), "[1,3]", + "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)"); + received = []; + received_mql = []; + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + await tick(); + + // 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 753777: test that things work in a freshly-created iframe */ + { + let newIframe = document.createElement("iframe"); + document.body.appendChild(newIframe); + + is(newIframe.contentWindow.matchMedia("all").matches, true, + "matchMedia should work in newly-created iframe"); + is(newIframe.contentWindow.matchMedia("(min-width: 1px)").matches, true, + "(min-width: 1px) should match in newly-created iframe"); + is(newIframe.contentWindow.matchMedia("(max-width: 1px)").matches, false, + "(max-width: 1px) should not match in newly-created iframe"); + + document.body.removeChild(newIframe); + } + + /* Bug 716751: listeners lost due to GC */ + var gc_received = []; + { + let received = []; + let listener1 = function(event) { + gc_received.push(1); + } + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + await tick(); + + let 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 + await tick(); + + 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); + } + + async function step2() { + SpecialPowers.DOMWindowUtils.garbageCollect(); + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + await tick(); + + is(JSON.stringify(gc_received), "[1,1]", "GC test: after notification 2"); + + bug1270626(); + } + + /* Bug 1270626: listeners that throw exceptions */ + async function bug1270626() { + var throwingListener = function(event) { + throw "error"; + } + + iframe.style.width = "200px"; + subroot.offsetWidth; // flush layout + await tick(); + + 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 + await tick(); + + is(SimpleTest.isExpectingUncaughtException(), false, + "should have gotten an uncaught exception"); + + SimpleTest.finish(); + } +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_media_query_serialization.html b/layout/style/test/test_media_query_serialization.html new file mode 100644 index 0000000000..ed82653db0 --- /dev/null +++ b/layout/style/test/test_media_query_serialization.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test media query list serialization</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<style> + @media PrInT {} + @media screen, PrInT, SPEECH {} + + @media GARbAGE7 {} + @media AAAAA {} + @media ZZZZZ {} + + @media NotAValidMediaType_!000 {} + @media NotAValidMediaType_!000, AlsoInvalid!!!! {} + + @media PrInT, GARbAGE7, notAValidMediaType_!000 {} +</style> +<script type="application/javascript"> + +let expectedValues = [ + // Valid types + ["print", "Valid media types are ascii lowercased."], + ["screen, print, speech", "Media query lists with only valid types are ascii lowercased."], + + // Invalid types + ["garbage7", "Invalid media types are ascii lowercased."], + ["aaaaa", "Ascii conversion handles 'A' correctly."], + ["zzzzz", "Ascii conversion handles 'Z' correctly."], + + // Malformed types + ["not all", "Malformed media types are serialized to 'not all'."], + ["not all, not all", "Multiple malformed media types are each serialized to 'not all'."], + + // Mixes + ["print, garbage7, not all", "Media query lists with a mix of valid, invalid, and malformed types serialize " + + "as lowercase with malformed types changed to 'not all'."], +]; + +let sheet = document.styleSheets[1]; + +expectedValues.forEach(function (entry, index) { + let rule = sheet.cssRules[index]; + let serializedList = rule.media.mediaText; + is(serializedList, entry[0], entry[1]); +}); + +</script> +</head> +<body> +</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 0000000000..0e3e143fe8 --- /dev/null +++ b/layout/style/test/test_moz_device_pixel_ratio.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=474356 +--> +<head> + <title>Test for Bug 474356</title> + <script 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)).visibility == "visible"; + } + + var screenPixelsPerCSSPixel = window.devicePixelRatio; + 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_moz_prefixed_cursor.html b/layout/style/test/test_moz_prefixed_cursor.html new file mode 100644 index 0000000000..db7ffaaf56 --- /dev/null +++ b/layout/style/test/test_moz_prefixed_cursor.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>Cursor aliases compute to the unprefixed keyword</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div></div> +<script> +test(function() { + let div = document.querySelector("div"); + for (const kw of ["grab", "grabbing", "zoom-in", "zoom-out"]) { + div.style.cursor = "-moz-" + kw; + assert_equals(getComputedStyle(div).cursor, kw); + } +}, "-moz- prefixed cursor keywords compute to its unprefixed version"); +</script> diff --git a/layout/style/test/test_mq_any_hover_and_any_pointer.html b/layout/style/test/test_mq_any_hover_and_any_pointer.html new file mode 100644 index 0000000000..1725af0725 --- /dev/null +++ b/layout/style/test/test_mq_any_hover_and_any_pointer.html @@ -0,0 +1,97 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1483111 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1035774</title> + <script 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=1483111">Mozilla Bug 1483111</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<script> + +const NO_POINTER = 0x00; +const COARSE_POINTER = 0x01; +const FINE_POINTER = 0x02; +const HOVER_CAPABLE_POINTER = 0x04; + +var isAndroid = navigator.appVersion.includes("Android"); + +add_task(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ ['privacy.resistFingerprinting', true] ] + }); + + // When resistFingerprinting is enabled, we pretend that the system has a + // mouse pointer (or finger on mobile). + let invertIfAndroid = function(b) { return isAndroid ? !b : b; }; + + ok(!matchMedia("(any-pointer: none)").matches, + "Doesn't match (any-pointer: none)"); + ok(matchMedia("(any-pointer)").matches, "Matches (any-pointer)"); + + ok(invertIfAndroid(!matchMedia("(any-pointer: coarse)").matches), + "Doesn't match (any-pointer: coarse)"); + ok(invertIfAndroid(matchMedia("(any-pointer: fine)").matches), "Matches (any-pointer: fine)"); + + ok(invertIfAndroid(!matchMedia("(any-hover: none)").matches), + "Doesn't match (any-hover: none)"); + ok(invertIfAndroid(matchMedia("(any-hover: hover)").matches), + "Matches (any-hover: hover)"); + ok(invertIfAndroid(matchMedia("(any-hover)").matches), "Matches (any-hover)"); + + await SpecialPowers.flushPrefEnv(); +}); + +add_task(async () => { + // No pointer. + await SpecialPowers.pushPrefEnv({ + set: [ ['ui.allPointerCapabilities', NO_POINTER] ] + }); + + ok(matchMedia("(any-pointer: none)").matches, "Matches (any-pointer: none)"); + ok(!matchMedia("(any-pointer: coarse)").matches, + "Doesn't match (any-pointer: coarse)"); + ok(!matchMedia("(any-pointer: fine)").matches, + "Doesn't match (any-pointer: fine)"); + ok(!matchMedia("(any-pointer)").matches, "Matches (any-pointer)"); + + ok(matchMedia("(any-hover: none)").matches, "Matches (any-hover: none)"); + ok(!matchMedia("(any-hover: hover)").matches, + "Doesn't match (any-hover: hover)"); + ok(!matchMedia("(any-hover)").matches, "Doesn't match (any-hover)"); +}); + +add_task(async () => { + // Mouse type pointer and touchscreen + await SpecialPowers.pushPrefEnv({ + set: [ ['ui.allPointerCapabilities', + FINE_POINTER | COARSE_POINTER | HOVER_CAPABLE_POINTER] ] + }); + + ok(!matchMedia("(any-pointer: none)").matches, + "Doesn't match (any-pointer: none)"); + ok(matchMedia("(any-pointer: coarse)").matches, + "Matches (any-pointer: coarse)"); + ok(matchMedia("(any-pointer: fine)").matches, "Matches (any-pointer: fine)"); + ok(matchMedia("(any-pointer)").matches, "Matches (any-pointer)"); + + ok(!matchMedia("(any-hover: none)").matches, + "Doesn't match (any-hover: none)"); + ok(matchMedia("(any-hover: hover)").matches, + "Matches (any-hover: hover)"); + ok(matchMedia("(any-hover)").matches, "Matches (any-hover)"); +}); + +</script> +</body> +</html> diff --git a/layout/style/test/test_mq_changes_in_iframe.html b/layout/style/test/test_mq_changes_in_iframe.html new file mode 100644 index 0000000000..3a36476c42 --- /dev/null +++ b/layout/style/test/test_mq_changes_in_iframe.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Media feature value change propagation in an iframe</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<iframe id="iframe"></iframe> +<pre id="test"></pre> +<script> +add_task(async () => { + const mqString = "(prefers-reduced-motion: reduce)"; + + await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 0]]}); + + iframe.src = SimpleTest.getTestFileURL("mq_changes_child.html") + .replace("mochi.test:8888", "example.com"); + await new Promise(resolve => window.addEventListener("message", event => { + if (event.data == "ready") { + resolve(); + } + }, { once: true } )); + + const mql = matchMedia(mqString); + ok(!mql.matches, `Doesn't matches ${mqString}`); + + const changedInThisDocument = new Promise(resolve => { + mql.addEventListener("change", event => { resolve(event.matches); }); + }); + const changedInIFrame = new Promise(resolve => { + window.addEventListener("message", event => { + if ("matches" in event.data) { + resolve(event.data.matches); + } + }, { once: true }); + }); + + await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 1]]}); + + const results = + await Promise.allSettled([ changedInThisDocument, changedInIFrame ]); + + results.forEach(result => { + is(result.status, "fulfilled"); + ok(result.value, `Matches ${mqString}`); + }); +}); +</script> +</body> +</html> diff --git a/layout/style/test/test_mq_hover_and_pointer.html b/layout/style/test/test_mq_hover_and_pointer.html new file mode 100644 index 0000000000..40eaed5d0b --- /dev/null +++ b/layout/style/test/test_mq_hover_and_pointer.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1035774 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1035774</title> + <script 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=1035774">Mozilla Bug 1035774</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<script> + +const NO_POINTER = 0x00; +const COARSE_POINTER = 0x01; +const FINE_POINTER = 0x02; +const HOVER_CAPABLE_POINTER = 0x04; + +var isAndroid = navigator.appVersion.includes("Android"); + +add_task(async () => { + await SpecialPowers.pushPrefEnv({ + set: [ ['privacy.resistFingerprinting', true] ] + }); + + // When resistFingerprinting is enabled, we pretend that the system has a + // mouse pointer (or finger on mobile). + let invertIfAndroid = function(b) { return isAndroid ? !b : b; }; + + ok(!matchMedia("(pointer: none)").matches, + "Doesn't match (pointer: none)"); + + ok(matchMedia("(pointer)").matches, "Matches (pointer)"); + + ok(invertIfAndroid(!matchMedia("(pointer: coarse)").matches), + "Doesn't match (pointer: coarse)"); + ok(invertIfAndroid(matchMedia("(pointer: fine)").matches), "Matches (pointer: fine)"); + + ok(invertIfAndroid(!matchMedia("(hover: none)").matches), "Doesn't match (hover: none)"); + ok(invertIfAndroid(matchMedia("(hover: hover)").matches), "Matches (hover: hover)"); + ok(invertIfAndroid(matchMedia("(hover)").matches), "Matches (hover)"); + + await SpecialPowers.flushPrefEnv(); +}); + +add_task(async () => { + // No pointer. + await SpecialPowers.pushPrefEnv({ + set: [ ['ui.primaryPointerCapabilities', NO_POINTER] ] + }); + + ok(matchMedia("(pointer: none)").matches, "Matches (pointer: none)"); + ok(!matchMedia("(pointer: coarse)").matches, + "Doesn't match (pointer: coarse)"); + ok(!matchMedia("(pointer: fine)").matches, "Doesn't match (pointer: fine)"); + ok(!matchMedia("(pointer)").matches, "Matches (pointer)"); + + ok(matchMedia("(hover: none)").matches, "Matches (hover: none)"); + ok(!matchMedia("(hover: hover)").matches, "Doesn't match (hover: hover)"); + ok(!matchMedia("(hover)").matches, "Doesn't match (hover)"); +}); + +add_task(async () => { + // Mouse type pointer. + await SpecialPowers.pushPrefEnv({ + set: [ ['ui.primaryPointerCapabilities', FINE_POINTER | HOVER_CAPABLE_POINTER] ] + }); + + ok(!matchMedia("(pointer: none)").matches, + "Doesn't match (pointer: none)"); + ok(!matchMedia("(pointer: coarse)").matches, + "Doesn't match (pointer: coarse)"); + ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)"); + ok(matchMedia("(pointer)").matches, "Matches (pointer)"); + + ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)"); + ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)"); + ok(matchMedia("(hover)").matches, "Matches (hover)"); +}); + +add_task(async () => { + // Mouse type pointer. + await SpecialPowers.pushPrefEnv({ + set: [ ['ui.primaryPointerCapabilities', + FINE_POINTER | + HOVER_CAPABLE_POINTER] ] + }); + + ok(!matchMedia("(pointer: none)").matches, + "Doesn't match (pointer: none)"); + ok(!matchMedia("(pointer: coarse)").matches, + "Doesn't match (pointer: coarse)"); + ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)"); + ok(matchMedia("(pointer)").matches, "Matches (pointer)"); + + ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)"); + ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)"); + ok(matchMedia("(hover)").matches, "Matches (hover)"); +}); + +add_task(async () => { + // Touch screen. + await SpecialPowers.pushPrefEnv({ + set: [ ['ui.primaryPointerCapabilities', COARSE_POINTER] ] + }); + + ok(!matchMedia("(pointer: none)").matches, "Doesn't match (pointer: none)"); + ok(matchMedia("(pointer: coarse)").matches, "Matches (pointer: coarse)"); + ok(!matchMedia("(pointer: fine)").matches, "Doesn't match (pointer: fine)"); + ok(matchMedia("(pointer)").matches, "Matches (pointer)"); + + ok(matchMedia("(hover: none)").matches, "Match (hover: none)"); + ok(!matchMedia("(hover: hover)").matches, "Doesn't match (hover: hover)"); + ok(!matchMedia("(hover)").matches, "Doesn't match (hover)"); +}); + +</script> +</body> +</html> diff --git a/layout/style/test/test_mq_prefers_contrast_dynamic.html b/layout/style/test/test_mq_prefers_contrast_dynamic.html new file mode 100644 index 0000000000..c3ccebbe82 --- /dev/null +++ b/layout/style/test/test_mq_prefers_contrast_dynamic.html @@ -0,0 +1,82 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1691793 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1691793</title> + <script 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=1691793">Mozilla Bug 1691793</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<script> +"use strict"; + +SimpleTest.registerCleanupFunction(async () => { + await SpecialPowers.flushPrefEnv(); +}); + +// Returns a Promise which will be resolved when the "change" event is received +// for the given media query string. +function promiseForChange(mediaQuery) { + return new Promise(resolve => { + window.matchMedia(mediaQuery).addEventListener("change", event => { + resolve(event.matches); + }, { once: true }); + }); +} + +add_task(async () => { + await SpecialPowers.pushPrefEnv({ set: [["layout.css.prefers-contrast.enabled", true]]}); + const initiallyMatches = + window.matchMedia("(prefers-contrast: more)").matches; + const changePromise = initiallyMatches ? + promiseForChange("(prefers-contrast: more)") : null; + await SpecialPowers.pushPrefEnv({ set: [["ui.useAccessibilityTheme", 0]]}); + + if (changePromise) { + await changePromise; + } + + ok(!window.matchMedia("(prefers-contrast: more)").matches, + "Does not match prefers-contrast: more) when the system unsets " + + "UseAccessibilityTheme"); + ok(!window.matchMedia("(prefers-contrast)").matches, + "Does not match (prefers-contrast) when the system unsets " + + "UseAccessibilityTheme"); + ok(window.matchMedia("(prefers-contrast: no-preference)").matches, + "Matches (prefers-contrast: no-preference) when the system unsets " + + "UseAccessibilityTheme"); +}); + +add_task(async () => { + const more = promiseForChange("(prefers-contrast: more)"); + const booleanContext = promiseForChange("(prefers-contrast)"); + const noPreference = promiseForChange("(prefers-contrast: no-preference)"); + + await SpecialPowers.pushPrefEnv({ set: [["ui.useAccessibilityTheme", 1]]}); + + const [ moreResult, booleanContextResult, noPreferenceResult ] = + await Promise.all([ more, booleanContext, noPreference ]); + + ok(moreResult, + "Matches (prefers-contrast: more) when the system sets " + + "UseAccessibilityTheme"); + ok(booleanContextResult, + "Matches (prefers-contrast) when the system sets UseAccessibilityTheme"); + ok(!noPreferenceResult, + "Does not match (prefers-contrast: no-preference) when the " + + "system sets UseAccessibilityTheme"); +}); + +</script> +</body> +</html> diff --git a/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html b/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html new file mode 100644 index 0000000000..570c0d3954 --- /dev/null +++ b/layout/style/test/test_mq_prefers_reduced_motion_dynamic.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1486971 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1478519</title> + <script 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=1486971">Mozilla Bug 1486971</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +<script> +'use strict'; + +async function waitForFrame() { + return new Promise(resolve => { + window.requestAnimationFrame(resolve); + }); +} + +// Returns a Promise which will be resolved when the 'change' event is received +// for the given media query string. +async function promiseForChange(mediaQuery) { + return new Promise(resolve => { + window.matchMedia(mediaQuery).addEventListener('change', event => { + resolve(event.matches); + }, { once: true }); + }); +} + +add_task(async () => { + const initiallyMatches = + window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + const changePromise = initiallyMatches ? promiseForChange('(prefers-reduced-motion: reduce)') : null; + + await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 0]]}); + + if (changePromise) { + await changePromise; + } + + ok(!window.matchMedia('(prefers-reduced-motion: reduce)').matches, + 'Does not matches prefers-reduced-motion: reduced) when the system sets ' + + 'prefers-reduced-motion false'); + ok(!window.matchMedia('(prefers-reduced-motion)').matches, + 'Does not matches (prefers-reduced-motion) when the system sets ' + + 'prefers-reduced-motion false'); + ok(window.matchMedia('(prefers-reduced-motion: no-preference)').matches, + 'Matches (prefers-reduced-motion: no-preference) when the system sets ' + + 'prefers-reduced-motion false'); +}); + +add_task(async () => { + const reduce = promiseForChange('(prefers-reduced-motion: reduce)'); + const booleanContext = promiseForChange('(prefers-reduced-motion)'); + const noPreference = promiseForChange('(prefers-reduced-motion: no-preference)'); + + await SpecialPowers.pushPrefEnv({ set: [['ui.prefersReducedMotion', 1]]}); + + const [ reduceResult, booleanContextResult, noPreferenceResult ] = + await Promise.all([ reduce, booleanContext, noPreference ]); + + ok(reduceResult, + 'Matches (prefers-reduced-motion: reduced) when the system sets ' + + 'prefers-reduced-motion true'); + ok(booleanContextResult, + 'Matches (prefers-reduced-motion) when the system sets ' + + 'prefers-reduced-motion true'); + ok(!noPreferenceResult, + 'Does not matches (prefers-reduced-motion: no-preference) when the ' + + 'system sets prefers-reduced-motion true'); + + await SpecialPowers.flushPrefEnv(); +}); +</script> +</body> +</html> diff --git a/layout/style/test/test_mql_event_listener_leaks.html b/layout/style/test/test_mql_event_listener_leaks.html new file mode 100644 index 0000000000..3ceb5412a2 --- /dev/null +++ b/layout/style/test/test_mql_event_listener_leaks.html @@ -0,0 +1,43 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1450271 - Test MediaQueryList event listener leak conditions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +// Manipulate MediaQueryList. Its important here that we create a +// listener callback from the DOM objects back to the frame's global +// in order to exercise the leak condition. +async function useMediaQuery(contentWindow) { + contentWindow.messageCount = 0; + + let mql = contentWindow.matchMedia("(max-width: 600px)"); + mql.onchange = _ => { + contentWindow.mediaCount += 1; + }; +} + +async function runTest() { + try { + await checkForEventListenerLeaks("MediaQueryList", useMediaQuery); + } catch (e) { + ok(false, e); + } finally { + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +addEventListener("load", runTest, { once: true }); +</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 0000000000..6e8ba52c90 --- /dev/null +++ b/layout/style/test/test_namespace_rule.html @@ -0,0 +1,461 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for CSS Namespace rules</title> + <script 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"; + +function run() { + var wrappedFrame = SpecialPowers.wrap($("iframe")); + var ifwin = wrappedFrame.contentWindow; + var ifdoc = wrappedFrame.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); + let style_text = ifdoc.createCDATASection(""); + style_elem.appendChild(style_text); + return style_text; + } + + let 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_non_content_accessible_env_vars.html b/layout/style/test/test_non_content_accessible_env_vars.html new file mode 100644 index 0000000000..d9714216b7 --- /dev/null +++ b/layout/style/test/test_non_content_accessible_env_vars.html @@ -0,0 +1,39 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe></iframe> +<iframe srcdoc="Foo"></iframe> +<script> +const NON_CONTENT_ACCESSIBLE_ENV_VARS = [ + "-moz-gtk-csd-titlebar-radius", + "-moz-gtk-csd-minimize-button-position", + "-moz-gtk-csd-maximize-button-position", + "-moz-gtk-csd-close-button-position", + "-moz-content-preferred-color-scheme", + "-moz-overlay-scrollbar-fade-duration", + "scrollbar-inline-size", +]; + +function testInWin(win) { + let doc = win.document; + const div = doc.createElement("div"); + doc.documentElement.appendChild(div); + for (const envVar of NON_CONTENT_ACCESSIBLE_ENV_VARS) { + div.style.setProperty("--foo", `env(${envVar},FALLBACK_VALUE)`); + + assert_equals( + win.getComputedStyle(div).getPropertyValue("--foo"), + "FALLBACK_VALUE", + `${envVar} shouldn't be exposed to content in ${doc.documentURI}` + ); + } +} + +let t = async_test("Test non-content-accessible env() vars"); +onload = t.step_func_done(function() { + testInWin(window); + for (let f of document.querySelectorAll("iframe")) { + testInWin(f.contentWindow); + } +}); +</script> diff --git a/layout/style/test/test_non_content_accessible_properties.html b/layout/style/test/test_non_content_accessible_properties.html new file mode 100644 index 0000000000..220ddf2d26 --- /dev/null +++ b/layout/style/test/test_non_content_accessible_properties.html @@ -0,0 +1,85 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<iframe></iframe> +<iframe srcdoc="Foo"></iframe> +<script> +const CHROME_ONLY_PROPERTIES = [ + "-moz-window-input-region-margin", + "-moz-window-shadow", + "-moz-window-opacity", + "-moz-window-transform", + "-moz-window-transform-origin", + "-moz-default-appearance", + "-moz-user-focus", +]; + +const UA_ONLY_PROPERTIES = [ + "-x-span", + "-x-lang", + "-x-text-scale", + "-moz-control-character-visibility", + "-moz-top-layer", + "-moz-script-level", + "-moz-math-display", + "-moz-math-variant", + "-moz-inert", + "-moz-min-font-size-ratio", + // TODO: This should ideally be in CHROME_ONLY_PROPERTIES, but due to how + // [Pref] and [ChromeOnly] interact in WebIDL, the former wins. + "-moz-context-properties", +]; + +function testInWin(win) { + const doc = win.document; + const sheet = doc.createElement("style"); + const div = doc.createElement("div"); + doc.documentElement.appendChild(sheet); + doc.documentElement.appendChild(div); + + sheet.textContent = `div { color: initial }`; + assert_equals(sheet.sheet.cssRules[0].style.length, 1, `sanity: ${doc.documentURI}`); + + for (const prop of CHROME_ONLY_PROPERTIES.concat(UA_ONLY_PROPERTIES)) { + sheet.textContent = `div { ${prop}: initial }`; + let block = sheet.sheet.cssRules[0].style; + assert_false(prop in block, `${prop} shouldn't be exposed in CSS2Properties`); + + let isUAOnly = UA_ONLY_PROPERTIES.includes(prop); + assert_equals(prop in SpecialPowers.wrap(block), !isUAOnly, `${prop} should be exposed to chrome code if needed`); + + assert_equals( + block.length, + 0, + `${prop} shouldn't be parsed in ${doc.documentURI}` + ); + block.setProperty(prop, "initial"); + assert_equals( + block.length, + 0, + `${prop} shouldn't be settable via CSSOM in ${doc.documentURI}` + ); + assert_equals( + win.getComputedStyle(div).getPropertyValue(prop), + "", + `${prop} shouldn't be accessible via CSSOM in ${doc.documentURI}` + ); + assert_false( + win.CSS.supports(prop + ': initial'), + `${prop} shouldn't be exposed in CSS.supports in ${doc.documentURI}` + ); + assert_false( + win.CSS.supports(prop, 'initial'), + `${prop} shouldn't be exposed in CSS.supports in ${doc.documentURI} (2-value version)` + ); + } +} + +let t = async_test("test non-content-accessible props"); +onload = t.step_func_done(function() { + testInWin(window); + for (let f of document.querySelectorAll("iframe")) { + testInWin(f.contentWindow); + } +}); +</script> diff --git a/layout/style/test/test_non_content_accessible_pseudos.html b/layout/style/test/test_non_content_accessible_pseudos.html new file mode 100644 index 0000000000..81493b1a4a --- /dev/null +++ b/layout/style/test/test_non_content_accessible_pseudos.html @@ -0,0 +1,69 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style id="sheet"></style> +<script> +// Even though some of these no longer exist, we still do want to test that +// they aren't exposed to the web. +const NON_CONTENT_ACCESIBLE_PSEUDOS = [ + "::-moz-complex-control-wrapper", + "::-moz-number-wrapper", + "::-moz-number-text", + "::-moz-number-spin-up", + "::-moz-number-spin-down", + "::-moz-number-spin-box", + "::-moz-search-clear-button", + + ":-moz-native-anonymous", + ":-moz-table-border-nonzero", + ":-moz-browser-frame", + ":-moz-devtools-highlighted", + ":-moz-styleeditor-transitioning", + ":-moz-handler-clicktoplay", + ":-moz-handler-vulnerable-updatable", + ":-moz-handler-vulnerable-no-update", + ":-moz-handler-disabled", + ":-moz-handler-blocked", + ":-moz-handler-chrased", + ":-moz-has-dir-attr", + ":-moz-dir-attr-ltr", + ":-moz-dir-attr-rtl", + ":-moz-dir-attr-like-auto", + ":-moz-autofill-preview", + ":-moz-lwtheme", + ":-moz-is-html", + ":-moz-locale-dir(rtl)", + ":-moz-locale-dir(ltr)", + + "::-moz-tree-row", + "::-moz-tree-row(foo)", +]; + +test(function() { + sheet.textContent = `div { color: initial }`; + assert_equals(sheet.sheet.cssRules.length, 1); +}, "sanity"); + +for (const pseudo of NON_CONTENT_ACCESIBLE_PSEUDOS) { + test(function() { + sheet.textContent = `${pseudo} { color: blue; }`; + assert_equals(sheet.sheet.cssRules.length, 0, + pseudo + " shouldn't be accessible to content"); + if (pseudo.indexOf("::") === 0) { + let pseudoStyle; + try { + pseudoStyle = getComputedStyle(document.documentElement, pseudo); + } catch (ex) { + // We can hit this when layout.css.computed-style.throw-on-invalid-pseudo is true + assert_true(true, `${pseudo} shouldn't be visible to content`); + return; + } + let elementStyle = getComputedStyle(document.documentElement); + for (prop of pseudoStyle) { + assert_equals(pseudoStyle[prop], elementStyle[prop], + pseudo + " styles shouldn't be visible from getComputedStyle"); + } + } + }, pseudo); +} +</script> diff --git a/layout/style/test/test_non_content_accessible_values.html b/layout/style/test/test_non_content_accessible_values.html new file mode 100644 index 0000000000..633427f8e1 --- /dev/null +++ b/layout/style/test/test_non_content_accessible_values.html @@ -0,0 +1,172 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style id="sheet"></style> +<div></div> +<script> +const NON_CONTENT_ACCESSIBLE_VALUES = { + "color": [ + "-moz-buttonactivetext", + "-moz-buttonactiveface", + "-moz-buttondisabledface", + "-moz-disabledfield", + "-moz-colheaderhovertext", + "-moz-colheadertext", + "-moz-nativevisitedhyperlinktext", + "text-select-disabled-background", + "text-select-attention-background", + "text-select-attention-foreground", + "-moz-autofill-background", + ], + "display": [ + "-moz-box", + "-moz-inline-box", + ], + "font": [ + "-moz-pull-down-menu", + "-moz-button", + "-moz-list", + "-moz-field", + ], + "-moz-appearance": [ + "button-arrow-down", + "button-arrow-next", + "button-arrow-previous", + "button-arrow-up", + "button-focus", + "dualbutton", + "groupbox", + "menubar", + "menuitem", + "checkmenuitem", + "radiomenuitem", + "menuitemtext", + "menupopup", + "menucheckbox", + "menuradio", + "menuseparator", + "menuimage", + "-moz-menulist-arrow-button", + "checkbox-container", + "radio-container", + "checkbox-label", + "radio-label", + "resizerpanel", + "resizer", + "scrollbar", + "scrollbar-small", + "scrollbar-horizontal", + "scrollbar-vertical", + "scrollbarbutton-up", + "scrollbarbutton-down", + "scrollbarbutton-left", + "scrollbarbutton-right", + "scrollcorner", + "separator", + "spinner", + "spinner-upbutton", + "spinner-downbutton", + "spinner-textfield", + "splitter", + "statusbar", + "statusbarpanel", + "tab", + "tabpanel", + "tabpanels", + "tab-scroll-arrow-back", + "tab-scroll-arrow-forward", + "toolbar", + "toolbarbutton", + "toolbarbutton-dropdown", + "toolbargripper", + "toolbox", + "tooltip", + "treeheader", + "treeheadercell", + "treeheadersortarrow", + "treeitem", + "treeline", + "treetwisty", + "treetwistyopen", + "treeview", + "window", + "dialog", + "-moz-win-communications-toolbox", + "-moz-win-media-toolbox", + "-moz-win-browsertabbar-toolbox", + "-moz-win-borderless-glass", + "-moz-win-exclude-glass", + "-moz-mac-help-button", + "-moz-window-button-box", + "-moz-window-button-box-maximized", + "-moz-window-button-close", + "-moz-window-button-maximize", + "-moz-window-button-minimize", + "-moz-window-button-restore", + "-moz-window-titlebar", + "-moz-window-titlebar-maximized", + "-moz-mac-active-source-list-selection", + "-moz-mac-disclosure-button-closed", + "-moz-mac-disclosure-button-open", + "-moz-mac-source-list", + "-moz-mac-source-list-selection", + + "button-bevel", + "caret", + "listitem", + "menulist-textfield", + "menulist-text", + ], + "user-select": [ + "-moz-text", + ], + "line-height": [ + "-moz-block-height", + ], + "text-align": [ + "-moz-center-or-inherit", + ], +}; + +const sheet = document.getElementById("sheet"); +const div = document.querySelector("div"); + +test(function() { + sheet.textContent = `div { color: initial }`; + assert_equals(sheet.sheet.cssRules[0].style.length, 1); +}, "sanity"); + +for (const prop in NON_CONTENT_ACCESSIBLE_VALUES) { + const values = NON_CONTENT_ACCESSIBLE_VALUES[prop]; + test(function() { + for (const value of values) { + sheet.textContent = `div { ${prop}: ${value} }`; + const block = sheet.sheet.cssRules[0].style; + assert_equals( + block.length, + 0, + `${prop}: ${value} should not be parsed in content` + ); + block.setProperty(prop, value); + assert_equals( + block.length, + 0, + `${prop}: ${value} should not be settable via CSSOM in content` + ); + div.style.setProperty(prop, value); + assert_equals( + div.style.length, + 0, + `${prop}: ${value} should not be settable via CSSOM in content (inline style)` + ); + assert_not_equals( + getComputedStyle(div).getPropertyValue(prop), + value, + `${prop}: ${value} should not be settable via CSSOM in content (gcs)` + ); + + assert_false(CSS.supports(prop, value), `${prop}: ${value} should not claim to be supported`) + } + }, prop + " non-accessible values: " + values.join(", ")) +} +</script> diff --git a/layout/style/test/test_non_matching_sheet_media.html b/layout/style/test/test_non_matching_sheet_media.html new file mode 100644 index 0000000000..a57444df45 --- /dev/null +++ b/layout/style/test/test_non_matching_sheet_media.html @@ -0,0 +1,30 @@ +<!doctype html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1386840 --> +<title>Test for Bug 1386840: non-matching media list doesn't block rendering</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test("Test that <link> doesn't block rendering with non-matching media"); +var loadFired = false; +var scriptExecuted = false; + +function sheetLoaded() { + loadFired = true; + assert_true(scriptExecuted, "Shouldn't wait for load to execute script"); + t.done(); +} +</script> +<!-- + NOTE(emilio): This can theoretically race if an HTTP packet boundary with a + very long delay came right after the link and before the script. If this + ever becomes a problem, the way to fix this is using document.write() as + explained in bug 1386840 comment 12. +--> +<link rel="stylesheet" href="data:text/css,foo {}" media="print" onload="t.step(sheetLoaded)"> +<script> + t.step(function() { + scriptExecuted = true; + assert_false(loadFired, "Shouldn't have waited for load"); + }); +</script> 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 0000000000..edf2e6ee97 --- /dev/null +++ b/layout/style/test/test_of_type_selectors.xhtml @@ -0,0 +1,97 @@ +<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 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 (let 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 + " }"; + let 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_overscroll_behavior_pref.html b/layout/style/test/test_overscroll_behavior_pref.html new file mode 100644 index 0000000000..c12d4ce081 --- /dev/null +++ b/layout/style/test/test_overscroll_behavior_pref.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Test pref for overscroll-behavior property</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + </head> +<script class="testbody" type="text/javascript"> +function runTest() { + let css = "div { overscroll-behavior: auto; }"; + let style = document.createElement('style'); + style.appendChild(document.createTextNode(css)); + document.head.appendChild(style); + + is(document.styleSheets[0].cssRules[0].style.length, + 0, + "overscroll-behavior shouldn't be parsed if the pref is off"); + SimpleTest.finish(); +} +SpecialPowers.pushPrefEnv({ set: [["layout.css.overscroll-behavior.enabled", false]] }, + runTest); +SimpleTest.waitForExplicitFinish(); +</script> +</html> diff --git a/layout/style/test/test_page_parser.html b/layout/style/test/test_page_parser.html new file mode 100644 index 0000000000..6f6860d0f2 --- /dev/null +++ b/layout/style/test/test_page_parser.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=115199 --> +<head> + <meta charset="UTF-8"> + <title>Test of @page parser</title> + <script 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++) { + while(sheet.cssRules.length > 0) { + sheet.deleteRule(0); + } + sheet.insertRule(testset[curTest].rule, 0); + + try { + is(sheet.cssRules.length, 1, + testset[curTest].rule + " rule count"); + is(sheet.cssRules[0].type, CSSRule.PAGE_RULE, + testset[curTest].rule + " rule type"); + + if (testset[curTest].expected) { + 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 { + is(Object.keys(sheet.cssRules[0].style).length, 0, + testset[curTest].rule + " rule has no properties"); + } + } 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 0000000000..63ef95b980 --- /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 0000000000..390412e2fc --- /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 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 0000000000..0fb1cc80ed --- /dev/null +++ b/layout/style/test/test_parse_rule.html @@ -0,0 +1,261 @@ +<!DOCTYPE html> +<html lang=en> +<script 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(); +SimpleTest.requestLongerTimeout(2); + +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 = ""; + let computedBase = 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(computedBase, 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(); +}; + +// Running a debug build on the Android emulator is slooow and collecting +// all the session history this test amasses through repeated navigations +// adds considerably to the running time. Therefore, we restrict the +// amount of session history we keep around during this test. +SpecialPowers.pushPrefEnv({"set": [['browser.sessionhistory.max_entries', 4]]}, 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 0000000000..7a637c18e3 --- /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 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 0000000000..f872c00a8f --- /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"> +// 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. + +SpecialPowers.wrap(window).docShell.cssErrorReportingEnabled = true; + +// 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. +let 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{ color: '<t>'}" , o: 'color but found \u2018"<s>"\u2019.' }, + // _Bad_String + { i: "x{ color: '<t>\n}", o: 'color but found \u2018"<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 \x80\x81\x82", + s: "\\7f \x80\x81\x82" }, + { t: "\\\x83\\\x84\\\x85\\\x86", i: "\x83\x84\x85\x86", + s: "\x83\x84\x85\x86" }, + { t: "\\\x87\\\x88\\\x89\\\x8A", i: "\x87\x88\x89\x8A", + s: "\x87\x88\x89\x8A" }, + { t: "\\\x8B\\\x8C\\\x8D\\\x8E", i: "\x8B\x8C\x8D\x8E", + s: "\x8B\x8C\x8D\x8E" }, + { t: "\\\x8F\\\x90\\\x91\\\x92", i: "\x8F\x90\x91\x92", + s: "\x8F\x90\x91\x92" }, + { t: "\\\x93\\\x94\\\x95\\\x96", i: "\x93\x94\x95\x96", + s: "\x93\x94\x95\x96" }, + { t: "\\\x97\\\x98\\\x99\\\x9A", i: "\x97\x98\x99\x9A", + s: "\x97\x98\x99\x9A" }, + { t: "\\\x9B\\\x9C\\\x9D\\\x9E\\\x9F", i: "\x9B\x9C\x9D\x9E\x9F", + s: "\x9B\x9C\x9D\x9E\x9F" }, + + // CSS doesn't bother with the full Unicode rules for identifiers, + // instead declaring that any code point greater than or equal to + // U+0080 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])); + + info(css); + info(msg); + 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 0000000000..346547507c --- /dev/null +++ b/layout/style/test/test_pixel_lengths.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that pixel lengths don't change based on DPI</title> + <script 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> +<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); + +function test1() { + checkPixelRelativeUnits(); + SpecialPowers.pushPrefEnv({'set': [['layout.css.dpi', dpi=192]]}, test2); +} + +function test2() { + checkPixelRelativeUnits(); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_placeholder_restrictions.html b/layout/style/test/test_placeholder_restrictions.html new file mode 100644 index 0000000000..4e3e87ef16 --- /dev/null +++ b/layout/style/test/test_placeholder_restrictions.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1382786 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1382786</title> + <script 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 id="s"></style> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1382786">Mozilla Bug 1382786</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<input id="test"> +<input id="control"> +<script type="application/javascript"> + +/** Test for Bug 1382786 **/ +var test = getComputedStyle($("test"), "::placeholder"); +var control = getComputedStyle($("control"), "::placeholder"); +for (let prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if (info.type == CSS_TYPE_TRUE_SHORTHAND) { + // Can't get useful info out of getComputedStyle. + continue; + } + let prereqs = ""; + if (info.prerequisites) { + for (let name in info.prerequisites) { + prereqs += `${name}: ${info.prerequisites[name]}; `; + } + } + $("s").textContent = ` + #control::placeholder { ${prop}: ${info.initial_values[0]}; ${prereqs} } + #test::placeholder { ${prop}: ${info.other_values[0]}; ${prereqs} } + `; + // line-height does apply to ::placeholder, but only on <textarea>. We could + // switch the test to use a <textarea>. + if (info.applies_to_placeholder && prop != "line-height") { + isnot(get_computed_value(test, prop), + get_computed_value(control, prop), + `${prop} should apply to ::placeholder`); + } else { + is(get_computed_value(test, prop), + get_computed_value(control, prop), + `${prop} should not apply to ::placeholder`); + } +} +</script> +</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 0000000000..faeb6c6335 --- /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 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 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"> + +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); + 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 0000000000..ee75144dcb --- /dev/null +++ b/layout/style/test/test_position_float_display.html @@ -0,0 +1,111 @@ +<!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 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", + "block ruby": "block ruby", + "ruby": "block ruby", + "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", + "flow-root": "flow-root", + // Note: this is sometimes block + "list-item": "list-item", + "inline list-item": "list-item", + "inline flow-root list-item": "list-item", +}; + +function test_display_value(val) +{ + var floatLeftElem = document.getElementById("float-left"); + floatLeftElem.style.display = val; + var floatLeftConversion = window.getComputedStyle(floatLeftElem).display; + floatLeftElem.style.display = ""; + + var floatRightElem = document.getElementById("float-right"); + floatRightElem.style.display = val; + var floatRightConversion = window.getComputedStyle(floatRightElem).display; + floatRightElem.style.display = ""; + + var posAbsoluteElem = document.getElementById("position-absolute"); + posAbsoluteElem.style.display = val; + var posAbsoluteConversion = window.getComputedStyle(posAbsoluteElem).display; + posAbsoluteElem.style.display = ""; + + var posFixedElem = document.getElementById("position-fixed"); + posFixedElem.style.display = val; + var posFixedConversion = window.getComputedStyle(posFixedElem).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 0000000000..b542737e54 --- /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 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_prefers_contrast_color_pairs.html b/layout/style/test/test_prefers_contrast_color_pairs.html new file mode 100644 index 0000000000..f4a8945804 --- /dev/null +++ b/layout/style/test/test_prefers_contrast_color_pairs.html @@ -0,0 +1,49 @@ +<!doctype html> +<title>Test for Bug 922669</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<script> + function assertMatches(query) { + ok(matchMedia(query).matches, `${query} should match`); + } + function assertPrefersContrastIs(value) { + assertMatches(`(prefers-contrast: ${value})`); + } + add_task(async function setupPrefs() { + assertPrefersContrastIs("no-preference"); + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.display.document_color_use", 2], + ["browser.display.use_system_colors", false], + ] + }); + assertMatches("(prefers-contrast)"); + }); + async function testColors(foreground, background, expected) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.display.foreground_color", foreground], + ["browser.display.background_color", background], + ] + }); + + assertPrefersContrastIs(expected); + + // Test the inversed order too. + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.display.foreground_color", background], + ["browser.display.background_color", foreground], + ] + }); + + assertPrefersContrastIs(expected); + } + + add_task(async function test_prefers_contrast_colors() { + await testColors("black", "black", "less"); + await testColors("black", "white", "more"); + await testColors("red", "black", "custom"); + }); +</script> diff --git a/layout/style/test/test_priority_preservation.html b/layout/style/test/test_priority_preservation.html new file mode 100644 index 0000000000..7177949555 --- /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 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 0000000000..ba04ec341a --- /dev/null +++ b/layout/style/test/test_property_database.html @@ -0,0 +1,173 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test that property_database.js contains all supported CSS properties</title> + <script 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_LEGACY_SHORTHAND || + gCSSProperties[prop.name].type == CSS_TYPE_SHORTHAND_AND_LONGHAND, + "'" + prop.name + "' listed as CSS_TYPE_TRUE_SHORTHAND, CSS_TYPE_LEGACY_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 or its aliases are listed as logical + * properties. + */ +for (var prop in gCSSProperties) { + var entry = gCSSProperties[prop]; + if (entry.logical) { + ok(entry.type == CSS_TYPE_LONGHAND || + (entry.alias_for && gCSSProperties[entry.alias_for].logical), + "property '" + prop + "' is listed as CSS_TYPE_LONGHAND or is an alias due to it " + + "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 0000000000..1e3f382036 --- /dev/null +++ b/layout/style/test/test_property_syntax_errors.html @@ -0,0 +1,155 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test that we reject syntax errors listed in property_database.js</title> + <script 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.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(4); + +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 quirksFrame = document.getElementById("quirks"); + var wrappedFrame = SpecialPowers.wrap(quirksFrame); + var gQuirksDeclaration = wrappedFrame.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_pseudo_display_fixup.html b/layout/style/test/test_pseudo_display_fixup.html new file mode 100644 index 0000000000..de4dd0f810 --- /dev/null +++ b/layout/style/test/test_pseudo_display_fixup.html @@ -0,0 +1,29 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test item blockification of pseudo-elements</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #test { + display: flex; + } + #test::before, #test::after { + content: "test"; + display: inline-block; + color: green; + /* + * NOTE(emilio): The transition rule is very intentional, to avoid testing + * the eagerly resolved style. + */ + transition: color 1s ease; + } +</style> +<div id="test"></div> +<script> +test(function() { + document.body.offsetTop; + let test = document.getElementById("test"); + assert_equals(getComputedStyle(test, "::before").display, "block"); + assert_equals(getComputedStyle(test, "::after").display, "block"); +}, "::before and ::after pseudo-elements are blockified"); +</script> diff --git a/layout/style/test/test_pseudoelement_parsing.html b/layout/style/test/test_pseudoelement_parsing.html new file mode 100644 index 0000000000..b6fcf783f7 --- /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 0000000000..0a8c3d52f6 --- /dev/null +++ b/layout/style/test/test_pseudoelement_state.html @@ -0,0 +1,185 @@ +<!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 srcdoc="<!DOCTYPE html><style></style><div></div>"></iframe> + +<script> +var gIsAndroid = navigator.appVersion.includes("Android"); + +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; }' }, + + // Do the same as above, but using :is instead. + { 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:is(:hover) { background: green; }', + hover_reference_style: 'body input::-moz-range-thumb { background: green; }', + active_test_style: 'body input::-moz-range-thumb:is(:active) { background: lime; }', + active_reference_style: 'body input::-moz-range-thumb { background: lime; }' }, + + // Do the same as above, but using :not instead. + { 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: green } input::-moz-range-thumb:not(:hover) { background: black; }', + hover_test_style: '', + hover_reference_style: 'input::-moz-range-thumb { background: green !important; }', + active_test_style: 'input::-moz-range-thumb:active { background: lime !important; }', + active_reference_style: 'input::-moz-range-thumb { background: lime !important; }' }, + + // ::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", async function() { + await SpecialPowers.contentTransformsReceived(window); + runTests(); +}); +</script> diff --git a/layout/style/test/test_query_container_for.html b/layout/style/test/test_query_container_for.html new file mode 100644 index 0000000000..90dca9fab9 --- /dev/null +++ b/layout/style/test/test_query_container_for.html @@ -0,0 +1,62 @@ +<!doctype html> +<title>Test for bug 1789191</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> +<style> +@container (min-width: 0px) { +} + +@container name (min-width: 0px) { +} + +@container (min-height: 0px) { +} + +div { + width: 100px; + height: 100px; + background-color: blue; + margin: 10px; +} + +.container-height { + container-type: size; +} + +.container-unnamed { + container-type: inline-size; +} + +.container-named { + container-type: inline-size; + container-name: name; +} +</style> + +<div id="child1"></div> + +<div class="container-height" id="container1"> + <div class="container-named" id="container2"> + <div class="container-unnamed" id="container3"> + <div id="child2"></div> + </div> + </div> +</div> + +<script> + let sheet = document.querySelector("style").sheet; + let withoutFilter = SpecialPowers.wrap(sheet.cssRules[0]); + let withFilter = SpecialPowers.wrap(sheet.cssRules[1]); + let heightQuery = SpecialPowers.wrap(sheet.cssRules[2]); + + // Container query selection requires up-to-date layout information. + document.body.getBoundingClientRect(); + + is(withoutFilter.queryContainerFor(child1), null, "No filter, no container"); + is(withFilter.queryContainerFor(child1), null, "Filter, no container"); + is(heightQuery.queryContainerFor(child1), null, "Height, no container"); + + is(SpecialPowers.unwrap(withoutFilter.queryContainerFor(child2)), container3, "No filter, container"); + is(SpecialPowers.unwrap(withFilter.queryContainerFor(child2)), container2, "Filter"); + is(SpecialPowers.unwrap(heightQuery.queryContainerFor(child2)), container1, "Height"); +</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 0000000000..c6930ae401 --- /dev/null +++ b/layout/style/test/test_redundant_font_download.html @@ -0,0 +1,131 @@ +<!DOCTYPE HTML> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=879963 --> +<head> + <meta charset="utf-8"> + <title>Test for bug 879963</title> + <script 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, synchronously + 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 animation frame callback until it has + // seen a specific request, or until maxTime ms have passed + function waitForRequest(request, maxTime, func) { + timeLimit = Date.now() + maxTime; + requestAnimationFrame(function rAF() { + var req = getLastRequest(); + if (req == request || Date.now() > timeLimit) { + func(); + return; + } + requestAnimationFrame(rAF); + }); + } + + // 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", 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", 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", 1000, continueTest3); + } + + function continueTest3() { + is(getRequestLog(), "init;font;image", "should NOT have re-requested the font"); + + SimpleTest.finish(); + } + + waitForRequest("init", 5000, startTest); + + </script> + +</body> + +</html> diff --git a/layout/style/test/test_reframe_cb.html b/layout/style/test/test_reframe_cb.html new file mode 100644 index 0000000000..549d04c5c0 --- /dev/null +++ b/layout/style/test/test_reframe_cb.html @@ -0,0 +1,56 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + Test for bug 1519371: We don't reframe for changes that affect our containing + block status unless whether we're a containing block really changed. +</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<div id="content"></div> +<script> +SimpleTest.waitForExplicitFinish(); +const utils = SpecialPowers.getDOMWindowUtils(window); + +function expectReframe(shouldHaveReframed, callback) { + document.documentElement.offsetTop; + const previousConstructCount = utils.framesConstructed; + const previousRestyleGeneration = utils.restyleGeneration; + + callback(); + + document.documentElement.offsetTop; + isnot(previousRestyleGeneration, utils.restyleGeneration, + "We should have restyled"); + (shouldHaveReframed ? isnot : is)(previousConstructCount, + utils.framesConstructed, + `We should ${shouldHaveReframed ? "" : "not"} have reframed`); +} + +const content = document.getElementById("content"); + +// Without fixed-pos descendants, we should not reframe. +expectReframe(false, () => { + content.style.transform = "scale(1)"; +}); + +content.style.transform = ""; +content.innerHTML = `<div style="position: fixed"></div>`; + +// With fixed-pos descendants, got to reframe. +expectReframe(true, () => { + content.style.transform = "scale(1)"; +}); + +// If our containing-block-ness didn't change, we should not need to reframe. +expectReframe(false, () => { + content.style.willChange = "transform"; +}); + +// If it does change, we need to reframe. +expectReframe(true, () => { + content.style.willChange = ""; + content.style.transform = ""; +}); + +SimpleTest.finish(); +</script> diff --git a/layout/style/test/test_reframe_image_loading.html b/layout/style/test/test_reframe_image_loading.html new file mode 100644 index 0000000000..2f8a44c361 --- /dev/null +++ b/layout/style/test/test_reframe_image_loading.html @@ -0,0 +1,29 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + Test for bug 1395964: We don't reframe for image loading state changes. +</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<img id="content" src="support/1x1-transparent.png"></div> +<script> +SimpleTest.waitForExplicitFinish(); +const utils = SpecialPowers.getDOMWindowUtils(window); + +const content = document.getElementById("content"); + +onload = function() { + content.offsetTop; + + const previousConstructCount = utils.framesConstructed; + + content.addEventListener("load", function() { + content.offsetTop; + is(previousConstructCount, utils.framesConstructed, + "We should not have reframed"); + SimpleTest.finish(); + }); + + content.src = "support/blue-100x100.png"; +} +</script> diff --git a/layout/style/test/test_reframe_input.html b/layout/style/test/test_reframe_input.html new file mode 100644 index 0000000000..2887548abf --- /dev/null +++ b/layout/style/test/test_reframe_input.html @@ -0,0 +1,48 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for bug 1658302: We don't reframe for placeholder attribute value changes.</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<input id="input"> +<textarea id="textarea"></textarea> +<script> +SimpleTest.waitForExplicitFinish(); +const utils = SpecialPowers.DOMWindowUtils; + +function expectReframe(shouldHaveReframed, callback) { + document.documentElement.offsetTop; + const previousConstructCount = utils.framesConstructed; + const previousReflowCount = utils.framesReflowed; + + callback(); + + document.documentElement.offsetTop; + isnot(previousReflowCount, utils.framesReflowed, "We should have reflowed"); + (shouldHaveReframed ? isnot : is)(previousConstructCount, + utils.framesConstructed, + `We should ${shouldHaveReframed ? "" : "not"} have reframed`); +} + +for (const control of document.querySelectorAll("input, textarea")) { + // Creating the placeholder attribute reframes right now. + // + // TODO: Could be avoided with some more work. + expectReframe(true, () => { + control.placeholder = "foo"; + }); + + // Incrementally changing it should not reframe, just reflow. + expectReframe(false, () => { + control.placeholder = "bar"; + }); + + // Removing the placeholder attribute reframes right now. + // + // TODO: Could maybe be avoided with some more work. + expectReframe(true, () => { + control.removeAttribute("placeholder"); + }); +} + +SimpleTest.finish(); +</script> diff --git a/layout/style/test/test_reframe_pseudo_element.html b/layout/style/test/test_reframe_pseudo_element.html new file mode 100644 index 0000000000..0e0b418e81 --- /dev/null +++ b/layout/style/test/test_reframe_pseudo_element.html @@ -0,0 +1,46 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + Test for bug 1376352: We don't reframe all the time a replaced element that + matches generated content rules. +</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<style> +#flex::before, +input::before { + content: "Foo"; +} +</style> +<input type="text"> +<div id="flex"></div> +<script> +SimpleTest.waitForExplicitFinish(); +const utils = SpecialPowers.getDOMWindowUtils(window); + +function testNoReframe(callback) { + document.documentElement.offsetTop; + const previousConstructCount = utils.framesConstructed; + const previousRestyleGeneration = utils.restyleGeneration; + + callback(); + + document.documentElement.offsetTop; + isnot(previousRestyleGeneration, utils.restyleGeneration, + "We should have restyled"); + is(previousConstructCount, utils.framesConstructed, + "We shouldn't have reframed"); +} + +testNoReframe(function() { + const input = document.querySelector('input'); + input.style.color = "blue"; +}); + +testNoReframe(function() { + const flex = document.getElementById('flex'); + flex.style.color = "blue"; +}); + +SimpleTest.finish(); +</script> diff --git a/layout/style/test/test_rem_unit.html b/layout/style/test/test_rem_unit.html new file mode 100644 index 0000000000..bd46524798 --- /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 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_restyle_table_wrapper.html b/layout/style/test/test_restyle_table_wrapper.html new file mode 100644 index 0000000000..d8049b142e --- /dev/null +++ b/layout/style/test/test_restyle_table_wrapper.html @@ -0,0 +1,33 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + Test for bug 1371955: We don't incorrectly think that a table wrapper style + is the main table element style. +</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<style> +/* Test implicitly ::before and ::after too */ +span::before, span::after { + content: ""; + display: table; +} +</style> +<table id="realTable" style="margin: 10px"></table> +<span id="spanTable" style="display: table; padding: 10px;"></span> +<script> +SimpleTest.waitForExplicitFinish(); +const utils = SpecialPowers.getDOMWindowUtils(window); +document.documentElement.offsetTop; +for (const element of [realTable, spanTable]) { + const previousReflowCount = utils.framesReflowed; + const previousRestyleGeneration = utils.restyleGeneration; + element.style.color = "blue"; + document.documentElement.offsetTop; + isnot(previousRestyleGeneration, utils.restyleGeneration, + "We should have restyled"); + is(previousReflowCount, utils.framesReflowed, + "We shouldn't have reflowed"); +} +SimpleTest.finish(); +</script> 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 0000000000..17c1ebb2ab --- /dev/null +++ b/layout/style/test/test_restyles_in_smil_animation.html @@ -0,0 +1,137 @@ +<!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/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"; + +// Waits for |frameCount| requestAnimationFrame callbacks. +// Returns the number of frame actually waited. +function waitForAnimationFrames(frameCount) { + return new Promise(function(resolve, reject) { + let previousTime = document.timeline.currentTime; + let framesWaited = 0; + function handleFrame() { + // SMIL uses a time resolution of 1ms but our software-based vsync timer + // sometimes produces ticks with an interval of less than 1ms. In such + // cases we will skip restyling for SMIL animations since the SMIL time + // will not change. + // + // In the following we attempt to detect such situations and wait an + // additional frame when we detect it. However, the detection is not + // completely accurate since it uses the timeline time which is based on + // the navigation start time whereas the SMIL start time is based on the + // refresh driver time. + // + // To account for this inaccuracy the Promise returned by this method + // resolves with the number of frames waited with the additional frames. + // This can be used by the call site to add a suitable tolerance to the + // number of restylings it expects to happen. For example, if a call site + // is anticipating each animation frame to cause restyling, then the + // number of restylings, x, it should expect is frameCount <= x <= + // framesWaited. + const difference = document.timeline.currentTime - previousTime; + framesWaited++; + if (difference >= 1.0 && --frameCount <= 0) { + resolve(framesWaited); + return; + } + + previousTime = document.timeline.currentTime; + window.requestAnimationFrame(handleFrame); // wait another frame + } + window.requestAnimationFrame(handleFrame); + }); +} + +// Returns an object consisting of observed styling count and the number of +// frames actually waited because we detected a possibly overflapping SMIL +// time. +function observeStyling(frameCount) { + let priorAnimationTriggeredRestyles = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles; + + return new Promise(function(resolve) { + return waitForAnimationFrames(frameCount).then(framesWaited => { + const restyleCount = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles - priorAnimationTriggeredRestyles; + resolve({ + stylingCount: restyleCount, + framesWaited: framesWaited, + }); + }); + }); +} + +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(async function smil_is_in_display_none_subtree() { + await 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); + + await waitForAnimationFrames(2); + + let result = await observeStyling(5); + // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target + // element is newly associated with an nsIFrame. + ok(result.stylingCount >= 4 && + result.stylingCount <= result.framesWaited, + `should restyle in most frames (got ${result.stylingCount} restyles ` + + `over ${result.framesWaited} frames, expected 4~${result.framesWaited})`); + + var div = document.getElementById("target-div"); + + div.style.display = "none"; + getComputedStyle(div).display; + await waitForPaintFlushed(); + + result = await observeStyling(5); + is(result.stylingCount, 0, "should never restyle if display:none"); + + div.style.display = ""; + getComputedStyle(div).display; + await waitForAnimationFrames(2); + + result = await observeStyling(5); + // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target + // element is newly associated with an nsIFrame. + ok(result.stylingCount >= 4 && + result.stylingCount <= result.framesWaited, + `should restyle again (got ${result.stylingCount} restyles over ` + + `${result.framesWaited} frames, expected 4~${result.framesWaited})`); + + await ensureElementRemoval(animate); +}); +</script> +</body> diff --git a/layout/style/test/test_revert.html b/layout/style/test/test_revert.html new file mode 100644 index 0000000000..48897a75ca --- /dev/null +++ b/layout/style/test/test_revert.html @@ -0,0 +1,103 @@ +<!DOCTYPE HTML> +<title>Test for computation of CSS 'revert' on all properties</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="property_database.js"></script> +<style id="stylesheet"></style> +<link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +<div id="inheritanceParent"> + <div id="inherited"></div> +</div> +<div id="nonInherited"></div> +<div id="noAuthorStyleApplied"></div> +<pre id="test"> +<script class="testbody"> + +const kSheet = document.getElementById("stylesheet").sheet; +const kAllDifferentFromInitialRule = kSheet.cssRules[kSheet.insertRule("#inheritanceParent {}", kSheet.cssRules.length)]; +const kPrerequisites = kSheet.cssRules[kSheet.insertRule("#inherited, #inheritanceParent, #nonInherited, #noAuthorStyleApplied {}", kSheet.cssRules.length)]; +const kResetPropRule = kSheet.cssRules[kSheet.insertRule("#nonInherited {}", kSheet.cssRules.length)]; + +const kInheritedDiv = document.getElementById("inherited"); +const kResetDiv = document.getElementById("nonInherited"); +const kNoAuthorStylesDiv = document.getElementById("noAuthorStyleApplied"); + +function computedValue(node, property) { + return get_computed_value(getComputedStyle(node), property); +} + +function getInitialValue(node, property) { + node.style.setProperty(property, "initial"); + const initial = computedValue(node, property); + node.style.removeProperty(property); + return initial; +} + +function testResetProperty(property, info) { + kResetPropRule.style.setProperty(property, info.other_values[0]); + + const div = kResetDiv; + const initial = getInitialValue(div, property); + const computed = computedValue(div, property); + + isnot(computed, initial, `${property}: Should get something non-initial to begin with`); + + const defaultValue = computedValue(kNoAuthorStylesDiv, property); + + div.style.setProperty(property, "revert"); + const reverted = computedValue(div, property); + is(reverted, defaultValue, `${property}: Should behave as if there was no author style`); + div.style.removeProperty(property); + kResetPropRule.style.removeProperty(property); +} + +function testInheritedProperty(property, info) { + // Given how line-height works, and that it always returns the used value, we + // cannot test it. The prerequisites for line-height makes getComputedStyle + // and getDefaultComputedStyle return the same, even though the computed value + // is different (normal vs. 19px). + if (property == "line-height") + return; + + const div = kInheritedDiv; + const initial = getInitialValue(div, property); + const parentValue = computedValue(div.parentNode, property); + + isnot(parentValue, initial, `${property}: Should inherit something non-initial to begin with`); + + const inheritedValue = computedValue(div, property); + const hasUARule = inheritedValue != parentValue; + + const defaultValue = computedValue(kNoAuthorStylesDiv, property); + (hasUARule ? is : isnot)(defaultValue, inheritedValue, `${property}: Should get the non-inherited value from somewhere (expected ${hasUARule ? "UA Rule" : "inheritance"} - inherited: ${inheritedValue} - parent: ${parentValue} - default: ${defaultValue})`); + + div.style.setProperty(property, "revert"); + const reverted = computedValue(div, property); + if (hasUARule) + is(reverted, defaultValue, `${property}: Should behave as if there was no author style`); + else + is(reverted, inheritedValue, `${property}: Should behave as if there was no author style`); + div.style.removeProperty(property); +} + +function testProperty(property, info) { + if (info.prerequisites) + for (const prereq in info.prerequisites) + kPrerequisites.style.setProperty(prereq, info.prerequisites[prereq]); + if (info.inherited) + testInheritedProperty(property, info); + else + testResetProperty(property, info); + kPrerequisites.style = ""; +} + +for (const prop in gCSSProperties) { + const info = gCSSProperties[prop]; + if (info.type != CSS_TYPE_LONGHAND) + continue; + kAllDifferentFromInitialRule.style.setProperty(prop, info.other_values[0]); +} + +for (const prop in gCSSProperties) + testProperty(prop, gCSSProperties[prop]); +</script> +</pre> 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 0000000000..54dd9c222c --- /dev/null +++ b/layout/style/test/test_root_node_display.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=969460 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 969460</title> + <script 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).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).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 '" + val + "' 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 == "inline list-item" || + val == "inline flow-root list-item") { + is(floatConversion, "list-item", "'float' should blockify '" + val + "'"); + is(rootConversion, "block", + "We traditionally convert '" + val + "' 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 0000000000..e3103309aa --- /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 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"], + ["@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).textDecorationLine == + (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, "initial counter style is decimal"); + + // 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_rules_out_of_sheets.html b/layout/style/test/test_rules_out_of_sheets.html new file mode 100644 index 0000000000..2ca53d31b7 --- /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 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(ruleInner) { + return keep_rule_alive_by_matching( + get_only_child(ruleInner)); + }); +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 0000000000..d688139d3c --- /dev/null +++ b/layout/style/test/test_selectors.html @@ -0,0 +1,1348 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for CSS Selectors</title> + <script 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() { + SpecialPowers.pushPrefEnv({ set: [["layout.css.xul-tree-pseudos.content.enabled", true]] }, runTests); +} +function runTests() { + 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 for " + selector); + } + 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 (let i = 0; i < should_match.length; ++i) { + let e = should_match[i]; + is(ifwin.getComputedStyle(e).zIndex, String(zi), + "element in " + body_contents + " matched " + selector); + } + for (let i = 0; i < should_not_match.length; ++i) { + let 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 (let i = 0; i < should_match.length; ++i) { + let e = should_match[i]; + is(ifwin.getComputedStyle(e).zIndex, String(zi), + "element in " + body_contents + " matched " + ser1 + + " which is the reserialization of " + selector); + } + for (let i = 0; i < should_not_match.length; ++i) { + let 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. + isnot(style_text.parentNode.sheet.cssRules[idx], undefined, + "parse of selector \"" + ser1 + "\" failed"); + + if (style_text.parentNode.sheet.cssRules[idx] !== undefined) { + 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 wrappedCloneFrame = SpecialPowers.wrap(cloneiframe); + var clonedoc = wrappedCloneFrame.contentDocument; + var clonewin = wrappedCloneFrame.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].remove(); + + var should_match1 = match_fn(clonedoc); + var should_not_match1 = notmatch_fn(clonedoc); + + if (should_match1.length + should_not_match1.length == 0) { + ok(false, "nothing to check"); + } + + for (let i = 0; i < should_match1.length; ++i) { + let e = should_match1[i]; + is(clonewin.getComputedStyle(e).zIndex, String(zi), + "element in " + body_contents + " matched clone of " + + selector); + } + for (let i = 0; i < should_not_match1.length; ++i) { + let e = should_not_match1[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 wrappedCloneFrame = SpecialPowers.wrap(cloneiframe); + var clonedoc = wrappedCloneFrame.contentDocument; + var clonewin = wrappedCloneFrame.contentWindow; + var links = clonedoc.getElementsByTagName("link"); + // cause a clone + links[1].sheet.insertRule("#nonexistent { color: purple}", 0); + // remove the uncloned sheet + links[0].remove(); + + 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"); + test_parseable("[ATTR]") + should_serialize_to("[attr]", "[attr]"); + should_serialize_to("[ATTR]", "[ATTR]"); + + // Whether we should drop the bar is debatable. This matches Edge + // and Safari at the time of writing. + should_serialize_to("[|attr]", "[attr]"); + should_serialize_to("[|ATTR]", "[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]"); + + // And now tests for correctness of matching of attr selectors. + var attrTestBody = + // Paragraphs 1-5 + "<p attr></p> <p attr=''></p> <p attr='foo'></p> <p att></p> <p></p>" + + // Paragraphs 6-8 + "<p attr='foo bar'></p> <p attr='foo-bar'></p> <p attr='foobar'></p>" + + // Paragraphs 9-10 + "<p attr='foo bar baz'></p> <p attr='foo-bar-baz'></p>" + + // Paragraphs 11-12 + "<p attr='foo-bar baz'></p> <p attr=' foo-bar '></p> " + + // Paragraph 13-15 + "<p attr=' foo '></p> <p attr='fo'></p> <p attr='bar baz-foo'></p>"; + test_selector_in_html( + "[attr]", attrTestBody, + pset([1,2,3,6,7,8,9,10,11,12,13,14,15]), pset([4,5])); + test_selector_in_html( + "[attr=foo]", attrTestBody, + pset([3]), pset([1,2,4,5,6,7,8,9,10,11,12,13,14,15])); + test_selector_in_html( + "[attr~=foo]", attrTestBody, + pset([3,6,9,13]), pset([1,2,4,5,7,8,10,11,12,14,15])); + test_selector_in_html( + "[attr~=bar]", attrTestBody, + pset([6,9,15]), pset([1,2,3,4,5,7,8,10,11,12,13,14])); + test_selector_in_html( + "[attr~=baz]", attrTestBody, + pset([9,11]), pset([1,2,3,4,5,6,7,8,10,12,13,14,15])); + test_selector_in_html( + "[attr|=foo]", attrTestBody, + pset([3,7,10,11]), pset([1,2,4,5,6,8,9,12,13,14,15])); + test_selector_in_html( + "[attr|='bar baz']", attrTestBody, + pset([15]), pset([1,2,3,4,5,6,7,8,9,10,11,12,13,14])); + test_selector_in_html( + "[attr$=foo]", attrTestBody, + pset([3,15]), pset([1,2,4,5,6,7,8,9,10,11,12,13,14])); + test_selector_in_html( + "[attr$=bar]", attrTestBody, + pset([6,7,8]), pset([1,2,3,4,5,9,10,11,12,13,14,15])); + test_selector_in_html( + "[attr^=foo]", attrTestBody, + pset([3,6,7,8,9,10,11]), pset([1,2,4,5,12,13,14,15])); + test_selector_in_html( + "[attr*=foo]", attrTestBody, + pset([3,6,7,8,9,10,11,12,13,15]), pset([1,2,4,5,14])); + + // 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_balanced_unparseable(":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_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(-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_balanced_unparseable(":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_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(-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_balanced_unparseable(":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_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(-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_balanced_unparseable(":nth-child( -/**/2/**/n/**/+/**/4 )"); + test_parseable(":nth-child( -2n/**/ + /**/4 )"); + test_parseable(":nth-child( -2n/**/+/**/4 )"); + test_parseable(":nth-child( -2n /**/+/**/4 )"); + test_balanced_unparseable(":nth-child( -/**/n /**/+ /**/ 4 )"); + test_parseable(":nth-child( +/**/n /**/+ /**/ 4 )"); + test_balanced_unparseable(":nth-child(+1/**/n-1)"); + test_balanced_unparseable(":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 ::-moz-placeholder. + should_serialize_to("input::-moz-placeholder", "input::placeholder"); + + should_serialize_to(':lang("foo\\"bar")', ':lang(foo\\"bar)'); + + // 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_balanced_unparseable(":-moz-locale-dir(ltr)"); + test_balanced_unparseable(":-moz-locale-dir(rtl)"); + test_balanced_unparseable(":-moz-locale-dir(rTl)"); + test_balanced_unparseable(":-moz-locale-dir(LTR)"); + test_balanced_unparseable(":-moz-locale-dir(other)"); + + 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 chrome-only -moz-lwtheme + test_balanced_unparseable(":-moz-lwtheme"); + test_balanced_unparseable(":-moz-broken"); + + test_balanced_unparseable(":-moz-tree-row(selected)"); + test_balanced_unparseable("::-moz-tree-row(selected)"); + test_balanced_unparseable("::-MoZ-trEE-RoW(sElEcTeD)"); + test_balanced_unparseable(":-moz-tree-row(selected focus)"); + test_balanced_unparseable(":-moz-tree-row(selected , focus)"); + test_balanced_unparseable("::-moz-tree-row(selected ,focus)"); + test_balanced_unparseable(":-moz-tree-row(selected, focus)"); + test_balanced_unparseable("::-moz-tree-row(selected,focus)"); + test_balanced_unparseable(":-moz-tree-row(selected focus)"); + test_balanced_unparseable("::-moz-tree-row(selected , focus)"); + test_balanced_unparseable("::-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-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-inert"); + test_balanced_unparseable(":-moz-native-anonymous"); + test_balanced_unparseable(":-moz-table-border-nonzero"); + + // 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(()))"); + + // Bug 1685621 - Serialization of :not() + should_serialize_to(":not([disabled][selected])", ":not([disabled][selected])"); + should_serialize_to(":not([disabled],[selected])", ":not([disabled], [selected])"); + + // :-moz-any() + test_parseable(":-moz-any()"); + test_parseable(":-moz-any('foo')"); + test_parseable(":-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+p)"); + test_parseable(":-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,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_parseable(":-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 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_parseable("::-moz-color-swatch:is(:hover)"); + test_parseable("::-moz-color-swatch:not(:hover)"); + test_parseable("::-moz-color-swatch:where(:hover)"); + test_balanced_unparseable("::-moz-color-swatch:not(.foo)"); + test_balanced_unparseable("::-moz-color-swatch:first-child"); + test_balanced_unparseable("::-moz-color-swatch:host"); + test_balanced_unparseable("::-moz-color-swatch:host(div)"); + test_balanced_unparseable("::-moz-color-swatch:nth-child(1)"); + test_balanced_unparseable("::-moz-color-swatch:hover#foo"); + test_balanced_unparseable(".foo::after:not(.bar) ~ h3"); + + for (let selector of [ + "::-moz-color-swatch:where(.foo)", + "::-moz-color-swatch:is(.foo)", + "::-moz-color-swatch:where(p, :hover)", + "::-moz-color-swatch:is(p, :hover)", + ]) { + test_parseable(selector); + should_serialize_to(selector, selector); + ok(!CSS.supports(`selector(${selector})`), "supports should report false for forging selector parse failure"); + } + + 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_setPropertyWithNull.html b/layout/style/test/test_setPropertyWithNull.html new file mode 100644 index 0000000000..d54fd03cdd --- /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 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_shape_outside_CORS.html b/layout/style/test/test_shape_outside_CORS.html new file mode 100644 index 0000000000..2d2ee1c32f --- /dev/null +++ b/layout/style/test/test_shape_outside_CORS.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Test: shape-outside with a CORS violation</title> +<link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com"/> +<link rel="help" href="https://drafts.csswg.org/css-shapes/#shape-outside-property"/> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"/> + +<style> +.container { + clear: both; + width: 500px; +} +.shaper { + width: 50px; + height: 50px; + float: left; + background-color: green; +} +.shapeAllow { + shape-outside: url("support/1x1-transparent.png"); +} +.shapeRefuse { + shape-outside: url("http://example.com/layout/style/test/support/1x1-transparent.png"); +} +.sibling { + display: inline-block; +} +</style> + +<script> +SimpleTest.waitForExplicitFinish(); + +function runTests() { + let divAllow = document.getElementById("allow"); + let divAllowSib = divAllow.nextElementSibling; + ok(divAllowSib.getBoundingClientRect().left == divAllow.getBoundingClientRect().left, + "Test 1: Sibling should be at same left offset as div (shape-outside should be allowed), and onload should only fire after layout is complete."); + + let divRefuse = document.getElementById("refuse"); + let divRefuseSib = divRefuse.nextElementSibling; + ok(divRefuseSib.getBoundingClientRect().left != divRefuse.getBoundingClientRect().left, + "Test 2: Sibling should be at different left offset from div (shape-outside should be refused)."); + + SimpleTest.finish(); +} +</script> +</head> +<body onload="runTests()"> + <div class="container"> + <div id="allow" class="shaper shapeAllow"></div><div class="sibling">allow (image is blank, so text is flush left)</div> + </div> + <div class="container"> + <div id="refuse" class="shaper shapeRefuse"></div><div class="sibling">refuse (image unread, so text is moved to box edge)</div> + </div> +</body> +</html> diff --git a/layout/style/test/test_shared_sheet_caching.html b/layout/style/test/test_shared_sheet_caching.html new file mode 100644 index 0000000000..e59c0c139e --- /dev/null +++ b/layout/style/test/test_shared_sheet_caching.html @@ -0,0 +1,35 @@ +<!doctype html> +<meta charset="utf-8"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script> + function childWindowLoaded(childWin) { + is( + childWin.getComputedStyle(childWin.document.documentElement).backgroundColor, + "rgb(0, 255, 0)", + "Sheet should apply", + ) + is( + SpecialPowers.getDOMWindowUtils(childWin).parsedStyleSheets, + 0, + `Shouldn't need to parse the stylesheet ${childWin.parent == window ? "frame" : "top"}` + ); + if (childWin.top == childWin) { + SimpleTest.finish(); + } + } +</script> +<link rel="stylesheet" href="file_shared_sheet_caching.css"> +<iframe src="file_shared_sheet_caching.html"></iframe> +<a rel="opener" href="file_shared_sheet_caching.html" target="_blank">Navigation</a> +<script> +SimpleTest.waitForExplicitFinish(); +onload = function() { + info("Sheets parsed: " + SpecialPowers.DOMWindowUtils.parsedStyleSheets); + is( + getComputedStyle(document.documentElement).backgroundColor, + "rgb(0, 255, 0)", + "Sheet should apply", + ) + document.querySelector("a").click(); +} +</script> diff --git a/layout/style/test/test_sheet_privilege.html b/layout/style/test/test_sheet_privilege.html new file mode 100644 index 0000000000..d089fdc137 --- /dev/null +++ b/layout/style/test/test_sheet_privilege.html @@ -0,0 +1,33 @@ +<!doctype html> +<title>Test whether it can access a filed in MediaList with normal privilege after accessing with chrome privilege.</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + +<style> +@media screen and (max-width: 800px) { + body { + background-color: lime; + } +} +</style> +<script> +"use strict"; + +function assertMediaText(rule, description) { + is(rule.media.mediaText, "screen and (max-width: 800px)", description); +} + +add_task(function testCssRuleMedia() { + assertMediaText(SpecialPowers.wrap(document).styleSheets[0].cssRules[0], "privileged document"); + assertMediaText(document.styleSheets[0].cssRules[0], "unprivileged document"); +}); + +add_task(function testSheetFromCache() { + for (let i = 0; i < 2; ++i) { + const style = document.createElement("style"); + style.textContent = `@media screen and (max-width: 800px) {}`; + document.head.append(style); + assertMediaText(SpecialPowers.wrap(style).sheet.cssRules[0], "privileged sheet " + i); + assertMediaText(style.sheet.cssRules[0], "unprivileged sheet " + i); + } +}); +</script> 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 0000000000..cade526183 --- /dev/null +++ b/layout/style/test/test_shorthand_property_getters.html @@ -0,0 +1,248 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=376075 +--> +<head> + <title>Test for Bug 376075</title> + <script 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 = "; 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"); +is(e.style.border, "solid", "implied default color omitted serializing border"); +is(e.style.borderLeft, "solid", "implied default color omitted serializing border-left"); +is(e.style.cssText, "border: solid;", "implied default color omitted serializing declaration"); +e.setAttribute("style", "border-right: medium solid"); +is(e.style.borderRight, "solid", "implied default color omitted serializing border-right"); +is(e.style.cssText, "border-right: solid;", "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", "transition: color, width; transition-delay: 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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto"); +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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto;"); +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; animation-timeline: auto, auto, auto;"); +is(e.style.animation, "", "should not have animation shorthand (lists different lengths)"); + +// Check that the 'border' shorthand resets 'border-image' and +// 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 repeat; border-left: solid green;', + "border-left does NOT reset border-image"); +e.setAttribute("style", 'border-image: url("foo.png") 5 5 5 5; border: solid green'); +is(e.style.cssText, 'border: solid green;', "border DOES reset border-image"); + +// Test that the color goes at the beginning of the last item of the +// background shorthand. +e.setAttribute("style", "background: url(foo.png) blue"); +is(e.style.cssText, + "background: blue url(\"foo.png\");", + "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\"), blue url(\"foo.png\");", + "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 0000000000..5624b7398a --- /dev/null +++ b/layout/style/test/test_specified_value_serialization.html @@ -0,0 +1,281 @@ +<!doctype html> +<html> +<head> + <title>Test for miscellaneous specified value issues</title> + <script 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.transform = arr[0]; + is(document.documentElement.style.transform, arr[1], + "bug-721136 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)", + "bug-721136 expected case canonicalization of transform functions"); + + elt.setAttribute("style", + "font-variant-alternates: SWASH(fOo) stYLIStiC(Bar)"); + is(elt.style.getPropertyValue("font-variant-alternates"), + "stylistic(Bar) swash(fOo)", + "expected case and ordering canonicalization of font-variant-alternates 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)"], + ]; + + var css_color_4 = [ + ["rgba(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["rgba(0 0 0 / 0.1)", "rgba(0, 0, 0, 0.1)"], + ["rgb(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["rgb(0 0 0 / 0.2)", "rgba(0, 0, 0, 0.2)"], + ["hsla(0 0% 0% / 1)", "rgb(0, 0, 0)"], + ["hsla(0deg 0% 0% / 0.3)", "rgba(0, 0, 0, 0.3)"], + ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"], + ["hsl(0 0% 0% / 0.4)", "rgba(0, 0, 0, 0.4)"], + ]; + + 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]); + } + for (var i = 0; i < css_color_4.length; ++i) { + var test = css_color_4[i]; + p.style.color = test[0]; + is(p.style.color, test[1], "css-color-4 serialization value of " + test[0]); + } + + p.remove(); +})(); + +(function test_bug_1357117() { + // Test that vendor-prefixed gradient styles round-trip with the same prefix, + // or with no prefix. + var backgroundImages = [ + // [ specified style, + // expected serialization, + // descriptionOfTestcase ], + // Linear gradient with legacy-gradient-line (needs prefixed syntax): + [ "-webkit-linear-gradient(10deg, red, blue)", + "-webkit-linear-gradient(10deg, red, blue)", + "-webkit-linear-gradient with angled legacy-gradient-line" ], + + // Linear gradient with box corner (needs prefixed syntax): + [ "-webkit-linear-gradient(top left, red, blue)", + "-webkit-linear-gradient(left top, red, blue)", + "-webkit-linear-gradient with box corner" ], + + // Servo keeps the original prefix form which is closer to other impls. + [ "-webkit-radial-gradient(contain, red, blue)", + "-webkit-radial-gradient(closest-side, red, blue)", + "-webkit-radial-gradient with legacy 'contain' keyword" ], + ]; + + var frame_container = document.getElementById("display"); + var p = document.createElement("p"); + frame_container.appendChild(p); + + for (var i = 0; i < backgroundImages.length; ++i) { + var test = backgroundImages[i]; + p.style.backgroundImage = test[0]; + is(p.style.backgroundImage, test[1], + "serialization value of prefixed gradient expression (" + test[2] + ")"); + } + + p.remove(); +})(); + +(function test_bug_1357932() { + // Test for box-position keyword ordering, in serialization of specified CSS + // gradients. + var backgroundImages = [ + // [ specified style, + // expected serialization, + // descriptionOfTestcase ], + // Linear gradient to box position ordering: + [ "linear-gradient(to right top, gray, pink)", + "linear-gradient(to right top, gray, pink)", + "linear-gradient ordering to right top" ], + [ "linear-gradient(to top right, yellow, teal)", + "linear-gradient(to right top, yellow, teal)", + "linear-gradient ordering to top right" ], + ]; + + var frame_container = document.getElementById("display"); + var p = document.createElement("p"); + frame_container.appendChild(p); + + for (var i = 0; i < backgroundImages.length; ++i) { + var test = backgroundImages[i]; + p.style.backgroundImage = test[0]; + is(p.style.backgroundImage, test[1], + "serialization value of gradient expression (" + test[2] + ")"); + } + + p.remove(); +})(); + +(function test_bug_1367028() { + const borderImageSubprops = [ + "border-image-slice", + "border-image-outset", + "border-image-width" + ]; + const rectValues = [ + { + values: ["5 5 5 5", "5 5 5", "5 5", "5"], + expected: "5", + desc: "identical four sides", + }, + { + values: ["5 6 5 6", "5 6 5", "5 6"], + expected: "5 6", + desc: "identical values on each axis", + }, + { + values: ["5 6 7 6", "5 6 7"], + expected: "5 6 7", + desc: "identical values on left and right", + }, + { + values: ["5 6 5 7"], + desc: "identical values on top and bottom", + }, + { + values: ["5 5 6 6", "5 6 6 5"], + desc: "identical values on unrelated sides", + }, + { + values: ["5 6 7 8"], + desc: "different values on all sides", + }, + ]; + + let frameContainer = document.getElementById("display"); + let p = document.createElement("p"); + frameContainer.appendChild(p); + + for (let prop of borderImageSubprops) { + for (let {values, expected, desc} of rectValues) { + for (let value of values) { + p.style.setProperty(prop, value); + is(p.style.getPropertyValue(prop), + expected ? expected : value, `${desc} for ${prop}`); + p.style.removeProperty(prop); + } + } + } + + p.remove(); +})(); + +(function test_bug_1397619() { + var borderRadius = [ + // [ specified style, + // expected serialization, + // descriptionOfTestcase ], + [ "10px 10px", + "10px", + "Width and height specified for " ], + [ "10px", + "10px", + "Only width specified for " ], + ]; + + + var frame_container = document.getElementById("display"); + var p = document.createElement("p"); + frame_container.appendChild(p); + + [ + "border-top-left-radius", + "border-bottom-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-spacing", + ].forEach(function(property) { + for (var i = 0; i < borderRadius.length; ++i) { + var test = borderRadius[i]; + p.style.setProperty(property, test[0]); + is(p.style.getPropertyValue(property), test[1], test[2] + property); + } + }); + + p.style.paintOrder = "markers"; + is(p.style.paintOrder, "markers", + "specified value serialization for paint-order doesn't contain repetitive values"); + + p.remove(); +})(); + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv( + { + set: [['layout.css.individual-transform.enabled', true]], + }, + () => + window.open('file_specified_value_serialization_individual_transforms.html') +); + +</script> +</pre> +</body> +</html> diff --git a/layout/style/test/test_style_attr_listener.html b/layout/style/test/test_style_attr_listener.html new file mode 100644 index 0000000000..b824fe7ff4 --- /dev/null +++ b/layout/style/test/test_style_attr_listener.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test for Bug 338679 (from bug 1340341)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<div style=""></div> +<script> +SimpleTest.waitForExplicitFinish(); + +// Run the test first with mutation events enabled and then disabled. +SpecialPowers.pushPrefEnv( + { 'set': [['dom.mutation-events.cssom.disabled', false]] }, + runTest +); + +function runTest(stop) { + let div = document.querySelector('div'); + let expectation; + let count = 0; + div.style.color = "red"; + div.addEventListener('DOMAttrModified', function(evt) { + count++; + is(evt.prevValue, expectation.prevValue, `Previous value for event ${count}`); + is(evt.newValue, expectation.newValue, `New value for event ${count}`); + }); + expectation = { prevValue: 'color: red;', newValue: 'color: green;' }; + div.style.color = "green"; + expectation = { prevValue: 'color: green;', newValue: '' }; + div.style.color = ''; + if (SpecialPowers.getBoolPref("dom.mutation-events.cssom.disabled")) { + is(count, 0, "No DOMAttrModified event should be triggered"); + } else { + is(count, 2, "DOMAttrModified events should have been triggered"); + } + + if (!stop) { + div.setAttribute("style", ""); + SpecialPowers.pushPrefEnv( + { 'set': [['dom.mutation-events.cssom.disabled', true]] }, + function() { + runTest(true); + SimpleTest.finish(); + } + ); + } +} +</script> +</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 0000000000..5a5b87e122 --- /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 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 0000000000..e6e64afc24 --- /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 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 0000000000..95f727a58d --- /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 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_stylesheet_additions.html b/layout/style/test/test_stylesheet_additions.html new file mode 100644 index 0000000000..0e115e91f4 --- /dev/null +++ b/layout/style/test/test_stylesheet_additions.html @@ -0,0 +1,68 @@ +<!doctype html> +<meta charset="utf-8"> +<title> + Test for bug 1273303: Stylesheet additions and removals known to not + affect the document don't trigger restyles +</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<div class="classScope"> + <div></div> +</div> +<div id="idScope"> + <div></div> +</div> +<!-- + We do it this way, using `disabled`, because appending stylesheets to the + document marks a restyle for that <style> element as needed, so we can't + accurately measure whether we've restyled or not. +--> +<style id="target" disabled></style> +<script> +SimpleTest.waitForExplicitFinish(); +const utils = SpecialPowers.getDOMWindowUtils(window); +const TESTS = [ + { selector: ".nonexistentClassScope", restyle: false }, + { selector: ".nonexistentClassScope + div", restyle: true }, + { selector: ".nonexistentClassScope div + div", restyle: false }, + { selector: ".classScope", restyle: true }, + { selector: ".classScope div", restyle: true }, + { selector: "#idScope", restyle: true }, + { selector: "#nonexistentIdScope", restyle: false }, + { selector: "#nonexistentIdScope div + bar", restyle: false }, + { selector: "baz", restyle: false }, + { cssText: " ", restyle: false }, + { cssText: "@keyframes foo { from { color: green } to { color: red } } #whatever { animation-name: foo; }", restyle: false }, +]; + +for (const test of TESTS) { + let cssText = test.cssText ? test.cssText : (test.selector + " { color: green; }"); + target.innerHTML = cssText; + + document.body.offsetWidth; + const prevGeneration = utils.restyleGeneration; + + target.disabled = true; + + document.body.offsetWidth; + (test.restyle ? isnot : is)(utils.restyleGeneration, prevGeneration, + `Stylesheet removal with ${cssText} should ${test.restyle ? "have" : "not have"} caused a restyle`); + + target.disabled = false; // Make the stylesheet effective. + + if (test.selector) { + let element = document.querySelector(test.selector); + if (element) { + is(test.restyle, true, "How could we not expect a restyle?"); + is(getComputedStyle(element).color, "rgb(0, 128, 0)", + "Element style should've changed appropriately"); + } + } + + document.body.offsetWidth; + (test.restyle ? isnot : is)(utils.restyleGeneration, prevGeneration, + `Stylesheet addition with ${cssText} should ${test.restyle ? "have" : "not have"} caused a restyle`); +} + +SimpleTest.finish(); +</script> diff --git a/layout/style/test/test_stylesheet_clone_font_face.html b/layout/style/test/test_stylesheet_clone_font_face.html new file mode 100644 index 0000000000..e50bfec661 --- /dev/null +++ b/layout/style/test/test_stylesheet_clone_font_face.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html lang="en-US"> +<link rel="stylesheet" href="data:text/css,@font-face { font-family: 'MarkA'; src: url(../fonts/markA.ttf); }"> +<link rel="stylesheet" href="data:text/css,@font-face { font-family: 'MarkA'; src: url(../fonts/markA.ttf); }"> + +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<style> + body { font-family: "MarkA"; } +</style> +<div>ABC</div> +<script> + function runTest() { + var links = document.getElementsByTagName("link"); + links[0].sheet.cssRules[0].style.src = "../fonts/markB.ttf"; + + // Test that the cloned sheet is unaffected. + isnot(links[1].sheet.cssRules[0].style.src, "../fonts/markB.ttf", "Cloned sheet left unchanged."); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + runTest(); +</script> +</html> diff --git a/layout/style/test/test_supports_rules.html b/layout/style/test/test_supports_rules.html new file mode 100644 index 0000000000..8b88fd7836 --- /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 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_inner, expected_match) { + style.textContent = "#display1, #display2 { text-decoration: overline }\n" + + "@supports " + condition_inner + "{\n" + + " #display1 { text-decoration: line-through }\n" + + "}\n" + + "#display2 { text-decoration: underline }\n"; + is(cs1.textDecorationLine, + expected_match ? "line-through" : "overline", + "@supports condition \"" + condition_inner + "\" should " + + (expected_match ? "" : "NOT ") + "match"); + is(cs2.textDecorationLine, "underline", + "@supports condition \"" + condition_inner + "\" 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)", true); + 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)", true); + check_balanced_condition("(color: green)and (color: blue)", true); + 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)", true); + check_balanced_condition("(color: green)or (color: blue)", true); + + 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 0000000000..10fb54c45a --- /dev/null +++ b/layout/style/test/test_system_font_serialization.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=475214 +--> +<head> + <title>Test for Bug 475214</title> + <script 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"> + +/* Helper copied from property_database.js */ +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; +} + +/** 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.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.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: inherit; font-family: Helvetica;"); +EXPECTED_DECLS = [ + "font-family: Helvetica;", + "font-feature-settings: inherit;", + "font-kerning: inherit;", + "font-language-override: inherit;", + "font-size-adjust: inherit;", + "font-size: inherit;", + "font-stretch: inherit;", + "font-style: inherit;", + "font-variant: inherit;", + "font-weight: inherit;", + "line-height: inherit;", +]; +if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) { + EXPECTED_DECLS.push("font-optical-sizing: inherit;"); + EXPECTED_DECLS.push("font-variation-settings: inherit;"); +} +EXPECTED_DECLS = EXPECTED_DECLS.sort().join(" "); +let sortedDecls = e.style.cssText.split(/ (?=font-|line-)/).sort().join(" "); +is(sortedDecls, EXPECTED_DECLS, "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 0000000000..d2cfed6667 --- /dev/null +++ b/layout/style/test/test_text_decoration_shorthands.html @@ -0,0 +1,136 @@ +<!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", + textDecorationThickness: "auto", +}; + +// 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: "20px", + textDecorationThickness: "20px", + }, + { + specified: "auto", + textDecorationThickness: "auto", + }, + { + specified: "from-font", + textDecorationThickness: "from-font", + }, + { + 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", + }, + { + specified: "solid underline 10px", + textDecorationStyle: "solid", + textDecorationLine: "underline", + textDecorationThickness: "10px", + }, + { + specified: "line-through blue 200px", + textDecorationLine: "line-through", + textDecorationColor: "rgb(0, 0, 255)", + textDecorationThickness: "200px", + }, + { + specified: "underline wavy red 0", + textDecorationLine: "underline", + textDecorationStyle: "wavy", + textDecorationColor: "rgb(255, 0, 0)", + textDecorationThickness: "0px", + }, + { + specified: "overline -30px", + textDecorationLine: "overline", + textDecorationThickness: "-30px", + }, + { + specified: "underline red from-font", + textDecorationLine: "underline", + textDecorationColor: "rgb(255, 0, 0)", + textDecorationThickness: "from-font", + }, +]; + +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", "textDecorationThickness"]); + +</script> +</body> +</html> diff --git a/layout/style/test/test_transitions.html b/layout/style/test/test_transitions.html new file mode 100644 index 0000000000..c26ba9be8f --- /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 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() { + let 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 (let tf in timingFunctions) { + let p = document.createElement("p"); + let 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 (let itime = 2; itime < 8; itime += 2) { + let p = document.createElement("p"); + let 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) { + let 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 (let d = -4; d <= 4; ++d) { + let p = document.createElement("p"); + let delay = d + "s"; + let 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 (let d = -4; d <= 4; ++d) { + let p = document.createElement("p"); + let delay = d + "s"; + let 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) +{ + let p = document.createElement("p"); + let 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 (let i in descendant_tests) { + let 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 (let i in number_tests) { + let test = number_tests[i]; + let p = document.createElement("p"); + p.setAttribute("style", test.style); + let 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) +{ + let 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) +{ + let 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, "").width; + +// 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 (let 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 (let test in tftests) { + let p = tftests[test][0]; + p.style.textIndent = "100px"; +} +for (let test in interrupt_tests) { + let p = interrupt_tests[test][0]; + p.style.textIndent = "100px"; +} +for (let d in delay_tests) { + let p = delay_tests[d]; + p.style.marginLeft = "100px"; +} +for (let d in delay_zero_tests) { + let p = delay_zero_tests[d]; + p.style.marginLeft = "100px"; +} +reset_test.style.marginLeft = "100px"; +reset_test_reference.style.marginLeft = "100px"; +for (let i in descendant_tests) { + let test = descendant_tests[i]; + test.parentNode.style.textIndent = "150px"; + test.parentNode.style.letterSpacing = "5px"; +} +for (let i in number_tests) { + let 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 (let i in pseudo_element_tests) { + let test = pseudo_element_tests[i]; + test.element.classList.add("started"); +} +lateref.style.textIndent = "1000px"; + +// flush style changes +x = getComputedStyle(div, "").width; + +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 (let 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 (let 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) +{ + let tf = timingFunctions["ease-out"]; + for (let d in delay_tests) { + let 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 (let i = 1; i <= 8; ++i) { + add_future_call(i, check_delay_test); +} + +function check_delay_zero_test(time) +{ + for (let d in delay_zero_tests) { + let 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 (let 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 (let 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 ]; + let tf = timingFunctions.ease; + + var time = px_to_num(earlyrefcs.textIndent) / REF_PX_PER_SEC; + + for (let i in descendant_tests) { + let 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() +{ + let tf = timingFunctions.ease; + for (let d in number_tests) { + let test = number_tests[d]; + let 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 (let i in display_tests) { + let 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) +{ + let tf = timingFunctions["ease-in-out"]; + for (let i in pseudo_element_tests) { + let 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 0000000000..dfc16e54d1 --- /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 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 0000000000..35fc608c20 --- /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 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 0000000000..e95581be32 --- /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 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); +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_at_start.html b/layout/style/test/test_transitions_at_start.html new file mode 100644 index 0000000000..bad36e7673 --- /dev/null +++ b/layout/style/test/test_transitions_at_start.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1380133 +--> +<head> + <meta charset=utf-8> + <title>Test for transition value at start (Bug 1380133)</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel="stylesheet" href="/resources/testharness.css"> + <style> + a { + color: red; + transition: 100s color; + } + a.active { + color: blue; + } + </style> +</head> +<body> +<a id=anchor><span id=span>Test</span></a> +</body> +<script> +'use strict'; + +const anchor = document.getElementById('anchor'); +const span = document.getElementById('span'); + +test(() => { + anchor.getBoundingClientRect(); + anchor.classList.add('active'); + assert_equals(getComputedStyle(span).color, 'rgb(255, 0, 0)', + 'The child of a transitioning element should inherit its' + + ' parent\'s transition start value'); +}, 'Transition start value should be inherited'); +</script> +</html> diff --git a/layout/style/test/test_transitions_bug537151.html b/layout/style/test/test_transitions_bug537151.html new file mode 100644 index 0000000000..8d3b84a5fc --- /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 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); +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 0000000000..496d95e6a1 --- /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 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.from(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(); + } + }); + }); + + cases.forEach(aCase => aCase.className = 'another' ); + + window.setTimeout(() => cases[0].className = '', 500); + window.setTimeout(() => cases[1].className = cases[2].className = '', 250); + +}); + +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 0000000000..3dfad41e58 --- /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 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 0000000000..7b350de6b2 --- /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 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 0000000000..4d49db1e3a --- /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 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 = "letter-spacing"; +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); +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 0000000000..d04348bc0a --- /dev/null +++ b/layout/style/test/test_transitions_events.html @@ -0,0 +1,294 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=531585 +--> +<head> + <title>Test for Bug 531585 (transitionend event)</title> + <script 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 */ + 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_finish_five = false; +var did_finish_six = false; +var got_before = false; +var got_after = false; + +// Flush layout to guarantee consistent transitions. +document.body.getBoundingClientRect(); + +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 ((event.target == $("five") && did_finish_five) || + (event.target == $("six") && did_finish_six)) { + todo(false, + "it seems that transitionstart and transitionend had been " + + "processed in the same frame"); + return; + } + ok(false, + "unexpected event on " + event.target.nodeName + + " element with id '" + event.target.id + "' " + + "elapsedTime=" + event.elapsedTime + + " propertyName='" + event.propertyName + "'"); + } + }); + +$("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(); + }); + +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(); + }); + +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"); + +$("five").addEventListener("transitionstart", function() { + if (cs("five").color == "rgb(0, 255, 0)") { + // The transition has finished already. + did_finish_five = true; + } + $("five").style.transitionProperty = "margin-left"; +}); + +$("six").addEventListener("transitionstart", function() { + if (cs("six").color == "rgb(0, 255, 0)") { + // The transition has finished already. + did_finish_six = true; + } + $("six").style.transitionDuration = "0s"; + $("six").style.transitionDelay = "0s"; + $("six").style.color = "blue"; +}); + +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 0000000000..058152adfb --- /dev/null +++ b/layout/style/test/test_transitions_per_property.html @@ -0,0 +1,3245 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=435441 +--> +<head> + <title>Test for Bug 435441</title> + <meta charset=utf-8> + <script 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> +<pre id="test"> +<script type="application/javascript"> + +/* eslint no-shadow: ["error", {"allow": ["prop", "div"]}] */ +/* eslint-disable dot-notation */ + + +/** 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 = { + "aspect-ratio" : [ test_aspect_ratio_transition ], + "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 ], + "border-start-start-radius": [ test_radius_transition ], + "border-start-end-radius": [ test_radius_transition ], + "border-end-start-radius": [ test_radius_transition ], + "border-end-end-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-rule-color": [ test_color_transition, + test_currentcolor_transition ], + "column-rule-width": [ test_length_transition, + test_length_clamped ], + "column-width": [ test_length_transition, + test_length_clamped ], + "cx": [ test_length_transition, test_percent_transition, + test_length_unclamped, test_percent_unclamped ], + "cy": [ test_length_transition, test_percent_transition, + test_length_unclamped, test_percent_unclamped ], + "background-color": [ test_color_transition, + test_currentcolor_transition ], + "background-position": [ test_background_position_transition, + 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, + test_length_percent_pair_clamped ], + "border-bottom-color": [ test_color_transition, + test_currentcolor_transition ], + "border-bottom-width": [ test_length_transition, + test_length_clamped ], + "border-left-color": [ test_color_transition, + test_currentcolor_transition ], + "border-left-width": [ test_length_transition, + test_length_clamped ], + "border-right-color": [ test_color_transition, + test_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_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 ], + "accent-color": [ test_color_transition, + test_currentcolor_transition, + test_auto_color_transition ], + "caret-color": [ test_color_transition, + test_currentcolor_transition, + test_auto_color_transition ], + "clip": [ test_rect_transition ], + "clip-path": [ test_basic_shape_or_url_transition, + test_path_function ], + "color": [ test_color_transition, + test_currentcolor_transition ], + "d": [ test_path_function ], + "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, + test_flex_basis_content_transition ], + "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_percent_transition, test_percent_clamped ], + "font-weight": [ test_font_weight ], + "column-gap": [ test_grid_gap ], + "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 ], + "mask-position": [ test_background_position_transition, + test_length_percent_pair_unclamped ], + "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 */ ], + "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 */ ], + "mask-size": [ test_background_size_transition, + test_length_percent_pair_clamped ], + "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 ], + "overflow-clip-margin": [ test_length_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_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 ], + "r": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "rx": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "ry": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "shape-image-threshold": [ test_float_zeroToOne_transition, + // shape-image-threshold (like opacity) is + // clamped in computed style + // (not parsing/interpolation) + test_float_zeroToOne_clamped ], + "shape-margin": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped ], + "shape-outside": [ test_basic_shape_or_url_transition ], + "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 ], + "stroke-dashoffset": [ test_length_transition, test_percent_transition, + test_length_unclamped, test_percent_unclamped, ], + "stroke-miterlimit": [ test_float_zeroToOne_transition, + test_float_aboveOne_transition, + test_float_aboveZero_clamped ], + "stroke-opacity" : [ test_float_zeroToOne_transition, + // opacity is clamped in computed style + // (not parsing/interpolation) + test_float_zeroToOne_clamped ], + "stroke-width": [ test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped, ], + "tab-size": [ test_float_zeroToOne_transition, + test_float_aboveOne_transition, test_length_clamped ], + "text-decoration": [ test_color_shorthand_transition, + test_currentcolor_shorthand_transition ], + "text-decoration-color": [ test_color_transition, + test_currentcolor_transition ], + "text-emphasis-color": [ test_color_transition, + test_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 ], + "x": [ test_length_transition, test_percent_transition, + test_length_unclamped, test_percent_unclamped ], + "y": [ test_length_transition, test_percent_transition, + test_length_unclamped, test_percent_unclamped ], + "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ], + "-webkit-line-clamp": [ test_pos_integer_or_none_transition ], + "-webkit-text-fill-color": [ test_color_transition, + test_currentcolor_transition ], + "-webkit-text-stroke-color": [ test_color_transition, + test_currentcolor_transition ], + "text-underline-offset": [ test_length_transition ], + "text-decoration-thickness": [ test_length_transition ], + "scroll-margin-top": [ + test_length_transition, + ], + "scroll-margin-right": [ + test_length_transition, + ], + "scroll-margin-bottom": [ + test_length_transition, + ], + "scroll-margin-left": [ + test_length_transition, + ], + "scroll-padding-top": [ + test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped, + ], + "scroll-padding-right": [ + test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped, + ], + "scroll-padding-bottom": [ + test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped, + ], + "scroll-padding-left": [ + test_length_transition, test_percent_transition, + test_length_clamped, test_percent_clamped, + ], + "scrollbar-color": [ test_scrollbar_color_transition ], +}; + +if (IsCSSPropertyPrefEnabled("layout.css.backdrop-filter.enabled")) { + supported_properties["backdrop-filter"] = [ test_filter_transition ]; +} + +if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) { + supported_properties["font-variation-settings"] = [ test_font_variations_transition ]; +} + +if (IsCSSPropertyPrefEnabled("layout.css.individual-transform.enabled")) { + supported_properties["rotate"] = [ test_rotate_transition ]; + supported_properties["scale"] = [ test_scale_transition ]; + supported_properties["translate"] = [ test_translate_transition ]; +} + +if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) { + supported_properties["contain-intrinsic-width"] = [ test_length_transition, test_auto_with_length_transition ]; + supported_properties["contain-intrinsic-height"] = supported_properties["contain-intrinsic-width"]; +} + +if (IsCSSPropertyPrefEnabled("layout.css.content-visibility.enabled")) { + supported_properties["content-visibility"] = [test_content_visibility_transition]; +} + +if (IsCSSPropertyPrefEnabled("layout.css.zoom.enabled")) { + Object.assign(supported_properties, { + "zoom": [ test_number_transition, test_percent_transition ], + }); +} + +// For properties which are well-tested by web-platform-tests, we don't need to +// test animations/transitions again on them. +var skipped_transitionable_properties = [ + "border-image-outset", + "border-image-slice", + "border-image-width", + "font-style", // Tests being added in https://github.com/web-platform-tests/wpt/pull/37570 + "grid-template-columns", + "grid-template-rows", + "offset-path", + "offset-distance", + "offset-rotate", + "offset-anchor", + "offset-position", +] + +// Logical properties. +for (const logical_side of ["inline-start", "inline-end", "block-start", "block-end"]) { + supported_properties["border-" + logical_side + "-color"] = supported_properties["border-top-color"]; + supported_properties["border-" + logical_side + "-width"] = supported_properties["border-top-width"]; + supported_properties["margin-" + logical_side] = supported_properties["margin-top"]; + supported_properties["padding-" + logical_side] = supported_properties["padding-top"]; + supported_properties["inset-" + logical_side] = supported_properties["top"]; + supported_properties["scroll-margin-" + logical_side] = supported_properties["scroll-margin-top"]; + supported_properties["scroll-padding-" + logical_side] = supported_properties["scroll-padding-top"]; +} + +for (const logical_size of ["inline", "block"]) { + supported_properties[logical_size + "-size"] = supported_properties["width"]; + supported_properties["min-" + logical_size + "-size"] = supported_properties["min-width"]; + supported_properties["max-" + logical_size + "-size"] = supported_properties["max-width"]; + if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) { + supported_properties["contain-intrinsic-" + logical_size + "-size"] = supported_properties["contain-intrinsic-width"]; + } +} + +var div = document.getElementById("display"); +var cs = getComputedStyle(div, ""); +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: 'matrix(1, 0, 0, 1, 0, 0)', + end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)', + expected: 'matrix(1, 0, 0, 1, -2.5, 15)', + round_error_ok: true }, + { start: 'matrix(1, 0, 0, 1, 0, 0)', + 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_uncomputed: 'skewX(-11.25deg) translate(0)' }, + // 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 }, + // extended shorter transform list + { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)', + expected_uncomputed: 'skewY(30deg) translateX(0)' }, + + + // 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 + { 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: 'skewX(45deg) rotate(90deg)', + end: 'skewX(-45deg) rotate(90deg)', + expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' }, + + // extended lists + { start: 'skewY(45deg) rotate(90deg) translate(0)', + end: 'skewY(-45deg) rotate(90deg)', + expected_uncomputed: 'skewY(22.5deg) rotate(90deg) translate(0)' }, + { start: 'skewX(-60deg) rotate(90deg) translate(0)', + end: 'skewX(60deg) rotate(90deg)', + expected_uncomputed: 'skewX(-30deg) rotate(90deg) translate(0)' }, +]; + +// We intentionally use a non-default reference-box so we always serialize it. +// Therefore, we can reuse these tests for clip-path and shape-outside. +// Bug 1313619: Add some tests for two basic shapes with an explicit +// reference-box and a default one, for each property (because they use +// different default reference-box). +const basicShapesTests = [ + { start: "none", end: "none", + expected: ["none"] }, + // none to shape + { start: "none", + end: "circle(500px at 500px 500px) content-box", + expected: ["circle", ["500px at 500px 500px"], "content-box"] + }, + { start: "none", + end: "ellipse(500px 500px at 500px 500px) content-box", + expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"] + }, + { start: "none", + end: "polygon(evenodd, 500px 500px, 500px 500px) content-box", + expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "content-box"] + }, + { start: "none", + end: "inset(500px 500px 500px 500px round 500px 500px) content-box", + expected: ["inset", ["500px round 500px"], "content-box"] + }, + // matching functions + { start: "circle(100px)", end: "circle(500px)", + expected: ["circle", ["200px"]] }, + { start: "ellipse(100px 100px)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["200px 200px"]] }, + { start: "circle(100px at 100px 100px) content-box", + end: "circle(500px at 500px 500px) content-box", + expected: ["circle", ["200px at 200px 200px"], "content-box"] + }, + { start: "ellipse(100px 100px at 100px 100px) content-box", + end: "ellipse(500px 500px at 500px 500px) content-box", + expected: ["ellipse", ["200px 200px at 200px 200px"], "content-box"] + }, + { start: "polygon(evenodd, 100px 100px, 100px 100px) content-box", + end: "polygon(evenodd, 500px 500px, 500px 500px) content-box", + expected: ["polygon", ["evenodd, 200px 200px, 200px 200px"], "content-box"] + }, + { start: "inset(100px 100px 100px 100px round 100px 100px) content-box", + end: "inset(500px 500px 500px 500px round 500px 500px) content-box", + expected: ["inset", ["200px round 200px"], "content-box"] + }, + // matching functions percentage + { start: "circle(100%)", end: "circle(500%)", + expected: ["circle", ["200%"]] }, + { start: "ellipse(100% 100%)", end: "ellipse(500% 500%)", + expected: ["ellipse", ["200% 200%"]] }, + { start: "circle(100% at 100% 100%) content-box", + end: "circle(500% at 500% 500%) content-box", + expected: ["circle", ["200% at 200% 200%"], "content-box"] + }, + { start: "ellipse(100% 100% at 100% 100%) content-box", + end: "ellipse(500% 500% at 500% 500%) content-box", + expected: ["ellipse", ["200% 200% at 200% 200%"], "content-box"] + }, + { start: "polygon(evenodd, 100% 100%, 100% 100%) content-box", + end: "polygon(evenodd, 500% 500%, 500% 500%) content-box", + expected: ["polygon", ["evenodd, 200% 200%, 200% 200%"], "content-box"] + }, + { start: "inset(100% 100% 100% 100% round 100% 100%) content-box", + end: "inset(500% 500% 500% 500% round 500% 500%) content-box", + expected: ["inset", ["200% round 200%"], "content-box"] }, + // matching functions with calc() values + { start: "circle(calc(80px + 20px))", end: "circle(calc(200px + 300px))", + expected: ["circle", ["200px"]] }, + { start: "circle(calc(80% + 20%))", end: "circle(calc(200% + 300%))", + expected: ["circle", ["200%"]] }, + { start: "circle(calc(10px + 20%))", end: "circle(calc(50px + 40%))", + expected: ["circle", ["calc(25% + 20px)"]] }, + // matching functions with interpolation between percentage/pixel values + { start: "circle(20px)", end: "circle(100%)", + expected: ["circle", ["calc(25% + 15px)"]] }, + { start: "ellipse(100% 100px at 8px 20%) content-box", + end: "ellipse(40px 4% at 80% 60px) content-box", + expected: ["ellipse", ["calc(75% + 10px) calc(1% + 75px) at " + + "calc(20% + 6px) calc(15% + 15px)"], + "content-box"] }, + // no interpolation for keywords + { start: "circle()", end: "circle(50px)", + expected: ["circle", ["50px"]] }, + { start: "circle(closest-side)", end: "circle(500px)", + expected: ["circle", ["500px"]] }, + { start: "circle(farthest-side)", end: "circle(500px)", + expected: ["circle", ["500px"]] }, + { start: "circle(500px)", end: "circle(farthest-side)", + expected: ["circle", ["farthest-side"]]}, + { start: "circle(500px)", end: "circle(closest-side)", + expected: ["circle", [""]]}, + { start: "ellipse()", end: "ellipse(50px 50px)", + expected: ["ellipse", ["50px 50px"]] }, + { start: "ellipse(closest-side closest-side)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["500px 500px"]] }, + { start: "ellipse(farthest-side closest-side)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["500px 500px"]] }, + { start: "ellipse(farthest-side farthest-side)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["500px 500px"]] }, + { start: "ellipse(500px 500px)", end: "ellipse(farthest-side farthest-side)", + expected: ["ellipse", ["farthest-side farthest-side"]] }, + { start: "ellipse(500px 500px)", end: "ellipse(closest-side closest-side)", + expected: ["ellipse", [""]] }, + // mismatching boxes + { start: "circle(100px at 100px 100px) border-box", + end: "circle(500px at 500px 500px) content-box", + expected: ["circle", ["500px at 500px 500px"], "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 500px 500px"], "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) content-box", + end: "ellipse(500px 500px at 500px 500px) content-box", + expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"] + }, + { start: "inset(0px round 20px)", end: "ellipse(500px 500px)", + expected: ["ellipse", ["500px 500px"]] + }, + // shape to reference box + { start: "circle(20px)", end: "content-box", expected: ["content-box"] }, + { start: "content-box", end: "circle(20px)", expected: ["circle", ["20px"]] }, + // url to shape + { start: "circle(20px)", end: "url(http://localhost/a.png)", expected: ["url", ["\"http://localhost/a.png\""]] }, + { start: "url(http://localhost/a.png)", end: "circle(20px)", expected: ["circle", ["20px"]] }, + // url to none + { start: "none", end: "url(http://localhost/a.png)", expected: ["url", ["\"http://localhost/a.png\""]] }, + { start: "http://localhost/a.png", end: "none", expected: ["none"] }, +]; + +const basicShapesWithFragmentUrlTests = [ + // Fragment url to shape + { start: "circle(20px)", end: "url('#a')", expected: ["url", ["\"#a\""]] }, + { start: "url('#a')", end: "circle(20px)", expected: ["circle", ["20px"]] }, + // Fragment url to none + { start: "none", end: "url('#a')", expected: ["url", ["\"#a\""]] }, + { start: "url('#a')", end: "none", expected: ["none"] }, +]; + +// We have a lot of tests in web-platform-tests already, so here we only test +// basic interpolation cases. +const pathFunctionTests = [ + { start: "none", end: "none", + expected: ["none"] }, + // none to path + { start: "none", + end: "path('M 100 100')", + expected: ["path", '"M 100 100"'] + }, + // path to none + { start: "path('M 100 100')", + end: "none", + expected: ["none"] + }, + // mismatch + { + start: "path('M 0 0 H 100 H 200')", + end: "path('M 0 0 H 500')", + expected: ["path", '"M 0 0 H 500"'] + }, + { + start: "path('M 0 0 V 100')", + end: "path('M 0 0 H 500')", + expected: ["path", '"M 0 0 H 500"'] + }, + // match + { + start: "path('M 100 100')", + end: "path('M 100 500')", + expected: ["path", '"M 100 200"'] + }, + { + start: "path('M 10 10 L 100 100')", + end: "path('M 10 10 L 100 500')", + expected: ["path", '"M 10 10 L 100 200"'] + }, + { + start: "path('M 10 10 H 100')", + end: "path('M 10 10 H 500')", + expected: ["path", '"M 10 10 H 200"'] + }, + { + start: "path('M 10 10 V 100')", + end: "path('M 10 10 V 500')", + expected: ["path", '"M 10 10 V 200"'] + }, + { + start: "path('M 10 10 C 32 42 52 62 120 2200')", + end: "path('M 10 10 C 40 50 60 70 200 3000')", + expected: ["path", '"M 10 10 C 34 44 54 64 140 2400"'] + }, + { + start: "path('M 10 10 S 45 67 89 123')", + end: "path('M 10 10 S 61 51 113 99')", + expected: ["path", '"M 10 10 S 49 63 95 117"'] + }, + { + start: "path('M 10 10 Q 32 42 120 2200')", + end: "path('M 10 10 Q 40 50 200 3000')", + expected: ["path", '"M 10 10 Q 34 44 140 2400"'] + }, + { + start: "path('M 10 10 T 100 200')", + end: "path('M 10 10 T 500 280')", + expected: ["path", '"M 10 10 T 200 220"'] + }, + { + start: "path('M 10 10 A 10 20 30 0 1 140 450')", + end: "path('M 10 10 A 50 60 70 0 1 380 290')", + expected: ["path", '"M 10 10 A 20 30 40 0 1 200 410"'] + }, + { + start: "path('M 10 10 A 10 20 30 1 0 140 450')", + end: "path('M 10 10 A 50 60 70 0 1 380 290')", + expected: ["path", '"M 10 10 A 20 30 40 1 0 200 410"'] + }, + // mix relative and absolute coordinates + { + start: "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')", + // =="path('M 10 20 H 40 V 80 H 50 V 70 L 160 130')" + end: "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')", + expected: ["path", '"M 40 50 H 60 V 100 H 70 V 90 L 170 140"'] + }, +]; + +const clipPathPathFunctionTests = [ + // match fill-rule + { + start: "path(nonzero, 'M 100 100')", + end: "path(nonzero, 'M 100 500')", + expected: ["path", '"M 100 200"'] + }, + { + start: "path(evenodd, 'M 100 100')", + end: "path(evenodd, 'M 100 500')", + expected: ["path", 'evenodd, "M 100 200"'] + }, + // mismatch fill-rule + { + start: "path(nonzero, 'M 100 100')", + end: "path(evenodd, 'M 100 500')", + expected: ["path", 'evenodd, "M 100 500"'] + }, +]; + +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", "180deg"] }, + { start: "hue-rotate(0grad)", end: "hue-rotate(800grad)", + expected: ["hue-rotate", "180deg"] }, + { start: "hue-rotate(0turn)", end: "hue-rotate(2turn)", + expected: ["hue-rotate", "180deg"] }, + { start: "hue-rotate(0deg)", end: "hue-rotate("+4*Math.PI+"rad)", + expected: ["hue-rotate", "180deg"] }, + { start: "hue-rotate(0turn)", end: "hue-rotate(800grad)", + expected: ["hue-rotate", "180deg"] }, + { start: "hue-rotate(0grad)", end: "hue-rotate("+4*Math.PI+"rad)", + expected: ["hue-rotate", "180deg"] }, + { start: "hue-rotate(0grad)", end: "hue-rotate(0turn)", + expected: ["hue-rotate", "0deg"] }, + // 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", "rgba(0, 0, 0, 0.75) 6px 6px 0px"] }, + { start: "blur(75px)", + end: "blur(25px) drop-shadow(8px 8px)", + expected: ["blur", 62.5, "drop-shadow", "rgba(0, 0, 0, 0.25) 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 (let 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) && + !skipped_transitionable_properties.includes(prop) && + info.type != CSS_TYPE_TRUE_SHORTHAND && + info.type != CSS_TYPE_LEGACY_SHORTHAND && + !("alias_for" in info) && + !prop.match(/^transition-/) && + 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) { + let 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 (let i = 0; i < all_values.length; ++i) { + for (let 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(prop) { + 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), "6px", + "length-valued property " + prop + ": interpolation of lengths"); + check_distance(prop, "4px", "6px", "12px"); +} + +function test_length_clamped(prop) { + test_length_clamped_or_unclamped(prop, true); +} + +function test_length_unclamped(prop) { + test_length_clamped_or_unclamped(prop, false); +} + +function test_length_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", ""); + let zero_val = cs.getPropertyValue(prop); // Flushes + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "100px", ""); + (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val, + "length-valued property " + prop + ": clamping of negatives"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +// Test transition to/from the special 'flex-basis: content' keyword. +function test_flex_basis_content_transition(prop) { + is(prop, "flex-basis", "this test function should only be called for 'flex-basis'"); + + // Test transition from length to 'content': + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "8px", ""); + is(cs.getPropertyValue(prop), "8px", + "property " + prop + ": computed value before transition to 'content'"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "content", ""); + is(cs.getPropertyValue(prop), "content", + "property " + prop + ": transition to 'content' (should be discrete)"); + + // Test transition from 'content' to length: + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "content", ""); + is(cs.getPropertyValue(prop), "content", + "property " + prop + ": computed value before transition from 'content'"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "6px", ""); + is(cs.getPropertyValue(prop), "6px", + "property " + prop + ": transition from 'content' (should be discrete)"); +} + +// 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) +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_aboveZero_clamped(prop) { + 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, "5", ""); + is(cs.getPropertyValue(prop), "0", + "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", ""); +} + +// FIXME: This doesn't deal well with properties for which the resolved value +// is not the used value, like stroke-dashoffset or stroke-width. +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)"); +} + +// This can deal well with properties for which the computed value +// is not the used value, e.g. translate. +function test_calc_wrapped_calc_transition(prop) { + // Test interpolation that computes to calc() (transition from % to px) + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "20%", ""); + is(cs.getPropertyValue(prop), "20%", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "12px", ""); + is(cs.getPropertyValue(prop), "calc(15% + 3px)", + "property " + prop + ": interpolation that computes to calc()"); + + check_distance(prop, "20%", "calc(15% + 3px)", "12px"); + + // Test interpolation that computes to calc() (transition from px to %) + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "12px", ""); + is(cs.getPropertyValue(prop), "12px", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "20%", ""); + is(cs.getPropertyValue(prop), "calc(5% + 9px)", + "property " + prop + ": interpolation that computes to calc()"); + + check_distance(prop, "12px", "calc(5% + 9px)", "20%"); + + // Test interpolation between calc() and non-calc() + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "calc(40px + 10%)", ""); + is(cs.getPropertyValue(prop), "calc(10% + 40px)", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "30%", ""); + is(cs.getPropertyValue(prop), "calc(15% + 30px)", + "property " + prop + ": interpolation between calc() and non-calc()"); + + check_distance(prop, "calc(40px + 10%)", "calc(30px + 15%)", "30%"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "16px", ""); + is(cs.getPropertyValue(prop), "16px", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "calc(8px + 60%)", ""); + is(cs.getPropertyValue(prop), "calc(15% + 14px)", + "property " + prop + ": interpolation between calc() and non-calc()"); + + check_distance(prop, "16px", "calc(14px + 15%)", "calc(8px + 60%)"); +} + +function test_number_transition(prop) { + div.style.transitionProperty = 'none'; + div.style[prop] = '10'; + is(cs[prop], '10', + `number property ${prop}: computed value before transition`); + div.style.transitionProperty = prop; + div.style[prop] = '50'; + is(cs[prop], '20', `number property ${prop}: interpolation of numbers`); + check_distance(prop, '10', '20', '50'); +} + +function test_angle_transition(prop) { + div.style.transitionProperty = 'none'; + div.style[prop] = '45deg'; + is(cs[prop], '45deg', + `angle property ${prop}: computed value before transition`); + div.style.transitionProperty = prop; + div.style[prop] = '145deg'; + is(cs[prop], '70deg', + `angle property ${prop}: interpolation of angles`); + check_distance(prop, '45deg', '70deg', '145deg'); +} + +function get_color_options(options) { + let { + get_color = x => x, + set_color = x => x, + is_shorthand = false, + } = options; + return { get_color, set_color, is_shorthand }; +} + +function test_color_transition(prop, options={}) { + let { get_color, set_color, is_shorthand } = get_color_options(options); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, set_color("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, set_color("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, set_color("rgb(255, 28, 0)"), + set_color("rgb(210, 42, 32)"), + set_color("rgb(75, 84, 128)")); + } + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, set_color("rgb(0, 255, 0)"), ""); + var color = get_color(cs.getPropertyValue(prop)); + var vals = color.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, set_color("rgb(255, 0, 128)"), ""); + // FIXME: Once we support non-sRGB colors, these tests will need fixing. + color = get_color(cs.getPropertyValue(prop)); + vals = color.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, options={}) { + let { get_color, set_color } = get_color_options(options); + + const msg_prefix = `color-valued property ${prop}: `; + div.style.setProperty("transition-property", "none", ""); + (prop == "color" ? div.parentNode : div).style. + setProperty("color", "rgb(128, 0, 0)", ""); + div.style.setProperty(prop, set_color("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, set_color("currentcolor"), ""); + is(get_color(cs.getPropertyValue(prop)), "rgb(32, 0, 96)", + msg_prefix + "interpolation of rgb color and currentcolor"); + + if (prop != "color") { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty("color", "rgb(128, 0, 0)", ""); + div.style.setProperty(prop, set_color("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, set_color("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", ""); + (prop == "color" ? div.parentNode : div).style. + setProperty("color", "rgba(128, 0, 0, 0.6)", ""); + div.style.setProperty(prop, set_color("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, set_color("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. + + (prop == "color" ? div.parentNode : div).style.removeProperty("color"); +} + +function test_auto_color_transition(prop, options={}) { + let { get_color, set_color } = get_color_options(options); + + const msg_prefix = `color-valued property ${prop}: `; + const test_color = "rgb(51, 102, 153)"; + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "auto", ""); + if (prop == "scrollbar-color") { + is(cs.getPropertyValue(prop), "auto", + msg_prefix + "auto should not be resolved to rgb color"); + } else { + let used_value_of_auto = get_color(cs.getPropertyValue(prop)); + isnot(used_value_of_auto, test_color, + msg_prefix + "ensure used auto value is different than our test color"); + } + + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, set_color(test_color), ""); + is(get_color(cs.getPropertyValue(prop)), test_color, + msg_prefix + "not interpolatable between auto and rgb 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: get_color_from_shorthand_value, + is_shorthand: true, + }); +} + +function test_currentcolor_shorthand_transition(prop) { + test_currentcolor_transition(prop, { + get_color: get_color_from_shorthand_value, + is_shorthand: true, + }); +} + +function test_scrollbar_color_transition(prop) { + function split_colors(value) { + const colors = value.match(/^(rgba?\(.+?\)) (rgba?\(.+?\))$/); + isnot(colors, null, "scrollbar-color should consist of two colors"); + return { thumb: colors[1], track: colors[2] }; + } + const TEST_FUNCS = [ + test_color_transition, + test_currentcolor_transition, + test_auto_color_transition, + ]; + for (let test_func of TEST_FUNCS) { + test_func(prop, { + get_color: value => split_colors(value).thumb, + set_color: value => value + " blue", + }); + test_func(prop, { + get_color: value => split_colors(value).track, + set_color: value => "blue " + value, + }); + } +} + +function test_shape_or_url_equals(computedValStr, expected) +{ + // Check simple case "none" + if (computedValStr == "none" && computedValStr == expected[0]) { + return true; + } + // We will update the expected list in this function for checking the result, + // so we clone it first to avoid affecting the input parameter. + var expectedList = expected.slice(); + + 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; + } + ok(false, "expected basic 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 test_path_function_equals(computedValStr, expectedList) +{ + // Check simple case "none" + if (expectedList.length === 1 && computedValStr === expectedList[0]) { + return true; + } + + var regex = /([a-z]+)\((.*)\)/; + matches = computedValStr.match(regex) + if (!matches || matches[0] != computedValStr) { + ok(false, "Invalid function value"); + return false; + } + + // Bug 1480665: Support ray() for motion path. For now, only path(...) is + // acceptable. + if (matches[1] != "path") { + ok(false, "Only support path function"); + return false; + } + + // Check argument values. + if (matches[2] != 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 (let 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_basic_shape_or_url_transition(prop) { + let tests = basicShapesTests; + if (prop === "clip-path") { + // Clip-path won't resolve fragment URLs. + tests = tests.concat(basicShapesWithFragmentUrlTests); + } + + for (let test of tests) { + 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_shape_or_url_equals(actual, test.expected), + prop + " property is " + actual + " expected values of " + + test.expected); + } +} + +function test_path_function(prop) { + let tests = pathFunctionTests; + if (prop === "clip-path") { + // The syntax of path() in clip-path has fill-rule, so we have to test more. + tests = tests.concat(clipPathPathFunctionTests); + } + + for (const test of tests) { + 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, ""); + const actual = cs.getPropertyValue(prop); + ok(test_path_function_equals(actual, test.expected), + prop + " property is " + actual + " expected values of " + + test.expected[0] + "(" + test.expected[1] + ")"); + } +} + +function test_filter_transition(prop) { + for (let 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 origTimingFunc = div.style.getPropertyValue("transition-timing-function"); + 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"); + } + + // Transition beween values with color and without color. + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty("color", "rgb(3, 0, 0)", ""); + div.style.setProperty(prop, "2px 2px 2px", ""); + is(cs.getPropertyValue(prop), "rgb(3, 0, 0) 2px 2px 2px" + 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, 0, 0) 3.5px 3.5px 3.5px" + spreadStr, + "shadow-valued property " + prop + + ": interpolation values with/without color"); + + // Transition beween values without color. + var defaultColor = cs.getPropertyValue("color") + " "; + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "2px 2px 2px", ""); + is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr, + "shadow-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + 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"); + + // Transition between values with currentcolor transitioning. + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty("color", "rgb(0, 255, 0)", ""); + div.style.setProperty(prop, "2px 2px 2px", ""); + is(cs.getPropertyValue(prop), "rgb(0, 255, 0) 2px 2px 2px" + spreadStr, + "shadow-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", "color, " + prop, ""); + div.style.setProperty("color", "rgb(0, 0, 255)", ""); + div.style.setProperty(prop, "6px 10px 14px red", ""); + is(cs.getPropertyValue(prop), "rgb(64, 143, 48) 3px 4px 5px" + spreadStr, + "shadow-valued property " + prop + ": interpolation with interpolating" + + "currentcolor"); + + 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"); + } + + // A test case that timing function produces values greater than 1.0. + div.style.setProperty("transition-timing-function", + // This function produces 1.2989961788069297 at 25%. + "cubic-bezier(0, 1.5, 0, 1.5)", ""); + 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, "0px 0px 0px rgba(100, 100, 100, 0.5)", ""); + // The alpha value, 0.5 * 1.2989961788069297 * 255, is 165.622012798, and then + // converted to 0.649. + is(cs.getPropertyValue(prop), "rgba(100, 100, 100, 0.649) 0px 0px 0px" + spreadStr, + "shadow-valued property " + prop + ": interpolation of shadows with " + + "timing function which produces values greater than 1.0"); + + div.style.setProperty("transition-timing-function", origTimingFunc, ""); +} + +function test_dasharray_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "3", ""); + is(cs.getPropertyValue(prop), "3px", + "dasharray-valued property " + prop + + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "15px", ""); + is(cs.getPropertyValue(prop), "6px", + "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), "6px, 8px, 4px, 4px", + "dasharray-valued property " + prop + + ": computed value before transition"); + div.style.setProperty(prop, "14, 12,16,16px", ""); + is(cs.getPropertyValue(prop), "8px, 9px, 7px, 7px", + "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), "8px, 16px, 4px", + "dasharray-valued property " + prop + + ": computed value before transition"); + div.style.setProperty(prop, "4,8,12,16", ""); + is(cs.getPropertyValue(prop), "7px, 14px, 6px, 10px, 13px, 5px, 9px, 16px, 4px, 8px, 15px, 7px", + "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), + "5.75px, calc(12.5% + 10.5px), 6px, 10px, 10.25px, calc(12.5% + 3.75px), 8.25px, 14.5px, 3.5px, calc(12.5% + 6px), 12.75px, 7.75px", + "dasharray-valued property " + prop + ": interpolability of mixed units"); + div.style.setProperty(prop, "none", ""); + is(cs.getPropertyValue(prop), "none", + "dasharray-valued property " + prop + ": non-interpolability of none"); + div.style.setProperty(prop, "2,50%,6,10", ""); + is(cs.getPropertyValue(prop), "2px, 50%, 6px, 10px", + "dasharray-valued property " + prop + ": non-interpolability of none"); + div.style.setProperty(prop, "6,30%,2,2", ""); + is(cs.getPropertyValue(prop), "3px, 45%, 5px, 8px", + "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), "0px, 0%", + "dasharray-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "5px, 25%", ""); + is(cs.getPropertyValue(prop), "0px, 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(4.6875% + 16px) calc(9.375% + 4px)", + "radius-valued property " + prop + ": interpolation of radius with mixed units"); + check_distance(prop, "6.25% 12.5%", + "calc(4.6875% + 16px) calc(9.375% + 4px)", + "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), "0", + "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_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), "525", + "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, "1", ""); + is(cs.getPropertyValue(prop), "1", + "font-weight property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "1000", ""); + is(cs.getPropertyValue(prop), "1", + "font-weight property " + prop + ": clamping of values"); + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "1000", ""); + is(cs.getPropertyValue(prop), "1000", + "font-weight property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "1", ""); + is(cs.getPropertyValue(prop), "1000", + "font-weight property " + prop + ": clamping of values"); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_grid_gap(prop) { + test_length_transition(prop); + test_length_clamped(prop); + test_percent_transition(prop); + test_percent_clamped(prop); +} + +function test_pos_integer_or_keyword_transition(prop, keyword) { + 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), "6", + "integer-valued property " + prop + ": interpolation of integers"); + check_distance(prop, "4", "6", "12"); + div.style.setProperty(prop, keyword, ""); + is(cs.getPropertyValue(prop), keyword, + "integer-valued property " + prop + ": " + keyword + " 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_pos_integer_or_auto_transition(prop) { + return test_pos_integer_or_keyword_transition(prop, "auto"); +} + +function test_pos_integer_or_none_transition(prop) { + return test_pos_integer_or_keyword_transition(prop, "none"); +} + +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%", ""); + var is_zero = function(val) { + if (prop == "transform-origin" || prop == "perspective-origin") { + // These two properties resolve percentages to pixels. + return val == "0px 0px"; + } + return val == "0px 0%"; + } + ok(is_zero(cs.getPropertyValue(prop)), + "length+percent-valued property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "30px 25%", ""); + is(is_zero(cs.getPropertyValue(prop)), is_clamped, + "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)"); + 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 do_test(prop, from_value, to_value, interp_value) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, from_value, ""); + is(cs.getPropertyValue(prop), from_value, + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, to_value, ""); + is(cs.getPropertyValue(prop), interp_value, + "property " + prop + ": interpolation of " + prop); +} + +function do_negative_test(prop, from_value, to_value, interpolable) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, from_value, ""); + is(cs.getPropertyValue(prop), from_value, + "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, + "property " + prop + ": clamping of negatives"); +} + +function do_overone_test(prop, from_value, to_value) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, from_value, ""); + is(cs.getPropertyValue(prop), from_value, + "property " + prop + ": flush before clamping test"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, to_value, ""); + is(cs.getPropertyValue(prop), to_value, + "property " + prop + ": clamping of over-ones"); +} + +function test_visibility_transition(prop) { + do_test(prop, "visible", "hidden", "visible"); + do_test(prop, "hidden", "visible", "visible"); + do_test(prop, "hidden", "collapse", "collapse"); /* not interpolable */ + do_test(prop, "collapse", "hidden", "hidden"); /* not interpolable */ + do_test(prop, "visible", "collapse", "visible"); + do_test(prop, "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 be zero"); + is(get_distance(prop, "hidden", "hidden"), 0, + "distance between hidden and hidden should be zero"); + is(get_distance(prop, "collapse", "collapse"), 0, + "distance between collapse and collapse should be zero"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + do_negative_test(prop, "visible", "hidden", true); + do_negative_test(prop, "hidden", "visible", true); + do_negative_test(prop, "hidden", "collapse", false); + do_negative_test(prop, "collapse", "hidden", false); + do_negative_test(prop, "visible", "collapse", true); + do_negative_test(prop, "collapse", "visible", true); + + div.style.setProperty("transition-delay", "-3s", ""); + div.style.setProperty("transition-timing-function", FUNC_OVERONE, ""); + do_overone_test(prop, "visible", "hidden"); + do_overone_test(prop, "hidden", "visible"); + do_overone_test(prop, "hidden", "collapse"); + do_overone_test(prop, "collapse", "hidden"); + do_overone_test(prop, "visible", "collapse"); + do_overone_test(prop, "collapse", "visible"); + + div.style.setProperty("transition-delay", "-1s", ""); + div.style.setProperty("transition-timing-function", "linear", ""); +} + +function test_content_visibility_transition(prop) { + do_test(prop, "visible", "hidden", "visible"); + do_test(prop, "hidden", "visible", "visible"); + do_test(prop, "hidden", "auto", "auto"); + do_test(prop, "auto", "hidden", "auto"); + do_test(prop, "visible", "auto", "auto"); /* not interpolable */ + do_test(prop, "auto", "visible", "visible"); /* not interpolable */ + + isnot(get_distance(prop, "visible", "hidden"), 0, + "distance between visible and hidden should not be zero"); + isnot(get_distance(prop, "auto", "hidden"), 0, + "distance between auto and hidden should not be zero"); + is(get_distance(prop, "visible", "visible"), 0, + "distance between visible and visible should be zero"); + is(get_distance(prop, "hidden", "hidden"), 0, + "distance between hidden and hidden should be zero"); + is(get_distance(prop, "auto", "auto"), 0, + "distance between auto and auto should be zero"); + + div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, ""); + do_negative_test(prop, "visible", "hidden", true); + do_negative_test(prop, "hidden", "visible", true); + do_negative_test(prop, "hidden", "auto", true); + do_negative_test(prop, "auto", "hidden", true); + do_negative_test(prop, "visible", "auto", false); + do_negative_test(prop, "auto", "visible", false); + + div.style.setProperty("transition-delay", "-3s", ""); + div.style.setProperty("transition-timing-function", FUNC_OVERONE, ""); + do_overone_test(prop, "visible", "hidden"); + do_overone_test(prop, "hidden", "visible"); + do_overone_test(prop, "hidden", "auto"); + do_overone_test(prop, "auto", "hidden"); + do_overone_test(prop, "visible", "auto"); + do_overone_test(prop, "auto", "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(100% - 20px) 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(80% - 5px) calc(60% + 3px)", + "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(100% - 20px)", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "calc(40px + 20%)", ""); + is(cs.getPropertyValue(prop), "calc(80% - 5px)", + "property " + prop + ": interpolation of edge keywords w/ offsets & calc"); + check_distance(prop, `${endEdge} 20px`, + "calc(-5px + 80%)", + "calc(40px + 20%)"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "10px, 50px, 30px", ""); + is(cs.getPropertyValue(prop), "10px, 50px, 30px", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + 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("transition-property", "none", ""); + 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("transition-property", prop, ""); + 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("transition-property", "none", ""); + div.style.setProperty(prop, "20%, 8px", ""); + is(cs.getPropertyValue(prop), "20%, 8px", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "12px, 40%", ""); + is(cs.getPropertyValue(prop), "calc(15% + 3px), calc(10% + 6px)", + "property " + prop + ": interpolation that computes to calc()"); + check_distance(prop, "20%, 8px", + "calc(3px + 15%), calc(6px + 10%)", + "12px, 40%"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "calc(20% + 40px), 8px, calc(20px + 12%)", ""); + is(cs.getPropertyValue(prop), "calc(20% + 40px), 8px, calc(12% + 20px)", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "12px, calc(20%), calc(8px + 20%)", ""); + is(cs.getPropertyValue(prop), "calc(15% + 33px), calc(5% + 6px), calc(14% + 17px)", + "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(15% + 3px) calc(30% + 5px)", + "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(5% + 9px) calc(10% + 15px)", + "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(10% + 40px) 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(15% + 30px) calc(15% + 14px)", + "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("transition-property", "none", ""); + 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("transition-property", prop, ""); + 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("transition-property", "none", ""); + 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("transition-property", prop, ""); + 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("transition-property", "none", ""); + 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("transition-property", prop, ""); + div.style.setProperty(prop, "12px 20px, 40% 16%", ""); + is(cs.getPropertyValue(prop), + "calc(15% + 3px) calc(30% + 5px), calc(10% + 6px) calc(4% + 9px)", + "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("transition-property", "none", ""); + div.style.setProperty(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)", ""); + is(cs.getPropertyValue(prop), + "calc(20% + 40px) calc(40% + 40px), 8px 12%, calc(12% + 20px) calc(8% + 24px)", + "property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)", ""); + is(cs.getPropertyValue(prop), + "calc(15% + 33px) calc(35% + 30px), calc(5% + 6px) calc(24% + 4px), calc(14% + 17px) calc(10% + 28px)", + "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 (let 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 (let 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 (let j = 0; j < 6; ++j) { + // 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[j]) - Number(expected_split[j])) > + 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 test_rotate_transition(prop) { + // One value: <angle> + test_angle_transition(prop); + + // With axis: <number> <number> <number> <angle> + // + // We don't test for interpolation of the numbers here since it's quite + // complicated and this is tested by the web-platform tests for this property. + // Now that we have web-platform tests for animation properties the main + // purpose of the tests in this file is to check that transitions run on the + // properties we expect them to. + div.style.transitionProperty = 'none'; + div.style[prop] = '0 1 0 45deg'; + is(cs[prop], 'y 45deg', + `rotate property ${prop}: computed value before transition`); + div.style.transitionProperty = prop; + div.style[prop] = '0 1 0 145deg'; + is(cs[prop], 'y 70deg', + `rotate property ${prop}: interpolation of angles`); + check_distance(prop, '0 1 0 45deg', '0 1 0 70deg', '0 1 0 145deg'); +} + +function test_scale_transition(prop) { + // One value: <number> + test_number_transition(prop); + + // Two values: <number> <number> + div.style.transitionProperty = 'none'; + div.style[prop] = '10 20'; + is(cs[prop], '10 20', + `number property ${prop}: computed value before transition`); + div.style.transitionProperty = prop; + div.style[prop] = '50 60'; + is(cs[prop], '20 30', `number property ${prop}: interpolation of numbers`); + check_distance(prop, '10 20', '20 30', '50 60'); + + // Three values: <number> <number> <number> + div.style.transitionProperty = 'none'; + div.style[prop] = '10 20 30'; + is(cs[prop], '10 20 30', + `number property ${prop}: computed value before transition`); + div.style.transitionProperty = prop; + div.style[prop] = '50 60 70'; + is(cs[prop], '20 30 40', `number property ${prop}: interpolation of numbers`); + check_distance(prop, '10 20 30', '20 30 40', '50 60 70'); +} + +function test_translate_transition(prop) { + // One value: <length-percentage> + test_length_transition(prop); + test_length_unclamped(prop); + test_percent_transition(prop); + test_percent_unclamped(prop); + test_calc_wrapped_calc_transition(prop); + + // Two values: <length-percentage> <length-percentage> + // Note: Cannot use test_length_percent_pair_transition(prop) because we + // don't resolve the percentage. + test_length_pair_transition(prop); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "4px 50%", ""); + is(cs.getPropertyValue(prop), "4px 50%", + `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 55%", + `length-valued property ${prop}: interpolation of lengths`); + check_distance(prop, "4px 50%", "6px 55%", "12px 70%"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "4px 50%", ""); + is(cs.getPropertyValue(prop), "4px 50%", + `length-valued property ${prop}: computed value before transition`); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "20% 20px", ""); + is(cs.getPropertyValue(prop), "calc(5% + 3px) calc(37.5% + 5px)", + `length-valued property ${prop}: interpolation of lengths`); + check_distance(prop, "4px 50%", "calc(5% + 3px) calc(37.5% + 5px)", + "20% 20px"); + // We can't use test_length_percent_pair_unclamped here since + // it assumes that "0px 0px" is serialized as "0px 0px" but + // translate should serialize it as "0px". + + // Three values: <length-percentage> <length-percentage> <length> + div.style.transitionProperty = 'none'; + div.style[prop] = '10px 200% 30px'; + is(cs[prop], '10px 200% 30px', + `translate property ${prop}: computed value before transition`); + div.style.transitionProperty = prop; + div.style[prop] = '50px 600% 70px'; + is(cs[prop], '20px 300% 40px', + `translate property ${prop}: interpolation of three values`); + check_distance(prop, '10px 20px 30px', '20px 30px 40px', '50px 60px 70px'); +} + +function test_font_variations_transition(prop) { + is(prop, "font-variation-settings", "only designed for one property"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "\"wght\" 0, \"wdth\" 1.5", ""); + // Note that computed-style returns the tags in sorted order. + is(cs.getPropertyValue(prop), "\"wdth\" 1.5, \"wght\" 0", + "font-variation-settings property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "\"wght\" 2, \"wdth\" 0.5", ""); + is(cs.getPropertyValue(prop), "\"wdth\" 1.25, \"wght\" 0.5", + "font-variation-settings property " + prop + ": interpolation of font-variation-settings"); + check_distance(prop, "\"wght\" 0, \"wdth\" 1.5", "\"wght\" 0.5, \"wdth\" 1.25", "\"wght\" 2, \"wdth\" 0.5"); + + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "\"wght\" 2, \"wdth\" 0.5", ""); + is(cs.getPropertyValue(prop), "\"wdth\" 0.5, \"wght\" 2", + "font-variation-settings property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "\"wght\" 0, \"wdth\" 1.5", ""); + is(cs.getPropertyValue(prop), "\"wdth\" 0.75, \"wght\" 1.5", + "font-variation-settings property " + prop + ": interpolation of font-variation-settings"); + check_distance(prop, "\"wght\" 2, \"wdth\" 0.5", "\"wght\" 1.5, \"wdth\" 0.75", "\"wght\" 0, \"wdth\" 1.5"); +} + +function test_aspect_ratio_transition(prop) { + [ + // No transition between auto and <ratio>. + { start: "auto", end: "1 / 1", + expected: "1 / 1" }, + // No transition between auto && <ratio> and <ratio>. + { start: "auto 1 / 1", end: "1 / 1", + expected: "1 / 1" }, + // No transition between auto && <ratio> and auto. + { start: "auto 1 / 1", end: "auto", + expected: "auto" }, + { start: "1 / 2", end: "8 / 1", + expected: "1 / 1" }, + { start: "auto 1 / 2", end: "auto 8 / 1", + expected: "auto 1 / 1" }, + ].forEach(test => { + div.style.transitionProperty = 'none'; + div.style[prop] = test.start; + is(cs[prop], test.start, + `aspect-ratio: computed value before transition`); + div.style.transitionProperty = prop; + div.style[prop] = test.end; + is(cs[prop], test.expected, + `aspect-ratio: interpolation of aspect-ratio`); + // We check distance only if there is a transition. + if (test.end != test.expected) { + check_distance(prop, test.start, test.expected, test.end); + } + }); +} + +function test_auto_with_length_transition(prop) { + div.style.setProperty("transition-property", "none", ""); + div.style.setProperty(prop, "auto 4px", ""); + is(cs.getPropertyValue(prop), "auto 4px", + "auto+length-valued property " + prop + ": computed value before transition"); + div.style.setProperty("transition-property", prop, ""); + div.style.setProperty(prop, "auto 12px", ""); + is(cs.getPropertyValue(prop), "auto 6px", + "auto+length-valued property " + prop + ": interpolation of lengths"); + check_distance(prop, "auto 4px", "auto 6px", "auto 12px"); +} + +var OMTAdiv; +var OMTACs; + +function prepareForOMTATest() { + if (OMTAdiv) { + OMTAdiv.remove(); + } + OMTAdiv = document.createElement("div"); + OMTAdiv.style = "height:100px; width:100px; background-color:blue;"; + OMTAdiv.style.setProperty("transition-duration", "300s", ""); + OMTAdiv.style.setProperty("transition-timing-function", "linear", ""); + document.body.appendChild(OMTAdiv); + + OMTACs = getComputedStyle(OMTAdiv, ""); +} + +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. + addAsyncTransformTests(); + addAsyncOpacityTest(); + addAsyncDelayTest(); + + runAllAsyncAnimTests().then(function() { + OMTAdiv.style.removeProperty("transition"); + SimpleTest.finish(); + }); +} + +function addAsyncTransformTests() { + transformTests.forEach(function(test) { + addAsyncAnimTest(function () { return runTransformTest(test); } ); + }); +} + +async function runTransformTest(test) { + prepareForOMTATest(); + + 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"); + await 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); + await waitForPaints(); + winUtils.advanceTimeAndRefresh(100000); + await waitForPaints(); + } else { + winUtils.advanceTimeAndRefresh(200000); + await 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(async function() { + prepareForOMTATest(); + + 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"); + + await 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(async function() { + prepareForOMTATest(); + + 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); + await 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 0000000000..527c98ae85 --- /dev/null +++ b/layout/style/test/test_transitions_replacement_on_busy_frame.html @@ -0,0 +1,100 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1167519 +--> +<head> + <meta charset=utf-8> + <title>Test for bug 1167519</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> + <script src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <style> + #target { + height: 100px; + width: 100px; + background: green; + transition: transform 100s linear; + } + </style> +</head> +<body> +<div id="target"></div> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); + +const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations'; +const omtaEnabled = + SpecialPowers.DOMWindowUtils.layerManagerRemote && + SpecialPowers.getBoolPref(OMTAPrefKey); + +window.addEventListener('load', async function() { + if (!omtaEnabled) { + ok(true, 'Skipping the test since OMTA is disabled'); + SimpleTest.finish(); + return; + } + + const div = document.getElementById('target'); + + // Start first transition + div.style.transform = 'translateX(300px)'; + const firstTransition = div.getAnimations()[0]; + + // Wait for first transition to start running on the main thread and + // compositor. + await firstTransition.ready; + await waitForPaints(); + + await new Promise(resolve => requestAnimationFrame(resolve)); + + // Start second transition + div.style.transform = 'translateX(600px)'; + const secondTransition = div.getAnimations()[0]; + + const originalProperties = SpecialPowers.wrap( + secondTransition.effect + ).getProperties(); + const previousPropertyValue = originalProperties[0].values[0].value; + const previousKeyframeValue = secondTransition.effect.getKeyframes()[0] + .transform; + + // 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. + const 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). + await waitForPaints(); + + const updatedProperties = SpecialPowers.wrap( + secondTransition.effect + ).getProperties(); + const currentPropertyValue = updatedProperties[0].values[0].value; + isnot( + currentPropertyValue, + previousPropertyValue, + 'From value of transition is updated since the moment when ' + + 'it was generated' + ); + isnot( + secondTransition.effect.getKeyframes()[0].transform, + previousKeyframeValue, + 'Keyframe value of transition is updated since the moment when ' + + 'it was generated' + ); + SimpleTest.finish(); +}); + +</script> +</body> +</html> diff --git a/layout/style/test/test_transitions_replacement_with_setKeyframes.html b/layout/style/test/test_transitions_replacement_with_setKeyframes.html new file mode 100644 index 0000000000..85e9e40127 --- /dev/null +++ b/layout/style/test/test_transitions_replacement_with_setKeyframes.html @@ -0,0 +1,88 @@ +<!doctype html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1292001 +--> +<head> + <meta charset=utf-8> + <title>Test for bug 1292001</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> + <script src="animation_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <style> + #target { + height: 100px; + width: 100px; + background: green; + transition: transform 100s linear; + } + </style> +</head> +<body> +<div id="target"></div> +<script> +'use strict'; + +SimpleTest.waitForExplicitFinish(); + +const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations'; +const omtaEnabled = + SpecialPowers.DOMWindowUtils.layerManagerRemote && + SpecialPowers.getBoolPref(OMTAPrefKey); + +window.addEventListener('load', async function() { + if (!omtaEnabled) { + ok(true, 'Skipping the test since OMTA is disabled'); + SimpleTest.finish(); + return; + } + + const div = document.getElementById('target'); + + // Start first transition + div.style.transform = 'translateX(300px)'; + const firstTransition = div.getAnimations()[0]; + + // But then change its keyframes to something completely different. + firstTransition.effect.setKeyframes({ 'opacity': ['0', '1'] }); + + // Wait for the transition to start running on the main thread and + // compositor. + await firstTransition.ready; + await waitForPaints(); + await new Promise(resolve => requestAnimationFrame(resolve)); + + // Start second transition + div.style.transform = 'translateX(600px)'; + const secondTransition = div.getAnimations()[0]; + + const previousKeyframeValue = secondTransition.effect.getKeyframes()[0] + .transform; + + // 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. + const startTime = performance.now(); + while (performance.now() - startTime < 300); + + // Ensure that our paint process has been done. + // + // (See explanation in test_transitions_replacement_on_busy_frame.html for + // why we don't use requestAnimationFrame here.) + await waitForPaints(); + + // Now check that the keyframes are NOT updated. + is( + secondTransition.effect.getKeyframes()[0].transform, + previousKeyframeValue, + 'Keyframe value of transition is NOT updated since the moment when ' + + 'it was generated' + ); + + SimpleTest.finish(); +}); + +</script> +</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 0000000000..c0205a8d2f --- /dev/null +++ b/layout/style/test/test_transitions_step_functions.html @@ -0,0 +1,131 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=435441 +--> +<head> + <title>Test for Bug 435441</title> + <script 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); + +// steps(_, jump-*) +run_test("steps(2, jump-start)", 0.00, 0.5); +run_test("steps(2, jump-start)", 0.49, 0.5); +run_test("steps(2, jump-start)", 0.50, 1.0); +run_test("steps(2, jump-start)", 0.99, 1.0); +run_test("steps(2, jump-start)", 1.00, 1.0); + +run_test("steps(2, jump-end)", 0.00, 0.0); +run_test("steps(2, jump-end)", 0.49, 0.0); +run_test("steps(2, jump-end)", 0.50, 0.5); +run_test("steps(2, jump-end)", 0.99, 0.5); +run_test("steps(2, jump-end)", 1.00, 1.0); + +run_test("steps(1, jump-both)", 0.00, 0.5); +run_test("steps(1, jump-both)", 0.10, 0.5); +run_test("steps(1, jump-both)", 0.99, 0.5); +run_test("steps(1, jump-both)", 1.00, 1.0); + +run_test("steps(3, jump-both)", 0.00, 0.25); +run_test("steps(3, jump-both)", 0.33, 0.25); +run_test("steps(3, jump-both)", 0.34, 0.5); +run_test("steps(3, jump-both)", 0.66, 0.5); +run_test("steps(3, jump-both)", 0.67, 0.75); +run_test("steps(3, jump-both)", 0.99, 0.75); +run_test("steps(3, jump-both)", 1.00, 1.0); + +run_test("steps(2, jump-none)", 0.00, 0.0); +run_test("steps(2, jump-none)", 0.49, 0.0); +run_test("steps(2, jump-none)", 0.50, 1.0); +run_test("steps(2, jump-none)", 1.00, 1.0); + +run_test("steps(3, jump-none)", 0.00, 0.0); +run_test("steps(3, jump-none)", 0.33, 0.0); +run_test("steps(3, jump-none)", 0.34, 0.5); +run_test("steps(3, jump-none)", 0.66, 0.5); +run_test("steps(3, jump-none)", 0.67, 1.0); +run_test("steps(3, jump-none)", 1.00, 1.0); + +</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 0000000000..7e8052892c --- /dev/null +++ b/layout/style/test/test_unclosed_parentheses.html @@ -0,0 +1,262 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=575672 +--> +<head> + <title>Test for Bug 575672</title> + <script 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: linear-gradient(red, blue", + "background-image: linear-gradient(red, yellow, blue", + "background-image: linear-gradient(red 1px, yellow 5px, blue 10px", + "background-image: linear-gradient(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: radial-gradient(at top left, red, blue", + "background-image: radial-gradient(farthest-corner, red, blue", + "background-image: radial-gradient(ellipse closest-corner, red, hsl(240, 50%, 50%)", + "background-image: radial-gradient(farthest-side circle, 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: repeating-radial-gradient(circle, red, blue", + "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 0000000000..43622e2ae5 --- /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 0000000000..a4432b7650 --- /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 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", "transform: rotate(" + test + ")"); + is(p.style.getPropertyValue("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. (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 0000000000..cb5c0de20d --- /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 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 0000000000..623f13df33 --- /dev/null +++ b/layout/style/test/test_units_length.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for serialization and equivalence of length units</title> + <script 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; + + ok(Math.abs(parseFloat(cm1, 10) - parseFloat(cm2, 10)) <= 0.0001, test + " should compute to the same as " + equiv + ", modulo floating point math error"); + } +} + +// 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 0000000000..16211c0207 --- /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 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_use_counters.html b/layout/style/test/test_use_counters.html new file mode 100644 index 0000000000..0706c9702d --- /dev/null +++ b/layout/style/test/test_use_counters.html @@ -0,0 +1,159 @@ +<!doctype html> +<title>Test for Bug 1425700: CSS properties use-counters</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<body> +<iframe id="iframe"></iframe> +<iframe id="second-iframe"></iframe> +<script> +const iframe = document.getElementById("iframe"); + +function iframe_reload(frame = iframe) { + return new Promise(resolve => { + frame.addEventListener("load", _ => resolve()); + frame.contentWindow.location.reload(); + }); +} + +function assert_recorded(win, recorded, properties, desc) { + const utils = SpecialPowers.getDOMWindowUtils(win); + isnot(properties.length, 0, "Sanity check"); + for (const prop of properties) { + try { + is(utils.isCssPropertyRecordedInUseCounter(prop), recorded, + `${desc} - ${prop}`) + } catch(ex) { + ok(false, "Threw: " + prop); + } + } +} + +// NOTE(emilio): This is no longer meaningful now we always record in the style +// system itself, which is what this tests. But we could conceivably change +// it so it doesn't hurt. +add_task(async () => { + await SpecialPowers.pushPrefEnv({ + "set": [ + ["layout.css.use-counters.enabled", true], + ["layout.css.use-counters-unimplemented.enabled", true] + ] + }); +}); + +// TODO(emilio): Make work (and test) inline style and maybe even CSSOM and +// such? +// +// Make sure that something on the lines of the following passes: +// +// element.style.webkitTransform = "rotate(1deg)" +// assert_recorded(true, ["-webkit-transform"]); +// assert_recorded(false, ["transform"]); +// +const IMPLEMENTED_PROPERTIES = { + description: "unimplemented properties", + css: ` + * { + grid-gap: 1px; /* shorthand alias */ + -webkit-background-size: 100px 100px; /* longhand alias */ + transform-origin: top left; /* longhand */ + background: green; /* shorthand */ + } + `, + recorded: [ + "grid-gap", + "-webkit-background-size", + "transform-origin", + "background", + ], + // Should only record the aliases, not the non-aliased property. + // Should only record shorthands, not the longhands it expands to. + not_recorded: [ + "gap", + "background-size", + "-moz-transform-origin", + "-webkit-transform-origin", + "background-color", + ], +}; + +const UNIMPLEMENTED_PROPERTIES = { + description: "unimplemented properties", + css: ` + * { + grid-gap: 1px; /* shorthand alias */ + -webkit-background-size: 100px 100px; /* longhand alias */ + transform-origin: top left; /* longhand */ + background: green; /* shorthand */ + -webkit-font-smoothing: auto; /* counted unknown */ + } + `, + recorded: [ + "grid-gap", + "-webkit-background-size", + "transform-origin", + "background", + "-webkit-font-smoothing", + ], + not_recorded: [ + "size", + "speak", + ], +}; + +// Test on regular <style> elements. +add_task(async () => { + for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) { + await iframe_reload(); + + const win = iframe.contentWindow; + const style = document.createElement('style'); + style.textContent = test.css; + + iframe.contentDocument.body.appendChild(style); + + assert_recorded(win, true, test.recorded, `Test ${test.description} in <style> elements`); + assert_recorded(win, false, test.not_recorded, `Test ${test.description} in <style> elements`); + } +}); + +// Test on constructable stylesheets. +add_task(async () => { + for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) { + for (let method of ["replace", "replaceSync"]) { + await iframe_reload(); + const win = iframe.contentWindow; + + const sheet = new win.CSSStyleSheet(); + await sheet[method](test.css); + + assert_recorded(win, true, test.recorded, `Test ${test.description} in constructed sheet`); + assert_recorded(win, false, test.not_recorded, `Test ${test.description} in constructed sheet`); + } + } +}); + +add_task(async () => { + // Test for <link rel="stylesheet">. One iteration for the uncached version, one for the cached one. + for (let test of [IMPLEMENTED_PROPERTIES, UNIMPLEMENTED_PROPERTIES]) { + const uri = "data:text/css," + encodeURIComponent(test.css); + for (let frame of [iframe, document.getElementById("second-iframe")]) { + await iframe_reload(frame); + const win = frame.contentWindow; + const doc = frame.contentDocument; + + const link = doc.createElement("link"); + link.rel = "stylesheet"; + const linkLoaded = new Promise(resolve => { + link.onload = resolve; + }); + link.href = uri; + doc.body.appendChild(link); + await linkLoaded; + assert_recorded(win, true, test.recorded, `Test ${test.description} in <link> ${frame.id}`); + assert_recorded(win, false, test.not_recorded, `Test ${test.description} in <link> ${frame.id}`); + } + } +}); + +</script> +</body> diff --git a/layout/style/test/test_user_sheet_shadow_dom.html b/layout/style/test/test_user_sheet_shadow_dom.html new file mode 100644 index 0000000000..cd7a44b308 --- /dev/null +++ b/layout/style/test/test_user_sheet_shadow_dom.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<title>Test for bug 1576229 - Nodes in Shadow DOM react properly to dynamic changes in user sheets</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<div></div> +<span id="host" style="display: block"></span> +<script> +const gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"] + .getService(SpecialPowers.Ci.nsIIOService) + +const gSSService = SpecialPowers.Cc["@mozilla.org/content/style-sheet-service;1"] + .getService(SpecialPowers.Ci.nsIStyleSheetService); + +const windowUtils = SpecialPowers.getDOMWindowUtils(window); + +function loadUserSheet(style) { + const uri = gIOService.newURI("data:text/css," + style); + windowUtils.loadSheet(uri, windowUtils.USER_SHEET); +} + +SimpleTest.waitForExplicitFinish(); + +onload = function() { + loadUserSheet(` + div { + width: 100px; + height: 100px; + background-color: red; + } + .foo { + background-color: green; + } + `); + let host = document.querySelector("#host"); + host.attachShadow({ mode: "open" }).innerHTML = ` + <div></div> + `; + let light = document.querySelector('div'); + let shadow = host.shadowRoot.querySelector('div'); + is(getComputedStyle(light).backgroundColor, "rgb(255, 0, 0)", "User sheet works in light DOM"); + is(getComputedStyle(shadow).backgroundColor, "rgb(255, 0, 0)", "User sheet works in shadow DOM"); + light.classList.add("foo"); + shadow.classList.add("foo"); + is(getComputedStyle(light).backgroundColor, "rgb(0, 128, 0)", "Dynamic change for user sheet works in light DOM"); + is(getComputedStyle(shadow).backgroundColor, "rgb(0, 128, 0)", "Dynamic change for user sheet works in shadow DOM"); + SimpleTest.finish(); +} +</script> diff --git a/layout/style/test/test_value_cloning.html b/layout/style/test/test_value_cloning.html new file mode 100644 index 0000000000..ceaa9d3c66 --- /dev/null +++ b/layout/style/test/test_value_cloning.html @@ -0,0 +1,181 @@ +<!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 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 test_queue = []; +var iframe = document.getElementById("iframe"); + +SimpleTest.waitForExplicitFinish(); + +for (var prop in gCSSProperties) { + let 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 (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 (let idx in info.initial_values) { + test_queue.push({ prop: prop, value: info.initial_values[idx] }); + } + for (let 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 (let idx = 0; idx < test_queue.length; ++idx) { + var current_item = test_queue[idx]; + + let 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 (let 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 wrappedFrame = SpecialPowers.wrap(iframe); + var ifdoc = wrappedFrame.contentDocument; + var ifwin = wrappedFrame.contentWindow; + + for (let 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 = ifwin.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.remove(); + + // Force a flush + ifdoc.body.style.display="none"; + var ow = ifdoc.body.offsetWidth; + ifdoc.body.style.display=""; + + for (let 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 0000000000..df38a24b9b --- /dev/null +++ b/layout/style/test/test_value_computation.html @@ -0,0 +1,236 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for computation of values in property database</title> + <script 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(); + SimpleTest.requestLongerTimeout(2); + + var load_count = 0; + function load_done() { + if (++load_count == 3) + run_tests(); + } + </script> +</head> +<body> +<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 id="display"><span><span id="elementf"></span></span></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 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 (!has_frame && (property in gBadComputedNoFrame) && + gBadComputedNoFrame[property].includes(value)) + 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" ], + // When there's a frame, these keywords works out to the same as the initial + // value, i.e. `auto`, given the prerequisites of only 'display: block'. + "height": [ "-moz-max-content", "-moz-min-content", "-moz-fit-content", + "-moz-available", "max-content", "min-content", "fit-content", + "fit-content(100px)", "fit-content(10%)", + "fit-content(calc(3*25px + 50%))" ], + "block-size": [ "-moz-max-content", "-moz-min-content", "-moz-fit-content", + "-moz-available", "max-content", "min-content", "fit-content", + "fit-content(100px)", "fit-content(10%)", + "fit-content(calc(3*25px + 50%))" ], +}; + +function swap_when_frame(property, value) { + return (property in gSwapInitialWhenHaveFrame) && + gSwapInitialWhenHaveFrame[property].includes(value); +} + +var gDisplayTree = document.getElementById("display"); +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 used to be important for values that are supposed to compute to the + // initial value (given the design of the old rule tree, 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'd have a cached aStartStruct from the parent in the rule + // tree (caching the "other" value), so we'd make sure we don't get the + // initial value from the luck of default-initialization. This means + // that it would've been important that we set the prereqs on + // gRule1.style rather than on gElement.style. + // + // However, the rule tree no longer stores cached structs, and we only + // temporarily cache reset structs during a single restyle. So the + // particular failure mode this was designed to test for isn't as + // likely to eventuate. + 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); + } +} + +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 0000000000..542cad91ed --- /dev/null +++ b/layout/style/test/test_value_storage.html @@ -0,0 +1,365 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for parsing, storage, and serialization of CSS values</title> + <script 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", + "icon", + "menu", + "message-box", + "small-caption", + "status-bar", + "-moz-button", + "-moz-pull-down-menu", + "-moz-list", + "-moz-field", +]; + +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].includes(value)) + 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.includes("Android")) { + 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(propName, 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 " + propName + " 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; + var is_system_font = property == "font" && gSystemFont.includes(value); + + var colon = ": "; + 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 && + // System font doesn't produce meaningful value for subproperties. + !is_system_font) + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + if (value_has_variable_reference && + (!info.alias_for || info.type == CSS_TYPE_TRUE_SHORTHAND || + info.type == CSS_TYPE_LEGACY_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) { + let newValue = info.legacy_mapping && info.legacy_mapping[step1val] + ? info.legacy_mapping[step1val] : step1val; + // FIXME(emilio): This is a bit unfortunate: + // https://github.com/w3c/csswg-drafts/issues/3332 + if (info.type == CSS_TYPE_LEGACY_SHORTHAND && value_has_variable_reference) + newValue = ""; + expected_serialization = info.alias_for + colon + newValue + ";"; + } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) { + is(property, "zoom", "Zoom is a bit special because it never " + + "serializes as-is, we always serialize the longhands, " + + "but it doesn't just map to a single property " + + "(and thus we can't use the 'alias_for' mechanism)"); + let transform = step1val == "1" ? "none" : "scale(" + step1val + ")"; + let origin = step1val == "1" ? "50% 50% 0px" : "0px 0px 0px"; + if (value_has_variable_reference) { // See above. + transform = ""; + origin = ""; + } + expected_serialization = "transform" + colon + transform + "; transform-origin" + colon + origin + ";"; + } else { + expected_serialization = property + colon + 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 + colon + 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. + !is_system_font && + !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 + colon + value + "'"); + } + + // FIXME(emilio): Why is mask special? + 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 && !is_system_font) { + 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. + 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); +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 0000000000..2814e4ab93 --- /dev/null +++ b/layout/style/test/test_variable_serialization_computed.html @@ -0,0 +1,79 @@ +<!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: 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}: ${declaration}`); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +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 0000000000..cae8871cb2 --- /dev/null +++ b/layout/style/test/test_variable_serialization_specified.html @@ -0,0 +1,116 @@ +<!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(); +runTest(); +</script> diff --git a/layout/style/test/test_variables.html b/layout/style/test/test_variables.html new file mode 100644 index 0000000000..e26dc5a0f7 --- /dev/null +++ b/layout/style/test/test_variables.html @@ -0,0 +1,129 @@ +<!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> + +<style id="test8"> +</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=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() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=1330172 + var test8 = document.getElementById("test8"); + test8.textContent = "p { --a:inHerit; }"; + is(test8.sheet.cssRules[0].style.cssText, "--a: inherit;"); + test8.textContent = "p { --b: initial!important; }"; + is(test8.sheet.cssRules[0].style.cssText, "--b: initial !important;"); + test8.textContent = "p { --c: UNSET !important }"; + is(test8.sheet.cssRules[0].style.cssText, "--c: unset !important;"); + }, +]; + +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(); +prepareTest(); +</script> diff --git a/layout/style/test/test_variables_loop.html b/layout/style/test/test_variables_loop.html new file mode 100644 index 0000000000..76e97e26f3 --- /dev/null +++ b/layout/style/test/test_variables_loop.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>CSS variables loop resolving</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="test"> + #outer { + --a: a; + --b: b; + --c: c; + --d: d; + --e: e; + } + #inner { + --a: var(--d, ad); + --b: var(--d, ad); + --c: var(--d, ad); + --d: var(--e, de); + --e: var(--a, ea) var(--b, eb) var(--c, ec); + } +</style> +<div id="outer"> + <div id="inner"></div> +</div> +<script> +let inner_cs = getComputedStyle(document.getElementById("inner")); +for (let v of ['a', 'b', 'c', 'd', 'e']) { + is(inner_cs.getPropertyValue(`--${v}`), "", + `Variable --${v} should be eliminated`); +} +</script> diff --git a/layout/style/test/test_variables_order.html b/layout/style/test/test_variables_order.html new file mode 100644 index 0000000000..5adb9edf75 --- /dev/null +++ b/layout/style/test/test_variables_order.html @@ -0,0 +1,53 @@ +<!DOCTYPE type> +<title>CSS variables order 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="test"> +</style> + +<div id="t4"></div> + +<script> + +/* + * Although the spec does not enforce any specific order, Gecko and Servo + * implement a consistent ordering for CSSDeclaration objects in the DOM. + * CSSDeclarations expose property names as indexed properties, which need + * to be stable. This order is the order that properties are cascaded in. + * + * We have this test just to prevent regressions, rather than testing specific + * mandated behavior. + */ + +function prepareTest() { + 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() { + var test = document.getElementById("test"); + test.textContent = "div { --SomeVariableName: a; }"; + + var declaration = test.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("t4"); + var cs = window.getComputedStyle(element); + + ["--SomeVariableName", "--a"].forEach((varName, index) => { + is(cs.item(cs.length - (index + 1)), varName, "custom property name returned by item() on computed style"); + is(cs[cs.length - (index + 1)], varName, "custom property name returned by indexed getter on style declaration"); + }); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +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 0000000000..e673ba204e --- /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 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_scrollbar_causing_reflow.html b/layout/style/test/test_viewport_scrollbar_causing_reflow.html new file mode 100644 index 0000000000..ced14f352a --- /dev/null +++ b/layout/style/test/test_viewport_scrollbar_causing_reflow.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1367568 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1367568</title> + <script 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 1367568</a> +<div id="content"> + <!-- Some fixed-width divs that we shouldn't have to reflow when the viewport + changes. More than 5 so that our leeway for scrollbar parts doesn't + accidentally cause the test to pass --> + <div style="width: 100px">fixed-width <div>(child)</div></div> + <div style="width: 100px">fixed-width <div>(child)</div></div> + <div style="width: 100px">fixed-width <div>(child)</div></div> + <div style="width: 100px">fixed-width <div>(child)</div></div> + <div style="width: 100px">fixed-width <div>(child)</div></div> + <div style="width: 100px">fixed-width <div>(child)</div></div> + <div style="position: absolute; width: 150px"> + abs-fixed-width + <div>(child)</div> + </div> +</div> +<pre id="test"> +<script> +"use strict"; + +/** Test for Bug 1367568 **/ + +/** + * This test verifies that "overflow" changes on the <body> don't cause + * an unnecessarily large amount of reflow. + */ + +// Vars used in setStyleAndMeasure that we really only have to look up once: +const gUtils = SpecialPowers.getDOMWindowUtils(window); + +function setStyleAndMeasure(initialStyle, finalStyle) { + is(document.body.style.length, 0, + "Bug in test - body should start with empty style"); + let unusedVal = document.body.offsetHeight; // flush layout + let constructCount = gUtils.framesConstructed; + + document.body.style = initialStyle; + unusedVal = document.body.offsetHeight; // flush layout + let reflowCountBeforeTweak = gUtils.framesReflowed; + + document.body.style = finalStyle; + unusedVal = document.body.offsetHeight; // flush layout + let reflowCountAfterTweak = gUtils.framesReflowed; + + // Clean up: + document.body.style = ""; + + is(gUtils.framesConstructed, constructCount, + "Style tweak shouldn't have triggered frame construction"); + + // ...and return the delta: + return reflowCountAfterTweak - reflowCountBeforeTweak; +} + +function main() { + // First, we sanity-check that our measurement make sense -- if we leave + // styles unchanged, we should measure no frames being reflowed: + let count = setStyleAndMeasure("width: 50px; height: 80px", + "width: 50px; height: 80px"); + is(count, 0, + "Shouldn't reflow anything when we leave 'width' & 'height' unchanged"); + + // Now: see how many frames are reflowed when the "width" & "height" change. + // We'll use this as the reference when measuring reflow counts for various + // changes to "overflow" below. + count = setStyleAndMeasure("width: 50px; height: 80px", + "width: 90px; height: 60px"); + ok(count > 0, + "Should reflow some frames when 'width' & 'height' change"); + + const scrollbarsHaveButtons = navigator.platform.includes("Win"); + // This is to allow for reflowing scrollbar parts themselves. + const scrollbarReflows = scrollbarsHaveButtons ? 5 : 2; + // Expected maximum number of frames reflowed for "overflow" changes + const expectedMax = count + scrollbarReflows; + + // Shared ending for messages in all ok() checks below: + const messageSuffix = + " shouldn't be greater than count for tweaking width/height on body (" + + expectedMax + ")"; + + // OK, here is where the relevant tests actually begin!! + // See how many frames we reflow for various tweaks to "overflow" on + // the body -- we expect the count to be no larger than |expectedMax|. + count = setStyleAndMeasure("", "overflow: scroll"); + ok(count <= expectedMax, + "Reflow count when setting 'overflow: scroll' on body (" + count + ")" + + messageSuffix); + + count = setStyleAndMeasure("", "overflow: hidden"); + ok(count <= expectedMax, + "Reflow count when setting 'overflow: hidden' on body (" + count + ")" + + messageSuffix); + + // Test removal of "overflow: scroll": + count = setStyleAndMeasure("overflow: scroll", ""); + ok(count <= expectedMax, + "Reflow count when removing 'overflow: scroll' from body (" + count + ")" + + messageSuffix); + + count = setStyleAndMeasure("overflow: hidden", ""); + ok(count <= expectedMax, + "Reflow count when removing 'overflow: hidden' from body (" + count + ")" + + messageSuffix); + + // Test change between two non-'visible' overflow values: + count = setStyleAndMeasure("overflow: scroll", "overflow: hidden"); + ok(count <= expectedMax, + "Reflow count when changing 'overflow' on body (" + count + ")" + + messageSuffix); +} + +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 0000000000..fa7df88eb6 --- /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 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); + +</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 0000000000..09aae8e53c --- /dev/null +++ b/layout/style/test/test_visited_image_loading.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=557287 +--> +<head> + <title>Test for Bug 557287</title> + <script 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> +<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); + +function run() +{ + subwin = window.open("visited_image_loading_frame.html", "_blank"); + subwin.addEventListener("load", function() { + subdoc = subwin.document; + 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); + // 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); + var s = document.createElement("script"); + s.src = "visited_image_loading.sjs?waitforresult"; + document.body.appendChild(s); + subwin.close(); +} + +</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 0000000000..6687254720 --- /dev/null +++ b/layout/style/test/test_visited_image_loading_empty.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=557287 +--> +<head> + <title>Test for Bug 557287</title> + <script 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> +<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); + +function run() +{ + subwin = window.open("visited_image_loading_frame_empty.html", "_blank"); + subwin.addEventListener("load", function() { + subdoc = subwin.document; + 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); + // 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); + var s = document.createElement("script"); + s.src = "visited_image_loading.sjs?waitforresult"; + document.body.appendChild(s); + subwin.close(); +} + +</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 0000000000..116d301cf1 --- /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 src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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); + +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 = top.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 0000000000..481a71bfef --- /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 src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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); + +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 = top.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 0000000000..0353d948fc --- /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 src="/tests/SimpleTest/SimpleTest.js"></script> + <script 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", + "needs-focus == caret-color-on-visited-1.html caret-color-on-visited-1-ref.html", + "!= 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", + "== color-on-visited-text-1.html color-on-visited-text-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-adj-sibling-3.xhtml selector-adj-sibling-3-ref.xhtml", + "== 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-1a.html svg-image-visited-1-ref.html", + "== svg-image-visited-1b.html svg-image-visited-1-ref.html", + "== svg-image-visited-1c.html svg-image-visited-1-ref.html", + "== svg-image-visited-1d.html svg-image-visited-1-ref.html", + // 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", + "== placeholder-1.html placeholder-1-ref.html", + "== visited-inherit-1.html visited-inherit-1-ref.html", + "== transition-on-visited.html transition-on-visited-ref.html", + "== logical-box-border-color-visited-link-001.html logical-box-border-color-visited-link-ref.html", + "== logical-box-border-color-visited-link-002.html logical-box-border-color-visited-link-ref.html", + "== logical-box-border-color-visited-link-003.html logical-box-border-color-visited-link-ref.html", + "== svg-paint-currentcolor-visited.svg svg-paint-currentcolor-visited-ref.svg", + "== variables-visited.html variables-visited-ref.html", +]; + +// 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 startIframe(url) { + return new Promise(resolve => { + var element = document.createElement("iframe"); + element.addEventListener("load", () => { + element.contentDocument.fonts.ready.then(() => { + resolve(element.contentWindow); + }); + }, {once: true}); + // smaller than normal reftests, but enough for these + element.setAttribute("style", "width: 30em; height: 10em"); + element.src = "css-visited/" + url; + document.body.appendChild(element); + }); +} + +async function runTests() { + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("async link coloring"); + // Set caret to a known size, for tests of :visited caret-color styling + await SpecialPowers.pushPrefEnv({'set': [['ui.caretWidth', 16]]}); + info("opening visited page"); + let win = window.open("css-visited/visited-page.html", "_blank"); + await new Promise(resolve => { + win.onload = resolve; + }); + info("running tests"); + await Promise.all(gTests.map(runTest)); + win.close(); + SimpleTest.finish(); +} + +function passes(equal, shot1, shot2) +{ + let [correct] = compareSnapshots(shot1, shot2, equal); + return correct; +} + +function waitFor100msAndIdle() { + return new Promise(resolve => setTimeout(function() { + requestIdleCallback(resolve); + }, 100)); +} + +async function runTest(testLine) { + let splitData = testLine.split(" "); + let isEqual; + let needsFocus = false; + while (true) { + let op = splitData.shift(); + if (op == "needs-focus") { + needsFocus = true; + } else if (op == "==" || op == "!=") { + isEqual = op == "=="; + break; + } else { + ok(false, "Unknown syntax"); + return; + } + } + let [testFile, refFile] = splitData; + + let promiseTestWin = startIframe(testFile); + let promiseRefWin = startIframe(refFile); + let refSnapshot = snapshotWindow(await promiseRefWin); + let testWindow = await promiseTestWin; + // 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. + await waitFor100msAndIdle(); + + let tries; + let testSnapshot; + for (tries = 0; tries < gMaxPassingTries * gTimeoutFactor; ++tries) { + if (needsFocus) { + await SimpleTest.promiseFocus(testWindow, false); + } + testSnapshot = snapshotWindow(testWindow, true); + if (passes(isEqual, testSnapshot, refSnapshot)) { + if (tries > gMaxPassingTries) { + gMaxPassingTries = tries; + } + break; + } + // Links might not have been colored yet. Try again in 100ms. + await waitFor100msAndIdle(); + } + + let result = assertSnapshots(testSnapshot, refSnapshot, + isEqual, null, testFile, refFile); + if (!result) { + info(`Gave up after ${tries} tries, ` + + `maxp=${gMaxPassingTries}, fact=${gTimeoutFactor}`); + } +} + +runTests(); + +</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 0000000000..69e50c58ff --- /dev/null +++ b/layout/style/test/test_webkit_device_pixel_ratio.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1176968 +--> +<head> + <title>Test for Bug 1176968</title> + <script 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)).visibility == "visible"; + } + + var screenPixelsPerCSSPixel = window.devicePixelRatio; + 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 0000000000..2f329ed67d --- /dev/null +++ b/layout/style/test/test_webkit_flex_display.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1274096 +--> +<head> + <title>Test for Bug 1274096</title> + <script 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(); +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/unstyled-frame.css b/layout/style/test/unstyled-frame.css new file mode 100644 index 0000000000..e69de29bb2 --- /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 0000000000..833b4f112f --- /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 0000000000..82767f9b2f --- /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 0000000000..86b7c54acd --- /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 0000000000..fd71a3cd3e --- /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 0000000000..ad1dac7587 --- /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 0000000000..31da176e44 --- /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 0000000000..79c03d7b54 --- /dev/null +++ b/layout/style/test/visited_image_loading.sjs @@ -0,0 +1,83 @@ +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 0000000000..f919f5eb75 --- /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 0000000000..21579bb9ca --- /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> |