Make the size of the benchmark canvas adaptive to the screen size and screen resolution
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 1 Nov 2015 08:26:46 +0000 (08:26 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 1 Nov 2015 08:26:46 +0000 (08:26 +0000)
https://bugs.webkit.org/show_bug.cgi?id=150530

Patch by Said Abou-Hallawa <sabouhallawa@apple,com> on 2015-11-01
Reviewed by Darin Adler.

We want to set the size of the benchmark stage dynamically such that it
depends on the screen resolution and the device scale factor. This patch
does more than that because the home page css was not done properly. To
use the flex box layout, the animometer.css has to be rewritten almost from
scratch. The suites tree has to be rewritten also because it was not collapsing
and with the flex box layout it was going outside of the window area. The
options handling and the local storage handling had to be rewritten to
allow more flexibility with this patch and the future patches. The code
in animometer.js was reorganized into objects to allow distributing the code
nicely among separate entities.

* Animometer/resources/extensions.js:
(Point.elementClientSize): Returns the client size of an HTMLElement as a Point object.
(Insets.prototype.get width): Follow the function opening brace style guidelines.
(Insets.prototype.get height):
(Insets.prototype.get size): Returns the size of an Insets as a Point object.

(window.DocumentExtension): Provides document helper functions. It should be assailable from the runner and the tests.
(window.DocumentExtension.createElement): Creates an HTMLElement given its name, attributes and parentElement.
(window.DocumentExtension.createSvgElement): Creates an SVGElement given its name, attributes and parentElement (moved from utilities.js).
(window.DocumentExtension.insertCssRuleAfter): Inserts a CSS rule after an exiting rule given its text.

(ResultsTable.prototype._showHeader): Use DocumentExtension functions.
(ResultsTable.prototype._showGraph): Use DocumentExtension functions and create a real button for "Graph..." option.
(ResultsTable.prototype._showJSON): Use DocumentExtension functions and create a real button for "JSON..." option.
(Options): Deleted.

* Animometer/runner/animometer.html: Restructure the page to use the flex box layout.

* Animometer/runner/resources/animometer.css:
(html,body):
(button):

(button.large-button):The large button appears in the animometer.html.
(button.large-button:active):
(button.large-button:disabled):

(button.small-button): The small button appears in the results table.
(button.small-button:active):

(.tree): The tree class is used to list the suites and their tests.
(.tree .expand-button): This button expands a tree element.
(.tree .expand-button ~ ul): Hide the children (<ul>...</ul>) of a parent node by default.
(.tree .expand-button:checked ~ ul): Show the children of a parent node only when checked.
(.tree ul): Hide the list bullets.
(.tree li): Indent every node in the tree relative to its parent.
(.tree ul li): Indent all the non top level nodes only (the tests nodes in our case).
(.tree > li:last-child): Do not indent the bottom of the last child node.
(.tree-label): Style for all the labels in the tree.
(label.tree-label): Style for the labels in the top level only (the suites nodes in our case).
(label.tree-label:before): Style the unchecked case of the expand-button.
(:checked ~ label.tree-label:before): Style the checked case of the expand-button.

(table.results-table): The results table appears while running the test and at the end.
(.results-table td):
(.results-table th):

(div.results-json): The JSON div appears per test or for the whole run.

(main): This is the flex box container.

(section): A section is displayed exclusively inside the <main>. It is hidden by default.
(section.selected): When it is selected, its layout is flex layout.
(section > footer): The header or the footer of a section should not take more than 15% of the container.

(section#home): The home section has <suites> and <options> parts to be laid out in the middle.
(section#home > options):
(section#home > suites): The <suites> should not take more than 40% of the width.
(section#home > options > label): The benchmark title.
(section#home > header > h2): The benchmark title.
(section#home > options > label > input[type="number"]): Sets the width of the option edit control.

(section#running): The running section contain the runner <iframe> which takes the whole area of the <main>.
(section#running > #running-test): This is the <iframe> container.
(section#running > #running-test > iframe): The <iframe> is created by the runner for each test.
(section#running > #progress): This is the progress bar.
(section#running > #progress > #progress-completed): This is another element which grows while the runner is progressing.
(section#running > #record): This the container of the record results table which is shown while running a test.

(section#results):
(section#json):
(section#test-json):
(section#test-graph): All these sections have the same layout. A <data> element is laid out between <header> and <footer>.

(section#results > data):
(section#json > data):
(section#test-json > data):
(section#test-graph > data): The <data> element should take 70% of the <section>.

(section#test-graph > data > svg):
(.axis line):
(.left-samples): These styles are for the d3 graph.

(section#test-json > data): This is the style of the JSON <data> element.

(iframe): Deleted.
(label, p): Deleted.
(section > p): Deleted.
(section#home > p): Deleted.
(section#home > p:first-child): Deleted.
(#testContainer): Deleted.
(section#running #progress-completed): Deleted.
(section#results > table): Deleted.
(section#results > table td, th): Deleted.
(section#results > table tr.alt, td): Deleted.
(section#results > table th): Deleted.
(section#json > textarea): Deleted.
(.options): Deleted.
(.options p): Deleted.
(#suites ul): Deleted.
(#suites ul ul): Deleted.
(#suites ul ul input, #suites ul ul label): Deleted.
(#suites.showTests ul ul): Deleted.
(.column): Deleted.
(input[type="number"]): Deleted.
(.buttons): Deleted.
(.small-button): Deleted.
(#graphContainer): Deleted.
(.right-samples): Deleted.
(.sample-time): Deleted.
(.left-mean): Deleted.
(.right-mean): Deleted.

* Animometer/runner/resources/animometer.js:
(window.benchmarkRunnerClient.initialize): Initialize the client object with the options and the suites.
(window.benchmarkRunnerClient.willStartFirstIteration): Use new css selectors for results and the record table.
(window.benchmarkRunnerClient.didFinishLastIteration): Move the code which sets the JSON text to sectionsManager.showJSON().

(window.sectionsManager): Responsible of managing the <section>s elements inside animometer.html.
(window.sectionsManager._sectionHeaderH1Element): Return the <h1> inside the <header> of a given section.
(window.sectionsManager._sectionDataDivElement): Return the <div> inside the <data> of a given section.
(window.sectionsManager.showScore): Show the score of the last benchmark run.
(window.sectionsManager.showTestName): Show the test name for detailed results <section>.
(window.sectionsManager.showJSON): Shows the JSON text of the last benchmark or for a specific test.
(window.sectionsManager.showSection): Shows a specific <section> in the <main> container.
(window.sectionsManager.setupSectionStyle): Sets css attributes for all the <section>s.
(window.sectionsManager.setupRunningSectionStyle): Sets the css attributes for the running <section> only.

(window.optionsManager): Responsible of managing the user options and streaming them to/form the localStorage.
(window.optionsManager._optionsElements): Returns the children <input> elements of the <options>.
(window.optionsManager.updateUIFromLocalStorage): Restore the values of the <options> UI elements from the local storage.
(window.optionsManager.updateLocalStorageFromUI): Saves the values of the <options> UI elements to the local storage.

(window.suitesManager): Responsible of managing the user suites and streaming them to/form the localStorage.
(window.suitesManager._treeElement): Returns the suites tree container element.
(window.suitesManager._suitesElements): Returns a list of the suites elements.
(window.suitesManager._checkboxElement): Returns the checkbox element of a given suite.
(window.suitesManager._localStorageNameForTest): Generates a string for the tuple <suite, test> to be saved in the localStorage.
(window.suitesManager._updateSuiteCheckboxState): Updates the state of a suite checkbox from the state of its tests' checkboxes.
(window.suitesManager._updateStartButtonState): Updates the state of the start button from the state of the suites' checkboxes.
(window.suitesManager._onChangeSuiteCheckbox): Called when a suite checkbox is clicked.
(window.suitesManager._onChangeTestCheckbox): Called when a test checkbox is clicked.
(window.suitesManager._createSuiteElement): Creates suite node in the suites tree.
(window.suitesManager._createTestElement): Creates test node in the suites tree.
(window.suitesManager.createElements): Creates the suites tree dynamically from the array Suites.
(window.suitesManager.updateUIFromLocalStorage): Restore the values of the <suites> UI elements from the local storage.
(window.suitesManager.updateLocalStorageFromUI): aves the values of the <suites> UI elements to the local storage.

(window.benchmarkController): This is the UI controller of the animometer.html page.
(window.benchmarkController.initialize): Called when the animometer.html page is loaded.
(window.benchmarkController._runBenchmark): Starts a benchmark run.
(window.benchmarkController.startTest): Called when the "Start Test" button is clicked.
(window.benchmarkController.showResults): Called at the end of the test to show the final results.
(window.benchmarkController.showJson): Called from the results page to show the JSON of the last benchmark run.
(window.benchmarkController.showTestGraph): Called from the results the table to show a graph for the samples of a specific test.
(window.benchmarkController.showTestJSON): Called from the results the table to show a JSON for the samples of a specific test.

(showSection): Deleted.
(startTest): Deleted.
(showResults): Deleted.
(showJson): Deleted.
(showTestGraph): Deleted.
(showTestJSON): Deleted.
(initialize.toggleTestsCheckbox.onchange): Deleted.
(initialize): Deleted.
(updateSuiteSelection): Deleted.
(updateTestSelection): Deleted.
(updateSuiteCheckbox): Deleted.
(localStorageNameForTest): Deleted.
(populateSettings.): Deleted.
(populateSettings): Deleted.

* Animometer/runner/resources/benchmark-runner.js:
(BenchmarkRunner): Pass the frameContainer element to the BenchmarkRunner.
(BenchmarkRunner.prototype._appendFrame): Remove unused parameter unwanted styling code.
(BenchmarkRunner.prototype.runMultipleIterations):  Use the this._client.iterationCount instead of passing it as a parameter also.

* Animometer/runner/resources/graph.js:
(graph): Calculate the size of the chart from the container element.

* Animometer/tests/bouncing-particles/resources/bouncing-svg-images.js:
(BouncingSvgImage):
* Animometer/tests/bouncing-particles/resources/bouncing-svg-particles.js:
(BouncingSvgParticlesStage.prototype._createDefs):
(BouncingSvgParticlesStage.prototype._createClipStar):
* Animometer/tests/bouncing-particles/resources/bouncing-svg-shapes.js:
(BouncingSvgShape.prototype._createShape):
(BouncingSvgShapesStage.prototype.createGradient):
Call DocumentExtension.createSvgElement() instead of calling Utilities.createSvgElement().

* Animometer/tests/resources/main.js:
(Animator.prototype.animate):
(Benchmark):
(Benchmark.prototype.update):
* Animometer/tests/resources/stage.js:
(StageBenchmark.prototype.showResults):
Rename the options to match the <input> ids in animometer.html.

* Animometer/tests/resources/utilities.js:
(window.Utilities.createSvgElement): Deleted.

* Animometer/tests/text/layering-text.html:
* Animometer/tests/text/resources/layering-text.js:
(LayeringTextStage):
(LayeringTextStage.prototype._setFontSize): Sets the size of the text dynamically such that they all fit in one stage.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@191853 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
PerformanceTests/Animometer/resources/extensions.js
PerformanceTests/Animometer/runner/animometer.html
PerformanceTests/Animometer/runner/resources/animometer.css
PerformanceTests/Animometer/runner/resources/animometer.js
PerformanceTests/Animometer/runner/resources/benchmark-runner.js
PerformanceTests/Animometer/runner/resources/graph.js
PerformanceTests/Animometer/tests/bouncing-particles/resources/bouncing-svg-images.js
PerformanceTests/Animometer/tests/bouncing-particles/resources/bouncing-svg-particles.js
PerformanceTests/Animometer/tests/bouncing-particles/resources/bouncing-svg-shapes.js
PerformanceTests/Animometer/tests/resources/main.js
PerformanceTests/Animometer/tests/resources/stage.js
PerformanceTests/Animometer/tests/resources/utilities.js
PerformanceTests/Animometer/tests/text/layering-text.html
PerformanceTests/Animometer/tests/text/resources/layering-text.js
PerformanceTests/ChangeLog

index c700d14..35673bf 100644 (file)
@@ -14,6 +14,11 @@ Point.pointOnEllipse = function(angle, radiuses)
     return new Point(radiuses.x * Math.cos(angle), radiuses.y * Math.sin(angle));
 }
 
+Point.elementClientSize = function(element)
+{
+    return new Point(element.clientWidth, element.clientHeight);
+}
+
 Point.prototype =
 {
     // Used when the point object is used as a size object.
@@ -65,12 +70,19 @@ function Insets(top, right, bottom, left)
 
 Insets.prototype =
 {
-    get width() {
+    get width()
+    {
         return this.left + this.right;
     },
 
-    get height() {
+    get height()
+    {
         return this.top + this.bottom;
+    },
+    
+    get size()
+    {
+        return new Point(this.width, this.height);
     }
 }
 
@@ -110,10 +122,51 @@ SimplePromise.prototype.resolve = function (value)
         this._chainedPromise.resolve(result);
 }
 
-function Options(testInterval, frameRate)
+window.DocumentExtension =
 {
-    this.testInterval = testInterval;
-    this.frameRate = frameRate;
+    createElement : function(name, attrs, parentElement)
+    {
+        var element = document.createElement(name);
+
+        for (var key in attrs)
+            element.setAttribute(key, attrs[key]);
+
+        parentElement.appendChild(element);
+        return element;
+    },
+
+    createSvgElement: function(name, attrs, xlinkAttrs, parentElement)
+    {
+        const svgNamespace = "http://www.w3.org/2000/svg";
+        const xlinkNamespace = "http://www.w3.org/1999/xlink";
+
+        var element = document.createElementNS(svgNamespace, name);
+        
+        for (var key in attrs)
+            element.setAttribute(key, attrs[key]);
+            
+        for (var key in xlinkAttrs)
+            element.setAttributeNS(xlinkNamespace, key, xlinkAttrs[key]);
+            
+        parentElement.appendChild(element);
+        return element;
+    },
+    
+    insertCssRuleAfter: function(newRule, referenceRule)
+    {
+        var styleSheets = document.styleSheets;
+
+        for (var i = 0; i < styleSheets.length; ++i) {       
+            for (var j = 0; j < styleSheets[i].cssRules.length; ++j) {
+                if (styleSheets[i].cssRules[j].selectorText == referenceRule) {
+                    styleSheets[i].insertRule(newRule, j + 1);
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
 }
 
 function ProgressBar(element, ranges)
@@ -227,11 +280,11 @@ ResultsTable.prototype =
 
     _showHeader: function(message)
     {
-        var row = document.createElement("tr");
+        var thead = DocumentExtension.createElement("thead", {}, this.element);
+        var row = DocumentExtension.createElement("tr", {}, thead);
 
         var queue = [];
         this._showHeaderRow(row, queue, this._headers, message);
-        this.element.appendChild(row);
 
         while (queue.length) {
             var row = null;
@@ -246,14 +299,13 @@ ResultsTable.prototype =
                 }
 
                 if (!row)
-                    var row = document.createElement("tr");
+                    row = DocumentExtension.createElement("tr", {}, thead);
 
                 this._showHeaderRow(row, queue, entry.headers, "");
                 entry.element.colSpan = entry.headers.length;
             }
 
             if (row) {
-                this.element.appendChild(row);
                 entries.forEach(function(entry) {
                     ++entry.rowSpan;
                 });
@@ -290,14 +342,14 @@ ResultsTable.prototype =
         }
         
         var td = document.createElement("td");
-        var button = document.createElement("div");
+        var button = document.createElement("button");
         button.className = "small-button";
 
         button.addEventListener("click", function() {
             var samples = data[Strings["JSON_GRAPH"][0]];
             var samplingTimeOffset = data[Strings["JSON_GRAPH"][1]];
             var axes = Strings["TEXT_EXPERIMENTS"];
-            window.showTestGraph(testName, axes, samples, samplingTimeOffset);
+            benchmarkController.showTestGraph(testName, axes, samples, samplingTimeOffset);
         });
             
         button.textContent = Strings["TEXT_RESULTS"][1] + "...";
@@ -314,11 +366,11 @@ ResultsTable.prototype =
         }
         
         var td = document.createElement("td");
-        var button = document.createElement("div");
+        var button = document.createElement("button");
         button.className = "small-button";
 
         button.addEventListener("click", function() {
-            window.showTestJSON(testName, testResults);
+            benchmarkController.showTestJSON(testName, testResults);
         });
             
         button.textContent = Strings["TEXT_RESULTS"][2] + "...";
@@ -390,3 +442,4 @@ ResultsTable.prototype =
         }, this);
     }
 }
+
index 2a93ba6..0aa2467 100644 (file)
 <body>
     <main>
         <section id="home" class="selected">
-            <p>
-                Animometer is a browser benchmark that measures the complexity of an animation for
-                which a browser can achieve 50 FPS (frame per second). It uses adaptive animations
-                to tune their complexities to stay close to 50 FPS.
-            </p>
-            <div class="options">
-                <div id="suites" class="column">
-                <p>
-                    <label><input type="checkbox" id="toggleTests"> Show individual tests</label>
-                </p>
-                Suites:<br>
-                </div>
-                <div>
-                    <label>Test interval: <input id="test-interval" type="number" value="30"> seconds</label><br>
-                    <label>Frame rate: <input id="frame-rate" type="number" value="50"> fps</label><br>
-                    <label><input id="estimated-frame-rate" type="checkbox" checked> Estimated Frame Rate</label><br>
-                    <label><input id="fix-test-complexity" type="checkbox"> Fix test complexity after warmup</label><br>
-                    <label><input id="show-running-results" type="checkbox"> Show running results</label>
-                </div>
-            </div>
-            <div class="buttons">
-                <button onclick="startTest()">Start Test</button>
-            </div>
+            <header>
+                <h2>
+                    Animometer is a browser benchmark that measures the complexity of an animation for
+                    which a browser can achieve 50 FPS (frame per second). It uses adaptive animations
+                    to tune their complexities to stay close to 50 FPS.
+                </h2>
+            </header>
+            <suites>
+                <h2>Suites:</h2>
+                <ul class="tree"></ul>
+            </suites>
+            <options>
+                <h2>Options:</h2>
+                <label>Test interval: <input id="test-interval" type="number" value="30"> seconds</label><br>
+                <label>Frame rate: <input id="frame-rate" type="number" value="50"> fps</label><br>
+                <label><input id="estimated-frame-rate" type="checkbox" checked> Estimated Frame Rate</label><br>
+                <label><input id="fix-test-complexity" type="checkbox"> Fix test complexity after warmup</label><br>
+                <label><input id="show-running-results" type="checkbox"> Show running results</label><br>
+                <label><input id="normalize-for-device-scale-factor" type="checkbox"> Normalize for device scale factor</label>
+            </options>
+            <footer>
+                <button class="large-button" onclick="benchmarkController.startTest()">Start Test</button>
+            </footer>
         </section>
         <section id="running">
-            <div id="testContainer"></div>
+            <div id="running-test"></div>
             <div id="progress">
                 <div id="progress-completed"></div>
             </div>
             <div id="record">
-                <table class="record-table"></table>
+                <table class="results-table"></table>
             </div>
         </section>
         <section id="results">
-            <h1>Results:</h1>
-            <table class="results-table"></table>
-            <div class="buttons">
-                <button onclick="showJson()">JSON</button>
-                <button onclick="startTest()">Test Again</button>
-            </div>
+            <header>
+                <h1>Results:</h1>
+            </header>
+            <data>
+                <table class="results-table"></table>
+            </data>
+            <footer>
+                <button class="large-button" onclick="benchmarkController.showJson()">JSON</button>
+                <button class="large-button" onclick="benchmarkController.startTest()">Test Again</button>
+            </footer>
         </section>  
         <section id="json">
-            <h1>JSON:</h1>
-            <textarea class="results-json"></textarea>
-            <div class="buttons">
-                <button onclick="showResults()">Results</button>
-                <button onclick="startTest()">Test Again</button>
-            </div>
-        </section>  
-        <section id="test-graph">
-            <h1>Graph:</h1>
-            <div id="graphContainer"></div>
-            <div class="buttons">
-                <button onclick="showResults()">Results</button>
-                <button onclick="startTest()">Test Again</button>        
-            </div>
+            <header>
+                <h1>JSON:</h1>
+            </header>
+            <data>
+                <div class="results-json" contentEditable="true"></div>
+            </data>
+            <footer>
+                <button class="large-button" onclick="benchmarkController.showResults()">Results</button>
+                <button class="large-button" onclick="benchmarkController.startTest()">Test Again</button>
+            </footer>
         </section>  
         <section id="test-json">
-            <h1>JSON:</h1>
-            <textarea class="results-json"></textarea>
-            <div class="buttons">
-                <button onclick="showResults()">Results</button>
-                <button onclick="startTest()">Test Again</button>        
-            </div>
+            <header>
+                <h1>JSON:</h1>
+            </header>
+            <data>
+                <div class="results-json" contentEditable="true"></div>
+            </data>
+            <footer>
+                <button class="large-button" onclick="benchmarkController.showResults()">Results</button>
+                <button class="large-button" onclick="benchmarkController.startTest()">Test Again</button>        
+            </footer>
+        </section>
+        <section id="test-graph">
+            <header>
+                <h1>Graph:</h1>
+            </header>
+            <data></data>
+            <footer>
+                <button class="large-button" onclick="benchmarkController.showResults()">Results</button>
+                <button class="large-button" onclick="benchmarkController.startTest()">Test Again</button>        
+            </footer>
         </section>  
     </main>
 </body>
index 4b3e6a0..e08b4c4 100644 (file)
+/* -------------------------------------------------------------------------- */
+/*                                HTML and Body                               */
+/* -------------------------------------------------------------------------- */
+
+html,body {
+    height: 100%;
+    margin: 0px;
+    padding: 0px;
+}
+
 body {
     background-color: rgb(96, 96, 96);
     color: rgb(235, 235, 235);
     font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
 }
 
-main {
-    display: block;
-    position: absolute;
-    width: 800px;
-    height: 600px;
-    top: 50%;
-    left: 50%;
-    margin-top: -321px;
-    margin-left: -421px;
-    padding: 15px;
-    border: 6px solid rgb(235, 235, 235);
-    border-radius: 20px;
+/* -------------------------------------------------------------------------- */
+/*                              Buttons                                       */
+/* -------------------------------------------------------------------------- */
+
+button {
+    -webkit-appearance: none;
+    -webkit-user-select: none;
+    background-color: transparent;
 }
 
-iframe {
-    width: 800px;
-    height: 600px;
-    border: 0px none;
-    position: absolute;
+button.large-button {
+    border: 3px solid rgb(235, 235, 235);
+    border-radius: 10px;
+    min-width: 200px;
+    padding: .5em 2em;
+    margin: 0 1em;
+    font-size: 25px;
+    color: rgb(235, 235, 235);
 }
 
-label, p {
-    font-size: 16px;
-    line-height: 21px;
+button.large-button:active {
+    background-color: rgb(235, 235, 235);
+    color: rgb(46, 51, 55);
+    border-color: rgb(235, 235, 235) !important;
 }
 
-section {
-    display: none;
+button.large-button:disabled {
+    background-color: rgb(96, 96, 96);
+    color: rgb(128, 128, 128);
+}
+
+button.small-button {
+    border: 1px solid DarkCyan;
+    border-radius: 2px;
+    padding: 1px 4px;
+    margin: 0 4px;
+    font-size: 9px;
+    color: black;
 }
 
-section > p {
-    margin: 10px 20px;
+button.small-button:active {
+    background-color: DarkCyan;
+    color: rgb(46, 51, 55);
+    border-color: DarkCyan !important;
 }
 
-section#home > p {
-    margin: 0 auto;
-    width: 70%;
-    text-align: center;
+/* -------------------------------------------------------------------------- */
+/*                               Tree                                         */
+/* -------------------------------------------------------------------------- */
+
+.tree {
+    margin: 1em;
+    overflow-y: scroll;
+    height: 80%;
 }
 
-section#home > p:first-child {
-    margin-top: 160px;
-    text-align: center;
+.tree .expand-button {
+    position: absolute;
+    clip: rect(0, 0, 0, 0);
 }
 
-section.selected {
+.tree .expand-button ~ ul {
+    display: none;
+}
+
+.tree .expand-button:checked ~ ul {
     display: block;
 }
 
-#testContainer {
-    position: absolute;
-    top: 15px;
-    left: 15px;
-    width: 800px;
-    height: 600px;
+.tree ul {
+    list-style-type:none;
 }
 
-section#running > #progress {
-    position: absolute;
-    bottom: -6px;
-    left: 60px;
-    right: 60px;
-    height: 6px;
-    background-color: rgb(128, 128, 128);
-    border-left: 6px solid rgb(46, 51, 55);
-    border-right: 6px solid rgb(46, 51, 55);
+.tree li {
+    position: relative;
+    padding: 0 0 1em 1em;
 }
 
-section#running #progress-completed {
-    position: absolute;
-    top: 0;
-    left: 0;
-    height: 6px;
-    width: 0;
-    background-color: rgb(235, 235, 235);
+.tree ul li {
+    list-style:none;
+    padding: 1em 0 0 0em;
 }
 
-section#running > #record {
-    position: absolute;
-    bottom: -130px;
-    left: 0px;
-    right: 0px;
-    height: 75px;
-    color: rgb(128, 128, 128);
-    padding: 15px;
-    border: 6px solid rgb(235, 235, 235);
-    border-radius: 20px;
+.tree > li:last-child {
+    padding-bottom: 0;
+}
+
+.tree-label {
+  position: relative;
+  display: inline-block;
+}
+
+label.tree-label {
+    cursor: pointer;
 }
 
-section#running > #record > table,
-section#results > table {
+label.tree-label:before {
+    background: black;
+    color: white;
+    position: relative;
+    z-index: 1;
+    float: left;
+    margin: 0 1em 0 -2.1em;
+    width: 1em;
+    height: 1em;
+    border-radius: 1em;
+    content: '+';
+    text-align: center;
+    line-height: .9em;
+}
+
+:checked ~ label.tree-label:before {
+    content: '\2013';
+}
+
+/* -------------------------------------------------------------------------- */
+/*                              Results Table                                 */
+/* -------------------------------------------------------------------------- */
+
+table.results-table {
     width: 100%;
+    border: 2px solid DarkCyan;
+    border-collapse: collapse;
 }
 
-section#running > #record > table td, th,
-section#results > table td, th {
+.results-table th,
+.results-table td {
     font-size: 11px;
-    border: 1px solid #98bf21;
     padding: 3px 4px 2px 4px;
 }
-                                           
-section#running > #record > table tr.alt, td
-section#results > table tr.alt, td {
-    color: #000000;
-    background-color: #EAF2D3;
+
+.results-table th {
+    background: DarkCyan;
+    border-left: 1px solid LightCyan;
+    border-right: 1px solid LightCyan;
+    border-top: 1px solid LightCyan;
 }
-                                             
-section#running > #record > table th,
-section#results > table th {
-    background-color: #A7C942; 
-    text-align: center;
+
+.results-table td {
+    background: white;
+    border-bottom: none;
+    border-left: none;
+    border-right: 1px solid DarkCyan;
+    border-top: 1px solid DarkCyan;
+    color: black;
 }
 
-section#test-json > textarea,
-section#json > textarea {
-    width: 800px;
-    height: 460px;
+/* -------------------------------------------------------------------------- */
+/*                              Results JSON                                  */
+/* -------------------------------------------------------------------------- */
+
+div.results-json {
+    width: 100%;
     background-color: rgb(128, 128, 128);
-    border: 1px solid rgb(235, 235, 235);
-    color: white;
+    color: rgb(235, 235, 235);
     white-space: pre;
-    overflow: scroll;
+    font-size: 12px;
 }
 
-.options {
-    margin:0 auto;    
-    margin-top: 30px;
-    width: 600px;
-    align: center;
+/* -------------------------------------------------------------------------- */
+/*                                 Main Layout                                */
+/* -------------------------------------------------------------------------- */
+
+main {
+    width: 100%;
+    height: 100%;
+    display: -ms-flexbox;
+    display: -webkit-flex;
+    display: flex;
+    -ms-flex-align: center;
+    -webkit-align-items: center;
+    -webkit-box-align: center;
+    align-items: center;
+    -webkit-justify-content: center;
+    -ms-flex-pack: center;
+    justify-content: center;
+}
+
+section {
+    width: 800px;
+    height: 600px;
+    display: none;
+    padding: 10px;
+    border: 6px solid rgb(235, 235, 235);
+    border-radius: 20px;
 }
 
-.options p {
-    margin-top: 0;
+section.selected {
+    display: -ms-flexbox;
+    display: -webkit-flex;
+    display: flex;
 }
 
-#suites ul {
-    list-style-type: none;
-    margin: 0;
-    padding: 0;
+section > header,
+section > footer {
+    margin: 10px;
+    padding: 10px;
+    -webkit-flex: 0 1 100%;
+    -ms-flex: 0 1 100%;
+    flex: 0 1 100%;
+    max-height: 15%;
 }
 
-#suites ul ul {
-    padding-left: 1.5em;
-    display: none;
+section > footer {
+    text-align: center;
+    clear: both;
+    display: -ms-flexbox;
+    display: -webkit-flex;
+    display: flex;
+    -ms-flex-align: center;
+    -webkit-align-items: center;
+    -webkit-box-align: center;
+    align-items: center;
+    -webkit-justify-content: center;
+    -ms-flex-pack: center;
+    justify-content: center;
 }
 
-#suites ul ul input, #suites ul ul label {
-    font-size: .8em;
+/* -------------------------------------------------------------------------- */
+/*                                 Home Section                               */
+/* -------------------------------------------------------------------------- */
+
+section#home {
+    -webkit-flex-direction: row;
+    -ms-flex-direction: row;
+    flex-direction: row;
+    -webkit-flex-wrap: wrap;
+    -ms-flex-wrap: wrap;
+    flex-wrap: wrap;
+    -webkit-align-content: center;
+    -ms-flex-line-pack: center;
+    align-content: center;
 }
 
-#suites.showTests ul ul {
-    display: block;
+section#home > suites,
+section#home > options {
+    padding: 10px;
+    margin: 10px;
+    height: 50%;
 }
 
-.column {
-    width: 55%;
-    float:left;
+section#home > suites {
+    padding-left: 80px;
+    -webkit-flex: 0 1 40%;
+    -ms-flex: 0 1 40%;
+    flex: 0 1 40%;
 }
 
-input[type="number"] {
-   width:50px;
+section#home > options {
+    -webkit-flex: 1 1 auto;
+    -ms-flex: 1 1 auto;
+    flex: 1 1 auto;
 }
 
-.buttons {
-    margin-top: 10px;
+section#home > options > label {
+    margin: 2em;
+    line-height: 2;
+}
+
+section#home > header > h2 {
+    margin: 0 auto;
+    width: 70%;
     text-align: center;
-    clear: both;
 }
 
-button {
-    -webkit-appearance: none;
-    border: 3px solid rgb(235, 235, 235);
-    border-radius: 10px;
-    min-width: 200px;
-    padding: 5px 20px;
-    margin: 0 40px;
-    font-size: 25px;
-    color: rgb(235, 235, 235);
-    background-color: transparent;
+section#home > options > label > input[type="number"] {
+   width: 50px;
+}
 
-    -webkit-user-select: none;
+/* -------------------------------------------------------------------------- */
+/*                           Running Section                                  */
+/* -------------------------------------------------------------------------- */
+
+section#running {
+    position: relative;
+    -ms-flex-align: center;
+    -webkit-align-items: center;
+    -webkit-box-align: center;
+    align-items: center;
+    -webkit-justify-content: center;
+    -ms-flex-pack: center;
+    justify-content: center;
 }
 
-button:active {
-    background-color: rgb(235, 235, 235);
-    color: rgb(46, 51, 55);
-    border-color: rgb(235, 235, 235) !important;
+section#running > #running-test {
+    width: 100%;
+    height: 100%;
+}
+
+section#running > #running-test > iframe {
+    width: 100%;
+    height: 100%;
+    border: 0px none;
 }
 
-button:focus {
-    outline: none;
-    border-color: rgb(232, 79, 79);
+section#running > #progress {
+    position: absolute;
+    bottom: -6px;
+    left: 60px;
+    right: 60px;
+    height: 6px;
+    background-color: rgb(128, 128, 128);
+    border-left: 6px solid rgb(46, 51, 55);
+    border-right: 6px solid rgb(46, 51, 55);
 }
 
-.small-button {
-    -webkit-appearance: none;
-    border: 1px solid rgb(96, 96, 96);
-    border-radius: 2px;
-    padding: 1px 4px;
-    margin: 0 4px;
-    background-color: transparent;
-    cursor: pointer;
-    text-align: center;
-    -webkit-user-select: none;
+section#running > #progress > #progress-completed {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 6px;
+    width: 0;
+    background-color: rgb(235, 235, 235);
 }
 
-#graphContainer {
-  font: 10px sans-serif;
-  color: rgb(235, 235, 235);  
+section#running > #record {
+    position: absolute;
+    bottom: -95px;
+    left: 0px;
+    right: 0px;
+    padding: 16px;
+}
+
+/* -------------------------------------------------------------------------- */
+/*                           Results Section                                  */
+/* -------------------------------------------------------------------------- */
+
+section#results,
+section#json,
+section#test-json,
+section#test-graph {
+    -webkit-flex-direction: row;
+    -ms-flex-direction: row;
+    flex-direction: row;
+    -webkit-flex-wrap: wrap;
+    -ms-flex-wrap: wrap;
+    flex-wrap: wrap;
+    -webkit-justify-content: space-between;
+    -ms-flex-pack: justify;
+    justify-content: space-between;
+    -webkit-align-content: space-between;
+    -ms-flex-line-pack: justify;
+    align-content: space-between;
+    -webkit-align-items: flex-start;
+    -ms-flex-align: start;
+    align-items: flex-start;
+}
+
+section#results > data,
+section#json > data,
+section#test-json > data,
+section#test-graph > data {
+    overflow-y: scroll;
+    height: 70%;
+    -webkit-flex: 0 1 100%;
+    -ms-flex: 0 1 100%;
+    flex: 0 1 100%;
+    -webkit-align-self: stretch;
+    -ms-flex-item-align: stretch;
+    align-self: stretch;
+}
+
+/* -------------------------------------------------------------------------- */
+/*                           Graph Section                                    */
+/* -------------------------------------------------------------------------- */
+
+section#test-graph > data {
+    font: 10px sans-serif;
+    color: rgb(235, 235, 235);
+}
+
+section#test-graph > data > svg {
+    fill: none;
+    shape-rendering: crispEdges;  
 }
 
 .axis path,
 .axis line {
-  fill: none;
-  stroke: #999;
-  shape-rendering: crispEdges;
+    fill: none;
+    stroke: #999999;
+    shape-rendering: crispEdges;
 }
 
 .left-samples {
-  fill: none;
-  stroke: #7ADD49;
-  stroke-width: 1.5px;
+    stroke: #7ADD49;
+    stroke-width: 1.5px;
 }
 
 .right-samples {
-    fill: none;
     stroke: #FA4925;
     stroke-width: 1.5px;
 }
 
 .sample-time {
-    fill: none;
     stroke: #5493D6;
-    stroke-width: 1px;
 }
 
 .left-mean {
-    fill: none;
     stroke: #7ADD49;
-    stroke-width: 1px;
     opacity: .8;
 }
 
 .right-mean {
-    fill: none;
     stroke: #FA4925;
-    stroke-width: 1px;
     opacity: .8;
 }
+
+/* -------------------------------------------------------------------------- */
+/*                           JSON Section                                  */
+/* -------------------------------------------------------------------------- */
+
+section#json > data,
+section#test-json > data {
+    border: 1px solid rgb(235, 235, 235);
+    box-sizing: border-box;
+}
index 0b1b7e8..c14e65b 100644 (file)
@@ -3,11 +3,17 @@ window.benchmarkRunnerClient = {
     testsCount: null,
     progressBar: null,
     recordTable: null,
-    options: { testInterval: 30000, frameRate: 50, estimatedFrameRate: true, fixTestComplexity : false },
+    options: null,
     score: 0,
     _resultsDashboard: null,
     _resultsTable: null,
     
+    initialize: function(suites, options)
+    {
+        this.testsCount = this.iterationCount * suites.reduce(function (count, suite) { return count + suite.tests.length; }, 0);
+        this.options = options;
+    },
+
     willAddTestFrame: function (frame)
     {
         var main = document.querySelector("main");
@@ -24,10 +30,10 @@ window.benchmarkRunnerClient = {
     willStartFirstIteration: function ()
     {
         this._resultsDashboard = new ResultsDashboard();
-        this._resultsTable = new ResultsTable(document.querySelector(".results-table"), Headers);
+        this._resultsTable = new ResultsTable(document.querySelector("section#results > data > table"), Headers);
         
         this.progressBar = new ProgressBar(document.getElementById("progress-completed"), this.testsCount);
-        this.recordTable = new ResultsTable(document.querySelector(".record-table"), Headers);
+        this.recordTable = new ResultsTable(document.querySelector("section#running > #record > table"), Headers);
     },
     
     didRunSuites: function (suitesSamplers)
@@ -38,226 +44,365 @@ window.benchmarkRunnerClient = {
     didFinishLastIteration: function ()
     {
         var json = this._resultsDashboard.toJSON(true, true);
-        this._resultsTable.showIterations(json[Strings["JSON_RESULTS"][0]]);
-        
-        var element = document.querySelector("#json > textarea");
-        element.innerHTML = JSON.stringify(json[Strings["JSON_RESULTS"][0]][0], function(key, value) { 
-            if (typeof value == "number")
-                return value.toFixed(2);
-            return value;
-        }, 4);
-        
         this.score = json[Strings["JSON_SCORE"]];
-        showResults();
+        this._resultsTable.showIterations(json[Strings["JSON_RESULTS"][0]]);
+        sectionsManager.showJSON("json", json[Strings["JSON_RESULTS"][0]][0]);
+        benchmarkController.showResults();
     }
 }
 
-function showSection(sectionIdentifier, pushState)
+window.sectionsManager =
 {
-    var currentSectionElement = document.querySelector("section.selected");
-    console.assert(currentSectionElement);
+    _sectionHeaderH1Element: function(sectionIdentifier)
+    {
+        return document.querySelector("#" + sectionIdentifier + " > header > h1");
+    },
+    
+    _sectionDataDivElement: function(sectionIdentifier)
+    {
+        return document.querySelector("#" + sectionIdentifier + " >  data > div");
+    },
+    
+    showScore: function(sectionIdentifier, title)
+    {
+        var element = this._sectionHeaderH1Element(sectionIdentifier);
+        element.textContent = title + ":";
 
-    var newSectionElement = document.getElementById(sectionIdentifier);
-    console.assert(newSectionElement);
+        var score = benchmarkRunnerClient.score.toFixed(2);
+        element.textContent += " [Score = " + score + "]";
+    },
+    
+    showTestName: function(sectionIdentifier, title, testName)
+    {
+        var element = this._sectionHeaderH1Element(sectionIdentifier);
+        element.textContent = title + ":";
 
-    currentSectionElement.classList.remove("selected");
-    newSectionElement.classList.add("selected");
+        if (!testName.length)
+            return;
+            
+        element.textContent += " [test = " + testName + "]";
+    },
+    
+    showJSON: function(sectionIdentifier, json)
+    {
+        var element = this._sectionDataDivElement(sectionIdentifier);
+        element.textContent = JSON.stringify(json, function(key, value) { 
+            if (typeof value == "number")
+                return value.toFixed(2);
+            return value;
+        }, 4);
+    },
+    
+    showSection: function(sectionIdentifier, pushState)
+    {
+        var currentSectionElement = document.querySelector("section.selected");
+        console.assert(currentSectionElement);
 
-    if (pushState)
-        history.pushState({section: sectionIdentifier}, document.title);
-}
+        var newSectionElement = document.getElementById(sectionIdentifier);
+        console.assert(newSectionElement);
 
-function startBenchmark()
-{
-    var enabledSuites = [];
-
-    localStorage.clear();
-    var suiteItems = document.querySelectorAll("#suites > ul > li");
-    for (var i = 0; i < suiteItems.length; ++i) {
-        var suiteItem = suiteItems[i];
-        var suiteCheckbox = suiteItem.querySelector("input");
-
-        if (!suiteCheckbox.checked)
-            continue;
-
-        var enabledTests = [];
-        var testCheckboxes = suiteItem.querySelector("ul").querySelectorAll("input");
-        for (var j = 0; j < testCheckboxes.length; ++j) {
-            var testCheckbox = testCheckboxes[j];
-            if (!testCheckbox.checked)
-                continue;
+        currentSectionElement.classList.remove("selected");
+        newSectionElement.classList.add("selected");
 
-            enabledTests.push(testCheckbox.test);
-            localStorage.setItem(localStorageNameForTest(suiteCheckbox.suite, testCheckbox.test), +testCheckbox.checked);
-        }
+        if (pushState)
+            history.pushState({section: sectionIdentifier}, document.title);
+    },
 
-        enabledSuites.push(new Suite(suiteCheckbox.suite.name, enabledTests));
+    setupSectionStyle: function()
+    {
+        if (screen.width >= 1800 && screen.height >= 1000)
+            DocumentExtension.insertCssRuleAfter(" section { width: 1600px; height: 800px; }", "section");
+        else
+            DocumentExtension.insertCssRuleAfter(" section { width: 800px; height: 600px; }", "section");
+    },
+    
+    setupRunningSectionStyle: function(options)
+    {
+        if (!options["show-running-results"])
+            document.getElementById("record").style.display = "none";
+
+        if (options["normalize-for-device-scale-factor"] && window.devicePixelRatio != 1) {
+            var percentage = window.devicePixelRatio * 100;
+            var rule = "section#running > #running-test > iframe";
+            var newRule = rule;
+            newRule += " { ";
+            newRule += "width: " + percentage + "%; ";
+            newRule += "height: " + percentage + "%; ";
+            newRule += "transform: scale(" + 100 / percentage + ") translate(" + (100 - percentage) / 2 + "%," + (100 - percentage) / 2 + "%);";
+            newRule += " }";
+            DocumentExtension.insertCssRuleAfter(newRule, rule);
+        }
     }
+}
 
-    localStorage.setItem("test-interval", document.getElementById("test-interval").value);
-
-    var testsCount = enabledSuites.reduce(function (testsCount, suite) { return testsCount + suite.tests.length; }, 0);
-    benchmarkRunnerClient.testsCount = benchmarkRunnerClient.iterationCount * testsCount;
-    benchmarkRunnerClient.options["testInterval"] = parseInt(document.getElementById("test-interval").value) * 1000;
-    benchmarkRunnerClient.options["frameRate"] = parseInt(document.getElementById("frame-rate").value);
-    benchmarkRunnerClient.options["estimatedFrameRate"] = document.getElementById("estimated-frame-rate").checked;    
-    benchmarkRunnerClient.options["fixTestComplexity"] = document.getElementById("fix-test-complexity").checked;
-    benchmarkRunnerClient.options["showRunningResults"] = document.getElementById("show-running-results").checked;
+window.optionsManager =
+{
+    _optionsElements : function()
+    {
+        return document.querySelectorAll("section#home > options input");;
+    },
     
-    if (!benchmarkRunnerClient.options["showRunningResults"])
-        document.getElementById("record").style.display = "none";
+    updateUIFromLocalStorage: function()
+    {
+        var optionsElements = this._optionsElements();
 
-    var runner = new BenchmarkRunner(enabledSuites, benchmarkRunnerClient);
-    runner.runMultipleIterations(benchmarkRunnerClient.iterationCount);
-}
+        for (var i = 0; i < optionsElements.length; ++i) {
+            var optionElement = optionsElements[i];
+            
+            var value = localStorage.getItem(optionElement.id);
+            if (value === null)
+                continue;
 
-function startTest()
-{
-    showSection("running");
-    startBenchmark();
-}
+            if (optionElement.getAttribute("type") == "number")
+                optionElement.value = +value;
+            else if (optionElement.getAttribute("type") == "checkbox")
+                optionElement.checked = value == "true";
+        }
+    },
 
-function showResults()
-{
-    var element = document.querySelector("#results > h1");
-    element.textContent = Strings["TEXT_RESULTS"][0] + ":";
+    updateLocalStorageFromUI: function()
+    {
+        var optionsElements = this._optionsElements();
+        var options = {};        
+
+        for (var i = 0; i < optionsElements.length; ++i) {
+            var optionElement = optionsElements[i];
+        
+            if (optionElement.getAttribute("type") == "number")
+                options[optionElement.id] = optionElement.value;
+            else if (optionElement.getAttribute("type") == "checkbox")
+                options[optionElement.id] = optionElement.checked;
     
-    var score = benchmarkRunnerClient.score.toFixed(2);
-    element.textContent += " [Score = " + score + "]";
+            localStorage.setItem(optionElement.id, options[optionElement.id]);
+        }
         
-    showSection("results", true);
+        return options;
+    }
 }
 
-function showJson()
+window.suitesManager =
 {
-    var element = document.querySelector("#json > h1");
-    element.textContent = Strings["TEXT_RESULTS"][2] + ":";
+    _treeElement : function()
+    {
+        return document.querySelector("suites > .tree");
+    },
+    
+    _suitesElements : function()
+    {
+        return document.querySelectorAll("#home > suites > ul > li");
+    },
     
-    var score = benchmarkRunnerClient.score.toFixed(2);
-    element.textContent += " [Score = " + score + "]";
+    _checkboxElement: function(element)
+    {
+        return element.querySelector("input[type='checkbox']:not(.expand-button)");
+    },
 
-    showSection("json", true);
-}
+    _localStorageNameForTest: function(suite, test)
+    {
+        return suite.name + "/" + test.name;
+    },
 
-function showTestGraph(testName, axes, samples, samplingTimeOffset)
-{
-    var element = document.querySelector("#test-graph > h1");
-    element.textContent = Strings["TEXT_RESULTS"][1] + ":";
+    _updateSuiteCheckboxState: function(suiteCheckbox)
+    {
+        var numberEnabledTests = 0;
+        suiteCheckbox.testsElements.forEach(function(testElement) {
+            var testCheckbox = this._checkboxElement(testElement);
+            if (testCheckbox.checked)
+                ++numberEnabledTests;
+        }, this);
+        suiteCheckbox.checked = numberEnabledTests > 0;
+        suiteCheckbox.indeterminate = numberEnabledTests > 0 && numberEnabledTests < suiteCheckbox.testsElements.length;
+    },
 
-    if (testName.length)
-        element.textContent += " [test = " + testName + "]";
+    _updateStartButtonState: function()
+    {
+        var suitesElements = this._suitesElements();
+        var startButton = document.querySelector("#home > footer > button");
+        
+        for (var i = 0; i < suitesElements.length; ++i) {
+            var suiteElement = suitesElements[i];
+            var suiteCheckbox = this._checkboxElement(suiteElement);
             
-    graph("#graphContainer", new Point(700, 400), new Insets(20, 50, 20, 50), axes, samples, samplingTimeOffset);
-    showSection("test-graph", true);    
-}
+            if (suiteCheckbox.checked) {
+                startButton.disabled = false;
+                return;
+            }
+        }
+    
+        startButton.disabled = true;
+    },
 
-function showTestJSON(testName, testResults)
-{
-    var element = document.querySelector("#test-json > h1");
-    element.textContent = Strings["TEXT_RESULTS"][2] + ":";
+    _onChangeSuiteCheckbox: function(event)
+    {
+        var selected = event.target.checked;
+        event.target.testsElements.forEach(function(testElement) {
+            var testCheckbox = this._checkboxElement(testElement);
+            testCheckbox.checked = selected;        
+        }, this);
+        this._updateStartButtonState();
+    },
 
-    if (testName.length)
-        element.textContent += " [test = " + testName + "]";
-            
-    var element = document.querySelector("#test-json > textarea");
-    element.innerHTML = JSON.stringify(testResults, function(key, value) { 
-        if (typeof value == "number")
-            return value.toFixed(2);
-        return value;
-    }, 4);
-
-    showSection("test-json", true);    
-}
+    _onChangeTestCheckbox: function(event)
+    {
+        var suiteCheckbox = event.target.suiteCheckbox;
+        this._updateSuiteCheckboxState(suiteCheckbox);
+        this._updateStartButtonState();
+    },
 
-function initialize() {
-    populateSettings();
+    _createSuiteElement: function(treeElement, suite, id)
+    {
+        var suiteElement = DocumentExtension.createElement("li", {}, treeElement);
+        var expand = DocumentExtension.createElement("input", { type: "checkbox",  class: "expand-button", id: id }, suiteElement);
+        var label = DocumentExtension.createElement("label", { class: "tree-label", for: id }, suiteElement);
 
-    var toggleTestsCheckbox = document.getElementById("toggleTests");
-    toggleTestsCheckbox.onchange = function(event) {
-        if (event.target.checked)
-            document.getElementById("suites").classList.add("showTests");
-        else
-            document.getElementById("suites").classList.remove("showTests");
-    };
-}
+        var suiteCheckbox = DocumentExtension.createElement("input", { type: "checkbox" }, label);
+        suiteCheckbox.suite = suite;
+        suiteCheckbox.onchange = this._onChangeSuiteCheckbox.bind(this);
+        suiteCheckbox.testsElements = [];
 
-function updateSuiteSelection(event) {
-    var selected = event.target.checked;
-    var testCheckboxes = event.target.parentNode.parentNode.querySelector("ul").querySelectorAll("input");
-    for (var i = 0; i < testCheckboxes.length; ++i) {
-        testCheckboxes[i].checked = selected;
-    }
-}
+        label.appendChild(document.createTextNode(" " + suite.name));
+        return suiteElement;
+    },
 
-function updateTestSelection(event) {
-    var testsList = event.target.parentNode.parentNode.parentNode;
-    var suiteCheckbox = testsList.parentNode.querySelector("label > input");
+    _createTestElement: function(listElement, test, suiteCheckbox)
+    {
+        var testElement = DocumentExtension.createElement("li", {}, listElement);
+        var span = DocumentExtension.createElement("span", { class: "tree-label" }, testElement);
 
-    updateSuiteCheckbox(testsList, suiteCheckbox);
-}
+        var testCheckbox = DocumentExtension.createElement("input", { type: "checkbox" }, span);
+        testCheckbox.test = test;
+        testCheckbox.onchange = this._onChangeTestCheckbox.bind(this);
+        testCheckbox.suiteCheckbox = suiteCheckbox;
 
-function updateSuiteCheckbox(testsList, suiteCheckbox) {
-    var numberEnabledTests = 0;
-    var testCheckboxes = testsList.querySelectorAll("input");
-    var totalCheckboxes = testCheckboxes.length;
-    for (var i = 0; i < totalCheckboxes; ++i) {
-        if (testCheckboxes[i].checked)
-            ++numberEnabledTests;
-    }
-    suiteCheckbox.checked = numberEnabledTests > 0;
-    suiteCheckbox.indeterminate = numberEnabledTests > 0 && numberEnabledTests < totalCheckboxes;
-}
+        suiteCheckbox.testsElements.push(testElement);
+        span.appendChild(document.createTextNode(" " + test.name));
+        return testElement;
+    },
 
-function localStorageNameForTest(suite, test) {
-    return suite.name + "/" + test.name;
-}
+    createElements: function()
+    {
+        var treeElement = this._treeElement();
 
-function populateSettings() {
-    var suitesDiv = document.getElementById("suites");
+        Suites.forEach(function(suite, index) {
+            var suiteElement = this._createSuiteElement(treeElement, suite, "suite-" + index);
+            var listElement = DocumentExtension.createElement("ul", {}, suiteElement);
+            var suiteCheckbox = this._checkboxElement(suiteElement);
 
-    var suitesList = document.createElement("ul");
-    suitesDiv.appendChild(suitesList);
+            suite.tests.forEach(function(test) {
+                var testElement = this._createTestElement(listElement, test, suiteCheckbox);
+            }, this);
+        }, this);
+    },
+    
+    updateUIFromLocalStorage: function()
+    {
+        var suitesElements = this._suitesElements();
+        
+        for (var i = 0; i < suitesElements.length; ++i) {
+            var suiteElement = suitesElements[i];
+            var suiteCheckbox = this._checkboxElement(suiteElement);
+            var suite = suiteCheckbox.suite;
+            
+            suiteCheckbox.testsElements.forEach(function(testElement) {
+                var testCheckbox = this._checkboxElement(testElement);
+                var test = testCheckbox.test;
+                
+                var str = localStorage.getItem(this._localStorageNameForTest(suite, test));
+                if (str === null)
+                    return;
+
+                var value = JSON.parse(str);
+                testCheckbox.checked = value.checked;
+            }, this);
+
+            this._updateSuiteCheckboxState(suiteCheckbox);
+        }
+        
+        this._updateStartButtonState();
+    },
+
+    updateLocalStorageFromUI: function()
+    {
+        var suitesElements = this._suitesElements();
+        var suites = [];
+        
+        for (var i = 0; i < suitesElements.length; ++i) {
+            var suiteElement = suitesElements[i];
+            var suiteCheckbox = this._checkboxElement(suiteElement);
+            var suite = suiteCheckbox.suite;
+
+            var tests = [];
+            suiteCheckbox.testsElements.forEach(function(testElement) {
+                var testCheckbox = this._checkboxElement(testElement);
+                var test = testCheckbox.test;
+                
+                if (testCheckbox.checked)
+                    tests.push(test);
+
+                var value = { checked: testCheckbox.checked }; 
+                localStorage.setItem(this._localStorageNameForTest(suite, test), JSON.stringify(value));
+            }, this);
+
+            if (tests.length)
+                suites.push(new Suite(suiteCheckbox.suite.name, tests));
+        }
 
-    Suites.forEach(function(suite) {
-        var suiteItem = document.createElement("li");
-        suitesList.appendChild(suiteItem);
+        return suites;
+    }
+}
 
-        var suiteLabel = document.createElement("label");
-        suiteItem.appendChild(suiteLabel);
+window.benchmarkController =
+{
+    initialize: function()
+    {
+        sectionsManager.setupSectionStyle();
+        optionsManager.updateUIFromLocalStorage();
+        suitesManager.createElements();
+        suitesManager.updateUIFromLocalStorage();
+    },
 
-        var suiteCheckbox = document.createElement("input");
-        suiteCheckbox.setAttribute("type", "checkbox");
-        suiteCheckbox.suite = suite;
-        suiteCheckbox.onchange = updateSuiteSelection;
-        suiteLabel.appendChild(suiteCheckbox);
-        suiteLabel.appendChild(document.createTextNode(" " + suite.name));
-
-        var testsList = document.createElement("ul");
-        suiteItem.appendChild(testsList);
-
-        suite.tests.forEach(function(test) {
-            var testItem = document.createElement("li");
-            testsList.appendChild(testItem);
-
-            var testLabel = document.createElement("label");
-            testItem.appendChild(testLabel);
-
-            var testCheckbox = document.createElement("input");
-            testCheckbox.setAttribute("type", "checkbox");
-            testCheckbox.test = test;
-            testCheckbox.onchange = updateTestSelection;
-            if (+localStorage.getItem(localStorageNameForTest(suite, test)))
-                testCheckbox.checked = true;
-            testLabel.appendChild(testCheckbox);
-            testLabel.appendChild(document.createTextNode(" " + test.name));
-        });
-
-        updateSuiteCheckbox(testsList, suiteCheckbox);
-    });
-
-    var interval = localStorage.getItem("test-interval");
-    if (interval) {
-        document.getElementById("test-interval").value = interval;
+    _runBenchmark: function(suites, options)
+    {
+        benchmarkRunnerClient.initialize(suites, options);
+        var frameContainer = document.querySelector("#running > #running-test");
+        var runner = new BenchmarkRunner(suites, frameContainer || document.body, benchmarkRunnerClient);
+        runner.runMultipleIterations();
+    },
+
+    startTest: function()
+    {
+        var options = optionsManager.updateLocalStorageFromUI();
+        var suites = suitesManager.updateLocalStorageFromUI();
+        sectionsManager.setupRunningSectionStyle(options);
+        this._runBenchmark(suites, options);
+        sectionsManager.showSection("running");
+    },
+    
+    showResults: function()
+    {
+        sectionsManager.showScore("results", Strings["TEXT_RESULTS"][0]);
+        sectionsManager.showSection("results", true);
+    },
+    
+    showJson: function()
+    {
+        sectionsManager.showScore("json", Strings["TEXT_RESULTS"][0]);
+        sectionsManager.showSection("json", true);
+    },
+    
+    showTestGraph: function(testName, axes, samples, samplingTimeOffset)
+    {
+        sectionsManager.showTestName("test-graph", Strings["TEXT_RESULTS"][1], testName);
+        sectionsManager.showSection("test-graph", true);
+        graph("section#test-graph > data", new Insets(20, 50, 20, 50), axes, samples, samplingTimeOffset);
+    },
+
+    showTestJSON: function(testName, json)
+    {
+        sectionsManager.showTestName("test-json", Strings["TEXT_RESULTS"][1], testName);
+        sectionsManager.showJSON("test-json", json);
+        sectionsManager.showSection("test-json", true);
     }
 }
-document.addEventListener("DOMContentLoaded", initialize);
+
+document.addEventListener("DOMContentLoaded", benchmarkController.initialize());
index 1273964..4f48345 100644 (file)
@@ -50,10 +50,11 @@ BenchmarkRunnerState.prototype.prepareCurrentTest = function(runner, frame)
     return promise;
 }
 
-function BenchmarkRunner(suites, client)
+function BenchmarkRunner(suites, frameContainer, client)
 {
     this._suites = suites;
     this._client = client;
+    this._frameContainer = frameContainer;
 }
 
 BenchmarkRunner.prototype.waitForElement = function(selector)
@@ -72,25 +73,15 @@ BenchmarkRunner.prototype.waitForElement = function(selector)
     return promise;
 }
 
-BenchmarkRunner.prototype._appendFrame = function(src)
+BenchmarkRunner.prototype._appendFrame = function()
 {
     var frame = document.createElement("iframe");
     frame.setAttribute("scrolling", "no");
 
-    var marginLeft = parseInt(getComputedStyle(document.body).marginLeft);
-    var marginTop = parseInt(getComputedStyle(document.body).marginTop);
-    if (window.innerWidth > 800 + marginLeft && window.innerHeight > 600 + marginTop) {
-        frame.style.left = marginLeft + "px";
-        frame.style.top = marginTop + "px";
-    } else {
-        frame.style.left = "0px";
-        frame.style.top = "0px";
-    }
-
     if (this._client && this._client.willAddTestFrame)
         this._client.willAddTestFrame(frame);
 
-    document.body.insertBefore(frame, document.body.firstChild);
+    this._frameContainer.insertBefore(frame, this._frameContainer.firstChild);
     this._frame = frame;
     return frame;
 }
@@ -167,21 +158,21 @@ BenchmarkRunner.prototype.runAllSteps = function(startingState)
     });
 }
 
-BenchmarkRunner.prototype.runMultipleIterations = function(iterationCount)
+BenchmarkRunner.prototype.runMultipleIterations = function()
 {
     var self = this;
     var currentIteration = 0;
 
     this._runNextIteration = function() {
         currentIteration++;
-        if (currentIteration < iterationCount)
+        if (currentIteration < self._client.iterationCount)
             self.runAllSteps();
         else if (this._client && this._client.didFinishLastIteration)
             self._client.didFinishLastIteration();
     }
 
     if (self._client && self._client.willStartFirstIteration)
-        self._client.willStartFirstIteration(iterationCount);
+        self._client.willStartFirstIteration();
 
     self.runAllSteps();
 }
index d0b1034..021f5f5 100644 (file)
@@ -1,7 +1,9 @@
-function graph(selector, size, margins, axes, samples, samplingTimeOffset)
+function graph(selector, margins, axes, samples, samplingTimeOffset)
 {
     var element = document.querySelector(selector);
-    element.textContent = null;
+    element.innerHTML = '';
+
+    var size = Point.elementClientSize(element).subtract(margins.size);
 
     var x = d3.scale.linear()
             .range([0, size.width])
index cfe2c40..c68bce0 100644 (file)
@@ -5,7 +5,7 @@ function BouncingSvgImage(stage)
     
     var attrs = { x: 0, y: 0, width: this._size.x, height: this._size.y };
     var xlinkAttrs = { href: stage.imageSrc };
-    this.element = Utilities.createSvgElement("image", attrs, xlinkAttrs, stage.element);
+    this.element = DocumentExtension.createSvgElement("image", attrs, xlinkAttrs, stage.element);
     this._move();
 }
 
index 2d29bf4..69c6754 100644 (file)
@@ -39,7 +39,7 @@ BouncingSvgParticlesStage.prototype.constructor = BouncingSvgParticlesStage;
 
 BouncingSvgParticlesStage.prototype._createDefs = function()
 {
-    return Utilities.createSvgElement("defs", {}, {}, this.element);
+    return DocumentExtension.createSvgElement("defs", {}, {}, this.element);
 }
                                                                
 BouncingSvgParticlesStage.prototype._ensureDefsIsCreated = function()
@@ -50,10 +50,10 @@ BouncingSvgParticlesStage.prototype._ensureDefsIsCreated = function()
 BouncingSvgParticlesStage.prototype._createClipStar = function()
 {
     var attrs = { id: "star-clip", clipPathUnits: "objectBoundingBox" };
-    var clipPath  = Utilities.createSvgElement("clipPath", attrs, {}, this._ensureDefsIsCreated());
+    var clipPath  = DocumentExtension.createSvgElement("clipPath", attrs, {}, this._ensureDefsIsCreated());
 
     attrs = { d: "M.50,0L.38,.38L0,.38L.30,.60L.18,1L.50,.75L.82,1L.70,.60L1,.38L.62,.38z" };
-    Utilities.createSvgElement("path", attrs, {}, clipPath);
+    DocumentExtension.createSvgElement("path", attrs, {}, clipPath);
     return clipPath;
 }
 
index 1980110..76dc9ee 100644 (file)
@@ -19,13 +19,13 @@ BouncingSvgShape.prototype._createShape = function(stage)
     switch (this._shape) {
     case "rect":
         var attrs = { x: 0, y: 0, width: this._size.x, height: this._size.y };
-        this.element = Utilities.createSvgElement("rect", attrs, {}, stage.element);
+        this.element = DocumentExtension.createSvgElement("rect", attrs, {}, stage.element);
         break;
     
     case "circle":
     default:
         var attrs = { cx: this._size.x / 2, cy: this._size.y / 2, r: Math.min(this._size.x, this._size.y) / 2 };
-        this.element = Utilities.createSvgElement("circle", attrs, {}, stage.element);
+        this.element = DocumentExtension.createSvgElement("circle", attrs, {}, stage.element);
         break;        
     }
 }
@@ -58,11 +58,11 @@ BouncingSvgShapesStage.prototype.constructor = BouncingSvgShapesStage;
 BouncingSvgShapesStage.prototype.createGradient = function(stops)
 {
     var attrs = { id: "gadient-" + ++this._gradientsCount };
-    var gradient = Utilities.createSvgElement("linearGradient", attrs, {}, this._ensureDefsIsCreated());    
+    var gradient = DocumentExtension.createSvgElement("linearGradient", attrs, {}, this._ensureDefsIsCreated());    
     
     for (var i = 0; i < stops; ++i) {
         attrs = { offset: i * 100 / stops + "%", 'stop-color': this.randomColor() };
-        Utilities.createSvgElement("stop", attrs, {}, gradient);
+        DocumentExtension.createSvgElement("stop", attrs, {}, gradient);
     }
 
     return gradient;
index ea77ceb..d645610 100644 (file)
@@ -109,7 +109,7 @@ Animator.prototype =
         var currentFrameRate = Math.floor(1000 / (measureTimeDelta / this._measureFrameCount));
          
         // Use Kalman filter to get a more non-fluctuating frame rate.
-        if (this._benchmark.options.estimatedFrameRate)
+        if (this._benchmark.options["estimated-frame-rate"])
             currentFrameRate = this._estimator.estimate(measureTimeDelta, currentFrameRate);
         
         // Adjust the test to reach the desired FPS.
@@ -146,9 +146,9 @@ function Benchmark(options)
     var lowValue = -parseInt(this._options["addLimit"]) || 1;
     var highValue = parseInt(this._options["removeLimit"]) || 1;
     
-    this._controller = new PIDController(gain, options.frameRate, lowValue, highValue);
+    this._controller = new PIDController(gain, options["frame-rate"], lowValue, highValue);
     this._sampler = new Sampler(2);
-    this._state = new BenchmarkState(this.options.testInterval);    
+    this._state = new BenchmarkState(this.options["test-interval"] * 1000);
 }
 
 Benchmark.prototype =
@@ -179,7 +179,7 @@ Benchmark.prototype =
         }
 
         var tuneValue = 0;
-        if (!(this._isSampling && this.options.fixTestComplexity)) {
+        if (!(this._isSampling && this.options["fix-test-complexity"])) {
             // The relationship between frameRate and test complexity is inverse-proportional so we
             // need to use the negative of PIDController.tune() to change the complexity of the test.
             tuneValue = -this._controller.tune(currentFrameRate, timeDelta / 1000);
index 07fa661..2bb49d5 100644 (file)
@@ -177,7 +177,7 @@ StageBenchmark.prototype.showResults = function(message, progress)
     if (!this._recordTable || !this._progressBar || !this._test)
         return;
 
-    if (this.options.showRunningResults)
+    if (this.options["show-running-results"])
         this._recordTable.showRecord(this._test.name, message, this._sampler.toJSON(true, false));
 
     this._progressBar.setPos(progress);
index 0726001..a6c7649 100644 (file)
@@ -39,22 +39,5 @@ window.Utilities =
     mergeObjects: function(obj1, obj2)
     {
         return this.extendObject(this.copyObject(obj1), obj2);
-    },
-    
-    createSvgElement: function(name, attrs, xlinkAttrs, parent)
-    {
-        const svgNamespace = "http://www.w3.org/2000/svg";
-        const xlinkNamespace = "http://www.w3.org/1999/xlink";
-
-        var element = document.createElementNS(svgNamespace, name);
-        
-        for (var key in attrs)
-            element.setAttribute(key, attrs[key]);
-            
-        for (var key in xlinkAttrs)
-            element.setAttributeNS(xlinkNamespace, key, xlinkAttrs[key]);
-            
-        parent.appendChild(element);
-        return element;
     }
 }
index f58ef4c..1f2a33e 100644 (file)
@@ -3,12 +3,12 @@
 <head>
     <style>
         .text-layer {
-            font-size: 5.5px;
             position: absolute;
             left: 0px;
             top: 0px;
             padding: 3px;
             background-color: transparent;
+            line-height: 1.2;
         }
     </style>  
     <link rel="stylesheet" type="text/css" href="../resources/stage.css">
index 385e197..1d64654 100644 (file)
@@ -6,6 +6,7 @@ function LayeringTextStage(element, options)
     this._textItemIndex = 0;
     this._colorIndex = 0;
     this._animateCounts = 0;
+    this._setFontSize();
 }
 
 LayeringTextStage.textItems = [
@@ -137,6 +138,14 @@ LayeringTextStage.colorIndexToTextElementIndex = function(colorIndex)
 LayeringTextStage.prototype = Object.create(Stage.prototype);
 LayeringTextStage.prototype.constructor = LayeringTextStage;
 
+LayeringTextStage.prototype._setFontSize = function()
+{
+    var lineHeight = this.size.height / LayeringTextStage.colorableTextItems;
+    var fontHeight = lineHeight / 1.5;
+    var fontSize = fontHeight * 72.0 / 96.0;
+    DocumentExtension.insertCssRuleAfter(".text-layer", ".text-layer { font-size: " + fontSize.toFixed(2) + "px; }");
+}
+
 LayeringTextStage.prototype._nextTextItem = function(textItemFlags)
 {
     var textItem = LayeringTextStage.textItems[this._textItemIndex];
index 3b9cae1..deba5a8 100644 (file)
@@ -1,3 +1,226 @@
+2015-11-01  Said Abou-Hallawa  <sabouhallawa@apple,com>
+
+        Make the size of the benchmark canvas adaptive to the screen size and screen resolution
+        https://bugs.webkit.org/show_bug.cgi?id=150530
+
+        Reviewed by Darin Adler.
+        
+        We want to set the size of the benchmark stage dynamically such that it
+        depends on the screen resolution and the device scale factor. This patch 
+        does more than that because the home page css was not done properly. To
+        use the flex box layout, the animometer.css has to be rewritten almost from
+        scratch. The suites tree has to be rewritten also because it was not collapsing
+        and with the flex box layout it was going outside of the window area. The 
+        options handling and the local storage handling had to be rewritten to
+        allow more flexibility with this patch and the future patches. The code
+        in animometer.js was reorganized into objects to allow distributing the code
+        nicely among separate entities.
+
+        * Animometer/resources/extensions.js:
+        (Point.elementClientSize): Returns the client size of an HTMLElement as a Point object.
+        (Insets.prototype.get width): Follow the function opening brace style guidelines.
+        (Insets.prototype.get height):
+        (Insets.prototype.get size): Returns the size of an Insets as a Point object.
+        
+        (window.DocumentExtension): Provides document helper functions. It should be assailable from the runner and the tests.
+        (window.DocumentExtension.createElement): Creates an HTMLElement given its name, attributes and parentElement. 
+        (window.DocumentExtension.createSvgElement): Creates an SVGElement given its name, attributes and parentElement (moved from utilities.js). 
+        (window.DocumentExtension.insertCssRuleAfter): Inserts a CSS rule after an exiting rule given its text.
+        
+        (ResultsTable.prototype._showHeader): Use DocumentExtension functions.
+        (ResultsTable.prototype._showGraph): Use DocumentExtension functions and create a real button for "Graph..." option.
+        (ResultsTable.prototype._showJSON): Use DocumentExtension functions and create a real button for "JSON..." option.
+        (Options): Deleted.
+        
+        * Animometer/runner/animometer.html: Restructure the page to use the flex box layout.
+        
+        * Animometer/runner/resources/animometer.css:
+        (html,body):
+        (button):
+        
+        (button.large-button):The large button appears in the animometer.html.
+        (button.large-button:active):
+        (button.large-button:disabled):
+        
+        (button.small-button): The small button appears in the results table.
+        (button.small-button:active):
+        
+        (.tree): The tree class is used to list the suites and their tests.
+        (.tree .expand-button): This button expands a tree element.
+        (.tree .expand-button ~ ul): Hide the children (<ul>...</ul>) of a parent node by default.
+        (.tree .expand-button:checked ~ ul): Show the children of a parent node only when checked.
+        (.tree ul): Hide the list bullets.
+        (.tree li): Indent every node in the tree relative to its parent.
+        (.tree ul li): Indent all the non top level nodes only (the tests nodes in our case).
+        (.tree > li:last-child): Do not indent the bottom of the last child node.
+        (.tree-label): Style for all the labels in the tree.
+        (label.tree-label): Style for the labels in the top level only (the suites nodes in our case).
+        (label.tree-label:before): Style the unchecked case of the expand-button.
+        (:checked ~ label.tree-label:before): Style the checked case of the expand-button.
+        
+        (table.results-table): The results table appears while running the test and at the end.
+        (.results-table td):
+        (.results-table th):
+        
+        (div.results-json): The JSON div appears per test or for the whole run.
+        
+        (main): This is the flex box container.
+        
+        (section): A section is displayed exclusively inside the <main>. It is hidden by default.
+        (section.selected): When it is selected, its layout is flex layout.
+        (section > footer): The header or the footer of a section should not take more than 15% of the container.
+        
+        (section#home): The home section has <suites> and <options> parts to be laid out in the middle.
+        (section#home > options): 
+        (section#home > suites): The <suites> should not take more than 40% of the width.
+        (section#home > options > label): The benchmark title.
+        (section#home > header > h2): The benchmark title.
+        (section#home > options > label > input[type="number"]): Sets the width of the option edit control.
+        
+        (section#running): The running section contain the runner <iframe> which takes the whole area of the <main>.
+        (section#running > #running-test): This is the <iframe> container.
+        (section#running > #running-test > iframe): The <iframe> is created by the runner for each test.
+        (section#running > #progress): This is the progress bar.
+        (section#running > #progress > #progress-completed): This is another element which grows while the runner is progressing.
+        (section#running > #record): This the container of the record results table which is shown while running a test.
+        
+        (section#results):
+        (section#json):
+        (section#test-json):
+        (section#test-graph): All these sections have the same layout. A <data> element is laid out between <header> and <footer>.
+
+        (section#results > data):
+        (section#json > data):
+        (section#test-json > data):
+        (section#test-graph > data): The <data> element should take 70% of the <section>.
+        
+        (section#test-graph > data > svg):
+        (.axis line):
+        (.left-samples): These styles are for the d3 graph.
+        
+        (section#test-json > data): This is the style of the JSON <data> element.
+        
+        (iframe): Deleted.
+        (label, p): Deleted.
+        (section > p): Deleted.
+        (section#home > p): Deleted.
+        (section#home > p:first-child): Deleted.
+        (#testContainer): Deleted.
+        (section#running #progress-completed): Deleted.
+        (section#results > table): Deleted.
+        (section#results > table td, th): Deleted.
+        (section#results > table tr.alt, td): Deleted.
+        (section#results > table th): Deleted.
+        (section#json > textarea): Deleted.
+        (.options): Deleted.
+        (.options p): Deleted.
+        (#suites ul): Deleted.
+        (#suites ul ul): Deleted.
+        (#suites ul ul input, #suites ul ul label): Deleted.
+        (#suites.showTests ul ul): Deleted.
+        (.column): Deleted.
+        (input[type="number"]): Deleted.
+        (.buttons): Deleted.
+        (.small-button): Deleted.
+        (#graphContainer): Deleted.
+        (.right-samples): Deleted.
+        (.sample-time): Deleted.
+        (.left-mean): Deleted.
+        (.right-mean): Deleted.
+        
+        * Animometer/runner/resources/animometer.js:
+        (window.benchmarkRunnerClient.initialize): Initialize the client object with the options and the suites.
+        (window.benchmarkRunnerClient.willStartFirstIteration): Use new css selectors for results and the record table.
+        (window.benchmarkRunnerClient.didFinishLastIteration): Move the code which sets the JSON text to sectionsManager.showJSON().
+        
+        (window.sectionsManager): Responsible of managing the <section>s elements inside animometer.html.
+        (window.sectionsManager._sectionHeaderH1Element): Return the <h1> inside the <header> of a given section.
+        (window.sectionsManager._sectionDataDivElement): Return the <div> inside the <data> of a given section.
+        (window.sectionsManager.showScore): Show the score of the last benchmark run.
+        (window.sectionsManager.showTestName): Show the test name for detailed results <section>.
+        (window.sectionsManager.showJSON): Shows the JSON text of the last benchmark or for a specific test.
+        (window.sectionsManager.showSection): Shows a specific <section> in the <main> container.
+        (window.sectionsManager.setupSectionStyle): Sets css attributes for all the <section>s.
+        (window.sectionsManager.setupRunningSectionStyle): Sets the css attributes for the running <section> only.
+        
+        (window.optionsManager): Responsible of managing the user options and streaming them to/form the localStorage.
+        (window.optionsManager._optionsElements): Returns the children <input> elements of the <options>.
+        (window.optionsManager.updateUIFromLocalStorage): Restore the values of the <options> UI elements from the local storage.
+        (window.optionsManager.updateLocalStorageFromUI): Saves the values of the <options> UI elements to the local storage.
+        
+        (window.suitesManager): Responsible of managing the user suites and streaming them to/form the localStorage.
+        (window.suitesManager._treeElement): Returns the suites tree container element.
+        (window.suitesManager._suitesElements): Returns a list of the suites elements.
+        (window.suitesManager._checkboxElement): Returns the checkbox element of a given suite.
+        (window.suitesManager._localStorageNameForTest): Generates a string for the tuple <suite, test> to be saved in the localStorage.
+        (window.suitesManager._updateSuiteCheckboxState): Updates the state of a suite checkbox from the state of its tests' checkboxes.
+        (window.suitesManager._updateStartButtonState): Updates the state of the start button from the state of the suites' checkboxes.
+        (window.suitesManager._onChangeSuiteCheckbox): Called when a suite checkbox is clicked.
+        (window.suitesManager._onChangeTestCheckbox): Called when a test checkbox is clicked.
+        (window.suitesManager._createSuiteElement): Creates suite node in the suites tree.
+        (window.suitesManager._createTestElement): Creates test node in the suites tree.
+        (window.suitesManager.createElements): Creates the suites tree dynamically from the array Suites.
+        (window.suitesManager.updateUIFromLocalStorage): Restore the values of the <suites> UI elements from the local storage.
+        (window.suitesManager.updateLocalStorageFromUI): aves the values of the <suites> UI elements to the local storage.
+        
+        (window.benchmarkController): This is the UI controller of the animometer.html page.
+        (window.benchmarkController.initialize): Called when the animometer.html page is loaded.
+        (window.benchmarkController._runBenchmark): Starts a benchmark run.
+        (window.benchmarkController.startTest): Called when the "Start Test" button is clicked.
+        (window.benchmarkController.showResults): Called at the end of the test to show the final results.
+        (window.benchmarkController.showJson): Called from the results page to show the JSON of the last benchmark run.
+        (window.benchmarkController.showTestGraph): Called from the results the table to show a graph for the samples of a specific test.
+        (window.benchmarkController.showTestJSON): Called from the results the table to show a JSON for the samples of a specific test.
+        
+        (showSection): Deleted.
+        (startTest): Deleted.
+        (showResults): Deleted.
+        (showJson): Deleted.
+        (showTestGraph): Deleted.
+        (showTestJSON): Deleted.
+        (initialize.toggleTestsCheckbox.onchange): Deleted.
+        (initialize): Deleted.
+        (updateSuiteSelection): Deleted.
+        (updateTestSelection): Deleted.
+        (updateSuiteCheckbox): Deleted.
+        (localStorageNameForTest): Deleted.
+        (populateSettings.): Deleted.
+        (populateSettings): Deleted.
+        
+        * Animometer/runner/resources/benchmark-runner.js:
+        (BenchmarkRunner): Pass the frameContainer element to the BenchmarkRunner.
+        (BenchmarkRunner.prototype._appendFrame): Remove unused parameter unwanted styling code.
+        (BenchmarkRunner.prototype.runMultipleIterations):  Use the this._client.iterationCount instead of passing it as a parameter also.
+
+        * Animometer/runner/resources/graph.js:
+        (graph): Calculate the size of the chart from the container element.
+        
+        * Animometer/tests/bouncing-particles/resources/bouncing-svg-images.js:
+        (BouncingSvgImage):
+        * Animometer/tests/bouncing-particles/resources/bouncing-svg-particles.js:
+        (BouncingSvgParticlesStage.prototype._createDefs):
+        (BouncingSvgParticlesStage.prototype._createClipStar):        
+        * Animometer/tests/bouncing-particles/resources/bouncing-svg-shapes.js:
+        (BouncingSvgShape.prototype._createShape):
+        (BouncingSvgShapesStage.prototype.createGradient):
+        Call DocumentExtension.createSvgElement() instead of calling Utilities.createSvgElement().
+        
+        * Animometer/tests/resources/main.js:
+        (Animator.prototype.animate):
+        (Benchmark):
+        (Benchmark.prototype.update):
+        * Animometer/tests/resources/stage.js:
+        (StageBenchmark.prototype.showResults):
+        Rename the options to match the <input> ids in animometer.html.
+        
+        * Animometer/tests/resources/utilities.js:
+        (window.Utilities.createSvgElement): Deleted.
+        
+        * Animometer/tests/text/layering-text.html:
+        * Animometer/tests/text/resources/layering-text.js:
+        (LayeringTextStage):
+        (LayeringTextStage.prototype._setFontSize): Sets the size of the text dynamically such that they all fit in one stage.
+
 2015-10-29  Simon Fraser  <simon.fraser@apple.com>
 
         Animometer computes frame rate incorrectly