@hominis/fireforge 0.9.0
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/CHANGELOG.md +7 -0
- package/LICENSE.md +294 -0
- package/README.md +435 -0
- package/dist/bin/fireforge.d.ts +10 -0
- package/dist/bin/fireforge.js +29 -0
- package/dist/src/cli.d.ts +33 -0
- package/dist/src/cli.js +180 -0
- package/dist/src/commands/bootstrap.d.ts +9 -0
- package/dist/src/commands/bootstrap.js +73 -0
- package/dist/src/commands/build.d.ts +11 -0
- package/dist/src/commands/build.js +102 -0
- package/dist/src/commands/config.d.ts +13 -0
- package/dist/src/commands/config.js +135 -0
- package/dist/src/commands/discard.d.ts +12 -0
- package/dist/src/commands/discard.js +84 -0
- package/dist/src/commands/doctor.d.ts +18 -0
- package/dist/src/commands/doctor.js +356 -0
- package/dist/src/commands/download.d.ts +11 -0
- package/dist/src/commands/download.js +127 -0
- package/dist/src/commands/export-all.d.ts +11 -0
- package/dist/src/commands/export-all.js +122 -0
- package/dist/src/commands/export-shared.d.ts +48 -0
- package/dist/src/commands/export-shared.js +208 -0
- package/dist/src/commands/export.d.ts +13 -0
- package/dist/src/commands/export.js +178 -0
- package/dist/src/commands/furnace/apply.d.ts +7 -0
- package/dist/src/commands/furnace/apply.js +80 -0
- package/dist/src/commands/furnace/create.d.ts +8 -0
- package/dist/src/commands/furnace/create.js +377 -0
- package/dist/src/commands/furnace/deploy.d.ts +8 -0
- package/dist/src/commands/furnace/deploy.js +338 -0
- package/dist/src/commands/furnace/diff.d.ts +7 -0
- package/dist/src/commands/furnace/diff.js +119 -0
- package/dist/src/commands/furnace/index.d.ts +16 -0
- package/dist/src/commands/furnace/index.js +121 -0
- package/dist/src/commands/furnace/list.d.ts +5 -0
- package/dist/src/commands/furnace/list.js +65 -0
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +188 -0
- package/dist/src/commands/furnace/preview.d.ts +7 -0
- package/dist/src/commands/furnace/preview.js +96 -0
- package/dist/src/commands/furnace/remove.d.ts +8 -0
- package/dist/src/commands/furnace/remove.js +159 -0
- package/dist/src/commands/furnace/scan.d.ts +5 -0
- package/dist/src/commands/furnace/scan.js +112 -0
- package/dist/src/commands/furnace/status.d.ts +7 -0
- package/dist/src/commands/furnace/status.js +137 -0
- package/dist/src/commands/furnace/validate.d.ts +6 -0
- package/dist/src/commands/furnace/validate.js +91 -0
- package/dist/src/commands/furnace/validation-output.d.ts +7 -0
- package/dist/src/commands/furnace/validation-output.js +22 -0
- package/dist/src/commands/import.d.ts +11 -0
- package/dist/src/commands/import.js +241 -0
- package/dist/src/commands/lint.d.ts +10 -0
- package/dist/src/commands/lint.js +118 -0
- package/dist/src/commands/package.d.ts +11 -0
- package/dist/src/commands/package.js +80 -0
- package/dist/src/commands/re-export.d.ts +12 -0
- package/dist/src/commands/re-export.js +242 -0
- package/dist/src/commands/rebase/abort.d.ts +7 -0
- package/dist/src/commands/rebase/abort.js +49 -0
- package/dist/src/commands/rebase/confirm.d.ts +18 -0
- package/dist/src/commands/rebase/confirm.js +33 -0
- package/dist/src/commands/rebase/continue.d.ts +7 -0
- package/dist/src/commands/rebase/continue.js +81 -0
- package/dist/src/commands/rebase/index.d.ts +22 -0
- package/dist/src/commands/rebase/index.js +127 -0
- package/dist/src/commands/rebase/patch-loop.d.ts +9 -0
- package/dist/src/commands/rebase/patch-loop.js +135 -0
- package/dist/src/commands/rebase/summary.d.ts +12 -0
- package/dist/src/commands/rebase/summary.js +43 -0
- package/dist/src/commands/rebase.d.ts +4 -0
- package/dist/src/commands/rebase.js +6 -0
- package/dist/src/commands/register.d.ts +13 -0
- package/dist/src/commands/register.js +67 -0
- package/dist/src/commands/reset.d.ts +11 -0
- package/dist/src/commands/reset.js +83 -0
- package/dist/src/commands/resolve.d.ts +9 -0
- package/dist/src/commands/resolve.js +124 -0
- package/dist/src/commands/run.d.ts +9 -0
- package/dist/src/commands/run.js +91 -0
- package/dist/src/commands/setup-support.d.ts +23 -0
- package/dist/src/commands/setup-support.js +310 -0
- package/dist/src/commands/setup.d.ts +11 -0
- package/dist/src/commands/setup.js +94 -0
- package/dist/src/commands/status.d.ts +11 -0
- package/dist/src/commands/status.js +268 -0
- package/dist/src/commands/test.d.ts +12 -0
- package/dist/src/commands/test.js +182 -0
- package/dist/src/commands/token-coverage.d.ts +5 -0
- package/dist/src/commands/token-coverage.js +57 -0
- package/dist/src/commands/token.d.ts +14 -0
- package/dist/src/commands/token.js +121 -0
- package/dist/src/commands/watch.d.ts +9 -0
- package/dist/src/commands/watch.js +112 -0
- package/dist/src/commands/wire.d.ts +13 -0
- package/dist/src/commands/wire.js +149 -0
- package/dist/src/core/ast-utils.d.ts +47 -0
- package/dist/src/core/ast-utils.js +57 -0
- package/dist/src/core/brand-validation.d.ts +7 -0
- package/dist/src/core/brand-validation.js +15 -0
- package/dist/src/core/branding.d.ts +49 -0
- package/dist/src/core/branding.js +229 -0
- package/dist/src/core/browser-wire.d.ts +40 -0
- package/dist/src/core/browser-wire.js +66 -0
- package/dist/src/core/build-prepare.d.ts +25 -0
- package/dist/src/core/build-prepare.js +93 -0
- package/dist/src/core/config-mutate.d.ts +15 -0
- package/dist/src/core/config-mutate.js +51 -0
- package/dist/src/core/config-paths.d.ts +28 -0
- package/dist/src/core/config-paths.js +65 -0
- package/dist/src/core/config-state.d.ts +28 -0
- package/dist/src/core/config-state.js +152 -0
- package/dist/src/core/config-validate.d.ts +11 -0
- package/dist/src/core/config-validate.js +141 -0
- package/dist/src/core/config.d.ts +39 -0
- package/dist/src/core/config.js +70 -0
- package/dist/src/core/file-lock.d.ts +11 -0
- package/dist/src/core/file-lock.js +80 -0
- package/dist/src/core/firefox-archive.d.ts +40 -0
- package/dist/src/core/firefox-archive.js +63 -0
- package/dist/src/core/firefox-cache.d.ts +23 -0
- package/dist/src/core/firefox-cache.js +134 -0
- package/dist/src/core/firefox-download.d.ts +21 -0
- package/dist/src/core/firefox-download.js +129 -0
- package/dist/src/core/firefox-extract.d.ts +21 -0
- package/dist/src/core/firefox-extract.js +53 -0
- package/dist/src/core/firefox.d.ts +34 -0
- package/dist/src/core/firefox.js +78 -0
- package/dist/src/core/furnace-apply-helpers.d.ts +21 -0
- package/dist/src/core/furnace-apply-helpers.js +244 -0
- package/dist/src/core/furnace-apply.d.ts +16 -0
- package/dist/src/core/furnace-apply.js +147 -0
- package/dist/src/core/furnace-config.d.ts +94 -0
- package/dist/src/core/furnace-config.js +372 -0
- package/dist/src/core/furnace-constants.d.ts +4 -0
- package/dist/src/core/furnace-constants.js +6 -0
- package/dist/src/core/furnace-registration-ast.d.ts +24 -0
- package/dist/src/core/furnace-registration-ast.js +218 -0
- package/dist/src/core/furnace-registration-remove.d.ts +14 -0
- package/dist/src/core/furnace-registration-remove.js +89 -0
- package/dist/src/core/furnace-registration-validate.d.ts +20 -0
- package/dist/src/core/furnace-registration-validate.js +40 -0
- package/dist/src/core/furnace-registration.d.ts +29 -0
- package/dist/src/core/furnace-registration.js +96 -0
- package/dist/src/core/furnace-rollback.d.ts +20 -0
- package/dist/src/core/furnace-rollback.js +66 -0
- package/dist/src/core/furnace-scanner.d.ts +40 -0
- package/dist/src/core/furnace-scanner.js +143 -0
- package/dist/src/core/furnace-stories.d.ts +37 -0
- package/dist/src/core/furnace-stories.js +185 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-accessibility.js +32 -0
- package/dist/src/core/furnace-validate-checks.d.ts +4 -0
- package/dist/src/core/furnace-validate-checks.js +7 -0
- package/dist/src/core/furnace-validate-compatibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-compatibility.js +57 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +28 -0
- package/dist/src/core/furnace-validate-helpers.js +129 -0
- package/dist/src/core/furnace-validate-registration.d.ts +37 -0
- package/dist/src/core/furnace-validate-registration.js +220 -0
- package/dist/src/core/furnace-validate-structure.d.ts +6 -0
- package/dist/src/core/furnace-validate-structure.js +66 -0
- package/dist/src/core/furnace-validate.d.ts +16 -0
- package/dist/src/core/furnace-validate.js +103 -0
- package/dist/src/core/git-base.d.ts +47 -0
- package/dist/src/core/git-base.js +50 -0
- package/dist/src/core/git-diff.d.ts +63 -0
- package/dist/src/core/git-diff.js +246 -0
- package/dist/src/core/git-file-ops.d.ts +65 -0
- package/dist/src/core/git-file-ops.js +141 -0
- package/dist/src/core/git-status.d.ts +65 -0
- package/dist/src/core/git-status.js +163 -0
- package/dist/src/core/git.d.ts +113 -0
- package/dist/src/core/git.js +363 -0
- package/dist/src/core/license-headers.d.ts +36 -0
- package/dist/src/core/license-headers.js +83 -0
- package/dist/src/core/mach-build-artifacts.d.ts +29 -0
- package/dist/src/core/mach-build-artifacts.js +117 -0
- package/dist/src/core/mach-mozconfig.d.ts +17 -0
- package/dist/src/core/mach-mozconfig.js +50 -0
- package/dist/src/core/mach-python.d.ts +16 -0
- package/dist/src/core/mach-python.js +126 -0
- package/dist/src/core/mach.d.ts +106 -0
- package/dist/src/core/mach.js +166 -0
- package/dist/src/core/manifest-helpers.d.ts +25 -0
- package/dist/src/core/manifest-helpers.js +96 -0
- package/dist/src/core/manifest-register.d.ts +30 -0
- package/dist/src/core/manifest-register.js +65 -0
- package/dist/src/core/manifest-rules.d.ts +39 -0
- package/dist/src/core/manifest-rules.js +151 -0
- package/dist/src/core/manifest-tokenizers.d.ts +34 -0
- package/dist/src/core/manifest-tokenizers.js +84 -0
- package/dist/src/core/parser-fallback.d.ts +36 -0
- package/dist/src/core/parser-fallback.js +43 -0
- package/dist/src/core/patch-apply-fuzz.d.ts +29 -0
- package/dist/src/core/patch-apply-fuzz.js +70 -0
- package/dist/src/core/patch-apply.d.ts +46 -0
- package/dist/src/core/patch-apply.js +235 -0
- package/dist/src/core/patch-export.d.ts +99 -0
- package/dist/src/core/patch-export.js +314 -0
- package/dist/src/core/patch-files.d.ts +11 -0
- package/dist/src/core/patch-files.js +51 -0
- package/dist/src/core/patch-lint.d.ts +72 -0
- package/dist/src/core/patch-lint.js +403 -0
- package/dist/src/core/patch-lock.d.ts +8 -0
- package/dist/src/core/patch-lock.js +29 -0
- package/dist/src/core/patch-manifest-consistency.d.ts +24 -0
- package/dist/src/core/patch-manifest-consistency.js +135 -0
- package/dist/src/core/patch-manifest-io.d.ts +36 -0
- package/dist/src/core/patch-manifest-io.js +77 -0
- package/dist/src/core/patch-manifest-query.d.ts +48 -0
- package/dist/src/core/patch-manifest-query.js +124 -0
- package/dist/src/core/patch-manifest-validate.d.ts +22 -0
- package/dist/src/core/patch-manifest-validate.js +72 -0
- package/dist/src/core/patch-manifest.d.ts +11 -0
- package/dist/src/core/patch-manifest.js +12 -0
- package/dist/src/core/patch-parse.d.ts +43 -0
- package/dist/src/core/patch-parse.js +143 -0
- package/dist/src/core/patch-transform.d.ts +21 -0
- package/dist/src/core/patch-transform.js +138 -0
- package/dist/src/core/rebase-session.d.ts +47 -0
- package/dist/src/core/rebase-session.js +65 -0
- package/dist/src/core/register-browser-content.d.ts +11 -0
- package/dist/src/core/register-browser-content.js +116 -0
- package/dist/src/core/register-module.d.ts +11 -0
- package/dist/src/core/register-module.js +76 -0
- package/dist/src/core/register-shared-css.d.ts +11 -0
- package/dist/src/core/register-shared-css.js +117 -0
- package/dist/src/core/register-test-manifest.d.ts +18 -0
- package/dist/src/core/register-test-manifest.js +99 -0
- package/dist/src/core/state-file.d.ts +4 -0
- package/dist/src/core/state-file.js +25 -0
- package/dist/src/core/token-coverage.d.ts +12 -0
- package/dist/src/core/token-coverage.js +74 -0
- package/dist/src/core/token-manager.d.ts +55 -0
- package/dist/src/core/token-manager.js +387 -0
- package/dist/src/core/wire-destroy.d.ts +21 -0
- package/dist/src/core/wire-destroy.js +103 -0
- package/dist/src/core/wire-dom-fragment.d.ts +23 -0
- package/dist/src/core/wire-dom-fragment.js +129 -0
- package/dist/src/core/wire-init.d.ts +23 -0
- package/dist/src/core/wire-init.js +201 -0
- package/dist/src/core/wire-subscript.d.ts +20 -0
- package/dist/src/core/wire-subscript.js +134 -0
- package/dist/src/core/wire-targets.d.ts +7 -0
- package/dist/src/core/wire-targets.js +9 -0
- package/dist/src/core/wire-utils.d.ts +88 -0
- package/dist/src/core/wire-utils.js +279 -0
- package/dist/src/errors/base.d.ts +60 -0
- package/dist/src/errors/base.js +87 -0
- package/dist/src/errors/build.d.ts +52 -0
- package/dist/src/errors/build.js +114 -0
- package/dist/src/errors/codes.d.ts +29 -0
- package/dist/src/errors/codes.js +30 -0
- package/dist/src/errors/config.d.ts +31 -0
- package/dist/src/errors/config.js +61 -0
- package/dist/src/errors/download.d.ts +42 -0
- package/dist/src/errors/download.js +95 -0
- package/dist/src/errors/furnace.d.ts +10 -0
- package/dist/src/errors/furnace.js +22 -0
- package/dist/src/errors/git.d.ts +41 -0
- package/dist/src/errors/git.js +99 -0
- package/dist/src/errors/patch.d.ts +10 -0
- package/dist/src/errors/patch.js +26 -0
- package/dist/src/errors/rebase.d.ts +20 -0
- package/dist/src/errors/rebase.js +30 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +21 -0
- package/dist/src/types/cli.d.ts +14 -0
- package/dist/src/types/cli.js +2 -0
- package/dist/src/types/commands/index.d.ts +6 -0
- package/dist/src/types/commands/index.js +6 -0
- package/dist/src/types/commands/options.d.ts +239 -0
- package/dist/src/types/commands/options.js +6 -0
- package/dist/src/types/commands/patches.d.ts +89 -0
- package/dist/src/types/commands/patches.js +6 -0
- package/dist/src/types/commands/project.d.ts +71 -0
- package/dist/src/types/commands/project.js +6 -0
- package/dist/src/types/config.d.ts +101 -0
- package/dist/src/types/config.js +2 -0
- package/dist/src/types/furnace.d.ts +158 -0
- package/dist/src/types/furnace.js +2 -0
- package/dist/src/types/index.d.ts +6 -0
- package/dist/src/types/index.js +6 -0
- package/dist/src/utils/errors.d.ts +2 -0
- package/dist/src/utils/errors.js +15 -0
- package/dist/src/utils/fs.d.ts +72 -0
- package/dist/src/utils/fs.js +179 -0
- package/dist/src/utils/logger.d.ts +58 -0
- package/dist/src/utils/logger.js +120 -0
- package/dist/src/utils/options.d.ts +8 -0
- package/dist/src/utils/options.js +16 -0
- package/dist/src/utils/package-root.d.ts +10 -0
- package/dist/src/utils/package-root.js +53 -0
- package/dist/src/utils/parse.d.ts +110 -0
- package/dist/src/utils/parse.js +200 -0
- package/dist/src/utils/paths.d.ts +10 -0
- package/dist/src/utils/paths.js +43 -0
- package/dist/src/utils/platform.d.ts +38 -0
- package/dist/src/utils/platform.js +56 -0
- package/dist/src/utils/process.d.ts +80 -0
- package/dist/src/utils/process.js +188 -0
- package/dist/src/utils/regex.d.ts +24 -0
- package/dist/src/utils/regex.js +40 -0
- package/dist/src/utils/validation.d.ts +133 -0
- package/dist/src/utils/validation.js +250 -0
- package/package.json +106 -0
- package/templates/configs/common.mozconfig +24 -0
- package/templates/configs/darwin.mozconfig +10 -0
- package/templates/configs/linux.mozconfig +12 -0
- package/templates/configs/win32.mozconfig +14 -0
- package/templates/licenses/0BSD.md +14 -0
- package/templates/licenses/EUPL-1.2.md +294 -0
- package/templates/licenses/GPL-2.0-or-later.md +339 -0
- package/templates/licenses/MPL-2.0.md +383 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* browser-init.js — destroy expression in onUnload()/uninit().
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import MagicString from 'magic-string';
|
|
7
|
+
import { GeneralError } from '../errors/base.js';
|
|
8
|
+
import { BuildError } from '../errors/build.js';
|
|
9
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
10
|
+
import { escapeRegex } from '../utils/regex.js';
|
|
11
|
+
import { detectIndent, parseScript } from './ast-utils.js';
|
|
12
|
+
import { withParserFallback } from './parser-fallback.js';
|
|
13
|
+
import { extractNameFromExpression, findMethodBody, findMethodBraceIndex, validateWireName, } from './wire-utils.js';
|
|
14
|
+
const BROWSER_INIT_JS = 'browser/base/content/browser-init.js';
|
|
15
|
+
/**
|
|
16
|
+
* AST-based implementation: finds onUnload()/uninit() method body and
|
|
17
|
+
* inserts the destroy block at the top (LIFO ordering).
|
|
18
|
+
*/
|
|
19
|
+
export function addDestroyAST(content, expression) {
|
|
20
|
+
const name = extractNameFromExpression(expression);
|
|
21
|
+
const ast = parseScript(content);
|
|
22
|
+
const ms = new MagicString(content);
|
|
23
|
+
const body = findMethodBody(ast, ['onUnload', 'uninit']);
|
|
24
|
+
if (!body) {
|
|
25
|
+
throw new BuildError('Could not find onUnload/uninit method body via AST');
|
|
26
|
+
}
|
|
27
|
+
// Insert at top of method body (LIFO ordering)
|
|
28
|
+
const firstStmt = body.body[0];
|
|
29
|
+
let insertPos;
|
|
30
|
+
let indent;
|
|
31
|
+
if (firstStmt) {
|
|
32
|
+
insertPos = firstStmt.start;
|
|
33
|
+
indent = detectIndent(content, firstStmt.start);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Empty method body — insert after opening {
|
|
37
|
+
insertPos = body.start + 1;
|
|
38
|
+
indent = ' ';
|
|
39
|
+
}
|
|
40
|
+
const block = [
|
|
41
|
+
`${indent}// ${name} destroy`,
|
|
42
|
+
`${indent}try {`,
|
|
43
|
+
`${indent} if (typeof ${name} !== "undefined") {`,
|
|
44
|
+
`${indent} ${expression};`,
|
|
45
|
+
`${indent} }`,
|
|
46
|
+
`${indent}} catch (e) {`,
|
|
47
|
+
`${indent} console.error("${name} destroy failed:", e);`,
|
|
48
|
+
`${indent}}`,
|
|
49
|
+
].join('\n');
|
|
50
|
+
ms.appendRight(insertPos, block + '\n');
|
|
51
|
+
return ms.toString();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Legacy regex/line-based implementation preserved as fallback.
|
|
55
|
+
*/
|
|
56
|
+
export function legacyAddDestroy(content, expression) {
|
|
57
|
+
const name = extractNameFromExpression(expression);
|
|
58
|
+
const lines = content.split('\n');
|
|
59
|
+
const destroyRegex = /\b(?:async\s+)?(onUnload|uninit)\s*[(:]/;
|
|
60
|
+
const found = findMethodBraceIndex(lines, destroyRegex);
|
|
61
|
+
if (!found) {
|
|
62
|
+
throw new GeneralError('Could not find "onUnload" or "uninit" method in browser-init.js.\n' +
|
|
63
|
+
'FireForge was looking for a signature matching: \\b(?:async\\s+)?(onUnload|uninit)\\s*[(:]');
|
|
64
|
+
}
|
|
65
|
+
const insertIndex = found.braceIndex + 1;
|
|
66
|
+
const block = [
|
|
67
|
+
` // ${name} destroy`,
|
|
68
|
+
` try {`,
|
|
69
|
+
` if (typeof ${name} !== "undefined") {`,
|
|
70
|
+
` ${expression};`,
|
|
71
|
+
` }`,
|
|
72
|
+
` } catch (e) {`,
|
|
73
|
+
` console.error("${name} destroy failed:", e);`,
|
|
74
|
+
` }`,
|
|
75
|
+
];
|
|
76
|
+
lines.splice(insertIndex, 0, ...block);
|
|
77
|
+
return lines.join('\n');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Adds a destroy expression to the top of onUnload() or uninit() in
|
|
81
|
+
* browser-init.js (LIFO ordering — newest first).
|
|
82
|
+
*
|
|
83
|
+
* @param engineDir - Engine source root
|
|
84
|
+
* @param expression - The destroy expression (e.g., "MyComponent.destroy()")
|
|
85
|
+
* @returns true if added, false if already present
|
|
86
|
+
*/
|
|
87
|
+
export async function addDestroyToBrowserInit(engineDir, expression) {
|
|
88
|
+
validateWireName(expression, 'destroy expression');
|
|
89
|
+
const filePath = join(engineDir, BROWSER_INIT_JS);
|
|
90
|
+
if (!(await pathExists(filePath))) {
|
|
91
|
+
throw new GeneralError(`${BROWSER_INIT_JS} not found in engine`);
|
|
92
|
+
}
|
|
93
|
+
const content = await readText(filePath);
|
|
94
|
+
// Idempotency check — use word-boundary regex to avoid substring false positives
|
|
95
|
+
const destroyPattern = new RegExp(`(?:^|\\W)${escapeRegex(expression)}\\s*;?\\s*$`, 'm');
|
|
96
|
+
if (destroyPattern.test(content)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const { value } = withParserFallback(() => addDestroyAST(content, expression), () => legacyAddDestroy(content, expression), BROWSER_INIT_JS);
|
|
100
|
+
await writeText(filePath, value);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=wire-destroy.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser.xhtml — DOM fragment insertion.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Tokenizer-based implementation for DOM fragment insertion.
|
|
6
|
+
*/
|
|
7
|
+
export declare function addDomFragmentTokenized(content: string, includeDirective: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Legacy line-based implementation preserved as fallback.
|
|
10
|
+
*/
|
|
11
|
+
export declare function legacyAddDomFragment(content: string, includeDirective: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Inserts a `#include` directive for an `.inc.xhtml` file into browser.xhtml,
|
|
14
|
+
* before `#include browser-sets.inc`.
|
|
15
|
+
*
|
|
16
|
+
* If the file's content was previously inlined (detected by root element id=),
|
|
17
|
+
* the inlined block is automatically replaced with the `#include` directive.
|
|
18
|
+
*
|
|
19
|
+
* @param engineDir - Engine source root
|
|
20
|
+
* @param domFilePath - Path to the `.inc.xhtml` file relative to engine root
|
|
21
|
+
* @returns true if inserted, false if already present
|
|
22
|
+
*/
|
|
23
|
+
export declare function addDomFragment(engineDir: string, domFilePath: string): Promise<boolean>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* browser.xhtml — DOM fragment insertion.
|
|
4
|
+
*/
|
|
5
|
+
import { join, relative } from 'node:path';
|
|
6
|
+
import { GeneralError } from '../errors/base.js';
|
|
7
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
8
|
+
import { toRootRelativePath } from '../utils/paths.js';
|
|
9
|
+
import { escapeRegex } from '../utils/regex.js';
|
|
10
|
+
import { withParserFallback } from './parser-fallback.js';
|
|
11
|
+
import { tokenizeXhtml } from './wire-utils.js';
|
|
12
|
+
const BROWSER_XHTML = 'browser/base/content/browser.xhtml';
|
|
13
|
+
/**
|
|
14
|
+
* Tokenizer-based implementation for DOM fragment insertion.
|
|
15
|
+
*/
|
|
16
|
+
export function addDomFragmentTokenized(content, includeDirective) {
|
|
17
|
+
const lines = content.split('\n');
|
|
18
|
+
const tokens = tokenizeXhtml(lines);
|
|
19
|
+
// Find the #include browser-sets.inc token
|
|
20
|
+
let insertIndex = -1;
|
|
21
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
22
|
+
const token = tokens[i];
|
|
23
|
+
if (token && token.type === 'macro' && token.raw.includes('browser-sets.inc')) {
|
|
24
|
+
insertIndex = i;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (insertIndex === -1) {
|
|
29
|
+
// Fallback: after <html:body>
|
|
30
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
31
|
+
const token = tokens[i];
|
|
32
|
+
if (token && token.type === 'xml' && /<html:body/.test(token.raw)) {
|
|
33
|
+
insertIndex = i + 1;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (insertIndex === -1) {
|
|
39
|
+
throw new GeneralError('Could not find insertion point in browser.xhtml');
|
|
40
|
+
}
|
|
41
|
+
lines.splice(insertIndex, 0, includeDirective);
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Legacy line-based implementation preserved as fallback.
|
|
46
|
+
*/
|
|
47
|
+
export function legacyAddDomFragment(content, includeDirective) {
|
|
48
|
+
const lines = content.split('\n');
|
|
49
|
+
let insertIndex = -1;
|
|
50
|
+
for (let i = 0; i < lines.length; i++) {
|
|
51
|
+
const line = lines[i] ?? '';
|
|
52
|
+
if (/browser-sets\.inc/.test(line)) {
|
|
53
|
+
insertIndex = i;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (insertIndex === -1) {
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
const line = lines[i] ?? '';
|
|
60
|
+
if (/<html:body/.test(line)) {
|
|
61
|
+
insertIndex = i + 1;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (insertIndex === -1) {
|
|
67
|
+
throw new GeneralError('Could not find insertion point in browser.xhtml');
|
|
68
|
+
}
|
|
69
|
+
lines.splice(insertIndex, 0, includeDirective);
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Inserts a `#include` directive for an `.inc.xhtml` file into browser.xhtml,
|
|
74
|
+
* before `#include browser-sets.inc`.
|
|
75
|
+
*
|
|
76
|
+
* If the file's content was previously inlined (detected by root element id=),
|
|
77
|
+
* the inlined block is automatically replaced with the `#include` directive.
|
|
78
|
+
*
|
|
79
|
+
* @param engineDir - Engine source root
|
|
80
|
+
* @param domFilePath - Path to the `.inc.xhtml` file relative to engine root
|
|
81
|
+
* @returns true if inserted, false if already present
|
|
82
|
+
*/
|
|
83
|
+
export async function addDomFragment(engineDir, domFilePath) {
|
|
84
|
+
const browserXhtmlPath = join(engineDir, BROWSER_XHTML);
|
|
85
|
+
const safeDomFilePath = toRootRelativePath(engineDir, domFilePath);
|
|
86
|
+
if (!(await pathExists(browserXhtmlPath))) {
|
|
87
|
+
throw new GeneralError(`${BROWSER_XHTML} not found in engine`);
|
|
88
|
+
}
|
|
89
|
+
// Compute include path relative to browser/base/content/ (where browser.xhtml lives)
|
|
90
|
+
const includePath = relative('browser/base/content', safeDomFilePath).replace(/\\/g, '/');
|
|
91
|
+
const includeDirective = `#include ${includePath}`;
|
|
92
|
+
let content = await readText(browserXhtmlPath);
|
|
93
|
+
// Idempotency: check if the #include directive already exists (line-anchored to avoid substring matches)
|
|
94
|
+
if (new RegExp(`^${escapeRegex(includeDirective)}$`, 'm').test(content)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
// Migration: check if inlined content from this file exists (by id= match)
|
|
98
|
+
// and replace it with the #include directive
|
|
99
|
+
const domFileFullPath = join(engineDir, safeDomFilePath);
|
|
100
|
+
if (await pathExists(domFileFullPath)) {
|
|
101
|
+
const domContent = await readText(domFileFullPath);
|
|
102
|
+
const idMatch = /id\s*=\s*["']([^"']+)["']/.exec(domContent);
|
|
103
|
+
if (idMatch && content.includes(`id="${idMatch[1]}"`)) {
|
|
104
|
+
const lines = content.split('\n');
|
|
105
|
+
const rootId = idMatch[1];
|
|
106
|
+
const startIdx = lines.findIndex((l) => l.includes(`id="${rootId}"`));
|
|
107
|
+
if (startIdx !== -1) {
|
|
108
|
+
let endIdx = startIdx;
|
|
109
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
110
|
+
const line = lines[i] ?? '';
|
|
111
|
+
if (i > startIdx && (/^#include\s/.test(line.trim()) || line.trim() === '')) {
|
|
112
|
+
endIdx = i;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
endIdx = i + 1;
|
|
116
|
+
}
|
|
117
|
+
lines.splice(startIdx, endIdx - startIdx, includeDirective);
|
|
118
|
+
content = lines.join('\n');
|
|
119
|
+
await writeText(browserXhtmlPath, content);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Normal insertion
|
|
125
|
+
const { value } = withParserFallback(() => addDomFragmentTokenized(content, includeDirective), () => legacyAddDomFragment(content, includeDirective), BROWSER_XHTML);
|
|
126
|
+
await writeText(browserXhtmlPath, value);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=wire-dom-fragment.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-init.js — init expression in onLoad().
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* AST-based implementation: finds onLoad() method body, locates existing
|
|
6
|
+
* fireforge init blocks (TryStatements containing typeof guards), and inserts
|
|
7
|
+
* after the correct position.
|
|
8
|
+
*/
|
|
9
|
+
export declare function addInitAST(content: string, expression: string, after?: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Legacy regex/line-based implementation preserved as fallback.
|
|
12
|
+
*/
|
|
13
|
+
export declare function legacyAddInit(content: string, expression: string, after?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Adds an init expression as the first statement(s) in gBrowserInit.onLoad()
|
|
16
|
+
* in browser-init.js, after any previously-wired fireforge init blocks.
|
|
17
|
+
*
|
|
18
|
+
* @param engineDir - Engine source root
|
|
19
|
+
* @param expression - The init expression (e.g., "MyComponent.init()")
|
|
20
|
+
* @param after - Optional name to insert after (e.g., "MyComponent" to insert after its block)
|
|
21
|
+
* @returns true if added, false if already present
|
|
22
|
+
*/
|
|
23
|
+
export declare function addInitToBrowserInit(engineDir: string, expression: string, after?: string): Promise<boolean>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* browser-init.js — init expression in onLoad().
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import MagicString from 'magic-string';
|
|
7
|
+
import { GeneralError } from '../errors/base.js';
|
|
8
|
+
import { BuildError } from '../errors/build.js';
|
|
9
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
10
|
+
import { escapeRegex } from '../utils/regex.js';
|
|
11
|
+
import { detectIndent, getNodeSource, parseScript } from './ast-utils.js';
|
|
12
|
+
import { withParserFallback } from './parser-fallback.js';
|
|
13
|
+
import { extractNameFromExpression, findInsertionAfterFireforgeBlocks, findMethodBody, findMethodBraceIndex, validateWireName, walkToTryBlockEnd, } from './wire-utils.js';
|
|
14
|
+
const BROWSER_INIT_JS = 'browser/base/content/browser-init.js';
|
|
15
|
+
/**
|
|
16
|
+
* AST-based implementation: finds onLoad() method body, locates existing
|
|
17
|
+
* fireforge init blocks (TryStatements containing typeof guards), and inserts
|
|
18
|
+
* after the correct position.
|
|
19
|
+
*/
|
|
20
|
+
export function addInitAST(content, expression, after) {
|
|
21
|
+
const name = extractNameFromExpression(expression);
|
|
22
|
+
const ast = parseScript(content);
|
|
23
|
+
const ms = new MagicString(content);
|
|
24
|
+
const body = findMethodBody(ast, 'onLoad');
|
|
25
|
+
if (!body) {
|
|
26
|
+
throw new BuildError('Could not find onLoad method body via AST');
|
|
27
|
+
}
|
|
28
|
+
// Collect fireforge try-catch blocks (those containing typeof guards)
|
|
29
|
+
const fireforgeBlocks = [];
|
|
30
|
+
for (const stmt of body.body) {
|
|
31
|
+
if (stmt.type === 'TryStatement') {
|
|
32
|
+
const tryNode = stmt;
|
|
33
|
+
const src = getNodeSource(content, tryNode);
|
|
34
|
+
if (/typeof\s+\w+\s*!==\s*"undefined"/.test(src)) {
|
|
35
|
+
fireforgeBlocks.push(tryNode);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
let insertPos;
|
|
40
|
+
let indent;
|
|
41
|
+
if (after) {
|
|
42
|
+
// Find the specific fireforge block containing the --after target
|
|
43
|
+
const targetBlock = fireforgeBlocks.find((block) => {
|
|
44
|
+
const src = getNodeSource(content, block);
|
|
45
|
+
return src.includes(`typeof ${after}`) || src.includes(`${after}.init(`);
|
|
46
|
+
});
|
|
47
|
+
if (targetBlock) {
|
|
48
|
+
insertPos = targetBlock.end;
|
|
49
|
+
indent = detectIndent(content, targetBlock.start);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// --after target not found: fall through to default (after last fireforge block)
|
|
53
|
+
if (fireforgeBlocks.length > 0) {
|
|
54
|
+
const lastBlock = fireforgeBlocks[fireforgeBlocks.length - 1];
|
|
55
|
+
if (!lastBlock)
|
|
56
|
+
throw new GeneralError('Unexpected empty fireforgeBlocks array');
|
|
57
|
+
insertPos = lastBlock.end;
|
|
58
|
+
indent = detectIndent(content, lastBlock.start);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// No fireforge blocks, insert at top of method body
|
|
62
|
+
const firstStmt = body.body[0];
|
|
63
|
+
if (firstStmt) {
|
|
64
|
+
insertPos = firstStmt.start;
|
|
65
|
+
indent = detectIndent(content, insertPos);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
insertPos = body.start + 1;
|
|
69
|
+
indent = ' ';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Default: insert after the last consecutive fireforge block at the start
|
|
76
|
+
if (fireforgeBlocks.length > 0) {
|
|
77
|
+
const lastBlock = fireforgeBlocks[fireforgeBlocks.length - 1];
|
|
78
|
+
if (!lastBlock)
|
|
79
|
+
throw new GeneralError('Unexpected empty fireforgeBlocks array');
|
|
80
|
+
insertPos = lastBlock.end;
|
|
81
|
+
indent = detectIndent(content, lastBlock.start);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const firstStmt = body.body[0];
|
|
85
|
+
if (firstStmt) {
|
|
86
|
+
insertPos = firstStmt.start;
|
|
87
|
+
indent = detectIndent(content, insertPos);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
insertPos = body.start + 1;
|
|
91
|
+
indent = ' ';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const block = [
|
|
96
|
+
`${indent}// ${name} init — must be first, before Firefox subsystem`,
|
|
97
|
+
`${indent}// inits that reference native UI elements we hide.`,
|
|
98
|
+
`${indent}try {`,
|
|
99
|
+
`${indent} if (typeof ${name} !== "undefined") {`,
|
|
100
|
+
`${indent} ${expression};`,
|
|
101
|
+
`${indent} }`,
|
|
102
|
+
`${indent}} catch (e) {`,
|
|
103
|
+
`${indent} console.error("${name} init failed:", e);`,
|
|
104
|
+
`${indent}}`,
|
|
105
|
+
].join('\n');
|
|
106
|
+
ms.appendRight(insertPos, '\n' + block + '\n');
|
|
107
|
+
return ms.toString();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Legacy regex/line-based implementation preserved as fallback.
|
|
111
|
+
*/
|
|
112
|
+
export function legacyAddInit(content, expression, after) {
|
|
113
|
+
const name = extractNameFromExpression(expression);
|
|
114
|
+
const lines = content.split('\n');
|
|
115
|
+
const onLoadRegex = /\b(?:async\s+)?onLoad\s*[(:]/;
|
|
116
|
+
const found = findMethodBraceIndex(lines, onLoadRegex);
|
|
117
|
+
if (!found) {
|
|
118
|
+
throw new GeneralError('Could not find "onLoad" method in browser-init.js.\n' +
|
|
119
|
+
'FireForge was looking for a signature matching: \\b(?:async\\s+)?onLoad\\s*[(:]');
|
|
120
|
+
}
|
|
121
|
+
const { braceIndex } = found;
|
|
122
|
+
let insertIndex = braceIndex + 1;
|
|
123
|
+
if (after) {
|
|
124
|
+
// Try to find the specific --after target block
|
|
125
|
+
let located = false;
|
|
126
|
+
for (let i = braceIndex + 1; i < lines.length; i++) {
|
|
127
|
+
const line = lines[i] ?? '';
|
|
128
|
+
if (line.includes(`typeof ${after}`) || line.includes(`${after}.init(`)) {
|
|
129
|
+
// Walk backward to find the enclosing try, including a preceding comment
|
|
130
|
+
let tryStart = i;
|
|
131
|
+
for (let k = i - 1; k > braceIndex; k--) {
|
|
132
|
+
if (/\btry\s*\{/.test(lines[k] ?? '')) {
|
|
133
|
+
tryStart = k;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (/\/\//.test(lines[k] ?? '') && /\btry\s*\{/.test(lines[k + 1] ?? '')) {
|
|
137
|
+
tryStart = k;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
insertIndex = walkToTryBlockEnd(lines, tryStart);
|
|
142
|
+
located = true;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// If --after target not found, fall through to default fireforge block scan
|
|
147
|
+
if (!located) {
|
|
148
|
+
insertIndex = findInsertionAfterFireforgeBlocks(lines, braceIndex + 1, braceIndex);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
insertIndex = findInsertionAfterFireforgeBlocks(lines, braceIndex + 1, braceIndex);
|
|
153
|
+
}
|
|
154
|
+
// Detect indent from surrounding code instead of hardcoding
|
|
155
|
+
const refLine = lines
|
|
156
|
+
.slice(0, insertIndex)
|
|
157
|
+
.reverse()
|
|
158
|
+
.find((l) => l.trim());
|
|
159
|
+
const baseIndent = refLine?.match(/^(\s*)/)?.[1] ?? ' ';
|
|
160
|
+
const inner = baseIndent + ' ';
|
|
161
|
+
const inner2 = inner + ' ';
|
|
162
|
+
const block = [
|
|
163
|
+
`${baseIndent}// ${name} init — must be first, before Firefox subsystem`,
|
|
164
|
+
`${baseIndent}// inits that reference native UI elements we hide.`,
|
|
165
|
+
`${baseIndent}try {`,
|
|
166
|
+
`${inner}if (typeof ${name} !== "undefined") {`,
|
|
167
|
+
`${inner2}${expression};`,
|
|
168
|
+
`${inner}}`,
|
|
169
|
+
`${baseIndent}} catch (e) {`,
|
|
170
|
+
`${inner}console.error("${name} init failed:", e);`,
|
|
171
|
+
`${baseIndent}}`,
|
|
172
|
+
];
|
|
173
|
+
lines.splice(insertIndex, 0, ...block);
|
|
174
|
+
return lines.join('\n');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Adds an init expression as the first statement(s) in gBrowserInit.onLoad()
|
|
178
|
+
* in browser-init.js, after any previously-wired fireforge init blocks.
|
|
179
|
+
*
|
|
180
|
+
* @param engineDir - Engine source root
|
|
181
|
+
* @param expression - The init expression (e.g., "MyComponent.init()")
|
|
182
|
+
* @param after - Optional name to insert after (e.g., "MyComponent" to insert after its block)
|
|
183
|
+
* @returns true if added, false if already present
|
|
184
|
+
*/
|
|
185
|
+
export async function addInitToBrowserInit(engineDir, expression, after) {
|
|
186
|
+
validateWireName(expression, 'init expression');
|
|
187
|
+
const filePath = join(engineDir, BROWSER_INIT_JS);
|
|
188
|
+
if (!(await pathExists(filePath))) {
|
|
189
|
+
throw new GeneralError(`${BROWSER_INIT_JS} not found in engine`);
|
|
190
|
+
}
|
|
191
|
+
const content = await readText(filePath);
|
|
192
|
+
// Idempotency check — use word-boundary regex to avoid substring false positives
|
|
193
|
+
const initPattern = new RegExp(`(?:^|\\W)${escapeRegex(expression)}\\s*;?\\s*$`, 'm');
|
|
194
|
+
if (initPattern.test(content)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
const { value } = withParserFallback(() => addInitAST(content, expression, after), () => legacyAddInit(content, expression, after), BROWSER_INIT_JS);
|
|
198
|
+
await writeText(filePath, value);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=wire-init.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-main.js — loadSubScript registration.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* AST-based implementation: finds the last try/catch containing
|
|
6
|
+
* `loadSubScript` and inserts a new try/catch block after it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function addSubscriptAST(content: string, name: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Legacy regex/line-based implementation preserved as fallback.
|
|
11
|
+
*/
|
|
12
|
+
export declare function legacyAddSubscript(content: string, name: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Adds a loadSubScript entry to browser-main.js with try/catch error handling.
|
|
15
|
+
*
|
|
16
|
+
* @param engineDir - Engine source root
|
|
17
|
+
* @param name - Subscript name (without .js extension)
|
|
18
|
+
* @returns true if added, false if already present
|
|
19
|
+
*/
|
|
20
|
+
export declare function addSubscriptToBrowserMain(engineDir: string, name: string): Promise<boolean>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* browser-main.js — loadSubScript registration.
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import MagicString from 'magic-string';
|
|
7
|
+
import { GeneralError } from '../errors/base.js';
|
|
8
|
+
import { BuildError } from '../errors/build.js';
|
|
9
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
10
|
+
import { detectIndent, getNodeSource, parseScript, walkAST, } from './ast-utils.js';
|
|
11
|
+
import { withParserFallback } from './parser-fallback.js';
|
|
12
|
+
import { findNearestTryLine, validateWireName, walkToTryBlockEnd } from './wire-utils.js';
|
|
13
|
+
const BROWSER_MAIN_JS = 'browser/base/content/browser-main.js';
|
|
14
|
+
/**
|
|
15
|
+
* AST-based implementation: finds the last try/catch containing
|
|
16
|
+
* `loadSubScript` and inserts a new try/catch block after it.
|
|
17
|
+
*/
|
|
18
|
+
export function addSubscriptAST(content, name) {
|
|
19
|
+
const ast = parseScript(content);
|
|
20
|
+
const ms = new MagicString(content);
|
|
21
|
+
// Collect all TryStatements containing loadSubScript
|
|
22
|
+
const tryNodes = [];
|
|
23
|
+
walkAST(ast, {
|
|
24
|
+
enter(node) {
|
|
25
|
+
if (node.type === 'TryStatement') {
|
|
26
|
+
const n = node;
|
|
27
|
+
const src = getNodeSource(content, n);
|
|
28
|
+
if (src.includes('loadSubScript')) {
|
|
29
|
+
tryNodes.push(n);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
let insertPos;
|
|
35
|
+
let indent;
|
|
36
|
+
if (tryNodes.length > 0) {
|
|
37
|
+
const lastTry = tryNodes[tryNodes.length - 1];
|
|
38
|
+
if (!lastTry)
|
|
39
|
+
throw new GeneralError('Unexpected empty tryNodes array');
|
|
40
|
+
insertPos = lastTry.end;
|
|
41
|
+
indent = detectIndent(content, lastTry.start);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// No existing loadSubScript — insert before last standalone closing brace
|
|
45
|
+
// Use line-based search to avoid matching braces inside strings/comments
|
|
46
|
+
const allLines = content.split('\n');
|
|
47
|
+
let lastBrace = -1;
|
|
48
|
+
for (let i = allLines.length - 1; i >= 0; i--) {
|
|
49
|
+
if (allLines[i]?.trim() === '}') {
|
|
50
|
+
lastBrace = allLines.slice(0, i).join('\n').length + 1;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (lastBrace === -1) {
|
|
55
|
+
throw new BuildError('Could not find closing brace in browser-main.js');
|
|
56
|
+
}
|
|
57
|
+
insertPos = lastBrace;
|
|
58
|
+
indent = detectIndent(content, lastBrace);
|
|
59
|
+
}
|
|
60
|
+
const block = [
|
|
61
|
+
`${indent}try {`,
|
|
62
|
+
`${indent} Services.scriptloader.loadSubScript("chrome://browser/content/${name}.js", this);`,
|
|
63
|
+
`${indent}} catch (e) {`,
|
|
64
|
+
`${indent} console.error("Failed to load ${name}.js:", e);`,
|
|
65
|
+
`${indent}}`,
|
|
66
|
+
].join('\n');
|
|
67
|
+
ms.appendRight(insertPos, '\n' + block + '\n');
|
|
68
|
+
return ms.toString();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Legacy regex/line-based implementation preserved as fallback.
|
|
72
|
+
*/
|
|
73
|
+
export function legacyAddSubscript(content, name) {
|
|
74
|
+
const lines = content.split('\n');
|
|
75
|
+
let lastSubScriptLine = -1;
|
|
76
|
+
for (let i = 0; i < lines.length; i++) {
|
|
77
|
+
if (/loadSubScript/.test(lines[i] ?? '')) {
|
|
78
|
+
lastSubScriptLine = i;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let insertIndex;
|
|
82
|
+
if (lastSubScriptLine !== -1) {
|
|
83
|
+
const tryStart = findNearestTryLine(lines, lastSubScriptLine - 1, -1);
|
|
84
|
+
insertIndex = tryStart !== -1 ? walkToTryBlockEnd(lines, tryStart) : lastSubScriptLine + 1;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
insertIndex = lines.length;
|
|
88
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
89
|
+
if (lines[i]?.trim() === '}') {
|
|
90
|
+
insertIndex = i;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Detect indent from surrounding code instead of hardcoding
|
|
96
|
+
const refLine = lines
|
|
97
|
+
.slice(0, insertIndex)
|
|
98
|
+
.reverse()
|
|
99
|
+
.find((l) => l.trim());
|
|
100
|
+
const ind = refLine?.match(/^(\s*)/)?.[1] ?? ' ';
|
|
101
|
+
const inner = ind + ' ';
|
|
102
|
+
const block = [
|
|
103
|
+
`${ind}try {`,
|
|
104
|
+
`${inner}Services.scriptloader.loadSubScript("chrome://browser/content/${name}.js", this);`,
|
|
105
|
+
`${ind}} catch (e) {`,
|
|
106
|
+
`${inner}console.error("Failed to load ${name}.js:", e);`,
|
|
107
|
+
`${ind}}`,
|
|
108
|
+
];
|
|
109
|
+
lines.splice(insertIndex, 0, ...block);
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Adds a loadSubScript entry to browser-main.js with try/catch error handling.
|
|
114
|
+
*
|
|
115
|
+
* @param engineDir - Engine source root
|
|
116
|
+
* @param name - Subscript name (without .js extension)
|
|
117
|
+
* @returns true if added, false if already present
|
|
118
|
+
*/
|
|
119
|
+
export async function addSubscriptToBrowserMain(engineDir, name) {
|
|
120
|
+
validateWireName(name, 'subscript name');
|
|
121
|
+
const filePath = join(engineDir, BROWSER_MAIN_JS);
|
|
122
|
+
if (!(await pathExists(filePath))) {
|
|
123
|
+
throw new GeneralError(`${BROWSER_MAIN_JS} not found in engine`);
|
|
124
|
+
}
|
|
125
|
+
const content = await readText(filePath);
|
|
126
|
+
// Idempotency check — include closing quote to avoid substring false positives
|
|
127
|
+
if (content.includes(`content/${name}.js"`)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const { value } = withParserFallback(() => addSubscriptAST(content, name), () => legacyAddSubscript(content, name), BROWSER_MAIN_JS);
|
|
131
|
+
await writeText(filePath, value);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=wire-subscript.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire targets barrel — re-exports all wiring target modules.
|
|
3
|
+
*/
|
|
4
|
+
export { addDestroyAST, addDestroyToBrowserInit, legacyAddDestroy } from './wire-destroy.js';
|
|
5
|
+
export { addDomFragment, addDomFragmentTokenized, legacyAddDomFragment, } from './wire-dom-fragment.js';
|
|
6
|
+
export { addInitAST, addInitToBrowserInit, legacyAddInit } from './wire-init.js';
|
|
7
|
+
export { addSubscriptAST, addSubscriptToBrowserMain, legacyAddSubscript, } from './wire-subscript.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Wire targets barrel — re-exports all wiring target modules.
|
|
4
|
+
*/
|
|
5
|
+
export { addDestroyAST, addDestroyToBrowserInit, legacyAddDestroy } from './wire-destroy.js';
|
|
6
|
+
export { addDomFragment, addDomFragmentTokenized, legacyAddDomFragment, } from './wire-dom-fragment.js';
|
|
7
|
+
export { addInitAST, addInitToBrowserInit, legacyAddInit } from './wire-init.js';
|
|
8
|
+
export { addSubscriptAST, addSubscriptToBrowserMain, legacyAddSubscript, } from './wire-subscript.js';
|
|
9
|
+
//# sourceMappingURL=wire-targets.js.map
|