| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2010 Google Inc. All rights reserved. | 2 * Copyright (C) 2010 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| 11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
| 12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
| 13 * distribution. | 13 * distribution. |
| 14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
| 15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
| 16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
| 17 * | 17 * |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ | 29 */ |
| 30 | |
| 31 WebInspector.AuditRules.IPAddressRegexp = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
; | 30 WebInspector.AuditRules.IPAddressRegexp = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
; |
| 32 | 31 |
| 33 WebInspector.AuditRules.CacheableResponseCodes = | 32 WebInspector.AuditRules.CacheableResponseCodes = { |
| 34 { | 33 200: true, |
| 35 200: true, | 34 203: true, |
| 36 203: true, | 35 206: true, |
| 37 206: true, | 36 300: true, |
| 38 300: true, | 37 301: true, |
| 39 301: true, | 38 410: true, |
| 40 410: true, | |
| 41 | 39 |
| 42 304: true // Underlying request is cacheable | 40 304: true // Underlying request is cacheable |
| 43 }; | 41 }; |
| 44 | 42 |
| 45 /** | 43 /** |
| 46 * @param {!Array.<!WebInspector.NetworkRequest>} requests | 44 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 47 * @param {?Array.<!WebInspector.ResourceType>} types | 45 * @param {?Array.<!WebInspector.ResourceType>} types |
| 48 * @param {boolean} needFullResources | 46 * @param {boolean} needFullResources |
| 49 * @return {!Object.<string, !Array.<!WebInspector.NetworkRequest|string>>} | 47 * @return {!Object.<string, !Array.<!WebInspector.NetworkRequest|string>>} |
| 50 */ | 48 */ |
| 51 WebInspector.AuditRules.getDomainToResourcesMap = function(requests, types, need
FullResources) | 49 WebInspector.AuditRules.getDomainToResourcesMap = function(requests, types, need
FullResources) { |
| 52 { | 50 var domainToResourcesMap = {}; |
| 53 var domainToResourcesMap = {}; | 51 for (var i = 0, size = requests.length; i < size; ++i) { |
| 54 for (var i = 0, size = requests.length; i < size; ++i) { | 52 var request = requests[i]; |
| 55 var request = requests[i]; | 53 if (types && types.indexOf(request.resourceType()) === -1) |
| 56 if (types && types.indexOf(request.resourceType()) === -1) | 54 continue; |
| 57 continue; | 55 var parsedURL = request.url.asParsedURL(); |
| 58 var parsedURL = request.url.asParsedURL(); | 56 if (!parsedURL) |
| 59 if (!parsedURL) | 57 continue; |
| 60 continue; | 58 var domain = parsedURL.host; |
| 61 var domain = parsedURL.host; | 59 var domainResources = domainToResourcesMap[domain]; |
| 62 var domainResources = domainToResourcesMap[domain]; | 60 if (domainResources === undefined) { |
| 63 if (domainResources === undefined) { | 61 domainResources = []; |
| 64 domainResources = []; | 62 domainToResourcesMap[domain] = domainResources; |
| 65 domainToResourcesMap[domain] = domainResources; | 63 } |
| 66 } | 64 domainResources.push(needFullResources ? request : request.url); |
| 67 domainResources.push(needFullResources ? request : request.url); | 65 } |
| 68 } | 66 return domainToResourcesMap; |
| 69 return domainToResourcesMap; | 67 }; |
| 70 }; | 68 |
| 71 | 69 /** |
| 72 /** | 70 * @unrestricted |
| 73 * @constructor | 71 */ |
| 74 * @extends {WebInspector.AuditRule} | 72 WebInspector.AuditRules.GzipRule = class extends WebInspector.AuditRule { |
| 75 */ | 73 constructor() { |
| 76 WebInspector.AuditRules.GzipRule = function() | 74 super('network-gzip', WebInspector.UIString('Enable gzip compression')); |
| 77 { | 75 } |
| 78 WebInspector.AuditRule.call(this, "network-gzip", WebInspector.UIString("Ena
ble gzip compression")); | 76 |
| 79 }; | 77 /** |
| 80 | 78 * @override |
| 81 WebInspector.AuditRules.GzipRule.prototype = { | 79 * @param {!WebInspector.Target} target |
| 82 /** | 80 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 83 * @override | 81 * @param {!WebInspector.AuditRuleResult} result |
| 84 * @param {!WebInspector.Target} target | 82 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 85 * @param {!Array.<!WebInspector.NetworkRequest>} requests | 83 * @param {!WebInspector.Progress} progress |
| 86 * @param {!WebInspector.AuditRuleResult} result | 84 */ |
| 87 * @param {function(?WebInspector.AuditRuleResult)} callback | 85 doRun(target, requests, result, callback, progress) { |
| 88 * @param {!WebInspector.Progress} progress | 86 var totalSavings = 0; |
| 89 */ | 87 var compressedSize = 0; |
| 90 doRun: function(target, requests, result, callback, progress) | 88 var candidateSize = 0; |
| 91 { | 89 var summary = result.addChild('', true); |
| 92 var totalSavings = 0; | 90 for (var i = 0, length = requests.length; i < length; ++i) { |
| 93 var compressedSize = 0; | 91 var request = requests[i]; |
| 94 var candidateSize = 0; | 92 if (request.cached() || request.statusCode === 304) |
| 95 var summary = result.addChild("", true); | 93 continue; // Do not test cached resources. |
| 96 for (var i = 0, length = requests.length; i < length; ++i) { | 94 if (this._shouldCompress(request)) { |
| 97 var request = requests[i]; | 95 var size = request.resourceSize; |
| 98 if (request.cached() || request.statusCode === 304) | 96 candidateSize += size; |
| 99 continue; // Do not test cached resources. | 97 if (this._isCompressed(request)) { |
| 100 if (this._shouldCompress(request)) { | 98 compressedSize += size; |
| 101 var size = request.resourceSize; | 99 continue; |
| 102 candidateSize += size; | 100 } |
| 103 if (this._isCompressed(request)) { | 101 var savings = 2 * size / 3; |
| 104 compressedSize += size; | 102 totalSavings += savings; |
| 105 continue; | 103 summary.addFormatted('%r could save ~%s', request.url, Number.bytesToStr
ing(savings)); |
| 106 } | 104 result.violationCount++; |
| 107 var savings = 2 * size / 3; | 105 } |
| 108 totalSavings += savings; | 106 } |
| 109 summary.addFormatted("%r could save ~%s", request.url, Number.by
tesToString(savings)); | 107 if (!totalSavings) { |
| 110 result.violationCount++; | 108 callback(null); |
| 111 } | 109 return; |
| 112 } | 110 } |
| 113 if (!totalSavings) { | 111 summary.value = WebInspector.UIString( |
| 114 callback(null); | 112 'Compressing the following resources with gzip could reduce their transf
er size by about two thirds (~%s):', |
| 115 return; | 113 Number.bytesToString(totalSavings)); |
| 116 } | 114 callback(result); |
| 117 summary.value = WebInspector.UIString("Compressing the following resourc
es with gzip could reduce their transfer size by about two thirds (~%s):", Numbe
r.bytesToString(totalSavings)); | 115 } |
| 118 callback(result); | 116 |
| 119 }, | 117 /** |
| 120 | 118 * @param {!WebInspector.NetworkRequest} request |
| 121 /** | 119 */ |
| 122 * @param {!WebInspector.NetworkRequest} request | 120 _isCompressed(request) { |
| 123 */ | 121 var encodingHeader = request.responseHeaderValue('Content-Encoding'); |
| 124 _isCompressed: function(request) | 122 if (!encodingHeader) |
| 125 { | 123 return false; |
| 126 var encodingHeader = request.responseHeaderValue("Content-Encoding"); | 124 |
| 127 if (!encodingHeader) | 125 return /\b(?:gzip|deflate)\b/.test(encodingHeader); |
| 128 return false; | 126 } |
| 129 | 127 |
| 130 return /\b(?:gzip|deflate)\b/.test(encodingHeader); | 128 /** |
| 131 }, | 129 * @param {!WebInspector.NetworkRequest} request |
| 132 | 130 */ |
| 133 /** | 131 _shouldCompress(request) { |
| 134 * @param {!WebInspector.NetworkRequest} request | 132 return request.resourceType().isTextType() && request.parsedURL.host && requ
est.resourceSize !== undefined && |
| 135 */ | 133 request.resourceSize > 150; |
| 136 _shouldCompress: function(request) | 134 } |
| 137 { | 135 }; |
| 138 return request.resourceType().isTextType() && request.parsedURL.host &&
request.resourceSize !== undefined && request.resourceSize > 150; | 136 |
| 139 }, | 137 /** |
| 140 | 138 * @unrestricted |
| 141 __proto__: WebInspector.AuditRule.prototype | 139 */ |
| 142 }; | 140 WebInspector.AuditRules.CombineExternalResourcesRule = class extends WebInspecto
r.AuditRule { |
| 143 | 141 /** |
| 144 /** | 142 * @param {string} id |
| 145 * @constructor | 143 * @param {string} name |
| 146 * @extends {WebInspector.AuditRule} | 144 * @param {!WebInspector.ResourceType} type |
| 147 * @param {string} id | 145 * @param {string} resourceTypeName |
| 148 * @param {string} name | 146 * @param {boolean} allowedPerDomain |
| 149 * @param {!WebInspector.ResourceType} type | 147 */ |
| 150 * @param {string} resourceTypeName | 148 constructor(id, name, type, resourceTypeName, allowedPerDomain) { |
| 151 * @param {boolean} allowedPerDomain | 149 super(id, name); |
| 152 */ | |
| 153 WebInspector.AuditRules.CombineExternalResourcesRule = function(id, name, type,
resourceTypeName, allowedPerDomain) | |
| 154 { | |
| 155 WebInspector.AuditRule.call(this, id, name); | |
| 156 this._type = type; | 150 this._type = type; |
| 157 this._resourceTypeName = resourceTypeName; | 151 this._resourceTypeName = resourceTypeName; |
| 158 this._allowedPerDomain = allowedPerDomain; | 152 this._allowedPerDomain = allowedPerDomain; |
| 159 }; | 153 } |
| 160 | 154 |
| 161 WebInspector.AuditRules.CombineExternalResourcesRule.prototype = { | 155 /** |
| 162 /** | 156 * @override |
| 163 * @override | 157 * @param {!WebInspector.Target} target |
| 164 * @param {!WebInspector.Target} target | 158 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 165 * @param {!Array.<!WebInspector.NetworkRequest>} requests | 159 * @param {!WebInspector.AuditRuleResult} result |
| 166 * @param {!WebInspector.AuditRuleResult} result | 160 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 167 * @param {function(?WebInspector.AuditRuleResult)} callback | 161 * @param {!WebInspector.Progress} progress |
| 168 * @param {!WebInspector.Progress} progress | 162 */ |
| 169 */ | 163 doRun(target, requests, result, callback, progress) { |
| 170 doRun: function(target, requests, result, callback, progress) | 164 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(r
equests, [this._type], false); |
| 171 { | 165 var penalizedResourceCount = 0; |
| 172 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesM
ap(requests, [this._type], false); | 166 // TODO: refactor according to the chosen i18n approach |
| 173 var penalizedResourceCount = 0; | 167 var summary = result.addChild('', true); |
| 174 // TODO: refactor according to the chosen i18n approach | 168 for (var domain in domainToResourcesMap) { |
| 175 var summary = result.addChild("", true); | 169 var domainResources = domainToResourcesMap[domain]; |
| 176 for (var domain in domainToResourcesMap) { | 170 var extraResourceCount = domainResources.length - this._allowedPerDomain; |
| 177 var domainResources = domainToResourcesMap[domain]; | 171 if (extraResourceCount <= 0) |
| 178 var extraResourceCount = domainResources.length - this._allowedPerDo
main; | 172 continue; |
| 179 if (extraResourceCount <= 0) | 173 penalizedResourceCount += extraResourceCount - 1; |
| 180 continue; | 174 summary.addChild(WebInspector.UIString( |
| 181 penalizedResourceCount += extraResourceCount - 1; | 175 '%d %s resources served from %s.', domainResources.length, this._resou
rceTypeName, |
| 182 summary.addChild(WebInspector.UIString("%d %s resources served from
%s.", domainResources.length, this._resourceTypeName, WebInspector.AuditRuleResu
lt.resourceDomain(domain))); | 176 WebInspector.AuditRuleResult.resourceDomain(domain))); |
| 183 result.violationCount += domainResources.length; | 177 result.violationCount += domainResources.length; |
| 184 } | 178 } |
| 185 if (!penalizedResourceCount) { | 179 if (!penalizedResourceCount) { |
| 186 callback(null); | 180 callback(null); |
| 187 return; | 181 return; |
| 188 } | 182 } |
| 189 | 183 |
| 190 summary.value = WebInspector.UIString("There are multiple resources serv
ed from same domain. Consider combining them into as few files as possible."); | 184 summary.value = WebInspector.UIString( |
| 191 callback(result); | 185 'There are multiple resources served from same domain. Consider combinin
g them into as few files as possible.'); |
| 192 }, | 186 callback(result); |
| 193 | 187 } |
| 194 __proto__: WebInspector.AuditRule.prototype | 188 }; |
| 195 }; | 189 |
| 196 | 190 /** |
| 197 /** | 191 * @unrestricted |
| 198 * @constructor | 192 */ |
| 199 * @extends {WebInspector.AuditRules.CombineExternalResourcesRule} | 193 WebInspector.AuditRules.CombineJsResourcesRule = class extends WebInspector.Audi
tRules.CombineExternalResourcesRule { |
| 200 */ | 194 constructor(allowedPerDomain) { |
| 201 WebInspector.AuditRules.CombineJsResourcesRule = function(allowedPerDomain) { | 195 super( |
| 202 WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-extern
aljs", WebInspector.UIString("Combine external JavaScript"), WebInspector.resour
ceTypes.Script, "JavaScript", allowedPerDomain); | 196 'page-externaljs', WebInspector.UIString('Combine external JavaScript'),
WebInspector.resourceTypes.Script, |
| 203 }; | 197 'JavaScript', allowedPerDomain); |
| 204 | 198 } |
| 205 WebInspector.AuditRules.CombineJsResourcesRule.prototype = { | 199 }; |
| 206 __proto__: WebInspector.AuditRules.CombineExternalResourcesRule.prototype | 200 |
| 207 }; | 201 /** |
| 208 | 202 * @unrestricted |
| 209 /** | 203 */ |
| 210 * @constructor | 204 WebInspector.AuditRules.CombineCssResourcesRule = class extends WebInspector.Aud
itRules.CombineExternalResourcesRule { |
| 211 * @extends {WebInspector.AuditRules.CombineExternalResourcesRule} | 205 constructor(allowedPerDomain) { |
| 212 */ | 206 super( |
| 213 WebInspector.AuditRules.CombineCssResourcesRule = function(allowedPerDomain) { | 207 'page-externalcss', WebInspector.UIString('Combine external CSS'), WebIn
spector.resourceTypes.Stylesheet, 'CSS', |
| 214 WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-extern
alcss", WebInspector.UIString("Combine external CSS"), WebInspector.resourceType
s.Stylesheet, "CSS", allowedPerDomain); | 208 allowedPerDomain); |
| 215 }; | 209 } |
| 216 | 210 }; |
| 217 WebInspector.AuditRules.CombineCssResourcesRule.prototype = { | 211 |
| 218 __proto__: WebInspector.AuditRules.CombineExternalResourcesRule.prototype | 212 /** |
| 219 }; | 213 * @unrestricted |
| 220 | 214 */ |
| 221 /** | 215 WebInspector.AuditRules.MinimizeDnsLookupsRule = class extends WebInspector.Audi
tRule { |
| 222 * @constructor | 216 constructor(hostCountThreshold) { |
| 223 * @extends {WebInspector.AuditRule} | 217 super('network-minimizelookups', WebInspector.UIString('Minimize DNS lookups
')); |
| 224 */ | |
| 225 WebInspector.AuditRules.MinimizeDnsLookupsRule = function(hostCountThreshold) { | |
| 226 WebInspector.AuditRule.call(this, "network-minimizelookups", WebInspector.UI
String("Minimize DNS lookups")); | |
| 227 this._hostCountThreshold = hostCountThreshold; | 218 this._hostCountThreshold = hostCountThreshold; |
| 228 }; | 219 } |
| 229 | 220 |
| 230 WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype = { | 221 /** |
| 231 /** | 222 * @override |
| 232 * @override | 223 * @param {!WebInspector.Target} target |
| 233 * @param {!WebInspector.Target} target | 224 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 234 * @param {!Array.<!WebInspector.NetworkRequest>} requests | 225 * @param {!WebInspector.AuditRuleResult} result |
| 235 * @param {!WebInspector.AuditRuleResult} result | 226 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 236 * @param {function(?WebInspector.AuditRuleResult)} callback | 227 * @param {!WebInspector.Progress} progress |
| 237 * @param {!WebInspector.Progress} progress | 228 */ |
| 238 */ | 229 doRun(target, requests, result, callback, progress) { |
| 239 doRun: function(target, requests, result, callback, progress) | 230 var summary = result.addChild(''); |
| 240 { | 231 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(r
equests, null, false); |
| 241 var summary = result.addChild(""); | 232 for (var domain in domainToResourcesMap) { |
| 242 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesM
ap(requests, null, false); | 233 if (domainToResourcesMap[domain].length > 1) |
| 243 for (var domain in domainToResourcesMap) { | 234 continue; |
| 244 if (domainToResourcesMap[domain].length > 1) | 235 var parsedURL = domain.asParsedURL(); |
| 245 continue; | 236 if (!parsedURL) |
| 246 var parsedURL = domain.asParsedURL(); | 237 continue; |
| 247 if (!parsedURL) | 238 if (!parsedURL.host.search(WebInspector.AuditRules.IPAddressRegexp)) |
| 248 continue; | 239 continue; // an IP address |
| 249 if (!parsedURL.host.search(WebInspector.AuditRules.IPAddressRegexp)) | 240 summary.addSnippet(domain); |
| 250 continue; // an IP address | 241 result.violationCount++; |
| 251 summary.addSnippet(domain); | 242 } |
| 252 result.violationCount++; | 243 if (!summary.children || summary.children.length <= this._hostCountThreshold
) { |
| 253 } | 244 callback(null); |
| 254 if (!summary.children || summary.children.length <= this._hostCountThres
hold) { | 245 return; |
| 255 callback(null); | 246 } |
| 256 return; | 247 |
| 257 } | 248 summary.value = WebInspector.UIString( |
| 258 | 249 'The following domains only serve one resource each. If possible, avoid
the extra DNS lookups by serving these resources from existing domains.'); |
| 259 summary.value = WebInspector.UIString("The following domains only serve
one resource each. If possible, avoid the extra DNS lookups by serving these res
ources from existing domains."); | 250 callback(result); |
| 260 callback(result); | 251 } |
| 261 }, | 252 }; |
| 262 | 253 |
| 263 __proto__: WebInspector.AuditRule.prototype | 254 /** |
| 264 }; | 255 * @unrestricted |
| 265 | 256 */ |
| 266 /** | 257 WebInspector.AuditRules.ParallelizeDownloadRule = class extends WebInspector.Aud
itRule { |
| 267 * @constructor | 258 constructor(optimalHostnameCount, minRequestThreshold, minBalanceThreshold) { |
| 268 * @extends {WebInspector.AuditRule} | 259 super('network-parallelizehosts', WebInspector.UIString('Parallelize downloa
ds across hostnames')); |
| 269 */ | |
| 270 WebInspector.AuditRules.ParallelizeDownloadRule = function(optimalHostnameCount,
minRequestThreshold, minBalanceThreshold) | |
| 271 { | |
| 272 WebInspector.AuditRule.call(this, "network-parallelizehosts", WebInspector.U
IString("Parallelize downloads across hostnames")); | |
| 273 this._optimalHostnameCount = optimalHostnameCount; | 260 this._optimalHostnameCount = optimalHostnameCount; |
| 274 this._minRequestThreshold = minRequestThreshold; | 261 this._minRequestThreshold = minRequestThreshold; |
| 275 this._minBalanceThreshold = minBalanceThreshold; | 262 this._minBalanceThreshold = minBalanceThreshold; |
| 276 }; | 263 } |
| 277 | 264 |
| 278 WebInspector.AuditRules.ParallelizeDownloadRule.prototype = { | 265 /** |
| 266 * @override |
| 267 * @param {!WebInspector.Target} target |
| 268 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 269 * @param {!WebInspector.AuditRuleResult} result |
| 270 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 271 * @param {!WebInspector.Progress} progress |
| 272 */ |
| 273 doRun(target, requests, result, callback, progress) { |
| 279 /** | 274 /** |
| 280 * @override | 275 * @param {string} a |
| 281 * @param {!WebInspector.Target} target | 276 * @param {string} b |
| 282 * @param {!Array.<!WebInspector.NetworkRequest>} requests | |
| 283 * @param {!WebInspector.AuditRuleResult} result | |
| 284 * @param {function(?WebInspector.AuditRuleResult)} callback | |
| 285 * @param {!WebInspector.Progress} progress | |
| 286 */ | 277 */ |
| 287 doRun: function(target, requests, result, callback, progress) | 278 function hostSorter(a, b) { |
| 288 { | 279 var aCount = domainToResourcesMap[a].length; |
| 289 /** | 280 var bCount = domainToResourcesMap[b].length; |
| 290 * @param {string} a | 281 return (aCount < bCount) ? 1 : (aCount === bCount) ? 0 : -1; |
| 291 * @param {string} b | 282 } |
| 292 */ | 283 |
| 293 function hostSorter(a, b) | 284 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap( |
| 294 { | 285 requests, [WebInspector.resourceTypes.Stylesheet, WebInspector.resourceT
ypes.Image], true); |
| 295 var aCount = domainToResourcesMap[a].length; | 286 |
| 296 var bCount = domainToResourcesMap[b].length; | 287 var hosts = []; |
| 297 return (aCount < bCount) ? 1 : (aCount === bCount) ? 0 : -1; | 288 for (var url in domainToResourcesMap) |
| 298 } | 289 hosts.push(url); |
| 299 | 290 |
| 300 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesM
ap( | 291 if (!hosts.length) { |
| 301 requests, | 292 callback(null); // no hosts (local file or something) |
| 302 [WebInspector.resourceTypes.Stylesheet, WebInspector.resourceTypes.I
mage], | 293 return; |
| 303 true); | 294 } |
| 304 | 295 |
| 305 var hosts = []; | 296 hosts.sort(hostSorter); |
| 306 for (var url in domainToResourcesMap) | 297 |
| 307 hosts.push(url); | 298 var optimalHostnameCount = this._optimalHostnameCount; |
| 308 | 299 if (hosts.length > optimalHostnameCount) |
| 309 if (!hosts.length) { | 300 hosts.splice(optimalHostnameCount); |
| 310 callback(null); // no hosts (local file or something) | 301 |
| 311 return; | 302 var busiestHostResourceCount = domainToResourcesMap[hosts[0]].length; |
| 312 } | 303 var requestCountAboveThreshold = busiestHostResourceCount - this._minRequest
Threshold; |
| 313 | 304 if (requestCountAboveThreshold <= 0) { |
| 314 hosts.sort(hostSorter); | 305 callback(null); |
| 315 | 306 return; |
| 316 var optimalHostnameCount = this._optimalHostnameCount; | 307 } |
| 317 if (hosts.length > optimalHostnameCount) | 308 |
| 318 hosts.splice(optimalHostnameCount); | 309 var avgResourcesPerHost = 0; |
| 319 | 310 for (var i = 0, size = hosts.length; i < size; ++i) |
| 320 var busiestHostResourceCount = domainToResourcesMap[hosts[0]].length; | 311 avgResourcesPerHost += domainToResourcesMap[hosts[i]].length; |
| 321 var requestCountAboveThreshold = busiestHostResourceCount - this._minReq
uestThreshold; | 312 |
| 322 if (requestCountAboveThreshold <= 0) { | 313 // Assume optimal parallelization. |
| 314 avgResourcesPerHost /= optimalHostnameCount; |
| 315 avgResourcesPerHost = Math.max(avgResourcesPerHost, 1); |
| 316 |
| 317 var pctAboveAvg = (requestCountAboveThreshold / avgResourcesPerHost) - 1.0; |
| 318 var minBalanceThreshold = this._minBalanceThreshold; |
| 319 if (pctAboveAvg < minBalanceThreshold) { |
| 320 callback(null); |
| 321 return; |
| 322 } |
| 323 |
| 324 var requestsOnBusiestHost = domainToResourcesMap[hosts[0]]; |
| 325 var entry = result.addChild( |
| 326 WebInspector.UIString( |
| 327 'This page makes %d parallelizable requests to %s. Increase download
parallelization by distributing the following requests across multiple hostname
s.', |
| 328 busiestHostResourceCount, hosts[0]), |
| 329 true); |
| 330 for (var i = 0; i < requestsOnBusiestHost.length; ++i) |
| 331 entry.addURL(requestsOnBusiestHost[i].url); |
| 332 |
| 333 result.violationCount = requestsOnBusiestHost.length; |
| 334 callback(result); |
| 335 } |
| 336 }; |
| 337 |
| 338 /** |
| 339 * @unrestricted |
| 340 */ |
| 341 WebInspector.AuditRules.UnusedCssRule = class extends WebInspector.AuditRule { |
| 342 /** |
| 343 * The reported CSS rule size is incorrect (parsed != original in WebKit), |
| 344 * so use percentages instead, which gives a better approximation. |
| 345 */ |
| 346 constructor() { |
| 347 super('page-unusedcss', WebInspector.UIString('Remove unused CSS rules')); |
| 348 } |
| 349 |
| 350 /** |
| 351 * @override |
| 352 * @param {!WebInspector.Target} target |
| 353 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 354 * @param {!WebInspector.AuditRuleResult} result |
| 355 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 356 * @param {!WebInspector.Progress} progress |
| 357 */ |
| 358 doRun(target, requests, result, callback, progress) { |
| 359 var domModel = WebInspector.DOMModel.fromTarget(target); |
| 360 var cssModel = WebInspector.CSSModel.fromTarget(target); |
| 361 if (!domModel || !cssModel) { |
| 362 callback(null); |
| 363 return; |
| 364 } |
| 365 |
| 366 /** |
| 367 * @param {!Array.<!WebInspector.AuditRules.ParsedStyleSheet>} styleSheets |
| 368 */ |
| 369 function evalCallback(styleSheets) { |
| 370 if (!styleSheets.length) |
| 371 return callback(null); |
| 372 |
| 373 var selectors = []; |
| 374 var testedSelectors = {}; |
| 375 for (var i = 0; i < styleSheets.length; ++i) { |
| 376 var styleSheet = styleSheets[i]; |
| 377 for (var curRule = 0; curRule < styleSheet.rules.length; ++curRule) { |
| 378 var selectorText = styleSheet.rules[curRule].selectorText; |
| 379 if (testedSelectors[selectorText]) |
| 380 continue; |
| 381 selectors.push(selectorText); |
| 382 testedSelectors[selectorText] = 1; |
| 383 } |
| 384 } |
| 385 |
| 386 var foundSelectors = {}; |
| 387 |
| 388 /** |
| 389 * @param {!Array.<!WebInspector.AuditRules.ParsedStyleSheet>} styleSheets |
| 390 */ |
| 391 function selectorsCallback(styleSheets) { |
| 392 if (progress.isCanceled()) { |
| 393 callback(null); |
| 394 return; |
| 395 } |
| 396 |
| 397 var inlineBlockOrdinal = 0; |
| 398 var totalStylesheetSize = 0; |
| 399 var totalUnusedStylesheetSize = 0; |
| 400 var summary; |
| 401 |
| 402 for (var i = 0; i < styleSheets.length; ++i) { |
| 403 var styleSheet = styleSheets[i]; |
| 404 var unusedRules = []; |
| 405 for (var curRule = 0; curRule < styleSheet.rules.length; ++curRule) { |
| 406 var rule = styleSheet.rules[curRule]; |
| 407 if (!testedSelectors[rule.selectorText] || foundSelectors[rule.selec
torText]) |
| 408 continue; |
| 409 unusedRules.push(rule.selectorText); |
| 410 } |
| 411 totalStylesheetSize += styleSheet.rules.length; |
| 412 totalUnusedStylesheetSize += unusedRules.length; |
| 413 |
| 414 if (!unusedRules.length) |
| 415 continue; |
| 416 |
| 417 var resource = WebInspector.resourceForURL(styleSheet.sourceURL); |
| 418 var isInlineBlock = |
| 419 resource && resource.request && resource.request.resourceType() ==
= WebInspector.resourceTypes.Document; |
| 420 var url = !isInlineBlock ? WebInspector.AuditRuleResult.linkifyDisplay
Name(styleSheet.sourceURL) : |
| 421 WebInspector.UIString('Inline block #%d', +
+inlineBlockOrdinal); |
| 422 var pctUnused = Math.round(100 * unusedRules.length / styleSheet.rules
.length); |
| 423 if (!summary) |
| 424 summary = result.addChild('', true); |
| 425 var entry = summary.addFormatted('%s: %d% is not used by the current p
age.', url, pctUnused); |
| 426 |
| 427 for (var j = 0; j < unusedRules.length; ++j) |
| 428 entry.addSnippet(unusedRules[j]); |
| 429 |
| 430 result.violationCount += unusedRules.length; |
| 431 } |
| 432 |
| 433 if (!totalUnusedStylesheetSize) |
| 434 return callback(null); |
| 435 |
| 436 var totalUnusedPercent = Math.round(100 * totalUnusedStylesheetSize / to
talStylesheetSize); |
| 437 summary.value = WebInspector.UIString( |
| 438 '%s rules (%d%) of CSS not used by the current page.', totalUnusedSt
ylesheetSize, totalUnusedPercent); |
| 439 |
| 440 callback(result); |
| 441 } |
| 442 |
| 443 /** |
| 444 * @param {?function()} boundSelectorsCallback |
| 445 * @param {string} selector |
| 446 * @param {?DOMAgent.NodeId} nodeId |
| 447 */ |
| 448 function queryCallback(boundSelectorsCallback, selector, nodeId) { |
| 449 if (nodeId) |
| 450 foundSelectors[selector] = true; |
| 451 if (boundSelectorsCallback) |
| 452 boundSelectorsCallback(); |
| 453 } |
| 454 |
| 455 /** |
| 456 * @param {!Array.<string>} selectors |
| 457 * @param {!WebInspector.DOMDocument} document |
| 458 */ |
| 459 function documentLoaded(selectors, document) { |
| 460 var pseudoSelectorRegexp = /::?(?:[\w-]+)(?:\(.*?\))?/g; |
| 461 if (!selectors.length) { |
| 462 selectorsCallback([]); |
| 463 return; |
| 464 } |
| 465 for (var i = 0; i < selectors.length; ++i) { |
| 466 if (progress.isCanceled()) { |
| 323 callback(null); | 467 callback(null); |
| 324 return; | 468 return; |
| 325 } | 469 } |
| 326 | 470 var effectiveSelector = selectors[i].replace(pseudoSelectorRegexp, '')
; |
| 327 var avgResourcesPerHost = 0; | 471 domModel.querySelector( |
| 328 for (var i = 0, size = hosts.length; i < size; ++i) | 472 document.id, effectiveSelector, |
| 329 avgResourcesPerHost += domainToResourcesMap[hosts[i]].length; | 473 queryCallback.bind( |
| 330 | 474 null, i === selectors.length - 1 ? selectorsCallback.bind(null
, styleSheets) : null, selectors[i])); |
| 331 // Assume optimal parallelization. | 475 } |
| 332 avgResourcesPerHost /= optimalHostnameCount; | 476 } |
| 333 avgResourcesPerHost = Math.max(avgResourcesPerHost, 1); | 477 |
| 334 | 478 domModel.requestDocument(documentLoaded.bind(null, selectors)); |
| 335 var pctAboveAvg = (requestCountAboveThreshold / avgResourcesPerHost) - 1
.0; | 479 } |
| 336 var minBalanceThreshold = this._minBalanceThreshold; | 480 |
| 337 if (pctAboveAvg < minBalanceThreshold) { | 481 var styleSheetInfos = cssModel.allStyleSheets(); |
| 338 callback(null); | 482 if (!styleSheetInfos || !styleSheetInfos.length) { |
| 339 return; | 483 evalCallback([]); |
| 340 } | 484 return; |
| 341 | 485 } |
| 342 var requestsOnBusiestHost = domainToResourcesMap[hosts[0]]; | 486 var styleSheetProcessor = new WebInspector.AuditRules.StyleSheetProcessor(st
yleSheetInfos, progress, evalCallback); |
| 343 var entry = result.addChild(WebInspector.UIString("This page makes %d pa
rallelizable requests to %s. Increase download parallelization by distributing t
he following requests across multiple hostnames.", busiestHostResourceCount, hos
ts[0]), true); | 487 styleSheetProcessor.run(); |
| 344 for (var i = 0; i < requestsOnBusiestHost.length; ++i) | 488 } |
| 345 entry.addURL(requestsOnBusiestHost[i].url); | 489 }; |
| 346 | 490 |
| 347 result.violationCount = requestsOnBusiestHost.length; | 491 /** |
| 348 callback(result); | |
| 349 }, | |
| 350 | |
| 351 __proto__: WebInspector.AuditRule.prototype | |
| 352 }; | |
| 353 | |
| 354 /** | |
| 355 * The reported CSS rule size is incorrect (parsed != original in WebKit), | |
| 356 * so use percentages instead, which gives a better approximation. | |
| 357 * @constructor | |
| 358 * @extends {WebInspector.AuditRule} | |
| 359 */ | |
| 360 WebInspector.AuditRules.UnusedCssRule = function() | |
| 361 { | |
| 362 WebInspector.AuditRule.call(this, "page-unusedcss", WebInspector.UIString("R
emove unused CSS rules")); | |
| 363 }; | |
| 364 | |
| 365 WebInspector.AuditRules.UnusedCssRule.prototype = { | |
| 366 /** | |
| 367 * @override | |
| 368 * @param {!WebInspector.Target} target | |
| 369 * @param {!Array.<!WebInspector.NetworkRequest>} requests | |
| 370 * @param {!WebInspector.AuditRuleResult} result | |
| 371 * @param {function(?WebInspector.AuditRuleResult)} callback | |
| 372 * @param {!WebInspector.Progress} progress | |
| 373 */ | |
| 374 doRun: function(target, requests, result, callback, progress) | |
| 375 { | |
| 376 var domModel = WebInspector.DOMModel.fromTarget(target); | |
| 377 var cssModel = WebInspector.CSSModel.fromTarget(target); | |
| 378 if (!domModel || !cssModel) { | |
| 379 callback(null); | |
| 380 return; | |
| 381 } | |
| 382 | |
| 383 /** | |
| 384 * @param {!Array.<!WebInspector.AuditRules.ParsedStyleSheet>} styleShee
ts | |
| 385 */ | |
| 386 function evalCallback(styleSheets) { | |
| 387 if (!styleSheets.length) | |
| 388 return callback(null); | |
| 389 | |
| 390 var selectors = []; | |
| 391 var testedSelectors = {}; | |
| 392 for (var i = 0; i < styleSheets.length; ++i) { | |
| 393 var styleSheet = styleSheets[i]; | |
| 394 for (var curRule = 0; curRule < styleSheet.rules.length; ++curRu
le) { | |
| 395 var selectorText = styleSheet.rules[curRule].selectorText; | |
| 396 if (testedSelectors[selectorText]) | |
| 397 continue; | |
| 398 selectors.push(selectorText); | |
| 399 testedSelectors[selectorText] = 1; | |
| 400 } | |
| 401 } | |
| 402 | |
| 403 var foundSelectors = {}; | |
| 404 | |
| 405 /** | |
| 406 * @param {!Array.<!WebInspector.AuditRules.ParsedStyleSheet>} style
Sheets | |
| 407 */ | |
| 408 function selectorsCallback(styleSheets) | |
| 409 { | |
| 410 if (progress.isCanceled()) { | |
| 411 callback(null); | |
| 412 return; | |
| 413 } | |
| 414 | |
| 415 var inlineBlockOrdinal = 0; | |
| 416 var totalStylesheetSize = 0; | |
| 417 var totalUnusedStylesheetSize = 0; | |
| 418 var summary; | |
| 419 | |
| 420 for (var i = 0; i < styleSheets.length; ++i) { | |
| 421 var styleSheet = styleSheets[i]; | |
| 422 var unusedRules = []; | |
| 423 for (var curRule = 0; curRule < styleSheet.rules.length; ++c
urRule) { | |
| 424 var rule = styleSheet.rules[curRule]; | |
| 425 if (!testedSelectors[rule.selectorText] || foundSelector
s[rule.selectorText]) | |
| 426 continue; | |
| 427 unusedRules.push(rule.selectorText); | |
| 428 } | |
| 429 totalStylesheetSize += styleSheet.rules.length; | |
| 430 totalUnusedStylesheetSize += unusedRules.length; | |
| 431 | |
| 432 if (!unusedRules.length) | |
| 433 continue; | |
| 434 | |
| 435 var resource = WebInspector.resourceForURL(styleSheet.source
URL); | |
| 436 var isInlineBlock = resource && resource.request && resource
.request.resourceType() === WebInspector.resourceTypes.Document; | |
| 437 var url = !isInlineBlock ? WebInspector.AuditRuleResult.link
ifyDisplayName(styleSheet.sourceURL) : WebInspector.UIString("Inline block #%d",
++inlineBlockOrdinal); | |
| 438 var pctUnused = Math.round(100 * unusedRules.length / styleS
heet.rules.length); | |
| 439 if (!summary) | |
| 440 summary = result.addChild("", true); | |
| 441 var entry = summary.addFormatted("%s: %d% is not used by the
current page.", url, pctUnused); | |
| 442 | |
| 443 for (var j = 0; j < unusedRules.length; ++j) | |
| 444 entry.addSnippet(unusedRules[j]); | |
| 445 | |
| 446 result.violationCount += unusedRules.length; | |
| 447 } | |
| 448 | |
| 449 if (!totalUnusedStylesheetSize) | |
| 450 return callback(null); | |
| 451 | |
| 452 var totalUnusedPercent = Math.round(100 * totalUnusedStylesheetS
ize / totalStylesheetSize); | |
| 453 summary.value = WebInspector.UIString("%s rules (%d%) of CSS not
used by the current page.", totalUnusedStylesheetSize, totalUnusedPercent); | |
| 454 | |
| 455 callback(result); | |
| 456 } | |
| 457 | |
| 458 /** | |
| 459 * @param {?function()} boundSelectorsCallback | |
| 460 * @param {string} selector | |
| 461 * @param {?DOMAgent.NodeId} nodeId | |
| 462 */ | |
| 463 function queryCallback(boundSelectorsCallback, selector, nodeId) | |
| 464 { | |
| 465 if (nodeId) | |
| 466 foundSelectors[selector] = true; | |
| 467 if (boundSelectorsCallback) | |
| 468 boundSelectorsCallback(); | |
| 469 } | |
| 470 | |
| 471 /** | |
| 472 * @param {!Array.<string>} selectors | |
| 473 * @param {!WebInspector.DOMDocument} document | |
| 474 */ | |
| 475 function documentLoaded(selectors, document) { | |
| 476 var pseudoSelectorRegexp = /::?(?:[\w-]+)(?:\(.*?\))?/g; | |
| 477 if (!selectors.length) { | |
| 478 selectorsCallback([]); | |
| 479 return; | |
| 480 } | |
| 481 for (var i = 0; i < selectors.length; ++i) { | |
| 482 if (progress.isCanceled()) { | |
| 483 callback(null); | |
| 484 return; | |
| 485 } | |
| 486 var effectiveSelector = selectors[i].replace(pseudoSelectorR
egexp, ""); | |
| 487 domModel.querySelector(document.id, effectiveSelector, query
Callback.bind(null, i === selectors.length - 1 ? selectorsCallback.bind(null, st
yleSheets) : null, selectors[i])); | |
| 488 } | |
| 489 } | |
| 490 | |
| 491 domModel.requestDocument(documentLoaded.bind(null, selectors)); | |
| 492 } | |
| 493 | |
| 494 var styleSheetInfos = cssModel.allStyleSheets(); | |
| 495 if (!styleSheetInfos || !styleSheetInfos.length) { | |
| 496 evalCallback([]); | |
| 497 return; | |
| 498 } | |
| 499 var styleSheetProcessor = new WebInspector.AuditRules.StyleSheetProcesso
r(styleSheetInfos, progress, evalCallback); | |
| 500 styleSheetProcessor.run(); | |
| 501 }, | |
| 502 | |
| 503 __proto__: WebInspector.AuditRule.prototype | |
| 504 }; | |
| 505 | |
| 506 /** | |
| 507 * @typedef {!{sourceURL: string, rules: !Array.<!WebInspector.CSSParser.StyleRu
le>}} | 492 * @typedef {!{sourceURL: string, rules: !Array.<!WebInspector.CSSParser.StyleRu
le>}} |
| 508 */ | 493 */ |
| 509 WebInspector.AuditRules.ParsedStyleSheet; | 494 WebInspector.AuditRules.ParsedStyleSheet; |
| 510 | 495 |
| 511 /** | 496 /** |
| 512 * @constructor | 497 * @unrestricted |
| 513 * @param {!Array.<!WebInspector.CSSStyleSheetHeader>} styleSheetHeaders | |
| 514 * @param {!WebInspector.Progress} progress | |
| 515 * @param {function(!Array.<!WebInspector.AuditRules.ParsedStyleSheet>)} styleSh
eetsParsedCallback | |
| 516 */ | 498 */ |
| 517 WebInspector.AuditRules.StyleSheetProcessor = function(styleSheetHeaders, progre
ss, styleSheetsParsedCallback) | 499 WebInspector.AuditRules.StyleSheetProcessor = class { |
| 518 { | 500 /** |
| 501 * @param {!Array.<!WebInspector.CSSStyleSheetHeader>} styleSheetHeaders |
| 502 * @param {!WebInspector.Progress} progress |
| 503 * @param {function(!Array.<!WebInspector.AuditRules.ParsedStyleSheet>)} style
SheetsParsedCallback |
| 504 */ |
| 505 constructor(styleSheetHeaders, progress, styleSheetsParsedCallback) { |
| 519 this._styleSheetHeaders = styleSheetHeaders; | 506 this._styleSheetHeaders = styleSheetHeaders; |
| 520 this._progress = progress; | 507 this._progress = progress; |
| 521 this._styleSheets = []; | 508 this._styleSheets = []; |
| 522 this._styleSheetsParsedCallback = styleSheetsParsedCallback; | 509 this._styleSheetsParsedCallback = styleSheetsParsedCallback; |
| 510 } |
| 511 |
| 512 run() { |
| 513 this._parser = new WebInspector.CSSParser(); |
| 514 this._processNextStyleSheet(); |
| 515 } |
| 516 |
| 517 _terminateWorker() { |
| 518 if (this._parser) { |
| 519 this._parser.dispose(); |
| 520 delete this._parser; |
| 521 } |
| 522 } |
| 523 |
| 524 _finish() { |
| 525 this._terminateWorker(); |
| 526 this._styleSheetsParsedCallback(this._styleSheets); |
| 527 } |
| 528 |
| 529 _processNextStyleSheet() { |
| 530 if (!this._styleSheetHeaders.length) { |
| 531 this._finish(); |
| 532 return; |
| 533 } |
| 534 this._currentStyleSheetHeader = this._styleSheetHeaders.shift(); |
| 535 this._parser.fetchAndParse(this._currentStyleSheetHeader, this._onStyleSheet
Parsed.bind(this)); |
| 536 } |
| 537 |
| 538 /** |
| 539 * @param {!Array.<!WebInspector.CSSParser.Rule>} rules |
| 540 */ |
| 541 _onStyleSheetParsed(rules) { |
| 542 if (this._progress.isCanceled()) { |
| 543 this._finish(); |
| 544 return; |
| 545 } |
| 546 |
| 547 var styleRules = []; |
| 548 for (var i = 0; i < rules.length; ++i) { |
| 549 var rule = rules[i]; |
| 550 if (rule.selectorText) |
| 551 styleRules.push(rule); |
| 552 } |
| 553 this._styleSheets.push({sourceURL: this._currentStyleSheetHeader.sourceURL,
rules: styleRules}); |
| 554 this._processNextStyleSheet(); |
| 555 } |
| 523 }; | 556 }; |
| 524 | 557 |
| 525 WebInspector.AuditRules.StyleSheetProcessor.prototype = { | 558 /** |
| 526 run: function() | 559 * @unrestricted |
| 527 { | 560 */ |
| 528 this._parser = new WebInspector.CSSParser(); | 561 WebInspector.AuditRules.CacheControlRule = class extends WebInspector.AuditRule
{ |
| 529 this._processNextStyleSheet(); | 562 constructor(id, name) { |
| 530 }, | 563 super(id, name); |
| 531 | 564 } |
| 532 _terminateWorker: function() | 565 |
| 533 { | 566 /** |
| 534 if (this._parser) { | 567 * @override |
| 535 this._parser.dispose(); | 568 * @param {!WebInspector.Target} target |
| 536 delete this._parser; | 569 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 570 * @param {!WebInspector.AuditRuleResult} result |
| 571 * @param {function(!WebInspector.AuditRuleResult)} callback |
| 572 * @param {!WebInspector.Progress} progress |
| 573 */ |
| 574 doRun(target, requests, result, callback, progress) { |
| 575 var cacheableAndNonCacheableResources = this._cacheableAndNonCacheableResour
ces(requests); |
| 576 if (cacheableAndNonCacheableResources[0].length) |
| 577 this.runChecks(cacheableAndNonCacheableResources[0], result); |
| 578 this.handleNonCacheableResources(cacheableAndNonCacheableResources[1], resul
t); |
| 579 |
| 580 callback(result); |
| 581 } |
| 582 |
| 583 handleNonCacheableResources(requests, result) { |
| 584 } |
| 585 |
| 586 _cacheableAndNonCacheableResources(requests) { |
| 587 var processedResources = [[], []]; |
| 588 for (var i = 0; i < requests.length; ++i) { |
| 589 var request = requests[i]; |
| 590 if (!this.isCacheableResource(request)) |
| 591 continue; |
| 592 if (this._isExplicitlyNonCacheable(request)) |
| 593 processedResources[1].push(request); |
| 594 else |
| 595 processedResources[0].push(request); |
| 596 } |
| 597 return processedResources; |
| 598 } |
| 599 |
| 600 execCheck(messageText, requestCheckFunction, requests, result) { |
| 601 var requestCount = requests.length; |
| 602 var urls = []; |
| 603 for (var i = 0; i < requestCount; ++i) { |
| 604 if (requestCheckFunction.call(this, requests[i])) |
| 605 urls.push(requests[i].url); |
| 606 } |
| 607 if (urls.length) { |
| 608 var entry = result.addChild(messageText, true); |
| 609 entry.addURLs(urls); |
| 610 result.violationCount += urls.length; |
| 611 } |
| 612 } |
| 613 |
| 614 /** |
| 615 * @param {!WebInspector.NetworkRequest} request |
| 616 * @param {number} timeMs |
| 617 * @return {boolean} |
| 618 */ |
| 619 freshnessLifetimeGreaterThan(request, timeMs) { |
| 620 var dateHeader = this.responseHeader(request, 'Date'); |
| 621 if (!dateHeader) |
| 622 return false; |
| 623 |
| 624 var dateHeaderMs = Date.parse(dateHeader); |
| 625 if (isNaN(dateHeaderMs)) |
| 626 return false; |
| 627 |
| 628 var freshnessLifetimeMs; |
| 629 var maxAgeMatch = this.responseHeaderMatch(request, 'Cache-Control', 'max-ag
e=(\\d+)'); |
| 630 |
| 631 if (maxAgeMatch) |
| 632 freshnessLifetimeMs = (maxAgeMatch[1]) ? 1000 * maxAgeMatch[1] : 0; |
| 633 else { |
| 634 var expiresHeader = this.responseHeader(request, 'Expires'); |
| 635 if (expiresHeader) { |
| 636 var expDate = Date.parse(expiresHeader); |
| 637 if (!isNaN(expDate)) |
| 638 freshnessLifetimeMs = expDate - dateHeaderMs; |
| 639 } |
| 640 } |
| 641 |
| 642 return (isNaN(freshnessLifetimeMs)) ? false : freshnessLifetimeMs > timeMs; |
| 643 } |
| 644 |
| 645 /** |
| 646 * @param {!WebInspector.NetworkRequest} request |
| 647 * @param {string} header |
| 648 * @return {string|undefined} |
| 649 */ |
| 650 responseHeader(request, header) { |
| 651 return request.responseHeaderValue(header); |
| 652 } |
| 653 |
| 654 /** |
| 655 * @param {!WebInspector.NetworkRequest} request |
| 656 * @param {string} header |
| 657 * @return {boolean} |
| 658 */ |
| 659 hasResponseHeader(request, header) { |
| 660 return request.responseHeaderValue(header) !== undefined; |
| 661 } |
| 662 |
| 663 /** |
| 664 * @param {!WebInspector.NetworkRequest} request |
| 665 * @return {boolean} |
| 666 */ |
| 667 isCompressible(request) { |
| 668 return request.resourceType().isTextType(); |
| 669 } |
| 670 |
| 671 /** |
| 672 * @param {!WebInspector.NetworkRequest} request |
| 673 * @return {boolean} |
| 674 */ |
| 675 isPubliclyCacheable(request) { |
| 676 if (this._isExplicitlyNonCacheable(request)) |
| 677 return false; |
| 678 |
| 679 if (this.responseHeaderMatch(request, 'Cache-Control', 'public')) |
| 680 return true; |
| 681 |
| 682 return request.url.indexOf('?') === -1 && !this.responseHeaderMatch(request,
'Cache-Control', 'private'); |
| 683 } |
| 684 |
| 685 /** |
| 686 * @param {!WebInspector.NetworkRequest} request |
| 687 * @param {string} header |
| 688 * @param {string} regexp |
| 689 * @return {?Array.<string>} |
| 690 */ |
| 691 responseHeaderMatch(request, header, regexp) { |
| 692 return request.responseHeaderValue(header) ? request.responseHeaderValue(hea
der).match(new RegExp(regexp, 'im')) : |
| 693 null; |
| 694 } |
| 695 |
| 696 /** |
| 697 * @param {!WebInspector.NetworkRequest} request |
| 698 * @return {boolean} |
| 699 */ |
| 700 hasExplicitExpiration(request) { |
| 701 return this.hasResponseHeader(request, 'Date') && |
| 702 (this.hasResponseHeader(request, 'Expires') || !!this.responseHeaderMatc
h(request, 'Cache-Control', 'max-age')); |
| 703 } |
| 704 |
| 705 /** |
| 706 * @param {!WebInspector.NetworkRequest} request |
| 707 * @return {boolean} |
| 708 */ |
| 709 _isExplicitlyNonCacheable(request) { |
| 710 var hasExplicitExp = this.hasExplicitExpiration(request); |
| 711 return !!this.responseHeaderMatch(request, 'Cache-Control', '(no-cache|no-st
ore)') || |
| 712 !!this.responseHeaderMatch(request, 'Pragma', 'no-cache') || |
| 713 (hasExplicitExp && !this.freshnessLifetimeGreaterThan(request, 0)) || |
| 714 (!hasExplicitExp && !!request.url && request.url.indexOf('?') >= 0) || |
| 715 (!hasExplicitExp && !this.isCacheableResource(request)); |
| 716 } |
| 717 |
| 718 /** |
| 719 * @param {!WebInspector.NetworkRequest} request |
| 720 * @return {boolean} |
| 721 */ |
| 722 isCacheableResource(request) { |
| 723 return request.statusCode !== undefined && WebInspector.AuditRules.Cacheable
ResponseCodes[request.statusCode]; |
| 724 } |
| 725 }; |
| 726 |
| 727 WebInspector.AuditRules.CacheControlRule.MillisPerMonth = 1000 * 60 * 60 * 24 *
30; |
| 728 |
| 729 /** |
| 730 * @unrestricted |
| 731 */ |
| 732 WebInspector.AuditRules.BrowserCacheControlRule = class extends WebInspector.Aud
itRules.CacheControlRule { |
| 733 constructor() { |
| 734 super('http-browsercache', WebInspector.UIString('Leverage browser caching')
); |
| 735 } |
| 736 |
| 737 /** |
| 738 * @override |
| 739 */ |
| 740 handleNonCacheableResources(requests, result) { |
| 741 if (requests.length) { |
| 742 var entry = result.addChild( |
| 743 WebInspector.UIString( |
| 744 'The following resources are explicitly non-cacheable. Consider ma
king them cacheable if possible:'), |
| 745 true); |
| 746 result.violationCount += requests.length; |
| 747 for (var i = 0; i < requests.length; ++i) |
| 748 entry.addURL(requests[i].url); |
| 749 } |
| 750 } |
| 751 |
| 752 runChecks(requests, result, callback) { |
| 753 this.execCheck( |
| 754 WebInspector.UIString( |
| 755 'The following resources are missing a cache expiration. Resources t
hat do not specify an expiration may not be cached by browsers:'), |
| 756 this._missingExpirationCheck, requests, result); |
| 757 this.execCheck( |
| 758 WebInspector.UIString( |
| 759 'The following resources specify a "Vary" header that disables cachi
ng in most versions of Internet Explorer:'), |
| 760 this._varyCheck, requests, result); |
| 761 this.execCheck( |
| 762 WebInspector.UIString('The following cacheable resources have a short fr
eshness lifetime:'), |
| 763 this._oneMonthExpirationCheck, requests, result); |
| 764 |
| 765 // Unable to implement the favicon check due to the WebKit limitations. |
| 766 this.execCheck( |
| 767 WebInspector.UIString( |
| 768 'To further improve cache hit rate, specify an expiration one year i
n the future for the following cacheable resources:'), |
| 769 this._oneYearExpirationCheck, requests, result); |
| 770 } |
| 771 |
| 772 _missingExpirationCheck(request) { |
| 773 return this.isCacheableResource(request) && !this.hasResponseHeader(request,
'Set-Cookie') && |
| 774 !this.hasExplicitExpiration(request); |
| 775 } |
| 776 |
| 777 _varyCheck(request) { |
| 778 var varyHeader = this.responseHeader(request, 'Vary'); |
| 779 if (varyHeader) { |
| 780 varyHeader = varyHeader.replace(/User-Agent/gi, ''); |
| 781 varyHeader = varyHeader.replace(/Accept-Encoding/gi, ''); |
| 782 varyHeader = varyHeader.replace(/[, ]*/g, ''); |
| 783 } |
| 784 return varyHeader && varyHeader.length && this.isCacheableResource(request)
&& |
| 785 this.freshnessLifetimeGreaterThan(request, 0); |
| 786 } |
| 787 |
| 788 _oneMonthExpirationCheck(request) { |
| 789 return this.isCacheableResource(request) && !this.hasResponseHeader(request,
'Set-Cookie') && |
| 790 !this.freshnessLifetimeGreaterThan(request, WebInspector.AuditRules.Cach
eControlRule.MillisPerMonth) && |
| 791 this.freshnessLifetimeGreaterThan(request, 0); |
| 792 } |
| 793 |
| 794 _oneYearExpirationCheck(request) { |
| 795 return this.isCacheableResource(request) && !this.hasResponseHeader(request,
'Set-Cookie') && |
| 796 !this.freshnessLifetimeGreaterThan(request, 11 * WebInspector.AuditRules
.CacheControlRule.MillisPerMonth) && |
| 797 this.freshnessLifetimeGreaterThan(request, WebInspector.AuditRules.Cache
ControlRule.MillisPerMonth); |
| 798 } |
| 799 }; |
| 800 |
| 801 /** |
| 802 * @unrestricted |
| 803 */ |
| 804 WebInspector.AuditRules.ImageDimensionsRule = class extends WebInspector.AuditRu
le { |
| 805 constructor() { |
| 806 super('page-imagedims', WebInspector.UIString('Specify image dimensions')); |
| 807 } |
| 808 |
| 809 /** |
| 810 * @override |
| 811 * @param {!WebInspector.Target} target |
| 812 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 813 * @param {!WebInspector.AuditRuleResult} result |
| 814 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 815 * @param {!WebInspector.Progress} progress |
| 816 */ |
| 817 doRun(target, requests, result, callback, progress) { |
| 818 var domModel = WebInspector.DOMModel.fromTarget(target); |
| 819 var cssModel = WebInspector.CSSModel.fromTarget(target); |
| 820 if (!domModel || !cssModel) { |
| 821 callback(null); |
| 822 return; |
| 823 } |
| 824 |
| 825 var urlToNoDimensionCount = {}; |
| 826 |
| 827 function doneCallback() { |
| 828 for (var url in urlToNoDimensionCount) { |
| 829 var entry = entry || |
| 830 result.addChild( |
| 831 WebInspector.UIString( |
| 832 'A width and height should be specified for all images in or
der to speed up page display. The following image(s) are missing a width and/or
height:'), |
| 833 true); |
| 834 var format = '%r'; |
| 835 if (urlToNoDimensionCount[url] > 1) |
| 836 format += ' (%d uses)'; |
| 837 entry.addFormatted(format, url, urlToNoDimensionCount[url]); |
| 838 result.violationCount++; |
| 839 } |
| 840 callback(entry ? result : null); |
| 841 } |
| 842 |
| 843 function imageStylesReady(imageId, styles) { |
| 844 if (progress.isCanceled()) { |
| 845 callback(null); |
| 846 return; |
| 847 } |
| 848 |
| 849 const node = domModel.nodeForId(imageId); |
| 850 var src = node.getAttribute('src'); |
| 851 if (!src.asParsedURL()) { |
| 852 for (var frameOwnerCandidate = node; frameOwnerCandidate; |
| 853 frameOwnerCandidate = frameOwnerCandidate.parentNode) { |
| 854 if (frameOwnerCandidate.baseURL) { |
| 855 var completeSrc = WebInspector.ParsedURL.completeURL(frameOwnerCandi
date.baseURL, src); |
| 856 break; |
| 857 } |
| 537 } | 858 } |
| 538 }, | 859 } |
| 539 | 860 if (completeSrc) |
| 540 _finish: function() | 861 src = completeSrc; |
| 541 { | 862 |
| 542 this._terminateWorker(); | 863 if (styles.computedStyle.get('position') === 'absolute') |
| 543 this._styleSheetsParsedCallback(this._styleSheets); | 864 return; |
| 544 }, | 865 |
| 545 | 866 var widthFound = false; |
| 546 _processNextStyleSheet: function() | 867 var heightFound = false; |
| 547 { | 868 for (var i = 0; !(widthFound && heightFound) && i < styles.nodeStyles.leng
th; ++i) { |
| 548 if (!this._styleSheetHeaders.length) { | 869 var style = styles.nodeStyles[i]; |
| 549 this._finish(); | 870 if (style.getPropertyValue('width') !== '') |
| 550 return; | 871 widthFound = true; |
| 872 if (style.getPropertyValue('height') !== '') |
| 873 heightFound = true; |
| 874 } |
| 875 |
| 876 if (!widthFound || !heightFound) { |
| 877 if (src in urlToNoDimensionCount) |
| 878 ++urlToNoDimensionCount[src]; |
| 879 else |
| 880 urlToNoDimensionCount[src] = 1; |
| 881 } |
| 882 } |
| 883 |
| 884 /** |
| 885 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds |
| 886 */ |
| 887 function getStyles(nodeIds) { |
| 888 if (progress.isCanceled()) { |
| 889 callback(null); |
| 890 return; |
| 891 } |
| 892 var targetResult = {}; |
| 893 |
| 894 /** |
| 895 * @param {?WebInspector.CSSMatchedStyles} matchedStyleResult |
| 896 */ |
| 897 function matchedCallback(matchedStyleResult) { |
| 898 if (!matchedStyleResult) |
| 899 return; |
| 900 targetResult.nodeStyles = matchedStyleResult.nodeStyles(); |
| 901 } |
| 902 |
| 903 /** |
| 904 * @param {?Map.<string, string>} computedStyle |
| 905 */ |
| 906 function computedCallback(computedStyle) { |
| 907 targetResult.computedStyle = computedStyle; |
| 908 } |
| 909 |
| 910 if (!nodeIds || !nodeIds.length) |
| 911 doneCallback(); |
| 912 |
| 913 var nodePromises = []; |
| 914 for (var i = 0; nodeIds && i < nodeIds.length; ++i) { |
| 915 var stylePromises = [ |
| 916 cssModel.matchedStylesPromise(nodeIds[i]).then(matchedCallback), |
| 917 cssModel.computedStylePromise(nodeIds[i]).then(computedCallback) |
| 918 ]; |
| 919 var nodePromise = Promise.all(stylePromises).then(imageStylesReady.bind(
null, nodeIds[i], targetResult)); |
| 920 nodePromises.push(nodePromise); |
| 921 } |
| 922 Promise.all(nodePromises).catchException(null).then(doneCallback); |
| 923 } |
| 924 |
| 925 function onDocumentAvailable(root) { |
| 926 if (progress.isCanceled()) { |
| 927 callback(null); |
| 928 return; |
| 929 } |
| 930 domModel.querySelectorAll(root.id, 'img[src]', getStyles); |
| 931 } |
| 932 |
| 933 if (progress.isCanceled()) { |
| 934 callback(null); |
| 935 return; |
| 936 } |
| 937 domModel.requestDocument(onDocumentAvailable); |
| 938 } |
| 939 }; |
| 940 |
| 941 /** |
| 942 * @unrestricted |
| 943 */ |
| 944 WebInspector.AuditRules.CssInHeadRule = class extends WebInspector.AuditRule { |
| 945 constructor() { |
| 946 super('page-cssinhead', WebInspector.UIString('Put CSS in the document head'
)); |
| 947 } |
| 948 |
| 949 /** |
| 950 * @override |
| 951 * @param {!WebInspector.Target} target |
| 952 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 953 * @param {!WebInspector.AuditRuleResult} result |
| 954 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 955 * @param {!WebInspector.Progress} progress |
| 956 */ |
| 957 doRun(target, requests, result, callback, progress) { |
| 958 var domModel = WebInspector.DOMModel.fromTarget(target); |
| 959 if (!domModel) { |
| 960 callback(null); |
| 961 return; |
| 962 } |
| 963 |
| 964 function evalCallback(evalResult) { |
| 965 if (progress.isCanceled()) { |
| 966 callback(null); |
| 967 return; |
| 968 } |
| 969 |
| 970 if (!evalResult) |
| 971 return callback(null); |
| 972 |
| 973 var summary = result.addChild(''); |
| 974 |
| 975 for (var url in evalResult) { |
| 976 var urlViolations = evalResult[url]; |
| 977 if (urlViolations[0]) { |
| 978 result.addFormatted( |
| 979 '%s style block(s) in the %r body should be moved to the document
head.', urlViolations[0], url); |
| 980 result.violationCount += urlViolations[0]; |
| 551 } | 981 } |
| 552 this._currentStyleSheetHeader = this._styleSheetHeaders.shift(); | 982 for (var i = 0; i < urlViolations[1].length; ++i) |
| 553 this._parser.fetchAndParse(this._currentStyleSheetHeader, this._onStyleS
heetParsed.bind(this)); | 983 result.addFormatted('Link node %r should be moved to the document head
in %r', urlViolations[1][i], url); |
| 554 }, | 984 result.violationCount += urlViolations[1].length; |
| 985 } |
| 986 summary.value = WebInspector.UIString('CSS in the document body adversely
impacts rendering performance.'); |
| 987 callback(result); |
| 988 } |
| 555 | 989 |
| 556 /** | 990 /** |
| 557 * @param {!Array.<!WebInspector.CSSParser.Rule>} rules | 991 * @param {!WebInspector.DOMNode} root |
| 992 * @param {!Array.<!DOMAgent.NodeId>=} inlineStyleNodeIds |
| 993 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds |
| 558 */ | 994 */ |
| 559 _onStyleSheetParsed: function(rules) | 995 function externalStylesheetsReceived(root, inlineStyleNodeIds, nodeIds) { |
| 560 { | 996 if (progress.isCanceled()) { |
| 561 if (this._progress.isCanceled()) { | 997 callback(null); |
| 562 this._finish(); | 998 return; |
| 563 return; | 999 } |
| 1000 |
| 1001 if (!nodeIds) |
| 1002 return; |
| 1003 var externalStylesheetNodeIds = nodeIds; |
| 1004 var result = null; |
| 1005 if (inlineStyleNodeIds.length || externalStylesheetNodeIds.length) { |
| 1006 var urlToViolationsArray = {}; |
| 1007 var externalStylesheetHrefs = []; |
| 1008 for (var j = 0; j < externalStylesheetNodeIds.length; ++j) { |
| 1009 var linkNode = domModel.nodeForId(externalStylesheetNodeIds[j]); |
| 1010 var completeHref = |
| 1011 WebInspector.ParsedURL.completeURL(linkNode.ownerDocument.baseURL,
linkNode.getAttribute('href')); |
| 1012 externalStylesheetHrefs.push(completeHref || '<empty>'); |
| 564 } | 1013 } |
| 565 | 1014 urlToViolationsArray[root.documentURL] = [inlineStyleNodeIds.length, ext
ernalStylesheetHrefs]; |
| 566 var styleRules = []; | 1015 result = urlToViolationsArray; |
| 567 for (var i = 0; i < rules.length; ++i) { | 1016 } |
| 568 var rule = rules[i]; | 1017 evalCallback(result); |
| 569 if (rule.selectorText) | 1018 } |
| 570 styleRules.push(rule); | 1019 |
| 1020 /** |
| 1021 * @param {!WebInspector.DOMNode} root |
| 1022 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds |
| 1023 */ |
| 1024 function inlineStylesReceived(root, nodeIds) { |
| 1025 if (progress.isCanceled()) { |
| 1026 callback(null); |
| 1027 return; |
| 1028 } |
| 1029 |
| 1030 if (!nodeIds) |
| 1031 return; |
| 1032 domModel.querySelectorAll( |
| 1033 root.id, 'body link[rel~=\'stylesheet\'][href]', externalStylesheetsRe
ceived.bind(null, root, nodeIds)); |
| 1034 } |
| 1035 |
| 1036 /** |
| 1037 * @param {!WebInspector.DOMNode} root |
| 1038 */ |
| 1039 function onDocumentAvailable(root) { |
| 1040 if (progress.isCanceled()) { |
| 1041 callback(null); |
| 1042 return; |
| 1043 } |
| 1044 |
| 1045 domModel.querySelectorAll(root.id, 'body style', inlineStylesReceived.bind
(null, root)); |
| 1046 } |
| 1047 |
| 1048 domModel.requestDocument(onDocumentAvailable); |
| 1049 } |
| 1050 }; |
| 1051 |
| 1052 /** |
| 1053 * @unrestricted |
| 1054 */ |
| 1055 WebInspector.AuditRules.StylesScriptsOrderRule = class extends WebInspector.Audi
tRule { |
| 1056 constructor() { |
| 1057 super('page-stylescriptorder', WebInspector.UIString('Optimize the order of
styles and scripts')); |
| 1058 } |
| 1059 |
| 1060 /** |
| 1061 * @override |
| 1062 * @param {!WebInspector.Target} target |
| 1063 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 1064 * @param {!WebInspector.AuditRuleResult} result |
| 1065 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 1066 * @param {!WebInspector.Progress} progress |
| 1067 */ |
| 1068 doRun(target, requests, result, callback, progress) { |
| 1069 var domModel = WebInspector.DOMModel.fromTarget(target); |
| 1070 if (!domModel) { |
| 1071 callback(null); |
| 1072 return; |
| 1073 } |
| 1074 |
| 1075 function evalCallback(resultValue) { |
| 1076 if (progress.isCanceled()) { |
| 1077 callback(null); |
| 1078 return; |
| 1079 } |
| 1080 |
| 1081 if (!resultValue) |
| 1082 return callback(null); |
| 1083 |
| 1084 var lateCssUrls = resultValue[0]; |
| 1085 var cssBeforeInlineCount = resultValue[1]; |
| 1086 |
| 1087 if (lateCssUrls.length) { |
| 1088 var entry = result.addChild( |
| 1089 WebInspector.UIString( |
| 1090 'The following external CSS files were included after an externa
l JavaScript file in the document head. To ensure CSS files are downloaded in pa
rallel, always include external CSS before external JavaScript.'), |
| 1091 true); |
| 1092 entry.addURLs(lateCssUrls); |
| 1093 result.violationCount += lateCssUrls.length; |
| 1094 } |
| 1095 |
| 1096 if (cssBeforeInlineCount) { |
| 1097 result.addChild(WebInspector.UIString( |
| 1098 ' %d inline script block%s found in the head between an external CSS
file and another resource. To allow parallel downloading, move the inline scrip
t before the external CSS file, or after the next resource.', |
| 1099 cssBeforeInlineCount, cssBeforeInlineCount > 1 ? 's were' : ' was'))
; |
| 1100 result.violationCount += cssBeforeInlineCount; |
| 1101 } |
| 1102 callback(result); |
| 1103 } |
| 1104 |
| 1105 /** |
| 1106 * @param {!Array.<!DOMAgent.NodeId>} lateStyleIds |
| 1107 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds |
| 1108 */ |
| 1109 function cssBeforeInlineReceived(lateStyleIds, nodeIds) { |
| 1110 if (progress.isCanceled()) { |
| 1111 callback(null); |
| 1112 return; |
| 1113 } |
| 1114 |
| 1115 if (!nodeIds) |
| 1116 return; |
| 1117 |
| 1118 var cssBeforeInlineCount = nodeIds.length; |
| 1119 var result = null; |
| 1120 if (lateStyleIds.length || cssBeforeInlineCount) { |
| 1121 var lateStyleUrls = []; |
| 1122 for (var i = 0; i < lateStyleIds.length; ++i) { |
| 1123 var lateStyleNode = domModel.nodeForId(lateStyleIds[i]); |
| 1124 var completeHref = WebInspector.ParsedURL.completeURL( |
| 1125 lateStyleNode.ownerDocument.baseURL, lateStyleNode.getAttribute('h
ref')); |
| 1126 lateStyleUrls.push(completeHref || '<empty>'); |
| 571 } | 1127 } |
| 572 this._styleSheets.push({ | 1128 result = [lateStyleUrls, cssBeforeInlineCount]; |
| 573 sourceURL: this._currentStyleSheetHeader.sourceURL, | 1129 } |
| 574 rules: styleRules | 1130 |
| 575 }); | 1131 evalCallback(result); |
| 576 this._processNextStyleSheet(); | 1132 } |
| 577 }, | 1133 |
| 1134 /** |
| 1135 * @param {!WebInspector.DOMDocument} root |
| 1136 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds |
| 1137 */ |
| 1138 function lateStylesReceived(root, nodeIds) { |
| 1139 if (progress.isCanceled()) { |
| 1140 callback(null); |
| 1141 return; |
| 1142 } |
| 1143 |
| 1144 if (!nodeIds) |
| 1145 return; |
| 1146 |
| 1147 domModel.querySelectorAll( |
| 1148 root.id, 'head link[rel~=\'stylesheet\'][href] ~ script:not([src])', |
| 1149 cssBeforeInlineReceived.bind(null, nodeIds)); |
| 1150 } |
| 1151 |
| 1152 /** |
| 1153 * @param {!WebInspector.DOMDocument} root |
| 1154 */ |
| 1155 function onDocumentAvailable(root) { |
| 1156 if (progress.isCanceled()) { |
| 1157 callback(null); |
| 1158 return; |
| 1159 } |
| 1160 |
| 1161 domModel.querySelectorAll( |
| 1162 root.id, 'head script[src] ~ link[rel~=\'stylesheet\'][href]', lateSty
lesReceived.bind(null, root)); |
| 1163 } |
| 1164 |
| 1165 domModel.requestDocument(onDocumentAvailable); |
| 1166 } |
| 578 }; | 1167 }; |
| 579 | 1168 |
| 580 /** | 1169 /** |
| 581 * @constructor | 1170 * @unrestricted |
| 582 * @extends {WebInspector.AuditRule} | |
| 583 */ | 1171 */ |
| 584 WebInspector.AuditRules.CacheControlRule = function(id, name) | 1172 WebInspector.AuditRules.CSSRuleBase = class extends WebInspector.AuditRule { |
| 585 { | 1173 constructor(id, name) { |
| 586 WebInspector.AuditRule.call(this, id, name); | 1174 super(id, name); |
| 1175 } |
| 1176 |
| 1177 /** |
| 1178 * @override |
| 1179 * @param {!WebInspector.Target} target |
| 1180 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 1181 * @param {!WebInspector.AuditRuleResult} result |
| 1182 * @param {function(?WebInspector.AuditRuleResult)} callback |
| 1183 * @param {!WebInspector.Progress} progress |
| 1184 */ |
| 1185 doRun(target, requests, result, callback, progress) { |
| 1186 var cssModel = WebInspector.CSSModel.fromTarget(target); |
| 1187 if (!cssModel) { |
| 1188 callback(null); |
| 1189 return; |
| 1190 } |
| 1191 |
| 1192 var headers = cssModel.allStyleSheets(); |
| 1193 if (!headers.length) { |
| 1194 callback(null); |
| 1195 return; |
| 1196 } |
| 1197 var activeHeaders = []; |
| 1198 for (var i = 0; i < headers.length; ++i) { |
| 1199 if (!headers[i].disabled) |
| 1200 activeHeaders.push(headers[i]); |
| 1201 } |
| 1202 |
| 1203 var styleSheetProcessor = new WebInspector.AuditRules.StyleSheetProcessor( |
| 1204 activeHeaders, progress, this._styleSheetsLoaded.bind(this, result, call
back, progress)); |
| 1205 styleSheetProcessor.run(); |
| 1206 } |
| 1207 |
| 1208 /** |
| 1209 * @param {!WebInspector.AuditRuleResult} result |
| 1210 * @param {function(!WebInspector.AuditRuleResult)} callback |
| 1211 * @param {!WebInspector.Progress} progress |
| 1212 * @param {!Array.<!WebInspector.AuditRules.ParsedStyleSheet>} styleSheets |
| 1213 */ |
| 1214 _styleSheetsLoaded(result, callback, progress, styleSheets) { |
| 1215 for (var i = 0; i < styleSheets.length; ++i) |
| 1216 this._visitStyleSheet(styleSheets[i], result); |
| 1217 callback(result); |
| 1218 } |
| 1219 |
| 1220 /** |
| 1221 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet |
| 1222 * @param {!WebInspector.AuditRuleResult} result |
| 1223 */ |
| 1224 _visitStyleSheet(styleSheet, result) { |
| 1225 this.visitStyleSheet(styleSheet, result); |
| 1226 |
| 1227 for (var i = 0; i < styleSheet.rules.length; ++i) |
| 1228 this._visitRule(styleSheet, styleSheet.rules[i], result); |
| 1229 |
| 1230 this.didVisitStyleSheet(styleSheet, result); |
| 1231 } |
| 1232 |
| 1233 /** |
| 1234 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet |
| 1235 * @param {!WebInspector.CSSParser.StyleRule} rule |
| 1236 * @param {!WebInspector.AuditRuleResult} result |
| 1237 */ |
| 1238 _visitRule(styleSheet, rule, result) { |
| 1239 this.visitRule(styleSheet, rule, result); |
| 1240 var allProperties = rule.properties; |
| 1241 for (var i = 0; i < allProperties.length; ++i) |
| 1242 this.visitProperty(styleSheet, rule, allProperties[i], result); |
| 1243 this.didVisitRule(styleSheet, rule, result); |
| 1244 } |
| 1245 |
| 1246 /** |
| 1247 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet |
| 1248 * @param {!WebInspector.AuditRuleResult} result |
| 1249 */ |
| 1250 visitStyleSheet(styleSheet, result) { |
| 1251 // Subclasses can implement. |
| 1252 } |
| 1253 |
| 1254 /** |
| 1255 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet |
| 1256 * @param {!WebInspector.AuditRuleResult} result |
| 1257 */ |
| 1258 didVisitStyleSheet(styleSheet, result) { |
| 1259 // Subclasses can implement. |
| 1260 } |
| 1261 |
| 1262 /** |
| 1263 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet |
| 1264 * @param {!WebInspector.CSSParser.StyleRule} rule |
| 1265 * @param {!WebInspector.AuditRuleResult} result |
| 1266 */ |
| 1267 visitRule(styleSheet, rule, result) { |
| 1268 // Subclasses can implement. |
| 1269 } |
| 1270 |
| 1271 /** |
| 1272 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet |
| 1273 * @param {!WebInspector.CSSParser.StyleRule} rule |
| 1274 * @param {!WebInspector.AuditRuleResult} result |
| 1275 */ |
| 1276 didVisitRule(styleSheet, rule, result) { |
| 1277 // Subclasses can implement. |
| 1278 } |
| 1279 |
| 1280 /** |
| 1281 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet |
| 1282 * @param {!WebInspector.CSSParser.StyleRule} rule |
| 1283 * @param {!WebInspector.CSSParser.Property} property |
| 1284 * @param {!WebInspector.AuditRuleResult} result |
| 1285 */ |
| 1286 visitProperty(styleSheet, rule, property, result) { |
| 1287 // Subclasses can implement. |
| 1288 } |
| 587 }; | 1289 }; |
| 588 | 1290 |
| 589 WebInspector.AuditRules.CacheControlRule.MillisPerMonth = 1000 * 60 * 60 * 24 *
30; | 1291 /** |
| 590 | 1292 * @unrestricted |
| 591 WebInspector.AuditRules.CacheControlRule.prototype = { | 1293 */ |
| 592 /** | 1294 WebInspector.AuditRules.CookieRuleBase = class extends WebInspector.AuditRule { |
| 593 * @override | 1295 constructor(id, name) { |
| 594 * @param {!WebInspector.Target} target | 1296 super(id, name); |
| 595 * @param {!Array.<!WebInspector.NetworkRequest>} requests | 1297 } |
| 596 * @param {!WebInspector.AuditRuleResult} result | 1298 |
| 597 * @param {function(!WebInspector.AuditRuleResult)} callback | 1299 /** |
| 598 * @param {!WebInspector.Progress} progress | 1300 * @override |
| 599 */ | 1301 * @param {!WebInspector.Target} target |
| 600 doRun: function(target, requests, result, callback, progress) | 1302 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 601 { | 1303 * @param {!WebInspector.AuditRuleResult} result |
| 602 var cacheableAndNonCacheableResources = this._cacheableAndNonCacheableRe
sources(requests); | 1304 * @param {function(!WebInspector.AuditRuleResult)} callback |
| 603 if (cacheableAndNonCacheableResources[0].length) | 1305 * @param {!WebInspector.Progress} progress |
| 604 this.runChecks(cacheableAndNonCacheableResources[0], result); | 1306 */ |
| 605 this.handleNonCacheableResources(cacheableAndNonCacheableResources[1], r
esult); | 1307 doRun(target, requests, result, callback, progress) { |
| 606 | 1308 var self = this; |
| 1309 function resultCallback(receivedCookies) { |
| 1310 if (progress.isCanceled()) { |
| 607 callback(result); | 1311 callback(result); |
| 608 }, | 1312 return; |
| 609 | 1313 } |
| 610 handleNonCacheableResources: function(requests, result) | 1314 |
| 611 { | 1315 self.processCookies(receivedCookies, requests, result); |
| 612 }, | 1316 callback(result); |
| 613 | 1317 } |
| 614 _cacheableAndNonCacheableResources: function(requests) | 1318 |
| 615 { | 1319 WebInspector.Cookies.getCookiesAsync(resultCallback); |
| 616 var processedResources = [[], []]; | 1320 } |
| 617 for (var i = 0; i < requests.length; ++i) { | 1321 |
| 618 var request = requests[i]; | 1322 mapResourceCookies(requestsByDomain, allCookies, callback) { |
| 619 if (!this.isCacheableResource(request)) | 1323 for (var i = 0; i < allCookies.length; ++i) { |
| 620 continue; | 1324 for (var requestDomain in requestsByDomain) { |
| 621 if (this._isExplicitlyNonCacheable(request)) | 1325 if (WebInspector.Cookies.cookieDomainMatchesResourceDomain(allCookies[i]
.domain(), requestDomain)) |
| 622 processedResources[1].push(request); | 1326 this._callbackForResourceCookiePairs(requestsByDomain[requestDomain],
allCookies[i], callback); |
| 623 else | 1327 } |
| 624 processedResources[0].push(request); | 1328 } |
| 625 } | 1329 } |
| 626 return processedResources; | 1330 |
| 627 }, | 1331 _callbackForResourceCookiePairs(requests, cookie, callback) { |
| 628 | 1332 if (!requests) |
| 629 execCheck: function(messageText, requestCheckFunction, requests, result) | 1333 return; |
| 630 { | 1334 for (var i = 0; i < requests.length; ++i) { |
| 631 var requestCount = requests.length; | 1335 if (WebInspector.Cookies.cookieMatchesResourceURL(cookie, requests[i].url)
) |
| 632 var urls = []; | 1336 callback(requests[i], cookie); |
| 633 for (var i = 0; i < requestCount; ++i) { | 1337 } |
| 634 if (requestCheckFunction.call(this, requests[i])) | 1338 } |
| 635 urls.push(requests[i].url); | |
| 636 } | |
| 637 if (urls.length) { | |
| 638 var entry = result.addChild(messageText, true); | |
| 639 entry.addURLs(urls); | |
| 640 result.violationCount += urls.length; | |
| 641 } | |
| 642 }, | |
| 643 | |
| 644 /** | |
| 645 * @param {!WebInspector.NetworkRequest} request | |
| 646 * @param {number} timeMs | |
| 647 * @return {boolean} | |
| 648 */ | |
| 649 freshnessLifetimeGreaterThan: function(request, timeMs) | |
| 650 { | |
| 651 var dateHeader = this.responseHeader(request, "Date"); | |
| 652 if (!dateHeader) | |
| 653 return false; | |
| 654 | |
| 655 var dateHeaderMs = Date.parse(dateHeader); | |
| 656 if (isNaN(dateHeaderMs)) | |
| 657 return false; | |
| 658 | |
| 659 var freshnessLifetimeMs; | |
| 660 var maxAgeMatch = this.responseHeaderMatch(request, "Cache-Control", "ma
x-age=(\\d+)"); | |
| 661 | |
| 662 if (maxAgeMatch) | |
| 663 freshnessLifetimeMs = (maxAgeMatch[1]) ? 1000 * maxAgeMatch[1] : 0; | |
| 664 else { | |
| 665 var expiresHeader = this.responseHeader(request, "Expires"); | |
| 666 if (expiresHeader) { | |
| 667 var expDate = Date.parse(expiresHeader); | |
| 668 if (!isNaN(expDate)) | |
| 669 freshnessLifetimeMs = expDate - dateHeaderMs; | |
| 670 } | |
| 671 } | |
| 672 | |
| 673 return (isNaN(freshnessLifetimeMs)) ? false : freshnessLifetimeMs > time
Ms; | |
| 674 }, | |
| 675 | |
| 676 /** | |
| 677 * @param {!WebInspector.NetworkRequest} request | |
| 678 * @param {string} header | |
| 679 * @return {string|undefined} | |
| 680 */ | |
| 681 responseHeader: function(request, header) | |
| 682 { | |
| 683 return request.responseHeaderValue(header); | |
| 684 }, | |
| 685 | |
| 686 /** | |
| 687 * @param {!WebInspector.NetworkRequest} request | |
| 688 * @param {string} header | |
| 689 * @return {boolean} | |
| 690 */ | |
| 691 hasResponseHeader: function(request, header) | |
| 692 { | |
| 693 return request.responseHeaderValue(header) !== undefined; | |
| 694 }, | |
| 695 | |
| 696 /** | |
| 697 * @param {!WebInspector.NetworkRequest} request | |
| 698 * @return {boolean} | |
| 699 */ | |
| 700 isCompressible: function(request) | |
| 701 { | |
| 702 return request.resourceType().isTextType(); | |
| 703 }, | |
| 704 | |
| 705 /** | |
| 706 * @param {!WebInspector.NetworkRequest} request | |
| 707 * @return {boolean} | |
| 708 */ | |
| 709 isPubliclyCacheable: function(request) | |
| 710 { | |
| 711 if (this._isExplicitlyNonCacheable(request)) | |
| 712 return false; | |
| 713 | |
| 714 if (this.responseHeaderMatch(request, "Cache-Control", "public")) | |
| 715 return true; | |
| 716 | |
| 717 return request.url.indexOf("?") === -1 && !this.responseHeaderMatch(requ
est, "Cache-Control", "private"); | |
| 718 }, | |
| 719 | |
| 720 /** | |
| 721 * @param {!WebInspector.NetworkRequest} request | |
| 722 * @param {string} header | |
| 723 * @param {string} regexp | |
| 724 * @return {?Array.<string>} | |
| 725 */ | |
| 726 responseHeaderMatch: function(request, header, regexp) | |
| 727 { | |
| 728 return request.responseHeaderValue(header) | |
| 729 ? request.responseHeaderValue(header).match(new RegExp(regexp, "im")
) | |
| 730 : null; | |
| 731 }, | |
| 732 | |
| 733 /** | |
| 734 * @param {!WebInspector.NetworkRequest} request | |
| 735 * @return {boolean} | |
| 736 */ | |
| 737 hasExplicitExpiration: function(request) | |
| 738 { | |
| 739 return this.hasResponseHeader(request, "Date") && | |
| 740 (this.hasResponseHeader(request, "Expires") || !!this.responseHeader
Match(request, "Cache-Control", "max-age")); | |
| 741 }, | |
| 742 | |
| 743 /** | |
| 744 * @param {!WebInspector.NetworkRequest} request | |
| 745 * @return {boolean} | |
| 746 */ | |
| 747 _isExplicitlyNonCacheable: function(request) | |
| 748 { | |
| 749 var hasExplicitExp = this.hasExplicitExpiration(request); | |
| 750 return !!this.responseHeaderMatch(request, "Cache-Control", "(no-cache|n
o-store)") || | |
| 751 !!this.responseHeaderMatch(request, "Pragma", "no-cache") || | |
| 752 (hasExplicitExp && !this.freshnessLifetimeGreaterThan(request, 0)) |
| | |
| 753 (!hasExplicitExp && !!request.url && request.url.indexOf("?") >= 0)
|| | |
| 754 (!hasExplicitExp && !this.isCacheableResource(request)); | |
| 755 }, | |
| 756 | |
| 757 /** | |
| 758 * @param {!WebInspector.NetworkRequest} request | |
| 759 * @return {boolean} | |
| 760 */ | |
| 761 isCacheableResource: function(request) | |
| 762 { | |
| 763 return request.statusCode !== undefined && WebInspector.AuditRules.Cache
ableResponseCodes[request.statusCode]; | |
| 764 }, | |
| 765 | |
| 766 __proto__: WebInspector.AuditRule.prototype | |
| 767 }; | 1339 }; |
| 768 | 1340 |
| 769 /** | 1341 /** |
| 770 * @constructor | 1342 * @unrestricted |
| 771 * @extends {WebInspector.AuditRules.CacheControlRule} | |
| 772 */ | 1343 */ |
| 773 WebInspector.AuditRules.BrowserCacheControlRule = function() | 1344 WebInspector.AuditRules.CookieSizeRule = class extends WebInspector.AuditRules.C
ookieRuleBase { |
| 774 { | 1345 constructor(avgBytesThreshold) { |
| 775 WebInspector.AuditRules.CacheControlRule.call(this, "http-browsercache", Web
Inspector.UIString("Leverage browser caching")); | 1346 super('http-cookiesize', WebInspector.UIString('Minimize cookie size')); |
| 776 }; | |
| 777 | |
| 778 WebInspector.AuditRules.BrowserCacheControlRule.prototype = { | |
| 779 handleNonCacheableResources: function(requests, result) | |
| 780 { | |
| 781 if (requests.length) { | |
| 782 var entry = result.addChild(WebInspector.UIString("The following res
ources are explicitly non-cacheable. Consider making them cacheable if possible:
"), true); | |
| 783 result.violationCount += requests.length; | |
| 784 for (var i = 0; i < requests.length; ++i) | |
| 785 entry.addURL(requests[i].url); | |
| 786 } | |
| 787 }, | |
| 788 | |
| 789 runChecks: function(requests, result, callback) | |
| 790 { | |
| 791 this.execCheck(WebInspector.UIString("The following resources are missin
g a cache expiration. Resources that do not specify an expiration may not be cac
hed by browsers:"), | |
| 792 this._missingExpirationCheck, requests, result); | |
| 793 this.execCheck(WebInspector.UIString("The following resources specify a
\"Vary\" header that disables caching in most versions of Internet Explorer:"), | |
| 794 this._varyCheck, requests, result); | |
| 795 this.execCheck(WebInspector.UIString("The following cacheable resources
have a short freshness lifetime:"), | |
| 796 this._oneMonthExpirationCheck, requests, result); | |
| 797 | |
| 798 // Unable to implement the favicon check due to the WebKit limitations. | |
| 799 this.execCheck(WebInspector.UIString("To further improve cache hit rate,
specify an expiration one year in the future for the following cacheable resour
ces:"), | |
| 800 this._oneYearExpirationCheck, requests, result); | |
| 801 }, | |
| 802 | |
| 803 _missingExpirationCheck: function(request) | |
| 804 { | |
| 805 return this.isCacheableResource(request) && !this.hasResponseHeader(requ
est, "Set-Cookie") && !this.hasExplicitExpiration(request); | |
| 806 }, | |
| 807 | |
| 808 _varyCheck: function(request) | |
| 809 { | |
| 810 var varyHeader = this.responseHeader(request, "Vary"); | |
| 811 if (varyHeader) { | |
| 812 varyHeader = varyHeader.replace(/User-Agent/gi, ""); | |
| 813 varyHeader = varyHeader.replace(/Accept-Encoding/gi, ""); | |
| 814 varyHeader = varyHeader.replace(/[, ]*/g, ""); | |
| 815 } | |
| 816 return varyHeader && varyHeader.length && this.isCacheableResource(reque
st) && this.freshnessLifetimeGreaterThan(request, 0); | |
| 817 }, | |
| 818 | |
| 819 _oneMonthExpirationCheck: function(request) | |
| 820 { | |
| 821 return this.isCacheableResource(request) && | |
| 822 !this.hasResponseHeader(request, "Set-Cookie") && | |
| 823 !this.freshnessLifetimeGreaterThan(request, WebInspector.AuditRules.
CacheControlRule.MillisPerMonth) && | |
| 824 this.freshnessLifetimeGreaterThan(request, 0); | |
| 825 }, | |
| 826 | |
| 827 _oneYearExpirationCheck: function(request) | |
| 828 { | |
| 829 return this.isCacheableResource(request) && | |
| 830 !this.hasResponseHeader(request, "Set-Cookie") && | |
| 831 !this.freshnessLifetimeGreaterThan(request, 11 * WebInspector.AuditR
ules.CacheControlRule.MillisPerMonth) && | |
| 832 this.freshnessLifetimeGreaterThan(request, WebInspector.AuditRules.C
acheControlRule.MillisPerMonth); | |
| 833 }, | |
| 834 | |
| 835 __proto__: WebInspector.AuditRules.CacheControlRule.prototype | |
| 836 }; | |
| 837 | |
| 838 /** | |
| 839 * @constructor | |
| 840 * @extends {WebInspector.AuditRule} | |
| 841 */ | |
| 842 WebInspector.AuditRules.ImageDimensionsRule = function() | |
| 843 { | |
| 844 WebInspector.AuditRule.call(this, "page-imagedims", WebInspector.UIString("S
pecify image dimensions")); | |
| 845 }; | |
| 846 | |
| 847 WebInspector.AuditRules.ImageDimensionsRule.prototype = { | |
| 848 /** | |
| 849 * @override | |
| 850 * @param {!WebInspector.Target} target | |
| 851 * @param {!Array.<!WebInspector.NetworkRequest>} requests | |
| 852 * @param {!WebInspector.AuditRuleResult} result | |
| 853 * @param {function(?WebInspector.AuditRuleResult)} callback | |
| 854 * @param {!WebInspector.Progress} progress | |
| 855 */ | |
| 856 doRun: function(target, requests, result, callback, progress) | |
| 857 { | |
| 858 var domModel = WebInspector.DOMModel.fromTarget(target); | |
| 859 var cssModel = WebInspector.CSSModel.fromTarget(target); | |
| 860 if (!domModel || !cssModel) { | |
| 861 callback(null); | |
| 862 return; | |
| 863 } | |
| 864 | |
| 865 var urlToNoDimensionCount = {}; | |
| 866 | |
| 867 function doneCallback() | |
| 868 { | |
| 869 for (var url in urlToNoDimensionCount) { | |
| 870 var entry = entry || result.addChild(WebInspector.UIString("A wi
dth and height should be specified for all images in order to speed up page disp
lay. The following image(s) are missing a width and/or height:"), true); | |
| 871 var format = "%r"; | |
| 872 if (urlToNoDimensionCount[url] > 1) | |
| 873 format += " (%d uses)"; | |
| 874 entry.addFormatted(format, url, urlToNoDimensionCount[url]); | |
| 875 result.violationCount++; | |
| 876 } | |
| 877 callback(entry ? result : null); | |
| 878 } | |
| 879 | |
| 880 function imageStylesReady(imageId, styles) | |
| 881 { | |
| 882 if (progress.isCanceled()) { | |
| 883 callback(null); | |
| 884 return; | |
| 885 } | |
| 886 | |
| 887 const node = domModel.nodeForId(imageId); | |
| 888 var src = node.getAttribute("src"); | |
| 889 if (!src.asParsedURL()) { | |
| 890 for (var frameOwnerCandidate = node; frameOwnerCandidate; frameO
wnerCandidate = frameOwnerCandidate.parentNode) { | |
| 891 if (frameOwnerCandidate.baseURL) { | |
| 892 var completeSrc = WebInspector.ParsedURL.completeURL(fra
meOwnerCandidate.baseURL, src); | |
| 893 break; | |
| 894 } | |
| 895 } | |
| 896 } | |
| 897 if (completeSrc) | |
| 898 src = completeSrc; | |
| 899 | |
| 900 if (styles.computedStyle.get("position") === "absolute") | |
| 901 return; | |
| 902 | |
| 903 var widthFound = false; | |
| 904 var heightFound = false; | |
| 905 for (var i = 0; !(widthFound && heightFound) && i < styles.nodeStyle
s.length; ++i) { | |
| 906 var style = styles.nodeStyles[i]; | |
| 907 if (style.getPropertyValue("width") !== "") | |
| 908 widthFound = true; | |
| 909 if (style.getPropertyValue("height") !== "") | |
| 910 heightFound = true; | |
| 911 } | |
| 912 | |
| 913 if (!widthFound || !heightFound) { | |
| 914 if (src in urlToNoDimensionCount) | |
| 915 ++urlToNoDimensionCount[src]; | |
| 916 else | |
| 917 urlToNoDimensionCount[src] = 1; | |
| 918 } | |
| 919 } | |
| 920 | |
| 921 /** | |
| 922 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds | |
| 923 */ | |
| 924 function getStyles(nodeIds) | |
| 925 { | |
| 926 if (progress.isCanceled()) { | |
| 927 callback(null); | |
| 928 return; | |
| 929 } | |
| 930 var targetResult = {}; | |
| 931 | |
| 932 /** | |
| 933 * @param {?WebInspector.CSSMatchedStyles} matchedStyleResult | |
| 934 */ | |
| 935 function matchedCallback(matchedStyleResult) | |
| 936 { | |
| 937 if (!matchedStyleResult) | |
| 938 return; | |
| 939 targetResult.nodeStyles = matchedStyleResult.nodeStyles(); | |
| 940 } | |
| 941 | |
| 942 /** | |
| 943 * @param {?Map.<string, string>} computedStyle | |
| 944 */ | |
| 945 function computedCallback(computedStyle) | |
| 946 { | |
| 947 targetResult.computedStyle = computedStyle; | |
| 948 } | |
| 949 | |
| 950 if (!nodeIds || !nodeIds.length) | |
| 951 doneCallback(); | |
| 952 | |
| 953 var nodePromises = []; | |
| 954 for (var i = 0; nodeIds && i < nodeIds.length; ++i) { | |
| 955 var stylePromises = [ | |
| 956 cssModel.matchedStylesPromise(nodeIds[i]).then(matchedCallba
ck), | |
| 957 cssModel.computedStylePromise(nodeIds[i]).then(computedCallb
ack) | |
| 958 ]; | |
| 959 var nodePromise = Promise.all(stylePromises).then(imageStylesRea
dy.bind(null, nodeIds[i], targetResult)); | |
| 960 nodePromises.push(nodePromise); | |
| 961 } | |
| 962 Promise.all(nodePromises) | |
| 963 .catchException(null) | |
| 964 .then(doneCallback); | |
| 965 } | |
| 966 | |
| 967 function onDocumentAvailable(root) | |
| 968 { | |
| 969 if (progress.isCanceled()) { | |
| 970 callback(null); | |
| 971 return; | |
| 972 } | |
| 973 domModel.querySelectorAll(root.id, "img[src]", getStyles); | |
| 974 } | |
| 975 | |
| 976 if (progress.isCanceled()) { | |
| 977 callback(null); | |
| 978 return; | |
| 979 } | |
| 980 domModel.requestDocument(onDocumentAvailable); | |
| 981 }, | |
| 982 | |
| 983 __proto__: WebInspector.AuditRule.prototype | |
| 984 }; | |
| 985 | |
| 986 /** | |
| 987 * @constructor | |
| 988 * @extends {WebInspector.AuditRule} | |
| 989 */ | |
| 990 WebInspector.AuditRules.CssInHeadRule = function() | |
| 991 { | |
| 992 WebInspector.AuditRule.call(this, "page-cssinhead", WebInspector.UIString("P
ut CSS in the document head")); | |
| 993 }; | |
| 994 | |
| 995 WebInspector.AuditRules.CssInHeadRule.prototype = { | |
| 996 /** | |
| 997 * @override | |
| 998 * @param {!WebInspector.Target} target | |
| 999 * @param {!Array.<!WebInspector.NetworkRequest>} requests | |
| 1000 * @param {!WebInspector.AuditRuleResult} result | |
| 1001 * @param {function(?WebInspector.AuditRuleResult)} callback | |
| 1002 * @param {!WebInspector.Progress} progress | |
| 1003 */ | |
| 1004 doRun: function(target, requests, result, callback, progress) | |
| 1005 { | |
| 1006 var domModel = WebInspector.DOMModel.fromTarget(target); | |
| 1007 if (!domModel) { | |
| 1008 callback(null); | |
| 1009 return; | |
| 1010 } | |
| 1011 | |
| 1012 function evalCallback(evalResult) | |
| 1013 { | |
| 1014 if (progress.isCanceled()) { | |
| 1015 callback(null); | |
| 1016 return; | |
| 1017 } | |
| 1018 | |
| 1019 if (!evalResult) | |
| 1020 return callback(null); | |
| 1021 | |
| 1022 var summary = result.addChild(""); | |
| 1023 | |
| 1024 for (var url in evalResult) { | |
| 1025 var urlViolations = evalResult[url]; | |
| 1026 if (urlViolations[0]) { | |
| 1027 result.addFormatted("%s style block(s) in the %r body should
be moved to the document head.", urlViolations[0], url); | |
| 1028 result.violationCount += urlViolations[0]; | |
| 1029 } | |
| 1030 for (var i = 0; i < urlViolations[1].length; ++i) | |
| 1031 result.addFormatted("Link node %r should be moved to the doc
ument head in %r", urlViolations[1][i], url); | |
| 1032 result.violationCount += urlViolations[1].length; | |
| 1033 } | |
| 1034 summary.value = WebInspector.UIString("CSS in the document body adve
rsely impacts rendering performance."); | |
| 1035 callback(result); | |
| 1036 } | |
| 1037 | |
| 1038 /** | |
| 1039 * @param {!WebInspector.DOMNode} root | |
| 1040 * @param {!Array.<!DOMAgent.NodeId>=} inlineStyleNodeIds | |
| 1041 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds | |
| 1042 */ | |
| 1043 function externalStylesheetsReceived(root, inlineStyleNodeIds, nodeIds) | |
| 1044 { | |
| 1045 if (progress.isCanceled()) { | |
| 1046 callback(null); | |
| 1047 return; | |
| 1048 } | |
| 1049 | |
| 1050 if (!nodeIds) | |
| 1051 return; | |
| 1052 var externalStylesheetNodeIds = nodeIds; | |
| 1053 var result = null; | |
| 1054 if (inlineStyleNodeIds.length || externalStylesheetNodeIds.length) { | |
| 1055 var urlToViolationsArray = {}; | |
| 1056 var externalStylesheetHrefs = []; | |
| 1057 for (var j = 0; j < externalStylesheetNodeIds.length; ++j) { | |
| 1058 var linkNode = domModel.nodeForId(externalStylesheetNodeIds[
j]); | |
| 1059 var completeHref = WebInspector.ParsedURL.completeURL(linkNo
de.ownerDocument.baseURL, linkNode.getAttribute("href")); | |
| 1060 externalStylesheetHrefs.push(completeHref || "<empty>"); | |
| 1061 } | |
| 1062 urlToViolationsArray[root.documentURL] = [inlineStyleNodeIds.len
gth, externalStylesheetHrefs]; | |
| 1063 result = urlToViolationsArray; | |
| 1064 } | |
| 1065 evalCallback(result); | |
| 1066 } | |
| 1067 | |
| 1068 /** | |
| 1069 * @param {!WebInspector.DOMNode} root | |
| 1070 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds | |
| 1071 */ | |
| 1072 function inlineStylesReceived(root, nodeIds) | |
| 1073 { | |
| 1074 if (progress.isCanceled()) { | |
| 1075 callback(null); | |
| 1076 return; | |
| 1077 } | |
| 1078 | |
| 1079 if (!nodeIds) | |
| 1080 return; | |
| 1081 domModel.querySelectorAll(root.id, "body link[rel~='stylesheet'][hre
f]", externalStylesheetsReceived.bind(null, root, nodeIds)); | |
| 1082 } | |
| 1083 | |
| 1084 /** | |
| 1085 * @param {!WebInspector.DOMNode} root | |
| 1086 */ | |
| 1087 function onDocumentAvailable(root) | |
| 1088 { | |
| 1089 if (progress.isCanceled()) { | |
| 1090 callback(null); | |
| 1091 return; | |
| 1092 } | |
| 1093 | |
| 1094 domModel.querySelectorAll(root.id, "body style", inlineStylesReceive
d.bind(null, root)); | |
| 1095 } | |
| 1096 | |
| 1097 domModel.requestDocument(onDocumentAvailable); | |
| 1098 }, | |
| 1099 | |
| 1100 __proto__: WebInspector.AuditRule.prototype | |
| 1101 }; | |
| 1102 | |
| 1103 /** | |
| 1104 * @constructor | |
| 1105 * @extends {WebInspector.AuditRule} | |
| 1106 */ | |
| 1107 WebInspector.AuditRules.StylesScriptsOrderRule = function() | |
| 1108 { | |
| 1109 WebInspector.AuditRule.call(this, "page-stylescriptorder", WebInspector.UISt
ring("Optimize the order of styles and scripts")); | |
| 1110 }; | |
| 1111 | |
| 1112 WebInspector.AuditRules.StylesScriptsOrderRule.prototype = { | |
| 1113 /** | |
| 1114 * @override | |
| 1115 * @param {!WebInspector.Target} target | |
| 1116 * @param {!Array.<!WebInspector.NetworkRequest>} requests | |
| 1117 * @param {!WebInspector.AuditRuleResult} result | |
| 1118 * @param {function(?WebInspector.AuditRuleResult)} callback | |
| 1119 * @param {!WebInspector.Progress} progress | |
| 1120 */ | |
| 1121 doRun: function(target, requests, result, callback, progress) | |
| 1122 { | |
| 1123 var domModel = WebInspector.DOMModel.fromTarget(target); | |
| 1124 if (!domModel) { | |
| 1125 callback(null); | |
| 1126 return; | |
| 1127 } | |
| 1128 | |
| 1129 function evalCallback(resultValue) | |
| 1130 { | |
| 1131 if (progress.isCanceled()) { | |
| 1132 callback(null); | |
| 1133 return; | |
| 1134 } | |
| 1135 | |
| 1136 if (!resultValue) | |
| 1137 return callback(null); | |
| 1138 | |
| 1139 var lateCssUrls = resultValue[0]; | |
| 1140 var cssBeforeInlineCount = resultValue[1]; | |
| 1141 | |
| 1142 if (lateCssUrls.length) { | |
| 1143 var entry = result.addChild(WebInspector.UIString("The following
external CSS files were included after an external JavaScript file in the docum
ent head. To ensure CSS files are downloaded in parallel, always include externa
l CSS before external JavaScript."), true); | |
| 1144 entry.addURLs(lateCssUrls); | |
| 1145 result.violationCount += lateCssUrls.length; | |
| 1146 } | |
| 1147 | |
| 1148 if (cssBeforeInlineCount) { | |
| 1149 result.addChild(WebInspector.UIString(" %d inline script block%s
found in the head between an external CSS file and another resource. To allow p
arallel downloading, move the inline script before the external CSS file, or aft
er the next resource.", cssBeforeInlineCount, cssBeforeInlineCount > 1 ? "s were
" : " was")); | |
| 1150 result.violationCount += cssBeforeInlineCount; | |
| 1151 } | |
| 1152 callback(result); | |
| 1153 } | |
| 1154 | |
| 1155 /** | |
| 1156 * @param {!Array.<!DOMAgent.NodeId>} lateStyleIds | |
| 1157 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds | |
| 1158 */ | |
| 1159 function cssBeforeInlineReceived(lateStyleIds, nodeIds) | |
| 1160 { | |
| 1161 if (progress.isCanceled()) { | |
| 1162 callback(null); | |
| 1163 return; | |
| 1164 } | |
| 1165 | |
| 1166 if (!nodeIds) | |
| 1167 return; | |
| 1168 | |
| 1169 var cssBeforeInlineCount = nodeIds.length; | |
| 1170 var result = null; | |
| 1171 if (lateStyleIds.length || cssBeforeInlineCount) { | |
| 1172 var lateStyleUrls = []; | |
| 1173 for (var i = 0; i < lateStyleIds.length; ++i) { | |
| 1174 var lateStyleNode = domModel.nodeForId(lateStyleIds[i]); | |
| 1175 var completeHref = WebInspector.ParsedURL.completeURL(lateSt
yleNode.ownerDocument.baseURL, lateStyleNode.getAttribute("href")); | |
| 1176 lateStyleUrls.push(completeHref || "<empty>"); | |
| 1177 } | |
| 1178 result = [ lateStyleUrls, cssBeforeInlineCount ]; | |
| 1179 } | |
| 1180 | |
| 1181 evalCallback(result); | |
| 1182 } | |
| 1183 | |
| 1184 /** | |
| 1185 * @param {!WebInspector.DOMDocument} root | |
| 1186 * @param {!Array.<!DOMAgent.NodeId>=} nodeIds | |
| 1187 */ | |
| 1188 function lateStylesReceived(root, nodeIds) | |
| 1189 { | |
| 1190 if (progress.isCanceled()) { | |
| 1191 callback(null); | |
| 1192 return; | |
| 1193 } | |
| 1194 | |
| 1195 if (!nodeIds) | |
| 1196 return; | |
| 1197 | |
| 1198 domModel.querySelectorAll(root.id, "head link[rel~='stylesheet'][hre
f] ~ script:not([src])", cssBeforeInlineReceived.bind(null, nodeIds)); | |
| 1199 } | |
| 1200 | |
| 1201 /** | |
| 1202 * @param {!WebInspector.DOMDocument} root | |
| 1203 */ | |
| 1204 function onDocumentAvailable(root) | |
| 1205 { | |
| 1206 if (progress.isCanceled()) { | |
| 1207 callback(null); | |
| 1208 return; | |
| 1209 } | |
| 1210 | |
| 1211 domModel.querySelectorAll(root.id, "head script[src] ~ link[rel~='st
ylesheet'][href]", lateStylesReceived.bind(null, root)); | |
| 1212 } | |
| 1213 | |
| 1214 domModel.requestDocument(onDocumentAvailable); | |
| 1215 }, | |
| 1216 | |
| 1217 __proto__: WebInspector.AuditRule.prototype | |
| 1218 }; | |
| 1219 | |
| 1220 /** | |
| 1221 * @constructor | |
| 1222 * @extends {WebInspector.AuditRule} | |
| 1223 */ | |
| 1224 WebInspector.AuditRules.CSSRuleBase = function(id, name) | |
| 1225 { | |
| 1226 WebInspector.AuditRule.call(this, id, name); | |
| 1227 }; | |
| 1228 | |
| 1229 WebInspector.AuditRules.CSSRuleBase.prototype = { | |
| 1230 /** | |
| 1231 * @override | |
| 1232 * @param {!WebInspector.Target} target | |
| 1233 * @param {!Array.<!WebInspector.NetworkRequest>} requests | |
| 1234 * @param {!WebInspector.AuditRuleResult} result | |
| 1235 * @param {function(?WebInspector.AuditRuleResult)} callback | |
| 1236 * @param {!WebInspector.Progress} progress | |
| 1237 */ | |
| 1238 doRun: function(target, requests, result, callback, progress) | |
| 1239 { | |
| 1240 var cssModel = WebInspector.CSSModel.fromTarget(target); | |
| 1241 if (!cssModel) { | |
| 1242 callback(null); | |
| 1243 return; | |
| 1244 } | |
| 1245 | |
| 1246 var headers = cssModel.allStyleSheets(); | |
| 1247 if (!headers.length) { | |
| 1248 callback(null); | |
| 1249 return; | |
| 1250 } | |
| 1251 var activeHeaders = []; | |
| 1252 for (var i = 0; i < headers.length; ++i) { | |
| 1253 if (!headers[i].disabled) | |
| 1254 activeHeaders.push(headers[i]); | |
| 1255 } | |
| 1256 | |
| 1257 var styleSheetProcessor = new WebInspector.AuditRules.StyleSheetProcesso
r(activeHeaders, progress, this._styleSheetsLoaded.bind(this, result, callback,
progress)); | |
| 1258 styleSheetProcessor.run(); | |
| 1259 }, | |
| 1260 | |
| 1261 /** | |
| 1262 * @param {!WebInspector.AuditRuleResult} result | |
| 1263 * @param {function(!WebInspector.AuditRuleResult)} callback | |
| 1264 * @param {!WebInspector.Progress} progress | |
| 1265 * @param {!Array.<!WebInspector.AuditRules.ParsedStyleSheet>} styleSheets | |
| 1266 */ | |
| 1267 _styleSheetsLoaded: function(result, callback, progress, styleSheets) | |
| 1268 { | |
| 1269 for (var i = 0; i < styleSheets.length; ++i) | |
| 1270 this._visitStyleSheet(styleSheets[i], result); | |
| 1271 callback(result); | |
| 1272 }, | |
| 1273 | |
| 1274 /** | |
| 1275 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet | |
| 1276 * @param {!WebInspector.AuditRuleResult} result | |
| 1277 */ | |
| 1278 _visitStyleSheet: function(styleSheet, result) | |
| 1279 { | |
| 1280 this.visitStyleSheet(styleSheet, result); | |
| 1281 | |
| 1282 for (var i = 0; i < styleSheet.rules.length; ++i) | |
| 1283 this._visitRule(styleSheet, styleSheet.rules[i], result); | |
| 1284 | |
| 1285 this.didVisitStyleSheet(styleSheet, result); | |
| 1286 }, | |
| 1287 | |
| 1288 /** | |
| 1289 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet | |
| 1290 * @param {!WebInspector.CSSParser.StyleRule} rule | |
| 1291 * @param {!WebInspector.AuditRuleResult} result | |
| 1292 */ | |
| 1293 _visitRule: function(styleSheet, rule, result) | |
| 1294 { | |
| 1295 this.visitRule(styleSheet, rule, result); | |
| 1296 var allProperties = rule.properties; | |
| 1297 for (var i = 0; i < allProperties.length; ++i) | |
| 1298 this.visitProperty(styleSheet, rule, allProperties[i], result); | |
| 1299 this.didVisitRule(styleSheet, rule, result); | |
| 1300 }, | |
| 1301 | |
| 1302 /** | |
| 1303 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet | |
| 1304 * @param {!WebInspector.AuditRuleResult} result | |
| 1305 */ | |
| 1306 visitStyleSheet: function(styleSheet, result) | |
| 1307 { | |
| 1308 // Subclasses can implement. | |
| 1309 }, | |
| 1310 | |
| 1311 /** | |
| 1312 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet | |
| 1313 * @param {!WebInspector.AuditRuleResult} result | |
| 1314 */ | |
| 1315 didVisitStyleSheet: function(styleSheet, result) | |
| 1316 { | |
| 1317 // Subclasses can implement. | |
| 1318 }, | |
| 1319 | |
| 1320 /** | |
| 1321 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet | |
| 1322 * @param {!WebInspector.CSSParser.StyleRule} rule | |
| 1323 * @param {!WebInspector.AuditRuleResult} result | |
| 1324 */ | |
| 1325 visitRule: function(styleSheet, rule, result) | |
| 1326 { | |
| 1327 // Subclasses can implement. | |
| 1328 }, | |
| 1329 | |
| 1330 /** | |
| 1331 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet | |
| 1332 * @param {!WebInspector.CSSParser.StyleRule} rule | |
| 1333 * @param {!WebInspector.AuditRuleResult} result | |
| 1334 */ | |
| 1335 didVisitRule: function(styleSheet, rule, result) | |
| 1336 { | |
| 1337 // Subclasses can implement. | |
| 1338 }, | |
| 1339 | |
| 1340 /** | |
| 1341 * @param {!WebInspector.AuditRules.ParsedStyleSheet} styleSheet | |
| 1342 * @param {!WebInspector.CSSParser.StyleRule} rule | |
| 1343 * @param {!WebInspector.CSSParser.Property} property | |
| 1344 * @param {!WebInspector.AuditRuleResult} result | |
| 1345 */ | |
| 1346 visitProperty: function(styleSheet, rule, property, result) | |
| 1347 { | |
| 1348 // Subclasses can implement. | |
| 1349 }, | |
| 1350 | |
| 1351 __proto__: WebInspector.AuditRule.prototype | |
| 1352 }; | |
| 1353 | |
| 1354 /** | |
| 1355 * @constructor | |
| 1356 * @extends {WebInspector.AuditRule} | |
| 1357 */ | |
| 1358 WebInspector.AuditRules.CookieRuleBase = function(id, name) | |
| 1359 { | |
| 1360 WebInspector.AuditRule.call(this, id, name); | |
| 1361 }; | |
| 1362 | |
| 1363 WebInspector.AuditRules.CookieRuleBase.prototype = { | |
| 1364 /** | |
| 1365 * @override | |
| 1366 * @param {!WebInspector.Target} target | |
| 1367 * @param {!Array.<!WebInspector.NetworkRequest>} requests | |
| 1368 * @param {!WebInspector.AuditRuleResult} result | |
| 1369 * @param {function(!WebInspector.AuditRuleResult)} callback | |
| 1370 * @param {!WebInspector.Progress} progress | |
| 1371 */ | |
| 1372 doRun: function(target, requests, result, callback, progress) | |
| 1373 { | |
| 1374 var self = this; | |
| 1375 function resultCallback(receivedCookies) | |
| 1376 { | |
| 1377 if (progress.isCanceled()) { | |
| 1378 callback(result); | |
| 1379 return; | |
| 1380 } | |
| 1381 | |
| 1382 self.processCookies(receivedCookies, requests, result); | |
| 1383 callback(result); | |
| 1384 } | |
| 1385 | |
| 1386 WebInspector.Cookies.getCookiesAsync(resultCallback); | |
| 1387 }, | |
| 1388 | |
| 1389 mapResourceCookies: function(requestsByDomain, allCookies, callback) | |
| 1390 { | |
| 1391 for (var i = 0; i < allCookies.length; ++i) { | |
| 1392 for (var requestDomain in requestsByDomain) { | |
| 1393 if (WebInspector.Cookies.cookieDomainMatchesResourceDomain(allCo
okies[i].domain(), requestDomain)) | |
| 1394 this._callbackForResourceCookiePairs(requestsByDomain[reques
tDomain], allCookies[i], callback); | |
| 1395 } | |
| 1396 } | |
| 1397 }, | |
| 1398 | |
| 1399 _callbackForResourceCookiePairs: function(requests, cookie, callback) | |
| 1400 { | |
| 1401 if (!requests) | |
| 1402 return; | |
| 1403 for (var i = 0; i < requests.length; ++i) { | |
| 1404 if (WebInspector.Cookies.cookieMatchesResourceURL(cookie, requests[i
].url)) | |
| 1405 callback(requests[i], cookie); | |
| 1406 } | |
| 1407 }, | |
| 1408 | |
| 1409 __proto__: WebInspector.AuditRule.prototype | |
| 1410 }; | |
| 1411 | |
| 1412 /** | |
| 1413 * @constructor | |
| 1414 * @extends {WebInspector.AuditRules.CookieRuleBase} | |
| 1415 */ | |
| 1416 WebInspector.AuditRules.CookieSizeRule = function(avgBytesThreshold) | |
| 1417 { | |
| 1418 WebInspector.AuditRules.CookieRuleBase.call(this, "http-cookiesize", WebInsp
ector.UIString("Minimize cookie size")); | |
| 1419 this._avgBytesThreshold = avgBytesThreshold; | 1347 this._avgBytesThreshold = avgBytesThreshold; |
| 1420 this._maxBytesThreshold = 1000; | 1348 this._maxBytesThreshold = 1000; |
| 1349 } |
| 1350 |
| 1351 _average(cookieArray) { |
| 1352 var total = 0; |
| 1353 for (var i = 0; i < cookieArray.length; ++i) |
| 1354 total += cookieArray[i].size(); |
| 1355 return cookieArray.length ? Math.round(total / cookieArray.length) : 0; |
| 1356 } |
| 1357 |
| 1358 _max(cookieArray) { |
| 1359 var result = 0; |
| 1360 for (var i = 0; i < cookieArray.length; ++i) |
| 1361 result = Math.max(cookieArray[i].size(), result); |
| 1362 return result; |
| 1363 } |
| 1364 |
| 1365 processCookies(allCookies, requests, result) { |
| 1366 function maxSizeSorter(a, b) { |
| 1367 return b.maxCookieSize - a.maxCookieSize; |
| 1368 } |
| 1369 |
| 1370 function avgSizeSorter(a, b) { |
| 1371 return b.avgCookieSize - a.avgCookieSize; |
| 1372 } |
| 1373 |
| 1374 var cookiesPerResourceDomain = {}; |
| 1375 |
| 1376 function collectorCallback(request, cookie) { |
| 1377 var cookies = cookiesPerResourceDomain[request.parsedURL.host]; |
| 1378 if (!cookies) { |
| 1379 cookies = []; |
| 1380 cookiesPerResourceDomain[request.parsedURL.host] = cookies; |
| 1381 } |
| 1382 cookies.push(cookie); |
| 1383 } |
| 1384 |
| 1385 if (!allCookies.length) |
| 1386 return; |
| 1387 |
| 1388 var sortedCookieSizes = []; |
| 1389 |
| 1390 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(r
equests, null, true); |
| 1391 this.mapResourceCookies(domainToResourcesMap, allCookies, collectorCallback)
; |
| 1392 |
| 1393 for (var requestDomain in cookiesPerResourceDomain) { |
| 1394 var cookies = cookiesPerResourceDomain[requestDomain]; |
| 1395 sortedCookieSizes.push( |
| 1396 {domain: requestDomain, avgCookieSize: this._average(cookies), maxCook
ieSize: this._max(cookies)}); |
| 1397 } |
| 1398 var avgAllCookiesSize = this._average(allCookies); |
| 1399 |
| 1400 var hugeCookieDomains = []; |
| 1401 sortedCookieSizes.sort(maxSizeSorter); |
| 1402 |
| 1403 for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) { |
| 1404 var maxCookieSize = sortedCookieSizes[i].maxCookieSize; |
| 1405 if (maxCookieSize > this._maxBytesThreshold) |
| 1406 hugeCookieDomains.push( |
| 1407 WebInspector.AuditRuleResult.resourceDomain(sortedCookieSizes[i].dom
ain) + ': ' + |
| 1408 Number.bytesToString(maxCookieSize)); |
| 1409 } |
| 1410 |
| 1411 var bigAvgCookieDomains = []; |
| 1412 sortedCookieSizes.sort(avgSizeSorter); |
| 1413 for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) { |
| 1414 var domain = sortedCookieSizes[i].domain; |
| 1415 var avgCookieSize = sortedCookieSizes[i].avgCookieSize; |
| 1416 if (avgCookieSize > this._avgBytesThreshold && avgCookieSize < this._maxBy
tesThreshold) |
| 1417 bigAvgCookieDomains.push( |
| 1418 WebInspector.AuditRuleResult.resourceDomain(domain) + ': ' + Number.
bytesToString(avgCookieSize)); |
| 1419 } |
| 1420 result.addChild(WebInspector.UIString( |
| 1421 'The average cookie size for all requests on this page is %s', Number.by
tesToString(avgAllCookiesSize))); |
| 1422 |
| 1423 if (hugeCookieDomains.length) { |
| 1424 var entry = result.addChild( |
| 1425 WebInspector.UIString( |
| 1426 'The following domains have a cookie size in excess of 1KB. This i
s harmful because requests with cookies larger than 1KB typically cannot fit int
o a single network packet.'), |
| 1427 true); |
| 1428 entry.addURLs(hugeCookieDomains); |
| 1429 result.violationCount += hugeCookieDomains.length; |
| 1430 } |
| 1431 |
| 1432 if (bigAvgCookieDomains.length) { |
| 1433 var entry = result.addChild( |
| 1434 WebInspector.UIString( |
| 1435 '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 ta
kes to send requests.', |
| 1436 this._avgBytesThreshold), |
| 1437 true); |
| 1438 entry.addURLs(bigAvgCookieDomains); |
| 1439 result.violationCount += bigAvgCookieDomains.length; |
| 1440 } |
| 1441 } |
| 1421 }; | 1442 }; |
| 1422 | 1443 |
| 1423 WebInspector.AuditRules.CookieSizeRule.prototype = { | 1444 /** |
| 1424 _average: function(cookieArray) | 1445 * @unrestricted |
| 1425 { | 1446 */ |
| 1426 var total = 0; | 1447 WebInspector.AuditRules.StaticCookielessRule = class extends WebInspector.AuditR
ules.CookieRuleBase { |
| 1427 for (var i = 0; i < cookieArray.length; ++i) | 1448 constructor(minResources) { |
| 1428 total += cookieArray[i].size(); | 1449 super('http-staticcookieless', WebInspector.UIString('Serve static content f
rom a cookieless domain')); |
| 1429 return cookieArray.length ? Math.round(total / cookieArray.length) : 0; | 1450 this._minResources = minResources; |
| 1430 }, | 1451 } |
| 1431 | 1452 |
| 1432 _max: function(cookieArray) | 1453 processCookies(allCookies, requests, result) { |
| 1433 { | 1454 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap( |
| 1434 var result = 0; | 1455 requests, [WebInspector.resourceTypes.Stylesheet, WebInspector.resourceT
ypes.Image], true); |
| 1435 for (var i = 0; i < cookieArray.length; ++i) | 1456 var totalStaticResources = 0; |
| 1436 result = Math.max(cookieArray[i].size(), result); | 1457 for (var domain in domainToResourcesMap) |
| 1437 return result; | 1458 totalStaticResources += domainToResourcesMap[domain].length; |
| 1438 }, | 1459 if (totalStaticResources < this._minResources) |
| 1439 | 1460 return; |
| 1440 processCookies: function(allCookies, requests, result) | 1461 var matchingResourceData = {}; |
| 1441 { | 1462 this.mapResourceCookies(domainToResourcesMap, allCookies, this._collectorCal
lback.bind(this, matchingResourceData)); |
| 1442 function maxSizeSorter(a, b) | 1463 |
| 1443 { | 1464 var badUrls = []; |
| 1444 return b.maxCookieSize - a.maxCookieSize; | 1465 var cookieBytes = 0; |
| 1445 } | 1466 for (var url in matchingResourceData) { |
| 1446 | 1467 badUrls.push(url); |
| 1447 function avgSizeSorter(a, b) | 1468 cookieBytes += matchingResourceData[url]; |
| 1448 { | 1469 } |
| 1449 return b.avgCookieSize - a.avgCookieSize; | 1470 if (badUrls.length < this._minResources) |
| 1450 } | 1471 return; |
| 1451 | 1472 |
| 1452 var cookiesPerResourceDomain = {}; | 1473 var entry = result.addChild( |
| 1453 | 1474 WebInspector.UIString( |
| 1454 function collectorCallback(request, cookie) | 1475 '%s of cookies were sent with the following static resources. Serve
these static resources from a domain that does not set cookies:', |
| 1455 { | 1476 Number.bytesToString(cookieBytes)), |
| 1456 var cookies = cookiesPerResourceDomain[request.parsedURL.host]; | 1477 true); |
| 1457 if (!cookies) { | 1478 entry.addURLs(badUrls); |
| 1458 cookies = []; | 1479 result.violationCount = badUrls.length; |
| 1459 cookiesPerResourceDomain[request.parsedURL.host] = cookies; | 1480 } |
| 1460 } | 1481 |
| 1461 cookies.push(cookie); | 1482 _collectorCallback(matchingResourceData, request, cookie) { |
| 1462 } | 1483 matchingResourceData[request.url] = (matchingResourceData[request.url] || 0)
+ cookie.size(); |
| 1463 | 1484 } |
| 1464 if (!allCookies.length) | |
| 1465 return; | |
| 1466 | |
| 1467 var sortedCookieSizes = []; | |
| 1468 | |
| 1469 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesM
ap(requests, | |
| 1470 null, | |
| 1471 true); | |
| 1472 this.mapResourceCookies(domainToResourcesMap, allCookies, collectorCallb
ack); | |
| 1473 | |
| 1474 for (var requestDomain in cookiesPerResourceDomain) { | |
| 1475 var cookies = cookiesPerResourceDomain[requestDomain]; | |
| 1476 sortedCookieSizes.push({ | |
| 1477 domain: requestDomain, | |
| 1478 avgCookieSize: this._average(cookies), | |
| 1479 maxCookieSize: this._max(cookies) | |
| 1480 }); | |
| 1481 } | |
| 1482 var avgAllCookiesSize = this._average(allCookies); | |
| 1483 | |
| 1484 var hugeCookieDomains = []; | |
| 1485 sortedCookieSizes.sort(maxSizeSorter); | |
| 1486 | |
| 1487 for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) { | |
| 1488 var maxCookieSize = sortedCookieSizes[i].maxCookieSize; | |
| 1489 if (maxCookieSize > this._maxBytesThreshold) | |
| 1490 hugeCookieDomains.push(WebInspector.AuditRuleResult.resourceDoma
in(sortedCookieSizes[i].domain) + ": " + Number.bytesToString(maxCookieSize)); | |
| 1491 } | |
| 1492 | |
| 1493 var bigAvgCookieDomains = []; | |
| 1494 sortedCookieSizes.sort(avgSizeSorter); | |
| 1495 for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) { | |
| 1496 var domain = sortedCookieSizes[i].domain; | |
| 1497 var avgCookieSize = sortedCookieSizes[i].avgCookieSize; | |
| 1498 if (avgCookieSize > this._avgBytesThreshold && avgCookieSize < this.
_maxBytesThreshold) | |
| 1499 bigAvgCookieDomains.push(WebInspector.AuditRuleResult.resourceDo
main(domain) + ": " + Number.bytesToString(avgCookieSize)); | |
| 1500 } | |
| 1501 result.addChild(WebInspector.UIString("The average cookie size for all r
equests on this page is %s", Number.bytesToString(avgAllCookiesSize))); | |
| 1502 | |
| 1503 if (hugeCookieDomains.length) { | |
| 1504 var entry = result.addChild(WebInspector.UIString("The following dom
ains 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."), tr
ue); | |
| 1505 entry.addURLs(hugeCookieDomains); | |
| 1506 result.violationCount += hugeCookieDomains.length; | |
| 1507 } | |
| 1508 | |
| 1509 if (bigAvgCookieDomains.length) { | |
| 1510 var entry = result.addChild(WebInspector.UIString("The following dom
ains have an average cookie size in excess of %d bytes. Reducing the size of coo
kies for these domains can reduce the time it takes to send requests.", this._av
gBytesThreshold), true); | |
| 1511 entry.addURLs(bigAvgCookieDomains); | |
| 1512 result.violationCount += bigAvgCookieDomains.length; | |
| 1513 } | |
| 1514 }, | |
| 1515 | |
| 1516 __proto__: WebInspector.AuditRules.CookieRuleBase.prototype | |
| 1517 }; | 1485 }; |
| 1518 | |
| 1519 /** | |
| 1520 * @constructor | |
| 1521 * @extends {WebInspector.AuditRules.CookieRuleBase} | |
| 1522 */ | |
| 1523 WebInspector.AuditRules.StaticCookielessRule = function(minResources) | |
| 1524 { | |
| 1525 WebInspector.AuditRules.CookieRuleBase.call(this, "http-staticcookieless", W
ebInspector.UIString("Serve static content from a cookieless domain")); | |
| 1526 this._minResources = minResources; | |
| 1527 }; | |
| 1528 | |
| 1529 WebInspector.AuditRules.StaticCookielessRule.prototype = { | |
| 1530 processCookies: function(allCookies, requests, result) | |
| 1531 { | |
| 1532 var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesM
ap(requests, | |
| 1533 [WebInspector.resourceTypes.Stylesheet, | |
| 1534 WebInspector.resourceTypes.Image], | |
| 1535 true); | |
| 1536 var totalStaticResources = 0; | |
| 1537 for (var domain in domainToResourcesMap) | |
| 1538 totalStaticResources += domainToResourcesMap[domain].length; | |
| 1539 if (totalStaticResources < this._minResources) | |
| 1540 return; | |
| 1541 var matchingResourceData = {}; | |
| 1542 this.mapResourceCookies(domainToResourcesMap, allCookies, this._collecto
rCallback.bind(this, matchingResourceData)); | |
| 1543 | |
| 1544 var badUrls = []; | |
| 1545 var cookieBytes = 0; | |
| 1546 for (var url in matchingResourceData) { | |
| 1547 badUrls.push(url); | |
| 1548 cookieBytes += matchingResourceData[url]; | |
| 1549 } | |
| 1550 if (badUrls.length < this._minResources) | |
| 1551 return; | |
| 1552 | |
| 1553 var entry = result.addChild(WebInspector.UIString("%s of cookies were se
nt with the following static resources. Serve these static resources from a doma
in that does not set cookies:", Number.bytesToString(cookieBytes)), true); | |
| 1554 entry.addURLs(badUrls); | |
| 1555 result.violationCount = badUrls.length; | |
| 1556 }, | |
| 1557 | |
| 1558 _collectorCallback: function(matchingResourceData, request, cookie) | |
| 1559 { | |
| 1560 matchingResourceData[request.url] = (matchingResourceData[request.url] |
| 0) + cookie.size(); | |
| 1561 }, | |
| 1562 | |
| 1563 __proto__: WebInspector.AuditRules.CookieRuleBase.prototype | |
| 1564 }; | |
| OLD | NEW |