diff options
author | Steve Block <steveblock@google.com> | 2010-02-15 12:23:52 +0000 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2010-02-16 11:48:32 +0000 |
commit | 8a0914b749bbe7da7768e07a7db5c6d4bb09472b (patch) | |
tree | 73f9065f370435d6fde32ae129d458a8c77c8dff /WebCore/inspector/front-end | |
parent | bf14be70295513b8076f3fa47a268a7e42b2c478 (diff) | |
download | external_webkit-8a0914b749bbe7da7768e07a7db5c6d4bb09472b.zip external_webkit-8a0914b749bbe7da7768e07a7db5c6d4bb09472b.tar.gz external_webkit-8a0914b749bbe7da7768e07a7db5c6d4bb09472b.tar.bz2 |
Merge webkit.org at r54731 : Initial merge by git
Change-Id: Ia79977b6cf3b0b00c06ef39419989b28e57e4f4a
Diffstat (limited to 'WebCore/inspector/front-end')
31 files changed, 2751 insertions, 2322 deletions
diff --git a/WebCore/inspector/front-end/AuditCategories.js b/WebCore/inspector/front-end/AuditCategories.js new file mode 100644 index 0000000..6931b5f --- /dev/null +++ b/WebCore/inspector/front-end/AuditCategories.js @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.AuditCategories.PagePerformance = function() { + WebInspector.AuditCategory.call(this, WebInspector.AuditCategories.PagePerformance.AuditCategoryName); +} + +WebInspector.AuditCategories.PagePerformance.AuditCategoryName = "Web Page Performance"; + +WebInspector.AuditCategories.PagePerformance.prototype = { + initialize: function() + { + this.addRule(new WebInspector.AuditRules.UnusedCssRule()); + this.addRule(new WebInspector.AuditRules.CssInHeadRule({InlineURLScore: 6, InlineStylesheetScore: 21})); + this.addRule(new WebInspector.AuditRules.StylesScriptsOrderRule({CSSAfterJSURLScore: 11, InlineBetweenResourcesScore: 21})); + } +} + +WebInspector.AuditCategories.PagePerformance.prototype.__proto__ = WebInspector.AuditCategory.prototype; + +WebInspector.AuditCategories.NetworkUtilization = function() { + WebInspector.AuditCategory.call(this, WebInspector.AuditCategories.NetworkUtilization.AuditCategoryName); +} + +WebInspector.AuditCategories.NetworkUtilization.AuditCategoryName = "Network Utilization"; + +WebInspector.AuditCategories.NetworkUtilization.prototype = { + initialize: function() + { + this.addRule(new WebInspector.AuditRules.GzipRule()); + this.addRule(new WebInspector.AuditRules.ImageDimensionsRule({ScorePerImageUse: 5})); + this.addRule(new WebInspector.AuditRules.CookieSizeRule({MinBytesThreshold: 400, MaxBytesThreshold: 1000})); + this.addRule(new WebInspector.AuditRules.StaticCookielessRule({MinResources: 5})); + this.addRule(new WebInspector.AuditRules.CombineJsResourcesRule({AllowedPerDomain: 2, ScorePerResource: 11})); + this.addRule(new WebInspector.AuditRules.CombineCssResourcesRule({AllowedPerDomain: 2, ScorePerResource: 11})); + this.addRule(new WebInspector.AuditRules.MinimizeDnsLookupsRule({HostCountThreshold: 4, ViolationDomainScore: 6})); + this.addRule(new WebInspector.AuditRules.ParallelizeDownloadRule({OptimalHostnameCount: 4, MinRequestThreshold: 10, MinBalanceThreshold: 0.5})); + this.addRule(new WebInspector.AuditRules.BrowserCacheControlRule()); + this.addRule(new WebInspector.AuditRules.ProxyCacheControlRule()); + } +} + +WebInspector.AuditCategories.NetworkUtilization.prototype.__proto__ = WebInspector.AuditCategory.prototype; diff --git a/WebCore/inspector/front-end/AuditLauncherView.js b/WebCore/inspector/front-end/AuditLauncherView.js index 79fbb92..f2a2fd2 100644 --- a/WebCore/inspector/front-end/AuditLauncherView.js +++ b/WebCore/inspector/front-end/AuditLauncherView.js @@ -63,11 +63,11 @@ WebInspector.AuditLauncherView = function(categoriesById, runnerCallback) } WebInspector.AuditLauncherView.prototype = { - updateResourceTrackingState: function() + updateResourceTrackingState: function(isTracking) { if (!this._auditPresentStateLabelElement) return; - if (InspectorBackend.resourceTrackingEnabled()) { + if (isTracking) { this._auditPresentStateLabelElement.nodeValue = WebInspector.UIString("Audit Present State"); this._auditPresentStateElement.disabled = false; this._auditPresentStateElement.parentElement.removeStyleClass("disabled"); @@ -197,7 +197,6 @@ WebInspector.AuditLauncherView.prototype = { this._selectAllClicked(this._selectAllCheckboxElement.checked); this.updateResourceTrackingState(); this._updateButton(); - this.resize(); }, _updateButton: function() @@ -209,6 +208,12 @@ WebInspector.AuditLauncherView.prototype = { this._launchButton.textContent = WebInspector.UIString("Run"); }, + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + setTimeout(this.resize(), 0); + }, + resize: function() { if (this._categoriesElement) diff --git a/WebCore/inspector/front-end/AuditRules.js b/WebCore/inspector/front-end/AuditRules.js new file mode 100644 index 0000000..210b8a9 --- /dev/null +++ b/WebCore/inspector/front-end/AuditRules.js @@ -0,0 +1,1213 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.AuditRules.IPAddressRegexp = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; + +WebInspector.AuditRules.CacheableResponseCodes = +{ + 200: true, + 203: true, + 206: true, + 300: true, + 301: true, + 410: true, + + 304: true // Underlying resource is cacheable +} + +/** + * @param {Array} array Array of Elements (outerHTML is used) or strings (plain value is used as innerHTML) + */ +WebInspector.AuditRules.arrayAsUL = function(array, shouldLinkify) +{ + if (!array.length) + return ""; + var ulElement = document.createElement("ul"); + for (var i = 0; i < array.length; ++i) { + var liElement = document.createElement("li"); + if (array[i] instanceof Element) + liElement.appendChild(array[i]); + else if (shouldLinkify) + liElement.appendChild(WebInspector.linkifyURLAsNode(array[i])); + else + liElement.innerHTML = array[i]; + ulElement.appendChild(liElement); + } + return ulElement.outerHTML; +} + +WebInspector.AuditRules.getDomainToResourcesMap = function(resources, types, regexp, needFullResources) +{ + var domainToResourcesMap = {}; + for (var i = 0, size = resources.length; i < size; ++i) { + var resource = resources[i]; + if (types && types.indexOf(resource.type) === -1) + continue; + var match = resource.url.match(regexp); + if (!match) + continue; + var domain = match[2]; + var domainResources = domainToResourcesMap[domain]; + if (domainResources === undefined) { + domainResources = []; + domainToResourcesMap[domain] = domainResources; + } + domainResources.push(needFullResources ? resource : resource.url); + } + return domainToResourcesMap; +} + +WebInspector.AuditRules.evaluateInTargetWindow = function(func, callback) +{ + InjectedScriptAccess.getDefault().evaluateOnSelf(func.toString(), callback); +} + + +WebInspector.AuditRules.GzipRule = function() +{ + WebInspector.AuditRule.call(this, "network-gzip", "Enable gzip compression"); +} + +WebInspector.AuditRules.GzipRule.prototype = { + doRun: function(resources, result, callback) + { + try { + var commonMessage = undefined; + var totalSavings = 0; + var compressedSize = 0 + var candidateSize = 0 + var outputResources = []; + for (var i = 0, length = resources.length; i < length; ++i) { + var resource = resources[i]; + if (this._shouldCompress(resource)) { + var size = resource.contentLength; + candidateSize += size; + if (this._isCompressed(resource)) { + compressedSize += size; + continue; + } + if (!commonMessage) + commonMessage = result.appendChild(""); + var savings = 2 * size / 3; + totalSavings += savings; + outputResources.push( + String.sprintf("Compressing %s could save ~%s", + WebInspector.linkifyURL(resource.url), Number.bytesToString(savings))); + } + } + if (commonMessage) { + commonMessage.value = + String.sprintf("Compressing the following resources with gzip could reduce their " + + "transfer size by about two thirds (~%s):", Number.bytesToString(totalSavings)); + commonMessage.appendChild(WebInspector.AuditRules.arrayAsUL(outputResources)); + result.score = 100 * compressedSize / candidateSize; + result.type = WebInspector.AuditRuleResult.Type.Violation; + } + } catch(e) { + console.log(e); + } finally { + callback(result); + } + }, + + _isCompressed: function(resource) + { + var encoding = resource.responseHeaders["Content-Encoding"]; + return encoding === "gzip" || encoding === "deflate"; + }, + + _shouldCompress: function(resource) + { + return WebInspector.Resource.Type.isTextType(resource.type) && resource.domain && resource.contentLength !== undefined && resource.contentLength > 150; + } +} + +WebInspector.AuditRules.GzipRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.CombineExternalResourcesRule = function(id, name, type, resourceTypeName, parametersObject) +{ + WebInspector.AuditRule.call(this, id, name, parametersObject); + this._type = type; + this._resourceTypeName = resourceTypeName; +} + +WebInspector.AuditRules.CombineExternalResourcesRule.prototype = { + doRun: function(resources, result, callback) + { + try { + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, [this._type], WebInspector.URLRegExp); + var penalizedResourceCount = 0; + // TODO: refactor according to the chosen i18n approach + for (var domain in domainToResourcesMap) { + var domainResources = domainToResourcesMap[domain]; + var extraResourceCount = domainResources.length - this.getValue("AllowedPerDomain"); + if (extraResourceCount <= 0) + continue; + penalizedResourceCount += extraResourceCount - 1; + result.appendChild( + String.sprintf("There are %d %s files served from %s. Consider combining them into as few files as possible.", + domainResources.length, this._resourceTypeName, domain)); + } + result.score = 100 - (penalizedResourceCount * this.getValue("ScorePerResource")); + result.type = WebInspector.AuditRuleResult.Type.Hint; + } catch(e) { + console.log(e); + } finally { + callback(result); + } + } +}; + +WebInspector.AuditRules.CombineExternalResourcesRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.CombineJsResourcesRule = function(parametersObject) { + WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-externaljs", "Combine external JavaScript", WebInspector.Resource.Type.Script, "JS", parametersObject); +} + +WebInspector.AuditRules.CombineJsResourcesRule.prototype.__proto__ = WebInspector.AuditRules.CombineExternalResourcesRule.prototype; + + +WebInspector.AuditRules.CombineCssResourcesRule = function(parametersObject) { + WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-externalcss", "Combine external CSS", WebInspector.Resource.Type.Stylesheet, "CSS", parametersObject); +} + +WebInspector.AuditRules.CombineCssResourcesRule.prototype.__proto__ = WebInspector.AuditRules.CombineExternalResourcesRule.prototype; + + +WebInspector.AuditRules.MinimizeDnsLookupsRule = function(parametersObject) { + WebInspector.AuditRule.call(this, "network-minimizelookups", "Minimize DNS lookups", parametersObject); +} + +WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype = { + doRun: function(resources, result, callback) + { + try { + var violationDomains = []; + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, undefined, WebInspector.URLRegExp); + for (var domain in domainToResourcesMap) { + if (domainToResourcesMap[domain].length > 1) + continue; + var match = domain.match(WebInspector.URLRegExp); + if (!match) + continue; + if (!match[2].search(WebInspector.AuditRules.IPAddressRegexp)) + continue; // an IP address + violationDomains.push(match[2]); + } + if (violationDomains.length <= this.getValue("HostCountThreshold")) + return; + var commonMessage = result.appendChild( + "The following domains only serve one resource each. If possible, avoid the extra DNS " + + "lookups by serving these resources from existing domains."); + commonMessage.appendChild(WebInspector.AuditRules.arrayAsUL(violationDomains)); + result.score = 100 - violationDomains.length * this.getValue("ViolationDomainScore"); + } catch(e) { + console.log(e); + } finally { + callback(result); + } + } +} + +WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.ParallelizeDownloadRule = function(parametersObject) +{ + WebInspector.AuditRule.call(this, "network-parallelizehosts", "Parallelize downloads across hostnames", parametersObject); +} + + +WebInspector.AuditRules.ParallelizeDownloadRule.prototype = { + doRun: function(resources, result, callback) + { + function hostSorter(a, b) + { + var aCount = domainToResourcesMap[a].length; + var bCount = domainToResourcesMap[b].length; + return (aCount < bCount) ? 1 : (aCount == bCount) ? 0 : -1; + } + + try { + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap( + resources, + [WebInspector.Resource.Type.Stylesheet, WebInspector.Resource.Type.Image], + WebInspector.URLRegExp, + true); + + var hosts = []; + for (var url in domainToResourcesMap) + hosts.push(url); + + if (!hosts.length) + return; // no hosts (local file or something) + + hosts.sort(hostSorter); + + var optimalHostnameCount = this.getValue("OptimalHostnameCount"); + if (hosts.length > optimalHostnameCount) + hosts.splice(optimalHostnameCount); + + var busiestHostResourceCount = domainToResourcesMap[hosts[0]].length; + var resourceCountAboveThreshold = busiestHostResourceCount - this.getValue("MinRequestThreshold"); + if (resourceCountAboveThreshold <= 0) + return; + + var avgResourcesPerHost = 0; + for (var i = 0, size = hosts.length; i < size; ++i) + avgResourcesPerHost += domainToResourcesMap[hosts[i]].length; + + // Assume optimal parallelization. + avgResourcesPerHost /= optimalHostnameCount; + + avgResourcesPerHost = Math.max(avgResourcesPerHost, 1); + + var pctAboveAvg = (resourceCountAboveThreshold / avgResourcesPerHost) - 1.0; + + var minBalanceThreshold = this.getValue("MinBalanceThreshold"); + if (pctAboveAvg < minBalanceThreshold) { + result.score = 100; + return; + } + + result.score = (1 - (pctAboveAvg - minBalanceThreshold)) * 100; + result.type = WebInspector.AuditRuleResult.Type.Hint; + + var resourcesOnBusiestHost = domainToResourcesMap[hosts[0]]; + var commonMessage = result.appendChild( + String.sprintf("This page makes %d parallelizable requests to %s" + + ". Increase download parallelization by distributing the following" + + " requests across multiple hostnames.", busiestHostResourceCount, hosts[0])); + var outputResources = []; + for (var i = 0, size = resourcesOnBusiestHost.length; i < size; ++i) + outputResources.push(resourcesOnBusiestHost[i].url); + commonMessage.appendChild(WebInspector.AuditRules.arrayAsUL(outputResources, true)); + } catch(e) { + console.log(e); + } finally { + callback(result); + } + } +} + +WebInspector.AuditRules.ParallelizeDownloadRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +// The reported CSS rule size is incorrect (parsed != original in WebKit), +// so use percentages instead, which gives a better approximation. +WebInspector.AuditRules.UnusedCssRule = function(parametersObject) +{ + WebInspector.AuditRule.call(this, "page-unusedcss", "Remove unused CSS", parametersObject); +} + +WebInspector.AuditRules.UnusedCssRule.prototype = { + _getUnusedStylesheetRatioMessage: function(unusedLength, type, location, styleSheetLength) + { + var url = type === "href" + ? WebInspector.linkifyURL(location) + : String.sprintf("Inline block #%s", location); + var pctUnused = Math.round(unusedLength / styleSheetLength * 100); + return String.sprintf("%s: %f%% (estimated) is not used by the current page.", url, pctUnused); + }, + + _getUnusedTotalRatioMessage: function(unusedLength, totalLength) + { + var pctUnused = Math.round(unusedLength / totalLength * 100); + return String.sprintf("%d%% of CSS (estimated) is not used by the current page.", pctUnused); + }, + + doRun: function(resources, result, callback) + { + var self = this; + function evalCallback(evalResult, isException) { + try { + if (isException) + return; + + var totalLength = 0; + var totalUnusedLength = 0; + var topMessage; + var styleSheetMessage; + for (var i = 0; i < evalResult.length; ) { + var type = evalResult[i++]; + if (type === "totalLength") { + totalLength = evalResult[i++]; + continue; + } + + var styleSheetLength = evalResult[i++]; + var location = evalResult[i++]; + var unusedRules = evalResult[i++]; + styleSheetMessage = undefined; + if (!topMessage) + topMessage = result.appendChild(""); + + var totalUnusedRuleLength = 0; + var ruleSelectors = []; + for (var j = 0; j < unusedRules.length; ++j) { + var rule = unusedRules[j]; + totalUnusedRuleLength += parseInt(rule[1]); + if (!styleSheetMessage) + styleSheetMessage = result.appendChild(""); + ruleSelectors.push(rule[0]); + } + styleSheetMessage.appendChild(WebInspector.AuditRules.arrayAsUL(ruleSelectors)); + + styleSheetMessage.value = self._getUnusedStylesheetRatioMessage(totalUnusedRuleLength, type, location, styleSheetLength); + totalUnusedLength += totalUnusedRuleLength; + } + if (totalUnusedLength) { + var totalUnusedPercent = totalUnusedLength / totalLength; + topMessage.value = self._getUnusedTotalRatioMessage(totalUnusedLength, totalLength); + var pctMultiplier = Math.log(Math.max(200, totalUnusedLength - 800)) / 7 - 0.6; + result.score = (1 - totalUnusedPercent * pctMultiplier) * 100; + result.type = WebInspector.AuditRuleResult.Type.Hint; + } else + result.score = 100; + } catch(e) { + console.log(e); + } finally { + callback(result); + } + } + + function routine() + { + var styleSheets = document.styleSheets; + if (!styleSheets) + return {}; + var styleSheetToUnusedRules = []; + var inlineBlockOrdinal = 0; + var totalCSSLength = 0; + var pseudoSelectorRegexp = /:hover|:link|:active|:visited|:focus/; + for (var i = 0; i < styleSheets.length; ++i) { + var styleSheet = styleSheets[i]; + if (!styleSheet.cssRules) + continue; + var currentStyleSheetSize = 0; + var unusedRules = []; + for (var curRule = 0; curRule < styleSheet.cssRules.length; ++curRule) { + var rule = styleSheet.cssRules[curRule]; + var textLength = rule.cssText ? rule.cssText.length : 0; + currentStyleSheetSize += textLength; + totalCSSLength += textLength; + if (rule.type !== 1 || rule.selectorText.match(pseudoSelectorRegexp)) + continue; + var nodes = document.querySelectorAll(rule.selectorText); + if (nodes && nodes.length) + continue; + unusedRules.push([rule.selectorText, textLength]); + } + if (unusedRules.length) { + styleSheetToUnusedRules.push(styleSheet.href ? "href" : "inline"); + styleSheetToUnusedRules.push(currentStyleSheetSize); + styleSheetToUnusedRules.push(styleSheet.href ? styleSheet.href : ++inlineBlockOrdinal); + styleSheetToUnusedRules.push(unusedRules); + } + } + styleSheetToUnusedRules.push("totalLength"); + styleSheetToUnusedRules.push(totalCSSLength); + return styleSheetToUnusedRules; + } + + WebInspector.AuditRules.evaluateInTargetWindow(routine, evalCallback); + } +} + +WebInspector.AuditRules.UnusedCssRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.CacheControlRule = function(id, name, parametersObject) +{ + WebInspector.AuditRule.call(this, id, name, parametersObject); +} + +WebInspector.AuditRules.CacheControlRule.MillisPerMonth = 1000 * 60 * 60 * 24 * 30; + +WebInspector.AuditRules.CacheControlRule.prototype = { + + InfoCheck: -1, + FailCheck: 0, + WarningCheck: 1, + SevereCheck: 2, + + doRun: function(resources, result, callback) + { + try { + var cacheableAndNonCacheableResources = this._cacheableAndNonCacheableResources(resources); + if (cacheableAndNonCacheableResources[0].length) { + result.score = 100; + this.runChecks(cacheableAndNonCacheableResources[0], result); + } + this.handleNonCacheableResources(cacheableAndNonCacheableResources[1], result); + } catch(e) { + console.log(e); + } finally { + callback(result); + } + }, + + handleNonCacheableResources: function() + { + }, + + _cacheableAndNonCacheableResources: function(resources) + { + var processedResources = [[], []]; + for (var i = 0; i < resources.length; ++i) { + var resource = resources[i]; + if (!this.isCacheableResource(resource)) + continue; + if (this._isExplicitlyNonCacheable(resource)) + processedResources[1].push(resource); + else + processedResources[0].push(resource); + } + return processedResources; + }, + + execCheck: function(messageText, resourceCheckFunction, resources, severity, result) + { + var topMessage; + var failingResources = 0; + var resourceCount = resources.length; + var outputResources = []; + for (var i = 0; i < resourceCount; ++i) { + if (resourceCheckFunction.call(this, resources[i])) { + ++failingResources; + if (!topMessage) + topMessage = result.appendChild(messageText); + outputResources.push(resources[i].url); + } + } + if (topMessage) + topMessage.appendChild(WebInspector.AuditRules.arrayAsUL(outputResources, true)); + if (failingResources) { + switch (severity) { + case this.FailCheck: + result.score = 0; + result.type = WebInspector.AuditRuleResult.Type.Violation; + break; + case this.SevereCheck: + case this.WarningCheck: + result.score -= 50 * severity * failingResources / resourceCount; + result.type = WebInspector.AuditRuleResult.Type.Hint; + break; + } + } + return topMessage; + }, + + freshnessLifetimeGreaterThan: function(resource, timeMs) + { + var dateHeader = this.responseHeader(resource, "Date"); + if (!dateHeader) + return false; + + var dateHeaderMs = Date.parse(dateHeader); + if (isNaN(dateHeaderMs)) + return false; + + var freshnessLifetimeMs; + var maxAgeMatch = this.responseHeaderMatch(resource, "Cache-Control", "max-age=(\\d+)"); + + if (maxAgeMatch) + freshnessLifetimeMs = (maxAgeMatch[1]) ? 1000 * maxAgeMatch[1] : 0; + else { + var expiresHeader = this.responseHeader(resource, "Expires"); + if (expiresHeader) { + var expDate = Date.parse(expiresHeader); + if (!isNaN(expDate)) + freshnessLifetimeMs = expDate - dateHeaderMs; + } + } + + return (isNaN(freshnessLifetimeMs)) ? false : freshnessLifetimeMs > timeMs; + }, + + responseHeader: function(resource, header) + { + return resource.responseHeaders[header]; + }, + + hasResponseHeader: function(resource, header) + { + return resource.responseHeaders[header] !== undefined; + }, + + isCompressible: function(resource) + { + return WebInspector.Resource.Type.isTextType(resource.type); + }, + + isPubliclyCacheable: function(resource) + { + if (this._isExplicitlyNonCacheable(resource)) + return false; + + if (this.responseHeaderMatch(resource, "Cache-Control", "public")) + return true; + + return resource.url.indexOf("?") == -1 && !this.responseHeaderMatch(resource, "Cache-Control", "private"); + }, + + responseHeaderMatch: function(resource, header, regexp) + { + return resource.responseHeaders[header] + ? resource.responseHeaders[header].match(new RegExp(regexp, "im")) + : undefined; + }, + + hasExplicitExpiration: function(resource) + { + return this.hasResponseHeader(resource, "Date") && + (this.hasResponseHeader(resource, "Expires") || this.responseHeaderMatch(resource, "Cache-Control", "max-age")); + }, + + _isExplicitlyNonCacheable: function(resource) + { + var hasExplicitExp = this.hasExplicitExpiration(resource); + return this.responseHeaderMatch(resource, "Cache-Control", "(no-cache|no-store|must-revalidate)") || + this.responseHeaderMatch(resource, "Pragma", "no-cache") || + (hasExplicitExp && !this.freshnessLifetimeGreaterThan(resource, 0)) || + (!hasExplicitExp && resource.url && resource.url.indexOf("?") >= 0) || + (!hasExplicitExp && !this.isCacheableResource(resource)); + }, + + isCacheableResource: function(resource) + { + return resource.statusCode !== undefined && WebInspector.AuditRules.CacheableResponseCodes[resource.statusCode]; + } +} + +WebInspector.AuditRules.CacheControlRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.BrowserCacheControlRule = function(parametersObject) +{ + WebInspector.AuditRules.CacheControlRule.call(this, "http-browsercache", "Leverage browser caching", parametersObject); +} + +WebInspector.AuditRules.BrowserCacheControlRule.prototype = { + handleNonCacheableResources: function(resources, result) + { + if (resources.length) { + var message = result.appendChild( + "The following resources are explicitly non-cacheable. Consider making them cacheable if possible:"); + var resourceOutput = []; + for (var i = 0; i < resources.length; ++i) + resourceOutput.push(resources[i].url); + message.appendChild(WebInspector.AuditRules.arrayAsUL(resourceOutput, true)); + } + }, + + runChecks: function(resources, result, callback) + { + this.execCheck( + "The following resources are missing a cache expiration." + + " Resources that do not specify an expiration may not be" + + " cached by browsers:", + this._missingExpirationCheck, resources, this.SevereCheck, result); + this.execCheck( + "The following resources specify a \"Vary\" header that" + + " disables caching in most versions of Internet Explorer:", + this._varyCheck, resources, this.SevereCheck, result); + this.execCheck( + "The following cacheable resources have a short" + + " freshness lifetime:", + this._oneMonthExpirationCheck, resources, this.WarningCheck, result); + + // Unable to implement the favicon check due to the WebKit limitations. + + this.execCheck( + "To further improve cache hit rate, specify an expiration" + + " one year in the future for the following cacheable" + + " resources:", + this._oneYearExpirationCheck, resources, this.InfoCheck, result); + }, + + _missingExpirationCheck: function(resource) + { + return this.isCacheableResource(resource) && !this.hasResponseHeader(resource, "Set-Cookie") && !this.hasExplicitExpiration(resource); + }, + + _varyCheck: function(resource) + { + var varyHeader = this.responseHeader(resource, "Vary"); + if (varyHeader) { + varyHeader = varyHeader.replace(/User-Agent/gi, ""); + varyHeader = varyHeader.replace(/Accept-Encoding/gi, ""); + varyHeader = varyHeader.replace(/[, ]*/g, ""); + } + return varyHeader && varyHeader.length && this.isCacheableResource(resource) && this.freshnessLifetimeGreaterThan(resource, 0); + }, + + _oneMonthExpirationCheck: function(resource) + { + return this.isCacheableResource(resource) && + !this.hasResponseHeader(resource, "Set-Cookie") && + !this.freshnessLifetimeGreaterThan(resource, WebInspector.AuditRules.CacheControlRule.MillisPerMonth) && + this.freshnessLifetimeGreaterThan(resource, 0); + }, + + _oneYearExpirationCheck: function(resource) + { + return this.isCacheableResource(resource) && + !this.hasResponseHeader(resource, "Set-Cookie") && + !this.freshnessLifetimeGreaterThan(resource, 11 * WebInspector.AuditRules.CacheControlRule.MillisPerMonth) && + this.freshnessLifetimeGreaterThan(resource, WebInspector.AuditRules.CacheControlRule.MillisPerMonth); + } +} + +WebInspector.AuditRules.BrowserCacheControlRule.prototype.__proto__ = WebInspector.AuditRules.CacheControlRule.prototype; + + +WebInspector.AuditRules.ProxyCacheControlRule = function(parametersObject) { + WebInspector.AuditRules.CacheControlRule.call(this, "http-proxycache", "Leverage proxy caching", parametersObject); +} + +WebInspector.AuditRules.ProxyCacheControlRule.prototype = { + runChecks: function(resources, result, callback) + { + this.execCheck( + "Resources with a \"?\" in the URL are not cached by most" + + " proxy caching servers:", + this._questionMarkCheck, resources, this.WarningCheck, result); + this.execCheck( + "Consider adding a \"Cache-Control: public\" header to the" + + " following resources:", + this._publicCachingCheck, resources, this.InfoCheck, result); + this.execCheck( + "The following publicly cacheable resources contain" + + " a Set-Cookie header. This security vulnerability" + + " can cause cookies to be shared by multiple users.", + this._setCookieCacheableCheck, resources, this.FailCheck, result); + }, + + _questionMarkCheck: function(resource) + { + return resource.url.indexOf("?") >= 0 && !this.hasResponseHeader(resource, "Set-Cookie") && this.isPubliclyCacheable(resource); + }, + + _publicCachingCheck: function(resource) + { + return this.isCacheableResource(resource) && + !this.isCompressible(resource) && + !this.responseHeaderMatch(resource, "Cache-Control", "public") && + !this.hasResponseHeader(resource, "Set-Cookie"); + }, + + _setCookieCacheableCheck: function(resource) + { + return this.hasResponseHeader(resource, "Set-Cookie") && this.isPubliclyCacheable(resource); + } +} + +WebInspector.AuditRules.ProxyCacheControlRule.prototype.__proto__ = WebInspector.AuditRules.CacheControlRule.prototype; + + +WebInspector.AuditRules.ImageDimensionsRule = function(parametersObject) +{ + WebInspector.AuditRule.call(this, "page-imagedims", "Specify image dimensions", parametersObject); +} + +WebInspector.AuditRules.ImageDimensionsRule.prototype = { + doRun: function(resources, result, callback) + { + function evalCallback(evalResult, isException) + { + try { + if (isException) + return; + if (!evalResult || !evalResult.totalImages) + return; + result.score = 100; + var topMessage = result.appendChild( + "A width and height should be specified for all images in order to " + + "speed up page display. The following image(s) are missing a width and/or height:"); + var map = evalResult.map; + var outputResources = []; + for (var url in map) { + var value = WebInspector.linkifyURL(url); + if (map[url] > 1) + value += " (" + map[url] + " uses)"; + outputResources.push(value); + result.score -= this.getValue("ScorePerImageUse") * map[url]; + result.type = WebInspector.AuditRuleResult.Type.Hint; + } + topMessage.appendChild(WebInspector.AuditRules.arrayAsUL(outputResources)); + } catch(e) { + console.log(e); + } finally { + callback(result); + } + } + + function routine() + { + var images = document.getElementsByTagName("img"); + const widthRegExp = /width[^:;]*:/gim; + const heightRegExp = /height[^:;]*:/gim; + + function hasDimension(element, cssText, rules, regexp, attributeName) { + if (element.attributes.getNamedItem(attributeName) != null || (cssText && cssText.match(regexp))) + return true; + + if (!rules) + return false; + for (var i = 0; i < rules.length; ++i) { + if (rules.item(i).style.cssText.match(regexp)) + return true; + } + return false; + } + + function hasWidth(element, cssText, rules) { + return hasDimension(element, cssText, rules, widthRegExp, "width"); + } + + function hasHeight(element, cssText, rules) { + return hasDimension(element, cssText, rules, heightRegExp, "height"); + } + + var urlToNoDimensionCount = {}; + var found = false; + for (var i = 0; i < images.length; ++i) { + var image = images[i]; + if (!image.src) + continue; + var position = document.defaultView.getComputedStyle(image).getPropertyValue("position"); + if (position === "absolute") + continue; + var cssText = (image.style && image.style.cssText) ? image.style.cssText : ""; + var rules = document.defaultView.getMatchedCSSRules(image, "", true); + if (!hasWidth(image, cssText, rules) || !hasHeight(image, cssText, rules)) { + found = true; + if (urlToNoDimensionCount.hasOwnProperty(image.src)) + ++urlToNoDimensionCount[image.src]; + else + urlToNoDimensionCount[image.src] = 1; + } + } + return found ? {totalImages: images.length, map: urlToNoDimensionCount} : null; + } + + WebInspector.AuditRules.evaluateInTargetWindow(routine, evalCallback.bind(this)); + } +} + +WebInspector.AuditRules.ImageDimensionsRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.CssInHeadRule = function(parametersObject) +{ + WebInspector.AuditRule.call(this, "page-cssinhead", "Put CSS in the document head", parametersObject); +} + +WebInspector.AuditRules.CssInHeadRule.prototype = { + doRun: function(resources, result, callback) + { + function evalCallback(evalResult, isException) + { + try { + if (isException) + return; + if (!evalResult) + return; + result.score = 100; + var outputMessages = []; + for (var url in evalResult) { + var urlViolations = evalResult[url]; + var topMessage = result.appendChild( + String.sprintf("CSS in the %s document body adversely impacts rendering performance.", + WebInspector.linkifyURL(url))); + if (urlViolations[0]) { + outputMessages.push( + String.sprintf("%s style block(s) in the body should be moved to the document head.", urlViolations[0])); + result.score -= this.getValue("InlineURLScore") * urlViolations[0]; + } + for (var i = 0; i < urlViolations[1].length; ++i) { + outputMessages.push( + String.sprintf("Link node %s should be moved to the document head", WebInspector.linkifyURL(urlViolations[1]))); + } + result.score -= this.getValue("InlineStylesheetScore") * urlViolations[1]; + result.type = WebInspector.AuditRuleResult.Type.Hint; + } + topMessage.appendChild(WebInspector.AuditRules.arrayAsUL(outputMessages)); + } catch(e) { + console.log(e); + } finally { + callback(result); + } + } + + function routine() + { + function allViews() { + var views = [document.defaultView]; + var curView = 0; + while (curView < views.length) { + var view = views[curView]; + var frames = view.frames; + for (var i = 0; i < frames.length; ++i) { + if (frames[i] !== view) + views.push(frames[i]); + } + ++curView; + } + return views; + } + + var views = allViews(); + var urlToViolationsArray = {}; + var found = false; + for (var i = 0; i < views.length; ++i) { + var view = views[i]; + if (!view.document) + continue; + + var inlineStyles = view.document.querySelectorAll("body style"); + var inlineStylesheets = view.document.querySelectorAll( + "body link[rel~='stylesheet'][href]"); + if (!inlineStyles.length && !inlineStylesheets.length) + continue; + + found = true; + var inlineStylesheetHrefs = []; + for (var j = 0; j < inlineStylesheets.length; ++j) + inlineStylesheetHrefs.push(inlineStylesheets[j].href); + + urlToViolationsArray[view.location.href] = + [inlineStyles.length, inlineStylesheetHrefs]; + } + return found ? urlToViolationsArray : null; + } + + WebInspector.AuditRules.evaluateInTargetWindow(routine, evalCallback); + } +} + +WebInspector.AuditRules.CssInHeadRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.StylesScriptsOrderRule = function(parametersObject) +{ + WebInspector.AuditRule.call(this, "page-stylescriptorder", "Optimize the order of styles and scripts", parametersObject); +} + +WebInspector.AuditRules.StylesScriptsOrderRule.prototype = { + doRun: function(resources, result, callback) + { + function evalCallback(evalResult, isException) + { + try { + if (isException) + return; + if (!evalResult) + return; + + result.score = 100; + var lateCssUrls = evalResult['late']; + if (lateCssUrls) { + var lateMessage = result.appendChild( + 'The following external CSS files were included after ' + + 'an external JavaScript file in the document head. To ' + + 'ensure CSS files are downloaded in parallel, always ' + + 'include external CSS before external JavaScript.'); + lateMessage.appendChild(WebInspector.AuditRules.arrayAsUL(lateCssUrls, true)); + result.score -= this.getValue("InlineBetweenResourcesScore") * lateCssUrls.length; + result.type = WebInspector.AuditRuleResult.Type.Violation; + } + if (evalResult['cssBeforeInlineCount']) { + var count = evalResult['cssBeforeInlineCount']; + result.appendChild(count + ' inline script block' + + (count > 1 ? 's were' : ' was') + ' found in the head between an ' + + 'external CSS file and another resource. To allow parallel ' + + 'downloading, move the inline script before the external CSS ' + + 'file, or after the next resource.'); + result.score -= this.getValue("CSSAfterJSURLScore") * count; + result.type = WebInspector.AuditRuleResult.Type.Violation; + } + } catch(e) { + console.log(e); + } finally { + callback(result); + } + } + + function routine() + { + var lateStyles = document.querySelectorAll( + "head script[src] ~ link[rel~='stylesheet'][href]"); + var stylesBeforeInlineScript = document.querySelectorAll( + "head link[rel~='stylesheet'][href] ~ script:not([src])"); + + var resultObject; + if (!lateStyles.length && !stylesBeforeInlineScript.length) + resultObject = null; + else { + resultObject = {}; + if (lateStyles.length) { + lateStyleUrls = []; + for (var i = 0; i < lateStyles.length; ++i) + lateStyleUrls.push(lateStyles[i].href); + resultObject["late"] = lateStyleUrls; + } + resultObject["cssBeforeInlineCount"] = stylesBeforeInlineScript.length; + } + return resultObject; + } + + WebInspector.AuditRules.evaluateInTargetWindow(routine, evalCallback.bind(this)); + } +} + +WebInspector.AuditRules.StylesScriptsOrderRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.CookieRuleBase = function(id, name, parametersObject) +{ + WebInspector.AuditRule.call(this, id, name, parametersObject); +} + +WebInspector.AuditRules.CookieRuleBase.prototype = { + doRun: function(resources, result, callback) + { + var self = this; + function resultCallback(receivedCookies, isAdvanced) { + try { + self.processCookies(isAdvanced ? receivedCookies : [], resources, result); + } catch(e) { + console.log(e); + } finally { + callback(result); + } + } + WebInspector.Cookies.getCookiesAsync(resultCallback); + }, + + mapResourceCookies: function(resourcesByDomain, allCookies, callback) + { + for (var i = 0; i < allCookies.length; ++i) { + for (var resourceDomain in resourcesByDomain) { + if (WebInspector.Cookies.cookieDomainMatchesResourceDomain(allCookies[i].domain, resourceDomain)) + this._callbackForResourceCookiePairs(resourcesByDomain[resourceDomain], allCookies[i], callback); + } + } + }, + + _callbackForResourceCookiePairs: function(resources, cookie, callback) + { + if (!resources) + return; + for (var i = 0; i < resources.length; ++i) { + if (WebInspector.Cookies.cookieMatchesResourceURL(cookie, resources[i].url)) + callback(resources[i], cookie); + } + } +} + +WebInspector.AuditRules.CookieRuleBase.prototype.__proto__ = WebInspector.AuditRule.prototype; + + +WebInspector.AuditRules.CookieSizeRule = function(parametersObject) +{ + WebInspector.AuditRules.CookieRuleBase.call(this, "http-cookiesize", "Minimize cookie size", parametersObject); +} + +WebInspector.AuditRules.CookieSizeRule.prototype = { + _average: function(cookieArray) + { + var total = 0; + for (var i = 0; i < cookieArray.length; ++i) + total += cookieArray[i].size; + return cookieArray.length ? Math.round(total / cookieArray.length) : 0; + }, + + _max: function(cookieArray) + { + var result = 0; + for (var i = 0; i < cookieArray.length; ++i) + result = Math.max(cookieArray[i].size, result); + return result; + }, + + processCookies: function(allCookies, resources, result) + { + function maxSizeSorter(a, b) + { + return b.maxCookieSize - a.maxCookieSize; + } + + function avgSizeSorter(a, b) + { + return b.avgCookieSize - a.avgCookieSize; + } + + var cookiesPerResourceDomain = {}; + + function collectorCallback(resource, cookie) + { + var cookies = cookiesPerResourceDomain[resource.domain]; + if (!cookies) { + cookies = []; + cookiesPerResourceDomain[resource.domain] = cookies; + } + cookies.push(cookie); + } + + if (!allCookies.length) + return; + + var sortedCookieSizes = []; + + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, + null, + WebInspector.URLRegExp, + true); + var matchingResourceData = {}; + this.mapResourceCookies(domainToResourcesMap, allCookies, collectorCallback.bind(this)); + + result.score = 100; + for (var resourceDomain in cookiesPerResourceDomain) { + var cookies = cookiesPerResourceDomain[resourceDomain]; + sortedCookieSizes.push({ + domain: resourceDomain, + avgCookieSize: this._average(cookies), + maxCookieSize: this._max(cookies) + }); + } + var avgAllCookiesSize = this._average(allCookies); + + var hugeCookieDomains = []; + sortedCookieSizes.sort(maxSizeSorter); + + var maxBytesThreshold = this.getValue("MaxBytesThreshold"); + var minBytesThreshold = this.getValue("MinBytesThreshold"); + + for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) { + var maxCookieSize = sortedCookieSizes[i].maxCookieSize; + if (maxCookieSize > maxBytesThreshold) + hugeCookieDomains.push(sortedCookieSizes[i].domain + ": " + Number.bytesToString(maxCookieSize)); + } + + var bigAvgCookieDomains = []; + sortedCookieSizes.sort(avgSizeSorter); + for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) { + var domain = sortedCookieSizes[i].domain; + var avgCookieSize = sortedCookieSizes[i].avgCookieSize; + if (avgCookieSize > minBytesThreshold && avgCookieSize < maxBytesThreshold) + bigAvgCookieDomains.push(domain + ": " + Number.bytesToString(avgCookieSize)); + } + result.appendChild("The average cookie size for all requests on this page is " + Number.bytesToString(avgAllCookiesSize)); + + var message; + if (hugeCookieDomains.length) { + result.score = 75; + result.type = WebInspector.AuditRuleResult.Type.Violation; + message = result.appendChild( + String.sprintf("The following domains have a cookie size in excess of %d " + + " bytes. This is harmful because requests with cookies larger than 1KB" + + " typically cannot fit into a single network packet.", maxBytesThreshold)); + message.appendChild(WebInspector.AuditRules.arrayAsUL(hugeCookieDomains)); + } + + if (bigAvgCookieDomains.length) { + this.score -= Math.max(0, avgAllCookiesSize - minBytesThreshold) / + (minBytesThreshold - minBytesThreshold) / this.getValue("TotalPoints"); + if (!result.type) + result.type = WebInspector.AuditRuleResult.Type.Hint; + message = result.appendChild( + String.sprintf("The following domains have an average cookie size in excess of %d" + + " bytes. Reducing the size of cookies" + + " for these domains can reduce the time it takes to send requests.", minBytesThreshold)); + message.appendChild(WebInspector.AuditRules.arrayAsUL(bigAvgCookieDomains)); + } + + if (!bigAvgCookieDomains.length && !hugeCookieDomains.length) + result.score = WebInspector.AuditCategoryResult.ScoreNA; + } +} + +WebInspector.AuditRules.CookieSizeRule.prototype.__proto__ = WebInspector.AuditRules.CookieRuleBase.prototype; + + +WebInspector.AuditRules.StaticCookielessRule = function(parametersObject) +{ + WebInspector.AuditRules.CookieRuleBase.call(this, "http-staticcookieless", "Serve static content from a cookieless domain", parametersObject); +} + +WebInspector.AuditRules.StaticCookielessRule.prototype = { + processCookies: function(allCookies, resources, result) + { + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, + [WebInspector.Resource.Type.Stylesheet, + WebInspector.Resource.Type.Image], + WebInspector.URLRegExp, + true); + var totalStaticResources = 0; + var minResources = this.getValue("MinResources"); + for (var domain in domainToResourcesMap) + totalStaticResources += domainToResourcesMap[domain].length; + if (totalStaticResources < minResources) + return; + var matchingResourceData = {}; + this.mapResourceCookies(domainToResourcesMap, allCookies, this._collectorCallback.bind(this, matchingResourceData)); + + var badUrls = []; + var cookieBytes = 0; + for (var url in matchingResourceData) { + badUrls.push(url); + cookieBytes += matchingResourceData[url] + } + if (badUrls.length < minResources) + return; + + result.score = 100; + var badPoints = cookieBytes / 75; + var violationPct = Math.max(badUrls.length / totalStaticResources, 0.6); + badPoints *= violationPct; + result.score -= badPoints; + result.score = Math.max(result.score, 0); + result.type = WebInspector.AuditRuleResult.Type.Violation; + result.appendChild(String.sprintf("%s of cookies were sent with the following static resources.", Number.bytesToString(cookieBytes))); + var message = result.appendChild("Serve these static resources from a domain that does not set cookies:"); + message.appendChild(WebInspector.AuditRules.arrayAsUL(badUrls, true)); + }, + + _collectorCallback: function(matchingResourceData, resource, cookie) + { + matchingResourceData[resource.url] = (matchingResourceData[resource.url] || 0) + cookie.size; + } +} + +WebInspector.AuditRules.StaticCookielessRule.prototype.__proto__ = WebInspector.AuditRules.CookieRuleBase.prototype; diff --git a/WebCore/inspector/front-end/AuditsPanel.js b/WebCore/inspector/front-end/AuditsPanel.js index 696d132..fcadb82 100644 --- a/WebCore/inspector/front-end/AuditsPanel.js +++ b/WebCore/inspector/front-end/AuditsPanel.js @@ -105,7 +105,8 @@ WebInspector.AuditsPanel.prototype = { this._auditCategoriesById = {}; for (var categoryCtorID in WebInspector.AuditCategories) { var auditCategory = new WebInspector.AuditCategories[categoryCtorID](); - this.categoriesById[auditCategory.id] = auditCategory; + auditCategory._id = categoryCtorID; + this.categoriesById[categoryCtorID] = auditCategory; } }, @@ -185,15 +186,13 @@ WebInspector.AuditsPanel.prototype = { _reloadResources: function(callback) { - function nullCallback() - { - } this._resourceTrackingCallback = callback; + if (!InspectorBackend.resourceTrackingEnabled()) { InspectorBackend.enableResourceTracking(false); - this._updateLauncherViewControls(); + this._updateLauncherViewControls(true); } else - InjectedScriptAccess.getDefault().evaluate("window.location.reload()", nullCallback); + InjectedScriptAccess.getDefault().evaluate("window.location.reload()", switchCallback); }, _didMainResourceLoad: function() @@ -239,7 +238,7 @@ WebInspector.AuditsPanel.prototype = { WebInspector.Panel.prototype.show.call(this); this.showView(); - this._updateLauncherViewControls(); + this._updateLauncherViewControls(InspectorBackend.resourceTrackingEnabled()); }, attach: function() @@ -254,10 +253,10 @@ WebInspector.AuditsPanel.prototype = { this.viewsContainerElement.style.left = width + "px"; }, - _updateLauncherViewControls: function() + _updateLauncherViewControls: function(isTracking) { if (this._launcherView) - this._launcherView.updateResourceTrackingState(); + this._launcherView.updateResourceTrackingState(isTracking); }, _clearButtonClicked: function() @@ -278,9 +277,8 @@ WebInspector.AuditsPanel.prototype.__proto__ = WebInspector.Panel.prototype; -WebInspector.AuditCategory = function(id, displayName) +WebInspector.AuditCategory = function(displayName) { - this._id = id; this._displayName = displayName; this._rules = []; } @@ -288,6 +286,7 @@ WebInspector.AuditCategory = function(id, displayName) WebInspector.AuditCategory.prototype = { get id() { + // this._id value is injected at construction time. return this._id; }, @@ -298,6 +297,7 @@ WebInspector.AuditCategory.prototype = { get ruleCount() { + this._ensureInitialized(); return this._rules.length; }, @@ -308,8 +308,18 @@ WebInspector.AuditCategory.prototype = { runRules: function(resources, callback) { + this._ensureInitialized(); for (var i = 0; i < this._rules.length; ++i) this._rules[i].run(resources, callback); + }, + + _ensureInitialized: function() + { + if (!this._initialized) { + if ("initialize" in this) + this.initialize(); + this._initialized = true; + } } } @@ -354,7 +364,6 @@ WebInspector.AuditRule.prototype = { WebInspector.AuditCategoryResult = function(category) { - this.categoryId = category.id; this.title = category.displayName; this.entries = []; } @@ -378,8 +387,13 @@ WebInspector.AuditRuleResult = function(value) } WebInspector.AuditRuleResult.Type = { + // Does not denote a discovered flaw but rather represents an informational message. NA: 0, + + // Denotes a minor impact on the checked metric. Hint: 1, + + // Denotes a major impact on the checked metric. Violation: 2 } diff --git a/WebCore/inspector/front-end/CookieItemsView.js b/WebCore/inspector/front-end/CookieItemsView.js index b31b7ea..b5674b8 100644 --- a/WebCore/inspector/front-end/CookieItemsView.js +++ b/WebCore/inspector/front-end/CookieItemsView.js @@ -27,7 +27,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -WebInspector.CookieItemsView = function(cookieDomain) +WebInspector.CookieItemsView = function(treeElement, cookieDomain) { WebInspector.View.call(this); @@ -41,7 +41,13 @@ WebInspector.CookieItemsView = function(cookieDomain) this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item"); this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false); + this._treeElement = treeElement; this._cookieDomain = cookieDomain; + + this._emptyMsgElement = document.createElement("div"); + this._emptyMsgElement.className = "storage-table-empty"; + this._emptyMsgElement.textContent = WebInspector.UIString("This site has no cookies."); + this.element.appendChild(this._emptyMsgElement); } WebInspector.CookieItemsView.prototype = { @@ -53,7 +59,7 @@ WebInspector.CookieItemsView.prototype = { show: function(parentElement) { WebInspector.View.prototype.show.call(this, parentElement); - this.update(); + this._update(); }, hide: function() @@ -62,37 +68,57 @@ WebInspector.CookieItemsView.prototype = { this.deleteButton.visible = false; }, - update: function() + _update: function() { - this.element.removeChildren(); - - var self = this; - function callback(allCookies, isAdvanced) { - var cookies = self._cookiesForDomain(allCookies); - var dataGrid = (isAdvanced ? self.dataGridForCookies(cookies) : self.simpleDataGridForCookies(cookies)); - if (dataGrid) { - self._dataGrid = dataGrid; - self.element.appendChild(dataGrid.element); - self._dataGrid.updateWidths(); - if (isAdvanced) - self.deleteButton.visible = true; + WebInspector.Cookies.getCookiesAsync(this._updateWithCookies.bind(this)); + }, + + _updateWithCookies: function(allCookies, isAdvanced) + { + if (isAdvanced) + this._filterCookiesForDomain(allCookies); + else + this._cookies = allCookies; + + if (!this._cookies.length) { + // Nothing to show. + this._emptyMsgElement.removeStyleClass("hidden"); + this.deleteButton.visible = false; + if (this._dataGrid) + this._dataGrid.element.addStyleClass("hidden"); + return; + } + + if (!this._dataGrid) { + if (isAdvanced) { + this._createDataGrid(); + this._populateDataGrid(); + this._dataGrid.autoSizeColumns(6, 33); + this._treeElement.subtitle = String.sprintf(WebInspector.UIString("%d cookies (%s)"), this._cookies.length, + Number.bytesToString(this._totalSize, WebInspector.UIString)); } else { - var emptyMsgElement = document.createElement("div"); - emptyMsgElement.className = "storage-table-empty"; - emptyMsgElement.textContent = WebInspector.UIString("This site has no cookies."); - self.element.appendChild(emptyMsgElement); - self._dataGrid = null; - self.deleteButton.visible = false; + this._createSimpleDataGrid(); + this._populateSimpleDataGrid(); + this._dataGrid.autoSizeColumns(20, 80); } + } else { + if (isAdvanced) + this._populateDataGrid(); + else + this._populateSimpleDataGrid(); } - WebInspector.Cookies.getCookiesAsync(callback); + this._dataGrid.element.removeStyleClass("hidden"); + this._emptyMsgElement.addStyleClass("hidden"); + if (isAdvanced) + this.deleteButton.visible = true; }, - _cookiesForDomain: function(allCookies) + _filterCookiesForDomain: function(allCookies) { - var cookiesForDomain = []; + this._cookies = []; var resourceURLsForDocumentURL = []; + this._totalSize = 0; for (var id in WebInspector.resources) { var resource = WebInspector.resources[id]; @@ -102,179 +128,151 @@ WebInspector.CookieItemsView.prototype = { } for (var i = 0; i < allCookies.length; ++i) { + var pushed = false; + var size = allCookies[i].size; for (var j = 0; j < resourceURLsForDocumentURL.length; ++j) { var resourceURL = resourceURLsForDocumentURL[j]; if (WebInspector.Cookies.cookieMatchesResourceURL(allCookies[i], resourceURL)) { - cookiesForDomain.push(allCookies[i]); - break; + this._totalSize += size; + if (!pushed) { + pushed = true; + this._cookies.push(allCookies[i]); + } } } } - return cookiesForDomain; }, - dataGridForCookies: function(cookies) + _createDataGrid: function() { - if (!cookies.length) - return null; - - for (var i = 0; i < cookies.length; ++i) - cookies[i].expires = new Date(cookies[i].expires); - var columns = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {} }; columns[0].title = WebInspector.UIString("Name"); - columns[0].width = columns[0].title.length; + columns[0].sortable = true; columns[1].title = WebInspector.UIString("Value"); - columns[1].width = columns[1].title.length; + columns[1].sortable = true; columns[2].title = WebInspector.UIString("Domain"); - columns[2].width = columns[2].title.length; + columns[2].sortable = true; columns[3].title = WebInspector.UIString("Path"); - columns[3].width = columns[3].title.length; + columns[3].sortable = true; columns[4].title = WebInspector.UIString("Expires"); - columns[4].width = columns[4].title.length; + columns[4].sortable = true; columns[5].title = WebInspector.UIString("Size"); - columns[5].width = columns[5].title.length; columns[5].aligned = "right"; + columns[5].sortable = true; columns[6].title = WebInspector.UIString("HTTP"); - columns[6].width = columns[6].title.length; columns[6].aligned = "centered"; + columns[6].sortable = true; columns[7].title = WebInspector.UIString("Secure"); - columns[7].width = columns[7].title.length; columns[7].aligned = "centered"; + columns[7].sortable = true; - function updateDataAndColumn(index, value) { - data[index] = value; - if (value.length > columns[index].width) - columns[index].width = value.length; - } + this._dataGrid = new WebInspector.DataGrid(columns, null, this._deleteCookieCallback.bind(this)); + this._dataGrid.addEventListener("sorting changed", this._populateDataGrid, this); + this.element.appendChild(this._dataGrid.element); + this._dataGrid.updateWidths(); + }, - var data; - var nodes = []; - for (var i = 0; i < cookies.length; ++i) { - var cookie = cookies[i]; - data = {}; - - updateDataAndColumn(0, cookie.name); - updateDataAndColumn(1, cookie.value); - updateDataAndColumn(2, cookie.domain); - updateDataAndColumn(3, cookie.path); - updateDataAndColumn(4, (cookie.session ? WebInspector.UIString("Session") : cookie.expires.toGMTString())); - updateDataAndColumn(5, Number.bytesToString(cookie.size, WebInspector.UIString)); - updateDataAndColumn(6, (cookie.httpOnly ? "\u2713" : "")); // Checkmark - updateDataAndColumn(7, (cookie.secure ? "\u2713" : "")); // Checkmark + _populateDataGrid: function() + { + var selectedCookie = this._dataGrid.selectedNode ? this._dataGrid.selectedNode.cookie : null; + var sortDirection = this._dataGrid.sortOrder === "ascending" ? 1 : -1; - var node = new WebInspector.DataGridNode(data, false); - node.cookie = cookie; - node.selectable = true; - nodes.push(node); + function localeCompare(field, cookie1, cookie2) + { + return sortDirection * (cookie1[field] + "").localeCompare(cookie2[field] + "") } - var totalColumnWidths = 0; - for (var columnIdentifier in columns) - totalColumnWidths += columns[columnIdentifier].width; - - // Enforce the Value column (the 2nd column) to be a max of 33% - // tweaking the raw total width because may massively outshadow the others - var valueColumnWidth = columns[1].width; - if (valueColumnWidth / totalColumnWidths > 0.33) { - totalColumnWidths -= valueColumnWidth; - totalColumnWidths *= 1.33; - columns[1].width = totalColumnWidths * 0.33; + function numberCompare(field, cookie1, cookie2) + { + return sortDirection * (cookie1[field] - cookie2[field]); } - // Calculate the percentage width for the columns. - const minimumPrecent = 6; - var recoupPercent = 0; - for (var columnIdentifier in columns) { - var width = columns[columnIdentifier].width; - width = Math.round((width / totalColumnWidths) * 100); - if (width < minimumPrecent) { - recoupPercent += (minimumPrecent - width); - width = minimumPrecent; - } - columns[columnIdentifier].width = width; - } + function expiresCompare(cookie1, cookie2) + { + if (cookie1.session !== cookie2.session) + return sortDirection * (cookie1.session ? 1 : -1); - // Enforce the minimum percentage width. (need to narrow total percentage due to earlier additions) - while (recoupPercent > 0) { - for (var columnIdentifier in columns) { - if (columns[columnIdentifier].width > minimumPrecent) { - --columns[columnIdentifier].width; - --recoupPercent; - if (!recoupPercent) - break; - } - } + if (cookie1.session) + return 0; + + return sortDirection * (cookie1.expires - cookie2.expires); } - for (var columnIdentifier in columns) - columns[columnIdentifier].width += "%"; + var comparator; + switch (parseInt(this._dataGrid.sortColumnIdentifier)) { + case 0: comparator = localeCompare.bind(this, "name"); break; + case 1: comparator = localeCompare.bind(this, "value"); break; + case 2: comparator = localeCompare.bind(this, "domain"); break; + case 3: comparator = localeCompare.bind(this, "path"); break; + case 4: comparator = expiresCompare; break; + case 5: comparator = numberCompare.bind(this, "size"); break; + case 6: comparator = localeCompare.bind(this, "httpOnly"); break; + case 7: comparator = localeCompare.bind(this, "secure"); break; + default: localeCompare.bind(this, "name"); + } - var dataGrid = new WebInspector.DataGrid(columns, null, this._deleteCookieCallback.bind(this)); - var length = nodes.length; - for (var i = 0; i < length; ++i) - dataGrid.appendChild(nodes[i]); - if (length > 0) - nodes[0].selected = true; + this._cookies.sort(comparator); - return dataGrid; + this._dataGrid.removeChildren(); + var nodeToSelect; + for (var i = 0; i < this._cookies.length; ++i) { + var data = {}; + var cookie = this._cookies[i]; + data[0] = cookie.name; + data[1] = cookie.value; + data[2] = cookie.domain; + data[3] = cookie.path; + data[4] = (cookie.session ? WebInspector.UIString("Session") : new Date(cookie.expires).toGMTString()); + data[5] = Number.bytesToString(cookie.size, WebInspector.UIString); + data[6] = (cookie.httpOnly ? "\u2713" : ""); // Checkmark + data[7] = (cookie.secure ? "\u2713" : ""); // Checkmark + + var node = new WebInspector.DataGridNode(data); + node.cookie = cookie; + node.selectable = true; + this._dataGrid.appendChild(node); + if (cookie === selectedCookie) + nodeToSelect = node; + } + if (nodeToSelect) + nodeToSelect.selected = true; + else + this._dataGrid.children[0].selected = true; }, - simpleDataGridForCookies: function(cookies) + _createSimpleDataGrid: function() { - if (!cookies.length) - return null; - var columns = {}; columns[0] = {}; columns[1] = {}; columns[0].title = WebInspector.UIString("Name"); - columns[0].width = columns[0].title.length; columns[1].title = WebInspector.UIString("Value"); - columns[1].width = columns[1].title.length; - var nodes = []; + this._dataGrid = new WebInspector.DataGrid(columns); + this.element.appendChild(this._dataGrid.element); + this._dataGrid.updateWidths(); + }, + + _populateSimpleDataGrid: function() + { + var cookies = this._cookies; + this._dataGrid.removeChildren(); + var addedCookies = {}; for (var i = 0; i < cookies.length; ++i) { - var cookie = cookies[i]; + if (addedCookies[cookies[i].name]) + continue; + addedCookies[cookies[i].name] = true; var data = {}; - - var name = cookie.name; - data[0] = name; - if (name.length > columns[0].width) - columns[0].width = name.length; - - var value = cookie.value; - data[1] = value; - if (value.length > columns[1].width) - columns[1].width = value.length; + data[0] = cookies[i].name; + data[1] = cookies[i].value; var node = new WebInspector.DataGridNode(data, false); node.selectable = true; - nodes.push(node); + this._dataGrid.appendChild(node); } - - var totalColumnWidths = columns[0].width + columns[1].width; - var width = Math.round((columns[0].width * 100) / totalColumnWidths); - const minimumPrecent = 20; - if (width < minimumPrecent) - width = minimumPrecent; - if (width > 100 - minimumPrecent) - width = 100 - minimumPrecent; - columns[0].width = width; - columns[1].width = 100 - width; - columns[0].width += "%"; - columns[1].width += "%"; - - var dataGrid = new WebInspector.DataGrid(columns); - var length = nodes.length; - for (var i = 0; i < length; ++i) - dataGrid.appendChild(nodes[i]); - if (length > 0) - nodes[0].selected = true; - - return dataGrid; + this._dataGrid.children[0].selected = true; }, - + resize: function() { if (this._dataGrid) @@ -288,17 +286,17 @@ WebInspector.CookieItemsView.prototype = { this._deleteCookieCallback(this._dataGrid.selectedNode); }, - + _deleteCookieCallback: function(node) { var cookie = node.cookie; InspectorBackend.deleteCookie(cookie.name, this._cookieDomain); - this.update(); + this._update(); }, _refreshButtonClicked: function(event) { - this.update(); + this._update(); } } diff --git a/WebCore/inspector/front-end/DOMStorageItemsView.js b/WebCore/inspector/front-end/DOMStorageItemsView.js index 7441f2e..dbd736b 100644 --- a/WebCore/inspector/front-end/DOMStorageItemsView.js +++ b/WebCore/inspector/front-end/DOMStorageItemsView.js @@ -69,7 +69,7 @@ WebInspector.DOMStorageItemsView.prototype = { { this._dataGrid = this._dataGridForDOMStorageEntries(entries); this.element.appendChild(this._dataGrid.element); - this._dataGrid.updateWidths(); + this._dataGrid.autoSizeColumns(10); this.deleteButton.visible = true; }, @@ -85,9 +85,7 @@ WebInspector.DOMStorageItemsView.prototype = { columns[0] = {}; columns[1] = {}; columns[0].title = WebInspector.UIString("Key"); - columns[0].width = columns[0].title.length; columns[1].title = WebInspector.UIString("Value"); - columns[1].width = columns[1].title.length; var nodes = []; @@ -98,31 +96,14 @@ WebInspector.DOMStorageItemsView.prototype = { var key = entries[i][0]; data[0] = key; - if (key.length > columns[0].width) - columns[0].width = key.length; - var value = entries[i][1]; data[1] = value; - if (value.length > columns[1].width) - columns[1].width = value.length; var node = new WebInspector.DataGridNode(data, false); node.selectable = true; nodes.push(node); keys.push(key); } - var totalColumnWidths = columns[0].width + columns[1].width; - var width = Math.round((columns[0].width * 100) / totalColumnWidths); - const minimumPrecent = 10; - if (width < minimumPrecent) - width = minimumPrecent; - if (width > 100 - minimumPrecent) - width = 100 - minimumPrecent; - columns[0].width = width; - columns[1].width = 100 - width; - columns[0].width += "%"; - columns[1].width += "%"; - var dataGrid = new WebInspector.DataGrid(columns, this._editingCallback.bind(this), this._deleteCallback.bind(this)); var length = nodes.length; for (var i = 0; i < length; ++i) diff --git a/WebCore/inspector/front-end/DataGrid.js b/WebCore/inspector/front-end/DataGrid.js index 3eca9e4..1ecc4f2 100644 --- a/WebCore/inspector/front-end/DataGrid.js +++ b/WebCore/inspector/front-end/DataGrid.js @@ -61,7 +61,7 @@ WebInspector.DataGrid = function(columns, editCallback, deleteCallback) var headerRow = document.createElement("tr"); var columnGroup = document.createElement("colgroup"); - var columnCount = 0; + this._columnCount = 0; for (var columnIdentifier in columns) { var column = columns[columnIdentifier]; @@ -71,6 +71,7 @@ WebInspector.DataGrid = function(columns, editCallback, deleteCallback) var col = document.createElement("col"); if (column.width) col.style.width = column.width; + column.element = col; columnGroup.appendChild(col); var cell = document.createElement("th"); @@ -98,10 +99,10 @@ WebInspector.DataGrid = function(columns, editCallback, deleteCallback) headerRow.appendChild(cell); - ++columnCount; + ++this._columnCount; } - columnGroup.span = columnCount; + columnGroup.span = this._columnCount; var cell = document.createElement("th"); cell.className = "corner"; @@ -114,7 +115,7 @@ WebInspector.DataGrid = function(columns, editCallback, deleteCallback) var fillerRow = document.createElement("tr"); fillerRow.className = "filler"; - for (var i = 0; i < columnCount; ++i) { + for (var i = 0; i < this._columnCount; ++i) { var cell = document.createElement("td"); fillerRow.appendChild(cell); } @@ -292,7 +293,70 @@ WebInspector.DataGrid.prototype = { return this._dataTableBody; }, - + + autoSizeColumns: function(minPercent, maxPercent) + { + if (minPercent) + minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount)); + var widths = {}; + var columns = this.columns; + for (var columnIdentifier in columns) + widths[columnIdentifier] = (columns[columnIdentifier].title || "").length; + + for (var i = 0; i < this.children.length; ++i) { + var node = this.children[i]; + for (var columnIdentifier in columns) { + var text = node.data[columnIdentifier] || ""; + if (text.length > widths[columnIdentifier]) + widths[columnIdentifier] = text.length; + } + } + + var totalColumnWidths = 0; + for (var columnIdentifier in columns) + totalColumnWidths += widths[columnIdentifier]; + + var recoupPercent = 0; + for (var columnIdentifier in columns) { + var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths); + if (minPercent && width < minPercent) { + recoupPercent += (minPercent - width); + width = minPercent; + } else if (maxPercent && width > maxPercent) { + recoupPercent -= (width - maxPercent); + width = maxPercent; + } + widths[columnIdentifier] = width; + } + + while (minPercent && recoupPercent > 0) { + for (var columnIdentifier in columns) { + if (widths[columnIdentifier] > minPercent) { + --widths[columnIdentifier]; + --recoupPercent; + if (!recoupPercent) + break; + } + } + } + + while (maxPercent && recoupPercent < 0) { + for (var columnIdentifier in columns) { + if (widths[columnIdentifier] < maxPercent) { + ++widths[columnIdentifier]; + ++recoupPercent; + if (!recoupPercent) + break; + } + } + } + + for (var columnIdentifier in columns) + columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%"; + this.columnWidthsInitialized = false; + this.updateWidths(); + }, + // Updates the widths of the table, including the positions of the column // resizers. // diff --git a/WebCore/inspector/front-end/DatabaseQueryView.js b/WebCore/inspector/front-end/DatabaseQueryView.js index 2656842..cc902e7 100644 --- a/WebCore/inspector/front-end/DatabaseQueryView.js +++ b/WebCore/inspector/front-end/DatabaseQueryView.js @@ -144,6 +144,7 @@ WebInspector.DatabaseQueryView.prototype = { return; dataGrid.element.addStyleClass("inline"); this._appendQueryResult(query, dataGrid.element); + dataGrid.autoSizeColumns(5); if (query.match(/^create /i) || query.match(/^drop table /i)) WebInspector.panels.storage.updateDatabaseTables(this.database); diff --git a/WebCore/inspector/front-end/DatabaseTableView.js b/WebCore/inspector/front-end/DatabaseTableView.js index aa76794..cd66ab7 100644 --- a/WebCore/inspector/front-end/DatabaseTableView.js +++ b/WebCore/inspector/front-end/DatabaseTableView.js @@ -68,6 +68,7 @@ WebInspector.DatabaseTableView.prototype = { } this.element.appendChild(dataGrid.element); + dataGrid.autoSizeColumns(5); }, _queryError: function(error) diff --git a/WebCore/inspector/front-end/ElementsTreeOutline.js b/WebCore/inspector/front-end/ElementsTreeOutline.js index 8d8d5db..4a8dae0 100644 --- a/WebCore/inspector/front-end/ElementsTreeOutline.js +++ b/WebCore/inspector/front-end/ElementsTreeOutline.js @@ -925,6 +925,10 @@ WebInspector.ElementsTreeElement.prototype = { info.title = "Document"; break; + case Node.DOCUMENT_FRAGMENT_NODE: + info.title = "Document Fragment"; + break; + case Node.ELEMENT_NODE: info.title = "<span class=\"webkit-html-tag\"><" + node.nodeName.toLowerCase().escapeHTML(); diff --git a/WebCore/inspector/front-end/InjectedScript.js b/WebCore/inspector/front-end/InjectedScript.js index 337628f..95867c4 100644 --- a/WebCore/inspector/front-end/InjectedScript.js +++ b/WebCore/inspector/front-end/InjectedScript.js @@ -52,11 +52,6 @@ InjectedScript.wrapObject = function(object, objectGroupName) return InjectedScript.createProxyObject(object, objectId); }; -InjectedScript.wrapAndStringifyObject = function(object, objectGroupName) { - var r = InjectedScript.wrapObject(object, objectGroupName); - return InjectedScript.JSON.stringify(r); -}; - InjectedScript.unwrapObject = function(objectId) { return InjectedScript.idToWrappedObject[objectId]; }; @@ -85,7 +80,7 @@ InjectedScript.reset(); InjectedScript.dispatch = function(methodName, args, callId) { - var argsArray = InjectedScript.JSON.parse(args); + var argsArray = eval("(" + args + ")"); if (callId) argsArray.splice(0, 0, callId); // Methods that run asynchronously have a call back id parameter. var result = InjectedScript[methodName].apply(InjectedScript, argsArray); @@ -93,7 +88,7 @@ InjectedScript.dispatch = function(methodName, args, callId) InjectedScript._window().console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); result = null; } - return InjectedScript.JSON.stringify(result); + return result; } InjectedScript.getStyles = function(nodeId, authorOnly) @@ -935,7 +930,7 @@ InjectedScript.callFrames = function() result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); callFrame = callFrame.caller; } while (callFrame); - return InjectedScript.JSON.stringify(result); + return result; } InjectedScript.evaluateInCallFrame = function(callFrameId, code, objectGroup) @@ -1220,12 +1215,12 @@ InjectedScript.executeSql = function(callId, databaseId, query) data[columnIdentifier] = String(text); } } - InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, InjectedScript.JSON.stringify(result), false); + InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, result, false); } function errorCallback(tx, error) { - InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, InjectedScript.JSON.stringify(error), false); + InjectedScriptHost.reportDidDispatchOnInjectedScript(callId, error, false); } function queryTransaction(tx) @@ -1339,296 +1334,5 @@ InjectedScript._escapeCharacters = function(str, chars) return result; } -InjectedScript.JSON = {}; - -// The following code is a slightly modified version of http://www.json.org/json2.js last modified on 2009-09-29. -// Compared to the original version it ignores toJSON method on objects it serializes. -// It's done to avoid weird behaviour when inspected application provides it's own implementation -// of toJSON methods to the Object and other intrinsic types. We use InjectedScript.JSON implementation -// instead of global JSON object since it can have been modified by the inspected code. -(function() { - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? - '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : - '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 ? '[]' : - gap ? '[\n' + gap + - partial.join(',\n' + gap) + '\n' + - mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 ? '{}' : - gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + - mind + '}' : '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - - InjectedScript.JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - - -// If the JSON object does not yet have a parse method, give it one. - - InjectedScript.JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/. -test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). -replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). -replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; -}()); - return InjectedScript; }); diff --git a/WebCore/inspector/front-end/InjectedScriptAccess.js b/WebCore/inspector/front-end/InjectedScriptAccess.js index 2dd2908..2558267 100644 --- a/WebCore/inspector/front-end/InjectedScriptAccess.js +++ b/WebCore/inspector/front-end/InjectedScriptAccess.js @@ -56,7 +56,7 @@ InjectedScriptAccess._installHandler = function(methodName, async) function myCallback(result, isException) { if (!isException) - callback(JSON.parse(result)); + callback(result); else WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage("Error dispatching: " + methodName)); } diff --git a/WebCore/inspector/front-end/NativeTextViewer.js b/WebCore/inspector/front-end/NativeTextViewer.js deleted file mode 100644 index 5e7db27..0000000 --- a/WebCore/inspector/front-end/NativeTextViewer.js +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -WebInspector.NativeTextViewer = function(textModel, platform, url) -{ - WebInspector.TextEditor.call(this, textModel, platform); - this._sheet.tabIndex = 0; - this._canvas.style.zIndex = 0; - this._createLineDivs(); - this._url = url; - this._selectionColor = "rgb(241, 234, 0)"; -} - -WebInspector.NativeTextViewer.prototype = { - // WebInspector.TextModel listener - _textChanged: function(oldRange, newRange, oldText, newText) - { - this._createLineDivs(); - WebInspector.TextEditor.prototype._textChanged.call(this, oldRange, newRange, oldText, newText); - }, - - _createLineDivs: function() - { - this._container.removeChild(this._sheet); - this._sheet.removeChildren(); - for (var i = 0; i < this._textModel.linesCount; ++i) { - var lineDiv = document.createElement("div"); - lineDiv.className = "native-text-editor-line"; - var text = this._textModel.line(i); - lineDiv.textContent = text; - if (!text) - lineDiv.style.minHeight = this._textLineHeight + "px"; - this._sheet.appendChild(lineDiv); - this._textModel.setAttribute(i, "line-div", lineDiv); - this._textModel.removeAttribute(i, "div-highlighted"); - } - this._container.appendChild(this._sheet); - }, - - _updatePreferredSize: function(startLine, endLine) - { - // Preferred size is automatically calculated based on the line divs. - // Only handle line numbers here. - - this.setCoalescingUpdate(true); - var newLineNumberDigits = this._decimalDigits(this._textModel.linesCount); - this._lineNumberWidth = (newLineNumberDigits + 2) * this._digitWidth; - - this._container.style.left = this._lineNumberWidth + "px"; - - this._lineNumberDigits = newLineNumberDigits; - this.repaintAll(); - - // Changes to size can change the client area (scrollers can appear/disappear) - this.resize(); - this.setCoalescingUpdate(false); - }, - - _scroll: function(e) - { - // Do instant repaint so that offset of canvas was in sync with the sheet. - this._repaintOnScroll(); - }, - - _registerMouseListeners: function() - { - this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false); - this.element.addEventListener("mousedown", this._mouseDown.bind(this), false); - }, - - _registerKeyboardListeners: function() - { - // Noop - let browser take care of this. - }, - - _registerClipboardListeners: function() - { - // Noop - let browser take care of this. - }, - - _positionDivDecoration: function() - { - // Div decorations have fixed positions in our case. - }, - - _registerShortcuts: function() - { - // Noop. - }, - - _mouseDown: function(e) - { - if (e.target !== this.element || e.button === 2 || (this._isMac && e.ctrlKey)) - return; - this._lineNumberDecorator.mouseDown(this._lineForMouseEvent(e), e); - }, - - _contextMenu: function(e) - { - if (e.target !== this.element) - return; - this._lineNumberDecorator.contextMenu(this._lineForMouseEvent(e), e); - }, - - _lineForMouseEvent: function(e) - { - return Math.max(0, this._offsetToLine(e.offsetY + this._scrollTop) - 1); - }, - - _lineHeight: function(lineNumber) - { - // Use cached value first. - if (this._lineOffsetsCache[lineNumber + 1]) - return this._lineOffsetsCache[lineNumber + 1] - this._lineOffsetsCache[lineNumber]; - - // Get metrics from the browser. - var element = this._textModel.getAttribute(lineNumber, "line-div"); - if (lineNumber + 1 < this._textModel.linesCount) { - var nextElement = this._textModel.getAttribute(lineNumber + 1, "line-div"); - return nextElement.offsetTop - element.offsetTop; - } - return element.parentElement.offsetHeight - element.offsetTop; - }, - - _paintLine: function(lineNumber, lineOffset) - { - var divHighlighted = this._textModel.getAttribute(lineNumber, "div-highlighted"); - if (divHighlighted) - return; - - var highlighterState = this._textModel.getAttribute(lineNumber, "highlighter-state"); - if (!highlighterState) - return; - - var line = this._textModel.line(lineNumber); - var element = this._textModel.getAttribute(lineNumber, "line-div"); - element.removeChildren(); - - var plainTextStart = -1; - for (var j = 0; j < line.length;) { - if (j > 1000) { - // This line is too long - do not waste cycles on minified js highlighting. - break; - } - var attribute = highlighterState && highlighterState.attributes[j]; - if (!attribute || !attribute.style) { - if (plainTextStart === -1) - plainTextStart = j; - j++; - } else { - if (plainTextStart !== -1) { - element.appendChild(document.createTextNode(line.substring(plainTextStart, j))); - plainTextStart = -1; - } - element.appendChild(this._createSpan(line.substring(j, j + attribute.length), attribute.tokenType)); - j += attribute.length; - } - } - if (plainTextStart !== -1) - element.appendChild(document.createTextNode(line.substring(plainTextStart, line.length))); - - this._textModel.setAttribute(lineNumber, "div-highlighted", true); - }, - - _createSpan: function(content, className) - { - if (className === "html-resource-link" || className === "html-external-link") - return this._createLink(content, className === "html-external-link"); - - var span = document.createElement("span"); - span.className = "webkit-" + className; - span.appendChild(document.createTextNode(content)); - return span; - }, - - _createLink: function(content, isExternal) - { - var quote = content.charAt(0); - if (content.length > 1 && (quote === "\"" || quote === "'")) - content = content.substring(1, content.length - 1); - else - quote = null; - - var a = WebInspector.linkifyURLAsNode(this._rewriteHref(content), content, null, isExternal); - var span = document.createElement("span"); - span.className = "webkit-html-attribute-value"; - if (quote) - span.appendChild(document.createTextNode(quote)); - span.appendChild(a); - if (quote) - span.appendChild(document.createTextNode(quote)); - return span; - }, - - _rewriteHref: function(hrefValue, isExternal) - { - if (!this._url || !hrefValue || hrefValue.indexOf("://") > 0) - return hrefValue; - return WebInspector.completeURL(this._url, hrefValue); - }, - - setDivDecoration: function(lineNumber, element) - { - var existingElement = this._textModel.getAttribute(lineNumber, "div-decoration"); - if (existingElement && existingElement.parentNode) - existingElement.parentNode.removeChild(existingElement); - this._textModel.removeAttribute(lineNumber, "div-decoration"); - - if (element) { - if (lineNumber < this._textModel.linesCount - 1) { - var lineDiv = this._textModel.getAttribute(lineNumber + 1, "line-div"); - this._sheet.insertBefore(element, lineDiv); - } else - this._sheet.appendChild(element); - this._textModel.setAttribute(lineNumber, "div-decoration", element); - } - this.revalidateDecorationsAndPaint(); - }, - - initFontMetrics: function() - { - WebInspector.TextEditor.prototype.initFontMetrics.call(this); - for (var i = 0; i < this._textModel.linesCount; ++i) { - var lineDiv = this._textModel.getAttribute(i, "line-div"); - if (!this._textModel.line(i)) - lineDiv.style.minHeight = this._textLineHeight + "px"; - } - } -} - -WebInspector.NativeTextViewer.prototype.__proto__ = WebInspector.TextEditor.prototype; diff --git a/WebCore/inspector/front-end/ScriptView.js b/WebCore/inspector/front-end/ScriptView.js index cf7f1b1..c5a8b81 100644 --- a/WebCore/inspector/front-end/ScriptView.js +++ b/WebCore/inspector/front-end/ScriptView.js @@ -33,7 +33,7 @@ WebInspector.ScriptView = function(script) this._frameNeedsSetup = true; this._sourceFrameSetup = false; - this.sourceFrame = new WebInspector.SourceFrame(this.element, this._addBreakpoint.bind(this)); + this.sourceFrame = new WebInspector.SourceFrame(this.element, this._addBreakpoint.bind(this), this._removeBreakpoint.bind(this)); } WebInspector.ScriptView.prototype = { @@ -87,6 +87,7 @@ WebInspector.ScriptView.prototype = { showingLastSearchResult: WebInspector.SourceView.prototype.showingLastSearchResult, _jumpToSearchResult: WebInspector.SourceView.prototype._jumpToSearchResult, _sourceFrameSetupFinished: WebInspector.SourceView.prototype._sourceFrameSetupFinished, + _removeBreakpoint: WebInspector.SourceView.prototype._removeBreakpoint, resize: WebInspector.SourceView.prototype.resize } diff --git a/WebCore/inspector/front-end/Settings.js b/WebCore/inspector/front-end/Settings.js index bc0daa5..e6fc0c3 100644 --- a/WebCore/inspector/front-end/Settings.js +++ b/WebCore/inspector/front-end/Settings.js @@ -38,8 +38,7 @@ var Preferences = { styleRulesExpandedState: {}, showMissingLocalizedStrings: false, samplingCPUProfiler: false, - showColorNicknames: true, - useCanvasBasedEditor: false + showColorNicknames: true } WebInspector.populateFrontendSettings = function(settingsString) @@ -68,7 +67,7 @@ WebInspector.Settings.prototype = { this._installSetting("lastViewedScriptFile", "last-viewed-script-file"); this._installSetting("showInheritedComputedStyleProperties", "show-inherited-computed-style-properties", false); this._installSetting("showUserAgentStyles", "show-user-agent-styles", true); - this._installSetting("resourceViewTab", "resource-view-tab", "headers"); + this._installSetting("resourceViewTab", "resource-view-tab", "content"); this.dispatchEventToListeners("loaded"); }, diff --git a/WebCore/inspector/front-end/SourceCSSTokenizer.re2js b/WebCore/inspector/front-end/SourceCSSTokenizer.re2js index 8d6c5f1..ac22bd4 100644 --- a/WebCore/inspector/front-end/SourceCSSTokenizer.re2js +++ b/WebCore/inspector/front-end/SourceCSSTokenizer.re2js @@ -98,35 +98,35 @@ WebInspector.SourceCSSTokenizer = function() this._valueKeywords = [ "above", "absolute", "activeborder", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll", - "alternate", "always","amharic", "amharic-abegede", "antialiased", "appworkspace", "aqua", "armenian", "auto", "avoid", - "background", "backwards", "baseline", "below", "bidi-override", "black", "blink", "block", "block-axis", "blue", "bold", - "bolder", "border", "border-box", "both", "bottom", "break-all", "break-word", "button", "button-bevel", "buttonface", - "buttonhighlight", "buttonshadow", "buttontext", "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", "cell", - "center", "checkbox", "circle", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", - "col-resize", "collapse", "compact", "condensed", "contain", "content", "content-box", "context-menu", "continuous", "copy", - "cover", "crop", "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", "decimal-leading-zero", "default", - "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "disc", "discard", "document", + "alternate", "always","amharic", "amharic-abegede", "antialiased", "appworkspace", "aqua", "arabic-indic", "armenian", + "auto", "avoid", "background", "backwards", "baseline", "below", "bidi-override", "binary", "bengali", "black", "blink", + "block", "block-axis", "blue", "bold", "bolder", "border", "border-box", "both", "bottom", "break-all", "break-word", "button", + "button-bevel", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", "capitalize", "caps-lock-indicator", + "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", + "clear", "clip", "close-quote", "col-resize", "collapse", "compact", "condensed", "contain", "content", "content-box", "context-menu", + "continuous", "copy", "cover", "crop", "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", "decimal-leading-zero", "default", + "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "forwards", "from", "fuchsia", "geometricPrecision", - "georgian", "gray", "graytext", "green", "grey", "groove", "hand", "hangul", "hangul-consonant", "hebrew", "help", "hidden", "hide", - "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", + "georgian", "gray", "graytext", "green", "grey", "groove", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", "help", + "hidden", "hide", "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline", - "inline-axis", "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "justify", "katakana", - "katakana-iroha", "landscape", "large", "larger", "left", "level", "lighter", "lime", "line-through", "linear", "lines", - "list-button", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-greek", "lower-latin", - "lower-norwegian", "lower-roman", "lowercase", "ltr", "maroon", "match", "media-controls-background", "media-current-time-display", + "inline-axis", "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "justify", "kannada", "katakana", + "katakana-iroha", "khmer", "landscape", "lao", "large", "larger", "left", "level", "lighter", "lime", "line-through", "linear", "lines", + "list-button", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-greek", "lower-hexadecimal", "lower-latin", + "lower-norwegian", "lower-roman", "lowercase", "ltr", "malayalam", "maroon", "match", "media-controls-background", "media-current-time-display", "media-fullscreen-button", "media-mute-button", "media-play-button", "media-return-to-realtime-button", "media-rewind-button", "media-seek-back-button", "media-seek-forward-button", "media-slider", "media-sliderthumb", "media-time-remaining-display", "media-volume-slider", "media-volume-slider-container", "media-volume-sliderthumb", "medium", "menu", "menulist", "menulist-button", - "menulist-text", "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", "mix", "monospace", "move", "multiple", - "n-resize", "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", - "normal", "not-allowed", "nowrap", "ns-resize", "nw-resize", "nwse-resize", "oblique", "olive", "open-quote", "optimizeLegibility", - "optimizeSpeed", "orange", "oromo", "outset", "outside", "overlay", "overline", "padding", "padding-box", "painted", "paused", - "plus-darker", "plus-lighter", "pointer", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "purple", + "menulist-text", "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", "mix", "mongolian", "monospace", "move", "multiple", + "myanmar", "n-resize", "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", + "normal", "not-allowed", "nowrap", "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "olive", "open-quote", "optimizeLegibility", + "optimizeSpeed", "orange", "oriya", "oromo", "outset", "outside", "overlay", "overline", "padding", "padding-box", "painted", "paused", + "persian", "plus-darker", "plus-lighter", "pointer", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "purple", "push-button", "radio", "read-only", "read-write", "read-write-plaintext-only", "red", "relative", "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", @@ -135,15 +135,15 @@ WebInspector.SourceCSSTokenizer = function() "small", "small-caps", "small-caption", "smaller", "solid", "somali", "source-atop", "source-in", "source-out", "source-over", "space", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", "subpixel-antialiased", "super", "sw-resize", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", - "table-row", "table-row-group", "teal", "text", "text-bottom", "text-top", "textarea", "textfield", "thick", "thin", "threeddarkshadow", - "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tigre", "tigrinya-er", "tigrinya-er-abegede", "tigrinya-et", - "tigrinya-et-abegede", "to", "top", "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "upper-alpha", "upper-greek", - "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "url", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", - "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", "window", "windowframe", "windowtext", "x-large", "x-small", - "xor", "xx-large", "xx-small", "yellow", "-wap-marquee", "-webkit-activelink", "-webkit-auto", "-webkit-baseline-middle", "-webkit-body", - "-webkit-box", "-webkit-center", "-webkit-control", "-webkit-focus-ring-color", "-webkit-grab", "-webkit-grabbing", "-webkit-gradient", "-webkit-inline-box", - "-webkit-left", "-webkit-link", "-webkit-marquee", "-webkit-mini-control", "-webkit-nowrap", "-webkit-right", "-webkit-small-control", - "-webkit-text", "-webkit-xxx-large", "-webkit-zoom-in", "-webkit-zoom-out", + "table-row", "table-row-group", "teal", "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", "thick", "thin", + "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "tigrinya-er-abegede", + "tigrinya-et", "tigrinya-et-abegede", "to", "top", "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "upper-alpha", "upper-greek", + "upper-hexadecimal", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", "vertical", "vertical-text", "visible", + "visibleFill", "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", "window", "windowframe", "windowtext", + "x-large", "x-small", "xor", "xx-large", "xx-small", "yellow", "-wap-marquee", "-webkit-activelink", "-webkit-auto", "-webkit-baseline-middle", + "-webkit-body", "-webkit-box", "-webkit-center", "-webkit-control", "-webkit-focus-ring-color", "-webkit-grab", "-webkit-grabbing", + "-webkit-gradient", "-webkit-inline-box", "-webkit-left", "-webkit-link", "-webkit-marquee", "-webkit-mini-control", "-webkit-nowrap", "-webkit-right", + "-webkit-small-control", "-webkit-text", "-webkit-xxx-large", "-webkit-zoom-in", "-webkit-zoom-out", ].keySet(); this._mediaTypes = ["all", "aural", "braille", "embossed", "handheld", "import", "print", "projection", "screen", "tty", "tv"].keySet(); diff --git a/WebCore/inspector/front-end/SourceFrame.js b/WebCore/inspector/front-end/SourceFrame.js index e30dbdb..0f90700 100644 --- a/WebCore/inspector/front-end/SourceFrame.js +++ b/WebCore/inspector/front-end/SourceFrame.js @@ -28,11 +28,12 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -WebInspector.SourceFrame = function(parentElement, addBreakpointDelegate) +WebInspector.SourceFrame = function(parentElement, addBreakpointDelegate, removeBreakpointDelegate) { this._parentElement = parentElement; this._textModel = new WebInspector.TextEditorModel(); + this._textModel.replaceTabsWithSpaces = true; this._messages = []; this._rowMessages = {}; @@ -43,6 +44,7 @@ WebInspector.SourceFrame = function(parentElement, addBreakpointDelegate) this._loaded = false; this._addBreakpointDelegate = addBreakpointDelegate; + this._removeBreakpointDelegate = removeBreakpointDelegate; } WebInspector.SourceFrame.prototype = { @@ -50,7 +52,7 @@ WebInspector.SourceFrame.prototype = { set visible(visible) { this._visible = visible; - this._createEditorIfNeeded(); + this._createViewerIfNeeded(); }, get executionLine() @@ -62,15 +64,18 @@ WebInspector.SourceFrame.prototype = { { if (this._executionLine === x) return; + + var previousLine = this._executionLine; this._executionLine = x; - if (this._editor) - this._editor.repaintAll(); + + if (this._textViewer) + this._updateExecutionLine(previousLine); }, revealLine: function(lineNumber) { - if (this._editor) - this._editor.reveal(lineNumber - 1, 0); + if (this._textViewer) + this._textViewer.revealLine(lineNumber - 1, 0); else this._lineNumberToReveal = lineNumber; }, @@ -81,7 +86,8 @@ WebInspector.SourceFrame.prototype = { breakpoint.addEventListener("enabled", this._breakpointChanged, this); breakpoint.addEventListener("disabled", this._breakpointChanged, this); breakpoint.addEventListener("condition-changed", this._breakpointChanged, this); - this._addBreakpointToSource(breakpoint); + if (this._textViewer) + this._addBreakpointToSource(breakpoint); }, removeBreakpoint: function(breakpoint) @@ -90,7 +96,8 @@ WebInspector.SourceFrame.prototype = { breakpoint.removeEventListener("enabled", null, this); breakpoint.removeEventListener("disabled", null, this); breakpoint.removeEventListener("condition-changed", null, this); - this._removeBreakpointFromSource(breakpoint); + if (this._textViewer) + this._removeBreakpointFromSource(breakpoint); }, addMessage: function(msg) @@ -99,7 +106,7 @@ WebInspector.SourceFrame.prototype = { if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning()) return; this._messages.push(msg) - if (this._editor) + if (this._textViewer) this._addMessageToSource(msg); }, @@ -113,14 +120,14 @@ WebInspector.SourceFrame.prototype = { this._messages = []; this._rowMessages = {}; this._messageBubbles = {}; - if (this._editor) - this._editor.revalidateDecorationsAndPaint(); + if (this._textViewer) + this._textViewer.resize(); }, sizeToFitContentHeight: function() { - if (this._editor) - this._editor.revalidateDecorationsAndPaint(); + if (this._textViewer) + this._textViewer.revalidateDecorationsAndPaint(); }, setContent: function(mimeType, content, url) @@ -129,47 +136,56 @@ WebInspector.SourceFrame.prototype = { this._textModel.setText(null, content); this._mimeType = mimeType; this._url = url; - this._createEditorIfNeeded(); + this._createViewerIfNeeded(); + }, + + highlightLine: function(line) + { + if (this._textViewer) + this._textViewer.highlightLine(line - 1); + else + this._lineToHighlight = line; }, - _createEditorIfNeeded: function() + _createViewerIfNeeded: function() { - if (!this._visible || !this._loaded || this._editor) + if (!this._visible || !this._loaded || this._textViewer) return; - var editorConstructor = Preferences.useCanvasBasedEditor ? WebInspector.TextEditor : WebInspector.NativeTextViewer; - this._editor = new editorConstructor(this._textModel, WebInspector.platform, this._url); - this._editor.lineNumberDecorator = new WebInspector.BreakpointLineNumberDecorator(this, this._editor.textModel); - this._editor.lineDecorator = new WebInspector.ExecutionLineDecorator(this); - this._editor.readOnly = true; - this._element = this._editor.element; - this._element.addEventListener("keydown", this._keyDown.bind(this), true); - this._parentElement.appendChild(this._element); - this._editor.initFontMetrics(); + this._textViewer = new WebInspector.TextViewer(this._textModel, WebInspector.platform, this._url); + var element = this._textViewer.element; + element.addEventListener("keydown", this._keyDown.bind(this), true); + element.addEventListener("contextmenu", this._contextMenu.bind(this), true); + element.addEventListener("mousedown", this._mouseDown.bind(this), true); + this._parentElement.appendChild(element); - this._editor.mimeType = this._mimeType; + this._needsProgramCounterImage = true; + this._needsBreakpointImages = true; + this._textViewer.beginUpdates(); + + this._textViewer.mimeType = this._mimeType; this._addExistingMessagesToSource(); this._addExistingBreakpointsToSource(); - - this._editor.setCoalescingUpdate(true); - this._editor.resize(); - this._editor.revalidateDecorationsAndPaint(); - - if (this._executionLine) - this.revealLine(this._executionLine); + this._updateExecutionLine(); + this._textViewer.resize(); if (this._lineNumberToReveal) { this.revealLine(this._lineNumberToReveal); delete this._lineNumberToReveal; } - if (this._pendingSelectionRange) { - var range = this._pendingSelectionRange; - this._editor.setSelection(range.startLine, range.startColumn, range.endLine, range.endColumn); - delete this._pendingSelectionRange; + if (this._pendingMarkRange) { + var range = this._pendingMarkRange; + this.markAndRevealRange(range); + delete this._pendingMarkRange; } - this._editor.setCoalescingUpdate(false); + + if (this._lineToHighlight) { + this.highlightLine(this._lineToHighlight); + delete this._lineToHighlight; + } + this._textViewer.endUpdates(); }, findSearchMatches: function(query) @@ -214,21 +230,20 @@ WebInspector.SourceFrame.prototype = { return ranges; }, - setSelection: function(range) + markAndRevealRange: function(range) { - if (this._editor) - this._editor.setSelection(range.startLine, range.startColumn, range.endLine, range.endColumn); + if (this._textViewer) + this._textViewer.markAndRevealRange(range); else - this._pendingSelectionRange = range; + this._pendingMarkRange = range; }, - clearSelection: function() + clearMarkedRange: function() { - if (this._editor) { - var range = this._editor.selection; - this._editor.setSelection(range.endLine, range.endColumn, range.endLine, range.endColumn); + if (this._textViewer) { + this._textViewer.markAndRevealRange(null); } else - delete this._pendingSelectionRange; + delete this._pendingMarkRange; }, _incrementMessageRepeatCount: function(msg, repeatDelta) @@ -246,6 +261,40 @@ WebInspector.SourceFrame.prototype = { msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount); }, + _breakpointChanged: function(event) + { + var breakpoint = event.target; + var lineNumber = breakpoint.line - 1; + if (lineNumber >= this._textModel.linesCount) + return; + + if (breakpoint.enabled) + this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled"); + else + this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled"); + + if (breakpoint.condition) + this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional"); + else + this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional"); + }, + + _updateExecutionLine: function(previousLine) + { + if (previousLine) { + if (previousLine - 1 < this._textModel.linesCount) + this._textViewer.removeDecoration(previousLine - 1, "webkit-execution-line"); + } + + if (!this._executionLine) + return; + + this._drawProgramCounterImageIfNeeded(); + + if (this._executionLine < this._textModel.linesCount) + this._textViewer.addDecoration(this._executionLine - 1, "webkit-execution-line"); + }, + _addExistingMessagesToSource: function() { var length = this._messages.length; @@ -263,7 +312,7 @@ WebInspector.SourceFrame.prototype = { messageBubbleElement = document.createElement("div"); messageBubbleElement.className = "webkit-html-message-bubble"; this._messageBubbles[msg.line] = messageBubbleElement; - this._editor.setDivDecoration(msg.line - 1, messageBubbleElement); + this._textViewer.addDecoration(msg.line - 1, messageBubbleElement); } var rowMessages = this._rowMessages[msg.line]; @@ -275,7 +324,6 @@ WebInspector.SourceFrame.prototype = { for (var i = 0; i < rowMessages.length; ++i) { if (rowMessages[i].isEqual(msg, true)) { this._incrementMessageRepeatCount(rowMessages[i], msg.repeatDelta); - this._editor.revalidateDecorationsAndPaint(); return; } } @@ -306,35 +354,51 @@ WebInspector.SourceFrame.prototype = { messageLineElement.appendChild(document.createTextNode(msg.message)); msg._resourceMessageLineElement = messageLineElement; - - this._editor.revalidateDecorationsAndPaint(); }, _addExistingBreakpointsToSource: function() { - var length = this.breakpoints.length; - for (var i = 0; i < length; ++i) + for (var i = 0; i < this.breakpoints.length; ++i) this._addBreakpointToSource(this.breakpoints[i]); }, _addBreakpointToSource: function(breakpoint) { - this._textModel.setAttribute(breakpoint.line - 1, "breakpoint", breakpoint); + var lineNumber = breakpoint.line - 1; + if (lineNumber >= this._textModel.linesCount) + return; + + this._textModel.setAttribute(lineNumber, "breakpoint", breakpoint); breakpoint.sourceText = this._textModel.line(breakpoint.line - 1); - this._editor.paintLineNumbers(); + this._drawBreakpointImagesIfNeeded(); + + this._textViewer.beginUpdates(); + this._textViewer.addDecoration(lineNumber, "webkit-breakpoint"); + if (!breakpoint.enabled) + this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled"); + if (breakpoint.condition) + this._textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional"); + this._textViewer.endUpdates(); }, _removeBreakpointFromSource: function(breakpoint) { - this._textModel.removeAttribute(breakpoint.line - 1, "breakpoint"); - this._editor.paintLineNumbers(); + var lineNumber = breakpoint.line - 1; + this._textViewer.beginUpdates(); + this._textModel.removeAttribute(lineNumber, "breakpoint"); + this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint"); + this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled"); + this._textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional"); + this._textViewer.endUpdates(); }, - _contextMenu: function(lineNumber, event) + _contextMenu: function(event) { - if (!this._addBreakpointDelegate) + if (event.target.className !== "webkit-line-number") return; + var row = event.target.parentElement; + var lineNumber = row.lineNumber; var contextMenu = new WebInspector.ContextMenu(); var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint"); @@ -363,13 +427,19 @@ WebInspector.SourceFrame.prototype = { contextMenu.show(event); }, - _toggleBreakpoint: function(lineNumber, event) + _mouseDown: function(event) { if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return; + if (event.target.className !== "webkit-line-number") + return; + var row = event.target.parentElement; + + var lineNumber = row.lineNumber; + var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint"); if (breakpoint) - WebInspector.panels.scripts.removeBreakpoint(breakpoint); + this._removeBreakpointDelegate(breakpoint); else if (this._addBreakpointDelegate) this._addBreakpointDelegate(lineNumber + 1); event.preventDefault(); @@ -382,14 +452,15 @@ WebInspector.SourceFrame.prototype = { function committed(element, newText) { breakpoint.condition = newText; - this._editor.paintLineNumbers(); dismissed.call(this); } function dismissed() { - this._editor.setDivDecoration(breakpoint.line - 1, null); + if (this._conditionElement) + this._textViewer.removeDecoration(breakpoint.line - 1, this._conditionElement); delete this._conditionEditorElement; + delete this._conditionElement; } var dismissedHandler = dismissed.bind(this); @@ -402,23 +473,23 @@ WebInspector.SourceFrame.prototype = { _showBreakpointConditionPopup: function(lineNumber) { - var conditionElement = this._createConditionElement(lineNumber); - this._editor.setDivDecoration(lineNumber - 1, conditionElement); + this._conditionElement = this._createConditionElement(lineNumber); + this._textViewer.addDecoration(lineNumber - 1, this._conditionElement); }, _createConditionElement: function(lineNumber) { var conditionElement = document.createElement("div"); - conditionElement.className = "source-breakpoint-condition"; + conditionElement.className = "source-frame-breakpoint-condition"; var labelElement = document.createElement("label"); - labelElement.className = "source-breakpoint-message"; - labelElement.htmlFor = "source-breakpoint-condition"; + labelElement.className = "source-frame-breakpoint-message"; + labelElement.htmlFor = "source-frame-breakpoint-condition"; labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber))); conditionElement.appendChild(labelElement); var editorElement = document.createElement("input"); - editorElement.id = "source-breakpoint-condition"; + editorElement.id = "source-frame-breakpoint-condition"; editorElement.className = "monospace"; editorElement.type = "text" conditionElement.appendChild(editorElement); @@ -434,9 +505,8 @@ WebInspector.SourceFrame.prototype = { if (handler) { handler(event); event.preventDefault(); - } else { + } else WebInspector.documentKeyDown(event); - } }, _evalSelectionInCallFrame: function(event) @@ -457,146 +527,139 @@ WebInspector.SourceFrame.prototype = { }); }, - _breakpointChanged: function(event) + resize: function() { - this._editor.paintLineNumbers(); + if (this._textViewer) + this._textViewer.resize(); }, - resize: function() + _drawProgramCounterInContext: function(ctx, glow) { - if (this._editor) - this._editor.resize(); - } -} + if (glow) + ctx.save(); -WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype; + ctx.beginPath(); + ctx.moveTo(17, 2); + ctx.lineTo(19, 2); + ctx.lineTo(19, 0); + ctx.lineTo(21, 0); + ctx.lineTo(26, 5.5); + ctx.lineTo(21, 11); + ctx.lineTo(19, 11); + ctx.lineTo(19, 9); + ctx.lineTo(17, 9); + ctx.closePath(); + ctx.fillStyle = "rgb(142, 5, 4)"; -WebInspector.BreakpointLineNumberDecorator = function(sourceFrame, textModel) -{ - this._sourceFrame = sourceFrame; - this._textModel = textModel; -} + if (glow) { + ctx.shadowBlur = 4; + ctx.shadowColor = "rgb(255, 255, 255)"; + ctx.shadowOffsetX = -1; + ctx.shadowOffsetY = 0; + } -WebInspector.BreakpointLineNumberDecorator.prototype = { - decorate: function(lineNumber, ctx, x, y, width, height, lineHeight) - { - var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint"); - var isExecutionLine = lineNumber + 1 === this._sourceFrame._executionLine; - if (breakpoint || isExecutionLine) { - ctx.save(); - ctx.translate(x + 4, y + 2); - var breakpointWidth = width - 6; - var breakpointHeight = lineHeight - 4; - - if (breakpoint) - this._paintBreakpoint(ctx, breakpointWidth, breakpointHeight, breakpoint); - - if (isExecutionLine) - this._paintProgramCounter(ctx, breakpointWidth, breakpointHeight, false); + ctx.fill(); + ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels. + if (glow) ctx.restore(); - } + }, - if (isExecutionLine) { - // Override default behavior. - return true; - } + _drawProgramCounterImageIfNeeded: function() + { + if (!this._needsProgramCounterImage) + return; + + var ctx = document.getCSSCanvasContext("2d", "program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + this._drawProgramCounterInContext(ctx, true); - ctx.fillStyle = breakpoint ? "rgb(255,255,255)" : "rgb(155,155,155)"; - return false; + delete this._needsProgramCounterImage; }, - _paintBreakpoint: function(ctx, width, height, breakpoint) + _drawBreakpointImagesIfNeeded: function(conditional) { - ctx.beginPath(); - ctx.moveTo(0, 2); - ctx.lineTo(2, 0); - ctx.lineTo(width - 5, 0); - ctx.lineTo(width, height / 2); - ctx.lineTo(width - 5, height); - ctx.lineTo(2, height); - ctx.lineTo(0, height - 2); - ctx.closePath(); - ctx.fillStyle = breakpoint.condition ? "rgb(217, 142, 1)" : "rgb(1, 142, 217)"; - ctx.strokeStyle = breakpoint.condition ? "rgb(205, 103, 0)" : "rgb(0, 103, 205)"; - ctx.lineWidth = 3; - ctx.fill(); + if (!this._needsBreakpointImages) + return; + + function drawBreakpoint(ctx, disabled, conditional) + { + ctx.beginPath(); + ctx.moveTo(0, 2); + ctx.lineTo(2, 0); + ctx.lineTo(21, 0); + ctx.lineTo(26, 5.5); + ctx.lineTo(21, 11); + ctx.lineTo(2, 11); + ctx.lineTo(0, 9); + ctx.closePath(); + ctx.fillStyle = conditional ? "rgb(217, 142, 1)" : "rgb(1, 142, 217)"; + ctx.strokeStyle = conditional ? "rgb(205, 103, 0)" : "rgb(0, 103, 205)"; + ctx.lineWidth = 3; + ctx.fill(); + ctx.save(); + ctx.clip(); + ctx.stroke(); + ctx.restore(); - ctx.save(); - ctx.clip(); - ctx.stroke(); - ctx.restore(); + if (!disabled) + return; - if (!breakpoint.enabled) { ctx.save(); ctx.globalCompositeOperation = "destination-out"; ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; - ctx.fillRect(0, 0, width, height); + ctx.fillRect(0, 0, 26, 11); ctx.restore(); } - }, - _paintProgramCounter: function(ctx, width, height) - { - ctx.save(); - ctx.beginPath(); - ctx.moveTo(width - 9, 2); - ctx.lineTo(width - 7, 2); - ctx.lineTo(width - 7, 0); - ctx.lineTo(width - 5, 0); - ctx.lineTo(width, height / 2); - ctx.lineTo(width - 5, height); - ctx.lineTo(width - 7, height); - ctx.lineTo(width - 7, height - 2); - ctx.lineTo(width - 9, height - 2); - ctx.closePath(); - ctx.fillStyle = "rgb(142, 5, 4)"; + // Unconditional breakpoints. - ctx.shadowBlur = 4; - ctx.shadowColor = "rgb(255, 255, 255)"; - ctx.shadowOffsetX = -1; - ctx.shadowOffsetY = 0; + var ctx = document.getCSSCanvasContext("2d", "breakpoint", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx); - ctx.fill(); - ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels. + var ctx = document.getCSSCanvasContext("2d", "breakpoint-program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx); + ctx.clearRect(20, 0, 6, 11); + this._drawProgramCounterInContext(ctx, true); - ctx.restore(); - }, + var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, true); - mouseDown: function(lineNumber, e) - { - this._sourceFrame._toggleBreakpoint(lineNumber, e); - return true; - }, + var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled-program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, true); + ctx.clearRect(20, 0, 6, 11); + this._drawProgramCounterInContext(ctx, true); - contextMenu: function(lineNumber, e) - { - this._sourceFrame._contextMenu(lineNumber, e); - return true; - } -} -WebInspector.ExecutionLineDecorator = function(sourceFrame) -{ - this._sourceFrame = sourceFrame; -} + // Conditional breakpoints. -WebInspector.ExecutionLineDecorator.prototype = { - decorate: function(lineNumber, ctx, x, y, width, height, lineHeight) - { - if (this._sourceFrame._executionLine !== lineNumber + 1) - return; - ctx.save(); - ctx.fillStyle = "rgb(171, 191, 254)"; - ctx.fillRect(x, y, width, height); - - ctx.beginPath(); - ctx.rect(x - 1, y, width + 2, height); - ctx.clip(); - ctx.strokeStyle = "rgb(64, 115, 244)"; - ctx.stroke(); + var ctx = document.getCSSCanvasContext("2d", "breakpoint-conditional", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, false, true); - ctx.restore(); + var ctx = document.getCSSCanvasContext("2d", "breakpoint-conditional-program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, false, true); + ctx.clearRect(20, 0, 6, 11); + this._drawProgramCounterInContext(ctx, true); + + var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled-conditional", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, true, true); + + var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled-conditional-program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, true, true); + ctx.clearRect(20, 0, 6, 11); + this._drawProgramCounterInContext(ctx, true); + + delete this._needsBreakpointImages; } } + +WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype; diff --git a/WebCore/inspector/front-end/SourceView.js b/WebCore/inspector/front-end/SourceView.js index 7fc8499..b401c12 100644 --- a/WebCore/inspector/front-end/SourceView.js +++ b/WebCore/inspector/front-end/SourceView.js @@ -32,7 +32,7 @@ WebInspector.SourceView = function(resource) this.element.addStyleClass("source"); - this.sourceFrame = new WebInspector.SourceFrame(this.contentElement, this._addBreakpoint.bind(this)); + this.sourceFrame = new WebInspector.SourceFrame(this.contentElement, this._addBreakpoint.bind(this), this._removeBreakpoint.bind(this)); resource.addEventListener("finished", this._resourceLoadingFinished, this); this._frameNeedsSetup = true; } @@ -58,16 +58,6 @@ WebInspector.SourceView.prototype = { this.sourceFrame.resize(); }, - detach: function() - { - WebInspector.ResourceView.prototype.detach.call(this); - - // FIXME: We need to mark the frame for setup on detach because the frame DOM is cleared - // when it is removed from the document. Is this a bug? - this._frameNeedsSetup = true; - this._sourceFrameSetup = false; - }, - setupSourceFrameIfNeeded: function() { if (!this._frameNeedsSetup) @@ -118,6 +108,12 @@ WebInspector.SourceView.prototype = { } }, + _removeBreakpoint: function(breakpoint) + { + if (WebInspector.panels.scripts) + WebInspector.panels.scripts.removeBreakpoint(breakpoint); + }, + // The rest of the methods in this prototype need to be generic enough to work with a ScriptView. // The ScriptView prototype pulls these methods into it's prototype to avoid duplicate code. @@ -125,7 +121,7 @@ WebInspector.SourceView.prototype = { { this._currentSearchResultIndex = -1; this._searchResults = []; - this.sourceFrame.clearSelection(); + this.sourceFrame.clearMarkedRange(); delete this._delayedFindSearchMatches; }, @@ -225,7 +221,7 @@ WebInspector.SourceView.prototype = { if (!foundRange) return; - this.sourceFrame.setSelection(foundRange); + this.sourceFrame.markAndRevealRange(foundRange); }, _sourceFrameSetupFinished: function() diff --git a/WebCore/inspector/front-end/StoragePanel.js b/WebCore/inspector/front-end/StoragePanel.js index dee4442..ca1b276 100644 --- a/WebCore/inspector/front-end/StoragePanel.js +++ b/WebCore/inspector/front-end/StoragePanel.js @@ -220,14 +220,14 @@ WebInspector.StoragePanel.prototype = { this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]); }, - showCookies: function(cookieDomain) + showCookies: function(treeElement, cookieDomain) { if (this.visibleView) this.visibleView.hide(); var view = this._cookieViews[cookieDomain]; if (!view) { - view = new WebInspector.CookieItemsView(cookieDomain); + view = new WebInspector.CookieItemsView(treeElement, cookieDomain); this._cookieViews[cookieDomain] = view; } @@ -300,52 +300,14 @@ WebInspector.StoragePanel.prototype = { var data = {}; var row = rows[i]; - for (var columnIdentifier in row) { - var text = row[columnIdentifier]; - data[columnIdentifier] = text; - if (text.length > columns[columnIdentifier].width) - columns[columnIdentifier].width = text.length; - } + for (var columnIdentifier in row) + data[columnIdentifier] = row[columnIdentifier]; var node = new WebInspector.DataGridNode(data, false); node.selectable = false; nodes.push(node); } - var totalColumnWidths = 0; - for (var columnIdentifier in columns) - totalColumnWidths += columns[columnIdentifier].width; - - // Calculate the percentage width for the columns. - const minimumPrecent = Math.min(5, Math.floor(100/numColumns)); - var recoupPercent = 0; - for (var columnIdentifier in columns) { - var width = columns[columnIdentifier].width; - width = Math.round((width / totalColumnWidths) * 100); - if (width < minimumPrecent) { - recoupPercent += (minimumPrecent - width); - width = minimumPrecent; - } - - columns[columnIdentifier].width = width; - } - - // Enforce the minimum percentage width. - while (recoupPercent > 0) { - for (var columnIdentifier in columns) { - if (columns[columnIdentifier].width > minimumPrecent) { - --columns[columnIdentifier].width; - --recoupPercent; - if (!recoupPercent) - break; - } - } - } - - // Change the width property to a string suitable for a style width. - for (var columnIdentifier in columns) - columns[columnIdentifier].width += "%"; - var dataGrid = new WebInspector.DataGrid(columns); var length = nodes.length; for (var i = 0; i < length; ++i) @@ -507,6 +469,7 @@ WebInspector.CookieSidebarTreeElement = function(cookieDomain) { WebInspector.SidebarTreeElement.call(this, "cookie-sidebar-tree-item", cookieDomain, "", null, false); this._cookieDomain = cookieDomain; + this._subtitle = ""; this.refreshTitles(); } @@ -514,9 +477,9 @@ WebInspector.CookieSidebarTreeElement = function(cookieDomain) WebInspector.CookieSidebarTreeElement.prototype = { onselect: function() { - WebInspector.panels.storage.showCookies(this._cookieDomain); + WebInspector.panels.storage.showCookies(this, this._cookieDomain); }, - + get mainTitle() { return this._cookieDomain ? this._cookieDomain : WebInspector.UIString("Local Files"); @@ -529,12 +492,13 @@ WebInspector.CookieSidebarTreeElement.prototype = { get subtitle() { - return ""; + return this._subtitle; }, set subtitle(x) { - // Do nothing. + this._subtitle = x; + this.refreshTitles(); } } diff --git a/WebCore/inspector/front-end/StylesSidebarPane.js b/WebCore/inspector/front-end/StylesSidebarPane.js index f04cb66..265e488 100644 --- a/WebCore/inspector/front-end/StylesSidebarPane.js +++ b/WebCore/inspector/front-end/StylesSidebarPane.js @@ -60,8 +60,8 @@ WebInspector.StylesSidebarPane = function() this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false); this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false); - WebInspector.settings.addEventListener("loaded", this._settingsLoaded, this); - + WebInspector.settings.addEventListener("loaded", this._settingsLoaded, this); + this.titleElement.appendChild(this.settingsSelectElement); } @@ -354,7 +354,7 @@ WebInspector.StylesSidebarPane.prototype = { var blankSection = new WebInspector.BlankStylePropertiesSection(appropriateSelectorForNode(this.node, true)); blankSection.pane = this; - var elementStyleSection = this.sections[1]; + var elementStyleSection = this.sections[1]; this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling); this.sections.splice(2, 0, blankSection); @@ -445,7 +445,7 @@ WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyl this.identifier = styleRule.selectorText; if (this.subtitle) - this.identifier += ":" + this.subtitleElement.textContent; + this.identifier += ":" + this.subtitleElement.textContent; } WebInspector.StylePropertiesSection.prototype = { @@ -530,6 +530,11 @@ WebInspector.StylePropertiesSection.prototype = { } } + this.afterUpdate(); + }, + + afterUpdate: function() + { if (this._afterUpdate) { this._afterUpdate(this); delete this._afterUpdate; @@ -1296,8 +1301,7 @@ WebInspector.StylePropertyTreeElement.prototype = { if (alreadyNew && !valueChanged) return; - var item = section.addNewBlankProperty(); - item.startEditing(); + section.addNewBlankProperty().startEditing(); return; } @@ -1315,6 +1319,7 @@ WebInspector.StylePropertyTreeElement.prototype = { if (this._newProperty) { // The user deleted everything, so remove the tree element and update. this.parent.removeChild(this); + section.afterUpdate(); return; } else { delete section._afterUpdate; diff --git a/WebCore/inspector/front-end/TextEditor.js b/WebCore/inspector/front-end/TextEditor.js deleted file mode 100644 index 9268280..0000000 --- a/WebCore/inspector/front-end/TextEditor.js +++ /dev/null @@ -1,1168 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -WebInspector.TextEditor = function(textModel, platform) -{ - this._textModel = textModel; - this._textModel.changeListener = this._textChanged.bind(this); - this._highlighter = new WebInspector.TextEditorHighlighter(this._textModel, this._highlightChanged.bind(this)); - - this.element = document.createElement("div"); - this.element.className = "text-editor monospace"; - this.element.tabIndex = 0; - - this._canvas = document.createElement("canvas"); - this._canvas.className = "text-editor-canvas"; - this.element.appendChild(this._canvas); - - this._container = document.createElement("div"); - this._container.className = "text-editor-container"; - this.element.appendChild(this._container); - - this._sheet = document.createElement("div"); - this._container.appendChild(this._sheet); - - var cursorElement = document.createElement("div"); - cursorElement.className = "text-editor-cursor"; - this._container.appendChild(cursorElement); - this._cursor = new WebInspector.TextCursor(cursorElement); - - this._container.addEventListener("scroll", this._scroll.bind(this), false); - - this._registerMouseListeners(); - this._registerKeyboardListeners(); - this._registerClipboardListeners(); - - this._desiredCaretColumn = 0; - this._scrollLeft = 0; - this._scrollTop = 0; - - this._ctx = this._canvas.getContext("2d"); - this._selection = new WebInspector.TextSelectionModel(this._selectionChanged.bind(this)); - - this._isMac = platform && (platform.indexOf("mac") === 0); - this._paintCoalescingLevel = 0; - - this._registerShortcuts(); - // Debugging flags, allow disabling / enabling highlights and track repaints. - this._highlightingEnabled = true; - this._debugMode = false; - - this._textWidth = 0; - this._longestLineNumber = 0; - - this._lineOffsetsCache = [0]; - this._readOnly = false; - this._selectionColor = "rgb(181, 213, 255)"; -} - -WebInspector.TextEditor.prototype = { - set text(text) - { - var lastLine = this._textModel.linesCount - 1; - this._textModel.setText(null, text); - this._textModel.resetUndoStack(); - this._setCaretLocation(0, 0); - }, - - set mimeType(mimeType) - { - this._highlighter.mimeType = mimeType; - }, - - get textModel() - { - return this._textModel; - }, - - set readOnly(readOnly) - { - this._readOnly = readOnly; - if (readOnly) - this.element.addStyleClass("text-editor-readonly") - else - this.element.removeStyleClass("text-editor-readonly") - }, - - set lineNumberDecorator(lineNumberDecorator) - { - this._lineNumberDecorator = lineNumberDecorator; - }, - - set lineDecorator(lineDecorator) - { - this._lineDecorator = lineDecorator; - }, - - get selection() - { - return this._selection.range(); - }, - - setSelection: function(startLine, startColumn, endLine, endColumn) - { - var start = this._fit(startLine, startColumn); - this._selection.setStart(start.line, start.column); - this._setSelectionEnd(endLine, endColumn); - }, - - setDivDecoration: function(lineNumber, element) - { - var existingElement = this._textModel.getAttribute(lineNumber, "div-decoration"); - if (existingElement && existingElement.parentNode) - existingElement.parentNode.removeChild(existingElement); - this._textModel.removeAttribute(lineNumber, "div-decoration"); - - if (element) { - this.element.appendChild(element); - this._textModel.setAttribute(lineNumber, "div-decoration", element); - } - this.revalidateDecorationsAndPaint(); - }, - - _registerMouseListeners: function() - { - this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false); - this.element.addEventListener("mouseup", this._mouseUp.bind(this), false); - this.element.addEventListener("mousedown", this._mouseDown.bind(this), false); - this.element.addEventListener("mousemove", this._mouseMove.bind(this), false); - this.element.addEventListener("mouseout", this._mouseOut.bind(this), false); - this.element.addEventListener("dblclick", this._dblClick.bind(this), false); - }, - - _registerKeyboardListeners: function() - { - this._container.addEventListener("keydown", this._keyDown.bind(this), false); - this._container.addEventListener("textInput", this._textInput.bind(this), false); - }, - - _registerClipboardListeners: function() - { - this._container.addEventListener("beforecopy", this._beforeCopy.bind(this), false); - this._container.addEventListener("copy", this._copy.bind(this), false); - this._container.addEventListener("beforecut", this._beforeCut.bind(this), false); - this._container.addEventListener("cut", this._cut.bind(this), false); - this._container.addEventListener("beforepaste", this._beforePaste.bind(this), false); - this._container.addEventListener("paste", this._paste.bind(this), false); - }, - - _offsetToLine: function(offset) - { - if (offset > this._lineOffsetsCache[this._lineOffsetsCache.length - 1]) { - // Seeking outside cached area. Fill the cache. - var lineNumber = this._lineOffsetsCache.length; - while (lineNumber < this._textModel.linesCount && this._lineToOffset(lineNumber) < offset) - lineNumber++; - return lineNumber; - } - - // Bisect. - var from = 0; - var to = this._lineOffsetsCache.length; - while (to > from + 1) { - var mid = Math.floor((from + to) / 2); - if (this._lineOffsetsCache[mid] > offset) - to = mid; - else - from = mid; - } - return to; - }, - - _lineToOffset: function(lineNumber) - { - var offset = this._lineOffsetsCache[lineNumber]; - if (offset) - return offset; - for (var line = lineNumber; line > 0; --line) { - if (this._lineOffsetsCache[line]) - break; - } - offset = this._lineOffsetsCache[line]; - for (var i = line + 1; i <= lineNumber; ++i) { - offset += this._lineHeight(i - 1); - this._lineOffsetsCache[i] = offset; - } - return offset; - }, - - _lineHeight: function(lineNumber) - { - // Use cached value first. - if (this._lineOffsetsCache[lineNumber + 1]) - return this._lineOffsetsCache[lineNumber + 1] - this._lineOffsetsCache[lineNumber]; - - var element = this._textModel.getAttribute(lineNumber, "div-decoration"); - if (element) - return 2 * this._textLineHeight + element.clientHeight; - return this._textLineHeight; - }, - - reveal: function(line, column) - { - this._scrollTop = this._container.scrollTop; - this._scrollLeft = this._container.scrollLeft; - - var maxScrollTop = this._lineToOffset(line); - var minScrollTop = maxScrollTop + this._lineHeight(line) - this._canvas.height; - if (this._scrollTop > maxScrollTop) - this._container.scrollTop = maxScrollTop - this._textLineHeight * 2; - else if (this._scrollTop < minScrollTop) - this._container.scrollTop = minScrollTop + this._textLineHeight * 2; - - var firstColumn = this._columnForOffset(line, this._scrollLeft); - var maxScrollLeft = this._columnToOffset(line, column); - var minScrollLeft = maxScrollLeft - this._container.clientWidth + this._lineNumberWidth; - if (this._scrollLeft < minScrollLeft) - this._container.scrollLeft = minScrollLeft + 100; - else if (this._scrollLeft > maxScrollLeft) - this._container.scrollLeft = maxScrollLeft; - else if (minScrollLeft < 0 && maxScrollLeft > 0) - this._container.scrollLeft = 0; - }, - - // WebInspector.TextModel listener - _textChanged: function(oldRange, newRange, oldText, newText) - { - if (newRange.linesCount == oldRange.linesCount) - this._invalidateLines(newRange.startLine, newRange.endLine + 1); - else - // Lines shifted, invalidate all under start line. Also clear lines that now are outside model range. - this._invalidateLines(newRange.startLine, this._textModel.linesCount + Math.max(0, oldRange.endLine - newRange.endLine)); - - if (this._highlightingEnabled) { - var lastVisibleLine = Math.min(this._textModel.linesCount, this._offsetToLine(this._scrollTop + this._canvas.height) + 1); - this._highlighter.updateHighlight(newRange.startLine, lastVisibleLine); - } - - this._updatePreferredSize(newRange.startLine, Math.max(newRange.endLine, oldRange.endLine)); - if (oldRange.linesCount !== newRange.linesCount) { - // Invalidate offset cache. - this._lineOffsetsCache.length = oldRange.startLine + 1; - // Force linenumber cache to be continuous. - this._lineToOffset(oldRange.startLine); - this.paintLineNumbers(); - } - this._paint(); - }, - - // WebInspector.TextSelectionModel listener - _selectionChanged: function(oldRange, newRange) - { - if (oldRange.isEmpty() && newRange.isEmpty() && oldRange.startLine === newRange.startLine) { - // Nothing to repaint. - return; - } - - this._invalidateLines(oldRange.startLine, oldRange.endLine + 1); - this._invalidateLines(newRange.startLine, newRange.endLine + 1); - this._paint(); - }, - - _highlightChanged: function(fromLine, toLine) - { - if (this._muteHighlightListener) - return; - - this._invalidateLines(fromLine, toLine); - this._paint(); - }, - - revalidateDecorationsAndPaint: function() - { - this.setCoalescingUpdate(true); - this._lineOffsetsCache = [0]; - this._updatePreferredSize(0, this._textModel.linesCount); - this.repaintAll(); - this.setCoalescingUpdate(false); - }, - - _updatePreferredSize: function(startLine, endLine) - { - this._ctx.font = this._font; - this.setCoalescingUpdate(true); - var guardedEndLine = Math.min(this._textModel.linesCount, endLine + 1); - var newMaximum = false; - for (var i = startLine; i < guardedEndLine; ++i) { - var lineWidth = this._ctx.measureText(this._textModel.line(i)).width; - if (lineWidth > this._textWidth) { - this._textWidth = lineWidth; - this._longestLineNumber = i; - newMaximum = true; - } - } - - if (!newMaximum && startLine <= this._longestLineNumber && this._longestLineNumber <= endLine) { - this._textWidth = 0; - this._longestLineNumber = 0; - for (var i = 0; i < this._textModel.linesCount; ++i) { - var lineWidth = this._ctx.measureText(this._textModel.line(i)).width; - if (lineWidth > this._textWidth) { - this._textWidth = lineWidth; - this._longestLineNumber = i; - } - } - } - - var newLineNumberDigits = this._decimalDigits(this._textModel.linesCount); - this._lineNumberWidth = (newLineNumberDigits + 2) * this._digitWidth; - this._container.style.left = this._lineNumberWidth + "px"; - - var newWidth = this._textWidth + "px"; - var newHeight = this._lineToOffset(this._textModel.linesCount) + "px"; - this._sheet.style.width = newWidth; - this._sheet.style.height = newHeight; - - if (newLineNumberDigits !== this._lineNumberDigits) { - this._lineNumberDigits = newLineNumberDigits; - this.repaintAll(); - } - - // Changes to size can change the client area (scrollers can appear/disappear) - this.resize(); - this.setCoalescingUpdate(false); - }, - - resize: function() - { - if (this._canvas.width !== this._container.clientWidth || this._canvas.height !== this._container.clientHeight) { - this._canvas.width = this._container.clientWidth + this._lineNumberWidth; - this._canvas.height = this._container.clientHeight; - this.repaintAll(); - } - }, - - repaintAll: function() - { - this._invalidateLines(0, this._textModel.linesCount); - this._paint(); - }, - - _invalidateLines: function(startLine, endLine) - { - if (!this._damage) - this._damage = [ { startLine: startLine, endLine: endLine } ]; - else { - for (var i = 0; i < this._damage.length; ++i) { - var chunk = this._damage[i]; - if (chunk.startLine <= endLine && chunk.endLine >= startLine) { - chunk.startLine = Math.min(chunk.startLine, startLine); - chunk.endLine = Math.max(chunk.endLine, endLine); - return; - } - } - this._damage.push({ startLine: startLine, endLine: endLine }); - } - }, - - _paint: function() - { - this._scrollTop = this._container.scrollTop; - this._scrollLeft = this._container.scrollLeft; - - if (this._paintCoalescingLevel) - return; - - this._updateDivDecorations(); - - this.paintLineNumbers(); - - for (var i = 0; this._damage && i < this._damage.length; ++i) - this._paintLines(this._damage[i].startLine, this._damage[i].endLine); - delete this._damage; - - this._updateCursor(this._selection.endLine, this._selection.endColumn); - }, - - _paintLines: function(firstLine, lastLine) - { - this._ctx.font = this._font; - this._ctx.textBaseline = "bottom"; - - firstLine = Math.max(firstLine, this._offsetToLine(this._scrollTop) - 1); - lastLine = Math.min(lastLine, this._offsetToLine(this._scrollTop + this._canvas.height) + 1); - if (firstLine > lastLine) - return; - - if (this._debugMode) { - WebInspector.log("Repaint %d:%d", firstLine, lastLine); - this._ctx.fillStyle = "rgb(255,255,0)"; - var fromOffset = this._lineToOffset(firstLine); - var toOffset = this._lineToOffset(lastLine); - this._ctx.fillRect(this._lineNumberWidth - 1, fromOffset - this._scrollTop, this._canvas.width - this._lineNumberWidth + 1, toOffset - fromOffset); - setTimeout(this._paintLinesContinuation.bind(this, firstLine, lastLine), 100); - } else - this._paintLinesContinuation(firstLine, lastLine); - }, - - _paintLinesContinuation: function(firstLine, lastLine) { - // Clip editor area. - this._ctx.save(); - this._ctx.beginPath(); - this._ctx.rect(this._lineNumberWidth - 1, 0, this._canvas.width - this._lineNumberWidth + 1, this._canvas.height); - this._ctx.clip(); - - // First clear the region, then update last line to fit model (this clears removed lines from the end of the document). - var fromOffset = this._lineToOffset(firstLine); - var toOffset = lastLine < this._textModel.linesCount ? this._lineToOffset(lastLine) : this._canvas.height + this._scrollTop; - - // Do not clear region when paintCurrentLine is likely to do all the necessary work. - if (this._readOnly || firstLine + 1 != lastLine || this._selection.endLine != firstLine) { - this._ctx.fillStyle = "rgb(255,255,255)"; - this._ctx.fillRect(0, fromOffset - this._scrollTop, this._canvas.width, toOffset - fromOffset); - } - lastLine = Math.min(lastLine, this._textModel.linesCount); - - // Paint current line for editable mode only. - if (!this._readOnly && this._selection.startLine === this._selection.endLine && firstLine <= this._selection.startLine && this._selection.startLine < lastLine) - this._paintCurrentLine(this._selection.startLine); - - this._paintSelection(firstLine, lastLine); - - if (this._highlightingEnabled) { - this._muteHighlightListener = true; - this._highlighter.highlight(lastLine); - delete this._muteHighlightListener; - } - for (var i = firstLine; i < lastLine; ++i) { - var lineOffset = this._lineToOffset(i) - this._scrollTop; - - if (this._lineDecorator) - this._lineDecorator.decorate(i, this._ctx, this._lineNumberWidth - 1, lineOffset, this._canvas.width - this._lineNumberWidth + 1, this._lineHeight(i), this._textLineHeight); - - var element = this._textModel.getAttribute(i, "div-decoration"); - if (element) - this._positionDivDecoration(i, element, true); - - this._paintLine(i, lineOffset); - } - this._ctx.restore(); - }, - - _paintLine: function(lineNumber, lineOffset) - { - var line = this._textModel.line(lineNumber); - if (!this._highlightingEnabled) { - this._ctx.fillStyle = "rgb(0,0,0)"; - this._ctx.fillText(line, this._lineNumberWidth - this._scrollLeft, lineOffset + this._textLineHeight); - return; - } - - if (line.length > 1000) { - // Optimization: no need to paint decorations outside visible area. - var firstColumn = this._columnForOffset(lineNumber, this._scrollLeft); - var lastColumn = this._columnForOffset(lineNumber, this._scrollLeft + this._canvas.width); - } - var highlighterState = this._textModel.getAttribute(lineNumber, "highlighter-state"); - var plainTextStart = -1; - for (var j = 0; j < line.length;) { - var attribute = highlighterState && highlighterState.attributes[j]; - if (attribute && firstColumn && j + attribute.length < firstColumn) { - j += attribute.length; - continue; - } - if (attribute && lastColumn && j > lastColumn) - break; - if (!attribute || !attribute.style) { - if (plainTextStart === -1) - plainTextStart = j; - j++; - } else { - if (plainTextStart !== -1) { - this._ctx.fillStyle = "rgb(0,0,0)"; - this._ctx.fillText(line.substring(plainTextStart, j), this._lineNumberWidth - this._scrollLeft + this._columnToOffset(lineNumber, plainTextStart), lineOffset + this._textLineHeight); - plainTextStart = -1; - } - this._ctx.fillStyle = attribute.style; - this._ctx.fillText(line.substring(j, j + attribute.length), this._lineNumberWidth - this._scrollLeft + this._columnToOffset(lineNumber, j), lineOffset + this._textLineHeight); - j += attribute.length; - } - } - if (plainTextStart !== -1) { - this._ctx.fillStyle = "rgb(0,0,0)"; - this._ctx.fillText(line.substring(plainTextStart, j), this._lineNumberWidth - this._scrollLeft + this._columnToOffset(lineNumber, plainTextStart), lineOffset + this._textLineHeight); - } - }, - - paintLineNumbers: function() - { - this._ctx.font = this._font; - this._ctx.textBaseline = "bottom"; - - this._ctx.fillStyle = "rgb(255,255,255)"; - this._ctx.fillRect(0, 0, this._lineNumberWidth - 2, this._canvas.height); - - this._ctx.fillStyle = "rgb(235,235,235)"; - this._ctx.fillRect(this._lineNumberWidth - 2, 0, 1, this._canvas.height); - - var firstLine = Math.max(0, this._offsetToLine(this._scrollTop) - 1); - var lastLine = Math.min(this._textModel.linesCount, this._offsetToLine(this._scrollTop + this._canvas.height) + 1); - - for (var i = firstLine; i < lastLine; ++i) { - var lineOffset = this._lineToOffset(i) - this._scrollTop; - this._ctx.fillStyle = "rgb(155,155,155)"; - if (this._lineNumberDecorator && this._lineNumberDecorator.decorate(i, this._ctx, 0, lineOffset, this._lineNumberWidth, this._lineHeight(i), this._textLineHeight)) - continue; - this._ctx.fillText(i + 1, (this._lineNumberDigits - this._decimalDigits(i + 1) + 1) * this._digitWidth, lineOffset + this._textLineHeight); - } - }, - - _paintCurrentLine: function(line) - { - this._ctx.fillStyle = "rgb(232, 242, 254)"; - this._ctx.fillRect(0, this._lineToOffset(line) - this._scrollTop, this._canvas.width, this._lineHeight(line)); - }, - - _scroll: function(e) - { - // Hide div-based cursor first. - this._cursor._cursorElement.style.display = "none"; - setTimeout(this._repaintOnScroll.bind(this), 10); - }, - - _repaintOnScroll: function() - { - if (this._scrollTop !== this._container.scrollTop || this._scrollLeft !== this._container.scrollLeft) { - this._scrollTop = this._container.scrollTop; - this._scrollLeft = this._container.scrollLeft; - this.repaintAll(); - } - }, - - _mouseUp: function(e) - { - this._isDragging = false; - }, - - _mouseDown: function(e) - { - if (e.button === 2 || (this._isMac && e.ctrlKey)) - return; - - var location = this._caretForMouseEvent(e); - - if (e.target === this.element && this._lineNumberDecorator) { - if (this._lineNumberDecorator.mouseDown(location.line, e)) - return; - } - - if (e.shiftKey) - this._setSelectionEnd(location.line, location.column); - else - this._setCaretLocation(location.line, location.column); - this._isDragging = true; - this._textModel.markUndoableState(); - }, - - _mouseMove: function(e) - { - if (!this._isDragging) - return; - var location = this._caretForMouseEvent(e); - this._setSelectionEnd(location.line, location.column) - }, - - _mouseOut: function(e) - { - }, - - _dblClick: function(e) - { - var location = this._caretForMouseEvent(e); - var range = this._textModel.wordRange(location.line, location.column); - this.setSelection(range.startLine, range.startColumn, range.endLine, range.endColumn); - }, - - _contextMenu: function(e) - { - if (e.target === this.element && this._lineNumberDecorator) { - var location = this._caretForMouseEvent(e); - if (this._lineNumberDecorator.contextMenu(location.line, e)) - return; - } else { - var range = this._selection.range(); - if (!range.isEmpty()) { - var text = this._textModel.copyRange(range); - var contextMenu = new WebInspector.ContextMenu(); - contextMenu.appendItem(WebInspector.UIString("Copy"), this._copy.bind(this)); - contextMenu.show(event); - } - } - }, - - _caretForMouseEvent: function(e) - { - var lineNumber = Math.max(0, this._offsetToLine(e.offsetY + (e.target === this.element ? this._scrollTop : 0)) - 1); - var offset = e.offsetX + this._scrollLeft; - return { line: lineNumber, column: this._columnForOffset(lineNumber, offset) }; - }, - - _columnForOffset: function(lineNumber, offset) - { - var length = 0; - var line = this._textModel.line(lineNumber); - - // First pretend it is monospace to get a quick guess. - var charWidth = this._ctx.measureText("a").width; - var index = Math.floor(offset / charWidth); - var indexOffset = this._ctx.measureText(line.substring(0, index)).width; - if (offset >= indexOffset && index < line.length && offset < indexOffset + this._ctx.measureText(line.charAt(index)).width) - return index; - - // Fallback to non-monospace. - var delta = indexOffset < offset ? 1 : -1; - while (index >=0 && index < line.length) { - index += delta; - indexOffset += delta * this._ctx.measureText(line.charAt(index)).width; - if (offset >= indexOffset && offset < indexOffset + charWidth) - return index; - } - return line.length; - }, - - _columnToOffset: function(lineNumber, column) - { - var line = this._textModel.line(lineNumber); - return this._ctx.measureText(line.substring(0, column)).width; - }, - - _keyDown: function(e) - { - var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e); - var handler = this._shortcuts[shortcutKey]; - if (handler) { - handler.call(this); - e.preventDefault(); - e.stopPropagation(); - return; - } - - if (this._handleNavigationKey(e)) { - e.preventDefault(); - e.stopPropagation(); - return; - } - - if (this._readOnly) - return; - - var keyCodes = WebInspector.KeyboardShortcut.KeyCodes; - switch (e.keyCode) { - case keyCodes.Backspace: - this._handleBackspaceKey(); - break; - case keyCodes.Delete: - this._handleDeleteKey(); - break; - case keyCodes.Tab: - this._replaceSelectionWith("\t"); - break; - case keyCodes.Enter: - this._replaceSelectionWith("\n"); - break; - default: - return; - } - - e.preventDefault(); - e.stopPropagation(); - }, - - _handleNavigationKey: function(e) - { - var caretLine = this._selection.endLine; - var caretColumn = this._selection.endColumn; - var arrowAction = e.shiftKey ? this._setSelectionEnd : this._setCaretLocation; - - var keyCodes = WebInspector.KeyboardShortcut.KeyCodes; - switch (e.keyCode) { - case keyCodes.Up: - case keyCodes.PageUp: - if (e.metaKey) - arrowAction.call(this, 0, 0, true); - else if (e.ctrlKey) - this._container.scrollTop -= this._lineHeight(caretLine); - else { - if (e.keyCode === keyCodes.Up) - arrowAction.call(this, caretLine - 1, this._desiredCaretColumn, true); - else { - var offset = Math.max(0, this._lineToOffset(caretLine) - this._canvas.height); - arrowAction.call(this, this._offsetToLine(offset), this._desiredCaretColumn, true); - } - } - break; - case keyCodes.Down: - case keyCodes.PageDown: - if (e.metaKey) - arrowAction.call(this, this._textModel.linesCount - 1, this._textModel.lineLength(this._textModel.linesCount - 1), true); - else if (e.ctrlKey) - this._container.scrollTop += this._lineHeight(caretLine); - else { - if (e.keyCode === keyCodes.Down) - arrowAction.call(this, caretLine + 1, this._desiredCaretColumn, true); - else { - var offset = this._lineToOffset(caretLine) + this._canvas.height; - arrowAction.call(this, this._offsetToLine(offset), this._desiredCaretColumn, true); - } - } - break; - case keyCodes.Home: - if (this._isMetaCtrl(e)) - arrowAction.call(this, 0, 0, true); - else - arrowAction.call(this, this._selection.endLine, 0); - break; - case keyCodes.End: - if (this._isMetaCtrl(e)) - arrowAction.call(this, this._textModel.linesCount - 1, this._textModel.lineLength(this._textModel.linesCount - 1), true); - else - arrowAction.call(this, this._selection.endLine, this._textModel.lineLength(this._selection.endLine)); - break; - case keyCodes.Left: - if (!e.shiftKey && !e.metaKey && !this._isAltCtrl(e) && !this._selection.isEmpty()) { - // Reset selection - var range = this._selection.range(); - this._setCaretLocation(range.startLine, range.startColumn); - } else if (e.metaKey) - arrowAction.call(this, this._selection.endLine, 0); - else if (caretColumn === 0 && caretLine > 0) - arrowAction.call(this, caretLine - 1, this._textModel.lineLength(caretLine - 1)); - else if (this._isAltCtrl(e)) { - caretColumn = this._textModel.wordStart(this._selection.endLine, this._selection.endColumn); - if (caretColumn === this._selection.endColumn) - caretColumn = 0; - arrowAction.call(this, caretLine, caretColumn); - } else - arrowAction.call(this, caretLine, caretColumn - 1); - break; - case keyCodes.Right: - var line = this._textModel.line(caretLine); - if (!e.shiftKey && !e.metaKey && !this._isAltCtrl(e) && !this._selection.isEmpty()) { - // Reset selection - var range = this._selection.range(); - this._setCaretLocation(range.endLine, range.endColumn); - } else if (e.metaKey) - arrowAction.call(this, this._selection.endLine, this._textModel.lineLength(this._selection.endLine)); - else if (caretColumn === line.length && caretLine < this._textModel.linesCount - 1) - arrowAction.call(this, caretLine + 1, 0); - else if (this._isAltCtrl(e)) { - caretColumn = this._textModel.wordEnd(this._selection.endLine, this._selection.endColumn); - if (caretColumn === this._selection.endColumn) - caretColumn = line.length; - arrowAction.call(this, caretLine, caretColumn); - } else - arrowAction.call(this, caretLine, caretColumn + 1); - break; - default: - return false; - } - this._textModel.markUndoableState(); - return true; - }, - - _textInput: function(e) - { - if (this._readOnly) - return; - - if (e.data && !e.altKey && !e.ctrlKey && !e.metaKey) { - this._replaceSelectionWith(e.data); - e.preventDefault(); - e.stopPropagation(); - } - }, - - _setCaretLocation: function(line, column, updown) - { - this.setSelection(line, column, line, column, updown); - }, - - _setSelectionEnd: function(line, column, updown) - { - if (!updown) - this._desiredCaretColumn = column; - - var end = this._fit(line, column); - this._selection.setEnd(end.line, end.column); - this.reveal(this._selection.endLine, this._selection.endColumn); - this._updateCursor(end.line, end.column); - }, - - _updateDivDecorations: function() - { - var firstLine = this._offsetToLine(this._scrollTop) - 1; - var lastLine = this._offsetToLine(this._scrollTop + this._canvas.height) + 1; - - var linesCount = this._textModel.linesCount; - for (var i = 0; i < linesCount; ++i) { - var element = this._textModel.getAttribute(i, "div-decoration"); - if (element) { - this._lineOffsetsCache.length = Math.min(this._lineOffsetsCache.length, i + 1); - this._positionDivDecoration(i, element, i > firstLine && i < lastLine); - } - } - }, - - _positionDivDecoration: function(lineNumber, element, visible) - { - element.style.position = "absolute"; - element.style.top = this._lineToOffset(lineNumber) - this._scrollTop + this._textLineHeight + "px"; - element.style.left = this._lineNumberWidth + "px"; - element.style.setProperty("max-width", this._canvas.width + "px"); - }, - - _updateCursor: function(line, column) - { - if (line >= this._textModel.linesCount) - return; - var offset = this._columnToOffset(line, column); - if (offset >= this._container.scrollLeft && !this._readOnly) - this._cursor.setLocation(this._lineNumberWidth + offset - 1, this._lineToOffset(line)); - else - this._cursor.hide(); - }, - - _fit: function(line, column) - { - line = Math.max(0, Math.min(line, this._textModel.linesCount - 1)); - var lineLength = this._textModel.lineLength(line); - column = Math.max(0, Math.min(column, lineLength)); - return { line: line, column: column }; - }, - - _paintSelection: function(firstLine, lastLine) - { - if (this._selection.isEmpty()) - return; - var range = this._selection.range(); - this._ctx.fillStyle = this._selectionColor; - - firstLine = Math.max(firstLine, range.startLine); - endLine = Math.min(lastLine, range.endLine + 1); - - for (var i = firstLine; i < endLine; ++i) { - var line = this._textModel.line(i); - var from, to; - - if (i === range.startLine) { - var offset = this._columnToOffset(range.startLine, range.startColumn); - from = offset - this._scrollLeft + this._lineNumberWidth - 1; - } else - from = 0; - - if (i === range.endLine) { - var offset = this._columnToOffset(range.endLine, range.endColumn); - to = offset - this._scrollLeft + this._lineNumberWidth - 1; - } else - to = this._canvas.width; - - this._ctx.fillRect(from, this._lineToOffset(i) - this._scrollTop, to - from, this._lineHeight(i)); - } - this._ctx.fillStyle = "rgb(0, 0, 0)"; - }, - - _beforeCopy: function(e) - { - if (!this._selection.isEmpty()) - e.preventDefault(); - }, - - _copy: function(e) - { - var range = this._selection.range(); - var text = this._textModel.copyRange(range); - - function delayCopy() - { - InspectorFrontendHost.copyText(text); - } - - setTimeout(delayCopy); - if (e) - e.preventDefault(); - }, - - _beforeCut: function(e) - { - if (!this._selection.isEmpty()) - e.preventDefault(); - }, - - _cut: function(e) - { - if (this._readOnly) { - e.preventDefault(); - return; - } - - this._textModel.markUndoableState(); - this._copy(e); - this._replaceSelectionWith(""); - }, - - _beforePaste: function(e) - { - e.preventDefault(); - }, - - _paste: function(e) - { - if (this._readOnly) { - e.preventDefault(); - return; - } - - var text = e.clipboardData.getData("Text"); - if (!text) - return; - - this._textModel.markUndoableState(); - this._replaceSelectionWith(text); - e.preventDefault(); - }, - - _replaceSelectionWith: function(newText, overrideRange) - { - var range = overrideRange || this._selection.range(); - this.setCoalescingUpdate(true); - var newRange = this._textModel.setText(range, newText); - this._setCaretLocation(newRange.endLine, newRange.endColumn); - this.setCoalescingUpdate(false); - }, - - setCoalescingUpdate: function(enabled) - { - if (enabled) - this._paintCoalescingLevel++; - else - this._paintCoalescingLevel--; - if (!this._paintCoalescingLevel) - this._paint(); - }, - - _selectAll: function() - { - // No need to reveal last selection line in select all. - this._selection.setStart(0, 0); - var lastLineNum = this._textModel.linesCount - 1; - this._selection.setEnd(lastLineNum, this._textModel.lineLength(lastLineNum)); - this._updateCursor(this._selection.endLine, this._selection.endColumn); - }, - - initFontMetrics: function() - { - var computedStyle = window.getComputedStyle(this.element); - this._font = computedStyle.fontSize + " " + computedStyle.fontFamily; - this._ctx.font = this._font; - this._digitWidth = this._ctx.measureText("0").width; - this._textLineHeight = Math.floor(parseInt(this._ctx.font) * 1.4); - this._cursor.setTextLineHeight(this._textLineHeight); - }, - - _registerShortcuts: function() - { - var modifiers = WebInspector.KeyboardShortcut.Modifiers; - this._shortcuts = {}; - this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", this._isMac ? modifiers.Meta : modifiers.Ctrl)] = this._handleUndo.bind(this); - this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.Shift | (this._isMac ? modifiers.Meta : modifiers.Ctrl))] = this._handleRedo.bind(this); - this._shortcuts[WebInspector.KeyboardShortcut.makeKey("a", this._isMac ? modifiers.Meta : modifiers.Ctrl)] = this._selectAll.bind(this); - if (this._isMac) - this._shortcuts[WebInspector.KeyboardShortcut.makeKey("d", modifiers.Ctrl)] = this._handleDeleteKey.bind(this); - - this._shortcuts[WebInspector.KeyboardShortcut.makeKey("d", modifiers.Ctrl | modifiers.Alt)] = this._handleToggleDebugMode.bind(this); - this._shortcuts[WebInspector.KeyboardShortcut.makeKey("h", modifiers.Ctrl | modifiers.Alt)] = this._handleToggleHighlightMode.bind(this); - }, - - _handleUndo: function() - { - this.setCoalescingUpdate(true); - var range = this._textModel.undo(); - if (range) - this._setCaretLocation(range.endLine, range.endColumn); - this.setCoalescingUpdate(false); - }, - - _handleRedo: function() - { - this.setCoalescingUpdate(true); - var range = this._textModel.redo(); - if (range) - this._setCaretLocation(range.endLine, range.endColumn); - this.setCoalescingUpdate(false); - }, - - _handleDeleteKey: function() - { - var range = this._selection.range(); - if (range.isEmpty()) { - if (range.endColumn < this._textModel.lineLength(range.startLine)) - range.endColumn++; - else if (range.endLine < this._textModel.linesCount) { - range.endLine++; - range.endColumn = 0; - } else - return; - } else - this._textModel.markUndoableState(); - this._replaceSelectionWith("", range); - }, - - _handleBackspaceKey: function() - { - var range = this._selection.range(); - if (range.isEmpty()) { - if (range.startColumn > 0) - range.startColumn--; - else if (range.startLine > 0) { - range.startLine--; - range.startColumn = this._textModel.lineLength(range.startLine); - } else - return; - } else - this._textModel.markUndoableState(); - this._replaceSelectionWith("", range); - }, - - _handleToggleDebugMode: function() - { - this._debugMode = !this._debugMode; - }, - - _handleToggleHighlightMode: function() - { - this._highlightingEnabled = !this._highlightingEnabled; - }, - - _isMetaCtrl: function(e) - { - return this._isMac ? e.metaKey : e.ctrlKey; - }, - - _isAltCtrl: function(e) - { - return this._isMac ? e.altKey : e.ctrlKey; - }, - - _decimalDigits: function(number) - { - return Math.ceil(Math.log(number + 1) / Math.log(10)); - } -} - -WebInspector.TextSelectionModel = function(changeListener) -{ - this.startLine = 0; - this.startColumn = 0; - this.endLine = 0; - this.endColumn = 0; - this._changeListener = changeListener; -} - -WebInspector.TextSelectionModel.prototype = { - setStart: function(line, column) - { - var oldRange = this.range(); - - this.startLine = line; - this.startColumn = column; - this.endLine = line; - this.endColumn = column; - - this._changeListener(oldRange, this.range()); - }, - - setEnd: function(line, column) - { - var oldRange = this.range(); - - this.endLine = line; - this.endColumn = column; - - this._changeListener(oldRange, this.range(), this.endLine, this.endColumn); - }, - - range: function() - { - if (this.startLine < this.endLine || (this.startLine === this.endLine && this.startColumn <= this.endColumn)) - return new WebInspector.TextRange(this.startLine, this.startColumn, this.endLine, this.endColumn); - else - return new WebInspector.TextRange(this.endLine, this.endColumn, this.startLine, this.startColumn); - }, - - isEmpty: function() - { - return this.startLine === this.endLine && this.startColumn === this.endColumn; - } -} - -WebInspector.TextCursor = function(cursorElement) -{ - this._visible = false; - this._cursorElement = cursorElement; -} - -WebInspector.TextCursor.prototype = { - setLocation: function(x, y) - { - this._x = x; - this._y = y; - if (this._paintInterval) { - window.clearInterval(this._paintInterval); - delete this._paintInterval; - } - this._paintInterval = window.setInterval(this._paint.bind(this, false), 500); - this._paint(true); - }, - - hide: function() - { - if (this._paintInterval) { - window.clearInterval(this._paintInterval); - delete this._paintInterval; - } - this._cursorElement.style.display = "none"; - }, - - setTextLineHeight: function(textLineHeight) - { - this._cursorElement.style.height = textLineHeight + "px"; - }, - - _paint: function(force) - { - if (force) - this._visible = true; - else - this._visible = !this._visible; - this._cursorElement.style.left = this._x + "px"; - this._cursorElement.style.top = this._y + "px"; - this._cursorElement.style.display = this._visible ? "block" : "none"; - } -} diff --git a/WebCore/inspector/front-end/TextEditorModel.js b/WebCore/inspector/front-end/TextEditorModel.js index fc56026..e56c269 100644 --- a/WebCore/inspector/front-end/TextEditorModel.js +++ b/WebCore/inspector/front-end/TextEditorModel.js @@ -97,6 +97,11 @@ WebInspector.TextEditorModel.prototype = { return newRange; }, + set replaceTabsWithSpaces(replaceTabsWithSpaces) + { + this._replaceTabsWithSpaces = replaceTabsWithSpaces; + }, + _innerSetText: function(range, text) { this._eraseRange(range); @@ -104,6 +109,8 @@ WebInspector.TextEditorModel.prototype = { return new WebInspector.TextRange(range.startLine, range.startColumn, range.startLine, range.startColumn); var newLines = text.split("\n"); + this._replaceTabsIfNeeded(newLines); + var prefix = this._lines[range.startLine].substring(0, range.startColumn); var prefixArguments = this._arguments var suffix = this._lines[range.startLine].substring(range.startColumn); @@ -124,6 +131,22 @@ WebInspector.TextEditorModel.prototype = { range.startLine + newLines.length - 1, postCaret); }, + _replaceTabsIfNeeded: function(lines) + { + if (!this._replaceTabsWithSpaces) + return; + var spaces = [ " ", " ", " ", " "]; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + var index = line.indexOf("\t"); + while (index !== -1) { + line = line.substring(0, index) + spaces[index % 4] + line.substring(index + 1); + index = line.indexOf("\t", index + 1); + } + lines[i] = line; + } + }, + _eraseRange: function(range) { if (range.isEmpty()) diff --git a/WebCore/inspector/front-end/TextViewer.js b/WebCore/inspector/front-end/TextViewer.js new file mode 100644 index 0000000..096464f --- /dev/null +++ b/WebCore/inspector/front-end/TextViewer.js @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.TextViewer = function(textModel, platform, url) +{ + this._textModel = textModel; + this._textModel.changeListener = this._buildChunks.bind(this); + this._highlighter = new WebInspector.TextEditorHighlighter(this._textModel, this._highlightDataReady.bind(this)); + + this.element = document.createElement("div"); + this.element.className = "text-editor monospace"; + this.element.tabIndex = 0; + + this.element.addEventListener("scroll", this._scroll.bind(this), false); + + this._url = url; + + this._linesContainerElement = document.createElement("table"); + this._linesContainerElement.className = "text-editor-lines"; + this._linesContainerElement.setAttribute("cellspacing", 0); + this._linesContainerElement.setAttribute("cellpadding", 0); + this.element.appendChild(this._linesContainerElement); + + this._defaultChunkSize = 50; + this._paintCoalescingLevel = 0; +} + +WebInspector.TextViewer.prototype = { + set mimeType(mimeType) + { + this._highlighter.mimeType = mimeType; + }, + + get textModel() + { + return this._textModel; + }, + + revealLine: function(lineNumber) + { + if (lineNumber >= this._textModel.linesCount) + return; + + var chunk = this._makeLineAChunk(lineNumber); + chunk.element.scrollIntoViewIfNeeded(); + }, + + addDecoration: function(lineNumber, decoration) + { + var chunk = this._makeLineAChunk(lineNumber); + chunk.addDecoration(decoration); + }, + + removeDecoration: function(lineNumber, decoration) + { + var chunk = this._makeLineAChunk(lineNumber); + chunk.removeDecoration(decoration); + }, + + markAndRevealRange: function(range) + { + if (this._rangeToMark) { + var markedLine = this._rangeToMark.startLine; + this._rangeToMark = null; + this._paintLines(markedLine, markedLine + 1); + } + + if (range) { + this._rangeToMark = range; + this.revealLine(range.startLine); + this._paintLines(range.startLine, range.startLine + 1); + } + }, + + highlightLine: function(lineNumber) + { + if (typeof this._highlightedLine === "number") { + var chunk = this._makeLineAChunk(this._highlightedLine); + chunk.removeDecoration("webkit-highlighted-line"); + } + this._highlightedLine = lineNumber; + this.revealLine(lineNumber); + var chunk = this._makeLineAChunk(lineNumber); + chunk.addDecoration("webkit-highlighted-line"); + }, + + _buildChunks: function() + { + this._linesContainerElement.removeChildren(); + + var paintLinesCallback = this._paintLines.bind(this); + this._textChunks = []; + for (var i = 0; i < this._textModel.linesCount; i += this._defaultChunkSize) { + var chunk = new WebInspector.TextChunk(this._textModel, i, i + this._defaultChunkSize, paintLinesCallback); + this._textChunks.push(chunk); + this._linesContainerElement.appendChild(chunk.element); + } + this._indexChunks(); + this._repaintAll(); + }, + + _makeLineAChunk: function(lineNumber) + { + if (!this._textChunks) + this._buildChunks(); + + var chunkNumber = this._chunkNumberForLine(lineNumber); + var oldChunk = this._textChunks[chunkNumber]; + if (oldChunk.linesCount === 1) + return oldChunk; + + var wasExpanded = oldChunk.expanded; + oldChunk.expanded = false; + + var insertIndex = oldChunk.chunkNumber + 1; + var paintLinesCallback = this._paintLines.bind(this); + + // Prefix chunk. + if (lineNumber > oldChunk.startLine) { + var prefixChunk = new WebInspector.TextChunk(this._textModel, oldChunk.startLine, lineNumber, paintLinesCallback); + this._textChunks.splice(insertIndex++, 0, prefixChunk); + this._linesContainerElement.insertBefore(prefixChunk.element, oldChunk.element); + } + + // Line chunk. + var lineChunk = new WebInspector.TextChunk(this._textModel, lineNumber, lineNumber + 1, paintLinesCallback); + this._textChunks.splice(insertIndex++, 0, lineChunk); + this._linesContainerElement.insertBefore(lineChunk.element, oldChunk.element); + + // Suffix chunk. + if (oldChunk.startLine + oldChunk.linesCount > lineNumber + 1) { + var suffixChunk = new WebInspector.TextChunk(this._textModel, lineNumber + 1, oldChunk.startLine + oldChunk.linesCount, paintLinesCallback); + this._textChunks.splice(insertIndex, 0, suffixChunk); + this._linesContainerElement.insertBefore(suffixChunk.element, oldChunk.element); + } + + // Remove enclosing chunk. + this._textChunks.splice(oldChunk.chunkNumber, 1); + this._linesContainerElement.removeChild(oldChunk.element); + this._indexChunks(); + + if (wasExpanded) { + if (prefixChunk) + prefixChunk.expanded = true; + lineChunk.expanded = true; + if (suffixChunk) + suffixChunk.expanded = true; + } + + return lineChunk; + }, + + _indexChunks: function() + { + for (var i = 0; i < this._textChunks.length; ++i) + this._textChunks[i].chunkNumber = i; + }, + + _scroll: function() + { + this._repaintAll(); + }, + + beginUpdates: function(enabled) + { + this._paintCoalescingLevel++; + }, + + endUpdates: function(enabled) + { + this._paintCoalescingLevel--; + if (!this._paintCoalescingLevel) + this._repaintAll(); + }, + + _chunkForOffset: function(offset) + { + var currentOffset = 0; + var row = this._linesContainerElement.firstChild; + while (row) { + var rowHeight = row.offsetHeight; + if (offset >= currentOffset && offset < currentOffset + rowHeight) + return row.chunkNumber; + row = row.nextSibling; + currentOffset += rowHeight; + } + return this._textChunks.length - 1; + }, + + _chunkNumberForLine: function(lineNumber) + { + for (var i = 0; i < this._textChunks.length; ++i) { + var line = this._textChunks[i].startLine; + if (lineNumber >= this._textChunks[i].startLine && lineNumber < this._textChunks[i].startLine + this._textChunks[i].linesCount) + return i; + } + return this._textChunks.length - 1; + }, + + _chunkForLine: function(lineNumber) + { + return this._textChunks[this._chunkNumberForLine(lineNumber)]; + }, + + _chunkStartLine: function(chunkNumber) + { + var lineNumber = 0; + for (var i = 0; i < chunkNumber && i < this._textChunks.length; ++i) + lineNumber += this._textChunks[i].linesCount; + return lineNumber; + }, + + _repaintAll: function() + { + if (this._paintCoalescingLevel) + return; + + if (!this._textChunks) + this._buildChunks(); + + var visibleFrom = this.element.scrollTop; + var visibleTo = this.element.scrollTop + this.element.clientHeight; + + var offset = 0; + var firstVisibleLine = -1; + var lastVisibleLine = 0; + var toExpand = []; + var toCollapse = []; + for (var i = 0; i < this._textChunks.length; ++i) { + var chunk = this._textChunks[i]; + var chunkHeight = chunk.height; + if (offset + chunkHeight > visibleFrom && offset < visibleTo) { + toExpand.push(chunk); + if (firstVisibleLine === -1) + firstVisibleLine = chunk.startLine; + lastVisibleLine = chunk.startLine + chunk.linesCount; + } else { + toCollapse.push(chunk); + if (offset >= visibleTo) + break; + } + offset += chunkHeight; + } + + for (var j = i; j < this._textChunks.length; ++j) + toCollapse.push(this._textChunks[i]); + + var selection = this._getSelection(); + + this._muteHighlightListener = true; + this._highlighter.highlight(lastVisibleLine); + delete this._muteHighlightListener; + + for (var i = 0; i < toCollapse.length; ++i) + toCollapse[i].expanded = false; + for (var i = 0; i < toExpand.length; ++i) + toExpand[i].expanded = true; + + this._restoreSelection(selection); + }, + + _highlightDataReady: function(fromLine, toLine) + { + if (this._muteHighlightListener) + return; + + var selection; + for (var i = fromLine; i < toLine; ++i) { + var lineRow = this._textModel.getAttribute(i, "line-row"); + if (!lineRow || lineRow.highlighted) + continue; + if (!selection) + selection = this._getSelection(); + this._paintLine(lineRow, i); + } + this._restoreSelection(selection); + }, + + _paintLines: function(fromLine, toLine) + { + for (var i = fromLine; i < toLine; ++i) { + var lineRow = this._textModel.getAttribute(i, "line-row"); + if (lineRow) + this._paintLine(lineRow, i); + } + }, + + _paintLine: function(lineRow, lineNumber) + { + var element = lineRow.lastChild; + var highlighterState = this._textModel.getAttribute(lineNumber, "highlighter-state"); + var line = this._textModel.line(lineNumber); + + if (!highlighterState) { + if (this._rangeToMark && this._rangeToMark.startLine === lineNumber) + this._markRange(element, line, this._rangeToMark.startColumn, this._rangeToMark.endColumn); + return; + } + + element.removeChildren(); + + var plainTextStart = -1; + for (var j = 0; j < line.length;) { + if (j > 1000) { + // This line is too long - do not waste cycles on minified js highlighting. + break; + } + var attribute = highlighterState && highlighterState.attributes[j]; + if (!attribute || !attribute.style) { + if (plainTextStart === -1) + plainTextStart = j; + j++; + } else { + if (plainTextStart !== -1) { + element.appendChild(document.createTextNode(line.substring(plainTextStart, j))); + plainTextStart = -1; + } + element.appendChild(this._createSpan(line.substring(j, j + attribute.length), attribute.tokenType)); + j += attribute.length; + } + } + if (plainTextStart !== -1) + element.appendChild(document.createTextNode(line.substring(plainTextStart, line.length))); + if (this._rangeToMark && this._rangeToMark.startLine === lineNumber) + this._markRange(element, line, this._rangeToMark.startColumn, this._rangeToMark.endColumn); + if (lineRow.decorationsElement) + element.appendChild(lineRow.decorationsElement); + }, + + _getSelection: function() + { + var selection = window.getSelection(); + if (selection.isCollapsed) + return null; + var selectionRange = selection.getRangeAt(0); + var start = this._selectionToPosition(selectionRange.startContainer, selectionRange.startOffset); + var end = this._selectionToPosition(selectionRange.endContainer, selectionRange.endOffset); + return new WebInspector.TextRange(start.line, start.column, end.line, end.column); + }, + + _restoreSelection: function(range) + { + if (!range) + return; + var startRow = this._textModel.getAttribute(range.startLine, "line-row"); + if (startRow) + var start = startRow.lastChild.rangeBoundaryForOffset(range.startColumn); + else { + var offset = range.startColumn; + var chunkNumber = this._chunkNumberForLine(range.startLine); + for (var i = this._chunkStartLine(chunkNumber); i < range.startLine; ++i) + offset += this._textModel.line(i).length + 1; // \n + var lineCell = this._textChunks[chunkNumber].element.lastChild; + if (lineCell.firstChild) + var start = { container: lineCell.firstChild, offset: offset }; + else + var start = { container: lineCell, offset: 0 }; + } + + var endRow = this._textModel.getAttribute(range.endLine, "line-row"); + if (endRow) + var end = endRow.lastChild.rangeBoundaryForOffset(range.endColumn); + else { + var offset = range.endColumn; + var chunkNumber = this._chunkNumberForLine(range.endLine); + for (var i = this._chunkStartLine(chunkNumber); i < range.endLine; ++i) + offset += this._textModel.line(i).length + 1; // \n + var lineCell = this._textChunks[chunkNumber].element.lastChild; + if (lineCell.firstChild) + var end = { container: lineCell.firstChild, offset: offset }; + else + var end = { container: lineCell, offset: 0 }; + } + + var selectionRange = document.createRange(); + selectionRange.setStart(start.container, start.offset); + selectionRange.setEnd(end.container, end.offset); + + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(selectionRange); + }, + + _selectionToPosition: function(container, offset) + { + if (container === this.element && offset === 0) + return { line: 0, column: 0 }; + if (container === this.element && offset === 1) + return { line: this._textModel.linesCount - 1, column: this._textModel.lineLength(this._textModel.linesCount - 1) }; + + var lineRow = container.enclosingNodeOrSelfWithNodeName("tr"); + var lineNumber = lineRow.lineNumber; + if (container.nodeName === "TD" && offset === 0) + return { line: lineNumber, column: 0 }; + if (container.nodeName === "TD" && offset === 1) + return { line: lineNumber, column: this._textModel.lineLength(lineNumber) }; + + var column = 0; + if (lineRow.chunk) { + // This is chunk. + var text = lineRow.lastChild.textContent; + for (var i = 0; i < offset; ++i) { + if (text.charAt(i) === "\n") { + lineNumber++; + column = 0; + } else + column++; + } + return { line: lineNumber, column: column }; + } + + // This is individul line. + var column = 0; + var node = lineRow.lastChild.traverseNextTextNode(lineRow.lastChild); + while (node && node !== container) { + column += node.textContent.length; + node = node.traverseNextTextNode(lineRow.lastChild); + } + column += offset; + return { line: lineRow.lineNumber, column: column }; + }, + + _createSpan: function(content, className) + { + if (className === "html-resource-link" || className === "html-external-link") + return this._createLink(content, className === "html-external-link"); + + var span = document.createElement("span"); + span.className = "webkit-" + className; + span.appendChild(document.createTextNode(content)); + return span; + }, + + _createLink: function(content, isExternal) + { + var quote = content.charAt(0); + if (content.length > 1 && (quote === "\"" || quote === "'")) + content = content.substring(1, content.length - 1); + else + quote = null; + + var a = WebInspector.linkifyURLAsNode(this._rewriteHref(content), content, null, isExternal); + var span = document.createElement("span"); + span.className = "webkit-html-attribute-value"; + if (quote) + span.appendChild(document.createTextNode(quote)); + span.appendChild(a); + if (quote) + span.appendChild(document.createTextNode(quote)); + return span; + }, + + _rewriteHref: function(hrefValue, isExternal) + { + if (!this._url || !hrefValue || hrefValue.indexOf("://") > 0) + return hrefValue; + return WebInspector.completeURL(this._url, hrefValue); + }, + + _markRange: function(element, lineText, startOffset, endOffset) + { + var markNode = document.createElement("span"); + markNode.className = "webkit-markup"; + markNode.textContent = lineText.substring(startOffset, endOffset); + + var markLength = endOffset - startOffset; + var boundary = element.rangeBoundaryForOffset(startOffset); + var textNode = boundary.container; + var text = textNode.textContent; + + if (boundary.offset + markLength < text.length) { + // Selection belong to a single split mode. + textNode.textContent = text.substring(boundary.offset + markLength); + textNode.parentElement.insertBefore(markNode, textNode); + var prefixNode = document.createTextNode(text.substring(0, boundary.offset)); + textNode.parentElement.insertBefore(prefixNode, markNode); + return; + } + + var parentElement = textNode.parentElement; + var anchorElement = textNode.nextSibling; + + markLength -= text.length - boundary.offset; + textNode.textContent = text.substring(0, boundary.offset); + textNode = textNode.traverseNextTextNode(element); + + while (textNode) { + var text = textNode.textContent; + if (markLength < text.length) { + textNode.textContent = text.substring(markLength); + break; + } + + markLength -= text.length; + textNode.textContent = ""; + textNode = textNode.traverseNextTextNode(element); + } + + parentElement.insertBefore(markNode, anchorElement); + }, + + resize: function() + { + this._repaintAll(); + } +} + +WebInspector.TextChunk = function(textModel, startLine, endLine, paintLinesCallback) +{ + this.element = document.createElement("tr"); + this._textModel = textModel; + this.element.chunk = this; + this.element.lineNumber = startLine; + + this.startLine = startLine; + endLine = Math.min(this._textModel.linesCount, endLine); + this.linesCount = endLine - startLine; + + this._lineNumberElement = document.createElement("td"); + this._lineNumberElement.className = "webkit-line-number"; + this._lineNumberElement.textContent = this._lineNumberText(this.startLine); + this.element.appendChild(this._lineNumberElement); + + this._lineContentElement = document.createElement("td"); + this._lineContentElement.className = "webkit-line-content"; + this.element.appendChild(this._lineContentElement); + + this._expanded = false; + + var lines = []; + for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) + lines.push(this._textModel.line(i)); + this._lineContentElement.textContent = lines.join("\n"); + this._paintLines = paintLinesCallback; +} + +WebInspector.TextChunk.prototype = { + addDecoration: function(decoration) + { + if (typeof decoration === "string") { + this.element.addStyleClass(decoration); + return; + } + if (!this.element.decorationsElement) { + this.element.decorationsElement = document.createElement("div"); + this._lineContentElement.appendChild(this.element.decorationsElement); + } + this.element.decorationsElement.appendChild(decoration); + }, + + removeDecoration: function(decoration) + { + if (typeof decoration === "string") { + this.element.removeStyleClass(decoration); + return; + } + if (!this.element.decorationsElement) + return; + this.element.decorationsElement.removeChild(decoration); + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(expanded) + { + if (this._expanded === expanded) + return; + + this._expanded = expanded; + + if (this.linesCount === 1) { + this._textModel.setAttribute(this.startLine, "line-row", this.element); + if (expanded) + this._paintLines(this.startLine, this.startLine + 1); + return; + } + + if (expanded) { + var parentElement = this.element.parentElement; + for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) { + var lineRow = document.createElement("tr"); + lineRow.lineNumber = i; + + var lineNumberElement = document.createElement("td"); + lineNumberElement.className = "webkit-line-number"; + lineNumberElement.textContent = this._lineNumberText(i); + lineRow.appendChild(lineNumberElement); + + var lineContentElement = document.createElement("td"); + lineContentElement.className = "webkit-line-content"; + lineContentElement.textContent = this._textModel.line(i); + lineRow.appendChild(lineContentElement); + + this._textModel.setAttribute(i, "line-row", lineRow); + parentElement.insertBefore(lineRow, this.element); + } + parentElement.removeChild(this.element); + + this._paintLines(this.startLine, this.startLine + this.linesCount); + } else { + var firstLine = this._textModel.getAttribute(this.startLine, "line-row"); + var parentElement = firstLine.parentElement; + + parentElement.insertBefore(this.element, firstLine); + for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) { + var lineRow = this._textModel.getAttribute(i, "line-row"); + this._textModel.removeAttribute(i, "line-row"); + parentElement.removeChild(lineRow); + } + } + }, + + get height() + { + if (!this._expanded) + return this.element.offsetHeight; + var result = 0; + for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) { + var lineRow = this._textModel.getAttribute(i, "line-row"); + result += lineRow.offsetHeight; + } + return result; + }, + + _lineNumberText: function(lineNumber) + { + var totalDigits = Math.ceil(Math.log(this._textModel.linesCount + 1) / Math.log(10)); + var digits = Math.ceil(Math.log(lineNumber + 2) / Math.log(10)); + + var text = ""; + for (var i = digits; i < totalDigits; ++i) + text += " "; + text += lineNumber + 1; + return text; + } +} diff --git a/WebCore/inspector/front-end/WebKit.qrc b/WebCore/inspector/front-end/WebKit.qrc index 20e9aa2..efa2bfc 100644 --- a/WebCore/inspector/front-end/WebKit.qrc +++ b/WebCore/inspector/front-end/WebKit.qrc @@ -2,8 +2,10 @@ <qresource prefix="/webkit/inspector"> <file>inspector.html</file> <file>AbstractTimelinePanel.js</file> + <file>AuditCategories.js</file> <file>AuditLauncherView.js</file> <file>AuditResultView.js</file> + <file>AuditRules.js</file> <file>AuditsPanel.js</file> <file>BottomUpProfileDataGridTree.js</file> <file>Breakpoint.js</file> @@ -37,7 +39,6 @@ <file>InspectorFrontendHostStub.js</file> <file>KeyboardShortcut.js</file> <file>MetricsSidebarPane.js</file> - <file>NativeTextViewer.js</file> <file>Object.js</file> <file>ObjectPropertiesSection.js</file> <file>ObjectProxy.js</file> @@ -73,10 +74,10 @@ <file>StylesSidebarPane.js</file> <file>SummaryBar.js</file> <file>TestController.js</file> - <file>TextEditor.js</file> <file>TextEditorHighlighter.js</file> <file>TextEditorModel.js</file> <file>TextPrompt.js</file> + <file>TextViewer.js</file> <file>TimelineAgent.js</file> <file>TimelineGrid.js</file> <file>TimelineOverviewPane.js</file> @@ -90,7 +91,7 @@ <file>audits.css</file> <file>inspector.css</file> <file>inspectorSyntaxHighlight.css</file> - <file>textEditor.css</file> + <file>textViewer.css</file> <file>Images/back.png</file> <file>Images/checker.png</file> <file>Images/clearConsoleButtonGlyph.png</file> diff --git a/WebCore/inspector/front-end/audits.css b/WebCore/inspector/front-end/audits.css index 35db76b..9d02c80 100644 --- a/WebCore/inspector/front-end/audits.css +++ b/WebCore/inspector/front-end/audits.css @@ -262,11 +262,11 @@ body.inactive .audit-launcher-view button, .audit-launcher-view button:disabled margin: 0 5px 5px 0; } -.audit-launcher-view input[type="radio"]:active { +.audit-launcher-view input[type="radio"]:active:not(:disabled) { background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(194, 194, 194)), to(rgb(239, 239, 239))); } -.audit-launcher-view input[type="radio"]:checked { +.audit-launcher-view input[type="radio"]:checked:not(:disabled), .audit-launcher-view input[type="radio"]:checked:disabled { background: url(Images/radioDot.png) center no-repeat, -webkit-gradient(linear, left top, left bottom, from(rgb(252, 252, 252)), to(rgb(223, 223, 223))); } diff --git a/WebCore/inspector/front-end/inspector.css b/WebCore/inspector/front-end/inspector.css index 45b8ec3..53f1e4b 100644 --- a/WebCore/inspector/front-end/inspector.css +++ b/WebCore/inspector/front-end/inspector.css @@ -3772,7 +3772,7 @@ ol.breakpoint-list { white-space: pre; } -.source-breakpoint-condition { +.source-frame-breakpoint-condition { z-index: 30; padding: 4px; background-color: rgb(203, 226, 255); @@ -3781,7 +3781,7 @@ ol.breakpoint-list { width: 90%; } -.source-breakpoint-message { +.source-frame-breakpoint-message { background-color: transparent; font-family: Lucida Grande, sans-serif; font-weight: normal; @@ -3793,7 +3793,7 @@ ol.breakpoint-list { margin: 0 0 2px 0; } -#source-breakpoint-condition { +#source-frame-breakpoint-condition { margin: 0; border: 1px inset rgb(190, 190, 190) !important; width: 100%; diff --git a/WebCore/inspector/front-end/inspector.html b/WebCore/inspector/front-end/inspector.html index 26264dc..4ddd10e 100644 --- a/WebCore/inspector/front-end/inspector.html +++ b/WebCore/inspector/front-end/inspector.html @@ -30,7 +30,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <link rel="stylesheet" type="text/css" href="audits.css"> - <link rel="stylesheet" type="text/css" href="textEditor.css"> + <link rel="stylesheet" type="text/css" href="textViewer.css"> <link rel="stylesheet" type="text/css" href="inspector.css"> <link rel="stylesheet" type="text/css" href="inspectorSyntaxHighlight.css"> <script type="text/javascript" src="utilities.js"></script> @@ -91,13 +91,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. <script type="text/javascript" src="AuditsPanel.js"></script> <script type="text/javascript" src="AuditResultView.js"></script> <script type="text/javascript" src="AuditLauncherView.js"></script> + <script type="text/javascript" src="AuditRules.js"></script> + <script type="text/javascript" src="AuditCategories.js"></script> <script type="text/javascript" src="ResourceView.js"></script> <script type="text/javascript" src="SourceFrame.js"></script> <script type="text/javascript" src="DOMSyntaxHighlighter.js"></script> <script type="text/javascript" src="TextEditorModel.js"></script> - <script type="text/javascript" src="TextEditor.js"></script> <script type="text/javascript" src="TextEditorHighlighter.js"></script> - <script type="text/javascript" src="NativeTextViewer.js"></script> + <script type="text/javascript" src="TextViewer.js"></script> <script type="text/javascript" src="SourceTokenizer.js"></script> <script type="text/javascript" src="SourceCSSTokenizer.js"></script> <script type="text/javascript" src="SourceHTMLTokenizer.js"></script> diff --git a/WebCore/inspector/front-end/inspector.js b/WebCore/inspector/front-end/inspector.js index de20739..77d3f42 100644 --- a/WebCore/inspector/front-end/inspector.js +++ b/WebCore/inspector/front-end/inspector.js @@ -468,17 +468,8 @@ WebInspector.loaded = function() var previousToolbarItem = toolbarElement.children[0]; this.panelOrder = []; - for (var panelName in this.panels) { - var panel = this.panels[panelName]; - var panelToolbarItem = panel.toolbarItem; - this.panelOrder.push(panel); - panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this)); - if (previousToolbarItem) - toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling); - else - toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild); - previousToolbarItem = panelToolbarItem; - } + for (var panelName in this.panels) + previousToolbarItem = WebInspector.addPanelToolbarIcon(toolbarElement, this.panels[panelName], previousToolbarItem); this.Tips = { ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")} @@ -529,6 +520,18 @@ WebInspector.loaded = function() InspectorFrontendHost.loaded(); } +WebInspector.addPanelToolbarIcon = function(toolbarElement, panel, previousToolbarItem) +{ + var panelToolbarItem = panel.toolbarItem; + this.panelOrder.push(panel); + panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this)); + if (previousToolbarItem) + toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling); + else + toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild); + return panelToolbarItem; +} + var windowLoaded = function() { var localizedStringsURL = InspectorFrontendHost.localizedStringsURL(); @@ -749,8 +752,11 @@ WebInspector.documentKeyDown = function(event) var shouldShowAuditsPanel = event.ctrlKey && !event.shiftKey && !event.metaKey && event.altKey; if (shouldShowAuditsPanel) { - if (!this.panels.audits) + if (!this.panels.audits) { this.panels.audits = new WebInspector.AuditsPanel(); + var toolbarElement = document.getElementById("toolbar"); + WebInspector.addPanelToolbarIcon(toolbarElement, this.panels.audits, this.panels.console.toolbarItem); + } this.currentPanel = this.panels.audits; } @@ -1050,7 +1056,7 @@ WebInspector.updateResource = function(identifier, payload) if (match) { var protocol = match[1].toLowerCase(); if (protocol.indexOf("http") === 0 || protocol === "file") - this.addCookieDomain(protocol === "file" ? "" : match[2]); + this._addCookieDomain(protocol === "file" ? "" : match[2]); } } @@ -1131,7 +1137,7 @@ WebInspector.addDatabase = function(payload) this.panels.storage.addDatabase(database); } -WebInspector.addCookieDomain = function(domain) +WebInspector._addCookieDomain = function(domain) { // Eliminate duplicate domains from the list. if (domain in this.cookieDomains) @@ -1208,7 +1214,6 @@ WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLin WebInspector.pausedScript = function(callFrames) { - callFrames = JSON.parse(callFrames); this.panels.scripts.debuggerPaused(callFrames); } @@ -1265,7 +1270,7 @@ WebInspector.updateConsoleMessageExpiredCount = function(count) WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage(message, WebInspector.ConsoleMessage.MessageLevel.Warning)); } -WebInspector.addConsoleMessage = function(payload, argumentsStringified, opt_args) +WebInspector.addConsoleMessage = function(payload, opt_args) { var consoleMessage = new WebInspector.ConsoleMessage( payload.source, @@ -1275,14 +1280,7 @@ WebInspector.addConsoleMessage = function(payload, argumentsStringified, opt_arg payload.url, payload.groupLevel, payload.repeatCount); - var parsedArguments = []; - for (var i = 2; i < arguments.length; i++) { - if (argumentsStringified) - parsedArguments.push(JSON.parse(arguments[i])); - else - parsedArguments.push(arguments[i]); - } - consoleMessage.setMessageBody(parsedArguments); + consoleMessage.setMessageBody(Array.prototype.slice.call(arguments, 1)); this.console.addMessage(consoleMessage); } diff --git a/WebCore/inspector/front-end/textEditor.css b/WebCore/inspector/front-end/textEditor.css deleted file mode 100644 index 93495f2..0000000 --- a/WebCore/inspector/front-end/textEditor.css +++ /dev/null @@ -1,90 +0,0 @@ -.text-editor { - position: absolute; - top:0; - left:0; - right:0; - bottom:0; - -webkit-user-select: text; - -webkit-user-modify: read-write-plaintext-only; -} - -.text-editor-readonly { - -webkit-user-modify: read-only; -} - -.text-editor-canvas { - position: absolute; - top:0; - left:0; - right:0; - bottom:0; - z-index: 10; - pointer-events: none; -} - -.text-editor-container { - position: absolute; - top:0; - left:0; - right:0; - bottom:0; - overflow: auto; -} - -.text-editor-cursor { - -webkit-user-select: none; - -webkit-user-modify: none; - position: absolute; - top:0; - left:0; - width:1px; - height: 14px; - z-index: 20; - background-color: black; - pointer-events: none; -} - -.native-text-editor-line { - white-space: pre; -} - -.webkit-html-message-bubble { - -webkit-box-shadow: black 0px 2px 5px; - -webkit-border-radius: 9px; - -webkit-border-fit: lines; - font-size: 10px; - font-family: Lucida Grande, sans-serif; - font-weight: bold; - margin: 6px 25px; - padding: 0 7px 1px; - z-index:20; -} - -.webkit-html-warning-message { - background-color: rgb(100%, 62%, 42%); - border: 2px solid rgb(100%, 52%, 21%); -} - -.webkit-html-error-message { - background-color: rgb(100%, 42%, 42%); - border: 2px solid rgb(100%, 31%, 31%); -} - -.webkit-html-message-line { - padding-left: 23px; - text-indent: -20px; -} - -.webkit-html-message-line-hover { - padding-left: 23px; - text-indent: -20px; - white-space: auto; - text-overflow: auto; - overflow: auto; -} - -.webkit-html-message-icon { - position: relative; - top: 2px; - margin: 0 4px; -} diff --git a/WebCore/inspector/front-end/textViewer.css b/WebCore/inspector/front-end/textViewer.css new file mode 100644 index 0000000..af079bc --- /dev/null +++ b/WebCore/inspector/front-end/textViewer.css @@ -0,0 +1,149 @@ +.text-editor { + position: absolute; + top:0; + left:0; + right:0; + bottom:0; + white-space: pre-wrap; + overflow: auto; +} + +.text-editor-lines { + border: 0; + width: 100%; + vertical-align: baseline; + -webkit-border-horizontal-spacing: 0; + -webkit-border-vertical-spacing: 0; + -webkit-user-select: text; +} + +.webkit-html-message-bubble { + -webkit-box-shadow: black 0px 2px 5px; + -webkit-border-radius: 9px; + -webkit-border-fit: lines; + font-size: 10px; + font-family: Lucida Grande, sans-serif; + font-weight: bold; + margin: 6px 25px; + padding: 0 7px 1px; + z-index:20; + max-width: 80%; + +} + +.webkit-html-warning-message { + background-color: rgb(100%, 62%, 42%); + border: 2px solid rgb(100%, 52%, 21%); +} + +.webkit-html-error-message { + background-color: rgb(100%, 42%, 42%); + border: 2px solid rgb(100%, 31%, 31%); +} + +.webkit-html-message-line { + padding-left: 23px; + text-indent: -20px; +} + +.webkit-html-message-line-hover { + padding-left: 23px; + text-indent: -20px; + white-space: auto; + text-overflow: auto; + overflow: auto; +} + +.webkit-html-message-icon { + position: relative; + top: 2px; + margin: 0 4px; +} + +.webkit-line-number { + color: rgb(128, 128, 128); + text-align: right; + white-space: pre; + word-break: normal; + -webkit-user-select: none; + background-color: rgb(240, 240, 240); + border-right: 1px solid rgb(187, 187, 187) !important; + padding-left: 2px; + padding-right: 2px; + vertical-align: top; + background-repeat: no-repeat; + background-position: right 1px; +} + +.webkit-line-content { + white-space: pre-wrap; + padding-left: 2px; +} + +.webkit-execution-line .webkit-line-number { + color: transparent; + background-image: -webkit-canvas(program-counter); +} + +.webkit-breakpoint .webkit-line-number { + color: white; + background-image: -webkit-canvas(breakpoint); +} + +.webkit-breakpoint-disabled .webkit-line-number { + color: white; + background-image: -webkit-canvas(breakpoint-disabled); +} + +.webkit-breakpoint.webkit-execution-line .webkit-line-number { + color: transparent; + background-image: -webkit-canvas(breakpoint-program-counter); +} + +.webkit-breakpoint-disabled.webkit-execution-line .webkit-line-number { + color: transparent; + background-image: -webkit-canvas(breakpoint-disabled-program-counter); +} + +.webkit-breakpoint.webkit-breakpoint-conditional .webkit-line-number { + color: white; + background-image: -webkit-canvas(breakpoint-conditional); +} + +.webkit-breakpoint-disabled.webkit-breakpoint-conditional .webkit-line-number { + color: white; + background-image: -webkit-canvas(breakpoint-disabled-conditional); +} + +.webkit-breakpoint.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { + color: transparent; + background-image: -webkit-canvas(breakpoint-conditional-program-counter); +} + +.webkit-breakpoint-disabled.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { + color: transparent; + background-image: -webkit-canvas(breakpoint-disabled-conditional-program-counter); +} + +.webkit-execution-line .webkit-line-content { + background-color: rgb(171, 191, 254); + outline: 1px solid rgb(64, 115, 244); +} + +.webkit-markup { + -webkit-border-radius: 4px; + padding: 2px 1px 2px 3px; + margin-left: -4px; + margin-top: -2px; + -webkit-box-shadow: rgba(0, 0, 0, .5) 3px 3px 4px; + background-color: rgb(241, 234, 0); +} + +.webkit-highlighted-line .webkit-line-content { + -webkit-animation: "fadeout" 2s 0s; +} + +@-webkit-keyframes fadeout { + from {background-color: rgb(255, 255, 120); } + to { background-color: white; } +} diff --git a/WebCore/inspector/front-end/utilities.js b/WebCore/inspector/front-end/utilities.js index d54005a..60d3b45 100644 --- a/WebCore/inspector/front-end/utilities.js +++ b/WebCore/inspector/front-end/utilities.js @@ -145,6 +145,30 @@ Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, di return result; } +Node.prototype.traverseNextTextNode = function(stayWithin) +{ + var node = this.traverseNextNode(stayWithin); + if (!node) + return; + + while (node && node.nodeType !== Node.TEXT_NODE) + node = node.traverseNextNode(stayWithin); + + return node; +} + +Node.prototype.rangeBoundaryForOffset = function(offset) +{ + var node = this.traverseNextTextNode(this); + while (node && offset > node.nodeValue.length) { + offset -= node.nodeValue.length; + node = node.traverseNextTextNode(this); + } + if (!node) + return { container: this, offset: 0 }; + return { container: node, offset: offset }; +} + Element.prototype.removeStyleClass = function(className) { // Test for the simple case first. |