chat/modules/imXPCOMUtils.jsm
author Richard Marti <richard.marti@gmail.com>
Sat, 28 Sep 2019 14:45:02 +0200
changeset 35894 786073dcb3052a788c29ab438a196b4aa6fb7906
parent 35824 fda8338f7b7782a9a2ffcc8e260290395b055858
permissions -rw-r--r--
Bug 1584697 - Don't apply the -moz-image-region to the dropmarker to not virtually extent the menulist. r+a=jorgk

/* 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/. */

this.EXPORTED_SYMBOLS = [
  "XPCOMUtils",
  "setTimeout",
  "clearTimeout",
  "executeSoon",
  "nsSimpleEnumerator",
  "EmptyEnumerator",
  "ClassInfo",
  "l10nHelper",
  "initLogModule",
];

const { XPCOMUtils } = ChromeUtils.import(
  "resource://gre/modules/XPCOMUtils.jsm"
);
const { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");

var kLogLevelPref = "purple.debug.loglevel";

/**
 * Creates an nsIScriptError instance and logs it.
 *
 * @param aModule
 *        string identifying the module within which the error occurred.
 * @param aLevel
 *        the error level as defined in imIDebugMessage.
 * @param aMessage
 *        the error message string.
 * @param aOriginalError
 *        (optional) JS Error object containing the location where the
 *        actual error occurred. Its error message is appended to aMessage.
 */
function scriptError(aModule, aLevel, aMessage, aOriginalError) {
  // Figure out the log level, based on the module and the prefs set.
  // The module name is split on periods, and if no pref is set the pref with
  // the last section removed is attempted (until no sections are left, using
  // the global default log level).
  let logLevel = -1;
  let logKeys = ["level"].concat(aModule.split("."));
  for (; logKeys.length > 0; logKeys.pop()) {
    let logKey = logKeys.join(".");
    if (logKey in gLogLevels) {
      logLevel = gLogLevels[logKey];
      break;
    }
  }

  // Only continue if we will log this message.
  if (logLevel > aLevel && !("imAccount" in this)) {
    return;
  }

  let flag = Ci.nsIScriptError.warningFlag;
  if (aLevel >= Ci.imIDebugMessage.LEVEL_ERROR) {
    flag = Ci.nsIScriptError.errorFlag;
  }

  let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
    Ci.nsIScriptError
  );
  let caller = Components.stack.caller;
  let sourceLine = aModule || caller.sourceLine;
  if (caller.name) {
    if (sourceLine) {
      sourceLine += ": ";
    }
    sourceLine += caller.name;
  }
  let fileName = caller.filename;
  let lineNumber = caller.lineNumber;
  if (aOriginalError) {
    aMessage += "\n" + (aOriginalError.message || aOriginalError);
    if (aOriginalError.fileName) {
      fileName = aOriginalError.fileName;
    }
    if (aOriginalError.lineNumber) {
      lineNumber = aOriginalError.lineNumber;
    }
  }
  scriptError.init(
    aMessage,
    fileName,
    sourceLine,
    lineNumber,
    null,
    flag,
    "component javascript"
  );

  if (logLevel <= aLevel) {
    dump(aModule + ": " + aMessage + "\n");
    if (aLevel == Ci.imIDebugMessage.LEVEL_LOG && logLevel == aLevel) {
      Services.console.logStringMessage(aMessage);
    } else {
      Services.console.logMessage(scriptError);
    }
  }
  if ("imAccount" in this) {
    this.imAccount.logDebugMessage(scriptError, aLevel);
  }
}
function initLogModule(aModule, aObj = {}) {
  aObj.DEBUG = scriptError.bind(aObj, aModule, Ci.imIDebugMessage.LEVEL_DEBUG);
  aObj.LOG = scriptError.bind(aObj, aModule, Ci.imIDebugMessage.LEVEL_LOG);
  aObj.WARN = scriptError.bind(aObj, aModule, Ci.imIDebugMessage.LEVEL_WARNING);
  aObj.ERROR = scriptError.bind(aObj, aModule, Ci.imIDebugMessage.LEVEL_ERROR);
  return aObj;
}
XPCOMUtils.defineLazyGetter(this, "gLogLevels", function() {
  // This object functions both as an obsever as well as a dict keeping the
  // log levels with prefs; the log levels all start with "level" (i.e. "level"
  // for the global level, "level.irc" for the IRC module).  The dual-purpose
  // is necessary to make sure the observe is left alive while being a weak ref
  // to avoid cycles with the pref service.
  let logLevels = {
    observe(aSubject, aTopic, aData) {
      let module = "level" + aData.substr(kLogLevelPref.length);
      if (Services.prefs.getPrefType(aData) == Services.prefs.PREF_INT) {
        gLogLevels[module] = Services.prefs.getIntPref(aData);
      } else {
        delete gLogLevels[module];
      }
    },
    QueryInterface: ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]),
  };

  // Add weak pref observer to see log level pref changes.
  Services.prefs.addObserver(kLogLevelPref, logLevels, true /* weak */);

  // Initialize with existing log level prefs.
  for (let pref of Services.prefs.getChildList(kLogLevelPref)) {
    if (Services.prefs.getPrefType(pref) == Services.prefs.PREF_INT) {
      logLevels[
        "level" + pref.substr(kLogLevelPref.length)
      ] = Services.prefs.getIntPref(pref);
    }
  }

  // Let environment variables override prefs.
  Cc["@mozilla.org/process/environment;1"]
    .getService(Ci.nsIEnvironment)
    .get("PRPL_LOG")
    .split(/[;,]/)
    .filter(n => n != "")
    .forEach(function(env) {
      let [, module, level] = env.match(/(?:(.*?)[:=])?(\d+)/);
      logLevels["level" + (module ? "." + module : "")] = parseInt(level, 10);
    });

  return logLevels;
});

