Bug 553114: Add a list of enabled add-ons to crash reports. r=robstrong

This commit is contained in:
Dave Townsend 2010-04-07 11:05:03 -07:00
Родитель 2a4bb56ec1
Коммит dc19acf501
7 изменённых файлов: 172 добавлений и 17 удалений

Просмотреть файл

@ -61,6 +61,7 @@ const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
const PREF_EM_CHECK_COMPATIBILITY = "extensions.checkCompatibility";
const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
const PREF_EM_UPDATE_URL = "extensions.update.url";
const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons";
const PREF_XPI_ENABLED = "xpinstall.enabled";
const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
@ -768,6 +769,8 @@ var XPIProvider = {
bootstrappedAddons: {},
// A dictionary of JS scopes of loaded bootstrappable add-ons by ID
bootstrapScopes: {},
// A string listing the enabled add-ons for annotating crash reports
enabledAddons: null,
/**
* Starts the XPI provider initializes the install locations and prefs.
@ -821,6 +824,20 @@ var XPIProvider = {
true)
this.checkUpdateSecurity = Prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY,
true)
this.enabledAddons = Prefs.getCharPref(PREF_EM_ENABLED_ADDONS, "");
if ("nsICrashReporter" in Ci &&
Services.appinfo instanceof Ci.nsICrashReporter) {
// Annotate the crash report with relevant add-on information.
try {
Services.appinfo.annotateCrashReport("Theme", this.selectedSkin);
} catch (e) { }
try {
Services.appinfo.annotateCrashReport("EMCheckCompatibility",
this.checkCompatibility);
} catch (e) { }
this.addAddonsToCrashReporter();
}
Services.prefs.addObserver(this.checkCompatibilityPref, this, false);
Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false);
@ -846,6 +863,24 @@ var XPIProvider = {
this.installLocationsByName = null;
},
/**
* Adds a list of currently active add-ons to the next crash report.
*/
addAddonsToCrashReporter: function XPI_addAddonsToCrashReporter() {
if (!("nsICrashReporter" in Ci) ||
!(Services.appinfo instanceof Ci.nsICrashReporter))
return;
let data = this.enabledAddons;
for (let id in this.bootstrappedAddons)
data += (data ? "," : "") + id + ":" + this.bootstrappedAddons[id].version;
try {
Services.appinfo.annotateCrashReport("Add-ons", data);
}
catch (e) { }
},
/**
* Gets the add-on states for an install location.
*
@ -1124,7 +1159,10 @@ var XPIProvider = {
!oldAddon.userDisabled) {
oldAddon.active = true;
XPIDatabase.updateAddonActive(oldAddon);
XPIProvider.bootstrappedAddons[oldAddon.id] = addonState.descriptor;
XPIProvider.bootstrappedAddons[oldAddon.id] = {
version: oldAddon.version,
descriptor: addonState.descriptor
};
}
else {
// Otherwise a restart is necessary
@ -1153,10 +1191,15 @@ var XPIProvider = {
// appDisabled.
oldAddon.active = !oldAddon.appDisabled;
XPIDatabase.updateAddonActive(oldAddon);
if (oldAddon.active)
XPIProvider.bootstrappedAddons[oldAddon.id] = addonState.descriptor;
else
if (oldAddon.active) {
XPIProvider.bootstrappedAddons[oldAddon.id] = {
version: oldAddon.version,
descriptor: addonState.descriptor
};
}
else {
delete XPIProvider.bootstrappedAddons[oldAddon.id];
}
}
else {
changed = true;
@ -1261,7 +1304,10 @@ var XPIProvider = {
if (newAddon.type != "bootstrapped")
return true;
XPIProvider.bootstrappedAddons[newAddon.id] = addonState.descriptor;
XPIProvider.bootstrappedAddons[newAddon.id] = {
version: newAddon.version,
descriptor: addonState.descriptor
};
}
return false;
@ -1417,8 +1463,8 @@ var XPIProvider = {
this.bootstrappedAddons = {};
for (let id in bootstrappedAddons) {
let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
dir.persistentDescriptor = bootstrappedAddons[id];
this.activateAddon(id, dir, true, false);
dir.persistentDescriptor = bootstrappedAddons[id].descriptor;
this.activateAddon(id, bootstrappedAddons[id].version, dir, true, false);
}
// Let these shutdown a little earlier when they still have access to most
@ -1788,6 +1834,8 @@ var XPIProvider = {
*
* @param id
* The ID of the add-on being activated
* @param version
* The version of the add-on being activated
* @param dir
* The directory containing the add-on
* @param startup
@ -1795,7 +1843,7 @@ var XPIProvider = {
* @param install
* true if the add-on is being activated during installation
*/
activateAddon: function XPI_activateAddon(id, dir, startup, install) {
activateAddon: function XPI_activateAddon(id, version, dir, startup, install) {
let methods = ["enable"];
if (startup)
methods.unshift("startup");
@ -1811,17 +1859,22 @@ var XPIProvider = {
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
createInstance(Ci.mozIJSSubScriptLoader);
// Mark the add-on as active for the crash reporter before loading
this.bootstrappedAddons[id] = {
version: version,
descriptor: dir.persistentDescriptor
};
this.bootstrapScopes[id] = scope;
this.addAddonsToCrashReporter();
try {
loader.loadSubScript(uri.spec, scope);
}
catch (e) {
WARN("Error loading bootstrap.js for " + id + ": " + e);
return;
}
this.callBootstrapMethod(id, scope, methods);
this.bootstrappedAddons[id] = dir.persistentDescriptor;
this.bootstrapScopes[id] = scope;
}
else {
WARN("Bootstrap missing for " + id);
@ -1855,6 +1908,8 @@ var XPIProvider = {
if (uninstall)
methods.unshift("uninstall");
this.callBootstrapMethod(id, scope, methods);
this.addAddonsToCrashReporter();
},
/**
@ -1938,7 +1993,7 @@ var XPIProvider = {
else {
if (addon.type == "bootstrapped") {
let dir = addon._installLocation.getLocationForID(addon.id);
this.activateAddon(addon.id, dir, false, false);
this.activateAddon(addon.id, addon.version, dir, false, false);
}
AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
}
@ -3029,13 +3084,16 @@ var XPIDatabase = {
let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
true);
let enabledAddons = [];
let text = "[ExtensionDirs]\r\n";
let count = 0;
let stmt = this.getStatement("getActiveAddons");
for (let row in resultRows(stmt))
for (let row in resultRows(stmt)) {
text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
enabledAddons.push(row.id + ":" + row.version);
}
// The selected skin may come from an inactive theme (the default theme
// when a lightweight theme is applied for example)
@ -3043,12 +3101,16 @@ var XPIDatabase = {
stmt = this.getStatement("getActiveTheme");
stmt.params.internalName = XPIProvider.selectedSkin;
count = 0;
for (let row in resultRows(stmt))
for (let row in resultRows(stmt)) {
text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
enabledAddons.push(row.id + ":" + row.version);
}
var fos = FileUtils.openSafeFileOutputStream(addonsList);
fos.write(text, text.length);
FileUtils.closeSafeFileOutputStream(fos);
Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(","));
}
};
@ -3683,7 +3745,7 @@ AddonInstall.prototype = {
function(a) {
self.addon = a;
if (self.addon.active && self.addon.type == "bootstrapped")
XPIProvider.activateAddon(self.addon.id, dir, false, true);
XPIProvider.activateAddon(self.addon.id, self.addon.version, dir, false, true);
AddonManagerPrivate.callAddonListeners("onInstalled",
createWrapper(self.addon));

Просмотреть файл

@ -37,8 +37,16 @@ function createAppInfo(id, name, version, platformVersion) {
// Do nothing
},
// nsICrashReporter
annotations: {},
annotateCrashReport: function(key, data) {
this.annotations[key] = data;
},
QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIXULAppInfo,
AM_Ci.nsIXULRuntime,
AM_Ci.nsICrashReporter,
AM_Ci.nsISupports])
};
@ -54,6 +62,50 @@ function createAppInfo(id, name, version, platformVersion) {
XULAPPINFO_CONTRACTID, XULAppInfoFactory);
}
/**
* Tests that an add-on does appear in the crash report annotations, if
* crash reporting is enabled. The test will fail if the add-on is not in the
* annotation.
* @param id
* The ID of the add-on
* @param version
* The version of the add-on
*/
function do_check_in_crash_annotation(id, version) {
if (!("nsICrashReporter" in AM_Ci))
return;
if (!("Add-ons" in gAppInfo.annotations)) {
do_check_false(true);
return;
}
let addons = gAppInfo.annotations["Add-ons"].split(",");
do_check_false(addons.indexOf(id + ":" + version) < 0);
}
/**
* Tests that an add-on does not appear in the crash report annotations, if
* crash reporting is enabled. The test will fail if the add-on is in the
* annotation.
* @param id
* The ID of the add-on
* @param version
* The version of the add-on
*/
function do_check_not_in_crash_annotation(id, version) {
if (!("nsICrashReporter" in AM_Ci))
return;
if (!("Add-ons" in gAppInfo.annotations)) {
do_check_true(true);
return;
}
let addons = gAppInfo.annotations["Add-ons"].split(",");
do_check_true(addons.indexOf(id + ":" + version) < 0);
}
/**
* Returns a testcase xpi
*
@ -146,6 +198,9 @@ function shutdownManager() {
obs.notifyObservers(null, "quit-application-granted", null);
gInternalManager.observe(null, "xpcom-shutdown", null);
gInternalManager = null;
// Clear any crash report annotations
gAppInfo.annotations = {};
}
function loadAddonsList(appChanged) {

Просмотреть файл

@ -40,6 +40,7 @@ function run_test_1() {
do_check_true(install.addon.hasResource("install.rdf"));
do_check_true(install.addon.hasResource("bootstrap.js"));
do_check_false(install.addon.hasResource("foo.bar"));
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
prepare_test({
"bootstrap1@tests.mozilla.org": [
@ -65,6 +66,7 @@ function check_test_1() {
do_check_true(b1.hasResource("install.rdf"));
do_check_true(b1.hasResource("bootstrap.js"));
do_check_false(b1.hasResource("foo.bar"));
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
let dir = profileDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
@ -99,6 +101,7 @@ function run_test_2() {
do_check_true(b1.userDisabled);
do_check_false(b1.isActive);
do_check_eq(getActivatedVersion(), 0);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
AddonManager.getAddon("bootstrap1@tests.mozilla.org", function(newb1) {
do_check_neq(newb1, null);
@ -118,6 +121,7 @@ function run_test_3() {
do_check_eq(getActivatedVersion(), 0);
startupManager(0, false);
do_check_eq(getActivatedVersion(), 0);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
AddonManager.getAddon("bootstrap1@tests.mozilla.org", function(b1) {
do_check_neq(b1, null);
@ -149,6 +153,7 @@ function run_test_4() {
do_check_false(b1.userDisabled);
do_check_true(b1.isActive);
do_check_eq(getActivatedVersion(), 1);
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
AddonManager.getAddon("bootstrap1@tests.mozilla.org", function(newb1) {
do_check_neq(newb1, null);
@ -166,8 +171,10 @@ function run_test_4() {
function run_test_5() {
shutdownManager();
do_check_eq(getActivatedVersion(), 0);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
startupManager(0, false);
do_check_eq(getActivatedVersion(), 1);
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
AddonManager.getAddon("bootstrap1@tests.mozilla.org", function(b1) {
do_check_neq(b1, null);
@ -217,6 +224,8 @@ function check_test_6() {
do_check_false(b1.userDisabled);
do_check_true(b1.isActive);
do_check_eq(getActivatedVersion(), 2);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0");
run_test_7();
});
@ -241,6 +250,7 @@ function run_test_7() {
function check_test_7() {
ensure_test_completed();
do_check_eq(getActivatedVersion(), 0);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0");
AddonManager.getAddon("bootstrap1@tests.mozilla.org", function(b1) {
do_check_eq(b1, null);
@ -282,6 +292,7 @@ function run_test_8() {
do_check_false(b1.userDisabled);
do_check_true(b1.isActive);
do_check_eq(getActivatedVersion(), 1);
do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
run_test_9();
});
@ -298,6 +309,7 @@ function run_test_9() {
AddonManager.getAddon("bootstrap1@tests.mozilla.org", function(b1) {
do_check_eq(b1, null);
do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0");
do_test_finished();
});

Просмотреть файл

@ -28,6 +28,7 @@ function run_test() {
AddonManager.getAddon("addon1@tests.mozilla.org", function(a1) {
do_check_eq(a1, null);
do_check_not_in_crash_annotation(addon1.id, addon1.version);
var dest = profileDir.clone();
dest.append("addon1@tests.mozilla.org");
@ -43,6 +44,7 @@ function run_test() {
do_check_true(isExtensionInAddonsList(profileDir, newa1.id));
do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE));
do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE));
do_check_in_crash_annotation(addon1.id, addon1.version);
run_test_1();
});
@ -61,6 +63,7 @@ function run_test_1() {
a1.userDisabled = true;
do_check_false(hasFlag(a1.permissions, AddonManager.PERM_CAN_DISABLE));
do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_ENABLE));
do_check_in_crash_annotation(addon1.id, addon1.version);
ensure_test_completed();
@ -78,6 +81,7 @@ function run_test_1() {
do_check_false(isExtensionInAddonsList(profileDir, newa1.id));
do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE));
do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE));
do_check_not_in_crash_annotation(addon1.id, addon1.version);
run_test_2();
});
@ -113,6 +117,7 @@ function run_test_2() {
do_check_true(isExtensionInAddonsList(profileDir, newa1.id));
do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE));
do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE));
do_check_in_crash_annotation(addon1.id, addon1.version);
run_test_3();
});

Просмотреть файл

@ -108,6 +108,7 @@ function check_test_1() {
do_check_eq(a1.name, "Test 1");
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_true(do_get_addon("test_install1").exists());
do_check_in_crash_annotation(a1.id, a1.version);
// Should have been installed sometime in the last two second.
let difference = Date.now() - a1.installDate.getTime();
@ -129,6 +130,7 @@ function check_test_1() {
a1.uninstall();
restartManager(0);
do_check_not_in_crash_annotation(a1.id, a1.version);
run_test_2();
});
@ -209,6 +211,7 @@ function check_test_3() {
do_check_eq(a2.name, "Real Test 2");
do_check_true(isExtensionInAddonsList(profileDir, a2.id));
do_check_true(do_get_addon("test_install2_1").exists());
do_check_in_crash_annotation(a2.id, a2.version);
// Should have been installed sometime in the last two second.
let difference = Date.now() - a2.installDate.getTime();
@ -306,6 +309,7 @@ function check_test_5(install) {
do_check_true(a2.isActive);
do_check_true(isExtensionInAddonsList(profileDir, a2.id));
do_check_true(do_get_addon("test_install2_2").exists());
do_check_in_crash_annotation(a2.id, a2.version);
do_check_eq(a2.installDate.getTime(), gInstallDate);
// Update date should be later (or the same if this test is too fast)

Просмотреть файл

@ -94,8 +94,11 @@ function run_test() {
function([a1, a2, a3, a4, a5]) {
do_check_eq(a1, null);
do_check_not_in_crash_annotation(addon1.id, addon1.version);
do_check_eq(a2, null);
do_check_not_in_crash_annotation(addon2.id, addon2.version);
do_check_eq(a3, null);
do_check_not_in_crash_annotation(addon3.id, addon3.version);
do_check_eq(a4, null);
do_check_eq(a5, null);
@ -142,6 +145,7 @@ function run_test_1() {
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon1.id, addon1.version);
do_check_neq(a2, null);
do_check_eq(a2.id, "addon2@tests.mozilla.org");
@ -150,6 +154,7 @@ function run_test_1() {
do_check_true(isExtensionInAddonsList(profileDir, a2.id));
do_check_true(hasFlag(a2.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_true(hasFlag(a2.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon2.id, addon2.version);
do_check_neq(a3, null);
do_check_eq(a3.id, "addon3@tests.mozilla.org");
@ -158,6 +163,7 @@ function run_test_1() {
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
do_check_true(hasFlag(a3.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_true(hasFlag(a3.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon3.id, addon3.version);
do_check_eq(a4, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon4@tests.mozilla.org"));
@ -220,6 +226,7 @@ function run_test_2() {
do_check_false(isExtensionInAddonsList(userDir, a1.id));
do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon1.id, a1.version);
do_check_neq(a2, null);
do_check_eq(a2.id, "addon2@tests.mozilla.org");
@ -229,9 +236,11 @@ function run_test_2() {
do_check_false(isExtensionInAddonsList(globalDir, a2.id));
do_check_true(hasFlag(a2.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_true(hasFlag(a2.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon2.id, a2.version);
do_check_eq(a3, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon3@tests.mozilla.org"));
do_check_not_in_crash_annotation(addon3.id, addon3.version);
do_check_eq(a4, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon4@tests.mozilla.org"));
@ -271,6 +280,7 @@ function run_test_3() {
do_check_true(isExtensionInAddonsList(userDir, a1.id));
do_check_false(hasFlag(a1.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_false(hasFlag(a1.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon1.id, a1.version);
do_check_neq(a2, null);
do_check_eq(a2.id, "addon2@tests.mozilla.org");
@ -280,6 +290,7 @@ function run_test_3() {
do_check_false(isExtensionInAddonsList(globalDir, a2.id));
do_check_false(hasFlag(a2.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_false(hasFlag(a2.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon2.id, a2.version);
do_check_eq(a3, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon3@tests.mozilla.org"));
@ -324,6 +335,7 @@ function run_test_4() {
do_check_false(isExtensionInAddonsList(userDir, a1.id));
do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon1.id, a1.version);
do_check_neq(a2, null);
do_check_eq(a2.id, "addon2@tests.mozilla.org");
@ -333,6 +345,7 @@ function run_test_4() {
do_check_true(isExtensionInAddonsList(globalDir, a2.id));
do_check_false(hasFlag(a2.permissions, AddonManager.PERM_CAN_UNINSTALL));
do_check_false(hasFlag(a2.permissions, AddonManager.PERM_CAN_UPGRADE));
do_check_in_crash_annotation(addon2.id, a2.version);
do_check_eq(a3, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon3@tests.mozilla.org"));
@ -487,6 +500,8 @@ function run_test_7() {
do_check_false(isExtensionInAddonsList(globalDir, "addon3@tests.mozilla.org"));
do_check_false(isExtensionInAddonsList(globalDir, "addon4@tests.mozilla.org"));
do_check_false(isExtensionInAddonsList(globalDir, "addon5@tests.mozilla.org"));
do_check_not_in_crash_annotation(addon1.id, addon1.version);
do_check_not_in_crash_annotation(addon2.id, addon2.version);
end_test();
});

Просмотреть файл

@ -18,8 +18,7 @@ var addon1 = {
const profileDir = gProfD.clone();
profileDir.append("extensions");
// Sets up the profile by installing a couple of add-ons. One is dependent on
// the other.
// Sets up the profile by installing an add-on.
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
@ -41,6 +40,7 @@ function run_test() {
do_check_false(a1.userDisabled);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_eq(a1.pendingOperations, 0);
do_check_in_crash_annotation(addon1.id, addon1.version);
run_test_1();
});
@ -62,6 +62,7 @@ function run_test_1() {
do_check_eq(a1.pendingOperations, 0);
a1.uninstall();
do_check_true(hasFlag(a1.pendingOperations, AddonManager.PENDING_UNINSTALL));
do_check_in_crash_annotation(addon1.id, addon1.version);
ensure_test_completed();
@ -80,6 +81,7 @@ function check_test_1() {
AddonManager.getAddon("addon1@tests.mozilla.org", function(a1) {
do_check_eq(a1, null);
do_check_false(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org"));
do_check_not_in_crash_annotation(addon1.id, addon1.version);
var dest = profileDir.clone();
dest.append("addon1@tests.mozilla.org");