https://github.com/mozilla/gecko-dev
Raw File
Tip revision: 7d573c967f2bdc44ad44e754192674fd3d204a19 authored by ffxbld on 03 March 2016, 21:59:23 UTC
Added FENNEC_45_0_RELEASE FENNEC_45_0_BUILD3 tag(s) for changeset 018c9e0e7da3. DONTBUILD CLOSED TREE a=release
Tip revision: 7d573c9
SharedPreferences.jsm
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["SharedPreferences"];

const { classes: Cc, interfaces: Ci, utils: Cu } = Components;

// For adding observers.
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");

var Scope = Object.freeze({
  APP:          "app",
  PROFILE:      "profile",
  GLOBAL:       "global"
});

/**
 * Public API to getting a SharedPreferencesImpl instance. These scopes mirror GeckoSharedPrefs.
 */
var SharedPreferences = {
  forApp: function() {
    return new SharedPreferencesImpl({ scope: Scope.APP });
  },

  forProfile: function() {
    return new SharedPreferencesImpl({ scope: Scope.PROFILE });
  },

  /**
   * Get SharedPreferences for the named profile; if the profile name is null,
   * returns the preferences for the current profile (just like |forProfile|).
   */
  forProfileName: function(profileName) {
    return new SharedPreferencesImpl({ scope: Scope.PROFILE, profileName: profileName });
  },

  /**
   * Get SharedPreferences for the given Android branch; if the branch is null,
   * returns the default preferences branch for the application, which is the
   * output of |PreferenceManager.getDefaultSharedPreferences|.
   */
  forAndroid: function(branch) {
    return new SharedPreferencesImpl({ scope: Scope.GLOBAL, branch: branch });
  }
};

/**
 * Create an interface to an Android SharedPreferences branch.
 *
 * options {Object} with the following valid keys:
 *   - scope {String} (required) specifies the scope of preferences that should be accessed.
 *   - branch {String} (only when using Scope.GLOBAL) should be a string describing a preferences branch,
 *     like "UpdateService" or "background.data", or null to access the
 *     default preferences branch for the application.
 *   - profileName {String} (optional, only valid when using Scope.PROFILE)
 */
function SharedPreferencesImpl(options = {}) {
  if (!(this instanceof SharedPreferencesImpl)) {
    return new SharedPreferencesImpl(options);
  }

  if (options.scope == null || options.scope == undefined) {
    throw "Shared Preferences must specifiy a scope.";
  }

  this._scope = options.scope;
  this._profileName = options.profileName;
  this._branch = options.branch;
  this._observers = {};
}

SharedPreferencesImpl.prototype = Object.freeze({
  _set: function _set(prefs) {
    Messaging.sendRequest({
      type: "SharedPreferences:Set",
      preferences: prefs,
      scope: this._scope,
      profileName: this._profileName,
      branch: this._branch,
    });
  },

  _setOne: function _setOne(prefName, value, type) {
    let prefs = [];
    prefs.push({
      name: prefName,
      value: value,
      type: type,
    });
    this._set(prefs);
  },

  setBoolPref: function setBoolPref(prefName, value) {
    this._setOne(prefName, value, "bool");
  },

  setCharPref: function setCharPref(prefName, value) {
    this._setOne(prefName, value, "string");
  },

  setIntPref: function setIntPref(prefName, value) {
    this._setOne(prefName, value, "int");
  },

  _get: function _get(prefs, callback) {
    let result = null;
    Messaging.sendRequestForResult({
      type: "SharedPreferences:Get",
      preferences: prefs,
      scope: this._scope,
      profileName: this._profileName,
      branch: this._branch,
    }).then((data) => {
      result = data.values;
    });

    let thread = Services.tm.currentThread;
    while (result == null)
      thread.processNextEvent(true);

    return result;
  },

  _getOne: function _getOne(prefName, type) {
    let prefs = [];
    prefs.push({
      name: prefName,
      type: type,
    });
    let values = this._get(prefs);
    if (values.length != 1) {
      throw new Error("Got too many values: " + values.length);
    }
    return values[0].value;
  },

  getBoolPref: function getBoolPref(prefName) {
    return this._getOne(prefName, "bool");
  },

  getCharPref: function getCharPref(prefName) {
    return this._getOne(prefName, "string");
  },

  getIntPref: function getIntPref(prefName) {
    return this._getOne(prefName, "int");
  },

  /**
   * Invoke `observer` after a change to the preference `domain` in
   * the current branch.
   *
   * `observer` should implement the nsIObserver.observe interface.
   */
  addObserver: function addObserver(domain, observer, holdWeak) {
    if (!domain)
      throw new Error("domain must not be null");
    if (!observer)
      throw new Error("observer must not be null");
    if (holdWeak)
      throw new Error("Weak references not yet implemented.");

    if (!this._observers.hasOwnProperty(domain))
      this._observers[domain] = [];
    if (this._observers[domain].indexOf(observer) > -1)
      return;

    this._observers[domain].push(observer);

    this._updateAndroidListener();
  },

  /**
   * Do not invoke `observer` after a change to the preference
   * `domain` in the current branch.
   */
  removeObserver: function removeObserver(domain, observer) {
    if (!this._observers.hasOwnProperty(domain))
      return;
    let index = this._observers[domain].indexOf(observer);
    if (index < 0)
      return;

    this._observers[domain].splice(index, 1);
    if (this._observers[domain].length < 1)
      delete this._observers[domain];

    this._updateAndroidListener();
  },

  _updateAndroidListener: function _updateAndroidListener() {
    if (this._listening && Object.keys(this._observers).length < 1)
      this._uninstallAndroidListener();
    if (!this._listening && Object.keys(this._observers).length > 0)
      this._installAndroidListener();
  },

  _installAndroidListener: function _installAndroidListener() {
    if (this._listening)
      return;
    this._listening = true;

    Services.obs.addObserver(this, "SharedPreferences:Changed", false);
    Messaging.sendRequest({
      type: "SharedPreferences:Observe",
      enable: true,
      scope: this._scope,
      profileName: this._profileName,
      branch: this._branch,
    });
  },

  observe: function observe(subject, topic, data) {
    if (topic != "SharedPreferences:Changed") {
      return;
    }

    let msg = JSON.parse(data);
    if (msg.scope !== this._scope ||
        ((this._scope === Scope.PROFILE) && (msg.profileName !== this._profileName)) ||
        ((this._scope === Scope.GLOBAL)  && (msg.branch !== this._branch))) {
      return;
    }

    if (!this._observers.hasOwnProperty(msg.key)) {
      return;
    }

    let observers = this._observers[msg.key];
    for (let obs of observers) {
      obs.observe(obs, msg.key, msg.value);
    }
  },

  _uninstallAndroidListener: function _uninstallAndroidListener() {
    if (!this._listening)
      return;
    this._listening = false;

    Services.obs.removeObserver(this, "SharedPreferences:Changed");
    Messaging.sendRequest({
      type: "SharedPreferences:Observe",
      enable: false,
      scope: this._scope,
      profileName: this._profileName,
      branch: this._branch,
    });
  },
});
back to top