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