|
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 // gSyncUI handles updating the tools menu and displaying notifications. |
|
6 let gSyncUI = { |
|
7 DEFAULT_EOL_URL: "https://www.mozilla.org/firefox/?utm_source=synceol", |
|
8 |
|
9 _obs: ["weave:service:sync:start", |
|
10 "weave:service:quota:remaining", |
|
11 "weave:service:setup-complete", |
|
12 "weave:service:login:start", |
|
13 "weave:service:login:finish", |
|
14 "weave:service:logout:finish", |
|
15 "weave:service:start-over", |
|
16 "weave:service:start-over:finish", |
|
17 "weave:ui:login:error", |
|
18 "weave:ui:sync:error", |
|
19 "weave:ui:sync:finish", |
|
20 "weave:ui:clear-error", |
|
21 "weave:eol", |
|
22 ], |
|
23 |
|
24 _unloaded: false, |
|
25 |
|
26 init: function () { |
|
27 Cu.import("resource://services-common/stringbundle.js"); |
|
28 |
|
29 // Proceed to set up the UI if Sync has already started up. |
|
30 // Otherwise we'll do it when Sync is firing up. |
|
31 let xps = Components.classes["@mozilla.org/weave/service;1"] |
|
32 .getService(Components.interfaces.nsISupports) |
|
33 .wrappedJSObject; |
|
34 if (xps.ready) { |
|
35 this.initUI(); |
|
36 return; |
|
37 } |
|
38 |
|
39 Services.obs.addObserver(this, "weave:service:ready", true); |
|
40 |
|
41 // Remove the observer if the window is closed before the observer |
|
42 // was triggered. |
|
43 window.addEventListener("unload", function onUnload() { |
|
44 gSyncUI._unloaded = true; |
|
45 window.removeEventListener("unload", onUnload, false); |
|
46 Services.obs.removeObserver(gSyncUI, "weave:service:ready"); |
|
47 |
|
48 if (Weave.Status.ready) { |
|
49 gSyncUI._obs.forEach(function(topic) { |
|
50 Services.obs.removeObserver(gSyncUI, topic); |
|
51 }); |
|
52 } |
|
53 }, false); |
|
54 }, |
|
55 |
|
56 initUI: function SUI_initUI() { |
|
57 // If this is a browser window? |
|
58 if (gBrowser) { |
|
59 this._obs.push("weave:notification:added"); |
|
60 } |
|
61 |
|
62 this._obs.forEach(function(topic) { |
|
63 Services.obs.addObserver(this, topic, true); |
|
64 }, this); |
|
65 |
|
66 if (gBrowser && Weave.Notifications.notifications.length) { |
|
67 this.initNotifications(); |
|
68 } |
|
69 this.updateUI(); |
|
70 }, |
|
71 |
|
72 initNotifications: function SUI_initNotifications() { |
|
73 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
|
74 let notificationbox = document.createElementNS(XULNS, "notificationbox"); |
|
75 notificationbox.id = "sync-notifications"; |
|
76 notificationbox.setAttribute("flex", "1"); |
|
77 |
|
78 let bottombox = document.getElementById("browser-bottombox"); |
|
79 bottombox.insertBefore(notificationbox, bottombox.firstChild); |
|
80 |
|
81 // Force a style flush to ensure that our binding is attached. |
|
82 notificationbox.clientTop; |
|
83 |
|
84 // notificationbox will listen to observers from now on. |
|
85 Services.obs.removeObserver(this, "weave:notification:added"); |
|
86 }, |
|
87 |
|
88 _needsSetup: function SUI__needsSetup() { |
|
89 // We want to treat "account needs verification" as "needs setup". So |
|
90 // "reach in" to Weave.Status._authManager to check whether we the signed-in |
|
91 // user is verified. |
|
92 // Referencing Weave.Status spins a nested event loop to initialize the |
|
93 // authManager, so this should always return a value directly. |
|
94 // This only applies to fxAccounts-based Sync. |
|
95 if (Weave.Status._authManager._signedInUser) { |
|
96 // If we have a signed in user already, and that user is not verified, |
|
97 // revert to the "needs setup" state. |
|
98 if (!Weave.Status._authManager._signedInUser.verified) { |
|
99 return true; |
|
100 } |
|
101 } |
|
102 |
|
103 let firstSync = ""; |
|
104 try { |
|
105 firstSync = Services.prefs.getCharPref("services.sync.firstSync"); |
|
106 } catch (e) { } |
|
107 |
|
108 return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED || |
|
109 firstSync == "notReady"; |
|
110 }, |
|
111 |
|
112 _loginFailed: function () { |
|
113 return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED; |
|
114 }, |
|
115 |
|
116 updateUI: function SUI_updateUI() { |
|
117 let needsSetup = this._needsSetup(); |
|
118 let loginFailed = this._loginFailed(); |
|
119 |
|
120 // Start off with a clean slate |
|
121 document.getElementById("sync-reauth-state").hidden = true; |
|
122 document.getElementById("sync-setup-state").hidden = true; |
|
123 document.getElementById("sync-syncnow-state").hidden = true; |
|
124 |
|
125 if (loginFailed) { |
|
126 document.getElementById("sync-reauth-state").hidden = false; |
|
127 } else if (needsSetup) { |
|
128 document.getElementById("sync-setup-state").hidden = false; |
|
129 } else { |
|
130 document.getElementById("sync-syncnow-state").hidden = false; |
|
131 } |
|
132 |
|
133 if (!gBrowser) |
|
134 return; |
|
135 |
|
136 let syncButton = document.getElementById("sync-button"); |
|
137 let panelHorizontalButton = document.getElementById("PanelUI-fxa-status"); |
|
138 [syncButton, panelHorizontalButton].forEach(function(button) { |
|
139 if (!button) |
|
140 return; |
|
141 button.removeAttribute("status"); |
|
142 }); |
|
143 |
|
144 if (needsSetup && syncButton) |
|
145 syncButton.removeAttribute("tooltiptext"); |
|
146 |
|
147 this._updateLastSyncTime(); |
|
148 }, |
|
149 |
|
150 |
|
151 // Functions called by observers |
|
152 onActivityStart: function SUI_onActivityStart() { |
|
153 if (!gBrowser) |
|
154 return; |
|
155 |
|
156 ["sync-button", "PanelUI-fxa-status"].forEach(function(id) { |
|
157 let button = document.getElementById(id); |
|
158 if (!button) |
|
159 return; |
|
160 button.setAttribute("status", "active"); |
|
161 }); |
|
162 }, |
|
163 |
|
164 onLoginFinish: function SUI_onLoginFinish() { |
|
165 // Clear out any login failure notifications |
|
166 let title = this._stringBundle.GetStringFromName("error.login.title"); |
|
167 this.clearError(title); |
|
168 }, |
|
169 |
|
170 onSetupComplete: function SUI_onSetupComplete() { |
|
171 this.onLoginFinish(); |
|
172 }, |
|
173 |
|
174 onLoginError: function SUI_onLoginError() { |
|
175 // if login fails, any other notifications are essentially moot |
|
176 Weave.Notifications.removeAll(); |
|
177 |
|
178 // if we haven't set up the client, don't show errors |
|
179 if (this._needsSetup()) { |
|
180 this.updateUI(); |
|
181 return; |
|
182 } |
|
183 // if we are still waiting for the identity manager to initialize, don't show errors |
|
184 if (Weave.Status.login == Weave.LOGIN_FAILED_NOT_READY) { |
|
185 this.updateUI(); |
|
186 return; |
|
187 } |
|
188 |
|
189 let title = this._stringBundle.GetStringFromName("error.login.title"); |
|
190 |
|
191 let description; |
|
192 if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) { |
|
193 // Convert to days |
|
194 let lastSync = |
|
195 Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400; |
|
196 description = |
|
197 this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1); |
|
198 } else { |
|
199 let reason = Weave.Utils.getErrorString(Weave.Status.login); |
|
200 description = |
|
201 this._stringBundle.formatStringFromName("error.sync.description", [reason], 1); |
|
202 } |
|
203 |
|
204 let buttons = []; |
|
205 buttons.push(new Weave.NotificationButton( |
|
206 this._stringBundle.GetStringFromName("error.login.prefs.label"), |
|
207 this._stringBundle.GetStringFromName("error.login.prefs.accesskey"), |
|
208 function() { gSyncUI.openPrefs(); return true; } |
|
209 )); |
|
210 |
|
211 let notification = new Weave.Notification(title, description, null, |
|
212 Weave.Notifications.PRIORITY_WARNING, buttons); |
|
213 Weave.Notifications.replaceTitle(notification); |
|
214 this.updateUI(); |
|
215 }, |
|
216 |
|
217 onLogout: function SUI_onLogout() { |
|
218 this.updateUI(); |
|
219 }, |
|
220 |
|
221 onStartOver: function SUI_onStartOver() { |
|
222 this.clearError(); |
|
223 }, |
|
224 |
|
225 onQuotaNotice: function onQuotaNotice(subject, data) { |
|
226 let title = this._stringBundle.GetStringFromName("warning.sync.quota.label"); |
|
227 let description = this._stringBundle.GetStringFromName("warning.sync.quota.description"); |
|
228 let buttons = []; |
|
229 buttons.push(new Weave.NotificationButton( |
|
230 this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"), |
|
231 this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.accesskey"), |
|
232 function() { gSyncUI.openQuotaDialog(); return true; } |
|
233 )); |
|
234 |
|
235 let notification = new Weave.Notification( |
|
236 title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons); |
|
237 Weave.Notifications.replaceTitle(notification); |
|
238 }, |
|
239 |
|
240 _getAppName: function () { |
|
241 let brand = new StringBundle("chrome://branding/locale/brand.properties"); |
|
242 return brand.get("brandShortName"); |
|
243 }, |
|
244 |
|
245 onEOLNotice: function (data) { |
|
246 let code = data.code; |
|
247 let kind = (code == "hard-eol") ? "error" : "warning"; |
|
248 let url = data.url || gSyncUI.DEFAULT_EOL_URL; |
|
249 |
|
250 let title = this._stringBundle.GetStringFromName(kind + ".sync.eol.label"); |
|
251 let description = this._stringBundle.formatStringFromName(kind + ".sync.eol.description", |
|
252 [this._getAppName()], |
|
253 1); |
|
254 |
|
255 let buttons = []; |
|
256 buttons.push(new Weave.NotificationButton( |
|
257 this._stringBundle.GetStringFromName("sync.eol.learnMore.label"), |
|
258 this._stringBundle.GetStringFromName("sync.eol.learnMore.accesskey"), |
|
259 function() { |
|
260 window.openUILinkIn(url, "tab"); |
|
261 return true; |
|
262 } |
|
263 )); |
|
264 |
|
265 let priority = (kind == "error") ? Weave.Notifications.PRIORITY_WARNING : |
|
266 Weave.Notifications.PRIORITY_INFO; |
|
267 let notification = new Weave.Notification(title, description, null, priority, buttons); |
|
268 Weave.Notifications.replaceTitle(notification); |
|
269 }, |
|
270 |
|
271 openServerStatus: function () { |
|
272 let statusURL = Services.prefs.getCharPref("services.sync.statusURL"); |
|
273 window.openUILinkIn(statusURL, "tab"); |
|
274 }, |
|
275 |
|
276 // Commands |
|
277 doSync: function SUI_doSync() { |
|
278 setTimeout(function() Weave.Service.errorHandler.syncAndReportErrors(), 0); |
|
279 }, |
|
280 |
|
281 handleToolbarButton: function SUI_handleStatusbarButton() { |
|
282 if (this._needsSetup()) |
|
283 this.openSetup(); |
|
284 else |
|
285 this.doSync(); |
|
286 }, |
|
287 |
|
288 //XXXzpao should be part of syncCommon.js - which we might want to make a module... |
|
289 // To be fixed in a followup (bug 583366) |
|
290 |
|
291 /** |
|
292 * Invoke the Sync setup wizard. |
|
293 * |
|
294 * @param wizardType |
|
295 * Indicates type of wizard to launch: |
|
296 * null -- regular set up wizard |
|
297 * "pair" -- pair a device first |
|
298 * "reset" -- reset sync |
|
299 */ |
|
300 |
|
301 openSetup: function SUI_openSetup(wizardType) { |
|
302 let xps = Components.classes["@mozilla.org/weave/service;1"] |
|
303 .getService(Components.interfaces.nsISupports) |
|
304 .wrappedJSObject; |
|
305 if (xps.fxAccountsEnabled) { |
|
306 fxAccounts.getSignedInUser().then(userData => { |
|
307 if (userData) { |
|
308 this.openPrefs(); |
|
309 } else { |
|
310 switchToTabHavingURI("about:accounts", true); |
|
311 } |
|
312 }); |
|
313 } else { |
|
314 let win = Services.wm.getMostRecentWindow("Weave:AccountSetup"); |
|
315 if (win) |
|
316 win.focus(); |
|
317 else { |
|
318 window.openDialog("chrome://browser/content/sync/setup.xul", |
|
319 "weaveSetup", "centerscreen,chrome,resizable=no", |
|
320 wizardType); |
|
321 } |
|
322 } |
|
323 }, |
|
324 |
|
325 openAddDevice: function () { |
|
326 if (!Weave.Utils.ensureMPUnlocked()) |
|
327 return; |
|
328 |
|
329 let win = Services.wm.getMostRecentWindow("Sync:AddDevice"); |
|
330 if (win) |
|
331 win.focus(); |
|
332 else |
|
333 window.openDialog("chrome://browser/content/sync/addDevice.xul", |
|
334 "syncAddDevice", "centerscreen,chrome,resizable=no"); |
|
335 }, |
|
336 |
|
337 openQuotaDialog: function SUI_openQuotaDialog() { |
|
338 let win = Services.wm.getMostRecentWindow("Sync:ViewQuota"); |
|
339 if (win) |
|
340 win.focus(); |
|
341 else |
|
342 Services.ww.activeWindow.openDialog( |
|
343 "chrome://browser/content/sync/quota.xul", "", |
|
344 "centerscreen,chrome,dialog,modal"); |
|
345 }, |
|
346 |
|
347 openPrefs: function SUI_openPrefs() { |
|
348 openPreferences("paneSync"); |
|
349 }, |
|
350 |
|
351 openSignInAgainPage: function () { |
|
352 switchToTabHavingURI("about:accounts?action=reauth", true); |
|
353 }, |
|
354 |
|
355 // Helpers |
|
356 _updateLastSyncTime: function SUI__updateLastSyncTime() { |
|
357 if (!gBrowser) |
|
358 return; |
|
359 |
|
360 let syncButton = document.getElementById("sync-button"); |
|
361 if (!syncButton) |
|
362 return; |
|
363 |
|
364 let lastSync; |
|
365 try { |
|
366 lastSync = Services.prefs.getCharPref("services.sync.lastSync"); |
|
367 } |
|
368 catch (e) { }; |
|
369 if (!lastSync || this._needsSetup()) { |
|
370 syncButton.removeAttribute("tooltiptext"); |
|
371 return; |
|
372 } |
|
373 |
|
374 // Show the day-of-week and time (HH:MM) of last sync |
|
375 let lastSyncDate = new Date(lastSync).toLocaleFormat("%a %H:%M"); |
|
376 let lastSyncLabel = |
|
377 this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDate], 1); |
|
378 |
|
379 syncButton.setAttribute("tooltiptext", lastSyncLabel); |
|
380 }, |
|
381 |
|
382 clearError: function SUI_clearError(errorString) { |
|
383 Weave.Notifications.removeAll(errorString); |
|
384 this.updateUI(); |
|
385 }, |
|
386 |
|
387 onSyncFinish: function SUI_onSyncFinish() { |
|
388 let title = this._stringBundle.GetStringFromName("error.sync.title"); |
|
389 |
|
390 // Clear out sync failures on a successful sync |
|
391 this.clearError(title); |
|
392 }, |
|
393 |
|
394 onSyncError: function SUI_onSyncError() { |
|
395 let title = this._stringBundle.GetStringFromName("error.sync.title"); |
|
396 |
|
397 if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) { |
|
398 this.onLoginError(); |
|
399 return; |
|
400 } |
|
401 |
|
402 let description; |
|
403 if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) { |
|
404 // Convert to days |
|
405 let lastSync = |
|
406 Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400; |
|
407 description = |
|
408 this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1); |
|
409 } else { |
|
410 let error = Weave.Utils.getErrorString(Weave.Status.sync); |
|
411 description = |
|
412 this._stringBundle.formatStringFromName("error.sync.description", [error], 1); |
|
413 } |
|
414 let priority = Weave.Notifications.PRIORITY_WARNING; |
|
415 let buttons = []; |
|
416 |
|
417 // Check if the client is outdated in some way |
|
418 let outdated = Weave.Status.sync == Weave.VERSION_OUT_OF_DATE; |
|
419 for (let [engine, reason] in Iterator(Weave.Status.engines)) |
|
420 outdated = outdated || reason == Weave.VERSION_OUT_OF_DATE; |
|
421 |
|
422 if (outdated) { |
|
423 description = this._stringBundle.GetStringFromName( |
|
424 "error.sync.needUpdate.description"); |
|
425 buttons.push(new Weave.NotificationButton( |
|
426 this._stringBundle.GetStringFromName("error.sync.needUpdate.label"), |
|
427 this._stringBundle.GetStringFromName("error.sync.needUpdate.accesskey"), |
|
428 function() { window.openUILinkIn("https://services.mozilla.com/update/", "tab"); return true; } |
|
429 )); |
|
430 } |
|
431 else if (Weave.Status.sync == Weave.OVER_QUOTA) { |
|
432 description = this._stringBundle.GetStringFromName( |
|
433 "error.sync.quota.description"); |
|
434 buttons.push(new Weave.NotificationButton( |
|
435 this._stringBundle.GetStringFromName( |
|
436 "error.sync.viewQuotaButton.label"), |
|
437 this._stringBundle.GetStringFromName( |
|
438 "error.sync.viewQuotaButton.accesskey"), |
|
439 function() { gSyncUI.openQuotaDialog(); return true; } ) |
|
440 ); |
|
441 } |
|
442 else if (Weave.Status.enforceBackoff) { |
|
443 priority = Weave.Notifications.PRIORITY_INFO; |
|
444 buttons.push(new Weave.NotificationButton( |
|
445 this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"), |
|
446 this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"), |
|
447 function() { gSyncUI.openServerStatus(); return true; } |
|
448 )); |
|
449 } |
|
450 else { |
|
451 priority = Weave.Notifications.PRIORITY_INFO; |
|
452 buttons.push(new Weave.NotificationButton( |
|
453 this._stringBundle.GetStringFromName("error.sync.tryAgainButton.label"), |
|
454 this._stringBundle.GetStringFromName("error.sync.tryAgainButton.accesskey"), |
|
455 function() { gSyncUI.doSync(); return true; } |
|
456 )); |
|
457 } |
|
458 |
|
459 let notification = |
|
460 new Weave.Notification(title, description, null, priority, buttons); |
|
461 Weave.Notifications.replaceTitle(notification); |
|
462 |
|
463 this.updateUI(); |
|
464 }, |
|
465 |
|
466 observe: function SUI_observe(subject, topic, data) { |
|
467 if (this._unloaded) { |
|
468 Cu.reportError("SyncUI observer called after unload: " + topic); |
|
469 return; |
|
470 } |
|
471 |
|
472 // Unwrap, just like Svc.Obs, but without pulling in that dependency. |
|
473 if (subject && typeof subject == "object" && |
|
474 ("wrappedJSObject" in subject) && |
|
475 ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) { |
|
476 subject = subject.wrappedJSObject.object; |
|
477 } |
|
478 |
|
479 switch (topic) { |
|
480 case "weave:service:sync:start": |
|
481 this.onActivityStart(); |
|
482 break; |
|
483 case "weave:ui:sync:finish": |
|
484 this.onSyncFinish(); |
|
485 break; |
|
486 case "weave:ui:sync:error": |
|
487 this.onSyncError(); |
|
488 break; |
|
489 case "weave:service:quota:remaining": |
|
490 this.onQuotaNotice(); |
|
491 break; |
|
492 case "weave:service:setup-complete": |
|
493 this.onSetupComplete(); |
|
494 break; |
|
495 case "weave:service:login:start": |
|
496 this.onActivityStart(); |
|
497 break; |
|
498 case "weave:service:login:finish": |
|
499 this.onLoginFinish(); |
|
500 break; |
|
501 case "weave:ui:login:error": |
|
502 this.onLoginError(); |
|
503 break; |
|
504 case "weave:service:logout:finish": |
|
505 this.onLogout(); |
|
506 break; |
|
507 case "weave:service:start-over": |
|
508 this.onStartOver(); |
|
509 break; |
|
510 case "weave:service:start-over:finish": |
|
511 this.updateUI(); |
|
512 break; |
|
513 case "weave:service:ready": |
|
514 this.initUI(); |
|
515 break; |
|
516 case "weave:notification:added": |
|
517 this.initNotifications(); |
|
518 break; |
|
519 case "weave:ui:clear-error": |
|
520 this.clearError(); |
|
521 break; |
|
522 case "weave:eol": |
|
523 this.onEOLNotice(subject); |
|
524 break; |
|
525 } |
|
526 }, |
|
527 |
|
528 QueryInterface: XPCOMUtils.generateQI([ |
|
529 Ci.nsIObserver, |
|
530 Ci.nsISupportsWeakReference |
|
531 ]) |
|
532 }; |
|
533 |
|
534 XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() { |
|
535 //XXXzpao these strings should probably be moved from /services to /browser... (bug 583381) |
|
536 // but for now just make it work |
|
537 return Cc["@mozilla.org/intl/stringbundle;1"]. |
|
538 getService(Ci.nsIStringBundleService). |
|
539 createBundle("chrome://weave/locale/services/sync.properties"); |
|
540 }); |
|
541 |