PromptCollection.sys.mjs (8639B)
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 const lazy = {}; 6 ChromeUtils.defineESModuleGetters(lazy, { 7 SelectableProfileService: 8 "resource:///modules/profiles/SelectableProfileService.sys.mjs", 9 }); 10 11 /** 12 * Implements nsIPromptCollection 13 * 14 * @class PromptCollection 15 */ 16 export class PromptCollection { 17 confirmRepost(browsingContext) { 18 let brandName; 19 try { 20 brandName = this.stringBundles.brand.GetStringFromName("brandShortName"); 21 } catch (exception) { 22 // That's ok, we'll use a generic version of the prompt 23 } 24 25 let message; 26 let resendLabel; 27 try { 28 if (brandName) { 29 message = this.stringBundles.app.formatStringFromName( 30 "confirmRepostPrompt", 31 [brandName] 32 ); 33 } else { 34 // Use a generic version of this prompt. 35 message = this.stringBundles.app.GetStringFromName( 36 "confirmRepostPrompt" 37 ); 38 } 39 resendLabel = 40 this.stringBundles.app.GetStringFromName("resendButton.label"); 41 } catch (exception) { 42 console.error("Failed to get strings from appstrings.properties"); 43 return false; 44 } 45 46 let docViewer = browsingContext?.docShell?.docViewer; 47 let modalType = docViewer?.isTabModalPromptAllowed 48 ? Ci.nsIPromptService.MODAL_TYPE_CONTENT 49 : Ci.nsIPromptService.MODAL_TYPE_WINDOW; 50 let buttonFlags = 51 (Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * 52 Ci.nsIPromptService.BUTTON_POS_0) | 53 (Ci.nsIPromptService.BUTTON_TITLE_CANCEL * 54 Ci.nsIPromptService.BUTTON_POS_1); 55 let buttonPressed = Services.prompt.confirmExBC( 56 browsingContext, 57 modalType, 58 null, 59 message, 60 buttonFlags, 61 resendLabel, 62 null, 63 null, 64 null, 65 {} 66 ); 67 68 return buttonPressed === 0; 69 } 70 71 async asyncBeforeUnloadCheck(browsingContext) { 72 const docViewer = browsingContext?.docShell?.docViewer; 73 if ( 74 (docViewer && !docViewer.isTabModalPromptAllowed) || 75 !browsingContext.ancestorsAreCurrent 76 ) { 77 console.error("Can't prompt from inactive content viewer"); 78 return true; 79 } 80 81 let originNoSuffix = 82 browsingContext.embedderElement?.contentPrincipal.originNoSuffix; 83 const isPDFjs = originNoSuffix === "resource://pdf.js"; 84 const isProfilePage = 85 originNoSuffix === "about:newprofile" || 86 originNoSuffix === "about:editprofile"; 87 88 let title, message, leaveLabel, stayLabel, buttonFlags; 89 let args = { 90 // Tell the prompt service that this is a permit unload prompt 91 // so that it can set the appropriate flag on the detail object 92 // of the events it dispatches. 93 inPermitUnload: true, 94 }; 95 96 try { 97 if (isPDFjs) { 98 title = this.stringBundles.dom.GetStringFromName( 99 "OnBeforeUnloadPDFjsTitle" 100 ); 101 message = this.stringBundles.dom.GetStringFromName( 102 "OnBeforeUnloadPDFjsMessage" 103 ); 104 buttonFlags = 105 Ci.nsIPromptService.BUTTON_POS_0_DEFAULT | 106 (Ci.nsIPrompt.BUTTON_TITLE_SAVE * Ci.nsIPrompt.BUTTON_POS_0) | 107 (Ci.nsIPrompt.BUTTON_TITLE_CANCEL * Ci.nsIPrompt.BUTTON_POS_1) | 108 (Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE * Ci.nsIPrompt.BUTTON_POS_2); 109 args.useTitle = true; 110 args.headerIconCSSValue = 111 "url('chrome://branding/content/document_pdf.svg')"; 112 } else if (isProfilePage) { 113 title = this.stringBundles.dom.GetStringFromName( 114 "OnBeforeUnloadAboutNewProfileTitle" 115 ); 116 let defaultName = lazy.SelectableProfileService.currentProfile.name; 117 message = this.stringBundles.dom.formatStringFromName( 118 "OnBeforeUnloadAboutNewProfileMessage", 119 [defaultName] 120 ); 121 leaveLabel = this.stringBundles.dom.GetStringFromName( 122 "OnBeforeUnloadAboutNewProfileLeaveButton" 123 ); 124 stayLabel = this.stringBundles.dom.GetStringFromName( 125 "OnBeforeUnloadAboutNewProfileStayButton" 126 ); 127 buttonFlags = 128 Ci.nsIPromptService.BUTTON_POS_0_DEFAULT | 129 (Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * 130 Ci.nsIPromptService.BUTTON_POS_0) | 131 (Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * 132 Ci.nsIPromptService.BUTTON_POS_1); 133 args.useTitle = true; 134 } else { 135 title = this.stringBundles.dom.GetStringFromName("OnBeforeUnloadTitle"); 136 message = this.stringBundles.dom.GetStringFromName( 137 "OnBeforeUnloadMessage2" 138 ); 139 leaveLabel = this.stringBundles.dom.GetStringFromName( 140 "OnBeforeUnloadLeaveButton" 141 ); 142 stayLabel = this.stringBundles.dom.GetStringFromName( 143 "OnBeforeUnloadStayButton" 144 ); 145 buttonFlags = 146 Ci.nsIPromptService.BUTTON_POS_0_DEFAULT | 147 (Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * 148 Ci.nsIPromptService.BUTTON_POS_0) | 149 (Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * 150 Ci.nsIPromptService.BUTTON_POS_1); 151 } 152 } catch (exception) { 153 console.error("Failed to get strings from dom.properties"); 154 return false; 155 } 156 157 const result = await Services.prompt.asyncConfirmEx( 158 browsingContext, 159 Services.prompt.MODAL_TYPE_CONTENT, 160 title, 161 message, 162 buttonFlags, 163 leaveLabel, 164 stayLabel, 165 null, 166 null, 167 false, 168 args 169 ); 170 const buttonNumClicked = result 171 .QueryInterface(Ci.nsIPropertyBag2) 172 .get("buttonNumClicked"); 173 if (isPDFjs) { 174 if (buttonNumClicked === 0) { 175 const savePdfPromise = new Promise(resolve => { 176 Services.obs.addObserver( 177 { 178 observe(_aSubject, aTopic) { 179 if (aTopic === "pdfjs:saveComplete") { 180 Services.obs.removeObserver(this, aTopic); 181 resolve(); 182 } 183 }, 184 }, 185 "pdfjs:saveComplete" 186 ); 187 }); 188 const actor = browsingContext.currentWindowGlobal.getActor("Pdfjs"); 189 actor.sendAsyncMessage("PDFJS:Save"); 190 await savePdfPromise; 191 } 192 return buttonNumClicked !== 1; 193 } else if (isProfilePage) { 194 if (buttonNumClicked === 0) { 195 Services.prefs.setBoolPref( 196 "browser.profiles.profile-name.updated", 197 true 198 ); 199 } 200 let gleanFn = 201 originNoSuffix === "about:newprofile" 202 ? "profilesNew" 203 : "profilesExisting"; 204 let value = buttonNumClicked === 0 ? "leave" : "cancel"; 205 Glean[gleanFn].alert.record({ value }); 206 207 return buttonNumClicked !== 1; 208 } 209 210 return buttonNumClicked === 0; 211 } 212 213 confirmFolderUpload(browsingContext, directoryName) { 214 let title; 215 let message; 216 let acceptLabel; 217 218 try { 219 title = this.stringBundles.dom.GetStringFromName( 220 "FolderUploadPrompt.title" 221 ); 222 message = this.stringBundles.dom.formatStringFromName( 223 "FolderUploadPrompt.message", 224 [directoryName] 225 ); 226 acceptLabel = this.stringBundles.dom.GetStringFromName( 227 "FolderUploadPrompt.acceptButtonLabel" 228 ); 229 } catch (exception) { 230 console.error("Failed to get strings from dom.properties"); 231 return false; 232 } 233 234 let buttonFlags = 235 Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 + 236 Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 + 237 Services.prompt.BUTTON_POS_1_DEFAULT; 238 239 return ( 240 Services.prompt.confirmExBC( 241 browsingContext, 242 Services.prompt.MODAL_TYPE_TAB, 243 title, 244 message, 245 buttonFlags | Ci.nsIPrompt.BUTTON_DELAY_ENABLE, 246 acceptLabel, 247 null, 248 null, 249 null, 250 {} 251 ) === 0 252 ); 253 } 254 } 255 256 const BUNDLES = { 257 dom: "chrome://global/locale/dom/dom.properties", 258 app: "chrome://global/locale/appstrings.properties", 259 brand: "chrome://branding/locale/brand.properties", 260 }; 261 262 PromptCollection.prototype.stringBundles = {}; 263 264 for (const [bundleName, bundleUrl] of Object.entries(BUNDLES)) { 265 ChromeUtils.defineLazyGetter( 266 PromptCollection.prototype.stringBundles, 267 bundleName, 268 function () { 269 let bundle = Services.strings.createBundle(bundleUrl); 270 if (!bundle) { 271 throw new Error("String bundle for dom not present!"); 272 } 273 return bundle; 274 } 275 ); 276 } 277 278 PromptCollection.prototype.QueryInterface = ChromeUtils.generateQI([ 279 "nsIPromptCollection", 280 ]);