summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--application/basilisk/base/content/browser-context.inc3
-rw-r--r--application/basilisk/base/content/browser-sidebar.js6
-rw-r--r--application/basilisk/base/content/browser.js16
-rw-r--r--application/basilisk/base/content/nsContextMenu.js62
-rw-r--r--application/basilisk/base/content/tabbrowser.xml2
-rw-r--r--application/basilisk/components/customizableui/CustomizableWidgets.jsm4
-rw-r--r--application/basilisk/components/customizableui/CustomizeMode.jsm17
-rw-r--r--application/basilisk/components/nsBrowserGlue.js2
-rw-r--r--application/basilisk/components/search/content/search.xml1
-rw-r--r--application/basilisk/components/syncedtabs/TabListComponent.js4
-rw-r--r--application/basilisk/modules/BrowserUITelemetry.jsm888
-rw-r--r--application/basilisk/modules/moz.build1
-rw-r--r--application/palemoon/app/profile/palemoon.js13
-rw-r--r--application/palemoon/base/content/browser.js29
-rw-r--r--application/palemoon/base/content/newtab/drag.js2
-rw-r--r--application/palemoon/base/content/newtab/dragDataHelper.js2
-rw-r--r--application/palemoon/base/content/newtab/dropTargetShim.js154
-rw-r--r--application/palemoon/base/content/newtab/grid.js217
-rw-r--r--application/palemoon/base/content/newtab/newTab.css261
-rw-r--r--application/palemoon/base/content/newtab/newTab.js17
-rw-r--r--application/palemoon/base/content/newtab/newTab.xhtml60
-rw-r--r--application/palemoon/base/content/newtab/newTab.xul55
-rw-r--r--application/palemoon/base/content/newtab/page.js214
-rw-r--r--application/palemoon/base/content/newtab/search.js134
-rw-r--r--application/palemoon/base/content/newtab/sites.js227
-rw-r--r--application/palemoon/base/content/newtab/transformations.js27
-rw-r--r--application/palemoon/base/content/utilityOverlay.js29
-rw-r--r--application/palemoon/base/jar.mn2
-rw-r--r--application/palemoon/components/about/AboutRedirector.cpp2
-rw-r--r--application/palemoon/locales/en-US/chrome/browser/newTab.dtd9
-rw-r--r--application/palemoon/locales/en-US/chrome/browser/newTab.properties41
-rw-r--r--application/palemoon/themes/linux/jar.mn7
-rw-r--r--application/palemoon/themes/linux/newtab/newTab.css162
-rw-r--r--application/palemoon/themes/osx/jar.mn7
-rw-r--r--application/palemoon/themes/osx/newtab/controls.pngbin4180 -> 0 bytes
-rw-r--r--application/palemoon/themes/osx/newtab/newTab.css162
-rw-r--r--application/palemoon/themes/osx/newtab/noise.pngbin2118 -> 0 bytes
-rw-r--r--application/palemoon/themes/shared/newtab/controls.png (renamed from application/palemoon/themes/linux/newtab/controls.png)bin4180 -> 4180 bytes
-rw-r--r--application/palemoon/themes/shared/newtab/newTab.css.inc203
-rw-r--r--application/palemoon/themes/shared/newtab/noise.png (renamed from application/palemoon/themes/linux/newtab/noise.png)bin2118 -> 2118 bytes
-rw-r--r--application/palemoon/themes/shared/newtab/pinned.pngbin0 -> 307 bytes
-rw-r--r--application/palemoon/themes/windows/jar.mn7
-rw-r--r--application/palemoon/themes/windows/newtab/controls.pngbin4180 -> 0 bytes
-rw-r--r--application/palemoon/themes/windows/newtab/newTab.css162
-rw-r--r--application/palemoon/themes/windows/newtab/noise.pngbin2118 -> 0 bytes
45 files changed, 1378 insertions, 1833 deletions
diff --git a/application/basilisk/base/content/browser-context.inc b/application/basilisk/base/content/browser-context.inc
index 9fa90b11c..36e0478af 100644
--- a/application/basilisk/base/content/browser-context.inc
+++ b/application/basilisk/base/content/browser-context.inc
@@ -3,9 +3,6 @@
# 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/.
-# NB: IF YOU ADD ITEMS TO THIS FILE, PLEASE UPDATE THE WHITELIST IN
-# BrowserUITelemetry.jsm. SEE BUG 991757 FOR DETAILS.
-
<menugroup id="context-navigation">
<menuitem id="context-back"
class="menuitem-iconic"
diff --git a/application/basilisk/base/content/browser-sidebar.js b/application/basilisk/base/content/browser-sidebar.js
index 5893e6015..27517d162 100644
--- a/application/basilisk/base/content/browser-sidebar.js
+++ b/application/basilisk/base/content/browser-sidebar.js
@@ -194,10 +194,6 @@ var SidebarUI = {
return;
}
- if (this.isOpen && commandID != this.currentID) {
- BrowserUITelemetry.countSidebarEvent(this.currentID, "hide");
- }
-
let broadcasters = document.getElementsByAttribute("group", "sidebar");
for (let broadcaster of broadcasters) {
// skip elements that observe sidebar broadcasters and random
@@ -260,7 +256,6 @@ var SidebarUI = {
selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
{commandID: commandID, isOpen: true}
);
- BrowserUITelemetry.countSidebarEvent(commandID, "show");
});
},
@@ -298,7 +293,6 @@ var SidebarUI = {
selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
{commandID: commandID, isOpen: false}
);
- BrowserUITelemetry.countSidebarEvent(commandID, "hide");
},
};
diff --git a/application/basilisk/base/content/browser.js b/application/basilisk/base/content/browser.js
index 64c0d86f5..38c340eea 100644
--- a/application/basilisk/base/content/browser.js
+++ b/application/basilisk/base/content/browser.js
@@ -16,7 +16,6 @@ Cu.import("resource://gre/modules/NotificationDB.jsm");
["AboutHome", "resource:///modules/AboutHome.jsm"],
["AddonWatcher", "resource://gre/modules/AddonWatcher.jsm"],
["AppConstants", "resource://gre/modules/AppConstants.jsm"],
- ["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
["BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"],
["CastingApps", "resource:///modules/CastingApps.jsm"],
@@ -3722,18 +3721,6 @@ const BrowserSearch = {
openUILinkIn(this.searchEnginesURL, where);
},
- _getSearchEngineId: function (engine) {
- if (engine && engine.identifier) {
- return engine.identifier;
- }
-
- if (!engine || (engine.name === undefined) ||
- !Services.prefs.getBoolPref("toolkit.telemetry.enabled"))
- return "other";
-
- return "other-" + engine.name;
- },
-
/**
* Helper to record a search with Telemetry.
*
@@ -3752,7 +3739,6 @@ const BrowserSearch = {
* selected it: {selection: {index: The selected index, kind: "key" or "mouse"}}
*/
recordSearchInTelemetry: function (engine, source, details={}) {
- BrowserUITelemetry.countSearchEvent(source, null, details.selection);
try {
BrowserUsageTelemetry.recordSearch(engine, source, details);
} catch (ex) {
@@ -3776,8 +3762,6 @@ const BrowserSearch = {
* (string) Where was the search link opened (e.g. new tab, current tab, ..).
*/
recordOneoffSearchInTelemetry: function (engine, source, type, where) {
- let id = this._getSearchEngineId(engine) + "." + source;
- BrowserUITelemetry.countOneoffSearchEvent(id, type, where);
try {
const details = {type, isOneOff: true};
BrowserUsageTelemetry.recordSearch(engine, source, details);
diff --git a/application/basilisk/base/content/nsContextMenu.js b/application/basilisk/base/content/nsContextMenu.js
index 589d670ab..3f77dcb90 100644
--- a/application/basilisk/base/content/nsContextMenu.js
+++ b/application/basilisk/base/content/nsContextMenu.js
@@ -85,9 +85,6 @@ nsContextMenu.prototype = {
// Initialize (disable/remove) menu items.
this.initItems();
-
- // Register this opening of the menu with telemetry:
- this._checkTelemetryForMenu(aXulMenu);
},
hiding: function CM_hiding() {
@@ -1782,65 +1779,6 @@ nsContextMenu.prototype = {
menuItem.label = menuLabel;
menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey");
},
-
- _getTelemetryClickInfo: function(aXulMenu) {
- this._onPopupHiding = () => {
- aXulMenu.ownerDocument.removeEventListener("command", activationHandler, true);
- aXulMenu.removeEventListener("popuphiding", this._onPopupHiding, true);
- delete this._onPopupHiding;
-
- let eventKey = [
- this._telemetryPageContext,
- this._telemetryHadCustomItems ? "withcustom" : "withoutcustom"
- ];
- let target = this._telemetryClickID || "close-without-interaction";
- BrowserUITelemetry.registerContextMenuInteraction(eventKey, target);
- };
- let activationHandler = (e) => {
- // Deal with command events being routed to command elements; figure out
- // what triggered the event (which will have the right e.target)
- if (e.sourceEvent) {
- e = e.sourceEvent;
- }
- // Target should be in the menu (this catches using shortcuts for items
- // not in the menu while the menu is up)
- if (!aXulMenu.contains(e.target)) {
- return;
- }
-
- // Check if this is a page menu item:
- if (e.target.hasAttribute(PageMenuParent.GENERATEDITEMID_ATTR)) {
- this._telemetryClickID = "custom-page-item";
- } else {
- this._telemetryClickID = (e.target.id || "unknown").replace(/^context-/i, "");
- }
- };
- aXulMenu.ownerDocument.addEventListener("command", activationHandler, true);
- aXulMenu.addEventListener("popuphiding", this._onPopupHiding, true);
- },
-
- _getTelemetryPageContextInfo: function() {
- let rv = [];
- for (let k of ["isContentSelected", "onLink", "onImage", "onCanvas", "onVideo", "onAudio",
- "onTextInput"]) {
- if (this[k]) {
- rv.push(k.replace(/^(?:is|on)(.)/, (match, firstLetter) => firstLetter.toLowerCase()));
- }
- }
- if (!rv.length) {
- rv.push('other');
- }
-
- return JSON.stringify(rv);
- },
-
- _checkTelemetryForMenu: function(aXulMenu) {
- this._telemetryClickID = null;
- this._telemetryPageContext = this._getTelemetryPageContextInfo();
- this._telemetryHadCustomItems = this.hasPageMenu;
- this._getTelemetryClickInfo(aXulMenu);
- },
-
createContainerMenu: function(aEvent) {
return createUserContextMenu(aEvent, true,
gContextMenuContentData.userContextId);
diff --git a/application/basilisk/base/content/tabbrowser.xml b/application/basilisk/base/content/tabbrowser.xml
index 463e74a52..f8dbcf364 100644
--- a/application/basilisk/base/content/tabbrowser.xml
+++ b/application/basilisk/base/content/tabbrowser.xml
@@ -6878,11 +6878,9 @@
if (browser.audioMuted) {
browser.unmute();
this.removeAttribute("muted");
- BrowserUITelemetry.countTabMutingEvent("unmute", aMuteReason);
} else {
browser.mute();
this.setAttribute("muted", "true");
- BrowserUITelemetry.countTabMutingEvent("mute", aMuteReason);
}
this.muteReason = aMuteReason || null;
modifiedAttrs.push("muted");
diff --git a/application/basilisk/components/customizableui/CustomizableWidgets.jsm b/application/basilisk/components/customizableui/CustomizableWidgets.jsm
index 3e00d385f..642e06f0f 100644
--- a/application/basilisk/components/customizableui/CustomizableWidgets.jsm
+++ b/application/basilisk/components/customizableui/CustomizableWidgets.jsm
@@ -11,8 +11,6 @@ Cu.import("resource:///modules/CustomizableUI.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
- "resource:///modules/BrowserUITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
@@ -497,7 +495,6 @@ const CustomizableWidgets = [
item.addEventListener("click", e => {
doc.defaultView.openUILink(tabInfo.url, e);
CustomizableUI.hidePanelForNode(item);
- BrowserUITelemetry.countSyncedTabEvent("open", "toolbarbutton-subview");
});
return item;
},
@@ -1179,7 +1176,6 @@ if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
this._ensureSanitizer();
this._sanitizer.range = this._getSanitizeRange(doc);
let group = doc.getElementById("PanelUI-panic-timeSpan");
- BrowserUITelemetry.countPanicEvent(group.selectedItem.id);
group.selectedItem = doc.getElementById("PanelUI-panic-5min");
let itemsToClear = [
"cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads"
diff --git a/application/basilisk/components/customizableui/CustomizeMode.jsm b/application/basilisk/components/customizableui/CustomizeMode.jsm
index e63e25b0a..4365ddfbc 100644
--- a/application/basilisk/components/customizableui/CustomizeMode.jsm
+++ b/application/basilisk/components/customizableui/CustomizeMode.jsm
@@ -31,8 +31,6 @@ Cu.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
"resource:///modules/DragPositionManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
- "resource:///modules/BrowserUITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
@@ -1119,7 +1117,6 @@ CustomizeMode.prototype = {
this.resetting = true;
// Disable the reset button temporarily while resetting:
let btn = this.document.getElementById("customization-reset-button");
- BrowserUITelemetry.countCustomizationEvent("reset");
btn.disabled = true;
return Task.spawn(function*() {
this._removePanelCustomizationPlaceholders();
@@ -1755,7 +1752,6 @@ CustomizeMode.prototype = {
}
CustomizableUI.removeWidgetFromArea(aDraggedItemId);
- BrowserUITelemetry.countCustomizationEvent("remove");
// Special widgets are removed outright, we can return here:
if (CustomizableUI.isSpecialWidget(aDraggedItemId)) {
return;
@@ -1797,7 +1793,6 @@ CustomizeMode.prototype = {
this.wrapToolbarItem(aTargetNode, place);
}
this.wrapToolbarItem(draggedItem, place);
- BrowserUITelemetry.countCustomizationEvent("move");
return;
}
@@ -1805,12 +1800,6 @@ CustomizeMode.prototype = {
// widget to the end of the area.
if (aTargetNode == aTargetArea.customizationTarget) {
CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id);
- // For the purposes of BrowserUITelemetry, we consider both moving a widget
- // within the same area, and adding a widget from one area to another area
- // as a "move". An "add" is only when we move an item from the palette into
- // an area.
- let custEventType = aOriginArea.id == kPaletteId ? "add" : "move";
- BrowserUITelemetry.countCustomizationEvent(custEventType);
this._onDragEnd(aEvent);
return;
}
@@ -1847,14 +1836,8 @@ CustomizeMode.prototype = {
} else {
CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id, position);
}
-
this._onDragEnd(aEvent);
- // For BrowserUITelemetry, an "add" is only when we move an item from the palette
- // into an area. Otherwise, it's a move.
- let custEventType = aOriginArea.id == kPaletteId ? "add" : "move";
- BrowserUITelemetry.countCustomizationEvent(custEventType);
-
// If we dropped onto a skipintoolbarset item, manually correct the drop location:
if (aTargetNode != itemForPlacement) {
let draggedWrapper = draggedItem.parentNode;
diff --git a/application/basilisk/components/nsBrowserGlue.js b/application/basilisk/components/nsBrowserGlue.js
index ae00e30cf..3258159b6 100644
--- a/application/basilisk/components/nsBrowserGlue.js
+++ b/application/basilisk/components/nsBrowserGlue.js
@@ -27,7 +27,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s
["AutoCompletePopup", "resource://gre/modules/AutoCompletePopup.jsm"],
["BookmarkHTMLUtils", "resource://gre/modules/BookmarkHTMLUtils.jsm"],
["BookmarkJSONUtils", "resource://gre/modules/BookmarkJSONUtils.jsm"],
- ["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
["ContentClick", "resource:///modules/ContentClick.jsm"],
["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm"],
@@ -667,7 +666,6 @@ BrowserGlue.prototype = {
SessionStore.init();
BrowserUsageTelemetry.init();
- BrowserUITelemetry.init();
ContentSearch.init();
FormValidationHandler.init();
diff --git a/application/basilisk/components/search/content/search.xml b/application/basilisk/components/search/content/search.xml
index 5c67bc649..41a5256d5 100644
--- a/application/basilisk/components/search/content/search.xml
+++ b/application/basilisk/components/search/content/search.xml
@@ -1324,7 +1324,6 @@
<method name="showSettings">
<body><![CDATA[
- BrowserUITelemetry.countSearchSettingsEvent(this.telemetryOrigin);
openPreferences("paneSearch");
// If the preference tab was already selected, the panel doesn't
// close itself automatically.
diff --git a/application/basilisk/components/syncedtabs/TabListComponent.js b/application/basilisk/components/syncedtabs/TabListComponent.js
index d3aace8f9..aa60e4769 100644
--- a/application/basilisk/components/syncedtabs/TabListComponent.js
+++ b/application/basilisk/components/syncedtabs/TabListComponent.js
@@ -11,8 +11,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let log = Cu.import("resource://gre/modules/Log.jsm", {})
.Log.repository.getLogger("Sync.RemoteTabs");
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
- "resource:///modules/BrowserUITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
"resource:///modules/PlacesUIUtils.jsm");
@@ -115,7 +113,6 @@ TabListComponent.prototype = {
onOpenTab(url, where, params) {
this._window.openUILinkIn(url, where, params);
- BrowserUITelemetry.countSyncedTabEvent("open", "sidebar");
},
onOpenTabs(urls, where) {
@@ -129,7 +126,6 @@ TabListComponent.prototype = {
let loadInBackground = where == "tabshifted" ? true : false;
this._getChromeWindow(this._window).gBrowser.loadTabs(urls, loadInBackground, false);
}
- BrowserUITelemetry.countSyncedTabEvent("openmultiple", "sidebar");
},
onCopyTabLocation(url) {
diff --git a/application/basilisk/modules/BrowserUITelemetry.jsm b/application/basilisk/modules/BrowserUITelemetry.jsm
deleted file mode 100644
index 2b7cc8c20..000000000
--- a/application/basilisk/modules/BrowserUITelemetry.jsm
+++ /dev/null
@@ -1,888 +0,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/.
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["BrowserUITelemetry"];
-
-const {interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
- "resource://gre/modules/UITelemetry.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
- "resource:///modules/RecentWindow.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
- "resource:///modules/CustomizableUI.jsm");
-XPCOMUtils.defineLazyGetter(this, "Timer", function() {
- let timer = {};
- Cu.import("resource://gre/modules/Timer.jsm", timer);
- return timer;
-});
-
-const MS_SECOND = 1000;
-const MS_MINUTE = MS_SECOND * 60;
-const MS_HOUR = MS_MINUTE * 60;
-
-XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREA_PLACEMENTS", function() {
- let result = {
- "PanelUI-contents": [
- "edit-controls",
- "zoom-controls",
- "new-window-button",
- "privatebrowsing-button",
- "save-page-button",
- "print-button",
- "history-panelmenu",
- "fullscreen-button",
- "find-button",
- "preferences-button",
- "add-ons-button",
- "sync-button",
- "developer-button",
- ],
- "nav-bar": [
- "urlbar-container",
- "search-container",
- "bookmarks-menu-button",
- "pocket-button",
- "downloads-button",
- "home-button",
- ],
- // It's true that toolbar-menubar is not visible
- // on OS X, but the XUL node is definitely present
- // in the document.
- "toolbar-menubar": [
- "menubar-items",
- ],
- "TabsToolbar": [
- "tabbrowser-tabs",
- "new-tab-button",
- "alltabs-button",
- ],
- "PersonalToolbar": [
- "personal-bookmarks",
- ],
- };
-
- let showCharacterEncoding = Services.prefs.getComplexValue(
- "browser.menu.showCharacterEncoding",
- Ci.nsIPrefLocalizedString
- ).data;
- if (showCharacterEncoding == "true") {
- result["PanelUI-contents"].push("characterencoding-button");
- }
-
- return result;
-});
-
-XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREAS", function() {
- return Object.keys(DEFAULT_AREA_PLACEMENTS);
-});
-
-XPCOMUtils.defineLazyGetter(this, "PALETTE_ITEMS", function() {
- let result = [
- "open-file-button",
- "developer-button",
- "feed-button",
- "email-link-button",
- "containers-panelmenu",
- ];
-
- let panelPlacements = DEFAULT_AREA_PLACEMENTS["PanelUI-contents"];
- if (panelPlacements.indexOf("characterencoding-button") == -1) {
- result.push("characterencoding-button");
- }
-
- if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
- result.push("panic-button");
- }
-
- return result;
-});
-
-XPCOMUtils.defineLazyGetter(this, "DEFAULT_ITEMS", function() {
- let result = [];
- for (let [, buttons] of Object.entries(DEFAULT_AREA_PLACEMENTS)) {
- result = result.concat(buttons);
- }
- return result;
-});
-
-XPCOMUtils.defineLazyGetter(this, "ALL_BUILTIN_ITEMS", function() {
- // These special cases are for click events on built-in items that are
- // contained within customizable items (like the navigation widget).
- const SPECIAL_CASES = [
- "back-button",
- "forward-button",
- "urlbar-stop-button",
- "urlbar-go-button",
- "urlbar-reload-button",
- "searchbar",
- "cut-button",
- "copy-button",
- "paste-button",
- "zoom-out-button",
- "zoom-reset-button",
- "zoom-in-button",
- "BMB_bookmarksPopup",
- "BMB_unsortedBookmarksPopup",
- "BMB_bookmarksToolbarPopup",
- "search-go-button",
- "soundplaying-icon",
- ]
- return DEFAULT_ITEMS.concat(PALETTE_ITEMS)
- .concat(SPECIAL_CASES);
-});
-
-const OTHER_MOUSEUP_MONITORED_ITEMS = [
- "PlacesChevron",
- "PlacesToolbarItems",
- "menubar-items",
-];
-
-// Items that open arrow panels will often be overlapped by
-// the panel that they're opening by the time the mouseup
-// event is fired, so for these items, we monitor mousedown.
-const MOUSEDOWN_MONITORED_ITEMS = [
- "PanelUI-menu-button",
-];
-
-// Weakly maps browser windows to objects whose keys are relative
-// timestamps for when some kind of session started. For example,
-// when a customization session started. That way, when the window
-// exits customization mode, we can determine how long the session
-// lasted.
-const WINDOW_DURATION_MAP = new WeakMap();
-
-// Default bucket name, when no other bucket is active.
-const BUCKET_DEFAULT = "__DEFAULT__";
-// Bucket prefix, for named buckets.
-const BUCKET_PREFIX = "bucket_";
-// Standard separator to use between different parts of a bucket name, such
-// as primary name and the time step string.
-const BUCKET_SEPARATOR = "|";
-
-this.BrowserUITelemetry = {
- init: function() {
- UITelemetry.addSimpleMeasureFunction("toolbars",
- this.getToolbarMeasures.bind(this));
- UITelemetry.addSimpleMeasureFunction("contextmenu",
- this.getContextMenuInfo.bind(this));
- UITelemetry.addSimpleMeasureFunction("syncstate",
- this.getSyncState.bind(this));
-
- Services.obs.addObserver(this, "sessionstore-windows-restored", false);
- Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
- Services.obs.addObserver(this, "autocomplete-did-enter-text", false);
- CustomizableUI.addListener(this);
- },
-
- observe: function(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "sessionstore-windows-restored":
- this._gatherFirstWindowMeasurements();
- break;
- case "browser-delayed-startup-finished":
- this._registerWindow(aSubject);
- break;
- case "autocomplete-did-enter-text":
- let input = aSubject.QueryInterface(Ci.nsIAutoCompleteInput);
- if (input && input.id == "urlbar" && !input.inPrivateContext &&
- input.popup.selectedIndex != -1) {
- this._logAwesomeBarSearchResult(input.textValue);
- }
- break;
- }
- },
-
- /**
- * For the _countableEvents object, constructs a chain of
- * Javascript Objects with the keys in aKeys, with the final
- * key getting the value in aEndWith. If the final key already
- * exists in the final object, its value is not set. In either
- * case, a reference to the second last object in the chain is
- * returned.
- *
- * Example - suppose I want to store:
- * _countableEvents: {
- * a: {
- * b: {
- * c: 0
- * }
- * }
- * }
- *
- * And then increment the "c" value by 1, you could call this
- * function like this:
- *
- * let example = this._ensureObjectChain([a, b, c], 0);
- * example["c"]++;
- *
- * Subsequent repetitions of these last two lines would
- * simply result in the c value being incremented again
- * and again.
- *
- * @param aKeys the Array of keys to chain Objects together with.
- * @param aEndWith the value to assign to the last key.
- * @param aRoot the root object onto which we create/get the object chain
- * designated by aKeys.
- * @returns a reference to the second last object in the chain -
- * so in our example, that'd be "b".
- */
- _ensureObjectChain: function(aKeys, aEndWith, aRoot) {
- let current = aRoot;
- let parent = null;
- aKeys.unshift(this._bucket);
- for (let [i, key] of aKeys.entries()) {
- if (!(key in current)) {
- if (i == aKeys.length - 1) {
- current[key] = aEndWith;
- } else {
- current[key] = {};
- }
- }
- parent = current;
- current = current[key];
- }
- return parent;
- },
-
- _countableEvents: {},
- _countEvent: function(aKeyArray, root=this._countableEvents) {
- let countObject = this._ensureObjectChain(aKeyArray, 0, root);
- let lastItemKey = aKeyArray[aKeyArray.length - 1];
- countObject[lastItemKey]++;
- },
-
- _countMouseUpEvent: function(aCategory, aAction, aButton) {
- const BUTTONS = ["left", "middle", "right"];
- let buttonKey = BUTTONS[aButton];
- if (buttonKey) {
- this._countEvent([aCategory, aAction, buttonKey]);
- }
- },
-
- _firstWindowMeasurements: null,
- _gatherFirstWindowMeasurements: function() {
- // We'll gather measurements as soon as the session has restored.
- // We do this here instead of waiting for UITelemetry to ask for
- // our measurements because at that point all browser windows have
- // probably been closed, since the vast majority of saved-session
- // pings are gathered during shutdown.
- let win = RecentWindow.getMostRecentBrowserWindow({
- private: false,
- allowPopups: false,
- });
-
- Services.search.init(rv => {
- // If there are no such windows (or we've just about found one
- // but it's closed already), we're out of luck. :(
- let hasWindow = win && !win.closed;
- this._firstWindowMeasurements = hasWindow ? this._getWindowMeasurements(win, rv)
- : {};
- });
- },
-
- _registerWindow: function(aWindow) {
- aWindow.addEventListener("unload", this);
- let document = aWindow.document;
-
- for (let areaID of CustomizableUI.areas) {
- let areaNode = document.getElementById(areaID);
- if (areaNode) {
- (areaNode.customizationTarget || areaNode).addEventListener("mouseup", this);
- }
- }
-
- for (let itemID of OTHER_MOUSEUP_MONITORED_ITEMS) {
- let item = document.getElementById(itemID);
- if (item) {
- item.addEventListener("mouseup", this);
- }
- }
-
- for (let itemID of MOUSEDOWN_MONITORED_ITEMS) {
- let item = document.getElementById(itemID);
- if (item) {
- item.addEventListener("mousedown", this);
- }
- }
-
- WINDOW_DURATION_MAP.set(aWindow, {});
- },
-
- _unregisterWindow: function(aWindow) {
- aWindow.removeEventListener("unload", this);
- let document = aWindow.document;
-
- for (let areaID of CustomizableUI.areas) {
- let areaNode = document.getElementById(areaID);
- if (areaNode) {
- (areaNode.customizationTarget || areaNode).removeEventListener("mouseup", this);
- }
- }
-
- for (let itemID of OTHER_MOUSEUP_MONITORED_ITEMS) {
- let item = document.getElementById(itemID);
- if (item) {
- item.removeEventListener("mouseup", this);
- }
- }
-
- for (let itemID of MOUSEDOWN_MONITORED_ITEMS) {
- let item = document.getElementById(itemID);
- if (item) {
- item.removeEventListener("mousedown", this);
- }
- }
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "unload":
- this._unregisterWindow(aEvent.currentTarget);
- break;
- case "mouseup":
- this._handleMouseUp(aEvent);
- break;
- case "mousedown":
- this._handleMouseDown(aEvent);
- break;
- }
- },
-
- _handleMouseUp: function(aEvent) {
- let targetID = aEvent.currentTarget.id;
-
- switch (targetID) {
- case "PlacesToolbarItems":
- this._PlacesToolbarItemsMouseUp(aEvent);
- break;
- case "PlacesChevron":
- this._PlacesChevronMouseUp(aEvent);
- break;
- case "menubar-items":
- this._menubarMouseUp(aEvent);
- break;
- default:
- this._checkForBuiltinItem(aEvent);
- }
- },
-
- _handleMouseDown: function(aEvent) {
- if (aEvent.currentTarget.id == "PanelUI-menu-button") {
- // _countMouseUpEvent expects a detail for the second argument,
- // but we don't really have any details to give. Just passing in
- // "button" is probably simpler than trying to modify
- // _countMouseUpEvent for this particular case.
- this._countMouseUpEvent("click-menu-button", "button", aEvent.button);
- }
- },
-
- _PlacesChevronMouseUp: function(aEvent) {
- let target = aEvent.originalTarget;
- let result = target.id == "PlacesChevron" ? "chevron" : "overflowed-item";
- this._countMouseUpEvent("click-bookmarks-bar", result, aEvent.button);
- },
-
- _PlacesToolbarItemsMouseUp: function(aEvent) {
- let target = aEvent.originalTarget;
- // If this isn't a bookmark-item, we don't care about it.
- if (!target.classList.contains("bookmark-item")) {
- return;
- }
-
- let result = target.hasAttribute("container") ? "container" : "item";
- this._countMouseUpEvent("click-bookmarks-bar", result, aEvent.button);
- },
-
- _menubarMouseUp: function(aEvent) {
- let target = aEvent.originalTarget;
- let tag = target.localName
- let result = (tag == "menu" || tag == "menuitem") ? tag : "other";
- this._countMouseUpEvent("click-menubar", result, aEvent.button);
- },
-
- _bookmarksMenuButtonMouseUp: function(aEvent) {
- let bookmarksWidget = CustomizableUI.getWidget("bookmarks-menu-button");
- if (bookmarksWidget.areaType == CustomizableUI.TYPE_MENU_PANEL) {
- // In the menu panel, only the star is visible, and that opens up the
- // bookmarks subview.
- this._countMouseUpEvent("click-bookmarks-menu-button", "in-panel",
- aEvent.button);
- } else {
- let clickedItem = aEvent.originalTarget;
- // Did we click on the star, or the dropmarker? The star
- // has an anonid of "button". If we don't find that, we'll
- // assume we clicked on the dropmarker.
- let action = "menu";
- if (clickedItem.getAttribute("anonid") == "button") {
- // We clicked on the star - now we just need to record
- // whether or not we're adding a bookmark or editing an
- // existing one.
- let bookmarksMenuNode =
- bookmarksWidget.forWindow(aEvent.target.ownerGlobal).node;
- action = bookmarksMenuNode.hasAttribute("starred") ? "edit" : "add";
- }
- this._countMouseUpEvent("click-bookmarks-menu-button", action,
- aEvent.button);
- }
- },
-
- _checkForBuiltinItem: function(aEvent) {
- let item = aEvent.originalTarget;
-
- // We don't want to count clicks on the private browsing
- // button for privacy reasons. See bug 1176391.
- if (item.id == "privatebrowsing-button") {
- return;
- }
-
- // We special-case the bookmarks-menu-button, since we want to
- // monitor more than just clicks on it.
- if (item.id == "bookmarks-menu-button" ||
- getIDBasedOnFirstIDedAncestor(item) == "bookmarks-menu-button") {
- this._bookmarksMenuButtonMouseUp(aEvent);
- return;
- }
-
- // Perhaps we're seeing one of the default toolbar items
- // being clicked.
- if (ALL_BUILTIN_ITEMS.indexOf(item.id) != -1) {
- // Base case - we clicked directly on one of our built-in items,
- // and we can go ahead and register that click.
- this._countMouseUpEvent("click-builtin-item", item.id, aEvent.button);
- return;
- }
-
- // If not, we need to check if the item's anonid is in our list
- // of built-in items to check.
- if (ALL_BUILTIN_ITEMS.indexOf(item.getAttribute("anonid")) != -1) {
- this._countMouseUpEvent("click-builtin-item", item.getAttribute("anonid"), aEvent.button);
- return;
- }
-
- // If not, we need to check if one of the ancestors of the clicked
- // item is in our list of built-in items to check.
- let candidate = getIDBasedOnFirstIDedAncestor(item);
- if (ALL_BUILTIN_ITEMS.indexOf(candidate) != -1) {
- this._countMouseUpEvent("click-builtin-item", candidate, aEvent.button);
- }
- },
-
- _getWindowMeasurements: function(aWindow, searchResult) {
- let document = aWindow.document;
- let result = {};
-
- // Determine if the window is in the maximized, normal or
- // fullscreen state.
- result.sizemode = document.documentElement.getAttribute("sizemode");
-
- // Determine if the Bookmarks bar is currently visible
- let bookmarksBar = document.getElementById("PersonalToolbar");
- result.bookmarksBarEnabled = bookmarksBar && !bookmarksBar.collapsed;
-
- // Determine if the menubar is currently visible. On OS X, the menubar
- // is never shown, despite not having the collapsed attribute set.
- let menuBar = document.getElementById("toolbar-menubar");
- result.menuBarEnabled =
- menuBar && Services.appinfo.OS != "Darwin"
- && menuBar.getAttribute("autohide") != "true";
-
- // Determine if the titlebar is currently visible.
- result.titleBarEnabled = !Services.prefs.getBoolPref("browser.tabs.drawInTitlebar");
-
- // Examine all customizable areas and see what default items
- // are present and missing.
- let defaultKept = [];
- let defaultMoved = [];
- let nondefaultAdded = [];
-
- for (let areaID of CustomizableUI.areas) {
- let items = CustomizableUI.getWidgetIdsInArea(areaID);
- for (let item of items) {
- // Is this a default item?
- if (DEFAULT_ITEMS.indexOf(item) != -1) {
- // Ok, it's a default item - but is it in its default
- // toolbar? We use Array.isArray instead of checking for
- // toolbarID in DEFAULT_AREA_PLACEMENTS because an add-on might
- // be clever and give itself the id of "toString" or something.
- if (Array.isArray(DEFAULT_AREA_PLACEMENTS[areaID]) &&
- DEFAULT_AREA_PLACEMENTS[areaID].indexOf(item) != -1) {
- // The item is in its default toolbar
- defaultKept.push(item);
- } else {
- defaultMoved.push(item);
- }
- } else if (PALETTE_ITEMS.indexOf(item) != -1) {
- // It's a palette item that's been moved into a toolbar
- nondefaultAdded.push(item);
- }
- // else, it's provided by an add-on, and we won't record it.
- }
- }
-
- // Now go through the items in the palette to see what default
- // items are in there.
- let paletteItems =
- CustomizableUI.getUnusedWidgets(aWindow.gNavToolbox.palette);
- let defaultRemoved = [];
- for (let item of paletteItems) {
- if (DEFAULT_ITEMS.indexOf(item.id) != -1) {
- defaultRemoved.push(item.id);
- }
- }
-
- result.defaultKept = defaultKept;
- result.defaultMoved = defaultMoved;
- result.nondefaultAdded = nondefaultAdded;
- result.defaultRemoved = defaultRemoved;
-
- // Next, determine how many add-on provided toolbars exist.
- let addonToolbars = 0;
- let toolbars = document.querySelectorAll("toolbar[customizable=true]");
- for (let toolbar of toolbars) {
- if (DEFAULT_AREAS.indexOf(toolbar.id) == -1) {
- addonToolbars++;
- }
- }
- result.addonToolbars = addonToolbars;
-
- // Find out how many open tabs we have in each window
- let winEnumerator = Services.wm.getEnumerator("navigator:browser");
- let visibleTabs = [];
- let hiddenTabs = [];
- while (winEnumerator.hasMoreElements()) {
- let someWin = winEnumerator.getNext();
- if (someWin.gBrowser) {
- let visibleTabsNum = someWin.gBrowser.visibleTabs.length;
- visibleTabs.push(visibleTabsNum);
- hiddenTabs.push(someWin.gBrowser.tabs.length - visibleTabsNum);
- }
- }
- result.visibleTabs = visibleTabs;
- result.hiddenTabs = hiddenTabs;
-
- if (Components.isSuccessCode(searchResult)) {
- result.currentSearchEngine = Services.search.currentEngine.name;
- }
-
- return result;
- },
-
- getToolbarMeasures: function() {
- let result = this._firstWindowMeasurements || {};
- result.countableEvents = this._countableEvents;
- result.durations = this._durations;
- return result;
- },
-
- getSyncState: function() {
- let result = {};
- for (let sub of ["desktop", "mobile"]) {
- let count = 0;
- try {
- count = Services.prefs.getIntPref("services.sync.clients.devices." + sub);
- } catch (ex) {}
- result[sub] = count;
- }
- return result;
- },
-
- countCustomizationEvent: function(aEventType) {
- this._countEvent(["customize", aEventType]);
- },
-
- countSearchEvent: function(source, query, selection) {
- this._countEvent(["search", source]);
- if ((/^[a-zA-Z]+:[^\/\\]/).test(query)) {
- this._countEvent(["search", "urlbar-keyword"]);
- }
- if (selection) {
- this._countEvent(["search", "selection", source, selection.index, selection.kind]);
- }
- },
-
- countOneoffSearchEvent: function(id, type, where) {
- this._countEvent(["search-oneoff", id, type, where]);
- },
-
- countSearchSettingsEvent: function(source) {
- this._countEvent(["click-builtin-item", source, "search-settings"]);
- },
-
- countPanicEvent: function(timeId) {
- this._countEvent(["forget-button", timeId]);
- },
-
- countTabMutingEvent: function(action, reason) {
- this._countEvent(["tab-audio-control", action, reason || "no reason given"]);
- },
-
- countSyncedTabEvent: function(what, where) {
- // "what" will be, eg, "open"
- // "where" will be "toolbarbutton-subview" or "sidebar"
- this._countEvent(["synced-tabs", what, where]);
- },
-
- countSidebarEvent: function(sidebarID, action) {
- // sidebarID is the ID of the sidebar (duh!)
- // action will be "hide" or "show"
- this._countEvent(["sidebar", sidebarID, action]);
- },
-
- _logAwesomeBarSearchResult: function (url) {
- let spec = Services.search.parseSubmissionURL(url);
- if (spec.engine) {
- let matchedEngine = "default";
- if (spec.engine.name !== Services.search.currentEngine.name) {
- matchedEngine = "other";
- }
- this.countSearchEvent("autocomplete-" + matchedEngine);
- }
- },
-
- _durations: {
- customization: [],
- },
-
- onCustomizeStart: function(aWindow) {
- this._countEvent(["customize", "start"]);
- let durationMap = WINDOW_DURATION_MAP.get(aWindow);
- if (!durationMap) {
- durationMap = {};
- WINDOW_DURATION_MAP.set(aWindow, durationMap);
- }
-
- durationMap.customization = {
- start: aWindow.performance.now(),
- bucket: this._bucket,
- };
- },
-
- onCustomizeEnd: function(aWindow) {
- let durationMap = WINDOW_DURATION_MAP.get(aWindow);
- if (durationMap && "customization" in durationMap) {
- let duration = aWindow.performance.now() - durationMap.customization.start;
- this._durations.customization.push({
- duration: duration,
- bucket: durationMap.customization.bucket,
- });
- delete durationMap.customization;
- }
- },
-
- _contextMenuItemWhitelist: new Set([
- "close-without-interaction", // for closing the menu without clicking it.
- "custom-page-item", // The ID we use for page-provided items
- "unknown", // The bucket for stuff with no id.
- // Everything we know of so far (which will exclude add-on items):
- "navigation", "back", "forward", "reload", "stop", "bookmarkpage",
- "spell-no-suggestions", "spell-add-to-dictionary",
- "spell-undo-add-to-dictionary", "openlinkincurrent", "openlinkintab",
- "openlink",
- // "openlinkprivate" intentionally omitted for privacy reasons. See bug 1176391.
- "bookmarklink", "savelink",
- "marklinkMenu", "copyemail", "copylink", "media-play", "media-pause",
- "media-mute", "media-unmute", "media-playbackrate",
- "media-playbackrate-050x", "media-playbackrate-100x",
- "media-playbackrate-125x", "media-playbackrate-150x", "media-playbackrate-200x",
- "media-showcontrols", "media-hidecontrols",
- "video-fullscreen", "leave-dom-fullscreen",
- "reloadimage", "viewimage", "viewvideo", "copyimage-contents", "copyimage",
- "copyvideourl", "copyaudiourl", "saveimage", "sendimage",
- "setDesktopBackground", "viewimageinfo", "viewimagedesc", "savevideo",
- "saveaudio", "video-saveimage", "sendvideo", "sendaudio",
- "ctp-play", "ctp-hide", "savepage", "pocket", "markpageMenu",
- "viewbgimage", "undo", "cut", "copy", "paste", "delete", "selectall",
- "keywordfield", "searchselect", "frame", "showonlythisframe",
- "openframeintab", "openframe", "reloadframe", "bookmarkframe", "saveframe",
- "printframe", "viewframesource", "viewframeinfo",
- "viewpartialsource-selection", "viewpartialsource-mathml",
- "viewsource", "viewinfo", "spell-check-enabled",
- "spell-add-dictionaries-main", "spell-dictionaries",
- "spell-dictionaries-menu", "spell-add-dictionaries",
- "bidi-text-direction-toggle", "bidi-page-direction-toggle", "inspect",
- "media-eme-learn-more"
- ]),
-
- _contextMenuInteractions: {},
-
- registerContextMenuInteraction: function(keys, itemID) {
- if (itemID) {
- if (itemID == "openlinkprivate") {
- // Don't record anything, not even an other-item count
- // if the user chose to open in a private window. See
- // bug 1176391.
- return;
- }
-
- if (!this._contextMenuItemWhitelist.has(itemID)) {
- itemID = "other-item";
- }
- keys.push(itemID);
- }
-
- this._countEvent(keys, this._contextMenuInteractions);
- },
-
- getContextMenuInfo: function() {
- return this._contextMenuInteractions;
- },
-
- _bucket: BUCKET_DEFAULT,
- _bucketTimer: null,
-
- /**
- * Default bucket name, when no other bucket is active.
- */
- get BUCKET_DEFAULT() {
- return BUCKET_DEFAULT;
- },
-
- /**
- * Bucket prefix, for named buckets.
- */
- get BUCKET_PREFIX() {
- return BUCKET_PREFIX;
- },
-
- /**
- * Standard separator to use between different parts of a bucket name, such
- * as primary name and the time step string.
- */
- get BUCKET_SEPARATOR() {
- return BUCKET_SEPARATOR;
- },
-
- get currentBucket() {
- return this._bucket;
- },
-
- /**
- * Sets a named bucket for all countable events and select durections to be
- * put into.
- *
- * @param aName Name of bucket, or null for default bucket name (__DEFAULT__)
- */
- setBucket: function(aName) {
- if (this._bucketTimer) {
- Timer.clearTimeout(this._bucketTimer);
- this._bucketTimer = null;
- }
-
- if (aName)
- this._bucket = BUCKET_PREFIX + aName;
- else
- this._bucket = BUCKET_DEFAULT;
- },
-
- /**
- * Sets a bucket that expires at the rate of a given series of time steps.
- * Once the bucket expires, the current bucket will automatically revert to
- * the default bucket. While the bucket is expiring, it's name is postfixed
- * by '|' followed by a short string representation of the time step it's
- * currently in.
- * If any other bucket (expiring or normal) is set while an expiring bucket is
- * still expiring, the old expiring bucket stops expiring and the new bucket
- * immediately takes over.
- *
- * @param aName Name of bucket.
- * @param aTimeSteps An array of times in milliseconds to count up to before
- * reverting back to the default bucket. The array of times
- * is expected to be pre-sorted in ascending order.
- * For example, given a bucket name of 'bucket', the times:
- * [60000, 300000, 600000]
- * will result in the following buckets:
- * * bucket|1m - for the first 1 minute
- * * bucket|5m - for the following 4 minutes
- * (until 5 minutes after the start)
- * * bucket|10m - for the following 5 minutes
- * (until 10 minutes after the start)
- * * __DEFAULT__ - until a new bucket is set
- * @param aTimeOffset Time offset, in milliseconds, from which to start
- * counting. For example, if the first time step is 1000ms,
- * and the time offset is 300ms, then the next time step
- * will become active after 700ms. This affects all
- * following time steps also, meaning they will also all be
- * timed as though they started expiring 300ms before
- * setExpiringBucket was called.
- */
- setExpiringBucket: function(aName, aTimeSteps, aTimeOffset = 0) {
- if (aTimeSteps.length === 0) {
- this.setBucket(null);
- return;
- }
-
- if (this._bucketTimer) {
- Timer.clearTimeout(this._bucketTimer);
- this._bucketTimer = null;
- }
-
- // Make a copy of the time steps array, so we can safely modify it without
- // modifying the original array that external code has passed to us.
- let steps = [...aTimeSteps];
- let msec = steps.shift();
- let postfix = this._toTimeStr(msec);
- this.setBucket(aName + BUCKET_SEPARATOR + postfix);
-
- this._bucketTimer = Timer.setTimeout(() => {
- this._bucketTimer = null;
- this.setExpiringBucket(aName, steps, aTimeOffset + msec);
- }, msec - aTimeOffset);
- },
-
- /**
- * Formats a time interval, in milliseconds, to a minimal non-localized string
- * representation. Format is: 'h' for hours, 'm' for minutes, 's' for seconds,
- * 'ms' for milliseconds.
- * Examples:
- * 65 => 65ms
- * 1000 => 1s
- * 60000 => 1m
- * 61000 => 1m01s
- *
- * @param aTimeMS Time in milliseconds
- *
- * @return Minimal string representation.
- */
- _toTimeStr: function(aTimeMS) {
- let timeStr = "";
-
- function reduce(aUnitLength, aSymbol) {
- if (aTimeMS >= aUnitLength) {
- let units = Math.floor(aTimeMS / aUnitLength);
- aTimeMS = aTimeMS - (units * aUnitLength)
- timeStr += units + aSymbol;
- }
- }
-
- reduce(MS_HOUR, "h");
- reduce(MS_MINUTE, "m");
- reduce(MS_SECOND, "s");
- reduce(1, "ms");
-
- return timeStr;
- },
-};
-
-/**
- * Returns the id of the first ancestor of aNode that has an id. If aNode
- * has no parent, or no ancestor has an id, returns null.
- *
- * @param aNode the node to find the first ID'd ancestor of
- */
-function getIDBasedOnFirstIDedAncestor(aNode) {
- while (!aNode.id) {
- aNode = aNode.parentNode;
- if (!aNode) {
- return null;
- }
- }
-
- return aNode.id;
-}
diff --git a/application/basilisk/modules/moz.build b/application/basilisk/modules/moz.build
index 684f662d7..d043d4799 100644
--- a/application/basilisk/modules/moz.build
+++ b/application/basilisk/modules/moz.build
@@ -8,7 +8,6 @@ EXTRA_JS_MODULES += [
'AboutHome.jsm',
'AboutNewTab.jsm',
'AttributionCode.jsm',
- 'BrowserUITelemetry.jsm',
'BrowserUsageTelemetry.jsm',
'CastingApps.jsm',
'ContentClick.jsm',
diff --git a/application/palemoon/app/profile/palemoon.js b/application/palemoon/app/profile/palemoon.js
index 38559888b..1894a30f2 100644
--- a/application/palemoon/app/profile/palemoon.js
+++ b/application/palemoon/app/profile/palemoon.js
@@ -1056,11 +1056,16 @@ pref("browser.newtabpage.enabled", true);
// XXX: Remove this when "enhanced" tiles are dead
pref("browser.newtabpage.enhanced", false);
+// enables showing basic placeholders for missing thumbnails
+pref("browser.newtabpage.thumbnailPlaceholder", false);
+
+pref("privacy.usercontext.about_newtab_segregation.enabled", false);
+
// number of columns of newtab grid
pref("browser.newtabpage.columns", 4);
// number of rows of newtab grid
-pref("browser.newtabpage.rows", 4);
+pref("browser.newtabpage.rows", 3);
// Enable the DOM fullscreen API.
pref("full-screen-api.enabled", true);
@@ -1117,6 +1122,12 @@ pref("browser.padlock.urlbar_background", 2);
//Pale Moon standalone image background color
pref("browser.display.standalone_images.background_color", "#2E3B41");
+// These are the thumbnail width/height set in about:newtab.
+// If you change this, ENSURE IT IS THE SAME SIZE SET
+// by about:newtab. These values are in CSS pixels.
+pref("toolkit.pageThumbs.minWidth", 250);
+pref("toolkit.pageThumbs.minHeight", 180);
+
// ****************** domain-specific UAs ******************
// AMO needs "Firefox", obviously - pass on the OS (determined at build time)
diff --git a/application/palemoon/base/content/browser.js b/application/palemoon/base/content/browser.js
index 6df6488b1..386bd418b 100644
--- a/application/palemoon/base/content/browser.js
+++ b/application/palemoon/base/content/browser.js
@@ -2400,6 +2400,9 @@ function PageProxyClickHandler(aEvent)
* to the DOM for unprivileged pages.
*/
function BrowserOnAboutPageLoad(doc) {
+
+ /* === about:home === */
+
if (doc.documentURI.toLowerCase() == "about:home") {
let ss = Components.classes["@mozilla.org/browser/sessionstore;1"].
getService(Components.interfaces.nsISessionStore);
@@ -2438,6 +2441,32 @@ function BrowserOnAboutPageLoad(doc) {
Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified");
}, false);
}
+
+ /* === about:newtab === */
+
+ if (doc.documentURI.toLowerCase() == "about:newtab") {
+
+ let docElt = doc.documentElement;
+
+ function updateSearchEngine() {
+ let engine = AboutHomeUtils.defaultSearchEngine;
+ docElt.setAttribute("searchEngineName", engine.name);
+ docElt.setAttribute("searchEnginePostData", engine.postDataString || "");
+ docElt.setAttribute("searchEngineURL", engine.searchURL);
+ }
+ updateSearchEngine();
+
+ // Listen for the event that's triggered when the user changes search engine.
+ // At this point we simply reload about:newtab to reflect the change.
+ Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false);
+
+ // Remove the observer when the page is reloaded or closed.
+ doc.defaultView.addEventListener("pagehide", function removeObserver() {
+ doc.defaultView.removeEventListener("pagehide", removeObserver);
+ Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified");
+ }, false);
+ }
+
}
/**
diff --git a/application/palemoon/base/content/newtab/drag.js b/application/palemoon/base/content/newtab/drag.js
index fbd688faa..e3928ebd0 100644
--- a/application/palemoon/base/content/newtab/drag.js
+++ b/application/palemoon/base/content/newtab/drag.js
@@ -140,7 +140,7 @@ var gDrag = {
// drag image with its default opacity.
let dragElement = document.createElementNS(HTML_NAMESPACE, "div");
dragElement.classList.add("newtab-drag");
- let scrollbox = document.getElementById("newtab-scrollbox");
+ let scrollbox = document.getElementById("newtab-vertical-margin");
scrollbox.appendChild(dragElement);
dt.setDragImage(dragElement, 0, 0);
diff --git a/application/palemoon/base/content/newtab/dragDataHelper.js b/application/palemoon/base/content/newtab/dragDataHelper.js
index 54348ab14..675ff2671 100644
--- a/application/palemoon/base/content/newtab/dragDataHelper.js
+++ b/application/palemoon/base/content/newtab/dragDataHelper.js
@@ -11,7 +11,7 @@ var gDragDataHelper = {
getLinkFromDragEvent: function DragDataHelper_getLinkFromDragEvent(aEvent) {
let dt = aEvent.dataTransfer;
- if (!dt || !dt.types.contains(this.mimeType)) {
+ if (!dt || !dt.types.includes(this.mimeType)) {
return null;
}
diff --git a/application/palemoon/base/content/newtab/dropTargetShim.js b/application/palemoon/base/content/newtab/dropTargetShim.js
index 046dbea1e..57a97fa00 100644
--- a/application/palemoon/base/content/newtab/dropTargetShim.js
+++ b/application/palemoon/base/content/newtab/dropTargetShim.js
@@ -23,27 +23,53 @@ var gDropTargetShim = {
/**
* Initializes the drop target shim.
*/
- init: function DropTargetShim_init() {
- let node = gGrid.node;
+ init: function () {
+ gGrid.node.addEventListener("dragstart", this, true);
+ },
+
+ /**
+ * Add all event listeners needed during a drag operation.
+ */
+ _addEventListeners: function () {
+ gGrid.node.addEventListener("dragend", this);
- // Add drag event handlers.
- node.addEventListener("dragstart", this, true);
- node.addEventListener("dragend", this, true);
+ let docElement = document.documentElement;
+ docElement.addEventListener("dragover", this);
+ docElement.addEventListener("dragenter", this);
+ docElement.addEventListener("drop", this);
+ },
+
+ /**
+ * Remove all event listeners that were needed during a drag operation.
+ */
+ _removeEventListeners: function () {
+ gGrid.node.removeEventListener("dragend", this);
+
+ let docElement = document.documentElement;
+ docElement.removeEventListener("dragover", this);
+ docElement.removeEventListener("dragenter", this);
+ docElement.removeEventListener("drop", this);
},
/**
* Handles all shim events.
*/
- handleEvent: function DropTargetShim_handleEvent(aEvent) {
+ handleEvent: function (aEvent) {
switch (aEvent.type) {
case "dragstart":
- this._start(aEvent);
+ this._dragstart(aEvent);
+ break;
+ case "dragenter":
+ aEvent.preventDefault();
break;
case "dragover":
this._dragover(aEvent);
break;
+ case "drop":
+ this._drop(aEvent);
+ break;
case "dragend":
- this._end(aEvent);
+ this._dragend(aEvent);
break;
}
},
@@ -52,69 +78,63 @@ var gDropTargetShim = {
* Handles the 'dragstart' event.
* @param aEvent The 'dragstart' event.
*/
- _start: function DropTargetShim_start(aEvent) {
+ _dragstart: function (aEvent) {
if (aEvent.target.classList.contains("newtab-link")) {
gGrid.lock();
-
- // XXX bug 505521 - Listen for dragover on the document.
- document.documentElement.addEventListener("dragover", this, false);
+ this._addEventListeners();
}
},
/**
- * Handles the 'drag' event and determines the current drop target.
- * @param aEvent The 'drag' event.
+ * Handles the 'dragover' event.
+ * @param aEvent The 'dragover' event.
*/
- _drag: function DropTargetShim_drag(aEvent) {
- // Let's see if we find a drop target.
- let target = this._findDropTarget(aEvent);
-
- if (target != this._lastDropTarget) {
- if (this._lastDropTarget)
- // We left the last drop target.
- this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
-
- if (target)
- // We're now hovering a (new) drop target.
- this._dispatchEvent(aEvent, "dragenter", target);
+ _dragover: function (aEvent) {
+ // XXX bug 505521 - Use the dragover event to retrieve the
+ // current mouse coordinates while dragging.
+ let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
+ gDrag.drag(sourceNode._newtabSite, aEvent);
- if (this._lastDropTarget)
- // We left the last drop target.
- this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+ // Find the current drop target, if there's one.
+ this._updateDropTarget(aEvent);
- this._lastDropTarget = target;
+ // If we have a valid drop target,
+ // let the drag-and-drop service know.
+ if (this._lastDropTarget) {
+ aEvent.preventDefault();
}
},
/**
- * Handles the 'dragover' event as long as bug 505521 isn't fixed to get
- * current mouse cursor coordinates while dragging.
- * @param aEvent The 'dragover' event.
+ * Handles the 'drop' event.
+ * @param aEvent The 'drop' event.
*/
- _dragover: function DropTargetShim_dragover(aEvent) {
- let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
- gDrag.drag(sourceNode._newtabSite, aEvent);
+ _drop: function (aEvent) {
+ // We're accepting all drops.
+ aEvent.preventDefault();
- this._drag(aEvent);
+ // remember that drop event was seen, this explicitly
+ // assumes that drop event preceeds dragend event
+ this._dropSeen = true;
+
+ // Make sure to determine the current drop target
+ // in case the dragover event hasn't been fired.
+ this._updateDropTarget(aEvent);
+
+ // A site was successfully dropped.
+ this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
},
/**
* Handles the 'dragend' event.
* @param aEvent The 'dragend' event.
*/
- _end: function DropTargetShim_end(aEvent) {
- // Make sure to determine the current drop target in case the dragenter
- // event hasn't been fired.
- this._drag(aEvent);
-
+ _dragend: function (aEvent) {
if (this._lastDropTarget) {
- if (aEvent.dataTransfer.mozUserCancelled) {
- // The drag operation was cancelled.
+ if (aEvent.dataTransfer.mozUserCancelled || !this._dropSeen) {
+ // The drag operation was cancelled or no drop event was generated
this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
- } else {
- // A site was successfully dropped.
- this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
}
// Clean up.
@@ -122,10 +142,35 @@ var gDropTargetShim = {
this._cellPositions = null;
}
+ this._dropSeen = false;
gGrid.unlock();
+ this._removeEventListeners();
+ },
- // XXX bug 505521 - Remove the document's dragover listener.
- document.documentElement.removeEventListener("dragover", this, false);
+ /**
+ * Tries to find the current drop target and will fire
+ * appropriate dragenter, dragexit, and dragleave events.
+ * @param aEvent The current drag event.
+ */
+ _updateDropTarget: function (aEvent) {
+ // Let's see if we find a drop target.
+ let target = this._findDropTarget(aEvent);
+
+ if (target != this._lastDropTarget) {
+ if (this._lastDropTarget)
+ // We left the last drop target.
+ this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+
+ if (target)
+ // We're now hovering a (new) drop target.
+ this._dispatchEvent(aEvent, "dragenter", target);
+
+ if (this._lastDropTarget)
+ // We left the last drop target.
+ this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+
+ this._lastDropTarget = target;
+ }
},
/**
@@ -133,7 +178,7 @@ var gDropTargetShim = {
* against all cells in the grid.
* @return The currently hovered drop target or null.
*/
- _findDropTarget: function DropTargetShim_findDropTarget() {
+ _findDropTarget: function () {
// These are the minimum intersection values - we want to use the cell if
// the site is >= 50% hovering its position.
let minWidth = gDrag.cellWidth / 2;
@@ -174,13 +219,12 @@ var gDropTargetShim = {
* @param aType The event type.
* @param aTarget The target node that receives the event.
*/
- _dispatchEvent:
- function DropTargetShim_dispatchEvent(aEvent, aType, aTarget) {
-
+ _dispatchEvent: function (aEvent, aType, aTarget) {
let node = aTarget.node;
- let event = document.createEvent("DragEvents");
+ let event = document.createEvent("DragEvent");
- event.initDragEvent(aType, true, true, window, 0, 0, 0, 0, 0, false, false,
+ // The event should not bubble to prevent recursion.
+ event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false,
false, false, 0, node, aEvent.dataTransfer);
node.dispatchEvent(event);
diff --git a/application/palemoon/base/content/newtab/grid.js b/application/palemoon/base/content/newtab/grid.js
index fbeb7bd27..be5a57c4b 100644
--- a/application/palemoon/base/content/newtab/grid.js
+++ b/application/palemoon/base/content/newtab/grid.js
@@ -5,6 +5,12 @@
#endif
/**
+ * Define various fixed dimensions
+ */
+const GRID_BOTTOM_EXTRA = 7; // title's line-height extends 7px past the margin
+const GRID_WIDTH_EXTRA = 1; // provide 1px buffer to allow for rounding error
+
+/**
* This singleton represents the grid that contains all sites.
*/
var gGrid = {
@@ -12,6 +18,7 @@ var gGrid = {
* The DOM node of the grid.
*/
_node: null,
+ _gridDefaultContent: null,
get node() { return this._node; },
/**
@@ -22,7 +29,7 @@ var gGrid = {
/**
* All cells contained in the grid.
*/
- _cells: null,
+ _cells: [],
get cells() { return this._cells; },
/**
@@ -31,7 +38,10 @@ var gGrid = {
get sites() { return [for (cell of this.cells) cell.site]; },
// Tells whether the grid has already been initialized.
- get ready() { return !!this._node; },
+ get ready() { return !!this._ready; },
+
+ // Returns whether the page has finished loading yet.
+ get isDocumentLoaded() { return document.readyState == "complete"; },
/**
* Initializes the grid.
@@ -39,8 +49,26 @@ var gGrid = {
*/
init: function Grid_init() {
this._node = document.getElementById("newtab-grid");
+ this._gridDefaultContent = this._node.lastChild;
this._createSiteFragment();
- this._render();
+
+ gLinks.populateCache(() => {
+ this._refreshGrid();
+ this._ready = true;
+
+ // If fetching links took longer than loading the page itself then
+ // we need to resize the grid as that was blocked until now.
+ // We also want to resize now if the page was already loaded when
+ // initializing the grid (the user toggled the page).
+ this._resizeGrid();
+
+ addEventListener("resize", this);
+ });
+
+ // Resize the grid as soon as the page loads.
+ if (!this.isDocumentLoaded) {
+ addEventListener("load", this);
+ }
},
/**
@@ -56,25 +84,15 @@ var gGrid = {
},
/**
- * Refreshes the grid and re-creates all sites.
+ * Handles all grid events.
*/
- refresh: function Grid_refresh() {
- let cells = this.cells;
- if (!cells) {
- return;
+ handleEvent: function Grid_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "load":
+ case "resize":
+ this._resizeGrid();
+ break;
}
-
- // Remove all sites.
- cells.forEach(function (cell) {
- let node = cell.node;
- let child = node.firstElementChild;
-
- if (child)
- node.removeChild(child);
- }, this);
-
- // Render the grid again.
- this._render();
},
/**
@@ -92,34 +110,62 @@ var gGrid = {
},
/**
- * Creates the newtab grid.
+ * Renders and resizes the gird. _resizeGrid() call is needed to ensure
+ * that scrollbar disappears when the bottom row becomes empty following
+ * the block action, or tile display is turmed off via cog menu
+ */
+
+ refresh() {
+ this._refreshGrid();
+ this._resizeGrid();
+ },
+
+ /**
+ * Renders the grid, including cells and sites.
*/
- _renderGrid: function Grid_renderGrid() {
- let row = document.createElementNS(HTML_NAMESPACE, "div");
+ _refreshGrid() {
let cell = document.createElementNS(HTML_NAMESPACE, "div");
- row.classList.add("newtab-row");
cell.classList.add("newtab-cell");
- // Clear the grid
- this._node.innerHTML = "";
-
- // Creates the structure of one row
- for (let i = 0; i < gGridPrefs.gridColumns; i++) {
- row.appendChild(cell.cloneNode(true));
+ // Creates all the cells up to the maximum
+ let fragment = document.createDocumentFragment();
+ for (let i = 0; i < gGridPrefs.gridColumns * gGridPrefs.gridRows; i++) {
+ fragment.appendChild(cell.cloneNode(true));
}
- // Creates the grid
- for (let j = 0; j < gGridPrefs.gridRows; j++) {
- this._node.appendChild(row.cloneNode(true));
+
+ // Create cells.
+ let cells = Array.from(fragment.childNodes, (cell) => new Cell(this, cell));
+
+ // Fetch links.
+ let links = gLinks.getLinks();
+
+ // Create sites.
+ let numLinks = Math.min(links.length, cells.length);
+ let hasHistoryTiles = false;
+ for (let i = 0; i < numLinks; i++) {
+ if (links[i]) {
+ this.createSite(links[i], cells[i]);
+ if (links[i].type == "history") {
+ hasHistoryTiles = true;
+ }
+ }
}
- // (Re-)initialize all cells.
- let cellElements = this.node.querySelectorAll(".newtab-cell");
- // Tycho: this._cells = [new Cell(this, cell) for (cell of cellElements)];
- this._cells = [];
-
- for (let cellItem of cellElements) {
- this._cells.push(new Cell(this, cellItem));
+ this._cells = cells;
+ while (this._gridDefaultContent.nextSibling) {
+ this._gridDefaultContent.nextSibling.remove();
}
+ this._node.appendChild(fragment);
+ },
+
+ /**
+ * Calculate the height for a number of rows up to the maximum rows
+ * @param rows Number of rows defaulting to the max
+ */
+ _computeHeight: function Grid_computeHeight(aRows) {
+ let {gridRows} = gGridPrefs;
+ aRows = aRows === undefined ? gridRows : Math.min(gridRows, aRows);
+ return aRows * this._cellHeight + GRID_BOTTOM_EXTRA;
},
/**
@@ -133,7 +179,8 @@ var gGrid = {
// Create the site's inner HTML code.
site.innerHTML =
'<a class="newtab-link">' +
- ' <span class="newtab-thumbnail"/>' +
+ ' <span class="newtab-thumbnail placeholder"/>' +
+ ' <span class="newtab-thumbnail thumbnail"/>' +
' <span class="newtab-title"/>' +
'</a>' +
'<input type="button" title="' + newTabString("pin") + '"' +
@@ -146,36 +193,80 @@ var gGrid = {
},
/**
- * Renders the sites, creates all sites and puts them into their cells.
+ * Test a tile at a given position for being pinned or history
+ * @param position Position in sites array
*/
- _renderSites: function Grid_renderSites() {
- let cells = this.cells;
- // Put sites into the cells.
- let links = gLinks.getLinks();
- let length = Math.min(links.length, cells.length);
-
- for (let i = 0; i < length; i++) {
- if (links[i])
- this.createSite(links[i], cells[i]);
- }
+ _isHistoricalTile: function Grid_isHistoricalTile(aPos) {
+ let site = this.sites[aPos];
+ return site && (site.isPinned() || site.link && site.link.type == "history");
},
/**
- * Renders the grid.
+ * Make sure the correct number of rows and columns are visible
*/
- _render: function Grid_render() {
- if (this._shouldRenderGrid()) {
- this._renderGrid();
+ _resizeGrid: function Grid_resizeGrid() {
+ // If we're somehow called before the page has finished loading,
+ // let's bail out to avoid caching zero heights and widths.
+ // We'll be called again when DOMContentLoaded fires.
+ // Same goes for the grid if that's not ready yet.
+ if (!this.isDocumentLoaded || !this._ready) {
+ return;
}
- this._renderSites();
- },
+ // Save the cell's computed height/width including margin and border
+ if (this._cellHeight === undefined) {
+ let refCell = document.querySelector(".newtab-cell");
+ let style = getComputedStyle(refCell);
+ this._cellHeight = refCell.offsetHeight +
+ parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+ this._cellWidth = refCell.offsetWidth +
+ parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+ }
+
+ let searchContainer = document.querySelector("#searchContainer");
+ // Save search-container margin height
+ if (this._searchContainerMargin === undefined) {
+ let style = getComputedStyle(searchContainer);
+ this._searchContainerMargin = parseFloat(style.marginBottom) +
+ parseFloat(style.marginTop);
+ }
+
+ // Find the number of rows we can place into view port
+ let availHeight = document.documentElement.clientHeight -
+ searchContainer.offsetHeight - this._searchContainerMargin;
+ let visibleRows = Math.floor(availHeight / this._cellHeight);
+
+ // Find the number of columns that fit into view port
+ let maxGridWidth = gGridPrefs.gridColumns * this._cellWidth + GRID_WIDTH_EXTRA;
+ // available width is current grid width, but no greater than maxGridWidth
+ let availWidth = Math.min(document.querySelector("#newtab-grid").clientWidth,
+ maxGridWidth);
+ // finally get the number of columns we can fit into view port
+ let gridColumns = Math.floor(availWidth / this._cellWidth);
+ // walk sites backwords until a pinned or history tile is found or visibleRows reached
+ let tileIndex = Math.min(gGridPrefs.gridRows * gridColumns, this.sites.length) - 1;
+ while (tileIndex >= visibleRows * gridColumns) {
+ if (this._isHistoricalTile(tileIndex)) {
+ break;
+ }
+ tileIndex--;
+ }
- _shouldRenderGrid : function Grid_shouldRenderGrid() {
- let rowsLength = this._node.querySelectorAll(".newtab-row").length;
- let cellsLength = this._node.querySelectorAll(".newtab-cell").length;
+ // Compute the actual number of grid rows we will display (potentially
+ // with a scroll bar). tileIndex now points to a historical tile with
+ // heighest index or to the last index of the visible row, if none found
+ // Dividing tileIndex by number of tiles in a column gives the rows
+ let gridRows = Math.floor(tileIndex / gridColumns) + 1;
- return (rowsLength != gGridPrefs.gridRows ||
- cellsLength != (gGridPrefs.gridRows * gGridPrefs.gridColumns));
+ // we need to set grid width, for otherwise the scrollbar may shrink
+ // the grid when shown and cause grid layout to be different from
+ // what being computed above. This, in turn, may cause scrollbar shown
+ // for directory tiles, and introduce jitter when grid width is aligned
+ // exactly on the column boundary
+ this._node.style.width = gridColumns * this._cellWidth + "px";
+ this._node.style.maxWidth = gGridPrefs.gridColumns * this._cellWidth +
+ GRID_WIDTH_EXTRA + "px";
+ this._node.style.height = this._computeHeight() + "px";
+ this._node.style.maxHeight = this._computeHeight(gridRows) + "px";
}
};
diff --git a/application/palemoon/base/content/newtab/newTab.css b/application/palemoon/base/content/newtab/newTab.css
index 830e4a8c1..a5431cf65 100644
--- a/application/palemoon/base/content/newtab/newTab.css
+++ b/application/palemoon/base/content/newtab/newTab.css
@@ -2,26 +2,37 @@
* 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/. */
-input[type=button] {
- cursor: pointer;
+html {
+ width: 100%;
+ height: 100%;
}
-/* SCROLLBOX */
-#newtab-scrollbox {
+body {
+ font: message-box;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ background-color: #F9F9F9;
display: -moz-box;
position: relative;
-moz-box-flex: 1;
-moz-user-focus: normal;
+ -moz-box-orient: vertical;
}
-#newtab-scrollbox:not([page-disabled]) {
- overflow: auto;
+input {
+ font: message-box;
+ font-size: 16px;
+}
+
+input[type=button] {
+ cursor: pointer;
}
/* UNDO */
#newtab-undo-container {
transition: opacity 100ms ease-out;
- display: -moz-box;
-moz-box-align: center;
-moz-box-pack: center;
}
@@ -31,18 +42,6 @@ input[type=button] {
pointer-events: none;
}
-/* TOGGLE */
-#newtab-toggle {
- position: absolute;
- top: 12px;
- right: 12px;
-}
-
-#newtab-toggle:-moz-locale-dir(rtl) {
- left: 12px;
- right: auto;
-}
-
/* MARGINS */
#newtab-vertical-margin {
display: -moz-box;
@@ -51,40 +50,52 @@ input[type=button] {
-moz-box-orient: vertical;
}
-#newtab-margin-top {
- min-height: 50px;
- max-height: 80px;
+#newtab-margin-undo-container {
+ display: -moz-box;
+ left: 6px;
+ position: absolute;
+ top: 6px;
+ z-index: 1;
+}
+
+#newtab-margin-undo-container:dir(rtl) {
+ left: auto;
+ right: 6px;
+}
+
+#newtab-undo-close-button:dir(rtl) {
+ float:left;
+}
+
+#newtab-horizontal-margin {
display: -moz-box;
-moz-box-flex: 1;
- -moz-box-align: center;
- -moz-box-pack: center;
}
+#newtab-margin-top,
#newtab-margin-bottom {
- min-height: 40px;
- max-height: 100px;
+ display: -moz-box;
+ position: relative;
+}
+
+#newtab-margin-top {
-moz-box-flex: 1;
}
-#newtab-horizontal-margin {
- display: -moz-box;
- -moz-box-flex: 5;
+#newtab-margin-bottom {
+ -moz-box-flex: 2;
}
.newtab-side-margin {
- min-width: 40px;
- max-width: 300px;
+ min-width: 10px;
-moz-box-flex: 1;
}
/* GRID */
#newtab-grid {
- display: -moz-box;
-moz-box-flex: 5;
- -moz-box-orient: vertical;
- min-width: 600px;
- min-height: 400px;
- transition: 100ms ease-out;
+ overflow: hidden;
+ transition: 300ms ease-out;
transition-property: opacity;
}
@@ -97,25 +108,25 @@ input[type=button] {
pointer-events: none;
}
-/* ROWS */
-.newtab-row {
- display: -moz-box;
- -moz-box-orient: horizontal;
- -moz-box-direction: normal;
- -moz-box-flex: 1;
-}
-
+/*
+ * If you change the sizes here, make sure you
+ * change the preferences:
+ * toolkit.pageThumbs.minWidth
+ * toolkit.pageThumbs.minHeight
+ */
/* CELLS */
.newtab-cell {
display: -moz-box;
- -moz-box-flex: 1;
+ height: 180px;
+ margin: 15px 10px 30px;
+ width: 250px;
}
/* SITES */
.newtab-site {
position: relative;
-moz-box-flex: 1;
- transition: 100ms ease-out;
+ transition: 200ms ease-out;
transition-property: top, left, opacity;
}
@@ -139,38 +150,35 @@ input[type=button] {
bottom: 0;
}
-.newtab-thumbnail {
- opacity: .8;
- transition: opacity 100ms ease-out;
-}
-
-.newtab-thumbnail[dragged],
-.newtab-link:-moz-focusring > .newtab-thumbnail,
-.newtab-site:hover > .newtab-link > .newtab-thumbnail {
- opacity: 1;
-}
-
/* TITLES */
.newtab-title {
+ overflow: hidden;
position: absolute;
- left: 0;
right: 0;
+ text-align: center;
+}
+
+.newtab-title {
bottom: 0;
white-space: nowrap;
- overflow: hidden;
text-overflow: ellipsis;
+ vertical-align: middle;
+}
+
+.newtab-title {
+ left: 0;
+ padding: 0 4px;
}
/* CONTROLS */
.newtab-control {
position: absolute;
- top: 4px;
opacity: 0;
transition: opacity 100ms ease-out;
}
.newtab-control:-moz-focusring,
-.newtab-site:hover > .newtab-control {
+.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-control {
opacity: 1;
}
@@ -184,16 +192,6 @@ input[type=button] {
}
}
-.newtab-control-pin:-moz-locale-dir(ltr),
-.newtab-control-block:-moz-locale-dir(rtl) {
- left: 4px;
-}
-
-.newtab-control-block:-moz-locale-dir(ltr),
-.newtab-control-pin:-moz-locale-dir(rtl) {
- right: 4px;
-}
-
/* DRAG & DROP */
/*
@@ -207,3 +205,124 @@ input[type=button] {
background-color: #fff;
opacity: 0.01;
}
+
+/* SEARCH */
+#searchContainer {
+ display: -moz-box;
+ position: relative;
+ -moz-box-pack: center;
+ margin: 40px 0 15px;
+}
+
+#searchContainer[page-disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+#searchForm {
+ display: -moz-box;
+ position: relative;
+ height: 36px;
+ -moz-box-flex: 1;
+ max-width: 600px; /* 2 * (290 cell width + 10 cell margin) */
+}
+
+#searchEngineLogo {
+ border: 1px transparent;
+ padding: 2px 4px;
+ margin: 0;
+ width: 32px;
+ height: 32px;
+ position: absolute;
+}
+
+#searchText {
+ -moz-box-flex: 1;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 42px;
+ padding-inline-end: 8px;
+ background: hsla(0,0%,100%,.9) padding-box;
+ border: 1px solid;
+ border-spacing: 0;
+ 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 {
+ border-color: hsla(216,100%,60%,.6) hsla(216,76%,52%,.6) hsla(214,100%,40%,.6);
+}
+
+#searchSubmit {
+ margin-inline-start: -1px;
+ padding: 0;
+ border: 1px solid;
+ background-color: #e0e0e0;
+ color: black;
+ border-color: hsla(220,54%,20%,.15) hsla(220,54%,20%,.17) hsla(220,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;
+}
+
+#searchSubmit:hover {
+ background-color: hsl(220,54%,20%);
+ color: white;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText + #searchSubmit:hover {
+ border-color: #5985fc #4573e7 #3264d5;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText[keepfocus] + #searchSubmit {
+ 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(220,54%,20%,.03);
+}
+
+#searchText + #searchSubmit:hover {
+ 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(220,54%,20%,.03),
+ 0 0 4px hsla(216,100%,20%,.2);
+}
+
+#searchText + #searchSubmit:hover:active {
+ box-shadow: 0 1px 1px hsla(221,79%,6%,.1) inset,
+ 0 0 1px hsla(221,79%,6%,.2) inset;
+ transition-duration: 0ms;
+}
+
+.contentSearchSuggestionTable {
+ font: message-box;
+ font-size: 16px;
+}
diff --git a/application/palemoon/base/content/newtab/newTab.js b/application/palemoon/base/content/newtab/newTab.js
index bea545ab5..0022f21bb 100644
--- a/application/palemoon/base/content/newtab/newTab.js
+++ b/application/palemoon/base/content/newtab/newTab.js
@@ -10,6 +10,7 @@ var Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
Cu.import("resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Rect",
@@ -31,13 +32,24 @@ XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
createBundle("chrome://browser/locale/newTab.properties");
});
-function newTabString(name) gStringBundle.GetStringFromName('newtab.' + name);
+function newTabString(name, args) {
+ let stringName = "newtab." + name;
+ if (!args) {
+ return gStringBundle.GetStringFromName(stringName);
+ }
+ return gStringBundle.formatStringFromName(stringName, args, args.length);
+}
function inPrivateBrowsingMode() {
- return PrivateBrowsingUtils.isWindowPrivate(window);
+ return PrivateBrowsingUtils.isContentWindowPrivate(window);
}
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
+const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-tiles-work-firefox";
+const TILES_INTRO_LINK = "https://www.mozilla.org/firefox/tiles/";
+const TILES_PRIVACY_LINK = "https://www.mozilla.org/privacy/";
#include transformations.js
#include page.js
@@ -51,6 +63,7 @@ const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
#include dropPreview.js
#include updater.js
#include undo.js
+#include search.js
// Everything is loaded. Initialize the New Tab Page.
gPage.init();
diff --git a/application/palemoon/base/content/newtab/newTab.xhtml b/application/palemoon/base/content/newtab/newTab.xhtml
new file mode 100644
index 000000000..eac62c987
--- /dev/null
+++ b/application/palemoon/base/content/newtab/newTab.xhtml
@@ -0,0 +1,60 @@
+<?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 % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
+ %newTabDTD;
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+ %browserDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&newtab.pageTitle;</title>
+
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/" />
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/newtab/newTab.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/newtab/newTab.css" />
+</head>
+
+<body dir="&locale.dir;">
+ <div id="newtab-vertical-margin">
+ <div id="newtab-margin-top"/>
+
+ <div id="newtab-margin-undo-container">
+ <div id="newtab-undo-container" undo-disabled="true">
+ <label id="newtab-undo-label">&newtab.undo.removedLabel;</label>
+ <button id="newtab-undo-button" tabindex="-1"
+ class="newtab-undo-button">&newtab.undo.undoButton;</button>
+ <button id="newtab-undo-restore-button" tabindex="-1"
+ class="newtab-undo-button">&newtab.undo.restoreButton;</button>
+ <button id="newtab-undo-close-button" tabindex="-1" title="&newtab.undo.closeTooltip;"/>
+ </div>
+ </div>
+
+ <div id="searchContainer">
+ <form name="searchForm" id="searchForm" onsubmit="onSearchSubmit(event)">
+ <div id="searchLogoContainer"><img id="searchEngineLogo"/></div>
+ <input type="text" name="q" value="" id="searchText" maxlength="256"/>
+ <input id="searchSubmit" type="submit" value="&newtab.searchEngineButton.label;"/>
+ </form>
+ </div>
+
+ <div id="newtab-horizontal-margin">
+ <div class="newtab-side-margin"/>
+ <div id="newtab-grid">
+ <!-- site grid -->
+ </div>
+ <div class="newtab-side-margin"/>
+ </div>
+
+ <div id="newtab-margin-bottom"/>
+ </div>
+</body>
+<script type="text/javascript;version=1.8" src="chrome://browser/content/newtab/newTab.js"/>
+</html>
diff --git a/application/palemoon/base/content/newtab/newTab.xul b/application/palemoon/base/content/newtab/newTab.xul
deleted file mode 100644
index 6fc202f29..000000000
--- a/application/palemoon/base/content/newtab/newTab.xul
+++ /dev/null
@@ -1,55 +0,0 @@
-<?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/. -->
-
-<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?>
-
-<!DOCTYPE window [
- <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
- %newTabDTD;
-]>
-
-<xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- title="&newtab.pageTitle;">
-
- <div id="newtab-scrollbox">
-
- <div id="newtab-vertical-margin">
- <div id="newtab-margin-top">
- <div id="newtab-undo-container" undo-disabled="true">
- <xul:label id="newtab-undo-label"
- value="&newtab.undo.removedLabel;" />
- <xul:button id="newtab-undo-button" tabindex="-1"
- label="&newtab.undo.undoButton;"
- class="newtab-undo-button" />
- <xul:button id="newtab-undo-restore-button" tabindex="-1"
- label="&newtab.undo.restoreButton;"
- class="newtab-undo-button" />
- <xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1"
- class="close-icon"
- tooltiptext="&newtab.undo.closeTooltip;" />
- </div>
- </div>
-
- <div id="newtab-horizontal-margin">
- <div class="newtab-side-margin"/>
-
- <div id="newtab-grid">
- </div>
-
- <div class="newtab-side-margin"/>
- </div>
-
- <div id="newtab-margin-bottom"/>
- </div>
- <input id="newtab-toggle" type="button"/>
- </div>
-
- <xul:script type="text/javascript;version=1.8"
- src="chrome://browser/content/newtab/newTab.js"/>
-</xul:window>
diff --git a/application/palemoon/base/content/newtab/page.js b/application/palemoon/base/content/newtab/page.js
index fc836a55e..cbd6750b6 100644
--- a/application/palemoon/base/content/newtab/page.js
+++ b/application/palemoon/base/content/newtab/page.js
@@ -4,6 +4,9 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#endif
+// The amount of time we wait while coalescing updates for hidden pages.
+const SCHEDULE_UPDATE_TIMEOUT_MS = 1000;
+
/**
* This singleton represents the whole 'New Tab Page' and takes care of
* initializing all its components.
@@ -19,9 +22,10 @@ var gPage = {
// Listen for 'unload' to unregister this page.
addEventListener("unload", this, false);
- // Listen for toggle button clicks.
- let button = document.getElementById("newtab-toggle");
- button.addEventListener("click", this, false);
+ // XXX bug 991111 - Not all click events are correctly triggered when
+ // listening from xhtml nodes -- in particular middle clicks on sites, so
+ // listen from the xul window and filter then delegate
+ addEventListener("click", this, false);
// Check if the new tab feature is enabled.
let enabled = gAllPages.enabled;
@@ -34,26 +38,65 @@ var gPage = {
/**
* Listens for notifications specific to this page.
*/
- observe: function Page_observe() {
- let enabled = gAllPages.enabled;
- this._updateAttributes(enabled);
+ observe: function Page_observe(aSubject, aTopic, aData) {
+ if (aTopic == "nsPref:changed") {
+ let enabled = gAllPages.enabled;
+ this._updateAttributes(enabled);
- // Initialize the whole page if we haven't done that, yet.
- if (enabled) {
- this._init();
- } else {
- gUndoDialog.hide();
+ // Update thumbnails to the new enhanced setting
+ if (aData == "browser.newtabpage.enhanced") {
+ this.update();
+ }
+
+ // Initialize the whole page if we haven't done that, yet.
+ if (enabled) {
+ this._init();
+ } else {
+ gUndoDialog.hide();
+ }
+ } else if (aTopic == "page-thumbnail:create" && gGrid.ready) {
+ for (let site of gGrid.sites) {
+ if (site && site.url === aData) {
+ site.refreshThumbnail();
+ }
+ }
}
},
/**
- * Updates the whole page and the grid when the storage has changed.
+ * Updates the page's grid right away for visible pages. If the page is
+ * currently hidden, i.e. in a background tab or in the preloader, then we
+ * batch multiple update requests and refresh the grid once after a short
+ * delay. Accepts a single parameter the specifies the reason for requesting
+ * a page update. The page may decide to delay or prevent a requested updated
+ * based on the given reason.
*/
- update: function Page_update() {
- // The grid might not be ready yet as we initialize it asynchronously.
- if (gGrid.ready) {
- gGrid.refresh();
+ update(reason = "") {
+ // Update immediately if we're visible.
+ if (!document.hidden) {
+ // Ignore updates where reason=links-changed as those signal that the
+ // provider's set of links changed. We don't want to update visible pages
+ // in that case, it is ok to wait until the user opens the next tab.
+ if (reason != "links-changed" && gGrid.ready) {
+ gGrid.refresh();
+ }
+
+ return;
+ }
+
+ // Bail out if we scheduled before.
+ if (this._scheduleUpdateTimeout) {
+ return;
}
+
+ this._scheduleUpdateTimeout = setTimeout(() => {
+ // Refresh if the grid is ready.
+ if (gGrid.ready) {
+ gGrid.refresh();
+ }
+
+ this._scheduleUpdateTimeout = null;
+ }, SCHEDULE_UPDATE_TIMEOUT_MS);
},
/**
@@ -66,19 +109,28 @@ var gPage = {
this._initialized = true;
- gLinks.populateCache(function () {
- // Initialize and render the grid.
- gGrid.init();
+ // 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";
+
+ if (document.hidden) {
+ addEventListener("visibilitychange", this);
+ } else {
+ setTimeout(() => this.onPageFirstVisible());
+ }
+
+ // Initialize and render the grid.
+ gGrid.init();
- // Initialize the drop target shim.
- gDropTargetShim.init();
+ // Initialize the drop target shim.
+ gDropTargetShim.init();
#ifdef XP_MACOSX
- // Workaround to prevent a delay on MacOSX due to a slow drop animation.
- document.addEventListener("dragover", this, false);
- document.addEventListener("drop", this, false);
+ // Workaround to prevent a delay on MacOSX due to a slow drop animation.
+ document.addEventListener("dragover", this, false);
+ document.addEventListener("drop", this, false);
#endif
- }.bind(this));
},
/**
@@ -87,7 +139,7 @@ var gPage = {
*/
_updateAttributes: function Page_updateAttributes(aValue) {
// Set the nodes' states.
- let nodeSelector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid";
+ let nodeSelector = "#newtab-grid, #searchContainer";
for (let node of document.querySelectorAll(nodeSelector)) {
if (aValue)
node.removeAttribute("page-disabled");
@@ -98,15 +150,28 @@ var gPage = {
// Enables/disables the control and link elements.
let inputSelector = ".newtab-control, .newtab-link";
for (let input of document.querySelectorAll(inputSelector)) {
- if (aValue)
+ if (aValue)
input.removeAttribute("tabindex");
else
input.setAttribute("tabindex", "-1");
}
+ },
- // Update the toggle button's title.
- let toggle = document.getElementById("newtab-toggle");
- toggle.setAttribute("title", newTabString(aValue ? "hide" : "show"));
+ /**
+ * Handles unload event
+ */
+ _handleUnloadEvent: function Page_handleUnloadEvent() {
+ gAllPages.unregister(this);
+ // compute page life-span and send telemetry probe: using milli-seconds will leave
+ // many low buckets empty. Instead we use half-second precision to make low end
+ // of histogram linear and not lose the change in user attention
+ let delta = Math.round((Date.now() - this._firstVisibleTime) / 500);
+ if (this._suggestedTilePresent) {
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_LIFE_SPAN_SUGGESTED").add(delta);
+ }
+ else {
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_LIFE_SPAN").add(delta);
+ }
},
/**
@@ -114,11 +179,22 @@ var gPage = {
*/
handleEvent: function Page_handleEvent(aEvent) {
switch (aEvent.type) {
+ case "load":
+ this.onPageVisibleAndLoaded();
+ break;
case "unload":
- gAllPages.unregister(this);
+ this._handleUnloadEvent();
break;
case "click":
- gAllPages.enabled = !gAllPages.enabled;
+ let {button, target} = aEvent;
+ // Go up ancestors until we find a Site or not
+ while (target) {
+ if (target.hasOwnProperty("_newtabSite")) {
+ target._newtabSite.onClick(aEvent);
+ break;
+ }
+ target = target.parentNode;
+ }
break;
case "dragover":
if (gDrag.isValid(aEvent) && gDrag.draggedSite)
@@ -130,6 +206,78 @@ var gPage = {
aEvent.stopPropagation();
}
break;
+ case "visibilitychange":
+ // Cancel any delayed updates for hidden pages now that we're visible.
+ if (this._scheduleUpdateTimeout) {
+ clearTimeout(this._scheduleUpdateTimeout);
+ this._scheduleUpdateTimeout = null;
+
+ // An update was pending so force an update now.
+ this.update();
+ }
+
+ setTimeout(() => this.onPageFirstVisible());
+ removeEventListener("visibilitychange", this);
+ break;
+ }
+ },
+
+ onPageFirstVisible: function () {
+ // Record another page impression.
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
+
+ for (let site of gGrid.sites) {
+ if (site) {
+ // The site may need to modify and/or re-render itself if
+ // something changed after newtab was created by preloader.
+ // For example, the suggested tile endTime may have passed.
+ site.onFirstVisible();
+ }
+ }
+
+ // save timestamp to compute page life-span delta
+ this._firstVisibleTime = Date.now();
+
+ if (document.readyState == "complete") {
+ this.onPageVisibleAndLoaded();
+ } else {
+ addEventListener("load", this);
}
- }
+ },
+
+ onPageVisibleAndLoaded() {
+ // Send the index of the last visible tile.
+ this.reportLastVisibleTileIndex();
+ // Maybe tell the user they can undo an initial automigration
+ this.maybeShowAutoMigrationUndoNotification();
+ },
+
+ reportLastVisibleTileIndex() {
+ let cwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let rect = cwu.getBoundsWithoutFlushing(gGrid.node);
+ let nodes = cwu.nodesFromRect(rect.left, rect.top, 0, rect.width,
+ rect.height, 0, true, false);
+
+ let i = -1;
+ let lastIndex = -1;
+ let sites = gGrid.sites;
+
+ for (let node of nodes) {
+ if (node.classList && node.classList.contains("newtab-cell")) {
+ if (sites[++i]) {
+ lastIndex = i;
+ if (sites[i].link.targetedSite) {
+ // record that suggested tile is shown to use suggested-tiles-histogram
+ this._suggestedTilePresent = true;
+ }
+ }
+ }
+ }
+ },
+
+ maybeShowAutoMigrationUndoNotification() {
+ // sendAsyncMessage("NewTab:MaybeShowAutoMigrationUndoNotification");
+ },
};
diff --git a/application/palemoon/base/content/newtab/search.js b/application/palemoon/base/content/newtab/search.js
new file mode 100644
index 000000000..8bc959eee
--- /dev/null
+++ b/application/palemoon/base/content/newtab/search.js
@@ -0,0 +1,134 @@
+#ifdef 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
+
+const SEARCH_ENGINES = {
+ "DuckDuckGo": {
+ image: "data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAACT1BMVEXvISn/////9/fvUlr3ra3/" +
+ "zs7/7+/va2v/5+f/xsbvMTn/tbX/3t7/vb3vOUL3WmPvQkr/zgDvKTHvSlL3hIT3paX/1tbnISn3" +
+ "c3v3e3v3a3P3jIz3nJz/tb33c3PvKSn3lJT39/cAc73vSkr3e4Tv7+/3Yxj3pa3/tQj3jJT3nKX3" +
+ "Y2P/xs73hIzvQkL/vQjvQiHn5+f3hBD/ztbvMTH/vcb/3ucIc733lJz/pQilzufe7/fvMSHOzs73" +
+ "//cQrUpKvVprxmP3Y2vvShiUzmvWlJRzzmMYtUrvOTnn7/davVrWra3v9//nY2PvISGUxudztd7e" +
+ "3t7/76XvKSHea2v/xgDnOUK93vfW5/f/1t73Uhj/52ut3q2l3rXO784pjMZrrdb/rQjera3/5+/e" +
+ "paWMxufO79aEazkYrUr/nAj3jBD3axj3lBD///fehIRKpd7/1hCEYzk5vVL3//8ptVLW77UxtVLn" +
+ "SlLW1tZCvVp7vef/1gj/3invSkL//+fWtbXvpaX/3kr/97XvnJznWmMxjM5zvefOxsbWnKXWjIzG" +
+ "3u/ea3Pn997O5/fnQkqExuf3Whit1u/nUlrnxs7v5+d7zmuU1pT3exDOSjFjrVL/987/pUoQe8b/" +
+ "75T/3jFKxnO158bWKSl7zoRSxmtajEK1e0pzxlqcUjH/1iHOMSnOvb33cxDWnJx7td6EzmP/74xz" +
+ "azlrcznec3Pe771jxlpzczne78YpvVqEvWPn99YxvWOtSjHee3vG787OOTE5lEK1QjHv9+drzmve" +
+ "tbXO772q+r8wAAAFbUlEQVR4Xo2X84PzTBDHN3Zqu2fbemzbNl7atm3btvGHvTNJ2myuyd3NL2mT" +
+ "zmdnvjM76RImyGQlH5dCHBeSmscNmQkyfwBrZMLEY2aRF5cMSDYPEx+LZpUlAYRQbVEpnuc1je/M" +
+ "SbVwYoVFAbpE0IaLmiwqiVymmE3H84YuGs2mheCEhQH5qPUrje2ONxHKVIkXR2x2MxsMkDnLvftk" +
+ "2fSTQNCzSAgngwCCipkXxHiU+BsnCDFE8f6AQgnwaTGhkmDLymW8jPsBeIsth8iCpha618El1wgo" +
+ "4FOhWyWLWY+O8pbnAwTI29S1ElncJBmF4L0AGeJSdR4dUpt5w+DL0nAgoUuGGKKCBxDCOxrykaDb" +
+ "+yFQjhUylLlXpAB5jGnIqV6uvvWUcAAhLmDBXIAMrkXRdHQ+cerUiWefq1hRrAgg8LikUgdkQUAx" +
+ "6+2Ze0WLEO/1BQzrHCFNrAPAeDSD4q/Ln6R3p68MSYzDAUiwIEutJM0bHXE/gpEhJMxaAB3T6aT8" +
+ "mfkm+QBiMlwKFqAHvrHu9tvTOLrEdX4hFAkJWQB42qbVyam75ruv3zvF+wBCKJ0MAAV6SAy5+raA" +
+ "y+lb9tYBUw9sffKRJh+CDl2SAEAPquaC76swU1c+zlxbA9if/EIY78AcCBODDKjnVzDM0+sb57zq" +
+ "N14gdpbg4nraBaxm3NWpIDKNgJIIDTxEAKMyVM9/VrFcpijK52PbNhmk0RQORCA8dhGhIkDA+qPV" +
+ "Y/U8No2NHZsUfQCdzYTECSiRSRJKgxYAnK6+tnVrPYL7q2P7GNNnT0L3SQSS61AowK4BAExWq9XJ" +
+ "OmDT5D4GtUab7p92W1aD6AFBOjUKcONNKMG2o9vmScmhd+v5SCTS91StDLBwmHR5q0iiM4yv3X5g" +
+ "sD1i24tUHc0GQOrOihdw+ZV7drx+8I1IzfpaCQ1oSIGsbqEBdxy8KkLb8dYt7m7AFBpEJI8OUIAd" +
+ "Hve+wX509IqYgzLqxKMi5X+r6737wgHfMrZBKGwpQMWP0PN8/8qLn15cSRosEQeI3coxGrzRVfE2" +
+ "BEyTAMNpmbA3k2erPOyq+CUCPGvv3OmGykYBQhiYFbynDLu2uyW826qb7bSlv/VCe2R3vQqhIYQQ" +
+ "nLmSGKUAT1AqXn7V6p72iUsTThsNuhKUAeKMNFaiW2nG08H90IF1m6DywVdsHgA4bPgRGgAqUgBr" +
+ "DwxOtPcdv9RK6yklnaGKOXBMmN7RVCtJJMiUdG2s78dv9HbY7KrI9AQBOHwjaxaA6cKhRLXCHkpF" +
+ "PrAJYBz1su7LtSBQIjzozgI5AJDWsQ7gTJxETTHuEh5yW8kR5+1fvQBT5PDdWgPokE6GSuK3Aaby" +
+ "2KwNyGFIZ8/NfexVMAGXEfe8MA5QTVdrgGe2M9evev6FMwiAYr308nVzcx/SgHwSlswyLgDLHU0K" +
+ "tX5UZwCwZsM1b7516J1333v/g2UAuJoCNMsmZkEDZBXujCoOIfVJxQKsvXnDshvWfrEcAV9RAoqY" +
+ "rfdvHjY06R3tVmtjzQYsQ8ByC/C1O0dEzqkAGqELbiZ1W/RvBr51Ad9ZgO8dQCkh4/q5xvMC6hot" +
+ "sBl7rP1QT+HHQz9RGoSHhkyMgqEBdNPFWSWMY+1nBPxy+MjvZ2aZxB9n/zz3FwKiOTZfotb3AhhF" +
+ "xSUUNmGSjX+vWvPPYacVWJOkUilUT05ymEVb0JFHj9l/AVn+35b/jsx6YzNz8mja+iAEH7rYDntY" +
+ "Gaz3dizW080KWaeICx77kiG7lTKG6EEoPb0Wu0lZ9OA5whFH8GxHQjOMQls5HSs5t/glHX2FYtT/" +
+ "mGAs/fCtFU0vQJUSQYfvIBvVyukuLhbjuood/H6WCbD/AQSFvIO3JDxgAAAAAElFTkSuQmCC"
+ }
+};
+
+// 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) {
+ if (mutation.attributeName == "searchEngineURL") {
+ setupSearchEngine();
+ if (!gInitialized) {
+ gInitialized = true;
+ }
+ return;
+ }
+ }
+});
+
+window.addEventListener("pageshow", function () {
+ window.gObserver.observe(document.documentElement, { attributes: true });
+});
+
+window.addEventListener("pagehide", function() {
+ window.gObserver.disconnect();
+});
+
+function onSearchSubmit(aEvent) {
+ let searchTerms = document.getElementById("searchText").value;
+ let searchURL = document.documentElement.getAttribute("searchEngineURL");
+
+ if (searchURL && searchTerms.length > 0) {
+ const SEARCH_TOKEN = "_searchTerms_";
+ let searchPostData = document.documentElement.getAttribute("searchEnginePostData");
+ if (searchPostData) {
+ // Check if a post form already exists. If so, remove it.
+ const POST_FORM_NAME = "searchFormPost";
+ let form = document.forms[POST_FORM_NAME];
+ if (form) {
+ form.parentNode.removeChild(form);
+ }
+
+ // Create a new post form.
+ form = document.body.appendChild(document.createElement("form"));
+ form.setAttribute("name", POST_FORM_NAME);
+ // Set the URL to submit the form to.
+ form.setAttribute("action", searchURL.replace(SEARCH_TOKEN, searchTerms));
+ form.setAttribute("method", "post");
+
+ // Create new <input type=hidden> elements for search param.
+ searchPostData = searchPostData.split("&");
+ for (let postVar of searchPostData) {
+ let [name, value] = postVar.split("=");
+ if (value == SEARCH_TOKEN) {
+ value = searchTerms;
+ }
+ let input = document.createElement("input");
+ input.setAttribute("type", "hidden");
+ input.setAttribute("name", name);
+ input.setAttribute("value", value);
+ form.appendChild(input);
+ }
+ // Submit the form.
+ form.submit();
+ } else {
+ searchURL = searchURL.replace(SEARCH_TOKEN, encodeURIComponent(searchTerms));
+ window.location.href = searchURL;
+ }
+ }
+
+ aEvent.preventDefault();
+}
+
+
+function setupSearchEngine() {
+ let searchText = document.getElementById("searchText");
+ let searchEngineName = document.documentElement.getAttribute("searchEngineName");
+ let searchEngineInfo = SEARCH_ENGINES[searchEngineName];
+ let logoElt = document.getElementById("searchEngineLogo");
+
+ // Add search engine logo.
+ if (searchEngineInfo && searchEngineInfo.image) {
+ logoElt.parentNode.hidden = false;
+ logoElt.src = searchEngineInfo.image;
+ logoElt.alt = searchEngineName;
+ searchText.placeholder = "";
+ } else {
+ logoElt.parentNode.hidden = true;
+ searchText.placeholder = searchEngineName;
+ }
+}
diff --git a/application/palemoon/base/content/newtab/sites.js b/application/palemoon/base/content/newtab/sites.js
index 873ef201c..a368146bb 100644
--- a/application/palemoon/base/content/newtab/sites.js
+++ b/application/palemoon/base/content/newtab/sites.js
@@ -4,6 +4,9 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#endif
+const THUMBNAIL_PLACEHOLDER_ENABLED =
+ Services.prefs.getBoolPref("browser.newtabpage.thumbnailPlaceholder");
+
/**
* This class represents a site that is contained in a cell and can be pinned,
* moved around or deleted.
@@ -37,7 +40,7 @@ Site.prototype = {
/**
* The title of the site's link.
*/
- get title() { return this.link.title; },
+ get title() { return this.link.title || this.link.url; },
/**
* The site's parent cell.
@@ -50,13 +53,19 @@ Site.prototype = {
/**
* Pins the site on its current or a given index.
* @param aIndex The pinned index (optional).
+ * @return true if link changed type after pin
*/
pin: function Site_pin(aIndex) {
if (typeof aIndex == "undefined")
aIndex = this.cell.index;
this._updateAttributes(true);
- gPinnedLinks.pin(this._link, aIndex);
+ let changed = gPinnedLinks.pin(this._link, aIndex);
+ if (changed) {
+ // render site again
+ this._render();
+ }
+ return changed;
},
/**
@@ -108,33 +117,145 @@ Site.prototype = {
let control = this._querySelector(".newtab-control-pin");
if (aPinned) {
- control.setAttribute("pinned", true);
+ this.node.setAttribute("pinned", true);
control.setAttribute("title", newTabString("unpin"));
} else {
- control.removeAttribute("pinned");
+ this.node.removeAttribute("pinned");
control.setAttribute("title", newTabString("pin"));
}
},
+ _newTabString: function(str, substrArr) {
+ let regExp = /%[0-9]\$S/g;
+ let matches;
+ while ((matches = regExp.exec(str))) {
+ let match = matches[0];
+ let index = match.charAt(1); // Get the digit in the regExp.
+ str = str.replace(match, substrArr[index - 1]);
+ }
+ return str;
+ },
+
+ _getSuggestedTileExplanation: function() {
+ let targetedName = `<strong> ${this.link.targetedName} </strong>`;
+ let targetedSite = `<strong> ${this.link.targetedSite} </strong>`;
+ if (this.link.explanation) {
+ return this._newTabString(this.link.explanation, [targetedName, targetedSite]);
+ }
+ return newTabString("suggested.button", [targetedName]);
+ },
+
+ /**
+ * Checks for and modifies link at campaign end time
+ */
+ _checkLinkEndTime: function Site_checkLinkEndTime() {
+ if (this.link.endTime && this.link.endTime < Date.now()) {
+ let oldUrl = this.url;
+ // chop off the path part from url
+ this.link.url = Services.io.newURI(this.url, null, null).resolve("/");
+ // clear supplied images - this triggers thumbnail download for new url
+ delete this.link.imageURI;
+ delete this.link.enhancedImageURI;
+ // remove endTime to avoid further time checks
+ delete this.link.endTime;
+ // clear enhanced-content image that may still exist in preloaded page
+ this._querySelector(".enhanced-content").style.backgroundImage = "";
+ gPinnedLinks.replace(oldUrl, this.link);
+ }
+ },
+
/**
* Renders the site's data (fills the HTML fragment).
*/
_render: function Site_render() {
+ // first check for end time, as it may modify the link
+ this._checkLinkEndTime();
+ // setup display variables
let url = this.url;
- let title = this.title || url;
- let tooltip = (title == url ? title : title + "\n" + url);
+ let title = this.link.type == "history" ? this.link.baseDomain :
+ this.title;
+ let tooltip = (this.title == url ? this.title : this.title + "\n" + url);
let link = this._querySelector(".newtab-link");
link.setAttribute("title", tooltip);
link.setAttribute("href", url);
- this._querySelector(".newtab-title").textContent = title;
+ this.node.setAttribute("type", this.link.type);
+
+ let titleNode = this._querySelector(".newtab-title");
+ titleNode.textContent = title;
+ if (this.link.titleBgColor) {
+ titleNode.style.backgroundColor = this.link.titleBgColor;
+ }
if (this.isPinned())
this._updateAttributes(true);
+ // Capture the page if the thumbnail is missing, which will cause page.js
+ // to be notified and call our refreshThumbnail() method.
+ this.captureIfMissing();
+ // but still display whatever thumbnail might be available now.
+ this.refreshThumbnail();
+ },
+
+ /**
+ * Called when the site's tab becomes visible for the first time.
+ * Since the newtab may be preloaded long before it's displayed,
+ * check for changed conditions and re-render if needed
+ */
+ onFirstVisible: function Site_onFirstVisible() {
+ if (this.link.endTime && this.link.endTime < Date.now()) {
+ // site needs to change landing url and background image
+ this._render();
+ }
+ else {
+ this.captureIfMissing();
+ }
+ },
+
+ /**
+ * Captures the site's thumbnail in the background, but only if there's no
+ * existing thumbnail and the page allows background captures.
+ */
+ captureIfMissing: function Site_captureIfMissing() {
+ if (!document.hidden && !this.link.imageURI) {
+ BackgroundPageThumbs.captureIfMissing(this.url);
+ }
+ },
- let thumbnailURL = PageThumbs.getThumbnailURL(this.url);
- let thumbnail = this._querySelector(".newtab-thumbnail");
- thumbnail.style.backgroundImage = "url(" + thumbnailURL + ")";
+ /**
+ * Refreshes the thumbnail for the site.
+ */
+ refreshThumbnail: function Site_refreshThumbnail() {
+ let link = this.link;
+
+ let thumbnail = this._querySelector(".newtab-thumbnail.thumbnail");
+ if (link.bgColor) {
+ thumbnail.style.backgroundColor = link.bgColor;
+ }
+ let uri = link.imageURI || PageThumbs.getThumbnailURL(this.url);
+ thumbnail.style.backgroundImage = 'url("' + uri + '")';
+
+ if (THUMBNAIL_PLACEHOLDER_ENABLED &&
+ link.type == "history" &&
+ link.baseDomain) {
+ let placeholder = this._querySelector(".newtab-thumbnail.placeholder");
+ let charCodeSum = 0;
+ for (let c of link.baseDomain) {
+ charCodeSum += c.charCodeAt(0);
+ }
+ const COLORS = 16;
+ let hue = Math.round((charCodeSum % COLORS) / COLORS * 360);
+ placeholder.style.backgroundColor = "hsl(" + hue + ",80%,40%)";
+ placeholder.textContent = link.baseDomain.substr(0,1).toUpperCase();
+ }
+ },
+
+ _ignoreHoverEvents: function(element) {
+ element.addEventListener("mouseover", () => {
+ this.cell.node.setAttribute("ignorehover", "true");
+ });
+ element.addEventListener("mouseout", () => {
+ this.cell.node.removeAttribute("ignorehover");
+ });
},
/**
@@ -145,10 +266,6 @@ Site.prototype = {
this._node.addEventListener("dragstart", this, false);
this._node.addEventListener("dragend", this, false);
this._node.addEventListener("mouseover", this, false);
-
- let controls = this.node.querySelectorAll(".newtab-control");
- for (let i = 0; i < controls.length; i++)
- controls[i].addEventListener("click", this, false);
},
/**
@@ -157,7 +274,75 @@ Site.prototype = {
_speculativeConnect: function Site_speculativeConnect() {
let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect);
let uri = Services.io.newURI(this.url, null, null);
- sc.speculativeConnect(uri, null);
+ try {
+ // This can throw for certain internal URLs, when they wind up in
+ // about:newtab. Be sure not to propagate the error.
+ sc.speculativeConnect(uri, null);
+ } catch (e) {}
+ },
+
+ /**
+ * Record interaction with site using telemetry.
+ */
+ _recordSiteClicked: function Site_recordSiteClicked(aIndex) {
+ if (Services.prefs.prefHasUserValue("browser.newtabpage.rows") ||
+ Services.prefs.prefHasUserValue("browser.newtabpage.columns") ||
+ aIndex > 8) {
+ // We only want to get indices for the default configuration, everything
+ // else goes in the same bucket.
+ aIndex = 9;
+ }
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
+ .add(aIndex);
+ },
+
+ _toggleLegalText: function(buttonClass, explanationTextClass) {
+ let button = this._querySelector(buttonClass);
+ if (button.hasAttribute("active")) {
+ let explain = this._querySelector(explanationTextClass);
+ explain.parentNode.removeChild(explain);
+
+ button.removeAttribute("active");
+ }
+ },
+
+ /**
+ * Handles site click events.
+ */
+ onClick: function Site_onClick(aEvent) {
+ let action;
+ let pinned = this.isPinned();
+ let tileIndex = this.cell.index;
+ let {button, target} = aEvent;
+
+ // Handle tile/thumbnail link click
+ if (target.classList.contains("newtab-link") ||
+ target.parentElement.classList.contains("newtab-link")) {
+ // Record for primary and middle clicks
+ if (button == 0 || button == 1) {
+ this._recordSiteClicked(tileIndex);
+ action = "click";
+ }
+ }
+ // Only handle primary clicks for the remaining targets
+ else if (button == 0) {
+ aEvent.preventDefault();
+ if (target.classList.contains("newtab-control-block")) {
+ this.block();
+ action = "block";
+ }
+ else if (pinned && target.classList.contains("newtab-control-pin")) {
+ this.unpin();
+ action = "unpin";
+ }
+ else if (!pinned && target.classList.contains("newtab-control-pin")) {
+ if (this.pin()) {
+ // suggested link has changed - update rest of the pages
+ gAllPages.update(gPage);
+ }
+ action = "pin";
+ }
+ }
},
/**
@@ -165,15 +350,6 @@ Site.prototype = {
*/
handleEvent: function Site_handleEvent(aEvent) {
switch (aEvent.type) {
- case "click":
- aEvent.preventDefault();
- if (aEvent.target.classList.contains("newtab-control-block"))
- this.block();
- else if (this.isPinned())
- this.unpin();
- else
- this.pin();
- break;
case "mouseover":
this._node.removeEventListener("mouseover", this, false);
this._speculativeConnect();
@@ -181,9 +357,6 @@ Site.prototype = {
case "dragstart":
gDrag.start(this, aEvent);
break;
- case "drag":
- gDrag.drag(this, aEvent);
- break;
case "dragend":
gDrag.end(this, aEvent);
break;
diff --git a/application/palemoon/base/content/newtab/transformations.js b/application/palemoon/base/content/newtab/transformations.js
index 978116182..f7db0ad84 100644
--- a/application/palemoon/base/content/newtab/transformations.js
+++ b/application/palemoon/base/content/newtab/transformations.js
@@ -156,7 +156,7 @@ var gTransformation = {
finish();
} else {
this.setSitePosition(aSite, targetPosition);
- this._whenTransitionEnded(aSite.node, finish);
+ this._whenTransitionEnded(aSite.node, ["left", "top"], finish);
}
},
@@ -181,13 +181,13 @@ var gTransformation = {
batch.push(new Promise(resolve => {
if (!cells[aIndex]) {
- // The site disappeared from the grid, hide it.
+ // The site disappeared from the grid, hide it.
this.hideSite(aSite, resolve);
} else if (this._getNodeOpacity(aSite.node) != 1) {
- // The site disappeared before but is now back, show it.
+ // The site disappeared before but is now back, show it.
this.showSite(aSite, resolve);
} else {
- // The site's position has changed, move it around.
+ // The site's position has changed, move it around.
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: resolve});
}
}));
@@ -202,15 +202,19 @@ var gTransformation = {
* Listens for the 'transitionend' event on a given node and calls the given
* callback.
* @param aNode The node that is transitioned.
+ * @param aProperties The properties we'll wait to be transitioned.
* @param aCallback The callback to call when finished.
*/
_whenTransitionEnded:
- function Transformation_whenTransitionEnded(aNode, aCallback) {
+ function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) {
- aNode.addEventListener("transitionend", function onEnd() {
- aNode.removeEventListener("transitionend", onEnd, false);
- aCallback();
- }, false);
+ let props = new Set(aProperties);
+ aNode.addEventListener("transitionend", function onEnd(e) {
+ if (props.has(e.propertyName)) {
+ aNode.removeEventListener("transitionend", onEnd);
+ aCallback();
+ }
+ });
},
/**
@@ -236,8 +240,9 @@ var gTransformation = {
if (aCallback)
aCallback();
} else {
- if (aCallback)
- this._whenTransitionEnded(aNode, aCallback);
+ if (aCallback) {
+ this._whenTransitionEnded(aNode, ["opacity"], aCallback);
+ }
aNode.style.opacity = aOpacity;
}
diff --git a/application/palemoon/base/content/utilityOverlay.js b/application/palemoon/base/content/utilityOverlay.js
index c4cd87f60..c45297e0b 100644
--- a/application/palemoon/base/content/utilityOverlay.js
+++ b/application/palemoon/base/content/utilityOverlay.js
@@ -328,6 +328,7 @@ function openLinkIn(url, where, params) {
// result in a new frontmost window (e.g. "javascript:window.open('');").
w.focus();
+ let browserUsedForLoad = null;
switch (where) {
case "current":
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
@@ -346,27 +347,35 @@ function openLinkIn(url, where, params) {
referrerPolicy: aReferrerPolicy,
postData: aPostData,
});
+ browserUsedForLoad = aCurrentBrowser;
break;
case "tabshifted":
loadInBackground = !loadInBackground;
// fall through
case "tab":
let browser = w.gBrowser;
- browser.loadOneTab(url, {
- referrerURI: aReferrerURI,
- referrerPolicy: aReferrerPolicy,
- charset: aCharset,
- postData: aPostData,
- inBackground: loadInBackground,
- allowThirdPartyFixup: aAllowThirdPartyFixup,
- relatedToCurrent: aRelatedToCurrent});
+ let tabUsedForLoad = browser.loadOneTab(url, {
+ referrerURI: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: loadInBackground,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ relatedToCurrent: aRelatedToCurrent});
+ browserUsedForLoad = tabUsedForLoad.linkedBrowser;
break;
}
- w.gBrowser.selectedBrowser.focus();
+ // Focus the content, but only if the browser used for the load is selected.
+ if (browserUsedForLoad &&
+ browserUsedForLoad == browserUsedForLoad.getTabBrowser().selectedBrowser) {
+ browserUsedForLoad.focus();
+ }
if (!loadInBackground && w.isBlankPageURL(url))
- w.focusAndSelectUrlBar();
+ if (!w.focusAndSelectUrlBar()) {
+ console.error("Unable to focus and select address bar.")
+ }
}
// Used as an onclick handler for UI elements with link-like behavior.
diff --git a/application/palemoon/base/jar.mn b/application/palemoon/base/jar.mn
index 622d8e0da..246cf9017 100644
--- a/application/palemoon/base/jar.mn
+++ b/application/palemoon/base/jar.mn
@@ -69,7 +69,7 @@ browser.jar:
content/browser/padlock_classic_https.png (content/padlock_classic_https.png)
content/browser/padlock_classic_low.png (content/padlock_classic_low.png)
content/browser/padlock_classic_broken.png (content/padlock_classic_broken.png)
- content/browser/newtab/newTab.xul (content/newtab/newTab.xul)
+ content/browser/newtab/newTab.xhtml (content/newtab/newTab.xhtml)
* content/browser/newtab/newTab.js (content/newtab/newTab.js)
content/browser/newtab/newTab.css (content/newtab/newTab.css)
* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
diff --git a/application/palemoon/components/about/AboutRedirector.cpp b/application/palemoon/components/about/AboutRedirector.cpp
index 12143b399..7e4c634f7 100644
--- a/application/palemoon/components/about/AboutRedirector.cpp
+++ b/application/palemoon/components/about/AboutRedirector.cpp
@@ -53,7 +53,7 @@ static RedirEntry kRedirMap[] = {
nsIAboutModule::ALLOW_SCRIPT
},
{
- "newtab", "chrome://browser/content/newtab/newTab.xul",
+ "newtab", "chrome://browser/content/newtab/newTab.xhtml",
nsIAboutModule::ALLOW_SCRIPT
},
{
diff --git a/application/palemoon/locales/en-US/chrome/browser/newTab.dtd b/application/palemoon/locales/en-US/chrome/browser/newTab.dtd
index ce9e3e4b0..f19297272 100644
--- a/application/palemoon/locales/en-US/chrome/browser/newTab.dtd
+++ b/application/palemoon/locales/en-US/chrome/browser/newTab.dtd
@@ -3,8 +3,15 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- These strings are used in the about:newtab page -->
-<!ENTITY newtab.pageTitle "New Tab">
+<!ENTITY newtab.pageTitle "Quickdial">
<!ENTITY newtab.undo.removedLabel "Thumbnail removed.">
<!ENTITY newtab.undo.undoButton "Undo.">
<!ENTITY newtab.undo.restoreButton "Restore All.">
<!ENTITY newtab.undo.closeTooltip "Hide">
+<!ENTITY newtab.searchEngineButton.label "Search">
+
+<!-- LOCALIZATION NOTE (contentSearchInput.label):
+ This is set as the aria-label attribute for the search input box in the
+ in-content search UI, to be used by screen readers. -->
+<!ENTITY contentSearchInput.label "Search query">
+<!ENTITY contentSearchSubmit.tooltip "Submit search">
diff --git a/application/palemoon/locales/en-US/chrome/browser/newTab.properties b/application/palemoon/locales/en-US/chrome/browser/newTab.properties
index a249356f5..7b3fe248e 100644
--- a/application/palemoon/locales/en-US/chrome/browser/newTab.properties
+++ b/application/palemoon/locales/en-US/chrome/browser/newTab.properties
@@ -5,5 +5,42 @@
newtab.pin=Pin this site at its current position
newtab.unpin=Unpin this site
newtab.block=Remove this site
-newtab.show=Show the new tab page
-newtab.hide=Hide the new tab page
+# LOCALIZATION NOTE(newtab.sponsored.button): This text appears for sponsored
+# and enhanced tiles on the same line as the tile's title, so prefer short
+# strings to avoid overlap. This string should be uppercase.
+newtab.sponsored.button=SPONSORED
+# LOCALIZATION NOTE(newtab.suggested.button): This text appears for sponsored
+# and suggested tiles on the same line as the tile's title, so prefer short
+# strings to avoid overlap. This string should be uppercase.
+newtab.suggested.tag=SUGGESTED
+# LOCALIZATION NOTE(newtab.suggested.button): %1$S will be replaced inline by
+# one of the user's top 100 sites that triggered this suggested tile.
+# This text appears for suggested tiles under the tile's title, so prefer short
+# strings to avoid truncating important text.
+newtab.suggested.button=Suggested for %1$S visitors
+# LOCALIZATION NOTE(newtab.sponsored.explain): %1$S will be replaced inline by
+# the (X) block icon. %2$S will be replaced by an active link using string
+# newtab.learn.link as text.
+newtab.sponsored.explain=This tile is being shown to you on behalf of a Mozilla partner. You can remove it at any time by clicking the %1$S button. %2$S
+# LOCALIZATION NOTE(newtab.sponsored.explain2): %1$S will be replaced inline by
+# the (X) block icon. %2$S will be replaced by an active link using string
+# newtab.learn.link as text.
+newtab.sponsored.explain2=This site is suggested to you on behalf of a Mozilla partner. You can remove it at any time by clicking the %1$S button. %2$S
+# LOCALIZATION NOTE(newtab.suggested.explain): %1$S will be replaced inline by
+# the (X) block icon. %2$S will be replaced by an active link using string
+# newtab.learn.link as text.
+newtab.suggested.explain=This site is suggested to you by Mozilla. You can remove it at any time by clicking the %1$S button. %2$S
+# LOCALIZATION NOTE(newtab.enhanced.explain): %1$S will be replaced inline by
+# the gear icon used to customize the new tab window. %2$S will be replaced by
+# an active link using string newtab.learn.link as text.
+newtab.enhanced.explain=A Mozilla partner has visually enhanced this tile, replacing the screenshot. You can turn off enhanced tiles by clicking the %1$S button for your preferences. %2$S
+newtab.intro1.paragraph1=Now when you open New Tab, you’ll also see sites we think might be interesting to you. Some may be suggested by Mozilla or sponsored by one of our partners.
+# LOCALIZATION NOTE(newtab.intro1.paragraph2): %1$S will be replaced inline by
+# an active link using string newtab.privacy.link as text. %2$S will be replaced
+# inline by the gear icon used to customize the new tab window.
+newtab.intro1.paragraph2=In order to provide this service, some data is automatically sent back to us in accordance with our %1$S. You can turn this off by unchecking the option under the gear icon (%2$S).
+newtab.learn.link=Learn more…
+newtab.privacy.link=Privacy Notice
+newtab.learn.link2=More about New Tab
+newtab.intro.header.update=New Tab got an update!
+newtab.intro.gotit=Got it!
diff --git a/application/palemoon/themes/linux/jar.mn b/application/palemoon/themes/linux/jar.mn
index ec638f54c..8b2e9dc77 100644
--- a/application/palemoon/themes/linux/jar.mn
+++ b/application/palemoon/themes/linux/jar.mn
@@ -76,9 +76,10 @@ browser.jar:
skin/classic/browser/feeds/audioFeedIcon16.png (feeds/feedIcon16.png)
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
- skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
- skin/classic/browser/newtab/controls.png (newtab/controls.png)
- skin/classic/browser/newtab/noise.png (newtab/noise.png)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
+ skin/classic/browser/newtab/noise.png (../shared/newtab/noise.png)
+ skin/classic/browser/newtab/pinned.png (../shared/newtab/pinned.png)
skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
skin/classic/browser/places/calendar.png (places/calendar.png)
diff --git a/application/palemoon/themes/linux/newtab/newTab.css b/application/palemoon/themes/linux/newtab/newTab.css
index 9c132892e..357b3139b 100644
--- a/application/palemoon/themes/linux/newtab/newTab.css
+++ b/application/palemoon/themes/linux/newtab/newTab.css
@@ -2,33 +2,7 @@
* 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/. */
-:root {
- -moz-appearance: none;
- font-size: 75%;
- background-color: transparent;
-}
-
-/* SCROLLBOX */
-#newtab-scrollbox:not([page-disabled]) {
- background-color: rgb(229,229,229);
- background-image: url(chrome://browser/skin/newtab/noise.png),
- linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,.2));
- background-attachment: fixed;
-}
-
-/* UNDO */
-#newtab-undo-container {
- padding: 4px 3px;
- border: 1px solid;
- border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
- background-color: rgba(255,255,255,.4);
- color: #525e69;
-}
-
-#newtab-undo-label {
- margin-top: 0;
- margin-bottom: 0;
-}
+%include ../../shared/newtab/newTab.css.inc
.newtab-undo-button {
-moz-appearance: none;
@@ -42,14 +16,6 @@
min-width: 0;
}
-.newtab-undo-button:hover {
- text-decoration: underline;
-}
-
-.newtab-undo-button:-moz-focusring {
- outline: 1px dotted;
-}
-
#newtab-undo-close-button {
padding: 0;
border: none;
@@ -59,129 +25,3 @@
#newtab-undo-close-button > .toolbarbutton-icon {
margin: -4px;
}
-
-#newtab-undo-close-button > .toolbarbutton-text {
- display: none;
-}
-
-#newtab-undo-close-button:-moz-focusring {
- outline: 1px dotted;
-}
-
-/* TOGGLE */
-#newtab-toggle {
- width: 16px;
- height: 16px;
- padding: 0;
- border: none;
- background: -216px 0 transparent url(chrome://browser/skin/newtab/controls.png);
-}
-
-#newtab-toggle[page-disabled] {
- background-position: -232px 0;
-}
-
-/* ROWS */
-.newtab-row {
- margin-bottom: 20px;
-}
-
-.newtab-row:last-child {
- margin-bottom: 0;
-}
-
-/* CELLS */
-.newtab-cell {
- -moz-margin-end: 20px;
- background-color: rgba(255,255,255,.2);
- border: 1px solid;
- border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
- border-radius: 1px;
- transition: border-color 100ms ease-out;
-}
-
-.newtab-cell:empty {
- border: 1px dashed;
- border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
-}
-
-.newtab-cell:last-child {
- -moz-margin-end: 0;
-}
-
-.newtab-cell:hover:not(:empty):not([dragged]) {
- border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
-}
-
-/* SITES */
-.newtab-site {
- text-decoration: none;
- transition-property: top, left, opacity, box-shadow, background-color;
-}
-
-.newtab-site:hover,
-.newtab-site[dragged] {
- box-shadow: 0 0 10px rgba(8,22,37,.3);
-}
-
-.newtab-site[dragged] {
- transition-property: box-shadow, background-color;
- background-color: rgb(242,242,242);
-}
-
-/* THUMBNAILS */
-.newtab-thumbnail {
- background-origin: padding-box;
- background-clip: padding-box;
- background-repeat: no-repeat;
- background-size: cover;
-}
-
-/* TITLES */
-.newtab-title {
- padding: 0 8px;
- background-color: rgba(248,249,251,.95);
- color: #1f364c;
- line-height: 24px;
-}
-
-/* CONTROLS */
-.newtab-control {
- width: 24px;
- height: 24px;
- padding: 1px 2px 3px;
- border: none;
- background: transparent url(chrome://browser/skin/newtab/controls.png);
-}
-
-.newtab-control-pin:hover {
- background-position: -24px 0;
-}
-
-.newtab-control-pin:active {
- background-position: -48px 0;
-}
-
-.newtab-control-pin[pinned] {
- background-position: -72px 0;
-}
-
-.newtab-control-pin[pinned]:hover {
- background-position: -96px 0;
-}
-
-.newtab-control-pin[pinned]:active {
- background-position: -120px 0;
-}
-
-.newtab-control-block {
- background-position: -144px 0;
-}
-
-.newtab-control-block:hover {
- background-position: -168px 0;
-}
-
-.newtab-control-block:active {
- background-position: -192px 0;
-}
diff --git a/application/palemoon/themes/osx/jar.mn b/application/palemoon/themes/osx/jar.mn
index 17d0637f7..e0c1ccbba 100644
--- a/application/palemoon/themes/osx/jar.mn
+++ b/application/palemoon/themes/osx/jar.mn
@@ -93,9 +93,10 @@ browser.jar:
skin/classic/browser/feeds/videoFeedIcon16.png (feeds/feedIcon16.png)
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
- skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
- skin/classic/browser/newtab/controls.png (newtab/controls.png)
- skin/classic/browser/newtab/noise.png (newtab/noise.png)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
+ skin/classic/browser/newtab/noise.png (../shared/newtab/noise.png)
+ skin/classic/browser/newtab/pinned.png (../shared/newtab/pinned.png)
skin/classic/browser/places/places.css (places/places.css)
* skin/classic/browser/places/organizer.css (places/organizer.css)
skin/classic/browser/places/editBookmark.png (places/editBookmark.png)
diff --git a/application/palemoon/themes/osx/newtab/controls.png b/application/palemoon/themes/osx/newtab/controls.png
deleted file mode 100644
index 14f382fbd..000000000
--- a/application/palemoon/themes/osx/newtab/controls.png
+++ /dev/null
Binary files differ
diff --git a/application/palemoon/themes/osx/newtab/newTab.css b/application/palemoon/themes/osx/newtab/newTab.css
index d0403004a..b8b0fd699 100644
--- a/application/palemoon/themes/osx/newtab/newTab.css
+++ b/application/palemoon/themes/osx/newtab/newTab.css
@@ -2,33 +2,7 @@
* 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/. */
-:root {
- -moz-appearance: none;
- font-size: 75%;
- background-color: transparent;
-}
-
-/* SCROLLBOX */
-#newtab-scrollbox:not([page-disabled]) {
- background-color: rgb(229,229,229);
- background-image: url(chrome://browser/skin/newtab/noise.png),
- linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,.2));
- background-attachment: fixed;
-}
-
-/* UNDO */
-#newtab-undo-container {
- padding: 4px 3px;
- border: 1px solid;
- border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
- background-color: rgba(255,255,255,.4);
- color: #525e69;
-}
-
-#newtab-undo-label {
- margin-top: 0;
- margin-bottom: 0;
-}
+%include ../../shared/newtab/newTab.css.inc
.newtab-undo-button {
-moz-appearance: none;
@@ -43,14 +17,6 @@
min-width: 0;
}
-.newtab-undo-button:hover {
- text-decoration: underline;
-}
-
-.newtab-undo-button:-moz-focusring {
- outline: 1px dotted;
-}
-
.newtab-undo-button > .button-box {
padding: 0;
}
@@ -61,129 +27,3 @@
border: none;
-moz-user-focus: normal;
}
-
-#newtab-undo-close-button > .toolbarbutton-text {
- display: none;
-}
-
-#newtab-undo-close-button:-moz-focusring {
- outline: 1px dotted;
-}
-
-/* TOGGLE */
-#newtab-toggle {
- width: 16px;
- height: 16px;
- padding: 0;
- border: none;
- background: -216px 0 transparent url(chrome://browser/skin/newtab/controls.png);
-}
-
-#newtab-toggle[page-disabled] {
- background-position: -232px 0;
-}
-
-/* ROWS */
-.newtab-row {
- margin-bottom: 20px;
-}
-
-.newtab-row:last-child {
- margin-bottom: 0;
-}
-
-/* CELLS */
-.newtab-cell {
- -moz-margin-end: 20px;
- background-color: rgba(255,255,255,.2);
- border: 1px solid;
- border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
- border-radius: 1px;
- transition: border-color 100ms ease-out;
-}
-
-.newtab-cell:empty {
- border: 1px dashed;
- border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
-}
-
-.newtab-cell:last-child {
- -moz-margin-end: 0;
-}
-
-.newtab-cell:hover:not(:empty):not([dragged]) {
- border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
-}
-
-/* SITES */
-.newtab-site {
- text-decoration: none;
- transition-property: top, left, opacity, box-shadow, background-color;
-}
-
-.newtab-site:hover,
-.newtab-site[dragged] {
- box-shadow: 0 0 10px rgba(8,22,37,.3);
-}
-
-.newtab-site[dragged] {
- transition-property: box-shadow, background-color;
- background-color: rgb(242,242,242);
-}
-
-/* THUMBNAILS */
-.newtab-thumbnail {
- background-origin: padding-box;
- background-clip: padding-box;
- background-repeat: no-repeat;
- background-size: cover;
-}
-
-/* TITLES */
-.newtab-title {
- padding: 0 8px;
- background-color: rgba(248,249,251,.95);
- color: #1f364c;
- line-height: 24px;
-}
-
-/* CONTROLS */
-.newtab-control {
- width: 24px;
- height: 24px;
- padding: 1px 2px 3px;
- border: none;
- background: transparent url(chrome://browser/skin/newtab/controls.png);
-}
-
-.newtab-control-pin:hover {
- background-position: -24px 0;
-}
-
-.newtab-control-pin:active {
- background-position: -48px 0;
-}
-
-.newtab-control-pin[pinned] {
- background-position: -72px 0;
-}
-
-.newtab-control-pin[pinned]:hover {
- background-position: -96px 0;
-}
-
-.newtab-control-pin[pinned]:active {
- background-position: -120px 0;
-}
-
-.newtab-control-block {
- background-position: -144px 0;
-}
-
-.newtab-control-block:hover {
- background-position: -168px 0;
-}
-
-.newtab-control-block:active {
- background-position: -192px 0;
-}
diff --git a/application/palemoon/themes/osx/newtab/noise.png b/application/palemoon/themes/osx/newtab/noise.png
deleted file mode 100644
index 01d340aaa..000000000
--- a/application/palemoon/themes/osx/newtab/noise.png
+++ /dev/null
Binary files differ
diff --git a/application/palemoon/themes/linux/newtab/controls.png b/application/palemoon/themes/shared/newtab/controls.png
index 14f382fbd..14f382fbd 100644
--- a/application/palemoon/themes/linux/newtab/controls.png
+++ b/application/palemoon/themes/shared/newtab/controls.png
Binary files differ
diff --git a/application/palemoon/themes/shared/newtab/newTab.css.inc b/application/palemoon/themes/shared/newtab/newTab.css.inc
new file mode 100644
index 000000000..4ffd32d50
--- /dev/null
+++ b/application/palemoon/themes/shared/newtab/newTab.css.inc
@@ -0,0 +1,203 @@
+/* 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/. */
+
+:root {
+ -moz-appearance: none;
+ font-size: 75%;
+ background-color: transparent;
+}
+
+body {
+ background: linear-gradient(to top,#DFF3FF,#F9F9F9) fixed;
+}
+
+/* SCROLLBOX */
+#newtab-scrollbox:not([page-disabled]) {
+ background-color: rgb(229,229,229);
+ background-image: url(chrome://browser/skin/newtab/noise.png),
+ linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,.2));
+ background-attachment: fixed;
+}
+
+/* UNDO */
+#newtab-undo-container {
+ padding: 4px 3px;
+ border: 1px solid;
+ border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
+ background-color: rgba(255,255,255,.4);
+ color: #525e69;
+}
+
+#newtab-undo-label {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+
+.newtab-undo-button:hover {
+ text-decoration: underline;
+}
+
+.newtab-undo-button:-moz-focusring {
+ outline: 1px dotted;
+}
+
+
+#newtab-undo-close-button > .toolbarbutton-text {
+ display: none;
+}
+
+#newtab-undo-close-button:-moz-focusring {
+ outline: 1px dotted;
+}
+
+/* TOGGLE */
+#newtab-toggle {
+ width: 16px;
+ height: 16px;
+ padding: 0;
+ border: none;
+ background: -216px 0 transparent url(chrome://browser/skin/newtab/controls.png);
+}
+
+#newtab-toggle[page-disabled] {
+ background-position: -232px 0;
+}
+
+/* ROWS */
+.newtab-row {
+ margin-bottom: 20px;
+}
+
+.newtab-row:last-child {
+ margin-bottom: 0;
+}
+
+/* CELLS */
+.newtab-cell {
+ -moz-margin-end: 20px;
+ background-color: rgba(255,255,255,.2);
+ border: 1px solid;
+ border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
+ border-radius: 1px;
+ transition: border-color 100ms ease-out;
+}
+
+.newtab-cell:empty {
+ border: 1px dashed;
+ border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
+}
+
+.newtab-cell:last-child {
+ -moz-margin-end: 0;
+}
+
+.newtab-cell:hover:not(:empty):not([dragged]) {
+ border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
+}
+
+/* SITES */
+.newtab-site {
+ text-decoration: none;
+ transition-property: top, left, opacity, box-shadow, background-color;
+}
+
+.newtab-site:hover,
+.newtab-site[dragged] {
+ box-shadow: 0 3px 10px rgba(8,20,37,.6);
+}
+
+.newtab-site[dragged] {
+ transition-property: box-shadow, background-color;
+ background-color: rgb(242,242,242);
+}
+
+/* THUMBNAILS */
+.newtab-thumbnail {
+ background-origin: padding-box;
+ background-clip: padding-box;
+ background-repeat: no-repeat;
+ background-size: cover;
+}
+
+/* TITLES */
+.newtab-title {
+ padding: 0 8px 1px;
+ background-color: rgba(248,249,251,.95);
+ color: #1f364c;
+ line-height: 24px;
+}
+
+.newtab-site[pinned] .newtab-title {
+ padding-inline-start: 16px;
+}
+
+.newtab-site[pinned] .newtab-title::before {
+ background-image: url(chrome://browser/skin/newtab/pinned.png);
+ content: "";
+ left: 2px;
+ top: 2px;
+ position: absolute;
+ width: 12px;
+ height: 20px;
+}
+
+.newtab-site[pinned] .newtab-title:dir(rtl)::before {
+ left: auto;
+ right: 2px;
+}
+
+/* CONTROLS */
+.newtab-control {
+ background-color: transparent;
+ background-size: 24px;
+ border: none;
+ height: 24px;
+ width: 24px;
+ top: 4px;
+ background: transparent url(chrome://browser/skin/newtab/controls.png);
+}
+
+.newtab-control-pin:dir(ltr),
+.newtab-control-block:dir(rtl) {
+ left: 4px;
+}
+
+.newtab-control-block:dir(ltr),
+.newtab-control-pin:dir(rtl) {
+ right: 4px;
+}
+
+.newtab-control-pin:hover {
+ background-position: -24px 0;
+}
+
+.newtab-control-pin:active {
+ background-position: -48px 0;
+}
+
+.newtab-site[pinned] .newtab-control-pin {
+ background-position: -72px 0;
+}
+
+.newtab-site[pinned] .newtab-control-pin:hover {
+ background-position: -96px 0;
+}
+
+.newtab-site[pinned] .newtab-control-pin:active {
+ background-position: -120px 0;
+}
+
+.newtab-control-block {
+ background-position: -144px 0;
+}
+
+.newtab-control-block:hover {
+ background-position: -168px 0;
+}
+
+.newtab-control-block:active {
+ background-position: -192px 0;
+}
+
diff --git a/application/palemoon/themes/linux/newtab/noise.png b/application/palemoon/themes/shared/newtab/noise.png
index 01d340aaa..01d340aaa 100644
--- a/application/palemoon/themes/linux/newtab/noise.png
+++ b/application/palemoon/themes/shared/newtab/noise.png
Binary files differ
diff --git a/application/palemoon/themes/shared/newtab/pinned.png b/application/palemoon/themes/shared/newtab/pinned.png
new file mode 100644
index 000000000..ddd731bc6
--- /dev/null
+++ b/application/palemoon/themes/shared/newtab/pinned.png
Binary files differ
diff --git a/application/palemoon/themes/windows/jar.mn b/application/palemoon/themes/windows/jar.mn
index b660ba296..0a4342d40 100644
--- a/application/palemoon/themes/windows/jar.mn
+++ b/application/palemoon/themes/windows/jar.mn
@@ -95,9 +95,10 @@ browser.jar:
skin/classic/browser/feeds/feed-icons-16.png (feeds/feed-icons-16.png)
skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
- skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
- skin/classic/browser/newtab/controls.png (newtab/controls.png)
- skin/classic/browser/newtab/noise.png (newtab/noise.png)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
+ skin/classic/browser/newtab/noise.png (../shared/newtab/noise.png)
+ skin/classic/browser/newtab/pinned.png (../shared/newtab/pinned.png)
skin/classic/browser/places/places.css (places/places.css)
* skin/classic/browser/places/organizer.css (places/organizer.css)
skin/classic/browser/places/editBookmark.png (places/editBookmark.png)
diff --git a/application/palemoon/themes/windows/newtab/controls.png b/application/palemoon/themes/windows/newtab/controls.png
deleted file mode 100644
index 14f382fbd..000000000
--- a/application/palemoon/themes/windows/newtab/controls.png
+++ /dev/null
Binary files differ
diff --git a/application/palemoon/themes/windows/newtab/newTab.css b/application/palemoon/themes/windows/newtab/newTab.css
index d0403004a..b8b0fd699 100644
--- a/application/palemoon/themes/windows/newtab/newTab.css
+++ b/application/palemoon/themes/windows/newtab/newTab.css
@@ -2,33 +2,7 @@
* 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/. */
-:root {
- -moz-appearance: none;
- font-size: 75%;
- background-color: transparent;
-}
-
-/* SCROLLBOX */
-#newtab-scrollbox:not([page-disabled]) {
- background-color: rgb(229,229,229);
- background-image: url(chrome://browser/skin/newtab/noise.png),
- linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,.2));
- background-attachment: fixed;
-}
-
-/* UNDO */
-#newtab-undo-container {
- padding: 4px 3px;
- border: 1px solid;
- border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
- background-color: rgba(255,255,255,.4);
- color: #525e69;
-}
-
-#newtab-undo-label {
- margin-top: 0;
- margin-bottom: 0;
-}
+%include ../../shared/newtab/newTab.css.inc
.newtab-undo-button {
-moz-appearance: none;
@@ -43,14 +17,6 @@
min-width: 0;
}
-.newtab-undo-button:hover {
- text-decoration: underline;
-}
-
-.newtab-undo-button:-moz-focusring {
- outline: 1px dotted;
-}
-
.newtab-undo-button > .button-box {
padding: 0;
}
@@ -61,129 +27,3 @@
border: none;
-moz-user-focus: normal;
}
-
-#newtab-undo-close-button > .toolbarbutton-text {
- display: none;
-}
-
-#newtab-undo-close-button:-moz-focusring {
- outline: 1px dotted;
-}
-
-/* TOGGLE */
-#newtab-toggle {
- width: 16px;
- height: 16px;
- padding: 0;
- border: none;
- background: -216px 0 transparent url(chrome://browser/skin/newtab/controls.png);
-}
-
-#newtab-toggle[page-disabled] {
- background-position: -232px 0;
-}
-
-/* ROWS */
-.newtab-row {
- margin-bottom: 20px;
-}
-
-.newtab-row:last-child {
- margin-bottom: 0;
-}
-
-/* CELLS */
-.newtab-cell {
- -moz-margin-end: 20px;
- background-color: rgba(255,255,255,.2);
- border: 1px solid;
- border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
- border-radius: 1px;
- transition: border-color 100ms ease-out;
-}
-
-.newtab-cell:empty {
- border: 1px dashed;
- border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
-}
-
-.newtab-cell:last-child {
- -moz-margin-end: 0;
-}
-
-.newtab-cell:hover:not(:empty):not([dragged]) {
- border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
-}
-
-/* SITES */
-.newtab-site {
- text-decoration: none;
- transition-property: top, left, opacity, box-shadow, background-color;
-}
-
-.newtab-site:hover,
-.newtab-site[dragged] {
- box-shadow: 0 0 10px rgba(8,22,37,.3);
-}
-
-.newtab-site[dragged] {
- transition-property: box-shadow, background-color;
- background-color: rgb(242,242,242);
-}
-
-/* THUMBNAILS */
-.newtab-thumbnail {
- background-origin: padding-box;
- background-clip: padding-box;
- background-repeat: no-repeat;
- background-size: cover;
-}
-
-/* TITLES */
-.newtab-title {
- padding: 0 8px;
- background-color: rgba(248,249,251,.95);
- color: #1f364c;
- line-height: 24px;
-}
-
-/* CONTROLS */
-.newtab-control {
- width: 24px;
- height: 24px;
- padding: 1px 2px 3px;
- border: none;
- background: transparent url(chrome://browser/skin/newtab/controls.png);
-}
-
-.newtab-control-pin:hover {
- background-position: -24px 0;
-}
-
-.newtab-control-pin:active {
- background-position: -48px 0;
-}
-
-.newtab-control-pin[pinned] {
- background-position: -72px 0;
-}
-
-.newtab-control-pin[pinned]:hover {
- background-position: -96px 0;
-}
-
-.newtab-control-pin[pinned]:active {
- background-position: -120px 0;
-}
-
-.newtab-control-block {
- background-position: -144px 0;
-}
-
-.newtab-control-block:hover {
- background-position: -168px 0;
-}
-
-.newtab-control-block:active {
- background-position: -192px 0;
-}
diff --git a/application/palemoon/themes/windows/newtab/noise.png b/application/palemoon/themes/windows/newtab/noise.png
deleted file mode 100644
index 01d340aaa..000000000
--- a/application/palemoon/themes/windows/newtab/noise.png
+++ /dev/null
Binary files differ