Skip to content
Permalink
Browse files

fix(ivy): ensure that the correct `document` is available

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 DIand 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 angular#33651
  • Loading branch information
petebacondarwin committed Nov 9, 2019
1 parent 1c5fd3f commit b964d123d686ad7086abc69f735988c3d939b27d
@@ -243,6 +243,10 @@ export {
LContext as ɵLContext,
} from './render3/interfaces/context';

export {
setDocument as ɵsetDocument
} from './render3/interfaces/renderer';

// we reexport these symbols just so that they are retained during the dead code elimination
// performed by rollup while it's creating fesm files.
//
@@ -22,7 +22,7 @@ import {TsickleIssue1009, allocExpando, elementAttributeInternal, elementPropert
import {LContainer, NATIVE} from './interfaces/container';
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';
import {RComment, RElement, RText, getDocument} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {isLContainer} from './interfaces/type_checks';
import {HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from './interfaces/view';
@@ -1180,7 +1180,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');
@@ -8,8 +8,8 @@

/**
* 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.
@@ -18,6 +18,24 @@
import {RendererStyleFlags2, RendererType2} from '../../render/api';


let DOCUMENT: Document|undefined = undefined;

export function setDocument(document: Document | undefined): void {
DOCUMENT = document;
}

export function getDocument(): Document {
if (DOCUMENT !== undefined) {
return DOCUMENT;
} else if (typeof document !== 'undefined') {
return document;
} else {
throw new Error(
'No "document" can be found. If you are running outside a browser then you must call `setDocument()` before using the renderer.');
}
}


// TODO: cleanup once the code is merged in angular/angular
export enum RendererStyleFlags3 {
Important = 1 << 0,
@@ -105,7 +123,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. */
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {getDocument} from '../render3/interfaces/renderer';
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));
}

/**
@@ -26,6 +26,9 @@
{
"name": "DECLARATION_VIEW"
},
{
"name": "DOCUMENT"
},
{
"name": "DepComponent"
},
@@ -308,6 +311,9 @@
{
"name": "getDirectiveDef"
},
{
"name": "getDocument"
},
{
"name": "getElementDepthCount"
},
@@ -26,6 +26,9 @@
{
"name": "DECLARATION_VIEW"
},
{
"name": "DOCUMENT"
},
{
"name": "EMPTY_ARRAY"
},
@@ -245,6 +248,9 @@
{
"name": "getDirectiveDef"
},
{
"name": "getDocument"
},
{
"name": "getFactoryDef"
},
@@ -47,6 +47,9 @@
{
"name": "DEFAULT_TOTAL_SOURCES"
},
{
"name": "DOCUMENT"
},
{
"name": "DefaultIterableDiffer"
},
@@ -656,6 +659,9 @@
{
"name": "getDirectiveDef"
},
{
"name": "getDocument"
},
{
"name": "getElementDepthCount"
},
@@ -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;
}

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

/**
@@ -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;
}

0 comments on commit b964d12

Please sign in to comment.
You can’t perform that action at this time.