From e511bfcab586a94cdae1491e0ec194141445578b Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sat, 9 Nov 2019 19:00:53 +0000 Subject: [PATCH] fix(ivy): ensure that the correct `document` is available (#33712) Most of the use of `document` in the framework is within the DI so they just inject the `DOCUMENT` token and are done. Ivy is special because it does not rely upon the DI and must get hold of the document some other way. There are a limited number of places relevant to ivy that currently consume a global document object. The solution is modelled on the `LOCALE_ID` approach, which has `getLocaleId()` and `setLocaleId()` top-level functions for ivy (see `core/src/render3/i18n.ts`). In the rest of Angular (i.e. using DI) the `LOCALE_ID` token has a provider that also calls setLocaleId() to ensure that ivy has the same value. This commit defines `getDocument()` and `setDocument() `top-level functions for ivy. Wherever ivy needs the global `document`, it calls `getDocument()` instead. Each of the platforms (e.g. Browser, Server, WebWorker) have providers for `DOCUMENT`. In each of those providers they also call `setDocument()` accordingly. Fixes #33651 PR Close #33712 --- .../core/src/core_render3_private_export.ts | 4 ++ packages/core/src/render3/i18n.ts | 3 +- .../core/src/render3/interfaces/document.ts | 56 +++++++++++++++++++ .../core/src/render3/interfaces/renderer.ts | 8 +-- .../core/src/sanitization/sanitization.ts | 3 +- .../cyclic_import/bundle.golden_symbols.json | 6 ++ .../hello_world/bundle.golden_symbols.json | 6 ++ .../bundling/todo/bundle.golden_symbols.json | 6 ++ packages/platform-browser/src/browser.ts | 4 +- packages/platform-server/src/server.ts | 12 ++-- .../platform-webworker/src/worker_render.ts | 4 +- .../platform-webworker.d.ts | 2 +- 12 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 packages/core/src/render3/interfaces/document.ts diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 46198607fe..49beffaba4 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -243,6 +243,10 @@ export { LContext as ɵLContext, } from './render3/interfaces/context'; +export { + setDocument as ɵsetDocument +} from './render3/interfaces/document'; + // we reexport these symbols just so that they are retained during the dead code elimination // performed by rollup while it's creating fesm files. // diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 73453cc179..29d29b1aee 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -20,6 +20,7 @@ import {setDelayProjection} from './instructions/all'; import {attachI18nOpCodesDebug} from './instructions/lview_debug'; import {TsickleIssue1009, allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, setNgReflectProperties, textBindingInternal} from './instructions/shared'; import {LContainer, NATIVE} from './interfaces/container'; +import {getDocument} from './interfaces/document'; import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n'; import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from './interfaces/node'; import {RComment, RElement, RText} from './interfaces/renderer'; @@ -1180,7 +1181,7 @@ function icuStart( function parseIcuCase( unsafeHtml: string, parentIndex: number, nestedIcus: IcuExpression[], tIcus: TIcu[], expandoStartIndex: number): IcuCase { - const inertBodyHelper = new InertBodyHelper(document); + const inertBodyHelper = new InertBodyHelper(getDocument()); const inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml); if (!inertBodyElement) { throw new Error('Unable to generate inert body element'); diff --git a/packages/core/src/render3/interfaces/document.ts b/packages/core/src/render3/interfaces/document.ts new file mode 100644 index 0000000000..3e630c38ee --- /dev/null +++ b/packages/core/src/render3/interfaces/document.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** + * Most of the use of `document` in Angular is from within the DI system so it is possible to simply + * inject the `DOCUMENT` token and are done. + * + * Ivy is special because it does not rely upon the DI and must get hold of the document some other + * way. + * + * The solution is to define `getDocument()` and `setDocument()` top-level functions for ivy. + * Wherever ivy needs the global document, it calls `getDocument()` instead. + * + * When running ivy outside of a browser environment, it is necessary to call `setDocument()` to + * tell ivy what the global `document` is. + * + * Angular does this for us in each of the standard platforms (`Browser`, `Server`, and `WebWorker`) + * by calling `setDocument()` when providing the `DOCUMENT` token. + */ +let DOCUMENT: Document|undefined = undefined; + +/** + * Tell ivy what the `document` is for this platform. + * + * It is only necessary to call this if the current platform is not a browser. + * + * @param document The object representing the global `document` in this environment. + */ +export function setDocument(document: Document | undefined): void { + DOCUMENT = document; +} + +/** + * Access the object that represents the `document` for this platform. + * + * Ivy calls this whenever it needs to access the `document` object. + * For example to create the renderer or to do sanitization. + */ +export function getDocument(): Document { + if (DOCUMENT !== undefined) { + return DOCUMENT; + } else if (typeof document !== 'undefined') { + return document; + } + // No "document" can be found. This should only happen if we are running ivy outside Angular and + // the current platform is not a browser. Since this is not a supported scenario at the moment + // this should not happen in Angular apps. + // Once we support running ivy outside of Angular we will need to publish `setDocument()` as a + // public API. Meanwhile we just return `undefined` and let the application fail. + return undefined !; +} diff --git a/packages/core/src/render3/interfaces/renderer.ts b/packages/core/src/render3/interfaces/renderer.ts index 598a94d72d..fac99d5323 100644 --- a/packages/core/src/render3/interfaces/renderer.ts +++ b/packages/core/src/render3/interfaces/renderer.ts @@ -8,15 +8,15 @@ /** * The goal here is to make sure that the browser DOM API is the Renderer. - * We do this by defining a subset of DOM API to be the renderer and than - * use that time for rendering. + * We do this by defining a subset of DOM API to be the renderer and then + * use that at runtime for rendering. * * At runtime we can then use the DOM api directly, in server or web-worker * it will be easy to implement such API. */ import {RendererStyleFlags2, RendererType2} from '../../render/api'; - +import {getDocument} from './document'; // TODO: cleanup once the code is merged in angular/angular export enum RendererStyleFlags3 { @@ -105,7 +105,7 @@ export interface RendererFactory3 { export const domRendererFactory3: RendererFactory3 = { createRenderer: (hostElement: RElement | null, rendererType: RendererType2 | null): - Renderer3 => { return document;} + Renderer3 => { return getDocument();} }; /** Subset of API needed for appending elements and text nodes. */ diff --git a/packages/core/src/sanitization/sanitization.ts b/packages/core/src/sanitization/sanitization.ts index 6f655c1274..9644cf3194 100644 --- a/packages/core/src/sanitization/sanitization.ts +++ b/packages/core/src/sanitization/sanitization.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {getDocument} from '../render3/interfaces/document'; import {SANITIZER} from '../render3/interfaces/view'; import {getLView} from '../render3/state'; import {renderStringify} from '../render3/util/misc_utils'; @@ -42,7 +43,7 @@ export function ɵɵsanitizeHtml(unsafeHtml: any): string { if (allowSanitizationBypassAndThrow(unsafeHtml, BypassType.Html)) { return unwrapSafeValue(unsafeHtml); } - return _sanitizeHtml(document, renderStringify(unsafeHtml)); + return _sanitizeHtml(getDocument(), renderStringify(unsafeHtml)); } /** diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 683d908c09..01ae439028 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -26,6 +26,9 @@ { "name": "DECLARATION_VIEW" }, + { + "name": "DOCUMENT" + }, { "name": "DepComponent" }, @@ -308,6 +311,9 @@ { "name": "getDirectiveDef" }, + { + "name": "getDocument" + }, { "name": "getElementDepthCount" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index b7f192fac0..e233d9d0f1 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -26,6 +26,9 @@ { "name": "DECLARATION_VIEW" }, + { + "name": "DOCUMENT" + }, { "name": "EMPTY_ARRAY" }, @@ -245,6 +248,9 @@ { "name": "getDirectiveDef" }, + { + "name": "getDocument" + }, { "name": "getFactoryDef" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index b6de69e9a6..cb14f93b75 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -47,6 +47,9 @@ { "name": "DEFAULT_TOTAL_SOURCES" }, + { + "name": "DOCUMENT" + }, { "name": "DefaultIterableDiffer" }, @@ -656,6 +659,9 @@ { "name": "getDirectiveDef" }, + { + "name": "getDocument" + }, { "name": "getElementDepthCount" }, diff --git a/packages/platform-browser/src/browser.ts b/packages/platform-browser/src/browser.ts index bad1d64f90..756fec46a5 100644 --- a/packages/platform-browser/src/browser.ts +++ b/packages/platform-browser/src/browser.ts @@ -7,7 +7,7 @@ */ import {CommonModule, DOCUMENT, PlatformLocation, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common'; -import {APP_ID, ApplicationModule, ErrorHandler, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore, ɵConsole as Console, ɵINJECTOR_SCOPE as INJECTOR_SCOPE} from '@angular/core'; +import {APP_ID, ApplicationModule, ErrorHandler, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore, ɵConsole as Console, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵsetDocument} from '@angular/core'; import {BrowserDomAdapter} from './browser/browser_adapter'; import {SERVER_TRANSITION_PROVIDERS, TRANSITION_ID} from './browser/server-transition'; import {BrowserGetTestability} from './browser/testability'; @@ -57,6 +57,8 @@ export function errorHandler(): ErrorHandler { } export function _document(): any { + // Tell ivy about the global document + ɵsetDocument(document); return document; } diff --git a/packages/platform-server/src/server.ts b/packages/platform-server/src/server.ts index db9118aa62..3b1d3d7e93 100644 --- a/packages/platform-server/src/server.ts +++ b/packages/platform-server/src/server.ts @@ -9,7 +9,7 @@ import {ɵAnimationEngine} from '@angular/animations/browser'; import {DOCUMENT, PlatformLocation, ViewportScroller, ɵNullViewportScroller as NullViewportScroller, ɵPLATFORM_SERVER_ID as PLATFORM_SERVER_ID, ɵgetDOM as getDOM} from '@angular/common'; import {HttpClientModule} from '@angular/common/http'; -import {Injector, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, StaticProvider, Testability, createPlatformFactory, platformCore, ɵALLOW_MULTIPLE_PLATFORMS as ALLOW_MULTIPLE_PLATFORMS} from '@angular/core'; +import {Injector, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, StaticProvider, Testability, createPlatformFactory, platformCore, ɵALLOW_MULTIPLE_PLATFORMS as ALLOW_MULTIPLE_PLATFORMS, ɵsetDocument} from '@angular/core'; import {BrowserModule, EVENT_MANAGER_PLUGINS, ɵSharedStylesHost as SharedStylesHost} from '@angular/platform-browser'; import {ɵplatformCoreDynamic as platformCoreDynamic} from '@angular/platform-browser-dynamic'; import {NoopAnimationsModule, ɵAnimationRendererFactory} from '@angular/platform-browser/animations'; @@ -81,11 +81,11 @@ export class ServerModule { function _document(injector: Injector) { let config: PlatformConfig|null = injector.get(INITIAL_CONFIG, null); - if (config && config.document) { - return parseDocument(config.document, config.url); - } else { - return getDOM().createHtmlDocument(); - } + const document = config && config.document ? parseDocument(config.document, config.url) : + getDOM().createHtmlDocument(); + // Tell ivy about the global document + ɵsetDocument(document); + return document; } /** diff --git a/packages/platform-webworker/src/worker_render.ts b/packages/platform-webworker/src/worker_render.ts index 724a913c2d..5c8081b27c 100644 --- a/packages/platform-webworker/src/worker_render.ts +++ b/packages/platform-webworker/src/worker_render.ts @@ -7,7 +7,7 @@ */ import {DOCUMENT, ɵPLATFORM_WORKER_UI_ID as PLATFORM_WORKER_UI_ID} from '@angular/common'; -import {ErrorHandler, Injectable, InjectionToken, Injector, NgZone, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, StaticProvider, Testability, createPlatformFactory, isDevMode, platformCore, ɵAPP_ID_RANDOM_PROVIDER as APP_ID_RANDOM_PROVIDER} from '@angular/core'; +import {ErrorHandler, Injectable, InjectionToken, Injector, NgZone, PLATFORM_ID, PLATFORM_INITIALIZER, RendererFactory2, StaticProvider, Testability, createPlatformFactory, isDevMode, platformCore, ɵAPP_ID_RANDOM_PROVIDER as APP_ID_RANDOM_PROVIDER, ɵsetDocument} from '@angular/core'; import {EVENT_MANAGER_PLUGINS, EventManager, HAMMER_GESTURE_CONFIG, HammerGestureConfig, ɵBROWSER_SANITIZATION_PROVIDERS as BROWSER_SANITIZATION_PROVIDERS, ɵBrowserDomAdapter as BrowserDomAdapter, ɵBrowserGetTestability as BrowserGetTestability, ɵDomEventsPlugin as DomEventsPlugin, ɵDomRendererFactory2 as DomRendererFactory2, ɵDomSharedStylesHost as DomSharedStylesHost, ɵHammerGesturesPlugin as HammerGesturesPlugin, ɵKeyEventsPlugin as KeyEventsPlugin, ɵSharedStylesHost as SharedStylesHost} from '@angular/platform-browser'; import {ON_WEB_WORKER} from './web_workers/shared/api'; @@ -159,6 +159,8 @@ function _exceptionHandler(): ErrorHandler { } function _document(): any { + // Tell ivy about the global document + ɵsetDocument(document); return document; } diff --git a/tools/public_api_guard/platform-webworker/platform-webworker.d.ts b/tools/public_api_guard/platform-webworker/platform-webworker.d.ts index 2a3f72ab58..09bb337566 100644 --- a/tools/public_api_guard/platform-webworker/platform-webworker.d.ts +++ b/tools/public_api_guard/platform-webworker/platform-webworker.d.ts @@ -40,7 +40,7 @@ export interface MessageBusSource { /** @deprecated */ export declare const platformWorkerApp: (extraProviders?: StaticProvider[] | undefined) => PlatformRef; -export declare const platformWorkerUi: (extraProviders?: StaticProvider[] | undefined) => PlatformRef; +export declare const platformWorkerUi: (extraProviders?: StaticProvider[] | undefined) => import("@angular/core").PlatformRef; /** @deprecated */ export interface ReceivedMessage {