Home | History | Annotate | Download | only in dom-perf
      1 /*
      2  * Copyright (C) 2009 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 // Events test - test the hooking and dispatch of events.
     32 //
     33 // This is a fairly simple test for measuring event peformance.
     34 // We create a DOM structure (a set of nested divs) to test with.
     35 //
     36 // The Hooking test measures the time to register onclick handlers for
     37 // each node in the structure.  This simulates conditions where applications
     38 // register event handlers on many nodes programatically.
     39 //
     40 // The Dispatch test measures the time to dispatch events to each node
     41 // in the structure.  In this case, we register the event handler as part
     42 // of the HTML for the structure, and then simply simulate onclick events
     43 // to each node.
     44 //
     45 // Works in IE, FF, Safari, and Chrome.
     46 
     47 var Events_counter = 0;
     48 function EventClickHandler() {
     49     Events_counter++;
     50 }
     51 
     52 function EventsTest(rows, cols) {
     53     var me = this;
     54     this.rows = rows;
     55     this.cols = cols;
     56     this.cell_count = 0; // Track the number of cells created in our dom tree.
     57     this.proxies = [];
     58     this.random_ids = [];
     59 
     60     // Create a DOM structure and optionally register event handlers on each node.
     61     // Create the structure by setting innerHTML so that the DOM nodes are not
     62     // pre-wrapped for JS access.
     63     this.CreateTable = function(add_event_listeners) {
     64         var html_string = '<div>';
     65         for (var i = 0; i < me.rows; i++)
     66             html_string += me.CreateRow(i, me.cols, add_event_listeners);
     67         return html_string + '</div>';
     68     };
     69 
     70     // Returns an html string for a div with a row/column based id, with an optional onclick handler.
     71     this.CreateCell = function(row_id, col_id, add_event_listeners) {
     72         var str =  '<div id="r' + row_id + 'c' + col_id + '"';
     73         if (add_event_listeners)
     74             str += ' onclick="EventClickHandler();"';
     75         str += '>'+ me.cell_count++ + '</div>';
     76         return str;
     77     };
     78 
     79     // Returns an html string with an outer div containing |cols| inner divs,
     80     // optionally having an onclick handler.
     81     this.CreateRow = function(row_id, cols, add_event_listeners) {
     82         var html_string = '<div id="r' + row_id + '">';
     83         for (var i = 0; i < cols; i++)
     84             html_string += me.CreateCell(row_id, i, add_event_listeners);
     85         return html_string + '</div>';
     86     };
     87 
     88     // Prepares for testing with elements that have no pre-defined onclick
     89     // handlers.
     90     this.Setup = function() {
     91         me.cell_count = 0;
     92         Events_counter = 0;
     93         var root_element = document.getElementById("benchmark_content");
     94         root_element.innerHTML = me.CreateTable(false);
     95         return root_element;
     96     };
     97 
     98     // Similar to Setup, but with onclick handlers already defined in the html.
     99     this.SetupWithListeners = function() {
    100         me.cell_count = 0;
    101         Events_counter = 0;
    102         var root_element = document.getElementById("benchmark_content");
    103         root_element.innerHTML = me.CreateTable(true);
    104         return root_element;
    105     };
    106 
    107     // Sets up for testing performance of removing event handlers.
    108     this.SetupForTeardown = function() {
    109         me.random_ids = [];
    110         me.SetupWithListeners();
    111         var tmp = [];
    112         for (var row = 0; row < me.rows; row++) {
    113             for (var col = 0; col < me.cols; col++)
    114                 tmp.push("r" + row + "c" + col);
    115         }
    116         while (tmp.length > 0) {
    117             var index = Math.floor(Math.random() * tmp.length);
    118             me.random_ids.push(tmp.splice(index, 1));
    119         }
    120     };
    121 
    122     // Tests the time it takes to go through and hook all elements in our dom.
    123     this.HookTest = function() {
    124         var node_count = 0;
    125 
    126         var row_id = 0;
    127         while(true) {
    128             var row = document.getElementById('r' + row_id);
    129             if (row == undefined)
    130                 break;
    131 
    132             var col_id = 0;
    133             while(true) {
    134                 var col = document.getElementById('r' + row_id + 'c' + col_id);
    135                 if (col == undefined)
    136                     break;
    137 
    138                 if (col.addEventListener)
    139                     col.addEventListener("click", EventClickHandler, false);
    140                 else if (col.attachEvent)
    141                     col.attachEvent("onclick", EventClickHandler); // To support IE
    142                 else
    143                     throw "FAILED TO ATTACH EVENTS";
    144                 col_id++;
    145                 node_count++;
    146             }
    147 
    148             row_id++;
    149         }
    150 
    151         if (node_count != me.rows * me.cols)
    152             throw "ERROR - did not iterate all nodes";
    153     };
    154 
    155     // Tests the time it takes to go through and hook all elements in our dom.
    156     // Creates new proxy object for each registration
    157     this.HookTestProxy = function() {
    158         var node_count = 0;
    159 
    160         var row_id = 0;
    161         while(true) {
    162             var row = document.getElementById('r' + row_id);
    163             if (row == undefined)
    164                 break;
    165 
    166             var col_id = 0;
    167             while(true) {
    168                 var col = document.getElementById('r' + row_id + 'c' + col_id);
    169                 if (col == undefined)
    170                     break;
    171 
    172                 var proxy = function() {};
    173                 proxy.col = col;
    174                 me.proxies.push(proxy);
    175                 if (col.addEventListener)
    176                     col.addEventListener("click", proxy, false);
    177                 else if (col.attachEvent)
    178                     col.attachEvent("onclick", proxy); // To support IE
    179                 else
    180                     throw "FAILED TO ATTACH EVENTS";
    181                 col_id++;
    182                 node_count++;
    183             }
    184 
    185             row_id++;
    186         }
    187 
    188         if (node_count != me.rows * me.cols)
    189             throw "ERROR - did not iterate all nodes";
    190     };
    191 
    192     // Tests firing the events for each element in our dom.
    193     this.DispatchTest = function() {
    194         var node_count = 0;
    195 
    196         var row_id = 0;
    197         while(true) {
    198             var row = document.getElementById('r' + row_id);
    199             if (row == undefined)
    200                 break;
    201 
    202             var col_id = 0;
    203             while(true) {
    204                 var col = document.getElementById('r' + row_id + 'c' + col_id);
    205                 if (col == undefined)
    206                   break;
    207 
    208                 if (document.createEvent) {
    209                     var event = document.createEvent("MouseEvents");
    210                     event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    211                     col.dispatchEvent(event);
    212                 } else if (col.fireEvent) {
    213                     var event = document.createEventObject();
    214                     col.fireEvent("onclick", event);
    215                 } else
    216                     throw "FAILED TO FIRE EVENTS";
    217 
    218                 col_id++;
    219                 node_count++;
    220             }
    221 
    222             row_id++;
    223         }
    224 
    225         if (Events_counter != me.rows * me.cols)
    226             throw "ERROR - did not fire events on all nodes!" + Events_counter;
    227     };
    228 
    229     // Tests removing event handlers.
    230     this.TeardownTest = function() {
    231         var node_count = 0;
    232         for (var i = 0; i < me.random_ids.length; i++) {
    233             var col = document.getElementById(me.random_ids[i]);
    234             if (col.removeEventListener)
    235                 col.removeEventListener("click", EventClickHandler, false);
    236             else if (col.detachEvent)
    237                 col.detachEvent("onclick", EventClickHandler);
    238             else
    239                 throw "FAILED TO FIRE EVENTS";
    240             node_count++;
    241         }
    242 
    243         if (node_count != me.rows * me.cols)
    244             throw "ERROR - did not remove listeners from all nodes! " + node_count;
    245     };
    246 
    247     // Removes event handlers and their associated proxy objects.
    248     this.ProxyCleanup = function() {
    249         for (var i = 0, n = me.proxies.length; i < n; i++) {
    250             var proxy = me.proxies[i];
    251             var col = proxy.col;
    252             if (col.removeEventListener)
    253                 col.removeEventListener("click", proxy, false);
    254             else if (col.detachEvent)
    255                 col.detachEvent("onclick", proxy); // To support IE
    256         }
    257         me.proxies = [];
    258     };
    259 }
    260 
    261 var small_test = new EventsTest(100, 10);
    262 var large_test = new EventsTest(100, 50);
    263 var extra_large_test = new EventsTest(200, 20);
    264 
    265 var EventTest = new BenchmarkSuite('Events', [
    266     new Benchmark("Event Hooking (1000 nodes)", small_test.HookTest, small_test.Setup),
    267     new Benchmark("Event Dispatch (1000 nodes)", small_test.DispatchTest, small_test.SetupWithListeners),
    268     new Benchmark("Event Hooking (5000 nodes)", large_test.HookTest, large_test.Setup),
    269     new Benchmark("Event Hooking Proxy (4000 nodes)",
    270         extra_large_test.HookTestProxy, extra_large_test.Setup, extra_large_test.ProxyCleanup),
    271     new Benchmark("Event Dispatch (5000 nodes)", large_test.DispatchTest, large_test.SetupWithListeners),
    272     new Benchmark("Event Teardown (5000 nodes)", large_test.TeardownTest, large_test.SetupForTeardown),
    273     new Benchmark("Event Teardown (4000 nodes)", extra_large_test.TeardownTest, extra_large_test.SetupForTeardown)
    274 ]);
    275