├── 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 |