browser_geolocation_indicator.js (11043B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 requestLongerTimeout(2); 7 8 const { PermissionUI } = ChromeUtils.importESModule( 9 "resource:///modules/PermissionUI.sys.mjs" 10 ); 11 12 const { PermissionTestUtils } = ChromeUtils.importESModule( 13 "resource://testing-common/PermissionTestUtils.sys.mjs" 14 ); 15 16 const CP = Cc["@mozilla.org/content-pref/service;1"].getService( 17 Ci.nsIContentPrefService2 18 ); 19 20 const EXAMPLE_PAGE_URL = "https://example.com"; 21 const EXAMPLE_PAGE_URI = Services.io.newURI(EXAMPLE_PAGE_URL); 22 const EXAMPLE_PAGE_PRINCIPAL = 23 Services.scriptSecurityManager.createContentPrincipal(EXAMPLE_PAGE_URI, {}); 24 const GEO_CONTENT_PREF_KEY = "permissions.geoLocation.lastAccess"; 25 const POLL_INTERVAL_FALSE_STATE = 50; 26 27 async function testGeoSharingIconVisible(state = true) { 28 let sharingIcon = document.getElementById("geo-sharing-icon"); 29 ok(sharingIcon, "Geo sharing icon exists"); 30 31 try { 32 await TestUtils.waitForCondition( 33 () => sharingIcon.hasAttribute("sharing") === true, 34 "Waiting for geo sharing icon visibility state", 35 // If we wait for sharing icon to *not* show, waitForCondition will always timeout on correct state. 36 // In these cases we want to reduce the wait time from 5 seconds to 2.5 seconds to prevent test duration timeouts 37 !state ? POLL_INTERVAL_FALSE_STATE : undefined 38 ); 39 } catch (e) { 40 ok(!state, "Geo sharing icon not showing"); 41 return; 42 } 43 ok(state, "Geo sharing icon showing"); 44 } 45 46 async function checkForDOMElement(state, id) { 47 info(`Testing state ${state} of element ${id}`); 48 let el; 49 try { 50 await TestUtils.waitForCondition( 51 () => { 52 el = document.getElementById(id); 53 return el != null; 54 }, 55 `Waiting for ${id}`, 56 !state ? POLL_INTERVAL_FALSE_STATE : undefined 57 ); 58 } catch (e) { 59 ok(!state, `${id} has correct state`); 60 return el; 61 } 62 ok(state, `${id} has correct state`); 63 64 return el; 65 } 66 67 async function testPermissionPopupGeoContainer( 68 containerVisible, 69 timestampVisible 70 ) { 71 // The container holds the timestamp element, therefore we can't have a 72 // visible timestamp without the container. 73 if (timestampVisible && !containerVisible) { 74 ok(false, "Can't have timestamp without container"); 75 } 76 77 // Only call openPermissionPopup if popup is closed, otherwise it does not resolve 78 if (!gPermissionPanel._identityPermissionBox.hasAttribute("open")) { 79 await openPermissionPopup(); 80 } 81 82 let checkContainer = checkForDOMElement( 83 containerVisible, 84 "permission-popup-geo-container" 85 ); 86 87 if (containerVisible && timestampVisible) { 88 // Wait for the geo container to be fully populated. 89 // The time label is computed async. 90 let container = await checkContainer; 91 await TestUtils.waitForCondition( 92 () => container.childElementCount == 2, 93 "permission-popup-geo-container should have two elements." 94 ); 95 is( 96 container.childNodes[0].classList[0], 97 "permission-popup-permission-item", 98 "Geo container should have permission item." 99 ); 100 is( 101 container.childNodes[1].id, 102 "geo-access-indicator-item", 103 "Geo container should have indicator item." 104 ); 105 } 106 let checkAccessIndicator = checkForDOMElement( 107 timestampVisible, 108 "geo-access-indicator-item" 109 ); 110 111 return Promise.all([checkContainer, checkAccessIndicator]); 112 } 113 114 function openExamplePage(tabbrowser = gBrowser) { 115 return BrowserTestUtils.openNewForegroundTab(tabbrowser, EXAMPLE_PAGE_URL); 116 } 117 118 function requestGeoLocation(browser) { 119 return SpecialPowers.spawn(browser, [], () => { 120 return new Promise(resolve => { 121 content.navigator.geolocation.getCurrentPosition( 122 () => resolve(true), 123 error => resolve(error.code !== 1) // PERMISSION_DENIED = 1 124 ); 125 }); 126 }); 127 } 128 129 function answerGeoLocationPopup(allow, remember = false) { 130 let notification = PopupNotifications.getNotification("geolocation"); 131 ok( 132 PopupNotifications.isPanelOpen && notification, 133 "Geolocation notification is open" 134 ); 135 136 let rememberCheck = PopupNotifications.panel.querySelector( 137 ".popup-notification-checkbox" 138 ); 139 rememberCheck.checked = remember; 140 141 let popupHidden = BrowserTestUtils.waitForEvent( 142 PopupNotifications.panel, 143 "popuphidden" 144 ); 145 if (allow) { 146 let allowBtn = PopupNotifications.panel.querySelector( 147 ".popup-notification-primary-button" 148 ); 149 allowBtn.click(); 150 } else { 151 let denyBtn = PopupNotifications.panel.querySelector( 152 ".popup-notification-secondary-button" 153 ); 154 denyBtn.click(); 155 } 156 return popupHidden; 157 } 158 159 function setGeoLastAccess(browser, state) { 160 return new Promise(resolve => { 161 let host = browser.currentURI.host; 162 let handler = { 163 handleCompletion: () => resolve(), 164 }; 165 166 if (!state) { 167 CP.removeByDomainAndName( 168 host, 169 GEO_CONTENT_PREF_KEY, 170 browser.loadContext, 171 handler 172 ); 173 return; 174 } 175 CP.set( 176 host, 177 GEO_CONTENT_PREF_KEY, 178 new Date().toString(), 179 browser.loadContext, 180 handler 181 ); 182 }); 183 } 184 185 async function testGeoLocationLastAccessSet(browser) { 186 let timestamp = await new Promise(resolve => { 187 let lastAccess = null; 188 CP.getByDomainAndName( 189 gBrowser.currentURI.spec, 190 GEO_CONTENT_PREF_KEY, 191 browser.loadContext, 192 { 193 handleResult(pref) { 194 lastAccess = pref.value; 195 }, 196 handleCompletion() { 197 resolve(lastAccess); 198 }, 199 } 200 ); 201 }); 202 203 Assert.notEqual(timestamp, null, "Geo last access timestamp set"); 204 205 let parseSuccess = true; 206 try { 207 timestamp = new Date(timestamp); 208 } catch (e) { 209 parseSuccess = false; 210 } 211 ok( 212 parseSuccess && !isNaN(timestamp), 213 "Geo last access timestamp is valid Date" 214 ); 215 } 216 217 async function cleanup(tab) { 218 await setGeoLastAccess(tab.linkedBrowser, false); 219 SitePermissions.removeFromPrincipal( 220 tab.linkedBrowser.contentPrincipal, 221 "geo", 222 tab.linkedBrowser 223 ); 224 gBrowser.resetBrowserSharing(tab.linkedBrowser); 225 BrowserTestUtils.removeTab(tab); 226 } 227 228 async function testIndicatorGeoSharingState(active) { 229 let tab = await openExamplePage(); 230 gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: active }); 231 await testGeoSharingIconVisible(active); 232 233 await cleanup(tab); 234 } 235 236 async function testIndicatorExplicitAllow(persistent) { 237 let tab = await openExamplePage(); 238 239 let popupShown = BrowserTestUtils.waitForEvent( 240 PopupNotifications.panel, 241 "popupshown" 242 ); 243 info("Requesting geolocation"); 244 let request = requestGeoLocation(tab.linkedBrowser); 245 await popupShown; 246 info("Allowing geolocation via popup"); 247 answerGeoLocationPopup(true, persistent); 248 await request; 249 250 await Promise.all([ 251 testGeoSharingIconVisible(true), 252 testPermissionPopupGeoContainer(true, true), 253 testGeoLocationLastAccessSet(tab.linkedBrowser), 254 ]); 255 256 await cleanup(tab); 257 } 258 259 // Indicator and permission popup entry shown after explicit PermissionUI geolocation allow 260 add_task(function test_indicator_and_timestamp_after_explicit_allow() { 261 return testIndicatorExplicitAllow(false); 262 }); 263 add_task(function test_indicator_and_timestamp_after_explicit_allow_remember() { 264 return testIndicatorExplicitAllow(true); 265 }); 266 267 // Indicator and permission popup entry shown after auto PermissionUI geolocation allow 268 add_task(async function test_indicator_and_timestamp_after_implicit_allow() { 269 PermissionTestUtils.add( 270 EXAMPLE_PAGE_URI, 271 "geo", 272 Services.perms.ALLOW_ACTION, 273 Services.perms.EXPIRE_NEVER 274 ); 275 let tab = await openExamplePage(); 276 let result = await requestGeoLocation(tab.linkedBrowser); 277 ok(result, "Request should be allowed"); 278 279 await Promise.all([ 280 testGeoSharingIconVisible(true), 281 testPermissionPopupGeoContainer(true, true), 282 testGeoLocationLastAccessSet(tab.linkedBrowser), 283 ]); 284 285 await cleanup(tab); 286 }); 287 288 // Indicator shown when manually setting sharing state to true 289 add_task(function test_indicator_sharing_state_active() { 290 return testIndicatorGeoSharingState(true); 291 }); 292 293 // Indicator not shown when manually setting sharing state to false 294 add_task(function test_indicator_sharing_state_inactive() { 295 return testIndicatorGeoSharingState(false); 296 }); 297 298 // Permission popup shows permission if geo permission is set to persistent allow 299 add_task(async function test_permission_popup_permission_scope_permanent() { 300 PermissionTestUtils.add( 301 EXAMPLE_PAGE_URI, 302 "geo", 303 Services.perms.ALLOW_ACTION, 304 Services.perms.EXPIRE_NEVER 305 ); 306 let tab = await openExamplePage(); 307 308 await testPermissionPopupGeoContainer(true, false); // Expect permission to be visible, but not lastAccess indicator 309 310 await cleanup(tab); 311 }); 312 313 // Sharing state set, but no permission 314 add_task(async function test_permission_popup_permission_sharing_state() { 315 let tab = await openExamplePage(); 316 gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); 317 await testPermissionPopupGeoContainer(true, false); 318 319 await cleanup(tab); 320 }); 321 322 // Permission popup has correct state if sharing state and last geo access timestamp are set 323 add_task( 324 async function test_permission_popup_permission_sharing_state_timestamp() { 325 let tab = await openExamplePage(); 326 gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); 327 await setGeoLastAccess(tab.linkedBrowser, true); 328 329 await testPermissionPopupGeoContainer(true, true); 330 331 await cleanup(tab); 332 } 333 ); 334 335 // Clicking permission clear button clears permission and resets geo sharing state 336 add_task(async function test_permission_popup_permission_clear() { 337 PermissionTestUtils.add( 338 EXAMPLE_PAGE_URI, 339 "geo", 340 Services.perms.ALLOW_ACTION, 341 Services.perms.EXPIRE_NEVER 342 ); 343 let tab = await openExamplePage(); 344 gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); 345 346 await openPermissionPopup(); 347 348 let clearButton = document.querySelector( 349 "#permission-popup-geo-container button" 350 ); 351 ok(clearButton, "Clear button is visible"); 352 clearButton.click(); 353 354 await Promise.all([ 355 testGeoSharingIconVisible(false), 356 testPermissionPopupGeoContainer(false, false), 357 TestUtils.waitForCondition(() => { 358 let sharingState = tab._sharingState; 359 return ( 360 sharingState == null || 361 sharingState.geo == null || 362 sharingState.geo === false 363 ); 364 }, "Waiting for geo sharing state to reset"), 365 ]); 366 await cleanup(tab); 367 }); 368 369 /** 370 * Tests that we only show the last access label once when the sharing 371 * state is updated multiple times while the popup is open. 372 */ 373 add_task(async function test_permission_no_duplicate_last_access_label() { 374 let tab = await openExamplePage(); 375 await setGeoLastAccess(tab.linkedBrowser, true); 376 await openPermissionPopup(); 377 gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); 378 gBrowser.updateBrowserSharing(tab.linkedBrowser, { geo: true }); 379 await testPermissionPopupGeoContainer(true, true); 380 await cleanup(tab); 381 });