@angular/core
Version:
Angular - the core framework
557 lines • 82.8 kB
JavaScript
/**
* @license
* Copyright Google LLC 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
*/
import { isDetachedByI18n } from '../i18n/utils';
import { ViewEncapsulation } from '../metadata';
import { assertTNode } from '../render3/assert';
import { collectNativeNodes, collectNativeNodesInLContainer } from '../render3/collect_native_nodes';
import { getComponentDef } from '../render3/definition';
import { CONTAINER_HEADER_OFFSET } from '../render3/interfaces/container';
import { isTNodeShape } from '../render3/interfaces/node';
import { hasI18n, isComponentHost, isLContainer, isProjectionTNode, isRootView, } from '../render3/interfaces/type_checks';
import { CONTEXT, HEADER_OFFSET, HOST, PARENT, RENDERER, TVIEW, } from '../render3/interfaces/view';
import { unwrapLView, unwrapRNode } from '../render3/util/view_utils';
import { TransferState } from '../transfer_state';
import { unsupportedProjectionOfDomNodes } from './error_handling';
import { collectDomEventsInfo } from './event_replay';
import { setJSActionAttributes } from '../event_delegation_utils';
import { getOrComputeI18nChildren, isI18nHydrationEnabled, isI18nHydrationSupportEnabled, trySerializeI18nBlock, } from './i18n';
import { CONTAINERS, DISCONNECTED_NODES, ELEMENT_CONTAINERS, I18N_DATA, MULTIPLIER, NODES, NUM_ROOT_NODES, TEMPLATE_ID, TEMPLATES, } from './interfaces';
import { calcPathForNode, isDisconnectedNode } from './node_lookup_utils';
import { isInSkipHydrationBlock, SKIP_HYDRATION_ATTR_NAME } from './skip_hydration';
import { EVENT_REPLAY_ENABLED_DEFAULT, IS_EVENT_REPLAY_ENABLED } from './tokens';
import { getLNodeForHydration, NGH_ATTR_NAME, NGH_DATA_KEY, processTextNodeBeforeSerialization, } from './utils';
/**
* A collection that tracks all serialized views (`ngh` DOM annotations)
* to avoid duplication. An attempt to add a duplicate view results in the
* collection returning the index of the previously collected serialized view.
* This reduces the number of annotations needed for a given page.
*/
class SerializedViewCollection {
constructor() {
this.views = [];
this.indexByContent = new Map();
}
add(serializedView) {
const viewAsString = JSON.stringify(serializedView);
if (!this.indexByContent.has(viewAsString)) {
const index = this.views.length;
this.views.push(serializedView);
this.indexByContent.set(viewAsString, index);
return index;
}
return this.indexByContent.get(viewAsString);
}
getAll() {
return this.views;
}
}
/**
* Global counter that is used to generate a unique id for TViews
* during the serialization process.
*/
let tViewSsrId = 0;
/**
* Generates a unique id for a given TView and returns this id.
* The id is also stored on this instance of a TView and reused in
* subsequent calls.
*
* This id is needed to uniquely identify and pick up dehydrated views
* at runtime.
*/
function getSsrId(tView) {
if (!tView.ssrId) {
tView.ssrId = `t${tViewSsrId++}`;
}
return tView.ssrId;
}
/**
* Computes the number of root nodes in a given view
* (or child nodes in a given container if a tNode is provided).
*/
function calcNumRootNodes(tView, lView, tNode) {
const rootNodes = [];
collectNativeNodes(tView, lView, tNode, rootNodes);
return rootNodes.length;
}
/**
* Computes the number of root nodes in all views in a given LContainer.
*/
function calcNumRootNodesInLContainer(lContainer) {
const rootNodes = [];
collectNativeNodesInLContainer(lContainer, rootNodes);
return rootNodes.length;
}
/**
* Annotates root level component's LView for hydration,
* see `annotateHostElementForHydration` for additional information.
*/
function annotateComponentLViewForHydration(lView, context) {
const hostElement = lView[HOST];
// Root elements might also be annotated with the `ngSkipHydration` attribute,
// check if it's present before starting the serialization process.
if (hostElement && !hostElement.hasAttribute(SKIP_HYDRATION_ATTR_NAME)) {
return annotateHostElementForHydration(hostElement, lView, context);
}
return null;
}
/**
* Annotates root level LContainer for hydration. This happens when a root component
* injects ViewContainerRef, thus making the component an anchor for a view container.
* This function serializes the component itself as well as all views from the view
* container.
*/
function annotateLContainerForHydration(lContainer, context) {
const componentLView = unwrapLView(lContainer[HOST]);
// Serialize the root component itself.
const componentLViewNghIndex = annotateComponentLViewForHydration(componentLView, context);
if (componentLViewNghIndex === null) {
// Component was not serialized (for example, if hydration was skipped by adding
// the `ngSkipHydration` attribute or this component uses i18n blocks in the template,
// but `withI18nSupport()` was not added), avoid annotating host element with the `ngh`
// attribute.
return;
}
const hostElement = unwrapRNode(componentLView[HOST]);
// Serialize all views within this view container.
const rootLView = lContainer[PARENT];
const rootLViewNghIndex = annotateHostElementForHydration(hostElement, rootLView, context);
const renderer = componentLView[RENDERER];
// For cases when a root component also acts as an anchor node for a ViewContainerRef
// (for example, when ViewContainerRef is injected in a root component), there is a need
// to serialize information about the component itself, as well as an LContainer that
// represents this ViewContainerRef. Effectively, we need to serialize 2 pieces of info:
// (1) hydration info for the root component itself and (2) hydration info for the
// ViewContainerRef instance (an LContainer). Each piece of information is included into
// the hydration data (in the TransferState object) separately, thus we end up with 2 ids.
// Since we only have 1 root element, we encode both bits of info into a single string:
// ids are separated by the `|` char (e.g. `10|25`, where `10` is the ngh for a component view
// and 25 is the `ngh` for a root view which holds LContainer).
const finalIndex = `${componentLViewNghIndex}|${rootLViewNghIndex}`;
renderer.setAttribute(hostElement, NGH_ATTR_NAME, finalIndex);
}
/**
* Annotates all components bootstrapped in a given ApplicationRef
* with info needed for hydration.
*
* @param appRef An instance of an ApplicationRef.
* @param doc A reference to the current Document instance.
* @return event types that need to be replayed
*/
export function annotateForHydration(appRef, doc) {
const injector = appRef.injector;
const isI18nHydrationEnabledVal = isI18nHydrationEnabled(injector);
const serializedViewCollection = new SerializedViewCollection();
const corruptedTextNodes = new Map();
const viewRefs = appRef._views;
const shouldReplayEvents = injector.get(IS_EVENT_REPLAY_ENABLED, EVENT_REPLAY_ENABLED_DEFAULT);
const eventTypesToReplay = {
regular: new Set(),
capture: new Set(),
};
for (const viewRef of viewRefs) {
const lNode = getLNodeForHydration(viewRef);
// An `lView` might be `null` if a `ViewRef` represents
// an embedded view (not a component view).
if (lNode !== null) {
const context = {
serializedViewCollection,
corruptedTextNodes,
isI18nHydrationEnabled: isI18nHydrationEnabledVal,
i18nChildren: new Map(),
eventTypesToReplay,
shouldReplayEvents,
};
if (isLContainer(lNode)) {
annotateLContainerForHydration(lNode, context);
}
else {
annotateComponentLViewForHydration(lNode, context);
}
insertCorruptedTextNodeMarkers(corruptedTextNodes, doc);
}
}
// Note: we *always* include hydration info key and a corresponding value
// into the TransferState, even if the list of serialized views is empty.
// This is needed as a signal to the client that the server part of the
// hydration logic was setup and enabled correctly. Otherwise, if a client
// hydration doesn't find a key in the transfer state - an error is produced.
const serializedViews = serializedViewCollection.getAll();
const transferState = injector.get(TransferState);
transferState.set(NGH_DATA_KEY, serializedViews);
return eventTypesToReplay;
}
/**
* Serializes the lContainer data into a list of SerializedView objects,
* that represent views within this lContainer.
*
* @param lContainer the lContainer we are serializing
* @param context the hydration context
* @returns an array of the `SerializedView` objects
*/
function serializeLContainer(lContainer, context) {
const views = [];
let lastViewAsString = '';
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
let childLView = lContainer[i];
let template;
let numRootNodes;
let serializedView;
if (isRootView(childLView)) {
// If this is a root view, get an LView for the underlying component,
// because it contains information about the view to serialize.
childLView = childLView[HEADER_OFFSET];
// If we have an LContainer at this position, this indicates that the
// host element was used as a ViewContainerRef anchor (e.g. a `ViewContainerRef`
// was injected within the component class). This case requires special handling.
if (isLContainer(childLView)) {
// Calculate the number of root nodes in all views in a given container
// and increment by one to account for an anchor node itself, i.e. in this
// scenario we'll have a layout that would look like this:
// `<app-root /><#VIEW1><#VIEW2>...<!--container-->`
// The `+1` is to capture the `<app-root />` element.
numRootNodes = calcNumRootNodesInLContainer(childLView) + 1;
annotateLContainerForHydration(childLView, context);
const componentLView = unwrapLView(childLView[HOST]);
serializedView = {
[TEMPLATE_ID]: componentLView[TVIEW].ssrId,
[NUM_ROOT_NODES]: numRootNodes,
};
}
}
if (!serializedView) {
const childTView = childLView[TVIEW];
if (childTView.type === 1 /* TViewType.Component */) {
template = childTView.ssrId;
// This is a component view, thus it has only 1 root node: the component
// host node itself (other nodes would be inside that host node).
numRootNodes = 1;
}
else {
template = getSsrId(childTView);
numRootNodes = calcNumRootNodes(childTView, childLView, childTView.firstChild);
}
serializedView = {
[TEMPLATE_ID]: template,
[NUM_ROOT_NODES]: numRootNodes,
...serializeLView(lContainer[i], context),
};
}
// Check if the previous view has the same shape (for example, it was
// produced by the *ngFor), in which case bump the counter on the previous
// view instead of including the same information again.
const currentViewAsString = JSON.stringify(serializedView);
if (views.length > 0 && currentViewAsString === lastViewAsString) {
const previousView = views[views.length - 1];
previousView[MULTIPLIER] ??= 1;
previousView[MULTIPLIER]++;
}
else {
// Record this view as most recently added.
lastViewAsString = currentViewAsString;
views.push(serializedView);
}
}
return views;
}
/**
* Helper function to produce a node path (which navigation steps runtime logic
* needs to take to locate a node) and stores it in the `NODES` section of the
* current serialized view.
*/
function appendSerializedNodePath(ngh, tNode, lView, excludedParentNodes) {
const noOffsetIndex = tNode.index - HEADER_OFFSET;
ngh[NODES] ??= {};
// Ensure we don't calculate the path multiple times.
ngh[NODES][noOffsetIndex] ??= calcPathForNode(tNode, lView, excludedParentNodes);
}
/**
* Helper function to append information about a disconnected node.
* This info is needed at runtime to avoid DOM lookups for this element
* and instead, the element would be created from scratch.
*/
function appendDisconnectedNodeIndex(ngh, tNodeOrNoOffsetIndex) {
const noOffsetIndex = typeof tNodeOrNoOffsetIndex === 'number'
? tNodeOrNoOffsetIndex
: tNodeOrNoOffsetIndex.index - HEADER_OFFSET;
ngh[DISCONNECTED_NODES] ??= [];
if (!ngh[DISCONNECTED_NODES].includes(noOffsetIndex)) {
ngh[DISCONNECTED_NODES].push(noOffsetIndex);
}
}
/**
* Serializes the lView data into a SerializedView object that will later be added
* to the TransferState storage and referenced using the `ngh` attribute on a host
* element.
*
* @param lView the lView we are serializing
* @param context the hydration context
* @returns the `SerializedView` object containing the data to be added to the host node
*/
function serializeLView(lView, context) {
const ngh = {};
const tView = lView[TVIEW];
const i18nChildren = getOrComputeI18nChildren(tView, context);
const nativeElementsToEventTypes = context.shouldReplayEvents
? collectDomEventsInfo(tView, lView, context.eventTypesToReplay)
: null;
// Iterate over DOM element references in an LView.
for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
const tNode = tView.data[i];
const noOffsetIndex = i - HEADER_OFFSET;
// Attempt to serialize any i18n data for the given slot. We do this first, as i18n
// has its own process for serialization.
const i18nData = trySerializeI18nBlock(lView, i, context);
if (i18nData) {
ngh[I18N_DATA] ??= {};
ngh[I18N_DATA][noOffsetIndex] = i18nData.caseQueue;
for (const nodeNoOffsetIndex of i18nData.disconnectedNodes) {
appendDisconnectedNodeIndex(ngh, nodeNoOffsetIndex);
}
for (const nodeNoOffsetIndex of i18nData.disjointNodes) {
const tNode = tView.data[nodeNoOffsetIndex + HEADER_OFFSET];
ngDevMode && assertTNode(tNode);
appendSerializedNodePath(ngh, tNode, lView, i18nChildren);
}
continue;
}
// Skip processing of a given slot in the following cases:
// - Local refs (e.g. <div #localRef>) take up an extra slot in LViews
// to store the same element. In this case, there is no information in
// a corresponding slot in TNode data structure.
// - When a slot contains something other than a TNode. For example, there
// might be some metadata information about a defer block or a control flow block.
if (!isTNodeShape(tNode)) {
continue;
}
// Skip any nodes that are in an i18n block but are considered detached (i.e. not
// present in the template). These nodes are disconnected from the DOM tree, and
// so we don't want to serialize any information about them.
if (isDetachedByI18n(tNode)) {
continue;
}
// Check if a native node that represents a given TNode is disconnected from the DOM tree.
// Such nodes must be excluded from the hydration (since the hydration won't be able to
// find them), so the TNode ids are collected and used at runtime to skip the hydration.
//
// This situation may happen during the content projection, when some nodes don't make it
// into one of the content projection slots (for example, when there is no default
// <ng-content /> slot in projector component's template).
if (isDisconnectedNode(tNode, lView) && isContentProjectedNode(tNode)) {
appendDisconnectedNodeIndex(ngh, tNode);
continue;
}
// Attach `jsaction` attribute to elements that have registered listeners,
// thus potentially having a need to do an event replay.
if (nativeElementsToEventTypes && tNode.type & 2 /* TNodeType.Element */) {
const nativeElement = unwrapRNode(lView[i]);
if (nativeElementsToEventTypes.has(nativeElement)) {
setJSActionAttributes(nativeElement, nativeElementsToEventTypes.get(nativeElement));
}
}
if (Array.isArray(tNode.projection)) {
for (const projectionHeadTNode of tNode.projection) {
// We may have `null`s in slots with no projected content.
if (!projectionHeadTNode)
continue;
if (!Array.isArray(projectionHeadTNode)) {
// If we process re-projected content (i.e. `<ng-content>`
// appears at projection location), skip annotations for this content
// since all DOM nodes in this projection were handled while processing
// a parent lView, which contains those nodes.
if (!isProjectionTNode(projectionHeadTNode) &&
!isInSkipHydrationBlock(projectionHeadTNode)) {
if (isDisconnectedNode(projectionHeadTNode, lView)) {
// Check whether this node is connected, since we may have a TNode
// in the data structure as a projection segment head, but the
// content projection slot might be disabled (e.g.
// <ng-content *ngIf="false" />).
appendDisconnectedNodeIndex(ngh, projectionHeadTNode);
}
else {
appendSerializedNodePath(ngh, projectionHeadTNode, lView, i18nChildren);
}
}
}
else {
// If a value is an array, it means that we are processing a projection
// where projectable nodes were passed in as DOM nodes (for example, when
// calling `ViewContainerRef.createComponent(CmpA, {projectableNodes: [...]})`).
//
// In this scenario, nodes can come from anywhere (either created manually,
// accessed via `document.querySelector`, etc) and may be in any state
// (attached or detached from the DOM tree). As a result, we can not reliably
// restore the state for such cases during hydration.
throw unsupportedProjectionOfDomNodes(unwrapRNode(lView[i]));
}
}
}
conditionallyAnnotateNodePath(ngh, tNode, lView, i18nChildren);
if (isLContainer(lView[i])) {
// Serialize information about a template.
const embeddedTView = tNode.tView;
if (embeddedTView !== null) {
ngh[TEMPLATES] ??= {};
ngh[TEMPLATES][noOffsetIndex] = getSsrId(embeddedTView);
}
// Serialize views within this LContainer.
const hostNode = lView[i][HOST]; // host node of this container
// LView[i][HOST] can be of 2 different types:
// - either a DOM node
// - or an array that represents an LView of a component
if (Array.isArray(hostNode)) {
// This is a component, serialize info about it.
const targetNode = unwrapRNode(hostNode);
if (!targetNode.hasAttribute(SKIP_HYDRATION_ATTR_NAME)) {
annotateHostElementForHydration(targetNode, hostNode, context);
}
}
ngh[CONTAINERS] ??= {};
ngh[CONTAINERS][noOffsetIndex] = serializeLContainer(lView[i], context);
}
else if (Array.isArray(lView[i])) {
// This is a component, annotate the host node with an `ngh` attribute.
const targetNode = unwrapRNode(lView[i][HOST]);
if (!targetNode.hasAttribute(SKIP_HYDRATION_ATTR_NAME)) {
annotateHostElementForHydration(targetNode, lView[i], context);
}
}
else {
// <ng-container> case
if (tNode.type & 8 /* TNodeType.ElementContainer */) {
// An <ng-container> is represented by the number of
// top-level nodes. This information is needed to skip over
// those nodes to reach a corresponding anchor node (comment node).
ngh[ELEMENT_CONTAINERS] ??= {};
ngh[ELEMENT_CONTAINERS][noOffsetIndex] = calcNumRootNodes(tView, lView, tNode.child);
}
else if (tNode.type & (16 /* TNodeType.Projection */ | 128 /* TNodeType.LetDeclaration */)) {
// Current TNode represents an `<ng-content>` slot or `@let` declaration,
// thus it has no DOM elements associated with it, so the **next sibling**
// node would not be able to find an anchor. In this case, use full path instead.
let nextTNode = tNode.next;
// Skip over all `<ng-content>` slots and `@let` declarations in a row.
while (nextTNode !== null &&
nextTNode.type & (16 /* TNodeType.Projection */ | 128 /* TNodeType.LetDeclaration */)) {
nextTNode = nextTNode.next;
}
if (nextTNode && !isInSkipHydrationBlock(nextTNode)) {
// Handle a tNode after the `<ng-content>` slot.
appendSerializedNodePath(ngh, nextTNode, lView, i18nChildren);
}
}
else if (tNode.type & 1 /* TNodeType.Text */) {
const rNode = unwrapRNode(lView[i]);
processTextNodeBeforeSerialization(context, rNode);
}
}
}
return ngh;
}
/**
* Serializes node location in cases when it's needed, specifically:
*
* 1. If `tNode.projectionNext` is different from `tNode.next` - it means that
* the next `tNode` after projection is different from the one in the original
* template. Since hydration relies on `tNode.next`, this serialized info
* is required to help runtime code find the node at the correct location.
* 2. In certain content projection-based use-cases, it's possible that only
* a content of a projected element is rendered. In this case, content nodes
* require an extra annotation, since runtime logic can't rely on parent-child
* connection to identify the location of a node.
*/
function conditionallyAnnotateNodePath(ngh, tNode, lView, excludedParentNodes) {
// Handle case #1 described above.
if (tNode.projectionNext &&
tNode.projectionNext !== tNode.next &&
!isInSkipHydrationBlock(tNode.projectionNext)) {
appendSerializedNodePath(ngh, tNode.projectionNext, lView, excludedParentNodes);
}
// Handle case #2 described above.
// Note: we only do that for the first node (i.e. when `tNode.prev === null`),
// the rest of the nodes would rely on the current node location, so no extra
// annotation is needed.
if (tNode.prev === null &&
tNode.parent !== null &&
isDisconnectedNode(tNode.parent, lView) &&
!isDisconnectedNode(tNode, lView)) {
appendSerializedNodePath(ngh, tNode, lView, excludedParentNodes);
}
}
/**
* Determines whether a component instance that is represented
* by a given LView uses `ViewEncapsulation.ShadowDom`.
*/
function componentUsesShadowDomEncapsulation(lView) {
const instance = lView[CONTEXT];
return instance?.constructor
? getComponentDef(instance.constructor)?.encapsulation === ViewEncapsulation.ShadowDom
: false;
}
/**
* Annotates component host element for hydration:
* - by either adding the `ngh` attribute and collecting hydration-related info
* for the serialization and transferring to the client
* - or by adding the `ngSkipHydration` attribute in case Angular detects that
* component contents is not compatible with hydration.
*
* @param element The Host element to be annotated
* @param lView The associated LView
* @param context The hydration context
* @returns An index of serialized view from the transfer state object
* or `null` when a given component can not be serialized.
*/
function annotateHostElementForHydration(element, lView, context) {
const renderer = lView[RENDERER];
if ((hasI18n(lView) && !isI18nHydrationSupportEnabled()) ||
componentUsesShadowDomEncapsulation(lView)) {
// Attach the skip hydration attribute if this component:
// - either has i18n blocks, since hydrating such blocks is not yet supported
// - or uses ShadowDom view encapsulation, since Domino doesn't support
// shadow DOM, so we can not guarantee that client and server representations
// would exactly match
renderer.setAttribute(element, SKIP_HYDRATION_ATTR_NAME, '');
return null;
}
else {
const ngh = serializeLView(lView, context);
const index = context.serializedViewCollection.add(ngh);
renderer.setAttribute(element, NGH_ATTR_NAME, index.toString());
return index;
}
}
/**
* Physically inserts the comment nodes to ensure empty text nodes and adjacent
* text node separators are preserved after server serialization of the DOM.
* These get swapped back for empty text nodes or separators once hydration happens
* on the client.
*
* @param corruptedTextNodes The Map of text nodes to be replaced with comments
* @param doc The document
*/
function insertCorruptedTextNodeMarkers(corruptedTextNodes, doc) {
for (const [textNode, marker] of corruptedTextNodes) {
textNode.after(doc.createComment(marker));
}
}
/**
* Detects whether a given TNode represents a node that
* is being content projected.
*/
function isContentProjectedNode(tNode) {
let currentTNode = tNode;
while (currentTNode != null) {
// If we come across a component host node in parent nodes -
// this TNode is in the content projection section.
if (isComponentHost(currentTNode)) {
return true;
}
currentTNode = currentTNode.parent;
}
return false;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYW5ub3RhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9jb3JlL3NyYy9oeWRyYXRpb24vYW5ub3RhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HO0FBSUgsT0FBTyxFQUFDLGdCQUFnQixFQUFDLE1BQU0sZUFBZSxDQUFDO0FBQy9DLE9BQU8sRUFBQyxpQkFBaUIsRUFBQyxNQUFNLGFBQWEsQ0FBQztBQUU5QyxPQUFPLEVBQUMsV0FBVyxFQUFDLE1BQU0sbUJBQW1CLENBQUM7QUFDOUMsT0FBTyxFQUFDLGtCQUFrQixFQUFFLDhCQUE4QixFQUFDLE1BQU0saUNBQWlDLENBQUM7QUFDbkcsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLHVCQUF1QixDQUFDO0FBQ3RELE9BQU8sRUFBQyx1QkFBdUIsRUFBYSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3BGLE9BQU8sRUFBQyxZQUFZLEVBQW1CLE1BQU0sNEJBQTRCLENBQUM7QUFFMUUsT0FBTyxFQUNMLE9BQU8sRUFDUCxlQUFlLEVBQ2YsWUFBWSxFQUNaLGlCQUFpQixFQUNqQixVQUFVLEdBQ1gsTUFBTSxtQ0FBbUMsQ0FBQztBQUMzQyxPQUFPLEVBQ0wsT0FBTyxFQUNQLGFBQWEsRUFDYixJQUFJLEVBRUosTUFBTSxFQUNOLFFBQVEsRUFFUixLQUFLLEdBRU4sTUFBTSw0QkFBNEIsQ0FBQztBQUNwQyxPQUFPLEVBQUMsV0FBVyxFQUFFLFdBQVcsRUFBQyxNQUFNLDRCQUE0QixDQUFDO0FBQ3BFLE9BQU8sRUFBQyxhQUFhLEVBQUMsTUFBTSxtQkFBbUIsQ0FBQztBQUVoRCxPQUFPLEVBQUMsK0JBQStCLEVBQUMsTUFBTSxrQkFBa0IsQ0FBQztBQUNqRSxPQUFPLEVBQUMsb0JBQW9CLEVBQUMsTUFBTSxnQkFBZ0IsQ0FBQztBQUNwRCxPQUFPLEVBQUMscUJBQXFCLEVBQUMsTUFBTSwyQkFBMkIsQ0FBQztBQUNoRSxPQUFP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QyxPQUFPLEVBQUUsYUFBYSxFQUFFLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ2hFLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztBQUNILENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILFNBQVMsOEJBQThCLENBQ3JDLGtCQUE0QyxFQUM1QyxHQUFhO0lBRWIsS0FBSyxNQUFNLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxJQUFJLGtCQUFrQixFQUFFLENBQUM7UUFDcEQsUUFBUSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7SUFDNUMsQ0FBQztBQUNILENBQUM7QUFFRDs7O0dBR0c7QUFDSCxTQUFTLHNCQUFzQixDQUFDLEtBQVk7SUFDMUMsSUFBSSxZQUFZLEdBQUcsS0FBSyxDQUFDO0lBQ3pCLE9BQU8sWUFBWSxJQUFJLElBQUksRUFBRSxDQUFDO1FBQzVCLDREQUE0RDtRQUM1RCxtREFBbUQ7UUFDbkQsSUFBSSxlQUFlLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUNsQyxPQUFPLElBQUksQ0FBQztRQUNkLENBQUM7UUFDRCxZQUFZLEdBQUcsWUFBWSxDQUFDLE1BQWUsQ0FBQztJQUM5QyxDQUFDO0lBQ0QsT0FBTyxLQUFLLENBQUM7QUFDZixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBMTEMgQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbiAqXG4gKiBVc2Ugb2YgdGhpcyBzb3VyY2UgY29kZSBpcyBnb3Zlcm5lZCBieSBhbiBNSVQtc3R5bGUgbGljZW5zZSB0aGF0IGNhbiBiZVxuICogZm91bmQgaW4gdGhlIExJQ0VOU0UgZmlsZSBhdCBodHRwczovL2FuZ3VsYXIuaW8vbGljZW5zZVxuICovXG5cbmltcG9ydCB7QXBwbGljYXRpb25SZWZ9IGZyb20gJy4uL2FwcGxpY2F0aW9uL2FwcGxpY2F0aW9uX3JlZic7XG5pbXBvcnQge0FQUF9JRH0gZnJvbSAnLi4vYXBwbGljYXRpb24vYXBwbGljYXRpb25fdG9rZW5zJztcbmltcG9ydCB7aXNEZXRhY2hlZEJ5STE4bn0gZnJvbSAnLi4vaTE4bi91dGlscyc7XG5pbXBvcnQge1ZpZXdFbmNhcHN1bGF0aW9ufSBmcm9tICcuLi9tZXRhZGF0YSc7XG5pbXBvcnQge1JlbmRlcmVyMn0gZnJvbSAnLi4vcmVuZGVyJztcbmltcG9ydCB7YXNzZXJ0VE5vZGV9IGZyb20gJy4uL3JlbmRlcjMvYXNzZXJ0JztcbmltcG9ydCB7Y29sbGVjdE5hdGl2ZU5vZGVzLCBjb2xsZWN0TmF0aXZlTm9kZXNJbkxDb250YWluZXJ9IGZyb20gJy4uL3JlbmRlcjMvY29sbGVjdF9uYXRpdmVfbm9kZXMnO1xuaW1wb3J0IHtnZXRDb21wb25lbnREZWZ9IGZyb20gJy4uL3JlbmRlcjMvZGVmaW5pdGlvbic7XG5pbXBvcnQge0NPTlRBSU5FUl9IRUFERVJfT0ZGU0VULCBMQ29udGFpbmVyfSBmcm9tICcuLi9yZW5kZXIzL2ludGVyZmFjZXMvY29udGFpbmVyJztcbmltcG9ydCB7aXNUTm9kZVNoYXBlLCBUTm9kZSwgVE5vZGVUeXBlfSBmcm9tICcuLi9yZW5kZXIzL2ludGVyZmFjZXMvbm9kZSc7XG5pbXBvcnQge1JFbGVtZW50fSBmcm9tICcuLi9yZW5kZXIzL2ludGVyZmFjZXMvcmVuZGVyZXJfZG9tJztcbmltcG9ydCB7XG4gIGhhc0kxOG4sXG4gIGlzQ29tcG9uZW50SG9zdCxcbiAgaXNMQ29udGFpbmVyLFxuICBpc1Byb2plY3Rpb25UTm9kZSxcbiAgaXNSb290Vmlldyxcbn0gZnJvbSAnLi4vcmVuZGVyMy9pbnRlcmZhY2VzL3R5cGVfY2hlY2tzJztcbmltcG9ydCB7XG4gIENPTlRFWFQsXG4gIEhFQURFUl9PRkZTRVQsXG4gIEhPU1QsXG4gIExWaWV3LFxuICBQQVJFTlQsXG4gIFJFTkRFUkVSLFxuICBUVmlldyxcbiAgVFZJRVcsXG4gIFRWaWV3VHlwZSxcbn0gZnJvbSAnLi4vcmVuZGVyMy9pbnRlcmZhY2VzL3ZpZXcnO1xuaW1wb3J0IHt1bndyYXBMVmlldywgdW53cmFwUk5vZGV9IGZyb20gJy4uL3JlbmRlcjMvdXRpbC92aWV3X3V0aWxzJztcbmltcG9ydCB7VHJhbnNmZXJTdGF0ZX0gZnJvbSAnLi4vdHJhbnNmZXJfc3RhdGUnO1xuXG5pbXBvcnQge3Vuc3VwcG9ydGVkUHJvamVjdGlvbk9mRG9tTm9kZXN9IGZyb20gJy4vZXJyb3JfaGFuZGxpbmcnO1xuaW1wb3J0IHtjb2xsZWN0RG9tRXZlbnRzSW5mb30gZnJvbSAnLi9ldmVudF9yZXBsYXknO1xuaW1wb3J0IHtzZXRKU0FjdGlvbkF0dHJpYnV0ZXN9IGZyb20gJy4uL2V2ZW50X2RlbGVnYXRpb25fdXRpbHMnO1xuaW1wb3J0IHtcbiAgZ2V0T3JDb21wdXRlSTE4bkNoaWxkcmVuLFxuICBpc0kxOG5IeWRyYXRpb25FbmFibGVkLFxuICBpc0kxOG5IeWRyYXRpb25TdXBwb3J0RW5hYmxlZCxcbiAgdHJ5U2VyaWFsaXplSTE4bkJsb2NrLFxufSBmcm9tICcuL2kxOG4nO1xuaW1wb3J0IHtcbiAgQ09OVEFJTkVSUyxcbiAgRElTQ09OTkVDVEVEX05PREVTLFxuICBFTEVNRU5UX0NPTlRBSU5FUlMsXG4gIEkxOE5fREFUQSxcbiAgTVVMVElQTElFUixcbiAgTk9ERVMsXG4gIE5VTV9ST09UX05PREVTLFxuICBTZXJpYWxpemVkQ29udGFpbmVyVmlldyxcbiAgU2VyaWFsaXplZFZpZXcsXG4gIFRFTVBMQVRFX0lELFxuICBURU1QTEFURVMsXG59IGZyb20gJy4vaW50ZXJmYWNlcyc7XG5pbXBvcnQge2NhbGNQYXRoRm9yTm9kZSwgaXNEaXNjb25uZWN0ZWROb2RlfSBmcm9tICcuL25vZGVfbG9va3VwX3V0aWxzJztcbmltcG9ydCB7aXNJblNraXBIeWRyYXRpb25CbG9jaywgU0tJUF9IWURSQVRJT05fQVRUUl9OQU1FfSBmcm9tICcuL3NraXBfaHlkcmF0aW9uJztcbmltcG9ydCB7RVZFTlRfUkVQTEFZX0VOQUJMRURfREVGQVVMVCwgSVNfRVZFTlRfUkVQTEFZX0VOQUJMRUR9IGZyb20gJy4vdG9rZW5zJztcbmltcG9ydCB7XG4gIGdldExOb2RlRm9ySHlkcmF0aW9uLFxuICBOR0hfQVRUUl9OQU1FLFxuICBOR0hfREFUQV9LRVksXG4gIHByb2Nlc3NUZXh0Tm9kZUJlZm9yZVNlcmlhbGl6YXRpb24sXG4gIFRleHROb2RlTWFya2VyLFxufSBmcm9tICcuL3V0aWxzJztcblxuLyoqXG4gKiBBIGNvbGxlY3Rpb24gdGhhdCB0cmFja3MgYWxsIHNlcmlhbGl6ZWQgdmlld3MgKGBuZ2hgIERPTSBhbm5vdGF0aW9ucylcbiAqIHRvIGF2b2lkIGR1cGxpY2F0aW9uLiBBbiBhdHRlbXB0IHRvIGFkZCBhIGR1cGxpY2F0ZSB2aWV3IHJlc3VsdHMgaW4gdGhlXG4gKiBjb2xsZWN0aW9uIHJldHVybmluZyB0aGUgaW5kZXggb2YgdGhlIHByZXZpb3VzbHkgY29sbGVjdGVkIHNlcmlhbGl6ZWQgdmlldy5cbiAqIFRoaXMgcmVkdWNlcyB0aGUgbnVtYmVyIG9mIGFubm90YXRpb25zIG5lZWRlZCBmb3IgYSBnaXZlbiBwYWdlLlxuICovXG5jbGFzcyBTZXJpYWxpemVkVmlld0NvbGxlY3Rpb24ge1xuICBwcml2YXRlIHZpZXdzOiBTZXJpYWxpemVkVmlld1tdID0gW107XG4gIHByaXZhdGUgaW5kZXhCeUNvbnRlbnQgPSBuZXcgTWFwPHN0cmluZywgbnVtYmVyPigpO1xuXG4gIGFkZChzZXJpYWxpemVkVmlldzogU2VyaWFsaXplZFZpZXcpOiBudW1iZXIge1xuICAgIGNvbnN0IHZpZXdBc1N0cmluZyA9IEpTT04uc3RyaW5naWZ5KHNlcmlhbGl6ZWRWaWV3KTtcbiAgICBpZiAoIXRoaXMuaW5kZXhCeUNvbnRlbnQuaGFzKHZpZXdBc1N0cmluZykpIHtcbiAgICAgIGNvbnN0IGluZGV4ID0gdGhpcy52aWV3cy5sZW5ndGg7XG4gICAgICB0aGlzLnZpZXdzLnB1c2goc2VyaWFsaXplZFZpZXcpO1xuICAgICAgdGhpcy5pbmRleEJ5Q29udGVudC5zZXQodmlld0FzU3RyaW5nLCBpbmRleCk7XG4gICAgICByZXR1cm4gaW5kZXg7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmluZGV4QnlDb250ZW50LmdldCh2aWV3QXNTdHJpbmcpITtcbiAgfVxuXG