perf(ivy): move local references into consts array (#33129)
Follow-up from #32798. Moves the local references array into the component def's `consts` in order to make it compress better. Before: ``` const _c0 = ['foo', '']; SomeComp.ngComponentDef = defineComponent({ template: function() { element(0, 'div', null, _c0); } }); ``` After: ``` SomeComp.ngComponentDef = defineComponent({ consts: [['foo', '']], template: function() { element(0, 'div', null, 0); } }); ``` PR Close #33129
This commit is contained in:
@ -18,7 +18,7 @@ import {stringify} from '../util/stringify';
|
||||
import {EMPTY_ARRAY, EMPTY_OBJ} from './empty';
|
||||
import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_LOC_ID_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields';
|
||||
import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction} from './interfaces/definition';
|
||||
import {TAttributes} from './interfaces/node';
|
||||
import {TConstants} from './interfaces/node';
|
||||
// while SelectorFlags is unused here, it's required so that types don't get resolved lazily
|
||||
// see: https://github.com/Microsoft/web-build-tools/issues/1050
|
||||
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
|
||||
@ -172,8 +172,11 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
|
||||
*/
|
||||
template: ComponentTemplate<T>;
|
||||
|
||||
/** Constants for the nodes in the component's view. */
|
||||
consts?: TAttributes[];
|
||||
/**
|
||||
* Constants for the nodes in the component's view.
|
||||
* Includes attribute arrays, local definition arrays etc.
|
||||
*/
|
||||
consts?: TConstants;
|
||||
|
||||
/**
|
||||
* An array of `ngContent[selector]` values that were found in the template.
|
||||
|
@ -17,7 +17,7 @@ import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, RENDERER, TVIEW
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {appendChild, removeView} from '../node_manipulation';
|
||||
import {getBindingIndex, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {load} from '../util/view_utils';
|
||||
import {getConstant, load} from '../util/view_utils';
|
||||
|
||||
import {addToViewTree, createDirectivesInstances, createLContainer, createTNode, createTView, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared';
|
||||
|
||||
@ -56,8 +56,8 @@ export function ɵɵcontainer(index: number): void {
|
||||
* @param decls The number of nodes, local refs, and pipes for this template
|
||||
* @param vars The number of bindings for this template
|
||||
* @param tagName The name of the container element, if applicable
|
||||
* @param constsIndex Index of template in the `consts` array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
* @param attrsIndex Index of template attributes in the `consts` array.
|
||||
* @param localRefs Index of the local references in the `consts` array.
|
||||
* @param localRefExtractor A function which extracts local-refs values from the template.
|
||||
* Defaults to the current element associated with the local-ref.
|
||||
*
|
||||
@ -65,7 +65,7 @@ export function ɵɵcontainer(index: number): void {
|
||||
*/
|
||||
export function ɵɵtemplate(
|
||||
index: number, templateFn: ComponentTemplate<any>| null, decls: number, vars: number,
|
||||
tagName?: string | null, constsIndex?: number | null, localRefs?: string[] | null,
|
||||
tagName?: string | null, attrsIndex?: number | null, localRefsIndex?: number | null,
|
||||
localRefExtractor?: LocalRefExtractor) {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
@ -73,11 +73,11 @@ export function ɵɵtemplate(
|
||||
|
||||
// TODO: consider a separate node type for templates
|
||||
const tContainerNode = containerInternal(
|
||||
lView, index, tagName || null,
|
||||
tViewConsts === null || constsIndex == null ? null : tViewConsts[constsIndex]);
|
||||
lView, index, tagName || null, getConstant(tViewConsts, attrsIndex) as TAttributes);
|
||||
const localRefs = getConstant(tViewConsts, localRefsIndex) as string[];
|
||||
if (tView.firstTemplatePass) {
|
||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||
resolveDirectives(tView, lView, tContainerNode, localRefs || null);
|
||||
resolveDirectives(tView, lView, tContainerNode, localRefs);
|
||||
registerPostOrderHooks(tView, tContainerNode);
|
||||
|
||||
const embeddedTView = tContainerNode.tViews = createTView(
|
||||
|
@ -20,7 +20,7 @@ import {appendChild} from '../node_manipulation';
|
||||
import {decreaseElementDepthCount, getBindingIndex, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {setUpAttributes} from '../util/attrs_utils';
|
||||
import {getInitialStylingValue, hasClassInput, hasStyleInput, selectClassBasedInputName} from '../util/styling_utils';
|
||||
import {getNativeByTNode, getTNode} from '../util/view_utils';
|
||||
import {getConstant, getNativeByTNode, getTNode} from '../util/view_utils';
|
||||
|
||||
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, matchingSchemas, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
|
||||
import {registerInitialStylingOnTNode} from './styling';
|
||||
@ -32,8 +32,8 @@ import {registerInitialStylingOnTNode} from './styling';
|
||||
*
|
||||
* @param index Index of the element in the LView array
|
||||
* @param name Name of the DOM Node
|
||||
* @param constsIndex Index of the element in the `consts` array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
* @param attrsIndex Index of the element's attributes in the `consts` array.
|
||||
* @param localRefsIndex Index of the element's local references in the `consts` array.
|
||||
*
|
||||
* Attributes and localRefs are passed as an array of strings where elements with an even index
|
||||
* hold an attribute name and elements with an odd index hold an attribute value, ex.:
|
||||
@ -42,25 +42,25 @@ import {registerInitialStylingOnTNode} from './styling';
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵelementStart(
|
||||
index: number, name: string, constsIndex?: number | null, localRefs?: string[] | null): void {
|
||||
index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number): void {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
const tViewConsts = tView.consts;
|
||||
const consts = tViewConsts === null || constsIndex == null ? null : tViewConsts[constsIndex];
|
||||
const attrs = getConstant(tViewConsts, attrsIndex) as TAttributes;
|
||||
const localRefs = getConstant(tViewConsts, localRefsIndex) as string[];
|
||||
ngDevMode && assertEqual(
|
||||
getBindingIndex(), tView.bindingStartIndex,
|
||||
'elements should be created before any bindings');
|
||||
|
||||
ngDevMode && ngDevMode.rendererCreateElement++;
|
||||
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
|
||||
const renderer = lView[RENDERER];
|
||||
const native = lView[index + HEADER_OFFSET] = elementCreate(name, renderer, getNamespace());
|
||||
const tNode = getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, consts);
|
||||
const tNode = getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, attrs);
|
||||
|
||||
if (consts != null) {
|
||||
const lastAttrIndex = setUpAttributes(renderer, native, consts);
|
||||
if (attrs != null) {
|
||||
const lastAttrIndex = setUpAttributes(renderer, native, attrs);
|
||||
if (tView.firstTemplatePass) {
|
||||
registerInitialStylingOnTNode(tNode, consts, lastAttrIndex);
|
||||
registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ export function ɵɵelementStart(
|
||||
// and `[class]` bindings work for multiple directives.)
|
||||
if (tView.firstTemplatePass) {
|
||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||
const hasDirectives = resolveDirectives(tView, lView, tNode, localRefs || null);
|
||||
const hasDirectives = resolveDirectives(tView, lView, tNode, localRefs);
|
||||
ngDevMode && validateElement(lView, native, tNode, hasDirectives);
|
||||
|
||||
if (tView.queries !== null) {
|
||||
@ -148,14 +148,14 @@ export function ɵɵelementEnd(): void {
|
||||
*
|
||||
* @param index Index of the element in the data array
|
||||
* @param name Name of the DOM Node
|
||||
* @param constsIndex Index of the element in the `consts` array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
* @param attrsIndex Index of the element's attributes in the `consts` array.
|
||||
* @param localRefsIndex Index of the element's local references in the `consts` array.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵelement(
|
||||
index: number, name: string, constsIndex?: number | null, localRefs?: string[] | null): void {
|
||||
ɵɵelementStart(index, name, constsIndex, localRefs);
|
||||
index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number): void {
|
||||
ɵɵelementStart(index, name, attrsIndex, localRefsIndex);
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import {HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {appendChild} from '../node_manipulation';
|
||||
import {getBindingIndex, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {getConstant} from '../util/view_utils';
|
||||
|
||||
import {createDirectivesInstances, executeContentQueries, getOrCreateTNode, resolveDirectives, saveResolvedLocalsInData} from './shared';
|
||||
import {registerInitialStylingOnTNode} from './styling';
|
||||
@ -26,8 +27,8 @@ import {registerInitialStylingOnTNode} from './styling';
|
||||
* The instruction must later be followed by `elementContainerEnd()` call.
|
||||
*
|
||||
* @param index Index of the element in the LView array
|
||||
* @param constsIndex Index of the container in the `consts` array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
* @param attrsIndex Index of the container attributes in the `consts` array.
|
||||
* @param localRefsIndex Index of the container's local references in the `consts` array.
|
||||
*
|
||||
* Even if this instruction accepts a set of attributes no actual attribute values are propagated to
|
||||
* the DOM (as a comment node can't have attributes). Attributes are here only for directive
|
||||
@ -36,13 +37,14 @@ import {registerInitialStylingOnTNode} from './styling';
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵelementContainerStart(
|
||||
index: number, constsIndex?: number | null, localRefs?: string[] | null): void {
|
||||
index: number, attrsIndex?: number | null, localRefsIndex?: number): void {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
const renderer = lView[RENDERER];
|
||||
const tagName = 'ng-container';
|
||||
const tViewConsts = tView.consts;
|
||||
const consts = tViewConsts === null || constsIndex == null ? null : tViewConsts[constsIndex];
|
||||
const attrs = getConstant(tViewConsts, attrsIndex) as TAttributes;
|
||||
const localRefs = getConstant(tViewConsts, localRefsIndex) as string[];
|
||||
ngDevMode && assertEqual(
|
||||
getBindingIndex(), tView.bindingStartIndex,
|
||||
'element containers should be created before any bindings');
|
||||
@ -53,12 +55,12 @@ export function ɵɵelementContainerStart(
|
||||
|
||||
ngDevMode && assertDataInRange(lView, index - 1);
|
||||
const tNode =
|
||||
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.ElementContainer, tagName, consts);
|
||||
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.ElementContainer, tagName, attrs);
|
||||
|
||||
if (consts && tView.firstTemplatePass) {
|
||||
if (attrs && tView.firstTemplatePass) {
|
||||
// While ng-container doesn't necessarily support styling, we use the style context to identify
|
||||
// and execute directives on the ng-container.
|
||||
registerInitialStylingOnTNode(tNode, consts as TAttributes, 0);
|
||||
registerInitialStylingOnTNode(tNode, attrs, 0);
|
||||
}
|
||||
|
||||
appendChild(native, tNode, lView);
|
||||
@ -66,7 +68,7 @@ export function ɵɵelementContainerStart(
|
||||
|
||||
if (tView.firstTemplatePass) {
|
||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||
resolveDirectives(tView, lView, tNode, localRefs || null);
|
||||
resolveDirectives(tView, lView, tNode, localRefs);
|
||||
if (tView.queries) {
|
||||
tView.queries.elementStart(tView, tNode);
|
||||
}
|
||||
@ -114,13 +116,13 @@ export function ɵɵelementContainerEnd(): void {
|
||||
* and {@link elementContainerEnd}
|
||||
*
|
||||
* @param index Index of the element in the LView array
|
||||
* @param constsIndex Index of the container in the `consts` array.
|
||||
* @param localRefs A set of local reference bindings on the element.
|
||||
* @param attrsIndex Index of the container attributes in the `consts` array.
|
||||
* @param localRefsIndex Index of the container's local references in the `consts` array.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵelementContainer(
|
||||
index: number, constsIndex?: number | null, localRefs?: string[] | null): void {
|
||||
ɵɵelementContainerStart(index, constsIndex, localRefs);
|
||||
index: number, attrsIndex?: number | null, localRefsIndex?: number): void {
|
||||
ɵɵelementContainerStart(index, attrsIndex, localRefsIndex);
|
||||
ɵɵelementContainerEnd();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {initNgDevMode} from '../../util/ng_dev_mode';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
|
||||
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
|
||||
import {PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
|
||||
import {PropertyAliases, TConstants, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
|
||||
import {SelectorFlags} from '../interfaces/projection';
|
||||
import {TQueries} from '../interfaces/query';
|
||||
import {RComment, RElement, RNode} from '../interfaces/renderer';
|
||||
@ -102,7 +102,7 @@ export const TViewConstructor = class TView implements ITView {
|
||||
public pipeRegistry: PipeDefList|null, //
|
||||
public firstChild: TNode|null, //
|
||||
public schemas: SchemaMetadata[]|null, //
|
||||
public consts: TAttributes[]|null, //
|
||||
public consts: TConstants|null, //
|
||||
) {}
|
||||
|
||||
get template_(): string {
|
||||
|
@ -23,7 +23,7 @@ import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, re
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TConstants, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
||||
import {RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
|
||||
@ -588,7 +588,7 @@ export function createTView(
|
||||
viewIndex: number, templateFn: ComponentTemplate<any>| null, decls: number, vars: number,
|
||||
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
||||
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null,
|
||||
consts: TAttributes[] | null): TView {
|
||||
consts: TConstants | null): TView {
|
||||
ngDevMode && ngDevMode.tView++;
|
||||
const bindingStartIndex = HEADER_OFFSET + decls;
|
||||
// This length does not yet contain host bindings from child directives because at this point,
|
||||
@ -627,7 +627,7 @@ export function createTView(
|
||||
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
|
||||
null, // firstChild: TNode|null,
|
||||
schemas, // schemas: SchemaMetadata[]|null,
|
||||
consts) : // consts: TAttributes[]
|
||||
consts) : // consts: TConstants|null
|
||||
{
|
||||
id: viewIndex,
|
||||
blueprint: blueprint,
|
||||
@ -1048,7 +1048,7 @@ export function resolveDirectives(
|
||||
if (!getBindingsEnabled()) return false;
|
||||
|
||||
const directives: DirectiveDef<any>[]|null = findDirectiveMatches(tView, lView, tNode);
|
||||
const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
|
||||
const exportsMap: ({[key: string]: number} | null) = localRefs === null ? null : {'': -1};
|
||||
let hasDirectives = false;
|
||||
|
||||
if (directives !== null) {
|
||||
|
@ -10,7 +10,7 @@ import {SchemaMetadata, ViewEncapsulation} from '../../core';
|
||||
import {ProcessProvidersFunction} from '../../di/interface/provider';
|
||||
import {Type} from '../../interface/type';
|
||||
|
||||
import {TAttributes} from './node';
|
||||
import {TConstants} from './node';
|
||||
import {CssSelectorList} from './projection';
|
||||
import {TView} from './view';
|
||||
|
||||
@ -227,7 +227,7 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||
readonly template: ComponentTemplate<T>;
|
||||
|
||||
/** Constants associated with the component's view. */
|
||||
readonly consts: TAttributes[]|null;
|
||||
readonly consts: TConstants|null;
|
||||
|
||||
/**
|
||||
* An array of `ngContent[selector]` values that were found in the template.
|
||||
|
@ -224,6 +224,13 @@ export const enum AttributeMarker {
|
||||
*/
|
||||
export type TAttributes = (string | AttributeMarker | CssSelector)[];
|
||||
|
||||
/**
|
||||
* Constants that are associated with a view. Includes:
|
||||
* - Attribute arrays.
|
||||
* - Local definition arrays.
|
||||
*/
|
||||
export type TConstants = (TAttributes | string)[];
|
||||
|
||||
/**
|
||||
* Binding data (flyweight) for a particular node that is shared between all templates
|
||||
* of a specific type.
|
||||
|
@ -15,7 +15,7 @@ import {Sanitizer} from '../../sanitization/sanitizer';
|
||||
import {LContainer} from './container';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBindingsFunction, PipeDef, PipeDefList, ViewQueriesFunction} from './definition';
|
||||
import {I18nUpdateOpCodes, TI18n} from './i18n';
|
||||
import {TAttributes, TElementNode, TNode, TViewNode} from './node';
|
||||
import {TAttributes, TConstants, TElementNode, TNode, TViewNode} from './node';
|
||||
import {PlayerHandler} from './player';
|
||||
import {LQueries, TQueries} from './query';
|
||||
import {RElement, Renderer3, RendererFactory3} from './renderer';
|
||||
@ -555,10 +555,10 @@ export interface TView {
|
||||
schemas: SchemaMetadata[]|null;
|
||||
|
||||
/**
|
||||
* Array of attributes for all of the elements in the view. Used
|
||||
* for directive matching and attribute bindings.
|
||||
* Array of constants for the view. Includes attribute arrays, local definition arrays etc.
|
||||
* Used for directive matching, attribute bindings, local definitions and more.
|
||||
*/
|
||||
consts: TAttributes[]|null;
|
||||
consts: TConstants|null;
|
||||
}
|
||||
|
||||
export const enum RootContextFlags {Empty = 0b00, DetectChanges = 0b01, FlushPlayers = 0b10}
|
||||
|
@ -10,7 +10,7 @@ import {assertDataInRange, assertDefined, assertDomNode, assertGreaterThan, asse
|
||||
import {assertTNodeForLView} from '../assert';
|
||||
import {LContainer, TYPE} from '../interfaces/container';
|
||||
import {LContext, MONKEY_PATCH_KEY_NAME} from '../interfaces/context';
|
||||
import {TNode} from '../interfaces/node';
|
||||
import {TConstants, TNode} from '../interfaces/node';
|
||||
import {RNode, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {isLContainer, isLView} from '../interfaces/type_checks';
|
||||
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, RENDERER, TData, TVIEW} from '../interfaces/view';
|
||||
@ -175,6 +175,11 @@ export function viewAttachedToContainer(view: LView): boolean {
|
||||
return isLContainer(view[PARENT]);
|
||||
}
|
||||
|
||||
/** Returns a constant from `TConstants` instance. */
|
||||
export function getConstant(consts: TConstants | null, index: number | null | undefined) {
|
||||
return consts === null || index == null ? null : consts[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the pre-order hook flags of the view.
|
||||
* @param lView the LView on which the flags are reset
|
||||
|
Reference in New Issue
Block a user