|
1 #ifdef 0 |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 #endif |
|
6 |
|
7 /** |
|
8 * This class represents a site that is contained in a cell and can be pinned, |
|
9 * moved around or deleted. |
|
10 */ |
|
11 function Site(aNode, aLink) { |
|
12 this._node = aNode; |
|
13 this._node._newtabSite = this; |
|
14 |
|
15 this._link = aLink; |
|
16 |
|
17 this._render(); |
|
18 this._addEventHandlers(); |
|
19 } |
|
20 |
|
21 Site.prototype = { |
|
22 /** |
|
23 * The site's DOM node. |
|
24 */ |
|
25 get node() this._node, |
|
26 |
|
27 /** |
|
28 * The site's link. |
|
29 */ |
|
30 get link() this._link, |
|
31 |
|
32 /** |
|
33 * The url of the site's link. |
|
34 */ |
|
35 get url() this.link.url, |
|
36 |
|
37 /** |
|
38 * The title of the site's link. |
|
39 */ |
|
40 get title() this.link.title, |
|
41 |
|
42 /** |
|
43 * The site's parent cell. |
|
44 */ |
|
45 get cell() { |
|
46 let parentNode = this.node.parentNode; |
|
47 return parentNode && parentNode._newtabCell; |
|
48 }, |
|
49 |
|
50 /** |
|
51 * Pins the site on its current or a given index. |
|
52 * @param aIndex The pinned index (optional). |
|
53 */ |
|
54 pin: function Site_pin(aIndex) { |
|
55 if (typeof aIndex == "undefined") |
|
56 aIndex = this.cell.index; |
|
57 |
|
58 this._updateAttributes(true); |
|
59 gPinnedLinks.pin(this._link, aIndex); |
|
60 }, |
|
61 |
|
62 /** |
|
63 * Unpins the site and calls the given callback when done. |
|
64 */ |
|
65 unpin: function Site_unpin() { |
|
66 if (this.isPinned()) { |
|
67 this._updateAttributes(false); |
|
68 gPinnedLinks.unpin(this._link); |
|
69 gUpdater.updateGrid(); |
|
70 } |
|
71 }, |
|
72 |
|
73 /** |
|
74 * Checks whether this site is pinned. |
|
75 * @return Whether this site is pinned. |
|
76 */ |
|
77 isPinned: function Site_isPinned() { |
|
78 return gPinnedLinks.isPinned(this._link); |
|
79 }, |
|
80 |
|
81 /** |
|
82 * Blocks the site (removes it from the grid) and calls the given callback |
|
83 * when done. |
|
84 */ |
|
85 block: function Site_block() { |
|
86 if (!gBlockedLinks.isBlocked(this._link)) { |
|
87 gUndoDialog.show(this); |
|
88 gBlockedLinks.block(this._link); |
|
89 gUpdater.updateGrid(); |
|
90 } |
|
91 }, |
|
92 |
|
93 /** |
|
94 * Gets the DOM node specified by the given query selector. |
|
95 * @param aSelector The query selector. |
|
96 * @return The DOM node we found. |
|
97 */ |
|
98 _querySelector: function Site_querySelector(aSelector) { |
|
99 return this.node.querySelector(aSelector); |
|
100 }, |
|
101 |
|
102 /** |
|
103 * Updates attributes for all nodes which status depends on this site being |
|
104 * pinned or unpinned. |
|
105 * @param aPinned Whether this site is now pinned or unpinned. |
|
106 */ |
|
107 _updateAttributes: function (aPinned) { |
|
108 let control = this._querySelector(".newtab-control-pin"); |
|
109 |
|
110 if (aPinned) { |
|
111 control.setAttribute("pinned", true); |
|
112 control.setAttribute("title", newTabString("unpin")); |
|
113 } else { |
|
114 control.removeAttribute("pinned"); |
|
115 control.setAttribute("title", newTabString("pin")); |
|
116 } |
|
117 }, |
|
118 |
|
119 /** |
|
120 * Renders the site's data (fills the HTML fragment). |
|
121 */ |
|
122 _render: function Site_render() { |
|
123 let url = this.url; |
|
124 let title = this.title || url; |
|
125 let tooltip = (title == url ? title : title + "\n" + url); |
|
126 |
|
127 let link = this._querySelector(".newtab-link"); |
|
128 link.setAttribute("title", tooltip); |
|
129 link.setAttribute("href", url); |
|
130 this._querySelector(".newtab-title").textContent = title; |
|
131 this.node.setAttribute("type", this.link.type); |
|
132 |
|
133 if (this.isPinned()) |
|
134 this._updateAttributes(true); |
|
135 // Capture the page if the thumbnail is missing, which will cause page.js |
|
136 // to be notified and call our refreshThumbnail() method. |
|
137 this.captureIfMissing(); |
|
138 // but still display whatever thumbnail might be available now. |
|
139 this.refreshThumbnail(); |
|
140 }, |
|
141 |
|
142 /** |
|
143 * Captures the site's thumbnail in the background, but only if there's no |
|
144 * existing thumbnail and the page allows background captures. |
|
145 */ |
|
146 captureIfMissing: function Site_captureIfMissing() { |
|
147 if (gPage.allowBackgroundCaptures && !this.link.imageURI) { |
|
148 BackgroundPageThumbs.captureIfMissing(this.url); |
|
149 } |
|
150 }, |
|
151 |
|
152 /** |
|
153 * Refreshes the thumbnail for the site. |
|
154 */ |
|
155 refreshThumbnail: function Site_refreshThumbnail() { |
|
156 let thumbnail = this._querySelector(".newtab-thumbnail"); |
|
157 if (this.link.bgColor) { |
|
158 thumbnail.style.backgroundColor = this.link.bgColor; |
|
159 } |
|
160 let uri = this.link.imageURI || PageThumbs.getThumbnailURL(this.url); |
|
161 thumbnail.style.backgroundImage = "url(" + uri + ")"; |
|
162 }, |
|
163 |
|
164 /** |
|
165 * Adds event handlers for the site and its buttons. |
|
166 */ |
|
167 _addEventHandlers: function Site_addEventHandlers() { |
|
168 // Register drag-and-drop event handlers. |
|
169 this._node.addEventListener("dragstart", this, false); |
|
170 this._node.addEventListener("dragend", this, false); |
|
171 this._node.addEventListener("mouseover", this, false); |
|
172 |
|
173 // Specially treat the sponsored icon to prevent regular hover effects |
|
174 let sponsored = this._querySelector(".newtab-control-sponsored"); |
|
175 sponsored.addEventListener("mouseover", () => { |
|
176 this.cell.node.setAttribute("ignorehover", "true"); |
|
177 }); |
|
178 sponsored.addEventListener("mouseout", () => { |
|
179 this.cell.node.removeAttribute("ignorehover"); |
|
180 }); |
|
181 }, |
|
182 |
|
183 /** |
|
184 * Speculatively opens a connection to the current site. |
|
185 */ |
|
186 _speculativeConnect: function Site_speculativeConnect() { |
|
187 let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect); |
|
188 let uri = Services.io.newURI(this.url, null, null); |
|
189 sc.speculativeConnect(uri, null); |
|
190 }, |
|
191 |
|
192 /** |
|
193 * Record interaction with site using telemetry. |
|
194 */ |
|
195 _recordSiteClicked: function Site_recordSiteClicked(aIndex) { |
|
196 if (Services.prefs.prefHasUserValue("browser.newtabpage.rows") || |
|
197 Services.prefs.prefHasUserValue("browser.newtabpage.columns") || |
|
198 aIndex > 8) { |
|
199 // We only want to get indices for the default configuration, everything |
|
200 // else goes in the same bucket. |
|
201 aIndex = 9; |
|
202 } |
|
203 Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED") |
|
204 .add(aIndex); |
|
205 |
|
206 // Specially count clicks on directory tiles |
|
207 let typeIndex = DirectoryLinksProvider.linkTypes.indexOf(this.link.type); |
|
208 if (typeIndex != -1) { |
|
209 Services.telemetry.getHistogramById("NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED") |
|
210 .add(typeIndex); |
|
211 } |
|
212 }, |
|
213 |
|
214 /** |
|
215 * Handles site click events. |
|
216 */ |
|
217 onClick: function Site_onClick(aEvent) { |
|
218 let {button, target} = aEvent; |
|
219 if (target.classList.contains("newtab-link") || |
|
220 target.parentElement.classList.contains("newtab-link")) { |
|
221 // Record for primary and middle clicks |
|
222 if (button == 0 || button == 1) { |
|
223 this._recordSiteClicked(this.cell.index); |
|
224 } |
|
225 return; |
|
226 } |
|
227 |
|
228 // Only handle primary clicks for the remaining targets |
|
229 if (button != 0) { |
|
230 return; |
|
231 } |
|
232 |
|
233 aEvent.preventDefault(); |
|
234 if (aEvent.target.classList.contains("newtab-control-block")) |
|
235 this.block(); |
|
236 else if (target.classList.contains("newtab-control-sponsored")) |
|
237 gPage.showSponsoredPanel(target); |
|
238 else if (this.isPinned()) |
|
239 this.unpin(); |
|
240 else |
|
241 this.pin(); |
|
242 }, |
|
243 |
|
244 /** |
|
245 * Handles all site events. |
|
246 */ |
|
247 handleEvent: function Site_handleEvent(aEvent) { |
|
248 switch (aEvent.type) { |
|
249 case "mouseover": |
|
250 this._node.removeEventListener("mouseover", this, false); |
|
251 this._speculativeConnect(); |
|
252 break; |
|
253 case "dragstart": |
|
254 gDrag.start(this, aEvent); |
|
255 break; |
|
256 case "dragend": |
|
257 gDrag.end(this, aEvent); |
|
258 break; |
|
259 } |
|
260 } |
|
261 }; |