Skip to content

Instantly share code, notes, and snippets.

@Gelip
Forked from nohamelin/xseei.export-all.js
Last active May 1, 2026 19:18
Show Gist options
  • Select an option

  • Save Gelip/bdab86065841b2f67b7f951f40cdba77 to your computer and use it in GitHub Desktop.

Select an option

Save Gelip/bdab86065841b2f67b7f951f40cdba77 to your computer and use it in GitHub Desktop.
Export your search engines in only-WebExtensions Mozilla Firefox builds
(async function() {
"use strict";
const { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
const win = Services.wm.getMostRecentWindow("navigator:browser");
const MOZSEARCH_NS = "http://www.mozilla.org/2006/browser/search/";
const OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
const MOZSEARCH_EMPTY_ENGINE_DOC = `<?xml version="1.0"?>
<SearchPlugin xmlns="${MOZSEARCH_NS}" xmlns:os="${OPENSEARCH_NS}"/>
`;
let engines = await Services.search.getVisibleEngines();
console.log("Engines found: " + engines.length);
if (engines.length === 0) {
Services.prompt.alert(win, "Export", "No search engines found.");
return;
}
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(win, "Select folder for search engines export", Ci.nsIFilePicker.modeGetFolder);
let result = await new Promise(resolve => fp.open({ done: resolve }));
if (result === Ci.nsIFilePicker.returnCancel) return;
let folder = fp.file;
let serializer = new XMLSerializer();
let count = 0;
for (let engine of engines) {
try {
let e = engine.wrappedJSObject || engine;
let doc = new DOMParser().parseFromString(
MOZSEARCH_EMPTY_ENGINE_DOC, "application/xml"
);
doc.documentElement.appendChild(doc.createTextNode("\n"));
addNode(doc, OPENSEARCH_NS, "ShortName", e.name);
addNode(doc, OPENSEARCH_NS, "Description", e.description);
addNode(doc, OPENSEARCH_NS, "InputEncoding", e.queryCharset);
if (e.iconURI) {
let iconSpec = (typeof e.iconURI === "string")
? e.iconURI : e.iconURI.spec;
let img = addNode(doc, OPENSEARCH_NS, "Image", iconSpec);
if (img) {
img.setAttribute("width", "16");
img.setAttribute("height", "16");
}
}
addNode(doc, MOZSEARCH_NS, "UpdateInterval", e._updateInterval);
addNode(doc, MOZSEARCH_NS, "UpdateUrl", e._updateURL);
addNode(doc, MOZSEARCH_NS, "IconUpdateUrl", e._iconUpdateURL);
addNode(doc, MOZSEARCH_NS, "SearchForm", e.searchForm);
if (e._extensionID) {
addNode(doc, MOZSEARCH_NS, "ExtensionID", e._extensionID);
}
if (e._urls) {
for (let u of e._urls) {
let urlEl = doc.createElementNS(OPENSEARCH_NS, "Url");
urlEl.setAttribute("type", u.type);
urlEl.setAttribute("method", u.method);
urlEl.setAttribute("template", u.template);
if (u.rels && u.rels.length)
urlEl.setAttribute("rel", u.rels.join(" "));
if (u.resultDomain)
urlEl.setAttribute("resultDomain", u.resultDomain);
if (u.params) {
for (let p of u.params) {
if (p.purpose) continue;
let param = doc.createElementNS(OPENSEARCH_NS, "Param");
param.setAttribute("name", p.name);
param.setAttribute("value", p.value);
urlEl.appendChild(doc.createTextNode("\n "));
urlEl.appendChild(param);
}
}
urlEl.appendChild(doc.createTextNode("\n"));
doc.documentElement.appendChild(urlEl);
doc.documentElement.appendChild(doc.createTextNode("\n"));
}
}
let str = serializer.serializeToString(doc);
let filename = sanitize(engine.name);
let targetFile = folder.clone();
targetFile.append(filename + ".xml");
if (targetFile.exists()) {
let n = 1;
do {
targetFile = folder.clone();
targetFile.append(filename + " (" + n + ").xml");
n++;
} while (targetFile.exists());
}
let fos = Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream);
fos.init(targetFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE
| FileUtils.MODE_TRUNCATE,
0o644, 0);
let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Ci.nsIConverterOutputStream);
converter.init(fos, "UTF-8", 0, 0);
converter.writeString(str);
converter.close();
count++;
console.log(" OK: " + engine.name + " -> " + targetFile.leafName);
} catch(ex) {
console.error(" ERROR: " + engine.name, ex);
}
}
Services.prompt.alert(win, "Search Engines Export",
"Exported " + count + " of " + engines.length + " engines to:\n"
+ folder.path);
console.log("Export complete: " + count + "/" + engines.length
+ " -> " + folder.path);
function addNode(doc, ns, name, value) {
if (!value) return null;
let node = doc.createElementNS(ns, name);
node.appendChild(doc.createTextNode(String(value)));
doc.documentElement.appendChild(node);
doc.documentElement.appendChild(doc.createTextNode("\n"));
return node;
}
function sanitize(name) {
name = name.toLowerCase()
.replace(/\s+/g, "-")
.replace(/-{2,}/g, "-")
.normalize("NFKD")
.replace(/[^-a-z0-9]/g, "");
if (name.length < 1)
name = Math.random().toString(36).replace(/^.*\./, "");
return name.substring(0, 60);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment