├── Readme.md
├── actors.js
├── application.ini
├── bootstrap.js
├── branding
├── brand.dtd
└── brand.properties
├── chrome.manifest
├── firebox.js
├── hidden-window.xul
├── package.json
├── shell.css
├── shell.js
├── shell.xul
├── substitution-protocol.jsm
└── webapp.dtd
/Readme.md:
--------------------------------------------------------------------------------
1 | # Firebox
2 |
3 | Application runtime shared with [Firefox][] that can be used to create
4 | cross platform desktop applications such as [Firefox][] itself using web technologies. Applications packages are just [Firefox OS Packged Apps][FxApps].
5 |
6 |
7 | ## Usage
8 |
9 | Commonly you would install firebox as a `devDependency` of your npm package / application and run it as such:
10 |
11 | ```sh
12 | node ./node_modules/firebox/firebox.js ./manifest.webapp
13 | ```
14 |
15 | Argument passed is a path to an application [manifest](https://developer.mozilla.org/en-US/Apps/Build/Manifest) which is json file with information about the application (follow the link for details).
16 |
17 | Commonly one would also add a `start` script to `package.json` to just
18 | use `npm start` for lunching app.
19 |
20 | Please note that firebox expects to find a nightly build of firefox on your system in order to borrow it's runtime & will fail if it's not installed.
21 |
22 |
23 | It is also possible to just use Firefox [Nightly][] build by pointing it to firebox, although in that case usage is little more complicated:
24 |
25 | ```sh
26 | /Applications/FirefoxNightly.app/Contents/MacOS/firefox -app /path/to/firebox/application.ini /path/to/app/manifest.webapp
27 | ```
28 |
29 | As a matter of fact `firebox.js` script does the same it just finds a Firefox installation on the system.
30 |
31 |
32 | ## Debugging
33 |
34 | During development you may want to use debugger, which is possible by passing additional arguments:
35 |
36 | ```sh
37 | # using node
38 | node ./node_modules/firebox/firebox.js ./manifest.webapp --debugger 6000
39 |
40 | # using firefox
41 | /Applications/FirefoxNightly.app/Contents/MacOS/firefox -app /path/to/firebox/application.ini /path/to/app/manifest.webapp --debugger 6000
42 | ```
43 |
44 | If application was lunched with a `--debugger` flag it will listen on given port (in this case `6000`) to which you can connect via Firefox [WebIDE][].
45 |
46 | ## Examples
47 |
48 | You can check actual [application example][symbiont] to see how all pieces fit together.
49 |
50 |
51 | [Firefox]:https://www.mozilla.org/en-US/firefox/desktop/
52 | [XULRunner]:https://developer.mozilla.org/en-US/docs/Mozilla/Projects/XULRunner
53 | [node-webkit]:https://github.com/rogerwang/node-webkit
54 | [XUL]:https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL
55 | [XPCOM]:https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM
56 | [JSCTypes]:https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes
57 | [Nightly]:https://nightly.mozilla.org/
58 | [WebIDE]:https://developer.mozilla.org/en-US/docs/Tools/WebIDE
59 | [FxApps]:https://developer.mozilla.org/en-US/Marketplace/Options/Packaged_apps
60 | [symbiont]:https://github.com/gozala/symbiont
61 |
--------------------------------------------------------------------------------
/actors.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | 'use strict';
6 |
7 | let { BrowserTabActor, BrowserTabList, allAppShellDOMWindows,
8 | sendShutdownEvent } = require("devtools/server/actors/webbrowser");
9 | let { RootActor } = require("devtools/server/actors/root");
10 | let { DebuggerServer } = require("devtools/server/main");
11 | /**
12 | * WebappRT-specific actors.
13 | */
14 |
15 | /**
16 | * Construct a root actor appropriate for use in a server running in the webapp
17 | * runtime. The returned root actor:
18 | * - respects the factories registered with DebuggerServer.addGlobalActor,
19 | * - uses a WebappTabList to supply tab actors,
20 | * - sends all webapprt:webapp window documents a Debugger:Shutdown event
21 | * when it exits.
22 | *
23 | * * @param connection DebuggerServerConnection
24 | * The conection to the client.
25 | */
26 | function createRootActor(connection)
27 | {
28 | let parameters = {
29 | tabList: new WebappTabList(connection),
30 | globalActorFactories: DebuggerServer.globalActorFactories,
31 | onShutdown: sendShutdownEvent
32 | };
33 | return new RootActor(connection, parameters);
34 | }
35 |
36 | /**
37 | * A live list of BrowserTabActors representing the current webapp windows,
38 | * to be provided to the root actor to answer 'listTabs' requests. In the
39 | * webapp runtime, only a single tab per window is ever present.
40 | *
41 | * @param connection DebuggerServerConnection
42 | * The connection in which this list's tab actors may participate.
43 | *
44 | * @see BrowserTabList for more a extensive description of how tab list objects
45 | * work.
46 | */
47 | function WebappTabList(connection)
48 | {
49 | BrowserTabList.call(this, connection);
50 | }
51 |
52 | WebappTabList.prototype = Object.create(BrowserTabList.prototype);
53 |
54 | WebappTabList.prototype.constructor = WebappTabList;
55 |
56 | WebappTabList.prototype.getList = function() {
57 | let topXULWindow = Services.wm.getMostRecentWindow(this._windowType);
58 |
59 | // As a sanity check, make sure all the actors presently in our map get
60 | // picked up when we iterate over all windows.
61 | let initialMapSize = this._actorByBrowser.size;
62 | let foundCount = 0;
63 |
64 | // To avoid mysterious behavior if windows are closed or opened mid-iteration,
65 | // we update the map first, and then make a second pass over it to yield
66 | // the actors. Thus, the sequence yielded is always a snapshot of the
67 | // actors that were live when we began the iteration.
68 |
69 | // Iterate over all webapprt:webapp XUL windows.
70 | for (let win of allAppShellDOMWindows(this._windowType)) {
71 | let browser = win.document.getElementById("content");
72 | if (!browser) {
73 | continue;
74 | }
75 |
76 | // Do we have an existing actor for this browser? If not, create one.
77 | let actor = this._actorByBrowser.get(browser);
78 | if (actor) {
79 | foundCount++;
80 | } else {
81 | actor = new WebappTabActor(this._connection, browser);
82 | this._actorByBrowser.set(browser, actor);
83 | }
84 |
85 | actor.selected = (win == topXULWindow);
86 | }
87 |
88 | if (this._testing && initialMapSize !== foundCount) {
89 | throw Error("_actorByBrowser map contained actors for dead tabs");
90 | }
91 |
92 | this._mustNotify = true;
93 | this._checkListening();
94 |
95 | return Promise.resolve([actor for ([_, actor] of this._actorByBrowser)]);
96 | };
97 |
98 | /**
99 | * Creates a tab actor for handling requests to the single tab, like
100 | * attaching and detaching. WebappTabActor respects the actor factories
101 | * registered with DebuggerServer.addTabActor.
102 | *
103 | * We override the title of the XUL window in content/webapp.js so here
104 | * we need to override the title property to avoid confusion to the user.
105 | * We won't return the title of the contained browser, but the title of
106 | * the webapp window.
107 | *
108 | * @param connection DebuggerServerConnection
109 | * The conection to the client.
110 | * @param browser browser
111 | * The browser instance that contains this tab.
112 | */
113 | function WebappTabActor(connection, browser)
114 | {
115 | BrowserTabActor.call(this, connection, browser);
116 | }
117 |
118 | WebappTabActor.prototype.constructor = WebappTabActor;
119 |
120 | WebappTabActor.prototype = Object.create(BrowserTabActor.prototype);
121 |
122 | Object.defineProperty(WebappTabActor.prototype, "title", {
123 | get: function() {
124 | return this.browser.contentDocument.title;
125 | },
126 | enumerable: true,
127 | configurable: false
128 | });
129 |
--------------------------------------------------------------------------------
/application.ini:
--------------------------------------------------------------------------------
1 | [App]
2 | Vendor=Mozilla
3 | Name=Firebox
4 | CodeName=Firebox
5 | Version=0.1
6 | BuildID=20141003
7 | SourceRepository=https://github.com/gozala/firebox
8 | ID={e5296581-cf15-cc4f-af28-3067d9ccefea}
9 |
10 | [Gecko]
11 | MinVersion=35.0a1
12 | MaxVersion=*
13 |
14 | [XRE]
15 | EnableProfileMigrator=0
16 | EnableExtensionManager=1
17 |
--------------------------------------------------------------------------------
/bootstrap.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */
4 | "use strict";
5 |
6 | const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm } = Components;
7 | const { require } = Cu.import("resource://gre/modules/commonjs/toolkit/require.js", {});
8 |
9 | const ioService = Cc["@mozilla.org/network/io-service;1"].
10 | getService(Ci.nsIIOService);
11 | const resourceHandler = ioService.getProtocolHandler("resource").
12 | QueryInterface(Ci.nsIResProtocolHandler);
13 | const branch = Cc["@mozilla.org/preferences-service;1"].
14 | getService(Ci.nsIPrefService).
15 | QueryInterface(Ci.nsIPrefBranch2).
16 | getDefaultBranch("");
17 | const uuid = Cc["@mozilla.org/uuid-generator;1"].
18 | getService(Ci.nsIUUIDGenerator);
19 | const AppsService = Cc["@mozilla.org/AppsService;1"].
20 | getService(Ci.nsIAppsService);
21 |
22 |
23 | const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
24 | const { Services } = require("resource://gre/modules/Services.jsm");
25 | const { DOMApplicationRegistry } = require("resource://gre/modules/Webapps.jsm");
26 | const { SubstitutionProtocol } = require("resource://firebox/substitution-protocol.jsm");
27 | const { Task } = require("resource://gre/modules/Task.jsm");
28 | const { OS: {File, Path}} = require("resource://gre/modules/osfile.jsm");
29 |
30 | const appProtocol = new SubstitutionProtocol("app");
31 | appProtocol.register();
32 |
33 | const readCMDArgs = cmdLine => {
34 | let count = cmdLine.length
35 | let args = []
36 | let index = 0
37 | while (index < count) {
38 | args[index] = cmdLine.getArgument(index)
39 | index = index + 1
40 | }
41 | return args
42 | }
43 |
44 | // Utility function that synchronously reads local resource from the given
45 | // `uri` and returns content string.
46 | const readURI = (uri, charset="UTF-8") => {
47 | const channel = ioService.newChannel(uri, charset, null)
48 | const stream = channel.open()
49 | const converter = Cc["@mozilla.org/intl/converter-input-stream;1"].
50 | createInstance(Ci.nsIConverterInputStream)
51 | converter.init(stream, charset, 0, 0)
52 | let buffer = {}
53 | let content = ""
54 | let read = 0
55 | do {
56 | read = converter.readString(0xffffffff, buffer);
57 | content = content + buffer.value;
58 | } while (read != 0)
59 | converter.close()
60 | return content
61 | }
62 |
63 |
64 | const setPrefs = (settings, root, branch) =>
65 | void Object.keys(settings).forEach(id => {
66 | const key = root ? `${root}.${id}` : id
67 | const value = settings[id]
68 | const type = typeof(value)
69 | value === null ? void(0) :
70 | value === undefined ? void(0) :
71 | type === "boolean" ? branch.setBoolPref(key, value) :
72 | type === "string" ? branch.setCharPref(key, value) :
73 | type === "number" ? branch.setIntPref(key, parseInt(value)) :
74 | type === "object" ? setPrefs(value, key, branch) :
75 | void(0)
76 | })
77 |
78 | const onDocumentInserted = (window, resolve) => {
79 | const observerService = Cc["@mozilla.org/observer-service;1"]
80 | .getService(Ci.nsIObserverService)
81 | observerService.addObserver({
82 | observe: function(subject, topic) {
83 | if (subject.defaultView === window) {
84 | //observerService.removeObserver(this, topic)
85 | resolve(subject)
86 | }
87 | }
88 | }, "chrome-document-interactive", false)
89 | }
90 |
91 | const baseURI = Symbol("baseURI");
92 | const manifestURI = Symbol("manifestURI");
93 | const baseName = Symbol("baseName");
94 | const manifestJSON = Symbol("manifestJSON");
95 |
96 | const makeID = () => uuid.generateUUID().toString().replace(/{|}/g, "");
97 |
98 | const makeApp = (uri, manifest) => {
99 | const fileName = uri.substr(uri.lastIndexOf("/") + 1);
100 | const rootURI = uri.substr(0, uri.lastIndexOf("/") + 1);
101 |
102 | const origin = manifest.origin || `app://${makeID()}`;
103 | const id = origin.replace(/^\S+\:\/\//, "");
104 | const localId = id in DOMApplicationRegistry.webapps ?
105 | DOMApplicationRegistry.webapps[id].localId :
106 | DOMApplicationRegistry._nextLocalId();
107 |
108 | return {id, localId, origin,
109 | installOrigin: origin,
110 | removable: false,
111 | basePath: DOMApplicationRegistry.getWebAppsBasePath(),
112 | manifestURL: `${origin}/${fileName}`,
113 | appStatus: Ci.nsIPrincipal.APP_STATUS_CERTIFIED,
114 | receipts: null,
115 | kind: DOMApplicationRegistry.kPackaged,
116 |
117 | [manifestJSON]: manifest,
118 | [baseName]: fileName,
119 | [manifestURI]: uri,
120 | [baseURI]: rootURI}
121 | }
122 |
123 | const installZip = (app) => {
124 | let installDir = DOMApplicationRegistry._getAppDir(app.id);
125 |
126 | let manifestFile = installDir.clone();
127 | manifestFile.append("manifest.webapp");
128 | //File.writeAtomic(manifestFile.path, readURI(app[manifestURI]));
129 |
130 | const fileStream = Cc["@mozilla.org/network/file-output-stream;1"].
131 | createInstance(Ci.nsIFileOutputStream);
132 | fileStream.init(manifestFile, 0x04 | 0x08 | 0x20, null, 0);
133 | const converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
134 | createInstance(Ci.nsIConverterOutputStream);
135 | converter.init(fileStream, "UTF-8", 0, 0);
136 | converter.writeString(JSON.stringify(app[manifestJSON], 2, 2));
137 | converter.close();
138 | }
139 |
140 | const installApp = app => {
141 | appProtocol.setSubstitution(app.id, ioService.newURI(app[baseURI], null, null));
142 |
143 | const install = Object.assign({}, app,
144 | {installTime: Date.now(),
145 | installState: "installed"});
146 |
147 |
148 |
149 | setPrefs({
150 | "security.apps.certified.CSP.default": `default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline' ${app.origin}`,
151 | "network.dns.localDomains": app.origin
152 | }, null, branch)
153 |
154 | DOMApplicationRegistry.webapps[install.id] = install;
155 | DOMApplicationRegistry.updatePermissionsForApp(install.id);
156 |
157 | // Fake first run, which will trigger re-installation of the app.
158 | branch.clearUserPref("gecko.mstone");
159 | }
160 |
161 | const launch = (manifestURI, args) => Task.spawn(function*() {
162 | const {preferences} = JSON.parse(readURI("resource://firebox/package.json"));
163 | setPrefs(preferences, null, branch);
164 |
165 | const manifest = JSON.parse(readURI(manifestURI));
166 | const app = makeApp(manifestURI, manifest);
167 | const launchURI = `${app.origin}${manifest.launch_path}`;
168 | const window = Services.ww.openWindow(null,
169 | "chrome://firebox/content/shell.xul",
170 | "_blank",
171 | "chrome,dialog=no,resizable,scrollbars,centerscreen",
172 | null);
173 |
174 | if (args.indexOf("-debugger") >= 0) {
175 | setPrefs({
176 | "devtools.debugger": {
177 | "remote-enabled": true,
178 | "force-local": true,
179 | "prompt-connection": false
180 | }
181 | }, null, branch)
182 | const port = parseInt(args[args.indexOf("-debugger") + 1])
183 | startDebugger(port)
184 | }
185 |
186 |
187 | onDocumentInserted(window, document => {
188 | dump("document ready")
189 | const root = document.documentElement;
190 | const { appVersion } = window.navigator;
191 | const os = appVersion.contains('Win') ? "windows" :
192 | appVersion.contains("Mac") ? "osx" :
193 | appVersion.contains("X11") ? "linux" :
194 | "Unknown";
195 |
196 |
197 | root.setAttribute("os", os);
198 | root.setAttribute("draw-in-titlebar", true);
199 | root.setAttribute("chromemargin", os == "windows" ?
200 | "0,2,2,2" :
201 | "0,-1,-1,-1");
202 |
203 | const content = document.getElementById("content");
204 | const docShell = content.
205 | contentWindow.
206 | QueryInterface(Ci.nsIInterfaceRequestor).
207 | getInterface(Ci.nsIWebNavigation).
208 | QueryInterface(Ci.nsIDocShell);
209 |
210 | dump(`set frame to be an app: ${app.localId}\n`);
211 | docShell.setIsApp(app.localId);
212 | content.setAttribute("src", launchURI);
213 | });
214 |
215 | yield DOMApplicationRegistry.registryReady;
216 | yield installZip(app);
217 | installApp(app);
218 | });
219 |
220 | const startDebugger = port => {
221 | const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {})
222 | DebuggerServer.init()
223 | DebuggerServer.addBrowserActors("")
224 | DebuggerServer.addActors("chrome://firebox/content/actors.js")
225 |
226 | const listener = DebuggerServer.createListener();
227 | listener.portOrPath = port;
228 | listener.open();
229 | }
230 |
231 | const CommandLineHandler = function() {}
232 | CommandLineHandler.prototype = {
233 | constructor: CommandLineHandler,
234 | classID: Components.ID("{5d8dfc0f-6c5e-2e4a-aa60-0fb8d3f51446}"),
235 | QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
236 | helpInfo : "/path/to/firefox -app /path/to/fierbox /path/to/app-package",
237 | handle: cmdLine => {
238 | const args = readCMDArgs(cmdLine)
239 | const rootURI = cmdLine.resolveURI(args[0]).spec
240 | launch(rootURI, args)
241 | }
242 | }
243 |
244 | const components = [CommandLineHandler];
245 | const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
246 |
--------------------------------------------------------------------------------
/branding/brand.dtd:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/branding/brand.properties:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | brandShortName=Nightly
6 | brandFullName=Nightly
7 | vendorShortName=Mozilla
8 |
9 | syncBrandShortName=Sync
10 |
--------------------------------------------------------------------------------
/chrome.manifest:
--------------------------------------------------------------------------------
1 | locale branding en-US ./branding
2 | content branding ./branding
3 |
4 | # Register chrome://firebox/content
5 | content firebox ./
6 | # Register resource://firebox/
7 | resource firebox ./
8 | # Register command line handler
9 | component {5d8dfc0f-6c5e-2e4a-aa60-0fb8d3f51446} ./bootstrap.js
10 | contract @mozilla.org/firebox/clh;1 {5d8dfc0f-6c5e-2e4a-aa60-0fb8d3f51446}
11 | category command-line-handler x-default @mozilla.org/firebox/clh;1
12 |
--------------------------------------------------------------------------------
/firebox.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Winreg = require("winreg");
4 | var os = require("os");
5 | var Promise = require("es6-promise").Promise;
6 | var spawn = require("child_process").spawn;
7 | var path = require("path");
8 |
9 |
10 | var readRegistry = function(key, field) {
11 | return new Promise(function(resolve, reject) {
12 | var registry = new Winreg({
13 | hive: Winreg.HKLM,
14 | key: key
15 | });
16 |
17 | registry.get(field, function(error, data) {
18 | if (error) {
19 | reject(error)
20 | }
21 | else {
22 | resolve(data.value)
23 | }
24 | });
25 | });
26 | }
27 |
28 | var guessWindowsAppPath = function(root, name) {
29 | var programs = arch === "(64)" ? "ProgramFiles(x86)" :
30 | "ProgramFiles";
31 | return path.join(process.env[programs],
32 | name,
33 | "firefox.exe");
34 | };
35 |
36 | var resolveWindowsAppPath = function(id, arch) {
37 | var root = arch === "(64)" ? "\\Software\\Wow6432Node\\Mozilla" :
38 | "\\Software\\Mozilla\\";
39 | var name = resolveWindowsAppPath.names[id] || id;
40 | return readRegistry(path.join(root, appName),
41 | "CurrentVersion").
42 | then(function(version) {
43 | return readRegistry(path.join(root, version, "Main"),
44 | "PathToExe");
45 | }).catch(guessWindowsAppPath.bind(null, name, arch));
46 | };
47 | resolveWindowsAppPath.names = {
48 | // the default path in the beta installer is the same as the stable one
49 | "beta": "Mozilla Firefox",
50 | "devedition": "DeveloperEdition",
51 | "dev": "DeveloperEdition",
52 | "aurora": "DeveloperEdition",
53 | "nightly": "Nightly",
54 | "firefox": "Mozilla Firefox"
55 | };
56 |
57 | var resolveOSXAppPath = function(app, arch) {
58 | var appPath = resolveOSXAppPath.paths[app.toLowerCase()] ||
59 | app;
60 |
61 | return path.extname(appPath) != ".app" ? appPath :
62 | path.join(appPath, "Contents/MacOS/firefox-bin");
63 | };
64 | resolveOSXAppPath.paths = {
65 | "firefox": "/Applications/Firefox.app/Contents/MacOS/firefox-bin",
66 | "beta": "/Applications/FirefoxBeta.app/Contents/MacOS/firefox-bin",
67 | "dev": "/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin",
68 | "devedition": "/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin",
69 | "dev-edition": "/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin",
70 | "aurora": "/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin",
71 | "nightly": "/Applications/FirefoxNightly.app/Contents/MacOS/firefox-bin",
72 | }
73 |
74 | var resolveLinuxAppPath = function(app, arch) {
75 | var appName = resolveLinuxAppPath.names[app];
76 | var libName = arg == "(64)" ? "lib64" : "lib";
77 |
78 | return appName ? "/usr/" + libName + "/" + appName :
79 | app;
80 | };
81 | resolveLinuxAppPath.names = {
82 | "firefox": "firefox",
83 | "aurora": "firefox-developeredition",
84 | "dev-edition": "firefox-developeredition",
85 | "devedition": "firefox-developeredition",
86 | "dev": "firefox-developeredition",
87 | "nightly": "firefox-nightly",
88 | "beta": "firefox-beta"
89 | };
90 |
91 |
92 | var resolveAppPath = function(app, platform, arch) {
93 | arch = arch || os.arch();
94 | arch = /64/.test(arch) ? "(64)" : "";
95 | platform = platform || process.platform;
96 |
97 | platform = /darwin/i.test(platform) ? "osx" :
98 | /win/i.test(platform) ? "windows" + arch :
99 | /linux/i.test(platform) ? "linux" + arch :
100 | platform;
101 |
102 | var resolvePlatformAppPath = resolveAppPath[platform] ||
103 | resolveAppPath[""];
104 |
105 | return Promise.resolve(resolvePlatformAppPath(app, arch));
106 | }
107 | resolveAppPath["windows"] = resolveWindowsAppPath;
108 | resolveAppPath["osx"] = resolveOSXAppPath;
109 | resolveAppPath["linux"] = resolveLinuxAppPath;
110 | resolveAppPath[""] = function(app) { return app };
111 | exports.resolveAppPath = resolveAppPath;
112 |
113 |
114 | var params = process.argv.slice(process.argv.indexOf(module.filename) + 1)
115 | var app = params.indexOf("--binary") >= 0 ? params[params.indexOf("--binary") + 1] :
116 | "nightly";
117 |
118 | resolveAppPath(app).then(function(binary) {
119 | var args = [
120 | "-app",
121 | require.resolve("./application.ini"),
122 | path.resolve(params[0])
123 | ].concat(params.slice(1));
124 |
125 | var task = spawn(binary, args, {stdio: "inherit"});
126 | });
127 |
--------------------------------------------------------------------------------
/hidden-window.xul:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "firebox",
3 | "description": "SDK on top of Firefox for building desktop apps using HTML/CSS/JS",
4 | "version": "0.2.0",
5 | "keywords": [
6 | "firebox",
7 | "gecko",
8 | "firefox",
9 | "application",
10 | "jetpack",
11 | "mozilla"
12 | ],
13 | "homepage": "https://github.com/Gozala/firebox",
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/Gozala/firebox.git",
17 | "web": "https://github.com/Gozala/firebox"
18 | },
19 | "bugs": {
20 | "url": "http://github.com/Gozala/firebox/issues/"
21 | },
22 | "preferences": {
23 | "browser.tabs.remote.autostart": true,
24 | "browser.chromeURL": "chrome://firebox/content/shell.xul",
25 | "full-screen-api.enabled": true,
26 | "dom.webcomponents.enabled": true,
27 | "browser.dom.window.dump.enabled": true,
28 | "dom.mozBrowserFramesEnabled": true,
29 | "dom.ipc.processCount": 100000,
30 | "dom.webapps.useCurrentProfile": true,
31 | "dom.sysmsg.enabled": true,
32 | "devtools.chrome.enabled": true,
33 | "devtools.apps.forbidden-permissions": "",
34 | "devtools.debugger.forbid-certified-apps": false
35 | },
36 | "licenses": [
37 | {
38 | "type": "MPL",
39 | "version": "2.0",
40 | "url": "http://mozilla.org/MPL/2.0/"
41 | }
42 | ],
43 | "dependencies": {
44 | "es6-promise": "^2.0.1",
45 | "winreg": "0.0.12"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/shell.css:
--------------------------------------------------------------------------------
1 | window {
2 | min-width: 300px;
3 | min-height: 300px;
4 | }
5 |
6 | window[os=unix] {
7 | -moz-appearance: menubar;
8 | }
9 |
10 | window[os=osx] {
11 | -moz-appearance: dialog;
12 | -moz-window-dragging: drag;
13 | }
14 |
15 | window[os=windows] {
16 | -moz-appearance: -moz-win-borderless-glass;
17 | }
18 |
19 |
20 | @media (-moz-mac-yosemite-theme) {
21 | window[os=osx] {
22 | -moz-appearance: -moz-mac-vibrancy-light;
23 | }
24 | }
25 |
26 | window:not([draw-in-titlebar]) #titlebar {
27 | display: none;
28 | }
29 |
30 | window[draw-in-titlebar=true] #titlebar {
31 | -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
32 | -moz-window-dragging: drag;
33 | }
34 |
35 | window[draw-in-titlebar=true][os=osx] #titlebar {
36 | margin-bottom: -39px;
37 | min-height: 39px;
38 | }
39 |
40 | window[draw-in-titlebar=true][os=windows] #titlebar {
41 | margin-bottom: -16px;
42 | }
43 |
44 | #titlebar-buttonbox {
45 | -moz-appearance: -moz-window-button-box;
46 | }
47 |
48 | window[os=osx] #titlebar-buttonbox {
49 | margin-top: 11px;
50 | }
51 |
52 |
53 | window[os=windows] #titlebar-buttonbox-container {
54 | -moz-box-direction: reverse;
55 | }
56 |
57 | window[os=windows] #titlebar-spacer {
58 | pointer-events: none;
59 | }
60 |
61 |
62 | iframe,
63 | browser {
64 | display: -moz-box;
65 | -moz-box-flex: 1;
66 | border-width: 0;
67 | background: transparent;
68 | }
69 |
70 | #init {
71 | margin: 40px;;
72 | padding: 40px;
73 | font: -moz-field;
74 | color: black;
75 | background-color: white;
76 | }
77 |
78 | #init input {
79 | width: 100%;
80 | }
81 |
--------------------------------------------------------------------------------
/shell.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function reload() {
4 | document.querySelector("#content").
5 | webNavigation.
6 | reload(0);
7 | }
8 |
9 | function showContextMenu() {
10 | }
11 |
12 | function hideContextMenu() {
13 | }
14 |
15 | function updateEditUIVisibility() {
16 | }
17 |
18 | function toggleFullScreen() {
19 | if (!document.fullscreenElement && // alternative standard method
20 | !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement ) { // current working methods
21 | if (document.documentElement.requestFullscreen) {
22 | document.documentElement.requestFullscreen();
23 | } else if (document.documentElement.msRequestFullscreen) {
24 | document.documentElement.msRequestFullscreen();
25 | } else if (document.documentElement.mozRequestFullScreen) {
26 | document.documentElement.mozRequestFullScreen();
27 | } else if (document.documentElement.webkitRequestFullscreen) {
28 | document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
29 | }
30 | } else {
31 | if (document.exitFullscreen) {
32 | document.exitFullscreen();
33 | } else if (document.msExitFullscreen) {
34 | document.msExitFullscreen();
35 | } else if (document.mozCancelFullScreen) {
36 | document.mozCancelFullScreen();
37 | } else if (document.webkitExitFullscreen) {
38 | document.webkitExitFullscreen();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/shell.xul:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | %webappDTD;
6 | ]>
7 |
8 |
9 |
10 |
11 |
12 |
13 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
39 |
43 |
46 |
49 |
52 |
55 |
58 |
61 |
64 |
68 |
71 |
74 |
75 |
79 |
80 |
81 |
82 |
83 |
93 |
104 |
166 |
167 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
204 |
205 |
206 |
207 |
213 |
214 |
--------------------------------------------------------------------------------
/substitution-protocol.jsm:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const EXPORTED_SYMBOLS = ["SubstitutionProtocol"];
4 |
5 | const { utils: Cu, classes: Cc, interfaces: Ci, manager: Cm,
6 | results: Cr } = Components;
7 | Cm.QueryInterface(Ci.nsIComponentRegistrar);
8 |
9 | const io = Cc["@mozilla.org/network/io-service;1"]
10 | .getService(Ci.nsIIOService);
11 | const branch = Cc["@mozilla.org/preferences-service;1"].
12 | getService(Ci.nsIPrefService).
13 | QueryInterface(Ci.nsIPrefBranch2).
14 | getBranch("network.protocol.substitutions.");
15 | const childBus = Cc["@mozilla.org/childprocessmessagemanager;1"].
16 | getService(Ci.nsISyncMessageSender);
17 | const parentBus = Cc["@mozilla.org/parentprocessmessagemanager;1"].
18 | getService(Ci.nsIMessageBroadcaster);
19 | const runtime = Cc["@mozilla.org/xre/app-info;1"].
20 | getService(Ci.nsIXULRuntime);
21 | const uuid = Cc["@mozilla.org/uuid-generator;1"].
22 | getService(Ci.nsIUUIDGenerator);
23 |
24 | const securityManager = Cc["@mozilla.org/scriptsecuritymanager;1"].
25 | getService(Ci.nsIScriptSecurityManager);
26 |
27 |
28 | const isParentProcess = runtime.processType == runtime.PROCESS_TYPE_DEFAULT;
29 |
30 | const SubstitutionProtocol = function(scheme, classID=uuid.generateUUID()) {
31 | this.scheme = scheme;
32 | this.contract = `@mozilla.org/network/protocol;1?name=${scheme}`;
33 | this.classID = classID;
34 | }
35 | SubstitutionProtocol.prototype = {
36 | constructor: SubstitutionProtocol,
37 | description: "Custom substitution protocol implemantion",
38 | QueryInterface(iid) {
39 | if (iid.equals(Ci.nsISupports) ||
40 | iid.equals(Ci.nsIProtocolHandler) ||
41 | iid.equals(Ci.nsIResProtocolHandler) ||
42 | iid.equals(Ci.nsIFactory))
43 | {
44 | return this
45 | }
46 |
47 | throw Cr.NS_ERROR_NO_INTERFACE
48 | },
49 |
50 | // nsIResProtocolHandler
51 | getSubstitution(host) {
52 | if (!this.hasSubstitution(host)) {
53 | throw Cr.NS_ERROR_NOT_AVAILABLE;
54 | }
55 |
56 | return io.newURI(branch.getCharPref(`${this.scheme}.${host}`),
57 | null, null);
58 | },
59 | hasSubstitution(host) {
60 | return branch.prefHasUserValue(`${this.scheme}.${host}`);
61 | },
62 | setSubstitution(host, uri) {
63 | if (uri) {
64 | branch.setCharPref(`${this.scheme}.${host}`, uri.spec);
65 | } else {
66 | branch.clearUserPref(`${this.scheme}.${host}`);
67 | }
68 | },
69 | resolveURI({host, path}) {
70 | let uri = this.getSubstitution(host);
71 | if (!uri) {
72 | throw Cr.NS_ERROR_NOT_AVAILABLE;
73 | }
74 | return uri.resolve(path.substr(1));
75 | },
76 |
77 | // nsIProtocolHandler
78 | defaultPort: -1,
79 | protocolFlags: Ci.nsIProtocolHandler.URI_NOAUTH |
80 | Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
81 | Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
82 | Ci.nsIProtocolHandler.URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM,
83 | scheme: null,
84 | newURI(spec, originCharset, baseURI) {
85 | let uri = Cc["@mozilla.org/network/standard-url;1"]
86 | .createInstance(Ci.nsIStandardURL)
87 | uri.init(Ci.nsIStandardURL.URLTYPE_STANDARD,
88 | -1, spec, originCharset, baseURI)
89 | return uri.QueryInterface(Ci.nsIURI)
90 | },
91 |
92 | newChannel(uri) {
93 | let channel = io.newChannel(this.resolveURI(uri), null, null);
94 | channel.QueryInterface(Ci.nsIChannel).originalURI = uri;
95 | // channel.owner = securityManager.getSimpleCodebasePrincipal(this.newURI(`${uri.scheme}://${uri.host}`, null, null));
96 | return channel;
97 | },
98 |
99 | // nsIFactory
100 | createInstance(outer, iid) {
101 | if (outer) {
102 | throw Cr.NS_ERROR_NO_AGGREGATION;
103 | }
104 | return this
105 | },
106 | lockFactory: function(aLock) {
107 | throw Cr.NS_ERROR_NOT_IMPLEMENTED;
108 | },
109 | // component
110 | register() {
111 | Cm.registerFactory(this.classID,
112 | this.description,
113 | this.contract,
114 | this);
115 |
116 | if (isParentProcess) {
117 | parentBus.broadcastAsyncMessage("network/protocol/substitution/register",
118 | this.toJSON());
119 | }
120 | },
121 | unregister() {
122 | branch.deleteBranch(this.scheme);
123 | Cm.unregisterFactory(this.classID, this);
124 |
125 | if (isParentProcess) {
126 | parentBus.broadcastAsyncMessage("network/protocol/substitution/unregister",
127 | this.toJSON());
128 | }
129 | },
130 | toJSON() {
131 | return {
132 | scheme: this.scheme,
133 | contract: this.contract,
134 | classID: this.classID.toString()
135 | }
136 | }
137 | }
138 |
139 | // E10S Propagation.
140 |
141 |
142 | if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
143 | Cc["@mozilla.org/globalmessagemanager;1"].
144 | getService(Ci.nsIMessageListenerManager).
145 | loadFrameScript(`data:,Components.utils.import("${__URI__}", {})`, true);
146 | } else {
147 | childBus.addMessageListener("network/protocol/substitution/register", ({data}) => {
148 | new SubstitutionProtocol(data.scheme, Components.ID(data.classID)).register();
149 | });
150 | childBus.addMessageListener("network/protocol/substitution/unregister", ({data}) => {
151 | let factory = Cm.getClassObjectByContractID(data.contract,
152 | Ci.nsIFactory,
153 | {});
154 | Cm.unregisterFactory(Components.ID(data.classID), factory);
155 | });
156 | }
157 |
--------------------------------------------------------------------------------
/webapp.dtd:
--------------------------------------------------------------------------------
1 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------