1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2014 Kris Maglione <maglione.k@gmail.com>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
10 defineModule("config", {
11 exports: ["ConfigBase", "Config", "config"],
12 require: ["io", "protocol", "services"]
15 lazyRequire("addons", ["AddonManager"]);
16 lazyRequire("cache", ["cache"]);
17 lazyRequire("dom", ["DOM"]);
18 lazyRequire("highlight", ["highlight"]);
19 lazyRequire("messages", ["_"]);
20 lazyRequire("prefs", ["localPrefs", "prefs"]);
21 lazyRequire("storage", ["storage", "File"]);
22 lazyRequire("styles", ["Styles"]);
23 lazyRequire("template", ["template"]);
24 lazyRequire("util", ["util"]);
26 function AboutHandler() {}
27 AboutHandler.prototype = {
28 get classDescription() "About " + config.appName + " Page",
30 classID: Components.ID("81495d80-89ee-4c36-a88d-ea7c4e5ac63f"),
32 get contractID() services.ABOUT + config.name,
34 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
36 newChannel: function (uri) {
37 let channel = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
38 .newChannel("dactyl://content/about.xul", null, null);
39 channel.originalURI = uri;
43 getURIFlags: function (uri) Ci.nsIAboutModule.ALLOW_SCRIPT
45 var ConfigBase = Class("ConfigBase", {
47 * Called on dactyl startup to allow for any arbitrary application-specific
48 * initialization code. Must call superclass's init function.
50 init: function init() {
51 if (!config.haveGecko("26"))
52 this.modules.global = this.modules.global.filter(m => m != "downloads"); // FIXME
56 util.trapErrors(() => {
57 JSMLoader.registerFactory(JSMLoader.Factory(AboutHandler));
59 util.withProperErrors(() => {
60 JSMLoader.registerFactory(JSMLoader.Factory(
61 Protocol("dactyl", "{9c8f2530-51c8-4d41-b356-319e0b155c44}",
62 "resource://dactyl-content/")));
65 this.protocolLoaded = true;
66 this.timeout(function () {
67 cache.register("config.dtd", () => util.makeDTD(config.dtd),
71 // FIXME: May not be ready before first window opens.
72 AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", a => {
74 config.features.delete("default-theme");
77 services["dactyl:"].pages["dtd"] = () => [null, cache.get("config.dtd")];
79 update(services["dactyl:"].providers, {
80 "locale": function (uri, path) LocaleChannel("dactyl-locale", config.locale, path, uri),
81 "locale-local": function (uri, path) LocaleChannel("dactyl-local-locale", config.locale, path, uri)
85 get prefs() localPrefs,
87 has: function (feature) this.features.has(feature),
90 "resource://dactyl-common/config.json",
91 "resource://dactyl-local/config.json"
94 configs: Class.Memoize(function () this.configFiles.map(url => JSON.parse(File.readURL(url)))),
96 loadConfig: function loadConfig(documentURL) {
98 for (let config of this.configs) {
100 config = config.overlays && config.overlays[documentURL] || {};
102 for (let [name, value] in Iterator(config)) {
103 let prop = util.camelCase(name);
105 if (isArray(this[prop]))
106 this[prop] = [].concat(this[prop], value);
107 else if (isinstance(this[prop], ["Set"]))
108 for (let key of value)
110 else if (isObject(this[prop])) {
114 this[prop] = update({}, this[prop],
115 iter([util.camelCase(k), value[k]]
116 for (k in value)).toObject());
128 ["bookmarkcache", "bookmarkcache"],
146 ["promises", "Promise", "Task", "promises"],
170 loadStyles: function loadStyles(force) {
171 highlight.styleableChrome = this.styleableChrome;
173 highlight.loadCSS(this.CSS.replace(/__MSG_(.*?)__/g,
175 highlight.loadCSS(this.helpCSS.replace(/__MSG_(.*?)__/g,
178 let hl = highlight.set("Find", "");
179 hl.onChange = function () {
180 function hex(val) ("#" + util.regexp.iterate(/\d+/g, val)
181 .map(num => ("0" + Number(num).toString(16)).slice(-2))
185 let elem = services.appShell.hiddenDOMWindow.document.createElement("div");
186 elem.style.cssText = this.cssText;
188 let keys = iter(Styles.propertyIter(this.cssText)).map(p => p.name).toArray();
189 let bg = keys.some(bind("test", /^background/));
190 let fg = keys.indexOf("color") >= 0;
192 let style = DOM(elem).style;
193 prefs[bg ? "safeSet" : "safeReset"]("ui.textHighlightBackground", hex(style.backgroundColor));
194 prefs[fg ? "safeSet" : "safeReset"]("ui.textHighlightForeground", hex(style.color));
198 get addonID() this.name + "@dactyl.googlecode.com",
200 addon: Class.Memoize(function () {
201 return (JSMLoader.bootstrap || {}).addon ||
202 AddonManager.getAddonByID(this.addonID);
205 get styleableChrome() Object.keys(this.overlays),
208 * The current application locale.
210 appLocale: Class.Memoize(() => services.chromeRegistry.getSelectedLocale("global")),
213 * The current dactyl locale.
215 locale: Class.Memoize(function () this.bestLocale(this.locales)),
218 * The current application locale.
220 locales: Class.Memoize(function () {
221 // TODO: Merge with completion.file code.
222 function getDir(str) str.match(/^(?:.*[\/\\])?/)[0];
224 let uri = "resource://dactyl-locale/";
225 let jar = io.isJarURL(uri);
227 let prefix = getDir(jar.JAREntry);
228 var res = iter(s.slice(prefix.length).replace(/\/.*/, "")
229 for (s in io.listJar(jar.JARFile, prefix)))
233 res = array(f.leafName
234 // Fails on FF3: for (f in util.getFile(uri).iterDirectory())
235 for (f in values(util.getFile(uri).readDirectory()))
236 if (f.isDirectory())).array;
239 let exists = function exists(pkg) services["resource:"].hasSubstitution("dactyl-locale-" + pkg);
241 return array.uniq([this.appLocale, this.appLocale.replace(/-.*/, "")]
247 * Returns the best locale match to the current locale from a list
248 * of available locales.
250 * @param {[string]} list A list of available locales
253 bestLocale: function (list) {
254 return values([this.appLocale, this.appLocale.replace(/-.*/, ""),
255 "en", "en-US", list[0]])
256 .find(bind("has", RealSet(list)));
260 * A list of all known registered chrome and resource packages.
262 get chromePackages() {
265 function process(manifest) {
266 for (let line of manifest.split(/\n+/)) {
267 let match = /^\s*(content|skin|locale|resource)\s+([^\s#]+)\s/.exec(line);
269 res[match[2]] = true;
272 function processJar(file) {
273 let jar = services.ZipReader(file.file);
276 if (jar.hasEntry("chrome.manifest"))
277 process(File.readStream(jar.getInputStream("chrome.manifest")));
284 for (let dir of ["UChrm", "AChrom"]) {
285 dir = File(services.directory.get(dir, Ci.nsIFile));
286 if (dir.exists() && dir.isDirectory())
287 for (let file in dir.iterDirectory())
288 if (/\.manifest$/.test(file.leafName))
289 process(file.read());
291 dir = File(dir.parent);
292 if (dir.exists() && dir.isDirectory())
293 for (let file in dir.iterDirectory())
294 if (/\.jar$/.test(file.leafName))
297 dir = dir.child("extensions");
298 if (dir.exists() && dir.isDirectory())
299 for (let ext in dir.iterDirectory()) {
300 if (/\.xpi$/.test(ext.leafName))
304 ext = File(ext.read().replace(/\n*$/, ""));
305 let mf = ext.child("chrome.manifest");
311 return Object.keys(res).sort();
315 * Returns true if the current Gecko runtime is of the given version
318 * @param {string} min The minimum required version. @optional
319 * @param {string} max The maximum required version. @optional
322 haveGecko: function (min, max) let ({ compare } = services.versionCompare,
323 { platformVersion } = services.runtime)
324 (min == null || compare(platformVersion, min) >= 0) &&
325 (max == null || compare(platformVersion, max) < 0),
327 /** Dactyl's notion of the current operating system platform. */
329 _arch: services.runtime.OS,
331 * @property {string} The normalised name of the OS. This is one of
332 * "Windows", "Mac OS X" or "Unix".
334 get name() this.isWindows ? "Windows" : this.isMacOSX ? "Mac OS X" : "Unix",
335 /** @property {boolean} True if the OS is Windows. */
336 get isWindows() this._arch == "WINNT",
337 /** @property {boolean} True if the OS is Mac OS X. */
338 get isMacOSX() this._arch == "Darwin",
339 /** @property {boolean} True if the OS is some other *nix variant. */
340 get isUnix() !this.isWindows,
341 /** @property {RegExp} A RegExp which matches illegal characters in path components. */
342 get illegalCharacters() this.isWindows ? /[<>:"/\\|?*\x00-\x1f]/g : /[\/\x00]/g,
344 get pathListSep() this.isWindows ? ";" : ":"
348 * @property {string} The pathname of the VCS repository clone's root
349 * directory if the application is running from one via an extension
352 VCSPath: Class.Memoize(function () {
353 if (/pre$/.test(this.addon.version)) {
354 let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg");
355 if (uri instanceof Ci.nsIFileURL &&
358 return uri.file.parent.path;
364 * @property {string} The name of the VCS branch that the application is
365 * running from if using an extension proxy file or was built from if
366 * installed as an XPI.
368 branch: Class.Memoize(function () {
370 return io.system(["hg", "-R", this.VCSPath, "branch"]).output;
371 return (/pre-hg\d+-(\S*)/.exec(this.version) || [])[1];
374 /** @property {string} The name of the current user profile. */
375 profileName: Class.Memoize(function () {
376 // NOTE: services.profile.selectedProfile.name doesn't return
377 // what you might expect. It returns the last _actively_ selected
378 // profile (i.e. via the Profile Manager or -P option) rather than the
379 // current profile. These will differ if the current process was run
380 // without explicitly selecting a profile.
382 let dir = services.directory.get("ProfD", Ci.nsIFile);
383 for (let prof in iter(services.profile.profiles))
384 if (prof.QueryInterface(Ci.nsIToolkitProfile).rootDir.path === dir.path)
389 /** @property {string} The Dactyl version string. */
390 version: Class.Memoize(function () {
392 return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
393 "--template=hg{rev}-{branch}"]).output;
395 return this.addon.version;
398 buildDate: Class.Memoize(function () {
400 return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
401 "--template={date|isodate}"]).output;
402 if ("@DATE@" !== "@" + "DATE@")
403 return _("dactyl.created", "@DATE@");
406 get fileExt() this.name.slice(0, -6),
408 dtd: Class.Memoize(function ()
410 (["dactyl." + k, v] for ([k, v] in iter(config.dtdDactyl))),
411 (["dactyl." + s, config[s]] for (s of config.dtdStrings)))
415 get name() config.name,
416 get home() "http://5digits.org/",
417 get apphome() this.home + this.name,
418 code: "http://code.google.com/p/dactyl/",
419 get issues() this.home + "bug/" + this.name,
420 get plugins() "http://5digits.org/" + this.name + "/plugins",
421 get faq() this.home + this.name + "/faq",
423 "list.mailto": Class.Memoize(() => config.name + "@googlegroups.com"),
424 "list.href": Class.Memoize(() => "http://groups.google.com/group/" + config.name),
426 "hg.latest": Class.Memoize(function () this.code + "source/browse/"), // XXX
427 "irc": "irc://irc.oftc.net/#pentadactyl"
431 "xmlns.dactyl": "http://vimperator.org/namespaces/liberator",
432 "xmlns.html": "http://www.w3.org/1999/xhtml",
433 "xmlns.xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
435 "tag.command-line": ["link", { xmlns: "dactyl", topic: "command-line" }, "command line"],
436 "tag.status-line": ["link", { xmlns: "dactyl", topic: "status-line" }, "status line"],
437 "mode.command-line": ["link", { xmlns: "dactyl", topic: "command-line-mode" }, "Command Line"]
450 helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Dense|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/,
451 styleHelp: function styleHelp() {
452 if (!this.helpStyled) {
453 for (let k in keys(highlight.loaded))
454 if (this.helpStyles.test(k))
455 highlight.loaded[k] = true;
460 Local: function Local(dactyl, modules, { document, window }) ({
461 init: function init() {
462 this.loadConfig(document.documentURI);
465 ["menupopup", { id: "viewSidebarMenu", xmlns: "xul" }],
466 ["broadcasterset", { id: "mainBroadcasterSet", xmlns: "xul" }]];
468 for (let [id, [name, key, uri]] in Iterator(this.sidebars)) {
470 ["menuitem", { observes: "pentadactyl-" + id + "Sidebar", label: name,
473 ["broadcaster", { id: "pentadactyl-" + id + "Sidebar", autoCheck: "false",
474 type: "checkbox", group: "sidebar", sidebartitle: name,
476 oncommand: "toggleSidebar(this.id || this.observes);" }]);
479 util.overlayWindow(window, { append: append });
484 get document() document,
487 get commandContainer() document.documentElement.id
490 browser: Class.Memoize(() => window.gBrowser),
491 tabbrowser: Class.Memoize(() => window.gBrowser),
493 get browserModes() [modules.modes.NORMAL],
496 * @property {string} The ID of the application's main XUL window.
498 mainWindowId: document.documentElement.id,
501 * @property {number} The height (px) that is available to the output
504 get outputHeight() this.browser.mPanelContainer.boxObject.height,
506 tabStrip: Class.Memoize(function () document.getElementById("TabsToolbar") || this.tabbrowser.mTabContainer)
510 * @property {Object} A mapping of names and descriptions
511 * of the autocommands available in this application. Primarily used
512 * for completion results.
517 * @property {Object} A map of :command-complete option values to completer
523 * @property {Object} Application specific defaults for option values. The
524 * property names must be the options' canonical names, and the values
525 * must be strings as entered via :set.
532 * @property {Object} A map of dialogs available via the
533 * :dialog command. Property names map dialog names to an array
534 * with the following elements:
535 * [0] description - A description of the dialog, used in
536 * command completion results for :dialog.
537 * [1] action - The function executed by :dialog.
538 * [2] test - Function which returns true if the dialog is available in
539 * the current window. @optional
544 * @property {set} A list of features available in this
545 * application. Used extensively in feature test macros. Use
546 * dactyl.has(feature) to check for a feature's presence
549 features: RealSet(["default-theme"]),
552 * @property {string} The file extension used for command script files.
553 * This is the name string sans "dactyl".
555 get fileExtension() this.name.slice(0, -6),
560 * @property {string} The name of the application that hosts the
561 * extension. E.g., "Firefox" or "XULRunner".
566 * @property {string} The name of the extension.
572 * @property {[string]} A list of extra scripts in the dactyl or
573 * application namespaces which should be loaded before dactyl
582 * @property {string} The default highlighting rules.
583 * See {@link Highlights#loadCSS} for details.
585 CSS: Class.Memoize(() => File.readURL("resource://dactyl-skin/global-styles.css")),
587 helpCSS: Class.Memoize(() => File.readURL("resource://dactyl-skin/help-styles.css"))
590 JSMLoader.loadSubScript("resource://dactyl-local-content/config.js", this);
592 config.INIT = update(Object.create(config.INIT), config.INIT, {
593 init: function init(dactyl, modules, window) {
594 init.superapply(this, arguments);
596 let img = new window.Image;
597 img.src = this.logo || "resource://dactyl-local-content/logo.png";
598 img.onload = util.wrapCallback(function () {
599 highlight.loadCSS(literal(/*
601 display: inline-block;
602 background: url({src});
606 */).replace(/\{(.*?)\}/g, (m, m1) => img[m1]));
611 load: function load(dactyl, modules, window) {
612 load.superapply(this, arguments);
614 this.timeout(function () {
615 if (this.branch && this.branch !== "default" &&
616 modules.yes_i_know_i_should_not_report_errors_in_these_branches_thanks.indexOf(this.branch) === -1)
617 dactyl.warn(_("warn.notDefaultBranch", config.appName, this.branch));
624 // catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
626 // vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: