Mozilla Cross-Reference mozilla-central
mozilla/ browser/ base/ content/ browser-syncui.js
Hg Log
Hg Blame
Diff file
Raw file
view using tree:
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 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
6 
7 #ifdef MOZ_SERVICES_CLOUDSYNC
8 XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
9                                   "resource://gre/modules/CloudSync.jsm");
10 #else
11 let CloudSync = null;
12 #endif
13 
14 XPCOMUtils.defineLazyModuleGetter(this, "ReadingListScheduler",
15                                   "resource:///modules/readinglist/Scheduler.jsm");
16 
17 // gSyncUI handles updating the tools menu and displaying notifications.
18 let gSyncUI = {
19   _obs: ["weave:service:sync:start",
20          "weave:service:sync:finish",
21          "weave:service:sync:error",
22          "weave:service:quota:remaining",
23          "weave:service:setup-complete",
24          "weave:service:login:start",
25          "weave:service:login:finish",
26          "weave:service:login:error",
27          "weave:service:logout:finish",
28          "weave:service:start-over",
29          "weave:service:start-over:finish",
30          "weave:ui:login:error",
31          "weave:ui:sync:error",
32          "weave:ui:sync:finish",
33          "weave:ui:clear-error",
34 
35          "readinglist:sync:start",
36          "readinglist:sync:finish",
37          "readinglist:sync:error",
38   ],
39 
40   _unloaded: false,
41   // The number of "active" syncs - while this is non-zero, our button will spin
42   _numActiveSyncTasks: 0,
43 
44   init: function () {
45     Cu.import("resource://services-common/stringbundle.js");
46 
47     // Proceed to set up the UI if Sync has already started up.
48     // Otherwise we'll do it when Sync is firing up.
49     let xps = Components.classes["@mozilla.org/weave/service;1"]
50                                 .getService(Components.interfaces.nsISupports)
51                                 .wrappedJSObject;
52     if (xps.ready) {
53       this.initUI();
54       return;
55     }
56 
57     Services.obs.addObserver(this, "weave:service:ready", true);
58 
59     // Remove the observer if the window is closed before the observer
60     // was triggered.
61     window.addEventListener("unload", function onUnload() {
62       gSyncUI._unloaded = true;
63       window.removeEventListener("unload", onUnload, false);
64       Services.obs.removeObserver(gSyncUI, "weave:service:ready");
65 
66       if (Weave.Status.ready) {
67         gSyncUI._obs.forEach(function(topic) {
68           Services.obs.removeObserver(gSyncUI, topic);
69         });
70       }
71     }, false);
72   },
73 
74   initUI: function SUI_initUI() {
75     // If this is a browser window?
76     if (gBrowser) {
77       this._obs.push("weave:notification:added");
78     }
79 
80     this._obs.forEach(function(topic) {
81       Services.obs.addObserver(this, topic, true);
82     }, this);
83 
84     if (gBrowser && Weave.Notifications.notifications.length) {
85       this.initNotifications();
86     }
87     this.updateUI();
88   },
89 
90   initNotifications: function SUI_initNotifications() {
91     const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
92     let notificationbox = document.createElementNS(XULNS, "notificationbox");
93     notificationbox.id = "sync-notifications";
94     notificationbox.setAttribute("flex", "1");
95 
96     let bottombox = document.getElementById("browser-bottombox");
97     bottombox.insertBefore(notificationbox, bottombox.firstChild);
98 
99     // Force a style flush to ensure that our binding is attached.
100     notificationbox.clientTop;
101 
102     // notificationbox will listen to observers from now on.
103     Services.obs.removeObserver(this, "weave:notification:added");
104     let idx = this._obs.indexOf("weave:notification:added");
105     if (idx >= 0) {
106       this._obs.splice(idx, 1);
107     }
108   },
109 
110   _needsSetup() {
111     // We want to treat "account needs verification" as "needs setup". So
112     // "reach in" to Weave.Status._authManager to check whether we the signed-in
113     // user is verified.
114     // Referencing Weave.Status spins a nested event loop to initialize the
115     // authManager, so this should always return a value directly.
116     // This only applies to fxAccounts-based Sync.
117     if (Weave.Status._authManager._signedInUser !== undefined) {
118       // So we are using Firefox accounts - in this world, checking Sync isn't
119       // enough as reading list may be configured but not Sync.
120       // We consider ourselves setup if we have a verified user.
121       // XXX - later we should consider checking preferences to ensure at least
122       // one engine is enabled?
123       return !Weave.Status._authManager._signedInUser ||
124              !Weave.Status._authManager._signedInUser.verified;
125     }
126 
127     // So we are using legacy sync, and reading-list isn't supported for such
128     // users, so check sync itself.
129     let firstSync = "";
130     try {
131       firstSync = Services.prefs.getCharPref("services.sync.firstSync");
132     } catch (e) { }
133 
134     return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
135            firstSync == "notReady";
136   },
137 
138   _loginFailed: function () {
139     return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED ||
140            ReadingListScheduler.state == ReadingListScheduler.STATE_ERROR_AUTHENTICATION;
141   },
142 
143   updateUI: function SUI_updateUI() {
144     let needsSetup = this._needsSetup();
145     let loginFailed = this._loginFailed();
146 
147     // Start off with a clean slate
148     document.getElementById("sync-reauth-state").hidden = true;
149     document.getElementById("sync-setup-state").hidden = true;
150     document.getElementById("sync-syncnow-state").hidden = true;
151 
152     if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
153       document.getElementById("sync-syncnow-state").hidden = false;
154     } else if (loginFailed) {
155       document.getElementById("sync-reauth-state").hidden = false;
156       this.showLoginError();
157     } else if (needsSetup) {
158       document.getElementById("sync-setup-state").hidden = false;
159     } else {
160       document.getElementById("sync-syncnow-state").hidden = false;
161     }
162 
163     if (!gBrowser)
164       return;
165 
166     let syncButton = document.getElementById("sync-button");
167     if (needsSetup && syncButton)
168       syncButton.removeAttribute("tooltiptext");
169 
170     this._updateLastSyncTime();
171   },
172 
173 
174   // Functions called by observers
175   onActivityStart() {
176     if (!gBrowser)
177       return;
178 
179     this.log.debug("onActivityStart with numActive", this._numActiveSyncTasks);
180     if (++this._numActiveSyncTasks == 1) {
181       let button = document.getElementById("sync-button");
182       if (button) {
183         button.setAttribute("status", "active");
184       }
185       button = document.getElementById("PanelUI-fxa-status");
186       if (button) {
187         button.setAttribute("syncstatus", "active");
188       }
189     }
190   },
191 
192   onActivityStop() {
193     if (!gBrowser)
194       return;
195     this.log.debug("onActivityStop with numActive", this._numActiveSyncTasks);
196     if (--this._numActiveSyncTasks) {
197       if (this._numActiveSyncTasks < 0) {
198         // This isn't particularly useful (it seems more likely we'll set a
199         // "start" without a "stop" meaning it forever remains > 0) but it
200         // might offer some value...
201         this.log.error("mismatched onActivityStart/Stop calls",
202                        new Error("active=" + this._numActiveSyncTasks));
203       }
204       return; // active tasks are still ongoing...
205     }
206 
207     let syncButton = document.getElementById("sync-button");
208     if (syncButton) {
209       syncButton.removeAttribute("status");
210     }
211     let panelHorizontalButton = document.getElementById("PanelUI-fxa-status");
212     if (panelHorizontalButton) {
213       panelHorizontalButton.removeAttribute("syncstatus");
214     }
215   },
216 
217   onLoginFinish: function SUI_onLoginFinish() {
218     // Clear out any login failure notifications
219     let title = this._stringBundle.GetStringFromName("error.login.title");
220     this.clearError(title);
221   },
222 
223   onSetupComplete: function SUI_onSetupComplete() {
224     this.onLoginFinish();
225   },
226 
227   onLoginError: function SUI_onLoginError() {
228     // Note: This is used for *both* Sync and ReadingList login errors.
229     // if login fails, any other notifications are essentially moot
230     Weave.Notifications.removeAll();
231 
232     // if we haven't set up the client, don't show errors
233     if (this._needsSetup()) {
234       this.updateUI();
235       return;
236     }
237     // if we are still waiting for the identity manager to initialize, don't show errors
238     if (Weave.Status.login == Weave.LOGIN_FAILED_NOT_READY) {
239       this.updateUI();
240       return;
241     }
242     this.showLoginError();
243     this.updateUI();
244   },
245 
246   showLoginError() {
247     // Note: This is used for *both* Sync and ReadingList login errors.
248     let title = this._stringBundle.GetStringFromName("error.login.title");
249 
250     let description;
251     if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE ||
252         this.isProlongedReadingListError()) {
253       this.log.debug("showLoginError has a prolonged login error");
254       // Convert to days
255       let lastSync =
256         Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
257       description =
258         this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
259     } else {
260       let reason = Weave.Utils.getErrorString(Weave.Status.login);
261       description =
262         this._stringBundle.formatStringFromName("error.sync.description", [reason], 1);
263       this.log.debug("showLoginError has a non-prolonged error", reason);
264     }
265 
266     let buttons = [];
267     buttons.push(new Weave.NotificationButton(
268       this._stringBundle.GetStringFromName("error.login.prefs.label"),
269       this._stringBundle.GetStringFromName("error.login.prefs.accesskey"),
270       function() { gSyncUI.openPrefs(); return true; }
271     ));
272 
273     let notification = new Weave.Notification(title, description, null,
274                                               Weave.Notifications.PRIORITY_WARNING, buttons);
275     Weave.Notifications.replaceTitle(notification);
276   },
277 
278   onLogout: function SUI_onLogout() {
279     this.updateUI();
280   },
281 
282   onStartOver: function SUI_onStartOver() {
283     this.clearError();
284   },
285 
286   onQuotaNotice: function onQuotaNotice(subject, data) {
287     let title = this._stringBundle.GetStringFromName("warning.sync.quota.label");
288     let description = this._stringBundle.GetStringFromName("warning.sync.quota.description");
289     let buttons = [];
290     buttons.push(new Weave.NotificationButton(
291       this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
292       this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.accesskey"),
293       function() { gSyncUI.openQuotaDialog(); return true; }
294     ));
295 
296     let notification = new Weave.Notification(
297       title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons);
298     Weave.Notifications.replaceTitle(notification);
299   },
300 
301   _getAppName: function () {
302     let brand = new StringBundle("chrome://branding/locale/brand.properties");
303     return brand.get("brandShortName");
304   },
305 
306   openServerStatus: function () {
307     let statusURL = Services.prefs.getCharPref("services.sync.statusURL");
308     window.openUILinkIn(statusURL, "tab");
309   },
310 
311   // Commands
312   doSync: function SUI_doSync() {
313     let needsSetup = this._needsSetup();
314 
315     if (!needsSetup) {
316       setTimeout(function () Weave.Service.errorHandler.syncAndReportErrors(), 0);
317     }
318 
319     Services.obs.notifyObservers(null, "cloudsync:user-sync", null);
320     Services.obs.notifyObservers(null, "readinglist:user-sync", null);
321   },
322 
323   handleToolbarButton: function SUI_handleStatusbarButton() {
324     if (this._needsSetup())
325       this.openSetup();
326     else
327       this.doSync();
328   },
329 
330   //XXXzpao should be part of syncCommon.js - which we might want to make a module...
331   //        To be fixed in a followup (bug 583366)
332 
333   /**
334    * Invoke the Sync setup wizard.
335    *
336    * @param wizardType
337    *        Indicates type of wizard to launch:
338    *          null    -- regular set up wizard
339    *          "pair"  -- pair a device first
340    *          "reset" -- reset sync
341    * @param entryPoint
342    *        Indicates the entrypoint from where this method was called.
343    */
344 
345   openSetup: function SUI_openSetup(wizardType, entryPoint = "syncbutton") {
346     let xps = Components.classes["@mozilla.org/weave/service;1"]
347                                 .getService(Components.interfaces.nsISupports)
348                                 .wrappedJSObject;
349     if (xps.fxAccountsEnabled) {
350       fxAccounts.getSignedInUser().then(userData => {
351         if (userData) {
352           this.openPrefs();
353         } else {
354           // If the user is also in an uitour, set the entrypoint to `uitour`
355           if (UITour.tourBrowsersByWindow.get(window) &&
356               UITour.tourBrowsersByWindow.get(window).has(gBrowser.selectedBrowser)) {
357             entryPoint = "uitour";
358           }
359           switchToTabHavingURI("about:accounts?entrypoint=" + entryPoint, true, {
360             replaceQueryString: true
361           });
362         }
363       });
364     } else {
365       let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
366       if (win)
367         win.focus();
368       else {
369         window.openDialog("chrome://browser/content/sync/setup.xul",
370                           "weaveSetup", "centerscreen,chrome,resizable=no",
371                           wizardType);
372       }
373     }
374   },
375 
376   openAddDevice: function () {
377     if (!Weave.Utils.ensureMPUnlocked())
378       return;
379 
380     let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
381     if (win)
382       win.focus();
383     else
384       window.openDialog("chrome://browser/content/sync/addDevice.xul",
385                         "syncAddDevice", "centerscreen,chrome,resizable=no");
386   },
387 
388   openQuotaDialog: function SUI_openQuotaDialog() {
389     let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
390     if (win)
391       win.focus();
392     else
393       Services.ww.activeWindow.openDialog(
394         "chrome://browser/content/sync/quota.xul", "",
395         "centerscreen,chrome,dialog,modal");
396   },
397 
398   openPrefs: function SUI_openPrefs() {
399     openPreferences("paneSync");
400   },
401 
402   openSignInAgainPage: function (entryPoint = "syncbutton") {
403     gFxAccounts.openSignInAgainPage(entryPoint);
404   },
405 
406   // Helpers
407   _updateLastSyncTime: function SUI__updateLastSyncTime() {
408     if (!gBrowser)
409       return;
410 
411     let syncButton = document.getElementById("sync-button");
412     if (!syncButton)
413       return;
414 
415     let lastSync;
416     try {
417       lastSync = new Date(Services.prefs.getCharPref("services.sync.lastSync"));
418     }
419     catch (e) { };
420     // and reading-list time - we want whatever one is the most recent.
421     try {
422       let lastRLSync = new Date(Services.prefs.getCharPref("readinglist.scheduler.lastSync"));
423       if (!lastSync || lastRLSync > lastSync) {
424         lastSync = lastRLSync;
425       }
426     }
427     catch (e) { };
428     if (!lastSync || this._needsSetup()) {
429       syncButton.removeAttribute("tooltiptext");
430       return;
431     }
432 
433     // Show the day-of-week and time (HH:MM) of last sync
434     let lastSyncDateString = lastSync.toLocaleFormat("%a %H:%M");
435     let lastSyncLabel =
436       this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
437 
438     syncButton.setAttribute("tooltiptext", lastSyncLabel);
439   },
440 
441   clearError: function SUI_clearError(errorString) {
442     Weave.Notifications.removeAll(errorString);
443     this.updateUI();
444   },
445 
446   onSyncFinish: function SUI_onSyncFinish() {
447     let title = this._stringBundle.GetStringFromName("error.sync.title");
448 
449     // Clear out sync failures on a successful sync
450     this.clearError(title);
451   },
452 
453   // Return true if the reading-list is in a "prolonged" error state. That
454   // engine doesn't impose what that means, so calculate it here. For
455   // consistency, we just use the sync prefs.
456   isProlongedReadingListError() {
457     let lastSync, threshold, prolonged;
458     try {
459       lastSync = new Date(Services.prefs.getCharPref("readinglist.scheduler.lastSync"));
460       threshold = new Date(Date.now() - Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") * 1000);
461       prolonged = lastSync <= threshold;
462     } catch (ex) {
463       // no pref, assume not prolonged.
464       prolonged = false;
465     }
466     this.log.debug("isProlongedReadingListError has last successful sync at ${lastSync}, threshold is ${threshold}, prolonged=${prolonged}",
467                    {lastSync, threshold, prolonged});
468     return prolonged;
469   },
470 
471   onRLSyncError() {
472     // Like onSyncError, but from the reading-list engine.
473     // However, the current UX around Sync is that error notifications should
474     // generally *not* be seen as they typically aren't actionable - so only
475     // authentication errors (which require user action) and "prolonged" errors
476     // (which technically aren't actionable, but user really should know anyway)
477     // are shown.
478     this.log.debug("onRLSyncError with readingList state", ReadingListScheduler.state);
479     if (ReadingListScheduler.state == ReadingListScheduler.STATE_ERROR_AUTHENTICATION) {
480       this.onLoginError();
481       return;
482     }
483     // If it's not prolonged there's nothing to do.
484     if (!this.isProlongedReadingListError()) {
485       this.log.debug("onRLSyncError has a non-authentication, non-prolonged error, so not showing any error UI");
486       return;
487     }
488     // So it's a prolonged error.
489     // Unfortunate duplication from below...
490     this.log.debug("onRLSyncError has a prolonged error");
491     let title = this._stringBundle.GetStringFromName("error.sync.title");
492     // XXX - this is somewhat wrong - we are reporting the threshold we consider
493     // to be prolonged, not how long it actually has been. (ie, lastSync below
494     // is effectively constant) - bit it too is copied from below.
495     let lastSync =
496       Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
497     let description =
498       this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
499     let priority = Weave.Notifications.PRIORITY_INFO;
500     let buttons = [
501       new Weave.NotificationButton(
502         this._stringBundle.GetStringFromName("error.sync.tryAgainButton.label"),
503         this._stringBundle.GetStringFromName("error.sync.tryAgainButton.accesskey"),
504         function() { gSyncUI.doSync(); return true; }
505       ),
506     ];
507     let notification =
508       new Weave.Notification(title, description, null, priority, buttons);
509     Weave.Notifications.replaceTitle(notification);
510 
511     this.updateUI();
512   },
513 
514   onSyncError: function SUI_onSyncError() {
515     this.log.debug("onSyncError");
516     let title = this._stringBundle.GetStringFromName("error.sync.title");
517 
518     if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
519       this.onLoginError();
520       return;
521     }
522 
523     let description;
524     if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
525       // Convert to days
526       let lastSync =
527         Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
528       description =
529         this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
530     } else {
531       let error = Weave.Utils.getErrorString(Weave.Status.sync);
532       description =
533         this._stringBundle.formatStringFromName("error.sync.description", [error], 1);
534     }
535     let priority = Weave.Notifications.PRIORITY_WARNING;
536     let buttons = [];
537 
538     // Check if the client is outdated in some way (but note: we've never in the
539     // past, and probably never will, bump the relevent version numbers, so
540     // this is effectively dead code!)
541     let outdated = Weave.Status.sync == Weave.VERSION_OUT_OF_DATE;
542     for (let [engine, reason] in Iterator(Weave.Status.engines))
543       outdated = outdated || reason == Weave.VERSION_OUT_OF_DATE;
544 
545     if (outdated) {
546       description = this._stringBundle.GetStringFromName(
547         "error.sync.needUpdate.description");
548       buttons.push(new Weave.NotificationButton(
549         this._stringBundle.GetStringFromName("error.sync.needUpdate.label"),
550         this._stringBundle.GetStringFromName("error.sync.needUpdate.accesskey"),
551         function() { window.openUILinkIn("https://services.mozilla.com/update/", "tab"); return true; }
552       ));
553     }
554     else if (Weave.Status.sync == Weave.OVER_QUOTA) {
555       description = this._stringBundle.GetStringFromName(
556         "error.sync.quota.description");
557       buttons.push(new Weave.NotificationButton(
558         this._stringBundle.GetStringFromName(
559           "error.sync.viewQuotaButton.label"),
560         this._stringBundle.GetStringFromName(
561           "error.sync.viewQuotaButton.accesskey"),
562         function() { gSyncUI.openQuotaDialog(); return true; } )
563       );
564     }
565     else if (Weave.Status.enforceBackoff) {
566       priority = Weave.Notifications.PRIORITY_INFO;
567       buttons.push(new Weave.NotificationButton(
568         this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
569         this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
570         function() { gSyncUI.openServerStatus(); return true; }
571       ));
572     }
573     else {
574       priority = Weave.Notifications.PRIORITY_INFO;
575       buttons.push(new Weave.NotificationButton(
576         this._stringBundle.GetStringFromName("error.sync.tryAgainButton.label"),
577         this._stringBundle.GetStringFromName("error.sync.tryAgainButton.accesskey"),
578         function() { gSyncUI.doSync(); return true; }
579       ));
580     }
581 
582     let notification =
583       new Weave.Notification(title, description, null, priority, buttons);
584     Weave.Notifications.replaceTitle(notification);
585 
586     this.updateUI();
587   },
588 
589   observe: function SUI_observe(subject, topic, data) {
590     this.log.debug("observed", topic);
591     if (this._unloaded) {
592       Cu.reportError("SyncUI observer called after unload: " + topic);
593       return;
594     }
595 
596     // Unwrap, just like Svc.Obs, but without pulling in that dependency.
597     if (subject && typeof subject == "object" &&
598         ("wrappedJSObject" in subject) &&
599         ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) {
600       subject = subject.wrappedJSObject.object;
601     }
602 
603     // First handle "activity" only.
604     switch (topic) {
605       case "weave:service:sync:start":
606       case "weave:service:login:start":
607       case "readinglist:sync:start":
608         this.onActivityStart();
609         break;
610       case "weave:service:sync:finish":
611       case "weave:service:sync:error":
612       case "weave:service:login:finish":
613       case "weave:service:login:error":
614       case "readinglist:sync:finish":
615       case "readinglist:sync:error":
616         this.onActivityStop();
617         break;
618     }
619     // Now non-activity state (eg, enabled, errors, etc)
620     // Note that sync uses the ":ui:" notifications for errors because sync.
621     // ReadingList has no such concept (yet?; hopefully the :error is enough!)
622     switch (topic) {
623       case "weave:ui:sync:finish":
624         this.onSyncFinish();
625         break;
626       case "weave:ui:sync:error":
627         this.onSyncError();
628         break;
629       case "weave:service:quota:remaining":
630         this.onQuotaNotice();
631         break;
632       case "weave:service:setup-complete":
633         this.onSetupComplete();
634         break;
635       case "weave:service:login:finish":
636         this.onLoginFinish();
637         break;
638       case "weave:ui:login:error":
639         this.onLoginError();
640         break;
641       case "weave:service:logout:finish":
642         this.onLogout();
643         break;
644       case "weave:service:start-over":
645         this.onStartOver();
646         break;
647       case "weave:service:start-over:finish":
648         this.updateUI();
649         break;
650       case "weave:service:ready":
651         this.initUI();
652         break;
653       case "weave:notification:added":
654         this.initNotifications();
655         break;
656       case "weave:ui:clear-error":
657         this.clearError();
658         break;
659 
660       case "readinglist:sync:error":
661         this.onRLSyncError();
662         break;
663       case "readinglist:sync:finish":
664         this.clearError();
665         break;
666     }
667   },
668 
669   QueryInterface: XPCOMUtils.generateQI([
670     Ci.nsIObserver,
671     Ci.nsISupportsWeakReference
672   ])
673 };
674 
675 XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
676   //XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
677   //        but for now just make it work
678   return Cc["@mozilla.org/intl/stringbundle;1"].
679          getService(Ci.nsIStringBundleService).
680          createBundle("chrome://weave/locale/services/sync.properties");
681 });
682 
683 XPCOMUtils.defineLazyGetter(gSyncUI, "log", function() {
684   return Log.repository.getLogger("browserwindow.syncui");
685 });
686 
view http://hg.mozilla.org/mozilla-central/rev/ /browser/base/content/browser-syncui.js