VulpesOS/gaia/build/additional-extensions.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

354 lines
10 KiB
JavaScript
Executable file

'use strict';
/* global require, exports, Promise */
const { Cc, Ci, Cu} = require('chrome');
Cu.import('resource://gre/modules/FileUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/Promise.jsm');
var utils = require('./utils');
var dm = require('./download-manager');
var config;
var gaiaDir;
var stageDir;
var AMO_URL =
'https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/';
function logLine(msg) {
utils.log('additional-extensions', msg);
}
var InstallationManager = {
// utils.processEvents tends to end thread earlier than
// the time we start downloading extensions,
// because we asynchonously get correct urls to download from.
// So we add this flag to prevent thread ended too early.
_processing: false,
_onTheFly: [],
_downloadedExtensions: {},
_callbacksOnAllFinished: [],
_removeUrl: function im_remove_url(url) {
var index = this._onTheFly.indexOf(url);
if (index > -1) {
this._onTheFly.splice(index, 1);
}
if (this._onTheFly.length > 0) {
this._processing = true;
} else {
while (this._callbacksOnAllFinished.length > 0) {
var cb = this._callbacksOnAllFinished.pop();
cb(this._downloadedExtensions);
}
this._processing = false;
}
},
_getExtensionIdFromInstallRdf:
function im_get_extension_id_from_install_rdf(doc) {
var elementsOfNameOfEmId = doc.getElementsByTagName('em:id');
var result;
var index = 0;
var elem;
for (index = 0; index < elementsOfNameOfEmId.length; index += 1) {
elem = elementsOfNameOfEmId[index];
if (elem.parentNode && elem.parentNode.tagName === 'Description') {
result = elem.innerHTML;
break;
}
}
if (!result) {
throw new Error('No extension ID found');
} else {
return result;
}
},
_getUnpackFromInstallRdf: function im_get_unpack_from_install_rdf(doc) {
var elems = doc.getElementsByTagName('em:unpack');
var result = false;
if (elems && elems.length > 0) {
result = doc.getElementsByTagName('em:unpack')[0].innerHTML === 'true';
}
return result;
},
_installFromUrl: function im_install_from_url(sourceUrl, filePath) {
var zipReader;
var file;
var sourceFileName;
var sourceUrlTokens;
var extractedDir;
var installRdf;
var installRdfContent;
var extensionId;
var unpack;
var entryEnumerator;
var extensionDir;
sourceUrlTokens = sourceUrl.split('/');
sourceFileName = sourceUrlTokens[sourceUrlTokens.length - 1];
zipReader =
Cc['@mozilla.org/libjar/zip-reader;1'].createInstance(Ci.nsIZipReader);
file = utils.getFile(filePath);
zipReader.open(file);
extractedDir = utils.getTempFolder(sourceFileName);
installRdf = utils.getFile(extractedDir.path, 'install.rdf');
// peek install.rdf and look for what we need: extionsion id and unpack
zipReader.extract('install.rdf', installRdf);
installRdfContent = utils.getXML(installRdf);
extensionId =
InstallationManager._getExtensionIdFromInstallRdf(installRdfContent);
unpack =
InstallationManager._getUnpackFromInstallRdf(installRdfContent);
if (unpack) {
// case 1: extract content of xpi file to
// build_stage/additional-extensions/<extensionId>/
extensionDir =
utils.getFile(stageDir, 'additional-extensions', extensionId);
utils.ensureFolderExists(extensionDir);
entryEnumerator = zipReader.findEntries('*');
while (entryEnumerator.hasMore()) {
try {
var zipEntryName = entryEnumerator.getNext();
var zipEntry = zipReader.getEntry(zipEntryName);
var targetFile = utils.getFile(
stageDir, 'additional-extensions', extensionId, zipEntryName);
var targetFileType = zipEntry.isDirectory ?
Ci.nsIFile.DIRECTORY_TYPE : Ci.nsIFile.NORMAL_FILE_TYPE;
if (!targetFile.exists()) {
targetFile.create(targetFileType, parseInt('0644', 8));
zipReader.extract(zipEntryName, targetFile);
}
} catch (e) {
logLine('Error extracting ' + zipEntryName + ' due to ' + e.message);
}
}
} else {
// case 2: put xpi file directly at build+stage/additional-extensions
extensionDir = utils.getFile(stageDir, 'additional-extensions');
utils.ensureFolderExists(extensionDir);
file.copyTo(extensionDir, extensionId + '.xpi');
}
logLine(sourceUrl + ' downloaded');
zipReader.close();
InstallationManager.finish(sourceUrl);
},
init: function im_init(downloadedExtensions) {
if (downloadedExtensions) {
this._downloadedExtensions = downloadedExtensions;
}
this._processing = true;
},
startInstalling: function im_start_installing(url) {
var self = this;
if (self._onTheFly.indexOf(url) < 0) {
self._onTheFly.push(url);
}
dm.getDownloadManager().download(
url, null, null, self._installFromUrl,
function(url) {
logLine('error downloading ' + url);
self.failOrCancel(url);
});
},
failOrCancel: function im_failOrCancel() {
this._removeUrl();
},
finish: function im_finish(url) {
this._downloadedExtensions[url] = 'done';
this._removeUrl(url);
},
whenAllFinished: function im_when_all_finished(callback) {
if (callback && typeof callback === 'function') {
this._callbacksOnAllFinished.push(callback);
}
},
isBusy: function im_is_busy() {
return this.howManyAreOnTheFly() > 0 ||
dm.getDownloadManager().count > 0 ||
this._processing;
},
howManyAreOnTheFly: function im_how_many_are_on_the_fly() {
return this._onTheFly.length;
}
};
var AdditionalExtensions = (function() {
// utility to merge two extensions objects (both in JSON format)
// if extensions1 and extensions2 both have a property with
// the same name, use the one in extensions2
function merge(extensions1, extensions2) {
var result = extensions1 || {};
Object.keys(extensions2).forEach(function(key) {
result[key] = extensions2[key];
});
return result;
}
function loadDownloadedExtensions() {
var file;
var downloadedExtensions;
logLine('load downloaded extensions');
try {
file = utils.getFile(
stageDir, 'additional-extensions', 'downloaded.json');
downloadedExtensions = utils.getJSON(file);
} catch (e) {
downloadedExtensions = {};
}
return downloadedExtensions;
}
function loadCustomExtensions() {
var file;
var customExtensions;
logLine('load custom extensions');
try {
file = utils.getFile(gaiaDir, 'build', 'config',
'custom-extensions.json');
customExtensions = utils.getJSON(file);
} catch (e) {
customExtensions = {};
}
return customExtensions;
}
function loadAdditionalExtensions() {
var file;
var additionalExtensions;
logLine('load additional extensions');
try {
file = utils.getFile(gaiaDir, 'build', 'config',
'additional-extensions.json');
additionalExtensions = utils.getJSON(file);
} catch (e) {
additionalExtensions = {};
}
return additionalExtensions;
}
function writeDownloadedExtensions(downloaded) {
var dir;
var file;
var content;
// output as an array (it's the original design of additional-extensions.py)
content = JSON.stringify(Object.keys(downloaded), undefined, 2);
dir = utils.getFile(stageDir, 'additional-extensions');
utils.ensureFolderExists(dir);
file = utils.getFile(stageDir, 'additional-extensions', 'downloaded.json');
utils.writeContent(file, content);
}
function getExtensionUrl(extension) {
var deferred = Promise.defer();
var url;
try {
url = extension.url;
if (url) {
deferred.resolve(url);
} else {
getUrlFromAMO(extension.amo).then(function(amoUrl) {
deferred.resolve(amoUrl);
});
}
} catch (e) {
deferred.reject(e);
}
return deferred.promise;
}
function getUrlFromAMO(amoId) {
var deferred = Promise.defer();
function extractDownloadUrlFromAMO(sourceUrl, filePath) {
var xmlDoc;
var elems;
try {
xmlDoc = utils.getXML(utils.getFile(filePath));
elems = xmlDoc.getElementsByTagName('install');
deferred.resolve(elems[0].innerHTML);
} catch (e) {
deferred.reject(e);
}
}
dm.getDownloadManager().download(AMO_URL + amoId,
null,
null,
extractDownloadUrlFromAMO,
function() {
deferred.reject('No Download URL found for AMO ID: ' + amoId);
});
return deferred.promise;
}
// main function
function execute(options) {
logLine(JSON.stringify(options, null, ' '));
config = options;
gaiaDir = config.GAIA_DIR;
stageDir = config.STAGE_DIR;
var extensions;
var downloadedExtensions;
var keys;
function downloadAndInstall(url) {
try {
if (downloadedExtensions && downloadedExtensions[url]) {
logLine(' already downloaded');
} else {
logLine('download from ' + url);
InstallationManager.startInstalling(url);
}
} catch (e) {
InstallationManager.failOrCancel(url);
logLine(' failed');
}
}
downloadedExtensions = loadDownloadedExtensions();
extensions = merge(loadAdditionalExtensions(), loadCustomExtensions());
InstallationManager.init(downloadedExtensions);
InstallationManager.whenAllFinished(function(exts) {
writeDownloadedExtensions(exts);
});
keys = Object.keys(extensions);
if (keys.length > 0) {
keys.forEach(function(prop) {
logLine('Installing ' + prop + '...');
getExtensionUrl(extensions[prop]).then(downloadAndInstall);
});
} else {
InstallationManager.failOrCancel();
}
utils.processEvents(function exitFunc() {
return {
wait: InstallationManager.isBusy()
};
});
}
return {
execute: execute
};
}());
exports.execute = AdditionalExtensions.execute;