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 |