-
Notifications
You must be signed in to change notification settings - Fork 9.5k
/
Copy pathentity-classification.js
157 lines (134 loc) · 4.95 KB
/
entity-classification.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {makeComputedArtifact} from './computed-artifact.js';
import {NetworkRecords} from './network-records.js';
import {Util} from '../../shared/util.js';
import UrlUtils from '../lib/url-utils.js';
import thirdPartyWeb from '../lib/third-party-web.js';
/** @typedef {Map<string, LH.Artifacts.Entity>} EntityCache */
class EntityClassification {
/**
* @param {EntityCache} entityCache
* @param {string} url
* @param {string=} extensionName
* @return {LH.Artifacts.Entity}
*/
static makeupChromeExtensionEntity_(entityCache, url, extensionName) {
const origin = Util.getChromeExtensionOrigin(url);
const host = new URL(origin).host;
const name = extensionName || host;
const cachedEntity = entityCache.get(origin);
if (cachedEntity) return cachedEntity;
const chromeExtensionEntity = {
name,
company: name,
category: 'Chrome Extension',
homepage: 'https://chromewebstore.google.com/detail/' + host,
categories: [],
domains: [],
averageExecutionTime: 0,
totalExecutionTime: 0,
totalOccurrences: 0,
};
entityCache.set(origin, chromeExtensionEntity);
return chromeExtensionEntity;
}
/**
* @param {EntityCache} entityCache
* @param {string} url
* @return {LH.Artifacts.Entity | undefined}
*/
static _makeUpAnEntity(entityCache, url) {
if (!UrlUtils.isValid(url)) return;
const parsedUrl = Util.createOrReturnURL(url);
if (parsedUrl.protocol === 'chrome-extension:') {
return EntityClassification.makeupChromeExtensionEntity_(entityCache, url);
}
// Make up an entity only for valid http/https URLs.
if (!parsedUrl.protocol.startsWith('http')) return;
const rootDomain = UrlUtils.getRootDomain(url);
if (!rootDomain) return;
if (entityCache.has(rootDomain)) return entityCache.get(rootDomain);
const unrecognizedEntity = {
name: rootDomain,
company: rootDomain,
category: '',
categories: [],
domains: [rootDomain],
averageExecutionTime: 0,
totalExecutionTime: 0,
totalOccurrences: 0,
isUnrecognized: true,
};
entityCache.set(rootDomain, unrecognizedEntity);
return unrecognizedEntity;
}
/**
* Preload Chrome extensions found in the devtoolsLog into cache.
* @param {EntityCache} entityCache
* @param {LH.DevtoolsLog} devtoolsLog
*/
static _preloadChromeExtensionsToCache(entityCache, devtoolsLog) {
for (const entry of devtoolsLog.values()) {
if (entry.method !== 'Runtime.executionContextCreated') continue;
const origin = entry.params.context.origin;
if (!origin.startsWith('chrome-extension:')) continue;
if (entityCache.has(origin)) continue;
EntityClassification.makeupChromeExtensionEntity_(entityCache, origin,
entry.params.context.name);
}
}
/**
* @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog}} data
* @param {LH.Artifacts.ComputedContext} context
* @return {Promise<LH.Artifacts.EntityClassification>}
*/
static async compute_(data, context) {
const networkRecords = await NetworkRecords.request(data.devtoolsLog, context);
/** @type {EntityCache} */
const madeUpEntityCache = new Map();
/** @type {Map<string, LH.Artifacts.Entity>} */
const entityByUrl = new Map();
/** @type {Map<LH.Artifacts.Entity, Set<string>>} */
const urlsByEntity = new Map();
EntityClassification._preloadChromeExtensionsToCache(madeUpEntityCache, data.devtoolsLog);
for (const record of networkRecords) {
const {url} = record;
if (entityByUrl.has(url)) continue;
const entity = thirdPartyWeb.getEntity(url) ||
EntityClassification._makeUpAnEntity(madeUpEntityCache, url);
if (!entity) continue;
const entityURLs = urlsByEntity.get(entity) || new Set();
entityURLs.add(url);
urlsByEntity.set(entity, entityURLs);
entityByUrl.set(url, entity);
}
// When available, first party identification will be done via
// `mainDocumentUrl` (for navigations), and falls back to `finalDisplayedUrl` (for timespan/snapshot).
// See https://github.com/GoogleChrome/lighthouse/issues/13706
const firstPartyUrl = data.URL.mainDocumentUrl || data.URL.finalDisplayedUrl;
const firstParty = thirdPartyWeb.getEntity(firstPartyUrl) ||
EntityClassification._makeUpAnEntity(madeUpEntityCache, firstPartyUrl);
/**
* Convenience function to check if a URL belongs to first party.
* @param {string} url
* @return {boolean}
*/
function isFirstParty(url) {
const entityUrl = entityByUrl.get(url);
return entityUrl === firstParty;
}
return {
entityByUrl,
urlsByEntity,
firstParty,
isFirstParty,
};
}
}
const EntityClassificationComputed = makeComputedArtifact(EntityClassification,
['URL', 'devtoolsLog']);
export {EntityClassificationComputed as EntityClassification};