diff options
Diffstat (limited to 'browser/base/content/abouthome')
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&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 Binary files differnew file mode 100644 index 000000000..41519ce49 --- /dev/null +++ b/browser/base/content/abouthome/addons.png diff --git a/browser/base/content/abouthome/addons@2x.png b/browser/base/content/abouthome/addons@2x.png Binary files differnew file mode 100644 index 000000000..d4d04ee8c --- /dev/null +++ b/browser/base/content/abouthome/addons@2x.png diff --git a/browser/base/content/abouthome/bookmarks.png b/browser/base/content/abouthome/bookmarks.png Binary files differnew file mode 100644 index 000000000..5c7e194a6 --- /dev/null +++ b/browser/base/content/abouthome/bookmarks.png diff --git a/browser/base/content/abouthome/bookmarks@2x.png b/browser/base/content/abouthome/bookmarks@2x.png Binary files differnew file mode 100644 index 000000000..7ede00744 --- /dev/null +++ b/browser/base/content/abouthome/bookmarks@2x.png diff --git a/browser/base/content/abouthome/downloads.png b/browser/base/content/abouthome/downloads.png Binary files differnew file mode 100644 index 000000000..3d4d10e7a --- /dev/null +++ b/browser/base/content/abouthome/downloads.png diff --git a/browser/base/content/abouthome/downloads@2x.png b/browser/base/content/abouthome/downloads@2x.png Binary files differnew file mode 100644 index 000000000..d384a22c6 --- /dev/null +++ b/browser/base/content/abouthome/downloads@2x.png diff --git a/browser/base/content/abouthome/history.png b/browser/base/content/abouthome/history.png Binary files differnew file mode 100644 index 000000000..ae742b1aa --- /dev/null +++ b/browser/base/content/abouthome/history.png diff --git a/browser/base/content/abouthome/history@2x.png b/browser/base/content/abouthome/history@2x.png Binary files differnew file mode 100644 index 000000000..696902e7c --- /dev/null +++ b/browser/base/content/abouthome/history@2x.png diff --git a/browser/base/content/abouthome/mozilla.png b/browser/base/content/abouthome/mozilla.png Binary files differnew file mode 100644 index 000000000..f2c348d13 --- /dev/null +++ b/browser/base/content/abouthome/mozilla.png diff --git a/browser/base/content/abouthome/mozilla@2x.png b/browser/base/content/abouthome/mozilla@2x.png Binary files differnew file mode 100644 index 000000000..f8fc622d0 --- /dev/null +++ b/browser/base/content/abouthome/mozilla@2x.png diff --git a/browser/base/content/abouthome/restore-large.png b/browser/base/content/abouthome/restore-large.png Binary files differnew file mode 100644 index 000000000..ef593e6e1 --- /dev/null +++ b/browser/base/content/abouthome/restore-large.png diff --git a/browser/base/content/abouthome/restore-large@2x.png b/browser/base/content/abouthome/restore-large@2x.png Binary files differnew file mode 100644 index 000000000..d5c71d0b0 --- /dev/null +++ b/browser/base/content/abouthome/restore-large@2x.png diff --git a/browser/base/content/abouthome/restore.png b/browser/base/content/abouthome/restore.png Binary files differnew file mode 100644 index 000000000..5c3d6f437 --- /dev/null +++ b/browser/base/content/abouthome/restore.png diff --git a/browser/base/content/abouthome/restore@2x.png b/browser/base/content/abouthome/restore@2x.png Binary files differnew file mode 100644 index 000000000..5acb63052 --- /dev/null +++ b/browser/base/content/abouthome/restore@2x.png diff --git a/browser/base/content/abouthome/settings.png b/browser/base/content/abouthome/settings.png Binary files differnew file mode 100644 index 000000000..4b0c30990 --- /dev/null +++ b/browser/base/content/abouthome/settings.png diff --git a/browser/base/content/abouthome/settings@2x.png b/browser/base/content/abouthome/settings@2x.png Binary files differnew file mode 100644 index 000000000..c77cb9a92 --- /dev/null +++ b/browser/base/content/abouthome/settings@2x.png diff --git a/browser/base/content/abouthome/snippet1.png b/browser/base/content/abouthome/snippet1.png Binary files differnew file mode 100644 index 000000000..ce2ec55c2 --- /dev/null +++ b/browser/base/content/abouthome/snippet1.png diff --git a/browser/base/content/abouthome/snippet1@2x.png b/browser/base/content/abouthome/snippet1@2x.png Binary files differnew file mode 100644 index 000000000..f57cd0a82 --- /dev/null +++ b/browser/base/content/abouthome/snippet1@2x.png diff --git a/browser/base/content/abouthome/snippet2.png b/browser/base/content/abouthome/snippet2.png Binary files differnew file mode 100644 index 000000000..e0724fb6d --- /dev/null +++ b/browser/base/content/abouthome/snippet2.png diff --git a/browser/base/content/abouthome/snippet2@2x.png b/browser/base/content/abouthome/snippet2@2x.png Binary files differnew file mode 100644 index 000000000..40577f52f --- /dev/null +++ b/browser/base/content/abouthome/snippet2@2x.png diff --git a/browser/base/content/abouthome/sync.png b/browser/base/content/abouthome/sync.png Binary files differnew file mode 100644 index 000000000..11e40cc93 --- /dev/null +++ b/browser/base/content/abouthome/sync.png diff --git a/browser/base/content/abouthome/sync@2x.png b/browser/base/content/abouthome/sync@2x.png Binary files differnew file mode 100644 index 000000000..6354f5bf9 --- /dev/null +++ b/browser/base/content/abouthome/sync@2x.png |