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"use strict"; 6 7const EXPORTED_SYMBOLS = ["AboutWelcomeParent"]; 8const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 9 10const { XPCOMUtils } = ChromeUtils.import( 11 "resource://gre/modules/XPCOMUtils.jsm" 12); 13 14XPCOMUtils.defineLazyModuleGetters(this, { 15 FxAccounts: "resource://gre/modules/FxAccounts.jsm", 16 MigrationUtils: "resource:///modules/MigrationUtils.jsm", 17 OS: "resource://gre/modules/osfile.jsm", 18 SpecialMessageActions: 19 "resource://messaging-system/lib/SpecialMessageActions.jsm", 20 AboutWelcomeTelemetry: 21 "resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm", 22}); 23 24XPCOMUtils.defineLazyGetter(this, "log", () => { 25 const { Logger } = ChromeUtils.import( 26 "resource://messaging-system/lib/Logger.jsm" 27 ); 28 return new Logger("AboutWelcomeParent"); 29}); 30 31XPCOMUtils.defineLazyGetter( 32 this, 33 "Telemetry", 34 () => new AboutWelcomeTelemetry() 35); 36 37const DID_SEE_ABOUT_WELCOME_PREF = "trailhead.firstrun.didSeeAboutWelcome"; 38const AWTerminate = { 39 UNKNOWN: "unknown", 40 WINDOW_CLOSED: "welcome-window-closed", 41 TAB_CLOSED: "welcome-tab-closed", 42 APP_SHUT_DOWN: "app-shut-down", 43 ADDRESS_BAR_NAVIGATED: "address-bar-navigated", 44}; 45 46async function getImportableSites() { 47 const sites = []; 48 49 // Just handle these chromium-based browsers for now 50 for (const browserId of ["chrome", "chromium-edge", "chromium"]) { 51 // Skip if there's no profile data. 52 const migrator = await MigrationUtils.getMigrator(browserId); 53 if (!migrator) { 54 continue; 55 } 56 57 // Check each profile for top sites 58 const dataPath = await migrator.wrappedJSObject._getChromeUserDataPathIfExists(); 59 for (const profile of await migrator.getSourceProfiles()) { 60 let path = OS.Path.join(dataPath, profile.id, "Top Sites"); 61 // Skip if top sites data is missing 62 if (!(await OS.File.exists(path))) { 63 Cu.reportError(`Missing file at ${path}`); 64 continue; 65 } 66 67 try { 68 for (const row of await MigrationUtils.getRowsFromDBWithoutLocks( 69 path, 70 `Importable ${browserId} top sites`, 71 `SELECT url 72 FROM top_sites 73 ORDER BY url_rank` 74 )) { 75 sites.push(row.getString(0)); 76 } 77 } catch (ex) { 78 Cu.reportError( 79 `Failed to get importable top sites from ${browserId} ${ex}` 80 ); 81 } 82 } 83 } 84 return sites; 85} 86 87class AboutWelcomeObserver { 88 constructor() { 89 Services.obs.addObserver(this, "quit-application"); 90 91 this.win = Services.focus.activeWindow; 92 if (!this.win) { 93 return; 94 } 95 96 this.terminateReason = AWTerminate.UNKNOWN; 97 98 this.onWindowClose = () => { 99 this.terminateReason = AWTerminate.WINDOW_CLOSED; 100 }; 101 102 this.onTabClose = () => { 103 this.terminateReason = AWTerminate.TAB_CLOSED; 104 }; 105 106 this.win.addEventListener("TabClose", this.onTabClose, { once: true }); 107 this.win.addEventListener("unload", this.onWindowClose, { once: true }); 108 } 109 110 observe(aSubject, aTopic, aData) { 111 switch (aTopic) { 112 case "quit-application": 113 this.terminateReason = AWTerminate.APP_SHUT_DOWN; 114 break; 115 } 116 } 117 118 // Added for testing 119 get AWTerminate() { 120 return AWTerminate; 121 } 122 123 stop() { 124 log.debug(`Terminate reason is ${this.terminateReason}`); 125 Services.obs.removeObserver(this, "quit-application"); 126 if (!this.win) { 127 return; 128 } 129 this.win.removeEventListener("TabClose", this.onTabClose); 130 this.win.removeEventListener("unload", this.onWindowClose); 131 this.win = null; 132 } 133} 134 135class AboutWelcomeParent extends JSWindowActorParent { 136 constructor() { 137 super(); 138 this.AboutWelcomeObserver = new AboutWelcomeObserver(this); 139 } 140 141 didDestroy() { 142 if (this.AboutWelcomeObserver) { 143 this.AboutWelcomeObserver.stop(); 144 } 145 146 Telemetry.sendTelemetry({ 147 event: "SESSION_END", 148 event_context: { 149 reason: this.AboutWelcomeObserver.terminateReason, 150 page: "about:welcome", 151 }, 152 message_id: this.AWMessageId, 153 id: "ABOUT_WELCOME", 154 }); 155 } 156 157 /** 158 * Handle messages from AboutWelcomeChild.jsm 159 * 160 * @param {string} type 161 * @param {any=} data 162 * @param {Browser} browser 163 * @param {Window} window 164 */ 165 onContentMessage(type, data, browser, window) { 166 log.debug(`Received content event: ${type}`); 167 switch (type) { 168 case "AWPage:SET_WELCOME_MESSAGE_SEEN": 169 this.AWMessageId = data; 170 try { 171 Services.prefs.setBoolPref(DID_SEE_ABOUT_WELCOME_PREF, true); 172 } catch (e) { 173 log.debug(`Fails to set ${DID_SEE_ABOUT_WELCOME_PREF}.`); 174 } 175 break; 176 case "AWPage:SPECIAL_ACTION": 177 SpecialMessageActions.handleAction(data, browser); 178 break; 179 case "AWPage:FXA_METRICS_FLOW_URI": 180 return FxAccounts.config.promiseMetricsFlowURI("aboutwelcome"); 181 case "AWPage:IMPORTABLE_SITES": 182 return getImportableSites(); 183 case "AWPage:TELEMETRY_EVENT": 184 Telemetry.sendTelemetry(data); 185 break; 186 case "AWPage:LOCATION_CHANGED": 187 this.AboutWelcomeObserver.terminateReason = 188 AWTerminate.ADDRESS_BAR_NAVIGATED; 189 break; 190 case "AWPage:WAIT_FOR_MIGRATION_CLOSE": 191 return new Promise(resolve => 192 Services.ww.registerNotification(function observer(subject, topic) { 193 if ( 194 topic === "domwindowclosed" && 195 subject.document.documentURI === 196 "chrome://browser/content/migration/migration.xhtml" 197 ) { 198 Services.ww.unregisterNotification(observer); 199 resolve(); 200 } 201 }) 202 ); 203 default: 204 log.debug(`Unexpected event ${type} was not handled.`); 205 } 206 207 return undefined; 208 } 209 210 /** 211 * @param {{name: string, data?: any}} message 212 * @override 213 */ 214 receiveMessage(message) { 215 const { name, data } = message; 216 let browser; 217 let window; 218 219 if (this.manager.rootFrameLoader) { 220 browser = this.manager.rootFrameLoader.ownerElement; 221 window = browser.ownerGlobal; 222 return this.onContentMessage(name, data, browser, window); 223 } 224 225 log.warn(`Not handling ${name} because the browser doesn't exist.`); 226 return null; 227 } 228} 229