UNPKG

@angular/core

Version:

Angular - the core framework

557 lines • 82.8 kB
/** * @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