diff --git a/src/app/scene.js b/src/app/scene.js index 0a61ea7c6..5821ed23e 100644 --- a/src/app/scene.js +++ b/src/app/scene.js @@ -39,15 +39,27 @@ export async function createApplicationScene({ signal: layerSession?.lifetime.signal ?? signal, }); if (layerSession) layerSession.operations = operations; - defer(initLogoGaze()); - const previousKey = window.__GOOGLE_MAPS_API_KEY__; - if (googleApiKey) { - window.__GOOGLE_MAPS_API_KEY__ = googleApiKey; - defer(() => { - if (window.__GOOGLE_MAPS_API_KEY__ !== googleApiKey) return; - if (previousKey === undefined) delete window.__GOOGLE_MAPS_API_KEY__; - else window.__GOOGLE_MAPS_API_KEY__ = previousKey; - }); + // The logo's gaze and the page's Google key belong to the page session + // when there is one: an engine switch keeps them. + const pageChrome = layerSession ? (layerSession.chrome ??= {}) : null; + const own = pageChrome + ? (release) => + layerSession.lifetime.signal.addEventListener('abort', release, { + once: true, + }) + : defer; + if (!pageChrome?.sceneChrome) { + if (pageChrome) pageChrome.sceneChrome = true; + own(initLogoGaze()); + const previousKey = window.__GOOGLE_MAPS_API_KEY__; + if (googleApiKey) { + window.__GOOGLE_MAPS_API_KEY__ = googleApiKey; + own(() => { + if (window.__GOOGLE_MAPS_API_KEY__ !== googleApiKey) return; + if (previousKey === undefined) delete window.__GOOGLE_MAPS_API_KEY__; + else window.__GOOGLE_MAPS_API_KEY__ = previousKey; + }); + } } // A cached renderer is built once, by the first application that asks for // its engine, and later applications reuse it warm. Everything this closure diff --git a/src/standalone/application.js b/src/standalone/application.js index 4bbf0f405..2aac4918c 100644 --- a/src/standalone/application.js +++ b/src/standalone/application.js @@ -31,6 +31,11 @@ export function pageEngineKind() { return resolveEngineKind({ search: globalThis.location?.search, storage }); } +/** The page-session state shared by every application on that session. */ +function sessionChrome(layerSession) { + return (layerSession.chrome ??= {}); +} + /** Compose one engine session. Destroy it before starting another. */ export function createStandaloneApplication({ googleApiKey, @@ -64,22 +69,38 @@ export function createStandaloneApplication({ let catalog; const application = createApplication({ createScene: async (context) => { + // The page's language listener and place search belong to the page + // session when there is one (an engine switch keeps them), else to + // this application. + const session = layerSession ? sessionChrome(layerSession) : null; // A language switch applies in place; the page's own markup follows. - context.defer( - onLocaleChange(() => { + if (!session?.locale) { + const offLocale = onLocaleChange(() => { applyDocumentLanguage(document, getLocale()); applyDocumentTranslations(document); - }), - ); - placeSearch = createStandalonePlaceSearch({ - // The bundled city and landmark data the offline name provider reads. - // The search package takes it as plain data rather than importing it, - // so it stays free of application state. - presets: CITY_POIS, - ...geospatial, - resolveApiKey: () => googleApiKey, - signal: context.signal, - }); + }); + if (session) { + session.locale = offLocale; + layerSession.lifetime.signal.addEventListener('abort', offLocale, { + once: true, + }); + } else context.defer(offLocale); + } + const createPlaceSearch = (signal) => + createStandalonePlaceSearch({ + // The bundled city and landmark data the offline name provider + // reads. The search package takes it as plain data rather than + // importing it, so it stays free of application state. + presets: CITY_POIS, + ...geospatial, + resolveApiKey: () => googleApiKey, + signal, + }); + placeSearch = session + ? (session.placeSearch ??= createPlaceSearch( + layerSession.lifetime.signal, + )) + : createPlaceSearch(context.signal); const scene = await createStandaloneScene({ ...context, googleApiKey,