Thu, 15 Jan 2015 15:55:04 +0100
Back out 97036ab72558 which inappropriately compared turds to third parties.
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/.
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",
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 ],
24 _unloaded: false,
26 init: function () {
27 Cu.import("resource://services-common/stringbundle.js");
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 }
39 Services.obs.addObserver(this, "weave:service:ready", true);
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");
48 if (Weave.Status.ready) {
49 gSyncUI._obs.forEach(function(topic) {
50 Services.obs.removeObserver(gSyncUI, topic);
51 });
52 }
53 }, false);
54 },
56 initUI: function SUI_initUI() {
57 // If this is a browser window?
58 if (gBrowser) {
59 this._obs.push("weave:notification:added");
60 }
62 this._obs.forEach(function(topic) {
63 Services.obs.addObserver(this, topic, true);
64 }, this);
66 if (gBrowser && Weave.Notifications.notifications.length) {
67 this.initNotifications();
68 }
69 this.updateUI();
70 },
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");
78 let bottombox = document.getElementById("browser-bottombox");
79 bottombox.insertBefore(notificationbox, bottombox.firstChild);
81 // Force a style flush to ensure that our binding is attached.
82 notificationbox.clientTop;
84 // notificationbox will listen to observers from now on.
85 Services.obs.removeObserver(this, "weave:notification:added");
86 },
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 }
103 let firstSync = "";
104 try {
105 firstSync = Services.prefs.getCharPref("services.sync.firstSync");
106 } catch (e) { }
108 return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
109 firstSync == "notReady";
110 },
112 _loginFailed: function () {
113 return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
114 },
116 updateUI: function SUI_updateUI() {
117 let needsSetup = this._needsSetup();
118 let loginFailed = this._loginFailed();
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;
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 }
133 if (!gBrowser)
134 return;
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 });
144 if (needsSetup && syncButton)
145 syncButton.removeAttribute("tooltiptext");
147 this._updateLastSyncTime();
148 },
151 // Functions called by observers
152 onActivityStart: function SUI_onActivityStart() {
153 if (!gBrowser)
154 return;
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 },
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 },
170 onSetupComplete: function SUI_onSetupComplete() {
171 this.onLoginFinish();
172 },
174 onLoginError: function SUI_onLoginError() {
175 // if login fails, any other notifications are essentially moot
176 Weave.Notifications.removeAll();
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 }
189 let title = this._stringBundle.GetStringFromName("error.login.title");
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 }
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 ));
211 let notification = new Weave.Notification(title, description, null,
212 Weave.Notifications.PRIORITY_WARNING, buttons);
213 Weave.Notifications.replaceTitle(notification);
214 this.updateUI();
215 },
217 onLogout: function SUI_onLogout() {
218 this.updateUI();
219 },
221 onStartOver: function SUI_onStartOver() {
222 this.clearError();
223 },
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 ));
235 let notification = new Weave.Notification(
236 title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons);
237 Weave.Notifications.replaceTitle(notification);
238 },
240 _getAppName: function () {
241 let brand = new StringBundle("chrome://branding/locale/brand.properties");
242 return brand.get("brandShortName");
243 },
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;
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);
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 ));
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 },
271 openServerStatus: function () {
272 let statusURL = Services.prefs.getCharPref("services.sync.statusURL");
273 window.openUILinkIn(statusURL, "tab");
274 },
276 // Commands
277 doSync: function SUI_doSync() {
278 setTimeout(function() Weave.Service.errorHandler.syncAndReportErrors(), 0);
279 },
281 handleToolbarButton: function SUI_handleStatusbarButton() {
282 if (this._needsSetup())
283 this.openSetup();
284 else
285 this.doSync();
286 },
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)
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 */
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 },
325 openAddDevice: function () {
326 if (!Weave.Utils.ensureMPUnlocked())
327 return;
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 },
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 },
347 openPrefs: function SUI_openPrefs() {
348 openPreferences("paneSync");
349 },
351 openSignInAgainPage: function () {
352 switchToTabHavingURI("about:accounts?action=reauth", true);
353 },
355 // Helpers
356 _updateLastSyncTime: function SUI__updateLastSyncTime() {
357 if (!gBrowser)
358 return;
360 let syncButton = document.getElementById("sync-button");
361 if (!syncButton)
362 return;
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 }
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);
379 syncButton.setAttribute("tooltiptext", lastSyncLabel);
380 },
382 clearError: function SUI_clearError(errorString) {
383 Weave.Notifications.removeAll(errorString);
384 this.updateUI();
385 },
387 onSyncFinish: function SUI_onSyncFinish() {
388 let title = this._stringBundle.GetStringFromName("error.sync.title");
390 // Clear out sync failures on a successful sync
391 this.clearError(title);
392 },
394 onSyncError: function SUI_onSyncError() {
395 let title = this._stringBundle.GetStringFromName("error.sync.title");
397 if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
398 this.onLoginError();
399 return;
400 }
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 = [];
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;
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 }
459 let notification =
460 new Weave.Notification(title, description, null, priority, buttons);
461 Weave.Notifications.replaceTitle(notification);
463 this.updateUI();
464 },
466 observe: function SUI_observe(subject, topic, data) {
467 if (this._unloaded) {
468 Cu.reportError("SyncUI observer called after unload: " + topic);
469 return;
470 }
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 }
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 },
528 QueryInterface: XPCOMUtils.generateQI([
529 Ci.nsIObserver,
530 Ci.nsISupportsWeakReference
531 ])
532 };
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 });