Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into mob/concatenate-adj…
Browse files Browse the repository at this point in the history
…acent-text-nodes-party-again-yay
  • Loading branch information
nolanlawson committed Dec 20, 2024
2 parents 36c2e11 + 593da92 commit 8c8e469
Show file tree
Hide file tree
Showing 24 changed files with 146 additions and 49 deletions.
19 changes: 0 additions & 19 deletions packages/@lwc/engine-core/src/framework/attributes.ts

This file was deleted.

9 changes: 5 additions & 4 deletions packages/@lwc/engine-core/src/framework/html-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import {
AriaPropNameToAttrNameMap,
create,
forEach,
getPropertyDescriptor,
isUndefined,
keys,
AriaPropNameToAttrNameMap,
REFLECTIVE_GLOBAL_PROPERTY_SET,
} from '@lwc/shared';

import { HTMLElementPrototype } from './html-element';
import { defaultDefHTMLPropertyNames } from './attributes';

/**
* This is a descriptor map that contains
Expand All @@ -32,12 +32,13 @@ forEach.call(keys(AriaPropNameToAttrNameMap), (propName: string) => {
HTMLElementOriginalDescriptors[propName] = descriptor;
}
});
forEach.call(defaultDefHTMLPropertyNames, (propName) => {

for (const propName of REFLECTIVE_GLOBAL_PROPERTY_SET) {
// Note: intentionally using our in-house getPropertyDescriptor instead of getOwnPropertyDescriptor here because
// in IE11, id property is on Element.prototype instead of HTMLElement, and we suspect that more will fall into
// this category, so, better to be sure.
const descriptor = getPropertyDescriptor(HTMLElementPrototype, propName);
if (!isUndefined(descriptor)) {
HTMLElementOriginalDescriptors[propName] = descriptor;
}
});
}
15 changes: 15 additions & 0 deletions packages/@lwc/engine-dom/src/language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2024, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

import { getOwnPropertyDescriptors } from '@lwc/shared';

// Like @lwc/shared, but for DOM APIs

export const ElementDescriptors = getOwnPropertyDescriptors(Element.prototype);

export const ElementAttachShadow = ElementDescriptors.attachShadow.value!;
export const ElementShadowRootGetter = ElementDescriptors.shadowRoot.get!;
9 changes: 6 additions & 3 deletions packages/@lwc/engine-dom/src/renderer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { assert, isNull, isUndefined } from '@lwc/shared';
import { ElementAttachShadow, ElementShadowRootGetter } from '../language';

function cloneNode(node: Node, deep: boolean): Node {
return node.cloneNode(deep);
Expand Down Expand Up @@ -57,10 +58,12 @@ function attachShadow(element: Element, options: ShadowRootInit): ShadowRoot {
// 1. upon initial load with an SSR-generated DOM, while in Shadow render mode
// 2. when a webapp author places <c-app> in their static HTML and mounts their
// root component with customElement.define('c-app', Ctor)
if (!isNull(element.shadowRoot)) {
return element.shadowRoot;
// see W-17441501
const shadowRoot = ElementShadowRootGetter.call(element);
if (!isNull(shadowRoot)) {
return shadowRoot;
}
return element.attachShadow(options);
return ElementAttachShadow.call(element, options);
}

function setText(node: Node, content: string): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<x-attribute-component-global-html>
<template shadowrootmode="open">
<x-child accesskey="A" class="bar foo" contenteditable="true" data-test="test" draggable="true" hidden id="foo" itemprop="foo" spellcheck="true" style="color: red; background: blue;" tabindex="-1">
<x-child accesskey="A" class="bar foo" data-test="test" draggable="true" hidden id="foo" spellcheck="true" style="color: red; background: blue;" tabindex="-1">
<template shadowrootmode="open">
Passthrough properties:
<ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<x-component>
<template shadowrootmode="open">
<x-child accesskey="foo" aria-activedescendant="foo" aria-atomic="foo" aria-autocomplete="foo" aria-busy="foo" aria-checked="foo" aria-colcount="foo" aria-colindex="foo" aria-colspan="foo" aria-controls="foo" aria-current="foo" aria-describedby="foo" aria-details="foo" aria-disabled="foo" aria-errormessage="foo" aria-expanded="foo" aria-flowto="foo" aria-haspopup="foo" aria-hidden="foo" aria-invalid="foo" aria-keyshortcuts="foo" aria-label="foo" aria-labelledby="foo" aria-level="foo" aria-live="foo" aria-modal="foo" aria-multiline="foo" aria-multiselectable="foo" aria-orientation="foo" aria-owns="foo" aria-placeholder="foo" aria-posinset="foo" aria-pressed="foo" aria-readonly="foo" aria-relevant="foo" aria-required="foo" aria-roledescription="foo" aria-rowcount="foo" aria-rowindex="foo" aria-rowspan="foo" aria-selected="foo" aria-setsize="foo" aria-sort="foo" aria-valuemax="foo" aria-valuemin="foo" aria-valuenow="foo" aria-valuetext="foo" autocapitalize="foo" autofocus="foo" contenteditable="foo" dir="foo" draggable="foo" enterkeyhint="foo" exportparts="foo" id="foo" inputmode="foo" itemid="foo" itemprop="foo" itemref="foo" itemscope="foo" itemtype="foo" lang="foo" nonce="foo" role="foo" spellcheck="foo" tabindex="foo" title="foo" translate="foo">
<x-child accesskey="foo" aria-activedescendant="foo" aria-atomic="foo" aria-autocomplete="foo" aria-busy="foo" aria-checked="foo" aria-colcount="foo" aria-colindex="foo" aria-colspan="foo" aria-controls="foo" aria-current="foo" aria-describedby="foo" aria-details="foo" aria-disabled="foo" aria-errormessage="foo" aria-expanded="foo" aria-flowto="foo" aria-haspopup="foo" aria-hidden="foo" aria-invalid="foo" aria-keyshortcuts="foo" aria-label="foo" aria-labelledby="foo" aria-level="foo" aria-live="foo" aria-modal="foo" aria-multiline="foo" aria-multiselectable="foo" aria-orientation="foo" aria-owns="foo" aria-placeholder="foo" aria-posinset="foo" aria-pressed="foo" aria-readonly="foo" aria-relevant="foo" aria-required="foo" aria-roledescription="foo" aria-rowcount="foo" aria-rowindex="foo" aria-rowspan="foo" aria-selected="foo" aria-setsize="foo" aria-sort="foo" aria-valuemax="foo" aria-valuemin="foo" aria-valuenow="foo" aria-valuetext="foo" dir="foo" draggable="foo" exportparts="foo" id="foo" lang="foo" role="foo" spellcheck="foo" tabindex="foo" title="foo">
<template shadowrootmode="open">
<span>
accessKey: foo
Expand Down
32 changes: 17 additions & 15 deletions packages/@lwc/engine-server/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {
isAriaAttribute,
isBooleanAttribute,
isFunction,
isGlobalHtmlAttribute,
isNull,
isUndefined,
noop,
REFLECTIVE_GLOBAL_PROPERTY_SET,
StringToLowerCase,
} from '@lwc/shared';

Expand Down Expand Up @@ -157,46 +157,46 @@ function attachShadow(element: E, config: ShadowRootInit) {
return element[HostShadowRootKey] as any;
}

function getProperty(node: N, key: string) {
if (key in node) {
return (node as any)[key];
function getProperty(node: N, propName: string) {
if (propName in node) {
return (node as any)[propName];
}

if (node[HostTypeKey] === HostNodeType.Element) {
const attrName = htmlPropertyToAttribute(key);
const attrName = htmlPropertyToAttribute(propName);

// Handle all the boolean properties.
if (isBooleanAttribute(attrName, node.tagName)) {
return getAttribute(node, attrName) ?? false;
}

// Handle global html attributes and AOM.
if (isGlobalHtmlAttribute(attrName) || isAriaAttribute(attrName)) {
if (REFLECTIVE_GLOBAL_PROPERTY_SET.has(propName) || isAriaAttribute(attrName)) {
return getAttribute(node, attrName);
}

// Handle special elements live bindings. The checked property is already handled above
// in the boolean case.
if (node.tagName === 'input' && key === 'value') {
if (node.tagName === 'input' && propName === 'value') {
return getAttribute(node, 'value') ?? '';
}
}

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.error(`Unexpected "${key}" property access from the renderer`);
console.error(`Unexpected "${propName}" property access from the renderer`);
}
}

function setProperty(node: N, key: string, value: any): void {
if (key in node) {
return ((node as any)[key] = value);
function setProperty(node: N, propName: string, value: any): void {
if (propName in node) {
return ((node as any)[propName] = value);
}

if (node[HostTypeKey] === HostNodeType.Element) {
const attrName = htmlPropertyToAttribute(key);
const attrName = htmlPropertyToAttribute(propName);

if (key === 'innerHTML') {
if (propName === 'innerHTML') {
node[HostChildrenKey] = [
{
[HostTypeKey]: HostNodeType.Raw,
Expand Down Expand Up @@ -228,14 +228,16 @@ function setProperty(node: N, key: string, value: any): void {
}

// Handle global html attributes and AOM.
if (isGlobalHtmlAttribute(attrName) || isAriaAttribute(attrName)) {
if (REFLECTIVE_GLOBAL_PROPERTY_SET.has(propName) || isAriaAttribute(attrName)) {
return setAttribute(node, attrName, value);
}
}

if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.error(`Unexpected attempt to set "${key}=${value}" property from the renderer`);
console.error(
`Unexpected attempt to set "${propName}=${value}" property from the renderer`
);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<div>contenteditable: {contentEditable}</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LightningElement } from 'lwc';

export default class extends LightningElement {
value = {
contenteditable: 'true',
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
function getAllDivs(target) {
const childs = [...target.shadowRoot.querySelectorAll('x-child')];
return childs.flatMap((child) => [...child.shadowRoot.querySelectorAll('div')]);
}
export default {
snapshot(target) {
const divs = getAllDivs(target);
return { divs };
},
test(target, snapshots, consoleCalls) {
const divs = getAllDivs(target);
expect(divs.length).toBe(snapshots.divs.length);
for (let i = 0; i < divs.length; i += 1) {
expect(divs[i]).toBe(snapshots.divs[i]);
}
expect(consoleCalls.warn).toHaveSize(0);
expect(consoleCalls.error).toHaveSize(0);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { LightningElement } from 'lwc';

export default class extends LightningElement {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<x-child
accesskey={value.accesskey}
aria-label={value.arialabel}
dir={value.dir}
draggable={value.draggable}
hidden={value.hidden}
id={value.id}
lang={value.lang}
role={value.role}
spellcheck={value.spellcheck}
tabindex={value.tabindex}
title={value.title}
></x-child>
<x-child
accesskey="tata"
aria-label="titi"
dir="auto"
draggable="true"
hidden
id="tutu"
lang="jp"
role="scrollbar"
spellcheck="false"
tabindex="0"
title="tete"
></x-child>
</template>
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { createElement } from 'lwc';
import XTest from 'x/test';
import Basic from 'x/basic';
import Correct from 'x/correct';

it('should always return null when accessing shadowRoot property from within the component', () => {
const el = createElement('x-test', { is: XTest });
const el = createElement('x-basic', { is: Basic });
document.body.appendChild(el);

expect(el.getShadowRoot()).toBe(null);
});

it('should be able to access the shadowRoot property from outside the component', () => {
const el = createElement('x-test', { is: XTest });
const el = createElement('x-basic', { is: Basic });
document.body.appendChild(el);

expect(el.shadowRoot).not.toBe(null);
expect(el.shadowRoot).toBeInstanceOf(ShadowRoot);
});

it('uses the correct shadow root - W-17441501', () => {
const el = createElement('x-correct', { is: Correct });
document.body.appendChild(el);

expect(el.shadowRoot.textContent).toBe('');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
Hello, world!
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { LightningElement, api } from 'lwc';

export default class extends LightningElement {
@api
get shadowRoot() {
return (this._shadowRoot =
this._shadowRoot || document.body.appendChild(document.createElement('p')));
}
}
16 changes: 16 additions & 0 deletions packages/@lwc/shared/src/html-attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ export const SPECIAL_PROPERTY_ATTRIBUTE_MAPPING = /*@__PURE__@*/ new Map([
['htmlFor', 'for'],
]);

