15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)/* 25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * Copyright (C) 2010 Google Inc. All rights reserved. 35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * 45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * Redistribution and use in source and binary forms, with or without 55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * modification, are permitted provided that the following conditions are 65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * met: 75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * 85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * * Redistributions of source code must retain the above copyright 95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * notice, this list of conditions and the following disclaimer. 105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * * Redistributions in binary form must reproduce the above 115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * copyright notice, this list of conditions and the following disclaimer 125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * in the documentation and/or other materials provided with the 135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * distribution. 145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * * Neither the name of Google Inc. nor the names of its 155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * contributors may be used to endorse or promote products derived from 165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * this software without specific prior written permission. 175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * 185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) */ 305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)/** 325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * @constructor 33d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) * @extends {WebInspector.VBox} 34a854de003a23bf3c7f95ec0f8154ada64092ff5cTorne (Richard Coles) * @param {!WebInspector.NetworkRequest} request 355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) */ 365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)WebInspector.RequestTimingView = function(request) 375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){ 38d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) WebInspector.VBox.call(this); 39a854de003a23bf3c7f95ec0f8154ada64092ff5cTorne (Richard Coles) this.element.classList.add("resource-timing-view"); 405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) this._request = request; 425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)} 435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)WebInspector.RequestTimingView.prototype = { 455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) wasShown: function() 465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) { 47926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) this._request.addEventListener(WebInspector.NetworkRequest.Events.TimingChanged, this._refresh, this); 4809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) this._request.addEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refresh, this); 49926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) 505c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if (!this._request.timing) { 515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if (!this._emptyView) { 525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("This request has no detailed timing info.")); 535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) this._emptyView.show(this.element); 545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) this.innerView = this._emptyView; 555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) } 565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return; 575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) } 585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if (this._emptyView) { 605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) this._emptyView.detach(); 615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) delete this._emptyView; 625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) } 635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) this._refresh(); 655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) }, 665c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 67926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) willHide: function() 68926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) { 69926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) this._request.removeEventListener(WebInspector.NetworkRequest.Events.TimingChanged, this._refresh, this); 7009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) this._request.removeEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refresh, this); 71926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) }, 72926b001d589ce2f10facb93dd4b87578ea35a855Torne (Richard Coles) 735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) _refresh: function() 745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) { 755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) if (this._tableElement) 76591b958dee2cf159d33a0b931e6231072eaf38d5Ben Murdoch this._tableElement.remove(); 775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) this._tableElement = WebInspector.RequestTimingView.createTimingTable(this._request); 795c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) this.element.appendChild(this._tableElement); 805c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) }, 815c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 82d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles) __proto__: WebInspector.VBox.prototype 835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)} 845c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 855c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 865d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)/** 875d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles) * @param {!WebInspector.NetworkRequest} request 885d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles) * @return {!Element} 895d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles) */ 905c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)WebInspector.RequestTimingView.createTimingTable = function(request) 915c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles){ 929e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) var tableElement = document.createElementWithClass("table", "network-timing-table"); 939e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) 949e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) /** 959e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) * @param {string} title 969e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) * @param {string} className 979e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) * @param {number} start 989e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) * @param {number} end 999e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) */ 1005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) function addRow(title, className, start, end) 1015c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) { 1029e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) var tr = tableElement.createChild("tr"); 1039e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) tr.createChild("td").createTextChild(title); 1049e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) var td = tr.createChild("td"); 1059e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) td.width = chartWidth + "px"; 1069e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) var row = td.createChild("div", "network-timing-row"); 1079e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) 1089e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) var bar = row.createChild("span", "network-timing-bar " + className); 1099e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) bar.style.left = Math.floor(scale * start) + "px"; 1109e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) bar.style.right = Math.floor(scale * (total - end)) + "px"; 1119e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) bar.textContent = "\u200B"; // Important for 0-time items to have 0 width. 1129e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) 1139e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) var label = row.createChild("span", "network-timing-bar-title"); 1149e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) if (total - end < start) 1159e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) label.style.right = (scale * (total - end)) + "px"; 1169e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) else 1179e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) label.style.left = (scale * start) + "px"; 1189e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) label.textContent = Number.secondsToString((end - start) / 1000, true); 1195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) } 1205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1219e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) /** 1229e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) * @param {!Array.<number>} numbers 1239e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) * @return {number|undefined} 1249e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles) */ 12509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) function firstPositive(numbers) 12609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) { 12709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) for (var i = 0; i < numbers.length; ++i) { 12809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (numbers[i] > 0) 12909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return numbers[i]; 13009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } 13109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return undefined; 13209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } 13309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 1347242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci function createCommunicationTimingTable() 1357242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci { 1367242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (blocking > 0) 1377242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Stalled"), "blocking", 0, blocking); 1387242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1397242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (timing.proxyStart !== -1) 1407242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Proxy negotiation"), "proxy", timing.proxyStart, timing.proxyEnd); 1417242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1427242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (timing.dnsStart !== -1) 1437242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("DNS Lookup"), "dns", timing.dnsStart, timing.dnsEnd); 1447242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1457242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (timing.connectStart !== -1) 1467242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Initial connection"), "connecting", timing.connectStart, timing.connectEnd); 14709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 1487242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (timing.sslStart !== -1) 1497242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("SSL"), "ssl", timing.sslStart, timing.sslEnd); 15006f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles) 1517242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Request sent"), "sending", timing.sendStart, timing.sendEnd); 1527242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Waiting (TTFB)"), "waiting", timing.sendEnd, timing.receiveHeadersEnd); 1535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1547242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (request.endTime !== -1) 1557242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Content Download"), "receiving", (request.responseReceivedTime - timing.requestTime) * 1000, total); 1567242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci } 1575c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1587242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci function createServiceWorkerTimingTable() 1597242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci { 1607242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Stalled"), "blocking", 0, timing.serviceWorkerFetchStart); 1615c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1627242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Request to ServiceWorker"), "serviceworker", timing.serviceWorkerFetchStart, timing.serviceWorkerFetchEnd); 1637242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("ServiceWorker Preparation"), "serviceworker", timing.serviceWorkerFetchStart, timing.serviceWorkerFetchReady); 1647242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Waiting (TTFB)"), "waiting", timing.serviceWorkerFetchEnd, timing.receiveHeadersEnd); 1655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 1667242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (request.endTime !== -1) 1677242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci addRow(WebInspector.UIString("Content Download"), "receiving", (request.responseReceivedTime - timing.requestTime) * 1000, total); 1687242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci } 1697242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 1707242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci var timing = request.timing; 1717242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci var blocking = firstPositive([timing.dnsStart, timing.connectStart, timing.sendStart]); 1727242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci var endTime = firstPositive([request.endTime, request.responseReceivedTime, timing.requestTime]); 1737242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci var total = (endTime - timing.requestTime) * 1000; 1747242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci const chartWidth = 200; 1757242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci var scale = chartWidth / total; 17609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 1777242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (request.fetchedViaServiceWorker) 1787242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci createServiceWorkerTimingTable(); 1797242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci else 1807242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci createCommunicationTimingTable(); 18109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 18209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (!request.finished) { 18309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) var cell = tableElement.createChild("tr").createChild("td", "caution"); 18409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) cell.colSpan = 2; 18509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) cell.createTextChild(WebInspector.UIString("CAUTION: request is not finished yet!")); 18609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } 18709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 1885c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) return tableElement; 1895c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)} 190