diff options
Diffstat (limited to 'WebCore/inspector/front-end')
68 files changed, 4247 insertions, 2767 deletions
diff --git a/WebCore/inspector/front-end/AbstractTimelinePanel.js b/WebCore/inspector/front-end/AbstractTimelinePanel.js index 6c5d715..cfec9da 100644 --- a/WebCore/inspector/front-end/AbstractTimelinePanel.js +++ b/WebCore/inspector/front-end/AbstractTimelinePanel.js @@ -308,8 +308,10 @@ WebInspector.AbstractTimelinePanel.prototype = { staleItemsLength = this._staleItems.length; } + + const isBarOpaqueAtLeft = this.sidebarTree.selectedTreeElement && this.sidebarTree.selectedTreeElement.isBarOpaqueAtLeft; for (var i = 0; i < staleItemsLength; ++i) - this._staleItems[i]._itemsTreeElement._itemGraph.refresh(this.calculator); + this._staleItems[i]._itemsTreeElement._itemGraph.refresh(this.calculator, isBarOpaqueAtLeft); this._staleItems = []; diff --git a/WebCore/inspector/front-end/AuditCategories.js b/WebCore/inspector/front-end/AuditCategories.js index 6931b5f..01b5ff9 100644 --- a/WebCore/inspector/front-end/AuditCategories.js +++ b/WebCore/inspector/front-end/AuditCategories.js @@ -37,9 +37,9 @@ WebInspector.AuditCategories.PagePerformance.AuditCategoryName = "Web Page Perfo 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})); + this.addRule(new WebInspector.AuditRules.UnusedCssRule(), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.CssInHeadRule(), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.StylesScriptsOrderRule(), WebInspector.AuditRule.Severity.Severe); } } @@ -54,16 +54,16 @@ WebInspector.AuditCategories.NetworkUtilization.AuditCategoryName = "Network Uti 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()); + this.addRule(new WebInspector.AuditRules.GzipRule(), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.ImageDimensionsRule(), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.CookieSizeRule(400), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.StaticCookielessRule(5), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.CombineJsResourcesRule(2), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.CombineCssResourcesRule(2), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.MinimizeDnsLookupsRule(4), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.ParallelizeDownloadRule(4, 10, 0.5), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.BrowserCacheControlRule(), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.ProxyCacheControlRule(), WebInspector.AuditRule.Severity.Warning); } } diff --git a/WebCore/inspector/front-end/AuditLauncherView.js b/WebCore/inspector/front-end/AuditLauncherView.js index f2a2fd2..60b7c32 100644 --- a/WebCore/inspector/front-end/AuditLauncherView.js +++ b/WebCore/inspector/front-end/AuditLauncherView.js @@ -42,6 +42,8 @@ WebInspector.AuditLauncherView = function(categoriesById, runnerCallback) this._contentElement.className = "audit-launcher-view-content"; this.element.appendChild(this._contentElement); + this._resetResourceCount(); + function categorySortFunction(a, b) { var aTitle = a.displayName || ""; @@ -67,6 +69,9 @@ WebInspector.AuditLauncherView.prototype = { { if (!this._auditPresentStateLabelElement) return; + + this._resetResourceCount(); + if (isTracking) { this._auditPresentStateLabelElement.nodeValue = WebInspector.UIString("Audit Present State"); this._auditPresentStateElement.disabled = false; @@ -79,12 +84,63 @@ WebInspector.AuditLauncherView.prototype = { } }, + get totalResources() + { + return this._totalResources; + }, + + set totalResources(x) + { + if (this._totalResources === x) + return; + this._totalResources = x; + this._updateResourceProgress(); + }, + + get loadedResources() + { + return this._loadedResources; + }, + + set loadedResources(x) + { + if (this._loadedResources === x) + return; + this._loadedResources = x; + this._updateResourceProgress(); + }, + + _resetResourceCount: function() + { + this.loadedResources = 0; + + // We never receive a resourceStarted notification for the main resource + // (see InspectorController.willSendRequest()) + this.totalResources = 1; + }, + + resourceStarted: function(resource) + { + ++this.totalResources; + }, + + resourceFinished: function(resource) + { + ++this.loadedResources; + }, + + reset: function() + { + this._resetResourceCount(); + }, + _setAuditRunning: function(auditRunning) { if (this._auditRunning === auditRunning) return; this._auditRunning = auditRunning; this._updateButton(); + this._updateResourceProgress(); }, _launchButtonClicked: function(event) @@ -95,12 +151,8 @@ WebInspector.AuditLauncherView.prototype = { if (this._categoriesById[id]._checkboxElement.checked) catIds.push(id); } - function profilingFinishedCallback() - { - this._setAuditRunning(false); - } this._setAuditRunning(true); - this._runnerCallback(catIds, this._auditPresentStateElement.checked, profilingFinishedCallback.bind(this)); + this._runnerCallback(catIds, this._auditPresentStateElement.checked, this._setAuditRunning.bind(this, false)); }, _selectAllClicked: function(checkCategories) @@ -121,11 +173,10 @@ WebInspector.AuditLauncherView.prototype = { _createCategoryElement: function(title, id) { - var element; var labelElement = document.createElement("label"); labelElement.id = this._categoryIdPrefix + id; - element = document.createElement("input"); + var element = document.createElement("input"); element.type = "checkbox"; labelElement.appendChild(element); labelElement.appendChild(document.createTextNode(title)); @@ -166,6 +217,10 @@ WebInspector.AuditLauncherView.prototype = { this._totalCategoriesCount = this._categoriesElement.childNodes.length; this._currentCategoriesCount = 0; + var flexibleSpaceElement = document.createElement("div"); + flexibleSpaceElement.className = "flexible-space"; + this._contentElement.appendChild(flexibleSpaceElement); + this._buttonContainerElement = document.createElement("div"); this._buttonContainerElement.className = "button-container"; @@ -188,10 +243,19 @@ WebInspector.AuditLauncherView.prototype = { this._buttonContainerElement.appendChild(labelElement); this._launchButton = document.createElement("button"); - this._launchButton.setAttribute("type", "button"); + this._launchButton.type = "button"; + this._launchButton.textContent = WebInspector.UIString("Run"); this._launchButton.addEventListener("click", this._launchButtonClicked.bind(this), false); this._buttonContainerElement.appendChild(this._launchButton); + this._resourceProgressContainer = document.createElement("span"); + this._resourceProgressContainer.className = "resource-progress"; + var resourceProgressImage = document.createElement("img"); + this._resourceProgressContainer.appendChild(resourceProgressImage); + this._resourceProgressTextElement = document.createElement("span"); + this._resourceProgressContainer.appendChild(this._resourceProgressTextElement); + this._buttonContainerElement.appendChild(this._resourceProgressContainer); + this._contentElement.appendChild(this._buttonContainerElement); this._selectAllClicked(this._selectAllCheckboxElement.checked); @@ -199,25 +263,22 @@ WebInspector.AuditLauncherView.prototype = { this._updateButton(); }, - _updateButton: function() + _updateResourceProgress: function() { - this._launchButton.disabled = !this._currentCategoriesCount || this._auditRunning; - if (this._auditRunning) - this._launchButton.textContent = WebInspector.UIString("Running..."); - else - this._launchButton.textContent = WebInspector.UIString("Run"); - }, + if (!this._resourceProgressContainer) + return; - show: function(parentElement) - { - WebInspector.View.prototype.show.call(this, parentElement); - setTimeout(this.resize(), 0); + if (!this._auditRunning) + this._resourceProgressContainer.addStyleClass("hidden"); + else { + this._resourceProgressContainer.removeStyleClass("hidden"); + this._resourceProgressTextElement.textContent = WebInspector.UIString("Loading (%d of %d)", this.loadedResources, this.totalResources); + } }, - resize: function() + _updateButton: function() { - if (this._categoriesElement) - this._categoriesElement.style.height = (this._buttonContainerElement.totalOffsetTop - this._categoriesElement.totalOffsetTop) + "px"; + this._launchButton.disabled = !this._currentCategoriesCount || this._auditRunning; } } diff --git a/WebCore/inspector/front-end/AuditResultView.js b/WebCore/inspector/front-end/AuditResultView.js index ac818fb..2f4afbd 100644 --- a/WebCore/inspector/front-end/AuditResultView.js +++ b/WebCore/inspector/front-end/AuditResultView.js @@ -31,24 +31,14 @@ WebInspector.AuditResultView = function(categoryResults) { WebInspector.View.call(this); + this.element.className = "audit-result-view"; - this.element.id = "audit-result-view"; - - function entrySortFunction(a, b) - { - var result = b.type - a.type; - if (!result) - result = (a.value || "").localeCompare(b.value || ""); - return result; - } - - for (var i = 0; i < categoryResults.length; ++i) { - var entries = categoryResults[i].entries; - if (entries) { - entries.sort(entrySortFunction); - this.element.appendChild(new WebInspector.AuditCategoryResultPane(categoryResults[i]).element); - } + function categorySorter(a, b) { + return (a.title || "").localeCompare(b.title || ""); } + categoryResults.sort(categorySorter); + for (var i = 0; i < categoryResults.length; ++i) + this.element.appendChild(new WebInspector.AuditCategoryResultPane(categoryResults[i]).element); } WebInspector.AuditResultView.prototype.__proto__ = WebInspector.View.prototype; @@ -57,82 +47,60 @@ WebInspector.AuditResultView.prototype.__proto__ = WebInspector.View.prototype; WebInspector.AuditCategoryResultPane = function(categoryResult) { WebInspector.SidebarPane.call(this, categoryResult.title); - this.expand(); - for (var i = 0; i < categoryResult.entries.length; ++i) - this.bodyElement.appendChild(new WebInspector.AuditRuleResultPane(categoryResult.entries[i]).element); -} - -WebInspector.AuditCategoryResultPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; - - -WebInspector.AuditRuleResultPane = function(ruleResult) -{ - WebInspector.SidebarPane.call(this, ruleResult.value); - if (!ruleResult.children) - return; - - this._decorateRuleResult(ruleResult); + var treeOutlineElement = document.createElement("ol"); + this.bodyElement.addStyleClass("audit-result-tree"); + this.bodyElement.appendChild(treeOutlineElement); - for (var i = 0; i < ruleResult.children.length; ++i) { - var section = new WebInspector.AuditRuleResultChildSection(ruleResult.children[i]); - this.bodyElement.appendChild(section.element); - } -} + this._treeOutline = new TreeOutline(treeOutlineElement); + this._treeOutline.expandTreeElementsWhenArrowing = true; -WebInspector.AuditRuleResultPane.prototype = { - _decorateRuleResult: function(ruleResult) + function ruleSorter(a, b) { - if (ruleResult.type == WebInspector.AuditRuleResult.Type.NA) - return; - - var scoreElement = document.createElement("img"); - scoreElement.className = "score"; - var className = (ruleResult.type == WebInspector.AuditRuleResult.Type.Violation) ? "red" : "green"; - scoreElement.addStyleClass(className); - this.element.insertBefore(scoreElement, this.element.firstChild); + var result = WebInspector.AuditRule.SeverityOrder[a.severity || 0] - WebInspector.AuditRule.SeverityOrder[b.severity || 0]; + if (!result) + result = (a.value || "").localeCompare(b.value || ""); + return result; } -} -WebInspector.AuditRuleResultPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + categoryResult.ruleResults.sort(ruleSorter); + for (var i = 0; i < categoryResult.ruleResults.length; ++i) { + var ruleResult = categoryResult.ruleResults[i]; + var treeElement = this._appendResult(this._treeOutline, ruleResult); + treeElement.listItemElement.addStyleClass("audit-result"); -WebInspector.AuditRuleResultChildSection = function(entry) -{ - WebInspector.Section.call(this, entry.value); - var children = entry.children; - this._hasChildren = !!children && children.length; - if (!this._hasChildren) - this.element.addStyleClass("blank-section"); - else { - this.contentElement = document.createElement("div"); - this.contentElement.addStyleClass("section-content"); - for (var i = 0; i < children.length; ++i) { - var paraElement = document.createElement("p"); - paraElement.innerHTML = children[i].value; - this.contentElement.appendChild(paraElement); + if (ruleResult.severity) { + var severityElement = document.createElement("img"); + severityElement.className = "severity-" + ruleResult.severity; + treeElement.listItemElement.appendChild(severityElement); } - this.contentElement.appendChild(paraElement); - this.element.appendChild(this.contentElement); } + this.expand(); } -WebInspector.AuditRuleResultChildSection.prototype = { - - // title is considered pure HTML - set title(x) - { - if (this._title === x) - return; - this._title = x; - - this.titleElement.innerHTML = x; - }, - - expand: function() +WebInspector.AuditCategoryResultPane.prototype = { + _appendResult: function(parentTreeElement, result) { - if (this._hasChildren) - WebInspector.Section.prototype.expand.call(this); + var title = result.value; + if (result.violationCount) + title = String.sprintf("%s (%d)", title, result.violationCount); + + var treeElement = new TreeElement(title, null, !!result.children); + parentTreeElement.appendChild(treeElement); + + if (result.className) + treeElement.listItemElement.addStyleClass(result.className); + if (result.children) { + for (var i = 0; i < result.children.length; ++i) + this._appendResult(treeElement, result.children[i]); + } + if (result.expanded) { + treeElement.listItemElement.removeStyleClass("parent"); + treeElement.listItemElement.addStyleClass("parent-expanded"); + treeElement.expand(); + } + return treeElement; } } -WebInspector.AuditRuleResultChildSection.prototype.__proto__ = WebInspector.Section.prototype; +WebInspector.AuditCategoryResultPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/WebCore/inspector/front-end/AuditRules.js b/WebCore/inspector/front-end/AuditRules.js index 210b8a9..d2c5f09 100644 --- a/WebCore/inspector/front-end/AuditRules.js +++ b/WebCore/inspector/front-end/AuditRules.js @@ -42,27 +42,6 @@ WebInspector.AuditRules.CacheableResponseCodes = 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 = {}; @@ -84,9 +63,9 @@ WebInspector.AuditRules.getDomainToResourcesMap = function(resources, types, reg return domainToResourcesMap; } -WebInspector.AuditRules.evaluateInTargetWindow = function(func, callback) +WebInspector.AuditRules.evaluateInTargetWindow = function(func, args, callback) { - InjectedScriptAccess.getDefault().evaluateOnSelf(func.toString(), callback); + InjectedScriptAccess.getDefault().evaluateOnSelf(func.toString(), args, callback); } @@ -98,43 +77,29 @@ WebInspector.AuditRules.GzipRule = function() 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))); + var totalSavings = 0; + var compressedSize = 0; + var candidateSize = 0; + var summary = result.addChild("", true); + for (var i = 0, length = resources.length; i < length; ++i) { + var resource = resources[i]; + if (this._shouldCompress(resource)) { + var size = resource.resourceSize; + candidateSize += size; + if (this._isCompressed(resource)) { + compressedSize += size; + continue; } + var savings = 2 * size / 3; + totalSavings += savings; + summary.addChild(String.sprintf("%s could save ~%s", WebInspector.AuditRuleResult.linkifyDisplayName(resource.url), Number.bytesToString(savings))); + result.violationCount++; } - 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); } + if (!totalSavings) + return callback(null); + summary.value = String.sprintf("Compressing the following resources with gzip could reduce their transfer size by about two thirds (~%s):", Number.bytesToString(totalSavings)); + callback(result); }, _isCompressed: function(resource) @@ -145,105 +110,100 @@ WebInspector.AuditRules.GzipRule.prototype = { _shouldCompress: function(resource) { - return WebInspector.Resource.Type.isTextType(resource.type) && resource.domain && resource.contentLength !== undefined && resource.contentLength > 150; + return WebInspector.Resource.Type.isTextType(resource.type) && resource.domain && resource.resourceSize !== undefined && resource.resourceSize > 150; } } WebInspector.AuditRules.GzipRule.prototype.__proto__ = WebInspector.AuditRule.prototype; -WebInspector.AuditRules.CombineExternalResourcesRule = function(id, name, type, resourceTypeName, parametersObject) +WebInspector.AuditRules.CombineExternalResourcesRule = function(id, name, type, resourceTypeName, allowedPerDomain) { - WebInspector.AuditRule.call(this, id, name, parametersObject); + WebInspector.AuditRule.call(this, id, name); this._type = type; this._resourceTypeName = resourceTypeName; + this._allowedPerDomain = allowedPerDomain; } 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); + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, [this._type], WebInspector.URLRegExp); + var penalizedResourceCount = 0; + // TODO: refactor according to the chosen i18n approach + var summary = result.addChild("", true); + for (var domain in domainToResourcesMap) { + var domainResources = domainToResourcesMap[domain]; + var extraResourceCount = domainResources.length - this._allowedPerDomain; + if (extraResourceCount <= 0) + continue; + penalizedResourceCount += extraResourceCount - 1; + summary.addChild(String.sprintf("%d %s resources served from %s.", domainResources.length, this._resourceTypeName, WebInspector.AuditRuleResult.resourceDomain(domain))); + result.violationCount += domainResources.length; } + if (!penalizedResourceCount) + return callback(null); + + summary.value = "There are multiple resources served from same domain. Consider combining them into as few files as possible."; + 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 = function(allowedPerDomain) { + WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-externaljs", "Combine external JavaScript", WebInspector.Resource.Type.Script, "JavaScript", allowedPerDomain); } 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 = function(allowedPerDomain) { + WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-externalcss", "Combine external CSS", WebInspector.Resource.Type.Stylesheet, "CSS", allowedPerDomain); } 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 = function(hostCountThreshold) { + WebInspector.AuditRule.call(this, "network-minimizelookups", "Minimize DNS lookups"); + this._hostCountThreshold = hostCountThreshold; } 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); + var summary = result.addChild(""); + 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 + summary.addSnippet(match[2]); + result.violationCount++; } + if (!summary.children || summary.children.length <= this._hostCountThreshold) + return callback(null); + + summary.value = "The following domains only serve one resource each. If possible, avoid the extra DNS lookups by serving these resources from existing domains."; + callback(result); } } WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype.__proto__ = WebInspector.AuditRule.prototype; -WebInspector.AuditRules.ParallelizeDownloadRule = function(parametersObject) +WebInspector.AuditRules.ParallelizeDownloadRule = function(optimalHostnameCount, minRequestThreshold, minBalanceThreshold) { - WebInspector.AuditRule.call(this, "network-parallelizehosts", "Parallelize downloads across hostnames", parametersObject); + WebInspector.AuditRule.call(this, "network-parallelizehosts", "Parallelize downloads across hostnames"); + this._optimalHostnameCount = optimalHostnameCount; + this._minRequestThreshold = minRequestThreshold; + this._minBalanceThreshold = minBalanceThreshold; } @@ -257,65 +217,50 @@ WebInspector.AuditRules.ParallelizeDownloadRule.prototype = { 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); + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap( + resources, + [WebInspector.Resource.Type.Stylesheet, WebInspector.Resource.Type.Image], + WebInspector.URLRegExp, + true); - if (!hosts.length) - return; // no hosts (local file or something) + var hosts = []; + for (var url in domainToResourcesMap) + hosts.push(url); - hosts.sort(hostSorter); + if (!hosts.length) + return callback(null); // no hosts (local file or something) - var optimalHostnameCount = this.getValue("OptimalHostnameCount"); - if (hosts.length > optimalHostnameCount) - hosts.splice(optimalHostnameCount); + hosts.sort(hostSorter); - var busiestHostResourceCount = domainToResourcesMap[hosts[0]].length; - var resourceCountAboveThreshold = busiestHostResourceCount - this.getValue("MinRequestThreshold"); - if (resourceCountAboveThreshold <= 0) - return; + var optimalHostnameCount = this._optimalHostnameCount; + if (hosts.length > optimalHostnameCount) + hosts.splice(optimalHostnameCount); - var avgResourcesPerHost = 0; - for (var i = 0, size = hosts.length; i < size; ++i) - avgResourcesPerHost += domainToResourcesMap[hosts[i]].length; + var busiestHostResourceCount = domainToResourcesMap[hosts[0]].length; + var resourceCountAboveThreshold = busiestHostResourceCount - this._minRequestThreshold; + if (resourceCountAboveThreshold <= 0) + return callback(null); - // Assume optimal parallelization. - avgResourcesPerHost /= optimalHostnameCount; + var avgResourcesPerHost = 0; + for (var i = 0, size = hosts.length; i < size; ++i) + avgResourcesPerHost += domainToResourcesMap[hosts[i]].length; - avgResourcesPerHost = Math.max(avgResourcesPerHost, 1); + // Assume optimal parallelization. + avgResourcesPerHost /= optimalHostnameCount; + avgResourcesPerHost = Math.max(avgResourcesPerHost, 1); - var pctAboveAvg = (resourceCountAboveThreshold / avgResourcesPerHost) - 1.0; + var pctAboveAvg = (resourceCountAboveThreshold / avgResourcesPerHost) - 1.0; + var minBalanceThreshold = this._minBalanceThreshold; + if (pctAboveAvg < minBalanceThreshold) + return callback(null); - var minBalanceThreshold = this.getValue("MinBalanceThreshold"); - if (pctAboveAvg < minBalanceThreshold) { - result.score = 100; - return; - } + var resourcesOnBusiestHost = domainToResourcesMap[hosts[0]]; + var entry = result.addChild(String.sprintf("This page makes %d parallelizable requests to %s. Increase download parallelization by distributing the following requests across multiple hostnames.", busiestHostResourceCount, hosts[0]), true); + for (var i = 0; i < resourcesOnBusiestHost.length; ++i) + entry.addURL(resourcesOnBusiestHost[i].url); - 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); - } + result.violationCount = resourcesOnBusiestHost.length; + callback(result); } } @@ -324,156 +269,132 @@ WebInspector.AuditRules.ParallelizeDownloadRule.prototype.__proto__ = WebInspect // 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.AuditRules.UnusedCssRule = function() { - WebInspector.AuditRule.call(this, "page-unusedcss", "Remove unused CSS", parametersObject); + WebInspector.AuditRule.call(this, "page-unusedcss", "Remove unused CSS rules"); } 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 evalCallback(styleSheets) { + if (!styleSheets.length) + return callback(null); - function routine() - { - var styleSheets = document.styleSheets; - if (!styleSheets) - return {}; - var styleSheetToUnusedRules = []; - var inlineBlockOrdinal = 0; - var totalCSSLength = 0; - var pseudoSelectorRegexp = /:hover|:link|:active|:visited|:focus/; + var pseudoSelectorRegexp = /:hover|:link|:active|:visited|:focus|:before|:after/; + var selectors = []; + var testedSelectors = {}; 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)) + if (rule.selectorText.match(pseudoSelectorRegexp)) continue; - var nodes = document.querySelectorAll(rule.selectorText); - if (nodes && nodes.length) + selectors.push(rule.selectorText); + testedSelectors[rule.selectorText] = 1; + } + } + + function selectorsCallback(callback, styleSheets, testedSelectors, foundSelectors) + { + var inlineBlockOrdinal = 0; + var totalStylesheetSize = 0; + var totalUnusedStylesheetSize = 0; + var summary; + + for (var i = 0; i < styleSheets.length; ++i) { + var styleSheet = styleSheets[i]; + var stylesheetSize = 0; + var unusedStylesheetSize = 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; + stylesheetSize += textLength; + if (!testedSelectors[rule.selectorText] || foundSelectors[rule.selectorText]) + continue; + unusedStylesheetSize += textLength; + unusedRules.push(rule.selectorText); + } + totalStylesheetSize += stylesheetSize; + totalUnusedStylesheetSize += unusedStylesheetSize; + + if (!unusedRules.length) continue; - unusedRules.push([rule.selectorText, textLength]); + + var url = styleSheet.href ? WebInspector.AuditRuleResult.linkifyDisplayName(styleSheet.href) : String.sprintf("Inline block #%d", ++inlineBlockOrdinal); + var pctUnused = Math.round(100 * unusedStylesheetSize / stylesheetSize); + if (!summary) + summary = result.addChild("", true); + var entry = summary.addChild(String.sprintf("%s: %d%% (estimated) is not used by the current page.", url, pctUnused)); + + for (var j = 0; j < unusedRules.length; ++j) + entry.addSnippet(unusedRules[j]); + + result.violationCount += unusedRules.length; } - if (unusedRules.length) { - styleSheetToUnusedRules.push(styleSheet.href ? "href" : "inline"); - styleSheetToUnusedRules.push(currentStyleSheetSize); - styleSheetToUnusedRules.push(styleSheet.href ? styleSheet.href : ++inlineBlockOrdinal); - styleSheetToUnusedRules.push(unusedRules); + + if (!totalUnusedStylesheetSize) + return callback(null); + + var totalUnusedPercent = Math.round(100 * totalUnusedStylesheetSize / totalStylesheetSize); + summary.value = String.sprintf("%d%% of CSS (estimated) is not used by the current page.", totalUnusedPercent); + + callback(result); + } + + function routine(selectorArray) + { + var result = {}; + for (var i = 0; i < selectorArray.length; ++i) { + try { + var nodes = document.querySelectorAll(selectorArray[i]); + if (nodes && nodes.length) + result[selectorArray[i]] = true; + } catch(e) { + // ignore and mark as unused + } } + return result; } - styleSheetToUnusedRules.push("totalLength"); - styleSheetToUnusedRules.push(totalCSSLength); - return styleSheetToUnusedRules; + + WebInspector.AuditRules.evaluateInTargetWindow(routine, [selectors], selectorsCallback.bind(null, callback, styleSheets, testedSelectors)); } - WebInspector.AuditRules.evaluateInTargetWindow(routine, evalCallback); + function routine() + { + var styleSheets = document.styleSheets; + if (!styleSheets) + return false; + + return routineResult; + } + + InspectorBackend.getAllStyles(WebInspector.Callback.wrap(evalCallback)); } } WebInspector.AuditRules.UnusedCssRule.prototype.__proto__ = WebInspector.AuditRule.prototype; -WebInspector.AuditRules.CacheControlRule = function(id, name, parametersObject) +WebInspector.AuditRules.CacheControlRule = function(id, name) { - WebInspector.AuditRule.call(this, id, name, parametersObject); + WebInspector.AuditRule.call(this, id, name); } 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); - } + var cacheableAndNonCacheableResources = this._cacheableAndNonCacheableResources(resources); + if (cacheableAndNonCacheableResources[0].length) + this.runChecks(cacheableAndNonCacheableResources[0], result); + this.handleNonCacheableResources(cacheableAndNonCacheableResources[1], result); + + callback(result); }, handleNonCacheableResources: function() @@ -495,36 +416,19 @@ WebInspector.AuditRules.CacheControlRule.prototype = { return processedResources; }, - execCheck: function(messageText, resourceCheckFunction, resources, severity, result) + execCheck: function(messageText, resourceCheckFunction, resources, result) { - var topMessage; - var failingResources = 0; var resourceCount = resources.length; - var outputResources = []; + var urls = []; 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 (resourceCheckFunction.call(this, resources[i])) + urls.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; - } + if (urls.length) { + var entry = result.addChild(messageText, true); + entry.addURLs(urls); + result.violationCount += urls.length; } - return topMessage; }, freshnessLifetimeGreaterThan: function(resource, timeMs) @@ -612,47 +516,34 @@ WebInspector.AuditRules.CacheControlRule.prototype = { WebInspector.AuditRules.CacheControlRule.prototype.__proto__ = WebInspector.AuditRule.prototype; -WebInspector.AuditRules.BrowserCacheControlRule = function(parametersObject) +WebInspector.AuditRules.BrowserCacheControlRule = function() { - WebInspector.AuditRules.CacheControlRule.call(this, "http-browsercache", "Leverage browser caching", parametersObject); + WebInspector.AuditRules.CacheControlRule.call(this, "http-browsercache", "Leverage browser caching"); } 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 = []; + var entry = result.addChild("The following resources are explicitly non-cacheable. Consider making them cacheable if possible:", true); + result.violationCount += resources.length; for (var i = 0; i < resources.length; ++i) - resourceOutput.push(resources[i].url); - message.appendChild(WebInspector.AuditRules.arrayAsUL(resourceOutput, true)); + entry.addURL(resources[i].url); } }, 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); + 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, result); + this.execCheck("The following resources specify a \"Vary\" header that disables caching in most versions of Internet Explorer:", + this._varyCheck, resources, result); + this.execCheck("The following cacheable resources have a short freshness lifetime:", + this._oneMonthExpirationCheck, resources, 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); + this.execCheck("To further improve cache hit rate, specify an expiration one year in the future for the following cacheable resources:", + this._oneYearExpirationCheck, resources, result); }, _missingExpirationCheck: function(resource) @@ -691,26 +582,19 @@ WebInspector.AuditRules.BrowserCacheControlRule.prototype = { 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 = function() { + WebInspector.AuditRules.CacheControlRule.call(this, "http-proxycache", "Leverage proxy caching"); } 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); + this.execCheck("Resources with a \"?\" in the URL are not cached by most proxy caching servers:", + this._questionMarkCheck, resources, result); + this.execCheck("Consider adding a \"Cache-Control: public\" header to the following resources:", + this._publicCachingCheck, resources, 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, result); }, _questionMarkCheck: function(resource) @@ -735,102 +619,104 @@ WebInspector.AuditRules.ProxyCacheControlRule.prototype = { WebInspector.AuditRules.ProxyCacheControlRule.prototype.__proto__ = WebInspector.AuditRules.CacheControlRule.prototype; -WebInspector.AuditRules.ImageDimensionsRule = function(parametersObject) +WebInspector.AuditRules.ImageDimensionsRule = function() { - WebInspector.AuditRule.call(this, "page-imagedims", "Specify image dimensions", parametersObject); + WebInspector.AuditRule.call(this, "page-imagedims", "Specify image dimensions"); } WebInspector.AuditRules.ImageDimensionsRule.prototype = { doRun: function(resources, result, callback) { - function evalCallback(evalResult, isException) + function doneCallback(context) { - 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); + var map = context.urlToNoDimensionCount; + for (var url in map) { + var entry = entry || result.addChild("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:", true); + var value = WebInspector.AuditRuleResult.linkifyDisplayName(url); + if (map[url] > 1) + value += String.sprintf(" (%d uses)", map[url]); + entry.addChild(value); + result.violationCount++; } + callback(entry ? result : null); } - function routine() + function imageStylesReady(imageId, context, styles) { - 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; + --context.imagesLeft; + + const node = WebInspector.domAgent.nodeForId(imageId); + var src = node.getAttribute("src"); + for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) { + if (frameOwnerCandidate.documentURL) { + var completeSrc = WebInspector.completeURL(frameOwnerCandidate.documentURL, src); + break; } - return false; } + if (completeSrc) + src = completeSrc; - function hasWidth(element, cssText, rules) { - return hasDimension(element, cssText, rules, widthRegExp, "width"); + const computedStyle = new WebInspector.CSSStyleDeclaration(styles.computedStyle); + if (computedStyle.getPropertyValue("position") === "absolute") { + if (!context.imagesLeft) + doneCallback(context); + return; } - function hasHeight(element, cssText, rules) { - return hasDimension(element, cssText, rules, heightRegExp, "height"); + var widthFound = "width" in styles.styleAttributes; + var heightFound = "height" in styles.styleAttributes; + + for (var i = styles.matchedCSSRules.length - 1; i >= 0 && !(widthFound && heightFound); --i) { + var style = WebInspector.CSSStyleDeclaration.parseRule(styles.matchedCSSRules[i]).style; + if (style.getPropertyValue("width") !== "") + widthFound = true; + if (style.getPropertyValue("height") !== "") + heightFound = true; + } + + if (!widthFound || !heightFound) { + if (src in context.urlToNoDimensionCount) + ++context.urlToNoDimensionCount[src]; + else + context.urlToNoDimensionCount[src] = 1; } - 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") + if (!context.imagesLeft) + doneCallback(context); + } + + function receivedImages(imageIds) + { + if (!imageIds || !imageIds.length) + return callback(null); + var context = {imagesLeft: imageIds.length, urlToNoDimensionCount: {}}; + for (var i = imageIds.length - 1; i >= 0; --i) + InspectorBackend.getStyles(WebInspector.Callback.wrap(imageStylesReady.bind(this, imageIds[i], context)), imageIds[i], true); + } + + function pushImageNodes() + { + const nodeIds = []; + var nodes = document.getElementsByTagName("img"); + for (var i = 0; i < nodes.length; ++i) { + if (!nodes[i].src) 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; - } + var nodeId = this.getNodeId(nodes[i]); + nodeIds.push(nodeId); } - return found ? {totalImages: images.length, map: urlToNoDimensionCount} : null; + return nodeIds; } - WebInspector.AuditRules.evaluateInTargetWindow(routine, evalCallback.bind(this)); + WebInspector.AuditRules.evaluateInTargetWindow(pushImageNodes, null, receivedImages); } } WebInspector.AuditRules.ImageDimensionsRule.prototype.__proto__ = WebInspector.AuditRule.prototype; -WebInspector.AuditRules.CssInHeadRule = function(parametersObject) +WebInspector.AuditRules.CssInHeadRule = function() { - WebInspector.AuditRule.call(this, "page-cssinhead", "Put CSS in the document head", parametersObject); + WebInspector.AuditRule.call(this, "page-cssinhead", "Put CSS in the document head"); } WebInspector.AuditRules.CssInHeadRule.prototype = { @@ -838,36 +724,24 @@ WebInspector.AuditRules.CssInHeadRule.prototype = { { 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; + if (isException || !evalResult) + return callback(null); + + var summary = result.addChild(""); + + var outputMessages = []; + for (var url in evalResult) { + var urlViolations = evalResult[url]; + if (urlViolations[0]) { + result.addChild(String.sprintf("%s style block(s) in the %s body should be moved to the document head.", urlViolations[0], WebInspector.AuditRuleResult.linkifyDisplayName(url))); + result.violationCount += urlViolations[0]; } - topMessage.appendChild(WebInspector.AuditRules.arrayAsUL(outputMessages)); - } catch(e) { - console.log(e); - } finally { - callback(result); + for (var i = 0; i < urlViolations[1].length; ++i) + result.addChild(String.sprintf("Link node %s should be moved to the document head in %s", WebInspector.AuditRuleResult.linkifyDisplayName(urlViolations[1][i]), WebInspector.AuditRuleResult.linkifyDisplayName(url))); + result.violationCount += urlViolations[1].length; } + summary.value = String.sprintf("CSS in the document body adversely impacts rendering performance."); + callback(result); } function routine() @@ -891,112 +765,81 @@ WebInspector.AuditRules.CssInHeadRule.prototype = { 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]; + 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.evaluateInTargetWindow(routine, null, evalCallback); } } WebInspector.AuditRules.CssInHeadRule.prototype.__proto__ = WebInspector.AuditRule.prototype; -WebInspector.AuditRules.StylesScriptsOrderRule = function(parametersObject) +WebInspector.AuditRules.StylesScriptsOrderRule = function() { - WebInspector.AuditRule.call(this, "page-stylescriptorder", "Optimize the order of styles and scripts", parametersObject); + WebInspector.AuditRule.call(this, "page-stylescriptorder", "Optimize the order of styles and scripts"); } WebInspector.AuditRules.StylesScriptsOrderRule.prototype = { doRun: function(resources, result, callback) { - function evalCallback(evalResult, isException) + function evalCallback(resultValue, 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); + if (isException || !resultValue) + return callback(null); + + var lateCssUrls = resultValue[0]; + var cssBeforeInlineCount = resultValue[1]; + + var entry = result.addChild("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.", true); + entry.addURLs(lateCssUrls); + result.violationCount += lateCssUrls.length; + + if (cssBeforeInlineCount) { + result.addChild(String.sprintf(" %d inline script block%s 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.", cssBeforeInlineCount, cssBeforeInlineCount > 1 ? "s were" : " was")); + result.violationCount += cssBeforeInlineCount; } + 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; + var lateStyles = document.querySelectorAll("head script[src] ~ link[rel~='stylesheet'][href]"); + var cssBeforeInlineCount = document.querySelectorAll("head link[rel~='stylesheet'][href] ~ script:not([src])").length; + if (!lateStyles.length && !cssBeforeInlineCount) + return null; + + var lateStyleUrls = []; + for (var i = 0; i < lateStyles.length; ++i) + lateStyleUrls.push(lateStyles[i].href); + return [ lateStyleUrls, cssBeforeInlineCount ]; } - WebInspector.AuditRules.evaluateInTargetWindow(routine, evalCallback.bind(this)); + WebInspector.AuditRules.evaluateInTargetWindow(routine, null, evalCallback.bind(this)); } } WebInspector.AuditRules.StylesScriptsOrderRule.prototype.__proto__ = WebInspector.AuditRule.prototype; -WebInspector.AuditRules.CookieRuleBase = function(id, name, parametersObject) +WebInspector.AuditRules.CookieRuleBase = function(id, name) { - WebInspector.AuditRule.call(this, id, name, parametersObject); + WebInspector.AuditRule.call(this, id, name); } WebInspector.AuditRules.CookieRuleBase.prototype = { @@ -1004,13 +847,8 @@ WebInspector.AuditRules.CookieRuleBase.prototype = { { var self = this; function resultCallback(receivedCookies, isAdvanced) { - try { - self.processCookies(isAdvanced ? receivedCookies : [], resources, result); - } catch(e) { - console.log(e); - } finally { - callback(result); - } + self.processCookies(isAdvanced ? receivedCookies : [], resources, result); + callback(result); } WebInspector.Cookies.getCookiesAsync(resultCallback); }, @@ -1039,9 +877,11 @@ WebInspector.AuditRules.CookieRuleBase.prototype = { WebInspector.AuditRules.CookieRuleBase.prototype.__proto__ = WebInspector.AuditRule.prototype; -WebInspector.AuditRules.CookieSizeRule = function(parametersObject) +WebInspector.AuditRules.CookieSizeRule = function(avgBytesThreshold) { - WebInspector.AuditRules.CookieRuleBase.call(this, "http-cookiesize", "Minimize cookie size", parametersObject); + WebInspector.AuditRules.CookieRuleBase.call(this, "http-cookiesize", "Minimize cookie size"); + this._avgBytesThreshold = avgBytesThreshold; + this._maxBytesThreshold = 1000; } WebInspector.AuditRules.CookieSizeRule.prototype = { @@ -1097,7 +937,6 @@ WebInspector.AuditRules.CookieSizeRule.prototype = { var matchingResourceData = {}; this.mapResourceCookies(domainToResourcesMap, allCookies, collectorCallback.bind(this)); - result.score = 100; for (var resourceDomain in cookiesPerResourceDomain) { var cookies = cookiesPerResourceDomain[resourceDomain]; sortedCookieSizes.push({ @@ -1111,13 +950,10 @@ WebInspector.AuditRules.CookieSizeRule.prototype = { 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)); + if (maxCookieSize > this._maxBytesThreshold) + hugeCookieDomains.push(WebInspector.AuditRuleResult.resourceDomain(sortedCookieSizes[i].domain) + ": " + Number.bytesToString(maxCookieSize)); } var bigAvgCookieDomains = []; @@ -1125,45 +961,33 @@ WebInspector.AuditRules.CookieSizeRule.prototype = { 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)); + if (avgCookieSize > this._avgBytesThreshold && avgCookieSize < this._maxBytesThreshold) + bigAvgCookieDomains.push(WebInspector.AuditRuleResult.resourceDomain(domain) + ": " + Number.bytesToString(avgCookieSize)); } - result.appendChild("The average cookie size for all requests on this page is " + Number.bytesToString(avgAllCookiesSize)); + result.addChild(String.sprintf("The average cookie size for all requests on this page is %s", 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)); + var entry = result.addChild("The following domains have a cookie size in excess of 1KB. This is harmful because requests with cookies larger than 1KB typically cannot fit into a single network packet.", true); + entry.addURLs(hugeCookieDomains); + result.violationCount += hugeCookieDomains.length; } 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)); + var entry = result.addChild(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.", this._avgBytesThreshold), true); + entry.addURLs(bigAvgCookieDomains); + result.violationCount += bigAvgCookieDomains.length; } - - 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.StaticCookielessRule = function(minResources) { - WebInspector.AuditRules.CookieRuleBase.call(this, "http-staticcookieless", "Serve static content from a cookieless domain", parametersObject); + WebInspector.AuditRules.CookieRuleBase.call(this, "http-staticcookieless", "Serve static content from a cookieless domain"); + this._minResources = minResources; } WebInspector.AuditRules.StaticCookielessRule.prototype = { @@ -1175,10 +999,9 @@ WebInspector.AuditRules.StaticCookielessRule.prototype = { WebInspector.URLRegExp, true); var totalStaticResources = 0; - var minResources = this.getValue("MinResources"); for (var domain in domainToResourcesMap) totalStaticResources += domainToResourcesMap[domain].length; - if (totalStaticResources < minResources) + if (totalStaticResources < this._minResources) return; var matchingResourceData = {}; this.mapResourceCookies(domainToResourcesMap, allCookies, this._collectorCallback.bind(this, matchingResourceData)); @@ -1189,19 +1012,12 @@ WebInspector.AuditRules.StaticCookielessRule.prototype = { badUrls.push(url); cookieBytes += matchingResourceData[url] } - if (badUrls.length < minResources) + if (badUrls.length < this._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)); + var entry = result.addChild(String.sprintf("%s of cookies were sent with the following static resources. Serve these static resources from a domain that does not set cookies:", Number.bytesToString(cookieBytes)), true); + entry.addURLs(badUrls); + result.violationCount = badUrls.length; }, _collectorCallback: function(matchingResourceData, resource, cookie) diff --git a/WebCore/inspector/front-end/AuditsPanel.js b/WebCore/inspector/front-end/AuditsPanel.js index fcadb82..c3318ce 100644 --- a/WebCore/inspector/front-end/AuditsPanel.js +++ b/WebCore/inspector/front-end/AuditsPanel.js @@ -37,6 +37,7 @@ WebInspector.AuditsPanel = function() this.createSidebar(); this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("", {}, true); this.sidebarTree.appendChild(this.auditsTreeElement); + this.auditsTreeElement.listItemElement.addStyleClass("hidden"); this.auditsTreeElement.expand(); this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement(); @@ -54,6 +55,8 @@ WebInspector.AuditsPanel = function() this.viewsContainerElement = document.createElement("div"); this.viewsContainerElement.id = "audit-views"; this.element.appendChild(this.viewsContainerElement); + + this._launcherView = new WebInspector.AuditLauncherView(this.categoriesById, this.initiateAudit.bind(this)); } WebInspector.AuditsPanel.prototype = { @@ -95,9 +98,14 @@ WebInspector.AuditsPanel.prototype = { return this._auditCategoriesById; }, - get visibleView() + resourceStarted: function(resource) { - return this._visibleView; + this._launcherView.resourceStarted(resource); + }, + + resourceFinished: function(resource) + { + this._launcherView.resourceFinished(resource); }, _constructCategories: function() @@ -125,8 +133,8 @@ WebInspector.AuditsPanel.prototype = { function ruleResultReadyCallback(categoryResult, ruleResult) { - if (ruleResult.children) - categoryResult.entries.push(ruleResult); + if (ruleResult && ruleResult.children) + categoryResult.addRuleResult(ruleResult); --rulesRemaining; @@ -188,18 +196,18 @@ WebInspector.AuditsPanel.prototype = { { this._resourceTrackingCallback = callback; - if (!InspectorBackend.resourceTrackingEnabled()) { + if (!WebInspector.panels.resources.resourceTrackingEnabled) { InspectorBackend.enableResourceTracking(false); this._updateLauncherViewControls(true); } else - InjectedScriptAccess.getDefault().evaluate("window.location.reload()", switchCallback); + InspectorBackend.reloadPage(); }, _didMainResourceLoad: function() { if (this._resourceTrackingCallback) { var callback = this._resourceTrackingCallback; - this._resourceTrackingCallback = null; + delete this._resourceTrackingCallback; callback(); } }, @@ -209,36 +217,42 @@ WebInspector.AuditsPanel.prototype = { if (!categoryResults._resultView) categoryResults._resultView = new WebInspector.AuditResultView(categoryResults); - this.showView(categoryResults._resultView); + this.visibleView = categoryResults._resultView; }, showLauncherView: function() { - if (!this._launcherView) - this._launcherView = new WebInspector.AuditLauncherView(this.categoriesById, this.initiateAudit.bind(this)); - - this.showView(this._launcherView); + this.visibleView = this._launcherView; + }, + + get visibleView() + { + return this._visibleView; }, - showView: function(view) + set visibleView(x) { - if (view) { - if (this._visibleView === view) - return; - this._closeVisibleView(); - this._visibleView = view; - } - var visibleView = this.visibleView; - if (visibleView) - visibleView.show(this.viewsContainerElement); + if (this._visibleView === x) + return; + + if (this._visibleView) + this._visibleView.hide(); + + this._visibleView = x; + + if (x) + x.show(this.viewsContainerElement); }, show: function() { WebInspector.Panel.prototype.show.call(this); + this._updateLauncherViewControls(WebInspector.panels.resources.resourceTrackingEnabled); + }, - this.showView(); - this._updateLauncherViewControls(InspectorBackend.resourceTrackingEnabled()); + reset: function() + { + this._launcherView.reset(); }, attach: function() @@ -264,12 +278,6 @@ WebInspector.AuditsPanel.prototype = { this.auditsItemTreeElement.reveal(); this.auditsItemTreeElement.select(); this.auditResultsTreeElement.removeChildren(); - }, - - _closeVisibleView: function() - { - if (this.visibleView) - this.visibleView.hide(); } } @@ -301,8 +309,9 @@ WebInspector.AuditCategory.prototype = { return this._rules.length; }, - addRule: function(rule) + addRule: function(rule, severity) { + rule.severity = severity; this._rules.push(rule); }, @@ -324,11 +333,22 @@ WebInspector.AuditCategory.prototype = { } -WebInspector.AuditRule = function(id, displayName, parametersObject) +WebInspector.AuditRule = function(id, displayName) { this._id = id; this._displayName = displayName; - this._parametersObject = parametersObject; +} + +WebInspector.AuditRule.Severity = { + Info: "info", + Warning: "warning", + Severe: "severe" +} + +WebInspector.AuditRule.SeverityOrder = { + "info": 3, + "warning": 2, + "severe": 1 } WebInspector.AuditRule.prototype = { @@ -342,83 +362,82 @@ WebInspector.AuditRule.prototype = { return this._displayName; }, - run: function(resources, callback) + set severity(severity) { - this.doRun(resources, new WebInspector.AuditRuleResult(this.displayName), callback); + this._severity = severity; }, - doRun: function(resources, result, callback) + run: function(resources, callback) { - throw new Error("doRun() not implemented"); + var result = new WebInspector.AuditRuleResult(this.displayName); + result.severity = this._severity; + this.doRun(resources, result, callback); }, - getValue: function(key) + doRun: function(resources, result, callback) { - if (key in this._parametersObject) - return this._parametersObject[key]; - else - throw new Error(key + " not found in rule parameters"); + throw new Error("doRun() not implemented"); } } - WebInspector.AuditCategoryResult = function(category) { this.title = category.displayName; - this.entries = []; + this.ruleResults = []; } WebInspector.AuditCategoryResult.prototype = { - addEntry: function(value) + addRuleResult: function(ruleResult) { - var entry = new WebInspector.AuditRuleResult(value); - this.entries.push(entry); - return entry; + this.ruleResults.push(ruleResult); } } -/** - * @param {string} value The result message HTML contents. - */ -WebInspector.AuditRuleResult = function(value) +WebInspector.AuditRuleResult = function(value, expanded, className) { this.value = value; - this.type = WebInspector.AuditRuleResult.Type.NA; + this.className = className; + this.expanded = expanded; + this.violationCount = 0; } -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, +WebInspector.AuditRuleResult.linkifyDisplayName = function(url) +{ + return WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url)); +} - // Denotes a major impact on the checked metric. - Violation: 2 +WebInspector.AuditRuleResult.resourceDomain = function(domain) +{ + return domain || WebInspector.UIString("[empty domain]"); } WebInspector.AuditRuleResult.prototype = { - appendChild: function(value) + addChild: function(value, expanded, className) { if (!this.children) this.children = []; - var entry = new WebInspector.AuditRuleResult(value); + var entry = new WebInspector.AuditRuleResult(value, expanded, className); this.children.push(entry); return entry; }, - set type(x) + addURL: function(url) { - this._type = x; + return this.addChild(WebInspector.AuditRuleResult.linkifyDisplayName(url)); }, - get type() + addURLs: function(urls) { - return this._type; + for (var i = 0; i < urls.length; ++i) + this.addURL(urls[i]); + }, + + addSnippet: function(snippet) + { + return this.addChild(snippet, false, "source-code"); } } - WebInspector.AuditsSidebarTreeElement = function() { this.small = false; diff --git a/WebCore/inspector/front-end/Breakpoint.js b/WebCore/inspector/front-end/Breakpoint.js index 5d46cc9..7f3ef17 100644 --- a/WebCore/inspector/front-end/Breakpoint.js +++ b/WebCore/inspector/front-end/Breakpoint.js @@ -89,7 +89,7 @@ WebInspector.Breakpoint.prototype = { this.dispatchEventToListeners("condition-changed"); if (this.enabled) - InspectorBackend.updateBreakpoint(this.sourceID, this.line, c); + InspectorBackend.setBreakpoint(this.sourceID, this.line, this.enabled, this.condition); } } diff --git a/WebCore/inspector/front-end/BreakpointsSidebarPane.js b/WebCore/inspector/front-end/BreakpointsSidebarPane.js index 8865f0b..fcfd2ab 100644 --- a/WebCore/inspector/front-end/BreakpointsSidebarPane.js +++ b/WebCore/inspector/front-end/BreakpointsSidebarPane.js @@ -40,6 +40,16 @@ WebInspector.BreakpointsSidebarPane = function() } WebInspector.BreakpointsSidebarPane.prototype = { + reset: function() + { + this.breakpoints = {}; + this.listElement.removeChildren(); + if (this.listElement.parentElement) { + this.bodyElement.removeChild(this.listElement); + this.bodyElement.appendChild(this.emptyElement); + } + }, + addBreakpoint: function(breakpoint) { if (this.breakpoints[breakpoint.id]) @@ -58,11 +68,7 @@ WebInspector.BreakpointsSidebarPane.prototype = { this.bodyElement.appendChild(this.listElement); } - if (!InspectorBackend.debuggerEnabled() || !breakpoint.sourceID) - return; - - if (breakpoint.enabled) - InspectorBackend.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition); + InspectorBackend.setBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.enabled, breakpoint.condition); }, _appendBreakpointElement: function(breakpoint) @@ -77,9 +83,7 @@ WebInspector.BreakpointsSidebarPane.prototype = { function breakpointClicked() { - var script = WebInspector.panels.scripts.scriptOrResourceForID(breakpoint.sourceID); - if (script) - WebInspector.panels.scripts.showScript(script, breakpoint.line); + WebInspector.panels.scripts.showSourceLine(breakpoint.url, breakpoint.line); } var breakpointElement = document.createElement("li"); @@ -135,9 +139,6 @@ WebInspector.BreakpointsSidebarPane.prototype = { this.bodyElement.appendChild(this.emptyElement); } - if (!InspectorBackend.debuggerEnabled() || !breakpoint.sourceID) - return; - InspectorBackend.removeBreakpoint(breakpoint.sourceID, breakpoint.line); }, @@ -147,14 +148,7 @@ WebInspector.BreakpointsSidebarPane.prototype = { var checkbox = breakpoint._breakpointListElement.firstChild; checkbox.checked = breakpoint.enabled; - - if (!InspectorBackend.debuggerEnabled() || !breakpoint.sourceID) - return; - - if (breakpoint.enabled) - InspectorBackend.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition); - else - InspectorBackend.removeBreakpoint(breakpoint.sourceID, breakpoint.line); + InspectorBackend.setBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.enabled, breakpoint.condition); }, _breakpointTextChanged: function(event) diff --git a/WebCore/inspector/front-end/Checkbox.js b/WebCore/inspector/front-end/Checkbox.js new file mode 100644 index 0000000..b30da70 --- /dev/null +++ b/WebCore/inspector/front-end/Checkbox.js @@ -0,0 +1,56 @@ +/* + * 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.Checkbox = function(label, callback, checked, className, tooltip) +{ + this.element = document.createElement('label'); + this._inputElement = document.createElement('input'); + + function callbackWrapper(event) + { + if (callback) + callback(event); + event.stopPropagation(); + return true; + } + this._inputElement.type = "checkbox"; + this._inputElement.checked = checked; + this._inputElement.addEventListener("click", callbackWrapper, false); + + this.element.className = className; + this.element.appendChild(this._inputElement); + this.element.appendChild(document.createTextNode(label)); + if (tooltip) + this.element.title = tooltip; + this.element.addEventListener("click", callbackWrapper, false); +} + +WebInspector.Checkbox.prototype = { + checked: function() + { + return this._inputElement.checked; + } +} + diff --git a/WebCore/inspector/front-end/ConsoleView.js b/WebCore/inspector/front-end/ConsoleView.js index 07d9812..f840a81 100644 --- a/WebCore/inspector/front-end/ConsoleView.js +++ b/WebCore/inspector/front-end/ConsoleView.js @@ -48,6 +48,7 @@ WebInspector.ConsoleView = function(drawer) this.promptElement.className = "source-code"; this.promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true); this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + "."); + WebInspector.settings.addEventListener("loaded", this._settingsLoaded, this); this.topGroup = new WebInspector.ConsoleGroup(null, 0); this.messagesElement.insertBefore(this.topGroup.element, this.promptElement); @@ -92,11 +93,13 @@ WebInspector.ConsoleView = function(drawer) this._shortcuts = {}; var shortcut; - var clearConsoleHandler = this.requestClearMessages.bind(this); shortcut = WebInspector.KeyboardShortcut.makeKey("k", WebInspector.KeyboardShortcut.Modifiers.Meta); - this._shortcuts[shortcut] = clearConsoleHandler; + // This case requires a separate bound function as its isMacOnly property should not be shared among different shortcut handlers. + this._shortcuts[shortcut] = this.requestClearMessages.bind(this); this._shortcuts[shortcut].isMacOnly = true; + + var clearConsoleHandler = this.requestClearMessages.bind(this); shortcut = WebInspector.KeyboardShortcut.makeKey("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl); this._shortcuts[shortcut] = clearConsoleHandler; @@ -115,6 +118,10 @@ WebInspector.ConsoleView = function(drawer) } WebInspector.ConsoleView.prototype = { + _settingsLoaded: function() + { + this.prompt.history = WebInspector.settings.consoleHistory; + }, _updateFilter: function(e) { @@ -215,6 +222,19 @@ WebInspector.ConsoleView.prototype = { this.toggleConsoleButton.title = WebInspector.UIString("Show console."); }, + _scheduleScrollIntoView: function() + { + if (this._scrollIntoViewTimer) + return; + + function scrollIntoView() + { + this.promptElement.scrollIntoView(false); + delete this._scrollIntoViewTimer; + } + this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20); + }, + addMessage: function(msg) { if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) { @@ -256,7 +276,7 @@ WebInspector.ConsoleView.prototype = { this.currentGroup.addMessage(msg); } - this.promptElement.scrollIntoView(false); + this._scheduleScrollIntoView(); }, updateMessageRepeatCount: function(count) @@ -491,6 +511,9 @@ WebInspector.ConsoleView.prototype = { self.prompt.history.push(str); self.prompt.historyOffset = 0; self.prompt.text = ""; + + WebInspector.settings.consoleHistory = self.prompt.history.slice(-30); + self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage)); } this.evalInInspectedWindow(str, "console", printResult); @@ -504,7 +527,7 @@ WebInspector.ConsoleView.prototype = { var formatter = this._customFormatters[type]; if (!formatter || !isProxy) { formatter = this._formatvalue; - output = output.description || output; + output = output.description; } var span = document.createElement("span"); @@ -568,7 +591,7 @@ WebInspector.ConsoleView.prototype = { for (var i = 0; i < properties.length; ++i) { var name = properties[i].name; if (name == parseInt(name)) - elements[name] = this._format(properties[i].value); + elements[name] = this._formatAsArrayEntry(properties[i].value); } elem.appendChild(document.createTextNode("[")); @@ -582,6 +605,13 @@ WebInspector.ConsoleView.prototype = { elem.appendChild(document.createTextNode(", ")); } elem.appendChild(document.createTextNode("]")); + }, + + _formatAsArrayEntry: function(output) + { + var type = Object.proxyType(output); + // Prevent infinite expansion of cross-referencing arrays. + return this._format(output, type === "array"); } } diff --git a/WebCore/inspector/front-end/CookieItemsView.js b/WebCore/inspector/front-end/CookieItemsView.js index b5674b8..1baf4a6 100644 --- a/WebCore/inspector/front-end/CookieItemsView.js +++ b/WebCore/inspector/front-end/CookieItemsView.js @@ -122,7 +122,7 @@ WebInspector.CookieItemsView.prototype = { for (var id in WebInspector.resources) { var resource = WebInspector.resources[id]; - var match = resource.documentURL.match(WebInspector.URLRegExp); + var match = resource.documentURL.match(WebInspector.GenericURLRegExp); if (match && match[2] === this._cookieDomain) resourceURLsForDocumentURL.push(resource.url); } diff --git a/WebCore/inspector/front-end/DOMAgent.js b/WebCore/inspector/front-end/DOMAgent.js index 834f527..46ff0bf 100644 --- a/WebCore/inspector/front-end/DOMAgent.js +++ b/WebCore/inspector/front-end/DOMAgent.js @@ -211,30 +211,6 @@ WebInspector.DOMNode.prototype = { }; this._attributesMap[name] = attr; this.attributes.push(attr); - }, - - _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules) - { - this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle); - this.style = new WebInspector.CSSStyleDeclaration(inlineStyle); - - for (var name in styleAttributes) { - if (this._attributesMap[name]) - this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]); - } - - this._matchedCSSRules = []; - for (var i = 0; i < matchedCSSRules.length; i++) - this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i])); - }, - - _clearStyles: function() - { - this.computedStyle = null; - this.style = null; - for (var name in this._attributesMap) - this._attributesMap[name].style = null; - this._matchedCSSRules = null; } } @@ -308,16 +284,6 @@ WebInspector.DOMWindow.prototype = { Object: function() { - }, - - getComputedStyle: function(node) - { - return node._computedStyle; - }, - - getMatchedCSSRules: function(node, pseudoElement, authorOnly) - { - return node._matchedCSSRules; } } @@ -489,7 +455,7 @@ WebInspector.Cookies.buildCookiesFromString = function(rawCookieString) WebInspector.Cookies.cookieMatchesResourceURL = function(cookie, resourceURL) { - var match = resourceURL.match(WebInspector.URLRegExp); + var match = resourceURL.match(WebInspector.GenericURLRegExp); if (!match) return false; // See WebInspector.URLRegExp for definitions of the group index constants. @@ -523,13 +489,20 @@ WebInspector.EventListeners.getEventListenersForNodeAsync = function(node, callb WebInspector.CSSStyleDeclaration = function(payload) { this.id = payload.id; - this.injectedScriptId = payload.injectedScriptId; this.width = payload.width; this.height = payload.height; - this.__disabledProperties = payload.__disabledProperties; - this.__disabledPropertyValues = payload.__disabledPropertyValues; - this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities; - this.uniqueStyleProperties = payload.uniqueStyleProperties; + this.__disabledProperties = {}; + this.__disabledPropertyValues = {}; + this.__disabledPropertyPriorities = {}; + if (payload.disabled) { + for (var i = 0; i < payload.disabled.length; ++i) { + var property = payload.disabled[i]; + this.__disabledProperties[property.name] = true; + this.__disabledPropertyValues[property.name] = property.value; + this.__disabledPropertyPriorities[property.name] = property.priority; + } + } + this._shorthandValues = payload.shorthandValues; this._propertyMap = {}; this._longhandProperties = {}; @@ -540,12 +513,8 @@ WebInspector.CSSStyleDeclaration = function(payload) var name = property.name; this[i] = name; this._propertyMap[name] = property; - } - // Index longhand properties. - for (var i = 0; i < this.uniqueStyleProperties.length; ++i) { - var name = this.uniqueStyleProperties[i]; - var property = this._propertyMap[name]; + // Index longhand properties. if (property.shorthand) { var longhands = this._longhandProperties[property.shorthand]; if (!longhands) { @@ -566,13 +535,13 @@ WebInspector.CSSStyleDeclaration.parseRule = function(payload) { var rule = {}; rule.id = payload.id; - rule.injectedScriptId = payload.injectedScriptId; rule.selectorText = payload.selectorText; rule.style = new WebInspector.CSSStyleDeclaration(payload.style); rule.style.parentRule = rule; rule.isUserAgent = payload.isUserAgent; rule.isUser = payload.isUser; rule.isViaInspector = payload.isViaInspector; + rule.sourceLine = payload.sourceLine; if (payload.parentStyleSheet) rule.parentStyleSheet = { href: payload.parentStyleSheet.href }; @@ -697,4 +666,17 @@ WebInspector.didPerformSearch = WebInspector.Callback.processCallback; WebInspector.didApplyDomChange = WebInspector.Callback.processCallback; WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback; WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback; +WebInspector.didRemoveNode = WebInspector.Callback.processCallback; +WebInspector.didChangeTagName = WebInspector.Callback.processCallback; WebInspector.didGetEventListenersForNode = WebInspector.Callback.processCallback; + +WebInspector.didGetStyles = WebInspector.Callback.processCallback; +WebInspector.didGetAllStyles = WebInspector.Callback.processCallback; +WebInspector.didGetInlineStyle = WebInspector.Callback.processCallback; +WebInspector.didGetComputedStyle = WebInspector.Callback.processCallback; +WebInspector.didApplyStyleText = WebInspector.Callback.processCallback; +WebInspector.didSetStyleText = WebInspector.Callback.processCallback; +WebInspector.didSetStyleProperty = WebInspector.Callback.processCallback; +WebInspector.didToggleStyleEnabled = WebInspector.Callback.processCallback; +WebInspector.didSetRuleSelector = WebInspector.Callback.processCallback; +WebInspector.didAddRule = WebInspector.Callback.processCallback; diff --git a/WebCore/inspector/front-end/DatabaseQueryView.js b/WebCore/inspector/front-end/DatabaseQueryView.js index cc902e7..38c8df4 100644 --- a/WebCore/inspector/front-end/DatabaseQueryView.js +++ b/WebCore/inspector/front-end/DatabaseQueryView.js @@ -140,13 +140,15 @@ WebInspector.DatabaseQueryView.prototype = { _queryFinished: function(query, result) { var dataGrid = WebInspector.panels.storage.dataGridForResult(result); - if (!dataGrid) - return; - dataGrid.element.addStyleClass("inline"); - this._appendQueryResult(query, dataGrid.element); - dataGrid.autoSizeColumns(5); + var trimmedQuery = query.trim(); + + if (dataGrid) { + dataGrid.element.addStyleClass("inline"); + this._appendQueryResult(trimmedQuery, dataGrid.element); + dataGrid.autoSizeColumns(5); + } - if (query.match(/^create /i) || query.match(/^drop table /i)) + if (trimmedQuery.match(/^create /i) || trimmedQuery.match(/^drop table /i)) WebInspector.panels.storage.updateDatabaseTables(this.database); }, diff --git a/WebCore/inspector/front-end/ElementsPanel.js b/WebCore/inspector/front-end/ElementsPanel.js index 897fdd1..e2ca838 100644 --- a/WebCore/inspector/front-end/ElementsPanel.js +++ b/WebCore/inspector/front-end/ElementsPanel.js @@ -58,10 +58,6 @@ WebInspector.ElementsPanel = function() this.panel.updateProperties(); this.panel.updateEventListeners(); - if (InspectorBackend.searchingForNode()) { - InspectorBackend.toggleNodeSearch(); - this.panel.nodeSearchButton.toggled = false; - } if (this._focusedDOMNode) InjectedScriptAccess.get(this._focusedDOMNode.injectedScriptId).addInspectedNode(this._focusedDOMNode.id, function() {}); }; @@ -102,10 +98,8 @@ WebInspector.ElementsPanel = function() this.sidebarResizeElement.className = "sidebar-resizer-vertical"; this.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarResizerDragStart.bind(this), false); - this.nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIString("Select an element in the page to inspect it."), "node-search-status-bar-item"); - this.nodeSearchButton.addEventListener("click", this._nodeSearchButtonClicked.bind(this), false); - - this.searchingForNode = false; + this._nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIString("Select an element in the page to inspect it."), "node-search-status-bar-item"); + this._nodeSearchButton.addEventListener("click", this._nodeSearchButtonClicked.bind(this), false); this.element.appendChild(this.contentElement); this.element.appendChild(this.sidebarElement); @@ -126,7 +120,7 @@ WebInspector.ElementsPanel.prototype = { get statusBarItems() { - return [this.nodeSearchButton.element, this.crumbsElement]; + return [this._nodeSearchButton.element, this.crumbsElement]; }, get defaultFocusedElement() @@ -154,11 +148,7 @@ WebInspector.ElementsPanel.prototype = { WebInspector.Panel.prototype.hide.call(this); WebInspector.hoveredDOMNode = null; - - if (InspectorBackend.searchingForNode()) { - InspectorBackend.toggleNodeSearch(); - this.nodeSearchButton.toggled = false; - } + InspectorBackend.disableSearchingForNode(); }, resize: function() @@ -185,11 +175,6 @@ WebInspector.ElementsPanel.prototype = { WebInspector.hoveredDOMNode = null; - if (InspectorBackend.searchingForNode()) { - InspectorBackend.toggleNodeSearch(); - this.nodeSearchButton.toggled = false; - } - this.recentlyModifiedNodes = []; delete this.currentQuery; @@ -268,7 +253,17 @@ WebInspector.ElementsPanel.prototype = { this._matchesCountUpdateTimeout = null; this._searchQuery = query; - InjectedScriptAccess.getDefault().performSearch(whitespaceTrimmedQuery, function() {}); + InjectedScriptAccess.getDefault().performSearch(whitespaceTrimmedQuery, false, function() {}); + }, + + searchingForNodeWasEnabled: function() + { + this._nodeSearchButton.toggled = true; + }, + + searchingForNodeWasDisabled: function() + { + this._nodeSearchButton.toggled = false; }, _updateMatchesCount: function() @@ -678,47 +673,7 @@ WebInspector.ElementsPanel.prototype = { var crumbTitle; switch (current.nodeType) { case Node.ELEMENT_NODE: - crumbTitle = current.nodeName.toLowerCase(); - - var nameElement = document.createElement("span"); - nameElement.textContent = crumbTitle; - crumb.appendChild(nameElement); - - var idAttribute = current.getAttribute("id"); - if (idAttribute) { - var idElement = document.createElement("span"); - crumb.appendChild(idElement); - - var part = "#" + idAttribute; - crumbTitle += part; - idElement.appendChild(document.createTextNode(part)); - - // Mark the name as extra, since the ID is more important. - nameElement.className = "extra"; - } - - var classAttribute = current.getAttribute("class"); - if (classAttribute) { - var classes = classAttribute.split(/\s+/); - var foundClasses = {}; - - if (classes.length) { - var classesElement = document.createElement("span"); - classesElement.className = "extra"; - crumb.appendChild(classesElement); - - for (var i = 0; i < classes.length; ++i) { - var className = classes[i]; - if (className && !(className in foundClasses)) { - var part = "." + className; - crumbTitle += part; - classesElement.appendChild(document.createTextNode(part)); - foundClasses[className] = true; - } - } - } - } - + this.decorateNodeLabel(current, crumb); break; case Node.TEXT_NODE: @@ -737,17 +692,16 @@ WebInspector.ElementsPanel.prototype = { break; default: - crumbTitle = current.nodeName.toLowerCase(); + crumbTitle = this.treeOutline.nodeNameToCorrectCase(current.nodeName); } if (!crumb.childNodes.length) { var nameElement = document.createElement("span"); nameElement.textContent = crumbTitle; crumb.appendChild(nameElement); + crumb.title = crumbTitle; } - crumb.title = crumbTitle; - if (foundRoot) crumb.addStyleClass("dimmed"); if (current === this.focusedDOMNode) @@ -764,6 +718,51 @@ WebInspector.ElementsPanel.prototype = { this.updateBreadcrumbSizes(); }, + decorateNodeLabel: function(node, parentElement) + { + var title = this.treeOutline.nodeNameToCorrectCase(node.nodeName); + + var nameElement = document.createElement("span"); + nameElement.textContent = title; + parentElement.appendChild(nameElement); + + var idAttribute = node.getAttribute("id"); + if (idAttribute) { + var idElement = document.createElement("span"); + parentElement.appendChild(idElement); + + var part = "#" + idAttribute; + title += part; + idElement.appendChild(document.createTextNode(part)); + + // Mark the name as extra, since the ID is more important. + nameElement.className = "extra"; + } + + var classAttribute = node.getAttribute("class"); + if (classAttribute) { + var classes = classAttribute.split(/\s+/); + var foundClasses = {}; + + if (classes.length) { + var classesElement = document.createElement("span"); + classesElement.className = "extra"; + parentElement.appendChild(classesElement); + + for (var i = 0; i < classes.length; ++i) { + var className = classes[i]; + if (className && !(className in foundClasses)) { + var part = "." + className; + title += part; + classesElement.appendChild(document.createTextNode(part)); + foundClasses[className] = true; + } + } + } + } + parentElement.title = title; + }, + updateBreadcrumbSizes: function(focusedCrumb) { if (!this.visible) @@ -1108,9 +1107,10 @@ WebInspector.ElementsPanel.prototype = { _nodeSearchButtonClicked: function(event) { - InspectorBackend.toggleNodeSearch(); - - this.nodeSearchButton.toggled = InspectorBackend.searchingForNode(); + if (!this._nodeSearchButton.toggled) + InspectorBackend.enableSearchingForNode(); + else + InspectorBackend.disableSearchingForNode(); } } diff --git a/WebCore/inspector/front-end/ElementsTreeOutline.js b/WebCore/inspector/front-end/ElementsTreeOutline.js index fe7ae53..1add6cc 100644 --- a/WebCore/inspector/front-end/ElementsTreeOutline.js +++ b/WebCore/inspector/front-end/ElementsTreeOutline.js @@ -69,6 +69,11 @@ WebInspector.ElementsTreeOutline.prototype = { return this._isXMLMimeType; }, + nodeNameToCorrectCase: function(nodeName) + { + return this.isXMLMimeType ? nodeName : nodeName.toLowerCase(); + }, + get focusedDOMNode() { return this._focusedDOMNode; @@ -177,9 +182,16 @@ WebInspector.ElementsTreeOutline.prototype = { return null; }, + set suppressRevealAndSelect(x) + { + if (this._suppressRevealAndSelect === x) + return; + this._suppressRevealAndSelect = x; + }, + revealAndSelectNode: function(node) { - if (!node) + if (!node || this._suppressRevealAndSelect) return; var treeElement = this.createTreeElementFor(node); @@ -213,7 +225,7 @@ WebInspector.ElementsTreeOutline.prototype = { return element; }, - + _keyDown: function(event) { if (event.target !== this.treeOutline.element) @@ -225,6 +237,9 @@ WebInspector.ElementsTreeOutline.prototype = { if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Backspace || event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Delete) { + var startTagTreeElement = this.findTreeElement(selectedElement.representedObject); + if (selectedElement !== startTagTreeElement) + selectedElement = startTagTreeElement; selectedElement.remove(); event.preventDefault(); event.stopPropagation(); @@ -267,12 +282,12 @@ WebInspector.ElementsTreeOutline.prototype = { delete this._previousHoveredElement; } - if (element && !element.elementCloseTag) { + if (element) { element.hovered = true; this._previousHoveredElement = element; } - WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? element.representedObject : null); + WebInspector.hoveredDOMNode = (element ? element.representedObject : null); }, _onmouseout: function(event) @@ -309,14 +324,15 @@ WebInspector.ElementsTreeOutline.prototype = { WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype; -WebInspector.ElementsTreeElement = function(node) +WebInspector.ElementsTreeElement = function(node, elementCloseTag) { - var hasChildrenOverride = node.hasChildNodes() && !this._showInlineText(node); + this._elementCloseTag = elementCloseTag; + var hasChildrenOverride = !elementCloseTag && node.hasChildNodes() && !this._showInlineText(node); // The title will be updated in onattach. TreeElement.call(this, "", node, hasChildrenOverride); - if (this.representedObject.nodeType == Node.ELEMENT_NODE) + if (this.representedObject.nodeType == Node.ELEMENT_NODE && !elementCloseTag) this._canAddAttributes = true; this._searchQuery = null; this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit; @@ -332,6 +348,11 @@ WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [ "hr", "img", "input", "isindex", "keygen", "link", "meta", "param", "source" ].keySet(); +// These tags we do not allow editing their tag name. +WebInspector.ElementsTreeElement.EditTagBlacklist = [ + "html", "head", "body" +].keySet(); + WebInspector.ElementsTreeElement.prototype = { highlightSearchResults: function(searchQuery) { @@ -382,7 +403,7 @@ WebInspector.ElementsTreeElement.prototype = { get expandedChildCount() { var count = this.children.length; - if (count && this.children[count - 1].elementCloseTag) + if (count && this.children[count - 1]._elementCloseTag) count--; if (count && this.children[count - 1].expandAllButton) count--; @@ -391,6 +412,9 @@ WebInspector.ElementsTreeElement.prototype = { showChild: function(index) { + if (this._elementCloseTag) + return; + if (index >= this.expandedChildrenLimit) { this._expandedChildrenLimit = index + 1; this._updateChildren(true); @@ -402,6 +426,9 @@ WebInspector.ElementsTreeElement.prototype = { createTooltipForImageNode: function(node, callback) { + if (this._elementCloseTag) + return; + function createTooltipThenCallback(properties) { if (!properties) { @@ -443,8 +470,6 @@ WebInspector.ElementsTreeElement.prototype = { onattach: function() { - this.listItemElement.addEventListener("mousedown", this.onmousedown.bind(this), false); - if (this._hovered) { this.updateSelection(); this.listItemElement.addStyleClass("hovered"); @@ -467,20 +492,23 @@ WebInspector.ElementsTreeElement.prototype = { onpopulate: function() { - if (this.children.length || this._showInlineText(this.representedObject)) + if (this.children.length || this._showInlineText(this.representedObject) || this._elementCloseTag) return; this.updateChildren(); }, - + updateChildren: function(fullRefresh) { + if (this._elementCloseTag) + return; + WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._updateChildren.bind(this, fullRefresh)); }, - insertChildElement: function(child, index) + insertChildElement: function(child, index, closingTag) { - var newElement = new WebInspector.ElementsTreeElement(child); + var newElement = new WebInspector.ElementsTreeElement(child, closingTag); newElement.selectable = this.treeOutline.selectEnabled; this.insertChild(newElement, index); return newElement; @@ -489,10 +517,10 @@ WebInspector.ElementsTreeElement.prototype = { moveChild: function(child, targetIndex) { var wasSelected = child.selected; - treeElement.removeChild(child); - treeElement.insertChild(child, targetIndex); + this.removeChild(child); + this.insertChild(child, targetIndex); if (wasSelected) - existingTreeElement.select(); + child.select(); }, _updateChildren: function(fullRefresh) @@ -554,9 +582,6 @@ WebInspector.ElementsTreeElement.prototype = { // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent. for (var i = (this.children.length - 1); i >= 0; --i) { - if ("elementCloseTag" in this.children[i]) - continue; - var currentChild = this.children[i]; var currentNode = currentChild.representedObject; var currentParentNode = currentNode.parentNode; @@ -575,13 +600,8 @@ WebInspector.ElementsTreeElement.prototype = { this.adjustCollapsedRange(false); var lastChild = this.children[this.children.length - 1]; - if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild.elementCloseTag)) { - var title = "<span class=\"webkit-html-tag close\"></" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "></span>"; - var item = new TreeElement(title, null, false); - item.selectable = false; - item.elementCloseTag = true; - this.appendChild(item); - } + if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag)) + this.insertChildElement(this.representedObject, this.children.length, true); // We want to restore the original selection and tree scroll position after a full refresh, if possible. if (fullRefresh && elementToSelect) { @@ -635,12 +655,18 @@ WebInspector.ElementsTreeElement.prototype = { onexpand: function() { + if (this._elementCloseTag) + return; + this.updateTitle(); this.treeOutline.updateSelection(); }, oncollapse: function() { + if (this._elementCloseTag) + return; + this.updateTitle(); this.treeOutline.updateSelection(); }, @@ -653,19 +679,20 @@ WebInspector.ElementsTreeElement.prototype = { onselect: function() { + this.treeOutline.suppressRevealAndSelect = true; this.treeOutline.focusedDOMNode = this.representedObject; this.updateSelection(); + this.treeOutline.suppressRevealAndSelect = false; }, - onmousedown: function(event) + selectOnMouseDown: function(event) { - if (this._editing) - return; + TreeElement.prototype.selectOnMouseDown.call(this, event); - if (this.isEventWithinDisclosureTriangle(event)) + if (this._editing) return; - if (this.treeOutline.showInElementsPanelEnabled) { + if (this.treeOutline.showInElementsPanelEnabled) { WebInspector.showElementsPanel(); WebInspector.panels.elements.focusedDOMNode = this.representedObject; } @@ -677,10 +704,10 @@ WebInspector.ElementsTreeElement.prototype = { ondblclick: function(event) { - if (this._editing) + if (this._editing || this._elementCloseTag) return; - if (this._startEditingFromEvent(event)) + if (this._startEditingTarget(event.target)) return; if (this.hasChildren && !this.expanded) @@ -702,7 +729,7 @@ WebInspector.ElementsTreeElement.prototype = { this.updateSelection(); }, - _startEditingFromEvent: function(event) + _startEditingTarget: function(eventTarget) { if (this.treeOutline.focusedDOMNode != this.representedObject) return; @@ -710,15 +737,19 @@ WebInspector.ElementsTreeElement.prototype = { if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.representedObject.nodeType != Node.TEXT_NODE) return false; - var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node"); + var textNode = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-text-node"); if (textNode) return this._startEditingTextNode(textNode); - var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute"); + var attribute = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-attribute"); if (attribute) - return this._startEditingAttribute(attribute, event.target); + return this._startEditingAttribute(attribute, eventTarget); - var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute"); + var tagName = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tag-name"); + if (tagName) + return this._startEditingTagName(tagName); + + var newAttribute = eventTarget.enclosingNodeOrSelfWithClass("add-attribute"); if (newAttribute) return this._addNewAttribute(); @@ -736,7 +767,7 @@ WebInspector.ElementsTreeElement.prototype = { contextMenu.appendItem(WebInspector.UIString("Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target)); contextMenu.appendSeparator(); - // Add node-related actions. + // Add free-form node-related actions. contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._editAsHTML.bind(this)); contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this)); contextMenu.appendItem(WebInspector.UIString("Delete Node"), this.remove.bind(this)); @@ -772,17 +803,13 @@ WebInspector.ElementsTreeElement.prototype = { _addNewAttribute: function() { - var attr = document.createElement("span"); - attr.className = "webkit-html-attribute"; + // Cannot just convert the textual html into an element without + // a parent node. Use a temporary span container for the HTML. + var container = document.createElement("span"); + container.innerHTML = this._attributeHTML(" ", ""); + var attr = container.firstChild; attr.style.marginLeft = "2px"; // overrides the .editing margin rule attr.style.marginRight = "2px"; // overrides the .editing margin rule - var name = document.createElement("span"); - name.className = "webkit-html-attribute-name new-attribute"; - name.textContent = " "; - var value = document.createElement("span"); - value.className = "webkit-html-attribute-value"; - attr.appendChild(name); - attr.appendChild(value); var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0]; this._insertInLastAttributePosition(tag, attr); @@ -799,7 +826,7 @@ WebInspector.ElementsTreeElement.prototype = { continue; if (elem.hasStyleClass("webkit-html-attribute-value")) - return this._startEditingAttribute(attributeElements[i].parentNode, elem); + return this._startEditingAttribute(elem.parentNode, elem); } } } @@ -833,9 +860,7 @@ WebInspector.ElementsTreeElement.prototype = { // Remove zero-width spaces that were added by nodeTitleInfo. removeZeroWidthSpaceRecursive(attribute); - this._editing = true; - - WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName); + this._editing = WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName); window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1); return true; @@ -846,21 +871,59 @@ WebInspector.ElementsTreeElement.prototype = { if (WebInspector.isBeingEdited(textNode)) return true; - this._editing = true; - - WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this)); + this._editing = WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this)); window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1); return true; }, + _startEditingTagName: function(tagNameElement) + { + if (!tagNameElement) { + tagNameElement = this.listItemElement.getElementsByClassName("webkit-html-tag-name")[0]; + if (!tagNameElement) + return false; + } + + var tagName = tagNameElement.textContent; + if (WebInspector.ElementsTreeElement.EditTagBlacklist[tagName.toLowerCase()]) + return false; + + if (WebInspector.isBeingEdited(tagNameElement)) + return true; + + var closingTagElement = this._distinctClosingTagElement(); + + function keyupListener(event) + { + if (closingTagElement) + closingTagElement.textContent = "</" + tagNameElement.textContent + ">"; + } + + function editingComitted(element, newTagName) + { + tagNameElement.removeEventListener('keyup', keyupListener, false); + this._tagNameEditingCommitted.apply(this, arguments); + } + + function editingCancelled() + { + tagNameElement.removeEventListener('keyup', keyupListener, false); + this._editingCancelled.apply(this, arguments); + } + + tagNameElement.addEventListener('keyup', keyupListener, false); + + this._editing = WebInspector.startEditing(tagNameElement, editingComitted.bind(this), editingCancelled.bind(this), tagName); + window.getSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1); + return true; + }, + _startEditingAsHTML: function(commitCallback, initialValue) { if (this._htmlEditElement && WebInspector.isBeingEdited(this._htmlEditElement)) return true; - this._editing = true; - this._htmlEditElement = document.createElement("div"); this._htmlEditElement.className = "source-code elements-tree-editor"; this._htmlEditElement.textContent = initialValue; @@ -905,7 +968,7 @@ WebInspector.ElementsTreeElement.prototype = { this.updateSelection(); } - WebInspector.startEditing(this._htmlEditElement, commit.bind(this), dispose.bind(this), null, true); + this._editing = WebInspector.startEditing(this._htmlEditElement, commit.bind(this), dispose.bind(this), null, true); }, _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection) @@ -914,34 +977,59 @@ WebInspector.ElementsTreeElement.prototype = { // Before we do anything, determine where we should move // next based on the current element's settings - var moveToAttribute; - var newAttribute; + var moveToAttribute, moveToTagName, moveToNewAttribute; if (moveDirection) { var found = false; + + // Search for the attribute's position, and then decide where to move to. var attributes = this.representedObject.attributes; - for (var i = 0, len = attributes.length; i < len; ++i) { + for (var i = 0; i < attributes.length; ++i) { if (attributes[i].name === attributeName) { found = true; - if (moveDirection === "backward" && i > 0) - moveToAttribute = attributes[i - 1].name; - else if (moveDirection === "forward" && i < attributes.length - 1) - moveToAttribute = attributes[i + 1].name; - else if (moveDirection === "forward" && i === attributes.length - 1) - newAttribute = true; + if (moveDirection === "backward") { + if (i === 0) + moveToTagName = true; + else + moveToAttribute = attributes[i - 1].name; + } else if (moveDirection === "forward") { + if (i === attributes.length - 1) + moveToNewAttribute = true; + else + moveToAttribute = attributes[i + 1].name; + } } } - if (!found && moveDirection === "backward" && attributes.length > 0) - moveToAttribute = attributes[attributes.length - 1].name; - else if (!found && moveDirection === "forward" && !/^\s*$/.test(newText)) - newAttribute = true; + // Moving From the "New Attribute" position. + if (!found) { + if (moveDirection === "backward" && attributes.length > 0) + moveToAttribute = attributes[attributes.length - 1].name; + else if (moveDirection === "forward" && !/^\s*$/.test(newText)) + moveToNewAttribute = true; + } } - function moveToNextAttributeIfNeeded() { + function moveToNextAttributeIfNeeded() + { + // Cleanup empty new attribute sections. + if (element.textContent.trim().length === 0) + element.parentNode.removeChild(element); + + // Make the move. if (moveToAttribute) this._triggerEditAttribute(moveToAttribute); - else if (newAttribute) - this._addNewAttribute(this.listItemElement); + else if (moveToNewAttribute) + this._addNewAttribute(); + else if (moveToTagName) + this._startEditingTagName(); + } + + function regenerateStyledAttribute(name, value) + { + var previous = element.previousSibling; + if (!previous || previous.nodeType !== Node.TEXT_NODE) + element.parentNode.insertBefore(document.createTextNode(" "), element); + element.outerHTML = this._attributeHTML(name, value); } var parseContainerElement = document.createElement("span"); @@ -966,6 +1054,7 @@ WebInspector.ElementsTreeElement.prototype = { foundOriginalAttribute = foundOriginalAttribute || attr.name === attributeName; try { this.representedObject.setAttribute(attr.name, attr.value); + regenerateStyledAttribute.call(this, attr.name, attr.value); } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw errors, but this can) } @@ -977,6 +1066,64 @@ WebInspector.ElementsTreeElement.prototype = { moveToNextAttributeIfNeeded.call(this); }, + _tagNameEditingCommitted: function(element, newText, oldText, tagName, moveDirection) + { + delete this._editing; + var self = this; + + function cancel() + { + var closingTagElement = self._distinctClosingTagElement(); + if (closingTagElement) + closingTagElement.textContent = "</" + tagName + ">"; + + self._editingCancelled(element, tagName); + moveToNextAttributeIfNeeded.call(self); + } + + function moveToNextAttributeIfNeeded() + { + if (moveDirection !== "forward") + return; + + var attributes = this.representedObject.attributes; + if (attributes.length > 0) + this._triggerEditAttribute(attributes[0].name); + else + this._addNewAttribute(); + } + + newText = newText.trim(); + if (newText === oldText) { + cancel(); + return; + } + + var treeOutline = this.treeOutline; + var wasExpanded = this.expanded; + + function changeTagNameCallback(nodeId) + { + if (nodeId === -1) { + cancel(); + return; + } + + // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date. + WebInspector.panels.elements.updateModifiedNodes(); + + WebInspector.updateFocusedNode(nodeId); + var newTreeItem = treeOutline.findTreeElement(WebInspector.domAgent.nodeForId(nodeId)); + if (wasExpanded) + newTreeItem.expand(); + + moveToNextAttributeIfNeeded.call(newTreeItem); + } + + var callId = WebInspector.Callback.wrap(changeTagNameCallback); + InspectorBackend.changeTagName(callId, this.representedObject.id, newText, wasExpanded); + }, + _textNodeEditingCommitted: function(element, newText) { delete this._editing; @@ -1003,6 +1150,24 @@ WebInspector.ElementsTreeElement.prototype = { this.updateTitle(); }, + _distinctClosingTagElement: function() + { + // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling the DOM + + // For an expanded element, it will be the last element with class "close" + // in the child element list. + if (this.expanded) { + var closers = this._childrenListNode.querySelectorAll(".close"); + return closers[closers.length-1]; + } + + // Remaining cases are single line non-expanded elements with a closing + // tag, or HTML elements without a closing tag (such as <br>). Return + // null in the case where there isn't a closing tag. + var tags = this.listItemElement.getElementsByClassName("webkit-html-tag"); + return (tags.length === 1 ? null : tags[tags.length-1]); + }, + updateTitle: function() { // If we are editing, return early to prevent canceling the edit. @@ -1051,92 +1216,117 @@ WebInspector.ElementsTreeElement.prototype = { return hrefValue; }, + _attributeHTML: function(name, value, node, linkify, tooltipText) + { + var hasText = (value.length > 0); + var html = "<span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + name.escapeHTML() + "</span>"; + + if (hasText) + html += "=​\""; + + if (linkify && (name === "src" || name === "href")) { + value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B"); + html += linkify(this._rewriteAttrHref(node, value), value, "webkit-html-attribute-value", node.nodeName.toLowerCase() === "a", tooltipText); + } else { + value = value.escapeHTML().replace(/([\/;:\)\]\}])/g, "$1​"); + html += "<span class=\"webkit-html-attribute-value\">" + value + "</span>"; + } + + if (hasText) + html += "\""; + + html += "</span>"; + return html; + }, + + _tagHTML: function(tagName, isClosingTag, isDistinctTreeElement, linkify, tooltipText) + { + var node = this.representedObject; + var result = "<span class=\"webkit-html-tag" + (isClosingTag && isDistinctTreeElement ? " close" : "") + "\"><"; + result += "<span " + (isClosingTag ? "" : "class=\"webkit-html-tag-name\"") + ">" + (isClosingTag ? "/" : "") + tagName + "</span>"; + if (!isClosingTag && node.hasAttributes()) { + for (var i = 0; i < node.attributes.length; ++i) { + var attr = node.attributes[i]; + result += " " + this._attributeHTML(attr.name, attr.value, node, linkify, tooltipText); + } + } + result += "></span>​"; + + return result; + }, + _nodeTitleInfo: function(linkify, tooltipText) { var node = this.representedObject; var info = {title: "", hasChildren: this.hasChildren}; - + switch (node.nodeType) { case Node.DOCUMENT_NODE: info.title = "Document"; break; - + case Node.DOCUMENT_FRAGMENT_NODE: info.title = "Document Fragment"; break; case Node.ELEMENT_NODE: - var tagName = node.nodeName.toLowerCase().escapeHTML(); - info.title = "<span class=\"webkit-html-tag\"><" + tagName; - - if (node.hasAttributes()) { - for (var i = 0; i < node.attributes.length; ++i) { - var attr = node.attributes[i]; - info.title += " <span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=​\""; - - var value = attr.value; - if (linkify && (attr.name === "src" || attr.name === "href")) { - var value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B"); - info.title += linkify(this._rewriteAttrHref(node, attr.value), value, "webkit-html-attribute-value", node.nodeName.toLowerCase() == "a", tooltipText); - } else { - var value = value.escapeHTML(); - value = value.replace(/([\/;:\)\]\}])/g, "$1​"); - info.title += "<span class=\"webkit-html-attribute-value\">" + value + "</span>"; - } - info.title += "\"</span>"; - } + var tagName = this.treeOutline.nodeNameToCorrectCase(node.nodeName).escapeHTML(); + if (this._elementCloseTag) { + info.title = this._tagHTML(tagName, true, true); + info.hasChildren = false; + break; } - info.title += "></span>​"; - - const closingTagHTML = "<span class=\"webkit-html-tag\"></" + tagName + "></span>​"; + + info.title = this._tagHTML(tagName, false, false, linkify, tooltipText); + var textChild = onlyTextChild.call(node); var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength; if (!this.expanded && (!showInlineText && (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName]))) { if (this.hasChildren) info.title += "<span class=\"webkit-html-text-node\">…</span>​"; - info.title += closingTagHTML; + info.title += this._tagHTML(tagName, true, false); } // If this element only has a single child that is a text node, // just show that text and the closing tag inline rather than // create a subtree for them if (showInlineText) { - info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>​" + closingTagHTML; + info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>​" + this._tagHTML(tagName, true, false); info.hasChildren = false; } break; - + case Node.TEXT_NODE: if (isNodeWhitespace.call(node)) info.title = "(whitespace)"; else { - if (node.parentNode && node.parentNode.nodeName.toLowerCase() == "script") { + if (node.parentNode && node.parentNode.nodeName.toLowerCase() === "script") { var newNode = document.createElement("span"); newNode.textContent = node.textContent; var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript"); javascriptSyntaxHighlighter.syntaxHighlightNode(newNode); - + info.title = "<span class=\"webkit-html-text-node webkit-html-js-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "") + "</span>"; - } else if (node.parentNode && node.parentNode.nodeName.toLowerCase() == "style") { + } else if (node.parentNode && node.parentNode.nodeName.toLowerCase() === "style") { var newNode = document.createElement("span"); newNode.textContent = node.textContent; - + var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/css"); cssSyntaxHighlighter.syntaxHighlightNode(newNode); - + info.title = "<span class=\"webkit-html-text-node webkit-html-css-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "") + "</span>"; } else { - info.title = "\"<span class=\"webkit-html-text-node\">" + node.nodeValue.escapeHTML() + "</span>\""; + info.title = "\"<span class=\"webkit-html-text-node\">" + node.nodeValue.escapeHTML() + "</span>\""; } - } + } break; - + case Node.COMMENT_NODE: info.title = "<span class=\"webkit-html-comment\"><!--" + node.nodeValue.escapeHTML() + "--></span>"; break; - + case Node.DOCUMENT_TYPE_NODE: info.title = "<span class=\"webkit-html-doctype\"><!DOCTYPE " + node.nodeName; if (node.publicId) { @@ -1150,9 +1340,9 @@ WebInspector.ElementsTreeElement.prototype = { info.title += "></span>"; break; default: - info.title = node.nodeName.toLowerCase().collapseWhitespace().escapeHTML(); + info.title = this.treeOutline.nodeNameToCorrectCase(node.nodeName).collapseWhitespace().escapeHTML(); } - + return info; }, @@ -1165,7 +1355,7 @@ WebInspector.ElementsTreeElement.prototype = { } return false; }, - + remove: function() { var parentElement = this.parent; @@ -1242,5 +1432,3 @@ WebInspector.ElementsTreeElement.prototype = { } WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype; - -WebInspector.didRemoveNode = WebInspector.Callback.processCallback; diff --git a/WebCore/inspector/front-end/ImageView.js b/WebCore/inspector/front-end/ImageView.js index 96e1a6e..c13c9a5 100644 --- a/WebCore/inspector/front-end/ImageView.js +++ b/WebCore/inspector/front-end/ImageView.js @@ -31,45 +31,48 @@ WebInspector.ImageView = function(resource) WebInspector.ResourceView.call(this, resource); this.element.addStyleClass("image"); +} - var container = document.createElement("div"); - container.className = "image"; - this.contentElement.appendChild(container); +WebInspector.ImageView.prototype = { + contentTabSelected: function() + { + if (this._container) + return; + this._container = document.createElement("div"); + this._container.className = "image"; + this.contentElement.appendChild(this._container); - this.imagePreviewElement = document.createElement("img"); - this.imagePreviewElement.addStyleClass("resource-image-view"); - this.imagePreviewElement.setAttribute("src", this.resource.url); + this.imagePreviewElement = document.createElement("img"); + this.imagePreviewElement.addStyleClass("resource-image-view"); + this.imagePreviewElement.setAttribute("src", this.resource.url); - container.appendChild(this.imagePreviewElement); + this._container.appendChild(this.imagePreviewElement); - container = document.createElement("div"); - container.className = "info"; - this.contentElement.appendChild(container); + this._container = document.createElement("div"); + this._container.className = "info"; + this.contentElement.appendChild(this._container); - var imageNameElement = document.createElement("h1"); - imageNameElement.className = "title"; - imageNameElement.textContent = this.resource.displayName; - container.appendChild(imageNameElement); + var imageNameElement = document.createElement("h1"); + imageNameElement.className = "title"; + imageNameElement.textContent = this.resource.displayName; + this._container.appendChild(imageNameElement); - var infoListElement = document.createElement("dl"); - infoListElement.className = "infoList"; + var infoListElement = document.createElement("dl"); + infoListElement.className = "infoList"; - var imageProperties = [ - { name: WebInspector.UIString("Dimensions"), value: WebInspector.UIString("%d × %d", this.imagePreviewElement.naturalWidth, this.imagePreviewElement.height) }, - { name: WebInspector.UIString("File size"), value: Number.bytesToString(this.resource.contentLength, WebInspector.UIString.bind(WebInspector)) }, - { name: WebInspector.UIString("MIME type"), value: this.resource.mimeType } - ]; + var imageProperties = [ + { name: WebInspector.UIString("Dimensions"), value: WebInspector.UIString("%d × %d", this.imagePreviewElement.naturalWidth, this.imagePreviewElement.height) }, + { name: WebInspector.UIString("File size"), value: Number.bytesToString(this.resource.resourceSize, WebInspector.UIString.bind(WebInspector)) }, + { name: WebInspector.UIString("MIME type"), value: this.resource.mimeType } + ]; - var listHTML = ''; - for (var i = 0; i < imageProperties.length; ++i) - listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>"; + var listHTML = ''; + for (var i = 0; i < imageProperties.length; ++i) + listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>"; - infoListElement.innerHTML = listHTML; - container.appendChild(infoListElement); -} - -WebInspector.ImageView.prototype = { - + infoListElement.innerHTML = listHTML; + this._container.appendChild(infoListElement); + } } WebInspector.ImageView.prototype.__proto__ = WebInspector.ResourceView.prototype; diff --git a/WebCore/inspector/front-end/Images/auditsIcon.png b/WebCore/inspector/front-end/Images/auditsIcon.png Binary files differnew file mode 100644 index 0000000..8d1f752 --- /dev/null +++ b/WebCore/inspector/front-end/Images/auditsIcon.png diff --git a/WebCore/inspector/front-end/Images/breakpointBorder.png b/WebCore/inspector/front-end/Images/breakpointBorder.png Binary files differnew file mode 100644 index 0000000..0b1b550 --- /dev/null +++ b/WebCore/inspector/front-end/Images/breakpointBorder.png diff --git a/WebCore/inspector/front-end/Images/breakpointConditionalBorder.png b/WebCore/inspector/front-end/Images/breakpointConditionalBorder.png Binary files differnew file mode 100644 index 0000000..430e37e --- /dev/null +++ b/WebCore/inspector/front-end/Images/breakpointConditionalBorder.png diff --git a/WebCore/inspector/front-end/Images/breakpointConditionalCounterBorder.png b/WebCore/inspector/front-end/Images/breakpointConditionalCounterBorder.png Binary files differnew file mode 100644 index 0000000..b4a5030 --- /dev/null +++ b/WebCore/inspector/front-end/Images/breakpointConditionalCounterBorder.png diff --git a/WebCore/inspector/front-end/Images/breakpointCounterBorder.png b/WebCore/inspector/front-end/Images/breakpointCounterBorder.png Binary files differnew file mode 100644 index 0000000..8b77b61 --- /dev/null +++ b/WebCore/inspector/front-end/Images/breakpointCounterBorder.png diff --git a/WebCore/inspector/front-end/Images/breakpointsActivateButtonGlyph.png b/WebCore/inspector/front-end/Images/breakpointsActivateButtonGlyph.png Binary files differnew file mode 100644 index 0000000..ce49aac --- /dev/null +++ b/WebCore/inspector/front-end/Images/breakpointsActivateButtonGlyph.png diff --git a/WebCore/inspector/front-end/Images/breakpointsDeactivateButtonGlyph.png b/WebCore/inspector/front-end/Images/breakpointsDeactivateButtonGlyph.png Binary files differnew file mode 100644 index 0000000..5c5fcf6 --- /dev/null +++ b/WebCore/inspector/front-end/Images/breakpointsDeactivateButtonGlyph.png diff --git a/WebCore/inspector/front-end/Images/programCounterBorder.png b/WebCore/inspector/front-end/Images/programCounterBorder.png Binary files differnew file mode 100644 index 0000000..fed2f3e --- /dev/null +++ b/WebCore/inspector/front-end/Images/programCounterBorder.png diff --git a/WebCore/inspector/front-end/Images/spinner.gif b/WebCore/inspector/front-end/Images/spinner.gif Binary files differnew file mode 100644 index 0000000..5f68c02 --- /dev/null +++ b/WebCore/inspector/front-end/Images/spinner.gif diff --git a/WebCore/inspector/front-end/InjectedFakeWorker.js b/WebCore/inspector/front-end/InjectedFakeWorker.js new file mode 100644 index 0000000..7176844 --- /dev/null +++ b/WebCore/inspector/front-end/InjectedFakeWorker.js @@ -0,0 +1,345 @@ +/* + * 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. + */ + +var InjectedFakeWorker = function(InjectedScriptHost, inspectedWindow, injectedScriptId) +{ + +Worker = function(url) +{ + var impl = new FakeWorker(this, url); + if (impl === null) + return null; + + this.isFake = true; + this.postMessage = bind(impl.postMessage, impl); + this.terminate = bind(impl.terminate, impl); + + function onmessageGetter() + { + return impl.channel.port1.onmessage; + } + function onmessageSetter(callback) + { + impl.channel.port1.onmessage = callback; + } + this.__defineGetter__("onmessage", onmessageGetter); + this.__defineSetter__("onmessage", onmessageSetter); + this.addEventListener = bind(impl.channel.port1.addEventListener, impl.channel.port1); + this.removeEventListener = bind(impl.channel.port1.removeEventListener, impl.channel.port1); + this.dispatchEvent = bind(impl.channel.port1.dispatchEvent, impl.channel.port1); +} + +function FakeWorker(worker, url) +{ + var scriptURL = this._expandURLAndCheckOrigin(document.baseURI, location.href, url); + + this._worker = worker; + this._id = InjectedScriptHost.nextWorkerId(); + this.channel = new MessageChannel(); + this._listeners = []; + this._buildWorker(scriptURL); + + InjectedScriptHost.didCreateWorker(this._id, scriptURL.url, false); +} + +FakeWorker.prototype = { + postMessage: function(msg, opt_ports) + { + if (this._frame != null) + this.channel.port1.postMessage.apply(this.channel.port1, arguments); + else if (this._pendingMessages) + this._pendingMessages.push(arguments) + else + this._pendingMessages = [ arguments ]; + }, + + terminate: function() + { + InjectedScriptHost.didDestroyWorker(this._id); + + this.channel.port1.close(); + this.channel.port2.close(); + if (this._frame != null) + this._frame.frameElement.parentNode.removeChild(this._frame.frameElement); + this._frame = null; + this._worker = null; // Break reference loop. + }, + + _buildWorker: function(url) + { + var code = this._loadScript(url.url); + var iframeElement = document.createElement("iframe"); + iframeElement.style.display = "none"; + + this._document = document; + iframeElement.onload = bind(this._onWorkerFrameLoaded, this, iframeElement, url, code); + + if (document.body) + this._attachWorkerFrameToDocument(iframeElement, url, code); + else + window.addEventListener("load", bind(this._attachWorkerFrameToDocument, this, iframeElement), false); + }, + + _attachWorkerFrameToDocument: function(iframeElement) + { + document.body.appendChild(iframeElement); + }, + + _onWorkerFrameLoaded: function(iframeElement, url, code) + { + var frame = iframeElement.contentWindow; + this._frame = frame; + this._setupWorkerContext(frame, url); + + var frameContents = '(function() { var location = __devtools.location; var window; ' + code + '})();\n' + '//@ sourceURL=' + url.url; + + frame.eval(frameContents); + if (this._pendingMessages) { + for (var msg = 0; msg < this._pendingMessages.length; ++msg) + this.postMessage.apply(this, this._pendingMessages[msg]); + delete this._pendingMessages; + } + }, + + _setupWorkerContext: function(workerFrame, url) + { + workerFrame.__devtools = { + handleException: bind(this._handleException, this), + location: url.mockLocation() + }; + + var self = this; + + function onmessageGetter() + { + return self.channel.port2.onmessage ? self.channel.port2.onmessage.originalCallback : null; + } + + function onmessageSetter(callback) + { + var wrappedCallback = bind(self._callbackWrapper, self, callback); + wrappedCallback.originalCallback = callback; + self.channel.port2.onmessage = wrappedCallback; + } + + workerFrame.__defineGetter__("onmessage", onmessageGetter); + workerFrame.__defineSetter__("onmessage", onmessageSetter); + workerFrame.addEventListener = bind(this._addEventListener, this); + workerFrame.removeEventListener = bind(this._removeEventListener, this); + workerFrame.dispatchEvent = bind(this.channel.port2.dispatchEvent, this.channel.port2); + workerFrame.postMessage = bind(this.channel.port2.postMessage, this.channel.port2); + workerFrame.importScripts = bind(this._importScripts, this, workerFrame); + workerFrame.close = bind(this.terminate, this); + }, + + _addEventListener: function(type, callback, useCapture) + { + var wrappedCallback = bind(this._callbackWrapper, this, callback); + wrappedCallback.originalCallback = callback; + wrappedCallback.type = type; + wrappedCallback.useCapture = Boolean(useCapture); + + this.channel.port2.addEventListener(type, wrappedCallback, useCapture); + this._listeners.push(wrappedCallback); + }, + + _removeEventListener: function(type, callback, useCapture) + { + var listeners = this._listeners; + for (var i = 0; i < listeners.length; ++i) { + if (listeners[i].originalCallback === callback && + listeners[i].type === type && + listeners[i].useCapture === Boolean(useCapture)) { + this.channel.port2.removeEventListener(type, listeners[i], useCapture); + listeners[i] = listeners[listeners.length - 1]; + listeners.pop(); + break; + } + } + }, + + _callbackWrapper: function(callback, msg) + { + // Shortcut -- if no exception handlers installed, avoid try/catch so as not to obscure line number. + if (!this._frame.onerror && !this._worker.onerror) { + callback(msg); + return; + } + + try { + callback(msg); + } catch (e) { + this._handleException(e, this._frame.onerror, this._worker.onerror); + } + }, + + _handleException: function(e) + { + // NB: it should be an ErrorEvent, but creating it from script is not + // currently supported, so emulate it on top of plain vanilla Event. + var errorEvent = this._document.createEvent("Event"); + errorEvent.initEvent("Event", false, false); + errorEvent.message = "Uncaught exception"; + + for (var i = 1; i < arguments.length; ++i) { + if (arguments[i] && arguments[i](errorEvent)) + return; + } + + throw e; + }, + + _importScripts: function(targetFrame) + { + for (var i = 1; i < arguments.length; ++i) { + var workerOrigin = targetFrame.__devtools.location.href; + var url = this._expandURLAndCheckOrigin(workerOrigin, workerOrigin, arguments[i]); + targetFrame.eval(this._loadScript(url.url) + "\n//@ sourceURL= " + url.url); + } + }, + + _loadScript: function(url) + { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + xhr.send(null); + + var text = xhr.responseText; + if (xhr.status != 0 && xhr.status/100 !== 2) { // We're getting status === 0 when using file://. + console.error("Failed to load worker: " + url + "[" + xhr.status + "]"); + text = ""; // We've got error message, not worker code. + } + return text; + }, + + _expandURLAndCheckOrigin: function(baseURL, origin, url) + { + var scriptURL = new URL(baseURL).completeWith(url); + + if (!scriptURL.sameOrigin(origin)) + throw new DOMCoreException("SECURITY_ERR",18); + return scriptURL; + } +}; + +function URL(url) +{ + this.url = url; + this.split(); +} + +URL.prototype = { + urlRegEx: (/^(http[s]?|file):\/\/([^\/:]*)(:[\d]+)?(?:(\/[^#?]*)(\?[^#]*)?(?:#(.*))?)?$/i), + + split: function() + { + function emptyIfNull(str) + { + return str == null ? "" : str; + } + var parts = this.urlRegEx.exec(this.url); + + this.schema = parts[1]; + this.host = parts[2]; + this.port = emptyIfNull(parts[3]); + this.path = emptyIfNull(parts[4]); + this.query = emptyIfNull(parts[5]); + this.fragment = emptyIfNull(parts[6]); + }, + + mockLocation: function() + { + var host = this.host.replace(/^[^@]*@/, ""); + + return { + href: this.url, + protocol: this.schema + ":", + host: host, + hostname: host, + port: this.port, + pathname: this.path, + search: this.query, + hash: this.fragment + }; + }, + + completeWith: function(url) + { + if (url === "" || /^[^/]*:/.exec(url)) // If given absolute url, return as is now. + return new URL(url); + + var relParts = /^([^#?]*)(.*)$/.exec(url); // => [ url, path, query-andor-fragment ] + + var path = (relParts[1].slice(0, 1) === "/" ? "" : this.path.replace(/[^/]*$/, "")) + relParts[1]; + path = path.replace(/(\/\.)+(\/|$)/g, "/").replace(/[^/]*\/\.\.(\/|$)/g, ""); + + return new URL(this.schema + "://" + this.host + this.port + path + relParts[2]); + }, + + sameOrigin: function(url) + { + function normalizePort(schema, port) + { + var portNo = port.slice(1); + return (schema === "https" && portNo == 443 || schema === "http" && portNo == 80) ? "" : port; + } + + var other = new URL(url); + + return this.schema === other.schema && + this.host === other.host && + normalizePort(this.schema, this.port) === normalizePort(other.schema, other.port); + } +}; + +function DOMCoreException(name, code) +{ + function formatError() + { + return "Error: " + this.message; + } + + this.name = name; + this.message = name + ": DOM Exception " + code; + this.code = code; + this.toString = bind(formatError, this); +} + +function bind(func, thisObject) +{ + var args = Array.prototype.slice.call(arguments, 2); + return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); }; +} + +function noop() +{ +} + +} diff --git a/WebCore/inspector/front-end/InjectedScript.js b/WebCore/inspector/front-end/InjectedScript.js index 8d8fa88..8984d0e 100644 --- a/WebCore/inspector/front-end/InjectedScript.js +++ b/WebCore/inspector/front-end/InjectedScript.js @@ -26,28 +26,43 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var injectedScriptConstructor = (function (InjectedScriptHost, inspectedWindow, injectedScriptId) { +var injectedScriptConstructor = (function (InjectedScriptHost, inspectedWindow, injectedScriptId, jsEngine) { var InjectedScript = {}; InjectedScript.lastBoundObjectId = 1; InjectedScript.idToWrappedObject = {}; InjectedScript.objectGroups = {}; + +InjectedScript.wrapObjectForConsole = function(object, canAccessInspectedWindow) +{ + if (canAccessInspectedWindow) + return InjectedScript.wrapObject(object, "console"); + var result = {}; + result.type = typeof object; + result.description = InjectedScript._toString(object); + return result; +} + InjectedScript.wrapObject = function(object, objectGroupName) { - var objectId; - if (typeof object === "object" || typeof object === "function" || - (typeof object === "undefined" && object instanceof inspectedWindow.HTMLAllCollection)) { // FIXME(33716) - var id = InjectedScript.lastBoundObjectId++; - objectId = "object#" + id; - InjectedScript.idToWrappedObject[objectId] = object; - - var group = InjectedScript.objectGroups[objectGroupName]; - if (!group) { - group = []; - InjectedScript.objectGroups[objectGroupName] = group; + try { + var objectId; + if (typeof object === "object" || typeof object === "function" || InjectedScript._isHTMLAllCollection(object)) { + var id = InjectedScript.lastBoundObjectId++; + objectId = "object#" + id; + InjectedScript.idToWrappedObject[objectId] = object; + + var group = InjectedScript.objectGroups[objectGroupName]; + if (!group) { + group = []; + InjectedScript.objectGroups[objectGroupName] = group; + } + group.push(objectId); } - group.push(objectId); + return InjectedScript.createProxyObject(object, objectId); + } catch (e) { + return InjectedScript.createProxyObject("[ Exception: " + e.toString() + " ]"); } return InjectedScript.createProxyObject(object, objectId); }; @@ -68,10 +83,6 @@ InjectedScript.releaseWrapperObjectGroup = function(objectGroupName) { // Called from within InspectorController on the 'inspected page' side. InjectedScript.reset = function() { - InjectedScript._styles = {}; - InjectedScript._styleRules = {}; - InjectedScript._lastStyleId = 0; - InjectedScript._lastStyleRuleId = 0; InjectedScript._searchResults = []; InjectedScript._includedInSearchResultsPropertyName = "__includedInInspectorSearchResults"; } @@ -85,384 +96,12 @@ InjectedScript.dispatch = function(methodName, args, callId) argsArray.splice(0, 0, callId); // Methods that run asynchronously have a call back id parameter. var result = InjectedScript[methodName].apply(InjectedScript, argsArray); if (typeof result === "undefined") { - InjectedScript._window().console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); + inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); result = null; } return result; } -InjectedScript.getStyles = function(nodeId, authorOnly) -{ - var node = InjectedScript._nodeForId(nodeId); - if (!node) - return false; - var defaultView = node.ownerDocument.defaultView; - var matchedRules = defaultView.getMatchedCSSRules(node, "", authorOnly); - var matchedCSSRules = []; - for (var i = 0; matchedRules && i < matchedRules.length; ++i) - matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i])); - - var styleAttributes = {}; - var attributes = node.attributes; - for (var i = 0; attributes && i < attributes.length; ++i) { - if (attributes[i].style) - styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true); - } - var result = {}; - result.inlineStyle = InjectedScript._serializeStyle(node.style, true); - result.computedStyle = InjectedScript._serializeStyle(defaultView.getComputedStyle(node)); - result.matchedCSSRules = matchedCSSRules; - result.styleAttributes = styleAttributes; - return result; -} - -InjectedScript.getComputedStyle = function(nodeId) -{ - var node = InjectedScript._nodeForId(nodeId); - if (!node) - return false; - return InjectedScript._serializeStyle(node.ownerDocument.defaultView.getComputedStyle(node)); -} - -InjectedScript.getInlineStyle = function(nodeId) -{ - var node = InjectedScript._nodeForId(nodeId); - if (!node) - return false; - return InjectedScript._serializeStyle(node.style, true); -} - -InjectedScript.applyStyleText = function(styleId, styleText, propertyName) -{ - var style = InjectedScript._styles[styleId]; - if (!style) - return false; - - var styleTextLength = styleText.length; - - // Create a new element to parse the user input CSS. - var parseElement = document.createElement("span"); - parseElement.setAttribute("style", styleText); - - var tempStyle = parseElement.style; - if (tempStyle.length || !styleTextLength) { - // The input was parsable or the user deleted everything, so remove the - // original property from the real style declaration. If this represents - // a shorthand remove all the longhand properties. - if (style.getPropertyShorthand(propertyName)) { - var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); - for (var i = 0; i < longhandProperties.length; ++i) - style.removeProperty(longhandProperties[i]); - } else - style.removeProperty(propertyName); - } - - // Notify caller that the property was successfully deleted. - if (!styleTextLength) - return [null, [propertyName]]; - - if (!tempStyle.length) - return false; - - // Iterate of the properties on the test element's style declaration and - // add them to the real style declaration. We take care to move shorthands. - var foundShorthands = {}; - var changedProperties = []; - var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle); - for (var i = 0; i < uniqueProperties.length; ++i) { - var name = uniqueProperties[i]; - var shorthand = tempStyle.getPropertyShorthand(name); - - if (shorthand && shorthand in foundShorthands) - continue; - - if (shorthand) { - var value = InjectedScript._getShorthandValue(tempStyle, shorthand); - var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand); - foundShorthands[shorthand] = true; - } else { - var value = tempStyle.getPropertyValue(name); - var priority = tempStyle.getPropertyPriority(name); - } - - // Set the property on the real style declaration. - style.setProperty((shorthand || name), value, priority); - changedProperties.push(shorthand || name); - } - return [InjectedScript._serializeStyle(style, true), changedProperties]; -} - -InjectedScript.setStyleText = function(style, cssText) -{ - style.cssText = cssText; - return true; -} - -InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled) -{ - var style = InjectedScript._styles[styleId]; - if (!style) - return false; - - if (disabled) { - if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) { - style.__disabledProperties = {}; - style.__disabledPropertyValues = {}; - style.__disabledPropertyPriorities = {}; - } - - style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName); - style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName); - - if (style.getPropertyShorthand(propertyName)) { - var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); - for (var i = 0; i < longhandProperties.length; ++i) { - style.__disabledProperties[longhandProperties[i]] = true; - style.removeProperty(longhandProperties[i]); - } - } else { - style.__disabledProperties[propertyName] = true; - style.removeProperty(propertyName); - } - } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) { - var value = style.__disabledPropertyValues[propertyName]; - var priority = style.__disabledPropertyPriorities[propertyName]; - - style.setProperty(propertyName, value, priority); - delete style.__disabledProperties[propertyName]; - delete style.__disabledPropertyValues[propertyName]; - delete style.__disabledPropertyPriorities[propertyName]; - } - return InjectedScript._serializeStyle(style, true); -} - -InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNodeId) -{ - var rule = InjectedScript._styleRules[ruleId]; - if (!rule) - return false; - - var selectedNode = InjectedScript._nodeForId(selectedNodeId); - - try { - var stylesheet = rule.parentStyleSheet; - stylesheet.addRule(newContent); - var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1]; - newRule.style.cssText = rule.style.cssText; - - var parentRules = stylesheet.cssRules; - for (var i = 0; i < parentRules.length; ++i) { - if (parentRules[i] === rule) { - rule.parentStyleSheet.removeRule(i); - break; - } - } - - return [InjectedScript._serializeRule(newRule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode)]; - } catch(e) { - // Report invalid syntax. - return false; - } -} - -InjectedScript.addStyleSelector = function(newContent, selectedNodeId) -{ - var selectedNode = InjectedScript._nodeForId(selectedNodeId); - if (!selectedNode) - return false; - var ownerDocument = selectedNode.ownerDocument; - - var stylesheet = ownerDocument.__stylesheet; - if (!stylesheet) { - var head = ownerDocument.head; - var styleElement = ownerDocument.createElement("style"); - styleElement.type = "text/css"; - head.appendChild(styleElement); - stylesheet = ownerDocument.styleSheets[ownerDocument.styleSheets.length - 1]; - ownerDocument.__stylesheet = stylesheet; - } - - try { - stylesheet.addRule(newContent); - } catch (e) { - // Invalid Syntax for a Selector - return false; - } - - var rule = stylesheet.cssRules[stylesheet.cssRules.length - 1]; - rule.__isViaInspector = true; - - return [ InjectedScript._serializeRule(rule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode) ]; -} - -InjectedScript._doesSelectorAffectNode = function(selectorText, node) -{ - if (!node) - return false; - var nodes = node.ownerDocument.querySelectorAll(selectorText); - for (var i = 0; i < nodes.length; ++i) { - if (nodes[i] === node) { - return true; - } - } - return false; -} - -InjectedScript.setStyleProperty = function(styleId, name, value) -{ - var style = InjectedScript._styles[styleId]; - if (!style) - return false; - - style.setProperty(name, value, ""); - return true; -} - -InjectedScript._serializeRule = function(rule) -{ - var parentStyleSheet = rule.parentStyleSheet; - - var ruleValue = {}; - ruleValue.selectorText = rule.selectorText; - if (parentStyleSheet) { - ruleValue.parentStyleSheet = {}; - ruleValue.parentStyleSheet.href = parentStyleSheet.href; - } - ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href; - ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document"; - ruleValue.isViaInspector = !!rule.__isViaInspector; - - // Bind editable scripts only. - var doBind = !ruleValue.isUserAgent && !ruleValue.isUser; - ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind); - - if (doBind) { - if (!rule.id) { - rule.id = InjectedScript._lastStyleRuleId++; - InjectedScript._styleRules[rule.id] = rule; - } - ruleValue.id = rule.id; - ruleValue.injectedScriptId = injectedScriptId; - } - return ruleValue; -} - -InjectedScript._serializeStyle = function(style, doBind) -{ - var result = {}; - result.width = style.width; - result.height = style.height; - result.__disabledProperties = style.__disabledProperties; - result.__disabledPropertyValues = style.__disabledPropertyValues; - result.__disabledPropertyPriorities = style.__disabledPropertyPriorities; - result.properties = []; - result.shorthandValues = {}; - var foundShorthands = {}; - for (var i = 0; i < style.length; ++i) { - var property = {}; - var name = style[i]; - property.name = name; - property.priority = style.getPropertyPriority(name); - property.implicit = style.isPropertyImplicit(name); - var shorthand = style.getPropertyShorthand(name); - property.shorthand = shorthand; - if (shorthand && !(shorthand in foundShorthands)) { - foundShorthands[shorthand] = true; - result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand); - } - property.value = style.getPropertyValue(name); - result.properties.push(property); - } - result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style); - - if (doBind) { - if (!style.id) { - style.id = InjectedScript._lastStyleId++; - InjectedScript._styles[style.id] = style; - } - result.id = style.id; - result.injectedScriptId = injectedScriptId; - } - return result; -} - -InjectedScript._getUniqueStyleProperties = function(style) -{ - var properties = []; - var foundProperties = {}; - - for (var i = 0; i < style.length; ++i) { - var property = style[i]; - if (property in foundProperties) - continue; - foundProperties[property] = true; - properties.push(property); - } - - return properties; -} - - -InjectedScript._getLonghandProperties = function(style, shorthandProperty) -{ - var properties = []; - var foundProperties = {}; - - for (var i = 0; i < style.length; ++i) { - var individualProperty = style[i]; - if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) - continue; - foundProperties[individualProperty] = true; - properties.push(individualProperty); - } - - return properties; -} - -InjectedScript._getShorthandValue = function(style, shorthandProperty) -{ - var value = style.getPropertyValue(shorthandProperty); - if (!value) { - // Some shorthands (like border) return a null value, so compute a shorthand value. - // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed. - - var foundProperties = {}; - for (var i = 0; i < style.length; ++i) { - var individualProperty = style[i]; - if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) - continue; - - var individualValue = style.getPropertyValue(individualProperty); - if (style.isPropertyImplicit(individualProperty) || individualValue === "initial") - continue; - - foundProperties[individualProperty] = true; - - if (!value) - value = ""; - else if (value.length) - value += " "; - value += individualValue; - } - } - return value; -} - -InjectedScript._getShorthandPriority = function(style, shorthandProperty) -{ - var priority = style.getPropertyPriority(shorthandProperty); - if (!priority) { - for (var i = 0; i < style.length; ++i) { - var individualProperty = style[i]; - if (style.getPropertyShorthand(individualProperty) !== shorthandProperty) - continue; - priority = style.getPropertyPriority(individualProperty); - break; - } - } - return priority; -} - InjectedScript.getPrototypes = function(nodeId) { var node = InjectedScript._nodeForId(nodeId); @@ -486,10 +125,32 @@ InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty, abbre if (!InjectedScript._isDefined(object)) return false; var properties = []; + var propertyNames = ignoreHasOwnProperty ? InjectedScript._getPropertyNames(object) : Object.getOwnPropertyNames(object); if (!ignoreHasOwnProperty && object.__proto__) propertyNames.push("__proto__"); + if (jsEngine === "v8") { + // Check if the object is a scope. + if (InjectedScript._isScopeProxy(objectProxy)) { + propertyNames = []; + for (var name in object) + propertyNames.push(name); + } else { + // FIXME(http://crbug.com/41243): Object.getOwnPropertyNames may return duplicated names. + var a = []; + propertyNames.sort(); + var prev; + for (var i = 0; i < propertyNames.length; i++) { + var n = propertyNames[i]; + if (n != prev) + a.push(n); + prev = n; + } + propertyNames = a; + } + } + // Go over properties, prepare results. for (var i = 0; i < propertyNames.length; ++i) { var propertyName = propertyNames[i]; @@ -498,7 +159,7 @@ InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty, abbre property.name = propertyName + ""; property.parentObjectProxy = objectProxy; var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName); - if (!property.isGetter) { + if (!isGetter) { try { var childObject = object[propertyName]; var childObjectProxy = new InjectedScript.createProxyObject(childObject, objectProxy.objectId, abbreviate); @@ -519,6 +180,12 @@ InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty, abbre return properties; } +InjectedScript._isScopeProxy = function(objectProxy) +{ + var objectId = objectProxy.objectId; + return typeof objectId === "object" && !objectId.thisObject; +} + InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression) { var object = InjectedScript._resolveObject(objectProxy); @@ -538,13 +205,13 @@ InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression // There is a regression introduced here: eval is now happening against global object, // not call frame while on a breakpoint. // TODO: bring evaluation against call frame back. - var result = InjectedScript._window().eval("(" + expression + ")"); + var result = inspectedWindow.eval("(" + expression + ")"); // Store the result in the property. object[propertyName] = result; return true; } catch(e) { try { - var result = InjectedScript._window().eval("\"" + InjectedScript._escapeCharacters(expression, "\"") + "\""); + var result = inspectedWindow.eval("\"" + InjectedScript._escapeCharacters(expression, "\"") + "\""); object[propertyName] = result; return true; } catch(e) { @@ -606,7 +273,7 @@ InjectedScript.getCompletions = function(expression, includeInspectorCommandLine if (!callFrame) return props; if (expression) - expressionResult = InjectedScript._evaluateOn(callFrame.evaluate, callFrame, expression); + expressionResult = InjectedScript._evaluateOn(callFrame.evaluate, callFrame, expression, true); else { // Evaluate into properties in scope of the selected call frame. var scopeChain = callFrame.scopeChain; @@ -616,12 +283,12 @@ InjectedScript.getCompletions = function(expression, includeInspectorCommandLine } else { if (!expression) expression = "this"; - expressionResult = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression); + expressionResult = InjectedScript._evaluateOn(inspectedWindow.eval, inspectedWindow, expression); } if (typeof expressionResult == "object") InjectedScript._populatePropertyNames(expressionResult, props); if (includeInspectorCommandLineAPI) - for (var prop in InjectedScript._window().console._inspectorCommandLineAPI) + for (var prop in inspectedWindow.console._inspectorCommandLineAPI) if (prop.charAt(0) !== '_') props[prop] = true; } catch(e) { @@ -631,14 +298,14 @@ InjectedScript.getCompletions = function(expression, includeInspectorCommandLine InjectedScript.evaluate = function(expression, objectGroup) { - return InjectedScript._evaluateAndWrap(InjectedScript._window().eval, InjectedScript._window(), expression, objectGroup); + return InjectedScript._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, expression, objectGroup); } -InjectedScript._evaluateAndWrap = function(evalFunction, object, expression, objectGroup) +InjectedScript._evaluateAndWrap = function(evalFunction, object, expression, objectGroup, dontUseCommandLineAPI) { var result = {}; try { - result.value = InjectedScript.wrapObject(InjectedScript._evaluateOn(evalFunction, object, expression), objectGroup); + result.value = InjectedScript.wrapObject(InjectedScript._evaluateOn(evalFunction, object, expression, dontUseCommandLineAPI), objectGroup); // Handle error that might have happened while describing result. if (result.value.errorText) { @@ -652,12 +319,14 @@ InjectedScript._evaluateAndWrap = function(evalFunction, object, expression, obj return result; } -InjectedScript._evaluateOn = function(evalFunction, object, expression) +InjectedScript._evaluateOn = function(evalFunction, object, expression, dontUseCommandLineAPI) { InjectedScript._ensureCommandLineAPIInstalled(evalFunction, object); // Surround the expression in with statements to inject our command line API so that // the window object properties still take more precedent than our API functions. - expression = "with (window.console._inspectorCommandLineAPI) { with (window) {\n" + expression + "\n} }"; + if (!dontUseCommandLineAPI) + expression = "with (window.console._inspectorCommandLineAPI) { with (window) {\n" + expression + "\n} }"; + var value = evalFunction.call(object, expression); // When evaluating on call frame error is not thrown, but returned as a value. @@ -673,15 +342,20 @@ InjectedScript.addInspectedNode = function(nodeId) if (!node) return false; - InjectedScript._ensureCommandLineAPIInstalled(InjectedScript._window().eval, InjectedScript._window()); - var inspectedNodes = InjectedScript._window().console._inspectorCommandLineAPI._inspectedNodes; + InjectedScript._ensureCommandLineAPIInstalled(inspectedWindow.eval, inspectedWindow); + var inspectedNodes = inspectedWindow.console._inspectorCommandLineAPI._inspectedNodes; inspectedNodes.unshift(node); if (inspectedNodes.length >= 5) inspectedNodes.pop(); return true; } -InjectedScript.performSearch = function(whitespaceTrimmedQuery) +InjectedScript.getNodeId = function(node) +{ + return InjectedScriptHost.pushNodePathToFrontend(node, false, false); +} + +InjectedScript.performSearch = function(whitespaceTrimmedQuery, runSynchronously) { // FIXME: Few things are missing here: // 1) Search works with node granularity - number of matches within node is not calculated. @@ -830,7 +504,7 @@ InjectedScript.performSearch = function(whitespaceTrimmedQuery) delete InjectedScript._searchResults[i][searchResultsProperty]; } - const mainFrameDocument = InjectedScript._window().document; + const mainFrameDocument = inspectedWindow.document; const searchDocuments = [mainFrameDocument]; var searchFunctions; if (tagNameQuery && startTagFound && endTagFound) @@ -849,8 +523,7 @@ InjectedScript.performSearch = function(whitespaceTrimmedQuery) searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery]; // Find all frames, iframes and object elements to search their documents. - const querySelectorAllFunction = InjectedScript._window().Document.prototype.querySelectorAll; - const subdocumentResult = querySelectorAllFunction.call(mainFrameDocument, "iframe, frame, object"); + const subdocumentResult = mainFrameDocument.querySelectorAll("iframe, frame, object"); for (var i = 0; i < subdocumentResult.length; ++i) { var element = subdocumentResult.item(i); @@ -879,26 +552,27 @@ InjectedScript.performSearch = function(whitespaceTrimmedQuery) delete panel._currentSearchChunkIntervalIdentifier; clearInterval(chunkIntervalIdentifier); finishedSearching.call(panel); - return; + return false; } searchDocument = searchDocuments[documentIndex]; } - if (!searchDocument || !searchFunction) - return; - try { searchFunction.call(panel, searchDocument); } catch(err) { // ignore any exceptions. the query might be malformed, but we allow that. } + return true; } - processChunk(); - - chunkIntervalIdentifier = setInterval(processChunk, 25); - InjectedScript._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; + if (runSynchronously) + while (processChunk()) {} + else { + processChunk(); + chunkIntervalIdentifier = setInterval(processChunk, 25); + InjectedScript._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; + } return true; } @@ -926,7 +600,7 @@ InjectedScript.openInInspectedWindow = function(url) { // Don't call window.open on wrapper - popup blocker mutes it. // URIs should have no double quotes. - InjectedScript._window().eval("window.open(\"" + url + "\")"); + inspectedWindow.eval("window.open(\"" + url + "\")"); return true; } @@ -950,7 +624,7 @@ InjectedScript.evaluateInCallFrame = function(callFrameId, code, objectGroup) var callFrame = InjectedScript._callFrameForId(callFrameId); if (!callFrame) return false; - return InjectedScript._evaluateAndWrap(callFrame.evaluate, callFrame, code, objectGroup); + return InjectedScript._evaluateAndWrap(callFrame.evaluate, callFrame, code, objectGroup, true); } InjectedScript._callFrameForId = function(id) @@ -1088,13 +762,6 @@ InjectedScript._resolveObject = function(objectProxy) return object; } -InjectedScript._window = function() -{ - // TODO: replace with 'return window;' once this script is injected into - // the page's context. - return inspectedWindow; -} - InjectedScript._nodeForId = function(nodeId) { if (!nodeId) @@ -1158,9 +825,10 @@ InjectedScript.createProxyObject = function(object, objectId, abbreviate) return result; } -InjectedScript.evaluateOnSelf = function(funcBody) +InjectedScript.evaluateOnSelf = function(funcBody, args) { - return window.eval("(" + funcBody + ")();"); + var func = window.eval("(" + funcBody + ")"); + return func.apply(this, args || []); } InjectedScript.CallFrameProxy = function(id, callFrame) @@ -1173,6 +841,59 @@ InjectedScript.CallFrameProxy = function(id, callFrame) this.scopeChain = this._wrapScopeChain(callFrame); } +// FIXME(37663): unify scope chain representation and remove this if. +if (jsEngine === "v8") { + +InjectedScript.CallFrameProxy.prototype = { + _wrapScopeChain: function(callFrame) + { + const GLOBAL_SCOPE = 0; + const LOCAL_SCOPE = 1; + const WITH_SCOPE = 2; + const CLOSURE_SCOPE = 3; + const CATCH_SCOPE = 4; + + var scopeChain = callFrame.scopeChain; + var scopeChainProxy = []; + for (var i = 0; i < scopeChain.length; i++) { + var scopeType = callFrame.scopeType(i); + var scopeObject = scopeChain[i]; + var scopeObjectProxy = InjectedScript.createProxyObject(scopeObject, { callFrame: this.id, chainIndex: i }, true); + + var foundLocalScope = false; + switch(scopeType) { + case LOCAL_SCOPE: { + foundLocalScope = true; + scopeObjectProxy.isLocal = true; + scopeObjectProxy.thisObject = InjectedScript.createProxyObject(callFrame.thisObject, { callFrame: this.id, thisObject: true }, true); + break; + } + case CLOSURE_SCOPE: { + scopeObjectProxy.isClosure = true; + break; + } + case WITH_SCOPE: + case CATCH_SCOPE: { + scopeObjectProxy.isWithBlock = true; + break; + } + } + + if (foundLocalScope) { + if (scopeObject instanceof inspectedWindow.Element) + scopeObjectProxy.isElement = true; + else if (scopeObject instanceof inspectedWindow.Document) + scopeObjectProxy.isDocument = true; + } + + scopeChainProxy.push(scopeObjectProxy); + } + return scopeChainProxy; + } +} + +} else { + InjectedScript.CallFrameProxy.prototype = { _wrapScopeChain: function(callFrame) { @@ -1190,9 +911,9 @@ InjectedScript.CallFrameProxy.prototype = { scopeObjectProxy.isClosure = true; foundLocalScope = true; scopeObjectProxy.isLocal = true; - } else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Element) + } else if (foundLocalScope && scopeObject instanceof inspectedWindow.Element) scopeObjectProxy.isElement = true; - else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Document) + else if (foundLocalScope && scopeObject instanceof inspectedWindow.Document) scopeObjectProxy.isDocument = true; else if (!foundLocalScope) scopeObjectProxy.isWithBlock = true; @@ -1202,6 +923,8 @@ InjectedScript.CallFrameProxy.prototype = { } } +} + InjectedScript.executeSql = function(callId, databaseId, query) { function successCallback(tx, result) @@ -1243,7 +966,13 @@ InjectedScript.executeSql = function(callId, databaseId, query) InjectedScript._isDefined = function(object) { - return object || object instanceof inspectedWindow.HTMLAllCollection; + return object || InjectedScript._isHTMLAllCollection(object); +} + +InjectedScript._isHTMLAllCollection = function(object) +{ + // document.all is reported as undefined, but we still want to process it. + return (typeof object === "undefined") && inspectedWindow.HTMLAllCollection && object instanceof inspectedWindow.HTMLAllCollection; } InjectedScript._type = function(obj) @@ -1251,35 +980,38 @@ InjectedScript._type = function(obj) if (obj === null) return "null"; - // FIXME(33716): typeof document.all is always 'undefined'. - if (obj instanceof inspectedWindow.HTMLAllCollection) - return "array"; - var type = typeof obj; - if (type !== "object" && type !== "function") + if (type !== "object" && type !== "function") { + // FIXME(33716): typeof document.all is always 'undefined'. + if (InjectedScript._isHTMLAllCollection(obj)) + return "array"; return type; + } - var win = InjectedScript._window(); + // If owning frame has navigated to somewhere else window properties will be undefined. + // In this case just return result of the typeof. + if (!inspectedWindow.document) + return type; - if (obj instanceof win.Node) + if (obj instanceof inspectedWindow.Node) return (obj.nodeType === undefined ? type : "node"); - if (obj instanceof win.String) + if (obj instanceof inspectedWindow.String) return "string"; - if (obj instanceof win.Array) + if (obj instanceof inspectedWindow.Array) return "array"; - if (obj instanceof win.Boolean) + if (obj instanceof inspectedWindow.Boolean) return "boolean"; - if (obj instanceof win.Number) + if (obj instanceof inspectedWindow.Number) return "number"; - if (obj instanceof win.Date) + if (obj instanceof inspectedWindow.Date) return "date"; - if (obj instanceof win.RegExp) + if (obj instanceof inspectedWindow.RegExp) return "regexp"; - if (obj instanceof win.NodeList) + if (obj instanceof inspectedWindow.NodeList) return "array"; - if (obj instanceof win.HTMLCollection || obj instanceof win.HTMLAllCollection) + if (obj instanceof inspectedWindow.HTMLCollection) return "array"; - if (obj instanceof win.Error) + if (obj instanceof inspectedWindow.Error) return "error"; return type; } @@ -1301,20 +1033,27 @@ InjectedScript._describe = function(obj, abbreviated) return "\"" + obj.substring(0, 100) + "\u2026\""; return "\"" + obj + "\""; case "function": - var objectText = String(obj); + var objectText = InjectedScript._toString(obj); if (!/^function /.test(objectText)) objectText = (type2 == "object") ? type1 : type2; else if (abbreviated) objectText = /.*/.exec(obj)[0].replace(/ +$/g, ""); return objectText; default: - return String(obj); + return InjectedScript._toString(obj); } } +InjectedScript._toString = function(obj) +{ + // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page. + return "" + obj; +} + InjectedScript._className = function(obj) { - return Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1") + var str = inspectedWindow.Object ? inspectedWindow.Object.prototype.toString.call(obj) : InjectedScript._toString(obj); + return str.replace(/^\[object (.*)\]$/i, "$1"); } InjectedScript._escapeCharacters = function(str, chars) diff --git a/WebCore/inspector/front-end/InspectorBackendStub.js b/WebCore/inspector/front-end/InspectorBackendStub.js index ed03f73..4670af1 100644 --- a/WebCore/inspector/front-end/InspectorBackendStub.js +++ b/WebCore/inspector/front-end/InspectorBackendStub.js @@ -34,9 +34,6 @@ WebInspector.InspectorBackendStub = function() { this._searchingForNode = false; this._attachedWindowHeight = 0; - this._debuggerEnabled = true; - this._profilerEnabled = true; - this._resourceTrackingEnabled = false; this._timelineEnabled = false; } @@ -126,39 +123,42 @@ WebInspector.InspectorBackendStub.prototype = { return ""; }, - debuggerEnabled: function() - { - return this._debuggerEnabled; - }, - enableResourceTracking: function() { - this._resourceTrackingEnabled = true; WebInspector.resourceTrackingWasEnabled(); }, disableResourceTracking: function() { - this._resourceTrackingEnabled = false; WebInspector.resourceTrackingWasDisabled(); }, - resourceTrackingEnabled: function() + + enableSearchingForNode: function() + { + WebInspector.searchingForNodeWasEnabled(); + }, + + disableSearchingForNode: function() + { + WebInspector.searchingForNodeWasDisabled(); + }, + + reloadPage: function() { - return this._resourceTrackingEnabled; }, enableDebugger: function() { - this._debuggerEnabled = true; + WebInspector.debuggerWasEnabled(); }, disableDebugger: function() { - this._debuggerEnabled = false; + WebInspector.debuggerWasDisabled(); }, - addBreakpoint: function(sourceID, line, condition) + setBreakpoint: function(sourceID, line, enabled, condition) { }, @@ -166,40 +166,37 @@ WebInspector.InspectorBackendStub.prototype = { { }, - updateBreakpoint: function(sourceID, line, condition) + activateBreakpoints: function() { + this._breakpointsActivated = true; }, - pauseInDebugger: function() + deactivateBreakpoints: function() { + this._breakpointsActivated = false; }, - pauseOnExceptionsState: function() + pauseInDebugger: function() { - return 0; }, setPauseOnExceptionsState: function(value) { + WebInspector.updatePauseOnExceptionsState(value); }, resumeDebugger: function() { }, - profilerEnabled: function() - { - return true; - }, - enableProfiler: function() { - this._profilerEnabled = true; + WebInspector.profilerWasEnabled(); }, disableProfiler: function() { - this._profilerEnabled = false; + WebInspector.profilerWasDisabled(); }, startProfiling: function() @@ -217,10 +214,6 @@ WebInspector.InspectorBackendStub.prototype = { getProfile: function(callId, uid) { - if (WebInspector.__fullProfiles && (uid in WebInspector.__fullProfiles)) - { - WebInspector.didGetProfile(callId, WebInspector.__fullProfiles[uid]); - } }, takeHeapSnapshot: function() @@ -258,6 +251,14 @@ WebInspector.InspectorBackendStub.prototype = { setInjectedScriptSource: function() { + }, + + addScriptToEvaluateOnLoad: function() + { + }, + + removeAllScriptsToEvaluateOnLoad: function() + { } } diff --git a/WebCore/inspector/front-end/InspectorFrontendHostStub.js b/WebCore/inspector/front-end/InspectorFrontendHostStub.js index f1decb6..5456069 100644 --- a/WebCore/inspector/front-end/InspectorFrontendHostStub.js +++ b/WebCore/inspector/front-end/InspectorFrontendHostStub.js @@ -48,6 +48,11 @@ WebInspector.InspectorFrontendHostStub.prototype = { return "unknown"; }, + bringToFront: function() + { + this._windowVisible = true; + }, + closeWindow: function() { this._windowVisible = false; @@ -87,12 +92,17 @@ WebInspector.InspectorFrontendHostStub.prototype = { return ""; }, - windowUnloading: function() + inspectedURLChanged: function(url) { }, copyText: function() { + }, + + canAttachWindow: function() + { + return false; } } diff --git a/WebCore/inspector/front-end/MetricsSidebarPane.js b/WebCore/inspector/front-end/MetricsSidebarPane.js index 767da1f..ed5a7ec 100644 --- a/WebCore/inspector/front-end/MetricsSidebarPane.js +++ b/WebCore/inspector/front-end/MetricsSidebarPane.js @@ -30,7 +30,6 @@ WebInspector.MetricsSidebarPane = function() { WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics")); this._inlineStyleId = null; - this._inlineStyleInjectedScriptId = null; } WebInspector.MetricsSidebarPane.prototype = { @@ -53,15 +52,14 @@ WebInspector.MetricsSidebarPane.prototype = { var style = WebInspector.CSSStyleDeclaration.parseStyle(stylePayload); self._update(style); }; - InjectedScriptAccess.get(node.injectedScriptId).getComputedStyle(node.id, callback); + InspectorBackend.getComputedStyle(WebInspector.Callback.wrap(callback), node.id); var inlineStyleCallback = function(stylePayload) { if (!stylePayload) return; self._inlineStyleId = stylePayload.id; - self._inlineStyleInjectedScriptId = stylePayload.injectedScriptId; }; - InjectedScriptAccess.get(node.injectedScriptId).getInlineStyle(node.id, inlineStyleCallback); + InspectorBackend.getInlineStyle(WebInspector.Callback.wrap(inlineStyleCallback), node.id); }, _update: function(style) @@ -187,6 +185,11 @@ WebInspector.MetricsSidebarPane.prototype = { editingCommitted: function(element, userInput, previousContent, context) { + if (!this._inlineStyleId) { + // Element has no renderer. + return this.editingCancelled(element, context); // nothing changed, so cancel + } + if (userInput === previousContent) return this.editingCancelled(element, context); // nothing changed, so cancel @@ -206,7 +209,8 @@ WebInspector.MetricsSidebarPane.prototype = { self.dispatchEventToListeners("metrics edited"); self.update(); }; - InjectedScriptAccess.get(this._inlineStyleInjectedScriptId).setStyleProperty(this._inlineStyleId, context.styleProperty, userInput, callback); + + InspectorBackend.setStyleProperty(WebInspector.Callback.wrap(callback), this._inlineStyleId, context.styleProperty, userInput); } } diff --git a/WebCore/inspector/front-end/Panel.js b/WebCore/inspector/front-end/Panel.js index 5b01191..b916708 100644 --- a/WebCore/inspector/front-end/Panel.js +++ b/WebCore/inspector/front-end/Panel.js @@ -373,12 +373,12 @@ WebInspector.Panel.prototype = { visibleView.resize(); }, - canShowSourceLineForURL: function(url) + canShowSourceLine: function(url, line) { return false; }, - showSourceLineForURL: function(url, line) + showSourceLine: function(url, line) { return false; }, diff --git a/WebCore/inspector/front-end/Popover.js b/WebCore/inspector/front-end/Popover.js index 70e4ac9..848c99e 100644 --- a/WebCore/inspector/front-end/Popover.js +++ b/WebCore/inspector/front-end/Popover.js @@ -38,6 +38,8 @@ WebInspector.Popover = function(contentElement) this.element.appendChild(this._popupArrowElement); this.contentElement = contentElement; + this._contentDiv = document.createElement("div"); + this._contentDiv.className = "content"; } WebInspector.Popover.prototype = { @@ -54,25 +56,28 @@ WebInspector.Popover.prototype = { var preferredWidth = preferredWidth || this.contentElement.offsetWidth; var preferredHeight = preferredHeight || this.contentElement.offsetHeight; - this.contentElement.addStyleClass("content"); - this.element.appendChild(this.contentElement); + this._contentDiv.appendChild(this.contentElement); + this.element.appendChild(this._contentDiv); document.body.appendChild(this.element); this._positionElement(anchor, preferredWidth, preferredHeight); }, hide: function() { - delete WebInspector.Popover._popoverElement; - document.body.removeChild(this.element); + if (WebInspector.Popover._popoverElement) { + delete WebInspector.Popover._popoverElement; + document.body.removeChild(this.element); + } }, _positionElement: function(anchorElement, preferredWidth, preferredHeight) { const borderWidth = 25; const scrollerWidth = 11; - const arrowHeight = 10; - const arrowOffset = 15; - + const arrowHeight = 15; + const arrowOffset = 10; + const borderRadius = 10; + // Skinny tooltips are not pretty, their arrow location is not nice. preferredWidth = Math.max(preferredWidth, 50); const totalWidth = window.innerWidth; @@ -87,7 +92,7 @@ WebInspector.Popover.prototype = { anchorElement = anchorElement.parentElement; } - var newElementPosition = { x: 0, y: 0, width: preferredWidth + borderWidth * 2, height: preferredHeight + borderWidth * 2 }; + var newElementPosition = { x: 0, y: 0, width: preferredWidth + scrollerWidth, height: preferredHeight }; var verticalAlignment; var roomAbove = anchorBox.y; @@ -95,53 +100,140 @@ WebInspector.Popover.prototype = { if (roomAbove > roomBelow) { // Positioning above the anchor. - if (anchorBox.y > newElementPosition.height) - newElementPosition.y = anchorBox.y - newElementPosition.height; + if (anchorBox.y > newElementPosition.height + arrowHeight + borderRadius) + newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHeight; else { - newElementPosition.y = 0; - newElementPosition.height = anchorBox.y - newElementPosition.y; - // Reserve room for vertical scroller anyways. - newElementPosition.width += scrollerWidth; + newElementPosition.y = borderRadius * 2; + newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight; } verticalAlignment = "bottom"; } else { // Positioning below the anchor. - newElementPosition.y = anchorBox.y + anchorBox.height; - if (newElementPosition.y + newElementPosition.height >= totalHeight) { - newElementPosition.height = totalHeight - anchorBox.y - anchorBox.height; - // Reserve room for vertical scroller. - newElementPosition.width += scrollerWidth; - } + newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight; + if (newElementPosition.y + newElementPosition.height + arrowHeight - borderWidth >= totalHeight) + newElementPosition.height = totalHeight - anchorBox.y - anchorBox.height - borderRadius * 2 - arrowHeight; // Align arrow. - newElementPosition.y -= arrowHeight; verticalAlignment = "top"; } var horizontalAlignment; if (anchorBox.x + newElementPosition.width < totalWidth) { - newElementPosition.x = Math.max(0, anchorBox.x) - borderWidth - arrowOffset; + newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius - arrowOffset); horizontalAlignment = "left"; - } else if (newElementPosition.width < totalWidth) { - newElementPosition.x = totalWidth - newElementPosition.width; + } else if (newElementPosition.width + borderRadius * 2 < totalWidth) { + newElementPosition.x = totalWidth - newElementPosition.width - borderRadius; horizontalAlignment = "right"; // Position arrow accurately. - this._popupArrowElement.style.right = totalWidth - anchorBox.x - borderWidth - anchorBox.width + "px"; + var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.width - borderRadius - arrowOffset); + arrowRightPosition += anchorBox.width / 2; + this._popupArrowElement.style.right = arrowRightPosition + "px"; } else { - newElementPosition.x = 0; - newElementPosition.width = totalWidth; + newElementPosition.x = borderRadius; + newElementPosition.width = totalWidth - borderRadius * 2; + newElementPosition.height += scrollerWidth; horizontalAlignment = "left"; if (verticalAlignment === "bottom") newElementPosition.y -= scrollerWidth; // Position arrow accurately. - this._popupArrowElement.style.left = anchorBox.x - borderWidth + "px"; + this._popupArrowElement.style.left = Math.max(0, anchorBox.x - borderRadius * 2 - arrowOffset) + "px"; + this._popupArrowElement.style.left += anchorBox.width / 2; } - // Reserve room for horizontal scroller. - newElementPosition.height += scrollerWidth; - this.element.className = "popover " + verticalAlignment + "-" + horizontalAlignment + "-arrow"; - this.element.positionAt(newElementPosition.x, newElementPosition.y); - this.element.style.width = newElementPosition.width + "px"; - this.element.style.height = newElementPosition.height + "px"; + this.element.positionAt(newElementPosition.x - borderWidth, newElementPosition.y - borderWidth); + this.element.style.width = newElementPosition.width + borderWidth * 2 + "px"; + this.element.style.height = newElementPosition.height + borderWidth * 2 + "px"; + } +} + +WebInspector.PopoverHelper = function(panelElement, getAnchor, showPopup, showOnClick, onHide) +{ + this._panelElement = panelElement; + this._getAnchor = getAnchor; + this._showPopup = showPopup; + this._showOnClick = showOnClick; + this._onHide = onHide; + panelElement.addEventListener("mousedown", this._mouseDown.bind(this), false); + panelElement.addEventListener("mousemove", this._mouseMove.bind(this), false); +} + +WebInspector.PopoverHelper.prototype = { + _mouseDown: function(event) + { + this._killHidePopupTimer(); + this._handleMouseAction(event, true); + }, + + _mouseMove: function(event) + { + // Pretend that nothing has happened. + if (this._hoverElement === event.target || (this._hoverElement && this._hoverElement.isAncestor(event.target))) + return; + + // User has 500ms to reach the popup. + if (this._popup && !this._hidePopupTimer) { + var self = this; + function doHide() + { + self.hidePopup(); + delete self._hidePopupTimer; + } + this._hidePopupTimer = setTimeout(doHide, 500); + } + + this._handleMouseAction(event); + }, + + _handleMouseAction: function(event, isMouseDown) + { + this._resetHoverTimer(); + + this._hoverElement = this._getAnchor(event.target); + if (!this._hoverElement) + return; + + const toolTipDelay = isMouseDown ? 0 : (this._popup ? 600 : 1000); + this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay); + }, + + _resetHoverTimer: function() + { + if (this._hoverTimer) { + clearTimeout(this._hoverTimer); + delete this._hoverTimer; + } + }, + + hidePopup: function() + { + if (!this._popup) + return; + + if (this._onHide) + this._onHide(); + + this._popup.hide(); + delete this._popup; + }, + + _mouseHover: function(element) + { + delete this._hoverTimer; + + this._popup = this._showPopup(element); + if (this._popup) + this._popup.contentElement.addEventListener("mousemove", this._killHidePopupTimer.bind(this), true); + }, + + _killHidePopupTimer: function() + { + if (this._hidePopupTimer) { + clearTimeout(this._hidePopupTimer); + delete this._hidePopupTimer; + + // We know that we reached the popup, but we might have moved over other elements. + // Discard pending command. + this._resetHoverTimer(); + } } } diff --git a/WebCore/inspector/front-end/ProfileView.js b/WebCore/inspector/front-end/ProfileView.js index 1efa0dc..817f1f5 100644 --- a/WebCore/inspector/front-end/ProfileView.js +++ b/WebCore/inspector/front-end/ProfileView.js @@ -83,7 +83,7 @@ WebInspector.CPUProfileView = function(profile) var self = this; function profileCallback(profile) { - self.profile = profile; + self.profile.head = profile.head; self._assignParentsInProfile(); self.profileDataGridTree = self.bottomUpProfileDataGridTree; @@ -601,7 +601,7 @@ WebInspector.CPUProfileType.prototype = { get welcomeMessage() { - return WebInspector.UIString("Start CPU profiling by pressing<br>the %s button on the status bar."); + return WebInspector.UIString("Control CPU profiling by pressing the %s button on the status bar."); }, setRecordingProfile: function(isProfiling) diff --git a/WebCore/inspector/front-end/ProfilesPanel.js b/WebCore/inspector/front-end/ProfilesPanel.js index dd049a1..2f9d38d 100644 --- a/WebCore/inspector/front-end/ProfilesPanel.js +++ b/WebCore/inspector/front-end/ProfilesPanel.js @@ -120,6 +120,7 @@ WebInspector.ProfilesPanel = function() this.element.appendChild(this.welcomeView.element); this._profiles = []; + this._profilerEnabled = Preferences.profilerAlwaysEnabled; this.reset(); } @@ -163,6 +164,7 @@ WebInspector.ProfilesPanel.prototype = { populateInterface: function() { + this.reset(); if (this.visible) this._populateProfiles(); else @@ -171,12 +173,19 @@ WebInspector.ProfilesPanel.prototype = { profilerWasEnabled: function() { - this.reset(); + if (this._profilerEnabled) + return; + + this._profilerEnabled = true; this.populateInterface(); }, profilerWasDisabled: function() { + if (!this._profilerEnabled) + return; + + this._profilerEnabled = false; this.reset(); }, @@ -230,7 +239,6 @@ WebInspector.ProfilesPanel.prototype = { container.appendChild(part1); var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption); - button.element.addEventListener("click", profileType.buttonClicked.bind(profileType), false); container.appendChild(button.element); var part2 = document.createElement("span"); @@ -429,7 +437,7 @@ WebInspector.ProfilesPanel.prototype = { _updateInterface: function() { // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change. - if (InspectorBackend.profilerEnabled()) { + if (this._profilerEnabled) { this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable."); this.enableToggleButton.toggled = true; for (var typeId in this._profileTypeButtonsByIdMap) @@ -448,14 +456,14 @@ WebInspector.ProfilesPanel.prototype = { _enableProfiling: function() { - if (InspectorBackend.profilerEnabled()) + if (this._profilerEnabled) return; this._toggleProfiling(this.panelEnablerView.alwaysEnabled); }, _toggleProfiling: function(optionalAlways) { - if (InspectorBackend.profilerEnabled()) + if (this._profilerEnabled) InspectorBackend.disableProfiler(true); else InspectorBackend.enableProfiler(!!optionalAlways); diff --git a/WebCore/inspector/front-end/Resource.js b/WebCore/inspector/front-end/Resource.js index 9860300..4ee5f28 100644 --- a/WebCore/inspector/front-end/Resource.js +++ b/WebCore/inspector/front-end/Resource.js @@ -272,22 +272,28 @@ WebInspector.Resource.prototype = { return this._responseReceivedTime - this._startTime; }, - get contentLength() + get resourceSize() { - return this._contentLength || 0; + return this._resourceSize || 0; }, - set contentLength(x) + set resourceSize(x) { - if (this._contentLength === x) + if (this._resourceSize === x) return; - this._contentLength = x; + this._resourceSize = x; if (WebInspector.panels.resources) WebInspector.panels.resources.refreshResource(this); }, + get transferSize() + { + // FIXME: this is wrong for chunked-encoding resources. + return this.cached ? 0 : Number(this.responseHeaders["Content-Length"] || this.resourceSize || 0); + }, + get expectedContentLength() { return this._expectedContentLength || 0; @@ -603,9 +609,15 @@ WebInspector.Resource.CompareByLatency = function(a, b) WebInspector.Resource.CompareBySize = function(a, b) { - return a.contentLength - b.contentLength; + return a.resourceSize - b.resourceSize; } +WebInspector.Resource.CompareByTransferSize = function(a, b) +{ + return a.transferSize - b.transferSize; +} + + WebInspector.Resource.StatusTextForCode = function(code) { return code ? code + " " + WebInspector.Resource.StatusText[code] : ""; diff --git a/WebCore/inspector/front-end/ResourceView.js b/WebCore/inspector/front-end/ResourceView.js index b7b01ac..618a935 100644 --- a/WebCore/inspector/front-end/ResourceView.js +++ b/WebCore/inspector/front-end/ResourceView.js @@ -47,7 +47,7 @@ WebInspector.ResourceView = function(resource) this.tabsElement.appendChild(this.contentTabElement); this.headersTabElement.addEventListener("click", this._selectHeadersTab.bind(this), false); - this.contentTabElement.addEventListener("click", this._selectContentTab.bind(this), false); + this.contentTabElement.addEventListener("click", this.selectContentTab.bind(this), false); this.headersElement = document.createElement("div"); this.headersElement.className = "resource-view-headers"; @@ -155,7 +155,7 @@ WebInspector.ResourceView.prototype = { if (WebInspector.settings.resourceViewTab === "headers") this._selectHeadersTab(); else - this._selectContentTab(); + this.selectContentTab(); } else this._innerSelectContentTab(); }, @@ -169,7 +169,7 @@ WebInspector.ResourceView.prototype = { this.contentElement.addStyleClass("hidden"); }, - _selectContentTab: function() + selectContentTab: function() { WebInspector.settings.resourceViewTab = "content"; this._innerSelectContentTab(); @@ -183,8 +183,17 @@ WebInspector.ResourceView.prototype = { this.headersElement.addStyleClass("hidden"); if ("resize" in this) this.resize(); - if ("contentTabSelected" in this) - this.contentTabSelected(); + this.contentTabSelected(); + }, + + contentTabSelected: function() + { + if (!this._contentPlaceholderElement) { + this._contentPlaceholderElement = document.createElement("div"); + this._contentPlaceholderElement.className = "resource-content-unavailable"; + this._contentPlaceholderElement.textContent = WebInspector.UIString("No Content Available"); + this.contentElement.appendChild(this._contentPlaceholderElement); + } }, _refreshURL: function() diff --git a/WebCore/inspector/front-end/ResourcesPanel.js b/WebCore/inspector/front-end/ResourcesPanel.js index 19325bb..5284e68 100644 --- a/WebCore/inspector/front-end/ResourcesPanel.js +++ b/WebCore/inspector/front-end/ResourcesPanel.js @@ -47,6 +47,7 @@ WebInspector.ResourcesPanel = function() this.reset(); this.filter(this.filterAllElement, false); this.graphsTreeElement.children[0].select(); + this._resourceTrackingEnabled = false; } WebInspector.ResourcesPanel.prototype = { @@ -98,6 +99,7 @@ WebInspector.ResourcesPanel.prototype = { { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator }, ]; + timeGraphItem.isBarOpaqueAtLeft = false; timeGraphItem.selectedSortingOptionIndex = 1; var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size")); @@ -105,9 +107,11 @@ WebInspector.ResourcesPanel.prototype = { var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator(); sizeGraphItem.sortingOptions = [ + { name: WebInspector.UIString("Sort by Transfer Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize, calculator: transferSizeCalculator }, { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator }, ]; + sizeGraphItem.isBarOpaqueAtLeft = true; sizeGraphItem.selectedSortingOptionIndex = 0; this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true); @@ -123,6 +127,11 @@ WebInspector.ResourcesPanel.prototype = { this.itemsTreeElement.expand(); }, + get resourceTrackingEnabled() + { + return this._resourceTrackingEnabled; + }, + _createPanelEnabler: function() { var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel."); @@ -279,7 +288,7 @@ WebInspector.ResourcesPanel.prototype = { { if (this.visibleResource) return this.visibleResource._resourcesView; - return InspectorBackend.resourceTrackingEnabled() ? null : this.panelEnablerView; + return this._resourceTrackingEnabled ? null : this.panelEnablerView; }, get sortingFunction() @@ -308,11 +317,13 @@ WebInspector.ResourcesPanel.prototype = { resourceTrackingWasEnabled: function() { + this._resourceTrackingEnabled = true; this.reset(); }, resourceTrackingWasDisabled: function() { + this._resourceTrackingEnabled = false; this.reset(); }, @@ -344,7 +355,7 @@ WebInspector.ResourcesPanel.prototype = { this.summaryBar.reset(); - if (InspectorBackend.resourceTrackingEnabled()) { + if (this._resourceTrackingEnabled) { this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable."); this.enableToggleButton.toggled = true; this.largerResourcesButton.visible = true; @@ -450,14 +461,16 @@ WebInspector.ResourcesPanel.prototype = { if (oldViewParentNode) newView.show(oldViewParentNode); + + WebInspector.panels.scripts.viewRecreated(oldView, newView); }, - canShowSourceLineForURL: function(url) + canShowSourceLine: function(url, line) { - return !!WebInspector.resourceForURL(url); + return this._resourceTrackingEnabled && !!WebInspector.resourceForURL(url); }, - showSourceLineForURL: function(url, line) + showSourceLine: function(url, line) { this.showResource(WebInspector.resourceForURL(url), line); }, @@ -477,6 +490,7 @@ WebInspector.ResourcesPanel.prototype = { view.show(this.viewsContainerElement); if (line) { + view.selectContentTab(); if (view.revealLine) view.revealLine(line); if (view.highlightLine) @@ -564,7 +578,7 @@ WebInspector.ResourcesPanel.prototype = { var percent = this.calculator.computePercentageFromEventTime(this.mainResourceLoadTime); var loadDivider = document.createElement("div"); - loadDivider.className = "resources-onload-divider"; + loadDivider.className = "resources-event-divider resources-red-divider"; var loadDividerPadding = document.createElement("div"); loadDividerPadding.className = "resources-event-divider-padding"; @@ -579,7 +593,7 @@ WebInspector.ResourcesPanel.prototype = { var percent = this.calculator.computePercentageFromEventTime(this.mainResourceDOMContentTime); var domContentDivider = document.createElement("div"); - domContentDivider.className = "resources-ondomcontent-divider"; + domContentDivider.className = "resources-event-divider resources-blue-divider"; var domContentDividerPadding = document.createElement("div"); domContentDividerPadding.className = "resources-event-divider-padding"; @@ -685,16 +699,18 @@ WebInspector.ResourcesPanel.prototype = { _enableResourceTracking: function() { - if (InspectorBackend.resourceTrackingEnabled()) + if (this._resourceTrackingEnabled) return; this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled); }, _toggleResourceTracking: function(optionalAlways) { - if (InspectorBackend.resourceTrackingEnabled()) { + if (this._resourceTrackingEnabled) { this.largerResourcesButton.visible = false; this.sortingSelectElement.visible = false; + WebInspector.resources = {}; + WebInspector.resourceURLMap = {}; InspectorBackend.disableResourceTracking(true); } else { this.largerResourcesButton.visible = true; @@ -824,18 +840,20 @@ WebInspector.ResourceTimeCalculator.prototype = { computeBarGraphLabels: function(resource) { - var leftLabel = ""; - if (resource.latency > 0) - leftLabel = this.formatValue(resource.latency); - var rightLabel = ""; if (resource.responseReceivedTime !== -1 && resource.endTime !== -1) rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime); - if (leftLabel && rightLabel) { + var hasLatency = resource.latency > 0; + if (hasLatency) + var leftLabel = this.formatValue(resource.latency); + else + var leftLabel = rightLabel; + + if (hasLatency && rightLabel) { var total = this.formatValue(resource.duration); var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total); - } else if (leftLabel) + } else if (hasLatency) var tooltip = WebInspector.UIString("%s latency", leftLabel); else if (rightLabel) var tooltip = WebInspector.UIString("%s download", rightLabel); @@ -939,16 +957,39 @@ WebInspector.ResourceTransferSizeCalculator = function() WebInspector.ResourceTransferSizeCalculator.prototype = { computeBarGraphLabels: function(resource) { - const label = this.formatValue(this._value(resource)); - var tooltip = label; + var networkBytes = this._networkBytes(resource); + var resourceBytes = this._value(resource); + if (networkBytes && networkBytes !== resourceBytes) { + // Transferred size is not the same as reported resource length. + var networkBytesString = this.formatValue(networkBytes); + var left = networkBytesString; + var right = this.formatValue(resourceBytes); + var tooltip = right ? WebInspector.UIString("%s (%s transferred)", right, networkBytesString) : right; + } else { + var left = this.formatValue(resourceBytes); + var right = left; + var tooltip = left; + } if (resource.cached) tooltip = WebInspector.UIString("%s (from cache)", tooltip); - return {left: label, right: label, tooltip: tooltip}; + return {left: left, right: right, tooltip: tooltip}; + }, + + computeBarGraphPercentages: function(item) + { + const resourceBytesAsPercent = (this._value(item) / this.boundarySpan) * 100; + const networkBytesAsPercent = this._networkBytes(item) ? (this._networkBytes(item) / this.boundarySpan) * 100 : resourceBytesAsPercent; + return {start: 0, middle: networkBytesAsPercent, end: resourceBytesAsPercent}; }, _value: function(resource) { - return resource.contentLength; + return resource.resourceSize; + }, + + _networkBytes: function(resource) + { + return resource.transferSize; }, formatValue: function(value) @@ -1139,6 +1180,11 @@ WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b) return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource); } +WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize = function(a, b) +{ + return -1 * WebInspector.Resource.CompareByTransferSize(a.resource, b.resource); +} + WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; WebInspector.ResourceGraph = function(resource) @@ -1196,8 +1242,15 @@ WebInspector.ResourceGraph.prototype = { const labelPadding = 10; const barRightElementOffsetWidth = this._barRightElement.offsetWidth; const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth; - const rightBarWidth = (barRightElementOffsetWidth - labelPadding); - const leftBarWidth = ((barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding); + + if (this._isBarOpaqueAtLeft) { + var leftBarWidth = barLeftElementOffsetWidth - labelPadding; + var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding; + } else { + var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding; + var rightBarWidth = barRightElementOffsetWidth - labelPadding; + } + const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth; const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth; @@ -1205,8 +1258,22 @@ WebInspector.ResourceGraph.prototype = { const labelAfter = (labelRightElementOffsetWidth > rightBarWidth); const graphElementOffsetWidth = this._graphElement.offsetWidth; + if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10)) + var leftHidden = true; + + if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10)) + var rightHidden = true; + + if (barLeftElementOffsetWidth == barRightElementOffsetWidth) { + // The left/right label data are the same, so a before/after label can be replaced by an on-bar label. + if (labelBefore && !labelAfter) + leftHidden = true; + else if (labelAfter && !labelBefore) + rightHidden = true; + } + if (labelBefore) { - if ((graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10)) + if (leftHidden) this._labelLeftElement.addStyleClass("hidden"); this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%"); this._labelLeftElement.addStyleClass("before"); @@ -1216,7 +1283,7 @@ WebInspector.ResourceGraph.prototype = { } if (labelAfter) { - if ((graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10)) + if (rightHidden) this._labelRightElement.addStyleClass("hidden"); this._labelRightElement.style.setProperty("left", this._percentages.end + "%"); this._labelRightElement.addStyleClass("after"); @@ -1226,7 +1293,7 @@ WebInspector.ResourceGraph.prototype = { } }, - refresh: function(calculator) + refresh: function(calculator, isBarOpaqueAtLeft) { var percentages = calculator.computeBarGraphPercentages(this.resource); var labels = calculator.computeBarGraphLabels(this.resource); @@ -1241,11 +1308,32 @@ WebInspector.ResourceGraph.prototype = { } this._barLeftElement.style.setProperty("left", percentages.start + "%"); - this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); - - this._barRightElement.style.setProperty("left", percentages.middle + "%"); this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); + if (!isBarOpaqueAtLeft) { + this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); + this._barRightElement.style.setProperty("left", percentages.middle + "%"); + + if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) { + this._barLeftElement.addStyleClass("waiting"); + this._barRightElement.removeStyleClass("waiting-right"); + this._labelLeftElement.addStyleClass("waiting"); + this._labelRightElement.removeStyleClass("waiting-right"); + } + } else { + this._barLeftElement.style.setProperty("right", (100 - percentages.middle) + "%"); + this._barRightElement.style.setProperty("left", percentages.start + "%"); + + if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) { + this._barLeftElement.removeStyleClass("waiting"); + this._barRightElement.addStyleClass("waiting-right"); + this._labelLeftElement.removeStyleClass("waiting"); + this._labelRightElement.addStyleClass("waiting-right"); + } + } + + this._isBarOpaqueAtLeft = isBarOpaqueAtLeft; + this._labelLeftElement.textContent = labels.left; this._labelRightElement.textContent = labels.right; diff --git a/WebCore/inspector/front-end/Script.js b/WebCore/inspector/front-end/Script.js index e6413a9..79004f3 100644 --- a/WebCore/inspector/front-end/Script.js +++ b/WebCore/inspector/front-end/Script.js @@ -42,9 +42,20 @@ WebInspector.Script = function(sourceID, sourceURL, source, startingLine, errorL var match = pattern.exec(source); if (match) - this.sourceURL = WebInspector.UIString("(program): %s", match[1]); + this.sourceURL = match[1]; } } WebInspector.Script.prototype = { + get linesCount() + { + if (!this.source) + return 0; + this._linesCount = 0; + var lastIndex = this.source.indexOf("\n"); + while (lastIndex !== -1) { + lastIndex = this.source.indexOf("\n", lastIndex + 1) + this._linesCount++; + } + } } diff --git a/WebCore/inspector/front-end/ScriptView.js b/WebCore/inspector/front-end/ScriptView.js index c5a8b81..e55a685 100644 --- a/WebCore/inspector/front-end/ScriptView.js +++ b/WebCore/inspector/front-end/ScriptView.js @@ -33,7 +33,8 @@ WebInspector.ScriptView = function(script) this._frameNeedsSetup = true; this._sourceFrameSetup = false; - this.sourceFrame = new WebInspector.SourceFrame(this.element, this._addBreakpoint.bind(this), this._removeBreakpoint.bind(this)); + var canEditScripts = WebInspector.panels.scripts.canEditScripts(); + this.sourceFrame = new WebInspector.SourceFrame(this.element, this._addBreakpoint.bind(this), this._removeBreakpoint.bind(this), canEditScripts ? this._editLine.bind(this) : null); } WebInspector.ScriptView.prototype = { @@ -52,11 +53,18 @@ WebInspector.ScriptView.prototype = { this.attach(); - this.sourceFrame.setContent("text/javascript", this.script.source); + this.sourceFrame.setContent("text/javascript", this._prependWhitespace(this.script.source)); this._sourceFrameSetup = true; delete this._frameNeedsSetup; }, + _prependWhitespace: function(content) { + var prefix = ""; + for (var i = 0; i < this.script.startingLine - 1; ++i) + prefix += "\n"; + return prefix + content; + }, + attach: function() { if (!this.element.parentNode) @@ -69,6 +77,17 @@ WebInspector.ScriptView.prototype = { WebInspector.panels.scripts.addBreakpoint(breakpoint); }, + _editLine: function(line, newContent) + { + WebInspector.panels.scripts.editScriptLine(this.script.sourceID, line, newContent, this._editLineComplete.bind(this)); + }, + + _editLineComplete: function(newBody) + { + this.script.source = newBody; + this.sourceFrame.updateContent(this._prependWhitespace(newBody)); + }, + // The follow methods are pulled from SourceView, since they are // generic and work with ScriptView just fine. diff --git a/WebCore/inspector/front-end/ScriptsPanel.js b/WebCore/inspector/front-end/ScriptsPanel.js index da24ed2..987bdf2 100644 --- a/WebCore/inspector/front-end/ScriptsPanel.js +++ b/WebCore/inspector/front-end/ScriptsPanel.js @@ -105,6 +105,12 @@ WebInspector.ScriptsPanel = function() this.stepOutButton.appendChild(document.createElement("img")); this.sidebarButtonsElement.appendChild(this.stepOutButton); + this.toggleBreakpointsButton = new WebInspector.StatusBarButton("", "toggle-breakpoints"); + this.toggleBreakpointsButton.addEventListener("click", this._toggleBreakpointsClicked.bind(this), false); + this.sidebarButtonsElement.appendChild(this.toggleBreakpointsButton.element); + // Breakpoints should be activated by default, so emulate a click to toggle on. + this._toggleBreakpointsClicked(); + this.debuggerStatusElement = document.createElement("div"); this.debuggerStatusElement.id = "scripts-debugger-status"; this.sidebarButtonsElement.appendChild(this.debuggerStatusElement); @@ -129,6 +135,7 @@ WebInspector.ScriptsPanel = function() this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane(); this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane(); this.sidebarPanes.breakpoints = new WebInspector.BreakpointsSidebarPane(); + this.sidebarPanes.workers = new WebInspector.WorkersSidebarPane(); for (var pane in this.sidebarPanes) this.sidebarElement.appendChild(this.sidebarPanes[pane].element); @@ -154,10 +161,9 @@ WebInspector.ScriptsPanel = function() this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false); - this.pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item", 3); - this.pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false); - - this._breakpointsURLMap = {}; + this._pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item", 3); + this._pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false); + this._pauseOnExceptionButton.state = WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions; this._shortcuts = {}; var handler, shortcut; @@ -191,10 +197,13 @@ WebInspector.ScriptsPanel = function() shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier); this._shortcuts[shortcut] = handler; + this._debuggerEnabled = Preferences.debuggerAlwaysEnabled; + if (Preferences.debuggerAlwaysEnabled) + this._attachDebuggerWhenShown = true; this.reset(); } -// Keep these in sync with WebCore::JavaScriptDebugServer +// Keep these in sync with WebCore::ScriptDebugServer WebInspector.ScriptsPanel.PauseOnExceptionsState = { DontPauseOnExceptions : 0, PauseOnAllExceptions : 1, @@ -211,7 +220,7 @@ WebInspector.ScriptsPanel.prototype = { get statusBarItems() { - return [this.enableToggleButton.element, this.pauseOnExceptionButton.element]; + return [this.enableToggleButton.element, this._pauseOnExceptionButton.element]; }, get defaultFocusedElement() @@ -234,16 +243,6 @@ WebInspector.ScriptsPanel.prototype = { this.visibleView.headersVisible = false; this.visibleView.show(this.viewsContainerElement); } - // Hide any views that are visible that are not this panel's current visible view. - // This can happen when a ResourceView is visible in the Resources panel then switched - // to the this panel. - for (var sourceID in this._sourceIDMap) { - var scriptOrResource = this._sourceIDMap[sourceID]; - var view = this._sourceViewForScriptOrResource(scriptOrResource); - if (!view || view === this.visibleView) - continue; - view.visible = false; - } if (this._attachDebuggerWhenShown) { InspectorBackend.enableDebugger(false); delete this._attachDebuggerWhenShown; @@ -252,89 +251,74 @@ WebInspector.ScriptsPanel.prototype = { get searchableViews() { - var views = []; - - const visibleView = this.visibleView; - if (visibleView && visibleView.performSearch) { - visibleView.alreadySearching = true; - views.push(visibleView); - } - - for (var sourceID in this._sourceIDMap) { - var scriptOrResource = this._sourceIDMap[sourceID]; - var view = this._sourceViewForScriptOrResource(scriptOrResource); - if (!view || !view.performSearch || view.alreadySearching) - continue; - - view.alreadySearching = true; - views.push(view); - } - - for (var i = 0; i < views.length; ++i) - delete views[i].alreadySearching; + return [ this.visibleView ]; + }, - return views; + get breakpointsActivated() + { + return this.toggleBreakpointsButton.toggled; }, addScript: function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage) { var script = new WebInspector.Script(sourceID, sourceURL, source, startingLine, errorLine, errorMessage); - - if (sourceURL in WebInspector.resourceURLMap) { - var resource = WebInspector.resourceURLMap[sourceURL]; - resource.addScript(script); - } - - sourceURL = script.sourceURL; - - if (sourceID) - this._sourceIDMap[sourceID] = (resource || script); - - if (sourceURL in this._breakpointsURLMap && sourceID) { - var breakpoints = this._breakpointsURLMap[sourceURL]; - var breakpointsLength = breakpoints.length; - for (var i = 0; i < breakpointsLength; ++i) { - var breakpoint = breakpoints[i]; - - if (startingLine <= breakpoint.line) { - // remove and add the breakpoint, to clean up things like the sidebar - this.removeBreakpoint(breakpoint); - breakpoint.sourceID = sourceID; - this.addBreakpoint(breakpoint); - - if (breakpoint.enabled) - InspectorBackend.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition); + this._sourceIDMap[sourceID] = script; + + var resource = WebInspector.resourceURLMap[sourceURL]; + if (resource) { + if (resource.finished) { + // Resource is finished, bind the script right away. + resource.addScript(script); + this._sourceIDMap[sourceID] = resource; + } else { + // Resource is not finished, bind the script later. + if (!resource._scriptsPendingResourceLoad) { + resource._scriptsPendingResourceLoad = []; + resource.addEventListener("finished", this._resourceLoadingFinished, this); } + resource._scriptsPendingResourceLoad.push(script); } } - this._addScriptToFilesMenu(script); }, - scriptOrResourceForID: function(id) - { - return this._sourceIDMap[id]; - }, - - scriptForURL: function(url) + _resourceLoadingFinished: function(e) { - return this._scriptsForURLsInFilesSelect[url]; + var resource = e.target; + for (var i = 0; i < resource._scriptsPendingResourceLoad.length; ++i) { + // Bind script to resource. + var script = resource._scriptsPendingResourceLoad[i]; + resource.addScript(script); + this._sourceIDMap[script.sourceID] = resource; + + // Remove script from the files list. + script.filesSelectOption.parentElement.removeChild(script.filesSelectOption); + + // Move breakpoints to the resource's frame. + if (script._scriptView) { + var sourceFrame = script._scriptView.sourceFrame; + var resourceFrame = this._sourceFrameForScriptOrResource(resource); + for (var j = 0; j < sourceFrame.breakpoints; ++j) + resourceFrame.addBreakpoint(sourceFrame.breakpoints[j]); + } + } + // Adding first script will add resource. + this._addScriptToFilesMenu(resource._scriptsPendingResourceLoad[0]); + delete resource._scriptsPendingResourceLoad; }, addBreakpoint: function(breakpoint) { + if (!this.breakpointsActivated) + this._toggleBreakpointsClicked(); + this.sidebarPanes.breakpoints.addBreakpoint(breakpoint); var sourceFrame; if (breakpoint.url) { - if (!(breakpoint.url in this._breakpointsURLMap)) - this._breakpointsURLMap[breakpoint.url] = []; - this._breakpointsURLMap[breakpoint.url].unshift(breakpoint); - - if (breakpoint.url in WebInspector.resourceURLMap) { - var resource = WebInspector.resourceURLMap[breakpoint.url]; + var resource = WebInspector.resourceURLMap[breakpoint.url]; + if (resource && resource.finished) sourceFrame = this._sourceFrameForScriptOrResource(resource); - } } if (breakpoint.sourceID && !sourceFrame) { @@ -351,16 +335,10 @@ WebInspector.ScriptsPanel.prototype = { this.sidebarPanes.breakpoints.removeBreakpoint(breakpoint); var sourceFrame; - if (breakpoint.url && breakpoint.url in this._breakpointsURLMap) { - var breakpoints = this._breakpointsURLMap[breakpoint.url]; - breakpoints.remove(breakpoint); - if (!breakpoints.length) - delete this._breakpointsURLMap[breakpoint.url]; - - if (breakpoint.url in WebInspector.resourceURLMap) { - var resource = WebInspector.resourceURLMap[breakpoint.url]; + if (breakpoint.url) { + var resource = WebInspector.resourceURLMap[breakpoint.url]; + if (resource && resource.finished) sourceFrame = this._sourceFrameForScriptOrResource(resource); - } } if (breakpoint.sourceID && !sourceFrame) { @@ -372,6 +350,40 @@ WebInspector.ScriptsPanel.prototype = { sourceFrame.removeBreakpoint(breakpoint); }, + canEditScripts: function() + { + return !!InspectorBackend.editScriptLine; + }, + + editScriptLine: function(sourceID, line, newContent, callback) + { + if (!this.canEditScripts()) + return; + + // Need to clear breakpoints and re-create them later when editing source. + var breakpointsPanel = this.sidebarPanes.breakpoints; + var newBreakpoints = []; + for (var id in breakpointsPanel.breakpoints) { + var breakpoint = breakpointsPanel.breakpoints[id]; + breakpointsPanel.removeBreakpoint(breakpoint); + newBreakpoints.push(breakpoint); + } + + var linesCountToShift = newContent.split("\n").length - 1; + function mycallback(newBody) + { + callback(newBody); + for (var i = 0; i < newBreakpoints.length; ++i) { + var breakpoint = newBreakpoints[i]; + if (breakpoint.line >= line) + breakpoint.line += linesCountToShift; + this.addBreakpoint(breakpoint); + } + }; + var callbackId = WebInspector.Callback.wrap(mycallback.bind(this)) + InspectorBackend.editScriptLine(callbackId, sourceID, line, newContent); + }, + selectedCallFrameId: function() { var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame; @@ -444,22 +456,30 @@ WebInspector.ScriptsPanel.prototype = { debuggerWasEnabled: function() { - this.reset(); + if (this._debuggerEnabled) + return; + + this._debuggerEnabled = true; + this.reset(true); }, debuggerWasDisabled: function() { - this.reset(); + if (!this._debuggerEnabled) + return; + + this._debuggerEnabled = false; + this.reset(true); }, - reset: function() + reset: function(preserveWorkers) { this.visibleView = null; delete this.currentQuery; this.searchCanceled(); - if (!InspectorBackend.debuggerEnabled()) { + if (!this._debuggerEnabled) { this._paused = false; this._waitingToPause = false; this._stepping = false; @@ -471,7 +491,7 @@ WebInspector.ScriptsPanel.prototype = { this._currentBackForwardIndex = -1; this._updateBackAndForwardButtons(); - this._scriptsForURLsInFilesSelect = {}; + this._resourceForURLInFilesSelect = {}; this.filesSelectElement.removeChildren(); this.functionsSelectElement.removeChildren(); this.viewsContainerElement.removeChildren(); @@ -487,6 +507,9 @@ WebInspector.ScriptsPanel.prototype = { this._sourceIDMap = {}; this.sidebarPanes.watchExpressions.refreshExpressions(); + this.sidebarPanes.breakpoints.reset(); + if (!preserveWorkers) + this.sidebarPanes.workers.reset(); }, get visibleView() @@ -508,36 +531,50 @@ WebInspector.ScriptsPanel.prototype = { x.show(this.viewsContainerElement); }, - canShowSourceLineForURL: function(url) + viewRecreated: function(oldView, newView) { - return InspectorBackend.debuggerEnabled() && - !!(WebInspector.resourceForURL(url) || this.scriptForURL(url)); + if (this._visibleView === oldView) + this._visibleView = newView; }, - showSourceLineForURL: function(url, line) + canShowSourceLine: function(url, line) { - var resource = WebInspector.resourceForURL(url); - if (resource) - this.showResource(resource, line); - else - this.showScript(this.scriptForURL(url), line); + if (!this._debuggerEnabled) + return false; + return !!this._scriptOrResourceForURLAndLine(url, line); }, - showScript: function(script, line) + showSourceLine: function(url, line) { - this._showScriptOrResource(script, {line: line, shouldHighlightLine: true}); + var scriptOrResource = this._scriptOrResourceForURLAndLine(url, line); + this._showScriptOrResource(scriptOrResource, {line: line, shouldHighlightLine: true}); }, - showResource: function(resource, line) + _scriptOrResourceForURLAndLine: function(url, line) { - this._showScriptOrResource(resource, {line: line, shouldHighlightLine: true}); + var scriptWithMatchingUrl = null; + for (var sourceID in this._sourceIDMap) { + var scriptOrResource = this._sourceIDMap[sourceID]; + if (scriptOrResource instanceof WebInspector.Script) { + if (scriptOrResource.sourceURL !== url) + continue; + scriptWithMatchingUrl = scriptOrResource; + if (scriptWithMatchingUrl.startingLine <= line && scriptWithMatchingUrl.startingLine + scriptWithMatchingUrl.linesCount > line) + return scriptWithMatchingUrl; + } else { + var resource = scriptOrResource; + if (resource.url === url) + return resource; + } + } + return scriptWithMatchingUrl; }, showView: function(view) { if (!view) return; - this._showScriptOrResource((view.resource || view.script)); + this._showScriptOrResource(view.resource || view.script); }, handleShortcut: function(event) @@ -574,24 +611,10 @@ WebInspector.ScriptsPanel.prototype = { return view.sourceFrame; }, - _sourceViewForScriptOrResource: function(scriptOrResource) - { - if (scriptOrResource instanceof WebInspector.Resource) { - if (!WebInspector.panels.resources) - return null; - return WebInspector.panels.resources.resourceViewForResource(scriptOrResource); - } - if (scriptOrResource instanceof WebInspector.Script) - return this.scriptViewForScript(scriptOrResource); - }, - _sourceFrameForScriptOrResource: function(scriptOrResource) { - if (scriptOrResource instanceof WebInspector.Resource) { - if (!WebInspector.panels.resources) - return null; + if (scriptOrResource instanceof WebInspector.Resource) return WebInspector.panels.resources.sourceFrameForResource(scriptOrResource); - } if (scriptOrResource instanceof WebInspector.Script) return this.sourceFrameForScript(scriptOrResource); }, @@ -610,15 +633,13 @@ WebInspector.ScriptsPanel.prototype = { if (!WebInspector.panels.resources) return null; view = WebInspector.panels.resources.resourceViewForResource(scriptOrResource); - view.headersVisible = false; - - if (scriptOrResource.url in this._breakpointsURLMap) { - var sourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource); - if (sourceFrame && !sourceFrame.breakpoints.length) { - var breakpoints = this._breakpointsURLMap[scriptOrResource.url]; - var breakpointsLength = breakpoints.length; - for (var i = 0; i < breakpointsLength; ++i) - sourceFrame.addBreakpoint(breakpoints[i]); + view.headersVisible = false; + var breakpoints = this.sidebarPanes.breakpoints.breakpoints; + for (var breakpointId in breakpoints) { + var breakpoint = breakpoints[breakpointId]; + if (breakpoint.url === scriptOrResource.url) { + var sourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource); + sourceFrame.addBreakpoint(breakpoint); } } } else if (scriptOrResource instanceof WebInspector.Script) @@ -666,55 +687,50 @@ WebInspector.ScriptsPanel.prototype = { // hasn't been added yet - happens for stepping in evals, // so use the force option to force the script into the menu. if (!option) { - this._addScriptToFilesMenu(scriptOrResource, {force: true}); + this._addScriptToFilesMenu(scriptOrResource, true); option = scriptOrResource.filesSelectOption; } console.assert(option); - } else { - var script = this.scriptForURL(url); - if (script) - option = script.filesSelectOption; - } + } else + option = scriptOrResource.filesSelectOption; if (option) this.filesSelectElement.selectedIndex = option.index; }, - _addScriptToFilesMenu: function(script, options) + _addScriptToFilesMenu: function(script, force) { - var force = options && options.force; - if (!script.sourceURL && !force) return; - if (script.resource && this._scriptsForURLsInFilesSelect[script.sourceURL]) - return; - - this._scriptsForURLsInFilesSelect[script.sourceURL] = script; + if (script.resource) { + if (this._resourceForURLInFilesSelect[script.resource.url]) + return; + this._resourceForURLInFilesSelect[script.resource.url] = script.resource; + } + + var displayName = script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)"); var select = this.filesSelectElement; - var option = document.createElement("option"); - option.representedObject = (script.resource || script); - option.text = (script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)")); + option.representedObject = script.resource || script; + option.url = displayName; + option.startingLine = script.startingLine; + option.text = script.resource || script.startingLine === 1 ? displayName : String.sprintf("%s:%d", displayName, script.startingLine); function optionCompare(a, b) { - var aTitle = a.text.toLowerCase(); - var bTitle = b.text.toLowerCase(); - if (aTitle < bTitle) + if (a.url < b.url) return -1; - else if (aTitle > bTitle) + else if (a.url > b.url) return 1; - var aSourceID = a.representedObject.sourceID; - var bSourceID = b.representedObject.sourceID; - if (aSourceID < bSourceID) + if (typeof a.startingLine !== "number") return -1; - else if (aSourceID > bSourceID) - return 1; - return 0; + if (typeof b.startingLine !== "number") + return -1; + return a.startingLine - b.startingLine; } var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare); @@ -723,7 +739,10 @@ WebInspector.ScriptsPanel.prototype = { else select.insertBefore(option, select.childNodes.item(insertionIndex)); - script.filesSelectOption = option; + if (script.resource) + script.resource.filesSelectOption = option; + else + script.filesSelectOption = option; // Call _showScriptOrResource if the option we just appended ended up being selected. // This will happen for the first item added to the menu. @@ -803,35 +822,32 @@ WebInspector.ScriptsPanel.prototype = { event.preventDefault(); }, - _updatePauseOnExceptionsButton: function() - { - if (InspectorBackend.pauseOnExceptionsState() == WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions) - this.pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions."); - else if (InspectorBackend.pauseOnExceptionsState() == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnAllExceptions) - this.pauseOnExceptionButton.title = WebInspector.UIString("Pause on all exceptions.\nClick to Pause on uncaught exceptions."); - else if (InspectorBackend.pauseOnExceptionsState() == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions) - this.pauseOnExceptionButton.title = WebInspector.UIString("Pause on uncaught exceptions.\nClick to Not pause on exceptions."); - - this.pauseOnExceptionButton.state = InspectorBackend.pauseOnExceptionsState(); - + updatePauseOnExceptionsState: function(pauseOnExceptionsState) + { + if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions) + this._pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions."); + else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnAllExceptions) + this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on all exceptions.\nClick to Pause on uncaught exceptions."); + else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions) + this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on uncaught exceptions.\nClick to Not pause on exceptions."); + + this._pauseOnExceptionButton.state = pauseOnExceptionsState; }, _updateDebuggerButtons: function() { - if (InspectorBackend.debuggerEnabled()) { + if (this._debuggerEnabled) { this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable."); this.enableToggleButton.toggled = true; - this.pauseOnExceptionButton.visible = true; + this._pauseOnExceptionButton.visible = true; this.panelEnablerView.visible = false; } else { this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable."); this.enableToggleButton.toggled = false; - this.pauseOnExceptionButton.visible = false; + this._pauseOnExceptionButton.visible = false; this.panelEnablerView.visible = true; } - this._updatePauseOnExceptionsButton(); - if (this._paused) { this.pauseButton.addStyleClass("paused"); @@ -897,7 +913,7 @@ WebInspector.ScriptsPanel.prototype = { _enableDebugging: function() { - if (InspectorBackend.debuggerEnabled()) + if (this._debuggerEnabled) return; this._toggleDebugging(this.panelEnablerView.alwaysEnabled); }, @@ -908,7 +924,7 @@ WebInspector.ScriptsPanel.prototype = { this._waitingToPause = false; this._stepping = false; - if (InspectorBackend.debuggerEnabled()) + if (this._debuggerEnabled) InspectorBackend.disableDebugger(true); else InspectorBackend.enableDebugger(!!optionalAlways); @@ -916,8 +932,7 @@ WebInspector.ScriptsPanel.prototype = { _togglePauseOnExceptions: function() { - InspectorBackend.setPauseOnExceptionsState((InspectorBackend.pauseOnExceptionsState() + 1) % this.pauseOnExceptionButton.states); - this._updatePauseOnExceptionsButton(); + InspectorBackend.setPauseOnExceptionsState((this._pauseOnExceptionButton.state + 1) % this._pauseOnExceptionButton.states); }, _togglePause: function() @@ -963,7 +978,23 @@ WebInspector.ScriptsPanel.prototype = { this._clearInterface(); InspectorBackend.stepOutOfFunctionInDebugger(); + }, + + _toggleBreakpointsClicked: function() + { + this.toggleBreakpointsButton.toggled = !this.toggleBreakpointsButton.toggled; + if (this.toggleBreakpointsButton.toggled) { + InspectorBackend.activateBreakpoints(); + this.toggleBreakpointsButton.title = WebInspector.UIString("Deactivate all breakpoints."); + document.getElementById("main-panels").removeStyleClass("breakpoints-deactivated"); + } else { + InspectorBackend.deactivateBreakpoints(); + this.toggleBreakpointsButton.title = WebInspector.UIString("Activate all breakpoints."); + document.getElementById("main-panels").addStyleClass("breakpoints-deactivated"); + } } } WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.didEditScriptLine = WebInspector.Callback.processCallback; diff --git a/WebCore/inspector/front-end/Settings.js b/WebCore/inspector/front-end/Settings.js index e6fc0c3..b9b5f75 100644 --- a/WebCore/inspector/front-end/Settings.js +++ b/WebCore/inspector/front-end/Settings.js @@ -38,7 +38,10 @@ var Preferences = { styleRulesExpandedState: {}, showMissingLocalizedStrings: false, samplingCPUProfiler: false, - showColorNicknames: true + showColorNicknames: true, + debuggerAlwaysEnabled: false, + profilerAlwaysEnabled: false, + auditsPanelEnabled: true } WebInspector.populateFrontendSettings = function(settingsString) @@ -68,6 +71,7 @@ WebInspector.Settings.prototype = { this._installSetting("showInheritedComputedStyleProperties", "show-inherited-computed-style-properties", false); this._installSetting("showUserAgentStyles", "show-user-agent-styles", true); this._installSetting("resourceViewTab", "resource-view-tab", "content"); + this._installSetting("consoleHistory", "console-history", []); this.dispatchEventToListeners("loaded"); }, diff --git a/WebCore/inspector/front-end/SourceCSSTokenizer.js b/WebCore/inspector/front-end/SourceCSSTokenizer.js index 599cfb3..f7d7d51 100644 --- a/WebCore/inspector/front-end/SourceCSSTokenizer.js +++ b/WebCore/inspector/front-end/SourceCSSTokenizer.js @@ -1,4 +1,4 @@ -/* Generated by re2c 0.13.5 on Thu Jan 28 20:49:22 2010 */ +/* Generated by re2c 0.13.5 on Thu Feb 25 21:44:55 2010 */ /* * Copyright (C) 2009 Google Inc. All rights reserved. * @@ -99,35 +99,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", @@ -136,15 +136,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(); @@ -183,7 +183,7 @@ WebInspector.SourceCSSTokenizer.prototype = { _isPropertyValue: function() { - return this._parseCondition === this._parseConditions.PROPERTY_VALUE || this._parseCondition === this._parseConditions.AT_RULE; + return this._condition.parseCondition === this._parseConditions.PROPERTY_VALUE || this._condition.parseCondition === this._parseConditions.AT_RULE; }, nextToken: function(cursor) @@ -423,18 +423,18 @@ case 31: case 32: { var token = this._line.substring(cursorOnEnter, cursor); - if (this._parseCondition === this._parseConditions.INITIAL) { + if (this._condition.parseCondition === this._parseConditions.INITIAL) { if (token === "@import" || token === "@media") { this.tokenType = "css-at-rule"; - this._parseCondition = this._parseConditions.AT_RULE; + this._condition.parseCondition = this._parseConditions.AT_RULE; } else if (token.indexOf("@") === 0) this.tokenType = "css-at-rule"; else this.tokenType = "css-selector"; } - else if (this._parseCondition === this._parseConditions.AT_RULE && token in this._mediaTypes) + else if (this._condition.parseCondition === this._parseConditions.AT_RULE && token in this._mediaTypes) this.tokenType = "css-keyword"; - else if (this._parseCondition === this._parseConditions.PROPERTY && token in this._propertyKeywords) + else if (this._condition.parseCondition === this._parseConditions.PROPERTY && token in this._propertyKeywords) this.tokenType = "css-property"; else if (this._isPropertyValue() && token in this._valueKeywords) this.tokenType = "css-keyword"; @@ -647,35 +647,35 @@ case 40: ++cursor; { this.tokenType = null; - if (this._parseCondition === this._parseConditions.PROPERTY) - this._parseCondition = this._parseConditions.PROPERTY_VALUE; + if (this._condition.parseCondition === this._parseConditions.PROPERTY) + this._condition.parseCondition = this._parseConditions.PROPERTY_VALUE; return cursor; } case 42: ++cursor; { this.tokenType = null; - if (this._parseCondition === this._parseConditions.AT_RULE) - this._parseCondition = this._parseConditions.INITIAL; + if (this._condition.parseCondition === this._parseConditions.AT_RULE) + this._condition.parseCondition = this._parseConditions.INITIAL; else - this._parseCondition = this._parseConditions.PROPERTY; + this._condition.parseCondition = this._parseConditions.PROPERTY; return cursor; } case 44: ++cursor; { this.tokenType = null; - if (this._parseCondition === this._parseConditions.AT_RULE) - this._parseCondition = this._parseConditions.INITIAL; + if (this._condition.parseCondition === this._parseConditions.AT_RULE) + this._condition.parseCondition = this._parseConditions.INITIAL; else - this._parseCondition = this._parseConditions.PROPERTY; + this._condition.parseCondition = this._parseConditions.PROPERTY; return cursor; } case 46: ++cursor; { this.tokenType = null; - this._parseCondition = this._parseConditions.INITIAL; + this._condition.parseCondition = this._parseConditions.INITIAL; return cursor; } case 48: diff --git a/WebCore/inspector/front-end/SourceCSSTokenizer.re2js b/WebCore/inspector/front-end/SourceCSSTokenizer.re2js index ac22bd4..6ba9f60 100644 --- a/WebCore/inspector/front-end/SourceCSSTokenizer.re2js +++ b/WebCore/inspector/front-end/SourceCSSTokenizer.re2js @@ -182,7 +182,7 @@ WebInspector.SourceCSSTokenizer.prototype = { _isPropertyValue: function() { - return this._parseCondition === this._parseConditions.PROPERTY_VALUE || this._parseCondition === this._parseConditions.AT_RULE; + return this._condition.parseCondition === this._parseConditions.PROPERTY_VALUE || this._condition.parseCondition === this._parseConditions.AT_RULE; }, nextToken: function(cursor) @@ -244,35 +244,35 @@ WebInspector.SourceCSSTokenizer.prototype = { <INITIAL> OpenCurlyBracket { this.tokenType = null; - if (this._parseCondition === this._parseConditions.AT_RULE) - this._parseCondition = this._parseConditions.INITIAL; + if (this._condition.parseCondition === this._parseConditions.AT_RULE) + this._condition.parseCondition = this._parseConditions.INITIAL; else - this._parseCondition = this._parseConditions.PROPERTY; + this._condition.parseCondition = this._parseConditions.PROPERTY; return cursor; } <INITIAL> CloseCurlyBracket { this.tokenType = null; - this._parseCondition = this._parseConditions.INITIAL; + this._condition.parseCondition = this._parseConditions.INITIAL; return cursor; } <INITIAL> Colon { this.tokenType = null; - if (this._parseCondition === this._parseConditions.PROPERTY) - this._parseCondition = this._parseConditions.PROPERTY_VALUE; + if (this._condition.parseCondition === this._parseConditions.PROPERTY) + this._condition.parseCondition = this._parseConditions.PROPERTY_VALUE; return cursor; } <INITIAL> Semicolon { this.tokenType = null; - if (this._parseCondition === this._parseConditions.AT_RULE) - this._parseCondition = this._parseConditions.INITIAL; + if (this._condition.parseCondition === this._parseConditions.AT_RULE) + this._condition.parseCondition = this._parseConditions.INITIAL; else - this._parseCondition = this._parseConditions.PROPERTY; + this._condition.parseCondition = this._parseConditions.PROPERTY; return cursor; } @@ -288,18 +288,18 @@ WebInspector.SourceCSSTokenizer.prototype = { <INITIAL> Identifier { var token = this._line.substring(cursorOnEnter, cursor); - if (this._parseCondition === this._parseConditions.INITIAL) { + if (this._condition.parseCondition === this._parseConditions.INITIAL) { if (token === "@import" || token === "@media") { this.tokenType = "css-at-rule"; - this._parseCondition = this._parseConditions.AT_RULE; + this._condition.parseCondition = this._parseConditions.AT_RULE; } else if (token.indexOf("@") === 0) this.tokenType = "css-at-rule"; else this.tokenType = "css-selector"; } - else if (this._parseCondition === this._parseConditions.AT_RULE && token in this._mediaTypes) + else if (this._condition.parseCondition === this._parseConditions.AT_RULE && token in this._mediaTypes) this.tokenType = "css-keyword"; - else if (this._parseCondition === this._parseConditions.PROPERTY && token in this._propertyKeywords) + else if (this._condition.parseCondition === this._parseConditions.PROPERTY && token in this._propertyKeywords) this.tokenType = "css-property"; else if (this._isPropertyValue() && token in this._valueKeywords) this.tokenType = "css-keyword"; diff --git a/WebCore/inspector/front-end/SourceFrame.js b/WebCore/inspector/front-end/SourceFrame.js index 799628e..99280fc 100644 --- a/WebCore/inspector/front-end/SourceFrame.js +++ b/WebCore/inspector/front-end/SourceFrame.js @@ -28,7 +28,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -WebInspector.SourceFrame = function(parentElement, addBreakpointDelegate, removeBreakpointDelegate) +WebInspector.SourceFrame = function(parentElement, addBreakpointDelegate, removeBreakpointDelegate, editDelegate) { this._parentElement = parentElement; @@ -45,6 +45,7 @@ WebInspector.SourceFrame = function(parentElement, addBreakpointDelegate, remove this._addBreakpointDelegate = addBreakpointDelegate; this._removeBreakpointDelegate = removeBreakpointDelegate; + this._editDelegate = editDelegate; this._popoverObjectGroup = "popover"; } @@ -143,6 +144,11 @@ WebInspector.SourceFrame.prototype = { this._createViewerIfNeeded(); }, + updateContent: function(content) + { + this._textModel.setText(null, content); + }, + highlightLine: function(line) { if (this._textViewer) @@ -192,6 +198,8 @@ WebInspector.SourceFrame.prototype = { delete this._lineToHighlight; } this._textViewer.endUpdates(); + if (this._editDelegate) + this._textViewer.editCallback = this._editDelegate; }, findSearchMatches: function(query) @@ -288,8 +296,6 @@ WebInspector.SourceFrame.prototype = { if (!this._executionLine) return; - this._drawProgramCounterImageIfNeeded(); - if (this._executionLine < this._textModel.linesCount) this._textViewer.addDecoration(this._executionLine - 1, "webkit-execution-line"); }, @@ -369,7 +375,6 @@ WebInspector.SourceFrame.prototype = { this._textModel.setAttribute(lineNumber, "breakpoint", breakpoint); breakpoint.sourceText = this._textModel.line(breakpoint.line - 1); - this._drawBreakpointImagesIfNeeded(); this._textViewer.beginUpdates(); this._textViewer.addDecoration(lineNumber, "webkit-breakpoint"); @@ -393,9 +398,10 @@ WebInspector.SourceFrame.prototype = { _contextMenu: function(event) { - if (event.target.className !== "webkit-line-number") + var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number"); + if (!target) return; - var row = event.target.parentElement; + var row = target.parentElement; var lineNumber = row.lineNumber; var contextMenu = new WebInspector.ContextMenu(); @@ -435,18 +441,22 @@ WebInspector.SourceFrame.prototype = { { this._resetHoverTimer(); this._hidePopup(); - if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) + if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey) return; - if (event.target.className !== "webkit-line-number") + var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number"); + if (!target) return; - var row = event.target.parentElement; + var row = target.parentElement; var lineNumber = row.lineNumber; var breakpoint = this._textModel.getAttribute(lineNumber, "breakpoint"); - if (breakpoint) - this._removeBreakpointDelegate(breakpoint); - else if (this._addBreakpointDelegate) + if (breakpoint) { + if (event.shiftKey) + breakpoint.enabled = !breakpoint.enabled; + else + this._removeBreakpointDelegate(breakpoint); + } else this._addBreakpointDelegate(lineNumber + 1); event.preventDefault(); }, @@ -454,11 +464,10 @@ WebInspector.SourceFrame.prototype = { _mouseMove: function(event) { // Pretend that nothing has happened. - if (this._hoverElement === event.target) + if (this._hoverElement === event.target || event.target.hasStyleClass("source-frame-eval-expression")) return; this._resetHoverTimer(); - // User has 500ms to reach the popup. if (this._popup) { var self = this; @@ -483,7 +492,7 @@ WebInspector.SourceFrame.prototype = { } else if (!this._hoverElement.hasStyleClass("webkit-javascript-ident")) return; - const toolTipDelay = 1500; + const toolTipDelay = this._popup ? 600 : 1000; this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay); }, @@ -497,11 +506,22 @@ WebInspector.SourceFrame.prototype = { _hidePopup: function() { - if (this._popup) { - this._popup.hide(); - delete this._popup; - InspectorBackend.releaseWrapperObjectGroup(0, this._popoverObjectGroup); + if (!this._popup) + return; + + // Replace higlight element with its contents inplace. + var parentElement = this._popup.highlightElement.parentElement; + var child = this._popup.highlightElement.firstChild; + while (child) { + var nextSibling = child.nextSibling; + parentElement.insertBefore(child, this._popup.highlightElement); + child = nextSibling; } + parentElement.removeChild(this._popup.highlightElement); + + this._popup.hide(); + delete this._popup; + InspectorBackend.releaseWrapperObjectGroup(0, this._popoverObjectGroup); }, _mouseHover: function(element) @@ -515,27 +535,26 @@ WebInspector.SourceFrame.prototype = { if (!lineRow) return; - // Find text offset of the hovered node (iterate over text nodes until we hit ours). - var offset = 0; - var node = lineRow.lastChild.traverseNextTextNode(lineRow.lastChild); - while (node && node !== element.firstChild) { - offset += node.nodeValue.length; - node = node.traverseNextTextNode(lineRow.lastChild); + // Collect tokens belonging to evaluated exression. + var tokens = [ element ]; + var token = element.previousSibling; + while (token && (token.className === "webkit-javascript-ident" || token.className === "webkit-javascript-keyword" || token.textContent.trim() === ".")) { + tokens.push(token); + token = token.previousSibling; } + tokens.reverse(); - // Imagine that the line is "foo(A.B.C.D)" and we hit C. Following code goes through following steps: - // "foo(A.B.C" -> "C.B.A(oof" -> "C.B.A" -> "A.B.C" (target eval expression). - var lineNumber = lineRow.lineNumber; - var prefix = this._textModel.line(lineNumber).substring(0, offset + element.textContent.length); - var reversedPrefix = prefix.split("").reverse().join(""); - var match = /[a-zA-Z\x80-\xFF\_$0-9.]+/.exec(reversedPrefix); - if (!match) - return; - var expression = match[0].split("").reverse().join(""); - this._showPopup(element, expression); + // Wrap them with highlight element. + var parentElement = element.parentElement; + var nextElement = element.nextSibling; + var container = document.createElement("span"); + for (var i = 0; i < tokens.length; ++i) + container.appendChild(tokens[i]); + parentElement.insertBefore(container, nextElement); + this._showPopup(container); }, - _showPopup: function(element, expression) + _showPopup: function(element) { function killHidePopupTimer() { @@ -549,20 +568,6 @@ WebInspector.SourceFrame.prototype = { } } - function showTextPopup(text) - { - if (!WebInspector.panels.scripts.paused) - return; - - var popupContentElement = document.createElement("span"); - popupContentElement.className = "monospace"; - popupContentElement.style.whiteSpace = "pre"; - popupContentElement.textContent = text; - this._popup = new WebInspector.Popover(popupContentElement); - this._popup.show(element); - popupContentElement.addEventListener("mousemove", killHidePopupTimer.bind(this), true); - } - function showObjectPopup(result) { if (!WebInspector.panels.scripts.paused) @@ -595,6 +600,8 @@ WebInspector.SourceFrame.prototype = { const popupHeight = 250; this._popup.show(element, popupWidth, popupHeight); } + this._popup.highlightElement = element; + this._popup.highlightElement.addStyleClass("source-frame-eval-expression"); popupContentElement.addEventListener("mousemove", killHidePopupTimer.bind(this), true); } @@ -606,7 +613,7 @@ WebInspector.SourceFrame.prototype = { return; showObjectPopup.call(this, result); } - WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, this._popoverObjectGroup, evaluateCallback.bind(this)); + WebInspector.panels.scripts.evaluateInSelectedCallFrame(element.textContent, false, this._popoverObjectGroup, evaluateCallback.bind(this)); }, _editBreakpointCondition: function(breakpoint) @@ -695,134 +702,6 @@ WebInspector.SourceFrame.prototype = { { if (this._textViewer) this._textViewer.resize(); - }, - - _drawProgramCounterInContext: function(ctx, glow) - { - if (glow) - ctx.save(); - - 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)"; - - if (glow) { - ctx.shadowBlur = 4; - ctx.shadowColor = "rgb(255, 255, 255)"; - ctx.shadowOffsetX = -1; - ctx.shadowOffsetY = 0; - } - - ctx.fill(); - ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels. - - if (glow) - ctx.restore(); - }, - - _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); - - delete this._needsProgramCounterImage; - }, - - _drawBreakpointImagesIfNeeded: function(conditional) - { - 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(); - - if (!disabled) - return; - - ctx.save(); - ctx.globalCompositeOperation = "destination-out"; - ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; - ctx.fillRect(0, 0, 26, 11); - ctx.restore(); - } - - - // Unconditional breakpoints. - - var ctx = document.getCSSCanvasContext("2d", "breakpoint", 26, 11); - ctx.clearRect(0, 0, 26, 11); - drawBreakpoint(ctx); - - 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); - - var ctx = document.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11); - ctx.clearRect(0, 0, 26, 11); - drawBreakpoint(ctx, 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); - - - // Conditional breakpoints. - - var ctx = document.getCSSCanvasContext("2d", "breakpoint-conditional", 26, 11); - ctx.clearRect(0, 0, 26, 11); - drawBreakpoint(ctx, false, true); - - 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; } } diff --git a/WebCore/inspector/front-end/SourceHTMLTokenizer.js b/WebCore/inspector/front-end/SourceHTMLTokenizer.js index 3c9bd8c..cfbc44f 100644 --- a/WebCore/inspector/front-end/SourceHTMLTokenizer.js +++ b/WebCore/inspector/front-end/SourceHTMLTokenizer.js @@ -1,4 +1,4 @@ -/* Generated by re2c 0.13.5 on Mon Feb 15 19:30:21 2010 */ +/* Generated by re2c 0.13.5 on Thu Feb 25 21:44:55 2010 */ /* * Copyright (C) 2009 Google Inc. All rights reserved. * @@ -71,31 +71,43 @@ WebInspector.SourceHTMLTokenizer = function() }; this.initialCondition = { lexCondition: this._lexConditions.INITIAL, parseCondition: this._parseConditions.INITIAL }; + this.condition = this.initialCondition; } WebInspector.SourceHTMLTokenizer.prototype = { + set line(line) { + if (this._internalJavaScriptTokenizer) { + var match = /<\/script/i.exec(line); + if (match) { + this._internalJavaScriptTokenizer.line = line.substring(0, match.index); + } else + this._internalJavaScriptTokenizer.line = line; + } + this._line = line; + }, + _isExpectingAttribute: function() { - return this._parseCondition & this._parseConditions.ATTRIBUTE; + return this._condition.parseCondition & this._parseConditions.ATTRIBUTE; }, _isExpectingAttributeValue: function() { - return this._parseCondition & this._parseConditions.ATTRIBUTE_VALUE; + return this._condition.parseCondition & this._parseConditions.ATTRIBUTE_VALUE; }, _setExpectingAttribute: function() { if (this._isExpectingAttributeValue()) - this._parseCondition ^= this._parseConditions.ATTRIBUTE_VALUE; - this._parseCondition |= this._parseConditions.ATTRIBUTE; + this._condition.parseCondition ^= this._parseConditions.ATTRIBUTE_VALUE; + this._condition.parseCondition |= this._parseConditions.ATTRIBUTE; }, _setExpectingAttributeValue: function() { if (this._isExpectingAttribute()) - this._parseCondition ^= this._parseConditions.ATTRIBUTE; - this._parseCondition |= this._parseConditions.ATTRIBUTE_VALUE; + this._condition.parseCondition ^= this._parseConditions.ATTRIBUTE; + this._condition.parseCondition |= this._parseConditions.ATTRIBUTE_VALUE; }, _stringToken: function(cursor, stringEnds) @@ -112,8 +124,8 @@ WebInspector.SourceHTMLTokenizer.prototype = { _attrValueTokenType: function() { - if (this._parseCondition & this._parseConditions.LINKIFY) { - if (this._parseCondition & this._parseConditions.A_NODE) + if (this._condition.parseCondition & this._parseConditions.LINKIFY) { + if (this._condition.parseCondition & this._parseConditions.A_NODE) return "html-external-link"; return "html-resource-link"; } @@ -122,6 +134,20 @@ WebInspector.SourceHTMLTokenizer.prototype = { nextToken: function(cursor) { + if (this._internalJavaScriptTokenizer) { + // Re-set line to force </script> detection first. + this.line = this._line; + if (cursor !== this._internalJavaScriptTokenizer._line.length) { + // Tokenizer is stateless, so restore its condition before tokenizing and save it after. + this._internalJavaScriptTokenizer.condition = this._condition.internalJavaScriptTokenizerCondition; + var result = this._internalJavaScriptTokenizer.nextToken(cursor); + this.tokenType = this._internalJavaScriptTokenizer.tokenType; + this._condition.internalJavaScriptTokenizerCondition = this._internalJavaScriptTokenizer.condition; + return result; + } else if (cursor !== this._line.length) + delete this._internalJavaScriptTokenizer; + } + var cursorOnEnter = cursor; var gotoCase = 1; while (1) { @@ -305,14 +331,14 @@ case 39: case 40: this.setLexCondition(this._lexConditions.TAG); { - if (this._parseCondition & this._parseConditions.SCRIPT) { + if (this._condition.parseCondition & this._parseConditions.SCRIPT) { // Do not tokenize script tag contents, keep lexer state although processing "<". this.setLexCondition(this._lexConditions.INITIAL); this.tokenType = null; return cursor; } - this._parseCondition = this._parseConditions.INITIAL; + this._condition.parseCondition = this._parseConditions.INITIAL; this.tokenType = "html-tag"; return cursor; } @@ -413,14 +439,14 @@ case 66: ++cursor; this.setLexCondition(this._lexConditions.TAG); { - if (this._parseCondition & this._parseConditions.SCRIPT) { + if (this._condition.parseCondition & this._parseConditions.SCRIPT) { // Do not tokenize script tag contents, keep lexer state although processing "<". this.setLexCondition(this._lexConditions.INITIAL); this.tokenType = null; return cursor; } this.tokenType = "html-tag"; - this._parseCondition = this._parseConditions.SCRIPT; + this._condition.parseCondition = this._parseConditions.SCRIPT; this._setExpectingAttribute(); return cursor; } @@ -449,7 +475,7 @@ case 73: this.setLexCondition(this._lexConditions.TAG); { this.tokenType = "html-tag"; - this._parseCondition = this._parseConditions.INITIAL; + this._condition.parseCondition = this._parseConditions.INITIAL; return cursor; } /* *********************************** */ @@ -527,26 +553,26 @@ case this.case_TAG: { gotoCase = 109; continue; }; case 89: { - if (this._parseCondition === this._parseConditions.SCRIPT) { + if (this._condition.parseCondition === this._parseConditions.SCRIPT) { // Fall through if expecting attributes. this.tokenType = null; return cursor; } - if (this._parseCondition === this._parseConditions.INITIAL) { + if (this._condition.parseCondition === this._parseConditions.INITIAL) { this.tokenType = "html-tag"; this._setExpectingAttribute(); var token = this._line.substring(cursorOnEnter, cursor); if (token === "a") - this._parseCondition |= this._parseConditions.A_NODE; - else if (this._parseCondition & this._parseConditions.A_NODE) - this._parseCondition ^= this._parseConditions.A_NODE; + this._condition.parseCondition |= this._parseConditions.A_NODE; + else if (this._condition.parseCondition & this._parseConditions.A_NODE) + this._condition.parseCondition ^= this._parseConditions.A_NODE; } else if (this._isExpectingAttribute()) { var token = this._line.substring(cursorOnEnter, cursor); if (token === "href" || token === "src") - this._parseCondition |= this._parseConditions.LINKIFY; - else if (this._parseCondition |= this._parseConditions.LINKIFY) - this._parseCondition ^= this._parseConditions.LINKIFY; + this._condition.parseCondition |= this._parseConditions.LINKIFY; + else if (this._condition.parseCondition |= this._parseConditions.LINKIFY) + this._condition.parseCondition ^= this._parseConditions.LINKIFY; this.tokenType = "html-attribute-name"; } else if (this._isExpectingAttributeValue()) this.tokenType = this._attrValueTokenType(); @@ -577,14 +603,17 @@ case 96: ++cursor; this.setLexCondition(this._lexConditions.INITIAL); { - if (this._parseCondition & this._parseConditions.SCRIPT) { + this.tokenType = "html-tag"; + if (this._condition.parseCondition & this._parseConditions.SCRIPT) { + if (!this._internalJavaScriptTokenizer) { + this._internalJavaScriptTokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/javascript"); + this._condition.internalJavaScriptTokenizerCondition = this._internalJavaScriptTokenizer.initialCondition; + } // Do not tokenize script tag contents. - this.tokenType = null; return cursor; } - this._parseCondition = this._parseConditions.INITIAL; - this.tokenType = "html-tag"; + this._condition.parseCondition = this._parseConditions.INITIAL; return cursor; } case 98: diff --git a/WebCore/inspector/front-end/SourceHTMLTokenizer.re2js b/WebCore/inspector/front-end/SourceHTMLTokenizer.re2js index cfa8834..44c62b3 100644 --- a/WebCore/inspector/front-end/SourceHTMLTokenizer.re2js +++ b/WebCore/inspector/front-end/SourceHTMLTokenizer.re2js @@ -70,31 +70,43 @@ WebInspector.SourceHTMLTokenizer = function() }; this.initialCondition = { lexCondition: this._lexConditions.INITIAL, parseCondition: this._parseConditions.INITIAL }; + this.condition = this.initialCondition; } WebInspector.SourceHTMLTokenizer.prototype = { + set line(line) { + if (this._internalJavaScriptTokenizer) { + var match = /<\/script/i.exec(line); + if (match) { + this._internalJavaScriptTokenizer.line = line.substring(0, match.index); + } else + this._internalJavaScriptTokenizer.line = line; + } + this._line = line; + }, + _isExpectingAttribute: function() { - return this._parseCondition & this._parseConditions.ATTRIBUTE; + return this._condition.parseCondition & this._parseConditions.ATTRIBUTE; }, _isExpectingAttributeValue: function() { - return this._parseCondition & this._parseConditions.ATTRIBUTE_VALUE; + return this._condition.parseCondition & this._parseConditions.ATTRIBUTE_VALUE; }, _setExpectingAttribute: function() { if (this._isExpectingAttributeValue()) - this._parseCondition ^= this._parseConditions.ATTRIBUTE_VALUE; - this._parseCondition |= this._parseConditions.ATTRIBUTE; + this._condition.parseCondition ^= this._parseConditions.ATTRIBUTE_VALUE; + this._condition.parseCondition |= this._parseConditions.ATTRIBUTE; }, _setExpectingAttributeValue: function() { if (this._isExpectingAttribute()) - this._parseCondition ^= this._parseConditions.ATTRIBUTE; - this._parseCondition |= this._parseConditions.ATTRIBUTE_VALUE; + this._condition.parseCondition ^= this._parseConditions.ATTRIBUTE; + this._condition.parseCondition |= this._parseConditions.ATTRIBUTE_VALUE; }, _stringToken: function(cursor, stringEnds) @@ -111,8 +123,8 @@ WebInspector.SourceHTMLTokenizer.prototype = { _attrValueTokenType: function() { - if (this._parseCondition & this._parseConditions.LINKIFY) { - if (this._parseCondition & this._parseConditions.A_NODE) + if (this._condition.parseCondition & this._parseConditions.LINKIFY) { + if (this._condition.parseCondition & this._parseConditions.A_NODE) return "html-external-link"; return "html-resource-link"; } @@ -121,6 +133,20 @@ WebInspector.SourceHTMLTokenizer.prototype = { nextToken: function(cursor) { + if (this._internalJavaScriptTokenizer) { + // Re-set line to force </script> detection first. + this.line = this._line; + if (cursor !== this._internalJavaScriptTokenizer._line.length) { + // Tokenizer is stateless, so restore its condition before tokenizing and save it after. + this._internalJavaScriptTokenizer.condition = this._condition.internalJavaScriptTokenizerCondition; + var result = this._internalJavaScriptTokenizer.nextToken(cursor); + this.tokenType = this._internalJavaScriptTokenizer.tokenType; + this._condition.internalJavaScriptTokenizerCondition = this._internalJavaScriptTokenizer.condition; + return result; + } else if (cursor !== this._line.length) + delete this._internalJavaScriptTokenizer; + } + var cursorOnEnter = cursor; var gotoCase = 1; while (1) { @@ -174,14 +200,14 @@ WebInspector.SourceHTMLTokenizer.prototype = { <INITIAL> ScriptStart => TAG { - if (this._parseCondition & this._parseConditions.SCRIPT) { + if (this._condition.parseCondition & this._parseConditions.SCRIPT) { // Do not tokenize script tag contents, keep lexer state although processing "<". this.setLexCondition(this._lexConditions.INITIAL); this.tokenType = null; return cursor; } this.tokenType = "html-tag"; - this._parseCondition = this._parseConditions.SCRIPT; + this._condition.parseCondition = this._parseConditions.SCRIPT; this._setExpectingAttribute(); return cursor; } @@ -189,34 +215,37 @@ WebInspector.SourceHTMLTokenizer.prototype = { <INITIAL> ScriptEnd => TAG { this.tokenType = "html-tag"; - this._parseCondition = this._parseConditions.INITIAL; + this._condition.parseCondition = this._parseConditions.INITIAL; return cursor; } <INITIAL> LT => TAG { - if (this._parseCondition & this._parseConditions.SCRIPT) { + if (this._condition.parseCondition & this._parseConditions.SCRIPT) { // Do not tokenize script tag contents, keep lexer state although processing "<". this.setLexCondition(this._lexConditions.INITIAL); this.tokenType = null; return cursor; } - this._parseCondition = this._parseConditions.INITIAL; + this._condition.parseCondition = this._parseConditions.INITIAL; this.tokenType = "html-tag"; return cursor; } - + <TAG> GT => INITIAL { - if (this._parseCondition & this._parseConditions.SCRIPT) { + this.tokenType = "html-tag"; + if (this._condition.parseCondition & this._parseConditions.SCRIPT) { + if (!this._internalJavaScriptTokenizer) { + this._internalJavaScriptTokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/javascript"); + this._condition.internalJavaScriptTokenizerCondition = this._internalJavaScriptTokenizer.initialCondition; + } // Do not tokenize script tag contents. - this.tokenType = null; return cursor; } - this._parseCondition = this._parseConditions.INITIAL; - this.tokenType = "html-tag"; + this._condition.parseCondition = this._parseConditions.INITIAL; return cursor; } @@ -238,26 +267,26 @@ WebInspector.SourceHTMLTokenizer.prototype = { <TAG> Identifier { - if (this._parseCondition === this._parseConditions.SCRIPT) { + if (this._condition.parseCondition === this._parseConditions.SCRIPT) { // Fall through if expecting attributes. this.tokenType = null; return cursor; } - if (this._parseCondition === this._parseConditions.INITIAL) { + if (this._condition.parseCondition === this._parseConditions.INITIAL) { this.tokenType = "html-tag"; this._setExpectingAttribute(); var token = this._line.substring(cursorOnEnter, cursor); if (token === "a") - this._parseCondition |= this._parseConditions.A_NODE; - else if (this._parseCondition & this._parseConditions.A_NODE) - this._parseCondition ^= this._parseConditions.A_NODE; + this._condition.parseCondition |= this._parseConditions.A_NODE; + else if (this._condition.parseCondition & this._parseConditions.A_NODE) + this._condition.parseCondition ^= this._parseConditions.A_NODE; } else if (this._isExpectingAttribute()) { var token = this._line.substring(cursorOnEnter, cursor); if (token === "href" || token === "src") - this._parseCondition |= this._parseConditions.LINKIFY; - else if (this._parseCondition |= this._parseConditions.LINKIFY) - this._parseCondition ^= this._parseConditions.LINKIFY; + this._condition.parseCondition |= this._parseConditions.LINKIFY; + else if (this._condition.parseCondition |= this._parseConditions.LINKIFY) + this._condition.parseCondition ^= this._parseConditions.LINKIFY; this.tokenType = "html-attribute-name"; } else if (this._isExpectingAttributeValue()) this.tokenType = this._attrValueTokenType(); diff --git a/WebCore/inspector/front-end/SourceJavaScriptTokenizer.js b/WebCore/inspector/front-end/SourceJavaScriptTokenizer.js index 75abeee..fbd44d7 100644 --- a/WebCore/inspector/front-end/SourceJavaScriptTokenizer.js +++ b/WebCore/inspector/front-end/SourceJavaScriptTokenizer.js @@ -1,4 +1,4 @@ -/* Generated by re2c 0.13.5 on Thu Jan 28 20:49:23 2010 */ +/* Generated by re2c 0.13.5 on Thu Feb 25 21:44:55 2010 */ /* * Copyright (C) 2009 Google Inc. All rights reserved. * @@ -69,6 +69,7 @@ WebInspector.SourceJavaScriptTokenizer = function() this.case_REGEX = 1005; this.initialCondition = { lexCondition: this._lexConditions.NODIV } + this.condition = this.initialCondition; } WebInspector.SourceJavaScriptTokenizer.prototype = { diff --git a/WebCore/inspector/front-end/SourceJavaScriptTokenizer.re2js b/WebCore/inspector/front-end/SourceJavaScriptTokenizer.re2js index 053c82f..ae71efe 100644 --- a/WebCore/inspector/front-end/SourceJavaScriptTokenizer.re2js +++ b/WebCore/inspector/front-end/SourceJavaScriptTokenizer.re2js @@ -68,6 +68,7 @@ WebInspector.SourceJavaScriptTokenizer = function() this.case_REGEX = 1005; this.initialCondition = { lexCondition: this._lexConditions.NODIV } + this.condition = this.initialCondition; } WebInspector.SourceJavaScriptTokenizer.prototype = { diff --git a/WebCore/inspector/front-end/SourceTokenizer.js b/WebCore/inspector/front-end/SourceTokenizer.js index d498028..d30744c 100644 --- a/WebCore/inspector/front-end/SourceTokenizer.js +++ b/WebCore/inspector/front-end/SourceTokenizer.js @@ -40,28 +40,27 @@ WebInspector.SourceTokenizer.prototype = { set condition(condition) { - this._lexCondition = condition.lexCondition; - this._parseCondition = condition.parseCondition; + this._condition = condition; }, get condition() { - return { lexCondition: this._lexCondition, parseCondition: this._parseCondition }; + return this._condition; }, - hasCondition: function(condition) + get subTokenizer() { - return this._lexCondition === condition.lexCondition && this._parseCondition === condition.parseCondition; + return this._condition.subTokenizer; }, getLexCondition: function() { - return this._lexCondition; + return this.condition.lexCondition; }, setLexCondition: function(lexCondition) { - this._lexCondition = lexCondition; + this.condition.lexCondition = lexCondition; }, _charAt: function(cursor) @@ -76,9 +75,7 @@ WebInspector.SourceTokenizer.Registry = function() { this._tokenizerConstructors = { "text/css": "SourceCSSTokenizer", "text/html": "SourceHTMLTokenizer", - "text/javascript": "SourceJavaScriptTokenizer", - "application/javascript": "SourceJavaScriptTokenizer", - "application/x-javascript": "SourceJavaScriptTokenizer" + "text/javascript": "SourceJavaScriptTokenizer" }; } diff --git a/WebCore/inspector/front-end/SourceView.js b/WebCore/inspector/front-end/SourceView.js index b401c12..9fbd161 100644 --- a/WebCore/inspector/front-end/SourceView.js +++ b/WebCore/inspector/front-end/SourceView.js @@ -37,6 +37,14 @@ WebInspector.SourceView = function(resource) this._frameNeedsSetup = true; } +// This is a map from resource.type to mime types +// found in WebInspector.SourceTokenizer.Registry. +WebInspector.SourceView.DefaultMIMETypeForResourceType = { + 0: "text/html", + 1: "text/css", + 4: "text/javascript" +} + WebInspector.SourceView.prototype = { show: function(parentElement) { @@ -76,10 +84,16 @@ WebInspector.SourceView.prototype = { _contentLoaded: function(content) { - this.sourceFrame.setContent(this.resource.mimeType, content, this.resource.url); + var mimeType = this._canonicalMimeType(this.resource); + this.sourceFrame.setContent(mimeType, content, this.resource.url); this._sourceFrameSetupFinished(); }, + _canonicalMimeType: function(resource) + { + return WebInspector.SourceView.DefaultMIMETypeForResourceType[resource.type] || resource.mimeType; + }, + _resourceLoadingFinished: function(event) { this._frameNeedsSetup = true; diff --git a/WebCore/inspector/front-end/StylesSidebarPane.js b/WebCore/inspector/front-end/StylesSidebarPane.js index 265e488..eb5e012 100644 --- a/WebCore/inspector/front-end/StylesSidebarPane.js +++ b/WebCore/inspector/front-end/StylesSidebarPane.js @@ -65,6 +65,33 @@ WebInspector.StylesSidebarPane = function() this.titleElement.appendChild(this.settingsSelectElement); } +// Taken from http://www.w3.org/TR/CSS21/propidx.html. +WebInspector.StylesSidebarPane.InheritedProperties = [ + "azimuth", "border-collapse", "border-spacing", "caption-side", "color", "cursor", "direction", "elevation", + "empty-cells", "font-family", "font-size", "font-style", "font-variant", "font-weight", "font", "letter-spacing", + "line-height", "list-style-image", "list-style-position", "list-style-type", "list-style", "orphans", "pitch-range", + "pitch", "quotes", "richness", "speak-header", "speak-numeral", "speak-punctuation", "speak", "speech-rate", "stress", + "text-align", "text-indent", "text-transform", "visibility", "voice-family", "volume", "white-space", "widows", "word-spacing" +].keySet(); + +// Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes. +// First item is empty due to its artificial NOPSEUDO nature in the enum. +// FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at +// runtime. +WebInspector.StylesSidebarPane.PseudoIdNames = [ + "", "first-line", "first-letter", "before", "after", "selection", "", "-webkit-scrollbar", "-webkit-file-upload-button", + "-webkit-input-placeholder", "-webkit-slider-thumb", "-webkit-search-cancel-button", "-webkit-search-decoration", + "-webkit-search-results-decoration", "-webkit-search-results-button", "-webkit-media-controls-panel", + "-webkit-media-controls-play-button", "-webkit-media-controls-mute-button", "-webkit-media-controls-timeline", + "-webkit-media-controls-timeline-container", "-webkit-media-controls-volume-slider", + "-webkit-media-controls-volume-slider-container", "-webkit-media-controls-current-time-display", + "-webkit-media-controls-time-remaining-display", "-webkit-media-controls-seek-back-button", "-webkit-media-controls-seek-forward-button", + "-webkit-media-controls-fullscreen-button", "-webkit-media-controls-rewind-button", "-webkit-media-controls-return-to-realtime-button", + "-webkit-media-controls-toggle-closed-captions-button", "-webkit-media-controls-status-display", "-webkit-scrollbar-thumb", + "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-corner", + "-webkit-resizer", "-webkit-input-list-button", "-webkit-inner-spin-button", "-webkit-outer-spin-button" +]; + WebInspector.StylesSidebarPane.prototype = { _settingsLoaded: function() { @@ -102,76 +129,173 @@ WebInspector.StylesSidebarPane.prototype = { if (!node) { body.removeChildren(); - this.sections = []; + this.sections = {}; return; } - var self = this; - function callback(styles) + function getStylesCallback(styles) { - if (!styles) - return; - node._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules); - self._update(refresh, body, node, editedSection, forceUpdate); + if (styles) + this._rebuildUpdate(node, styles); } - InjectedScriptAccess.get(node.injectedScriptId).getStyles(node.id, !WebInspector.settings.showUserAgentStyles, callback); + function getComputedStyleCallback(computedStyle) + { + if (computedStyle) + this._refreshUpdate(node, computedStyle, editedSection); + }; + + if (refresh) + InspectorBackend.getComputedStyle(WebInspector.Callback.wrap(getComputedStyleCallback.bind(this)), node.id); + else + InspectorBackend.getStyles(WebInspector.Callback.wrap(getStylesCallback.bind(this)), node.id, !WebInspector.settings.showUserAgentStyles); }, - _update: function(refresh, body, node, editedSection, forceUpdate) + _refreshUpdate: function(node, computedStyle, editedSection) { - if (!refresh) { - body.removeChildren(); - this.sections = []; + for (var pseudoId in this.sections) { + var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle); + var usedProperties = {}; + var disabledComputedProperties = {}; + this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties); + this._refreshSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, editedSection); } + }, - var styleRules = []; + _rebuildUpdate: function(node, styles) + { + this.bodyElement.removeChildren(); + var styleRules = this._rebuildStyleRules(node, styles); + var usedProperties = {}; + var disabledComputedProperties = {}; + this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties); + this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, 0); - if (refresh) { - for (var i = 0; i < this.sections.length; ++i) { - var section = this.sections[i]; - if (section instanceof WebInspector.BlankStylePropertiesSection) - continue; - if (section.computedStyle) - section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node); - var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule }; - styleRules.push(styleRule); - } - } else { - var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); - styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false }); - - var nodeName = node.nodeName.toLowerCase(); - for (var i = 0; i < node.attributes.length; ++i) { - var attr = node.attributes[i]; - if (attr.style) { - var attrStyle = { style: attr.style, editable: false }; - attrStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", attr.name); - attrStyle.selectorText = nodeName + "[" + attr.name; - if (attr.value.length) - attrStyle.selectorText += "=" + attr.value; - attrStyle.selectorText += "]"; - styleRules.push(attrStyle); - } + for (var i = 0; i < styles.pseudoElements.length; ++i) { + var pseudoElementCSSRules = styles.pseudoElements[i]; + + styleRules = []; + var pseudoId = pseudoElementCSSRules.pseudoId; + + var entry = { isStyleSeparator: true, pseudoId: pseudoId }; + styleRules.push(entry); + + // Add rules in reverse order to match the cascade order. + for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) { + var rule = WebInspector.CSSStyleDeclaration.parseRule(pseudoElementCSSRules.rules[j]); + styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule }); } + usedProperties = {}; + disabledComputedProperties = {}; + this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties); + this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, pseudoId); + } + }, - // Always Show element's Style Attributes - if (node.nodeType === Node.ELEMENT_NODE) { - var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: node.style, isAttribute: true }; - inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style"); - styleRules.push(inlineStyle); + _refreshStyleRules: function(sections, computedStyle) + { + var nodeComputedStyle = new WebInspector.CSSStyleDeclaration(computedStyle); + var styleRules = []; + for (var i = 0; sections && i < sections.length; ++i) { + var section = sections[i]; + if (section instanceof WebInspector.BlankStylePropertiesSection) + continue; + if (section.computedStyle) + section.styleRule.style = nodeComputedStyle; + var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule }; + styleRules.push(styleRule); + } + return styleRules; + }, + + _rebuildStyleRules: function(node, styles) + { + var nodeComputedStyle = new WebInspector.CSSStyleDeclaration(styles.computedStyle); + this.sections = {}; + + var styleRules = []; + + styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: nodeComputedStyle, editable: false }); + + var styleAttributes = {}; + for (var name in styles.styleAttributes) { + var attrStyle = { style: new WebInspector.CSSStyleDeclaration(styles.styleAttributes[name]), editable: false }; + attrStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", name); + attrStyle.selectorText = WebInspector.panels.elements.treeOutline.nodeNameToCorrectCase(node.nodeName) + "[" + name; + if (node.getAttribute(name)) + attrStyle.selectorText += "=" + node.getAttribute(name); + attrStyle.selectorText += "]"; + styleRules.push(attrStyle); + } + + // Show element's Style Attributes + if (styles.inlineStyle && node.nodeType === Node.ELEMENT_NODE) { + var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: new WebInspector.CSSStyleDeclaration(styles.inlineStyle), isAttribute: true }; + inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style"); + styleRules.push(inlineStyle); + } + + // Add rules in reverse order to match the cascade order. + for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) { + var rule = WebInspector.CSSStyleDeclaration.parseRule(styles.matchedCSSRules[i]); + styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule }); + } + + // Collect used properties first in order to identify inherited ones. + var userPropertyNames = {}; + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + if (styleRule.computedStyle) + continue; + var style = styleRule.style; + for (var j = 0; j < style.length; ++j) + userPropertyNames[style[j]] = true; + } + + // Walk the node structure and identify styles with inherited properties. + var parentStyles = styles.parent; + var parentNode = node.parentNode; + function insertInheritedNodeSeparator(node) + { + var entry = {}; + entry.isStyleSeparator = true; + entry.node = node; + styleRules.push(entry); + } + + while (parentStyles) { + var separatorInserted = false; + if (parentStyles.inlineStyle) { + if (this._containsInherited(parentStyles.inlineStyle)) { + var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: new WebInspector.CSSStyleDeclaration(parentStyles.inlineStyle), isAttribute: true, isInherited: true }; + inlineStyle.subtitle = WebInspector.UIString("element’s “%s” attribute", "style"); + if (!separatorInserted) { + insertInheritedNodeSeparator(parentNode); + separatorInserted = true; + } + styleRules.push(inlineStyle); + } } - var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !WebInspector.settings.showUserAgentStyles); - if (matchedStyleRules) { - // Add rules in reverse order to match the cascade order. - for (var i = (matchedStyleRules.length - 1); i >= 0; --i) { - var rule = matchedStyleRules[i]; - styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule }); + for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) { + var rulePayload = parentStyles.matchedCSSRules[i]; + if (!this._containsInherited(rulePayload.style)) + continue; + var rule = WebInspector.CSSStyleDeclaration.parseRule(rulePayload); + if (!separatorInserted) { + insertInheritedNodeSeparator(parentNode); + separatorInserted = true; } + styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule, isInherited: true }); } + parentStyles = parentStyles.parent; + parentNode = parentNode.parentNode; } + return styleRules; + }, + _markUsedProperties: function(styleRules, usedProperties, disabledComputedProperties) + { function deleteDisabledProperty(style, name) { if (!style || !name) @@ -184,14 +308,12 @@ WebInspector.StylesSidebarPane.prototype = { delete style.__disabledProperties[name]; } - var usedProperties = {}; - var disabledComputedProperties = {}; var priorityUsed = false; // Walk the style rules and make a list of all used and overloaded properties. for (var i = 0; i < styleRules.length; ++i) { var styleRule = styleRules[i]; - if (styleRule.computedStyle) + if (styleRule.computedStyle || styleRule.isStyleSeparator) continue; if (styleRule.section && styleRule.section.noAffect) continue; @@ -245,13 +367,12 @@ WebInspector.StylesSidebarPane.prototype = { // Walk in reverse to match the order !important overrides. for (var i = (styleRules.length - 1); i >= 0; --i) { - if (styleRules[i].computedStyle) + if (styleRules[i].computedStyle || styleRules[i].isStyleSeparator) continue; var style = styleRules[i].style; - var uniqueProperties = style.uniqueStyleProperties; - for (var j = 0; j < uniqueProperties.length; ++j) { - var name = uniqueProperties[j]; + for (var j = 0; j < style.length; ++j) { + var name = style[j]; if (style.getPropertyPriority(name).length) { if (!(name in foundPriorityProperties)) styleRules[i].usedProperties[name] = true; @@ -263,58 +384,94 @@ WebInspector.StylesSidebarPane.prototype = { } } } + }, + + _refreshSectionsForStyleRules: function(styleRules, usedProperties, disabledComputedProperties, editedSection) + { + // Walk the style rules and update the sections with new overloaded and used properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var section = styleRule.section; + if (styleRule.computedStyle) + section.disabledComputedProperties = disabledComputedProperties; + section._usedProperties = (styleRule.usedProperties || usedProperties); + section.update((section === editedSection) || styleRule.computedStyle); + } + }, - if (refresh) { - // Walk the style rules and update the sections with new overloaded and used properties. - for (var i = 0; i < styleRules.length; ++i) { - var styleRule = styleRules[i]; - var section = styleRule.section; - if (styleRule.computedStyle) - section.disabledComputedProperties = disabledComputedProperties; - section._usedProperties = (styleRule.usedProperties || usedProperties); - section.update((section === editedSection) || styleRule.computedStyle); + _rebuildSectionsForStyleRules: function(styleRules, usedProperties, disabledComputedProperties, pseudoId) + { + // Make a property section for each style rule. + var sections = []; + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + if (styleRule.isStyleSeparator) { + var separatorElement = document.createElement("div"); + separatorElement.className = "styles-sidebar-separator"; + if (styleRule.node) { + var link = document.createElement("a"); + link.href = ""; + link.addEventListener("mousedown", this._selectNode.bind(this, styleRule.node.id), false); + WebInspector.panels.elements.decorateNodeLabel(styleRule.node, link); + separatorElement.appendChild(document.createTextNode(WebInspector.UIString("Inherited from") + " ")); + separatorElement.appendChild(link); + } else if ("pseudoId" in styleRule) { + var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId]; + if (pseudoName) + separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName); + else + separatorElement.textContent = WebInspector.UIString("Pseudo element"); + } + this.bodyElement.appendChild(separatorElement); + continue; } - } else { - // Make a property section for each style rule. - for (var i = 0; i < styleRules.length; ++i) { - var styleRule = styleRules[i]; - var subtitle = styleRule.subtitle; - delete styleRule.subtitle; - - var computedStyle = styleRule.computedStyle; - delete styleRule.computedStyle; - - var ruleUsedProperties = styleRule.usedProperties; - delete styleRule.usedProperties; - - var editable = styleRule.editable; - delete styleRule.editable; - - var isAttribute = styleRule.isAttribute; - delete styleRule.isAttribute; - - // Default editable to true if it was omitted. - if (typeof editable === "undefined") - editable = true; - - var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable); - if (computedStyle) - section.disabledComputedProperties = disabledComputedProperties; - section.pane = this; - - if (Preferences.styleRulesExpandedState && section.identifier in Preferences.styleRulesExpandedState) - section.expanded = Preferences.styleRulesExpandedState[section.identifier]; - else if (computedStyle) - section.collapse(true); - else if (isAttribute && styleRule.style.length === 0) - section.collapse(true); - else - section.expand(true); + var computedStyle = styleRule.computedStyle; + + // Default editable to true if it was omitted. + var editable = styleRule.editable; + if (typeof editable === "undefined") + editable = true; + + var section = new WebInspector.StylePropertiesSection(styleRule, styleRule.subtitle, styleRule.computedStyle, (styleRule.usedProperties || usedProperties), editable, styleRule.isInherited); + if (computedStyle) + section.disabledComputedProperties = disabledComputedProperties; + section.pane = this; + + if (Preferences.styleRulesExpandedState && section.identifier in Preferences.styleRulesExpandedState) + section.expanded = Preferences.styleRulesExpandedState[section.identifier]; + else if (computedStyle) + section.collapse(true); + else if (styleRule.isAttribute && styleRule.style.length === 0) + section.collapse(true); + else if (styleRule.isInherited) + section.collapse(true); + else + section.expand(true); - body.appendChild(section.element); - this.sections.push(section); - } + this.bodyElement.appendChild(section.element); + sections.push(section); + } + return sections; + }, + + _selectNode: function(nodeId, e) + { + WebInspector.updateFocusedNode(nodeId); + e.preventDefault(); + }, + + _containsInherited: function(payload) + { + var properties = []; + for (var i = 0; i < payload.properties.length; ++i) { + var property = payload.properties[i]; + // Does this style contain non-overriden inherited property? + if (property.name in WebInspector.StylesSidebarPane.InheritedProperties) + return true; } + if (payload.disabled && this._containsInherited(payload.disabled)) + return true; + return false; }, _changeSetting: function(event) @@ -340,8 +497,11 @@ WebInspector.StylesSidebarPane.prototype = { var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex]; WebInspector.settings.colorFormat = selectedOption.value; - for (var i = 0; i < this.sections.length; ++i) - this.sections[i].update(true); + for (var pseudoId in this.sections) { + var sections = this.sections[pseudoId]; + for (var i = 0; i < sections.length; ++i) + sections[i].update(true); + } }, _createNewRule: function(event) @@ -354,39 +514,43 @@ WebInspector.StylesSidebarPane.prototype = { var blankSection = new WebInspector.BlankStylePropertiesSection(appropriateSelectorForNode(this.node, true)); blankSection.pane = this; - var elementStyleSection = this.sections[1]; + var elementStyleSection = this.sections[0][1]; this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling); - this.sections.splice(2, 0, blankSection); + this.sections[0].splice(2, 0, blankSection); return blankSection; }, removeSection: function(section) { - var index = this.sections.indexOf(section); - if (index === -1) - return; - this.sections.splice(index, 1); - if (section.element.parentNode) - section.element.parentNode.removeChild(section.element); + for (var pseudoId in this.sections) { + var sections = this.sections[pseudoId]; + var index = sections.indexOf(section); + if (index === -1) + continue; + sections.splice(index, 1); + if (section.element.parentNode) + section.element.parentNode.removeChild(section.element); + } } } WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; -WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable) +WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable, isInherited) { WebInspector.PropertiesSection.call(this, styleRule.selectorText); - this.titleElement.addEventListener("click", this._clickSelector.bind(this), false); - this.titleElement.addEventListener("dblclick", this._dblclickSelector.bind(this), false); - this.element.addEventListener("dblclick", this._dblclickEmptySpace.bind(this), false); + this.titleElement.addEventListener("dblclick", this._handleSelectorDoubleClick.bind(this), false); + this.titleElement.addEventListener("click", this._handleSelectorClick.bind(this), false); + this.element.addEventListener("dblclick", this._handleEmptySpaceDoubleClick.bind(this), false); this.styleRule = styleRule; this.rule = this.styleRule.rule; this.computedStyle = computedStyle; this.editable = (editable && !computedStyle); + this.isInherited = isInherited; // Prevent editing the user agent and user rules. var isUserAgent = this.rule && this.rule.isUserAgent; @@ -398,38 +562,34 @@ WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyl this._usedProperties = usedProperties; + if (this.rule) + this.titleElement.addStyleClass("styles-selector"); + if (computedStyle) { this.element.addStyleClass("computed-style"); if (WebInspector.settings.showInheritedComputedStyleProperties) this.element.addStyleClass("show-inherited"); - var showInheritedLabel = document.createElement("label"); - var showInheritedInput = document.createElement("input"); - showInheritedInput.type = "checkbox"; - showInheritedInput.checked = WebInspector.settings.showInheritedComputedStyleProperties; - var computedStyleSection = this; var showInheritedToggleFunction = function(event) { - WebInspector.settings.showInheritedComputedStyleProperties = showInheritedInput.checked; + WebInspector.settings.showInheritedComputedStyleProperties = computedStyleSection._showInheritedCheckbox.checked(); if (WebInspector.settings.showInheritedComputedStyleProperties) computedStyleSection.element.addStyleClass("show-inherited"); else computedStyleSection.element.removeStyleClass("show-inherited"); - event.stopPropagation(); }; - showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false); + this._showInheritedCheckbox = new WebInspector.Checkbox(WebInspector.UIString("Show inherited"), + showInheritedToggleFunction, + WebInspector.settings.showInheritedComputedStyleProperties); - showInheritedLabel.appendChild(showInheritedInput); - showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited"))); - this.subtitleElement.appendChild(showInheritedLabel); + this.subtitleElement.appendChild(this._showInheritedCheckbox.element); } else { if (!subtitle) { if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) { var url = this.styleRule.parentStyleSheet.href; - subtitle = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url)); - this.subtitleElement.addStyleClass("file"); + this.subtitleElement.appendChild(WebInspector.linkifyResourceAsNode(url, "resources", this.rule.sourceLine + 1)); } else if (isUserAgent) subtitle = WebInspector.UIString("user agent stylesheet"); else if (isUser) @@ -439,8 +599,10 @@ WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyl else subtitle = WebInspector.UIString("inline stylesheet"); } - - this.subtitle = subtitle; + if (isInherited) + this.element.addStyleClass("show-inherited"); + if (subtitle) + this.subtitle = subtitle; } this.identifier = styleRule.selectorText; @@ -484,6 +646,12 @@ WebInspector.StylePropertiesSection.prototype = { isPropertyInherited: function(property) { + if (this.isInherited) { + // While rendering inherited stylesheet, reverse meaning of this property. + // Render truly inherited properties with black, i.e. return them as non-inherited. + return !(property in WebInspector.StylesSidebarPane.InheritedProperties); + } + if (!this.computedStyle || !this._usedProperties || this.noAffect) return false; // These properties should always show for Computed Style. @@ -496,6 +664,11 @@ WebInspector.StylePropertiesSection.prototype = { if (this.computedStyle || !this._usedProperties || this.noAffect) return false; + if (this.isInherited && !(property in WebInspector.StylesSidebarPane.InheritedProperties)) { + // In the inherited sections, only show overrides for the potentially inherited properties. + return false; + } + var used = (property in this.usedProperties); if (used || !shorthand) return !used; @@ -512,11 +685,6 @@ WebInspector.StylePropertiesSection.prototype = { return true; }, - isInspectorStylesheet: function() - { - return (this.styleRule.parentStyleSheet === WebInspector.panels.elements.stylesheet); - }, - update: function(full) { if (full || this.computedStyle) { @@ -546,9 +714,12 @@ WebInspector.StylePropertiesSection.prototype = { var style = this.styleRule.style; var foundShorthands = {}; - var uniqueProperties = style.uniqueStyleProperties; var disabledProperties = style.__disabledPropertyValues || {}; + var uniqueProperties = []; + for (var i = 0; i < style.length; ++i) + uniqueProperties.push(style[i]); + for (var name in disabledProperties) uniqueProperties.push(name); @@ -599,26 +770,28 @@ WebInspector.StylePropertiesSection.prototype = { return item; }, - _clickSelector: function(event) + _handleEmptySpaceDoubleClick: function(event) { - event.stopPropagation(); - - // Don't search "Computed Styles", "Style Attribute", or Mapped Attributes. - if (this.computedStyle || !this.rule || this.rule.isUser) + if (event.target.hasStyleClass("header")) { + event.stopPropagation(); return; + } + this.expand(); + this.addNewBlankProperty().startEditing(); + }, - var searchElement = document.getElementById("search"); - searchElement.value = this.styleRule.selectorText; - WebInspector.performSearch({ target: searchElement }); + _handleSelectorClick: function(event) + { + event.stopPropagation(); }, - _dblclickEmptySpace: function(event) + _handleSelectorDoubleClick: function(event) { - this.expand(); - this.addNewBlankProperty().startEditing(); + this._startEditingOnMouseEvent(); + event.stopPropagation(); }, - _dblclickSelector: function(event) + _startEditingOnMouseEvent: function() { if (!this.editable) return; @@ -633,7 +806,6 @@ WebInspector.StylePropertiesSection.prototype = { return; this.startEditingSelector(); - event.stopPropagation(); }, startEditingSelector: function() @@ -665,16 +837,14 @@ WebInspector.StylePropertiesSection.prototype = { return moveToNextIfNeeded.call(this); var self = this; - function callback(result) + function callback(newRulePayload, doesAffectSelectedNode) { - if (!result) { + if (!newRulePayload) { // Invalid Syntax for a Selector moveToNextIfNeeded.call(self); return; } - var newRulePayload = result[0]; - var doesAffectSelectedNode = result[1]; if (!doesAffectSelectedNode) { self.noAffect = true; self.element.addStyleClass("no-affect"); @@ -697,7 +867,7 @@ WebInspector.StylePropertiesSection.prototype = { moveToNextIfNeeded.call(self); } - InjectedScriptAccess.get(this.rule.injectedScriptId).applyStyleRuleText(this.rule.id, newContent, this.pane.node.id, callback); + InspectorBackend.setRuleSelector(WebInspector.Callback.wrap(callback), this.rule.id, newContent, this.pane.node.id); }, editingSelectorCancelled: function() @@ -724,17 +894,14 @@ WebInspector.BlankStylePropertiesSection.prototype = { editingSelectorCommitted: function(element, newContent, oldContent, context) { var self = this; - function callback(result) + function callback(rule, doesSelectorAffectSelectedNode) { - if (!result) { + if (!rule) { // Invalid Syntax for a Selector self.editingSelectorCancelled(); return; } - var rule = result[0]; - var doesSelectorAffectSelectedNode = result[1]; - var styleRule = WebInspector.CSSStyleDeclaration.parseRule(rule); styleRule.rule = rule; @@ -751,7 +918,7 @@ WebInspector.BlankStylePropertiesSection.prototype = { self.addNewBlankProperty().startEditing(); } - InjectedScriptAccess.get(this.pane.node.injectedScriptId).addStyleSelector(newContent, this.pane.node.id, callback); + InspectorBackend.addRule(WebInspector.Callback.wrap(callback), newContent, this.pane.node.id); }, editingSelectorCancelled: function() @@ -994,7 +1161,7 @@ WebInspector.StylePropertyTreeElement.prototype = { return container; } - var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b)/g; + var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g; var colorProcessor = processValue.bind(window, colorRegex, processColor, null); valueElement.appendChild(processValue(/url\(([^)]+)\)/g, linkifyURL, colorProcessor, value)); @@ -1058,7 +1225,7 @@ WebInspector.StylePropertyTreeElement.prototype = { self.updateAll(true); } - InjectedScriptAccess.get(this.style.injectedScriptId).toggleStyleEnabled(this.style.id, this.name, disabled, callback); + InspectorBackend.toggleStyleEnabled(WebInspector.Callback.wrap(callback), this.style.id, this.name, disabled); }, updateState: function() @@ -1211,7 +1378,6 @@ WebInspector.StylePropertyTreeElement.prototype = { selection.removeAllRanges(); selection.addRange(finalSelectionRange); - event.preventDefault(); event.handled = true; if (!this.originalCSSText) { @@ -1221,7 +1387,7 @@ WebInspector.StylePropertyTreeElement.prototype = { } else { // Restore the original CSS text before applying user changes. This is needed to prevent // new properties from sticking around if the user adds one, then removes it. - InjectedScriptAccess.get(this.style.injectedScriptId).setStyleText(this.style.id, this.originalCSSText); + InspectorBackend.setStyleText(WebInspector.Callback.wrap(null), this.style.id, this.originalCSSText); } this.applyStyleText(this.listItemElement.textContent); @@ -1241,7 +1407,7 @@ WebInspector.StylePropertyTreeElement.prototype = { if (this._newProperty) this.treeOutline.removeChild(this); else if (this.originalCSSText) { - InjectedScriptAccess.get(this.style.injectedScriptId).setStyleText(this.style.id, this.originalCSSText); + InspectorBackend.setStyleText(WebInspector.Callback.wrap(null), this.style.id, this.originalCSSText); if (this.treeOutline.section && this.treeOutline.section.pane) this.treeOutline.section.pane.dispatchEventToListeners("style edited"); @@ -1314,7 +1480,8 @@ WebInspector.StylePropertyTreeElement.prototype = { { var section = this.treeOutline.section; var elementsPanel = WebInspector.panels.elements; - var styleTextLength = styleText.trim().length; + styleText = styleText.replace(/\s/g, " ").trim(); // replace with whitespace. + var styleTextLength = styleText.length; if (!styleTextLength && updateInterface) { if (this._newProperty) { // The user deleted everything, so remove the tree element and update. @@ -1327,9 +1494,9 @@ WebInspector.StylePropertyTreeElement.prototype = { } var self = this; - function callback(result) + function callback(success, newPayload, changedProperties) { - if (!result) { + if (!success) { // The user typed something, but it didn't parse. Just abort and restore // the original title for this property. If this was a new attribute and // we couldn't parse, then just remove it. @@ -1342,8 +1509,6 @@ WebInspector.StylePropertyTreeElement.prototype = { return; } - var newPayload = result[0]; - var changedProperties = result[1]; elementsPanel.removeStyleChange(section.identifier, self.style, self.name); if (!styleTextLength) { @@ -1361,12 +1526,8 @@ WebInspector.StylePropertyTreeElement.prototype = { if (updateInterface) self.updateAll(true); - - if (!section.rule) - WebInspector.panels.elements.treeOutline.update(); } - - InjectedScriptAccess.get(this.style.injectedScriptId).applyStyleText(this.style.id, styleText.trim(), this.name, callback); + InspectorBackend.applyStyleText(WebInspector.Callback.wrap(callback), this.style.id, styleText, this.name); } } diff --git a/WebCore/inspector/front-end/TextEditorHighlighter.js b/WebCore/inspector/front-end/TextEditorHighlighter.js index cf0b590..4ac831e 100644 --- a/WebCore/inspector/front-end/TextEditorHighlighter.js +++ b/WebCore/inspector/front-end/TextEditorHighlighter.js @@ -34,24 +34,31 @@ WebInspector.TextEditorHighlighter = function(textModel, damageCallback) this._textModel = textModel; this._tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/html"); this._damageCallback = damageCallback; + this.reset(); } WebInspector.TextEditorHighlighter.prototype = { set mimeType(mimeType) { var tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer(mimeType); - if (tokenizer) + if (tokenizer) { this._tokenizer = tokenizer; + this._tokenizerCondition = this._tokenizer.initialCondition; + } + }, + + reset: function() + { + this._lastHighlightedLine = 0; + this._lastHighlightedColumn = 0; + this._tokenizerCondition = this._tokenizer.initialCondition; }, highlight: function(endLine) { // First check if we have work to do. - var state = this._textModel.getAttribute(endLine - 1, "highlighter-state") - if (state && !state.outOfDate) { - // Last line is highlighted, just exit. + if (endLine <= this._lastHighlightedLine) return; - } this._requestedEndLine = endLine; @@ -60,110 +67,66 @@ WebInspector.TextEditorHighlighter.prototype = { return; } - // We will be highlighting. First rewind to the last highlighted line to gain proper highlighter context. - var startLine = endLine; - while (startLine > 0) { - var state = this._textModel.getAttribute(startLine - 1, "highlighter-state"); - if (state && !state.outOfDate) - break; - startLine--; - } - // Do small highlight synchronously. This will provide instant highlight on PageUp / PageDown, gentle scrolling. - var toLine = Math.min(startLine + 200, endLine); - this._highlightInChunks(startLine, toLine); + this._highlightInChunks(endLine); // Schedule tail highlight if necessary. - if (endLine > toLine) - this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, toLine, endLine), 100); + if (this._lastHighlightedLine < endLine) + this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, endLine), 100); }, - _highlightInChunks: function(startLine, endLine) + _highlightInChunks: function(endLine) { delete this._highlightTimer; // First we always check if we have work to do. Could be that user scrolled back and we can quit. - var state = this._textModel.getAttribute(this._requestedEndLine - 1, "highlighter-state"); - if (state && !state.outOfDate) + if (this._requestedEndLine <= this._lastHighlightedLine) return; if (this._requestedEndLine !== endLine) { // User keeps updating the job in between of our timer ticks. Just reschedule self, don't eat CPU (they must be scrolling). - this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, startLine, this._requestedEndLine), 100); + this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, this._requestedEndLine), 100); return; } - // Highlight 500 lines chunk. - var toLine = Math.min(startLine + 500, this._requestedEndLine); - this._highlightLines(startLine, toLine); + this._highlightLines(this._requestedEndLine); // Schedule tail highlight if necessary. - if (toLine < this._requestedEndLine) - this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, toLine, this._requestedEndLine), 10); + if (this._lastHighlightedLine < this._requestedEndLine) + this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, this._requestedEndLine), 10); }, - updateHighlight: function(startLine, endLine) + _highlightLines: function(endLine) { - // Start line was edited, we should highlight everything until endLine synchronously. - if (startLine) { - var state = this._textModel.getAttribute(startLine - 1, "highlighter-state"); - if (!state || state.outOfDate) { - // Highlighter did not reach this point yet, nothing to update. It will reach it on subsequent timer tick and do the job. - return; - } - } - - var restored = this._highlightLines(startLine, endLine); - - // Set invalidated flag to the subsequent lines. - for (var i = endLine; i < this._textModel.linesCount; ++i) { - var highlighterState = this._textModel.getAttribute(i, "highlighter-state"); - if (highlighterState) - highlighterState.outOfDate = !restored; - else - return; - } - }, - - _highlightLines: function(startLine, endLine) - { - // Restore highlighter context taken from previous line. - var state = this._textModel.getAttribute(startLine - 1, "highlighter-state"); - if (state) - this._tokenizer.condition = state.postCondition; - else - this._tokenizer.condition = this._tokenizer.initialCondition; - - for (var i = startLine; i < endLine; ++i) { - state = {}; - state.preCondition = this._tokenizer.condition; - state.attributes = {}; - - this._lex(this._textModel.line(i), i, state.attributes); - - state.postCondition = this._tokenizer.condition; - this._textModel.setAttribute(i, "highlighter-state", state); - - var nextLineState = this._textModel.getAttribute(i + 1, "highlighter-state"); - if (nextLineState && this._tokenizer.hasCondition(nextLineState.preCondition)) { - // Following lines are up to date, no need re-highlight. - this._damageCallback(startLine, i + 1); - return true; - } + // Tokenizer is stateless and reused accross viewers, restore its condition before highlight and save it after. + this._tokenizer.condition = this._tokenizerCondition; + var tokensCount = 0; + for (var lineNumber = this._lastHighlightedLine; lineNumber < endLine; ++lineNumber) { + var line = this._textModel.line(lineNumber); + this._tokenizer.line = line; + var attributes = this._textModel.getAttribute(lineNumber, "highlight") || {}; + + // Highlight line. + do { + var newColumn = this._tokenizer.nextToken(this._lastHighlightedColumn); + var tokenType = this._tokenizer.tokenType; + if (tokenType) + attributes[this._lastHighlightedColumn] = { length: newColumn - this._lastHighlightedColumn, tokenType: tokenType, subTokenizer: this._tokenizer.subTokenizer }; + this._lastHighlightedColumn = newColumn; + if (++tokensCount > 1000) + break; + } while (this._lastHighlightedColumn < line.length) + + this._textModel.setAttribute(lineNumber, "highlight", attributes); + if (this._lastHighlightedColumn < line.length) { + // Too much work for single chunk - exit. + break; + } else + this._lastHighlightedColumn = 0; } - this._damageCallback(startLine, endLine); - return false; - }, - _lex: function(line, lineNumber, attributes) { - this._tokenizer.line = line; - var column = 0; - do { - var newColumn = this._tokenizer.nextToken(column); - var tokenType = this._tokenizer.tokenType; - if (tokenType) - attributes[column] = { length: newColumn - column, tokenType: tokenType }; - column = newColumn; - } while (column < line.length) + this._damageCallback(this._lastHighlightedLine, lineNumber); + this._tokenizerCondition = this._tokenizer.condition; + this._lastHighlightedLine = lineNumber; } } diff --git a/WebCore/inspector/front-end/TextEditorModel.js b/WebCore/inspector/front-end/TextEditorModel.js index e56c269..f23ce76 100644 --- a/WebCore/inspector/front-end/TextEditorModel.js +++ b/WebCore/inspector/front-end/TextEditorModel.js @@ -200,6 +200,9 @@ WebInspector.TextEditorModel.prototype = { copyRange: function(range) { + if (!range) + range = new WebInspector.TextRange(0, 0, this._lines.length - 1, this._lines[this._lines.length - 1].length); + var clip = []; if (range.startLine === range.endLine) { clip.push(this._lines[range.startLine].substring(range.startColumn, range.endColumn)); diff --git a/WebCore/inspector/front-end/TextPrompt.js b/WebCore/inspector/front-end/TextPrompt.js index 8554a28..d179a9a 100644 --- a/WebCore/inspector/front-end/TextPrompt.js +++ b/WebCore/inspector/front-end/TextPrompt.js @@ -99,6 +99,22 @@ WebInspector.TextPrompt.prototype = { } defaultAction.call(this); break; + case "U+0041": // Ctrl+A = Move caret to the start of prompt on non-Mac. + if (!WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { + handled = true; + this._moveCaretToStartOfPrompt(); + break; + } + defaultAction.call(this); + break; + case "U+0045": // Ctrl+E = Move caret to the end of prompt on non-Mac. + if (!WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { + handled = true; + this.moveCaretToEndOfPrompt(); + break; + } + defaultAction.call(this); + break; default: defaultAction.call(this); break; @@ -149,6 +165,7 @@ WebInspector.TextPrompt.prototype = { return; this._userEnteredRange.deleteContents(); + this.element.pruneEmptyTextNodes(); var userTextNode = document.createTextNode(this._userEnteredText); this._userEnteredRange.insertNode(userTextNode); @@ -171,7 +188,7 @@ WebInspector.TextPrompt.prototype = { this._completeTimeout = setTimeout(this.complete.bind(this, true), 250); }, - complete: function(auto) + complete: function(auto, reverse) { this.clearAutoComplete(true); var selection = window.getSelection(); @@ -184,10 +201,10 @@ WebInspector.TextPrompt.prototype = { if (auto && !this.isCaretAtEndOfPrompt()) return; var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this.completionStopCharacters, this.element, "backward"); - this.completions(wordPrefixRange, auto, this._completionsReady.bind(this, selection, auto, wordPrefixRange)); + this.completions(wordPrefixRange, auto, this._completionsReady.bind(this, selection, auto, wordPrefixRange, reverse)); }, - _completionsReady: function(selection, auto, originalWordPrefixRange, completions) + _completionsReady: function(selection, auto, originalWordPrefixRange, reverse, completions) { if (!completions || !completions.length) return; @@ -211,10 +228,13 @@ WebInspector.TextPrompt.prototype = { if (completions[i] === currentText) foundIndex = i; - if (foundIndex === null || (foundIndex + 1) >= completions.length) + var nextIndex = foundIndex + (reverse ? -1 : 1); + if (foundIndex === null || nextIndex >= completions.length) var completionText = completions[0]; + else if (nextIndex < 0) + var completionText = completions[completions.length - 1]; else - var completionText = completions[foundIndex + 1]; + var completionText = completions[nextIndex]; } var wordPrefixLength = originalWordPrefixRange.toString().length; @@ -223,6 +243,7 @@ WebInspector.TextPrompt.prototype = { this._userEnteredText = fullWordRange.toString(); fullWordRange.deleteContents(); + this.element.pruneEmptyTextNodes(); var finalSelectionRange = document.createRange(); @@ -334,6 +355,18 @@ WebInspector.TextPrompt.prototype = { return true; }, + _moveCaretToStartOfPrompt: function() + { + var selection = window.getSelection(); + var selectionRange = document.createRange(); + + selectionRange.setStart(this.element, 0); + selectionRange.setEnd(this.element, 0); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + }, + moveCaretToEndOfPrompt: function() { var selection = window.getSelection(); @@ -352,7 +385,7 @@ WebInspector.TextPrompt.prototype = { event.preventDefault(); event.stopPropagation(); - this.complete(); + this.complete(false, event.shiftKey); }, _upKeyPressed: function(event) diff --git a/WebCore/inspector/front-end/TextViewer.js b/WebCore/inspector/front-end/TextViewer.js index 9be6a00..de04641 100644 --- a/WebCore/inspector/front-end/TextViewer.js +++ b/WebCore/inspector/front-end/TextViewer.js @@ -1,5 +1,6 @@ /* * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2010 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -39,6 +40,10 @@ WebInspector.TextViewer = function(textModel, platform, url) this.element.tabIndex = 0; this.element.addEventListener("scroll", this._scroll.bind(this), false); + this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false); + this.element.addEventListener("beforecopy", this._beforeCopy.bind(this), false); + this.element.addEventListener("copy", this._copy.bind(this), false); + this.element.addEventListener("dblclick", this._handleDoubleClick.bind(this), false); this._url = url; @@ -75,6 +80,11 @@ WebInspector.TextViewer.prototype = { chunk.element.scrollIntoViewIfNeeded(); }, + set editCallback(editCallback) + { + this._editCallback = editCallback; + }, + addDecoration: function(lineNumber, decoration) { var chunk = this._makeLineAChunk(lineNumber); @@ -134,7 +144,9 @@ WebInspector.TextViewer.prototype = { this._textChunks.push(chunk); this._linesContainerElement.appendChild(chunk.element); } + this._indexChunks(); + this._highlighter.reset(); this._repaintAll(); }, @@ -203,6 +215,74 @@ WebInspector.TextViewer.prototype = { }.bind(this), 50); }, + _handleKeyDown: function() + { + if (this._editingLine || event.metaKey || event.shiftKey || event.ctrlKey || event.altKey) + return; + + var scrollValue = 0; + if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Up) + scrollValue = -1; + else if (event.keyCode == WebInspector.KeyboardShortcut.KeyCodes.Down) + scrollValue = 1; + + if (scrollValue) { + event.preventDefault(); + event.stopPropagation(); + this.element.scrollByLines(scrollValue); + return; + } + + scrollValue = 0; + if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Left) + scrollValue = -40; + else if (event.keyCode == WebInspector.KeyboardShortcut.KeyCodes.Right) + scrollValue = 40; + + if (scrollValue) { + event.preventDefault(); + event.stopPropagation(); + this.element.scrollLeft += scrollValue; + } + }, + + _handleDoubleClick: function(e) + { + if (!this._editCallback) + return; + + var lineRow = e.target.enclosingNodeOrSelfWithNodeName("TR"); + if (!lineRow) + return; + var oldContent = lineRow.lastChild.innerHTML; + this._editingLine = WebInspector.startEditing(lineRow.lastChild, this._commitEditingLine.bind(this, lineRow.lineNumber, lineRow.lastChild), this._cancelEditingLine.bind(this, lineRow.lastChild, oldContent), null, true); + }, + + _commitEditingLine: function(lineNumber, element) + { + this._editCallback(lineNumber, element.textContent) + delete this._editingLine; + }, + + _cancelEditingLine: function(element, oldContent, e) + { + element.innerHTML = oldContent; + delete this._editingLine; + }, + + _beforeCopy: function(e) + { + e.preventDefault(); + }, + + _copy: function(e) + { + var range = this._getSelection(); + var text = this._textModel.copyRange(range); + InspectorFrontendHost.copyText(text); + e.preventDefault(); + }, + beginUpdates: function(enabled) { this._paintCoalescingLevel++; @@ -330,25 +410,25 @@ WebInspector.TextViewer.prototype = { _paintLine: function(lineRow, lineNumber) { var element = lineRow.lastChild; - var highlighterState = this._textModel.getAttribute(lineNumber, "highlighter-state"); - var line = this._textModel.line(lineNumber); - - if (!highlighterState) { + var highlight = this._textModel.getAttribute(lineNumber, "highlight"); + if (!highlight) { if (this._rangeToMark && this._rangeToMark.startLine === lineNumber) this._markedRangeElement = highlightSearchResult(element, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn); return; } element.removeChildren(); + var line = this._textModel.line(lineNumber); 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. - plainTextStart = j; + if (plainTextStart === -1) + plainTextStart = j; break; } - var attribute = highlighterState && highlighterState.attributes[j]; + var attribute = highlight[j]; if (!attribute || !attribute.tokenType) { if (plainTextStart === -1) plainTextStart = j; @@ -576,7 +656,18 @@ WebInspector.TextChunk = function(textViewer, startLine, endLine) lineNumbers.push(i + 1); lines.push(this._textModel.line(i)); } - this._lineNumberElement.textContent = lineNumbers.join("\n"); + if (this.linesCount === 1) { + // Single line chunks are typically created for decorations. Host line number in + // the sub-element in order to allow flexible border / margin management. + var innerSpan = document.createElement("span"); + innerSpan.className = "webkit-line-number-inner"; + innerSpan.textContent = startLine + 1; + var outerSpan = document.createElement("div"); + outerSpan.className = "webkit-line-number-outer"; + outerSpan.appendChild(innerSpan); + this._lineNumberElement.appendChild(outerSpan); + } else + this._lineNumberElement.textContent = lineNumbers.join("\n"); this._lineContentElement.textContent = lines.join("\n"); } diff --git a/WebCore/inspector/front-end/TimelineAgent.js b/WebCore/inspector/front-end/TimelineAgent.js index c9e9d64..387d491 100644 --- a/WebCore/inspector/front-end/TimelineAgent.js +++ b/WebCore/inspector/front-end/TimelineAgent.js @@ -48,7 +48,12 @@ WebInspector.TimelineAgent.RecordType = { MarkTimeline : 11, ResourceSendRequest : 12, ResourceReceiveResponse : 13, - ResourceFinish : 14 + ResourceFinish : 14, + FunctionCall : 15, + ResourceReceiveData: 16, + GCEvent : 17, + MarkDOMContentEventType : 18, + MarkLoadEventType : 19 }; WebInspector.addRecordToTimeline = function(record) { diff --git a/WebCore/inspector/front-end/TimelineGrid.js b/WebCore/inspector/front-end/TimelineGrid.js index 12b889a..fb93b8f 100644 --- a/WebCore/inspector/front-end/TimelineGrid.js +++ b/WebCore/inspector/front-end/TimelineGrid.js @@ -37,15 +37,15 @@ WebInspector.TimelineGrid = function() this.element.appendChild(this._itemsGraphsElement); this._dividersElement = document.createElement("div"); - this._dividersElement.id = "resources-dividers"; + this._dividersElement.className = "resources-dividers"; this.element.appendChild(this._dividersElement); this._eventDividersElement = document.createElement("div"); - this._eventDividersElement.id = "resources-event-dividers"; + this._eventDividersElement.className = "resources-event-dividers"; this.element.appendChild(this._eventDividersElement); this._dividersLabelBarElement = document.createElement("div"); - this._dividersLabelBarElement.id = "resources-dividers-label-bar"; + this._dividersLabelBarElement.className = "resources-dividers-label-bar"; this.element.appendChild(this._dividersLabelBarElement); } @@ -135,6 +135,11 @@ WebInspector.TimelineGrid.prototype = { this._eventDividersElement.appendChild(divider); }, + removeEventDividers: function() + { + this._eventDividersElement.removeChildren(); + }, + setScrollAndDividerTop: function(scrollTop, dividersTop) { this._dividersElement.style.top = scrollTop + "px"; diff --git a/WebCore/inspector/front-end/TimelineOverviewPane.js b/WebCore/inspector/front-end/TimelineOverviewPane.js index 8ae8352..7191ef8 100644 --- a/WebCore/inspector/front-end/TimelineOverviewPane.js +++ b/WebCore/inspector/front-end/TimelineOverviewPane.js @@ -92,7 +92,6 @@ WebInspector.TimelineOverviewPane = function(categories) this.windowRight = 1.0; } - WebInspector.TimelineOverviewPane.prototype = { _onCheckboxClicked: function (category, event) { if (event.target.checked) @@ -103,8 +102,9 @@ WebInspector.TimelineOverviewPane.prototype = { this.dispatchEventToListeners("filter changed"); }, - update: function(records) + update: function(records, showShortEvents) { + this._showShortEvents = showShortEvents; // Clear summary bars. var timelines = {}; for (var category in this._categories) { @@ -128,6 +128,8 @@ WebInspector.TimelineOverviewPane.prototype = { function markTimeline(record) { + if (!(this._showShortEvents || record.isLong())) + return; var percentages = this._overviewCalculator.computeBarGraphPercentages(record); var end = Math.round(percentages.end); @@ -161,6 +163,18 @@ WebInspector.TimelineOverviewPane.prototype = { this._overviewGrid.updateDividers(true, this._overviewCalculator); }, + updateEventDividers: function(records, dividerConstructor) + { + this._overviewGrid.removeEventDividers(); + for (var i = 0; i < records.length; ++i) { + var record = records[i]; + var positions = this._overviewCalculator.computeBarGraphPercentages(record); + var divider = dividerConstructor(record); + divider.style.left = positions.start + "%"; + this._overviewGrid.addEventDivider(divider); + } + }, + setSidebarWidth: function(width) { this._overviewSidebarElement.style.width = width + "px"; @@ -175,7 +189,10 @@ WebInspector.TimelineOverviewPane.prototype = { { this.windowLeft = 0.0; this.windowRight = 1.0; - this._setWindowPosition(0, this._overviewGrid.element.clientWidth); + this._overviewWindowElement.style.left = "0%"; + this._overviewWindowElement.style.width = "100%"; + this._leftResizeElement.style.left = "0%"; + this._rightResizeElement.style.left = "100%"; this._overviewCalculator.reset(); this._overviewGrid.updateDividers(true, this._overviewCalculator); }, diff --git a/WebCore/inspector/front-end/TimelinePanel.js b/WebCore/inspector/front-end/TimelinePanel.js index b6d5fde..7b3e3b0 100644 --- a/WebCore/inspector/front-end/TimelinePanel.js +++ b/WebCore/inspector/front-end/TimelinePanel.js @@ -37,6 +37,7 @@ WebInspector.TimelinePanel = function() this._overviewPane.addEventListener("window changed", this._windowChanged, this); this._overviewPane.addEventListener("filter changed", this._refresh, this); this.element.appendChild(this._overviewPane.element); + this.element.tabIndex = 0; this._sidebarBackgroundElement = document.createElement("div"); this._sidebarBackgroundElement.className = "sidebar timeline-sidebar-background"; @@ -75,14 +76,31 @@ WebInspector.TimelinePanel = function() this._bottomGapElement.className = "timeline-gap"; this._itemsGraphsElement.appendChild(this._bottomGapElement); - this._createStatusbarButtons(); - - this._records = []; + this._rootRecord = this._createRootRecord(); this._sendRequestRecords = {}; + this._timerRecords = {}; + this._calculator = new WebInspector.TimelineCalculator(); + this._calculator._showShortEvents = false; + var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePanel.shortRecordThreshold, WebInspector.UIString.bind(WebInspector)); + this._showShortRecordsTitleText = WebInspector.UIString("Show the records that are shorter than %s", shortRecordThresholdTitle); + this._hideShortRecordsTitleText = WebInspector.UIString("Hide the records that are shorter than %s", shortRecordThresholdTitle); + this._createStatusbarButtons(); + this._boundariesAreValid = true; + this._scrollTop = 0; + + this._popoverHelper = new WebInspector.PopoverHelper(this._containerElement, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true); + + // Disable short events filter by default. + this.toggleFilterButton.toggled = true; + this._calculator._showShortEvents = this.toggleFilterButton.toggled; + this._markTimelineRecords = []; + this._expandOffset = 15; } +WebInspector.TimelinePanel.shortRecordThreshold = 0.015; + WebInspector.TimelinePanel.prototype = { toolbarItemClass: "timeline", @@ -93,7 +111,7 @@ WebInspector.TimelinePanel.prototype = { get statusBarItems() { - return [this.toggleTimelineButton.element, this.clearButton.element]; + return [this.toggleFilterButton.element, this.toggleTimelineButton.element, this.clearButton.element, this.recordsCounter]; }, get categories() @@ -108,21 +126,117 @@ WebInspector.TimelinePanel.prototype = { return this._categories; }, + get defaultFocusedElement() + { + return this.element; + }, + + get _recordStyles() + { + if (!this._recordStylesArray) { + var recordTypes = WebInspector.TimelineAgent.RecordType; + var recordStyles = {}; + recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting }; + recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering }; + recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering }; + recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering }; + recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading }; + recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting }; + recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting }; + recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting }; + recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting }; + recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting }; + recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting }; + recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting }; + recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading }; + recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading }; + recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading }; + recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: this.categories.scripting }; + recordStyles[recordTypes.ResourceReceiveData] = { title: WebInspector.UIString("Receive Data"), category: this.categories.loading }; + recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: this.categories.scripting }; + recordStyles[recordTypes.MarkDOMContentEventType] = { title: WebInspector.UIString("DOMContent event"), category: this.categories.scripting }; + recordStyles[recordTypes.MarkLoadEventType] = { title: WebInspector.UIString("Load event"), category: this.categories.scripting }; + this._recordStylesArray = recordStyles; + } + return this._recordStylesArray; + }, + _createStatusbarButtons: function() { - this.toggleTimelineButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item"); + this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item"); this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false); - this.clearButton = new WebInspector.StatusBarButton("", "timeline-clear-status-bar-item"); - this.clearButton.addEventListener("click", this.reset.bind(this), false); + this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "timeline-clear-status-bar-item"); + this.clearButton.addEventListener("click", this._clearPanel.bind(this), false); + + this.toggleFilterButton = new WebInspector.StatusBarButton(this._hideShortRecordsTitleText, "timeline-filter-status-bar-item"); + this.toggleFilterButton.addEventListener("click", this._toggleFilterButtonClicked.bind(this), false); + + this.recordsCounter = document.createElement("span"); + this.recordsCounter.className = "timeline-records-counter"; + }, + + _updateRecordsCounter: function() + { + this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", this._rootRecord._visibleRecordsCount, this._rootRecord._allRecordsCount); + }, + + _updateEventDividers: function() + { + this._timelineGrid.removeEventDividers(); + var clientWidth = this._graphRowsElement.offsetWidth - this._expandOffset; + for (var i = 0; i < this._markTimelineRecords.length; ++i) { + var record = this._markTimelineRecords[i]; + var positions = this._calculator.computeBarGraphWindowPosition(record, clientWidth); + if (positions.left < 0 || positions.left >= clientWidth) + continue; + + var divider = this._createEventDivider(record); + divider.style.left = (positions.left + this._expandOffset) + "px"; + + this._timelineGrid.addEventDivider(divider); + } + this._overviewPane.updateEventDividers(this._markTimelineRecords, this._createEventDivider.bind(this)); + }, + + _createEventDivider: function(record) + { + var eventDivider = document.createElement("div"); + eventDivider.className = "resources-event-divider"; + var recordTypes = WebInspector.TimelineAgent.RecordType; + + var eventDividerPadding = document.createElement("div"); + eventDividerPadding.className = "resources-event-divider-padding"; + eventDividerPadding.title = record.title; + + if (record.type === recordTypes.MarkDOMContentEventType) + eventDivider.className += " resources-blue-divider"; + else if (record.type === recordTypes.MarkLoadEventType) + eventDivider.className += " resources-red-divider"; + else if (record.type === recordTypes.MarkTimeline) { + eventDivider.className += " resources-orange-divider"; + eventDividerPadding.title = record.data.message; + } + eventDividerPadding.appendChild(eventDivider); + return eventDividerPadding; }, _toggleTimelineButtonClicked: function() { if (this.toggleTimelineButton.toggled) InspectorBackend.stopTimelineProfiler(); - else + else { + this._clearPanel(); InspectorBackend.startTimelineProfiler(); + } + }, + + _toggleFilterButtonClicked: function() + { + this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled; + this._calculator._showShortEvents = this.toggleFilterButton.toggled; + this.toggleFilterButton.element.title = this._calculator._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText; + this._scheduleRefresh(true); }, timelineWasStarted: function() @@ -137,116 +251,71 @@ WebInspector.TimelinePanel.prototype = { addRecordToTimeline: function(record) { - this._innerAddRecordToTimeline(record, this._records); + if (record.type == WebInspector.TimelineAgent.RecordType.ResourceSendRequest && record.data.isMainResource) { + if (this._mainResourceIdentifier != record.data.identifier) { + // We are loading new main resource -> clear the panel. Check above is necessary since + // there may be several resource loads with main resource marker upon redirects, redirects are reported with + // the original identifier. + this._mainResourceIdentifier = record.data.identifier; + this._clearPanel(); + } + } + this._innerAddRecordToTimeline(record, this._rootRecord); this._scheduleRefresh(); }, - _innerAddRecordToTimeline: function(record, collection) + _findParentRecord: function(record) { - var formattedRecord = this._formatRecord(record); - - // Glue subsequent records with same category and title together if they are closer than 100ms to each other. - if (this._lastRecord && (!record.children || !record.children.length) && - this._lastRecord.category == formattedRecord.category && - this._lastRecord.title == formattedRecord.title && - this._lastRecord.details == formattedRecord.details && - formattedRecord.startTime - this._lastRecord.endTime < 0.1) { - this._lastRecord.endTime = formattedRecord.endTime; - this._lastRecord.count++; - } else { - collection.push(formattedRecord); - for (var i = 0; record.children && i < record.children.length; ++i) { - if (!formattedRecord.children) - formattedRecord.children = []; - var formattedChild = this._innerAddRecordToTimeline(record.children[i], formattedRecord.children); - formattedChild.parent = formattedRecord; - } - this._lastRecord = record.children && record.children.length ? null : formattedRecord; - } - return formattedRecord; + var recordTypes = WebInspector.TimelineAgent.RecordType; + var parentRecord; + if (record.type === recordTypes.ResourceReceiveResponse || + record.type === recordTypes.ResourceFinish || + record.type === recordTypes.ResourceReceiveData) + parentRecord = this._sendRequestRecords[record.data.identifier]; + else if (record.type === recordTypes.TimerFire) + parentRecord = this._timerRecords[record.data.timerId]; + return parentRecord; }, - _formatRecord: function(record) + _innerAddRecordToTimeline: function(record, parentRecord) { - var recordTypes = WebInspector.TimelineAgent.RecordType; - if (!this._recordStyles) { - this._recordStyles = {}; - this._recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting }; - this._recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering }; - this._recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering }; - this._recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering }; - this._recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading }; - this._recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting }; - this._recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting }; - this._recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting }; - this._recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting }; - this._recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting }; - this._recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting }; - this._recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting }; - this._recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading }; - this._recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading }; - this._recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading }; + var connectedToOldRecord = false; + if (parentRecord === this._rootRecord) { + var newParentRecord = this._findParentRecord(record); + if (newParentRecord) { + parentRecord = newParentRecord; + connectedToOldRecord = true; + } } + var recordTypes = WebInspector.TimelineAgent.RecordType; - var style = this._recordStyles[record.type]; - if (!style) - style = this._recordStyles[recordTypes.EventDispatch]; - - var formattedRecord = {}; - formattedRecord.category = style.category; - formattedRecord.title = style.title; - formattedRecord.startTime = record.startTime / 1000; - formattedRecord.data = record.data; - formattedRecord.count = 1; - formattedRecord.type = record.type; - formattedRecord.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : formattedRecord.startTime; - formattedRecord.record = record; - - // Make resource receive record last since request was sent; make finish record last since response received. - if (record.type === WebInspector.TimelineAgent.RecordType.ResourceSendRequest) { - this._sendRequestRecords[record.data.identifier] = formattedRecord; - } else if (record.type === WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse) { - var sendRequestRecord = this._sendRequestRecords[record.data.identifier]; - if (sendRequestRecord) { // False if we started instrumentation in the middle of request. - sendRequestRecord._responseReceivedFormattedTime = formattedRecord.startTime; - formattedRecord.startTime = sendRequestRecord.startTime; - sendRequestRecord.details = this._getRecordDetails(record); - } - } else if (record.type === WebInspector.TimelineAgent.RecordType.ResourceFinish) { - var sendRequestRecord = this._sendRequestRecords[record.data.identifier]; - if (sendRequestRecord) // False for main resource. - formattedRecord.startTime = sendRequestRecord._responseReceivedFormattedTime; + var formattedRecord = new WebInspector.TimelinePanel.FormattedRecord(record, parentRecord, this._recordStyles, this._sendRequestRecords, this._timerRecords); + + if (record.type === recordTypes.MarkDOMContentEventType || record.type === recordTypes.MarkLoadEventType) { + // No bar entry for load events. + this._markTimelineRecords.push(formattedRecord); + return; } - formattedRecord.details = this._getRecordDetails(record); - return formattedRecord; - }, + ++this._rootRecord._allRecordsCount; - _getRecordDetails: function(record) - { - switch (record.type) { - case WebInspector.TimelineAgent.RecordType.EventDispatch: - return record.data ? record.data.type : ""; - case WebInspector.TimelineAgent.RecordType.Paint: - return record.data.width + "\u2009\u00d7\u2009" + record.data.height; - case WebInspector.TimelineAgent.RecordType.TimerInstall: - case WebInspector.TimelineAgent.RecordType.TimerRemove: - case WebInspector.TimelineAgent.RecordType.TimerFire: - return record.data.timerId; - case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange: - case WebInspector.TimelineAgent.RecordType.XHRLoad: - case WebInspector.TimelineAgent.RecordType.EvaluateScript: - case WebInspector.TimelineAgent.RecordType.ResourceSendRequest: - return WebInspector.displayNameForURL(record.data.url); - case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse: - case WebInspector.TimelineAgent.RecordType.ResourceFinish: - var sendRequestRecord = this._sendRequestRecords[record.data.identifier]; - return sendRequestRecord ? WebInspector.displayNameForURL(sendRequestRecord.data.url) : ""; - case WebInspector.TimelineAgent.RecordType.MarkTimeline: - return record.data.message; - default: - return ""; + if (parentRecord === this._rootRecord) + formattedRecord.collapsed = true; + + for (var i = 0; record.children && i < record.children.length; ++i) + this._innerAddRecordToTimeline(record.children[i], formattedRecord); + + if (connectedToOldRecord) { + var record = formattedRecord; + while (record.parent && record.parent._lastChildEndTime < record._lastChildEndTime) { + record.parent._lastChildEndTime = record._lastChildEndTime; + record = record.parent; + } } + + // Keep bar entry for mark timeline since nesting might be interesting to the user. + if (record.type === recordTypes.MarkTimeline) + this._markTimelineRecords.push(formattedRecord); }, setSidebarWidth: function(width) @@ -263,31 +332,52 @@ WebInspector.TimelinePanel.prototype = { this._overviewPane.updateMainViewWidth(width); }, - resize: function() { + resize: function() + { + this._closeRecordDetails(); this._scheduleRefresh(); }, - reset: function() + _createRootRecord: function() { - this._lastRecord = null; + var rootRecord = {}; + rootRecord.children = []; + rootRecord._visibleRecordsCount = 0; + rootRecord._allRecordsCount = 0; + return rootRecord; + }, + + _clearPanel: function() + { + this._markTimelineRecords = []; this._sendRequestRecords = {}; - this._records = []; + this._timerRecords = {}; + this._rootRecord = this._createRootRecord(); this._boundariesAreValid = false; this._overviewPane.reset(); this._adjustScrollPosition(0); this._refresh(); + this._closeRecordDetails(); }, show: function() { WebInspector.Panel.prototype.show.call(this); - - if (this._needsRefresh) + if (typeof this._scrollTop === "number") + this._containerElement.scrollTop = this._scrollTop; + else if (this._needsRefresh) this._refresh(); }, + hide: function() + { + WebInspector.Panel.prototype.hide.call(this); + this._closeRecordDetails(); + }, + _onScroll: function(event) { + this._closeRecordDetails(); var scrollTop = this._containerElement.scrollTop; var dividersTop = Math.max(0, scrollTop); this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop); @@ -296,11 +386,13 @@ WebInspector.TimelinePanel.prototype = { _windowChanged: function() { + this._closeRecordDetails(); this._scheduleRefresh(); }, _scheduleRefresh: function(preserveBoundaries) { + this._closeRecordDetails(); this._boundariesAreValid &= preserveBoundaries; if (this._needsRefresh) return; @@ -321,49 +413,72 @@ WebInspector.TimelinePanel.prototype = { clearTimeout(this._refreshTimeout); delete this._refreshTimeout; } - - if (!this._boundariesAreValid) - this._overviewPane.update(this._records); + + this._overviewPane.update(this._rootRecord.children, this._calculator._showShortEvents); this._refreshRecords(!this._boundariesAreValid); + this._updateRecordsCounter(); + this._updateEventDividers(); this._boundariesAreValid = true; }, - _refreshRecords: function(updateBoundaries) + _updateBoundaries: function() { - if (updateBoundaries) { - this._calculator.reset(); - this._calculator.windowLeft = this._overviewPane.windowLeft; - this._calculator.windowRight = this._overviewPane.windowRight; + this._calculator.reset(); + this._calculator.windowLeft = this._overviewPane.windowLeft; + this._calculator.windowRight = this._overviewPane.windowRight; + + for (var i = 0; i < this._rootRecord.children.length; ++i) + this._calculator.updateBoundaries(this._rootRecord.children[i]); - for (var i = 0; i < this._records.length; ++i) - this._calculator.updateBoundaries(this._records[i]); + this._calculator.calculateWindow(); + }, - this._calculator.calculateWindow(); + _addToRecordsWindow: function(record, recordsWindow, parentIsCollapsed) + { + if (!this._calculator._showShortEvents && !record.isLong()) + return; + var percentages = this._calculator.computeBarGraphPercentages(record); + if (percentages.start < 100 && percentages.endWithChildren >= 0 && !record.category.hidden) { + ++this._rootRecord._visibleRecordsCount; + ++record.parent._invisibleChildrenCount; + if (!parentIsCollapsed) + recordsWindow.push(record); } + var index = recordsWindow.length; + record._invisibleChildrenCount = 0; + for (var i = 0; i < record.children.length; ++i) + this._addToRecordsWindow(record.children[i], recordsWindow, parentIsCollapsed || record.collapsed); + record._visibleChildrenCount = recordsWindow.length - index; + }, + + _filterRecords: function() + { var recordsInWindow = []; - for (var i = 0; i < this._records.length; ++i) { - var record = this._records[i]; - var percentages = this._calculator.computeBarGraphPercentages(record); - if (percentages.start < 100 && percentages.end >= 0 && !record.category.hidden) - this._addToRecordsWindow(record, recordsInWindow); - } + this._rootRecord._visibleRecordsCount = 0; + for (var i = 0; i < this._rootRecord.children.length; ++i) + this._addToRecordsWindow(this._rootRecord.children[i], recordsInWindow); + return recordsInWindow; + }, + + _refreshRecords: function(updateBoundaries) + { + if (updateBoundaries) + this._updateBoundaries(); + + var recordsInWindow = this._filterRecords(); // Calculate the visible area. - var visibleTop = this._containerElement.scrollTop; + this._scrollTop = this._containerElement.scrollTop; + var visibleTop = this._scrollTop; var visibleBottom = visibleTop + this._containerElement.clientHeight; // Define row height, should be in sync with styles for timeline graphs. const rowHeight = 18; - const expandOffset = 15; // Convert visible area to visible indexes. Always include top-level record for a visible nested record. var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - 1, recordsInWindow.length - 1)); - while (startIndex > 0 && recordsInWindow[startIndex].parent) - startIndex--; var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight)); - while (endIndex < recordsInWindow.length - 1 && recordsInWindow[endIndex].parent) - endIndex++; // Resize gaps first. const top = (startIndex * rowHeight) + "px"; @@ -378,6 +493,7 @@ WebInspector.TimelinePanel.prototype = { this._itemsGraphsElement.removeChild(this._graphRowsElement); var graphRowElement = this._graphRowsElement.firstChild; var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true); + for (var i = startIndex; i < endIndex; ++i) { var record = recordsInWindow[i]; var isEven = !(i % 2); @@ -391,8 +507,8 @@ WebInspector.TimelinePanel.prototype = { this._graphRowsElement.appendChild(graphRowElement); } - listRowElement.listRow.update(record, isEven); - graphRowElement.graphRow.update(record, isEven, this._calculator, width, expandOffset, i); + listRowElement.row.update(record, isEven, this._calculator, visibleTop); + graphRowElement.row.update(record, isEven, this._calculator, width, this._expandOffset, i); listRowElement = listRowElement.nextSibling; graphRowElement = graphRowElement.nextSibling; @@ -401,45 +517,52 @@ WebInspector.TimelinePanel.prototype = { // Remove extra rows. while (listRowElement) { var nextElement = listRowElement.nextSibling; - listRowElement.listRow.dispose(); + listRowElement.row.dispose(); listRowElement = nextElement; } while (graphRowElement) { var nextElement = graphRowElement.nextSibling; - graphRowElement.graphRow.dispose(); + graphRowElement.row.dispose(); graphRowElement = nextElement; } this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement); + this.sidebarResizeElement.style.height = this.sidebarElement.clientHeight + "px"; // Reserve some room for expand / collapse controls to the left for records that start at 0ms. - var timelinePaddingLeft = this._calculator.windowLeft === 0 ? expandOffset : 0; + var timelinePaddingLeft = this._calculator.windowLeft === 0 ? this._expandOffset : 0; if (updateBoundaries) this._timelineGrid.updateDividers(true, this._calculator, timelinePaddingLeft); this._adjustScrollPosition((recordsInWindow.length + 1) * rowHeight); }, - _addToRecordsWindow: function(record, recordsWindow) - { - recordsWindow.push(record); - if (!record.collapsed) { - var index = recordsWindow.length; - for (var i = 0; record.children && i < record.children.length; ++i) - this._addToRecordsWindow(record.children[i], recordsWindow); - record.visibleChildrenCount = recordsWindow.length - index; - } - }, - _adjustScrollPosition: function(totalHeight) { // Prevent the container from being scrolled off the end. if ((this._containerElement.scrollTop + this._containerElement.offsetHeight) > totalHeight + 1) this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight); + }, + + _getPopoverAnchor: function(element) + { + return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || element.enclosingNodeOrSelfWithClass("timeline-tree-item"); + }, + + _showPopover: function(anchor) + { + var record = anchor.row._record; + var popover = new WebInspector.Popover(record._generatePopupContent(this._calculator)); + popover.show(anchor); + return popover; + }, + + _closeRecordDetails: function() + { + this._popoverHelper.hidePopup(); } } WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype; - WebInspector.TimelineCategory = function(name, title, color) { this.name = name; @@ -447,7 +570,6 @@ WebInspector.TimelineCategory = function(name, title, color) this.color = color; } - WebInspector.TimelineCalculator = function() { this.reset(); @@ -461,7 +583,22 @@ WebInspector.TimelineCalculator.prototype = { { var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100; var end = (record.endTime - this.minimumBoundary) / this.boundarySpan * 100; - return {start: start, end: end}; + var endWithChildren = (record._lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100; + return {start: start, end: end, endWithChildren: endWithChildren}; + }, + + computeBarGraphWindowPosition: function(record, clientWidth) + { + const minWidth = 5; + const borderWidth = 4; + var workingArea = clientWidth - minWidth - borderWidth; + var percentages = this.computeBarGraphPercentages(record); + var left = percentages.start / 100 * workingArea; + var width = (percentages.end - percentages.start) / 100 * workingArea + minWidth; + var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * workingArea; + if (percentages.endWithChildren > percentages.end) + widthWithChildren += borderWidth + minWidth; + return {left: left, width: width, widthWithChildren: widthWithChildren}; }, calculateWindow: function() @@ -483,7 +620,9 @@ WebInspector.TimelineCalculator.prototype = { if (this._absoluteMinimumBoundary === -1 || lowerBound < this._absoluteMinimumBoundary) this._absoluteMinimumBoundary = lowerBound; - var upperBound = record.endTime; + const minimumTimeFrame = 0.1; + const minimumDeltaForZeroSizeEvents = 0.01; + var upperBound = Math.max(record._lastChildEndTime + minimumDeltaForZeroSizeEvents, lowerBound + minimumTimeFrame); if (this._absoluteMaximumBoundary === -1 || upperBound > this._absoluteMaximumBoundary) this._absoluteMaximumBoundary = upperBound; }, @@ -498,7 +637,8 @@ WebInspector.TimelineCalculator.prototype = { WebInspector.TimelineRecordListRow = function() { this.element = document.createElement("div"); - this.element.listRow = this; + this.element.row = this; + this.element.style.cursor = "pointer"; var iconElement = document.createElement("span"); iconElement.className = "timeline-tree-icon"; this.element.appendChild(iconElement); @@ -523,18 +663,19 @@ WebInspector.TimelineRecordListRow = function() } WebInspector.TimelineRecordListRow.prototype = { - update: function(record, isEven) + update: function(record, isEven, calculator, offset) { + this._record = record; + this._calculator = calculator; + this._offset = offset; + this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : ""); this._typeElement.textContent = record.title; - if (record.details) { + if (record.details) this._dataElement.textContent = "(" + record.details + ")"; - this._dataElement.title = record.details; - } else { + else this._dataElement.textContent = ""; - this._dataElement.title = ""; - } if (record.count > 1) this._repeatCountElement.textContent = "\u2009\u00d7\u2009" + record.count; @@ -548,18 +689,23 @@ WebInspector.TimelineRecordListRow.prototype = { } } - -WebInspector.TimelineRecordGraphRow = function(graphContainer, refreshCallback, rowHeight) +WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh, rowHeight) { this.element = document.createElement("div"); - this.element.graphRow = this; + this.element.row = this; this._barAreaElement = document.createElement("div"); this._barAreaElement.className = "timeline-graph-bar-area"; this.element.appendChild(this._barAreaElement); + this._barWithChildrenElement = document.createElement("div"); + this._barWithChildrenElement.className = "timeline-graph-bar with-children"; + this._barWithChildrenElement.row = this; + this._barAreaElement.appendChild(this._barWithChildrenElement); + this._barElement = document.createElement("div"); this._barElement.className = "timeline-graph-bar"; + this._barElement.row = this; this._barAreaElement.appendChild(this._barElement); this._expandElement = document.createElement("div"); @@ -571,8 +717,9 @@ WebInspector.TimelineRecordGraphRow = function(graphContainer, refreshCallback, this._expandElement.appendChild(leftBorder); this._expandElement.addEventListener("click", this._onClick.bind(this)); - this._refreshCallback = refreshCallback; this._rowHeight = rowHeight; + + this._scheduleRefresh = scheduleRefresh; } WebInspector.TimelineRecordGraphRow.prototype = { @@ -580,18 +727,18 @@ WebInspector.TimelineRecordGraphRow.prototype = { { this._record = record; this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : ""); - var percentages = calculator.computeBarGraphPercentages(record); - var left = percentages.start / 100 * clientWidth; - var width = (percentages.end - percentages.start) / 100 * clientWidth; - this._barElement.style.left = (left + expandOffset) + "px"; - this._barElement.style.width = width + "px"; + var barPosition = calculator.computeBarGraphWindowPosition(record, clientWidth - expandOffset); + this._barWithChildrenElement.style.left = barPosition.left + expandOffset + "px"; + this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px"; + this._barElement.style.left = barPosition.left + expandOffset + "px"; + this._barElement.style.width = barPosition.width + "px"; - if (record.visibleChildrenCount) { + if (record._visibleChildrenCount || record._invisibleChildrenCount) { this._expandElement.style.top = index * this._rowHeight + "px"; - this._expandElement.style.left = left + "px"; - this._expandElement.style.width = Math.max(12, width + 25) + "px"; + this._expandElement.style.left = barPosition.left + "px"; + this._expandElement.style.width = Math.max(12, barPosition.width + 25) + "px"; if (!record.collapsed) { - this._expandElement.style.height = (record.visibleChildrenCount + 1) * this._rowHeight + "px"; + this._expandElement.style.height = (record._visibleChildrenCount + 1) * this._rowHeight + "px"; this._expandElement.addStyleClass("timeline-expandable-expanded"); this._expandElement.removeStyleClass("timeline-expandable-collapsed"); } else { @@ -608,7 +755,7 @@ WebInspector.TimelineRecordGraphRow.prototype = { _onClick: function(event) { this._record.collapsed = !this._record.collapsed; - this._refreshCallback(); + this._scheduleRefresh(); }, dispose: function() @@ -617,3 +764,199 @@ WebInspector.TimelineRecordGraphRow.prototype = { this._expandElement.parentElement.removeChild(this._expandElement); } } + +WebInspector.TimelinePanel.FormattedRecord = function(record, parentRecord, recordStyles, sendRequestRecords, timerRecords) +{ + var recordTypes = WebInspector.TimelineAgent.RecordType; + var style = recordStyles[record.type]; + + this.parent = parentRecord; + parentRecord.children.push(this); + this.category = style.category; + this.title = style.title; + this.startTime = record.startTime / 1000; + this.data = record.data; + this.count = 1; + this.type = record.type; + this.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : this.startTime; + this._lastChildEndTime = this.endTime; + this.originalRecordForTests = record; + this.callerScriptName = record.callerScriptName; + this.callerScriptLine = record.callerScriptLine; + this.totalHeapSize = record.totalHeapSize; + this.usedHeapSize = record.usedHeapSize; + + // Make resource receive record last since request was sent; make finish record last since response received. + if (record.type === recordTypes.ResourceSendRequest) { + sendRequestRecords[record.data.identifier] = this; + } else if (record.type === recordTypes.ResourceReceiveResponse) { + var sendRequestRecord = sendRequestRecords[record.data.identifier]; + if (sendRequestRecord) { // False if we started instrumentation in the middle of request. + record.data.url = sendRequestRecord.data.url; + // Now that we have resource in the collection, recalculate details in order to display short url. + sendRequestRecord.details = this._getRecordDetails(sendRequestRecord, sendRequestRecords); + } + } else if (record.type === recordTypes.ResourceReceiveData) { + var sendRequestRecord = sendRequestRecords[record.data.identifier]; + if (sendRequestRecord) // False for main resource. + record.data.url = sendRequestRecord.data.url; + } else if (record.type === recordTypes.ResourceFinish) { + var sendRequestRecord = sendRequestRecords[record.data.identifier]; + if (sendRequestRecord) // False for main resource. + record.data.url = sendRequestRecord.data.url; + } else if (record.type === recordTypes.TimerInstall) { + this.timeout = record.data.timeout; + this.singleShot = record.data.singleShot; + timerRecords[record.data.timerId] = this; + } else if (record.type === recordTypes.TimerFire) { + var timerInstalledRecord = timerRecords[record.data.timerId]; + if (timerInstalledRecord) { + this.callSiteScriptName = timerInstalledRecord.callerScriptName; + this.callSiteScriptLine = timerInstalledRecord.callerScriptLine; + this.timeout = timerInstalledRecord.timeout; + this.singleShot = timerInstalledRecord.singleShot; + } + } + this.details = this._getRecordDetails(record, sendRequestRecords); +} + +WebInspector.TimelinePanel.FormattedRecord.prototype = { + isLong: function() + { + return (this._lastChildEndTime - this.startTime) > WebInspector.TimelinePanel.shortRecordThreshold; + }, + + _createCell: function(content, styleName) + { + var text = document.createElement("label"); + text.appendChild(document.createTextNode(content)); + var cell = document.createElement("td"); + cell.className = "timeline-details"; + if (styleName) + cell.className += " " + styleName; + cell.textContent = content; + return cell; + }, + + get children() + { + if (!this._children) + this._children = []; + return this._children; + }, + + _createRow: function(title, content) + { + var row = document.createElement("tr"); + row.appendChild(this._createCell(title, "timeline-details-row-title")); + row.appendChild(this._createCell(content, "timeline-details-row-data")); + return row; + }, + + _createLinkRow: function(title, content) + { + var row = document.createElement("tr"); + row.appendChild(this._createCell(title, "timeline-details-row-title")); + var cell = document.createElement("td"); + cell.appendChild(content); + row.appendChild(cell); + return row; + }, + + _generatePopupContent: function(calculator) + { + var recordContentTable = document.createElement("table"); + var titleCell = this._createCell(WebInspector.UIString("%s - Details", this.title), "timeline-details-title"); + titleCell.colSpan = 2; + var titleRow = document.createElement("tr"); + titleRow.appendChild(titleCell); + recordContentTable.appendChild(titleRow); + var text = Number.secondsToString(this.endTime - this.startTime) + " (@" + + calculator.formatValue(this.startTime - calculator.minimumBoundary) + ")"; + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Duration"), text)); + + const recordTypes = WebInspector.TimelineAgent.RecordType; + if (this.details) { + if (this.type === recordTypes.GCEvent ) + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data.usedHeapSizeDelta))); + else if (this.type === recordTypes.TimerInstall || + this.type === recordTypes.TimerFire || + this.type === recordTypes.TimerRemove) { + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Timer Id"), this.data.timerId)); + if (typeof this.timeout === "number") { + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Timeout"), this.timeout)); + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Repeats"), !this.singleShot)); + } + if (typeof this.callSiteScriptLine === "number") { + var link = WebInspector.linkifyResourceAsNode(this.callSiteScriptName, "scripts", this.callSiteScriptLine, "timeline-details"); + recordContentTable.appendChild(this._createLinkRow(WebInspector.UIString("Call Site"), link)); + } + } else if (this.type === recordTypes.FunctionCall) { + var link = WebInspector.linkifyResourceAsNode(this.data.scriptName, "scripts", this.data.scriptLine, "timeline-details"); + recordContentTable.appendChild(this._createLinkRow(WebInspector.UIString("Location"), link)); + } else if (this.type === recordTypes.ResourceSendRequest || + this.type === recordTypes.ResourceReceiveResponse || + this.type === recordTypes.ResourceReceiveData || + this.type === recordTypes.ResourceFinish) { + var link = WebInspector.linkifyResourceAsNode(this.data.url, "resources", null, "timeline-details"); + recordContentTable.appendChild(this._createLinkRow(WebInspector.UIString("Resource"), link)); + if (this.data.requestMethod) + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Request Method"), this.data.requestMethod)); + if (typeof this.data.statusCode === "number") + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Status Code"), this.data.statusCode)); + if (this.data.mimeType) + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Mime Type"), this.data.mimeType)); + if (typeof this.data.expectedContentLength === "number" && this.data.expectedContentLength !== -1) + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Expected Content Length"), this.data.expectedContentLength)); + } else if (this.type === recordTypes.EvaluateScript) { + var link = WebInspector.linkifyResourceAsNode(this.data.url, "scripts", null, "timeline-details"); + recordContentTable.appendChild(this._createLinkRow(WebInspector.UIString("Script"), link)); + } else if (this.type === recordTypes.Paint) { + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Location"), this.data.x + "\u2009\u00d7\u2009" + this.data.y)); + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Dimensions"), this.data.width + "\u2009\u00d7\u2009" + this.data.height)); + } else + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Details"), this.details)); + } + + if (this.type !== recordTypes.GCEvent && this.callerScriptName) { + var link = WebInspector.linkifyResourceAsNode(this.callerScriptName, "scripts", this.callerScriptLine); + recordContentTable.appendChild(this._createLinkRow(WebInspector.UIString("Caller"), link)); + } + if (this.usedHeapSize) { + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize))); + recordContentTable.appendChild(this._createRow(WebInspector.UIString("Total Heap Size"), Number.bytesToString(this.totalHeapSize))); + } + return recordContentTable; + }, + + _getRecordDetails: function(record, sendRequestRecords) + { + switch (record.type) { + case WebInspector.TimelineAgent.RecordType.GCEvent: + return WebInspector.UIString("%s collected", Number.bytesToString(record.data.usedHeapSizeDelta)); + case WebInspector.TimelineAgent.RecordType.FunctionCall: + return WebInspector.displayNameForURL(record.data.scriptName) + ":" + record.data.scriptLine; + case WebInspector.TimelineAgent.RecordType.EventDispatch: + return record.data ? record.data.type : ""; + case WebInspector.TimelineAgent.RecordType.Paint: + return record.data.width + "\u2009\u00d7\u2009" + record.data.height; + case WebInspector.TimelineAgent.RecordType.TimerInstall: + case WebInspector.TimelineAgent.RecordType.TimerRemove: + case WebInspector.TimelineAgent.RecordType.TimerFire: + return record.data.timerId; + case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange: + case WebInspector.TimelineAgent.RecordType.XHRLoad: + case WebInspector.TimelineAgent.RecordType.EvaluateScript: + case WebInspector.TimelineAgent.RecordType.ResourceSendRequest: + case WebInspector.TimelineAgent.RecordType.ResourceReceiveData: + case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse: + case WebInspector.TimelineAgent.RecordType.ResourceFinish: + return WebInspector.displayNameForURL(record.data.url); + case WebInspector.TimelineAgent.RecordType.MarkTimeline: + return record.data.message; + default: + return ""; + } + } +} + diff --git a/WebCore/inspector/front-end/WebKit.qrc b/WebCore/inspector/front-end/WebKit.qrc index 73309d1..47a2e90 100644 --- a/WebCore/inspector/front-end/WebKit.qrc +++ b/WebCore/inspector/front-end/WebKit.qrc @@ -13,6 +13,7 @@ <file>Callback.js</file> <file>CallStackSidebarPane.js</file> <file>ChangesView.js</file> + <file>Checkbox.js</file> <file>Color.js</file> <file>ConsolePanel.js</file> <file>ConsoleView.js</file> @@ -32,6 +33,7 @@ <file>EventListenersSidebarPane.js</file> <file>FontView.js</file> <file>ImageView.js</file> + <file>InjectedFakeWorker.js</file> <file>InjectedScript.js</file> <file>InjectedScriptAccess.js</file> <file>inspector.js</file> @@ -88,12 +90,20 @@ <file>View.js</file> <file>WatchExpressionsSidebarPane.js</file> <file>WelcomeView.js</file> + <file>WorkersSidebarPane.js</file> <file>audits.css</file> <file>inspector.css</file> <file>inspectorSyntaxHighlight.css</file> <file>popover.css</file> <file>textViewer.css</file> + <file>Images/auditsIcon.png</file> <file>Images/back.png</file> + <file>Images/breakpointBorder.png</file> + <file>Images/breakpointConditionalBorder.png</file> + <file>Images/breakpointConditionalCounterBorder.png</file> + <file>Images/breakpointCounterBorder.png</file> + <file>Images/breakpointsActivateButtonGlyph.png</file> + <file>Images/breakpointsDeactivateButtonGlyph.png</file> <file>Images/checker.png</file> <file>Images/clearConsoleButtonGlyph.png</file> <file>Images/closeButtons.png</file> @@ -151,6 +161,7 @@ <file>Images/profilesIcon.png</file> <file>Images/profileSmallIcon.png</file> <file>Images/profilesSilhouette.png</file> + <file>Images/programCounterBorder.png</file> <file>Images/radioDot.png</file> <file>Images/recordButtonGlyph.png</file> <file>Images/recordToggledButtonGlyph.png</file> @@ -178,6 +189,7 @@ <file>Images/segmentSelected.png</file> <file>Images/segmentSelectedEnd.png</file> <file>Images/sessionStorage.png</file> + <file>Images/spinner.gif</file> <file>Images/splitviewDimple.png</file> <file>Images/splitviewDividerBackground.png</file> <file>Images/statusbarBackground.png</file> diff --git a/WebCore/inspector/front-end/WorkersSidebarPane.js b/WebCore/inspector/front-end/WorkersSidebarPane.js new file mode 100644 index 0000000..ed2b1c4 --- /dev/null +++ b/WebCore/inspector/front-end/WorkersSidebarPane.js @@ -0,0 +1,114 @@ +/* + * 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.WorkersSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Workers")); + + this._workers = {}; + + this._enableWorkersCheckbox = new WebInspector.Checkbox( + WebInspector.UIString("Debug"), + this._onTriggerInstrument.bind(this), + false, + "sidebar-pane-subtitle", + WebInspector.UIString("Allow debugging workers. Enabling this option will replace native workers with the iframe-based JavaScript implementation")); + + this.titleElement.insertBefore(this._enableWorkersCheckbox.element, this.titleElement.firstChild); + + this._listElement = document.createElement("ol"); + this._listElement.className = "workers-list"; + + this.bodyElement.appendChild(this._listElement); + this._treeOutline = new TreeOutline(this._listElement); +} + +WebInspector.WorkersSidebarPane.prototype = { + addWorker: function(id, url, isShared) + { + if (id in this._workers) + return; + var worker = new WebInspector.Worker(id, url, isShared); + this._workers[id] = worker; + + var title = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url), "worker-item", true, url); + var treeElement = new TreeElement(title, worker, false); + this._treeOutline.appendChild(treeElement); + }, + + removeWorker: function(id) + { + if (id in this._workers) { + this._treeOutline.removeChild(this._treeOutline.findTreeElement(this._workers[id])); + delete this._workers[id]; + } + }, + + setInstrumentation: function(enabled) + { + InspectorBackend.removeAllScriptsToEvaluateOnLoad(); + if (enabled) + InspectorBackend.addScriptToEvaluateOnLoad("(" + InjectedFakeWorker + ")"); + }, + + reset: function() + { + InspectorBackend.removeAllScriptsToEvaluateOnLoad(); + this.setInstrumentation(this._enableWorkersCheckbox.checked()); + this._treeOutline.removeChildren(); + this._workers = {}; + }, + + _onTriggerInstrument: function(event) + { + this.setInstrumentation(this._enableWorkersCheckbox.checked()); + } +}; + +WebInspector.WorkersSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.Worker = function(id, url, shared) +{ + this.id = id; + this.url = url; + this.shared = shared; +} + +WebInspector.didCreateWorker = function() +{ + var workersPane = WebInspector.panels.scripts.sidebarPanes.workers; + workersPane.addWorker.apply(workersPane, arguments); +} + +WebInspector.didDestroyWorker = function() +{ + var workersPane = WebInspector.panels.scripts.sidebarPanes.workers; + workersPane.removeWorker.apply(workersPane, arguments); +} diff --git a/WebCore/inspector/front-end/audits.css b/WebCore/inspector/front-end/audits.css index 9d02c80..ad12db3 100644 --- a/WebCore/inspector/front-end/audits.css +++ b/WebCore/inspector/front-end/audits.css @@ -50,104 +50,6 @@ button.clear-audit-results-status-bar-item .glyph { -webkit-mask-image: url(Images/clearConsoleButtonGlyph.png); } -#audit-result-view { - display: none; - overflow: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgb(245, 245, 245); - cursor: default; - overflow: auto; -} - -#audit-result-view.visible { - display: block; -} - -#audit-result-view > .pane img.score { - float: left; - margin-top: 2px; - position: relative; - height: 16px; - width: 16px; - z-index: 100; -} - -#audit-result-view > .pane img.score.red { - content: url(Images/errorRedDot.png); -} - -#audit-result-view > .pane img.score.green { - content: url(Images/successGreenDot.png); -} - -#audit-result-view > .pane.expanded:nth-last-of-type(1) { - border-bottom: 1px solid rgb(189, 189, 189) !important; -} - -#audit-result-view .pane.expanded:nth-last-of-type(1) { - border-bottom: 0px transparent none; -} - -#audit-result-view > .pane > .body > .pane > .title { - padding-left: 16px; - background-image: none; - border-bottom: none; -} - -#audit-result-view > .pane > .body > .pane > .body { - background-color: transparent; -} - -#audit-result-view > .pane > .body > .pane .section { - margin-left: 16px; -} - -#audit-result-view .section .header { - border: 0; - background-image: none; - background-color: transparent; -} - -#audit-result-view .section .header > .title { - color: rgb(0, 0, 0); -} - -#audit-result-view .section .section-content { - width: 100%; - padding-left: 18px; - display: none; -} - -#audit-result-view .section.expanded .section-content { - display: block; -} - -#audit-result-view .section.expanded .section-content > p:nth-of-type(1) { - margin-top: 0; -} - -#audit-result-view .section.expanded .section-content > p:nth-of-type(1) > *:nth-child(1) { - margin-top: 0; -} - -#audit-result-view .section .header::before { - content: url(Images/treeRightTriangleBlack.png); -} - -#audit-result-view .section.expanded .header::before { - content: url(Images/treeDownTriangleBlack.png); -} - -div.panel.audits .sidebar > ol.sidebar-tree > li:nth-child(1) { - height: 0px; - padding-top: 0; - padding-bottom: 0; -} - .audit-launcher-view { z-index: 1000; position: absolute; @@ -174,6 +76,8 @@ div.panel.audits .sidebar > ol.sidebar-tree > li:nth-child(1) { bottom: 0; padding: 0 0 0 16px; white-space: nowrap; + display: -webkit-box; + -webkit-box-orient: vertical; } .audit-launcher-view h1 { @@ -192,10 +96,13 @@ div.panel.audits .sidebar > ol.sidebar-tree > li:nth-child(1) { } .audit-launcher-view div.button-container { - position: absolute; + display: -webkit-box; + -webkit-box-orient: vertical; width: 100%; - bottom: 16px; - padding-top: 16px; + padding: 16px 0; +} +.audit-launcher-view .flexible-space { + -webkit-box-flex: 1; } .audit-launcher-view div.audit-categories-container { @@ -270,3 +177,109 @@ body.inactive .audit-launcher-view button, .audit-launcher-view button: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))); } + +.audit-launcher-view .resource-progress > img { + content: url(Images/spinner.gif); + vertical-align: text-top; + margin: 0 4px 0 8px; +} + +.audit-result-view { + overflow: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: none; +} + +.audit-result-view.visible { + display: block; +} + +.audit-result-view .severity-severe { + content: url(Images/errorRedDot.png); +} + +.audit-result-view .severity-warning { + content: url(Images/warningOrangeDot.png); +} + +.audit-result-view .severity-info { + content: url(Images/successGreenDot.png); +} + +.audit-result-tree li.parent::before { + content: url(Images/treeRightTriangleBlack.png); + float: left; + width: 8px; + height: 8px; + margin-top: 1px; + padding-right: 2px; +} + +.audit-result-tree { + font-size: 11px; + line-height: 14px; + -webkit-user-select: text; +} + +.audit-result-tree > ol { + position: relative; + padding: 2px 6px !important; + margin: 0; + color: rgb(84, 84, 84); + cursor: default; + min-width: 100%; +} + +.audit-result-tree, .audit-result-tree ol { + list-style-type: none; + -webkit-padding-start: 12px; + margin: 0; +} + +.audit-result-tree li { + padding: 0 0 0 14px; + margin-top: 1px; + margin-bottom: 1px; + word-wrap: break-word; + text-indent: -2px; +} + +.audit-result-tree li.parent { + text-indent: -12px +} + +.audit-result-tree li.parent::before { + content: url(Images/treeRightTriangleBlack.png); + float: left; + width: 8px; + height: 8px; + margin-top: 0; + padding-right: 2px; +} + +.audit-result-tree li.parent.expanded::before { + content: url(Images/treeDownTriangleBlack.png); +} + +.audit-result-tree ol.children { + display: none; +} + +.audit-result-tree ol.children.expanded { + display: block; +} + +.audit-result { + font-weight: bold; + color: black; +} + +.audit-result img { + float: left; + margin-left: -40px; + margin-top: -1px; +} diff --git a/WebCore/inspector/front-end/inspector.css b/WebCore/inspector/front-end/inspector.css index 76b31fc..581c75a 100644 --- a/WebCore/inspector/front-end/inspector.css +++ b/WebCore/inspector/front-end/inspector.css @@ -219,6 +219,10 @@ body.attached #search-results-matches { background-image: url(Images/profilesIcon.png); } +.toolbar-item.audits .toolbar-icon { + background-image: url(Images/auditsIcon.png); +} + .toolbar-item.console .toolbar-icon { background-image: url(Images/consoleIcon.png); } @@ -520,6 +524,11 @@ body.platform-windows .monospace, body.platform-windows .source-code { font-family: Consolas, Lucida Console, monospace; } +body.platform-linux .monospace, body.platform-linux .source-code { + font-size: 11px; + font-family: dejavu sans mono, monospace; +} + #console-messages { position: absolute; z-index: 0; @@ -877,6 +886,7 @@ body.platform-windows .monospace, body.platform-windows .source-code { right: 0; left: 0; bottom: 0; + overflow: auto; } .resource-view.headers-visible .resource-view-content { @@ -969,7 +979,7 @@ body.platform-windows .monospace, body.platform-windows .source-code { position: absolute; top: 0; left: 0; - right: 225px; + right: 300px; bottom: 0; } @@ -978,7 +988,7 @@ body.platform-windows .monospace, body.platform-windows .source-code { top: 0; right: 0; bottom: 0; - width: 225px; + width: 300px; background-color: rgb(245, 245, 245); border-left: 1px solid rgb(64%, 64%, 64%); cursor: default; @@ -1323,15 +1333,6 @@ body.inactive .placard.selected { display: inline; } -.section .header input[type=checkbox] { - height: 10px; - width: 10px; - margin-left: 0; - margin-top: 0; - margin-bottom: 0; - vertical-align: 2px; -} - .section .header .subtitle, .event-bar .header .subtitle { float: right; font-size: 10px; @@ -1496,6 +1497,10 @@ body.inactive .placard.selected { text-decoration: none !important; } +.editing br { + display: none; +} + .elements-tree-editor { -webkit-user-select: text; -webkit-user-modify: read-write-plaintext-only; @@ -1571,6 +1576,10 @@ li.editing .swatch, li.editing .enabled-button, li.editing-sub-part .delete-but display: block; } +.section .properties li.disabled .enabled-button { + display: block; +} + .section .properties .name, .event-properties .name { color: rgb(136, 19, 145); } @@ -1653,7 +1662,6 @@ li.editing .swatch, li.editing .enabled-button, li.editing-sub-part .delete-but } .pane > .title > select { - display: none; float: right; width: 23px; height: 17px; @@ -1668,10 +1676,6 @@ li.editing .swatch, li.editing .enabled-button, li.editing-sub-part .delete-but -webkit-appearance: none; } -.pane.expanded:hover > .title > select { - display: inline-block; -} - .pane > .title > select:hover { background-position: -23px 0px; } @@ -1716,6 +1720,21 @@ li.editing .swatch, li.editing .enabled-button, li.editing-sub-part .delete-but height: 5px; } +.sidebar-pane-subtitle { + float: right; + overflow: hidden; +} + +.sidebar-pane-subtitle input, .section .header input[type=checkbox] { + font-size: inherit; + hight: 1em; + width: 1em; + margin-left: 0; + margin-top: 0; + margin-bottom: 0.25em; + vertical-align: bottom; +} + .metrics { padding: 8px; font-size: 10px; @@ -2287,7 +2306,6 @@ button.show-all-nodes { .panel-enabler-view.welcome .instructions { display: inline-block; vertical-align: middle; - width: 330px; margin: 0; white-space: normal; line-height: 175%; @@ -2298,7 +2316,8 @@ button.show-all-nodes { } .panel-enabler-view.welcome button.status-bar-item { - vertical-align: middle; + background-image: none; + vertical-align: top; } .pane button { @@ -2410,6 +2429,15 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { content: url(Images/debuggerStepOut.png); } +.toggle-breakpoints .glyph { + -webkit-mask-image: url(Images/breakpointsActivateButtonGlyph.png); + background-color: rgb(96, 96, 96) !important; +} + +.toggle-breakpoints.toggled-on .glyph { + -webkit-mask-image: url(Images/breakpointsDeactivateButtonGlyph.png); +} + #scripts-debugger-status { position: absolute; line-height: 24px; @@ -2639,7 +2667,7 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { margin-right: 3px; } -#resources-dividers { +.resources-dividers { position: absolute; left: 0; right: 0; @@ -2648,7 +2676,7 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { z-index: -100; } -#resources-event-dividers { +.resources-event-dividers { position: absolute; left: 0; right: 0; @@ -2658,7 +2686,11 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { pointer-events: none; } -#resources-dividers-label-bar { +.timeline .resources-event-dividers { + height: 19px; +} + +.resources-dividers-label-bar { position: absolute; top: 0; left: 0px; @@ -2686,24 +2718,26 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { pointer-events: auto; } -.resources-onload-divider { +.resources-event-divider { position: absolute; width: 2px; top: 0; bottom: 0; z-index: 300; +} + +.resources-red-divider { background-color: rgba(255, 0, 0, 0.5); } -.resources-ondomcontent-divider { - position: absolute; - width: 2px; - top: 0; - bottom: 0; - z-index: 300; +.resources-blue-divider { background-color: rgba(0, 0, 255, 0.5); } +.resources-orange-divider { + background-color: rgba(255, 178, 23, 0.5); +} + .resources-divider.last { background-color: transparent; } @@ -2747,6 +2781,10 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { margin-right: 5px; } +.resources-graph-label.waiting-right { + margin-left: 5px; +} + .resources-graph-label.before { color: rgba(0, 0, 0, 0.7); text-shadow: none; @@ -2815,7 +2853,7 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { display: block; } -.resources-graph-bar.waiting { +.resources-graph-bar.waiting, .resources-graph-bar.waiting-right { opacity: 0.35; } @@ -3305,6 +3343,10 @@ body.inactive .sidebar-tree-item.selected .bubble.search-matches { height: 80px; } +#timeline-overview-panel .timeline-graph-bar { + pointer-events: none; +} + .timeline-sidebar-background { top: 90px; bottom: 0; @@ -3558,7 +3600,12 @@ body.inactive .sidebar-tree-item.selected .bubble.search-matches { min-width: 5px; opacity: 0.8; -webkit-border-image: url(Images/timelineBarGray.png) 4 4 5 4; - pointer-events: none; + z-index: 180; + pointer-events: visibleFill; +} + +.timeline-graph-bar.with-children { + opacity: 0.3; } .timeline-graph-side.even { @@ -3589,6 +3636,41 @@ body.inactive .sidebar-tree-item.selected .bubble.search-matches { background-position-y: 72px; } +.timeline-details { + -webkit-user-select: text; +} + +.timeline-details-row-title { + font-weight: bold; + text-align: right; + white-space: nowrap; +} + +.timeline-details-row-data { + white-space: nowrap; +} + +.timeline-details-title { + font-weight: bold; + white-space: nowrap; +} + +.timeline-filter-status-bar-item .glyph { + -webkit-mask-image: url(Images/largerResourcesButtonGlyph.png); +} + +.timeline-filter-status-bar-item.toggled-on .glyph { + background-color: rgb(66, 129, 235) !important; +} + +.timeline-records-counter { + font-size: 11px; + position: relative; + top: 5px; + margin-left: 5px; + text-shadow: white 0 1px 0; +} + /* Profiler Style */ #profile-views { @@ -3824,7 +3906,7 @@ ol.breakpoint-list { } .source-frame-popover-tree { - border-top: 1px solid rgb(190, 190, 190); + border-top: 1px solid rgb(194, 194, 147); overflow: auto; position: absolute; top: 15px; @@ -3832,3 +3914,58 @@ ol.breakpoint-list { left: 0; right: 0; } + +.source-frame-eval-expression { + border: 1px solid rgb(163, 41, 34); + margin: -1px; + background-color: rgb(255, 255, 194); +} + +.styles-sidebar-separator { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(243, 243, 243)), color-stop(0.05, rgb(243, 243, 243)), color-stop(0.05, rgb(230, 230, 230)), to(rgb(209, 209, 209))); + padding: 0 5px; + border-top: 1px solid rgb(189, 189, 189); + border-bottom: 1px solid rgb(189, 189, 189); + color: rgb(110, 110, 110); + text-shadow: white 0 1px 0; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.styles-selector { + cursor: text; + -webkit-user-select: text; +} + +.workers-list { + list-style: none; + margin: 0; + padding: 0; +} + +.workers-list > li { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-left: 1em; + font-size: 12px; +} + +a.worker-item { + color: rgb(33%, 33%, 33%); + cursor: pointer; + text-decoration: none; +} + +a.worker-item:hover { + color: rgb(15%, 15%, 15%); +} + +.resource-content-unavailable { + color: rgb(50%, 50%, 50%); + font-style: italic; + font-size: 14px; + text-align: center; + padding: 32px; +} diff --git a/WebCore/inspector/front-end/inspector.html b/WebCore/inspector/front-end/inspector.html index 85633b5..4fa8ddc 100644 --- a/WebCore/inspector/front-end/inspector.html +++ b/WebCore/inspector/front-end/inspector.html @@ -41,6 +41,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. <script type="text/javascript" src="InspectorFrontendHostStub.js"></script> <script type="text/javascript" src="Object.js"></script> <script type="text/javascript" src="Settings.js"></script> + <script type="text/javascript" src="Checkbox.js"></script> <script type="text/javascript" src="ContextMenu.js"></script> <script type="text/javascript" src="KeyboardShortcut.js"></script> <script type="text/javascript" src="TextPrompt.js"></script> @@ -74,6 +75,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. <script type="text/javascript" src="CallStackSidebarPane.js"></script> <script type="text/javascript" src="ScopeChainSidebarPane.js"></script> <script type="text/javascript" src="WatchExpressionsSidebarPane.js"></script> + <script type="text/javascript" src="WorkersSidebarPane.js"></script> <script type="text/javascript" src="MetricsSidebarPane.js"></script> <script type="text/javascript" src="PropertiesSidebarPane.js"></script> <script type="text/javascript" src="EventListenersSidebarPane.js"></script> @@ -85,6 +87,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. <script type="text/javascript" src="SummaryBar.js"></script> <script type="text/javascript" src="ElementsPanel.js"></script> <script type="text/javascript" src="ResourcesPanel.js"></script> + <script type="text/javascript" src="InjectedFakeWorker.js"></script> <script type="text/javascript" src="ScriptsPanel.js"></script> <script type="text/javascript" src="StoragePanel.js"></script> <script type="text/javascript" src="ProfilesPanel.js"></script> diff --git a/WebCore/inspector/front-end/inspector.js b/WebCore/inspector/front-end/inspector.js index 8bcdf63..c75dd9c 100644 --- a/WebCore/inspector/front-end/inspector.js +++ b/WebCore/inspector/front-end/inspector.js @@ -63,6 +63,7 @@ var WebInspector = { // 4 - ?path // 5 - ?fragment URLRegExp: /^(http[s]?|file):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i, + GenericURLRegExp: /^([^:]+):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i, get platform() { @@ -209,14 +210,10 @@ var WebInspector = { this.panels.profiles = new WebInspector.ProfilesPanel(); this.panels.profiles.registerProfileType(new WebInspector.CPUProfileType()); } - if (hiddenPanels.indexOf("storage") === -1 && hiddenPanels.indexOf("databases") === -1) this.panels.storage = new WebInspector.StoragePanel(); - - // FIXME: Uncomment when ready. - // if (hiddenPanels.indexOf("audits") === -1) - // this.panels.audits = new WebInspector.AuditsPanel(); - + if (Preferences.auditsPanelEnabled && hiddenPanels.indexOf("audits") === -1) + this.panels.audits = new WebInspector.AuditsPanel(); if (hiddenPanels.indexOf("console") === -1) this.panels.console = new WebInspector.ConsolePanel(); }, @@ -239,12 +236,10 @@ var WebInspector = { var body = document.body; if (x) { - InspectorFrontendHost.attach(); body.removeStyleClass("detached"); body.addStyleClass("attached"); dockToggleButton.title = WebInspector.UIString("Undock into separate window."); } else { - InspectorFrontendHost.detach(); body.removeStyleClass("attached"); body.addStyleClass("detached"); dockToggleButton.title = WebInspector.UIString("Dock to main window."); @@ -481,7 +476,6 @@ WebInspector.loaded = function() this.addMainEventListeners(document); - window.addEventListener("unload", this.windowUnload.bind(this), true); window.addEventListener("resize", this.windowResize.bind(this), true); document.addEventListener("focus", this.focusChanged.bind(this), true); @@ -565,11 +559,6 @@ WebInspector.dispatch = function() { setTimeout(delayDispatch, 0); } -WebInspector.windowUnload = function(event) -{ - InspectorFrontendHost.windowUnloading(); -} - WebInspector.windowResize = function(event) { if (this.currentPanel) @@ -607,9 +596,29 @@ WebInspector.setAttachedWindow = function(attached) WebInspector.close = function(event) { + if (this._isClosing) + return; + this._isClosing = true; InspectorFrontendHost.closeWindow(); } +WebInspector.inspectedPageDestroyed = function() +{ + WebInspector.close(); +} + +WebInspector.documentMouseOver = function(event) +{ + if (event.target.tagName !== "A") + return; + + const anchor = event.target; + if (!anchor.hasStyleClass("webkit-html-resource-link")) + return; + if (anchor.href && anchor.href.indexOf("/data:") != -1) + return; +} + WebInspector.documentClick = function(event) { var anchor = event.target.enclosingNodeOrSelfWithNodeName("a"); @@ -618,22 +627,38 @@ WebInspector.documentClick = function(event) // Prevent the link from navigating, since we don't do any navigation by following links normally. event.preventDefault(); + event.stopPropagation(); function followLink() { // FIXME: support webkit-html-external-link links here. - if (WebInspector.canShowSourceLineForURL(anchor.href, anchor.preferredPanel)) { + if (WebInspector.canShowSourceLine(anchor.href, anchor.lineNumber, anchor.preferredPanel)) { if (anchor.hasStyleClass("webkit-html-external-link")) { anchor.removeStyleClass("webkit-html-external-link"); anchor.addStyleClass("webkit-html-resource-link"); } - WebInspector.showSourceLineForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel); - } else { - var profileString = WebInspector.ProfileType.URLRegExp.exec(anchor.href); - if (profileString) - WebInspector.showProfileForURL(anchor.href); + WebInspector.showSourceLine(anchor.href, anchor.lineNumber, anchor.preferredPanel); + return; } + + const profileMatch = WebInspector.ProfileType.URLRegExp.exec(anchor.href); + if (profileMatch) { + WebInspector.showProfileForURL(anchor.href); + return; + } + + const urlMatch = WebInspector.GenericURLRegExp.exec(anchor.href); + if (urlMatch && urlMatch[1] === "webkit-link-action") { + if (urlMatch[2] === "show-panel") { + const panel = urlMatch[4].substring(1); + if (WebInspector.panels[panel]) + WebInspector.currentPanel = WebInspector.panels[panel]; + } + return; + } + + WebInspector.showResourcesPanel(); } if (WebInspector.followLinkTimeout) @@ -652,6 +677,9 @@ WebInspector.documentClick = function(event) WebInspector.documentKeyDown = function(event) { + if (WebInspector.isEditingAnyField()) + return; + if (this.currentFocusElement && this.currentFocusElement.handleKeyEvent) { this.currentFocusElement.handleKeyEvent(event); if (event.handled) { @@ -689,7 +717,13 @@ WebInspector.documentKeyDown = function(event) WebInspector.focusSearchField(); event.preventDefault(); } + break; + case "F3": + if (!isMac) { + WebInspector.focusSearchField(); + event.preventDefault(); + } break; case "U+0047": // G key @@ -743,21 +777,15 @@ WebInspector.documentKeyDown = function(event) break; - case "U+0041": // A key - if (isMac) - var shouldShowAuditsPanel = event.metaKey && !event.shiftKey && !event.ctrlKey && event.altKey; - else - var shouldShowAuditsPanel = event.ctrlKey && !event.shiftKey && !event.metaKey && event.altKey; - - if (shouldShowAuditsPanel) { - 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; + case "U+0052": // R key + if ((event.metaKey && isMac) || (event.ctrlKey && !isMac)) { + InspectorBackend.reloadPage(); + event.preventDefault(); } - + break; + case "F5": + if (!isMac) + InspectorBackend.reloadPage(); break; } } @@ -885,8 +913,10 @@ WebInspector.focusSearchField = function() WebInspector.toggleAttach = function() { - this.attached = !this.attached; - this.drawer.resize(); + if (!this.attached) + InspectorFrontendHost.requestAttachWindow(); + else + InspectorFrontendHost.requestDetachWindow(); } WebInspector.toolbarDragStart = function(event) @@ -1015,6 +1045,11 @@ WebInspector.showConsolePanel = function() this.currentPanel = this.panels.console; } +WebInspector.showAuditsPanel = function() +{ + this.currentPanel = this.panels.audits; +} + WebInspector.clearConsoleMessages = function() { WebInspector.console.clearMessages(); @@ -1041,6 +1076,8 @@ WebInspector.updateResource = function(identifier, payload) this.resourceURLMap[resource.url] = resource; if (this.panels.resources) this.panels.resources.addResource(resource); + if (this.panels.audits) + this.panels.audits.resourceStarted(resource); } if (payload.didRequestChange) { @@ -1057,11 +1094,10 @@ WebInspector.updateResource = function(identifier, payload) if (resource.mainResource) this.mainResource = resource; - var match = payload.documentURL.match(WebInspector.URLRegExp); + var match = payload.documentURL.match(WebInspector.GenericURLRegExp); if (match) { var protocol = match[1].toLowerCase(); - if (protocol.indexOf("http") === 0 || protocol === "file") - this._addCookieDomain(protocol === "file" ? "" : match[2]); + this._addCookieDomain(match[2]); } } @@ -1079,12 +1115,14 @@ WebInspector.updateResource = function(identifier, payload) } if (payload.didLengthChange) { - resource.contentLength = payload.contentLength; + resource.resourceSize = payload.resourceSize; } if (payload.didCompletionChange) { resource.failed = payload.failed; resource.finished = payload.finished; + if (this.panels.audits) + this.panels.audits.resourceFinished(resource); } if (payload.didTimingChange) { @@ -1182,6 +1220,17 @@ WebInspector.resourceTrackingWasDisabled = function() this.panels.resources.resourceTrackingWasDisabled(); } + +WebInspector.searchingForNodeWasEnabled = function() +{ + this.panels.elements.searchingForNodeWasEnabled(); +} + +WebInspector.searchingForNodeWasDisabled = function() +{ + this.panels.elements.searchingForNodeWasDisabled(); +} + WebInspector.attachDebuggerWhenShown = function() { this.panels.scripts.attachDebuggerWhenShown(); @@ -1192,6 +1241,11 @@ WebInspector.debuggerWasEnabled = function() this.panels.scripts.debuggerWasEnabled(); } +WebInspector.updatePauseOnExceptionsState = function(pauseOnExceptionsState) +{ + this.panels.scripts.updatePauseOnExceptionsState(pauseOnExceptionsState); +} + WebInspector.debuggerWasDisabled = function() { this.panels.scripts.debuggerWasDisabled(); @@ -1212,6 +1266,13 @@ WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, starting this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine); } +WebInspector.restoredBreakpoint = function(sourceID, sourceURL, line, enabled, condition) +{ + var breakpoint = new WebInspector.Breakpoint(sourceURL, line, sourceID, condition); + breakpoint.enabled = enabled; + this.panels.scripts.addBreakpoint(breakpoint); +} + WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage) { this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage); @@ -1257,6 +1318,16 @@ WebInspector.reset = function() this.console.clearMessages(); } +WebInspector.bringToFront = function() +{ + InspectorFrontendHost.bringToFront(); +} + +WebInspector.inspectedURLChanged = function(url) +{ + InspectorFrontendHost.inspectedURLChanged(url); +} + WebInspector.resourceURLChanged = function(resource, oldURL) { delete this.resourceURLMap[oldURL]; @@ -1294,7 +1365,7 @@ WebInspector.updateConsoleMessageRepeatCount = function(count) this.console.updateMessageRepeatCount(count); } -WebInspector.log = function(message) +WebInspector.log = function(message, messageLevel) { // remember 'this' for setInterval() callback var self = this; @@ -1348,7 +1419,7 @@ WebInspector.log = function(message) var msg = new WebInspector.ConsoleMessage( WebInspector.ConsoleMessage.MessageSource.Other, WebInspector.ConsoleMessage.MessageType.Log, - WebInspector.ConsoleMessage.MessageLevel.Debug, + messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug, -1, null, null, @@ -1437,7 +1508,19 @@ WebInspector.displayNameForURL = function(url) var resource = this.resourceURLMap[url]; if (resource) return resource.displayName; - return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : ""); + + if (!WebInspector.mainResource) + return url.trimURL(""); + + var lastPathComponent = WebInspector.mainResource.lastPathComponent; + var index = WebInspector.mainResource.url.indexOf(lastPathComponent); + if (index !== -1 && index + lastPathComponent.length === WebInspector.mainResource.url.length) { + var baseURL = WebInspector.mainResource.url.substring(0, index); + if (url.indexOf(baseURL) === 0) + return url.substring(index); + } + + return url.trimURL(WebInspector.mainResource.domain); } WebInspector.resourceForURL = function(url) @@ -1455,27 +1538,27 @@ WebInspector.resourceForURL = function(url) return null; } -WebInspector._choosePanelToShowSourceLineForURL = function(url, preferredPanel) +WebInspector._choosePanelToShowSourceLine = function(url, line, preferredPanel) { preferredPanel = preferredPanel || "resources"; var panel = this.panels[preferredPanel]; - if (panel && panel.canShowSourceLineForURL(url)) + if (panel && panel.canShowSourceLine(url, line)) return panel; panel = this.panels.resources; - return panel.canShowSourceLineForURL(url) ? panel : null; + return panel.canShowSourceLine(url, line) ? panel : null; } -WebInspector.canShowSourceLineForURL = function(url, preferredPanel) +WebInspector.canShowSourceLine = function(url, line, preferredPanel) { - return !!this._choosePanelToShowSourceLineForURL(url, preferredPanel); + return !!this._choosePanelToShowSourceLine(url, line, preferredPanel); } -WebInspector.showSourceLineForURL = function(url, line, preferredPanel) +WebInspector.showSourceLine = function(url, line, preferredPanel) { - this.currentPanel = this._choosePanelToShowSourceLineForURL(url, preferredPanel); + this.currentPanel = this._choosePanelToShowSourceLine(url, line, preferredPanel); if (!this.currentPanel) return false; - this.currentPanel.showSourceLineForURL(url, line); + this.currentPanel.showSourceLine(url, line); return true; } @@ -1540,6 +1623,17 @@ WebInspector.linkifyURL = function(url, linkText, classes, isExternal, tooltipTe return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal, tooltipText).outerHTML; } +WebInspector.linkifyResourceAsNode = function(url, preferredPanel, lineNumber, classes, tooltipText) +{ + var linkText = WebInspector.displayNameForURL(url); + if (lineNumber) + linkText += ":" + lineNumber; + var node = WebInspector.linkifyURLAsNode(url, linkText, classes, false, tooltipText); + node.lineNumber = lineNumber; + node.preferredPanel = preferredPanel; + return node; +} + WebInspector.completeURL = function(baseURL, href) { var match = baseURL.match(WebInspector.URLRegExp); @@ -1559,6 +1653,7 @@ WebInspector.addMainEventListeners = function(doc) doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false); doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false); doc.addEventListener("click", this.documentClick.bind(this), true); + doc.addEventListener("mouseover", this.documentMouseOver.bind(this), true); } WebInspector._searchFieldManualFocus = function(event) @@ -1721,11 +1816,17 @@ WebInspector.isBeingEdited = function(element) return element.__editing; } +WebInspector.isEditingAnyField = function() +{ + return this.__editing; +} + WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context, multiline) { if (element.__editing) return; element.__editing = true; + WebInspector.__editing = true; var oldText = getContent(element); var moveDirection = ""; @@ -1749,6 +1850,7 @@ WebInspector.startEditing = function(element, committedCallback, cancelledCallba function cleanUpAfterEditing() { delete this.__editing; + delete WebInspector.__editing; this.removeStyleClass("editing"); this.tabIndex = oldTabIndex; @@ -1801,6 +1903,10 @@ WebInspector.startEditing = function(element, committedCallback, cancelledCallba element.addEventListener("keydown", keyDownEventListener, true); WebInspector.currentFocusElement = element; + return { + cancel: editingCancelled.bind(element), + commit: editingCommitted.bind(element) + }; } WebInspector._toolbarItemClicked = function(event) diff --git a/WebCore/inspector/front-end/textViewer.css b/WebCore/inspector/front-end/textViewer.css index 1447dfd..b69545f 100644 --- a/WebCore/inspector/front-end/textViewer.css +++ b/WebCore/inspector/front-end/textViewer.css @@ -10,7 +10,6 @@ .text-editor-lines { border: 0; - width: 100%; -webkit-border-horizontal-spacing: 0; -webkit-border-vertical-spacing: 0; -webkit-user-select: text; @@ -61,66 +60,81 @@ .webkit-line-number { color: rgb(128, 128, 128); + background-color: rgb(240, 240, 240); + border-right: 1px solid rgb(187, 187, 187); text-align: right; 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; - background-repeat: no-repeat; - background-position: right 1px; + padding-right: 4px; + padding-left: 6px; +} + +.webkit-line-number-outer { + margin-right: -4px; + margin-left: -4px; + border-color: transparent; + border-style: solid; + border-width: 0 0 0px 2px; vertical-align: top; } +.webkit-line-number-inner { + margin-right: 4px; +} + +.webkit-breakpoint .webkit-line-number-inner, .webkit-breakpoint-conditional .webkit-line-number-inner, .webkit-execution-line .webkit-line-number-inner { + margin-right: -10px; +} + .webkit-line-content { + width: 100%; padding-left: 2px; vertical-align: top; } -.webkit-execution-line .webkit-line-number { - color: transparent; - background-image: -webkit-canvas(program-counter); -} - -.webkit-breakpoint .webkit-line-number { +.webkit-breakpoint .webkit-line-number-outer { color: white; - background-image: -webkit-canvas(breakpoint); + border-width: 0 14px 0px 2px; + -webkit-border-image: url(Images/breakpointBorder.png) 0 14 0 2; } -.webkit-breakpoint-disabled .webkit-line-number { +.webkit-breakpoint-conditional .webkit-line-number-outer { color: white; - background-image: -webkit-canvas(breakpoint-disabled); + border-width: 0 14px 0px 2px; + -webkit-border-image: url(Images/breakpointConditionalBorder.png) 0 14 0 2; } -.webkit-breakpoint.webkit-execution-line .webkit-line-number { +.webkit-execution-line .webkit-line-number-outer { color: transparent; - background-image: -webkit-canvas(breakpoint-program-counter); + border-width: 0 14px 0px 2px; + -webkit-border-image: url(Images/programCounterBorder.png) 0 14 0 2; +} + +.webkit-breakpoint.webkit-execution-line .webkit-line-number-outer { + color: white; + -webkit-border-image: url(Images/breakpointCounterBorder.png) 0 14 0 2; } -.webkit-breakpoint-disabled.webkit-execution-line .webkit-line-number { +.webkit-breakpoint.webkit-execution-line .webkit-line-number-outer { color: transparent; - background-image: -webkit-canvas(breakpoint-disabled-program-counter); + -webkit-border-image: url(Images/breakpointCounterBorder.png) 0 14 0 2; } -.webkit-breakpoint.webkit-breakpoint-conditional .webkit-line-number { - color: white; - background-image: -webkit-canvas(breakpoint-conditional); +.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number-outer { + color: transparent; + -webkit-border-image: url(Images/breakpointConditionalCounterBorder.png) 0 14 0 2; } -.webkit-breakpoint-disabled.webkit-breakpoint-conditional .webkit-line-number { - color: white; - background-image: -webkit-canvas(breakpoint-disabled-conditional); +.webkit-breakpoint-disabled .webkit-line-number-outer { + opacity: 0.5; } -.webkit-breakpoint.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { - color: transparent; - background-image: -webkit-canvas(breakpoint-conditional-program-counter); +.breakpoints-deactivated .webkit-breakpoint .webkit-line-number-outer { + opacity: 0.5; } -.webkit-breakpoint-disabled.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { - color: transparent; - background-image: -webkit-canvas(breakpoint-disabled-conditional-program-counter); +.breakpoints-deactivated .webkit-breakpoint-disabled .webkit-line-number-outer { + opacity: 0.3; } .webkit-execution-line .webkit-line-content { diff --git a/WebCore/inspector/front-end/treeoutline.js b/WebCore/inspector/front-end/treeoutline.js index b6e35bb..799d8af 100644 --- a/WebCore/inspector/front-end/treeoutline.js +++ b/WebCore/inspector/front-end/treeoutline.js @@ -287,7 +287,7 @@ TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, if (cachedElement) return cachedElement; - // The representedObject isn't know, so we start at the top of the tree and work down to find the first + // The representedObject isn't known, so we start at the top of the tree and work down to find the first // tree element that represents representedObject or one of its ancestors. var item; var found = false; @@ -567,7 +567,7 @@ TreeElement.prototype._attach = function() if (this.selected) this._listItemNode.addStyleClass("selected"); - this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false); + this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false); this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); @@ -595,7 +595,7 @@ TreeElement.prototype._detach = function() this._childrenListNode.parentNode.removeChild(this._childrenListNode); } -TreeElement.treeElementSelected = function(event) +TreeElement.treeElementMouseDown = function(event) { var element = event.currentTarget; if (!element || !element.treeElement || !element.treeElement.selectable) @@ -604,7 +604,7 @@ TreeElement.treeElementSelected = function(event) if (element.treeElement.isEventWithinDisclosureTriangle(event)) return; - element.treeElement.select(); + element.treeElement.selectOnMouseDown(event); } TreeElement.treeElementToggled = function(event) @@ -767,6 +767,11 @@ TreeElement.prototype.revealed = function() return true; } +TreeElement.prototype.selectOnMouseDown = function(event) +{ + this.select(); +} + TreeElement.prototype.select = function(supressOnSelect) { if (!this.treeOutline || !this.selectable || this.selected) diff --git a/WebCore/inspector/front-end/utilities.js b/WebCore/inspector/front-end/utilities.js index f30ab9f..98f34b9 100644 --- a/WebCore/inspector/front-end/utilities.js +++ b/WebCore/inspector/front-end/utilities.js @@ -219,6 +219,17 @@ Element.prototype.positionAt = function(x, y) this.style.top = y + "px"; } +Element.prototype.pruneEmptyTextNodes = function() +{ + var sibling = this.firstChild; + while (sibling) { + var nextSibling = sibling.nextSibling; + if (sibling.nodeType === this.TEXT_NODE && sibling.nodeValue === "") + this.removeChild(sibling); + sibling = nextSibling; + } +} + Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray) { for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) @@ -359,7 +370,7 @@ String.prototype.collapseWhitespace = function() String.prototype.trimURL = function(baseURLDomain) { - var result = this.replace(/^https?:\/\//i, ""); + var result = this.replace(/^(https|http|file):\/\//i, ""); if (baseURLDomain) result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), ""); return result; |
