Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 <!DOCTYPE HTML> | 1 <!DOCTYPE HTML> |
| 2 <html> | 2 <html> |
| 3 | 3 |
| 4 <head> | 4 <head> |
| 5 <title>Webkit Layout Test History</title> | 5 <title>Webkit Layout Test History</title> |
| 6 <style> | 6 <style> |
| 7 body { | 7 body { |
| 8 font-family: arial; | 8 font-family: arial; |
| 9 font-size: 13px; | 9 font-size: 13px; |
| 10 } | 10 } |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 74 .results { | 74 .results { |
| 75 cursor: pointer; | 75 cursor: pointer; |
| 76 padding: 0; | 76 padding: 0; |
| 77 font-size: 10px; | 77 font-size: 10px; |
| 78 text-align: center; | 78 text-align: center; |
| 79 } | 79 } |
| 80 #legend { | 80 #legend { |
| 81 position: fixed; | 81 position: fixed; |
| 82 top: 5px; | 82 top: 5px; |
| 83 right: 5px; | 83 right: 5px; |
| 84 width: 130px; | 84 width: 200px; |
| 85 padding: 2px; | |
| 85 border: 2px solid grey; | 86 border: 2px solid grey; |
| 86 background-color: white; | 87 background-color: white; |
| 87 } | 88 } |
| 88 #legend-contents * { | 89 #legend-contents * { |
| 89 margin: 3px; | 90 margin: 3px 0; |
| 90 padding: 0 2px; | 91 padding: 0 2px; |
| 91 } | 92 } |
| 92 body > div > :not(#legend) { | |
| 93 margin-right: 145px; | |
| 94 } | |
| 95 #builders * { | 93 #builders * { |
| 96 margin: 0 5px; | 94 margin: 0 5px; |
| 97 display: inline-block; | 95 display: inline-block; |
| 98 white-space: nowrap; | 96 white-space: nowrap; |
| 99 } | 97 } |
| 100 .test-table .wrong-expectations, | 98 .test-table .wrong-expectations, |
| 101 .wrong-expectations { | 99 .wrong-expectations { |
| 102 background-color: #pink; | 100 background-color: #pink; |
| 103 } | 101 } |
| 104 .P { | 102 .P { |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 121 } | 119 } |
| 122 .F { | 120 .F { |
| 123 background-color: #e98080; | 121 background-color: #e98080; |
| 124 } | 122 } |
| 125 .O { | 123 .O { |
| 126 background-color: #69f; | 124 background-color: #69f; |
| 127 } | 125 } |
| 128 .merge { | 126 .merge { |
| 129 background-color: grey; | 127 background-color: grey; |
| 130 } | 128 } |
| 131 :not(#legend-contents) > .merge { | 129 table .merge { |
| 132 width: 1px; | 130 width: 1px; |
| 133 } | 131 } |
| 134 .separator { | 132 .separator { |
| 135 border: 1px solid lightgray; | 133 border: 1px solid lightgray; |
| 136 height: 0px; | 134 height: 0px; |
| 137 } | 135 } |
| 138 .different-platform { | 136 .different-platform { |
| 139 color: gray; | 137 color: gray; |
| 140 font-size: 10px; | 138 font-size: 10px; |
| 141 } | 139 } |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 172 padding: 3px; | 170 padding: 3px; |
| 173 -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); | 171 -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); |
| 174 -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); | 172 -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); |
| 175 -webkit-border-radius: 5px; | 173 -webkit-border-radius: 5px; |
| 176 -moz-border-radius: 5px; | 174 -moz-border-radius: 5px; |
| 177 } | 175 } |
| 178 #popup > ul { | 176 #popup > ul { |
| 179 margin: 0; | 177 margin: 0; |
| 180 padding-left: 20px; | 178 padding-left: 20px; |
| 181 } | 179 } |
| 180 .expectations-container { | |
| 181 clear: both; | |
| 182 } | |
| 183 .expectations-item { | |
| 184 float: left; | |
| 185 border: 1px solid grey; | |
| 186 } | |
| 187 .expectations-item .expectation { | |
| 188 width: 400px; | |
| 189 height: 300px; | |
| 190 border: 0; | |
| 191 border-top: 1px solid grey; | |
| 192 } | |
| 193 .expectations-item .large { | |
| 194 width: 800px; | |
| 195 height: 600px; | |
| 196 } | |
| 197 .expectations-item .checksum { | |
| 198 height: 30px; | |
| 199 } | |
| 200 .fallback-list { | |
| 201 margin-top: 0; | |
| 202 } | |
| 203 .used-platform { | |
| 204 float: right; | |
| 205 color: darkblue; | |
| 206 margin: 0 5px; | |
| 207 } | |
| 208 .expectations-title { | |
| 209 /* Hack to make a containing block for absolute positioned elements. */ | |
| 210 position: relative; | |
| 211 overflow: hidden; | |
| 212 } | |
| 213 .title { | |
| 214 /* Position absolutely so the container does not grow to contain this. */ | |
| 215 position: absolute; | |
| 216 } | |
| 217 .platforms { | |
| 218 position: absolute; | |
| 219 background-color: white; | |
| 220 right: 0; | |
| 221 z-index: 1; | |
| 222 } | |
| 182 </style> | 223 </style> |
| 183 | 224 |
| 184 <script src="dashboards/dashboard_base.js"></script> | 225 <script src="dashboards/dashboard_base.js"></script> |
| 185 <script> | 226 <script> |
| 186 /** | 227 /** |
| 187 * @fileoverview Creates a dashboard for multiple runs of a given set of tests | 228 * @fileoverview Creates a dashboard for multiple runs of a given set of tests |
| 188 * on the buildbots. Pulls in JSONP-ish files with the results for running | 229 * on the buildbots. Pulls in JSONP-ish files with the results for running |
| 189 * tests on a given builder (i.e. ADD_RESULTS(json_here)) and the expectations | 230 * tests on a given builder (i.e. ADD_RESULTS(json_here)) and the expectations |
| 190 * for all tests on all builders (i.e. ADD_EXPECTATIONS(json_here)). | 231 * for all tests on all builders (i.e. ADD_EXPECTATIONS(json_here)). |
| 191 * | 232 * |
| 192 * This shows flakiness of the tests as well as runtimes for slow tests. | 233 * This shows flakiness of the tests as well as runtimes for slow tests. |
| 193 * | 234 * |
| 194 * Also, each column in the dashboard is sortable. | 235 * Also, each column in the dashboard is sortable. |
| 195 * | 236 * |
| 196 * Currently, only webkit tests are supported, but adding other test types | 237 * Currently, only webkit tests are supported, but adding other test types |
| 197 * should just require the following steps: | 238 * should just require the following steps: |
| 198 * -generate results.json and expectations.json for these tests | 239 * -generate results.json and expectations.json for these tests |
| 199 * -copy them to the appropriate location | 240 * -copy them to the appropriate location |
| 200 * -add the builder name to the list of builders below. | 241 * -add the builder name to the list of builders below. |
| 201 */ | 242 */ |
| 202 | 243 |
| 203 ////////////////////////////////////////////////////////////////////////////// | 244 ////////////////////////////////////////////////////////////////////////////// |
| 204 // CONSTANTS | 245 // CONSTANTS |
| 205 ////////////////////////////////////////////////////////////////////////////// | 246 ////////////////////////////////////////////////////////////////////////////// |
| 206 var ALL = 'ALL'; | 247 var ALL = 'ALL'; |
| 207 var FORWARD = 'forward'; | 248 var FORWARD = 'forward'; |
| 208 var BACKWARD = 'backward'; | 249 var BACKWARD = 'backward'; |
| 250 var LAYOUT_TESTS_PREFIX = 'LayoutTests/'; | |
| 251 var CHROME_TEST_BASE_URL = 'http://src.chromium.org/viewvc/chrome/trunk/' + | |
| 252 'src/webkit/data/layout_tests/platform/'; | |
| 209 var TEST_URL_BASE_PATH = | 253 var TEST_URL_BASE_PATH = |
| 210 'http://trac.webkit.org/projects/webkit/browser/trunk/'; | 254 'http://trac.webkit.org/projects/webkit/browser/trunk/'; |
| 211 var BUILDERS_BASE_PATH = | 255 var BUILDERS_BASE_PATH = |
| 212 'http://build.chromium.org/buildbot/waterfall/builders/'; | 256 'http://build.chromium.org/buildbot/waterfall/builders/'; |
| 213 var TEST_RESULTS_BASE_PATH = | 257 var TEST_RESULTS_BASE_PATH = |
| 214 'http://build.chromium.org/buildbot/layout_test_results/'; | 258 'http://build.chromium.org/buildbot/layout_test_results/'; |
| 215 var PLATFORMS = { | 259 var PLATFORMS = { |
| 216 'MAC': 'MAC', | 260 'MAC': 'MAC', |
| 217 'LINUX': 'LINUX', | 261 'LINUX': 'LINUX', |
| 218 'WIN': 'WIN', | 262 'WIN': 'WIN', |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 244 | 288 |
| 245 if (currentState.tests) { | 289 if (currentState.tests) { |
| 246 createTableHeadersArray('builder'); | 290 createTableHeadersArray('builder'); |
| 247 generatePageForIndividualTests(getIndividualTests()); | 291 generatePageForIndividualTests(getIndividualTests()); |
| 248 } else { | 292 } else { |
| 249 createTableHeadersArray('test'); | 293 createTableHeadersArray('test'); |
| 250 generatePageForBuilder(currentState.builder); | 294 generatePageForBuilder(currentState.builder); |
| 251 } | 295 } |
| 252 | 296 |
| 253 $('max-results-input').value = currentState.maxResults; | 297 $('max-results-input').value = currentState.maxResults; |
| 254 updateLegendDisplay(); | |
| 255 | 298 |
| 256 for (var builder in builders) { | 299 for (var builder in builders) { |
| 257 processTestResultsForBuilderAsync(builder); | 300 processTestResultsForBuilderAsync(builder); |
| 258 } | 301 } |
| 259 } | 302 } |
| 260 | 303 |
| 261 function handleValidHashParameter(key, value) { | 304 function handleValidHashParameter(key, value) { |
| 262 switch(key) { | 305 switch(key) { |
| 263 case 'tests': | 306 case 'tests': |
| 264 validateParameter(currentState, key, value, | 307 validateParameter(currentState, key, value, |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 298 return true; | 341 return true; |
| 299 | 342 |
| 300 case 'maxResults': | 343 case 'maxResults': |
| 301 validateParameter(currentState, key, value, | 344 validateParameter(currentState, key, value, |
| 302 function() { | 345 function() { |
| 303 return value.match(/^\d+$/) | 346 return value.match(/^\d+$/) |
| 304 }); | 347 }); |
| 305 | 348 |
| 306 return true; | 349 return true; |
| 307 | 350 |
| 351 case 'showCorrectExpectations': | |
| 352 case 'showExpectations': | |
| 353 case 'showFlaky': | |
| 354 case 'showLargeExpectations': | |
| 355 case 'showSkipped': | |
| 308 case 'showWontFix': | 356 case 'showWontFix': |
| 309 case 'showCorrectExpectations': | |
| 310 case 'showFlaky': | |
| 311 case 'showLegend': | |
| 312 case 'showSkipped': | |
| 313 currentState[key] = value == 'true'; | 357 currentState[key] = value == 'true'; |
| 314 | 358 |
| 315 return true; | 359 return true; |
| 316 | 360 |
| 317 default: | 361 default: |
| 318 return false; | 362 return false; |
| 319 } | 363 } |
| 320 } | 364 } |
| 321 | 365 |
| 322 defaultStateValues = { | 366 defaultStateValues = { |
| 323 sortOrder: BACKWARD, | 367 sortOrder: BACKWARD, |
| 324 sortColumn: 'flakiness', | 368 sortColumn: 'flakiness', |
| 369 showCorrectExpectations: false, | |
| 370 showExpectations: false, | |
| 371 showFlaky: true, | |
| 372 showLargeExpectations: false, | |
| 325 showWontFix: false, | 373 showWontFix: false, |
| 326 showCorrectExpectations: false, | |
| 327 showLegend: true, | |
| 328 showFlaky: true, | |
| 329 showSkipped: false, | 374 showSkipped: false, |
| 330 maxResults: 200, | 375 maxResults: 200 |
| 331 }; | 376 }; |
| 332 | 377 |
| 333 ////////////////////////////////////////////////////////////////////////////// | 378 ////////////////////////////////////////////////////////////////////////////// |
| 334 // GLOBALS | 379 // GLOBALS |
| 335 ////////////////////////////////////////////////////////////////////////////// | 380 ////////////////////////////////////////////////////////////////////////////// |
| 336 | 381 |
| 337 // Text to put inside the header for each column of the test table. | 382 // Text to put inside the header for each column of the test table. |
| 338 var tableHeaders; | 383 var tableHeaders; |
| 339 var perBuilderPlatformAndBuildType = {}; | 384 var perBuilderPlatformAndBuildType = {}; |
| 340 var perBuilderFailures = {}; | 385 var perBuilderFailures = {}; |
| (...skipping 546 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 887 var buildNumbers = resultsByBuilder[builder].buildNumbers; | 932 var buildNumbers = resultsByBuilder[builder].buildNumbers; |
| 888 html += '<li>' + | 933 html += '<li>' + |
| 889 getLinkHTMLToOpenWindow(BUILDERS_BASE_PATH + builder + '/builds/' + | 934 getLinkHTMLToOpenWindow(BUILDERS_BASE_PATH + builder + '/builds/' + |
| 890 buildNumbers[index], 'Build log and blamelist') + '</li></ul>'; | 935 buildNumbers[index], 'Build log and blamelist') + '</li></ul>'; |
| 891 | 936 |
| 892 showPopup(e, html); | 937 showPopup(e, html); |
| 893 } | 938 } |
| 894 | 939 |
| 895 function showPopupForTest(e, test) { | 940 function showPopupForTest(e, test) { |
| 896 showPopup(e, getHTMLForIndividulTestOnAllBuilders(test)); | 941 showPopup(e, getHTMLForIndividulTestOnAllBuilders(test)); |
| 942 appendExpectations(); | |
| 897 } | 943 } |
| 898 | 944 |
| 899 function getHtmlForTestResults(test, builder) { | 945 function getHtmlForTestResults(test, builder) { |
| 900 var html = ''; | 946 var html = ''; |
| 901 var results = test.rawResults.concat(); | 947 var results = test.rawResults.concat(); |
| 902 var times = test.rawTimes.concat(); | 948 var times = test.rawTimes.concat(); |
| 903 var buildNumbers = resultsByBuilder[builder].buildNumbers; | 949 var buildNumbers = resultsByBuilder[builder].buildNumbers; |
| 904 | 950 |
| 905 var indexToReplaceCurrentResult = -1; | 951 var indexToReplaceCurrentResult = -1; |
| 906 var indexToReplaceCurrentTime = -1; | 952 var indexToReplaceCurrentTime = -1; |
| (...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1179 } | 1225 } |
| 1180 | 1226 |
| 1181 tests.sort(sortFunctionGetter(resultsProperty, order == BACKWARD)); | 1227 tests.sort(sortFunctionGetter(resultsProperty, order == BACKWARD)); |
| 1182 } | 1228 } |
| 1183 | 1229 |
| 1184 function getHTMLForIndividulTestOnAllBuilders(test) { | 1230 function getHTMLForIndividulTestOnAllBuilders(test) { |
| 1185 for (var builder in builders) | 1231 for (var builder in builders) |
| 1186 processTestRunsForBuilder(builder); | 1232 processTestRunsForBuilder(builder); |
| 1187 | 1233 |
| 1188 var testResults = testToResultsMap[test]; | 1234 var testResults = testToResultsMap[test]; |
| 1235 var html = ''; | |
| 1189 if (testResults && testResults.length) { | 1236 if (testResults && testResults.length) { |
| 1190 var tracURL = TEST_URL_BASE_PATH + test | 1237 var tracURL = TEST_URL_BASE_PATH + test |
| 1191 var html = getLinkHTMLToOpenWindow(tracURL, tracURL) + | 1238 html += getLinkHTMLToOpenWindow(tracURL, tracURL) + |
| 1192 '<div><b>If a builder is not listed, that means the builder does ' + | 1239 '<div><b>If a builder is not listed, that means the builder does ' + |
| 1193 'run that test or all runs of the test passed.</b></div>'; | 1240 'run that test or all runs of the test passed.</b></div>'; |
| 1194 | 1241 |
| 1195 for (var j = 0; j < testResults.length; j++) { | 1242 for (var j = 0; j < testResults.length; j++) { |
| 1196 html += getHTMLForSingleTestRow(testResults[j].results, | 1243 html += getHTMLForSingleTestRow(testResults[j].results, |
| 1197 testResults[j].builder, true); | 1244 testResults[j].builder, true); |
| 1198 } | 1245 } |
| 1199 return getHTMLForTestTable(html); | 1246 html = getHTMLForTestTable(html); |
| 1200 } else { | 1247 } else { |
| 1201 var html = ''; | |
| 1202 if (expectationsByTest[test]) { | 1248 if (expectationsByTest[test]) { |
| 1203 for (var i = 0; i < expectationsByTest[test].length; i++) { | 1249 for (var i = 0; i < expectationsByTest[test].length; i++) { |
| 1204 html += '<div>' + expectationsByTest[test][i].modifiers + ' | ' + | 1250 html += '<div>' + expectationsByTest[test][i].modifiers + ' | ' + |
| 1205 expectationsByTest[test][i].expectations + '</div>'; | 1251 expectationsByTest[test][i].expectations + '</div>'; |
| 1206 } | 1252 } |
| 1207 } | 1253 } |
| 1208 return html + '<div class="not-found">Test not found. Either it does ' + | 1254 html += '<div class="not-found">Test not found. Either it does ' + |
| 1209 'not exist, is skipped or passes on all platforms.</div>'; | 1255 'not exist, is skipped or passes on all platforms.</div>'; |
| 1210 } | 1256 } |
| 1257 return html + '<div class=expectations test=' + test + '><div>' + | |
| 1258 getLinkHTMLToToggleState('showExpectations', 'expectations') + ' | ' + | |
| 1259 getLinkHTMLToToggleState('showLargeExpectations', 'large thumbnails') + | |
| 1260 '</div></div>'; | |
| 1261 } | |
| 1262 | |
| 1263 function getExpectationsContainer(expectationsContainers, parentContainer, | |
| 1264 expectationsType) { | |
| 1265 if (!expectationsContainers[expectationsType]) { | |
| 1266 var container = document.createElement('div'); | |
| 1267 container.className = 'expectations-container'; | |
| 1268 parentContainer.appendChild(container); | |
| 1269 expectationsContainers[expectationsType] = container; | |
| 1270 } | |
| 1271 return expectationsContainers[expectationsType]; | |
| 1272 } | |
| 1273 | |
| 1274 function getExtension(path) { | |
| 1275 var parts = path.split('.') | |
| 1276 var extension = parts[parts.length - 1]; | |
| 1277 return extension == 'html' ? 'txt' : extension; | |
| 1278 } | |
| 1279 | |
| 1280 function ensureTrailingSlash(path) { | |
| 1281 if (path.match(/\/$/)) | |
| 1282 return path; | |
| 1283 return path + '/'; | |
| 1284 } | |
| 1285 | |
| 1286 /** | |
| 1287 * Adds a specific expectation. If it's an image, it's only added on the | |
| 1288 * image's onload handler. If it's a text file, then a script tag is appended | |
| 1289 * as a hack to see if the file 404s (necessary since it's cross-domain). | |
| 1290 * Once all the expectations for a specific type have loaded or errored | |
| 1291 * (e.g. all the checksums), then we go through and identify which platform | |
| 1292 * uses which expectation. | |
| 1293 * | |
| 1294 * @param {Object} expectationsContainers Map from expectations type to | |
| 1295 * container DIV. | |
| 1296 * @param {Element} parentContainer Container element for | |
| 1297 * expectationsContainer divs. | |
| 1298 * @param {string} platform Platform string. "LayoutTests/" for non-platform | |
| 1299 * specific expectations. | |
| 1300 * @param {string} path Relative path to the expectation. | |
| 1301 * @param {string} base Base path for the expectation URL. | |
| 1302 * @param {string} opt_suffix Suffix to place at the end of the path. | |
| 1303 */ | |
| 1304 function addExpectationItem(expectationsContainers, parentContainer, platform, | |
| 1305 path, base, opt_suffix) { | |
| 1306 var fileExtension = getExtension(path); | |
| 1307 var container = getExpectationsContainer(expectationsContainers, | |
| 1308 parentContainer, fileExtension); | |
| 1309 var isImage = path.match(/\.png$/); | |
| 1310 | |
| 1311 // TODO(ojan): Is there any way to do this that doesn't rely on script | |
| 1312 // tags? They spew a lot of errors to the console. | |
| 1313 var dummyNode = document.createElement(isImage ? 'img' : 'script'); | |
| 1314 var suffix = opt_suffix || ''; | |
| 1315 var platformPart = platform ? ensureTrailingSlash(platform) : ''; | |
| 1316 dummyNode.src = base + platformPart + path + suffix; | |
| 1317 | |
| 1318 var childContainer = document.createElement('span'); | |
| 1319 childContainer.className = 'unloaded'; | |
| 1320 | |
| 1321 dummyNode.onload = function() { | |
| 1322 childContainer.appendChild(getExpectationsTitle(platform, path)); | |
| 1323 childContainer.className = 'expectations-item'; | |
| 1324 | |
| 1325 var item; | |
| 1326 if (isImage) { | |
| 1327 item = dummyNode; | |
| 1328 } else { | |
| 1329 item = document.createElement('iframe'); | |
| 1330 item.src = dummyNode.src; | |
| 1331 } | |
| 1332 | |
| 1333 item.className = 'expectation ' + fileExtension; | |
| 1334 if (currentState.showLargeExpectations) | |
| 1335 item.className += ' large'; | |
| 1336 childContainer.appendChild(item); | |
| 1337 handleFinishedLoadingExpectations(container); | |
| 1338 } | |
| 1339 dummyNode.onerror = function() { | |
| 1340 childContainer.parentNode.removeChild(childContainer); | |
| 1341 handleFinishedLoadingExpectations(container); | |
| 1342 } | |
| 1343 | |
| 1344 // Append script elements now so that they load. Images load without being | |
| 1345 // appended to the DOM. | |
| 1346 if (!isImage) { | |
| 1347 childContainer.appendChild(dummyNode); | |
| 1348 } | |
| 1349 | |
| 1350 container.appendChild(childContainer); | |
| 1351 } | |
| 1352 | |
| 1353 /** | |
| 1354 * Identifies which expectations are used on which platform once all the | |
| 1355 * expectations of a given type have loaded (e.g. the container for checksum | |
| 1356 * expectations for this test had no child elements with the class | |
| 1357 * "unloaded"). | |
| 1358 * | |
| 1359 * @param {string} container Element containing the expectations for a given | |
| 1360 * test and a given type (e.g. checksum). | |
| 1361 */ | |
| 1362 function handleFinishedLoadingExpectations(container) { | |
| 1363 if (container.getElementsByClassName('unloaded').length) | |
| 1364 return; | |
| 1365 | |
| 1366 var titles = container.getElementsByClassName('expectations-title'); | |
| 1367 for (var platform in fallbacksMap) { | |
| 1368 var fallbacks = fallbacksMap[platform]; | |
| 1369 var winner = null; | |
| 1370 var winningIndex = -1; | |
| 1371 for (var i = 0; i < titles.length; i++) { | |
| 1372 var title = titles[i]; | |
| 1373 | |
| 1374 if (!winner && title.platform == LAYOUT_TESTS_PREFIX) { | |
| 1375 winner = title; | |
| 1376 continue; | |
| 1377 } | |
| 1378 | |
| 1379 for (var j = 0; j < fallbacks.length; j++) { | |
| 1380 if ((winningIndex == -1 || winningIndex > j) && | |
| 1381 title.platform == fallbacks[j]) { | |
| 1382 winningIndex = j; | |
| 1383 winner = title; | |
| 1384 break; | |
| 1385 } | |
| 1386 } | |
| 1387 } | |
| 1388 if (winner) { | |
| 1389 winner.getElementsByClassName('platforms')[0].innerHTML += | |
| 1390 '<div class=used-platform>' + platform + '</div>'; | |
| 1391 } else { | |
| 1392 console.log('No expectations identified for this test. This means ' + | |
| 1393 'there is a logic bug in the dashboard for which expectations a ' + | |
| 1394 'platform uses or trac.webkit.org/src.chromium.org is giving ' + | |
| 1395 ' 5XXs.'); | |
| 1396 } | |
| 1397 } | |
| 1398 } | |
| 1399 | |
| 1400 var TRAC_IMAGE_BASE_URL; | |
| 1401 /** | |
| 1402 * Trac seems to only show the raw image if you're viewing at a specific | |
| 1403 * revision. Use the latest revision on any builder. | |
| 1404 */ | |
| 1405 function getTracImageBaseURL() { | |
| 1406 if (!TRAC_IMAGE_BASE_URL) { | |
| 1407 TRAC_IMAGE_BASE_URL = 'http://trac.webkit.org/export/' + | |
| 1408 getLatestKnownRevision(false) + '/trunk/'; | |
| 1409 } | |
| 1410 return TRAC_IMAGE_BASE_URL; | |
| 1411 } | |
| 1412 | |
| 1413 function getLatestKnownRevision(isChrome) { | |
| 1414 var revision = 0; | |
| 1415 for (var builder in builders) { | |
| 1416 var results = resultsByBuilder[builder]; | |
| 1417 var revisions = isChrome ? results.chromeRevision : | |
| 1418 results.webkitRevision; | |
| 1419 if (revision < revisions[0]) | |
| 1420 revision = revisions[0]; | |
| 1421 } | |
| 1422 return revision; | |
| 1423 } | |
| 1424 | |
| 1425 function addExpectations(expectationsContainers, container, base, imageBase, | |
| 1426 platform, text, checksum, png, textSuffix) { | |
| 1427 addExpectationItem(expectationsContainers, container, platform, text, base, | |
| 1428 textSuffix); | |
| 1429 addExpectationItem(expectationsContainers, container, platform, checksum, | |
| 1430 base, textSuffix); | |
| 1431 addExpectationItem(expectationsContainers, container, platform, png, | |
| 1432 imageBase); | |
| 1433 } | |
| 1434 | |
| 1435 function getExpectationsTitle(platform, path) { | |
| 1436 var header = document.createElement('h3'); | |
| 1437 header.className = 'expectations-title'; | |
| 1438 | |
| 1439 var innerHTML; | |
| 1440 if (platform == LAYOUT_TESTS_PREFIX) { | |
| 1441 var parts = path.split('/'); | |
| 1442 innerHTML = parts[parts.length - 1]; | |
| 1443 } else { | |
| 1444 innerHTML = platform || path; | |
| 1445 } | |
| 1446 | |
| 1447 header.innerHTML = '<div class=title>' + innerHTML + | |
| 1448 '</div><div style="float:left"> </div>' + | |
| 1449 '<div class=platforms style="float:right"></div>'; | |
| 1450 header.style.clear = 'both'; | |
| 1451 header.platform = platform; | |
| 1452 return header; | |
| 1453 } | |
| 1454 | |
| 1455 function loadExpectations(expectationsContainer) { | |
| 1456 // Map from file extension to container div for expectations of that type. | |
| 1457 var expectationsContainers = {}; | |
| 1458 | |
| 1459 var test = expectationsContainer.getAttribute('test'); | |
| 1460 var textSuffixWebKit = '?format=txt'; | |
| 1461 addExpectationItem(expectationsContainers, expectationsContainer, null, | |
| 1462 test, TEST_URL_BASE_PATH, textSuffixWebKit); | |
| 1463 | |
| 1464 var testWithoutSuffix = test.substring(0, test.lastIndexOf('.')); | |
| 1465 | |
| 1466 var isUpstreamTest = startsWith(test, LAYOUT_TESTS_PREFIX); | |
| 1467 if (isUpstreamTest) { | |
| 1468 var testWithoutPrefix = testWithoutSuffix.substring( | |
| 1469 LAYOUT_TESTS_PREFIX.length); | |
| 1470 var textWithoutPrefix = testWithoutPrefix + "-expected.txt"; | |
| 1471 var checksumWithoutPrefix = testWithoutPrefix + "-expected.checksum" | |
| 1472 var pngWithoutPrefix = testWithoutPrefix + "-expected.png"; | |
| 1473 | |
| 1474 addExpectations(expectationsContainers, expectationsContainer, | |
| 1475 TEST_URL_BASE_PATH, getTracImageBaseURL(), LAYOUT_TESTS_PREFIX, | |
| 1476 textWithoutPrefix, checksumWithoutPrefix, pngWithoutPrefix, | |
| 1477 textSuffixWebKit); | |
| 1478 } | |
| 1479 | |
| 1480 var text = testWithoutSuffix + "-expected.txt"; | |
| 1481 var checksum = testWithoutSuffix + "-expected.checksum" | |
| 1482 var png = testWithoutSuffix + "-expected.png"; | |
| 1483 | |
| 1484 var textSuffixChrome = '?revision=' + getLatestKnownRevision(true); | |
| 1485 | |
| 1486 var fallbacks = getAllFallbacks(); | |
| 1487 for (var i = 0; i < fallbacks.length; i++) { | |
| 1488 var fallback = fallbacks[i]; | |
| 1489 if (startsWith(fallback, 'platform')) { | |
| 1490 if (isUpstreamTest) { | |
| 1491 addExpectations(expectationsContainers, expectationsContainer, | |
| 1492 TEST_URL_BASE_PATH + LAYOUT_TESTS_PREFIX, | |
| 1493 getTracImageBaseURL() + LAYOUT_TESTS_PREFIX, | |
| 1494 fallback, textWithoutPrefix, | |
| 1495 checksumWithoutPrefix, pngWithoutPrefix, textSuffixWebKit); | |
| 1496 } | |
| 1497 } else { | |
| 1498 addExpectations(expectationsContainers, expectationsContainer, | |
| 1499 CHROME_TEST_BASE_URL, CHROME_TEST_BASE_URL, fallback, text, | |
| 1500 checksum, png, textSuffixChrome); | |
| 1501 } | |
| 1502 } | |
| 1503 | |
| 1504 // Add a clearing element so floated elements don't bleed out of their | |
| 1505 // containing block. | |
| 1506 var br = document.createElement('br'); | |
| 1507 br.style.clear = 'both'; | |
| 1508 expectationsContainer.appendChild(br); | |
| 1509 } | |
| 1510 | |
| 1511 var allFallbacks; | |
| 1512 | |
| 1513 /** | |
| 1514 * Returns the reverse sorted, deduped list of all platform fallback | |
| 1515 * directories. | |
| 1516 */ | |
| 1517 function getAllFallbacks() { | |
| 1518 if (!allFallbacks) { | |
| 1519 var holder = {}; | |
| 1520 for (var platform in fallbacksMap) { | |
| 1521 var fallbacks = fallbacksMap[platform]; | |
| 1522 for (var i = 0; i < fallbacks.length; i++) { | |
| 1523 holder[fallbacks[i]] = 1; | |
| 1524 } | |
| 1525 } | |
| 1526 | |
| 1527 allFallbacks = []; | |
| 1528 for (var fallback in holder) { | |
| 1529 allFallbacks.push(fallback); | |
| 1530 } | |
| 1531 allFallbacks.sort(function(a, b) { | |
| 1532 if (a == b) | |
| 1533 return 0; | |
| 1534 return a < b; | |
| 1535 }); | |
| 1536 } | |
| 1537 return allFallbacks; | |
| 1538 } | |
| 1539 | |
| 1540 /** | |
| 1541 * Appends the expectations for each test listed. | |
| 1542 */ | |
| 1543 function appendExpectations() { | |
| 1544 if (currentState.showExpectations) { | |
| 1545 var expectations = document.getElementsByClassName('expectations'); | |
| 1546 for (var i = 0, len = expectations.length; i < len; i++) { | |
| 1547 loadExpectations(expectations[i]); | |
| 1548 } | |
| 1549 } | |
| 1211 } | 1550 } |
| 1212 | 1551 |
| 1213 function generatePageForIndividualTests(tests) { | 1552 function generatePageForIndividualTests(tests) { |
| 1214 var html = getHTMLForNavBar(); | 1553 var testsHTML = []; |
| 1215 for (var i = 0; i < tests.length; i++) { | 1554 for (var i = 0; i < tests.length; i++) { |
| 1216 html += '<h2>' + tests[i] + '</h2>' + | 1555 testsHTML.push('<h2>' + tests[i] + '</h2>' + |
| 1217 getHTMLForIndividulTestOnAllBuilders(tests[i]); | 1556 getHTMLForIndividulTestOnAllBuilders(tests[i])); |
| 1218 } | 1557 } |
| 1219 setFullPageHTML(html); | 1558 setFullPageHTML(getHTMLForNavBar() + testsHTML.join('<hr>')); |
| 1220 | 1559 |
| 1560 appendExpectations(); | |
| 1221 $('tests-input').value = currentState.tests; | 1561 $('tests-input').value = currentState.tests; |
| 1222 } | 1562 } |
| 1223 | 1563 |
| 1224 function getHTMLForNavBar(opt_builderName) { | 1564 function getHTMLForNavBar(opt_builderName) { |
| 1225 var html = '<div id=builders>'; | 1565 var html = '<div id=builders>'; |
| 1226 for (var builder in builders) { | 1566 for (var builder in builders) { |
| 1227 var className = builder == opt_builderName ? 'current-builder' : 'link'; | 1567 var className = builder == opt_builderName ? 'current-builder' : 'link'; |
| 1228 html += '<span class=' + className + | 1568 html += '<span class=' + className + |
| 1229 ' onclick=\'setState("builder", "' + builder + '")\'>' + | 1569 ' onclick=\'setState("builder", "' + builder + '")\'>' + |
| 1230 builder + '</span>'; | 1570 builder + '</span>'; |
| 1231 } | 1571 } |
| 1232 html += '</div>' + | 1572 return html + '</div>' + |
| 1233 '<form id=tests-form ' + | 1573 '<form id=tests-form ' + |
| 1234 'onsubmit="setState(\'tests\', tests.value);return false;">' + | 1574 'onsubmit="setState(\'tests\', tests.value);return false;">' + |
| 1235 '<div>Show tests on all platforms: </div><input name=tests ' + | 1575 '<div>Show tests on all platforms: </div><input name=tests ' + |
| 1236 'placeholder="Comma or space-separated list of tests or partial ' + | 1576 'placeholder="Comma or space-separated list of tests or partial ' + |
| 1237 'paths to show test results across all builders, e.g., ' + | 1577 'paths to show test results across all builders, e.g., ' + |
| 1238 'LayoutTests/foo/bar.html,LayoutTests/foo/baz,forms" ' + | 1578 'LayoutTests/foo/bar.html,LayoutTests/foo/baz,forms" ' + |
| 1239 'id=tests-input></form>' + | 1579 'id=tests-input></form>' + |
| 1240 '<form id=max-results-form ' + | 1580 '<form id=max-results-form ' + |
| 1241 'onsubmit="setState(\'maxResults\', maxResults.value);return false;"' + | 1581 'onsubmit="setState(\'maxResults\', maxResults.value);return false;"' + |
| 1242 '><span>Number of results to show (max=500): </span>' + | 1582 '><span>Number of results to show (max=500): </span>' + |
| 1243 '<input name=maxResults id=max-results-input></form>' + | 1583 '<input name=maxResults id=max-results-input></form> | ' + |
| 1244 '<div id="loading-ui">LOADING...</div><div id=legend>' + | 1584 '<b>Type ? for legend and expectations fallback order</b>' + |
| 1245 '<div id=legend-toggle>' + getLinkHTMLToToggleLegendDisplay() + | 1585 '<div id="loading-ui">LOADING...</div>'; |
| 1246 '</div><div id=legend-contents>'; | |
| 1247 | |
| 1248 for (var expectation in EXPECTATIONS_MAP) { | |
| 1249 html += '<div class=' + expectation + '>' + | |
| 1250 EXPECTATIONS_MAP[expectation] + '</div>'; | |
| 1251 } | |
| 1252 return html + '<div class=wrong-expectations>WRONG EXPECTATIONS</div>' + | |
| 1253 '<div class=merge>WEBKIT MERGE</div></div></div>'; | |
| 1254 } | 1586 } |
| 1255 | 1587 |
| 1256 function getLinkHTMLToToggleState(key, linkText) { | 1588 function getLinkHTMLToToggleState(key, linkText) { |
| 1257 var isTrue = currentState[key]; | 1589 var isTrue = currentState[key]; |
| 1258 return '<span class=link onclick="setState(\'' + key + '\', ' + !isTrue + | 1590 return '<span class=link onclick="setState(\'' + key + '\', ' + !isTrue + |
| 1259 ')">' + (isTrue ? 'Hide' : 'Show') + ' ' + linkText + '</span>'; | 1591 ')">' + (isTrue ? 'Hide' : 'Show') + ' ' + linkText + '</span>'; |
| 1260 } | 1592 } |
| 1261 | 1593 |
| 1262 function generatePageForBuilder(builderName) { | 1594 function generatePageForBuilder(builderName) { |
| 1263 processTestRunsForBuilder(builderName); | 1595 processTestRunsForBuilder(builderName); |
| 1264 | 1596 |
| 1265 var tableRowsHTML = ''; | 1597 var tableRowsHTML = ''; |
| 1266 var results = perBuilderFailures[builderName]; | 1598 var results = perBuilderFailures[builderName]; |
| 1267 sortTests(results, currentState.sortColumn, currentState.sortOrder); | 1599 sortTests(results, currentState.sortColumn, currentState.sortOrder); |
| 1268 for (var i = 0; i < results.length; i++) { | 1600 for (var i = 0; i < results.length; i++) { |
| 1269 tableRowsHTML += getHTMLForSingleTestRow(results[i], builderName); | 1601 tableRowsHTML += getHTMLForSingleTestRow(results[i], builderName); |
| 1270 } | 1602 } |
| 1271 | 1603 |
| 1272 var testsHTML = tableRowsHTML ? getHTMLForTestTable(tableRowsHTML) : | 1604 var testsHTML = tableRowsHTML ? getHTMLForTestTable(tableRowsHTML) : |
| 1273 '<div>No tests. Try showing tests with correct expectations.</div>'; | 1605 '<div>No tests. Try showing tests with correct expectations.</div>'; |
| 1274 | 1606 |
| 1275 var html = getHTMLForNavBar(builderName) + | 1607 var html = getHTMLForNavBar(builderName) + |
| 1276 getHTMLForTestsWithExpectationsButNoFailures(builderName) + | 1608 getHTMLForTestsWithExpectationsButNoFailures(builderName) + |
| 1277 '<h2>Failing tests</h2><div>' + | 1609 '<h2>Failing tests</h2><div>' + |
| 1278 getLinkHTMLToToggleState('showWontFix', 'WONTFIX tests') + ' | ' + | 1610 getLinkHTMLToToggleState('showWontFix', 'WONTFIX tests') + ' | ' + |
| 1279 getLinkHTMLToToggleState('showCorrectExpectations', | 1611 getLinkHTMLToToggleState('showCorrectExpectations', |
| 1280 'tests with correct expectations') + ' | ' + | 1612 'tests with correct expectations') + ' | ' + |
| 1281 getLinkHTMLToToggleState('showFlaky', 'flaky tests') + ' | ' + | 1613 getLinkHTMLToToggleState('showFlaky', 'flaky tests') + ' | ' + |
| 1282 '<b>All columns are sortable. | ' + | 1614 'All columns are sortable. | ' + |
| 1283 'Flakiness reader order is newer --> older runs.</b></div>' + | 1615 'Flakiness reader order is newer --> older runs.</div>' + |
| 1284 testsHTML; | 1616 testsHTML; |
| 1285 | 1617 |
| 1286 setFullPageHTML(html); | 1618 setFullPageHTML(html); |
| 1287 | 1619 |
| 1288 var ths = document.getElementsByTagName('th'); | 1620 var ths = document.getElementsByTagName('th'); |
| 1289 for (var i = 0; i < ths.length; i++) { | 1621 for (var i = 0; i < ths.length; i++) { |
| 1290 ths[i].addEventListener('click', changeSort, false); | 1622 ths[i].addEventListener('click', changeSort, false); |
| 1291 ths[i].className = "sortable"; | 1623 ths[i].className = "sortable"; |
| 1292 } | 1624 } |
| 1293 } | 1625 } |
| 1294 | 1626 |
| 1295 function getLinkHTMLToToggleLegendDisplay() { | |
| 1296 return getLinkHTMLToToggleState('showLegend', 'Legend'); | |
| 1297 } | |
| 1298 | |
| 1299 function updateLegendDisplay() { | |
| 1300 $('legend-contents').style.display = currentState.showLegend ? '' : 'none'; | |
| 1301 } | |
| 1302 | |
| 1303 function createTableHeadersArray(firstColumnHeader) { | 1627 function createTableHeadersArray(firstColumnHeader) { |
| 1304 tableHeaders = [firstColumnHeader].concat(BASE_TABLE_HEADERS); | 1628 tableHeaders = [firstColumnHeader].concat(BASE_TABLE_HEADERS); |
| 1305 } | 1629 } |
| 1306 | 1630 |
| 1307 /** | 1631 /** |
| 1308 * Clears the processed test state for perBuilderFailures. | 1632 * Clears the processed test state for perBuilderFailures. |
| 1309 * TODO(ojan): This really should probably clear all the state we've | 1633 * TODO(ojan): This really should probably clear all the state we've |
| 1310 * generated, but that's kind of a pain given the many global objects state is | 1634 * generated, but that's kind of a pain given the many global objects state is |
| 1311 * stored in. There should probably be one global generatedState | 1635 * stored in. There should probably be one global generatedState |
| 1312 * object that all the generated state lives off of. | 1636 * object that all the generated state lives off of. |
| 1313 */ | 1637 */ |
| 1314 function clearProcessedTestState() { | 1638 function clearProcessedTestState() { |
| 1315 for (var builder in builders) { | 1639 for (var builder in builders) { |
| 1316 delete perBuilderFailures[builder]; | 1640 delete perBuilderFailures[builder]; |
| 1317 delete perBuilderPlatformAndBuildType[builder]; | 1641 delete perBuilderPlatformAndBuildType[builder]; |
| 1318 delete perBuilderWithExpectationsButNoFailures[builder]; | 1642 delete perBuilderWithExpectationsButNoFailures[builder]; |
| 1319 delete perBuilderSkippedPaths[builder]; | 1643 delete perBuilderSkippedPaths[builder]; |
| 1320 } | 1644 } |
| 1321 | 1645 |
| 1322 for (var key in testToResultsMap) { | 1646 for (var key in testToResultsMap) { |
| 1323 delete testToResultsMap[key] | 1647 delete testToResultsMap[key] |
| 1324 } | 1648 } |
| 1325 } | 1649 } |
| 1326 | 1650 |
| 1651 var VALID_KEYS_FOR_INDIVIDUAL_TESTS = { | |
| 1652 tests: 1, | |
| 1653 maxResults: 1, | |
| 1654 showExpectations: 1, | |
| 1655 showLargeExpectations: 1 | |
| 1656 }; | |
| 1657 | |
| 1327 /** | 1658 /** |
| 1328 * Sets the page state and regenerates the page. Takes varargs of key, value | 1659 * Sets the page state and regenerates the page. Takes varargs of key, value |
| 1329 * pairs. | 1660 * pairs. |
| 1330 */ | 1661 */ |
| 1331 function setState(var_args) { | 1662 function setState(var_args) { |
| 1332 var shouldRegeneratePage = true; | 1663 var shouldRegeneratePage = true; |
| 1333 for (var i = 0; i < arguments.length; i += 2) { | 1664 for (var i = 0; i < arguments.length; i += 2) { |
| 1334 var key = arguments[i]; | 1665 var key = arguments[i]; |
| 1335 | 1666 |
| 1336 if (key != 'tests' && key != 'maxResults') { | 1667 if (!(key in VALID_KEYS_FOR_INDIVIDUAL_TESTS)) { |
| 1337 delete currentState.tests; | 1668 delete currentState.tests; |
| 1338 } | 1669 } |
| 1339 | 1670 |
| 1340 if (key == 'maxResults') { | 1671 if (key == 'maxResults') { |
| 1341 // Processing the test results JSON makes assumptions about the number | 1672 // Processing the test results JSON makes assumptions about the number |
| 1342 // of results to show. This makes changing the number of maxResults slow | 1673 // of results to show. This makes changing the number of maxResults slow |
| 1343 // but is considerably easier than refactoring all the other code. | 1674 // but is considerably easier than refactoring all the other code. |
| 1344 clearProcessedTestState(); | 1675 clearProcessedTestState(); |
| 1345 } | 1676 } |
| 1346 | |
| 1347 if (key == 'showLegend') { | |
| 1348 // No need to regenerate the page if only the legend's display is being | |
| 1349 // updated. | |
| 1350 shouldRegeneratePage = keys.length == 1; | |
| 1351 updateLegendDisplay(); | |
| 1352 $('legend-toggle').innerHTML = getLinkHTMLToToggleLegendDisplay(); | |
| 1353 } | |
| 1354 } | 1677 } |
| 1355 | 1678 |
| 1356 // Set all the custom state for this dashboard before calling | 1679 // Set all the custom state for this dashboard before calling |
| 1357 // setQueryParameter since setQueryParameter updates the location bar. | 1680 // setQueryParameter since setQueryParameter updates the location bar. |
| 1358 setQueryParameter.apply(null, arguments); | 1681 setQueryParameter.apply(null, arguments); |
| 1359 | 1682 |
| 1360 if (shouldRegeneratePage) | 1683 if (shouldRegeneratePage) |
| 1361 handleLocationChange(); | 1684 handleLocationChange(); |
| 1362 } | 1685 } |
| 1363 | 1686 |
| 1687 function hideLegend() { | |
| 1688 var legend = $('legend'); | |
| 1689 if (legend) | |
| 1690 legend.parentNode.removeChild(legend); | |
| 1691 } | |
| 1692 | |
| 1693 var fallbacksMap = {}; | |
| 1694 fallbacksMap['WIN-VISTA'] = ['chromium-win-vista', 'chromium-win', | |
| 1695 'platform/win', 'platform/mac']; | |
| 1696 fallbacksMap['WIN-XP'] = ['chromium-win-xp'].concat( | |
| 1697 fallbacksMap['WIN-VISTA']); | |
| 1698 // Should mac look at the tiger results? | |
| 1699 fallbacksMap['MAC'] = ['chromium-mac', 'platform/mac', | |
| 1700 'platform/mac-snowleopard', 'platform/mac-leopard', 'platform/mac-tiger']; | |
| 1701 fallbacksMap['LINUX'] = ['chromium-linux', 'chromium-win', 'platform/win', | |
| 1702 'platform/mac']; | |
| 1703 | |
| 1704 function htmlForFallbackHelp(fallbacks) { | |
| 1705 return '<ol class=fallback-list><li>' + fallbacks.join('</li><li>') + | |
| 1706 '</li></ol>'; | |
| 1707 } | |
| 1708 | |
| 1709 function showLegend() { | |
| 1710 var legend = $('legend'); | |
| 1711 if (!legend) { | |
| 1712 legend = document.createElement('div'); | |
| 1713 legend.id = 'legend'; | |
| 1714 document.body.appendChild(legend); | |
| 1715 } | |
| 1716 | |
| 1717 var innerHTML = '<div id=legend-toggle onclick="hideLegend()">Hide ' + | |
| 1718 'legend (or hit esc to close)</div><div id=legend-contents>'; | |
| 1719 for (var expectation in EXPECTATIONS_MAP) { | |
| 1720 innerHTML += '<div class=' + expectation + '>' + | |
| 1721 EXPECTATIONS_MAP[expectation] + '</div>'; | |
| 1722 } | |
| 1723 innerHTML += '<div class=wrong-expectations>WRONG EXPECTATIONS</div>' + | |
| 1724 '<div class=merge>WEBKIT MERGE</div></div>' +'</div>' + | |
| 1725 '<h3>Test expectatons fallback order.</h3>'; | |
| 1726 | |
| 1727 for (var platform in fallbacksMap) { | |
| 1728 innerHTML += '<div class=fallback-header>' + platform + '</div>' + | |
| 1729 htmlForFallbackHelp(fallbacksMap[platform]); | |
| 1730 } | |
| 1731 legend.innerHTML = innerHTML; | |
| 1732 } | |
| 1733 | |
| 1734 document.addEventListener('keydown', function(e) { | |
| 1735 if (e.keyCode == 191 && e.shiftKey) { | |
| 1736 // ? key | |
|
arv (Not doing code reviews)
2009/10/05 23:59:29
This will not work on different keyboard layouts.
| |
| 1737 showLegend(); | |
| 1738 } else if (e.keyCode == 27) { | |
| 1739 // escape key | |
| 1740 hideLegend(); | |
| 1741 hidePopup(); | |
| 1742 } | |
| 1743 }, false); | |
| 1364 | 1744 |
| 1365 </script> | 1745 </script> |
| 1366 </head> | 1746 </head> |
| 1367 | 1747 |
| 1368 <body></body> | 1748 <body></body> |
| 1369 </html> | 1749 </html> |
| OLD | NEW |