diff options
Diffstat (limited to 'application/palemoon/base/content/newtab/page.js')
-rw-r--r-- | application/palemoon/base/content/newtab/page.js | 221 |
1 files changed, 189 insertions, 32 deletions
diff --git a/application/palemoon/base/content/newtab/page.js b/application/palemoon/base/content/newtab/page.js index fc836a55e..7117d4527 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. @@ -18,10 +21,15 @@ 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); + button.addEventListener("click", e => this.toggleEnabled(e)); + + // 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 +42,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 +113,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"; - // Initialize the drop target shim. - gDropTargetShim.init(); + if (document.hidden) { + addEventListener("visibilitychange", this); + } else { + setTimeout(() => this.onPageFirstVisible()); + } + + // Initialize and render the grid. + gGrid.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 +143,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 +154,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 +183,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 +210,83 @@ 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; + } + } + } + } + }, + + toggleEnabled: function(aEvent) { + gAllPages.enabled = !gAllPages.enabled; + event.stopPropagation(); + }, + + maybeShowAutoMigrationUndoNotification() { + // sendAsyncMessage("NewTab:MaybeShowAutoMigrationUndoNotification"); + }, }; |