whitespace
[conkeror.git] / components / application.js
blob241b280b29b7920b4fb470c9eba67fb05b10148a
1 /**
2  * (C) Copyright 2007,2010 John J. Foerch
3  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7 **/
9 const Cc = Components.classes;
10 const Ci = Components.interfaces;
11 const Cr = Components.results;
12 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
14 function application () {
15     Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", this);
17     this.wrappedJSObject = this;
18     this.conkeror = this;
20     this.load_url = this.subscript_loader.loadSubScript;
21     this.loading_urls = [];
22     this.loading_paths = [];
23     this.loading_modules = [];
24     this.loading_features = [];
25     this.features = {};
26     this.load_paths = [this.module_uri_prefix,
27                        this.module_uri_prefix+'extensions',
28                        this.module_uri_prefix+'page-modes'];
29     this.after_load_functions = {};
30     this.pending_loads = [];
32     this.module_assert_conflict_error.prototype = Error.prototype;
34     // clear the startup-cache so that modules and the user's rc are
35     // loaded from disk, not from a cache.  this problem is a
36     // characteristic of using mozIJSSubScriptLoader.loadSubScript as our
37     // primary means of loading, since XULRunner 8.0.
38     var obs = Cc["@mozilla.org/observer-service;1"]
39         .getService(Ci.nsIObserverService);
40     obs.notifyObservers(null, "startupcache-invalidate", null);
42     try {
43         this.require("conkeror.js", null);
44     } catch (e) {
45         this.dumpln("Error initializing.");
46         this.dump_error(e);
47     }
49 application.prototype = {
50     constructor: application,
51     Cc: Cc,
52     Ci: Ci,
53     Cr: Cr,
54     /* Note: resource://app currently doesn't result in xpcnativewrappers=yes */
55     module_uri_prefix: "chrome://conkeror/content/",
56     subscript_loader: Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader),
57     preferences: Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService),
58     get version () {
59         var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]
60             .getService(Ci.nsIURLFormatter);
61         return formatter.formatURL("%VERSION%");
62     },
63     dumpln: function (str) {
64         dump(str);
65         dump("\n");
66     },
67     dump_error: function (e) {
68         if (e instanceof Error) {
69             this.dumpln(e.name + ": " + e.message);
70             this.dumpln(e.fileName + ":" + e.lineNumber);
71             dump(e.stack);
72         } else if (e instanceof Ci.nsIException) {
73             this.dumpln(e.name + ": " + e.message);
74             var stack_frame = e.location;
75             while (stack_frame) {
76                 this.dumpln(stack_frame.name + "()@" + stack_frame.filename + ":" + stack_frame.lineNumber);
77                 stack_frame = stack_frame.caller;
78             }
79         } else {
80             this.dumpln("Error: " + e);
81         }
82     },
84     make_uri: function (uri, charset, base_uri) {
85         const io_service = Cc["@mozilla.org/network/io-service;1"]
86             .getService(Ci.nsIIOService2);
87         if (uri instanceof Ci.nsIURI)
88             return uri;
89         if (uri instanceof Ci.nsIFile)
90             return io_service.newFileURI(uri);
91         return io_service.newURI(uri, charset, base_uri);
92     },
93     skip_module_load: {},
94     load: function (module, as) {
95         var conkeror = this;
96         function module_scope () {
97             this.__proto__ = conkeror;
98             this.conkeror = conkeror;
99         }
100         function load1 (url, scope, path, as) {
101             var success;
102             try {
103                 this.loading_paths.unshift(path);
104                 this.loading_urls.unshift(url);
105                 this.loading_modules.unshift(as);
106                 this.loading_features.unshift({});
107                 if (this.loading_urls.indexOf(url, 1) != -1)
108                     throw new Error("Circular module dependency detected: "+
109                                     this.loading_urls.join(",\n"));
110                 this.load_url(url, scope);
111                 success = true;
112                 if (as)
113                     this[as] = scope;
114                 // call-after-load callbacks
115                 for (let f in this.loading_features[0]) {
116                     this.features[f] = true;
117                     this.run_after_load_functions(f);
118                 }
119             } finally {
120                 this.loading_paths.shift();
121                 this.loading_urls.shift();
122                 this.loading_modules.shift();
123                 this.loading_features.shift();
124             }
125             // do pending loads
126             if (success && this.loading_urls[0] === undefined) {
127                 let pending = this.pending_loads;
128                 this.pending_loads = [];
129                 for (let i = 0, m; m = pending[i]; ++i) {
130                     this.require(m[0], m[1]);
131                 }
132             }
133         }
134         var scope = as;
135         if (as == null)
136             scope = this;
137         else if (typeof as == 'string')
138             scope = new module_scope();
139         else
140             as = null;
141         var path, url;
142         if (module instanceof Ci.nsIURI)
143             path = module.spec.substr(0, module.spec.lastIndexOf('/')+1);
144         else if (module instanceof Ci.nsIFile)
145             path = module.parent.path;
146         var restarted = false;
147         if (path !== undefined) {
148             url = this.make_uri(module).spec;
149             do {
150                 try {
151                     load1.call(this, url, scope, path, as);
152                     return;
153                 } catch (e if e instanceof this.module_assert_error) {
154                     if (restarted)
155                         throw new this.module_assert_conflict_error(url);
156                     as = e.module;
157                     if (e.module)
158                         scope = new module_scope();
159                     else
160                         scope = this;
161                     restarted = true;
162                 } catch (e if e == this.skip_module_load) {
163                     return;
164                 }
165             } while (restarted);
166         } else {
167             // module name or relative path
168             var autoext = module.substr(-3) != '.js';
169             var suffix = false;
170             var i = -1;
171             var tried = {};
172             path = this.loading_paths[0];
173             if (path === undefined)
174                 path = this.load_paths[++i];
175             while (path !== undefined) {
176                 let truepath = path;
177                 try {
178                     let sep = path.substr(-1) == '/' ? '' : '/';
179                     url = path + sep + module + (suffix ? '.js' : '');
180                     let si = module.lastIndexOf('/');
181                     if (si > -1)
182                         truepath += module.substr(0, si);
183                     if (! tried[url] || tried[url] !== scope) {
184                         tried[url] = scope;
185                         load1.call(this, url, scope, truepath, as);
186                         return;
187                     }
188                 } catch (e if e instanceof this.module_assert_error) {
189                     if (restarted)
190                         throw new this.module_assert_conflict_error(url);
191                     as = e.module;
192                     if (e.module)
193                         scope = new module_scope();
194                     else
195                         scope = this;
196                     restarted = true;
197                     continue;
198                 } catch (e if (typeof e == 'string' &&
199                                {"ContentLength not available (not a local URL?)":true,
200                                 "Error creating channel (invalid URL scheme?)":true,
201                                 "Error opening input stream (invalid filename?)":true}
202                                [e])) {
203                     // null op. (suppress error, try next path)
204                 } catch (e if e == this.skip_module_load) {
205                     return;
206                 }
207                 if (autoext)
208                     suffix = !suffix;
209                 if (! suffix)
210                     path = this.load_paths[++i];
211             }
212             throw new Error("Module not found ("+module+")");
213         }
214     },
215     module_assert_error: function (module) {
216         this.module = module;
217     },
218     module_assert_conflict_error: function (url) { //subclass of Error
219         this.name = "module_assert_conflict_error";
220         this.message = "Conflicting in_module calls";
221         this.fileName = url;
222         this.lineNumber = '';
223     },
224     in_module: function (module) {
225         if (module != this.loading_modules[0])
226             throw new this.module_assert_error(module);
227     },
228     provide: function (symbol) {
229         if (! symbol)
230             throw new Error("Cannot provide null feature");
231         if (this.loading_urls[0] === undefined) {
232             this.features[symbol] = true;
233             this.run_after_load_functions(symbol);
234         } else
235             this.loading_features[0][symbol] = true;
236     },
237     featurep: function (symbol) {
238         return this.features[symbol] || false;
239     },
240     call_after_load: function (feature, func) {
241         if (this.featurep(feature))
242             func();
243         else {
244             var funcs = this.after_load_functions[feature];
245             if (! funcs)
246                 funcs = this.after_load_functions[feature] = [];
247             funcs.push(func);
248         }
249     },
250     run_after_load_functions: function (symbol) {
251         var funcs = this.after_load_functions[symbol];
252         if (funcs) {
253             delete this.after_load_functions[symbol];
254             for (var i = 0; funcs[i]; ++i) {
255                 try {
256                     funcs[i]();
257                 } catch (e) {
258                     dump_error(e);
259                 }
260             }
261         }
262     },
263     require: function (module, as) {
264         var feature = as;
265         if (! feature) {
266             if (module instanceof Ci.nsIURI)
267                 feature = module.spec.substr(module.spec.lastIndexOf('/')+1);
268             else if (module instanceof Ci.nsIFile)
269                 feature = module.leafName;
270             else
271                 feature = module.substr(module.lastIndexOf('/')+1);
272             let dot = feature.indexOf('.');
273             if (dot == 0)
274                 return false;
275             if (dot > 0)
276                 feature = feature.substr(0, dot);
277             feature = feature.replace('_', '-', 'g');
278         }
279         if (this.featurep(feature))
280             return true;
281         if (as === undefined)
282             as = feature.replace('-', '_', 'g');
283         try {
284             // ensure current path is not searched for 'require'
285             this.loading_paths.unshift(undefined);
286             this.load(module, as);
287         } finally {
288             this.loading_paths.shift();
289         }
290         return true;
291     },
292     require_later: function (module, as) {
293         this.pending_loads.push([module, as]);
294     },
296     /* nsISupports */
297     QueryInterface: XPCOMUtils.generateQI([]),
299     /* XPCOM registration */
300     classDescription: "Conkeror global object",
301     classID: Components.ID("{72a7eea7-a894-47ec-93a9-a7bc172cf1ac}"),
302     contractID: "@conkeror.mozdev.org/application;1"
305 if (XPCOMUtils.generateNSGetFactory)
306     var NSGetFactory = XPCOMUtils.generateNSGetFactory([application]); //XULRunner 2.0
307 else
308     var NSGetModule = XPCOMUtils.generateNSGetModule([application]);