Bug 1483038 - Optimize L10nRegistry generator to early exit on missing resources. r=mossop

Optimize L10nRegistry generator to early exit on missing resources.

Differential Revision: https://phabricator.services.mozilla.com/D3394

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Zibi Braniecki 2018-09-04 16:57:52 +00:00
parent 53f1370b5f
commit a7959b6272
3 changed files with 51 additions and 40 deletions

View File

@ -95,26 +95,18 @@ const L10nRegistry = {
const sourcesOrder = Array.from(this.sources.keys()).reverse();
const pseudoNameFromPref = Services.prefs.getStringPref("intl.l10n.pseudo", "");
for (const locale of requestedLangs) {
for (const fetchPromises of generateResourceSetsForLocale(locale, sourcesOrder, resourceIds)) {
const ctx = await Promise.all(fetchPromises).then(
dataSets => {
const ctx = new MessageContext(locale, {
...MSG_CONTEXT_OPTIONS,
transform: PSEUDO_STRATEGIES[pseudoNameFromPref],
});
for (const data of dataSets) {
if (data === null) {
return null;
}
ctx.addResource(data);
}
return ctx;
},
() => null
);
if (ctx !== null) {
yield ctx;
for await (const dataSets of generateResourceSetsForLocale(locale, sourcesOrder, resourceIds)) {
const ctx = new MessageContext(locale, {
...MSG_CONTEXT_OPTIONS,
transform: PSEUDO_STRATEGIES[pseudoNameFromPref],
});
for (const data of dataSets) {
if (data === null) {
return;
}
ctx.addResource(data);
}
yield ctx;
}
}
},
@ -190,7 +182,7 @@ const L10nRegistry = {
* @param {Array} [resolvedOrder]
* @returns {AsyncIterator<MessageContext>}
*/
function* generateResourceSetsForLocale(locale, sourcesOrder, resourceIds, resolvedOrder = []) {
async function* generateResourceSetsForLocale(locale, sourcesOrder, resourceIds, resolvedOrder = []) {
const resolvedLength = resolvedOrder.length;
const resourcesLength = resourceIds.length;
@ -200,18 +192,36 @@ function* generateResourceSetsForLocale(locale, sourcesOrder, resourceIds, resol
for (const sourceName of sourcesOrder) {
const order = resolvedOrder.concat(sourceName);
// We bail only if the hasFile returns a strict false here,
// because for FileSource it may also return undefined, which means
// that we simply don't know if the source contains the file and we'll
// have to perform the I/O to learn.
if (L10nRegistry.sources.get(sourceName).hasFile(locale, resourceIds[resolvedOrder.length]) === false) {
continue;
// We want to bail out early if we know that any of
// the (res)x(source) combinations in the permutation
// are unavailable.
// The combination may have been `undefined` when we
// stepped into this branch, and now is resolved to
// `false`.
//
// If the combination resolved to `false` is the last
// in the resolvedOrder, we want to continue in this
// loop, but if it's somewhere in the middle, we can
// safely bail from the whole branch.
for (let [idx, sourceName] of order.entries()) {
if (L10nRegistry.sources.get(sourceName).hasFile(locale, resourceIds[idx]) === false) {
if (idx === order.length - 1) {
continue;
} else {
return;
}
}
}
// If the number of resolved sources equals the number of resources,
// create the right context and return it if it loads.
if (resolvedLength + 1 === resourcesLength) {
yield generateResourceSet(locale, order, resourceIds);
let dataSet = await generateResourceSet(locale, order, resourceIds);
// Here we check again to see if the newly resolved
// resources returned `false` on any position.
if (!dataSet.includes(false)) {
yield dataSet;
}
} else if (resolvedLength < resourcesLength) {
// otherwise recursively load another generator that walks over the
// partially resolved list of sources.
@ -346,10 +356,10 @@ const PSEUDO_STRATEGIES = {
* @param {Array} resourceIds
* @returns {Promise<MessageContext>}
*/
function generateResourceSet(locale, sourcesOrder, resourceIds) {
return resourceIds.map((resourceId, i) => {
async function generateResourceSet(locale, sourcesOrder, resourceIds) {
return Promise.all(resourceIds.map((resourceId, i) => {
return L10nRegistry.sources.get(sourcesOrder[i]).fetchFile(locale, resourceId);
});
}));
}
/**
@ -420,14 +430,14 @@ class FileSource {
fetchFile(locale, path) {
if (!this.locales.includes(locale)) {
return Promise.reject(`The source has no resources for locale "${locale}"`);
return false;
}
const fullPath = this.getPath(locale, path);
if (this.cache.hasOwnProperty(fullPath)) {
if (this.cache[fullPath] === false) {
return Promise.reject(`The source has no resources for path "${fullPath}"`);
return false;
}
// `true` means that the file is indexed, but hasn't
// been fetched yet.
@ -435,7 +445,7 @@ class FileSource {
return this.cache[fullPath];
}
} else if (this.indexed) {
return Promise.reject(`The source has no resources for path "${fullPath}"`);
return false;
}
return this.cache[fullPath] = L10nRegistry.load(fullPath).then(
data => {
@ -443,7 +453,7 @@ class FileSource {
},
err => {
this.cache[fullPath] = false;
return Promise.reject(err);
return false;
}
);
}

View File

@ -97,7 +97,7 @@ class CachedAsyncIterable extends CachedIterable {
return {
async next() {
if (cached.length <= cur) {
cached.push(await cached.iterator.next());
cached.push(cached.iterator.next());
}
return cached[cur++];
}
@ -114,10 +114,10 @@ class CachedAsyncIterable extends CachedIterable {
let idx = 0;
while (idx++ < count) {
const last = this[this.length - 1];
if (last && last.done) {
if (last && await (last).done) {
break;
}
this.push(await this.iterator.next());
this.push(this.iterator.next());
}
// Return the last cached {value, done} object to allow the calling
// code to decide if it needs to call touchNext again.
@ -322,7 +322,6 @@ class Localization {
onChange() {
this.ctxs = CachedAsyncIterable.from(
this.generateMessages(this.resourceIds));
this.ctxs.touchNext(2);
}
}

View File

@ -1,6 +1,7 @@
{
const { DOMLocalization } =
ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm", {});
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
/**
* Polyfill for document.ready polyfill.
@ -46,8 +47,9 @@
document.l10n = new DOMLocalization(resourceIds);
// Trigger the first two contexts to be loaded eagerly.
document.l10n.ctxs.touchNext(2);
const appLocales = Services.locale.getAppLocalesAsBCP47();
const prefetchCount = appLocales.length > 1 ? 2 : 1;
document.l10n.ctxs.touchNext(prefetchCount);
document.l10n.ready = documentReady().then(() => {
document.l10n.registerObservers();