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 class represents a site that is contained in a cell and can be pinned, michael@0: * moved around or deleted. michael@0: */ michael@0: function Site(aNode, aLink) { michael@0: this._node = aNode; michael@0: this._node._newtabSite = this; michael@0: michael@0: this._link = aLink; michael@0: michael@0: this._render(); michael@0: this._addEventHandlers(); michael@0: } michael@0: michael@0: Site.prototype = { michael@0: /** michael@0: * The site's DOM node. michael@0: */ michael@0: get node() this._node, michael@0: michael@0: /** michael@0: * The site's link. michael@0: */ michael@0: get link() this._link, michael@0: michael@0: /** michael@0: * The url of the site's link. michael@0: */ michael@0: get url() this.link.url, michael@0: michael@0: /** michael@0: * The title of the site's link. michael@0: */ michael@0: get title() this.link.title, michael@0: michael@0: /** michael@0: * The site's parent cell. michael@0: */ michael@0: get cell() { michael@0: let parentNode = this.node.parentNode; michael@0: return parentNode && parentNode._newtabCell; michael@0: }, michael@0: michael@0: /** michael@0: * Pins the site on its current or a given index. michael@0: * @param aIndex The pinned index (optional). michael@0: */ michael@0: pin: function Site_pin(aIndex) { michael@0: if (typeof aIndex == "undefined") michael@0: aIndex = this.cell.index; michael@0: michael@0: this._updateAttributes(true); michael@0: gPinnedLinks.pin(this._link, aIndex); michael@0: }, michael@0: michael@0: /** michael@0: * Unpins the site and calls the given callback when done. michael@0: */ michael@0: unpin: function Site_unpin() { michael@0: if (this.isPinned()) { michael@0: this._updateAttributes(false); michael@0: gPinnedLinks.unpin(this._link); michael@0: gUpdater.updateGrid(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Checks whether this site is pinned. michael@0: * @return Whether this site is pinned. michael@0: */ michael@0: isPinned: function Site_isPinned() { michael@0: return gPinnedLinks.isPinned(this._link); michael@0: }, michael@0: michael@0: /** michael@0: * Blocks the site (removes it from the grid) and calls the given callback michael@0: * when done. michael@0: */ michael@0: block: function Site_block() { michael@0: if (!gBlockedLinks.isBlocked(this._link)) { michael@0: gUndoDialog.show(this); michael@0: gBlockedLinks.block(this._link); michael@0: gUpdater.updateGrid(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Gets the DOM node specified by the given query selector. michael@0: * @param aSelector The query selector. michael@0: * @return The DOM node we found. michael@0: */ michael@0: _querySelector: function Site_querySelector(aSelector) { michael@0: return this.node.querySelector(aSelector); michael@0: }, michael@0: michael@0: /** michael@0: * Updates attributes for all nodes which status depends on this site being michael@0: * pinned or unpinned. michael@0: * @param aPinned Whether this site is now pinned or unpinned. michael@0: */ michael@0: _updateAttributes: function (aPinned) { michael@0: let control = this._querySelector(".newtab-control-pin"); michael@0: michael@0: if (aPinned) { michael@0: control.setAttribute("pinned", true); michael@0: control.setAttribute("title", newTabString("unpin")); michael@0: } else { michael@0: control.removeAttribute("pinned"); michael@0: control.setAttribute("title", newTabString("pin")); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Renders the site's data (fills the HTML fragment). michael@0: */ michael@0: _render: function Site_render() { michael@0: let url = this.url; michael@0: let title = this.title || url; michael@0: let tooltip = (title == url ? title : title + "\n" + url); michael@0: michael@0: let link = this._querySelector(".newtab-link"); michael@0: link.setAttribute("title", tooltip); michael@0: link.setAttribute("href", url); michael@0: this._querySelector(".newtab-title").textContent = title; michael@0: this.node.setAttribute("type", this.link.type); michael@0: michael@0: if (this.isPinned()) michael@0: this._updateAttributes(true); michael@0: // Capture the page if the thumbnail is missing, which will cause page.js michael@0: // to be notified and call our refreshThumbnail() method. michael@0: this.captureIfMissing(); michael@0: // but still display whatever thumbnail might be available now. michael@0: this.refreshThumbnail(); michael@0: }, michael@0: michael@0: /** michael@0: * Captures the site's thumbnail in the background, but only if there's no michael@0: * existing thumbnail and the page allows background captures. michael@0: */ michael@0: captureIfMissing: function Site_captureIfMissing() { michael@0: if (gPage.allowBackgroundCaptures && !this.link.imageURI) { michael@0: BackgroundPageThumbs.captureIfMissing(this.url); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Refreshes the thumbnail for the site. michael@0: */ michael@0: refreshThumbnail: function Site_refreshThumbnail() { michael@0: let thumbnail = this._querySelector(".newtab-thumbnail"); michael@0: if (this.link.bgColor) { michael@0: thumbnail.style.backgroundColor = this.link.bgColor; michael@0: } michael@0: let uri = this.link.imageURI || PageThumbs.getThumbnailURL(this.url); michael@0: thumbnail.style.backgroundImage = "url(" + uri + ")"; michael@0: }, michael@0: michael@0: /** michael@0: * Adds event handlers for the site and its buttons. michael@0: */ michael@0: _addEventHandlers: function Site_addEventHandlers() { michael@0: // Register drag-and-drop event handlers. michael@0: this._node.addEventListener("dragstart", this, false); michael@0: this._node.addEventListener("dragend", this, false); michael@0: this._node.addEventListener("mouseover", this, false); michael@0: michael@0: // Specially treat the sponsored icon to prevent regular hover effects michael@0: let sponsored = this._querySelector(".newtab-control-sponsored"); michael@0: sponsored.addEventListener("mouseover", () => { michael@0: this.cell.node.setAttribute("ignorehover", "true"); michael@0: }); michael@0: sponsored.addEventListener("mouseout", () => { michael@0: this.cell.node.removeAttribute("ignorehover"); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Speculatively opens a connection to the current site. michael@0: */ michael@0: _speculativeConnect: function Site_speculativeConnect() { michael@0: let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect); michael@0: let uri = Services.io.newURI(this.url, null, null); michael@0: sc.speculativeConnect(uri, null); michael@0: }, michael@0: michael@0: /** michael@0: * Record interaction with site using telemetry. michael@0: */ michael@0: _recordSiteClicked: function Site_recordSiteClicked(aIndex) { michael@0: if (Services.prefs.prefHasUserValue("browser.newtabpage.rows") || michael@0: Services.prefs.prefHasUserValue("browser.newtabpage.columns") || michael@0: aIndex > 8) { michael@0: // We only want to get indices for the default configuration, everything michael@0: // else goes in the same bucket. michael@0: aIndex = 9; michael@0: } michael@0: Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED") michael@0: .add(aIndex); michael@0: michael@0: // Specially count clicks on directory tiles michael@0: let typeIndex = DirectoryLinksProvider.linkTypes.indexOf(this.link.type); michael@0: if (typeIndex != -1) { michael@0: Services.telemetry.getHistogramById("NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED") michael@0: .add(typeIndex); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handles site click events. michael@0: */ michael@0: onClick: function Site_onClick(aEvent) { michael@0: let {button, target} = aEvent; michael@0: if (target.classList.contains("newtab-link") || michael@0: target.parentElement.classList.contains("newtab-link")) { michael@0: // Record for primary and middle clicks michael@0: if (button == 0 || button == 1) { michael@0: this._recordSiteClicked(this.cell.index); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Only handle primary clicks for the remaining targets michael@0: if (button != 0) { michael@0: return; michael@0: } michael@0: michael@0: aEvent.preventDefault(); michael@0: if (aEvent.target.classList.contains("newtab-control-block")) michael@0: this.block(); michael@0: else if (target.classList.contains("newtab-control-sponsored")) michael@0: gPage.showSponsoredPanel(target); michael@0: else if (this.isPinned()) michael@0: this.unpin(); michael@0: else michael@0: this.pin(); michael@0: }, michael@0: michael@0: /** michael@0: * Handles all site events. michael@0: */ michael@0: handleEvent: function Site_handleEvent(aEvent) { michael@0: switch (aEvent.type) { michael@0: case "mouseover": michael@0: this._node.removeEventListener("mouseover", this, false); michael@0: this._speculativeConnect(); michael@0: break; michael@0: case "dragstart": michael@0: gDrag.start(this, aEvent); michael@0: break; michael@0: case "dragend": michael@0: gDrag.end(this, aEvent); michael@0: break; michael@0: } michael@0: } michael@0: };