var EXPORTED_SYMBOLS = ["YOONO_CMPT"];

Components.utils.import("resource://yoono/yoonoKeyValueDB.js");
Components.utils.import("resource://yoono/yoonoLog.js");
Components.utils.import("resource://yoono/yoonoPrefs.js");
Components.utils.import("resource://yoono/yoonoServerConnection.js");
Components.utils.import("resource://yoono/yoonoBkmSync.js");
Components.utils.import("resource://yoono/yoonoStorage.js");
Components.utils.import("resource://yoono/yoonoYEXTIF.js");

const PERMS_FILE      = 0644;
const PERMS_DIRECTORY = 0755;

try {

// Globals
const CI = Components.interfaces;
const CL = Components.classes;
const CONSOLESERVICE = CL["@mozilla.org/consoleservice;1"].getService(CI.nsIConsoleService);
const DIRSERVICE = CL['@mozilla.org/file/directory_service;1'].getService(CI.nsIProperties);
const YOONO_ID = "{d9284e50-81fc-11da-a72b-0800200c9a66}";

// yoono stuff
var yoono={};
var log = {info:function(m) {CONSOLESERVICE.logStringMessage(m);},
	debug:function(m) {CONSOLESERVICE.logStringMessage(m);},
	warn:function(m){CONSOLESERVICE.logStringMessage(m);},
	error:function(m){CONSOLESERVICE.logStringMessage(m);},
	fatal:function(m){CONSOLESERVICE.logStringMessage(m);}};

// List of preferences that must absolutely never be overriden by the server
// In case you need to add such a preference, just set it to true in the object below:
var safePreferences = {
  bookmarksLastModified: true,
  synchroaction: true,
  synchroask : true,
  syncid : true,
  userid : true
}

// interfaces
const PREFSSERVICE = CL["@mozilla.org/preferences-service;1"].getService(CI.nsIPrefService);
const WMED = CL['@mozilla.org/appshell/window-mediator;1'].getService(CI.nsIWindowMediator);

const PREFS = PREFSSERVICE.getBranch("extensions.yoono.");




// chromeUI : le composant en lui meme
function chromeUI() {
  this._started = false;
  this.uninstallDone = false;
  this.version = "7.X.X";
  this.addOns = {};
  
  this.timerLogIn = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer);
  // List of incompatible extensions. Ids MUST be lower case
  this.dualSidebarIncompatibleExtensions = {
    '{097d3191-e6fa-4728-9826-b533d755359d}' : 'All-in-One Sidebar',
    '{0eaf175c-0c46-4932-ab7d-f45d6c46f367}' : 'Ez Sidebar'
  };

  // List of incompatible bkm sync extensions. Ids MUST be lower case
  this.bkmSyncIncompatibleExtensions = {
    '{18b9b035-67a2-4db6-bf42-3993ae4d89a7}' : 'Bookmarks on line',
    '{66580a2f-7538-498d-b049-a540e77aad9d}' : 'AbstractMouse.com Shared Bookmarks',
    '{e133f188-27e7-401d-be2e-804643793acb}' : 'Bookmarks Synchronizer',
    'foxmarks@kei.com' : 'Foxmarks Bookmark Synchronizer',
    '{32537848-7d38-4ee2-b5a2-47562e69c59e}' : 'Foxylicious',
    'browserstate@google.com' : 'Google Browser Sync'
  };

  this.statDelay = 24 * 3600 ; // stats sent every 24 hours

  this.uninstallWhenQuitting = false;
  
  this.DEVICEVM_EXTENSION_ID = '{91c612bf-2a7a-48b8-8c8c-6de28589b7a0}';

  
  // initialisation of the component. (called by yoonoOverlay.js)
  this.init = function (y) {
    
    // ne s'execute qu'une seule fois par session
    if (this._started)
      return;
    this._started = true;
    
  	yoono = y;
  	log = y.log;
 
    // wait 5 seconds for sidebar to be ready before checking for stats
    this.timerLogIn.initWithCallback(this, 5000 , this.timerLogIn.TYPE_ONE_SHOT);
    
    this.startGlobalServices(yoono.yextif);
  	
  	yoono.bkm.start(yoono);
    
  };
  
  /*******************/
  /* Global services */
  this._globalService = null;
  this.getYServices = function () {
    return this._globalService;
  }
  this.startGlobalServices = function (yextif) {
    if (this._globalService)  return;
    
    try {
      log.debug("loading global services with yextif = "+yextif);
      
      // Get *the* hidden window
      var hiddenWindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
          .getService(Components.interfaces.nsIAppShellService)
          .hiddenDOMWindow;
      var doc=hiddenWindow.document;

      // Create our hidden iframe
      var iframe=doc.createElement("iframe");
      iframe.setAttribute("src","chrome://yoonosb/content/js/srv/hiddenIFrame.html");
      doc.lastChild.appendChild(iframe);
      
      
      // Wait for load in order to retrieve global object for YServices
      var _self=this;
      iframe.addEventListener("load",function () {
        try {
          _self._globalService = iframe.contentWindow.loadServices(yextif);
          var timer = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer);
          timer.initWithCallback({ notify : function(){
            _self._globalService.core.require( ["components","remoteAlerting"]);
          } }, 5000 , timer.TYPE_ONE_SHOT);

        } catch(e) {
          log.exception(e);
        }
      }, true);
    
    } catch(e) {
      log.exception(e);
    }
  }
  /* End global services */
  
  
	//Timer to handle timeout on requests
	var requestTimeoutTimer = function(request, handler) {
		this._self = this;
		this.request = request;
		this.handler = handler;
		this.timer = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer);
		var delay = YOONO_PREFS.get('request.timeout') * 1000; // la pref est en secondes
		this.timer.initWithCallback(this, delay, this.timer.TYPE_ONE_SHOT);
	}

	requestTimeoutTimer.prototype = {
		notify : function(timer) {
			try {
				log.debug("Request " + this.request.tstamp + " timed out" );
				this.request.abort();
				// yoono.dialogs.setThrobberFailed();
				if(this.handler)
				  this.handler(this.request, '');
				// cleanup
				try     { this._self.destroy() } catch(e) {log.exception(e);}
				finally { this._self = null }
			} catch(e) {
				log.exception(e);
			}
		}
	}
	// Wrapper method to be used when no headers are to be added to request
	// (historic...)
	// (don't use $this because this function is called without his environnement)
	this.sendRequest = function(url, method, mode, body, handler) {
		method = ('GET' == method)?'GET':'POST';
		return (yoono.main.sendFullRequest(url, method, null, mode, body, handler));
	}

	// NOV 2006 : this is the sending method that must now be used almost throughout the application.
	// @author : Xavier Grosjean
	// @param url : destination url
	// @param method : GET or POST
	// @param headers : Object with headers names and values : headers['name'] = value
	// @param mode : sync or async
	// @param body : post payload. If xml, will be serialized and script= will be inserted at begining
	// @param handler : handle for response (or null). Will receive request and xml result as param
	// @returns array with request and result (xml response payload if not async)
	this.sendFullRequest = function(url, method, headers, mode, body, handler) {
		var req = null;
		var result = '';
		try {
			req = CL["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(CI.nsIXMLHttpRequest);
			req.tstamp = 'id #' + new Date().getTime();
			log.info('Sending ' + mode + ' ' + method + ' on ' + url + "\n" + body + "\n>>>>>>>>>>>> end of request " + req.tstamp + " >>>>>>>>>>>>");
			var b=body;
			if('xml' == typeof(body)) body = 'script=' + encodeURIComponent(body.toString());
			
			method = method.toUpperCase();
			var async = ('async' == mode)?true:false;

			req.open(method,url,async);
			req.setRequestHeader("Cache-Control", "no-cache");

			if('POST' == method) {
				req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
			}
			// If there are headers, add them ...
			if(headers) {
				for(var name in headers) {
					req.setRequestHeader(name, encodeURIComponent(headers[name]));
				}
			}

			// Initialize a timer to timeout the request if necessary
			var timerRequest = new requestTimeoutTimer(req, handler);
			req.onload = function (event) {
				var result = '';
				timerRequest.timer.cancel();
				try {
					var request = event.target;
					var suggestUrl  = '';
					if((4 == request.readyState) && (200 == request.status)) {
						try {
							result = new XML(request.responseText.replace(/<\?xml.*?\?>\n?/g, ""));
						} catch(e) {
							log.error("Error parsing Response " + e);
							log.error("Response Text: " + request.status + "\n" + request.responseText + "\n<<<<<<<<<<< end of response " + request.tstamp + "<<<<<<<<<<<");
							yoono.dialogs.setThrobberFailed();
							return;
						}
						log.info("Response " + request.status + "\n" + result + "\n<<<<<<<<<<< end of response <<<<<<<<<<< " + request.tstamp);
						yoono.main.getServerContextData(result);
					} else {
            // Chrome urls return 0 for status
            if(!((0 ==  request.status) && (0 == url.indexOf('chrome:')))) {
  						log.error("Response " + request.status + ' to ' + url + ' body : ' + b  );
						  yoono.dialogs.setThrobberFailed();
            }
					}
					if(handler)
					handler(request, result, body);
				} catch(e) {
					log.exception(e);
					yoono.dialogs.setThrobberFailed();
				}
			};
			req.onerror = function (e) {
				try {
					timerRequest.timer.cancel();
					log.error("Request error on "+b );
					yoono.dialogs.setThrobberFailed();
					if(handler)
						handler(req, 'error', body);
				} catch(e) {
					log.exception(e);
					yoono.dialogs.setThrobberFailed();
				}
			};
			req.send(body);
			if(!async) {
				if((4 == req.readyState) && (200 == req.status)) {
					result = new XML(req.responseText.replace(/<\?xml.*?\?>\n?/g, ""));
					yoono.main.getServerContextData(result);
				}
			}
		} catch(e) {
			log.exception(e);
			yoono.dialogs.setThrobberFailed();
		}
		return (new Array(req, result));
	}
 



  this.notify = function(timer){
    try {
      this.checkStats();
    } catch(e) {
      log.exception(e);
    }
  };

  this.delayedUninstallFromButton = function() {
    var _self = this;
    var timer = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer);
    timer.initWithCallback({ notify : function(){
      _self.addStat(['clic.yoonobutton.uninstall'], 1);
      _self.uninstallFromButton()
    } }, 100 , timer.TYPE_ONE_SHOT);
  };

	this.uninstallFromButton = function() {
    try {
      // FF4
      Components.utils.import("resource://gre/modules/AddonManager.jsm");
      AddonManager.getAddonByID(YOONO_ID, function(addon) {
        addon.uninstall();
      });
    } catch(e) {
      var gExtensionManager = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager);
      var item = gExtensionManager.getItemForID(YOONO_ID);
      gExtensionManager.uninstallItem(YOONO_ID);
    }
	};
  this.uninstallFromDeviceVM = function() {
    // If partner is ignored, no longer uninstall
    if(YOONO_YEXTIF.methods.partnerPresent('deviceVM')) {
      this.uninstallFromButton();
    }
  }
  //check if stats must be sent
  this.checkStats = function(mode) {
    var win = WMED.getMostRecentWindow("navigator:browser");
    if (!win)
      win = Components.classes["@mozilla.org/appshell/appShellService;1"]
          .getService(Components.interfaces.nsIAppShellService)
          .hiddenDOMWindow;
    
    var offset = 0;
    try {

      // check if stats must be sent
      // date is read in prefs cache so that not cheatable from about:config without relaunching FF
      var dateNow = Math.round((new Date().getTime()) / 1000);
      var dateStat = YOONO_PREFS.get('stats.last');
      log.debug('YOONO_CMPT.checkStats checking stats, mode=' + mode + ', now=' + dateNow + ', previous=' + dateStat );
      if((dateStat != 0) || mode) { // first call
        if(mode || (dateNow >= (dateStat + this.statDelay))) {
          var script = <server-script version="1.0"/>;
          // watch out: yoono.yextif.methods.getVersion() and yoono.main.getExtensionVersion() differ on Yoono Desktop
          // The former is the version of YD while the latter is the version of the embedded extension within the YD
          var context = <context>
                          <locale>{yoono.utils.getLocale()}</locale>
                          <version>{yoono.yextif.methods.getVersion()}</version>
                          <agent>{yoono.utils.getUserAgent(win)}</agent>
                          <appversion>{yoono.utils.getUserAgentAppVersion(win)}</appversion>
                          <oscpu>{yoono.utils.getUserAgentOsCpu(win)}</oscpu>
                          <platform>{yoono.utils.getUserAgentPlatform(win)}</platform>
                          <client>{yoono.yextif.methods.getClientType()}</client>
                          <user-id>{YOONO_PREFS.get('userid')}</user-id>
                        </context>;
          script.appendChild(context);
          var stat = null;
          if(mode) {
            stat = <stats begins={dateStat} ends={dateNow} command={mode}/>;
          } else {
            stat = <stats begins={dateStat} ends={dateNow}/>;
          }
          var statData = PREFSSERVICE.getBranch("extensions.yoono.stats.data.");
          var childArray = statData.getChildList('', {});
          var prefName = '';
          var type = '';
          var tag = null;
          var value = 0;
          var tagData = null;

          for (var i = childArray.length ; i-- > 0 ; ) {
            prefName = childArray[i];
            type = statData.getPrefType(prefName);
            // get bits from string using '.' as separator
            tagData = prefName.split('.');
            // re-join extra bits of string beyond the 3rd one.
            if(tagData[3]) tagData[2] = tagData.splice(2).join('.');
            prefName = 'stats.data.' + prefName;
            value = YOONO_PREFS.get(prefName);
            if(!value) continue;
            tag = <{tagData[0]} />;
            if(tagData[1]) tag.@type = tagData[1];
            if(tagData[2]) tag.@ui = tagData[2];
            tag.@value = value;
            stat.appendChild(tag);
            YOONO_PREFS.set(prefName, 0, type);
          }
          // Send the date each yoodget was displayed first 
          try {
            var firstDisplayDates = YOONO_KEYVALUEDB.getKeyValueList('sidebarFirstDisplayDate');
            var widgetName = '';
            for(var aDisplayDateKey in firstDisplayDates) {
              widgetName = aDisplayDateKey.replace(/sidebarFirstDisplayDate/, '');
              stat.appendChild(<widget-first-displayed ui={widgetName} value={firstDisplayDates[aDisplayDateKey]}/>);
            }
          } catch(e){
            log.exception(e);
          }

          // if code below fails, still send other stats
          try {
            stat.appendChild(<nosynchro value={YOONO_PREFS.get('nosynchro')}/>);
          } catch(e){}
          try {
            var marketingCampaign = YOONO_KEYVALUEDB.getKeyValue('sidebarMarketingCampaign') || '0' ;
            if('0' != marketingCampaign) {
              stat.appendChild(<marketing type="campaign" value={marketingCampaign}/>);
            }
          } catch(e){}
          try {
            var yoodgetList = YOONO_KEYVALUEDB.getKeyValue('sidebarYoodgetList') || '' ;
            // FF2 does not support JSON, we no longer support FF2
            // FF3.0 and FF3.1 support it a very different way, so fuck JSON
            // I don't want to 'eval' this chunk of code
            var tab = yoodgetList.match(/"yuid"/g);
            var nb = 0;
            if(tab) nb = tab.length;
            stat.appendChild(<widget type="installed" value={nb}/>);
          } catch(e){}
          try {
            var installDate = PREFS.getIntPref("install_date");
            var age = dateNow - installDate;
            stat.appendChild(<install_age value={age}/>);
          } catch(e){}
          var update = YOONO_PREFS.get('updated.from') || '';
          if(update) {
            stat.appendChild(<update old={update} new={YOONO_PREFS.get('release')} client={yoono.yextif.methods.getClientType()}/>);
            YOONO_PREFS.set('updated.from', '', PREFS.PREF_STRING);
          }
          
          try {
            var yoolinkEnabled = PREFS.getBoolPref("highlight.enable");
            if (yoolinkEnabled)
              stat.appendChild(<yoolink type="underline-enable" value="1" />);
            else
              stat.appendChild(<yoolink type="underline-disable" value="1" />);
          } catch(e){}
          
          script.appendChild(stat);
          YOONO_PREFS.set('stats.last', dateNow, PREFS.PREF_INT);
          // Check if stat must be sent.
          // Check is done this late because if we don't send them we still want them to be reset
          var noStat = YOONO_PREFS.get('stats.no') ;
          if(noStat && ('manual' != mode)) {
            log.debug('YOONO_CMPT.checkStats stats sending disabled' );
          } else {
            // Send a request to increment the counter of sent statistics
            yoono.main.sendRequest(YOONO_PREFS.get('serverurl') + 'rest/counters/one/client-stats/send', 'POST', 'async', null, null);
            // Send the statistics
            var _self = this;
            yoono.main.sendRequest(YOONO_PREFS.get('serverurl') + 'linkserver', 'POST', 'async',
                                   script, function(request, result, body){_self.statResult(request, result, body)});
          }
        } else {
          offset = dateNow - dateStat;
        }
      } else {
        YOONO_PREFS.set('stats.last', dateNow, PREFS.PREF_INT);
      }
    } catch(e) {
      log.exception(e);
    }
    // send stats every 24 hours
    var delay = (this.statDelay - offset ) * 1000 ;
    this.timerLogIn.initWithCallback(this, delay , this.timerLogIn.TYPE_ONE_SHOT);
  };

  this.statResult = function(request, result, body) {
    var xml = request.responseXML;
    if(!xml) {
      yoono.main.sendRequest(YOONO_PREFS.get('serverurl') + 'rest/counters/one/client-stats-error/timeout', 'POST', 'async', null, null);
      return;
    }
    var dom = xml.documentElement;
    var msg = dom.getElementsByTagName('display-message');
    if (msg && msg[0]) {
      var code = msg[0].getAttribute('code');
      // Send a request to increment the counter of sent statistics
      yoono.main.sendRequest(YOONO_PREFS.get('serverurl') + 'rest/counters/one/client-stats-error/' + encodeURIComponent(code), 'POST', 'async', null, null);
    }
  };


  // increments a stat counter in prefs
  this.addStat = function(params, step) {
    if(null == step) step = 1;
    var namePref = 'stats.data.' + params.join('.');
    var current = parseInt(YOONO_PREFS.get(namePref) || 0);
    current += parseInt(step);
    if(PREFS.PREF_INT != PREFS.getPrefType(namePref)) {
      try {
        // pref may not exist
        PREFS.clearUserPref(namePref);
      } catch(e) {}
    }

    YOONO_PREFS.set(namePref, current, PREFS.PREF_INT);
    
    // Save a "total" value, that is not reseted on stat send
    var total = parseInt(YOONO_KEYVALUEDB.getKeyValue(namePref) || 0);
    total += parseInt(step);
    YOONO_KEYVALUEDB.setKeyValue(namePref, ""+total);
  }

  this.setStat = function(params, value) {
    var namePref = 'stats.data.' + params.join('.');
    var type = PREFS.PREF_INT;
    if((value + 1) != (1 + value))
      type = PREFS.PREF_STRING;
    YOONO_PREFS.set(namePref, value, type);
    
    // Save a value, that is not reseted on stat send
    YOONO_KEYVALUEDB.setKeyValue(namePref, ""+value);
  }

  this.inprogress = function () {
    return yoono.dialogs._connecting;
  };


  // check if incompatible extension 
  this.checkIncompatibility = function(extensionArray) {
    var result = false;
    try {
      // Won't work with FF4, but what the hell...
      var extmgr = CL['@mozilla.org/extensions/manager;1'].getService(CI.nsIExtensionManager);

      var extensionList = extmgr.getItemList(Components.interfaces.nsIUpdateItem.TYPE_ANY, {});
      var extId = '';
      for(var ind=0 ; ind < extensionList.length; ind ++) {
          extId = extensionList[ind].id;
          extId = extId.toLowerCase();
          // TODO : check if disabled...not that easy...
          if(extId in extensionArray) {
            result = true;
            log.debug('Incompatible extension detected: ' + extId);
          }
      }
    } catch(e) {
      Log.exception(e);
    }
    return result;
  };

  this.getUserInfo = function(login, mode, handler) {
    var async = ('async' == mode)?true:false;
    var script = <server-script version="1.0"/> ;
    script.appendChild( <context> <user-id>{login}</user-id> </context>);
    script.appendChild( <get-user-data/>);
    var url = YOONO_PREFS.get('serverurl') + 'linkserver';
    result = yoono.main.sendRequest(url, 'POST', async, script, handler) ;
    return (result);
  };

  // user may change his password anytime
  // @param aWithoutPwd boolean: on FF we need the login for the DB access
  // this param allows to not try reading the pwd from the DB since it
  // won't be opened yet.
  this.getUserCredential = function(aWithoutPwd) {
    var userId = PREFS.getCharPref("userid");
    if(!userId) return null; 
    var result = new Object;
    result.loggedIn = true;    
    // anonymous users have no ':' in their userid
    if(!userId.match(':')) {
      result.login = null;
      result.password = null;
      result.userId = userId;
      result.anonymous = true;
    } else {
      var userData = userId.split(':');
      result.anonymous = false;
      result.login = userData[0];
      result.userId = userData[0];

      if(aWithoutPwd) {
        return result;
      }
      var oldStylePwd = userData[1];
      // no longer storing the password in about:config
      // getting old password and storing it
      switch(oldStylePwd) {
        case '':
          YOONO_PREFS.set('userid', result.login + ':yoono', PREFS.PREF_STRING);
          YOONO_KEYVALUEDB.setKeyValue('yp', 'false:');

          result.loggedIn = false;
        break;
        
        case 'yoono':
          // Nothing to do
        break;

        // migrating old password
        default:
          YOONO_KEYVALUEDB.setKeyValue('yp', 'true:' + oldStylePwd );
          YOONO_PREFS.set('userid', result.login + ':yoono', PREFS.PREF_STRING);
      }
      var yp = YOONO_KEYVALUEDB.getKeyValue('yp');
      if(yp) {
        ypData = yp.split(':');
        result.password = ypData[1];
        result.userId += ':' + ypData[1];
        result.loggedIn = ('true' == ypData[0]);
      }
    }
    return(result);
  };

  this.registerUser = function (aUserData) {
    var userId = aUserData.login;
    if(!aUserData.anonymous) {
      userId += ':yoono';
    }
    var yp = aUserData.loggedIn + ':';
    if(aUserData.password) {
      yp += aUserData.password;
    }
    YOONO_KEYVALUEDB.setKeyValue('yp', yp);
    YOONO_PREFS.set('userid', userId, PREFS.PREF_STRING);
  };

  // Called from the sidebar to log an existing user installing the extension
  this.changeUser = function(aNewUserData, aSyncMode) {
    var currentCredentials = this.getUserCredential();
    // When creating the account, currentCredentials are empty
    if(!currentCredentials) {
      currentCredentials = {
        'userId': '',
        'login' : ''
      }
    }
    // If no change, nothing to do
    if((currentCredentials.userId == aNewUserData.userId)
          && (currentCredentials.loggedIn == aNewUserData.loggedIn)) return;

    if( aSyncMode == null ) {
      aSyncMode = 'manual-sync';
    }
    if (YOONO_PREFS.get('nosynchro')) {
      aSyncMode = 'no-sync';
    }
    log.backtrace("Changing current user : aSyncMode="+aSyncMode);
    
    this.registerUser(aNewUserData);
    
    // Switch database files only if login has changed (not password)
    if(aNewUserData.login != currentCredentials.login) {
      YOONO_STORAGE.switchDBStorage();
      YOONO_KEYVALUEDB.closeDB();	// New db will open itself the first time it will be needed (wow !)
    }

    if((aNewUserData.login != currentCredentials.login) || (aNewUserData.password != currentCredentials.password)) {
      var mm = this.getYServices().MemoMgr;
      if(mm) {
        mm.switchUser(aNewUserData.login, aNewUserData.password);
      }
      yoono.server.launch('connect',aSyncMode);

    }
  };


  this.escapeFileName = function(fileName) {
    var escFileName = fileName.replace(/[\/\\" :]/g, '_');
    return(escFileName);
  };

  // Processing of server-sent preferences
  this.getServerContextData = function(xml) {
    try {
      var contextPrefs = xml.context['client-prefs'];
      var pref = '';
      // toString is absolutely mandatory in comparaison operators
      for each (var indP in contextPrefs.pref) {
        pref = indP.@name.toString();
        // Test if this pref is allowed to be overriden by a server pref
        if(! (pref in safePreferences)) {
          switch(indP.@type.toString()) {
            case 'int':
              YOONO_PREFS.set(pref, indP.@value, PREFS.PREF_INT);
              break;
            case 'string':
              YOONO_PREFS.set(pref, indP.@value, PREFS.PREF_STRING);
              break;
            case 'bool':
              YOONO_PREFS.set(pref, indP.@value, PREFS.PREF_BOOL);
              break;
          }
        }
      }
      // Don't know why, but need to re-import module
      Components.utils.import("resource://yoono/yoonoKeyValueDB.js");
      var geoLocData = xml.context['geoloc'];
      if(geoLocData) {
        var ip = geoLocData.ip.toString();
        // Store data for sidebar access
        if(ip) YOONO_KEYVALUEDB.setKeyValue('sidebarIpAddress', ip);
        // countrycode now fetched from env YServices, so that it works with Chrome,
        // and also because country file on our serer is outdated.
//        var countryCode = geoLocData.country.@code.toString();
//        if(countryCode) YOONO_KEYVALUEDB.setKeyValue('sidebarCountryCode', countryCode);
      }
      var adsData = xml.context['ads'];
      if(adsData) {
        var mivaAuthToken = adsData.miva.toString();
        // Store data for sidebar access
        if(mivaAuthToken) YOONO_KEYVALUEDB.setKeyValue('sidebarMivaAuthToken', mivaAuthToken);
      }
    } catch(e) {
      log.exception(e);
    }
  }

  this.uninstallAddonWhenQuitting = function (yoonoItem) {
    try {
      var win = WMED.getMostRecentWindow("Extension:Manager");
      this.goodbyeWizardReturnObject = null;
      if(!this.uninstallWhenQuitting )  {
        this.uninstallWhenQuitting = true;
        // Ask user if he wants to remove his account and data on the server...
        this.goodbyeWizardReturnObject = new Object;
        // the returnObject will contain the script to send to the server, if any, when uninstalling
        var userCredentials = this.getUserCredential();
        if(userCredentials) {
          log.debug("Goodbye popup");
          yoono.dialogs.setDialog('goodbye', this.goodbyeWizardReturnObject);
          log.debug("Returned from uninstall popup: " + this.goodbyeWizardReturnObject.value);
          // If dialog canceled, cancel uninstallation
          if(!this.goodbyeWizardReturnObject.value) {
            log.debug("Cancelling uninstallation");
            if(win) {
              win.gExtensionsViewController.commands.cmd_cancelUninstall(yoonoItem);
              win.focus();
            } else {
              try {
                // FF4
                Components.utils.import("resource://gre/modules/AddonManager.jsm");
                AddonManager.getAddonByID(YOONO_ID, function(addon) {
                  addon.cancelUninstall();
                });
              } catch(e) {
                var gExtensionManager = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager);
                gExtensionManager.cancelUninstallItem(YOONO_ID);
              }
            }
            this.uninstallWhenQuitting = false;
            return;
          }
        }
      } else {
        this.uninstallWhenQuitting = true;
      }
      if(this.uninstallWhenQuitting) {
        log.debug("uninstallwhenquitting ...");
        this.checkStats('uninstall'); // Force sending stats
        // Close sidebar on each window so that extension does not try to make anymore access to preferences
        // that no longer exist...
        var _self = this;
        yoono.dialogs.traverseBrowsers(function(traversedWindow) {_self.hideYoonoStuff(traversedWindow)});
        if(win) {
          win.focus();
        }
        if(this.goodbyeWizardReturnObject.restart) {
          var app =  Components.classes["@mozilla.org/toolkit/app-startup;1"].getService( Components.interfaces.nsIAppStartup);
          app.quit( Components.interfaces.nsIAppStartup.eForceQuit|Components.interfaces.nsIAppStartup.eRestart);
        }
      }
    } catch(e) {
      log.exception(e);
    }
  }

  // Fonction appelée lorsque l'extension doit etre désinstallée, au moment où on quitte firefox
  this.uninstall = function () {
    try {
      log.debug("uninstallation");
      if(this.goodbyeWizardReturnObject && this.goodbyeWizardReturnObject.script) {
        var script = this.goodbyeWizardReturnObject.script;
        var reqResult = yoono.main.sendRequest(YOONO_PREFS.get('serverurl') + 'linkserver', 'POST', 'sync', script, null);
        try {
          var response = reqResult[1];
          var message = response['display-error'].@code;
          if('ERROR_INVALID_USER_ID' == message) {
          }
          message = response['display-message'].@code.toString();
          // TO BE DONE : what is best in case account could not be deleted ?
          switch(message) {
            case 'MSG_USER_ACCOUNT_REMOVED':
               // SUCCESSDELETED.hidden = false;
            break;
            case 'MSG_USER_ACCOUNT_NOT_REMOVED':
               // SUCCESSKEPT.hidden = false;
            break;
            default:
          }
        } catch(e) {
          log.exception(e);
        }
      }

      YOONO_SERVER.uninstall();

      YOONO_BKM.uninstall();

      YOONO_KEYVALUEDB.closeDB();

      YOONO_STORAGE.closeStorage();

    } catch(e) {
			log.exception(e);
    }
    this.cleanupYoonoDir();

    log.warn("Remove all yoono preferences");
    PREFS.deleteBranch("");

    this.uninstallDone = true;
  }

  this.cleanupYoonoDir = function () {
    try {
      var dir = this.getYoonoDir();
      this.removeDirRecursive(dir);
    } catch(e) {
      log.error("Could not remove yoono dir: " + e);
    }
  }

  this.getYoonoDir = function  () {
    var rootDir = DIRSERVICE.get("ProfDS", CI.nsIFile) ;
    rootDir.append('yoono');
    return(rootDir);
  }

  // Recursive removal of directory. Stolen with (little) shame from nsExtensionManager.js
// the file.remove method is recursive but will fail if file permissions are restrictive...
  this.removeDirRecursive = function (aDir) {
    try {
      aDir.remove(true);
      return;
    } catch (e) {
    }
    var dirEntries = aDir.directoryEntries;
    while (dirEntries.hasMoreElements()) {
      var entry = dirEntries.getNext().QueryInterface(CI.nsIFile);

      if (entry.isDirectory()) {
        removeDirRecursive(entry);
      } else {
        entry.permissions = PERMS_FILE;
        try {
          entry.remove(false);
        } catch(e) {
          YOONO_LOG.error(entry.path);
          YOONO_LOG.exception(e);
        }
      }
    }
    aDir.permissions = PERMS_DIRECTORY;
    aDir.remove(true);
  }

  this.loginMyYoonoPages = function(target, event, feedId, vsId) {
    var userData = this.getUserCredential();
    if(!userData || userData.anonymous) return;
    // encoding, may contain utf8
    var login = encodeURIComponent(userData.login);
    var passwd = encodeURIComponent(userData.password);

    var url = 'http://api.yoono.com/my-yoono/sign_in.jsp';

    const MIME_STREAM_CID = "@mozilla.org/network/mime-input-stream;1";
    const nsIMIMEInputStream = CI.nsIMIMEInputStream;
    var mis = CL[MIME_STREAM_CID];
    var headers = mis.createInstance(nsIMIMEInputStream);
    headers.addHeader('X-Login', login);
    headers.addHeader('X-Password', passwd);
    if(!target) target = 'buzz';
    if('webnote' == target) {
      if(feedId) {
        headers.addHeader('X-FeedId', feedId);
      }
      if(vsId) {
        headers.addHeader('X-VsId', vsId);
      }
    }
    headers.addHeader('X-Page', target);

    var obj   = Components.classes["@mozilla.org/io/string-input-stream;1"];
    var iface = Components.interfaces.nsIStringInputStream;

    var win = WMED.getMostRecentWindow("navigator:browser");
    var nav = win.getWebNavigation();
    // todo : open in tab according to mouse button. Pb : sending headers !
    nav.loadURI(url, 0, null, null, headers);
  }

  this.hideYoonoStuff = function(win) {
    this.closeSidebar(win);
    this.hideYoonoButton(win);
  }
  this.closeSidebar = function(win) {
    win.yoonoGlob.sidebar.hide();
  }
  this.hideYoonoButton = function(win) {
    doc = win.document;
    var toggle = doc.getElementById('yoono-toggle-sb');
    if(toggle) toggle.setAttribute('hidden', 'true');
  }

  this.setExtensionVersion = function(aVersion) {
    this.version = aVersion;
  }
  this.getExtensionVersion = function(aVersion) {
    return(this.version);
  }

  // Don't use this method directly, prefer "partnerPresent" from yoonoYEXTIF
  // to take into account ignored partners and be Chrome compatible :
  // YOONO_YEXTIF.methods.partnerPresent('deviceVM')
  this.isAddonInstalled = function(aAddOnId) {
    var result = false;
    if(aAddOnId in this.addOns) result = true;
    return result;
  }

  this.getInstalledAddons = function(aCallBack) {
    var _self = this;
    try {
      // That's for FF4
      Components.utils.import("resource://gre/modules/AddonManager.jsm");
      AddonManager.getAllAddons(function(aAddonList) {
        try {
          for(var i=0; i < aAddonList.length; i++) {
            _self.addOns[aAddonList[i].id] = aAddonList[i];
            if(YOONO_ID == aAddonList[i].id) {
              _self.setExtensionVersion(aAddonList[i].version);
            }
          }
        } catch(e) {
          CONSOLESERVICE.logStringMessage(e);
        };
        aCallBack();
      });
    } catch (e) {

      var aExtManager = CL["@mozilla.org/extensions/manager;1"].getService(CI.nsIExtensionManager);
      var aItemList = aExtManager.getItemList(CI.nsIUpdateItem.TYPE_ANY, { });
      for(var i in aItemList) {
        this.addOns[aItemList[i].id] = aItemList[i];
        if(YOONO_ID == aItemList[i].id) {
          this.setExtensionVersion(aItemList[i].version);
        }
      }
      aCallBack();
    }
  }


  this.observe = function(aSubject, aTopic, aData) {
    YOONO_LOG.debug('YOONO_CMPT.observe : ' + aTopic);
    switch(aTopic) {
      case "app-startup":  // FF3
      case "profile-after-change": // FF4
        var obsSvc = CL["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
        obsSvc.addObserver(this, "quit-application", false);
        obsSvc.addObserver(this, "profile-before-change", false);
        obsSvc.addObserver(this, "xpcom-shutdown", false);
        YOONO_PREFS.storeLastModified(aTopic);

      break;
      
      case "quit-application":
        this.getYServices().observer.notifyObservers("app.quit",null);
        // Close the local database
        try {
          YOONO_KEYVALUEDB.closeDB();
//          YOONO_STORAGE.closeStorage();   // COMMENTED OUT BECAUSE WAS CAUSING FF NOT EXITING PROPERLY !!
        } catch(e) {
          YOONO_LOG.exception(e);
        }
        // LQ-20060925
        YOONO_PREFS.storeLastModified(aTopic);
        try {
          if(this.uninstallWhenQuitting) {
            this.getYServices().observer.notifyObservers("app.uninstall",null);
            this.uninstall();
          }
        } catch(e) {
          YOONO_LOG.exception(e);
        }
      break;

      // LQ-20060925
      case "profile-before-change":
        YOONO_PREFS.storeLastModified(aTopic);
      break;
      case "xpcom-shutdown":
        YOONO_PREFS.storeLastModified(aTopic);
      break;

      default:
      throw Components.Exception("Unknown topic: " + aTopic);
    }

  }

};
//


var YOONO_CMPT = new chromeUI();

} catch(e) {
  var console = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
  console.logStringMessage(e);
}
