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"
+ }
+}