summaryrefslogtreecommitdiffstats
path: root/browser/base/content/abouthome
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/abouthome')
-rw-r--r--browser/base/content/abouthome/aboutHome.css454
-rw-r--r--browser/base/content/abouthome/aboutHome.js398
-rw-r--r--browser/base/content/abouthome/aboutHome.xhtml79
-rw-r--r--browser/base/content/abouthome/addons.pngbin0 -> 1444 bytes
-rw-r--r--browser/base/content/abouthome/addons@2x.pngbin0 -> 3783 bytes
-rw-r--r--browser/base/content/abouthome/bookmarks.pngbin0 -> 1276 bytes
-rw-r--r--browser/base/content/abouthome/bookmarks@2x.pngbin0 -> 2946 bytes
-rw-r--r--browser/base/content/abouthome/downloads.pngbin0 -> 898 bytes
-rw-r--r--browser/base/content/abouthome/downloads@2x.pngbin0 -> 2018 bytes
-rw-r--r--browser/base/content/abouthome/history.pngbin0 -> 1654 bytes
-rw-r--r--browser/base/content/abouthome/history@2x.pngbin0 -> 4629 bytes
-rw-r--r--browser/base/content/abouthome/mozilla.pngbin0 -> 2684 bytes
-rw-r--r--browser/base/content/abouthome/mozilla@2x.pngbin0 -> 5647 bytes
-rw-r--r--browser/base/content/abouthome/restore-large.pngbin0 -> 2841 bytes
-rw-r--r--browser/base/content/abouthome/restore-large@2x.pngbin0 -> 7267 bytes
-rw-r--r--browser/base/content/abouthome/restore.pngbin0 -> 1796 bytes
-rw-r--r--browser/base/content/abouthome/restore@2x.pngbin0 -> 4810 bytes
-rw-r--r--browser/base/content/abouthome/settings.pngbin0 -> 1557 bytes
-rw-r--r--browser/base/content/abouthome/settings@2x.pngbin0 -> 3836 bytes
-rw-r--r--browser/base/content/abouthome/snippet1.pngbin0 -> 1470 bytes
-rw-r--r--browser/base/content/abouthome/snippet1@2x.pngbin0 -> 3243 bytes
-rw-r--r--browser/base/content/abouthome/snippet2.pngbin0 -> 3287 bytes
-rw-r--r--browser/base/content/abouthome/snippet2@2x.pngbin0 -> 11027 bytes
-rw-r--r--browser/base/content/abouthome/sync.pngbin0 -> 1879 bytes
-rw-r--r--browser/base/content/abouthome/sync@2x.pngbin0 -> 4615 bytes
25 files changed, 931 insertions, 0 deletions
diff --git a/browser/base/content/abouthome/aboutHome.css b/browser/base/content/abouthome/aboutHome.css
new file mode 100644
index 000000000..c0b02e257
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.css
@@ -0,0 +1,454 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+html {
+ font: message-box;
+ font-size: 100%;
+ background-color: hsl(0,0%,95%);
+ color: #000;
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ width: 100%;
+ height: 100%;
+}
+
+input,
+button {
+ font-size: inherit;
+ font-family: inherit;
+}
+
+a {
+ color: -moz-nativehyperlinktext;
+ text-decoration: none;
+}
+
+.spacer {
+ -moz-box-flex: 1;
+}
+
+#topSection {
+ text-align: center;
+}
+
+#brandLogo {
+ height: 192px;
+ width: 192px;
+ margin: 22px auto 31px;
+ background-image: url("chrome://branding/content/about-logo.png");
+ background-size: 192px auto;
+ background-position: center center;
+ background-repeat: no-repeat;
+}
+
+#searchIconAndTextContainer,
+#snippets {
+ width: 470px;
+}
+
+#searchIconAndTextContainer {
+ display: -moz-box;
+ height: 36px;
+ position: relative;
+}
+
+#searchIcon {
+ border: 1px transparent;
+ padding: 0;
+ margin: 0;
+ width: 36px;
+ height: 36px;
+ background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat;
+ position: absolute;
+}
+
+#searchText {
+ margin-left: 0;
+ -moz-box-flex: 1;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 34px;
+ padding-inline-end: 8px;
+ background: hsla(0,0%,100%,.9) padding-box;
+ border: 1px solid;
+ border-radius: 2px 0 0 2px;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
+ 0 0 2px hsla(210,65%,9%,.1) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ color: inherit;
+ unicode-bidi: plaintext;
+}
+
+#searchText:dir(rtl) {
+ border-radius: 0 2px 2px 0;
+}
+
+#searchText[aria-expanded="true"] {
+ border-radius: 2px 0 0 0;
+}
+
+#searchText[aria-expanded="true"]:dir(rtl) {
+ border-radius: 0 2px 0 0;
+}
+
+#searchText[keepfocus],
+#searchText:focus,
+#searchText[autofocus] {
+ border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
+}
+
+#searchSubmit {
+ margin-inline-start: -1px;
+ color: transparent;
+ background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+ padding: 0;
+ border: 1px solid;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ border-radius: 0 2px 2px 0;
+ border-inline-start: 1px solid transparent;
+ box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+ width: 50px;
+}
+
+#searchSubmit:dir(rtl) {
+ border-radius: 2px 0 0 2px;
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl"), linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1));
+}
+
+#searchText:focus + #searchSubmit,
+#searchText[keepfocus] + #searchSubmit,
+#searchText + #searchSubmit:hover,
+#searchText[autofocus] + #searchSubmit {
+ border-color: #59b5fc #45a3e7 #3294d5;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText[keepfocus] + #searchSubmit,
+#searchText[autofocus] + #searchSubmit {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03);
+}
+
+#searchText:focus + #searchSubmit:dir(rtl),
+#searchText[keepfocus] + #searchSubmit:dir(rtl),
+#searchText[autofocus] + #searchSubmit:dir(rtl) {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#4cb1ff, #1793e5);
+}
+
+#searchText + #searchSubmit:hover {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#66bdff, #0d9eff);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03),
+ 0 0 4px hsla(206,100%,20%,.2);
+}
+
+#searchText + #searchSubmit:dir(rtl):hover {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#66bdff, #0d9eff);
+}
+
+#searchText + #searchSubmit:hover:active {
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
+ 0 0 1px hsla(211,79%,6%,.2) inset;
+ transition-duration: 0ms;
+}
+
+#defaultSnippet1,
+#defaultSnippet2,
+#rightsSnippet {
+ display: block;
+ min-height: 38px;
+ background: 0 center no-repeat;
+ padding: 6px 0;
+ padding-inline-start: 49px;
+}
+
+#rightsSnippet[hidden] {
+ display: none;
+}
+
+#defaultSnippet1:dir(rtl),
+#defaultSnippet2:dir(rtl),
+#rightsSnippet:dir(rtl) {
+ background-position: right 0 center;
+}
+
+#defaultSnippet1 {
+ background-image: url("chrome://browser/content/abouthome/snippet1.png");
+}
+
+#defaultSnippet2 {
+ background-image: url("chrome://browser/content/abouthome/snippet2.png");
+}
+
+#snippets {
+ display: inline-block;
+ text-align: start;
+ margin: 12px 0;
+ color: #3c3c3c;
+ font-size: 75%;
+ /* 12px is the computed font size, 15px the computed line height of the snippets
+ with Segoe UI on a default Windows 7 setup. The 15/12 multiplier approximately
+ converts em from units of font-size to units of line-height. The goal is to
+ preset the height of a three-line snippet to avoid visual moving/flickering as
+ the snippets load. */
+ min-height: calc(15/12 * 3em);
+}
+
+#launcher {
+ display: -moz-box;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ width: 100%;
+ background-color: hsla(0,0%,0%,.03);
+ border-top: 1px solid hsla(0,0%,0%,.03);
+ box-shadow: 0 1px 2px hsla(0,0%,0%,.02) inset,
+ 0 -1px 0 hsla(0,0%,100%,.25);
+}
+
+#launcher:not([session]),
+body[narrow] #launcher[session] {
+ display: block; /* display separator and restore button on separate lines */
+ text-align: center;
+ white-space: nowrap; /* prevent navigational buttons from wrapping */
+}
+
+.launchButton {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ margin: 16px 1px;
+ padding: 14px 6px;
+ min-width: 88px;
+ max-width: 176px;
+ max-height: 85px;
+ vertical-align: top;
+ white-space: normal;
+ background: transparent padding-box;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ color: #525c66;
+ font-size: 75%;
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+body[narrow] #launcher[session] > .launchButton {
+ margin: 4px 1px;
+}
+
+.launchButton:hover {
+ background-color: hsla(211,79%,6%,.03);
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+}
+
+.launchButton:hover:active {
+ background-image: linear-gradient(hsla(211,79%,6%,.02), hsla(211,79%,6%,.05));
+ border-color: hsla(210,54%,20%,.2) hsla(210,54%,20%,.23) hsla(210,54%,20%,.25);
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.05) inset,
+ 0 0 1px hsla(211,79%,6%,.1) inset;
+ transition-duration: 0ms;
+}
+
+.launchButton[hidden],
+#launcher:not([session]) > #restorePreviousSessionSeparator,
+#launcher:not([session]) > #restorePreviousSession {
+ display: none;
+}
+
+#restorePreviousSessionSeparator {
+ width: 3px;
+ height: 116px;
+ margin: 0 10px;
+ background-image: linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+ linear-gradient(hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+ linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+ background-position: left top, center, right bottom;
+ background-size: 1px auto;
+ background-repeat: no-repeat;
+}
+
+body[narrow] #restorePreviousSessionSeparator {
+ margin: 0 auto;
+ width: 512px;
+ height: 3px;
+ background-image: linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+ linear-gradient(to right, hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+ linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+ background-size: auto 1px;
+}
+
+#restorePreviousSession {
+ max-width: none;
+ font-size: 90%;
+}
+
+body[narrow] #restorePreviousSession {
+ font-size: 80%;
+}
+
+.launchButton::before {
+ display: block;
+ width: 32px;
+ height: 32px;
+ margin: 0 auto 6px;
+ line-height: 0; /* remove extra vertical space due to non-zero font-size */
+}
+
+#downloads::before {
+ content: url("chrome://browser/content/abouthome/downloads.png");
+}
+
+#bookmarks::before {
+ content: url("chrome://browser/content/abouthome/bookmarks.png");
+}
+
+#history::before {
+ content: url("chrome://browser/content/abouthome/history.png");
+}
+
+#addons::before {
+ content: url("chrome://browser/content/abouthome/addons.png");
+}
+
+#sync::before {
+ content: url("chrome://browser/content/abouthome/sync.png");
+}
+
+#settings::before {
+ content: url("chrome://browser/content/abouthome/settings.png");
+}
+
+#restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore-large.png");
+ height: 48px;
+ width: 48px;
+ display: inline-block; /* display on same line as text label */
+ vertical-align: middle;
+ margin-bottom: 0;
+ margin-inline-end: 8px;
+}
+
+#restorePreviousSession:dir(rtl)::before {
+ transform: scaleX(-1);
+}
+
+body[narrow] #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore.png");
+ height: 32px;
+ width: 32px;
+}
+
+#aboutMozilla {
+ display: block;
+ position: relative; /* pin wordmark to edge of document, not of viewport */
+ -moz-box-ordinal-group: 0;
+ opacity: .5;
+ transition: opacity 150ms;
+}
+
+#aboutMozilla:hover {
+ opacity: 1;
+}
+
+#aboutMozilla::before {
+ content: url("chrome://browser/content/abouthome/mozilla.png");
+ display: block;
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 69px;
+ height: 19px;
+}
+
+/* [HiDPI]
+ * At resolutions above 1dppx, prefer downscaling the 2x Retina graphics
+ * rather than upscaling the original-size ones (bug 818940).
+ */
+@media not all and (max-resolution: 1dppx) {
+ #brandLogo {
+ background-image: url("chrome://branding/content/about-logo@2x.png");
+ }
+
+ #defaultSnippet1,
+ #defaultSnippet2,
+ #rightsSnippet {
+ background-size: 40px;
+ }
+
+ #defaultSnippet1 {
+ background-image: url("chrome://browser/content/abouthome/snippet1@2x.png");
+ }
+
+ #defaultSnippet2 {
+ background-image: url("chrome://browser/content/abouthome/snippet2@2x.png");
+ }
+
+ .launchButton::before,
+ #aboutMozilla::before {
+ transform: scale(.5);
+ transform-origin: 0 0;
+ }
+
+ .launchButton:dir(rtl)::before,
+ #aboutMozilla:dir(rtl)::before {
+ transform: scale(.5) translateX(32px);
+ }
+
+ #downloads::before {
+ content: url("chrome://browser/content/abouthome/downloads@2x.png");
+ }
+
+ #bookmarks::before {
+ content: url("chrome://browser/content/abouthome/bookmarks@2x.png");
+ }
+
+ #history::before {
+ content: url("chrome://browser/content/abouthome/history@2x.png");
+ }
+
+ #addons::before {
+ content: url("chrome://browser/content/abouthome/addons@2x.png");
+ }
+
+ #sync::before {
+ content: url("chrome://browser/content/abouthome/sync@2x.png");
+ }
+
+ #settings::before {
+ content: url("chrome://browser/content/abouthome/settings@2x.png");
+ }
+
+ #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore-large@2x.png");
+ }
+
+ body[narrow] #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore@2x.png");
+ }
+
+ #restorePreviousSession:dir(rtl)::before {
+ transform: scale(-0.5, 0.5) translateX(24px);
+ transform-origin: top center;
+ }
+
+ #aboutMozilla::before {
+ content: url("chrome://browser/content/abouthome/mozilla@2x.png");
+ }
+}
+
diff --git a/browser/base/content/abouthome/aboutHome.js b/browser/base/content/abouthome/aboutHome.js
new file mode 100644
index 000000000..50f3e01cd
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -0,0 +1,398 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../contentSearchUI.js */
+
+// The process of adding a new default snippet involves:
+// * add a new entity to aboutHome.dtd
+// * add a <span/> for it in aboutHome.xhtml
+// * add an entry here in the proper ordering (based on spans)
+// The <a/> part of the snippet will be linked to the corresponding url.
+const DEFAULT_SNIPPETS_URLS = [
+ "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet"
+, "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons"
+];
+
+const SNIPPETS_UPDATE_INTERVAL_MS = 14400000; // 4 hours.
+
+// IndexedDB storage constants.
+const DATABASE_NAME = "abouthome";
+const DATABASE_VERSION = 1;
+const DATABASE_STORAGE = "persistent";
+const SNIPPETS_OBJECTSTORE_NAME = "snippets";
+var searchText;
+
+// This global tracks if the page has been set up before, to prevent double inits
+var gInitialized = false;
+var gObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ // The addition of the restore session button changes our width:
+ if (mutation.attributeName == "session") {
+ fitToWidth();
+ }
+ if (mutation.attributeName == "snippetsVersion") {
+ if (!gInitialized) {
+ ensureSnippetsMapThen(loadSnippets);
+ gInitialized = true;
+ }
+ return;
+ }
+ }
+});
+
+window.addEventListener("pageshow", function () {
+ // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs
+ // later and may use asynchronous getters.
+ window.gObserver.observe(document.documentElement, { attributes: true });
+ window.gObserver.observe(document.getElementById("launcher"), { attributes: true });
+ fitToWidth();
+ setupSearch();
+ window.addEventListener("resize", fitToWidth);
+
+ // Ask chrome to update snippets.
+ var event = new CustomEvent("AboutHomeLoad", {bubbles:true});
+ document.dispatchEvent(event);
+});
+
+window.addEventListener("pagehide", function() {
+ window.gObserver.disconnect();
+ window.removeEventListener("resize", fitToWidth);
+});
+
+window.addEventListener("keypress", ev => {
+ if (ev.defaultPrevented) {
+ return;
+ }
+
+ // don't focus the search-box on keypress if something other than the
+ // body or document element has focus - don't want to steal input from other elements
+ // Make an exception for <a> and <button> elements (and input[type=button|submit])
+ // which don't usefully take keypresses anyway.
+ // (except space, which is handled below)
+ if (document.activeElement && document.activeElement != document.body &&
+ document.activeElement != document.documentElement &&
+ !["a", "button"].includes(document.activeElement.localName) &&
+ !document.activeElement.matches("input:-moz-any([type=button],[type=submit])")) {
+ return;
+ }
+
+ let modifiers = ev.ctrlKey + ev.altKey + ev.metaKey;
+ // ignore Ctrl/Cmd/Alt, but not Shift
+ // also ignore Tab, Insert, PageUp, etc., and Space
+ if (modifiers != 0 || ev.charCode == 0 || ev.charCode == 32)
+ return;
+
+ searchText.focus();
+ // need to send the first keypress outside the search-box manually to it
+ searchText.value += ev.key;
+});
+
+// This object has the same interface as Map and is used to store and retrieve
+// the snippets data. It is lazily initialized by ensureSnippetsMapThen(), so
+// be sure its callback returned before trying to use it.
+var gSnippetsMap;
+var gSnippetsMapCallbacks = [];
+
+/**
+ * Ensure the snippets map is properly initialized.
+ *
+ * @param aCallback
+ * Invoked once the map has been initialized, gets the map as argument.
+ * @note Snippets should never directly manage the underlying storage, since
+ * it may change inadvertently.
+ */
+function ensureSnippetsMapThen(aCallback)
+{
+ if (gSnippetsMap) {
+ aCallback(gSnippetsMap);
+ return;
+ }
+
+ // Handle multiple requests during the async initialization.
+ gSnippetsMapCallbacks.push(aCallback);
+ if (gSnippetsMapCallbacks.length > 1) {
+ // We are already updating, the callbacks will be invoked when done.
+ return;
+ }
+
+ let invokeCallbacks = function () {
+ if (!gSnippetsMap) {
+ gSnippetsMap = Object.freeze(new Map());
+ }
+
+ for (let callback of gSnippetsMapCallbacks) {
+ callback(gSnippetsMap);
+ }
+ gSnippetsMapCallbacks.length = 0;
+ }
+
+ let openRequest = indexedDB.open(DATABASE_NAME, {version: DATABASE_VERSION,
+ storage: DATABASE_STORAGE});
+
+ openRequest.onerror = function (event) {
+ // Try to delete the old database so that we can start this process over
+ // next time.
+ indexedDB.deleteDatabase(DATABASE_NAME);
+ invokeCallbacks();
+ };
+
+ openRequest.onupgradeneeded = function (event) {
+ let db = event.target.result;
+ if (!db.objectStoreNames.contains(SNIPPETS_OBJECTSTORE_NAME)) {
+ db.createObjectStore(SNIPPETS_OBJECTSTORE_NAME);
+ }
+ }
+
+ openRequest.onsuccess = function (event) {
+ let db = event.target.result;
+
+ db.onerror = function (event) {
+ invokeCallbacks();
+ }
+
+ db.onversionchange = function (event) {
+ event.target.close();
+ invokeCallbacks();
+ }
+
+ let cache = new Map();
+ let cursorRequest;
+ try {
+ cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME)
+ .objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor();
+ } catch (ex) {
+ console.error(ex);
+ invokeCallbacks();
+ return;
+ }
+
+ cursorRequest.onerror = function (event) {
+ invokeCallbacks();
+ }
+
+ cursorRequest.onsuccess = function(event) {
+ let cursor = event.target.result;
+
+ // Populate the cache from the persistent storage.
+ if (cursor) {
+ cache.set(cursor.key, cursor.value);
+ cursor.continue();
+ return;
+ }
+
+ // The cache has been filled up, create the snippets map.
+ gSnippetsMap = Object.freeze({
+ get: (aKey) => cache.get(aKey),
+ set: function (aKey, aValue) {
+ db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
+ .objectStore(SNIPPETS_OBJECTSTORE_NAME).put(aValue, aKey);
+ return cache.set(aKey, aValue);
+ },
+ has: (aKey) => cache.has(aKey),
+ delete: function (aKey) {
+ db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
+ .objectStore(SNIPPETS_OBJECTSTORE_NAME).delete(aKey);
+ return cache.delete(aKey);
+ },
+ clear: function () {
+ db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
+ .objectStore(SNIPPETS_OBJECTSTORE_NAME).clear();
+ return cache.clear();
+ },
+ get size() { return cache.size; },
+ });
+
+ setTimeout(invokeCallbacks, 0);
+ }
+ }
+}
+
+function onSearchSubmit(aEvent)
+{
+ gContentSearchController.search(aEvent);
+}
+
+
+var gContentSearchController;
+
+function setupSearch()
+{
+ // Set submit button label for when CSS background are disabled (e.g.
+ // high contrast mode).
+ document.getElementById("searchSubmit").value =
+ document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
+
+ // The "autofocus" attribute doesn't focus the form element
+ // immediately when the element is first drawn, so the
+ // attribute is also used for styling when the page first loads.
+ searchText = document.getElementById("searchText");
+ searchText.addEventListener("blur", function searchText_onBlur() {
+ searchText.removeEventListener("blur", searchText_onBlur);
+ searchText.removeAttribute("autofocus");
+ });
+
+ if (!gContentSearchController) {
+ gContentSearchController =
+ new ContentSearchUIController(searchText, searchText.parentNode,
+ "abouthome", "homepage");
+ }
+}
+
+/**
+ * Inform the test harness that we're done loading the page.
+ */
+function loadCompleted()
+{
+ var event = new CustomEvent("AboutHomeLoadSnippetsCompleted", {bubbles:true});
+ document.dispatchEvent(event);
+}
+
+/**
+ * Update the local snippets from the remote storage, then show them through
+ * showSnippets.
+ */
+function loadSnippets()
+{
+ if (!gSnippetsMap)
+ throw new Error("Snippets map has not properly been initialized");
+
+ // Allow tests to modify the snippets map before using it.
+ var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles:true});
+ document.dispatchEvent(event);
+
+ // Check cached snippets version.
+ let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0;
+ let currentVersion = document.documentElement.getAttribute("snippetsVersion");
+ if (cachedVersion < currentVersion) {
+ // The cached snippets are old and unsupported, restart from scratch.
+ gSnippetsMap.clear();
+ }
+
+ // Check last snippets update.
+ let lastUpdate = gSnippetsMap.get("snippets-last-update");
+ let updateURL = document.documentElement.getAttribute("snippetsURL");
+ let shouldUpdate = !lastUpdate ||
+ Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
+ if (updateURL && shouldUpdate) {
+ // Try to update from network.
+ let xhr = new XMLHttpRequest();
+ xhr.timeout = 5000;
+ // Even if fetching should fail we don't want to spam the server, thus
+ // set the last update time regardless its results. Will retry tomorrow.
+ gSnippetsMap.set("snippets-last-update", Date.now());
+ xhr.onloadend = function (event) {
+ if (xhr.status == 200) {
+ gSnippetsMap.set("snippets", xhr.responseText);
+ gSnippetsMap.set("snippets-cached-version", currentVersion);
+ }
+ showSnippets();
+ loadCompleted();
+ };
+ try {
+ xhr.open("GET", updateURL, true);
+ xhr.send(null);
+ } catch (ex) {
+ showSnippets();
+ loadCompleted();
+ return;
+ }
+ } else {
+ showSnippets();
+ loadCompleted();
+ }
+}
+
+/**
+ * Shows locally cached remote snippets, or default ones when not available.
+ *
+ * @note: snippets should never invoke showSnippets(), or they may cause
+ * a "too much recursion" exception.
+ */
+var _snippetsShown = false;
+function showSnippets()
+{
+ let snippetsElt = document.getElementById("snippets");
+
+ // Show about:rights notification, if needed.
+ let showRights = document.documentElement.getAttribute("showKnowYourRights");
+ if (showRights) {
+ let rightsElt = document.getElementById("rightsSnippet");
+ let anchor = rightsElt.getElementsByTagName("a")[0];
+ anchor.href = "about:rights";
+ snippetsElt.appendChild(rightsElt);
+ rightsElt.removeAttribute("hidden");
+ return;
+ }
+
+ if (!gSnippetsMap)
+ throw new Error("Snippets map has not properly been initialized");
+ if (_snippetsShown) {
+ // There's something wrong with the remote snippets, just in case fall back
+ // to the default snippets.
+ showDefaultSnippets();
+ throw new Error("showSnippets should never be invoked multiple times");
+ }
+ _snippetsShown = true;
+
+ let snippets = gSnippetsMap.get("snippets");
+ // If there are remotely fetched snippets, try to to show them.
+ if (snippets) {
+ // Injecting snippets can throw if they're invalid XML.
+ try {
+ snippetsElt.innerHTML = snippets;
+ // Scripts injected by innerHTML are inactive, so we have to relocate them
+ // through DOM manipulation to activate their contents.
+ Array.forEach(snippetsElt.getElementsByTagName("script"), function(elt) {
+ let relocatedScript = document.createElement("script");
+ relocatedScript.type = "text/javascript;version=1.8";
+ relocatedScript.text = elt.text;
+ elt.parentNode.replaceChild(relocatedScript, elt);
+ });
+ return;
+ } catch (ex) {
+ // Bad content, continue to show default snippets.
+ }
+ }
+
+ showDefaultSnippets();
+}
+
+/**
+ * Clear snippets element contents and show default snippets.
+ */
+function showDefaultSnippets()
+{
+ // Clear eventual contents...
+ let snippetsElt = document.getElementById("snippets");
+ snippetsElt.innerHTML = "";
+
+ // ...then show default snippets.
+ let defaultSnippetsElt = document.getElementById("defaultSnippets");
+ let entries = defaultSnippetsElt.querySelectorAll("span");
+ // Choose a random snippet. Assume there is always at least one.
+ let randIndex = Math.floor(Math.random() * entries.length);
+ let entry = entries[randIndex];
+ // Inject url in the eventual link.
+ if (DEFAULT_SNIPPETS_URLS[randIndex]) {
+ let links = entry.getElementsByTagName("a");
+ // Default snippets can have only one link, otherwise something is messed
+ // up in the translation.
+ if (links.length == 1) {
+ links[0].href = DEFAULT_SNIPPETS_URLS[randIndex];
+ }
+ }
+ // Move the default snippet to the snippets element.
+ snippetsElt.appendChild(entry);
+}
+
+function fitToWidth() {
+ if (document.documentElement.scrollWidth > window.innerWidth) {
+ document.body.setAttribute("narrow", "true");
+ } else if (document.body.hasAttribute("narrow")) {
+ document.body.removeAttribute("narrow");
+ fitToWidth();
+ }
+}
diff --git a/browser/base/content/abouthome/aboutHome.xhtml b/browser/base/content/abouthome/aboutHome.xhtml
new file mode 100644
index 000000000..c288e732e
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+ %aboutHomeDTD;
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+ %browserDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&abouthome.pageTitle;</title>
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/content/contentSearchUI.css"/>
+ <link rel="stylesheet" type="text/css" media="all" defer="defer"
+ href="chrome://browser/content/abouthome/aboutHome.css"/>
+
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/abouthome/aboutHome.js"/>
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/contentSearchUI.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+ <div class="spacer"/>
+ <div id="topSection">
+ <div id="brandLogo"></div>
+
+ <div id="searchIconAndTextContainer">
+ <div id="searchIcon"/>
+ <input type="text" name="q" value="" id="searchText" maxlength="256"
+ aria-label="&contentSearchInput.label;" autofocus="autofocus"/>
+ <input id="searchSubmit" type="button" onclick="onSearchSubmit(event)"
+ title="&contentSearchSubmit.tooltip;"/>
+ </div>
+
+ <div id="snippetContainer">
+ <div id="defaultSnippets" hidden="true">
+ <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span>
+ <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span>
+ </div>
+ <span id="rightsSnippet" hidden="true">&abouthome.rightsSnippet;</span>
+ <div id="snippets"/>
+ </div>
+ </div>
+ <div class="spacer"/>
+
+ <div id="launcher">
+ <button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
+ <button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
+ <button class="launchButton" id="history">&abouthome.historyButton.label;</button>
+ <button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
+ <button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
+#ifdef XP_WIN
+ <button class="launchButton" id="settings">&abouthome.preferencesButtonWin.label;</button>
+#else
+ <button class="launchButton" id="settings">&abouthome.preferencesButtonUnix.label;</button>
+#endif
+ <div id="restorePreviousSessionSeparator"/>
+ <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
+ </div>
+
+ <a id="aboutMozilla" href="https://www.mozilla.org/about/?utm_source=about-home&amp;utm_medium=Referral"
+ aria-label="&abouthome.aboutMozilla.label;"/>
+ </body>
+</html>
diff --git a/browser/base/content/abouthome/addons.png b/browser/base/content/abouthome/addons.png
new file mode 100644
index 000000000..41519ce49
--- /dev/null
+++ b/browser/base/content/abouthome/addons.png
Binary files differ
diff --git a/browser/base/content/abouthome/addons@2x.png b/browser/base/content/abouthome/addons@2x.png
new file mode 100644
index 000000000..d4d04ee8c
--- /dev/null
+++ b/browser/base/content/abouthome/addons@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/bookmarks.png b/browser/base/content/abouthome/bookmarks.png
new file mode 100644
index 000000000..5c7e194a6
--- /dev/null
+++ b/browser/base/content/abouthome/bookmarks.png
Binary files differ
diff --git a/browser/base/content/abouthome/bookmarks@2x.png b/browser/base/content/abouthome/bookmarks@2x.png
new file mode 100644
index 000000000..7ede00744
--- /dev/null
+++ b/browser/base/content/abouthome/bookmarks@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/downloads.png b/browser/base/content/abouthome/downloads.png
new file mode 100644
index 000000000..3d4d10e7a
--- /dev/null
+++ b/browser/base/content/abouthome/downloads.png
Binary files differ
diff --git a/browser/base/content/abouthome/downloads@2x.png b/browser/base/content/abouthome/downloads@2x.png
new file mode 100644
index 000000000..d384a22c6
--- /dev/null
+++ b/browser/base/content/abouthome/downloads@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/history.png b/browser/base/content/abouthome/history.png
new file mode 100644
index 000000000..ae742b1aa
--- /dev/null
+++ b/browser/base/content/abouthome/history.png
Binary files differ
diff --git a/browser/base/content/abouthome/history@2x.png b/browser/base/content/abouthome/history@2x.png
new file mode 100644
index 000000000..696902e7c
--- /dev/null
+++ b/browser/base/content/abouthome/history@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/mozilla.png b/browser/base/content/abouthome/mozilla.png
new file mode 100644
index 000000000..f2c348d13
--- /dev/null
+++ b/browser/base/content/abouthome/mozilla.png
Binary files differ
diff --git a/browser/base/content/abouthome/mozilla@2x.png b/browser/base/content/abouthome/mozilla@2x.png
new file mode 100644
index 000000000..f8fc622d0
--- /dev/null
+++ b/browser/base/content/abouthome/mozilla@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore-large.png b/browser/base/content/abouthome/restore-large.png
new file mode 100644
index 000000000..ef593e6e1
--- /dev/null
+++ b/browser/base/content/abouthome/restore-large.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore-large@2x.png b/browser/base/content/abouthome/restore-large@2x.png
new file mode 100644
index 000000000..d5c71d0b0
--- /dev/null
+++ b/browser/base/content/abouthome/restore-large@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore.png b/browser/base/content/abouthome/restore.png
new file mode 100644
index 000000000..5c3d6f437
--- /dev/null
+++ b/browser/base/content/abouthome/restore.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore@2x.png b/browser/base/content/abouthome/restore@2x.png
new file mode 100644
index 000000000..5acb63052
--- /dev/null
+++ b/browser/base/content/abouthome/restore@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/settings.png b/browser/base/content/abouthome/settings.png
new file mode 100644
index 000000000..4b0c30990
--- /dev/null
+++ b/browser/base/content/abouthome/settings.png
Binary files differ
diff --git a/browser/base/content/abouthome/settings@2x.png b/browser/base/content/abouthome/settings@2x.png
new file mode 100644
index 000000000..c77cb9a92
--- /dev/null
+++ b/browser/base/content/abouthome/settings@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet1.png b/browser/base/content/abouthome/snippet1.png
new file mode 100644
index 000000000..ce2ec55c2
--- /dev/null
+++ b/browser/base/content/abouthome/snippet1.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet1@2x.png b/browser/base/content/abouthome/snippet1@2x.png
new file mode 100644
index 000000000..f57cd0a82
--- /dev/null
+++ b/browser/base/content/abouthome/snippet1@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet2.png b/browser/base/content/abouthome/snippet2.png
new file mode 100644
index 000000000..e0724fb6d
--- /dev/null
+++ b/browser/base/content/abouthome/snippet2.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet2@2x.png b/browser/base/content/abouthome/snippet2@2x.png
new file mode 100644
index 000000000..40577f52f
--- /dev/null
+++ b/browser/base/content/abouthome/snippet2@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/sync.png b/browser/base/content/abouthome/sync.png
new file mode 100644
index 000000000..11e40cc93
--- /dev/null
+++ b/browser/base/content/abouthome/sync.png
Binary files differ
diff --git a/browser/base/content/abouthome/sync@2x.png b/browser/base/content/abouthome/sync@2x.png
new file mode 100644
index 000000000..6354f5bf9
--- /dev/null
+++ b/browser/base/content/abouthome/sync@2x.png
Binary files differ