mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 19:59:17 +00:00
LibWeb: Parse all
as keyword in transition
shorthand
This ensures that the parsing of the `transition` shorthand property behaves in the same way as the `transition-property` longhand.4
This commit is contained in:
parent
a3e485e2d0
commit
e7ae9c8ebf
Notes:
github-actions[bot]
2025-04-28 18:52:33 +00:00
Author: https://github.com/tcl3
Commit: e7ae9c8ebf
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4503
8 changed files with 1076 additions and 3 deletions
|
@ -3756,7 +3756,18 @@ RefPtr<CSSStyleValue const> Parser::parse_transition_value(TokenStream<Component
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto transition_property = parse_custom_ident_value(tokens, { { "none"sv } })) {
|
if (auto token = tokens.peek_token(); token.is_ident("all"sv)) {
|
||||||
|
auto transition_keyword = parse_keyword_value(tokens);
|
||||||
|
VERIFY(transition_keyword->to_keyword() == Keyword::All);
|
||||||
|
if (transition.property_name) {
|
||||||
|
dbgln_if(CSS_PARSER_DEBUG, "Transition property has multiple property identifiers");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
transition.property_name = transition_keyword.release_nonnull();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto transition_property = parse_custom_ident_value(tokens, { { "all"sv, "none"sv } })) {
|
||||||
if (transition.property_name) {
|
if (transition.property_name) {
|
||||||
dbgln_if(CSS_PARSER_DEBUG, "Transition property has multiple property identifiers");
|
dbgln_if(CSS_PARSER_DEBUG, "Transition property has multiple property identifiers");
|
||||||
return {};
|
return {};
|
||||||
|
@ -3774,7 +3785,7 @@ RefPtr<CSSStyleValue const> Parser::parse_transition_value(TokenStream<Component
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!transition.property_name)
|
if (!transition.property_name)
|
||||||
transition.property_name = CustomIdentStyleValue::create("all"_fly_string);
|
transition.property_name = CSSKeywordValue::create(Keyword::All);
|
||||||
|
|
||||||
if (!transition.easing)
|
if (!transition.easing)
|
||||||
transition.easing = EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
|
transition.easing = EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace Web::CSS {
|
||||||
class TransitionStyleValue final : public StyleValueWithDefaultOperators<TransitionStyleValue> {
|
class TransitionStyleValue final : public StyleValueWithDefaultOperators<TransitionStyleValue> {
|
||||||
public:
|
public:
|
||||||
struct Transition {
|
struct Transition {
|
||||||
ValueComparingRefPtr<CustomIdentStyleValue const> property_name;
|
ValueComparingRefPtr<CSSStyleValue const> property_name;
|
||||||
TimeOrCalculated duration { CSS::Time::make_seconds(0.0) };
|
TimeOrCalculated duration { CSS::Time::make_seconds(0.0) };
|
||||||
TimeOrCalculated delay { CSS::Time::make_seconds(0.0) };
|
TimeOrCalculated delay { CSS::Time::make_seconds(0.0) };
|
||||||
ValueComparingRefPtr<EasingStyleValue const> easing;
|
ValueComparingRefPtr<EasingStyleValue const> easing;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
Harness status: OK
|
||||||
|
|
||||||
|
Found 18 tests
|
||||||
|
|
||||||
|
8 Pass
|
||||||
|
10 Fail
|
||||||
|
Pass margin-bottom percentage(%) / values
|
||||||
|
Fail margin-bottom percentage(%) / events
|
||||||
|
Pass margin-left percentage(%) / values
|
||||||
|
Fail margin-left percentage(%) / events
|
||||||
|
Pass margin-right percentage(%) / values
|
||||||
|
Fail margin-right percentage(%) / events
|
||||||
|
Pass margin-top percentage(%) / values
|
||||||
|
Fail margin-top percentage(%) / events
|
||||||
|
Pass padding-bottom percentage(%) / values
|
||||||
|
Fail padding-bottom percentage(%) / events
|
||||||
|
Pass padding-left percentage(%) / values
|
||||||
|
Fail padding-left percentage(%) / events
|
||||||
|
Pass padding-right percentage(%) / values
|
||||||
|
Fail padding-right percentage(%) / events
|
||||||
|
Pass padding-top percentage(%) / values
|
||||||
|
Fail padding-top percentage(%) / events
|
||||||
|
Fail vertical-align vertical(keyword) / values
|
||||||
|
Fail vertical-align vertical(keyword) / events
|
|
@ -0,0 +1,137 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>CSS Transitions Test: Intermediate Property Values of missing value types</title>
|
||||||
|
<meta name="assert" content="Test checks that expected value types that haven't been specified are transitionable">
|
||||||
|
<link rel="help" href="http://www.w3.org/TR/css3-transitions/#transitions">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/web-animations-1/#animation-type">
|
||||||
|
<link rel="author" title="Rodney Rehm" href="http://rodneyrehm.de/en/">
|
||||||
|
<meta name="flags" content="dom">
|
||||||
|
|
||||||
|
<script src="../../resources/testharness.js" type="text/javascript"></script>
|
||||||
|
<script src="../../resources/testharnessreport.js" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<script src="./support/vendorPrefix.js" type="text/javascript"></script>
|
||||||
|
<script src="./support/helper.js" type="text/javascript"></script>
|
||||||
|
<script src="./support/runParallelAsyncHarness.js" type="text/javascript"></script>
|
||||||
|
<script src="./support/generalParallelTest.js" type="text/javascript"></script>
|
||||||
|
<script src="./support/properties.js" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
#offscreen {
|
||||||
|
position: absolute;
|
||||||
|
top: -100000px;
|
||||||
|
left: -100000px;
|
||||||
|
width: 100000px;
|
||||||
|
height: 100000px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- required by testharnessreport.js -->
|
||||||
|
<div id="log"></div>
|
||||||
|
<!-- elements used for testing -->
|
||||||
|
<div id="fixture" class="fixture">
|
||||||
|
<div class="container">
|
||||||
|
<div class="transition">Text sample</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="offscreen"></div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SEE ./support/README.md for an abstract explanation of the test procedure
|
||||||
|
http://test.csswg.org/source/contributors/rodneyrehm/submitted/css3-transitions/README.md
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// this suite tests property value types that haven't been specified
|
||||||
|
// (like <percentage> for margin-bottom)
|
||||||
|
|
||||||
|
// this test takes its time, give it a minute to run
|
||||||
|
var timeout = 60000;
|
||||||
|
setup({timeout: timeout});
|
||||||
|
|
||||||
|
var tests = getMissingPropertyTests();
|
||||||
|
// for testing, limit to a couple of iterations
|
||||||
|
// tests = tests.slice(10, 30);
|
||||||
|
// or filter using one of:
|
||||||
|
// tests = filterPropertyTests(tests, "background-color color(rgba)");
|
||||||
|
// tests = filterPropertyTests(tests, ["background-color color(rgba)", ...]);
|
||||||
|
// tests = filterPropertyTests(tests, /^background-color/);
|
||||||
|
|
||||||
|
// general transition-duration
|
||||||
|
var duration = '2s';
|
||||||
|
|
||||||
|
runParallelAsyncHarness({
|
||||||
|
// array of test data
|
||||||
|
tests: tests,
|
||||||
|
// the number of tests to run in parallel
|
||||||
|
testsPerSlice: 50,
|
||||||
|
// milliseconds to wait before calling teardown and ending test
|
||||||
|
duration: parseFloat(duration) * 1000,
|
||||||
|
// prepare individual test
|
||||||
|
setup: function(data, options) {
|
||||||
|
var styles = {
|
||||||
|
'.fixture': {},
|
||||||
|
|
||||||
|
'.container': data.parentStyle,
|
||||||
|
'.container.to': {},
|
||||||
|
'.container.how': {},
|
||||||
|
|
||||||
|
'.transition': data.from,
|
||||||
|
'.transition.to' : data.to,
|
||||||
|
'.transition.how' : {transition: 'all ' + duration + ' linear 0s'}
|
||||||
|
};
|
||||||
|
|
||||||
|
generalParallelTest.setup(data, options);
|
||||||
|
generalParallelTest.addStyles(data, options, styles);
|
||||||
|
},
|
||||||
|
// cleanup after individual test
|
||||||
|
teardown: generalParallelTest.teardown,
|
||||||
|
// invoked prior to running a slice of tests
|
||||||
|
sliceStart: generalParallelTest.sliceStart,
|
||||||
|
// invoked after transitions have started
|
||||||
|
transitionsStarted: generalParallelTest.transitionsStarted,
|
||||||
|
// invoked after running a slice of tests
|
||||||
|
sliceDone: generalParallelTest.sliceDone,
|
||||||
|
// test cases, make them as granular as possible
|
||||||
|
cases: {
|
||||||
|
// test property values while transitioning
|
||||||
|
// values.start kicks off a transition
|
||||||
|
'values': {
|
||||||
|
// run actual test, assertions can be used here!
|
||||||
|
start: function(test, data, options) {
|
||||||
|
// identify initial and target values
|
||||||
|
generalParallelTest.getStyle(data);
|
||||||
|
// make sure values differ, if they don't, the property could most likely not be parsed
|
||||||
|
assert_not_equals(data.transition.from, data.transition.to, "initial and target values may not match");
|
||||||
|
// kick off the transition
|
||||||
|
generalParallelTest.startTransition(data);
|
||||||
|
|
||||||
|
// make sure we didn't get the target value immediately.
|
||||||
|
// If we did, there wouldn't be a transition!
|
||||||
|
var current = data.transition.computedStyle(data.property);
|
||||||
|
assert_not_equals(current, data.transition.to, "must not be target value after start");
|
||||||
|
},
|
||||||
|
done: function(test, data, options) {
|
||||||
|
// make sure the property's value were neither initial nor target while transitioning
|
||||||
|
test.step(generalParallelTest.assertIntermediateValuesFunc(data, 'transition'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// test TransitionEnd events
|
||||||
|
'events': {
|
||||||
|
done: function(test, data, options) {
|
||||||
|
// make sure there were no events on parent
|
||||||
|
test.step(generalParallelTest.assertExpectedEventsFunc(data, 'container', ""));
|
||||||
|
// make sure we got the event for the tested property only
|
||||||
|
test.step(generalParallelTest.assertExpectedEventsFunc(data, 'transition', addVendorPrefix(data.property) + ":" + duration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// called once all tests are done
|
||||||
|
done: generalParallelTest.done
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,248 @@
|
||||||
|
(function(root) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
var suite = root.generalParallelTest = {
|
||||||
|
// prepare individual test
|
||||||
|
setup: function(data, options) {
|
||||||
|
suite._setupDom(data, options);
|
||||||
|
suite._setupEvents(data, options);
|
||||||
|
},
|
||||||
|
// clone fixture and prepare data containers
|
||||||
|
_setupDom: function(data, options) {
|
||||||
|
// clone fixture into off-viewport test-canvas
|
||||||
|
data.fixture = document.getElementById('fixture').cloneNode(true);
|
||||||
|
data.fixture.id = 'test-' + (index++);
|
||||||
|
(document.getElementById('offscreen') || document.body).appendChild(data.fixture);
|
||||||
|
|
||||||
|
// data container for #fixture > .container > .transition
|
||||||
|
data.transition = {
|
||||||
|
node: data.fixture.querySelector('.transition'),
|
||||||
|
values: [],
|
||||||
|
events: [],
|
||||||
|
computedStyle: function(property) {
|
||||||
|
return computedStyle(data.transition.node, property);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// data container for #fixture > .container
|
||||||
|
data.container = {
|
||||||
|
node: data.transition.node.parentNode,
|
||||||
|
values: [],
|
||||||
|
events: [],
|
||||||
|
computedStyle: function(property) {
|
||||||
|
return computedStyle(data.container.node, property);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// data container for #fixture > .container > .transition[:before | :after]
|
||||||
|
if (data.pseudo) {
|
||||||
|
data.pseudo = {
|
||||||
|
name: data.pseudo,
|
||||||
|
values: [],
|
||||||
|
computedStyle: function(property) {
|
||||||
|
return computedStyle(data.transition.node, property, ':' + data.pseudo.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// bind TransitionEnd event listeners
|
||||||
|
_setupEvents: function(data, options) {
|
||||||
|
['transition', 'container'].forEach(function(elem) {
|
||||||
|
var handler = function(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
var name = event.propertyName;
|
||||||
|
var time = Math.round(event.elapsedTime * 1000) / 1000;
|
||||||
|
var pseudo = event.pseudoElement ? (':' + event.pseudoElement) : '';
|
||||||
|
data[elem].events.push(name + pseudo + ":" + time + "s");
|
||||||
|
};
|
||||||
|
data[elem].node.addEventListener('transitionend', handler, false);
|
||||||
|
data[elem]._events = {'transitionend': handler};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// cleanup after individual test
|
||||||
|
teardown: function(data, options) {
|
||||||
|
// data.fixture.remove();
|
||||||
|
if (data.fixture.parentNode) {
|
||||||
|
data.fixture.parentNode.removeChild(data.fixture);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// invoked prior to running a slice of tests
|
||||||
|
sliceStart: function(options, tests) {
|
||||||
|
// inject styles into document
|
||||||
|
setStyle(options.styles);
|
||||||
|
},
|
||||||
|
transitionsStarted: function(options, tests) {
|
||||||
|
// kick off value collection loop
|
||||||
|
generalParallelTest.startValueCollection(options);
|
||||||
|
},
|
||||||
|
// invoked after running a slice of tests
|
||||||
|
sliceDone: function(options, tests) {
|
||||||
|
// stop value collection loop
|
||||||
|
generalParallelTest.stopValueCollection(options);
|
||||||
|
// reset styles cache
|
||||||
|
options.styles = {};
|
||||||
|
},
|
||||||
|
// called once all tests are done
|
||||||
|
done: function(options) {
|
||||||
|
// reset document styles
|
||||||
|
setStyle();
|
||||||
|
reflow();
|
||||||
|
},
|
||||||
|
// add styles of individual test to slice cache
|
||||||
|
addStyles: function(data, options, styles) {
|
||||||
|
if (!options.styles) {
|
||||||
|
options.styles = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(styles).forEach(function(key) {
|
||||||
|
var selector = '#' + data.fixture.id
|
||||||
|
// fixture must become #fixture.fixture rather than a child selector
|
||||||
|
+ (key.substring(0, 8) === '.fixture' ? '' : ' ')
|
||||||
|
+ key;
|
||||||
|
|
||||||
|
options.styles[selector] = styles[key];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// set style and compute values for container and transition
|
||||||
|
getStyle: function(data) {
|
||||||
|
reflow();
|
||||||
|
// grab current styles: "initial state"
|
||||||
|
suite._getStyleFor(data, 'from');
|
||||||
|
// apply target state
|
||||||
|
suite._addClass(data, 'to', true);
|
||||||
|
// grab current styles: "target state"
|
||||||
|
suite._getStyleFor(data, 'to');
|
||||||
|
// remove target state
|
||||||
|
suite._removeClass(data, 'to', true);
|
||||||
|
|
||||||
|
// clean up the mess created for value collection
|
||||||
|
data.container._values = [];
|
||||||
|
data.transition._values = [];
|
||||||
|
if (data.pseudo) {
|
||||||
|
data.pseudo._values = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// grab current styles and store in respective element's data container
|
||||||
|
_getStyleFor: function(data, key) {
|
||||||
|
data.container[key] = data.container.computedStyle(data.property);
|
||||||
|
data.transition[key] = data.transition.computedStyle(data.property);
|
||||||
|
if (data.pseudo) {
|
||||||
|
data.pseudo[key] = data.pseudo.computedStyle(data.property);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// add class to test's elements and possibly reflow
|
||||||
|
_addClass: function(data, className, forceReflow) {
|
||||||
|
data.container.node.classList.add(className);
|
||||||
|
data.transition.node.classList.add(className);
|
||||||
|
if (forceReflow) {
|
||||||
|
reflow();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// remove class from test's elements and possibly reflow
|
||||||
|
_removeClass: function(data, className, forceReflow) {
|
||||||
|
data.container.node.classList.remove(className);
|
||||||
|
data.transition.node.classList.remove(className);
|
||||||
|
if (forceReflow) {
|
||||||
|
reflow();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// add transition and to classes to container and transition
|
||||||
|
startTransition: function(data) {
|
||||||
|
// add transition-defining class
|
||||||
|
suite._addClass(data, 'how', true);
|
||||||
|
// add target state (without reflowing)
|
||||||
|
suite._addClass(data, 'to', false);
|
||||||
|
},
|
||||||
|
// requestAnimationFrame runLoop to collect computed values
|
||||||
|
startValueCollection: function(options) {
|
||||||
|
const promises = [];
|
||||||
|
const animations = document.getAnimations();
|
||||||
|
animations.forEach(a => {
|
||||||
|
promises.push(new Promise(resolve => {
|
||||||
|
const listener = (event) => {
|
||||||
|
let found = false;
|
||||||
|
if (event.pseudoElement != '') {
|
||||||
|
if (event.pseudoElement == a.effect.pseudoElement) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
} else if (!a.effect.pseudoElement) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
a.effect.target.removeEventListener(
|
||||||
|
'transitionend', listener);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
a.effect.target.addEventListener('transitionend', listener);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
options.allTransitionsCompleted();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sample at these values expressed as relative progress.
|
||||||
|
// The last sample being the end of the transition ensure that the
|
||||||
|
// completion events are received in short order.
|
||||||
|
const sample_at = [0.2, 0.4, 0.6, 0.8, 1.0];
|
||||||
|
sample_at.forEach(at => {
|
||||||
|
const time = options.duration * at;
|
||||||
|
animations.forEach(a => { a.currentTime = time; });
|
||||||
|
// Collect current style for test's elements.
|
||||||
|
options.tests.forEach(function(data) {
|
||||||
|
if (!data.property) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
['transition', 'container', 'pseudo'].forEach(function(elem) {
|
||||||
|
var pseudo = null;
|
||||||
|
if (!data[elem] || (elem === 'pseudo' && !data.pseudo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var current = data[elem].computedStyle(data.property);
|
||||||
|
var values = data[elem].values;
|
||||||
|
var length = values.length;
|
||||||
|
if (!length || values[length - 1] !== current) {
|
||||||
|
values.push(current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// stop requestAnimationFrame runLoop collecting computed values
|
||||||
|
stopValueCollection: function(options) {
|
||||||
|
options._collectValues = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// generate test.step function asserting collected events match expected
|
||||||
|
assertExpectedEventsFunc: function(data, elem, expected) {
|
||||||
|
return function() {
|
||||||
|
var _result = data[elem].events.sort().join(" ");
|
||||||
|
var _expected = typeof expected === 'string' ? expected : expected.sort().join(" ");
|
||||||
|
assert_equals(_result, _expected, "Expected TransitionEnd events triggered on ." + elem);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// generate test.step function asserting collected values are neither initial nor target
|
||||||
|
assertIntermediateValuesFunc: function(data, elem) {
|
||||||
|
return function() {
|
||||||
|
// the first value (index: 0) is always going to be the initial value
|
||||||
|
// the last value is always going to be the target value
|
||||||
|
var values = data[elem].values;
|
||||||
|
if (data.flags.discrete) {
|
||||||
|
// a discrete value will just switch from one state to another without having passed intermediate states.
|
||||||
|
assert_equals(values[0], data[elem].from, "must be initial value while transitioning on ." + elem);
|
||||||
|
assert_equals(values[1], data[elem].to, "must be target value after transitioning on ." + elem);
|
||||||
|
assert_equals(values.length, 2, "discrete property only has 2 values ." + elem);
|
||||||
|
} else {
|
||||||
|
assert_not_equals(values[1], data[elem].from, "may not be initial value while transitioning on ." + elem);
|
||||||
|
assert_not_equals(values[1], data[elem].to, "may not be target value while transitioning on ." + elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: first value must be initial, last value must be target
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(window);
|
|
@ -0,0 +1,411 @@
|
||||||
|
(function(root){
|
||||||
|
|
||||||
|
/*
|
||||||
|
* General Value Types definition
|
||||||
|
* they return an object of arrays of type { <name>: [<start-value>, <end-value>], ... }
|
||||||
|
*/
|
||||||
|
var values = {
|
||||||
|
'length' : function() {
|
||||||
|
// http://www.w3.org/TR/css3-values/#lengths
|
||||||
|
return {
|
||||||
|
// CSS Values and Module Level 3
|
||||||
|
// ch: ['1ch', '10ch'],
|
||||||
|
// rem: ['1rem', '10rem'],
|
||||||
|
// vw: ['1vw', '10vw'],
|
||||||
|
// vh: ['1vh', '10vh'],
|
||||||
|
// vmin: ['1vmin', '10vmin'],
|
||||||
|
// vmax: ['1vmax', '10vmax'],
|
||||||
|
// CSS Values and Module Level 2
|
||||||
|
pt: ['1pt', '10pt'],
|
||||||
|
pc: ['1pc', '10pc'],
|
||||||
|
px: ['1px', '10px'],
|
||||||
|
// CSS Values and Module Level 1
|
||||||
|
em: ['1em', '10em'],
|
||||||
|
ex: ['1ex', '10ex'],
|
||||||
|
mm: ['1mm', '10mm'],
|
||||||
|
cm: ['1cm', '10cm'],
|
||||||
|
'in': ['1in', '10in']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'length-em': function() {
|
||||||
|
return {
|
||||||
|
em: ['1.1em', '1.5em']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'percentage': function() {
|
||||||
|
// http://www.w3.org/TR/css3-values/#percentages
|
||||||
|
return {
|
||||||
|
'%': ['33%', '80%']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'color': function() {
|
||||||
|
// http://www.w3.org/TR/css3-values/#colors
|
||||||
|
// http://www.w3.org/TR/css3-color/
|
||||||
|
return {
|
||||||
|
rgba: ['rgba(100,100,100,1)', 'rgba(10,10,10,0.4)']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'rectangle': function() {
|
||||||
|
// http://www.w3.org/TR/CSS2/visufx.html#value-def-shape
|
||||||
|
return {
|
||||||
|
rectangle: ['rect(10px,10px,10px,10px)', 'rect(15px,15px,5px,5px)']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'font-weight': function() {
|
||||||
|
// http://www.w3.org/TR/css3-fonts/#font-weight-prop
|
||||||
|
return {
|
||||||
|
keyword: ["normal", "bold"],
|
||||||
|
numeric: ["100", "900"]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'number': function() {
|
||||||
|
// http://www.w3.org/TR/css3-values/#number
|
||||||
|
return {
|
||||||
|
integer: ["1", "10"],
|
||||||
|
decimal: ["1.1", "9.55"]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'number[0,1]': function() {
|
||||||
|
// http://www.w3.org/TR/css3-values/#number
|
||||||
|
// applies to [0,1]-ranged properties like opacity
|
||||||
|
return {
|
||||||
|
"zero-to-one": ["0.2", "0.9"]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'integer': function() {
|
||||||
|
// http://www.w3.org/TR/css3-values/#integer
|
||||||
|
return {
|
||||||
|
integer: ["1", "10"]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'shadow': function() {
|
||||||
|
// http://www.w3.org/TR/css-text-decor-3/#text-shadow-property
|
||||||
|
return {
|
||||||
|
shadow: ['rgba(0,0,0,0.1) 5px 6px 7px', 'rgba(10,10,10,0.9) 5px 6px 7px']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'visibility': function() {
|
||||||
|
// http://www.w3.org/TR/CSS2/visufx.html#visibility
|
||||||
|
return {
|
||||||
|
keyword: ['visible', 'hidden', {discrete: true}]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// types reqired for non-specified properties
|
||||||
|
'border-radius': function() {
|
||||||
|
return {
|
||||||
|
px: ['1px', '10px'],
|
||||||
|
"px-px": ['1px 3px', '10px 13px']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'image' : function() {
|
||||||
|
var prefix = getValueVendorPrefix('background-image', 'linear-gradient(top, hsl(0, 80%, 70%), #bada55)');
|
||||||
|
return {
|
||||||
|
// Chrome implements this
|
||||||
|
url: ['url(support/one.gif)', 'url(support/two.gif)'],
|
||||||
|
data: ['url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=)', 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==)'],
|
||||||
|
// A hunch, as from the spec:
|
||||||
|
// http://www.w3.org/TR/css3-transitions/#animatable-types
|
||||||
|
// gradient: interpolated via the positions and colors of each stop. They must have the same type (radial or linear) and same number of stops in order to be animated. Note: [CSS3-IMAGES] may extend this definition.
|
||||||
|
gradient: [prefix + 'linear-gradient(top, hsl(0, 80%, 70%), #bada55)', prefix + 'linear-gradient(top, #bada55, hsl(0, 80%, 70%))']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'background-size': function() {
|
||||||
|
return {
|
||||||
|
keyword: ['cover', 'contain']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'box-shadow': function() {
|
||||||
|
// http://www.w3.org/TR/css3-background/#ltshadowgt
|
||||||
|
return {
|
||||||
|
shadow: ['60px -16px teal', '60px -16px red']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'vertical': function() {
|
||||||
|
return {
|
||||||
|
keyword: ['top', 'bottom']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'horizontal': function() {
|
||||||
|
return {
|
||||||
|
keyword: ['left', 'right']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'font-stretch': function() {
|
||||||
|
return {
|
||||||
|
keyword: ['condensed', 'expanded']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'transform': function() {
|
||||||
|
return {
|
||||||
|
rotate: ['rotate(10deg)', 'rotate(20deg)']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'position': function() {
|
||||||
|
return {
|
||||||
|
'static to absolute': ['static', 'absolute', {discrete: true}],
|
||||||
|
'relative to absolute': ['relative', 'absolute', {discrete: true}],
|
||||||
|
'absolute to fixed': ['absolute', 'fixed', {discrete: true}]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'display': function() {
|
||||||
|
return {
|
||||||
|
'static to absolute': ['none', 'block', {discrete: true}],
|
||||||
|
'block to inline-block': ['block', 'inline-block', {discrete: true}]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'object-view-box': function() {
|
||||||
|
return {
|
||||||
|
inset: ['inset(10% 10% 20% 20%)', 'inset(20% 20% 30% 30%)'],
|
||||||
|
rect: ['rect(10px 20px 30px 40px)', 'rect(20px 30px 40px 50px)'],
|
||||||
|
xywh: ['xywh(10px 20px 30px 40px)', 'xywh(20px 30px 40px 50px)'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Property to Type table
|
||||||
|
* (as stated in specification)
|
||||||
|
*/
|
||||||
|
var properties = {
|
||||||
|
'background-color': ['color'],
|
||||||
|
'background-position-x': ['length', 'percentage'],
|
||||||
|
|
||||||
|
'border-top-width': ['length'],
|
||||||
|
'border-right-width': ['length'],
|
||||||
|
'border-bottom-width': ['length'],
|
||||||
|
'border-left-width': ['length'],
|
||||||
|
|
||||||
|
'border-top-color': ['color'],
|
||||||
|
'border-right-color': ['color'],
|
||||||
|
'border-bottom-color': ['color'],
|
||||||
|
'border-left-color': ['color'],
|
||||||
|
|
||||||
|
'padding-bottom': ['length'],
|
||||||
|
'padding-left': ['length'],
|
||||||
|
'padding-right': ['length'],
|
||||||
|
'padding-top': ['length'],
|
||||||
|
|
||||||
|
'margin-bottom': ['length'],
|
||||||
|
'margin-left': ['length'],
|
||||||
|
'margin-right': ['length'],
|
||||||
|
'margin-top': ['length'],
|
||||||
|
|
||||||
|
'height': ['length', 'percentage'],
|
||||||
|
'width': ['length', 'percentage'],
|
||||||
|
'min-height': ['length', 'percentage'],
|
||||||
|
'min-width': ['length', 'percentage'],
|
||||||
|
'max-height': ['length', 'percentage'],
|
||||||
|
'max-width': ['length', 'percentage'],
|
||||||
|
|
||||||
|
'top': ['length', 'percentage'],
|
||||||
|
'right': ['length', 'percentage'],
|
||||||
|
'bottom': ['length', 'percentage'],
|
||||||
|
'left': ['length', 'percentage'],
|
||||||
|
|
||||||
|
'color': ['color'],
|
||||||
|
'font-size': ['length', 'percentage'],
|
||||||
|
'font-weight': ['font-weight'],
|
||||||
|
'line-height': ['number', 'length', 'percentage'],
|
||||||
|
'letter-spacing': ['length'],
|
||||||
|
// Note: percentage is Level3 and not implemented anywhere yet
|
||||||
|
// https://drafts.csswg.org/css3-text/#word-spacing
|
||||||
|
'word-spacing': ['length', 'percentage'],
|
||||||
|
'text-indent': ['length', 'percentage'],
|
||||||
|
'text-shadow': ['shadow'],
|
||||||
|
|
||||||
|
'outline-color': ['color'],
|
||||||
|
// outline-offset <integer> used to be an error in the spec
|
||||||
|
'outline-offset': ['length'],
|
||||||
|
'outline-width': ['length'],
|
||||||
|
|
||||||
|
'clip': ['rectangle'],
|
||||||
|
|
||||||
|
'vertical-align': ['length', 'percentage'],
|
||||||
|
'opacity': ['number[0,1]'],
|
||||||
|
'visibility': ['visibility'],
|
||||||
|
'z-index': ['integer']
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Property to Type table
|
||||||
|
* (missing value-types of specified properties)
|
||||||
|
*/
|
||||||
|
var missing_properties = {
|
||||||
|
'margin-bottom': ['percentage'],
|
||||||
|
'margin-left': ['percentage'],
|
||||||
|
'margin-right': ['percentage'],
|
||||||
|
'margin-top': ['percentage'],
|
||||||
|
'padding-bottom': ['percentage'],
|
||||||
|
'padding-left': ['percentage'],
|
||||||
|
'padding-right': ['percentage'],
|
||||||
|
'padding-top': ['percentage'],
|
||||||
|
'vertical-align': ['vertical']
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Property to Type table
|
||||||
|
* (properties that haven't been specified but implemented)
|
||||||
|
*/
|
||||||
|
var unspecified_properties = {
|
||||||
|
// http://oli.jp/2010/css-animatable-properties/
|
||||||
|
'border-top-left-radius': ['border-radius'],
|
||||||
|
'border-top-right-radius': ['border-radius'],
|
||||||
|
'border-bottom-left-radius': ['border-radius'],
|
||||||
|
'border-bottom-right-radius': ['border-radius'],
|
||||||
|
'background-image': ['image'],
|
||||||
|
'background-size': ['background-size'],
|
||||||
|
// https://drafts.csswg.org/css3-background/#the-box-shadow
|
||||||
|
// Animatable: yes, except between inner and outer shadows (Transition to/from an absent shadow is a transition to/from ‘0 0 transparent’ or ‘0 0 transparent inset’, as appropriate.)
|
||||||
|
'box-shadow': ['box-shadow'],
|
||||||
|
'font-size-adjust': ['number'],
|
||||||
|
'font-stretch': ['font-stretch'],
|
||||||
|
'text-decoration-color': ['color'],
|
||||||
|
'column-count': ['integer'],
|
||||||
|
'column-gap': ['length'],
|
||||||
|
'column-rule-color': ['color'],
|
||||||
|
'column-rule-width': ['length'],
|
||||||
|
'column-width': ['length'],
|
||||||
|
'column-height': ['length'],
|
||||||
|
'transform': ['transform'],
|
||||||
|
'transform-origin': ['horizontal'],
|
||||||
|
'display': ['display'],
|
||||||
|
'position': ['position'],
|
||||||
|
'object-view-box': ['object-view-box']
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* additional styles required to actually render
|
||||||
|
* (different browsers expect different environment)
|
||||||
|
*/
|
||||||
|
var additional_styles = {
|
||||||
|
// all browsers
|
||||||
|
'border-top-width': {'border-top-style' : 'solid'},
|
||||||
|
'border-right-width': {'border-right-style' : 'solid'},
|
||||||
|
'border-bottom-width': {'border-bottom-style' : 'solid'},
|
||||||
|
'border-left-width': {'border-left-style' : 'solid'},
|
||||||
|
'top': {'position': 'absolute'},
|
||||||
|
'right': {'position': 'absolute'},
|
||||||
|
'bottom': {'position': 'absolute'},
|
||||||
|
'left': {'position': 'absolute'},
|
||||||
|
'z-index': {'position': 'absolute'},
|
||||||
|
'outline-offset': {'outline-style': 'solid'},
|
||||||
|
'outline-width': {'outline-style': 'solid'},
|
||||||
|
'word-spacing': {'width': '100px', 'height': '100px'},
|
||||||
|
// unspecified properties
|
||||||
|
'column-rule-width': {'column-rule-style': 'solid'},
|
||||||
|
'position': {'width': '50px', 'height': '50px', top: '10px', left: '50px'}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* additional styles required *on the parent* to actually render
|
||||||
|
* (different browsers expect different environment)
|
||||||
|
*/
|
||||||
|
var parent_styles = {
|
||||||
|
'border-top-width': {'border-top-style' : 'solid'},
|
||||||
|
'border-right-width': {'border-right-style' : 'solid'},
|
||||||
|
'border-bottom-width': {'border-bottom-style' : 'solid'},
|
||||||
|
'border-left-width': {'border-left-style' : 'solid'},
|
||||||
|
'height': {'width': '100px', 'height': '100px'},
|
||||||
|
'min-height': {'width': '100px', 'height': '100px'},
|
||||||
|
'max-height': {'width': '100px', 'height': '100px'},
|
||||||
|
'width': {'width': '100px', 'height': '100px'},
|
||||||
|
'min-width': {'width': '100px', 'height': '100px'},
|
||||||
|
'max-width': {'width': '100px', 'height': '100px'},
|
||||||
|
// unspecified properties
|
||||||
|
'position': {'position': 'relative', 'width': '100px', 'height': '100px'},
|
||||||
|
// inheritance tests
|
||||||
|
'top': {'width': '100px', 'height': '100px', 'position': 'relative'},
|
||||||
|
'right': {'width': '100px', 'height': '100px', 'position': 'relative'},
|
||||||
|
'bottom': {'width': '100px', 'height': '100px', 'position': 'relative'},
|
||||||
|
'left': {'width': '100px', 'height': '100px', 'position': 'relative'}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function assemble(props) {
|
||||||
|
var tests = [];
|
||||||
|
|
||||||
|
// assemble tests
|
||||||
|
for (var property in props) {
|
||||||
|
props[property].forEach(function(type) {
|
||||||
|
var _values = values[type](property);
|
||||||
|
Object.keys(_values).forEach(function(unit) {
|
||||||
|
var data = {
|
||||||
|
name: property + ' ' + type + '(' + unit + ')',
|
||||||
|
property: property,
|
||||||
|
valueType : type,
|
||||||
|
unit : unit,
|
||||||
|
parentStyle: extend({}, parent_styles[property] || {}),
|
||||||
|
from: extend({}, additional_styles[property] || {}),
|
||||||
|
to: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
data.from[property] = _values[unit][0];
|
||||||
|
data.to[property] = _values[unit][1];
|
||||||
|
data.flags = _values[unit][2] || {};
|
||||||
|
|
||||||
|
tests.push(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.getPropertyTests = function() {
|
||||||
|
return assemble(properties);
|
||||||
|
};
|
||||||
|
|
||||||
|
root.getMissingPropertyTests = function() {
|
||||||
|
return assemble(missing_properties);
|
||||||
|
};
|
||||||
|
|
||||||
|
root.getUnspecifiedPropertyTests = function() {
|
||||||
|
return assemble(unspecified_properties);
|
||||||
|
};
|
||||||
|
|
||||||
|
root.getFontSizeRelativePropertyTests = function() {
|
||||||
|
var accepted = {};
|
||||||
|
|
||||||
|
for (var key in properties) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(properties, key) || key === "font-size") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties[key].indexOf('length') > -1) {
|
||||||
|
accepted[key] = ['length-em'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assemble(accepted);
|
||||||
|
};
|
||||||
|
|
||||||
|
root.filterPropertyTests = function(tests, names) {
|
||||||
|
var allowed = {};
|
||||||
|
var accepted = [];
|
||||||
|
|
||||||
|
if (typeof names === "string") {
|
||||||
|
names = [names];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(names instanceof RegExp)) {
|
||||||
|
names.forEach(function(name) {
|
||||||
|
allowed[name] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.forEach(function(test) {
|
||||||
|
if (names instanceof RegExp) {
|
||||||
|
if (!test.name.match(names)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (!allowed[test.name]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accepted.push(test);
|
||||||
|
});
|
||||||
|
|
||||||
|
return accepted;
|
||||||
|
};
|
||||||
|
|
||||||
|
})(window);
|
|
@ -0,0 +1,156 @@
|
||||||
|
(function(root){
|
||||||
|
'use strict';
|
||||||
|
// testharness doesn't know about async test queues,
|
||||||
|
// so this wrapper takes care of that
|
||||||
|
|
||||||
|
/* USAGE:
|
||||||
|
runParallelAsyncHarness({
|
||||||
|
// list of data to test, must be array of objects.
|
||||||
|
// each object must contain a "name" property to describe the test
|
||||||
|
// besides name, the object can contain whatever data you need
|
||||||
|
tests: [
|
||||||
|
{name: "name of test 1", custom: "data"},
|
||||||
|
{name: "name of test 2", custom: "data"},
|
||||||
|
// ...
|
||||||
|
],
|
||||||
|
|
||||||
|
// number of tests (tests, not test-cases!) to run concurrently
|
||||||
|
testsPerSlice: 100,
|
||||||
|
|
||||||
|
// time in milliseconds a test-run takes
|
||||||
|
duration: 1000,
|
||||||
|
|
||||||
|
// test-cases to run for for the test - there must be at least one
|
||||||
|
// each case creates its separate async_test() instance
|
||||||
|
cases: {
|
||||||
|
// test case named "test1"
|
||||||
|
test1: {
|
||||||
|
// run as a async_test.step() this callback contains your primary assertions
|
||||||
|
start: function(testCaseKey, data, options){},
|
||||||
|
// run as a async_test.step() this callback contains assertions to be run
|
||||||
|
// when the test ended, immediately before teardown
|
||||||
|
done: function(testCaseKey, data, options){}
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// all callbacks are optional:
|
||||||
|
|
||||||
|
// invoked for individual test before it starts so you can setup the environment
|
||||||
|
// like DOM, CSS, adding event listeners and such
|
||||||
|
setup: function(data, options){},
|
||||||
|
|
||||||
|
// invoked after a test ended, so you can clean up the environment
|
||||||
|
// like DOM, CSS, removing event listeners and such
|
||||||
|
teardown: function(data, options){},
|
||||||
|
|
||||||
|
// invoked before a batch of tests ("slice") are run concurrently
|
||||||
|
// tests is an array of test data objects
|
||||||
|
sliceStart: function(options, tests)
|
||||||
|
|
||||||
|
// invoked after a batch of tests ("slice") were run concurrently
|
||||||
|
// tests is an array of test data objects
|
||||||
|
sliceDone: function(options, tests)
|
||||||
|
|
||||||
|
// invoked once all tests are done
|
||||||
|
done: function(options){}
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
function DomContentLoadedPromise() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
root.runParallelAsyncHarness = async function(options) {
|
||||||
|
const ready = DomContentLoadedPromise();
|
||||||
|
|
||||||
|
if (!options.cases) {
|
||||||
|
throw new Error("Options don't contain test cases!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var noop = function(){};
|
||||||
|
|
||||||
|
// names of individual tests
|
||||||
|
var cases = Object.keys(options.cases);
|
||||||
|
|
||||||
|
// run tests in a batch of slices
|
||||||
|
// primarily not to overload weak devices (tablets, phones, …)
|
||||||
|
// with too many tests running simultaneously
|
||||||
|
var iteration = -1;
|
||||||
|
var testPerSlice = options.testsPerSlice || 100;
|
||||||
|
var slices = Math.ceil(options.tests.length / testPerSlice);
|
||||||
|
|
||||||
|
// initialize all async test cases
|
||||||
|
// Note: satisfying testharness.js needs to know all async tests before load-event
|
||||||
|
options.tests.forEach(function(data, index) {
|
||||||
|
data.cases = {};
|
||||||
|
cases.forEach(function(name) {
|
||||||
|
data.cases[name] = async_test(data.name + " / " + name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function runLoop() {
|
||||||
|
iteration++;
|
||||||
|
if (iteration >= slices) {
|
||||||
|
// no more slice, we're done
|
||||||
|
(options.done || noop)(options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab a slice of testss and initialize them
|
||||||
|
var offset = iteration * testPerSlice;
|
||||||
|
var tests = options.tests.slice(offset, offset + testPerSlice);
|
||||||
|
tests.forEach(function(data) {
|
||||||
|
(options.setup || noop)(data, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
// kick off the current slice of tests
|
||||||
|
(options.sliceStart || noop)(options, tests);
|
||||||
|
|
||||||
|
// perform individual "start" test-case
|
||||||
|
tests.forEach(function(data) {
|
||||||
|
cases.forEach(function(name) {
|
||||||
|
data.cases[name].step(function() {
|
||||||
|
(options.cases[name].start || noop)(data.cases[name], data, options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start sampling.
|
||||||
|
(options.transitionsStarted || noop)(options, tests);
|
||||||
|
|
||||||
|
// conclude slice (possibly abort)
|
||||||
|
var concludeSlice = function() {
|
||||||
|
tests.forEach(function(data) {
|
||||||
|
// perform individual "done" test-case
|
||||||
|
cases.forEach(function(name) {
|
||||||
|
data.cases[name].step(function() {
|
||||||
|
(options.cases[name].done || noop)(data.cases[name], data, options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// clean up after individual test
|
||||||
|
(options.teardown || noop)(data, options);
|
||||||
|
// tell harness we're done with individual test-cases
|
||||||
|
cases.forEach(function(name) {
|
||||||
|
data.cases[name].done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// finish the test for current slice of tests
|
||||||
|
(options.sliceDone || noop)(options, tests);
|
||||||
|
|
||||||
|
requestAnimationFrame(runLoop);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.allTransitionsCompleted = concludeSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow DOMContentLoaded before actually doing something
|
||||||
|
ready.then(runLoop);
|
||||||
|
};
|
||||||
|
|
||||||
|
})(window);
|
|
@ -0,0 +1,86 @@
|
||||||
|
//
|
||||||
|
// Vendor-Prefix Helper Functions For Testing CSS
|
||||||
|
//
|
||||||
|
|
||||||
|
(function(root) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var prefixCache = {};
|
||||||
|
|
||||||
|
// convert "foo-bar" to "fooBar"
|
||||||
|
function camelCase(str) {
|
||||||
|
return str.replace(/\-(\w)/g, function(match, letter){
|
||||||
|
return letter.toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// vendor-prefix a css property
|
||||||
|
root.addVendorPrefix = function (name) {
|
||||||
|
var prefix = getVendorPrefix(name);
|
||||||
|
if (prefix === false) {
|
||||||
|
// property unknown to browser
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix + name;
|
||||||
|
};
|
||||||
|
|
||||||
|
// vendor-prefix a css property value
|
||||||
|
root.addValueVendorPrefix = function (property, value) {
|
||||||
|
var prefix = getValueVendorPrefix(property, value);
|
||||||
|
if (prefix === false) {
|
||||||
|
// property unknown to browser
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix + value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// identify vendor-prefix for css property
|
||||||
|
root.getVendorPrefix = function(name) {
|
||||||
|
if (prefixCache[name] !== undefined) {
|
||||||
|
return prefixCache[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
var elem = document.createElement("div");
|
||||||
|
name = camelCase(name);
|
||||||
|
|
||||||
|
if (name in elem.style) {
|
||||||
|
return prefixCache[name] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefixes = ["Webkit", "Moz", "O", "ms"];
|
||||||
|
var styles = ["-webkit-", "-moz-", "-o-", "-ms-"];
|
||||||
|
var _name = name.substring(0, 1).toUpperCase() + name.substring(1);
|
||||||
|
|
||||||
|
for (var i = 0, length = prefixes.length; i < length; i++) {
|
||||||
|
if (prefixes[i] + _name in elem.style) {
|
||||||
|
return prefixCache[name] = styles[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefixCache[name] = name in elem.style ? "" : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// identify vendor-prefix for css property value
|
||||||
|
root.getValueVendorPrefix = function(property, value) {
|
||||||
|
var elem = document.createElement("div");
|
||||||
|
// note: webkit needs the element to be attached to the dom
|
||||||
|
document.body.appendChild(elem);
|
||||||
|
var styles = ["-webkit-", "-moz-", "-o-", "-ms-", ""];
|
||||||
|
var _property = getVendorPrefix(property) + property;
|
||||||
|
for (var i=0, length = styles.length; i < length; i++) {
|
||||||
|
var _value = styles[i] + value;
|
||||||
|
elem.setAttribute('style', _property + ": " + _value);
|
||||||
|
var _computed = computedStyle(elem, _property);
|
||||||
|
if (_computed && _computed !== 'none') {
|
||||||
|
document.body.removeChild(elem);
|
||||||
|
return styles[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.body.removeChild(elem);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
})(window);
|
Loading…
Add table
Add a link
Reference in a new issue