"use strict";const Cc=Components.classes;const Ci=Components.interfaces;const Cu=Components.utils;Cu.import("resource://gre/modules/XPCOMUtils.jsm");Cu.import("resource://gre/modules/Services.jsm");Cu.import("resource://gre/modules/DOMRequestHelper.jsm");let DEBUG=false;let VERBOSE=false;try{DEBUG=Services.prefs.getBoolPref("dom.mozSettings.SettingsManager.debug.enabled");VERBOSE=Services.prefs.getBoolPref("dom.mozSettings.SettingsManager.verbose.enabled");}catch(ex){}
function debug(s){dump("-*- SettingsManager: "+s+"\n");}
XPCOMUtils.defineLazyServiceGetter(Services,"DOMRequest","@mozilla.org/dom/dom-request-service;1","nsIDOMRequestService");XPCOMUtils.defineLazyServiceGetter(this,"cpmm","@mozilla.org/childprocessmessagemanager;1","nsIMessageSender");XPCOMUtils.defineLazyServiceGetter(this,"mrm","@mozilla.org/memory-reporter-manager;1","nsIMemoryReporterManager");XPCOMUtils.defineLazyServiceGetter(this,"uuidgen","@mozilla.org/uuid-generator;1","nsIUUIDGenerator");function SettingsLock(aSettingsManager){if(VERBOSE)debug("settings lock init");this._open=true;this._settingsManager=aSettingsManager;this._id=uuidgen.generateUUID().toString(); this.initDOMRequestHelper(this._settingsManager._window,["Settings:Get:OK","Settings:Get:KO","Settings:Clear:OK","Settings:Clear:KO","Settings:Set:OK","Settings:Set:KO","Settings:Finalize:OK","Settings:Finalize:KO"]);let createLockPayload={lockID:this._id,isServiceLock:false,windowID:this._settingsManager.innerWindowID,lockStack:(new Error).stack};this.sendMessage("Settings:CreateLock",createLockPayload);Services.tm.currentThread.dispatch(this._closeHelper.bind(this),Ci.nsIThread.DISPATCH_NORMAL);this._closeCalled=true;}
SettingsLock.prototype={__proto__:DOMRequestIpcHelper.prototype,set onsettingstransactionsuccess(aHandler){this.__DOM_IMPL__.setEventHandler("onsettingstransactionsuccess",aHandler);},get onsettingstransactionsuccess(){return this.__DOM_IMPL__.getEventHandler("onsettingstransactionsuccess");},set onsettingstransactionfailure(aHandler){this.__DOM_IMPL__.setEventHandler("onsettingstransactionfailure",aHandler);},get onsettingstransactionfailure(){return this.__DOM_IMPL__.getEventHandler("onsettingstransactionfailure");},get closed(){return!this._open;},_closeHelper:function(){if(VERBOSE)debug("closing lock "+this._id);this._open=false;this._closeCalled=false;if(!this._requests||Object.keys(this._requests).length==0){if(VERBOSE)debug("Requests exhausted, finalizing "+this._id);this._settingsManager.unregisterLock(this._id);this.sendMessage("Settings:Finalize",{lockID:this._id});}else{if(VERBOSE)debug("Requests left: "+Object.keys(this._requests).length);this.sendMessage("Settings:Run",{lockID:this._id});}},_wrap:function _wrap(obj){return Cu.cloneInto(obj,this._settingsManager._window);},sendMessage:function(aMessageName,aData){



if(!this._settingsManager._window){Cu.reportError("SettingsManager window died, cannot run settings transaction."+" SettingsMessage: "+aMessageName+" SettingsData: "+JSON.stringify(aData));return;}
cpmm.sendAsyncMessage(aMessageName,aData,undefined,this._settingsManager._window.document.nodePrincipal);},receiveMessage:function(aMessage){let msg=aMessage.data;
if(msg.lockID!=this._id){return;}
if(VERBOSE)debug("receiveMessage ("+this._id+"): "+aMessage.name);


if(!msg.requestID){let event;switch(aMessage.name){case"Settings:Finalize:OK":if(VERBOSE)debug("Lock finalize ok: "+this._id);event=new this._window.MozSettingsTransactionEvent("settingstransactionsuccess",{});this.__DOM_IMPL__.dispatchEvent(event);this.destroyDOMRequestHelper();break;case"Settings:Finalize:KO":if(DEBUG)debug("Lock finalize failed: "+this._id);event=new this._window.MozSettingsTransactionEvent("settingstransactionfailure",{error:msg.errorMsg});this.__DOM_IMPL__.dispatchEvent(event);this.destroyDOMRequestHelper();break;default:if(DEBUG)debug("Message type "+aMessage.name+" is missing a requestID");}
return;}
let req=this.getRequest(msg.requestID);if(!req){if(DEBUG)debug("Matching request not found.");return;}
this.removeRequest(msg.requestID);


if(!this._closeCalled){Services.tm.currentThread.dispatch(this._closeHelper.bind(this),Ci.nsIThread.DISPATCH_NORMAL);this._closeCalled=true;}
if(VERBOSE)debug("receiveMessage: "+aMessage.name);switch(aMessage.name){case"Settings:Get:OK":for(let i in msg.settings){msg.settings[i]=this._wrap(msg.settings[i]);}
this._open=true;Services.DOMRequest.fireSuccess(req.request,this._wrap(msg.settings));this._open=false;break;case"Settings:Set:OK":case"Settings:Clear:OK":this._open=true;Services.DOMRequest.fireSuccess(req.request,0);this._open=false;break;case"Settings:Get:KO":case"Settings:Set:KO":case"Settings:Clear:KO":if(DEBUG)debug("error:"+msg.errorMsg);Services.DOMRequest.fireError(req.request,msg.errorMsg);break;default:if(DEBUG)debug("Wrong message: "+aMessage.name);}},get:function get(aName){if(VERBOSE)debug("get ("+this._id+"): "+aName);if(!this._open){dump("Settings lock not open!\n");throw Components.results.NS_ERROR_ABORT;}
let req=this.createRequest();let reqID=this.getRequestId({request:req});this.sendMessage("Settings:Get",{requestID:reqID,lockID:this._id,name:aName});return req;},set:function set(aSettings){if(VERBOSE)debug("send: "+JSON.stringify(aSettings));if(!this._open){throw"Settings lock not open";}
let req=this.createRequest();let reqID=this.getRequestId({request:req});this.sendMessage("Settings:Set",{requestID:reqID,lockID:this._id,settings:aSettings});return req;},clear:function clear(){if(VERBOSE)debug("clear");if(!this._open){throw"Settings lock not open";}
let req=this.createRequest();let reqID=this.getRequestId({request:req});this.sendMessage("Settings:Clear",{requestID:reqID,lockID:this._id});return req;},classID:Components.ID("{60c9357c-3ae0-4222-8f55-da01428470d5}"),contractID:"@mozilla.org/settingsLock;1",QueryInterface:XPCOMUtils.generateQI([Ci.nsISupports,Ci.nsIObserver,Ci.nsISupportsWeakReference])};function SettingsManager(){this._callbacks=null;this._isRegistered=false;this._locks=[];this._createdLocks=0;this._unregisteredLocks=0;}
SettingsManager.prototype={_wrap:function _wrap(obj){return Cu.cloneInto(obj,this._window);},set onsettingchange(aHandler){this.__DOM_IMPL__.setEventHandler("onsettingchange",aHandler);this.checkMessageRegistration();},get onsettingchange(){return this.__DOM_IMPL__.getEventHandler("onsettingchange");},createLock:function(){let lock=new SettingsLock(this);if(VERBOSE)debug("creating lock "+lock._id);this._locks.push(lock._id);this._createdLocks++;return lock;},unregisterLock:function(aLockID){let lock_index=this._locks.indexOf(aLockID);if(lock_index!=-1){if(VERBOSE)debug("Unregistering lock "+aLockID);this._locks.splice(lock_index,1);this._unregisteredLocks++;}},receiveMessage:function(aMessage){if(VERBOSE)debug("Settings::receiveMessage: "+aMessage.name);let msg=aMessage.json;switch(aMessage.name){case"Settings:Change:Return:OK":if(VERBOSE)debug('data:'+msg.key+':'+msg.value+'\n');let event=new this._window.MozSettingsEvent("settingchange",this._wrap({settingName:msg.key,settingValue:msg.value}));this.__DOM_IMPL__.dispatchEvent(event);if(this._callbacks&&this._callbacks[msg.key]){if(VERBOSE)debug("observe callback called! "+msg.key+" "+this._callbacks[msg.key].length);this._callbacks[msg.key].forEach(function(cb){cb(this._wrap({settingName:msg.key,settingValue:msg.value}));}.bind(this));}else{if(VERBOSE)debug("no observers stored!");}
break;default:if(DEBUG)debug("Wrong message: "+aMessage.name);}},
checkMessageRegistration:function checkRegistration(){let handler=this.__DOM_IMPL__.getEventHandler("onsettingchange");if(!this._isRegistered){if(VERBOSE)debug("Registering for messages");cpmm.sendAsyncMessage("Settings:RegisterForMessages",undefined,undefined,this._window.document.nodePrincipal);this._isRegistered=true;}else{if((!this._callbacks||Object.keys(this._callbacks).length==0)&&!handler){if(VERBOSE)debug("Unregistering for messages");cpmm.sendAsyncMessage("Settings:UnregisterForMessages",undefined,undefined,this._window.document.nodePrincipal);this._isRegistered=false;this._callbacks=null;}}},addObserver:function addObserver(aName,aCallback){if(VERBOSE)debug("addObserver "+aName);if(!this._callbacks){this._callbacks={};}
if(!this._callbacks[aName]){this._callbacks[aName]=[aCallback];}else{this._callbacks[aName].push(aCallback);}
this.checkMessageRegistration();},removeObserver:function removeObserver(aName,aCallback){if(VERBOSE)debug("deleteObserver "+aName);if(this._callbacks&&this._callbacks[aName]){let index=this._callbacks[aName].indexOf(aCallback);if(index!=-1){this._callbacks[aName].splice(index,1);if(this._callbacks[aName].length==0){delete this._callbacks[aName];}}else{if(VERBOSE)debug("Callback not found for: "+aName);}}else{if(VERBOSE)debug("No observers stored for "+aName);}
this.checkMessageRegistration();},init:function(aWindow){if(VERBOSE)debug("SettingsManager init");mrm.registerStrongReporter(this);cpmm.addMessageListener("Settings:Change:Return:OK",this);Services.obs.addObserver(this,"inner-window-destroyed",false);let util=aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);this.innerWindowID=util.currentInnerWindowID;this._window=aWindow;},observe:function(aSubject,aTopic,aData){if(VERBOSE)debug("Topic: "+aTopic);if(aTopic==="inner-window-destroyed"){let wId=aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;if(wId===this.innerWindowID){if(DEBUG)debug("Received: inner-window-destroyed for valid innerWindowID="+wId+", cleanup.");this.cleanup();}}},collectReports:function(aCallback,aData,aAnonymize){for(let topic in this._callbacks){let length=this._callbacks[topic].length;if(length==0){continue;}
let path;if(length<20){path="settings-observers";}else{path="settings-observers-suspect/referent(topic="+
(aAnonymize?"<anonymized>":topic)+")";}
aCallback.callback("",path,Ci.nsIMemoryReporter.KIND_OTHER,Ci.nsIMemoryReporter.UNITS_COUNT,length,"The number of settings observers for this topic.",aData);}
aCallback.callback("","settings-locks/alive",Ci.nsIMemoryReporter.KIND_OTHER,Ci.nsIMemoryReporter.UNITS_COUNT,this._locks.length,"The number of locks that are currently alives.",aData);aCallback.callback("","settings-locks/created",Ci.nsIMemoryReporter.KIND_OTHER,Ci.nsIMemoryReporter.UNITS_COUNT,this._createdLocks,"The number of locks that were created.",aData);aCallback.callback("","settings-locks/deleted",Ci.nsIMemoryReporter.KIND_OTHER,Ci.nsIMemoryReporter.UNITS_COUNT,this._unregisteredLocks,"The number of locks that were deleted.",aData);},cleanup:function(){Services.obs.removeObserver(this,"inner-window-destroyed");

for(let i=0;i<this._locks.length;++i){if(DEBUG)debug("Lock alive at destroy, finalizing: "+this._locks[i]);

cpmm.sendAsyncMessage("Settings:Finalize",{lockID:this._locks[i]});}
cpmm.removeMessageListener("Settings:Change:Return:OK",this);mrm.unregisterStrongReporter(this);this.innerWindowID=null;this._window=null;},classID:Components.ID("{c40b1c70-00fb-11e2-a21f-0800200c9a66}"),contractID:"@mozilla.org/settingsManager;1",QueryInterface:XPCOMUtils.generateQI([Ci.nsISupports,Ci.nsIDOMGlobalPropertyInitializer,Ci.nsIObserver,Ci.nsIMemoryReporter]),};this.NSGetFactory=XPCOMUtils.generateNSGetFactory([SettingsManager,SettingsLock]);