diff options
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 Binary files differdeleted file mode 100644 index 14f382fbd..000000000 --- a/application/palemoon/themes/osx/newtab/controls.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 01d340aaa..000000000 --- a/application/palemoon/themes/osx/newtab/noise.png +++ /dev/null diff --git a/application/palemoon/themes/linux/newtab/controls.png b/application/palemoon/themes/shared/newtab/controls.png Binary files differindex 14f382fbd..14f382fbd 100644 --- a/application/palemoon/themes/linux/newtab/controls.png +++ b/application/palemoon/themes/shared/newtab/controls.png 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 Binary files differindex 01d340aaa..01d340aaa 100644 --- a/application/palemoon/themes/linux/newtab/noise.png +++ b/application/palemoon/themes/shared/newtab/noise.png diff --git a/application/palemoon/themes/shared/newtab/pinned.png b/application/palemoon/themes/shared/newtab/pinned.png Binary files differnew file mode 100644 index 000000000..ddd731bc6 --- /dev/null +++ b/application/palemoon/themes/shared/newtab/pinned.png 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 Binary files differdeleted file mode 100644 index 14f382fbd..000000000 --- a/application/palemoon/themes/windows/newtab/controls.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 01d340aaa..000000000 --- a/application/palemoon/themes/windows/newtab/noise.png +++ /dev/null |