michael@0: #ifdef 0 michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #endif michael@0: michael@0: /** michael@0: * This singleton represents the whole 'New Tab Page' and takes care of michael@0: * initializing all its components. michael@0: */ michael@0: let gPage = { michael@0: /** michael@0: * Initializes the page. michael@0: */ michael@0: init: function Page_init() { michael@0: // Add ourselves to the list of pages to receive notifications. michael@0: gAllPages.register(this); michael@0: michael@0: // Listen for 'unload' to unregister this page. michael@0: addEventListener("unload", this, false); michael@0: michael@0: // XXX bug 991111 - Not all click events are correctly triggered when michael@0: // listening from xhtml nodes -- in particular middle clicks on sites, so michael@0: // listen from the xul window and filter then delegate michael@0: addEventListener("click", this, false); michael@0: michael@0: // Initialize sponsored panel michael@0: this._sponsoredPanel = document.getElementById("sponsored-panel"); michael@0: let link = this._sponsoredPanel.querySelector(".text-link"); michael@0: link.addEventListener("click", () => this._sponsoredPanel.hidePopup()); michael@0: if (UpdateChannel.get().startsWith("release")) { michael@0: document.getElementById("sponsored-panel-trial-descr").style.display = "none"; michael@0: } michael@0: else { michael@0: document.getElementById("sponsored-panel-release-descr").style.display = "none"; michael@0: } michael@0: michael@0: // Check if the new tab feature is enabled. michael@0: let enabled = gAllPages.enabled; michael@0: if (enabled) michael@0: this._init(); michael@0: michael@0: this._updateAttributes(enabled); michael@0: }, michael@0: michael@0: /** michael@0: * True if the page is allowed to capture thumbnails using the background michael@0: * thumbnail service. michael@0: */ michael@0: get allowBackgroundCaptures() { michael@0: // The preloader is bypassed altogether for private browsing windows, and michael@0: // therefore allow-background-captures will not be set. In that case, the michael@0: // page is not preloaded and so it's visible, so allow background captures. michael@0: return inPrivateBrowsingMode() || michael@0: document.documentElement.getAttribute("allow-background-captures") == michael@0: "true"; michael@0: }, michael@0: michael@0: /** michael@0: * Listens for notifications specific to this page. michael@0: */ michael@0: observe: function Page_observe(aSubject, aTopic, aData) { michael@0: if (aTopic == "nsPref:changed") { michael@0: let enabled = gAllPages.enabled; michael@0: this._updateAttributes(enabled); michael@0: michael@0: // Initialize the whole page if we haven't done that, yet. michael@0: if (enabled) { michael@0: this._init(); michael@0: } else { michael@0: gUndoDialog.hide(); michael@0: } michael@0: } else if (aTopic == "page-thumbnail:create" && gGrid.ready) { michael@0: for (let site of gGrid.sites) { michael@0: if (site && site.url === aData) { michael@0: site.refreshThumbnail(); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Updates the whole page and the grid when the storage has changed. michael@0: * @param aOnlyIfHidden If true, the page is updated only if it's hidden in michael@0: * the preloader. michael@0: */ michael@0: update: function Page_update(aOnlyIfHidden=false) { michael@0: let skipUpdate = aOnlyIfHidden && this.allowBackgroundCaptures; michael@0: // The grid might not be ready yet as we initialize it asynchronously. michael@0: if (gGrid.ready && !skipUpdate) { michael@0: gGrid.refresh(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Shows sponsored panel michael@0: */ michael@0: showSponsoredPanel: function Page_showSponsoredPanel(aTarget) { michael@0: if (this._sponsoredPanel.state == "closed") { michael@0: let self = this; michael@0: this._sponsoredPanel.addEventListener("popuphidden", function onPopupHidden(aEvent) { michael@0: self._sponsoredPanel.removeEventListener("popuphidden", onPopupHidden, false); michael@0: aTarget.removeAttribute("panelShown"); michael@0: }); michael@0: } michael@0: aTarget.setAttribute("panelShown", "true"); michael@0: this._sponsoredPanel.openPopup(aTarget); michael@0: }, michael@0: michael@0: /** michael@0: * Internally initializes the page. This runs only when/if the feature michael@0: * is/gets enabled. michael@0: */ michael@0: _init: function Page_init() { michael@0: if (this._initialized) michael@0: return; michael@0: michael@0: this._initialized = true; michael@0: michael@0: gSearch.init(); michael@0: michael@0: this._mutationObserver = new MutationObserver(() => { michael@0: if (this.allowBackgroundCaptures) { michael@0: Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true); michael@0: michael@0: // Initialize type counting with the types we want to count michael@0: let directoryCount = {}; michael@0: for (let type of DirectoryLinksProvider.linkTypes) { michael@0: directoryCount[type] = 0; michael@0: } michael@0: michael@0: for (let site of gGrid.sites) { michael@0: if (site) { michael@0: site.captureIfMissing(); michael@0: let {type} = site.link; michael@0: if (type in directoryCount) { michael@0: directoryCount[type]++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Record how many directory sites were shown, but place counts over the michael@0: // default 9 in the same bucket michael@0: for (let [type, count] of Iterator(directoryCount)) { michael@0: let shownId = "NEWTAB_PAGE_DIRECTORY_" + type.toUpperCase() + "_SHOWN"; michael@0: let shownCount = Math.min(10, count); michael@0: Services.telemetry.getHistogramById(shownId).add(shownCount); michael@0: } michael@0: michael@0: // content.js isn't loaded for the page while it's in the preloader, michael@0: // which is why this is necessary. michael@0: gSearch.setUpInitialState(); michael@0: } michael@0: }); michael@0: this._mutationObserver.observe(document.documentElement, { michael@0: attributes: true, michael@0: attributeFilter: ["allow-background-captures"], michael@0: }); michael@0: michael@0: gLinks.populateCache(function () { michael@0: // Initialize and render the grid. michael@0: gGrid.init(); michael@0: michael@0: // Initialize the drop target shim. michael@0: gDropTargetShim.init(); michael@0: michael@0: #ifdef XP_MACOSX michael@0: // Workaround to prevent a delay on MacOSX due to a slow drop animation. michael@0: document.addEventListener("dragover", this, false); michael@0: document.addEventListener("drop", this, false); michael@0: #endif michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Updates the 'page-disabled' attributes of the respective DOM nodes. michael@0: * @param aValue Whether the New Tab Page is enabled or not. michael@0: */ michael@0: _updateAttributes: function Page_updateAttributes(aValue) { michael@0: // Set the nodes' states. michael@0: let nodeSelector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid, #newtab-search-container"; michael@0: for (let node of document.querySelectorAll(nodeSelector)) { michael@0: if (aValue) michael@0: node.removeAttribute("page-disabled"); michael@0: else michael@0: node.setAttribute("page-disabled", "true"); michael@0: } michael@0: michael@0: // Enables/disables the control and link elements. michael@0: let inputSelector = ".newtab-control, .newtab-link"; michael@0: for (let input of document.querySelectorAll(inputSelector)) { michael@0: if (aValue) michael@0: input.removeAttribute("tabindex"); michael@0: else michael@0: input.setAttribute("tabindex", "-1"); michael@0: } michael@0: michael@0: // Update the toggle button's title. michael@0: let toggle = document.getElementById("newtab-toggle"); michael@0: toggle.setAttribute("title", newTabString(aValue ? "hide" : "show")); michael@0: }, michael@0: michael@0: /** michael@0: * Handles all page events. michael@0: */ michael@0: handleEvent: function Page_handleEvent(aEvent) { michael@0: switch (aEvent.type) { michael@0: case "unload": michael@0: if (this._mutationObserver) michael@0: this._mutationObserver.disconnect(); michael@0: gAllPages.unregister(this); michael@0: break; michael@0: case "click": michael@0: let {button, target} = aEvent; michael@0: if (target.id == "newtab-toggle") { michael@0: if (button == 0) { michael@0: gAllPages.enabled = !gAllPages.enabled; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: // Go up ancestors until we find a Site or not michael@0: while (target) { michael@0: if (target.hasOwnProperty("_newtabSite")) { michael@0: target._newtabSite.onClick(aEvent); michael@0: break; michael@0: } michael@0: target = target.parentNode; michael@0: } michael@0: break; michael@0: case "dragover": michael@0: if (gDrag.isValid(aEvent) && gDrag.draggedSite) michael@0: aEvent.preventDefault(); michael@0: break; michael@0: case "drop": michael@0: if (gDrag.isValid(aEvent) && gDrag.draggedSite) { michael@0: aEvent.preventDefault(); michael@0: aEvent.stopPropagation(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: };