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
This commit is contained in:
Pete Bacon Darwin
2019-11-09 19:00:53 +00:00
committed by Kara Erickson
parent 5b292bf125
commit 83626962cf
12 changed files with 99 additions and 15 deletions

View File

@ -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');

View File

@ -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 !;
}

View File

@ -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. */