lighthouse 12.6.0 → 12.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/audits/deprecations.js +1 -1
- package/core/audits/dobetterweb/doctype.js +1 -1
- package/core/audits/dobetterweb/inspector-issues.js +6 -11
- package/core/audits/insights/document-latency-insight.js +8 -1
- package/core/audits/insights/dom-size-insight.js +9 -1
- package/core/audits/insights/insight-audit.js +7 -0
- package/core/audits/is-on-https.js +1 -1
- package/core/audits/third-party-cookies.js +1 -1
- package/core/computed/metrics/timing-summary.js +6 -1
- package/core/computed/page-dependency-graph.js +4 -0
- package/core/computed/trace-engine-result.js +7 -0
- package/core/config/constants.d.ts +2 -2
- package/core/config/constants.js +2 -2
- package/core/config/lr-desktop-config.js +1 -0
- package/core/config/lr-mobile-config.js +1 -0
- package/core/gather/gatherers/inspector-issues.d.ts +2 -2
- package/core/gather/gatherers/inspector-issues.js +31 -18
- package/core/lib/network-request.js +1 -0
- package/core/lib/trace-engine.d.ts +2 -0
- package/core/lib/trace-engine.js +1 -1
- package/dist/report/bundle.esm.js +14 -6
- package/dist/report/flow.js +12 -4
- package/dist/report/standalone.js +14 -6
- package/package.json +9 -9
- package/report/assets/styles.css +11 -3
- package/report/renderer/components.js +1 -1
- package/report/renderer/performance-category-renderer.js +1 -2
- package/shared/localization/locales/ar-XB.json +7 -10
- package/shared/localization/locales/ar.json +7 -10
- package/shared/localization/locales/bg.json +7 -10
- package/shared/localization/locales/ca.json +7 -10
- package/shared/localization/locales/cs.json +7 -10
- package/shared/localization/locales/da.json +7 -10
- package/shared/localization/locales/de.json +7 -10
- package/shared/localization/locales/el.json +7 -10
- package/shared/localization/locales/en-GB.json +7 -10
- package/shared/localization/locales/en-US.json +49 -7
- package/shared/localization/locales/en-XL.json +49 -7
- package/shared/localization/locales/es-419.json +7 -10
- package/shared/localization/locales/es.json +7 -10
- package/shared/localization/locales/fi.json +7 -10
- package/shared/localization/locales/fil.json +7 -10
- package/shared/localization/locales/fr.json +7 -10
- package/shared/localization/locales/he.json +7 -10
- package/shared/localization/locales/hi.json +7 -10
- package/shared/localization/locales/hr.json +7 -10
- package/shared/localization/locales/hu.json +7 -10
- package/shared/localization/locales/id.json +7 -10
- package/shared/localization/locales/it.json +7 -10
- package/shared/localization/locales/ja.json +7 -10
- package/shared/localization/locales/ko.json +7 -10
- package/shared/localization/locales/lt.json +7 -10
- package/shared/localization/locales/lv.json +7 -10
- package/shared/localization/locales/nl.json +7 -10
- package/shared/localization/locales/no.json +7 -10
- package/shared/localization/locales/pl.json +7 -10
- package/shared/localization/locales/pt-PT.json +7 -10
- package/shared/localization/locales/pt.json +7 -10
- package/shared/localization/locales/ro.json +7 -10
- package/shared/localization/locales/ru.json +7 -10
- package/shared/localization/locales/sk.json +7 -10
- package/shared/localization/locales/sl.json +7 -10
- package/shared/localization/locales/sr-Latn.json +7 -10
- package/shared/localization/locales/sr.json +7 -10
- package/shared/localization/locales/sv.json +7 -10
- package/shared/localization/locales/ta.json +7 -10
- package/shared/localization/locales/te.json +7 -10
- package/shared/localization/locales/th.json +7 -10
- package/shared/localization/locales/tr.json +7 -10
- package/shared/localization/locales/uk.json +7 -10
- package/shared/localization/locales/vi.json +7 -10
- package/shared/localization/locales/zh-HK.json +7 -10
- package/shared/localization/locales/zh-TW.json +7 -10
- package/shared/localization/locales/zh.json +7 -10
- package/shared/localization/locales.d.ts +2 -0
- package/shared/localization/locales.js +130 -139
- package/shared/tsconfig.json +2 -0
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +1 -0
- package/types/artifacts.d.ts +9 -26
- package/types/utility-types.d.ts +4 -0
|
@@ -58,7 +58,7 @@ class Deprecations extends Audit {
|
|
|
58
58
|
static async audit(artifacts, context) {
|
|
59
59
|
const bundles = await JSBundles.request(artifacts, context);
|
|
60
60
|
|
|
61
|
-
const deprecations = artifacts.InspectorIssues.deprecationIssue
|
|
61
|
+
const deprecations = (artifacts.InspectorIssues.deprecationIssue ?? [])
|
|
62
62
|
.map(deprecation => {
|
|
63
63
|
const {scriptId, url, lineNumber, columnNumber} = deprecation.sourceCodeLocation;
|
|
64
64
|
const bundle = bundles.find(bundle => bundle.script.scriptId === scriptId);
|
|
@@ -71,7 +71,7 @@ class Doctype extends Audit {
|
|
|
71
71
|
|
|
72
72
|
/** @type {LH.Crdp.Audits.QuirksModeIssueDetails[]} */
|
|
73
73
|
let quirksModeIssues = [];
|
|
74
|
-
if (trace && artifacts.InspectorIssues) {
|
|
74
|
+
if (trace && artifacts.InspectorIssues?.quirksModeIssue) {
|
|
75
75
|
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
76
76
|
const mainFrameId = processedTrace.mainFrameInfo.frameId;
|
|
77
77
|
quirksModeIssues =
|
|
@@ -160,25 +160,20 @@ class IssuesPanelEntries extends Audit {
|
|
|
160
160
|
/** @type LH.Audit.Details.TableItem[] */
|
|
161
161
|
const items = [];
|
|
162
162
|
|
|
163
|
-
if (issues.mixedContentIssue
|
|
163
|
+
if (issues.mixedContentIssue?.length) {
|
|
164
164
|
items.push(this.getMixedContentRow(issues.mixedContentIssue));
|
|
165
165
|
}
|
|
166
|
-
if (issues.cookieIssue
|
|
166
|
+
if (issues.cookieIssue?.length) {
|
|
167
167
|
items.push(this.getCookieRow(issues.cookieIssue));
|
|
168
168
|
}
|
|
169
|
-
if (issues.blockedByResponseIssue
|
|
169
|
+
if (issues.blockedByResponseIssue?.length) {
|
|
170
170
|
items.push(this.getBlockedByResponseRow(issues.blockedByResponseIssue));
|
|
171
171
|
}
|
|
172
|
-
if (issues.heavyAdIssue
|
|
172
|
+
if (issues.heavyAdIssue?.length) {
|
|
173
173
|
items.push({issueType: str_(UIStrings.issueTypeHeavyAds)});
|
|
174
174
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return issue.contentSecurityPolicyViolationType !== 'kTrustedTypesSinkViolation' &&
|
|
178
|
-
issue.contentSecurityPolicyViolationType !== 'kTrustedTypesPolicyViolation';
|
|
179
|
-
});
|
|
180
|
-
if (cspIssues.length) {
|
|
181
|
-
items.push(this.getContentSecurityPolicyRow(cspIssues));
|
|
175
|
+
if (issues.contentSecurityPolicyIssue?.length) {
|
|
176
|
+
items.push(this.getContentSecurityPolicyRow(issues.contentSecurityPolicyIssue));
|
|
182
177
|
}
|
|
183
178
|
return {
|
|
184
179
|
score: items.length > 0 ? 0 : 1,
|
|
@@ -40,7 +40,14 @@ class DocumentLatencyInsight extends Audit {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
const details = Audit.makeChecklistDetails(insight.data.checklist);
|
|
44
|
+
details.debugData = {
|
|
45
|
+
type: 'debugdata',
|
|
46
|
+
redirectDuration: insight.data.redirectDuration,
|
|
47
|
+
serverResponseTime: insight.data.serverResponseTime,
|
|
48
|
+
uncompressedResponseBytes: insight.data.uncompressedResponseBytes,
|
|
49
|
+
};
|
|
50
|
+
return details;
|
|
44
51
|
});
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -77,7 +77,15 @@ class DOMSizeInsight extends Audit {
|
|
|
77
77
|
},
|
|
78
78
|
},
|
|
79
79
|
];
|
|
80
|
-
|
|
80
|
+
|
|
81
|
+
const details = Audit.makeTableDetails(headings, items);
|
|
82
|
+
details.debugData = {
|
|
83
|
+
type: 'debugdata',
|
|
84
|
+
totalElements,
|
|
85
|
+
maxChildren: maxChildren.numChildren,
|
|
86
|
+
maxDepth: maxDepth.depth,
|
|
87
|
+
};
|
|
88
|
+
return details;
|
|
81
89
|
});
|
|
82
90
|
}
|
|
83
91
|
}
|
|
@@ -75,6 +75,13 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
if (insight.wastedBytes !== undefined) {
|
|
79
|
+
if (!details.debugData) {
|
|
80
|
+
details.debugData = {type: 'debugdata'};
|
|
81
|
+
}
|
|
82
|
+
details.debugData.wastedBytes = insight.wastedBytes;
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
// This hack is to add metric adorners if an insight category links it to a metric,
|
|
79
86
|
// but doesn't output a metric savings for that metric.
|
|
80
87
|
let metricSavings = insight.metricSavings;
|
|
@@ -86,7 +86,7 @@ class HTTPS extends Audit {
|
|
|
86
86
|
{key: 'resolution', valueType: 'text', label: str_(UIStrings.columnResolution)},
|
|
87
87
|
];
|
|
88
88
|
|
|
89
|
-
for (const details of artifacts.InspectorIssues.mixedContentIssue) {
|
|
89
|
+
for (const details of artifacts.InspectorIssues.mixedContentIssue ?? []) {
|
|
90
90
|
let item = items.find(item => item.url === details.insecureURL);
|
|
91
91
|
if (!item) {
|
|
92
92
|
item = {url: details.insecureURL};
|
|
@@ -68,7 +68,7 @@ class ThirdPartyCookies extends Audit {
|
|
|
68
68
|
|
|
69
69
|
/** @type {LH.Audit.Details.TableItem[]} */
|
|
70
70
|
const items = [];
|
|
71
|
-
for (const issue of artifacts.InspectorIssues.cookieIssue) {
|
|
71
|
+
for (const issue of artifacts.InspectorIssues.cookieIssue ?? []) {
|
|
72
72
|
const isPhaseoutWarn = issue.cookieWarningReasons.includes('WarnThirdPartyPhaseout');
|
|
73
73
|
const isPhaseoutExclude = issue.cookieExclusionReasons.includes('ExcludeThirdPartyPhaseout');
|
|
74
74
|
if (!isPhaseoutWarn && !isPhaseoutExclude) continue;
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import log from 'lighthouse-logger';
|
|
8
|
+
|
|
7
9
|
import {ProcessedTrace} from '../processed-trace.js';
|
|
8
10
|
import {ProcessedNavigation} from '../processed-navigation.js';
|
|
9
11
|
import {Speedline} from '../speedline.js';
|
|
@@ -43,7 +45,10 @@ class TimingSummary {
|
|
|
43
45
|
* @return {Promise<TReturn|undefined>}
|
|
44
46
|
*/
|
|
45
47
|
const requestOrUndefined = (Artifact, artifact) => {
|
|
46
|
-
return Artifact.request(artifact, context).catch(
|
|
48
|
+
return Artifact.request(artifact, context).catch(err => {
|
|
49
|
+
log.error('lh:computed:TimingSummary', err);
|
|
50
|
+
return undefined;
|
|
51
|
+
});
|
|
47
52
|
};
|
|
48
53
|
|
|
49
54
|
/* eslint-disable max-len */
|
|
@@ -36,6 +36,10 @@ class PageDependencyGraph {
|
|
|
36
36
|
return graph;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// TODO: currently the trace version has no requests that failed, or requests that have "Preflight".
|
|
40
|
+
// so the following gets the devtools log version _closer_ to the exact same results as the trace.
|
|
41
|
+
// const lanternRequests = networkRecords.map(NetworkRequest.asLanternNetworkRequest).filter(r => !r.failed && r.resourceType !== 'Preflight');
|
|
42
|
+
|
|
39
43
|
const lanternRequests = networkRecords.map(NetworkRequest.asLanternNetworkRequest);
|
|
40
44
|
return Lantern.Graph.PageDependencyGraph.createGraph(mainThreadEvents, lanternRequests, URL);
|
|
41
45
|
}
|
|
@@ -97,6 +97,9 @@ class TraceEngineResult {
|
|
|
97
97
|
if (value && typeof value === 'object' && '__i18nBytes' in value) {
|
|
98
98
|
values[key] = value.__i18nBytes;
|
|
99
99
|
// TODO: use an actual byte formatter. Right now, this shows the exact number of bytes.
|
|
100
|
+
} else if (value && typeof value === 'object' && '__i18nMillis' in value) {
|
|
101
|
+
values[key] = `${value.__i18nMillis} ms`;
|
|
102
|
+
// TODO: use an actual time formatter.
|
|
100
103
|
} else if (value && typeof value === 'object' && 'i18nId' in value) {
|
|
101
104
|
// TODO: add support for str_ values to be IcuMessage. For now, we translate it here.
|
|
102
105
|
// This means that locale swapping won't work for this portion of the IcuMessage.
|
|
@@ -172,6 +175,10 @@ class TraceEngineResult {
|
|
|
172
175
|
// @ts-expect-error
|
|
173
176
|
values[key] = value.__i18nBytes;
|
|
174
177
|
// TODO: use an actual byte formatter. Right now, this shows the exact number of bytes.
|
|
178
|
+
} else if (value && typeof value === 'object' && '__i18nMillis' in value) {
|
|
179
|
+
// @ts-expect-error
|
|
180
|
+
values[key] = `${value.__i18nMillis} ms`;
|
|
181
|
+
// TODO: use an actual time formatter.
|
|
175
182
|
} else if (value && typeof value === 'object' && 'i18nId' in value) {
|
|
176
183
|
// TODO: add support for str_ values to be IcuMessage.
|
|
177
184
|
// @ts-expect-error
|
|
@@ -51,7 +51,7 @@ declare const MOTOGPOWER_EMULATION_METRICS: Required<LH.SharedFlagsSettings["scr
|
|
|
51
51
|
* @type {Required<LH.SharedFlagsSettings['screenEmulation']>}
|
|
52
52
|
*/
|
|
53
53
|
declare const DESKTOP_EMULATION_METRICS: Required<LH.SharedFlagsSettings["screenEmulation"]>;
|
|
54
|
-
declare const MOTOG4_USERAGENT: "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
55
|
-
declare const DESKTOP_USERAGENT: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
54
|
+
declare const MOTOG4_USERAGENT: "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36";
|
|
55
|
+
declare const DESKTOP_USERAGENT: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36";
|
|
56
56
|
export {};
|
|
57
57
|
//# sourceMappingURL=constants.d.ts.map
|
package/core/config/constants.js
CHANGED
|
@@ -39,8 +39,8 @@ const screenEmulationMetrics = {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
const MOTOG4_USERAGENT = 'Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
43
|
-
const DESKTOP_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
42
|
+
const MOTOG4_USERAGENT = 'Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36'; // eslint-disable-line max-len
|
|
43
|
+
const DESKTOP_USERAGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36'; // eslint-disable-line max-len
|
|
44
44
|
|
|
45
45
|
const userAgents = {
|
|
46
46
|
mobile: MOTOG4_USERAGENT,
|
|
@@ -19,6 +19,7 @@ const config = {
|
|
|
19
19
|
skipAudits: [
|
|
20
20
|
// Skip the h2 audit so it doesn't lie to us. See https://github.com/GoogleChrome/lighthouse/issues/6539
|
|
21
21
|
'uses-http2',
|
|
22
|
+
'modern-http-insight',
|
|
22
23
|
// There are always bf-cache failures when testing in headless. Reenable when headless can give us realistic bf-cache insights.
|
|
23
24
|
'bf-cache',
|
|
24
25
|
],
|
|
@@ -18,6 +18,7 @@ const config = {
|
|
|
18
18
|
skipAudits: [
|
|
19
19
|
// Skip the h2 audit so it doesn't lie to us. See https://github.com/GoogleChrome/lighthouse/issues/6539
|
|
20
20
|
'uses-http2',
|
|
21
|
+
'modern-http-insight',
|
|
21
22
|
// There are always bf-cache failures when testing in headless. Reenable when headless can give us realistic bf-cache insights.
|
|
22
23
|
'bf-cache',
|
|
23
24
|
],
|
|
@@ -19,9 +19,9 @@ declare class InspectorIssues extends BaseGatherer {
|
|
|
19
19
|
stopInstrumentation(context: LH.Gatherer.Context): Promise<void>;
|
|
20
20
|
/**
|
|
21
21
|
* @param {LH.Gatherer.Context<'DevtoolsLog'>} context
|
|
22
|
-
* @return {Promise<LH.Artifacts
|
|
22
|
+
* @return {Promise<LH.Artifacts.InspectorIssues>}
|
|
23
23
|
*/
|
|
24
|
-
getArtifact(context: LH.Gatherer.Context<"DevtoolsLog">): Promise<LH.Artifacts
|
|
24
|
+
getArtifact(context: LH.Gatherer.Context<"DevtoolsLog">): Promise<LH.Artifacts.InspectorIssues>;
|
|
25
25
|
}
|
|
26
26
|
import BaseGatherer from '../base-gatherer.js';
|
|
27
27
|
//# sourceMappingURL=inspector-issues.d.ts.map
|
|
@@ -54,7 +54,7 @@ class InspectorIssues extends BaseGatherer {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* @param {LH.Gatherer.Context<'DevtoolsLog'>} context
|
|
57
|
-
* @return {Promise<LH.Artifacts
|
|
57
|
+
* @return {Promise<LH.Artifacts.InspectorIssues>}
|
|
58
58
|
*/
|
|
59
59
|
async getArtifact(context) {
|
|
60
60
|
const devtoolsLog = context.dependencies.DevtoolsLog;
|
|
@@ -62,6 +62,7 @@ class InspectorIssues extends BaseGatherer {
|
|
|
62
62
|
|
|
63
63
|
/** @type {LH.Artifacts.InspectorIssues} */
|
|
64
64
|
const artifact = {
|
|
65
|
+
// TODO(v13): remove empty arrays.
|
|
65
66
|
attributionReportingIssue: [],
|
|
66
67
|
blockedByResponseIssue: [],
|
|
67
68
|
bounceTrackingIssue: [],
|
|
@@ -86,27 +87,39 @@ class InspectorIssues extends BaseGatherer {
|
|
|
86
87
|
stylesheetLoadingIssue: [],
|
|
87
88
|
sriMessageSignatureIssue: [],
|
|
88
89
|
federatedAuthUserInfoRequestIssue: [],
|
|
90
|
+
userReidentificationIssue: [],
|
|
89
91
|
};
|
|
90
|
-
|
|
91
|
-
for (const
|
|
92
|
-
/** @type {
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
|
|
93
|
+
for (const issue of this._issues) {
|
|
94
|
+
const detailsKey = /** @type {keyof LH.Crdp.Audits.InspectorIssueDetails} */(
|
|
95
|
+
Object.keys(issue.details)[0]);
|
|
96
|
+
const details = issue.details[detailsKey];
|
|
97
|
+
if (!details) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const artifactKey =
|
|
102
|
+
/** @type {LH.Artifacts.InspectorIssuesKeyToArtifactKey<typeof detailsKey>} */(
|
|
103
|
+
detailsKey.replace('Details', ''));
|
|
104
|
+
|
|
105
|
+
// Duplicate issues can occur for the same request; only use the one with a matching networkRequest.
|
|
106
|
+
const requestId = 'request' in details && details.request && details.request.requestId;
|
|
107
|
+
if (requestId) {
|
|
108
|
+
if (networkRecords.find(req => req.requestId === requestId)) {
|
|
109
|
+
if (!artifact[artifactKey]) {
|
|
110
|
+
artifact[artifactKey] = [];
|
|
105
111
|
}
|
|
106
|
-
|
|
112
|
+
|
|
107
113
|
// @ts-expect-error - detail types are not all compatible
|
|
108
|
-
artifact[
|
|
114
|
+
artifact[artifactKey].push(details);
|
|
109
115
|
}
|
|
116
|
+
} else {
|
|
117
|
+
if (!artifact[artifactKey]) {
|
|
118
|
+
artifact[artifactKey] = [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// @ts-expect-error - detail types are not all compatible
|
|
122
|
+
artifact[artifactKey].push(details);
|
|
110
123
|
}
|
|
111
124
|
}
|
|
112
125
|
|
package/core/lib/trace-engine.js
CHANGED
|
@@ -4,7 +4,7 @@ import {polyfillDOMRect} from './polyfill-dom-rect.js';
|
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('@paulirish/trace_engine').Types.Events.SyntheticLayoutShift} SyntheticLayoutShift */
|
|
6
6
|
/** @typedef {SyntheticLayoutShift & {args: {data: NonNullable<SyntheticLayoutShift['args']['data']>}}} SaneSyntheticLayoutShift */
|
|
7
|
-
/** @typedef {{i18nId: string, values: Record<string, string|number|{__i18nBytes: number}>}} DevToolsIcuMessage */
|
|
7
|
+
/** @typedef {{i18nId: string, values: Record<string, string|number|{__i18nBytes: number}|{__i18nMillis: number}>}} DevToolsIcuMessage */
|
|
8
8
|
|
|
9
9
|
polyfillDOMRect();
|
|
10
10
|
|