function setTimeout(aFunction, aDelay) {
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  let args = Array.prototype.slice.call(arguments, 2);
  // A reference to the timer should be kept to ensure it won't be
  // GC'ed before firing the callback.
  let callback = {
    _timer: timer,
    notify(aTimer) {
      aFunction.apply(null, args);
      delete this._timer;
    },
  };
  timer.initWithCallback(callback, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
  return timer;
}
function clearTimeout(aTimer) {
  if (aTimer) {
    aTimer.cancel();
  }
}

function executeSoon(aFunction) {
  Services.tm.mainThread.dispatch(aFunction, Ci.nsIEventTarget.DISPATCH_NORMAL);
}

/* Common nsIClassInfo and QueryInterface implementation
 * shared by all generic objects implemented in this file. */
function ClassInfo(aInterfaces, aDescription = "JS Proto Object") {
  if (!(this instanceof ClassInfo)) {
    return new ClassInfo(aInterfaces, aDescription);
  }

  if (!Array.isArray(aInterfaces)) {
    aInterfaces = [aInterfaces];
  }

  for (let i of aInterfaces) {
    if (typeof i == "string" && !(i in Ci)) {
      Services.console.logStringMessage("ClassInfo: unknown interface " + i);
    }
  }

  this._interfaces = aInterfaces.map(i => (typeof i == "string" ? Ci[i] : i));

  this.classDescription = aDescription;
}
ClassInfo.prototype = {
  // eslint-disable-next-line mozilla/use-chromeutils-generateqi
  QueryInterface(iid) {
    if (
      iid.equals(Ci.nsISupports) ||
      iid.equals(Ci.nsIClassInfo) ||
      this._interfaces.some(i => i.equals(iid))
    ) {
      return this;
    }

    throw Cr.NS_ERROR_NO_INTERFACE;
  },
  get interfaces() {
    return [Ci.nsIClassInfo, Ci.nsISupports].concat(this._interfaces);
  },
  getScriptableHelper: () => null,
  contractID: null,
  classID: null,
  flags: 0,
};

function l10nHelper(aChromeURL) {
  let bundle = Services.strings.createBundle(aChromeURL);
  return function(aStringId) {
    try {
      if (arguments.length == 1) {
        return bundle.GetStringFromName(aStringId);
      }
      return bundle.formatStringFromName(
        aStringId,
        Array.prototype.slice.call(arguments, 1),
        arguments.length - 1
      );
    } catch (e) {
      Cu.reportError(e);
      dump("Failed to get " + aStringId + "\n");
      return aStringId;
    }
  };
}

/**
 * Constructs an nsISimpleEnumerator for the given array of items.
 * Copied from netwerk/test/httpserver/httpd.js
 *
 * @param items : Array
 *   the items, which must all implement nsISupports
 */
function nsSimpleEnumerator(items) {
  this._items = items;
  this._nextIndex = 0;
}
nsSimpleEnumerator.prototype = {
  hasMoreElements() {
    return this._nextIndex < this._items.length;
  },
  getNext() {
    if (!this.hasMoreElements()) {
      throw Cr.NS_ERROR_NOT_AVAILABLE;
    }

    return this._items[this._nextIndex++];
  },
  QueryInterface: ChromeUtils.generateQI([Ci.nsISimpleEnumerator]),
  [Symbol.iterator]() {
    return this._items.values();
  },
};

var EmptyEnumerator = {
  hasMoreElements: () => false,
  getNext() {
    throw Cr.NS_ERROR_NOT_AVAILABLE;
  },
  QueryInterface: ChromeUtils.generateQI([Ci.nsISimpleEnumerator]),
  *[Symbol.iterator]() {},
};