browser_pinned_tabs.js (10064B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const REMOTE_URL = "https://www.example.com/"; 7 const ABOUT_ROBOTS_URL = "about:robots"; 8 const NO_TITLE_URL = "data:text/plain,foo"; 9 10 const BACKUP_STATE = SessionStore.getBrowserState(); 11 registerCleanupFunction(() => promiseBrowserState(BACKUP_STATE)); 12 13 add_setup(async function () { 14 await SpecialPowers.pushPrefEnv({ 15 set: [ 16 ["browser.sessionstore.restore_on_demand", true], 17 ["browser.sessionstore.restore_tabs_lazily", true], 18 ], 19 }); 20 }); 21 22 /** 23 * When implementing batch insertion of tabs as part of session restore, 24 * we started reversing the insertion order of pinned tabs (bug 1607441). 25 * This test checks we don't regress that again. 26 */ 27 add_task(async function test_pinned_tabs_order() { 28 // we expect 3 pinned tabs plus the selected tab get content restored. 29 let allTabsRestored = promiseSessionStoreLoads(4); 30 await promiseBrowserState({ 31 windows: [ 32 { 33 selected: 4, // SessionStore uses 1-based indexing. 34 tabs: [ 35 { 36 pinned: true, 37 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 38 }, 39 { 40 pinned: true, 41 entries: [{ url: ABOUT_ROBOTS_URL, triggeringPrincipal_base64 }], 42 }, 43 { 44 pinned: true, 45 entries: [{ url: NO_TITLE_URL, triggeringPrincipal_base64 }], 46 }, 47 { entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }] }, 48 { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, 49 ], 50 }, 51 ], 52 }); 53 await allTabsRestored; 54 let [tab1, tab2, tab3, tab4, tab5] = gBrowser.tabs; 55 ok(tab1.pinned, "First tab is pinned"); 56 ok(tab2.pinned, "Second tab is pinned"); 57 ok(tab3.pinned, "Third tab is pinned"); 58 ok(!tab4.pinned, "Fourth tab is not pinned"); 59 ok(!tab5.pinned, "Fifth tab is not pinned"); 60 61 ok(tab4.selected, "Fourth tab is selected"); 62 is( 63 tab1.linkedBrowser.currentURI.spec, 64 REMOTE_URL, 65 "First tab has matching URL" 66 ); 67 is( 68 tab2.linkedBrowser.currentURI.spec, 69 ABOUT_ROBOTS_URL, 70 "Second tab has matching URL" 71 ); 72 is( 73 tab3.linkedBrowser.currentURI.spec, 74 NO_TITLE_URL, 75 "Third tab has matching URL" 76 ); 77 // Clean up for the next task. 78 await promiseBrowserState(BACKUP_STATE); 79 }); 80 81 /** 82 * When fixing the previous regression, pinned tabs started disappearing out 83 * of sessions with selected pinned tabs. This test checks that case. 84 */ 85 add_task(async function test_selected_pinned_tab_dataloss() { 86 // we expect 3 pinned tabs (one of which is selected) get content restored. 87 let allTabsRestored = promiseSessionStoreLoads(3); 88 await promiseBrowserState({ 89 windows: [ 90 { 91 selected: 1, // SessionStore uses 1-based indexing. 92 tabs: [ 93 { 94 pinned: true, 95 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 96 }, 97 { 98 pinned: true, 99 entries: [{ url: ABOUT_ROBOTS_URL, triggeringPrincipal_base64 }], 100 }, 101 { 102 pinned: true, 103 entries: [{ url: NO_TITLE_URL, triggeringPrincipal_base64 }], 104 }, 105 { entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }] }, 106 { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, 107 ], 108 }, 109 ], 110 }); 111 await allTabsRestored; 112 let [tab1, tab2, tab3, tab4, tab5] = gBrowser.tabs; 113 ok(tab5, "Should have 5 tabs"); 114 ok(tab1.pinned, "First tab is pinned"); 115 ok(tab2.pinned, "Second tab is pinned"); 116 ok(tab3.pinned, "Third tab is pinned"); 117 ok(tab4 && !tab4.pinned, "Fourth tab is not pinned"); 118 ok(tab5 && !tab5.pinned, "Fifth tab is not pinned"); 119 120 ok(tab1 && tab1.selected, "First (pinned) tab is selected"); 121 is( 122 tab1.linkedBrowser.currentURI.spec, 123 REMOTE_URL, 124 "First tab has matching URL" 125 ); 126 is( 127 tab2.linkedBrowser.currentURI.spec, 128 ABOUT_ROBOTS_URL, 129 "Second tab has matching URL" 130 ); 131 is( 132 tab3.linkedBrowser.currentURI.spec, 133 NO_TITLE_URL, 134 "Third tab has matching URL" 135 ); 136 // Clean up for the next task. 137 await promiseBrowserState(BACKUP_STATE); 138 }); 139 140 /** 141 * While we're here, it seems useful to have a test for mixed pinned and 142 * unpinned tabs in session store state, as well as userContextId. 143 */ 144 add_task(async function test_mixed_pinned_unpinned() { 145 // we expect 3 pinned tabs plus the selected tab get content restored. 146 let allTabsRestored = promiseSessionStoreLoads(4); 147 await promiseBrowserState({ 148 windows: [ 149 { 150 selected: 4, // SessionStore uses 1-based indexing. 151 tabs: [ 152 { 153 pinned: true, 154 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 155 }, 156 { entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }] }, 157 { 158 pinned: true, 159 entries: [{ url: ABOUT_ROBOTS_URL, triggeringPrincipal_base64 }], 160 }, 161 { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, 162 { 163 pinned: true, 164 entries: [{ url: NO_TITLE_URL, triggeringPrincipal_base64 }], 165 }, 166 ], 167 }, 168 ], 169 }); 170 await allTabsRestored; 171 let [tab1, tab2, tab3, tab4, tab5] = gBrowser.tabs; 172 ok(tab1.pinned, "First tab is pinned"); 173 ok(tab2.pinned, "Second tab is pinned"); 174 ok(tab3.pinned, "Third tab is pinned"); 175 ok(!tab4.pinned, "Fourth tab is not pinned"); 176 ok(!tab5.pinned, "Fifth tab is not pinned"); 177 178 // This is confusing to read - the 4th entry in the session data is 179 // selected. But the 5th entry is pinned, so it moves to the start of the 180 // tabstrip, so when we fetch `gBrowser.tabs`, the 4th entry in the list 181 // is actually the 5th tab. 182 ok(tab5.selected, "Fifth tab is selected"); 183 is( 184 tab1.linkedBrowser.currentURI.spec, 185 REMOTE_URL, 186 "First tab has matching URL" 187 ); 188 is( 189 tab2.linkedBrowser.currentURI.spec, 190 ABOUT_ROBOTS_URL, 191 "Second tab has matching URL" 192 ); 193 is( 194 tab3.linkedBrowser.currentURI.spec, 195 NO_TITLE_URL, 196 "Third tab has matching URL" 197 ); 198 // Clean up for the next task. 199 await promiseBrowserState(BACKUP_STATE); 200 }); 201 202 /** 203 * After session restore, if we crash an unpinned tab, we noticed pinned tabs 204 * created in the same process would lose all data (Bug 1624511). This test 205 * checks that case. 206 */ 207 add_task(async function test_pinned_tab_dataloss() { 208 // We do not run if there are no crash reporters to avoid 209 // problems with the intentional crash. 210 if (!AppConstants.MOZ_CRASHREPORTER) { 211 return; 212 } 213 // If we end up increasing the process count limit in future, 214 // we want to ensure that we don't stop testing this case 215 // of pinned tab data loss. 216 if (SpecialPowers.getIntPref("dom.ipc.processCount") > 8) { 217 ok( 218 false, 219 "Process count is greater than 8, update the number of pinned tabs in test." 220 ); 221 } 222 223 // We expect 17 pinned tabs plus the selected tab get content restored. 224 // Given that the default process count is currently 8, we need this 225 // number of pinned tabs to reproduce the data loss. If this changes, 226 // please add more pinned tabs. 227 let allTabsRestored = promiseSessionStoreLoads(18); 228 await promiseBrowserState({ 229 windows: [ 230 { 231 selected: 18, // SessionStore uses 1-based indexing. 232 tabs: [ 233 { 234 pinned: true, 235 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 236 }, 237 { 238 pinned: true, 239 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 240 }, 241 { 242 pinned: true, 243 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 244 }, 245 { 246 pinned: true, 247 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 248 }, 249 { 250 pinned: true, 251 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 252 }, 253 { 254 pinned: true, 255 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 256 }, 257 { 258 pinned: true, 259 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 260 }, 261 { 262 pinned: true, 263 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 264 }, 265 { 266 pinned: true, 267 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 268 }, 269 { 270 pinned: true, 271 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 272 }, 273 { 274 pinned: true, 275 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 276 }, 277 { 278 pinned: true, 279 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 280 }, 281 { 282 pinned: true, 283 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 284 }, 285 { 286 pinned: true, 287 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 288 }, 289 { 290 pinned: true, 291 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 292 }, 293 { 294 pinned: true, 295 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 296 }, 297 { 298 pinned: true, 299 entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }], 300 }, 301 { entries: [{ url: REMOTE_URL, triggeringPrincipal_base64 }] }, 302 ], 303 }, 304 ], 305 }); 306 await allTabsRestored; 307 308 let tabs = gBrowser.tabs; 309 await BrowserTestUtils.crashFrame(tabs[17].linkedBrowser); 310 311 await TestUtils.topicObserved("sessionstore-state-write-complete"); 312 313 for (let i = 0; i < tabs.length; i++) { 314 let tab = tabs[i]; 315 is( 316 tab.linkedBrowser.currentURI.spec, 317 REMOTE_URL, 318 `Tab ${i + 1} should have matching URL` 319 ); 320 } 321 322 // Clean up for the next task. 323 await promiseBrowserState(BACKUP_STATE); 324 });