From ee87a503c201a9ec671f049f82fc826c083d27ba Mon Sep 17 00:00:00 2001 From: "jonlee@apple.com" Date: Thu, 11 Feb 2016 07:42:00 +0000 Subject: [PATCH] Add new benchmark tests. https://bugs.webkit.org/show_bug.cgi?id=154063 Provisionally reviewed by Said Abou-Hallawa. Add tests for get/put image data, filters, opacity, and css transforms. * Animometer/resources/runner/benchmark-runner.js: (_runBenchmarkAndRecordResults): Update the body background color to match that of the stage. (this._runNextIteration): Clear the background color style for the results page. * Animometer/resources/runner/tests.js: * Animometer/tests/master/focus.html: Added. * Animometer/tests/master/image-data.html: Added. * Animometer/tests/master/multiply.html: Added. * Animometer/tests/master/resources/focus.js: Added. * Animometer/tests/master/resources/image-data.js: Added. * Animometer/tests/master/resources/multiply.js: Added. * Animometer/tests/master/resources/stage.css: Move common styles out. * Animometer/tests/resources/main.js: Update Stage.randomBool to use Math.random. Add Stage.randomSign for randomly setting a direction. Add the notion of the current timestamp of the test to Benchmark, since some animations cycle through colors and rely on an incremental counter like the time. git-svn-id: https://svn.webkit.org/repository/webkit/trunk@196415 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- .../Animometer/resources/extensions.js | 20 ++- .../resources/runner/benchmark-runner.js | 5 +- .../Animometer/resources/runner/tests.js | 12 ++ .../Animometer/tests/master/focus.html | 42 ++++++ .../Animometer/tests/master/image-data.html | 27 ++++ .../Animometer/tests/master/multiply.html | 60 ++++++++ .../Animometer/tests/master/resources/focus.js | 151 +++++++++++++++++++ .../tests/master/resources/image-data.js | 164 +++++++++++++++++++++ .../Animometer/tests/master/resources/multiply.js | 124 ++++++++++++++++ .../Animometer/tests/master/resources/stage.css | 16 +- .../Animometer/tests/resources/main.js | 19 ++- PerformanceTests/ChangeLog | 34 ++++- 12 files changed, 660 insertions(+), 14 deletions(-) create mode 100644 PerformanceTests/Animometer/tests/master/focus.html create mode 100644 PerformanceTests/Animometer/tests/master/image-data.html create mode 100644 PerformanceTests/Animometer/tests/master/multiply.html create mode 100644 PerformanceTests/Animometer/tests/master/resources/focus.js create mode 100644 PerformanceTests/Animometer/tests/master/resources/image-data.js create mode 100644 PerformanceTests/Animometer/tests/master/resources/multiply.js diff --git a/PerformanceTests/Animometer/resources/extensions.js b/PerformanceTests/Animometer/resources/extensions.js index 8ec3c80..11c6bff 100644 --- a/PerformanceTests/Animometer/resources/extensions.js +++ b/PerformanceTests/Animometer/resources/extensions.js @@ -83,21 +83,21 @@ Utilities = parentElement.appendChild(element); return element; }, - + browserPrefix: function() { // Get the HTML element's CSSStyleDeclaration var styles = window.getComputedStyle(document.documentElement, ''); - + // Convert the styles list to an array var stylesArray = Array.prototype.slice.call(styles); - + // Concatenate all the styles in one big string var stylesString = stylesArray.join(''); // Search the styles string for a known prefix type, settle on Opera if none is found. var prefixes = stylesString.match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']); - + // prefixes has two elements; e.g. for webkit it has ['-webkit-', 'webkit']; var prefix = prefixes[1]; @@ -112,10 +112,20 @@ Utilities = js: prefix[0].toUpperCase() + prefix.substr(1) }; }, - + setElementPrefixedProperty: function(element, property, value) { element.style[property] = element.style[this.browserPrefix().js + property[0].toUpperCase() + property.substr(1)] = value; + }, + + progressValue: function(value, min, max) + { + return (value - min) / (max - min); + }, + + lerp: function(value, min, max) + { + return min + (max - min) * value; } }; diff --git a/PerformanceTests/Animometer/resources/runner/benchmark-runner.js b/PerformanceTests/Animometer/resources/runner/benchmark-runner.js index b6a82be..a2ac7b2 100644 --- a/PerformanceTests/Animometer/resources/runner/benchmark-runner.js +++ b/PerformanceTests/Animometer/resources/runner/benchmark-runner.js @@ -94,6 +94,7 @@ BenchmarkRunner = Utilities.createClass( Utilities.extendObject(options, contentWindow.Utilities.parseParameters()); var benchmark = new contentWindow.benchmarkClass(options); + document.body.style.backgroundColor = benchmark.backgroundColor(); benchmark.run().then(function(results) { var suiteResults = self._suitesResults[suite.name] || {}; suiteResults[test.name] = results; @@ -153,8 +154,10 @@ BenchmarkRunner = Utilities.createClass( currentIteration++; if (currentIteration < self._client.iterationCount) self.runAllSteps(); - else if (this._client && this._client.didFinishLastIteration) + else if (this._client && this._client.didFinishLastIteration) { + document.body.style.backgroundColor = ""; self._client.didFinishLastIteration(); + } } if (this._client && this._client.willStartFirstIteration) diff --git a/PerformanceTests/Animometer/resources/runner/tests.js b/PerformanceTests/Animometer/resources/runner/tests.js index 1040c0a..cc85b9a 100644 --- a/PerformanceTests/Animometer/resources/runner/tests.js +++ b/PerformanceTests/Animometer/resources/runner/tests.js @@ -13,6 +13,10 @@ var Suites = []; Suites.push(new Suite("Animometer", [ { + url: "master/multiply.html", + name: "Multiply" + }, + { url: "master/canvas-stage.html?pathType=arcs", name: "Canvas arcs" }, @@ -25,6 +29,14 @@ Suites.push(new Suite("Animometer", name: "Canvas line segments" }, { + url: "master/focus.html", + name: "Focus" + }, + { + url: "master/image-data.html", + name: "Images" + }, + { url: "master/particles.html", name: "DOM particles, SVG masks" }, diff --git a/PerformanceTests/Animometer/tests/master/focus.html b/PerformanceTests/Animometer/tests/master/focus.html new file mode 100644 index 0000000..5263473 --- /dev/null +++ b/PerformanceTests/Animometer/tests/master/focus.html @@ -0,0 +1,42 @@ + + + + + + + +
+
focus
+
+ + + + + + + + + diff --git a/PerformanceTests/Animometer/tests/master/image-data.html b/PerformanceTests/Animometer/tests/master/image-data.html new file mode 100644 index 0000000..bc92e65 --- /dev/null +++ b/PerformanceTests/Animometer/tests/master/image-data.html @@ -0,0 +1,27 @@ + + + + + + + +
+ + + + + + + + + diff --git a/PerformanceTests/Animometer/tests/master/multiply.html b/PerformanceTests/Animometer/tests/master/multiply.html new file mode 100644 index 0000000..be1758d --- /dev/null +++ b/PerformanceTests/Animometer/tests/master/multiply.html @@ -0,0 +1,60 @@ + + + + + + + +
+ multiply +
+ + + + + + + + + diff --git a/PerformanceTests/Animometer/tests/master/resources/focus.js b/PerformanceTests/Animometer/tests/master/resources/focus.js new file mode 100644 index 0000000..482e7bc --- /dev/null +++ b/PerformanceTests/Animometer/tests/master/resources/focus.js @@ -0,0 +1,151 @@ +(function() { + +var maxVerticalOffset = 50; +var radius = 20; +var sizeVariance = 80; +var travelDistance = 70; + +var minObjectDepth = 0.2; +var maxObjectDepth = 1.0; + +var opacityMultiplier = 40; + +var FocusElement = Utilities.createClass( + function(stage) + { + var topOffset = maxVerticalOffset * Stage.randomSign(); + var top = Stage.random(0, stage.size.height - 2 * radius - sizeVariance); + var left = Stage.random(0, stage.size.width - 2 * radius - sizeVariance); + + // size and blurring are a function of depth + this._depth = Utilities.lerp(1 - Math.pow(Math.random(), 2), minObjectDepth, maxObjectDepth); + var distance = Utilities.lerp(this._depth, 1, sizeVariance); + var size = 2 * radius + sizeVariance - distance; + + this.element = document.createElement('div'); + this.element.style.width = size + "px"; + this.element.style.height = size + "px"; + this.element.style.top = top + "px"; + this.element.style.left = left + "px"; + this.element.style.zIndex = Math.round((1 - this._depth) * 10); + + Utilities.setElementPrefixedProperty(this.element, "filter", "blur(" + stage.getBlurValue(this._depth) + "px) opacity(" + stage.getOpacityValue(this._depth) + "%)"); + + var depthMultiplier = Utilities.lerp(1 - this._depth, 0.8, 1); + this._sinMultiplier = Math.random() * Stage.randomSign() * depthMultiplier; + this._cosMultiplier = Math.random() * Stage.randomSign() * depthMultiplier; + }, { + + animate: function(stage, sinTime, cosTime) + { + var top = sinTime * this._sinMultiplier * travelDistance; + var left = cosTime * this._cosMultiplier * travelDistance; + + Utilities.setElementPrefixedProperty(this.element, "filter", "blur(" + stage.getBlurValue(this._depth) + "px) opacity(" + stage.getOpacityValue(this._depth) + "%)"); + this.element.style.transform = "translateX(" + left + "%) translateY(" + top + "%)"; + } +}); + +var FocusStage = Utilities.createSubclass(Stage, + function() + { + Stage.call(this); + }, { + + movementDuration: 2500, + focusDuration: 1000, + + centerObjectDepth: 0.0, + + minBlurValue: 1.5, + maxBlurValue: 15, + maxCenterObjectBlurValue: 5, + + initialize: function(benchmark, options) + { + Stage.prototype.initialize.call(this, benchmark, options); + + this._testElements = []; + this._focalPoint = 0.5; + + this._centerElement = document.getElementById("center-text"); + this._centerElement.style.width = (radius * 5) + 'px'; + this._centerElement.style.height = (radius * 5) + 'px'; + this._centerElement.style.zIndex = Math.round(10 * this.centerObjectDepth); + + var blur = this.getBlurValue(this.centerObjectDepth); + Utilities.setElementPrefixedProperty(this._centerElement, "filter", "blur(" + blur + "px)"); + }, + + complexity: function() + { + return 1 + this._testElements.length; + }, + + tune: function(count) + { + if (count == 0) + return; + + if (count > 0) { + for (var i = 0; i < count; ++i) { + var obj = new FocusElement(this); + this._testElements.push(obj); + this.element.appendChild(obj.element); + } + return; + } + + while (count < 0) { + var obj = this._testElements.shift(); + if (!obj) + return; + + this.element.removeChild(obj.element); + count++; + } + }, + + animate: function() + { + var time = this._benchmark.timestamp; + var sinTime = Math.sin(time / this.movementDuration); + var cosTime = Math.cos(time / this.movementDuration); + + var focusProgress = Utilities.progressValue(Math.sin(time / this.focusDuration), -1, 1); + this._focalPoint = focusProgress; + + // update center element before loop + Utilities.setElementPrefixedProperty(this._centerElement, "filter", "blur(" + this.getBlurValue(this.centerObjectDepth, true) + "px)"); + + this._testElements.forEach(function(element) { + element.animate(this, sinTime, cosTime); + }, this); + }, + + getBlurValue: function(depth, isCenter) + { + var value = Math.abs(depth - this._focalPoint); + + if (isCenter) + return this.maxCenterObjectBlurValue * value; + + return Utilities.lerp(value, this.minBlurValue, this.maxBlurValue); + }, + + getOpacityValue: function(depth) + { + return opacityMultiplier * (1 - Math.abs(depth - this._focalPoint)); + }, +}); + +var FocusBenchmark = Utilities.createSubclass(Benchmark, + function(options) + { + Benchmark.call(this, new FocusStage(), options); + } +); + +window.benchmarkClass = FocusBenchmark; + +}()); diff --git a/PerformanceTests/Animometer/tests/master/resources/image-data.js b/PerformanceTests/Animometer/tests/master/resources/image-data.js new file mode 100644 index 0000000..23ae922 --- /dev/null +++ b/PerformanceTests/Animometer/tests/master/resources/image-data.js @@ -0,0 +1,164 @@ +(function() { + +var ImageDataStage = Utilities.createSubclass(Stage, + function() { + Stage.call(this); + + this.testElements = []; + this._offsetIndex = 0; + }, { + + testImageSrc: './../resources/yin-yang.png', + + initialize: function(benchmark) + { + Stage.prototype.initialize.call(this, benchmark); + + var waitForLoad = new SimplePromise; + var img = new Image; + + img.addEventListener('load', function onImageLoad(e) { + img.removeEventListener('load', onImageLoad); + waitForLoad.resolve(img); + }); + + img.src = this.testImageSrc; + + waitForLoad.then(function(img) { + this.testImage = img; + this.testImageWidth = img.naturalWidth; + this.testImageHeight = img.naturalHeight; + + this.diffuseXOffset = 4, + this.diffuseYOffset = this.testImageWidth * 4; + + benchmark.readyPromise.resolve(); + }.bind(this)); + }, + + tune: function(count) + { + if (count == 0) + return; + + if (count < 0) { + this._offsetIndex = Math.max(this._offsetIndex + count, 0); + for (var i = this._offsetIndex; i < this.testElements.length; ++i) + this.testElements[i].style.visibility = "hidden"; + return; + } + + this._offsetIndex = this._offsetIndex + count; + var index = Math.min(this._offsetIndex, this.testElements.length); + for (var i = 0; i < index; ++i) + this.testElements[i].style.visibility = ""; + if (this._offsetIndex <= this.testElements.length) + return; + + index = this._offsetIndex - this.testElements.length; + for (var i = 0; i < index; ++i) { + var element = this._createTestElement(); + this.testElements.push(element); + this.element.appendChild(element); + } + }, + + _createTestElement: function() { + var element = document.createElement('canvas'); + element.width = this.testImageWidth; + element.height = this.testImageHeight; + + var context = element.getContext("2d"); + + // Put draw image into the canvas + context.drawImage(this.testImage, 0, 0, this.testImageWidth, this.testImageHeight); + + // randomize location + var left = Stage.randomInt(0, this.size.width - this.testImageWidth); + var top = Stage.randomInt(0, this.size.height - this.testImageHeight); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = this.testImageWidth + 'px'; + element.style.height = this.testImageHeight + 'px'; + + return element; + }, + + animate: function(timeDelta) { + for (var i = 0; i < this._offsetIndex; ++i) { + var context = this.testElements[i].getContext("2d"); + + // Get image data + var imageData = context.getImageData(0, 0, this.testImageWidth, this.testImageHeight); + + var rgbaLen = 4, + didDraw = false, + neighborPixelIndex, + dataLen = imageData.data.length; + for (var j = 0; j < dataLen; j += rgbaLen) { + if (imageData.data[j + 3] === 0) + continue; + + // get random neighboring pixel color + neighborPixelIndex = this._getRandomNeighboringPixelIndex(j, dataLen); + + // Update the RGB data + imageData.data[j] = imageData.data[neighborPixelIndex]; + imageData.data[j + 1] = imageData.data[neighborPixelIndex + 1]; + imageData.data[j + 2] = imageData.data[neighborPixelIndex + 2]; + imageData.data[j + 3] = Math.max(imageData.data[neighborPixelIndex + 3] - j % 10, 0); + didDraw = true; + } + + // Put the image data back into the canvas + context.putImageData(imageData, 0, 0); + + // If it didn't draw restart + if (!didDraw) + context.drawImage(this.testImage, 0, 0, this.testImageWidth, this.testImageHeight) + } + }, + + _getRandomNeighboringPixelIndex: function(pixelIdx, pixelArrayLength) { + var xOffset = Stage.randomInt(-1, 1), + yOffset = Stage.randomInt(-1, 1), + resultPixelIdx = pixelIdx; + + // Add X to the result + resultPixelIdx += (this.diffuseXOffset * xOffset); + // Add Y to the result + resultPixelIdx += (this.diffuseYOffset * yOffset); + + // Don't fall off the end of the image + if (resultPixelIdx > pixelArrayLength) + resultPixelIdx = pixelIdx; + + // Don't fall off the beginning of the image + if (resultPixelIdx < 0) + resultPixelIdx = 0; + + return resultPixelIdx; + }, + + complexity: function() + { + return this._offsetIndex; + } +}); + +var ImageDataBenchmark = Utilities.createSubclass(Benchmark, + function(options) + { + Benchmark.call(this, new ImageDataStage(), options); + }, { + + waitUntilReady: function() { + this.readyPromise = new SimplePromise; + return this.readyPromise; + } +}); + +window.benchmarkClass = ImageDataBenchmark; + +}()); diff --git a/PerformanceTests/Animometer/tests/master/resources/multiply.js b/PerformanceTests/Animometer/tests/master/resources/multiply.js new file mode 100644 index 0000000..bae72ce --- /dev/null +++ b/PerformanceTests/Animometer/tests/master/resources/multiply.js @@ -0,0 +1,124 @@ +(function() { + +var MultiplyStage = Utilities.createSubclass(Stage, + function() + { + Stage.call(this); + this.tiles = []; + this._offsetIndex = 0; + }, { + + initialize: function(benchmark, options) + { + Stage.prototype.initialize.call(this, benchmark, options); + var tileSize = Math.round(this.size.height / 20); + + // Fill the scene with elements + var x = Math.round((this.size.width - tileSize) / 2); + var y = Math.round((this.size.height - tileSize) / 2); + var tileStride = tileSize; + var direction = 0; + var spiralCounter = 2; + var nextIndex = 1; + var maxSide = Math.floor(this.size.height / tileStride); + var centerSpiralCount = maxSide * maxSide; + for (var i = 0; i < centerSpiralCount; ++i) { + this._addTile(x, y, tileSize, i % 360); + + if (i == nextIndex) { + direction = (direction + 1) % 4; + spiralCounter++; + nextIndex += spiralCounter >> 1; + } + if (direction == 0) + x += tileStride; + else if (direction == 1) + y -= tileStride; + else if (direction == 2) + x -= tileStride; + else + y += tileStride; + } + + centerSpiralCount = maxSide * (maxSide + (maxSide & 1)); + for (var i = 0; i < centerSpiralCount; ++i) { + var sideX = x + Math.floor(Math.floor(i / maxSide) / 2) * tileStride; + var sideY = y - tileStride * (i % maxSide); + + if (Math.floor(i / maxSide) % 2 == 1) + sideX = this.width - sideX - tileSize + 1; + this._addTile(sideX, sideY, tileSize, (6 * i) % 360); + } + }, + + _addTile: function(x, y, tileSize, rotateDeg) + { + var tile = Utilities.createElement("div", { class: "div-" + Stage.randomInt(0,6) }, this.element); + var halfTileSize = tileSize / 2; + tile.style.left = x + 'px'; + tile.style.top = y + 'px'; + tile.style.width = tileSize + 'px'; + tile.style.height = tileSize + 'px'; + + var distance = 1.5 / tileSize * this.size.multiply(0.5).subtract(new Point(x + halfTileSize, y + halfTileSize)).length(); + + this.tiles.push({ + element: tile, + rotate: rotateDeg, + step: Math.max(1, distance / 4), + distance: distance, + active: false + }); + }, + + complexity: function() + { + return this._offsetIndex; + }, + + tune: function(count) + { + this._offsetIndex = Math.max(0, Math.min(this._offsetIndex + count, this.tiles.length)); + }, + + animate: function() + { + var distanceFactor = 1 / Math.sqrt(this._offsetIndex); + + var progress = this._benchmark.timestamp % 10000 / 10000; + var bounceProgress = Math.sin(2 * Math.abs( 0.5 - progress)); + var l = this._lerp(bounceProgress, 20, 50); + var hslPrefix = "hsla(" + this._lerp(progress, 0, 360) + ",100%,"; + + for (var i = 0; i < this._offsetIndex; ++i) { + var tile = this.tiles[i]; + tile.active = true; + tile.rotate += tile.step; + tile.element.style.transform = "rotate(" + tile.rotate + "deg)"; + + var influence = 1 - (tile.distance * distanceFactor); + tile.element.style.backgroundColor = hslPrefix + l * Math.tan(influence / 1.25) + "%," + influence + ")"; + } + + for (var i = this._offsetIndex; i < this.tiles.length && this.tiles[i].active; ++i) { + this.tiles[i].active = false; + this.tiles[i].element.style.backgroundColor = ""; + } + }, + + _lerp: function(value, min, max) + { + return min + ( max - min ) * value; + } +}); + +var MultiplyBenchmark = Utilities.createSubclass(Benchmark, + function(options) + { + Benchmark.call(this, new MultiplyStage(), options); + } +); + +window.benchmarkClass = MultiplyBenchmark; + +}()); diff --git a/PerformanceTests/Animometer/tests/master/resources/stage.css b/PerformanceTests/Animometer/tests/master/resources/stage.css index cb5f81c..853578b 100644 --- a/PerformanceTests/Animometer/tests/master/resources/stage.css +++ b/PerformanceTests/Animometer/tests/master/resources/stage.css @@ -8,10 +8,20 @@ body { margin: 0; padding: 0; background-color: rgb(241, 241, 241); + font-family: -apple-system, "Helvetica Neue", Helvetica, Verdana, sans-serif; } #stage { - width: 100%; - height: 100%; - background-color: rgb(241, 241, 241); + position: relative; + width: 100%; + height: 100%; + background-color: rgb(241, 241, 241); } + +#center-text { + position: absolute; + z-index: 3; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); +} \ No newline at end of file diff --git a/PerformanceTests/Animometer/tests/resources/main.js b/PerformanceTests/Animometer/tests/resources/main.js index 9425c8e..dafd6db 100644 --- a/PerformanceTests/Animometer/tests/resources/main.js +++ b/PerformanceTests/Animometer/tests/resources/main.js @@ -727,7 +727,12 @@ Utilities.extendObject(Stage, { randomBool: function() { - return !!Math.round(this.random(0, 1)); + return !!Math.round(Math.random()); + }, + + randomSign: function() + { + return Math.random() >= .5 ? 1 : -1; }, randomInt: function(min, max) @@ -906,6 +911,17 @@ Benchmark = Utilities.createClass( return this._stage; }, + get timestamp() + { + return this._currentTimestamp - this._startTimestamp; + }, + + backgroundColor: function() + { + var stage = window.getComputedStyle(document.getElementById("stage")); + return stage["background-color"]; + }, + run: function() { return this.waitUntilReady().then(function() { @@ -938,6 +954,7 @@ Benchmark = Utilities.createClass( if (!this._didWarmUp) { if (this._currentTimestamp - this._previousTimestamp >= 100) { this._didWarmUp = true; + this._startTimestamp = this._currentTimestamp; this._controller.start(this._currentTimestamp, this._stage); this._previousTimestamp = this._currentTimestamp; } diff --git a/PerformanceTests/ChangeLog b/PerformanceTests/ChangeLog index a708faa..1d5e383 100644 --- a/PerformanceTests/ChangeLog +++ b/PerformanceTests/ChangeLog @@ -1,3 +1,29 @@ +2016-02-10 Jon Lee + + Add new benchmark tests. + https://bugs.webkit.org/show_bug.cgi?id=154063 + + Provisionally reviewed by Said Abou-Hallawa. + + Add tests for get/put image data, filters, opacity, and css transforms. + + * Animometer/resources/runner/benchmark-runner.js: + (_runBenchmarkAndRecordResults): Update the body background color to match that of + the stage. + (this._runNextIteration): Clear the background color style for the results page. + * Animometer/resources/runner/tests.js: + * Animometer/tests/master/focus.html: Added. + * Animometer/tests/master/image-data.html: Added. + * Animometer/tests/master/multiply.html: Added. + * Animometer/tests/master/resources/focus.js: Added. + * Animometer/tests/master/resources/image-data.js: Added. + * Animometer/tests/master/resources/multiply.js: Added. + * Animometer/tests/master/resources/stage.css: Move common styles out. + * Animometer/tests/resources/main.js: Update Stage.randomBool to use Math.random. + Add Stage.randomSign for randomly setting a direction. Add the notion of the + current timestamp of the test to Benchmark, since some animations cycle through + colors and rely on an incremental counter like the time. + 2016-02-09 Said Abou-Hallawa Add internal benchmark tests for CSS mix-blend-modes and filters @@ -7,20 +33,20 @@ * Animometer/resources/debug-runner/tests.js: Include the new tests in the "HTML suite" of the debug runner. - + * Animometer/resources/extensions.js: (Utilities.browserPrefix): (Utilities.setElementPrefixedProperty): Utility functions to allow setting prefixed style properties. - + * Animometer/tests/bouncing-particles/resources/bouncing-css-shapes.js: Set the mix-blend-mode and the filter to some random values if the options of the test requested that. - + * Animometer/tests/bouncing-particles/resources/bouncing-particles.js: (parseShapeParameters): Parse the url options "blend" and "filter" and set the corresponding flags. - + * Animometer/tests/resources/main.js: (randomStyleMixBlendMode): (randomStyleFilter): Return random mix-blend-mode and filter. -- 1.8.3.1