-
-
Save nohamelin/6af8907ca2dd90a9c870629c396c9521 to your computer and use it in GitHub Desktop.
// -sp-context: browser | |
/* This Source Code Form is subject to the terms of the Mozilla Public | |
* License, v. 2.0. If a copy of the MPL was not distributed with this | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
* | |
* | |
* xseei.export-all.js | |
* =================== | |
* code-revision 3 | |
* https://gist.github.com/nohamelin/6af8907ca2dd90a9c870629c396c9521 | |
* | |
* ABOUT | |
* ----- | |
* It's a minimal no-configurable no-localized single-file re-package of the | |
* "Export All Search Engines as OpenSearch XML files in a ZIP File" feature | |
* of the "XML Search Engines Exporter/Importer" (XSEEI) legacy add-on for | |
* Mozilla Firefox: | |
* https://addons.mozilla.org/firefox/addon/search-engines-export-import/ | |
* | |
* ...to be used in those Firefox builds not supporting anymore legacy add-ons | |
* (i.e. Firefox 57 and later versions), where the unique system available | |
* to create extensions (called "WebExtensions") doesn't let to implement this | |
* functionality. | |
* As a bonus, it supports Gecko-comparable releases of the SeaMonkey suite | |
* too, that did not receive any official compatible release of the add-on. | |
* | |
* COMPATIBILITY | |
* ------------- | |
* It has been checked to work with: | |
* - Firefox 57 to 61 | |
* - Firefox Developer Edition 62.0b8 | |
* - Firefox ESR 60.0 | |
* - SeaMonkey 2.49.1 | |
* | |
* HOW TO RUN | |
* ---------- | |
* It's expected to be run via the Javascript Scratchpad tool: | |
* https://developer.mozilla.org/en-US/docs/Tools/Scratchpad | |
* | |
* 1) Ensure you have enabled "Enable browser chrome and add-on debugging | |
* toolboxes" in the Developer Tools settings, or, alternatively, ensure | |
* that the preference *devtools.chrome.enabled* is true in about:config. | |
* | |
* 2) Open a *stand-alone* Scratchpad window, either using the Shift+F4 | |
* keyboard shortcut or from the Web Developer submenu of the main | |
* Firefox menu. | |
* Using the embedded Scratchpad tab in the developer toolbox is NOT | |
* recommended: its behaviour is dependent of the type of content open | |
* in the active browser tab (web content will make this script to fail). | |
* | |
* 3) Use the "Open File..." command in the Scratchpad to load this file. | |
* A "This scratchpad executes in the Browser context" warning should be | |
* shown on top of the contents of the file. Otherwise, go to the | |
* "Environment" menu and ensure that "Browser" is selected. | |
* | |
* 4) Make sure there is no selected text in the editor, and use the command | |
* "Run". A file dialog will be open, to select where the ZIP file with | |
* your search engines will be saved. After picking it, a Javascript dialog | |
* will be shown to confirm you the successful task. Otherwise, check the | |
* Browser Console by any related error messages. | |
* | |
* CONTACT | |
* ------- | |
* You can use any of the provided support channels for the source add-on: | |
* https://github.com/nohamelin/xseei | |
* http://forums.mozillazine.org/viewtopic.php?f=48&t=3020165 | |
* | |
* SEE ALSO | |
* -------- | |
* xseei.import.js: | |
* https://gist.github.com/nohamelin/8e2e1b50dc7d97044992ae981487c6ec | |
*/ | |
(function() { | |
"use strict"; | |
const { classes: Cc, interfaces: Ci, utils: Cu } = Components; | |
Cu.import("resource://gre/modules/Services.jsm"); | |
Cu.import("resource://gre/modules/FileUtils.jsm"); | |
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}"/> | |
`; | |
function exportAllEnginesToFile() { | |
let engines = Services.search.getVisibleEngines(); | |
let fp = Cc["@mozilla.org/filepicker;1"] | |
.createInstance(Ci.nsIFilePicker); | |
fp.init(window, "Export All Search Engines", Ci.nsIFilePicker.modeSave); | |
fp.appendFilter("ZIP Files", "*.zip"); | |
fp.defaultString = "searchengines " + filenameDateString() + ".zip"; | |
fp.defaultExtension = "zip"; | |
fp.open({ | |
done: result => { | |
if (result === Ci.nsIFilePicker.returnCancel) | |
return; | |
saveEnginesToZipFile(engines, fp.file).then(() => { | |
alert("Successful export"); | |
}).catch(Cu.reportError); | |
} | |
}); | |
} | |
function saveEnginesToZipFile(engines, file) { | |
return Promise.resolve().then(() => { | |
if (engines.length === 0) { | |
throw Error("the given engines array must not be empty"); | |
} | |
let zw = Cc["@mozilla.org/zipwriter;1"] | |
.createInstance(Ci.nsIZipWriter); | |
zw.open(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | |
| FileUtils.MODE_TRUNCATE); | |
let serializer = new XMLSerializer(); | |
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] | |
.createInstance(Ci.nsIScriptableUnicodeConverter); | |
converter.charset = "UTF-8"; | |
engines.forEach(engine => { | |
let doc = serializeEngineToDocument(engine); | |
let str = serializer.serializeToString(doc); | |
let istream = converter.convertToInputStream(str); | |
// Engines with very similar names can end with the same | |
// sanitized filename; we catch these to add a proper suffix. | |
let filename = sanitizeEngineName(engine.name); | |
if (zw.hasEntry(filename + ".xml")) { | |
let candidateFilename; | |
let apparitions = 1; | |
do { | |
candidateFilename = `${filename} (${apparitions})`; | |
apparitions += 1; | |
} while (zw.hasEntry(candidateFilename + ".xml")); | |
filename = candidateFilename; | |
} | |
zw.addEntryStream(filename + ".xml", | |
Date.now() * 1000, | |
Ci.nsIZipWriter.COMPRESSION_DEFAULT, | |
istream, | |
false); | |
}); | |
zw.close(); | |
}); | |
} | |
function serializeEngineToDocument(engine) { | |
let e = engine.wrappedJSObject; | |
let doc = (new DOMParser()).parseFromString(MOZSEARCH_EMPTY_ENGINE_DOC, | |
"application/xml"); | |
doc.documentElement.appendChild(doc.createTextNode("\n")); | |
appendTextNode(doc, OPENSEARCH_NS, "ShortName", e.name); | |
appendTextNode(doc, OPENSEARCH_NS, "Description", e.description); | |
appendTextNode(doc, OPENSEARCH_NS, "InputEncoding", e.queryCharset); | |
if (e.iconURI) { | |
let imageNode = appendTextNode(doc, OPENSEARCH_NS, "Image", | |
e.iconURI.spec); | |
if (imageNode) { | |
imageNode.setAttribute("width", "16"); | |
imageNode.setAttribute("height", "16"); | |
} | |
} | |
appendTextNode(doc, MOZSEARCH_NS, "UpdateInterval", e._updateInterval); | |
appendTextNode(doc, MOZSEARCH_NS, "UpdateUrl", e._updateURL); | |
appendTextNode(doc, MOZSEARCH_NS, "IconUpdateUrl", e._iconUpdateURL); | |
appendTextNode(doc, MOZSEARCH_NS, "SearchForm", e.searchForm); | |
if (e._extensionID) { | |
appendTextNode(doc, MOZSEARCH_NS, "ExtensionID", e._extensionID); | |
} | |
for (let i = 0; i < e._urls.length; ++i) { | |
addSerializedEngineUrlToElement(e._urls[i], | |
doc, | |
doc.documentElement); | |
doc.documentElement.appendChild(doc.createTextNode("\n")); | |
} | |
return doc; | |
} | |
function addSerializedEngineUrlToElement(engineUrl, doc, element) { | |
let url = doc.createElementNS(OPENSEARCH_NS, "Url"); | |
url.setAttribute("type", engineUrl.type); | |
url.setAttribute("method", engineUrl.method); | |
url.setAttribute("template", engineUrl.template); | |
if (engineUrl.rels.length) | |
url.setAttribute("rel", engineUrl.rels.join(" ")); | |
if (engineUrl.resultDomain) | |
url.setAttribute("resultDomain", engineUrl.resultDomain); | |
for (let i = 0; i < engineUrl.params.length; ++i) { | |
if (engineUrl.params[i].purpose) { // non-standard MozParam found | |
continue; | |
} | |
let param = doc.createElementNS(OPENSEARCH_NS, "Param"); | |
param.setAttribute("name", engineUrl.params[i].name); | |
param.setAttribute("value", engineUrl.params[i].value); | |
url.appendChild(doc.createTextNode("\n ")); | |
url.appendChild(param); | |
} | |
url.appendChild(doc.createTextNode("\n")); | |
element.appendChild(url); | |
} | |
function appendTextNode(document, namespace, localName, value) { | |
if (!value) return null; | |
let node = document.createElementNS(namespace, localName); | |
node.appendChild(document.createTextNode(value)); | |
document.documentElement.appendChild(node); | |
document.documentElement.appendChild(document.createTextNode("\n")); | |
return node; | |
} | |
function sanitizeEngineName(name) { | |
name = name.toLowerCase() | |
.replace(/\s+/g, "-") // Replace spaces with a hyphen | |
.replace(/-{2,}/g, "-") // Reduce consecutive hyphens | |
.normalize("NFKD") // Decompose chars with diacritics | |
.replace(/[^-a-z0-9]/g, ""); // Final cleaning | |
if (name.length < 1) { | |
name = Math.random().toString(36).replace(/^.*\./, ""); | |
} | |
return name.substring(0, 60 /*=MAX_ENGINE_FILENAME_LENGTH*/); | |
} | |
function filenameDateString(date = new Date()) { | |
let year = String(date.getFullYear()).padStart(4, "0"); | |
let month = String(date.getMonth() + 1).padStart(2, "0"); | |
let day = String(date.getDate()).padStart(2, "0"); | |
return [year, month, day].join("-"); | |
} | |
// Run | |
exportAllEnginesToFile(); | |
})(); |
I don't see any problem with 61.0.
Please verify the devtools.chrome.enabled preference before trying it, as it's stated in the "How to run" section.
If you still have a problem, please give me more info, as your OS and the latest messages printed in the "Browser Console" developer tool.
Thanks for the report, I will investigate it.
The problem is only triggered by using the Scratchpad tool as an embedded tab in the dev toolbox: I updated the instructions to run it.
Cu
error
TypeError: Cu is undefined debugger eval code:80:1
debugger eval code:80
debugger eval code:246The Components object is deprecated. It will soon be removed. debugger eval code:78:51
Solution to above error
joshcangit/xseei.export-all.js
I've only changed line 78 because Components.utils.import()
was replaced with ChromeUtils.import()
.
This should get rid of that error.
Further problems
Another error is shown.
TypeError: engines.forEach is not a function debugger eval code:128:17
Appears to work but outputs to an empty .zip
file.
don't work with firefox 61.0