Skip to content

Commit e02ec0e

Browse files
committed
perf: 页面检测资源匹配先做多行预判,hint 索引改用小整数键
- 资源 URL 匹配先对拼接文本做一次多行预判,未命中不再逐条调用正则;含否定环视的正则仍逐条测试 - hint 分桶键限制在小整数范围,避免扫描时生成浮点堆对象 - 带页面 URL 的资源列表、类名列表、CSS 变量集合每次检测只计算一次 - 14 个站点采集数据对比识别结果逐项一致,page-detector 耗时再降 6%~30%
1 parent 6606552 commit e02ec0e

2 files changed

Lines changed: 56 additions & 17 deletions

File tree

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "stackprism",
33
"private": true,
4-
"version": "1.3.77",
4+
"version": "1.3.78",
55
"type": "module",
66
"description": "StackPrism 用于检测网页前端、后端、CDN、SaaS、广告营销、统计、登录、支付、网站程序和主题模板线索。",
77
"scripts": {

‎src/injected/page-detector.ts‎

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ const detectPageTechnologies = async (ruleConfig: Record<string, unknown> = {})
1313
const ruleRegexCache = new WeakMap()
1414
const ruleCombinedCache = new WeakMap()
1515
const ruleHintCache = new WeakMap()
16+
const lineProbeCache = new WeakMap()
1617
const resources = collectResources()
1718
const classTokens = collectClassTokens()
1819
const cssVariables = collectCssVariables()
1920
const documentHtmlSample = getHtmlSample()
2021
const globalKeys = safeGlobalKeys()
2122
const add = createCollector(technologies)
2223
let cachedBodyText = null
24+
let classTokenSource = null
25+
let classTokenNames = null
2326
let sliceStartedAt = performance.now()
2427
const hintIndex = createHintIndex(ruleConfig, {
2528
resources: resources.text,
@@ -1152,10 +1155,8 @@ ${html}`
11521155

11531156
const matchResource = shouldMatchTarget(rule, 'resources')
11541157
const matchHtml = !ruleResourceOnly && !context.resourceOnly && shouldMatchTarget(rule, 'html')
1155-
const allResources =
1156-
Array.isArray(rule.matchIn) && rule.matchIn.includes('url')
1157-
? unique([location.href, ...(context.resources?.all || [])])
1158-
: context.resources?.all || []
1158+
const includePageUrl = Array.isArray(rule.matchIn) && rule.matchIn.includes('url')
1159+
const allResources = getContextResources(context, includePageUrl)
11591160
const htmlText = context.text || ''
11601161
const formatUrlEvidence = resource =>
11611162
resource === location.href ? `页面 URL 匹配 ${shortUrl(resource)}` : `资源 URL 匹配 ${shortUrl(resource)}`
@@ -1187,10 +1188,7 @@ ${html}`
11871188
const combined = getCompiledCombinedPattern(rule)
11881189
if (combined) {
11891190
if (matchResource) {
1190-
const resource = allResources.find(url => {
1191-
combined.lastIndex = 0
1192-
return combined.test(url)
1193-
})
1191+
const resource = findMatchingResource(combined, allResources, context, includePageUrl)
11941192
if (resource) {
11951193
return {
11961194
confidence: rule.confidence || context.resourceConfidence || '高',
@@ -1211,10 +1209,7 @@ ${html}`
12111209
const patterns = getCompiledRulePatterns(rule)
12121210
for (const pattern of patterns) {
12131211
if (matchResource) {
1214-
const resource = allResources.find(url => {
1215-
pattern.lastIndex = 0
1216-
return pattern.test(url)
1217-
})
1212+
const resource = findMatchingResource(pattern, allResources, context, includePageUrl)
12181213
if (resource) {
12191214
return {
12201215
confidence: rule.confidence || context.resourceConfidence || '高',
@@ -1234,12 +1229,49 @@ ${html}`
12341229
return null
12351230
}
12361231

1232+
function getContextResources(context, includePageUrl) {
1233+
const key = includePageUrl ? '_resourcesWithPage' : '_resources'
1234+
if (!context[key]) {
1235+
const list = context.resources?.all || []
1236+
context[key] = includePageUrl ? unique([location.href, ...list]) : list
1237+
}
1238+
return context[key]
1239+
}
1240+
1241+
// 多行模式下 ^ / $ 按行匹配,与逐条 URL 测试等价;拼接只可能多出跨行命中,命中后仍逐条确认。
1242+
// 否定环视在行首行尾会把换行符当作相邻字符,可能少判,这类正则不做预判
1243+
function getLineProbe(pattern) {
1244+
let probe = lineProbeCache.get(pattern)
1245+
if (probe === undefined) {
1246+
probe = /\(\?<?!/.test(pattern.source) ? null : new RegExp(pattern.source, pattern.flags.replace(/[gym]/g, '') + 'm')
1247+
lineProbeCache.set(pattern, probe)
1248+
}
1249+
return probe
1250+
}
1251+
1252+
function findMatchingResource(pattern, resources, context, includePageUrl) {
1253+
if (!resources.length) return undefined
1254+
const probe = getLineProbe(pattern)
1255+
if (probe) {
1256+
const key = includePageUrl ? '_resourceLinesWithPage' : '_resourceLines'
1257+
if (context[key] === undefined) context[key] = resources.join('\n')
1258+
if (!probe.test(context[key])) return undefined
1259+
}
1260+
return resources.find(url => {
1261+
pattern.lastIndex = 0
1262+
return pattern.test(url)
1263+
})
1264+
}
1265+
12371266
function matchCssVariables(rule, cssVariables) {
12381267
if (!Array.isArray(rule.cssVariables) || !rule.cssVariables.length || !cssVariables?.names?.length) {
12391268
return null
12401269
}
12411270

1242-
const normalizedNames = new Set(cssVariables.names.map(name => name.toLowerCase()))
1271+
if (!cssVariables.lowerNameSet) {
1272+
cssVariables.lowerNameSet = new Set(cssVariables.names.map(name => name.toLowerCase()))
1273+
}
1274+
const normalizedNames = cssVariables.lowerNameSet
12431275
const matched = rule.cssVariables.filter(name => normalizedNames.has(String(name).toLowerCase()))
12441276
const minMatches = Math.max(1, Number(rule.minCssVariableMatches || 1))
12451277
if (matched.length < minMatches) {
@@ -1277,8 +1309,11 @@ ${html}`
12771309
const known = new Set()
12781310
const found = new Map()
12791311
const keyAt = (text, index) =>
1280-
((text.charCodeAt(index) * 65536 + text.charCodeAt(index + 1)) * 65536 + text.charCodeAt(index + 2)) * 65536 +
1281-
text.charCodeAt(index + 3)
1312+
((text.charCodeAt(index) << 21) ^
1313+
(text.charCodeAt(index + 1) << 14) ^
1314+
(text.charCodeAt(index + 2) << 7) ^
1315+
text.charCodeAt(index + 3)) &
1316+
0x3fffffff
12821317
const register = hint => {
12831318
if (hint.length < 4 || hint.includes('\n') || known.has(hint)) return
12841319
known.add(hint)
@@ -1655,7 +1690,11 @@ ${html}`
16551690
}
16561691

16571692
function hasClassPrefix(classes, prefix) {
1658-
return Object.keys(classes).some(name => name.startsWith(prefix))
1693+
if (!classTokenNames || classTokenSource !== classes) {
1694+
classTokenSource = classes
1695+
classTokenNames = Object.keys(classes)
1696+
}
1697+
return classTokenNames.some(name => name.startsWith(prefix))
16591698
}
16601699

16611700
function scoreTailwind(classes) {

0 commit comments

Comments
 (0)