mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-20 21:48:00 +01:00
Add cache module. Cleanup help and DTD generation. Fix :help version generation issues. Fix other assorted issues.
This commit is contained in:
2
common/bootstrap.js
vendored
2
common/bootstrap.js
vendored
@@ -207,6 +207,7 @@ function init() {
|
||||
let pref = "extensions.dactyl.cacheFlushCheck";
|
||||
let val = addon.version + "-" + hardSuffix;
|
||||
if (!Services.prefs.prefHasUserValue(pref) || Services.prefs.getCharPref(pref) != val) {
|
||||
var cacheFlush = true;
|
||||
Services.obs.notifyObservers(null, "startupcache-invalidate", "");
|
||||
Services.prefs.setCharPref(pref, val);
|
||||
}
|
||||
@@ -244,6 +245,7 @@ function init() {
|
||||
JSMLoader.load(BOOTSTRAP_JSM, global);
|
||||
|
||||
JSMLoader.init(suffix);
|
||||
JSMLoader.cacheFlush = cacheFlush;
|
||||
JSMLoader.load("base.jsm", global);
|
||||
|
||||
if (!(BOOTSTRAP_CONTRACT in Cc)) {
|
||||
|
||||
@@ -1291,6 +1291,10 @@ var Buffer = Module("buffer", {
|
||||
if (!DOM(elem).isScrollable(horizontal ? "horizontal" : "vertical"))
|
||||
return false;
|
||||
|
||||
return this.canScroll(elem, dir, horizontal);
|
||||
},
|
||||
|
||||
canScroll: function canScroll(elem, dir, horizontal) {
|
||||
let pos = "scrollTop", size = "clientHeight", max = "scrollHeight", layoutSize = "offsetHeight",
|
||||
overflow = "overflowX", border1 = "borderTopWidth", border2 = "borderBottomWidth";
|
||||
if (horizontal)
|
||||
|
||||
@@ -111,6 +111,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
|
||||
}
|
||||
},
|
||||
|
||||
signals: {
|
||||
"io.source": function ioSource(context, file, modTime) {
|
||||
if (context.INFO)
|
||||
help.flush("help/plugins.xml", modTime);
|
||||
}
|
||||
},
|
||||
|
||||
profileName: deprecated("config.profileName", { get: function profileName() config.profileName }),
|
||||
|
||||
/**
|
||||
@@ -484,8 +491,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
|
||||
|
||||
if (fileName && fileName[0] == "[")
|
||||
fileName = "dactyl://command-line/";
|
||||
|
||||
if (!context && fileName && fileName[0] !== "[")
|
||||
else if (!context)
|
||||
context = ctxt || _userContext;
|
||||
|
||||
if (isinstance(context, ["Sandbox"]))
|
||||
@@ -659,82 +665,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
|
||||
* @private
|
||||
* Initialize the help system.
|
||||
*/
|
||||
initHelp: function initHelp(force) {
|
||||
initHelp: function initHelp() {
|
||||
if ("noscriptOverlay" in window)
|
||||
noscriptOverlay.safeAllow("dactyl:", true, false);
|
||||
|
||||
if (!force && help.initialized)
|
||||
return;
|
||||
|
||||
help.initialize(force);
|
||||
|
||||
// Process plugin help entries.
|
||||
XML.ignoreWhiteSpace = XML.prettyPrinting = false;
|
||||
|
||||
let body = XML();
|
||||
for (let [, context] in Iterator(plugins.contexts))
|
||||
try {
|
||||
let info = contexts.getDocs(context);
|
||||
if (info instanceof XML) {
|
||||
if (info.*.@lang.length()) {
|
||||
let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
|
||||
|
||||
info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
|
||||
|
||||
for each (let elem in info.NS::info)
|
||||
for each (let attr in ["@name", "@summary", "@href"])
|
||||
if (elem[attr].length())
|
||||
info[attr] = elem[attr];
|
||||
}
|
||||
body += <h2 xmlns={NS.uri} tag={info.@name + '-plugin'}>{info.@summary}</h2> +
|
||||
info;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
util.reportError(e);
|
||||
}
|
||||
|
||||
help.files["plugins"] = function () ['text/xml;charset=UTF-8',
|
||||
'<?xml version="1.0"?>\n' +
|
||||
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
|
||||
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
|
||||
<document xmlns={NS}
|
||||
name="plugins" title={config.appName + " Plugins"}>
|
||||
<h1 tag="using-plugins">{_("help.title.Using Plugins")}</h1>
|
||||
<toc start="2"/>
|
||||
|
||||
{body}
|
||||
</document>.toXMLString()];
|
||||
|
||||
|
||||
default xml namespace = NS;
|
||||
|
||||
help.overlays["index"] = ['text/xml;charset=UTF-8',
|
||||
'<?xml version="1.0"?>\n' +
|
||||
<overlay xmlns={NS}>{
|
||||
template.map(dactyl.indices, function ([name, iter])
|
||||
<dl insertafter={name + "-index"}>{
|
||||
template.map(iter(), util.identity)
|
||||
}</dl>, <>{"\n\n"}</>)
|
||||
}</overlay>];
|
||||
|
||||
help.overlays["gui"] = ['text/xml;charset=UTF-8',
|
||||
'<?xml version="1.0"?>\n' +
|
||||
<overlay xmlns={NS}>
|
||||
<dl insertafter="dialog-list">{
|
||||
template.map(config.dialogs, function ([name, val])
|
||||
(!val[2] || val[2]())
|
||||
? <><dt>{name}</dt><dd>{val[0]}</dd></>
|
||||
: undefined,
|
||||
<>{"\n"}</>)
|
||||
}</dl>
|
||||
</overlay>];
|
||||
|
||||
help.tags["plugins"] = help.tags["plugins.xml"] = "plugins";
|
||||
help.tags["index"] = help.tags["index.xml"] = "index";
|
||||
|
||||
help.addTags("plugins", util.httpGet("dactyl://help/plugins").responseXML);
|
||||
help.addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
|
||||
help.initialize();
|
||||
},
|
||||
|
||||
stringifyXML: function (xml) {
|
||||
@@ -1280,6 +1215,73 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
|
||||
}, {
|
||||
toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
|
||||
}, {
|
||||
cache: function () {
|
||||
cache.register("help/plugins.xml", function () {
|
||||
// Process plugin help entries.
|
||||
XML.ignoreWhiteSpace = XML.prettyPrinting = false;
|
||||
|
||||
let body = XML();
|
||||
for (let [, context] in Iterator(plugins.contexts))
|
||||
try {
|
||||
let info = contexts.getDocs(context);
|
||||
if (info instanceof XML) {
|
||||
if (info.*.@lang.length()) {
|
||||
let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
|
||||
|
||||
info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
|
||||
|
||||
for each (let elem in info.NS::info)
|
||||
for each (let attr in ["@name", "@summary", "@href"])
|
||||
if (elem[attr].length())
|
||||
info[attr] = elem[attr];
|
||||
}
|
||||
body += <h2 xmlns={NS.uri} tag={info.@name + '-plugin'}>{info.@summary}</h2> +
|
||||
info;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
util.reportError(e);
|
||||
}
|
||||
|
||||
return '<?xml version="1.0"?>\n' +
|
||||
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
|
||||
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
|
||||
<document xmlns={NS}
|
||||
name="plugins" title={config.appName + " Plugins"}>
|
||||
<h1 tag="using-plugins">{_("help.title.Using Plugins")}</h1>
|
||||
<toc start="2"/>
|
||||
|
||||
{body}
|
||||
</document>.toXMLString();
|
||||
});
|
||||
|
||||
cache.register("help/index.xml", function () {
|
||||
default xml namespace = NS;
|
||||
|
||||
return '<?xml version="1.0"?>\n' +
|
||||
<overlay xmlns={NS}>{
|
||||
template.map(dactyl.indices, function ([name, iter])
|
||||
<dl insertafter={name + "-index"}>{
|
||||
template.map(iter(), util.identity)
|
||||
}</dl>, <>{"\n\n"}</>)
|
||||
}</overlay>;
|
||||
});
|
||||
|
||||
cache.register("help/gui.xml", function () {
|
||||
default xml namespace = NS;
|
||||
|
||||
return '<?xml version="1.0"?>\n' +
|
||||
<overlay xmlns={NS}>
|
||||
<dl insertafter="dialog-list">{
|
||||
template.map(config.dialogs, function ([name, val])
|
||||
(!val[2] || val[2]())
|
||||
? <><dt>{name}</dt><dd>{val[0]}</dd></>
|
||||
: undefined,
|
||||
<>{"\n"}</>)
|
||||
}</dl>
|
||||
</overlay>;
|
||||
});
|
||||
},
|
||||
events: function () {
|
||||
events.listen(window, dactyl, "events", true);
|
||||
},
|
||||
|
||||
@@ -213,57 +213,18 @@ var Modes = Module("modes", {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function makeTree() {
|
||||
let list = modes.all.filter(function (m) m.name !== m.description);
|
||||
|
||||
let tree = {};
|
||||
|
||||
for (let mode in values(list))
|
||||
tree[mode.name] = {};
|
||||
|
||||
for (let mode in values(list))
|
||||
for (let base in values(mode.bases))
|
||||
tree[base.name][mode.name] = tree[mode.name];
|
||||
|
||||
let roots = iter([m.name, tree[m.name]] for (m in values(list)) if (!m.bases.length)).toObject();
|
||||
|
||||
default xml namespace = NS;
|
||||
function rec(obj) {
|
||||
XML.ignoreWhitespace = XML.prettyPrinting = false;
|
||||
|
||||
let res = <ul dactyl:highlight="Dense" xmlns:dactyl={NS}/>;
|
||||
Object.keys(obj).sort().forEach(function (name) {
|
||||
let mode = modes.getMode(name);
|
||||
res.* += <li><em>{mode.displayName}</em>: {mode.description}{
|
||||
rec(obj[name])
|
||||
}</li>;
|
||||
});
|
||||
|
||||
if (res.*.length())
|
||||
return res;
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return rec(roots);
|
||||
}
|
||||
|
||||
util.timeout(function () {
|
||||
// Waits for the add-on to become available, if necessary.
|
||||
config.addon;
|
||||
config.version;
|
||||
|
||||
services["dactyl:"].pages["modes.dtd"] = services["dactyl:"].pages["modes.dtd"]();
|
||||
});
|
||||
|
||||
services["dactyl:"].pages["modes.dtd"] = function () [null,
|
||||
util.makeDTD(iter({ "modes.tree": makeTree() },
|
||||
config.dtd))];
|
||||
},
|
||||
|
||||
cleanup: function cleanup() {
|
||||
modes.reset();
|
||||
},
|
||||
|
||||
signals: {
|
||||
"io.source": function ioSource(context, file, modTime) {
|
||||
cache.flushEntry("modes.dtd", modTime);
|
||||
}
|
||||
},
|
||||
|
||||
_getModeMessage: function _getModeMessage() {
|
||||
// when recording a macro
|
||||
let macromode = "";
|
||||
@@ -604,6 +565,45 @@ var Modes = Module("modes", {
|
||||
}, desc));
|
||||
}
|
||||
}, {
|
||||
cache: function initCache() {
|
||||
function makeTree() {
|
||||
let list = modes.all.filter(function (m) m.name !== m.description);
|
||||
|
||||
let tree = {};
|
||||
|
||||
for (let mode in values(list))
|
||||
tree[mode.name] = {};
|
||||
|
||||
for (let mode in values(list))
|
||||
for (let base in values(mode.bases))
|
||||
tree[base.name][mode.name] = tree[mode.name];
|
||||
|
||||
let roots = iter([m.name, tree[m.name]] for (m in values(list)) if (!m.bases.length)).toObject();
|
||||
|
||||
default xml namespace = NS;
|
||||
function rec(obj) {
|
||||
XML.ignoreWhitespace = XML.prettyPrinting = false;
|
||||
|
||||
let res = <ul dactyl:highlight="Dense" xmlns:dactyl={NS}/>;
|
||||
Object.keys(obj).sort().forEach(function (name) {
|
||||
let mode = modes.getMode(name);
|
||||
res.* += <li><em>{mode.displayName}</em>: {mode.description}{
|
||||
rec(obj[name])
|
||||
}</li>;
|
||||
});
|
||||
|
||||
if (res.*.length())
|
||||
return res;
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return rec(roots);
|
||||
}
|
||||
|
||||
cache.register("modes.dtd", function ()
|
||||
util.makeDTD(iter({ "modes.tree": makeTree() },
|
||||
config.dtd)));
|
||||
},
|
||||
mappings: function initMappings() {
|
||||
mappings.add([modes.BASE, modes.NORMAL],
|
||||
["<Esc>", "<C-[>"],
|
||||
|
||||
@@ -21,7 +21,7 @@ var MOW = Module("mow", {
|
||||
if (modes.have(modes.OUTPUT_MULTILINE)) {
|
||||
this.resize(true);
|
||||
|
||||
if (options["more"] && this.isScrollable(1)) {
|
||||
if (options["more"] && this.canScroll(1)) {
|
||||
// start the last executed command's output at the top of the screen
|
||||
let elements = this.document.getElementsByClassName("ex-command-output");
|
||||
DOM(elements[elements.length - 1]).scrollIntoView(true);
|
||||
@@ -219,7 +219,7 @@ var MOW = Module("mow", {
|
||||
onKeyPress: function onKeyPress(eventList) {
|
||||
const KILL = false, PASS = true;
|
||||
|
||||
if (options["more"] && mow.isScrollable(1))
|
||||
if (options["more"] && mow.canScroll(1))
|
||||
this.updateMorePrompt(false, true);
|
||||
else {
|
||||
modes.pop();
|
||||
@@ -279,7 +279,7 @@ var MOW = Module("mow", {
|
||||
|
||||
if (showHelp)
|
||||
this.widgets.message = ["MoreMsg", _("mow.moreHelp")];
|
||||
else if (force || (options["more"] && Buffer.isScrollable(elem, 1)))
|
||||
else if (force || (options["more"] && Buffer.canScroll(elem, 1)))
|
||||
this.widgets.message = ["MoreMsg", _("mow.more")];
|
||||
else
|
||||
this.widgets.message = ["Question", _("mow.continue")];
|
||||
@@ -341,36 +341,36 @@ var MOW = Module("mow", {
|
||||
|
||||
bind(["j", "<C-e>", "<Down>"], "Scroll down one line",
|
||||
function ({ count }) { mow.scrollVertical("lines", 1 * (count || 1)); },
|
||||
function () mow.isScrollable(1), BEEP);
|
||||
function () mow.canScroll(1), BEEP);
|
||||
|
||||
bind(["k", "<C-y>", "<Up>"], "Scroll up one line",
|
||||
function ({ count }) { mow.scrollVertical("lines", -1 * (count || 1)); },
|
||||
function () mow.isScrollable(-1), BEEP);
|
||||
function () mow.canScroll(-1), BEEP);
|
||||
|
||||
bind(["<C-j>", "<C-m>", "<Return>"], "Scroll down one line, exit on last line",
|
||||
function ({ count }) { mow.scrollVertical("lines", 1 * (count || 1)); },
|
||||
function () mow.isScrollable(1), DROP);
|
||||
function () mow.canScroll(1), DROP);
|
||||
|
||||
// half page down
|
||||
bind(["<C-d>"], "Scroll down half a page",
|
||||
function ({ count }) { mow.scrollVertical("pages", .5 * (count || 1)); },
|
||||
function () mow.isScrollable(1), BEEP);
|
||||
function () mow.canScroll(1), BEEP);
|
||||
|
||||
bind(["<C-f>", "<PageDown>"], "Scroll down one page",
|
||||
function ({ count }) { mow.scrollVertical("pages", 1 * (count || 1)); },
|
||||
function () mow.isScrollable(1), BEEP);
|
||||
function () mow.canScroll(1), BEEP);
|
||||
|
||||
bind(["<Space>"], "Scroll down one page",
|
||||
function ({ count }) { mow.scrollVertical("pages", 1 * (count || 1)); },
|
||||
function () mow.isScrollable(1), DROP);
|
||||
function () mow.canScroll(1), DROP);
|
||||
|
||||
bind(["<C-u>"], "Scroll up half a page",
|
||||
function ({ count }) { mow.scrollVertical("pages", -.5 * (count || 1)); },
|
||||
function () mow.isScrollable(-1), BEEP);
|
||||
function () mow.canScroll(-1), BEEP);
|
||||
|
||||
bind(["<C-b>", "<PageUp>"], "Scroll up half a page",
|
||||
function ({ count }) { mow.scrollVertical("pages", -1 * (count || 1)); },
|
||||
function () mow.isScrollable(-1), BEEP);
|
||||
function () mow.canScroll(-1), BEEP);
|
||||
|
||||
bind(["gg"], "Scroll to the beginning of output",
|
||||
function () { mow.scrollToPercent(null, 0); });
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<include href="developer" tag="developer.xml"/>
|
||||
<include href="various" tag="various.xml"/>
|
||||
<include href="plugins" tag="plugins.xml"/>
|
||||
<include href="versions" tag="versions.xml"/>
|
||||
<include href="faq" tag="faq.xml"/>
|
||||
<include href="index" tag="index.xml"/>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>
|
||||
|
||||
<!DOCTYPE document SYSTEM "dactyl://content/modes.dtd">
|
||||
<!DOCTYPE document SYSTEM "dactyl://cache/modes.dtd">
|
||||
|
||||
<document
|
||||
name="map"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>
|
||||
|
||||
<!DOCTYPE document SYSTEM "dactyl://content/options.dtd">
|
||||
<!DOCTYPE document SYSTEM "dactyl://cache/options.dtd">
|
||||
|
||||
<document
|
||||
name="options"
|
||||
|
||||
232
common/modules/cache.jsm
Normal file
232
common/modules/cache.jsm
Normal file
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2011 by Kris Maglione <maglione.k@gmail.com>
|
||||
//
|
||||
// This work is licensed for reuse under an MIT license. Details are
|
||||
// given in the LICENSE.txt file included with this file.
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://dactyl/bootstrap.jsm");
|
||||
defineModule("cache", {
|
||||
exports: ["Cache", "cache"],
|
||||
require: ["config", "services", "util"]
|
||||
}, this);
|
||||
|
||||
var Cache = Module("Cache", XPCOM(Ci.nsIRequestObserver), {
|
||||
init: function init() {
|
||||
this.queue = [];
|
||||
this.cache = {};
|
||||
this.providers = {};
|
||||
this.localProviders = {};
|
||||
|
||||
if (JSMLoader.cacheFlush)
|
||||
this.flush();
|
||||
|
||||
update(services["dactyl:"].providers, {
|
||||
"cache": function (uri, path) {
|
||||
// TODO: Return zip reader stream
|
||||
|
||||
let result = cache.force(path);
|
||||
if (isArray(result))
|
||||
return result;
|
||||
|
||||
try {
|
||||
return [services.mime.getTypeFromURI(uri), result];
|
||||
}
|
||||
catch (e) {
|
||||
return ["text/plain", result];
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
Local: function Local(dactyl, modules, window) ({
|
||||
init: function init() {
|
||||
delete this.instance;
|
||||
this.providers = {};
|
||||
},
|
||||
|
||||
isLocal: true
|
||||
}),
|
||||
|
||||
compression: 9,
|
||||
|
||||
cacheFile: Class.Memoize(function () {
|
||||
let dir = File(services.directory.get("ProfD", Ci.nsIFile))
|
||||
.child("dactyl");
|
||||
if (!dir.exists())
|
||||
dir.create(dir.DIRECTORY_TYPE, octal(777));
|
||||
return dir.child("cache.zip");
|
||||
}),
|
||||
|
||||
get cacheReader() {
|
||||
if (!this._cacheReader && this.cacheFile.exists()
|
||||
&& !this.inQueue)
|
||||
try {
|
||||
this._cacheReader = services.ZipReader(this.cacheFile);
|
||||
}
|
||||
catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
|
||||
util.reportError(e);
|
||||
this.cacheFile.remove(false);
|
||||
}
|
||||
|
||||
return this._cacheReader;
|
||||
},
|
||||
|
||||
get inQueue() this._cacheWriter && this._cacheWriter.inQueue(),
|
||||
|
||||
getCacheWriter: function () {
|
||||
if (!this._cacheWriter)
|
||||
try {
|
||||
let mode = File.MODE_RDWR;
|
||||
if (!this.cacheFile.exists())
|
||||
mode |= File.MODE_CREATE;
|
||||
|
||||
cache._cacheWriter = services.ZipWriter(this.cacheFile, mode);
|
||||
}
|
||||
catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
|
||||
util.reportError(e);
|
||||
this.cacheFile.remove(false);
|
||||
|
||||
mode |= File.MODE_CREATE;
|
||||
cache._cacheWriter = services.ZipWriter(this.cacheFile, mode);
|
||||
}
|
||||
return this._cacheWriter;
|
||||
},
|
||||
|
||||
closeReader: function closeReader() {
|
||||
if (cache._cacheReader) {
|
||||
this.cacheReader.close();
|
||||
delete cache._cacheReader;
|
||||
}
|
||||
},
|
||||
|
||||
closeWriter: function closeWriter() {
|
||||
this.closeReader();
|
||||
|
||||
if (this._cacheWriter) {
|
||||
this._cacheWriter.close();
|
||||
delete cache._cacheWriter;
|
||||
|
||||
// ZipWriter bug.
|
||||
if (this.cacheFile.fileSize <= 22)
|
||||
this.cacheFile.remove(false);
|
||||
}
|
||||
},
|
||||
|
||||
flush: function flush() {
|
||||
cache.cache = {};
|
||||
if (this.cacheFile.eists()) {
|
||||
this.closeReader();
|
||||
|
||||
this.flushJAR(this.cacheFile);
|
||||
this.cacheFile.remove(false);
|
||||
}
|
||||
},
|
||||
|
||||
flushAll: function flushAll(file) {
|
||||
this.flushStartup();
|
||||
this.flush();
|
||||
},
|
||||
|
||||
flushEntry: function flushEntry(name, time) {
|
||||
if (this.cacheReader && this.cacheReader.hasEntry(name)) {
|
||||
if (time && this.cacheReader.getEntry(name).lastModifiedTime / 1000 >= time)
|
||||
return;
|
||||
|
||||
this.queue.push([null, name]);
|
||||
cache.processQueue();
|
||||
}
|
||||
|
||||
delete this.cache[name];
|
||||
},
|
||||
|
||||
flushJAR: function flushJAR(file) {
|
||||
services.observer.notifyObservers(file, "flush-cache-entry", "");
|
||||
},
|
||||
|
||||
flushStartup: function flushStartup() {
|
||||
services.observer.notifyObservers(null, "startupcache-invalidate", "");
|
||||
},
|
||||
|
||||
force: function force(name, localOnly) {
|
||||
if (this.cacheReader && this.cacheReader.hasEntry(name)) {
|
||||
return JSON.parse(File.readStream(
|
||||
this.cacheReader.getInputStream(name)));
|
||||
}
|
||||
|
||||
if (Set.has(this.localProviders, name) && !this.isLocal) {
|
||||
for each (let { cache } in overlay.modules)
|
||||
if (cache.has(name))
|
||||
return cache.force(name, true);
|
||||
}
|
||||
|
||||
if (Set.has(this.providers, name)) {
|
||||
let [func, self] = this.providers[name];
|
||||
this.cache[name] = func.call(self || this, name);
|
||||
|
||||
cache.queue.push([Date.now(), name]);
|
||||
cache.processQueue();
|
||||
|
||||
return this.cache[name];
|
||||
}
|
||||
|
||||
if (this.isLocal && !localOnly)
|
||||
return cache.force(name);
|
||||
},
|
||||
|
||||
get: function get(name) {
|
||||
if (!Set.has(this.cache, name)) {
|
||||
this.cache[name] = this.force(name);
|
||||
util.assert(this.cache[name] !== undefined,
|
||||
"No such cache key", false);
|
||||
}
|
||||
|
||||
return this.cache[name];
|
||||
},
|
||||
|
||||
has: function has(name) Set.has(this.providers, name) || set.has(this.cache, name),
|
||||
|
||||
register: function register(name, callback, self) {
|
||||
if (this.isLocal)
|
||||
Set.add(this.localProviders, name);
|
||||
|
||||
this.providers[name] = [callback, self];
|
||||
},
|
||||
|
||||
processQueue: function processQueue() {
|
||||
this.closeReader();
|
||||
this.closeWriter();
|
||||
|
||||
if (this.queue.length && !this.inQueue) {
|
||||
// removeEntry does not work properly with queues.
|
||||
for each (let [, entry] in this.queue)
|
||||
if (this.getCacheWriter().hasEntry(entry)) {
|
||||
this.getCacheWriter().removeEntry(entry, false);
|
||||
this.closeWriter();
|
||||
}
|
||||
|
||||
this.queue.splice(0).forEach(function ([time, entry]) {
|
||||
if (time && Set.has(this.cache, entry)) {
|
||||
let stream = services.CharsetConv("UTF-8")
|
||||
.convertToInputStream(JSON.stringify(this.cache[entry]));
|
||||
|
||||
this.getCacheWriter().addEntryStream(entry, time * 1000,
|
||||
this.compression, stream,
|
||||
true);
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (this._cacheWriter)
|
||||
this.getCacheWriter().processQueue(this, null);
|
||||
}
|
||||
},
|
||||
|
||||
onStopRequest: function onStopRequest() {
|
||||
this.processQueue();
|
||||
}
|
||||
});
|
||||
|
||||
endModule();
|
||||
|
||||
// catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
|
||||
|
||||
// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
|
||||
@@ -14,6 +14,7 @@ defineModule("config", {
|
||||
}, this);
|
||||
|
||||
this.lazyRequire("addons", ["AddonManager"]);
|
||||
this.lazyRequire("cache", ["cache"]);
|
||||
this.lazyRequire("highlight", ["highlight"]);
|
||||
this.lazyRequire("messages", ["_"]);
|
||||
|
||||
@@ -54,9 +55,11 @@ var ConfigBase = Class("ConfigBase", {
|
||||
"resource://dactyl-content/")));
|
||||
|
||||
this.timeout(function () {
|
||||
services["dactyl:"].pages.dtd = function () [null, util.makeDTD(config.dtd)];
|
||||
cache.register("config.dtd", function () util.makeDTD(config.dtd));
|
||||
});
|
||||
|
||||
services["dactyl:"].pages["dtd"] = function () [null, cache.get("config.dtd")];
|
||||
|
||||
update(services["dactyl:"].providers, {
|
||||
"locale": function (uri, path) LocaleChannel("dactyl-locale", config.locale, path, uri),
|
||||
"locale-local": function (uri, path) LocaleChannel("dactyl-local-locale", config.locale, path, uri)
|
||||
@@ -101,6 +104,7 @@ var ConfigBase = Class("ConfigBase", {
|
||||
global: ["addons",
|
||||
"base",
|
||||
"io",
|
||||
"cache",
|
||||
"commands",
|
||||
"completion",
|
||||
"config",
|
||||
|
||||
@@ -7,21 +7,79 @@
|
||||
Components.utils.import("resource://dactyl/bootstrap.jsm");
|
||||
defineModule("help", {
|
||||
exports: ["help"],
|
||||
require: ["dom", "javascript", "protocol", "services", "util"]
|
||||
require: ["cache", "dom", "javascript", "protocol", "services", "util"]
|
||||
}, this);
|
||||
|
||||
var HelpBuilder = Class("HelpBuilder", {
|
||||
init: function init() {
|
||||
try {
|
||||
help._data = this;
|
||||
|
||||
this.files = {};
|
||||
this.tags = {};
|
||||
this.overlays = {};
|
||||
|
||||
// Scrape the list of help files from all.xml
|
||||
this.tags["all"] = this.tags["all.xml"] = "all";
|
||||
let files = this.findHelpFile("all").map(function (doc)
|
||||
[f.value for (f in DOM.XPath("//dactyl:include/@href", doc))]);
|
||||
|
||||
// Scrape the tags from the rest of the help files.
|
||||
array.flatten(files).forEach(function (file) {
|
||||
this.tags[file + ".xml"] = file;
|
||||
this.findHelpFile(file).forEach(function (doc) {
|
||||
this.addTags(file, doc);
|
||||
}, this);
|
||||
}, this);
|
||||
}
|
||||
finally {
|
||||
delete help._data;
|
||||
}
|
||||
},
|
||||
|
||||
toJSON: function toJSON() ({
|
||||
files: this.files,
|
||||
overlays: this.overlays,
|
||||
tags: this.tags
|
||||
}),
|
||||
|
||||
// Find the tags in the document.
|
||||
addTags: function addTags(file, doc) {
|
||||
for (let elem in DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
|
||||
for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
|
||||
this.tags[tag] = file;
|
||||
},
|
||||
|
||||
bases: ["dactyl://locale-local/", "dactyl://locale/", "dactyl://cache/help/"],
|
||||
|
||||
// Find help and overlay files with the given name.
|
||||
findHelpFile: function findHelpFile(file) {
|
||||
let result = [];
|
||||
for (let base in values(this.bases)) {
|
||||
let url = [base, file, ".xml"].join("");
|
||||
let res = util.httpGet(url);
|
||||
if (res) {
|
||||
if (res.responseXML.documentElement.localName == "document")
|
||||
this.files[file] = url;
|
||||
if (res.responseXML.documentElement.localName == "overlay")
|
||||
this.overlays[file] = url;
|
||||
result.push(res.responseXML);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
var Help = Module("Help", {
|
||||
init: function init() {
|
||||
this.initialized = false;
|
||||
this.files = {};
|
||||
this.overlays = {};
|
||||
this.tags = {};
|
||||
|
||||
function Loop(fn)
|
||||
function (uri, path) {
|
||||
if (!help.initialized)
|
||||
return RedirectChannel(uri.spec, uri, 2,
|
||||
"Initializing. Please wait...");
|
||||
|
||||
return fn.apply(this, arguments);
|
||||
}
|
||||
|
||||
@@ -36,8 +94,136 @@ var Help = Module("Help", {
|
||||
return RedirectChannel("dactyl://help/" + help.tags[tag] + "#" + tag.replace(/#/g, encodeURIComponent), uri);
|
||||
})
|
||||
});
|
||||
|
||||
cache.register("help", HelpBuilder);
|
||||
|
||||
cache.register("help/versions.xml", function () {
|
||||
let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
|
||||
{ mimeType: "text/plain;charset=UTF-8" })
|
||||
.responseText;
|
||||
|
||||
let re = util.regexp(UTF8(<![CDATA[
|
||||
^ (?P<comment> \s* # .*\n)
|
||||
|
||||
| ^ (?P<space> \s*)
|
||||
(?P<char> [-•*+]) \ //
|
||||
(?P<content> .*\n
|
||||
(?: \2\ \ .*\n | \s*\n)* )
|
||||
|
||||
| (?P<par>
|
||||
(?: ^ [^\S\n]*
|
||||
(?:[^-•*+\s] | [-•*+]\S)
|
||||
.*\n
|
||||
)+
|
||||
)
|
||||
|
||||
| (?: ^ [^\S\n]* \n) +
|
||||
]]>), "gmxy");
|
||||
|
||||
let betas = util.regexp(/\[(b\d)\]/, "gx");
|
||||
|
||||
let beta = array(betas.iterate(NEWS))
|
||||
.map(function (m) m[1]).uniq().slice(-1)[0];
|
||||
|
||||
|
||||
default xml namespace = NS;
|
||||
function rec(text, level, li) {
|
||||
XML.ignoreWhitespace = XML.prettyPrinting = false;
|
||||
|
||||
let res = <></>;
|
||||
let list, space, i = 0;
|
||||
|
||||
|
||||
for (let match in re.iterate(text)) {
|
||||
if (match.comment)
|
||||
continue;
|
||||
else if (match.char) {
|
||||
if (!list)
|
||||
res += list = <ul/>;
|
||||
let li = <li/>;
|
||||
li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
|
||||
list.* += li;
|
||||
}
|
||||
else if (match.par) {
|
||||
let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
|
||||
let t = tags;
|
||||
tags = array(betas.iterate(tags)).map(function (m) m[1]);
|
||||
|
||||
let group = !tags.length ? "" :
|
||||
!tags.some(function (t) t == beta) ? "HelpNewsOld" : "HelpNewsNew";
|
||||
if (i === 0 && li) {
|
||||
li.@highlight = group;
|
||||
group = "";
|
||||
}
|
||||
|
||||
list = null;
|
||||
if (level == 0 && /^.*:\n$/.test(match.par)) {
|
||||
let text = par.slice(0, -1);
|
||||
res += <h2 tag={"news-" + text}>{template.linkifyHelp(text, true)}</h2>;
|
||||
}
|
||||
else {
|
||||
let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
|
||||
res += <p highlight={group + " HelpNews"}>{
|
||||
!tags.length ? "" :
|
||||
<hl key="HelpNewsTag">{tags.join(" ")}</hl>
|
||||
}{
|
||||
a ? <hl key="HelpWarning">{a}</hl> : ""
|
||||
}{
|
||||
template.linkifyHelp(b, true)
|
||||
}</p>;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
for each (let attr in res..@highlight) {
|
||||
attr.parent().@NS::highlight = attr;
|
||||
delete attr.parent().@highlight;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
XML.ignoreWhitespace = XML.prettyPrinting = false;
|
||||
let body = rec(NEWS, 0);
|
||||
for each (let li in body..li) {
|
||||
let list = li..li.(@NS::highlight == "HelpNewsOld");
|
||||
if (list.length() && list.length() == li..li.(@NS::highlight != "").length()) {
|
||||
for each (let li in list)
|
||||
li.@NS::highlight = "";
|
||||
li.@NS::highlight = "HelpNewsOld";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return '<?xml version="1.0"?>\n' +
|
||||
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
|
||||
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
|
||||
<document xmlns={NS} xmlns:dactyl={NS}
|
||||
name="versions" title={config.appName + " Versions"}>
|
||||
<h1 tag="versions news NEWS">{config.appName} Versions</h1>
|
||||
<toc start="2"/>
|
||||
|
||||
{body}
|
||||
</document>.toXMLString()
|
||||
});
|
||||
},
|
||||
|
||||
initialize: function initialize() {
|
||||
help.initialized = true;
|
||||
},
|
||||
|
||||
flush: function flush(entries, time) {
|
||||
cache.flushEntry("help", time);
|
||||
|
||||
for (let entry in values(Array.concat(entries || [])))
|
||||
cache.flushEntry(entry, time);
|
||||
},
|
||||
|
||||
get data() this._data || cache.get("help"),
|
||||
|
||||
get files() this.data.files,
|
||||
get overlays() this.data.overlays,
|
||||
get tags() this.data.tags,
|
||||
|
||||
Local: function Local(dactyl, modules, window) ({
|
||||
init: function init() {
|
||||
dactyl.commands["dactyl.help"] = function (event) {
|
||||
@@ -81,6 +267,7 @@ var Help = Module("Help", {
|
||||
*/
|
||||
help: function (topic, consolidated) {
|
||||
dactyl.initHelp();
|
||||
|
||||
if (!topic) {
|
||||
let helpFile = consolidated ? "all" : modules.options["helpfile"];
|
||||
|
||||
@@ -210,176 +397,7 @@ var Help = Module("Help", {
|
||||
zip.close();
|
||||
}, [function (context, args) completion.file(context)]),
|
||||
|
||||
}),
|
||||
|
||||
// Find the tags in the document.
|
||||
addTags: function addTags(file, doc) {
|
||||
for (let elem in DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
|
||||
for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
|
||||
this.tags[tag] = file;
|
||||
},
|
||||
|
||||
namespaces: ["locale-local", "locale"],
|
||||
|
||||
// Find help and overlay files with the given name.
|
||||
findHelpFile: function findHelpFile(file) {
|
||||
let result = [];
|
||||
for (let namespace in values(this.namespaces)) {
|
||||
let url = ["dactyl://", namespace, "/", file, ".xml"].join("");
|
||||
let res = util.httpGet(url);
|
||||
if (res) {
|
||||
if (res.responseXML.documentElement.localName == "document")
|
||||
this.files[file] = url;
|
||||
if (res.responseXML.documentElement.localName == "overlay")
|
||||
this.overlays[file] = url;
|
||||
result.push(res.responseXML);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
initialize: function initialize(force) {
|
||||
// Waits for the add-on to become available, if necessary.
|
||||
config.addon;
|
||||
config.version;
|
||||
|
||||
if (force || !this.initialized) {
|
||||
|
||||
this.files["versions"] = function () {
|
||||
let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
|
||||
{ mimeType: "text/plain;charset=UTF-8" })
|
||||
.responseText;
|
||||
|
||||
let re = util.regexp(UTF8(<![CDATA[
|
||||
^ (?P<comment> \s* # .*\n)
|
||||
|
||||
| ^ (?P<space> \s*)
|
||||
(?P<char> [-•*+]) \ //
|
||||
(?P<content> .*\n
|
||||
(?: \2\ \ .*\n | \s*\n)* )
|
||||
|
||||
| (?P<par>
|
||||
(?: ^ [^\S\n]*
|
||||
(?:[^-•*+\s] | [-•*+]\S)
|
||||
.*\n
|
||||
)+
|
||||
)
|
||||
|
||||
| (?: ^ [^\S\n]* \n) +
|
||||
]]>), "gmxy");
|
||||
|
||||
let betas = util.regexp(/\[(b\d)\]/, "gx");
|
||||
|
||||
let beta = array(betas.iterate(NEWS))
|
||||
.map(function (m) m[1]).uniq().slice(-1)[0];
|
||||
|
||||
|
||||
default xml namespace = NS;
|
||||
function rec(text, level, li) {
|
||||
XML.ignoreWhitespace = XML.prettyPrinting = false;
|
||||
|
||||
let res = <></>;
|
||||
let list, space, i = 0;
|
||||
|
||||
|
||||
for (let match in re.iterate(text)) {
|
||||
if (match.comment)
|
||||
continue;
|
||||
else if (match.char) {
|
||||
if (!list)
|
||||
res += list = <ul/>;
|
||||
let li = <li/>;
|
||||
li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
|
||||
list.* += li;
|
||||
}
|
||||
else if (match.par) {
|
||||
let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
|
||||
let t = tags;
|
||||
tags = array(betas.iterate(tags)).map(function (m) m[1]);
|
||||
|
||||
let group = !tags.length ? "" :
|
||||
!tags.some(function (t) t == beta) ? "HelpNewsOld" : "HelpNewsNew";
|
||||
if (i === 0 && li) {
|
||||
li.@highlight = group;
|
||||
group = "";
|
||||
}
|
||||
|
||||
list = null;
|
||||
if (level == 0 && /^.*:\n$/.test(match.par)) {
|
||||
let text = par.slice(0, -1);
|
||||
res += <h2 tag={"news-" + text}>{template.linkifyHelp(text, true)}</h2>;
|
||||
}
|
||||
else {
|
||||
let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
|
||||
res += <p highlight={group + " HelpNews"}>{
|
||||
!tags.length ? "" :
|
||||
<hl key="HelpNewsTag">{tags.join(" ")}</hl>
|
||||
}{
|
||||
a ? <hl key="HelpWarning">{a}</hl> : ""
|
||||
}{
|
||||
template.linkifyHelp(b, true)
|
||||
}</p>;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
for each (let attr in res..@highlight) {
|
||||
attr.parent().@NS::highlight = attr;
|
||||
delete attr.parent().@highlight;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
XML.ignoreWhitespace = XML.prettyPrinting = false;
|
||||
let body = rec(NEWS, 0);
|
||||
for each (let li in body..li) {
|
||||
let list = li..li.(@NS::highlight == "HelpNewsOld");
|
||||
if (list.length() && list.length() == li..li.(@NS::highlight != "").length()) {
|
||||
for each (let li in list)
|
||||
li.@NS::highlight = "";
|
||||
li.@NS::highlight = "HelpNewsOld";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ["application/xml",
|
||||
'<?xml version="1.0"?>\n' +
|
||||
'<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
|
||||
'<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
|
||||
<document xmlns={NS} xmlns:dactyl={NS}
|
||||
name="versions" title={config.appName + " Versions"}>
|
||||
<h1 tag="versions news NEWS">{config.appName} Versions</h1>
|
||||
<toc start="2"/>
|
||||
|
||||
{body}
|
||||
</document>.toXMLString()
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Scrape the list of help files from all.xml
|
||||
// Manually process main and overlay files, since XSLTProcessor and
|
||||
// XMLHttpRequest don't allow access to chrome documents.
|
||||
this.tags["all"] = this.tags["all.xml"] = "all";
|
||||
let files = this.findHelpFile("all").map(function (doc)
|
||||
[f.value for (f in DOM.XPath("//dactyl:include/@href", doc))]);
|
||||
|
||||
// Scrape the tags from the rest of the help files.
|
||||
array.flatten(files).forEach(function (file) {
|
||||
this.tags[file + ".xml"] = file;
|
||||
this.findHelpFile(file).forEach(function (doc) {
|
||||
this.addTags(file, doc);
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
this.tags["versions"] = this.tags["versions.xml"] = "versions";
|
||||
|
||||
this.addTags("versions", util.httpGet("dactyl://help/versions").responseXML);
|
||||
|
||||
help.initialized = true;
|
||||
}
|
||||
},
|
||||
})
|
||||
}, {
|
||||
}, {
|
||||
commands: function init_commands(dactyl, modules, window) {
|
||||
|
||||
@@ -175,7 +175,7 @@ var IO = Module("io", {
|
||||
util.flushCache();
|
||||
|
||||
dactyl.loadScript(uri.spec, context);
|
||||
help.initialized = false;
|
||||
dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
|
||||
}
|
||||
catch (e) {
|
||||
if (e.fileName && !(e instanceof FailedAssertion))
|
||||
@@ -200,6 +200,7 @@ var IO = Module("io", {
|
||||
group: context.GROUP,
|
||||
line: 1
|
||||
});
|
||||
dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime);
|
||||
}
|
||||
|
||||
Set.add(this._scriptNames, file.path);
|
||||
|
||||
@@ -794,7 +794,7 @@ var Options = Module("options", {
|
||||
opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true);
|
||||
}, window);
|
||||
|
||||
services["dactyl:"].pages["options.dtd"] = function () [null,
|
||||
modules.cache.register("options.dtd", function ()
|
||||
util.makeDTD(
|
||||
iter(([["option", o.name, "default"].join("."),
|
||||
o.type === "string" ? o.defaultValue.replace(/'/g, "''") :
|
||||
@@ -804,7 +804,13 @@ var Options = Module("options", {
|
||||
|
||||
([["option", o.name, "type"].join("."), o.type] for (o in self)),
|
||||
|
||||
config.dtd))];
|
||||
config.dtd)));
|
||||
},
|
||||
|
||||
signals: {
|
||||
"io.source": function ioSource(context, file, modTime) {
|
||||
cache.flushEntry("options.dtd", modTime);
|
||||
}
|
||||
},
|
||||
|
||||
dactyl: dactyl,
|
||||
|
||||
@@ -371,6 +371,9 @@ var Overlay = Module("Overlay", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReferen
|
||||
};
|
||||
},
|
||||
|
||||
get activeModules() this.activeWindow && this.activeWindow.dactyl.modules,
|
||||
|
||||
get modules() this.windows.map(function (w) w.dactyl.modules),
|
||||
|
||||
/**
|
||||
* The most recently active dactyl window.
|
||||
|
||||
@@ -106,8 +106,8 @@ var Services = Module("Services", {
|
||||
this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest", "open");
|
||||
this.addClass("XPathEvaluator", "@mozilla.org/dom/xpath-evaluator;1", "nsIDOMXPathEvaluator");
|
||||
this.addClass("XMLDocument", "@mozilla.org/xml/xml-document;1", ["nsIDOMXMLDocument", "nsIDOMNodeSelector"]);
|
||||
this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open");
|
||||
this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", "nsIZipWriter", "open");
|
||||
this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open", false);
|
||||
this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", "nsIZipWriter", "open", false);
|
||||
},
|
||||
reinit: function () {},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user