// Global properties that this framework currently reflects. For CSR, the native
// descriptors for these properties are added from HTMLElement.prototype to
// LightningElement.prototype. For SSR, in order to match CSR behavior, this
// list is used to determine which attributes to reflect.
export const REFLECTIVE_GLOBAL_PROPERTY_SET = /*@__PURE__@*/ new Set([
'accessKey',
'dir',
'draggable',
'hidden',
'id',
'lang',
'spellcheck',
'tabIndex',
'title',
]);

/**
* Map associating previously transformed HTML property into HTML attribute.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export const expectedFailures = new Set([
'attribute-aria/dynamic/index.js',
'attribute-class/with-scoped-styles-only-in-child/dynamic/index.js',
'attribute-class/with-scoped-styles/dynamic/index.js',
'attribute-component-global-html/index.js',
'attribute-global-html/as-component-prop/undeclared/index.js',
'attribute-global-html/as-component-prop/without-@api/index.js',
'exports/component-as-default/index.js',
Expand Down
4 changes: 2 additions & 2 deletions packages/@lwc/ssr-runtime/src/reflection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
entries,
htmlPropertyToAttribute,
isAriaAttribute,
isGlobalHtmlAttribute,
isNull,
keys,
REFLECTIVE_GLOBAL_PROPERTY_SET,
toString,
} from '@lwc/shared';

Expand All @@ -37,7 +37,7 @@ export function filterProperties(
const attrName = htmlPropertyToAttribute(propName);
if (
publicFieldSet.has(propName) ||
((isGlobalHtmlAttribute(attrName) || isAriaAttribute(attrName)) &&
((REFLECTIVE_GLOBAL_PROPERTY_SET.has(propName) || isAriaAttribute(attrName)) &&
!privateFieldSet.has(propName))
) {
propsToAssign[propName] = props[propName];
Expand Down

0 comments on commit 8c8e469

Please sign in to comment.