From a11bcf7184b0600fada2626ab748cbf2cf765c55 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Sun, 15 Aug 2021 14:30:25 +0800 Subject: [PATCH 1/3] fix: overflows with absolutely positioned content --- src/css/property-descriptors/overflow.ts | 5 +- src/render/canvas/canvas-renderer.ts | 10 +-- src/render/stacking-context.ts | 90 +++++++++++++++++------- tests/reftests/overflow/overflow.html | 19 +++++ 4 files changed, 92 insertions(+), 32 deletions(-) diff --git a/src/css/property-descriptors/overflow.ts b/src/css/property-descriptors/overflow.ts index 19ac5444c..ccb13a823 100644 --- a/src/css/property-descriptors/overflow.ts +++ b/src/css/property-descriptors/overflow.ts @@ -5,7 +5,8 @@ export enum OVERFLOW { VISIBLE = 0, HIDDEN = 1, SCROLL = 2, - AUTO = 3 + CLIP = 3, + AUTO = 4 } export const overflow: IPropertyListDescriptor = { @@ -20,6 +21,8 @@ export const overflow: IPropertyListDescriptor = { return OVERFLOW.HIDDEN; case 'scroll': return OVERFLOW.SCROLL; + case 'clip': + return OVERFLOW.CLIP; case 'auto': return OVERFLOW.AUTO; case 'visible': diff --git a/src/render/canvas/canvas-renderer.ts b/src/render/canvas/canvas-renderer.ts index 8ecedb531..48e731524 100644 --- a/src/render/canvas/canvas-renderer.ts +++ b/src/render/canvas/canvas-renderer.ts @@ -87,12 +87,12 @@ export class CanvasRenderer extends Renderer { ); } - applyEffects(effects: IElementEffect[], target: EffectTarget): void { + applyEffects(effects: IElementEffect[]): void { while (this._activeEffects.length) { this.popEffect(); } - effects.filter((effect) => contains(effect.target, target)).forEach((effect) => this.applyEffect(effect)); + effects.forEach((effect) => this.applyEffect(effect)); } applyEffect(effect: IElementEffect): void { @@ -293,7 +293,7 @@ export class CanvasRenderer extends Renderer { } async renderNodeContent(paint: ElementPaint): Promise { - this.applyEffects(paint.effects, EffectTarget.CONTENT); + this.applyEffects(paint.getEffects(EffectTarget.CONTENT)); const container = paint.container; const curves = paint.curves; const styles = container.styles; @@ -692,7 +692,7 @@ export class CanvasRenderer extends Renderer { } async renderNodeBackgroundAndBorders(paint: ElementPaint): Promise { - this.applyEffects(paint.effects, EffectTarget.BACKGROUND_BORDERS); + this.applyEffects(paint.getEffects(EffectTarget.BACKGROUND_BORDERS)); const styles = paint.container.styles; const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length; @@ -906,7 +906,7 @@ export class CanvasRenderer extends Renderer { const stack = parseStackingContexts(element); await this.renderStack(stack); - this.applyEffects([], EffectTarget.BACKGROUND_BORDERS); + this.applyEffects([]); return this.canvas; } } diff --git a/src/render/stacking-context.ts b/src/render/stacking-context.ts index 9da970c9c..b7b24710b 100644 --- a/src/render/stacking-context.ts +++ b/src/render/stacking-context.ts @@ -8,6 +8,7 @@ import {DISPLAY} from '../css/property-descriptors/display'; import {OLElementContainer} from '../dom/elements/ol-element-container'; import {LIElementContainer} from '../dom/elements/li-element-container'; import {createCounterText} from '../css/types/functions/counter'; +import {POSITION} from '../css/property-descriptors/position'; export class StackingContext { element: ElementPaint; @@ -32,49 +33,86 @@ export class StackingContext { } export class ElementPaint { - container: ElementContainer; - effects: IElementEffect[]; - curves: BoundCurves; + readonly overflowEffects: ClipEffect[] = this.getParentOverflowEffect(); + readonly effects: IElementEffect[] = this.getParentEffects(); + readonly curves: BoundCurves; listValue?: string; - constructor(element: ElementContainer, parentStack: IElementEffect[]) { - this.container = element; - this.effects = parentStack.slice(0); - this.curves = new BoundCurves(element); - if (element.styles.opacity < 1) { - this.effects.push(new OpacityEffect(element.styles.opacity)); + constructor(readonly container: ElementContainer, readonly parent: ElementPaint | null) { + this.curves = new BoundCurves(this.container); + if (this.container.styles.opacity < 1) { + this.effects.push(new OpacityEffect(this.container.styles.opacity)); } - if (element.styles.transform !== null) { - const offsetX = element.bounds.left + element.styles.transformOrigin[0].number; - const offsetY = element.bounds.top + element.styles.transformOrigin[1].number; - const matrix = element.styles.transform; + if (this.container.styles.transform !== null) { + const offsetX = this.container.bounds.left + this.container.styles.transformOrigin[0].number; + const offsetY = this.container.bounds.top + this.container.styles.transformOrigin[1].number; + const matrix = this.container.styles.transform; this.effects.push(new TransformEffect(offsetX, offsetY, matrix)); } - if (element.styles.overflowX !== OVERFLOW.VISIBLE) { + if (this.container.styles.overflowX !== OVERFLOW.VISIBLE) { const borderBox = calculateBorderBoxPath(this.curves); const paddingBox = calculatePaddingBoxPath(this.curves); if (equalPath(borderBox, paddingBox)) { - this.effects.push(new ClipEffect(borderBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT)); + this.overflowEffects.push( + new ClipEffect(borderBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT) + ); } else { - this.effects.push(new ClipEffect(borderBox, EffectTarget.BACKGROUND_BORDERS)); - this.effects.push(new ClipEffect(paddingBox, EffectTarget.CONTENT)); + this.overflowEffects.push(new ClipEffect(borderBox, EffectTarget.BACKGROUND_BORDERS)); + this.overflowEffects.push(new ClipEffect(paddingBox, EffectTarget.CONTENT)); } } } - getParentEffects(): IElementEffect[] { - const effects = this.effects.slice(0); - if (this.container.styles.overflowX !== OVERFLOW.VISIBLE) { - const borderBox = calculateBorderBoxPath(this.curves); - const paddingBox = calculatePaddingBoxPath(this.curves); + getEffects(target: EffectTarget): IElementEffect[] { + const effects = this.effects.concat(this.overflowEffects); + return effects.filter((effect) => contains(effect.target, target)); + } + + private getPositionAncestor(): ElementPaint { + let parent = this.parent; + while (parent) { + if (parent.container.styles.position !== POSITION.STATIC) { + return parent; + } + parent = parent.parent; + } + + return this; + } + + private getParentOverflowEffect(): ClipEffect[] { + if (!this.parent) { + return []; + } + const parent = + [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(this.container.styles.position) !== -1 + ? this.getPositionAncestor() + : this.parent; + + const parentOverflowEffects = parent.overflowEffects ?? []; + + if (parent.container.styles.overflowX !== OVERFLOW.VISIBLE) { + const borderBox = calculateBorderBoxPath(parent.curves); + const paddingBox = calculatePaddingBoxPath(parent.curves); if (!equalPath(borderBox, paddingBox)) { - effects.push(new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT)); + return parentOverflowEffects.concat([ + new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT) + ]); } } - return effects; + + return parentOverflowEffects.slice(0); + } + + getParentEffects(): IElementEffect[] { + if (!this.parent) { + return []; + } + + return this.parent.effects.slice(0); } } @@ -87,7 +125,7 @@ const parseStackTree = ( parent.container.elements.forEach((child) => { const treatAsRealStackingContext = contains(child.flags, FLAGS.CREATES_REAL_STACKING_CONTEXT); const createsStackingContext = contains(child.flags, FLAGS.CREATES_STACKING_CONTEXT); - const paintContainer = new ElementPaint(child, parent.getParentEffects()); + const paintContainer = new ElementPaint(child, parent); if (contains(child.styles.display, DISPLAY.LIST_ITEM)) { listItems.push(paintContainer); } @@ -182,7 +220,7 @@ const processListItems = (owner: ElementContainer, elements: ElementPaint[]) => }; export const parseStackingContexts = (container: ElementContainer): StackingContext => { - const paintContainer = new ElementPaint(container, []); + const paintContainer = new ElementPaint(container, null); const root = new StackingContext(paintContainer); const listItems: ElementPaint[] = []; parseStackTree(paintContainer, root, root, listItems); diff --git a/tests/reftests/overflow/overflow.html b/tests/reftests/overflow/overflow.html index 6a9f0e9a6..b9d1ea3e6 100644 --- a/tests/reftests/overflow/overflow.html +++ b/tests/reftests/overflow/overflow.html @@ -51,6 +51,9 @@ .scroll { overflow: scroll; } + .clip { + overflow: clip; + } .auto { overflow: auto; } @@ -70,6 +73,10 @@ scroll

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.

+
+ clip +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.

+
auto

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.

@@ -129,5 +136,17 @@

Overflow: hidden

+ + + + From 26c260d57aa09ea7c77093b7399125e1973c9e11 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Sun, 15 Aug 2021 18:51:34 +0800 Subject: [PATCH 2/3] fix: effect ordering --- src/render/effects.ts | 23 +++---------- src/render/stacking-context.ts | 62 ++++++++-------------------------- 2 files changed, 18 insertions(+), 67 deletions(-) diff --git a/src/render/effects.ts b/src/render/effects.ts index 1bb428349..d7d1b9504 100644 --- a/src/render/effects.ts +++ b/src/render/effects.ts @@ -20,36 +20,21 @@ export interface IElementEffect { export class TransformEffect implements IElementEffect { readonly type: EffectType = EffectType.TRANSFORM; readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT; - readonly offsetX: number; - readonly offsetY: number; - readonly matrix: Matrix; - - constructor(offsetX: number, offsetY: number, matrix: Matrix) { - this.offsetX = offsetX; - this.offsetY = offsetY; - this.matrix = matrix; - } + + constructor(readonly offsetX: number, readonly offsetY: number, readonly matrix: Matrix) {} } export class ClipEffect implements IElementEffect { readonly type: EffectType = EffectType.CLIP; - readonly target: number; - readonly path: Path[]; - constructor(path: Path[], target: EffectTarget) { - this.target = target; - this.path = path; - } + constructor(readonly path: Path[], readonly target: EffectTarget) {} } export class OpacityEffect implements IElementEffect { readonly type: EffectType = EffectType.OPACITY; readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT; - readonly opacity: number; - constructor(opacity: number) { - this.opacity = opacity; - } + constructor(readonly opacity: number) {} } export const isTransformEffect = (effect: IElementEffect): effect is TransformEffect => diff --git a/src/render/stacking-context.ts b/src/render/stacking-context.ts index b7b24710b..cb7dcc088 100644 --- a/src/render/stacking-context.ts +++ b/src/render/stacking-context.ts @@ -1,7 +1,7 @@ import {ElementContainer, FLAGS} from '../dom/element-container'; import {contains} from '../core/bitwise'; import {BoundCurves, calculateBorderBoxPath, calculatePaddingBoxPath} from './bound-curves'; -import {ClipEffect, EffectTarget, IElementEffect, OpacityEffect, TransformEffect} from './effects'; +import {ClipEffect, EffectTarget, IElementEffect, isClipEffect, OpacityEffect, TransformEffect} from './effects'; import {OVERFLOW} from '../css/property-descriptors/overflow'; import {equalPath} from './path'; import {DISPLAY} from '../css/property-descriptors/display'; @@ -33,8 +33,7 @@ export class StackingContext { } export class ElementPaint { - readonly overflowEffects: ClipEffect[] = this.getParentOverflowEffect(); - readonly effects: IElementEffect[] = this.getParentEffects(); + readonly effects: IElementEffect[] = []; readonly curves: BoundCurves; listValue?: string; @@ -56,63 +55,30 @@ export class ElementPaint { const paddingBox = calculatePaddingBoxPath(this.curves); if (equalPath(borderBox, paddingBox)) { - this.overflowEffects.push( - new ClipEffect(borderBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT) - ); + this.effects.push(new ClipEffect(borderBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT)); } else { - this.overflowEffects.push(new ClipEffect(borderBox, EffectTarget.BACKGROUND_BORDERS)); - this.overflowEffects.push(new ClipEffect(paddingBox, EffectTarget.CONTENT)); + this.effects.push(new ClipEffect(borderBox, EffectTarget.BACKGROUND_BORDERS)); + this.effects.push(new ClipEffect(paddingBox, EffectTarget.CONTENT)); } } } getEffects(target: EffectTarget): IElementEffect[] { - const effects = this.effects.concat(this.overflowEffects); - return effects.filter((effect) => contains(effect.target, target)); - } - - private getPositionAncestor(): ElementPaint { + let inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(this.container.styles.position) === -1; let parent = this.parent; + const effects = this.effects.slice(0); while (parent) { - if (parent.container.styles.position !== POSITION.STATIC) { - return parent; - } - parent = parent.parent; - } - - return this; - } - - private getParentOverflowEffect(): ClipEffect[] { - if (!this.parent) { - return []; - } - const parent = - [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(this.container.styles.position) !== -1 - ? this.getPositionAncestor() - : this.parent; - - const parentOverflowEffects = parent.overflowEffects ?? []; - - if (parent.container.styles.overflowX !== OVERFLOW.VISIBLE) { - const borderBox = calculateBorderBoxPath(parent.curves); - const paddingBox = calculatePaddingBoxPath(parent.curves); - if (!equalPath(borderBox, paddingBox)) { - return parentOverflowEffects.concat([ - new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT) - ]); + if (inFlow || parent.container.styles.position !== POSITION.STATIC || !parent.parent) { + effects.unshift(...parent.effects); + inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(parent.container.styles.position) === -1; + } else { + effects.unshift(...parent.effects.filter((effect) => !isClipEffect(effect))); } - } - return parentOverflowEffects.slice(0); - } - - getParentEffects(): IElementEffect[] { - if (!this.parent) { - return []; + parent = parent.parent; } - return this.parent.effects.slice(0); + return effects.filter((effect) => contains(effect.target, target)); } } From 80db597b29a1f26920feb423d7b8daf97bbb7145 Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Sun, 15 Aug 2021 19:23:13 +0800 Subject: [PATCH 3/3] fix: border clipping --- src/render/stacking-context.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/render/stacking-context.ts b/src/render/stacking-context.ts index cb7dcc088..c5ac088b0 100644 --- a/src/render/stacking-context.ts +++ b/src/render/stacking-context.ts @@ -68,11 +68,21 @@ export class ElementPaint { let parent = this.parent; const effects = this.effects.slice(0); while (parent) { + const croplessEffects = parent.effects.filter((effect) => !isClipEffect(effect)); if (inFlow || parent.container.styles.position !== POSITION.STATIC || !parent.parent) { - effects.unshift(...parent.effects); + effects.unshift(...croplessEffects); inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(parent.container.styles.position) === -1; + if (parent.container.styles.overflowX !== OVERFLOW.VISIBLE) { + const borderBox = calculateBorderBoxPath(parent.curves); + const paddingBox = calculatePaddingBoxPath(parent.curves); + if (!equalPath(borderBox, paddingBox)) { + effects.unshift( + new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT) + ); + } + } } else { - effects.unshift(...parent.effects.filter((effect) => !isClipEffect(effect))); + effects.unshift(...croplessEffects); } parent = parent.parent;