gecko-dev/toolkit/modules/Troubleshoot.jsm
Nicholas Nethercote f4dd5e2636 Bug 1414096 (attempt 2) - Remove support for nsISupportsString values in nsPrefBranch::{get,set}ComplexValue(). r=florian.
Bug 1345294 introduced nsPrefBranch::{get,set}StringPref(), which allowed the
getting of utf8 strings from prefs, which previously required using
nsISupportsString with {get,set}ComplexValue. That bug also converted most
uses.

This patch finishes the job.

- It removes the nsISupportsString support.

- It converts existing code that relied on the nsISupportsString.

- It removes the lint that was set up to detect such uses of nsISupportsString.

--HG--
extra : rebase_source : b885ee784704819e181430200af5ef762e269d14
2017-11-10 09:07:48 +11:00

731 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 = [
"Troubleshoot",
];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
var Experiments;
try {
Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments;
} catch (e) {
}
const env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
// We use a preferences whitelist to make sure we only show preferences that
// are useful for support and won't compromise the user's privacy. Note that
// entries are *prefixes*: for example, "accessibility." applies to all prefs
// under the "accessibility.*" branch.
const PREFS_WHITELIST = [
"accessibility.",
"apz.",
"browser.cache.",
"browser.display.",
"browser.download.folderList",
"browser.download.hide_plugins_without_extensions",
"browser.download.lastDir.savePerSite",
"browser.download.manager.addToRecentDocs",
"browser.download.manager.alertOnEXEOpen",
"browser.download.manager.resumeOnWakeDelay",
"browser.download.preferred.",
"browser.download.useDownloadDir",
"browser.fixup.",
"browser.history_expire_",
"browser.link.open_newwindow",
"browser.places.",
"browser.privatebrowsing.",
"browser.search.context.loadInBackground",
"browser.search.log",
"browser.search.openintab",
"browser.search.param",
"browser.search.searchEnginesURL",
"browser.search.suggest.enabled",
"browser.search.update",
"browser.search.useDBForOrder",
"browser.sessionstore.",
"browser.startup.homepage",
"browser.tabs.",
"browser.urlbar.",
"browser.zoom.",
"dom.",
"extensions.checkCompatibility",
"extensions.lastAppVersion",
"font.",
"general.autoScroll",
"general.useragent.",
"gfx.",
"html5.",
"image.",
"javascript.",
"keyword.",
"layers.",
"layout.css.dpi",
"layout.css.servo.",
"layout.display-list.",
"media.",
"mousewheel.",
"network.",
"permissions.default.image",
"places.",
"plugin.",
"plugins.",
"print.",
"privacy.",
"security.",
"services.sync.declinedEngines",
"services.sync.lastPing",
"services.sync.lastSync",
"services.sync.numClients",
"services.sync.engine.",
"storage.vacuum.last.",
"svg.",
"toolkit.startup.recent_crashes",
"ui.osk.enabled",
"ui.osk.detect_physical_keyboard",
"ui.osk.require_tablet_mode",
"ui.osk.debug.keyboardDisplayReason",
"webgl.",
];
// The blacklist, unlike the whitelist, is a list of regular expressions.
const PREFS_BLACKLIST = [
/^network[.]proxy[.]/,
/[.]print_to_filename$/,
/^print[.]macosx[.]pagesetup/,
];
// Table of getters for various preference types.
const PREFS_GETTERS = {};
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_STRING] = (prefs, name) => prefs.getStringPref(name);
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_INT] = (prefs, name) => prefs.getIntPref(name);
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_BOOL] = (prefs, name) => prefs.getBoolPref(name);
// Return the preferences filtered by PREFS_BLACKLIST and PREFS_WHITELIST lists
// and also by the custom 'filter'-ing function.
function getPrefList(filter) {
filter = filter || (name => true);
function getPref(name) {
let type = Services.prefs.getPrefType(name);
if (!(type in PREFS_GETTERS))
throw new Error("Unknown preference type " + type + " for " + name);
return PREFS_GETTERS[type](Services.prefs, name);
}
return PREFS_WHITELIST.reduce(function(prefs, branch) {
Services.prefs.getChildList(branch).forEach(function(name) {
if (filter(name) && !PREFS_BLACKLIST.some(re => re.test(name)))
prefs[name] = getPref(name);
});
return prefs;
}, {});
}
this.Troubleshoot = {
/**
* Captures a snapshot of data that may help troubleshooters troubleshoot
* trouble.
*
* @param done A function that will be asynchronously called when the
* snapshot completes. It will be passed the snapshot object.
*/
snapshot: function snapshot(done) {
let snapshot = {};
let numPending = Object.keys(dataProviders).length;
function providerDone(providerName, providerData) {
snapshot[providerName] = providerData;
if (--numPending == 0)
// Ensure that done is always and truly called asynchronously.
Services.tm.dispatchToMainThread(done.bind(null, snapshot));
}
for (let name in dataProviders) {
try {
dataProviders[name](providerDone.bind(null, name));
} catch (err) {
let msg = "Troubleshoot data provider failed: " + name + "\n" + err;
Cu.reportError(msg);
providerDone(name, msg);
}
}
},
kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days
};
// Each data provider is a name => function mapping. When a snapshot is
// captured, each provider's function is called, and it's the function's job to
// generate the provider's data. The function is passed a "done" callback, and
// when done, it must pass its data to the callback. The resulting snapshot
// object will contain a name => data entry for each provider.
var dataProviders = {
application: function application(done) {
let sysInfo = Cc["@mozilla.org/system-info;1"].
getService(Ci.nsIPropertyBag2);
let data = {
name: Services.appinfo.name,
osVersion: sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"),
version: AppConstants.MOZ_APP_VERSION_DISPLAY,
buildID: Services.appinfo.appBuildID,
userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].
getService(Ci.nsIHttpProtocolHandler).
userAgent,
safeMode: Services.appinfo.inSafeMode,
};
if (AppConstants.MOZ_UPDATER)
data.updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
// eslint-disable-next-line mozilla/use-default-preference-values
try {
data.vendor = Services.prefs.getCharPref("app.support.vendor");
} catch (e) {}
let urlFormatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
getService(Ci.nsIURLFormatter);
try {
data.supportURL = urlFormatter.formatURLPref("app.support.baseURL");
} catch (e) {}
data.numTotalWindows = 0;
data.numRemoteWindows = 0;
let winEnumer = Services.wm.getEnumerator("navigator:browser");
while (winEnumer.hasMoreElements()) {
data.numTotalWindows++;
let remote = winEnumer.getNext().
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsILoadContext).
useRemoteTabs;
if (remote) {
data.numRemoteWindows++;
}
}
data.remoteAutoStart = Services.appinfo.browserTabsRemoteAutostart;
// Services.ppmm.childCount is a count of how many processes currently
// exist that might respond to messages sent through the ppmm, including
// the parent process. So we subtract the parent process with the "- 1",
// and thats how many content processes were waiting for.
data.currentContentProcesses = Services.ppmm.childCount - 1;
data.maxContentProcesses = Services.appinfo.maxWebProcessCount;
try {
let e10sStatus = Cc["@mozilla.org/supports-PRUint64;1"]
.createInstance(Ci.nsISupportsPRUint64);
let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
appinfo.observe(e10sStatus, "getE10SBlocked", "");
data.autoStartStatus = e10sStatus.data;
} catch (e) {
data.autoStartStatus = -1;
}
data.styloBuild = AppConstants.MOZ_STYLO;
data.styloDefault = Services.prefs.getDefaultBranch(null)
.getBoolPref("layout.css.servo.enabled", false);
data.styloResult = false;
// Perhaps a bit redundant in places, but this is easier to compare with the
// the real check in `nsLayoutUtils.cpp` to ensure they test the same way.
if (AppConstants.MOZ_STYLO) {
if (env.get("STYLO_FORCE_ENABLED")) {
data.styloResult = true;
} else if (env.get("STYLO_FORCE_DISABLED")) {
data.styloResult = false;
} else {
data.styloResult =
Services.prefs.getBoolPref("layout.css.servo.enabled", false);
}
}
data.styloChromeDefault =
Services.prefs.getDefaultBranch(null)
.getBoolPref("layout.css.servo.chrome.enabled", false);
data.styloChromeResult = false;
if (data.styloResult) {
let winUtils = Services.wm.getMostRecentWindow("").
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils);
data.styloChromeResult = winUtils.isStyledByServo;
}
const keyGoogle = Services.urlFormatter.formatURL("%GOOGLE_API_KEY%").trim();
data.keyGoogleFound = keyGoogle != "no-google-api-key" && keyGoogle.length > 0;
const keyMozilla = Services.urlFormatter.formatURL("%MOZILLA_API_KEY%").trim();
data.keyMozillaFound = keyMozilla != "no-mozilla-api-key" && keyMozilla.length > 0;
done(data);
},
extensions: function extensions(done) {
AddonManager.getAddonsByTypes(["extension"], function(extensions) {
extensions = extensions.filter(e => !e.isSystem);
extensions.sort(function(a, b) {
if (a.isActive != b.isActive)
return b.isActive ? 1 : -1;
// In some unfortunate cases addon names can be null.
let aname = a.name || null;
let bname = b.name || null;
let lc = aname.localeCompare(bname);
if (lc != 0)
return lc;
if (a.version != b.version)
return a.version > b.version ? 1 : -1;
return 0;
});
let props = ["name", "version", "isActive", "id"];
done(extensions.map(function(ext) {
return props.reduce(function(extData, prop) {
extData[prop] = ext[prop];
return extData;
}, {});
}));
});
},
features: function features(done) {
AddonManager.getAddonsByTypes(["extension"], function(features) {
features = features.filter(f => f.isSystem);
features.sort(function(a, b) {
// In some unfortunate cases addon names can be null.
let aname = a.name || null;
let bname = b.name || null;
let lc = aname.localeCompare(bname);
if (lc != 0)
return lc;
if (a.version != b.version)
return a.version > b.version ? 1 : -1;
return 0;
});
let props = ["name", "version", "id"];
done(features.map(function(f) {
return props.reduce(function(fData, prop) {
fData[prop] = f[prop];
return fData;
}, {});
}));
});
},
experiments: function experiments(done) {
if (Experiments === undefined) {
done([]);
return;
}
// getExperiments promises experiment history
Experiments.instance().getExperiments().then(
experiments => done(experiments),
() => done([])
);
},
modifiedPreferences: function modifiedPreferences(done) {
done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
},
lockedPreferences: function lockedPreferences(done) {
done(getPrefList(name => Services.prefs.prefIsLocked(name)));
},
graphics: function graphics(done) {
function statusMsgForFeature(feature) {
// We return an array because in the tryNewerDriver case we need to
// include the suggested version, which the consumer likely needs to plug
// into a format string from a localization file. Rather than returning
// a string in some cases and an array in others, return an array always.
let msg = [""];
try {
var status = gfxInfo.getFeatureStatus(feature);
} catch (e) {}
switch (status) {
case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE:
case Ci.nsIGfxInfo.FEATURE_DISCOURAGED:
msg = ["blockedGfxCard"];
break;
case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION:
msg = ["blockedOSVersion"];
break;
case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION:
try {
var suggestedDriverVersion =
gfxInfo.getFeatureSuggestedDriverVersion(feature);
} catch (e) {}
msg = suggestedDriverVersion ?
["tryNewerDriver", suggestedDriverVersion] :
["blockedDriver"];
break;
case Ci.nsIGfxInfo.FEATURE_BLOCKED_MISMATCHED_VERSION:
msg = ["blockedMismatchedVersion"];
break;
}
return msg;
}
let data = {};
try {
// nsIGfxInfo may not be implemented on some platforms.
var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
} catch (e) {}
let promises = [];
// done will be called upon all pending promises being resolved.
// add your pending promise to promises when adding new ones.
function completed() {
Promise.all(promises).then(() => done(data));
}
data.numTotalWindows = 0;
data.numAcceleratedWindows = 0;
let winEnumer = Services.ww.getWindowEnumerator();
while (winEnumer.hasMoreElements()) {
let winUtils = winEnumer.getNext().
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils);
try {
// NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
if (winUtils.layerManagerType == "None" || !winUtils.layerManagerRemote) {
continue;
}
data.numTotalWindows++;
data.windowLayerManagerType = winUtils.layerManagerType;
data.windowLayerManagerRemote = winUtils.layerManagerRemote;
data.windowUsingAdvancedLayers = winUtils.usingAdvancedLayers;
} catch (e) {
continue;
}
if (data.windowLayerManagerType != "Basic")
data.numAcceleratedWindows++;
}
// If we had no OMTC windows, report back Basic Layers.
if (!data.windowLayerManagerType) {
data.windowLayerManagerType = "Basic";
data.windowLayerManagerRemote = false;
}
if (!data.numAcceleratedWindows && gfxInfo) {
let win = AppConstants.platform == "win";
let feature = win ? gfxInfo.FEATURE_DIRECT3D_9_LAYERS :
gfxInfo.FEATURE_OPENGL_LAYERS;
data.numAcceleratedWindowsMessage = statusMsgForFeature(feature);
}
if (!gfxInfo) {
completed();
return;
}
// keys are the names of attributes on nsIGfxInfo, values become the names
// of the corresponding properties in our data object. A null value means
// no change. This is needed so that the names of properties in the data
// object are the same as the names of keys in aboutSupport.properties.
let gfxInfoProps = {
adapterDescription: null,
adapterVendorID: null,
adapterDeviceID: null,
adapterSubsysID: null,
adapterRAM: null,
adapterDriver: "adapterDrivers",
adapterDriverVersion: "driverVersion",
adapterDriverDate: "driverDate",
adapterDescription2: null,
adapterVendorID2: null,
adapterDeviceID2: null,
adapterSubsysID2: null,
adapterRAM2: null,
adapterDriver2: "adapterDrivers2",
adapterDriverVersion2: "driverVersion2",
adapterDriverDate2: "driverDate2",
isGPU2Active: null,
D2DEnabled: "direct2DEnabled",
DWriteEnabled: "directWriteEnabled",
DWriteVersion: "directWriteVersion",
cleartypeParameters: "clearTypeParameters",
OffMainThreadPaintEnabled: "offMainThreadPaintEnabled",
};
for (let prop in gfxInfoProps) {
try {
data[gfxInfoProps[prop] || prop] = gfxInfo[prop];
} catch (e) {}
}
if (("direct2DEnabled" in data) && !data.direct2DEnabled)
data.direct2DEnabledMessage =
statusMsgForFeature(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
let doc =
Cc["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Ci.nsIDOMParser)
.parseFromString("<html/>", "text/html");
function GetWebGLInfo(data, keyPrefix, contextType) {
data[keyPrefix + "Renderer"] = "-";
data[keyPrefix + "Version"] = "-";
data[keyPrefix + "DriverExtensions"] = "-";
data[keyPrefix + "Extensions"] = "-";
data[keyPrefix + "WSIInfo"] = "-";
// //
let canvas = doc.createElement("canvas");
canvas.width = 1;
canvas.height = 1;
// //
let creationError = null;
canvas.addEventListener(
"webglcontextcreationerror",
function(e) {
creationError = e.statusMessage;
}
);
let gl = null;
try {
gl = canvas.getContext(contextType);
} catch (e) {
if (!creationError) {
creationError = e.toString();
}
}
if (!gl) {
data[keyPrefix + "Renderer"] = creationError || "(no creation error info)";
return;
}
// //
data[keyPrefix + "Extensions"] = gl.getSupportedExtensions().join(" ");
// //
let ext = gl.getExtension("MOZ_debug");
// This extension is unconditionally available to chrome. No need to check.
let vendor = ext.getParameter(gl.VENDOR);
let renderer = ext.getParameter(gl.RENDERER);
data[keyPrefix + "Renderer"] = vendor + " -- " + renderer;
data[keyPrefix + "Version"] = ext.getParameter(gl.VERSION);
data[keyPrefix + "DriverExtensions"] = ext.getParameter(ext.EXTENSIONS);
data[keyPrefix + "WSIInfo"] = ext.getParameter(ext.WSI_INFO);
// //
// Eagerly free resources.
let loseExt = gl.getExtension("WEBGL_lose_context");
loseExt.loseContext();
}
GetWebGLInfo(data, "webgl1", "webgl");
GetWebGLInfo(data, "webgl2", "webgl2");
let infoInfo = gfxInfo.getInfo();
if (infoInfo)
data.info = infoInfo;
let failureCount = {};
let failureIndices = {};
let failures = gfxInfo.getFailures(failureCount, failureIndices);
if (failures.length) {
data.failures = failures;
if (failureIndices.value.length == failures.length) {
data.indices = failureIndices.value;
}
}
data.featureLog = gfxInfo.getFeatureLog();
data.crashGuards = gfxInfo.getActiveCrashGuards();
completed();
},
media: function media(done) {
function convertDevices(devices) {
if (!devices) {
return undefined;
}
let infos = [];
for (let i = 0; i < devices.length; ++i) {
let device = devices.queryElementAt(i, Ci.nsIAudioDeviceInfo);
infos.push({
name: device.name,
groupId: device.groupId,
vendor: device.vendor,
type: device.type,
state: device.state,
preferred: device.preferred,
supportedFormat: device.supportedFormat,
defaultFormat: device.defaultFormat,
maxChannels: device.maxChannels,
defaultRate: device.defaultRate,
maxRate: device.maxRate,
minRate: device.minRate,
maxLatency: device.maxLatency,
minLatency: device.minLatency
});
}
return infos;
}
let data = {};
let winUtils = Services.wm.getMostRecentWindow("").
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils);
data.currentAudioBackend = winUtils.currentAudioBackend;
data.currentMaxAudioChannels = winUtils.currentMaxAudioChannels;
data.currentPreferredChannelLayout = winUtils.currentPreferredChannelLayout;
data.currentPreferredSampleRate = winUtils.currentPreferredSampleRate;
data.audioOutputDevices = convertDevices(winUtils.audioDevices(Ci.nsIDOMWindowUtils.AUDIO_OUTPUT).
QueryInterface(Ci.nsIArray));
data.audioInputDevices = convertDevices(winUtils.audioDevices(Ci.nsIDOMWindowUtils.AUDIO_INPUT).
QueryInterface(Ci.nsIArray));
done(data);
},
javaScript: function javaScript(done) {
let data = {};
let winEnumer = Services.ww.getWindowEnumerator();
if (winEnumer.hasMoreElements())
data.incrementalGCEnabled = winEnumer.getNext().
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils).
isIncrementalGCEnabled();
done(data);
},
accessibility: function accessibility(done) {
let data = {};
data.isActive = Services.appinfo.accessibilityEnabled;
// eslint-disable-next-line mozilla/use-default-preference-values
try {
data.forceDisabled =
Services.prefs.getIntPref("accessibility.force_disabled");
} catch (e) {}
data.handlerUsed = Services.appinfo.accessibleHandlerUsed;
data.instantiator = Services.appinfo.accessibilityInstantiator;
done(data);
},
libraryVersions: function libraryVersions(done) {
let data = {};
let verInfo = Cc["@mozilla.org/security/nssversion;1"].
getService(Ci.nsINSSVersion);
for (let prop in verInfo) {
let match = /^([^_]+)_((Min)?Version)$/.exec(prop);
if (match) {
let verProp = match[2][0].toLowerCase() + match[2].substr(1);
data[match[1]] = data[match[1]] || {};
data[match[1]][verProp] = verInfo[prop];
}
}
done(data);
},
userJS: function userJS(done) {
let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
userJSFile.append("user.js");
done({
exists: userJSFile.exists() && userJSFile.fileSize > 0,
});
},
intl: function intl(done) {
const osPrefs =
Cc["@mozilla.org/intl/ospreferences;1"].getService(Ci.mozIOSPreferences);
done({
localeService: {
requested: Services.locale.getRequestedLocales(),
available: Services.locale.getAvailableLocales(),
supported: Services.locale.getAppLocalesAsBCP47(),
regionalPrefs: Services.locale.getRegionalPrefsLocales(),
defaultLocale: Services.locale.defaultLocale,
},
osPrefs: {
systemLocales: osPrefs.getSystemLocales(),
regionalPrefsLocales: osPrefs.getRegionalPrefsLocales()
},
});
},
};
if (AppConstants.MOZ_CRASHREPORTER) {
dataProviders.crashes = function crashes(done) {
let CrashReports = Cu.import("resource://gre/modules/CrashReports.jsm").CrashReports;
let reports = CrashReports.getReports();
let now = new Date();
let reportsNew = reports.filter(report => (now - report.date < Troubleshoot.kMaxCrashAge));
let reportsSubmitted = reportsNew.filter(report => (!report.pending));
let reportsPendingCount = reportsNew.length - reportsSubmitted.length;
let data = {submitted: reportsSubmitted, pending: reportsPendingCount};
done(data);
};
}
if (AppConstants.MOZ_SANDBOX) {
dataProviders.sandbox = function sandbox(done) {
let data = {};
if (AppConstants.platform == "linux") {
const keys = ["hasSeccompBPF", "hasSeccompTSync",
"hasPrivilegedUserNamespaces", "hasUserNamespaces",
"canSandboxContent", "canSandboxMedia"];
let sysInfo = Cc["@mozilla.org/system-info;1"].
getService(Ci.nsIPropertyBag2);
for (let key of keys) {
if (sysInfo.hasKey(key)) {
data[key] = sysInfo.getPropertyAsBool(key);
}
}
let reporter = Cc["@mozilla.org/sandbox/syscall-reporter;1"].
getService(Ci.mozISandboxReporter);
const snapshot = reporter.snapshot();
let syscalls = [];
for (let index = snapshot.begin; index < snapshot.end; ++index) {
let report = snapshot.getElement(index);
let { msecAgo, pid, tid, procType, syscall } = report;
let args = [];
for (let i = 0; i < report.numArgs; ++i) {
args.push(report.getArg(i));
}
syscalls.push({ index, msecAgo, pid, tid, procType, syscall, args });
}
data.syscallLog = syscalls;
}
if (AppConstants.MOZ_CONTENT_SANDBOX) {
let sandboxSettings = Cc["@mozilla.org/sandbox/sandbox-settings;1"].
getService(Ci.mozISandboxSettings);
data.contentSandboxLevel =
Services.prefs.getIntPref("security.sandbox.content.level");
data.effectiveContentSandboxLevel =
sandboxSettings.effectiveContentSandboxLevel;
}
done(data);
};
}