Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(848)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/audits/AuditRules.js

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698