Bug 1648631: Part 1 - Make L10nRegistryService._synchronizeSharedData() explicitly flush and convert source registration to accept arrays; r=zbraniecki

Setting a key/value pair on the parent process message manager's `sharedData`
initiates a pending runnable that runs at **idle priority**. If the current
thread never gets a chance to idle, then those registry changes will never be
synchronized.

This patch adds an explicit flush so that `_synchronizeSharedData` does indeed
actually synchronize.

We replace the scalar `registerSource`, `updateSource`, and `removeSource` with
variants that accept arrays instead. This allows us to process multiple
registration changes while deferring the synchronization until the entire list
has been processed. The scalar variants were removed to future-proof against
perf issues.

Differential Revision: https://phabricator.services.mozilla.com/D81243
This commit is contained in:
Aaron Klotz 2020-06-26 21:04:21 +00:00
parent 57e09be6aa
commit 391ecb8515

View File

@ -105,12 +105,13 @@ class L10nRegistryService {
// - langpack-{locale}
//
// This should ensure that they're returned in the correct order.
let fileSources = [];
for (let {entry, value} of Services.catMan.enumerateCategory("l10n-registry")) {
if (!this.hasSource(entry)) {
const source = new FileSource(entry, locales, value);
this.registerSource(source);
fileSources.push(new FileSource(entry, locales, value));
}
}
this.registerSources(fileSources);
} else {
this._setSourcesFromSharedData();
Services.cpmm.sharedData.addEventListener("change", this);
@ -201,49 +202,66 @@ class L10nRegistryService {
}
/**
* Adds a new resource source to the L10nRegistry.
* Adds new resource source(s) to the L10nRegistry.
*
* @param {FileSource} source
* Notice: Each invocation of this method flushes any changes out to extant
* content processes, which is expensive. Please coalesce multiple
* registrations into a single sources array and then call this method once.
*
* @param {Array<FileSource>} sources
*/
registerSource(source) {
if (this.hasSource(source.name)) {
throw new Error(`Source with name "${source.name}" already registered.`);
registerSources(sources) {
for (const source of sources) {
if (this.hasSource(source.name)) {
throw new Error(`Source with name "${source.name}" already registered.`);
}
this.sources.set(source.name, source);
}
this.sources.set(source.name, source);
if (isParentProcess) {
if (isParentProcess && sources.length > 0) {
this._synchronizeSharedData();
Services.locale.availableLocales = this.getAvailableLocales();
}
}
/**
* Updates an existing source in the L10nRegistry
* Updates existing sources in the L10nRegistry
*
* That will usually happen when a new version of a source becomes
* available (for example, an updated version of a language pack).
*
* @param {FileSource} source
* Notice: Each invocation of this method flushes any changes out to extant
* content processes, which is expensive. Please coalesce multiple updates
* into a single sources array and then call this method once.
*
* @param {Array<FileSource>} sources
*/
updateSource(source) {
if (!this.hasSource(source.name)) {
throw new Error(`Source with name "${source.name}" is not registered.`);
updateSources(sources) {
for (const source of sources) {
if (!this.hasSource(source.name)) {
throw new Error(`Source with name "${source.name}" is not registered.`);
}
this.sources.set(source.name, source);
}
this.sources.set(source.name, source);
if (isParentProcess) {
if (isParentProcess && sources.length > 0) {
this._synchronizeSharedData();
Services.locale.availableLocales = this.getAvailableLocales();
}
}
/**
* Removes a source from the L10nRegistry.
* Removes sources from the L10nRegistry.
*
* @param {String} sourceId
* Notice: Each invocation of this method flushes any changes out to extant
* content processes, which is expensive. Please coalesce multiple removals
* into a single sourceNames array and then call this method once.
*
* @param {Array<String>} sourceNames
*/
removeSource(sourceName) {
this.sources.delete(sourceName);
if (isParentProcess) {
removeSources(sourceNames) {
for (const sourceName of sourceNames) {
this.sources.delete(sourceName);
}
if (isParentProcess && sourceNames.length > 0) {
this._synchronizeSharedData();
Services.locale.availableLocales = this.getAvailableLocales();
}
@ -260,7 +278,11 @@ class L10nRegistryService {
prePath: source.prePath,
});
}
Services.ppmm.sharedData.set("L10nRegistry:Sources", sources);
let sharedData = Services.ppmm.sharedData;
sharedData.set("L10nRegistry:Sources", sources);
// We must explicitly flush or else flushing won't happen until the main
// thread goes idle.
sharedData.flush();
}
_setSourcesFromSharedData() {
@ -269,17 +291,21 @@ class L10nRegistryService {
console.warn(`[l10nregistry] Failed to fetch sources from shared data.`);
return;
}
let registerSourcesList = [];
for (let [name, data] of sources.entries()) {
if (!this.hasSource(name)) {
const source = new FileSource(name, data.locales, data.prePath);
this.registerSource(source);
registerSourcesList.push(source);
}
}
this.registerSources(registerSourcesList);
let removeSourcesList = [];
for (let name of this.sources.keys()) {
if (!sources.has(name)) {
this.removeSource(name);
removeSourcesList.push(name);
}
}
this.removeSources(removeSourcesList);
}
/**