/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ var xpiPicker; var xpi2Picker; var sourcePicker; var destPicker; var xpiOK = false; var errorMessage; var extensionID; var extensionName; var extension2ID; var extension2Name; var suppressMigration; var repackMessage; var progress; var silent = false; const rdfs = Components.classes["@mozilla.org/rdf/rdf-service;1"]. getService(Components.interfaces.nsIRDFService); const platforms = { }; function setupListeners() { window.sizeToContent(); xpiPicker = document.getElementById("xpiPicker"); xpi2Picker = document.getElementById("xpi2Picker"); sourcePicker = document.getElementById("sourcePicker"); destPicker = document.getElementById("destPicker"); errorMessage = document.getElementById("errorMessage"); extensionID = document.getElementById("extensionID"); extensionName = document.getElementById("extensionName"); extension2ID = document.getElementById("extension2ID"); extension2Name = document.getElementById("extension2Name"); suppressMigration = document.getElementById("suppressMigration"); repackMessage = document.getElementById("repackMessage"); progress = document.getElementById("progress"); platforms.win = { check: document.getElementById("winCheck"), file: document.getElementById("winFile") }; platforms.zip = { check: document.getElementById("zipCheck"), file: document.getElementById("zipFile") }; platforms.mac = { check: document.getElementById("macCheck"), file: document.getElementById("macFile") }; platforms.unix = { check: document.getElementById("unixCheck"), file: document.getElementById("unixFile") }; // Read override options from the command line try { var cl = window.arguments[0]. QueryInterface(Components.interfaces.nsICommandLine); silent = cl.handleFlag("silent", true); if (silent) { suppressMigration.checked = false; xpi2Picker.value = ""; } var xpiArg = cl.handleFlagWithParam("xpi", true); if (xpiArg) xpiPicker.file = cl.resolveFile(xpiArg); var xpi2Arg = cl.handleFlagWithParam("xpi2", true); if (xpi2Arg) xpi2Picker.file = cl.resolveFile(xpi2Arg); var sourceArg = cl.handleFlagWithParam("source", true); if (sourceArg) sourcePicker.file = cl.resolveFile(sourceArg); var destArg = cl.handleFlagWithParam("dest", true); if (destArg) destPicker.file = cl.resolveFile(destArg); var platformsArg = cl.handleFlagWithParam("platforms", true); if (platformsArg) { platforms.win.check.checked = false; platforms.zip.check.checked = false; platforms.mac.check.checked = false; platforms.unix.check.checked = false; var setPlatforms = platformsArg.split(","); for each (p in setPlatforms) { if (!platforms[p]) throw Error("Unrecognized value for --platforms= flag."); platforms[p].check.checked = true; } } var doSuppression = cl.handleFlag("disable-migration", true); if (doSuppression) { suppressMigration.checked = true; } var doMigration = cl.handleFlag("enable-migration", true); if (doMigration) { suppressMigration.checked = false; } xpiPicker.addEventListener("change", setupXPI, false); xpi2Picker.addEventListener("change", setupXPI, false); sourcePicker.addEventListener("change", setupPlatforms, false); destPicker.addEventListener("change", setupDest, false); setupXPI(); setupPlatforms(); setupDest(); if (silent) doRepackage(); } catch (e) { dump("Error: " + e + "\n"); } if (silent) doQuit(); } const PS = Components.interfaces.nsIPromptService; function setupDest() { try { var f = destPicker.file; if (!f.exists()) { if (silent) throw Error("Destination directory does not exist."); var ps = Components. classes["@mozilla.org/embedcomp/prompt-service;1"]. getService(PS); if (ps.confirmEx(window, "Create Directory?", "The directory does not exist. Would you like to create it now?", PS.STD_YES_NO_BUTTONS, null, null, null, null, { value: false }) == 0) { f.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755); } } } catch (e) { if (silent) throw (e); Components.utils.reportError(e); } setupAccept(); } function setupPlatforms() { try { var f = sourcePicker.file; if (!f.exists() || !f.isDirectory()) { throw Error("Invalid file!"); } var win = false; var zip = false; var mac = false; var unix = false; var de = f.directoryEntries; while (de.hasMoreElements()) { var c = de.getNext().QueryInterface(Components.interfaces.nsIFile); var ln = c.leafName; if (!ln.match(/^firefox/i)) { continue; } if (ln.match(/cck/i)) { continue; } if (ln.match(/\.exe$/i)) { win = ln; continue; } if (ln.match(/\.zip$/i)) { zip = ln; continue; } if (ln.match(/\.dmg$/i)) { mac = ln; continue; } if (ln.match(/.tar\.gz$/i)) { unix = ln; continue; } } if (win) setPlatform("win", win); else naPlatform("win"); if (zip) setPlatform("zip", zip); else naPlatform("zip"); if (mac) setPlatform("mac", mac); else naPlatform("mac"); if (unix) setPlatform("unix", unix); else naPlatform("unix"); } catch (e) { naPlatform("win"); naPlatform("zip"); naPlatform("mac"); naPlatform("unix"); } setupAccept(); } function naPlatform(platform) { platforms[platform].check.disabled = true; platforms[platform].file.value = "(N/A)"; platforms[platform].file.disabled = true; } function setPlatform(platform, value) { platforms[platform].check.disabled = false; platforms[platform].file.value = value; platforms[platform].file.disabled = false; } function EM_R(param) { return rdfs.GetResource("http://www.mozilla.org/2004/em-rdf#" + param); } // @returns an array [ID, name] // no JS-1.7 yet, we still want to work with XR 1.8.0.x function getXPI(f) { if (!f.exists() || !f.isFile()) { throw Error("Invalid File"); } var ios = Components.classes["@mozilla.org/network/io-service;1"]. getService(Components.interfaces.nsIIOService); var fileuri = ios.newFileURI(f); var dsurispec = "jar:" + fileuri.spec + "!/install.rdf"; var ds = rdfs.GetDataSourceBlocking(dsurispec); var rootr = rdfs.GetResource("urn:mozilla:install-manifest"); var idp = EM_R("id"); var id = ds.GetTarget(rootr, idp, true). QueryInterface(Components.interfaces.nsIRDFLiteral).Value; var namep = EM_R("name"); var name = ds.GetTarget(rootr, namep, true). QueryInterface(Components.interfaces.nsIRDFLiteral).Value; return [id, name]; } function setupXPI() { var s; var ok = true; try { s = getXPI(xpiPicker.file); extensionID.value = s[0]; extensionName.value = s[1]; } catch (e) { ok = false; extensionID.value = "(N/A)"; } if (xpi2Picker.value == "") { extension2ID.value = "(N/A)"; extension2Name.value = "(N/A)"; } else { try { s = getXPI(xpi2Picker.file); extension2ID.value = s[0]; extension2Name.value = s[1]; } catch (e) { ok = false; extension2ID.value = "(N/A)"; } } xpiOK = ok; setupAccept(); } function setupAccept() { if (!xpiOK) { doAccept(false, "Specify a valid XPI file."); return; } try { var sourceDir = sourcePicker.file; if (!destDir.exists() || !destDir.isDirectory()) { throw Error("Invalid directory"); } } catch (e) { doAccept(false, "Select a valid source directory."); } if (!platforms.mac.check.disabled && platforms.mac.check.checked) { var DeRez = Components.classes["@mozilla.org/file/local;1"]. createInstance(Components.interfaces.nsILocalFile); DeRez.initWithPath("/Developer/Tools/DeRez"); if (!DeRez.exists()) { doAccept(false, "Install the Mac Developer Tools to repackage mac builds."); return; } } if ((platforms.win.check.disabled || !platforms.win.check.checked) && (platforms.zip.check.disabled || !platforms.zip.check.checked) && (platforms.mac.check.disabled || !platforms.mac.check.checked) && (platforms.unix.check.disabled || !platforms.unix.check.checked)) { doAccept(false, "Select at least one platform to repackage."); return; } try { var destDir = destPicker.file; if (!destDir.exists() || !destDir.isDirectory()) { throw Error("Invalid directory."); } if (destDir.equals(sourceDir)) { doAccept(false, "Destination directory and source directory must be different."); return; } } catch (e) { doAccept(false, "Select a valid destination directory."); return; } doAccept(true, ""); } function doAccept(ok, text) { errorMessage.value = text; document.documentElement.getButton("accept").disabled = !ok; } function doQuit() { var as = Components.classes["@mozilla.org/toolkit/app-startup;1"]. getService(Components.interfaces.nsIAppStartup); as.quit(Components.interfaces.nsIAppStartup.eAttemptQuit); } function doRepackage() { try { repackMessage.value = "Repackaging..."; progress.value = "10"; progress.mode = "determined"; // If we're in silent mode, a platform was specified for repackaging // but we can't find a suitable file to repackage, bail! if (silent) { if (platforms.win.check.checked && platforms.win.check.disabled) throw Error("Cannot find Windows build to repackage."); if (platforms.zip.check.checked && platforms.zip.check.disabled) throw Error("Cannot find Windows ZIP build to repackage."); if (platforms.mac.check.checked && platforms.mac.check.disabled) throw Error("Cannot find Mac build to repackage."); if (platforms.unix.check.checked && platforms.unix.check.disabled) throw Error("Cannot find Linux build to repackage."); } var doWin = !platforms.win.check.disabled && platforms.win.check.checked; var doZip = !platforms.zip.check.disabled && platforms.zip.check.checked; var doMac = !platforms.mac.check.disabled && platforms.mac.check.checked; var doUnix = !platforms.unix.check.disabled && platforms.unix.check.checked; var total = doWin + doZip + doMac + doUnix; var completed = 0; function update_progress() { progress.value = (completed / total) * 90 + 10; } if (doWin) { repackMessage.value = "Repackaging Windows..."; try { repackage_win(); } catch (e) { throw Error("Windows repackaging failed: " + e); } ++completed; update_progress(); } if (doZip) { repackMessage.value = "Repackaging Windows ZIP..."; try { repackage_zip(); } catch (e) { throw Error("Winows ZIP repackaging failed: " + e); } ++completed; update_progress; } if (doMac) { repackMessage.value = "Repackaging Mac..."; try { repackage_mac(); } catch (e) { throw Error("Mac repackaging failed: " + e); } ++completed; update_progress(); } if (doUnix) { repackMessage.value = "Repackaging Linux..."; try { repackage_unix(); } catch (e) { throw Error("Linux repackaging failed: " + e); } ++completed; update_progress(); } repackMessage.value = "Repackaging completed."; } catch (e) { Components.utils.reportError(e); if (silent) { throw (e); } else { repackMessage.value = e; } } } function getSpecialDir(key) { var dirs = Components.classes["@mozilla.org/file/directory_service;1"]. getService(Components.interfaces.nsIProperties); return dirs.get(key, Components.interfaces.nsIFile); } function getUniqueTempDir() { var tmpd = getSpecialDir("TmpD"); tmpd.append("cck-repack"); tmpd.createUnique(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0700); return tmpd; } function run_command() { var binary; var args = []; for (var i = 0; i < arguments.length; ++i) args.push(arguments[i]); if (args[0] instanceof Components.interfaces.nsIFile) { binary = args.shift(); } else { binary = Components.classes["@mozilla.org/file/local;1"]. createInstance(Components.interfaces.nsILocalFile); binary.initWithPath(args.shift()); } var p = Components.classes["@mozilla.org/process/util;1"]. createInstance(Components.interfaces.nsIProcess); p.init(binary); p.run(true, args, args.length); if (p.exitValue != 0) { throw Error("Failure running command: " + binary.path + " " + args.join(" ")); } } const PR_RDONLY = 0x01; const PR_WRONLY = 0x02; const PR_TRUNCATE = 0x20; function read_file(aFile) { var fstream = Components. classes["@mozilla.org/network/file-input-stream;1"]. createInstance(Components.interfaces.nsIFileInputStream); fstream.init(aFile, PR_RDONLY, 0, 0); var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]. createInstance(Components.interfaces.nsIScriptableInputStream); sstream.init(fstream); var data = ""; do { var str = sstream.read(4096); data += str; } while (str.length > 0); sstream.close(); fstream.close(); return data; } function write_file(aFile, aString) { var fstream = Components. classes["@mozilla.org/network/file-output-stream;1"]. createInstance(Components.interfaces.nsIFileOutputStream); fstream.init(aFile, PR_WRONLY | PR_TRUNCATE, 0x664, 0); fstream.write(aString, aString.length); fstream.close(); } function copy_override(destDir) { var overrideFile = getSpecialDir("resource:app"); overrideFile.append("resources"); overrideFile.append("override.ini"); overrideFile.copyTo(destDir, "override.ini"); } function repackage_win() { var tmpd = getUniqueTempDir(); try { var resDir = getSpecialDir("resource:app"); resDir.append("resources"); var sz = resDir.clone(); sz.append("7za"); run_command(sz, "x", "-o" + tmpd.path + "/unpack", sourcePicker.file.path + "/" + platforms.win.file.value); // read config.ini into a file var configini = tmpd.clone(); configini.append("unpack"); configini.append("config.ini"); var unpackDir = tmpd.clone(); unpackDir.append("unpack"); if (configini.exists()) { if (xpi2Picker.value != "") { throw Error("Cannot repack more than one extension into an old-style installer."); } if (suppressMigration.checked == true) { throw Error("Cannot suppress migration in an old-style installer."); } // If config.ini exists, this is an old-style (1.5) installer var size = xpiPicker.file.fileSize; var fileSize = (size / 1000).toFixed(0); var unzSize = (size / 500).toFixed(0); var configtext = read_file(configini); var configlines = configtext.split(/\n/); for (var i = 0; i < configlines.length; ++i) { if (/^\[Core\]$/.test(configlines[i])) { configlines.splice(i, 0, "[Component CCK]", "Description Short=" + extensionName.value, "Description Long=" + extensionName.value, "Archive=" + xpiPicker.file.leafName, "Install Size=" + unzSize, "Install Size System=0", "Install Size Archive=" + fileSize, "Attributes=SELECTED|INVISIBLE", "FileCount=5", ""); break; } var m = /^C(\d)=Component QFA$/.exec(configlines[i]); if (!m) continue; ++i; configlines.splice(i, 0, "C" + (Number(m[1]) + 1) + "=Component CCK"); } write_file(configini, configlines.join("\n")); xpiPicker.file.copyTo(unpackDir, ""); } else { // A NSIS installer is a lot easier to repackage run_command("/usr/bin/unzip", xpiPicker.file.path, "-d", tmpd.path + "/unpack/nonlocalized/extensions/" + extensionID.value); if (xpi2Picker.value != "") { run_command("/usr/bin/unzip", xpi2Picker.file.path, "-d", tmpd.path + "/unpack/nonlocalized/extensions/" + extension2ID.value); } if (suppressMigration.checked) { var appdir = tmpd.clone(); appdir.append("unpack"); appdir.append("nonlocalized"); copy_override(appdir); } } var szs = sz.clone(); szs.leafName = "7z.sh"; run_command(szs, sz.path, unpackDir.path, tmpd.path + "/app.7z"); var concat = szs.clone(); concat.leafName = "concat.sh"; run_command(concat, resDir.path + "/7zSD.sfx", resDir.path + "/app.tag", tmpd.path + "/app.7z", destPicker.file.path + "/" + platforms.win.file.value); } finally { tmpd.remove(true); } } function repackage_mac() { var tmpd = getUniqueTempDir(); try { var undmg = getSpecialDir("resource:app"); undmg.append("resources"); undmg.append("undmg.sh"); run_command(undmg, sourcePicker.file.path + "/" + platforms.mac.file.value, tmpd.path); run_command("/usr/bin/unzip", xpiPicker.file.path, "-d", tmpd.path + "/mount/Firefox.app/Contents/MacOS/extensions/" + extensionID.value); if (xpi2Picker.value != "") { run_command("/usr/bin/unzip", xpi2Picker.file.path, "-d", tmpd.path + "/mount/Firefox.app/Contents/MacOS/extensions/" + extension2ID.value); } if (suppressMigration.checked) { var appDir = tmpd.clone(); appDir.append("mount"); appDir.append("Firefox.app"); appDir.append("Contents"); appDir.append("MacOS"); copy_override(appDir); } undmg.leafName = "pkg-dmg"; run_command("/usr/bin/perl", "-w", undmg.path, "--source", tmpd.path + "/mount", "--target", destPicker.file.path + "/" + platforms.mac.file.value, "--volname", "Firefox", "--copy", tmpd.path + "/.DS_Store:/.DS_Store", "--mkdir", "/.background", "--copy", tmpd.path + "/background.png:/.background", "--icon", tmpd.path + "/.VolumeIcon.icns", "--resource", tmpd.path + "/license.r"); } finally { tmpd.remove(true); } } function repackage_unix() { var tmpd = getUniqueTempDir(); try { run_command("/usr/bin/tar", "-xzf", sourcePicker.file.path + "/" + platforms.unix.file.value, "-C", tmpd.path); run_command("/usr/bin/unzip", xpiPicker.file.path, "-d", tmpd.path + "/firefox/extensions/" + extensionID.value); if (xpi2Picker.value != "") { run_command("/usr/bin/unzip", xpi2Picker.file.path, "-d", tmpd.path + "/firefox/extensions/" + extension2ID.value); } if (suppressMigration.checked) { var appDir = tmpd.clone(); appDir.append("firefox"); copy_override(appDir); } run_command("/usr/bin/tar", "-c", "--owner=0", "--group=0", "--numeric-owner","--mode=go-w", "-zf", destPicker.file.path + "/" + platforms.unix.file.value, "-C", tmpd.path, "firefox"); } finally { tmpd.remove(true); } } function repackage_zip() { var tmpd = getUniqueTempDir(); try { run_command("/usr/bin/unzip", sourcePicker.file.path + "/" + platforms.zip.file.value, "-d", tmpd.path); run_command("/usr/bin/unzip", xpiPicker.file.path, "-d", tmpd.path + "/firefox/extensions/" + extensionID.value); if (xpi2Picker.value != "") { run_command("/usr/bin/unzip", xpi2Picker.file.path, "-d", tmpd.path + "/firefox/extensions/" + extension2ID.value); } run_command("/bin/sh", "-c", "cd \"" + tmpd.path + "\" && " + "/usr/bin/zip -r9D \"" + destPicker.file.path + "/" + platforms.zip.file.value + "\" firefox"); } finally { tmpd.remove(true); } }