debug.js (8985B)
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 https://mozilla.org/MPL/2.0/. */ 4 5 /* import-globals-from /toolkit/content/preferencesBindings.js */ 6 7 Preferences.addAll([ 8 { id: "browser.backup.enabled", type: "bool" }, 9 { id: "browser.backup.log", type: "bool" }, 10 ]); 11 12 const { BackupService } = ChromeUtils.importESModule( 13 "resource:///modules/backup/BackupService.sys.mjs" 14 ); 15 16 let DebugUI = { 17 init() { 18 let controls = document.querySelector("#controls"); 19 controls.addEventListener("click", this); 20 21 let encryptionEnabled = document.querySelector("#encryption-enabled"); 22 encryptionEnabled.addEventListener("click", this); 23 24 // We use `init` instead of `get` here, since this page might load before 25 // the BackupService has had a chance to initialize itself. 26 let service = BackupService.init(); 27 service.addEventListener("BackupService:StateUpdate", this); 28 this.onStateUpdate(); 29 30 // Kick-off reading any pre-existing encryption state off of the disk. 31 service.loadEncryptionState(); 32 }, 33 34 handleEvent(event) { 35 switch (event.type) { 36 case "BackupService:StateUpdate": { 37 this.onStateUpdate(); 38 break; 39 } 40 case "click": { 41 let target = event.target; 42 if (HTMLButtonElement.isInstance(event.target)) { 43 this.onButtonClick(target); 44 } else if ( 45 HTMLInputElement.isInstance(event.target) && 46 event.target.type == "checkbox" 47 ) { 48 event.preventDefault(); 49 this.onCheckboxClick(target); 50 } 51 break; 52 } 53 } 54 }, 55 56 secondsToHms(seconds) { 57 let h = Math.floor(seconds / 3600); 58 let m = Math.floor((seconds % 3600) / 60); 59 let s = Math.floor((seconds % 3600) % 60); 60 return `${h}h ${m}m ${s}s`; 61 }, 62 63 async onButtonClick(button) { 64 switch (button.id) { 65 case "create-backup": { 66 let service = BackupService.get(); 67 let lastBackupStatus = document.querySelector("#last-backup-status"); 68 lastBackupStatus.textContent = "Creating backup..."; 69 70 let then = ChromeUtils.now(); 71 button.disabled = true; 72 await service.createBackup(); 73 let totalTimeSeconds = (ChromeUtils.now() - then) / 1000; 74 button.disabled = false; 75 new Notification(`Backup created`, { 76 body: `Total time ${this.secondsToHms(totalTimeSeconds)}`, 77 }); 78 lastBackupStatus.textContent = `Backup created - total time: ${this.secondsToHms( 79 totalTimeSeconds 80 )}`; 81 break; 82 } 83 case "open-backup-folder": { 84 let backupsDir = PathUtils.join( 85 PathUtils.profileDir, 86 BackupService.PROFILE_FOLDER_NAME 87 ); 88 89 let nsLocalFile = Components.Constructor( 90 "@mozilla.org/file/local;1", 91 "nsIFile", 92 "initWithPath" 93 ); 94 95 if (await IOUtils.exists(backupsDir)) { 96 new nsLocalFile(backupsDir).reveal(); 97 } else { 98 alert("backups folder doesn't exist yet"); 99 } 100 101 break; 102 } 103 case "recover-from-staging": { 104 let backupsDir = PathUtils.join( 105 PathUtils.profileDir, 106 BackupService.PROFILE_FOLDER_NAME 107 ); 108 let fp = Cc["@mozilla.org/filepicker;1"].createInstance( 109 Ci.nsIFilePicker 110 ); 111 fp.init( 112 window.browsingContext, 113 "Choose a staging folder", 114 Ci.nsIFilePicker.modeGetFolder 115 ); 116 fp.displayDirectory = await IOUtils.getDirectory(backupsDir); 117 let result = await new Promise(resolve => fp.open(resolve)); 118 if (result == Ci.nsIFilePicker.returnCancel) { 119 break; 120 } 121 122 let path = fp.file.path; 123 let lastRecoveryStatus = document.querySelector( 124 "#last-recovery-status" 125 ); 126 lastRecoveryStatus.textContent = "Recovering from backup..."; 127 128 let service = BackupService.get(); 129 try { 130 let newProfile = await service.recoverFromSnapshotFolder( 131 path, 132 true /* shouldLaunch */ 133 ); 134 lastRecoveryStatus.textContent = `Created profile ${newProfile.name} at ${newProfile.rootDir.path}`; 135 } catch (e) { 136 lastRecoveryStatus.textContent( 137 `Failed to recover: ${e.message} Check the console for the full exception.` 138 ); 139 throw e; 140 } 141 break; 142 } 143 case "extract-from-archive": { 144 let backupsDir = PathUtils.join( 145 PathUtils.profileDir, 146 BackupService.PROFILE_FOLDER_NAME 147 ); 148 let fp = Cc["@mozilla.org/filepicker;1"].createInstance( 149 Ci.nsIFilePicker 150 ); 151 fp.init( 152 window.browsingContext, 153 "Choose an archive file", 154 Ci.nsIFilePicker.modeOpen 155 ); 156 fp.displayDirectory = await IOUtils.getDirectory(backupsDir); 157 let result = await new Promise(resolve => fp.open(resolve)); 158 if (result == Ci.nsIFilePicker.returnCancel) { 159 break; 160 } 161 162 let extractionStatus = document.querySelector("#extraction-status"); 163 extractionStatus.textContent = "Extracting..."; 164 165 let path = fp.file.path; 166 let dest = PathUtils.join(PathUtils.parent(path), "extraction.zip"); 167 let service = BackupService.get(); 168 try { 169 let { isEncrypted } = await service.sampleArchive(path); 170 let recoveryCode = undefined; 171 if (isEncrypted) { 172 recoveryCode = prompt("Please provide the decryption password"); 173 } 174 await service.extractCompressedSnapshotFromArchive( 175 path, 176 dest, 177 recoveryCode 178 ); 179 extractionStatus.textContent = `Extracted ZIP file to ${dest}`; 180 } catch (e) { 181 extractionStatus.textContent = `Failed to extract: ${e.message} Check the console for the full exception.`; 182 throw e; 183 } 184 break; 185 } 186 case "recover-from-archive": { 187 let backupsDir = PathUtils.join( 188 PathUtils.profileDir, 189 BackupService.PROFILE_FOLDER_NAME 190 ); 191 let fp = Cc["@mozilla.org/filepicker;1"].createInstance( 192 Ci.nsIFilePicker 193 ); 194 fp.init( 195 window.browsingContext, 196 "Choose an archive file", 197 Ci.nsIFilePicker.modeOpen 198 ); 199 fp.displayDirectory = await IOUtils.getDirectory(backupsDir); 200 fp.appendFilters(Ci.nsIFilePicker.filterHTML); 201 202 let result = await new Promise(resolve => fp.open(resolve)); 203 if (result == Ci.nsIFilePicker.returnCancel) { 204 break; 205 } 206 207 let recoverFromArchiveStatus = document.querySelector( 208 "#recover-from-archive-status" 209 ); 210 recoverFromArchiveStatus.textContent = 211 "Recovering from backup archive..."; 212 213 let path = fp.file.path; 214 let service = BackupService.get(); 215 try { 216 let { isEncrypted } = await service.sampleArchive(path); 217 let recoveryCode = undefined; 218 if (isEncrypted) { 219 recoveryCode = prompt("Please provide the decryption password"); 220 } 221 let newProfile = await service.recoverFromBackupArchive( 222 path, 223 recoveryCode, 224 true /* shouldLaunch */ 225 ); 226 recoverFromArchiveStatus.textContent = `Created profile ${newProfile.name} at ${newProfile.rootDir.path}`; 227 } catch (e) { 228 recoverFromArchiveStatus.textContent = `Failed to recover: ${e.message} Check the console for the full exception.`; 229 throw e; 230 } 231 break; 232 } 233 } 234 }, 235 236 async onCheckboxClick(checkbox) { 237 if (checkbox.id == "encryption-enabled") { 238 let service = BackupService.get(); 239 if (checkbox.checked) { 240 let password = prompt("What's the encryption password? (8 char min)"); 241 if (password != null) { 242 try { 243 await service.enableEncryption(password); 244 } catch (e) { 245 console.error(e); 246 } 247 } 248 } else if (confirm("Disable encryption?")) { 249 try { 250 await service.disableEncryption(); 251 } catch (e) { 252 console.error(e); 253 } 254 } 255 } 256 }, 257 258 onStateUpdate() { 259 let service = BackupService.get(); 260 let state = service.state; 261 262 let encryptionEnabled = document.querySelector("#encryption-enabled"); 263 encryptionEnabled.checked = state.encryptionEnabled; 264 }, 265 }; 266 267 // Wait until the load event fires before setting up any listeners or updating 268 // any of the state of the page. We do this in order to avoid having any of 269 // our control states overwritten by SessionStore after a restoration, as 270 // restoration of form state occurs _prior_ to the load event firing. 271 addEventListener( 272 "load", 273 () => { 274 DebugUI.init(); 275 }, 276 { once: true } 277 );