test_UrlbarProviderSemanticHistorySearch.js (9976B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 11 EnrollmentType: "resource://nimbus/ExperimentAPI.sys.mjs", 12 }); 13 14 const { UrlbarProviderSemanticHistorySearch } = ChromeUtils.importESModule( 15 "moz-src:///browser/components/urlbar/UrlbarProviderSemanticHistorySearch.sys.mjs" 16 ); 17 const { getPlacesSemanticHistoryManager } = ChromeUtils.importESModule( 18 "resource://gre/modules/PlacesSemanticHistoryManager.sys.mjs" 19 ); 20 ChromeUtils.defineLazyGetter(this, "QuickSuggestTestUtils", () => { 21 const { QuickSuggestTestUtils: module } = ChromeUtils.importESModule( 22 "resource://testing-common/QuickSuggestTestUtils.sys.mjs" 23 ); 24 module.init(this); 25 return module; 26 }); 27 28 let semanticManager = getPlacesSemanticHistoryManager(); 29 let hasSufficientEntriesStub = sinon 30 .stub(semanticManager, "hasSufficientEntriesForSearching") 31 .resolves(true); 32 33 add_task(async function setup() { 34 registerCleanupFunction(() => { 35 sinon.restore(); 36 }); 37 38 // stub getEnrollmentMetadata once, then configure for both cases: 39 const getEnrollmentStub = sinon.stub( 40 lazy.NimbusFeatures.urlbar, 41 "getEnrollmentMetadata" 42 ); 43 getEnrollmentStub 44 .withArgs(lazy.EnrollmentType.EXPERIMENT) 45 .returns({ slug: "test-slug", branch: "control" }); 46 getEnrollmentStub.withArgs(lazy.EnrollmentType.ROLLOUT).returns(null); 47 sinon.stub(lazy.NimbusFeatures.urlbar, "recordExposureEvent"); 48 49 // Set required prefs 50 Services.prefs.setBoolPref("browser.ml.enable", true); 51 Services.prefs.setBoolPref("places.semanticHistory.featureGate", true); 52 Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); 53 Services.prefs 54 .getDefaultBranch("") 55 .setIntPref("browser.urlbar.suggest.semanticHistory.minLength", 5); 56 57 let cleanup = await QuickSuggestTestUtils.setRegionAndLocale({ 58 region: "US", 59 locale: "en-US", 60 }); 61 registerCleanupFunction(cleanup); 62 }); 63 64 add_task(async function test_startQuery_adds_results() { 65 const provider = new UrlbarProviderSemanticHistorySearch(); 66 67 const queryContext = { searchString: "test page" }; 68 69 // Trigger isActive() to initialize the semantic manager 70 Assert.ok(await provider.isActive(queryContext), "Provider should be active"); 71 72 // Stub and simulate inference 73 sinon.stub(semanticManager.embedder, "ensureEngine").callsFake(() => {}); 74 let url = "https://example.com"; 75 let inferStub = sinon.stub(semanticManager, "infer").resolves({ 76 results: [ 77 { 78 id: 1, 79 title: "Test Page", 80 url, 81 frecency: 100, 82 }, 83 ], 84 }); 85 await PlacesTestUtils.addVisits(url); 86 87 let added = []; 88 await provider.startQuery(queryContext, (_provider, result) => { 89 added.push(result); 90 }); 91 92 Assert.equal(added.length, 1, "One result should be added"); 93 Assert.equal(added[0].payload.url, url, "Correct URL should be used"); 94 Assert.equal( 95 added[0].payload.icon, 96 UrlbarUtils.getIconForUrl(url), 97 "Correct icon should be used" 98 ); 99 Assert.ok(added[0].payload.isBlockable, "Result should be blockable"); 100 Assert.equal(added[0].payload.frecency, 100, "Frecency is returned"); 101 102 let controller = UrlbarTestUtils.newMockController(); 103 let stub = sinon.stub(controller, "removeResult"); 104 let promiseRemoved = PlacesTestUtils.waitForNotification("page-removed"); 105 await provider.onEngagement(queryContext, controller, { 106 selType: "dismiss", 107 result: { payload: { url } }, 108 }); 109 Assert.ok(stub.calledOnce, "Result should be removed on dismissal"); 110 await promiseRemoved; 111 let visited = await PlacesUtils.history.hasVisits(url); 112 Assert.ok(!visited, "URL should have been removed from history"); 113 inferStub.restore(); 114 }); 115 116 add_task(async function test_isActive_conditions() { 117 const provider = new UrlbarProviderSemanticHistorySearch(); 118 119 // Stub canUseSemanticSearch to control the return value 120 const canUseStub = sinon.stub(semanticManager, "canUseSemanticSearch"); 121 122 const shortQuery = { searchString: "hi" }; 123 const validQuery = { searchString: "hello world" }; 124 125 // Pref is disabled 126 Services.prefs.setBoolPref("browser.urlbar.suggest.history", false); 127 Assert.ok( 128 !(await provider.isActive(validQuery)), 129 "Should be inactive when pref is disabled" 130 ); 131 132 // Pref enabled, but string too short 133 Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); 134 Assert.ok( 135 !(await provider.isActive(shortQuery)), 136 "Should be inactive for short search strings" 137 ); 138 139 // All conditions met but semanticManager rejects 140 canUseStub.get(() => false); 141 hasSufficientEntriesStub.resetHistory(); 142 Assert.ok( 143 !(await provider.isActive(validQuery)), 144 "Should be inactive if canUseSemanticSearch returns false" 145 ); 146 Assert.ok( 147 hasSufficientEntriesStub.notCalled, 148 "hasSufficientEntriesForSearching should not have been called" 149 ); 150 151 // All conditions met 152 canUseStub.get(() => true); 153 Assert.ok( 154 await provider.isActive(validQuery), 155 "Should be active when all conditions are met" 156 ); 157 158 const engineSearchMode = createContext("hello world", { 159 searchMode: { engineName: "testEngine" }, 160 }); 161 Assert.ok( 162 !(await provider.isActive(engineSearchMode)), 163 "Should not be active when in search engine mode" 164 ); 165 166 const historySearchMode = createContext("hello world", { 167 searchMode: { source: UrlbarUtils.RESULT_SOURCE.HISTORY }, 168 }); 169 Assert.ok( 170 await provider.isActive(historySearchMode), 171 "Should be active when in history search mode" 172 ); 173 }); 174 175 add_task(async function test_switchTab() { 176 const userContextId1 = 2; 177 const userContextId2 = 3; 178 const privateContextId = -1; 179 const url1 = "http://foo.mozilla.org/"; 180 const url2 = "http://foo2.mozilla.org/"; 181 await UrlbarProviderOpenTabs.registerOpenTab( 182 url1, 183 userContextId1, 184 null, 185 false 186 ); 187 await UrlbarProviderOpenTabs.registerOpenTab( 188 url2, 189 userContextId1, 190 null, 191 false 192 ); 193 await UrlbarProviderOpenTabs.registerOpenTab( 194 url1, 195 userContextId2, 196 null, 197 false 198 ); 199 await UrlbarProviderOpenTabs.registerOpenTab( 200 url1, 201 privateContextId, 202 null, 203 false 204 ); 205 await PlacesTestUtils.addVisits([url1, url2]); 206 const provider = new UrlbarProviderSemanticHistorySearch(); 207 208 // Trigger isActive() to initialize the semantic manager 209 const queryContext = createContext("firefox", { isPrivate: false }); 210 Assert.ok(await provider.isActive(queryContext), "Provider should be active"); 211 let inferStub = sinon.stub(semanticManager, "infer").resolves({ 212 results: [ 213 { 214 id: 1, 215 title: "Test Page 1", 216 url: url1, 217 }, 218 { 219 id: 2, 220 title: "Test Page 2", 221 url: url2, 222 }, 223 ], 224 }); 225 226 function AssertSwitchToTabResult(result, url, userContextId, groupId = null) { 227 Assert.equal( 228 result.type, 229 UrlbarUtils.RESULT_TYPE.TAB_SWITCH, 230 "Check result type" 231 ); 232 Assert.equal(result.payload.url, url, "Check result URL"); 233 Assert.equal( 234 result.payload.userContextId, 235 userContextId, 236 "Check user context" 237 ); 238 Assert.equal(result.payload.tabGroup, groupId, "Check tab group"); 239 Assert.equal( 240 result.payload.icon, 241 UrlbarUtils.getIconForUrl(url), 242 "Check icon" 243 ); 244 } 245 function isUrlResult(result, url) { 246 return ( 247 result.type === UrlbarUtils.RESULT_TYPE.URL && result.payload.url === url 248 ); 249 } 250 251 let added = []; 252 await provider.startQuery(queryContext, (_provider, result) => { 253 added.push(result); 254 }); 255 Assert.equal(added.length, 3, "Threee result should be added"); 256 AssertSwitchToTabResult(added[0], url1, userContextId1); 257 AssertSwitchToTabResult(added[1], url1, userContextId2); 258 AssertSwitchToTabResult(added[2], url2, userContextId1); 259 260 info("Test private browsing context."); 261 const privateContext = createContext("firefox"); 262 added.length = 0; 263 await provider.startQuery(privateContext, (_provider, result) => { 264 added.push(result); 265 }); 266 Assert.equal(added.length, 2, "Two results should be added"); 267 AssertSwitchToTabResult(added[0], url1, privateContextId); 268 Assert.ok(isUrlResult(added[1], url2), "Second result should be URL"); 269 270 info("Test single container mode."); 271 Services.prefs.setBoolPref( 272 "browser.urlbar.switchTabs.searchAllContainers", 273 false 274 ); 275 const singleContext = createContext("firefox", { 276 isPrivate: false, 277 userContextId: userContextId1, 278 }); 279 added.length = 0; 280 await provider.startQuery(singleContext, (_provider, result) => { 281 added.push(result); 282 }); 283 Assert.equal(added.length, 2, "Two results should be added"); 284 AssertSwitchToTabResult(added[0], url1, userContextId1); 285 AssertSwitchToTabResult(added[1], url2, userContextId1); 286 Services.prefs.clearUserPref("browser.urlbar.switchTabs.searchAllContainers"); 287 288 info("Test tab groups and current page."); 289 let tabGroudId1 = "group1"; 290 let tabGroudId2 = "group2"; 291 await UrlbarProviderOpenTabs.registerOpenTab( 292 url1, 293 userContextId1, 294 tabGroudId1, 295 false 296 ); 297 await UrlbarProviderOpenTabs.registerOpenTab( 298 url2, 299 userContextId2, 300 tabGroudId2, 301 false 302 ); 303 const groupContext = createContext("firefox", { 304 isPrivate: false, 305 currentPage: url1, 306 userContextId: userContextId1, 307 tabGroup: tabGroudId1, 308 }); 309 310 added.length = 0; 311 await provider.startQuery(groupContext, (_provider, result) => { 312 added.push(result); 313 }); 314 Assert.equal(added.length, 4, "Three results should be added"); 315 AssertSwitchToTabResult(added[0], url1, userContextId1); 316 AssertSwitchToTabResult(added[1], url1, userContextId2); 317 AssertSwitchToTabResult(added[2], url2, userContextId1); 318 AssertSwitchToTabResult(added[3], url2, userContextId2, tabGroudId2); 319 320 inferStub.restore(); 321 });