diff --git a/README.md b/README.md new file mode 100644 index 0000000..69b92a4 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## The App + +Firefox.html is a reimplementation of Firefox Desktop UI in HTML. +The goal is to close the gap between B2G and Firefox Desktop. Maybe +one day it will be possible to render it with Servo (as it doesn't +require XUL). + +Firefox.html is a HTML app (like any B2G app). It uses the Browser API +(https://developer.mozilla.org/en-US/docs/DOM/Using_the_Browser_API). +No XUL is used at all. + +Current work aims to re-implement as many features of Firefox Desktop +as possible. + +## The Runtime + +Firefox.html requires a runtime: a special build of Gecko I call "htmlrunner". +See: https://github.com/paulrouget/gecko-dev/tree/htmlrunner + +It uses a `xul:window` that loads the Firefox.html app into an iframe. The +`xul:window` is still required to build a native window (draw in title bar, +support opening animations, native colors, ...). Eventually, I want to kill +this window and bring these native features to HTML. This window is nothing +but a window with window controls (close,minimize,maximize) and a background +with the right color. diff --git a/css/layout.css b/css/layout.css new file mode 100644 index 0000000..ea84e00 --- /dev/null +++ b/css/layout.css @@ -0,0 +1,93 @@ +/* Sensible defaults */ + +* { + -moz-appearance: none; + background-color: transparent; + -x-box-sizing: border-box; + box-sizing: content-box; + margin: 0; + padding: 0; + border-width: 0; + font-family: sans-serif; + font-size: 12px; + image-rendering: -moz-crisp-edges; +} + +*::-moz-focus-inner { + border: 0; +} + +html { + height: 100%; +} + +body { + display: flex; + flex-direction: column; + height: 100%; +} + +box, hbox, vbox, spacer { + display: flex; + flex-basis: 0; + white-space: pre; +} + +hbox { + flex-flow: row; +} + +vbox { + flex-flow: column; +} + +[hidden="true"] { + display: none; +} + +[flex="1"] { flex-grow: 1 } +[flex="2"] { flex-grow: 2 } +[flex="3"] { flex-grow: 3 } +[flex="4"] { flex-grow: 4 } +[flex="5"] { flex-grow: 5 } +[flex="6"] { flex-grow: 6 } +[flex="7"] { flex-grow: 7 } +[flex="8"] { flex-grow: 8 } +[flex="9"] { flex-grow: 9 } + +[align="start"] { align-items: flex-start } +[align="center"] { align-items: center } +[align="end"] { align-items: flex-end } +[align="baseline"] { align-items: baseline } +[align="stretch"] { align-items: stretch } + +[pack="start"] { justify-content: flex-start } +[pack="center"] { justify-content: center } +[pack="end"] { justify-content: flex-end } + +/* DEBUG */ +.debug * { + margin: 2px; + padding: 2px; + outline: 1px dashed black; + position: relative; +} +.debug hbox { + background: red; +} +.debug vbox { + background: blue; +} +.debug spacer, +.debug box{ + background: yellow; +} +.debug hbox:before, +.debug vbox:before, +.debug box:before { + position: absolute; + font-size: 10px; + top: 0; left: 0; + display: block; + content: attr(class); +} diff --git a/css/theme.css b/css/theme.css new file mode 100644 index 0000000..7850698 --- /dev/null +++ b/css/theme.css @@ -0,0 +1,220 @@ +:root { + --toolbarbutton-hover-background-image: linear-gradient(hsla(0,0%,100%,.7), hsla(0,0%,100%,.2)), + linear-gradient(hsla(0,0%,100%,.3), hsla(0,0%,100%,.3)); + --toolbarbutton-hover-boxshadow: 0 1px 0 hsla(0,0%,100%,.3) inset, 0 0 0 1px hsla(0,0%,100%,.2) inset, 0 1px 0 hsla(0,0%,0%,.03); + --toolbarbutton-hover-bordercolor: rgb(154,154,154); + + --toolbarbutton-active-boxshadow: 0 1px 1px hsla(0,0%,0%,.1) inset, 0 0 1px hsla(0,0%,0%,.3) inset; + --toolbarbutton-active-bordercolor: rgb(154,154,154); + --toolbarbutton-active-background-image: linear-gradient(hsla(0,0%,100%,.7), hsla(0,0%,100%,.4)), + linear-gradient(rgba(154,154,154,.5),rgba(154,154,154,.5)); + --hwd-pixel: 1px; + --radius: 3px; +} + +@media (min-resolution: 2dppx) { + :root { + --hwd-pixel: 0.5px; + } +} + +/* tabs */ + +.tabstrip { + padding: 0 28px 0 10px; + margin-bottom: calc(-1 * var(--hwd-pixel)); + z-index: 999; +} + +[os="osx"] .tabstrip { + padding-left: 70px; +} + +.tab { + -moz-user-select: none; + cursor: default; + color: #444; + height: 28px; + border-style: solid; + border-width: 0 28px; + border-image-slice: 0 28 0 28 fill; + border-color: transparent; + margin-right: -25px; + padding-top: 1px; + flex-basis: 120px; + background-clip: border-box; +} + +@media (min-resolution: 2dppx) { + .tab { + border-image-slice: 0 56 0 56 fill; + } +} + + +.tab.selected { + z-index: 1001; + border-image-source: -moz-element(#canvas-tab-selected); +} + +.tab:not(.selected):not(:hover) + .tab:not(.selected):not(:hover):not(:first-of-type) { + background-image: linear-gradient(to top, #AAA, transparent 80%); + background-size: 1px 100%; + background-repeat: no-repeat; + background-position: -14px 0; +} + +.tab:not(.selected):hover { + border-image-source: -moz-element(#canvas-tab-hover); +} + +.favicon, .throbber { + width: 16px; + height: 16px; + margin: 0 3px 0 -6px; +} + +.favicon:not([src]) { + display: none; +} + +.throbber { + background-image: url(../images/shared/loading.png); + background-size: 16px 16px; +} + +@media (min-resolution: 2dppx) { + .throbber { + background-image: url(../images/shared/loading@2x.png); + } +} + + +.tab[data-loading="true"] > .favicon, +.tab:not([data-loading="true"]) > .throbber { + display: none; +} + +.tab > .title { + flex-grow: 1; + width: 0; + text-overflow: ellipsis; + overflow-x: hidden; + z-index: 1000; +} + +.tab > .close-button { + width: 16px; + height: 16px; + background-color: transparent; + background-image: url(../images/shared/close.png); + background-size: 64px 16px;; + background-position: 0 0; + margin: 0 -6px 0 3px; +} + +@media (min-resolution: 2dppx) { + .tab > .close-button { + background-image: url(../images/shared/close@2x.png); + } +} + +.tab > .close-button:hover { + background-position: -16px 0; +} + +.tab > .close-button:hover:active { + background-position: -32px 0; +} + +/* navbar */ + +.navbar { + padding: 4px 6px; + background-image: linear-gradient(#EDEDED, #D3D3D3); + border-style: solid; + border-width: var(--hwd-pixel) 0; + border-color: #AAA; +} + +.navbar > button { + width: 18px; + min-width: 18px; + height: 18px; + padding: 2px 4px; + border: var(--hwd-pixel) solid transparent; + background-origin: content-box, border-box, border-box; + background-clip: content-box, border-box, border-box; + background-image: url(../images/shared/Toolbar.png); + background-size: 756px 72px; + border-radius: var(--radius); +} + +.navbar > button[disabled] { + pointer-events: none; + opacity: 0.4; +} + +.navbar > button:hover { + background-image: url(../images/shared/Toolbar.png), var(--toolbarbutton-hover-background-image); + border-color: var(--toolbarbutton-hover-bordercolor); + box-shadow: var(--toolbarbutton-hover-boxshadow); +} + +.navbar > button:hover:active { + background-image: url(../images/shared/Toolbar.png), var(--toolbarbutton-active-background-image); + border-color: var(--toolbarbutton-active-bordercolor); + box-shadow: var(--toolbarbutton-active-boxshadow); +} + +@media (min-resolution: 2dppx) { + .navbar > button { + background-image: url(../images/shared/Toolbar@2x.png); + } + .navbar > button:hover { + background-image: url(../images/shared/Toolbar@2x.png), var(--toolbarbutton-hover-background-image); + } + .navbar > button:hover:active { + background-image: url(../images/shared/Toolbar@2x.png), var(--toolbarbutton-active-background-image); + } +} + + +body[data-loading="true"] .reload-button, +body:not([data-loading="true"]) .stop-button { + display: none; +} + +.back-button { background-position: -36px 0px; } +.forward-button { background-position: -54px 0px; } +.reload-button { background-position: -73px 0px; } +.stop-button { background-position: -90px 0px; } +.menu-button { background-position: -468px 0px; } + +.urlbar, .searchbar { + background-color: white; + border-radius: var(--radius); + margin: 4px 6px 3px; + padding: 0 6px 0 24px; + border-style: solid; + border-color: #B0AEB0; + border-bottom-width: 1px; +} + +.urlbar.focus, .searchbar.focus { + border-color: white; + box-shadow: 0 0 0 3px #91B8E3; +} + +.urlinput, .searchinput { + font: -moz-field; + font-size: 12px; + line-height: 22px; + vertical-align: bottom; +} + +/* iframes */ + +body:not([os="osx"]) .iframes { + background-color: white; +} diff --git a/images/shared/Toolbar.png b/images/shared/Toolbar.png new file mode 100644 index 0000000..82f3bad Binary files /dev/null and b/images/shared/Toolbar.png differ diff --git a/images/shared/Toolbar@2x.png b/images/shared/Toolbar@2x.png new file mode 100644 index 0000000..e48e45d Binary files /dev/null and b/images/shared/Toolbar@2x.png differ diff --git a/images/shared/close.png b/images/shared/close.png new file mode 100644 index 0000000..9bba044 Binary files /dev/null and b/images/shared/close.png differ diff --git a/images/shared/close@2x.png b/images/shared/close@2x.png new file mode 100644 index 0000000..01c5ef4 Binary files /dev/null and b/images/shared/close@2x.png differ diff --git a/images/shared/loading.png b/images/shared/loading.png new file mode 100644 index 0000000..b886c73 Binary files /dev/null and b/images/shared/loading.png differ diff --git a/images/shared/loading@2x.png b/images/shared/loading@2x.png new file mode 100644 index 0000000..ac928c5 Binary files /dev/null and b/images/shared/loading@2x.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..f353f4c --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ + + + + + + tabs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/curvedTabs.js b/js/curvedTabs.js new file mode 100644 index 0000000..74c613c --- /dev/null +++ b/js/curvedTabs.js @@ -0,0 +1,73 @@ +(function drawCurves() { + let navbar = document.querySelector(".navbar"); + let navbarBorderColor = window.getComputedStyle(navbar).borderTopColor + + let c1 = document.createElement("canvas"); + c1.id = "canvas-tab-selected"; + c1.hidden = true; + c1.width = 3 * 28; + c1.height = 28; + drawBackgroundTab(c1,"#FBFBFB","#EDEDED",navbarBorderColor); + document.body.appendChild(c1); + + let c2 = document.createElement("canvas"); + c2.id = "canvas-tab-hover"; + c2.hidden = true; + c2.width = 3 * 28; + c2.height = 28; + drawBackgroundTab(c2,"transparent","transparent",navbarBorderColor); + document.body.appendChild(c2); +})(); + +function drawBackgroundTab(canvas,bg1,bg2,borderColor,shadowColor) { + canvas.width = window.devicePixelRatio * canvas.width; + canvas.height = window.devicePixelRatio * canvas.height; + let ctx = canvas.getContext("2d"); + let r = canvas.height; + ctx.save(); + ctx.beginPath(); + drawCurve(ctx,r); + ctx.lineTo(3 * r, r); + ctx.lineTo(0, r); + ctx.closePath(); + ctx.clip(); + // draw background + let lingrad = ctx.createLinearGradient(0,0,0,r); + lingrad.addColorStop(0, bg1); + lingrad.addColorStop(1, bg2); + ctx.fillStyle = lingrad; + ctx.fillRect(0,0,3*r,r); + if (borderColor) { + ctx.restore(); + ctx.beginPath(); + drawCurve(ctx,r); + ctx.strokeStyle = borderColor; + } + if (shadowColor) { + ctx.shadowColor = shadowColor; + ctx.shadowBlur = 0; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 1; + } + if (borderColor || shadowColor) { + ctx.stroke(); + } +} + +function drawCurve(ctx,r) { + ctx.moveTo(r * 0, r * 0.975); + ctx.bezierCurveTo(r * 0.27082458, r * 0.95840561, + r * 0.3853096, r * 0.81970962, + r * 0.43499998, r * 0.5625); + ctx.bezierCurveTo(r * 0.46819998, r * 0.3905, + r * 0.485, r * 0.0659, + r * 0.95, r * 0.0); + ctx.lineTo(r + r * 1.05, r * 0.0); + ctx.bezierCurveTo(3 * r - r * 0.485, r * 0.0659, + 3 * r - r * 0.46819998, r * 0.3905, + 3 * r - r * 0.43499998, r * 0.5625); + ctx.bezierCurveTo(3 * r - r * 0.3853096, r * 0.81970962, + 3 * r - r * 0.27082458, r * 0.95840561, + 3 * r - r * 0, r * 0.975); +} + diff --git a/js/gBrowser.js b/js/gBrowser.js new file mode 100644 index 0000000..438541f --- /dev/null +++ b/js/gBrowser.js @@ -0,0 +1,399 @@ + +// Operating System detection + +window.IS_PRIVILEGED = !!HTMLIFrameElement.prototype.setVisible; + +if (navigator.appVersion.indexOf("Win") >= 0) { + window.OS = "windows"; + document.body.setAttribute("os", "windows"); +} +if (navigator.appVersion.indexOf("Mac") >= 0) { + window.OS = "osx"; + document.body.setAttribute("os", "osx"); +} +if (navigator.appVersion.indexOf("X11") >= 0) { + window.OS = "linux"; + document.body.setAttribute("os", "linux"); +} + +// Keybdings + +window.addEventListener("keydown", e => { + // console.log(e.keyCode); + let end = () => {e.preventDefault(); e.stopPropagation()}; + + if (e.keyCode == 27) { + Cmds.navigation.stop(); + end(); + } + + if (e.keyCode == 9 && e.ctrlKey && !e.shiftKey) { + Cmds.tabs.selectNextTab(); + end(); + } + if (e.keyCode == 9 && e.ctrlKey && e.shiftKey) { + Cmds.tabs.selectPreviousTab(); + end(); + } + + if (window.OS == "osx") { + if (e.keyCode == 84 && e.metaKey) { + Cmds.tabs.createNewTab(); + end(); + } + if (e.keyCode == 82 && e.metaKey && !e.shiftKey) { + Cmds.navigation.reload(); + end(); + } + if (e.keyCode == 37 && e.metaKey) { + Cmds.navigation.goBack(); + end(); + } + if (e.keyCode == 39 && e.metaKey) { + Cmds.navigation.goForward(); + end(); + } + if (e.keyCode == 76 && e.metaKey) { + Cmds.ui.focusURLBar(); + end(); + } + if (e.keyCode == 75 && e.metaKey) { + Cmds.ui.focusSearchBar(); + end(); + } + } + + if (window.OS == "linux" || window.OS == "windows") { + if (e.keyCode == 84 && e.ctrlKey) { + Cmds.tabs.createNewTab(); + end(); + } + if (e.keyCode == 82 && e.ctrlKey && !e.shiftKey) { + Cmds.navigation.reload(); + end(); + } + if (e.keyCode == 37 && e.altKey) { + Cmds.navigation.goBack(); + end(); + } + if (e.keyCode == 39 && e.altKey) { + Cmds.navigation.goForward(); + end(); + } + } +}); + +// Extend Iframe prototype + +HTMLIFrameElement.prototype.show = function() { + this.removeAttribute("hidden"); + if (IS_PRIVILEGED) { + this.setVisible(true); + } +} +HTMLIFrameElement.prototype.hide = function() { + this.setAttribute("hidden", "true"); + if (IS_PRIVILEGED) { + this.setVisible(false); + } +} + +// gBrowser + +let gBrowser = { + _tabToIframe: new Map(), + _iframeToTab: new Map(), + + getIframeForTab: function(tab) { + return this._tabToIframe.get(tab); + }, + + getTabForIframe: function(iframe) { + return this._iframeToTab.get(iframe); + }, + + get selectedTab() { + return this._selectedTab; + }, + + get selectedIframe() { + return this.getIframeForTab(this.selectedTab); + }, + + isTabSelected: function(tab) { + return this._selectedTab === tab; + }, + + isIframeSelected: function(iframe) { + return this.isTabSelected(this.getTabForIframe(iframe)); + }, + + urlInputChanged: function() { + let urlinput = document.querySelector(".urlinput"); + let text = urlinput.value; + this.selectedIframe.src = text; + }, + + addTab: function(url) { + let hbox = document.createElement("hbox"); + hbox.className = "tab"; + hbox.setAttribute("align", "center"); + hbox.onclick = () => gBrowser.selectTab(hbox); + let throbber = document.createElement("div"); + throbber.className = "throbber"; + let favicon = document.createElement("img"); + favicon.className = "favicon"; + let title = document.createElement("hbox"); + title.className = "title"; + title.textContent = "xxx"; + let button = document.createElement("button"); + button.className = "close-button"; + hbox.appendChild(throbber); + hbox.appendChild(favicon); + hbox.appendChild(title); + hbox.appendChild(button); + document.querySelector(".tabstrip").appendChild(hbox); + let iframe = document.createElement("iframe"); + iframe.setAttribute("mozbrowser", "true"); + iframe.setAttribute("flex", "1"); + iframe.setAttribute("remote", "true"); + if (url && IS_PRIVILEGED) { + iframe.src = url; + } + this.trackIframe(iframe); + // FIXME: don't add iframe if there's no url + document.querySelector(".iframes").appendChild(iframe); + iframe.hide(); + this._tabToIframe.set(hbox, iframe); + this._iframeToTab.set(iframe, hbox); + return hbox; + }, + + selectTab: function(tab) { + if (this.selectedTab) { + let iframe = this.selectedIframe; + iframe.hide(); + this.selectedTab.classList.remove("selected"); + } + let iframe = this.getIframeForTab(tab); + iframe.show(); + iframe.focus(); + tab.classList.add("selected"); + this._selectedTab = tab; + this.selectedIframeChanged("tabselected"); + }, + + selectNextTab: function() { + let sibling = this.selectedTab.nextElementSibling; + if (sibling) { + this.selectTab(sibling); + } + }, + + selectPreviousTab: function() { + if (!this.selectedTab) { + return; + } + let sibling = this.selectedTab.previousElementSibling; + if (sibling) { + this.selectTab(sibling); + } + }, + + closeTab: function(tab) { + let iframe = this.getIframeForTab(tab); + this._tabToIframe.delete(tab); + this._iframeToTab.delete(tab); + iframe.remove(); + tab.remove(); + }, + + updateLoadStatus: function(iframe, loading) { + let tab = this.getTabForIframe(iframe); + tab.dataset.loading = loading; + iframe.dataset.loading = loading; + }, + + updateTitle: function(iframe, title) { + let tab = this.getTabForIframe(iframe); + tab.dataset.title = title; + iframe.dataset.title = title; + tab.querySelector(".title").textContent = title; + }, + + updateLocation: function(iframe, location) { + let tab = this.getTabForIframe(iframe); + tab.dataset.location = location; + iframe.dataset.location = location; + }, + + updateFavicon: function(iframe, faviconURL) { + let tab = this.getTabForIframe(iframe); + tab.dataset.faviconURL = faviconURL; + iframe.dataset.faviconURL = faviconURL; + + let img = tab.querySelector(".favicon"); + console.log("faviconURL:", faviconURL); + if (faviconURL) { + img.src = faviconURL; + } else { + img.removeAttribute("src"); + } + }, + + selectedIframeChanged: function(reason) { + let tab = this.selectedTab; + let iframe = this.selectedIframe; + + document.title = "browser2: " + tab.dataset.title;; + document.body.dataset.loading = tab.dataset.loading; + document.body.dataset.title = tab.dataset.title; + document.body.dataset.faviconURL = tab.dataset.faviconURL; + + if (tab.dataset.location) { + document.body.dataset.location = tab.dataset.location; + } + + /* + let urlinput = document.querySelector(".urlinput"); + let isUrlInputFocus = document.activeElement === urlinput; + if (!reason == "mozbrowserlocationchange" || !isUrlInputFocus) { + urlinput.value = tab.dataset.location || ""; + } + */ + + if (!IS_PRIVILEGED) { + return; + } + + if (reason == "tabselected" || "mozbrowserlocationchange") { + iframe.getCanGoBack().onsuccess = r => { + // Make sure iframe is still selected + if (!this.isIframeSelected(iframe)) { + return; + } + if (r.target.result) { + document.querySelector(".back-button").removeAttribute("disabled"); + } else { + document.querySelector(".back-button").setAttribute("disabled", "true"); + } + } + iframe.getCanGoForward().onsuccess = r => { + // Make sure iframe is still selected + if (!this.isIframeSelected(iframe)) { + return; + } + if (r.target.result) { + document.querySelector(".forward-button").removeAttribute("disabled"); + } else { + document.querySelector(".forward-button").setAttribute("disabled", "true"); + } + } + } + }, + + handleEvent: function(e) { + let iframe = e.target; + let somethingChanged = true; + // console.log("event", e.type); + switch(e.type) { + case "mozbrowserloadstart": + this.updateLoadStatus(iframe, true); + break; + case "mozbrowserloadend": + this.updateLoadStatus(iframe, false); + break; + case "mozbrowsertitlechange": + this.updateTitle(iframe, e.detail); + break; + case "mozbrowserlocationchange": + this.updateLocation(iframe, e.detail); + break; + case "mozbrowsericonchange": + this.updateFavicon(iframe, e.detail.href); + break; + case "mozbrowsererror": + this.updateLoadStatus(iframe, false); + break; + default: + somethingChanged = false; + } + if (somethingChanged && this.isIframeSelected(iframe)) { + this.selectedIframeChanged(e.type); + } + }, + + trackIframe: function(iframe) { + let events = ["mozbrowserasyncscroll", "mozbrowserclose", "mozbrowsercontextmenu", + "mozbrowsererror", "mozbrowsericonchange", "mozbrowserloadend", + "mozbrowserloadstart", "mozbrowserlocationchange", "mozbrowseropenwindow", + "mozbrowsersecuritychange", "mozbrowsershowmodalprompt", "mozbrowsertitlechange", + "mozbrowserusernameandpasswordrequired"]; + for (let eventName of events) { + iframe.addEventListener(eventName, this); + } + }, +} + +const Cmds = { + + navigation: { + + goBack: function() { + gBrowser.selectedIframe.goBack(); + }, + goForward: function() { + gBrowser.selectedIframe.goForward(); + }, + reload: function() { + gBrowser.selectedIframe.reload(); + }, + stop: function() { + gBrowser.selectedIframe.stop(); + }, + }, + + tabs: { + + createNewTab: function(url) { + let tab = gBrowser.addTab(url); + gBrowser.selectTab(tab); + }, + selectNextTab: function() { + gBrowser.selectNextTab(); + }, + selectPreviousTab: function() { + gBrowser.selectPreviousTab(); + }, + }, + + ui: { + focusURLBar: function() { + document.querySelector(".urlinput").focus(); + }, + focusSearchBar: function() { + document.querySelector(".searchinput").focus(); + }, + } +} + +document.querySelector(".back-button").onclick = () => Cmds.navigation.goBack(); +document.querySelector(".forward-button").onclick = () => Cmds.navigation.goForward(); +document.querySelector(".reload-button").onclick = () => Cmds.navigation.reload(); +document.querySelector(".stop-button").onclick = () => Cmds.navigation.stop(); + +let urlbar = document.querySelector(".urlbar"); +let urlinput = document.querySelector(".urlinput"); +urlinput.addEventListener("focus", () => urlbar.classList.add("focus")) +urlinput.addEventListener("blur", () => urlbar.classList.remove("focus")) +urlinput.addEventListener("keypress", (e) => { + if (e.keyCode == 13) { + gBrowser.urlInputChanged() + } +}); +let searchbar = document.querySelector(".searchbar"); +let searchinput = document.querySelector(".searchinput"); +searchinput.addEventListener("focus", () => searchbar.classList.add("focus")) +searchinput.addEventListener("blur", () => searchbar.classList.remove("focus")) + +Cmds.tabs.createNewTab("http://paulrouget.com"); diff --git a/manifest.webapp b/manifest.webapp new file mode 100644 index 0000000..c451505 --- /dev/null +++ b/manifest.webapp @@ -0,0 +1,25 @@ +{ + "name": "Browser2", + "description": "Firefox2", + "launch_path": "/index.html", + "type": "certified", + "role": "system", + "developer": { + "name": "Paul Rouget", + "url": "https://paulrouget.com" + }, + "permissions": { + "browser":{}, + "embed-apps":{}, + "systemXHR":{}, + "settings":{ "access": "readwrite" }, + "geolocation" : {}, + "storage": {}, + "desktop-notification" : {}, + "audio-capture": {}, + "video-capture": {} + }, + "icons": { + "284": "/branding/browser_284.png" + } +}