test_ext_topSites.js (9220B)
1 "use strict"; 2 3 const { PlacesUtils } = ChromeUtils.importESModule( 4 "resource://gre/modules/PlacesUtils.sys.mjs" 5 ); 6 const { NewTabUtils } = ChromeUtils.importESModule( 7 "resource://gre/modules/NewTabUtils.sys.mjs" 8 ); 9 const { PlacesTestUtils } = ChromeUtils.importESModule( 10 "resource://testing-common/PlacesTestUtils.sys.mjs" 11 ); 12 13 const SEARCH_SHORTCUTS_EXPERIMENT_PREF = 14 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts"; 15 16 // A small 1x1 test png 17 const IMAGE_1x1 = 18 "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="; 19 20 add_task(async function test_topSites() { 21 Services.prefs.setBoolPref(SEARCH_SHORTCUTS_EXPERIMENT_PREF, false); 22 let visits = []; 23 // `getTopFrecentSites` uses a threshold to filter low frecency results. 24 // The minimum visit threshold is meant to mimic meaningful user engagement. 25 // The date cutoff is to ensure visits in the recent past are still visible. 26 // Both values are meant to accomodate variations in the frecency scoring 27 // algorithm. 28 const numVisits = 5; 29 let visitDate = new Date(); 30 visitDate.setMonth(visitDate.getMonth() - 1); 31 visitDate = visitDate.getTime(); 32 33 // Add a visit that is outside the default threshold. It'll be visible 34 // when the threshold is the bare minimum but not when using the default 35 // higher threshold used by the extension is applied. 36 const OLD_URL = "https://www.oldexample.com"; 37 let olderVisitDate = new Date(); 38 olderVisitDate.setMonth(olderVisitDate.getMonth() - 6); 39 olderVisitDate = olderVisitDate.getTime(); 40 // We do two older visits because a likely default threshold could filter out 41 // one visit. 42 PlacesTestUtils.addVisits([ 43 { 44 url: OLD_URL, 45 visitDate: olderVisitDate * 1000, 46 }, 47 { 48 url: OLD_URL, 49 visitDate: olderVisitDate * 1000, 50 }, 51 ]); 52 53 async function setVisit(visit) { 54 for (let j = 0; j < numVisits; ++j) { 55 visitDate -= 1000; 56 visit.visits.push({ date: new Date(visitDate) }); 57 } 58 visits.push(visit); 59 await PlacesUtils.history.insert(visit); 60 } 61 // Stick a couple sites into history. 62 for (let i = 0; i < 2; ++i) { 63 await setVisit({ 64 url: `http://example${i}.com/`, 65 title: `visit${i}`, 66 visits: [], 67 }); 68 await setVisit({ 69 url: `http://www.example${i}.com/foobar`, 70 title: `visit${i}-www`, 71 visits: [], 72 }); 73 } 74 NewTabUtils.init(); 75 76 // Insert a favicon to show that favicons are not returned by default. 77 let faviconData = new Map(); 78 faviconData.set("http://example0.com", IMAGE_1x1); 79 await PlacesTestUtils.addFavicons(faviconData); 80 81 // Ensure our links show up in activityStream. 82 let links = await NewTabUtils.activityStreamLinks.getTopSites({ 83 onePerDomain: false, 84 topsiteFrecency: 1, 85 }); 86 87 // Sanity checks. 88 Assert.ok( 89 !visits.some(visit => visit.url && visit.url.includes(OLD_URL)), 90 "Recent visits don't include the oldest visit." 91 ); 92 Assert.ok( 93 links.some(link => link.url && link.url.includes(OLD_URL)), 94 "The returned links do include the oldest visit." 95 ); 96 97 equal( 98 links.length, 99 // The recent visits plus the one older visit. 100 visits.length + 1, 101 "Top sites has been successfully initialized" 102 ); 103 104 // Drop the visits.visits for later testing. 105 visits = visits.map(v => { 106 return { url: v.url, title: v.title, favicon: undefined, type: "url" }; 107 }); 108 109 // Test that results from all providers are returned by default. 110 let extension = ExtensionTestUtils.loadExtension({ 111 manifest: { 112 permissions: ["topSites"], 113 }, 114 background() { 115 browser.test.onMessage.addListener(async options => { 116 let sites = await browser.topSites.get(options); 117 browser.test.sendMessage("sites", sites); 118 }); 119 }, 120 }); 121 122 await extension.startup(); 123 124 function getSites(options) { 125 extension.sendMessage(options); 126 return extension.awaitMessage("sites"); 127 } 128 129 Assert.deepEqual( 130 [visits[0], visits[2]], 131 await getSites(), 132 "got topSites default" 133 ); 134 Assert.deepEqual( 135 visits, 136 await getSites({ onePerDomain: false }), 137 "got topSites all links" 138 ); 139 140 NewTabUtils.activityStreamLinks.blockURL(visits[0]); 141 ok( 142 NewTabUtils.blockedLinks.isBlocked(visits[0]), 143 `link ${visits[0].url} is blocked` 144 ); 145 146 Assert.deepEqual( 147 [visits[2], visits[1]], 148 await getSites(), 149 "got topSites with blocked links filtered out" 150 ); 151 Assert.deepEqual( 152 [visits[0], visits[2]], 153 await getSites({ includeBlocked: true }), 154 "got topSites with blocked links included" 155 ); 156 157 // Test favicon result 158 let topSites = await getSites({ includeBlocked: true, includeFavicon: true }); 159 equal(topSites[0].favicon, IMAGE_1x1, "received favicon"); 160 161 equal( 162 1, 163 (await getSites({ limit: 1, includeBlocked: true })).length, 164 "limit 1 topSite" 165 ); 166 167 NewTabUtils.uninit(); 168 await extension.unload(); 169 await PlacesUtils.history.clear(); 170 Services.prefs.clearUserPref(SEARCH_SHORTCUTS_EXPERIMENT_PREF); 171 }); 172 173 // Test pinned likns and search shortcuts. 174 add_task(async function test_topSites_complete() { 175 Services.prefs.setBoolPref(SEARCH_SHORTCUTS_EXPERIMENT_PREF, true); 176 NewTabUtils.init(); 177 let time = new Date(); 178 let pinnedIndex = 0; 179 let entries = [ 180 { 181 url: `http://pinned1.com/`, 182 title: "pinned1", 183 type: "url", 184 pinned: pinnedIndex++, 185 visitDate: time, 186 }, 187 { 188 url: `http://search1.com/`, 189 title: "@search1", 190 type: "search", 191 pinned: pinnedIndex++, 192 visitDate: new Date(--time), 193 }, 194 { 195 url: `https://amazon.com`, 196 title: "@amazon", 197 type: "search", 198 visitDate: new Date(--time), 199 }, 200 { 201 url: `http://history1.com/`, 202 title: "history1", 203 type: "url", 204 visitDate: new Date(--time), 205 }, 206 { 207 url: `http://history2.com/`, 208 title: "history2", 209 type: "url", 210 visitDate: new Date(--time), 211 }, 212 { 213 url: `https://blocked1.com/`, 214 title: "blocked1", 215 type: "blocked", 216 visitDate: new Date(--time), 217 }, 218 ]; 219 220 for (let entry of entries) { 221 // Build up frecency. 222 await PlacesUtils.history.insert({ 223 url: entry.url, 224 title: entry.title, 225 visits: new Array(15).fill({ 226 date: entry.visitDate, 227 transition: PlacesUtils.history.TRANSITIONS.LINK, 228 }), 229 }); 230 // Insert a favicon to show that favicons are not returned by default. 231 await PlacesTestUtils.addFavicons(new Map([[entry.url, IMAGE_1x1]])); 232 if (entry.pinned !== undefined) { 233 let info = 234 entry.type == "search" 235 ? { url: entry.url, label: entry.title, searchTopSite: true } 236 : { url: entry.url, title: entry.title }; 237 NewTabUtils.pinnedLinks.pin(info, entry.pinned); 238 } 239 if (entry.type == "blocked") { 240 NewTabUtils.activityStreamLinks.blockURL({ url: entry.url }); 241 } 242 } 243 244 // Some transformation is necessary to match output data. 245 let expectedResults = entries 246 .filter(e => e.type != "blocked") 247 .map(e => { 248 e.favicon = undefined; 249 delete e.visitDate; 250 delete e.pinned; 251 return e; 252 }); 253 254 let extension = ExtensionTestUtils.loadExtension({ 255 manifest: { 256 permissions: ["topSites"], 257 }, 258 background() { 259 browser.test.onMessage.addListener(async options => { 260 let sites = await browser.topSites.get(options); 261 browser.test.sendMessage("sites", sites); 262 }); 263 }, 264 }); 265 266 await extension.startup(); 267 268 // Test that results are returned by the API. 269 function getSites(options) { 270 extension.sendMessage(options); 271 return extension.awaitMessage("sites"); 272 } 273 274 Assert.deepEqual( 275 expectedResults, 276 await getSites({ includePinned: true, includeSearchShortcuts: true }), 277 "got topSites all links" 278 ); 279 280 // Test no shortcuts. 281 dump(JSON.stringify(await getSites({ includePinned: true })) + "\n"); 282 Assert.ok( 283 !(await getSites({ includePinned: true })).some( 284 link => link.type == "search" 285 ), 286 "should get no shortcuts" 287 ); 288 289 // Test favicons. 290 let topSites = await getSites({ 291 includePinned: true, 292 includeSearchShortcuts: true, 293 includeFavicon: true, 294 }); 295 Assert.ok( 296 topSites.every(f => f.favicon == IMAGE_1x1), 297 "favicon is correct" 298 ); 299 300 // Test options.limit. 301 Assert.equal( 302 1, 303 ( 304 await getSites({ 305 includePinned: true, 306 includeSearchShortcuts: true, 307 limit: 1, 308 }) 309 ).length, 310 "limit to 1 topSite" 311 ); 312 313 // Clear history for a pinned entry, then check results. 314 await PlacesUtils.history.remove("http://pinned1.com/"); 315 let links = await getSites({ includePinned: true }); 316 Assert.ok( 317 links.find(link => link.url == "http://pinned1.com/"), 318 "Check unvisited pinned links are returned." 319 ); 320 links = await getSites(); 321 Assert.ok( 322 !links.find(link => link.url == "http://pinned1.com/"), 323 "Check unvisited pinned links are not returned." 324 ); 325 326 await extension.unload(); 327 NewTabUtils.uninit(); 328 await PlacesUtils.history.clear(); 329 await PlacesUtils.bookmarks.eraseEverything(); 330 Services.prefs.clearUserPref(SEARCH_SHORTCUTS_EXPERIMENT_PREF); 331 });