From 0aa0d81cafb60d4d5b794937fe2efcdb463c47b9 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 5 Dec 2025 08:54:34 -0500 Subject: [PATCH] [mv3] Re-work specific cosmetic filtering-related content scripts To minimize delay to First Contentful Paint. The idea is to avoid complex data structures such as Map in order to speed up first content script execution. Use stringified arrays where practical and instanciate those arrays from their stringified representation only when there are actual cosmetic filters to apply. Related commit: https://github.com/gorhill/uBlock/commit/6039ef2b6d --- .../extension/js/scripting/css-procedural.js | 49 +-- .../extension/js/scripting/css-specific.js | 46 +-- .../extension/js/scripting/isolated-api.js | 22 ++ platform/mv3/make-rulesets.js | 289 ++++-------------- platform/mv3/make-scriptlets.js | 36 +-- .../mv3/scriptlets/css-procedural.template.js | 10 +- .../mv3/scriptlets/css-specific.template.js | 9 +- platform/mv3/scriptlets/scriptlet.template.js | 18 +- publish-extension | 2 +- 9 files changed, 169 insertions(+), 312 deletions(-) diff --git a/platform/mv3/extension/js/scripting/css-procedural.js b/platform/mv3/extension/js/scripting/css-procedural.js index 28c2d516b..a17da0020 100644 --- a/platform/mv3/extension/js/scripting/css-procedural.js +++ b/platform/mv3/extension/js/scripting/css-procedural.js @@ -30,42 +30,47 @@ self.proceduralImports = undefined; /******************************************************************************/ -const selectors = []; -const exceptions = []; +const isolatedAPI = self.isolatedAPI; +const selectors = new Set(); +const exceptions = new Set(); -const lookupHostname = (hostname, details, out) => { - let seqi = details.hostnamesMap.get(hostname); - if ( seqi === undefined ) { return; } - const { argsList, argsSeqs } = details; - for (;;) { - const argi = argsSeqs[seqi++]; - const done = argi > 0; - out.push(...JSON.parse(argsList[done ? argi : -argi])); - if ( done ) { break; } +const lookupHostname = (hostname, details) => { + const listref = isolatedAPI.binarySearch(details.hostnames, hostname); + if ( listref === -1 ) { return; } + if ( Array.isArray(details.selectorLists) === false ) { + details.selectorLists = details.selectorLists.split(';'); + details.selectorListRefs = JSON.parse(`[${details.selectorListRefs}]`); + } + const ilist = details.selectorListRefs[listref]; + const list = JSON.parse(`[${details.selectorLists[ilist]}]`); + for ( const iselector of list ) { + if ( iselector >= 0 ) { + selectors.add(details.selectors[iselector]); + } else { + exceptions.add(details.selectors[~iselector]); + } } }; const lookupAll = hostname => { for ( const details of proceduralImports ) { - lookupHostname(hostname, details, selectors); - const matches = []; - lookupHostname(`~${hostname}`, details, matches); - if ( matches.length === 0 ) { continue; } - exceptions.push(...matches.map(a => JSON.stringify(a))); + lookupHostname(hostname, details); } }; -self.isolatedAPI.forEachHostname(lookupAll, { +isolatedAPI.forEachHostname(lookupAll, { hasEntities: proceduralImports.some(a => a.hasEntities) }); + proceduralImports.length = 0; -if ( selectors.length === 0 ) { return; } +for ( const selector of exceptions ) { + selectors.delete(selector); +} -const exceptedSelectors = exceptions.length !== 0 - ? selectors.filter(a => exceptions.includes(JSON.stringify(a)) === false) - : selectors; -if ( exceptedSelectors.length === 0 ) { return; } +if ( selectors.size === 0 ) { return; } + +const exceptedSelectors = Array.from(selectors).map(a => JSON.parse(a)); const declaratives = exceptedSelectors.filter(a => a.cssable); if ( declaratives.length !== 0 ) { diff --git a/platform/mv3/extension/js/scripting/css-specific.js b/platform/mv3/extension/js/scripting/css-specific.js index e0117e7c3..4f2c878e1 100644 --- a/platform/mv3/extension/js/scripting/css-specific.js +++ b/platform/mv3/extension/js/scripting/css-specific.js @@ -30,42 +30,48 @@ self.specificImports = undefined; /******************************************************************************/ -const selectors = []; -const exceptions = []; +const isolatedAPI = self.isolatedAPI; +const selectors = new Set(); +const exceptions = new Set(); -const lookupHostname = (hostname, details, out) => { - let seqi = details.hostnamesMap.get(hostname); - if ( seqi === undefined ) { return; } - const { argsList, argsSeqs } = details; - for (;;) { - const argi = argsSeqs[seqi++]; - const done = argi > 0; - out.push(...argsList[done ? argi : -argi].split('\n')); - if ( done ) { break; } +const lookupHostname = (hostname, details) => { + const listref = isolatedAPI.binarySearch(details.hostnames, hostname); + if ( listref === -1 ) { return; } + if ( Array.isArray(details.selectorLists) === false ) { + details.selectorLists = details.selectorLists.split(';'); + details.selectorListRefs = JSON.parse(`[${details.selectorListRefs}]`); + } + const ilist = details.selectorListRefs[listref]; + const list = JSON.parse(`[${details.selectorLists[ilist]}]`); + for ( const iselector of list ) { + if ( iselector >= 0 ) { + selectors.add(details.selectors[iselector]); + } else { + exceptions.add(details.selectors[~iselector]); + } } }; const lookupAll = hostname => { for ( const details of specificImports ) { - lookupHostname(hostname, details, selectors); - lookupHostname(`~${hostname}`, details, exceptions); + lookupHostname(hostname, details); } }; -self.isolatedAPI.forEachHostname(lookupAll, { +isolatedAPI.forEachHostname(lookupAll, { hasEntities: specificImports.some(a => a.hasEntities) }); specificImports.length = 0; -if ( selectors.length === 0 ) { return; } +for ( const selector of exceptions ) { + selectors.delete(selector); +} -const exceptedSelectors = exceptions.length !== 0 - ? selectors.filter(a => exceptions.includes(a) === false) - : selectors; -if ( exceptedSelectors.length === 0 ) { return; } +if ( selectors.size === 0 ) { return; } -self.cssAPI.insert(`${exceptedSelectors.join(',')}{display:none!important;}`); +const css = `${Array.from(selectors).join(',\n')}{display:none!important;}`; +self.cssAPI.insert(css); /******************************************************************************/ diff --git a/platform/mv3/extension/js/scripting/isolated-api.js b/platform/mv3/extension/js/scripting/isolated-api.js index 4781f935c..4cf251548 100644 --- a/platform/mv3/extension/js/scripting/isolated-api.js +++ b/platform/mv3/extension/js/scripting/isolated-api.js @@ -73,6 +73,28 @@ if ( r !== undefined ) { return r; } } }; + + isolatedAPI.binarySearch = (sorted, target) => { + let l = 0, i = 0, d = 0; + let r = sorted.length; + let candidate; + while ( l < r ) { + i = l + r >>> 1; + candidate = sorted[i]; + d = target.length - candidate.length; + if ( d === 0 ) { + if ( target === candidate ) { return i; } + d = target < candidate ? -1 : 1; + } + if ( d < 0 ) { + r = i; + } else { + l = i + 1; + } + } + return -1; + }; + })(self.isolatedAPI); /******************************************************************************/ diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 9f307232c..9443e5ac9 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -90,11 +90,6 @@ const jsonSetMapReplacer = (k, v) => { return v; }; -const uidint32 = (s) => { - const h = createHash('sha256').update(s).digest('hex').slice(0,8); - return parseInt(h,16) & 0x7FFFFFFF; -}; - /******************************************************************************/ const consoleLog = console.log; @@ -812,84 +807,6 @@ const globalHighlyGenericExceptionSet = new Set(); /******************************************************************************/ -// This merges selectors which are used by the same hostnames - -function groupSelectorsByHostnames(mapin) { - if ( mapin === undefined ) { return []; } - const merged = new Map(); - for ( const [ selector, details ] of mapin ) { - if ( details.rejected ) { continue; } - const json = JSON.stringify(details); - let entries = merged.get(json); - if ( entries === undefined ) { - entries = new Set(); - merged.set(json, entries); - } - entries.add(selector); - } - const out = []; - for ( const [ json, entries ] of merged ) { - const details = JSON.parse(json); - details.selectors = Array.from(entries).sort(); - out.push(details); - } - return out; -} - -// This merges hostnames which have the same set of selectors. -// -// Also, we sort the hostnames to increase likelihood that selector with -// same hostnames will end up in same generated scriptlet. - -function groupHostnamesBySelectors(arrayin) { - const contentMap = new Map(); - for ( const entry of arrayin ) { - const id = uidint32(JSON.stringify(entry.selectors)); - let details = contentMap.get(id); - if ( details === undefined ) { - details = { a: entry.selectors }; - contentMap.set(id, details); - } - if ( entry.matches !== undefined ) { - if ( details.y === undefined ) { - details.y = new Set(); - } - for ( const hn of entry.matches ) { - details.y.add(hn); - } - } - if ( entry.excludeMatches !== undefined ) { - if ( details.n === undefined ) { - details.n = new Set(); - } - for ( const hn of entry.excludeMatches ) { - details.n.add(hn); - } - } - } - const out = Array.from(contentMap).map(a => [ - a[0], { - a: a[1].a, - y: a[1].y ? Array.from(a[1].y) : undefined, - n: a[1].n ? Array.from(a[1].n) : undefined, - } - ]); - return out; -} - -const scriptletHostnameToIdMap = (hostnames, id, map) => { - for ( const hn of hostnames ) { - const existing = map.get(hn); - if ( existing === undefined ) { - map.set(hn, id); - } else if ( Array.isArray(existing) ) { - existing.push(id); - } else { - map.set(hn, [ existing, id ]); - } - } -}; - const scriptletJsonReplacer = (k, v) => { if ( k === 'n' ) { if ( v === undefined || v.size === 0 ) { return; } @@ -904,175 +821,86 @@ const scriptletJsonReplacer = (k, v) => { /******************************************************************************/ -function argsMap2List(argsMap, hostnamesMap) { - const argsList = [ '' ]; - const indexMap = new Map(); - for ( const [ id, details ] of argsMap ) { - indexMap.set(id, argsList.length); - argsList.push(details); - } - const argsSeqs = [ 0 ]; - const argsSeqsIndices = new Map(); - for ( const [ hn, ids ] of hostnamesMap ) { - const seqKey = JSON.stringify(ids); - if ( argsSeqsIndices.has(seqKey) ) { - hostnamesMap.set(hn, argsSeqsIndices.get(seqKey)); - continue; - } - const seqIndex = argsSeqs.length; - argsSeqsIndices.set(seqKey, seqIndex); - hostnamesMap.set(hn, seqIndex); - if ( typeof ids === 'number' ) { - argsSeqs.push(indexMap.get(ids)); - continue; - } - for ( let i = 0; i < ids.length; i++ ) { - argsSeqs.push(-indexMap.get(ids[i])); - } - argsSeqs[argsSeqs.length-1] = -argsSeqs[argsSeqs.length-1]; - } - return { argsList, argsSeqs }; -} - -/******************************************************************************/ - -async function processCosmeticFilters(assetDetails, mapin) { +async function processCosmeticFilters(assetDetails, realm, mapin) { if ( mapin === undefined ) { return 0; } if ( mapin.size === 0 ) { return 0; } - const domainBasedEntries = groupHostnamesBySelectors( - groupSelectorsByHostnames(mapin) - ); - // We do not want more than n CSS files per subscription, so we will - // group multiple unrelated selectors in the same file, and distinct - // css declarations will be injected programmatically according to the - // hostname of the current document. - // + // Collate all distinct selectors + const allSelectors = new Map(); + const allHostnames = new Map(); + let hasEntities = false; + for ( const [ selector, details ] of mapin ) { + if ( details.rejected ) { continue; } + if ( allSelectors.has(selector) === false ) { + allSelectors.set(selector, allSelectors.size); + } + const iSelector = allSelectors.get(selector); + if ( details.matches ) { + for ( const hn of details.matches ) { + if ( allHostnames.has(hn) === false ) { + allHostnames.set(hn, new Set()); + } + allHostnames.get(hn).add(iSelector); + hasEntities ||= hn.endsWith('.*'); + } + } + if ( details.excludeMatches ) { + for ( const hn of details.excludeMatches ) { + if ( allHostnames.has(hn) === false ) { + allHostnames.set(hn, new Set()); + } + allHostnames.get(hn).add(~iSelector); + hasEntities ||= hn.endsWith('.*'); + } + } + } + const allSelectorLists = new Map(); + for ( const [ hn, selectorSet ] of allHostnames ) { + const list = JSON.stringify(Array.from(selectorSet).sort()).slice(1, -1); + if ( allSelectorLists.has(list) === false ) { + allSelectorLists.set(list, allSelectorLists.size); + } + allHostnames.set(hn, allSelectorLists.get(list)); + } + // The cosmetic filters will be injected programmatically as content // script and the decisions to activate the cosmetic filters will be // done at injection time according to the document's hostname. - const generatedFiles = []; - - const argsMap = domainBasedEntries.map(entry => [ - entry[0], - entry[1].a ? entry[1].a.join('\n') : undefined, - ]); - const hostnamesMap = new Map(); - let hasEntities = false; - for ( const [ id, details ] of domainBasedEntries ) { - if ( details.y ) { - scriptletHostnameToIdMap(details.y, id, hostnamesMap); - hasEntities ||= details.y.some(a => a.endsWith('.*')); - } - if ( details.n ) { - scriptletHostnameToIdMap(details.n.map(a => `~${a}`), id, hostnamesMap); - hasEntities ||= details.n.some(a => a.endsWith('.*')); - } - } - const { argsList, argsSeqs } = argsMap2List(argsMap, hostnamesMap); - const originalScriptletMap = await loadAllSourceScriptlets(); - let patchedScriptlet = originalScriptletMap.get('css-specific').replace( + let patchedScriptlet = originalScriptletMap.get(`css-${realm}`).replace( '$rulesetId$', assetDetails.id ); patchedScriptlet = safeReplace(patchedScriptlet, - /\bself\.\$argsList\$/, - `${JSON.stringify(argsList, scriptletJsonReplacer)}` + /\bself\.\$selectors\$/, + `/* ${allSelectors.size} */ ${JSON.stringify(Array.from(allSelectors.keys()))}` ); patchedScriptlet = safeReplace(patchedScriptlet, - /\bself\.\$argsSeqs\$/, - `${JSON.stringify(argsSeqs, scriptletJsonReplacer)}` + /\bself\.\$selectorLists\$/, + `/* ${allSelectorLists.size} */ ${JSON.stringify(Array.from(allSelectorLists.keys()).join(';'))}` ); - patchedScriptlet = safeReplace(patchedScriptlet, - /\bself\.\$hostnamesMap\$/, - `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` - ); - patchedScriptlet = safeReplace(patchedScriptlet, - 'self.$hasEntities$', - JSON.stringify(hasEntities) - ); - writeFile(`${scriptletDir}/specific/${assetDetails.id}.js`, patchedScriptlet); - generatedFiles.push(`${assetDetails.id}`); - - if ( generatedFiles.length !== 0 ) { - log(`CSS-specific: ${mapin.size} distinct filters`); - log(`\tCombined into ${hostnamesMap.size} distinct hostnames`); - } - - return hostnamesMap.size; -} - -/******************************************************************************/ - -async function processProceduralCosmeticFilters(assetDetails, mapin) { - if ( mapin === undefined ) { return 0; } - if ( mapin.size === 0 ) { return 0; } - - const procedurals = new Map(); - mapin.forEach((details, jsonSelector) => { - procedurals.set(jsonSelector, details); + const sortedHostnames = Array.from(allHostnames.keys()).toSorted((a, b) => { + const d = a.length - b.length; + if ( d !== 0 ) { return d; } + return a < b ? -1 : 1; }); - if ( procedurals.size === 0 ) { return 0; } - - const contentArray = groupHostnamesBySelectors( - groupSelectorsByHostnames(procedurals) - ); - - const argsMap = contentArray.map(entry => [ - entry[0], - entry[1].a, - ]); - const hostnamesMap = new Map(); - let hasEntities = false; - for ( const [ id, details ] of contentArray ) { - if ( details.y ) { - scriptletHostnameToIdMap(details.y, id, hostnamesMap); - hasEntities ||= details.y.some(a => a.endsWith('.*')); - } - if ( details.n ) { - scriptletHostnameToIdMap(details.n.map(a => `~${a}`), id, hostnamesMap); - hasEntities ||= details.n.some(a => a.endsWith('.*')); - } - } - const { argsList, argsSeqs } = argsMap2List(argsMap, hostnamesMap); - const argsListAfter = []; - for ( const a of argsList ) { - const aAfter = []; - for ( let b of a ) { - aAfter.push(JSON.parse(b)); - } - argsListAfter.push(JSON.stringify(aAfter)); - } - const originalScriptletMap = await loadAllSourceScriptlets(); - let patchedScriptlet = originalScriptletMap.get('css-procedural').replace( - '$rulesetId$', - assetDetails.id + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$selectorListRefs\$/, + `/* ${sortedHostnames.length} */ "${JSON.stringify(sortedHostnames.map(a => allHostnames.get(a))).slice(1, -1)}"` ); patchedScriptlet = safeReplace(patchedScriptlet, - /\bself\.\$argsList\$/, - `${JSON.stringify(argsListAfter, scriptletJsonReplacer)}` - ); - patchedScriptlet = safeReplace(patchedScriptlet, - /\bself\.\$argsSeqs\$/, - `${JSON.stringify(argsSeqs, scriptletJsonReplacer)}` - ); - patchedScriptlet = safeReplace(patchedScriptlet, - /\bself\.\$hostnamesMap\$/, - `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` + /\bself\.\$hostnames\$/, + `/* ${sortedHostnames.length} */ ${JSON.stringify(sortedHostnames)}` ); patchedScriptlet = safeReplace(patchedScriptlet, 'self.$hasEntities$', JSON.stringify(hasEntities) ); - writeFile(`${scriptletDir}/procedural/${assetDetails.id}.js`, patchedScriptlet); + writeFile(`${scriptletDir}/${realm}/${assetDetails.id}.js`, patchedScriptlet); - if ( contentArray.length !== 0 ) { - log(`Procedural-related distinct filters: ${procedurals.size} distinct combined selectors`); - log(`\tCombined into ${hostnamesMap.size} distinct hostnames`); - } + log(`CSS-${realm}: ${allSelectors.size} distinct filters for ${allHostnames.size} distinct hostnames`); - return hostnamesMap.size; + return sortedHostnames.length; } /******************************************************************************/ @@ -1213,10 +1041,13 @@ async function rulesetFromURLs(assetDetails) { ); const specificCosmeticStats = await processCosmeticFilters( assetDetails, + 'specific', declarativeCosmetic ); - const proceduralStats = await processProceduralCosmeticFilters( + + const proceduralStats = await processCosmeticFilters( assetDetails, + 'procedural', proceduralCosmetic ); const scriptletStats = await processScriptletFilters( diff --git a/platform/mv3/make-scriptlets.js b/platform/mv3/make-scriptlets.js index ae405c333..67226e5c4 100644 --- a/platform/mv3/make-scriptlets.js +++ b/platform/mv3/make-scriptlets.js @@ -173,40 +173,30 @@ export async function commit(rulesetId, path, writeFn) { return a[0] < b[0] ? -1 : 1; }).map(a => ([ a[0], JSON.stringify(Array.from(a[1]).map(a => JSON.parse(a))).slice(1,-1)])); let content = safeReplace(scriptletTemplate, /\$rulesetId\$/, rulesetId, 0); - if ( worldDetails.hasEntities ) { - content = safeReplace(content, - 'const $hasEntities$ = false;', - 'const $hasEntities$ = true;' - ); - } - if ( worldDetails.hasAncestors ) { - content = safeReplace(content, - 'const $hasAncestors$ = false;', - 'const $hasAncestors$ = true;' - ); - }; + content = safeReplace(content, 'self.$hasEntities$', 'true'); + content = safeReplace(content, 'self.$hasAncestors$', 'true'); content = safeReplace(content, - 'const $scriptletHostnames$ = [];', - `const $scriptletHostnames$ = /* ${hostnames.length} */ ${JSON.stringify(hostnames.map(a => a[0]))};` + 'self.$scriptletHostnames$', + `/* ${hostnames.length} */ ${JSON.stringify(hostnames.map(a => a[0]))}` ); content = safeReplace(content, - 'const $scriptletArglistRefs$ = [];', - `const $scriptletArglistRefs$ = /* ${hostnames.length} */ ${JSON.stringify(hostnames.map(a => a[1]).join(';'))};` + 'self.$scriptletArglistRefs$', + `/* ${hostnames.length} */ ${JSON.stringify(hostnames.map(a => a[1]).join(';'))}` ); content = safeReplace(content, - 'const $scriptletArglists$ = [];', - `const $scriptletArglists$ = /* ${arglists.size} */ ${JSON.stringify(Array.from(arglists.keys()).join(';'))};` + 'self.$scriptletArglists$', + `/* ${arglists.size} */ ${JSON.stringify(Array.from(arglists.keys()).join(';'))}` ); content = safeReplace(content, - 'const $scriptletArgs$ = [];', - `const $scriptletArgs$ = /* ${args.size} */ ${JSON.stringify(Array.from(args.keys()).join('\n'))};` + 'self.$scriptletArgs$', + `/* ${args.size} */ ${JSON.stringify(Array.from(args.keys()))}` ); content = safeReplace(content, - 'const $scriptletFunctions$ = [];', - `const $scriptletFunctions$ = /* ${scriptletFunctions.size} */\n[${Array.from(scriptletFunctions.keys()).join(',')}];` + 'self.$scriptletFunctions$', + `/* ${scriptletFunctions.size} */\n[${Array.from(scriptletFunctions.keys()).join(',')}]` ); content = safeReplace(content, - 'function $scriptletCode$(){} // eslint-disable-line', + 'self.$scriptletCode$', Array.from(allFunctions.values()).join('\n\n') ); writeFn(`${path}/${world.toLowerCase()}/${rulesetId}.js`, content); diff --git a/platform/mv3/scriptlets/css-procedural.template.js b/platform/mv3/scriptlets/css-procedural.template.js index 1928abdd4..1456dc1be 100644 --- a/platform/mv3/scriptlets/css-procedural.template.js +++ b/platform/mv3/scriptlets/css-procedural.template.js @@ -27,13 +27,15 @@ /******************************************************************************/ -const argsList = self.$argsList$; -const argsSeqs = self.$argsSeqs$; -const hostnamesMap = new Map(self.$hostnamesMap$); + +const selectors = self.$selectors$; +const selectorLists = self.$selectorLists$; +const selectorListRefs = self.$selectorListRefs$; +const hostnames = self.$hostnames$; const hasEntities = self.$hasEntities$; self.proceduralImports = self.proceduralImports || []; -self.proceduralImports.push({ argsList, argsSeqs, hostnamesMap, hasEntities }); +self.proceduralImports.push({ selectors, selectorLists, selectorListRefs, hostnames, hasEntities }); /******************************************************************************/ diff --git a/platform/mv3/scriptlets/css-specific.template.js b/platform/mv3/scriptlets/css-specific.template.js index f4c93cdf0..cfe84f385 100644 --- a/platform/mv3/scriptlets/css-specific.template.js +++ b/platform/mv3/scriptlets/css-specific.template.js @@ -27,13 +27,14 @@ /******************************************************************************/ -const argsList = self.$argsList$; -const argsSeqs = self.$argsSeqs$; -const hostnamesMap = new Map(self.$hostnamesMap$); +const selectors = self.$selectors$; +const selectorLists = self.$selectorLists$; +const selectorListRefs = self.$selectorListRefs$; +const hostnames = self.$hostnames$; const hasEntities = self.$hasEntities$; self.specificImports = self.specificImports || []; -self.specificImports.push({ argsList, argsSeqs, hostnamesMap, hasEntities }); +self.specificImports.push({ selectors, selectorLists, selectorListRefs, hostnames, hasEntities }); /******************************************************************************/ diff --git a/platform/mv3/scriptlets/scriptlet.template.js b/platform/mv3/scriptlets/scriptlet.template.js index 63c80f8cd..b8dedca6b 100644 --- a/platform/mv3/scriptlets/scriptlet.template.js +++ b/platform/mv3/scriptlets/scriptlet.template.js @@ -30,24 +30,24 @@ /******************************************************************************/ -function $scriptletCode$(){} // eslint-disable-line +self.$scriptletCode$ /******************************************************************************/ const scriptletGlobals = {}; // eslint-disable-line -const $scriptletFunctions$ = []; +const $scriptletFunctions$ = self.$scriptletFunctions$; -const $scriptletArgs$ = []; +const $scriptletArgs$ = self.$scriptletArgs$; -const $scriptletArglists$ = []; +const $scriptletArglists$ = self.$scriptletArglists$; -const $scriptletArglistRefs$ = []; +const $scriptletArglistRefs$ = self.$scriptletArglistRefs$; -const $scriptletHostnames$ = []; +const $scriptletHostnames$ = self.$scriptletHostnames$; -const $hasEntities$ = false; -const $hasAncestors$ = false; +const $hasEntities$ = self.$hasEntities$; +const $hasAncestors$ = self.$hasAncestors$; /******************************************************************************/ @@ -150,7 +150,7 @@ const todo = new Set(); // Execute scriplets { const arglists = $scriptletArglists$.split(';'); - const args = $scriptletArgs$.split('\n'); + const args = $scriptletArgs$; for ( const ref of todo ) { if ( ref < 0 ) { continue; } if ( todo.has(~ref) ) { continue; } diff --git a/publish-extension b/publish-extension index 7eab39d94..9e03b41c8 160000 --- a/publish-extension +++ b/publish-extension @@ -1 +1 @@ -Subproject commit 7eab39d94e48ba6410aad20780d8be1e28b70e27 +Subproject commit 9e03b41c86e9afc79a782f1cefcc5770d53a0c67