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
|
/**
* Replacements for Components.manager.addBootstrappedManifestLocation and
* Components.manager.removeBootstrappedManifestLocation, which have been nixed
* in mozilla142. Based on ideas from Github users onemen and 117649.
* Copyright 2025 Tobias Girstmair <https://gir.st/>, MPLv2.
*/
const ScriptableInputStream = Components.Constructor(
"@mozilla.org/scriptableinputstream;1",
"nsIScriptableInputStream",
"init"
);
const FileOutputStream = Components.Constructor(
"@mozilla.org/network/file-output-stream;1",
"nsIFileOutputStream",
"init"
);
export class LegacyFoxUtils {
static addBootstrappedManifestLocation(file, addon, uriMaker) {
// read chrome.manifest from .jar (or unpacked addon)
let channel = Services.io.newChannelFromURI(
uriMaker(file, "chrome.manifest"),
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER
);
let available, manifestContents = "";
let stream = channel.open(), sstream = new ScriptableInputStream(stream);
while ((available = sstream.available()) > 0)
manifestContents += sstream.read(available);
sstream.close(), stream.close();
// replace chrome:// and resource:// URIs with absolute paths to JAR
manifestContents = manifestContents
.split("\n")
.map(absolutizePaths.bind(null, uriMaker, file))
.join("\n");
// we store the temporary file in the user's profile, in a subdirectory
// analogous to webExtension's "browser-extension-data". the startupCache(?)
// sometimes interferes with us, so we name the file differently each time.
let manifest = Services.dirsvc.get('ProfD', Ci.nsIFile)
manifest.append('legacy-extension-data');
manifest.append(addon.id);
manifest.exists() || manifest.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
manifest.append(`${Date.now()}.manifest`); /* created or truncated by ostream */
// write modified chrome.manifest to profile directory
let ostream = new FileOutputStream(manifest, -1, -1, 0);
ostream.write(manifestContents, manifestContents.length);
ostream.close();
// let Firefox read and parse it
Components.manager.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(manifest);
}
static removeBootstrappedManifestLocation(addon) {
let manifestDir = Services.dirsvc.get('ProfD', Ci.nsIFile)
manifestDir.append('legacy-extension-data');
manifestDir.append(addon.id);
manifestDir.exists() && manifestDir.remove(/*recursive=*/true);
Cc['@mozilla.org/chrome/chrome-registry;1']
.getService(Ci.nsIXULChromeRegistry).checkForNewChrome();
}
}
function absolutizePaths(uriMaker, file, line) {
const manifestMethodPathLocation = {
content: 2, // content packagename uri/to/files/ [flags]
locale: 3, // locale packagename localename uri/to/files/ [flags]
skin: 3, // skin packagename skinname uri/to/files/ [flags]
resource: 2 // resource aliasname uri/to/files/ [flags]
};
let words = line.trim().split(/\s+/);
const index = manifestMethodPathLocation[words[0]];
if (index) {
words[index] = uriMaker(file, words[index]).spec;
line = words.join(" ");
}
return line;
}
|