BackupUIChild.sys.mjs (6382B)
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 /** 6 * A JSWindowActor that is responsible for marshalling information between 7 * the BackupService singleton and any registered UI widgets that need to 8 * represent data from that service. Any UI widgets that want to receive 9 * state updates from BackupService should emit a BackupUI:InitWidget 10 * event in a document that this actor pair is registered for. 11 */ 12 export class BackupUIChild extends JSWindowActorChild { 13 #inittedWidgets = new WeakSet(); 14 15 /** 16 * Handles custom events fired by widgets that want to register with 17 * BackupUIChild. 18 * 19 * @param {Event} event 20 * The custom event that the widget fired. 21 */ 22 async handleEvent(event) { 23 /** 24 * BackupUI:InitWidget sends a message to the parent to request the BackupService state 25 * which will result in a `backupServiceState` property of the widget to be set when that 26 * state is received. Subsequent state updates will also cause that state property to 27 * be set. 28 */ 29 if (event.type == "BackupUI:InitWidget") { 30 this.#inittedWidgets.add(event.target); 31 this.sendAsyncMessage("RequestState"); 32 } else if (event.type == "BackupUI:TriggerCreateBackup") { 33 let result = await this.sendQuery("TriggerCreateBackup", event.detail); 34 35 if (!result.success) { 36 event.target.backupErrorCode = result.errorCode; 37 } 38 } else if (event.type == "BackupUI:EnableScheduledBackups") { 39 const target = event.target; 40 41 const result = await this.sendQuery( 42 "EnableScheduledBackups", 43 event.detail 44 ); 45 if (result.success) { 46 target.close(); 47 } else { 48 target.enableBackupErrorCode = result.errorCode; 49 } 50 } else if (event.type == "BackupUI:DisableScheduledBackups") { 51 const target = event.target; 52 53 this.sendAsyncMessage("DisableScheduledBackups", event.detail); 54 // backups will always end up disabled even if there was an error 55 // with other bookkeeping related to turning off backups 56 57 target.close(); 58 } else if (event.type == "BackupUI:ShowFilepicker") { 59 let targetNodeName = event.composedTarget.nodeName; 60 let { path, filename, iconURL } = await this.sendQuery("ShowFilepicker", { 61 win: event.detail?.win, 62 filter: event.detail?.filter, 63 existingBackupPath: event.detail?.existingBackupPath, 64 }); 65 66 let widgets = ChromeUtils.nondeterministicGetWeakSetKeys( 67 this.#inittedWidgets 68 ); 69 70 for (let widget of widgets) { 71 if (widget.isConnected && widget.nodeName == targetNodeName) { 72 const win = widget.ownerGlobal; 73 // Using Cu.cloneInto here allows us to embed components that use this event 74 // in non-parent-processes such as about:welcome 75 const detail = Cu.cloneInto({ path, filename, iconURL }, win, { 76 wrapReflectors: true, 77 }); 78 const event = new win.CustomEvent( 79 "BackupUI:SelectNewFilepickerPath", 80 { 81 bubbles: true, 82 composed: true, 83 detail, 84 } 85 ); 86 widget.dispatchEvent(event); 87 break; 88 } 89 } 90 } else if (event.type == "BackupUI:GetBackupFileInfo") { 91 let { backupFile } = event.detail; 92 this.sendAsyncMessage("GetBackupFileInfo", { 93 backupFile, 94 }); 95 } else if (event.type == "BackupUI:RestoreFromBackupFile") { 96 let { backupFile, backupPassword } = event.detail; 97 let result = await this.sendQuery("RestoreFromBackupFile", { 98 backupFile, 99 backupPassword, 100 }); 101 102 if (result.success) { 103 event.target.restoreFromBackupDialogEl?.close(); 104 105 // Since we always launch the new profile from this event, let's close the current instance now 106 this.sendAsyncMessage("QuitCurrentProfile"); 107 } 108 } else if (event.type == "BackupUI:RestoreFromBackupChooseFile") { 109 this.sendAsyncMessage("RestoreFromBackupChooseFile"); 110 } else if (event.type == "BackupUI:EnableEncryption") { 111 const target = event.target; 112 113 const result = await this.sendQuery("EnableEncryption", event.detail); 114 if (result.success) { 115 target.close(); 116 } else { 117 target.enableEncryptionErrorCode = result.errorCode; 118 } 119 } else if (event.type == "BackupUI:DisableEncryption") { 120 const target = event.target; 121 122 const result = await this.sendQuery("DisableEncryption", event.detail); 123 if (result.success) { 124 target.close(); 125 } else { 126 target.disableEncryptionErrorCode = result.errorCode; 127 } 128 } else if (event.type == "BackupUI:ShowBackupLocation") { 129 this.sendAsyncMessage("ShowBackupLocation"); 130 } else if (event.type == "BackupUI:EditBackupLocation") { 131 this.sendAsyncMessage("EditBackupLocation"); 132 } else if (event.type == "BackupUI:SetEmbeddedComponentPersistentData") { 133 this.sendAsyncMessage("SetEmbeddedComponentPersistentData", event.detail); 134 } else if (event.type == "BackupUI:FlushEmbeddedComponentPersistentData") { 135 this.sendAsyncMessage("FlushEmbeddedComponentPersistentData"); 136 } else if (event.type == "BackupUI:ErrorBarDismissed") { 137 this.sendAsyncMessage("ErrorBarDismissed"); 138 } 139 } 140 141 /** 142 * Handles messages sent by BackupUIParent. 143 * 144 * @param {ReceiveMessageArgument} message 145 * The message received from the BackupUIParent. 146 */ 147 receiveMessage(message) { 148 if (message.name == "StateUpdate") { 149 let widgets = ChromeUtils.nondeterministicGetWeakSetKeys( 150 this.#inittedWidgets 151 ); 152 for (let widget of widgets) { 153 if (!widget.isConnected || !widget.ownerGlobal) { 154 continue; 155 } 156 157 const state = Cu.cloneInto(message.data.state, widget.ownerGlobal); 158 159 const waivedWidget = Cu.waiveXrays(widget); 160 waivedWidget.backupServiceState = state; 161 //dispatch the event for the React listeners 162 widget.dispatchEvent( 163 new this.contentWindow.CustomEvent("BackupUI:StateWasUpdated", { 164 bubbles: true, 165 composed: true, 166 detail: { state }, 167 }) 168 ); 169 } 170 } 171 } 172 }