-
Notifications
You must be signed in to change notification settings - Fork 398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(engine-dom): remove ARIA reflection global polyfill (BREAKING CHANGE) #3666
Changes from 21 commits
6c913e1
27fe2a4
32c0c7c
827c800
d47b1a2
d62fe1d
f5bc994
774d91b
4cafff8
7e5c53a
5e14ddf
953df88
88dd671
4068dfb
021998e
e7949e6
c14333e
540eeb7
4cf39a8
892212b
8ab13fa
85167e1
dbcdad9
d118c6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,7 +45,5 @@ | |
} | ||
} | ||
}, | ||
"dependencies": { | ||
"@lwc/shared": "3.2.0" | ||
} | ||
"dependencies": {} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,85 @@ | ||
/* | ||
* Copyright (c) 2018, salesforce.com, inc. | ||
* Copyright (c) 2023, salesforce.com, inc. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious whether you're updating these manually? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Manually, yes. We should really go through and just run a script or something. |
||
* 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 { AriaPropNameToAttrNameMap, keys } from '@lwc/shared'; | ||
import { detect } from './detect'; | ||
import { patch } from './polyfill'; | ||
|
||
export function applyAriaReflection(prototype: any = Element.prototype) { | ||
const ElementPrototypeAriaPropertyNames = keys(AriaPropNameToAttrNameMap); | ||
// Minimal polyfill of ARIA string reflection, plus some non-standard ARIA props | ||
// Taken from https://github.com/salesforce/lwc/blob/44a01ef/packages/%40lwc/shared/src/aria.ts#L22-L70 | ||
// This is designed for maximum backwards compatibility on LEX - it should never change. | ||
// We deliberately don't import from @lwc/shared because that would make this code less portable. | ||
const ARIA_PROPERTIES = [ | ||
'ariaActiveDescendant', | ||
'ariaAtomic', | ||
'ariaAutoComplete', | ||
'ariaBusy', | ||
'ariaChecked', | ||
'ariaColCount', | ||
'ariaColIndex', | ||
'ariaColSpan', | ||
'ariaControls', | ||
'ariaCurrent', | ||
'ariaDescribedBy', | ||
'ariaDetails', | ||
'ariaDisabled', | ||
'ariaErrorMessage', | ||
'ariaExpanded', | ||
'ariaFlowTo', | ||
'ariaHasPopup', | ||
'ariaHidden', | ||
'ariaInvalid', | ||
'ariaKeyShortcuts', | ||
'ariaLabel', | ||
'ariaLabelledBy', | ||
'ariaLevel', | ||
'ariaLive', | ||
'ariaModal', | ||
'ariaMultiLine', | ||
'ariaMultiSelectable', | ||
'ariaOrientation', | ||
'ariaOwns', | ||
'ariaPlaceholder', | ||
'ariaPosInSet', | ||
'ariaPressed', | ||
'ariaReadOnly', | ||
'ariaRelevant', | ||
'ariaRequired', | ||
'ariaRoleDescription', | ||
'ariaRowCount', | ||
'ariaRowIndex', | ||
'ariaRowSpan', | ||
'ariaSelected', | ||
'ariaSetSize', | ||
'ariaSort', | ||
'ariaValueMax', | ||
'ariaValueMin', | ||
'ariaValueNow', | ||
'ariaValueText', | ||
'role', | ||
]; | ||
|
||
for (let i = 0, len = ElementPrototypeAriaPropertyNames.length; i < len; i += 1) { | ||
const propName = ElementPrototypeAriaPropertyNames[i]; | ||
if (detect(propName, prototype)) { | ||
patch(propName, prototype); | ||
} | ||
for (const prop of ARIA_PROPERTIES) { | ||
const attribute = prop.replace(/^aria/, 'aria-').toLowerCase(); // e.g. ariaPosInSet => aria-posinset | ||
|
||
if (!Object.getOwnPropertyDescriptor(Element.prototype, prop)) { | ||
Object.defineProperty(Element.prototype, prop, { | ||
get() { | ||
return this.getAttribute(attribute); | ||
}, | ||
set(value) { | ||
// Per the spec, only null is treated as removing the attribute. However, Chromium/WebKit currently | ||
// differ from the spec and allow undefined as well. Here, we follow the spec, as well as | ||
// our historical behavior. See: https://github.com/w3c/aria/issues/1858 | ||
if (value === null) { | ||
this.removeAttribute(attribute); | ||
} else { | ||
this.setAttribute(attribute, value); | ||
} | ||
}, | ||
// configurable and enumerable to allow it to be overridden – this mimics Safari's/Chrome's behavior | ||
configurable: true, | ||
enumerable: true, | ||
}); | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,8 @@ import { | |
keys, | ||
htmlPropertyToAttribute, | ||
} from '@lwc/shared'; | ||
import { applyAriaReflection } from '@lwc/aria-reflection'; | ||
import { logError } from '../shared/logger'; | ||
import { applyAriaReflection } from '../libs/aria-reflection/aria-reflection'; | ||
import { getAssociatedVM } from './vm'; | ||
import { getReadOnlyProxy } from './membrane'; | ||
import { HTMLElementConstructor } from './html-element'; | ||
|
@@ -182,15 +182,15 @@ if (process.env.IS_BROWSER) { | |
// This ARIA reflection only really makes sense in the browser. On the server, there is no `renderedCallback()`, | ||
// so you cannot do e.g. `this.template.querySelector('x-child').ariaBusy = 'true'`. So we don't need to expose | ||
// ARIA props outside the LightningElement | ||
if (lwcRuntimeFlags.DISABLE_ARIA_REFLECTION_POLYFILL) { | ||
// If ARIA reflection is not applied globally to Element.prototype, apply it to HTMLBridgeElement.prototype. | ||
// This allows `elm.aria*` property accessors to work from outside a component, and to reflect `aria-*` attrs. | ||
// This is especially important because the template compiler compiles aria-* attrs on components to aria* props | ||
// | ||
// Also note that we apply this to BaseBridgeElement.prototype to avoid excessively redefining property | ||
// accessors inside the HTMLBridgeElementFactory. | ||
applyAriaReflection(BaseBridgeElement.prototype); | ||
} | ||
// | ||
// Apply ARIA reflection to HTMLBridgeElement.prototype. This allows `elm.aria*` property accessors to work from | ||
// outside a component, and to reflect `aria-*` attrs. This is especially important because the template compiler | ||
// compiles aria-* attrs on components to aria* props. | ||
// Note this works regardless of whether the global ARIA reflection polyfill is applied or not. | ||
// | ||
// Also note that we apply this to BaseBridgeElement.prototype to avoid excessively redefining property | ||
// accessors inside the HTMLBridgeElementFactory. | ||
applyAriaReflection(BaseBridgeElement.prototype); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applying ARIA reflection to the base bridge element and lightning element unilaterally has a few benefits:
|
||
} | ||
|
||
freeze(BaseBridgeElement); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
* Copyright (c) 2023, salesforce.com, 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 { | ||
AriaPropNameToAttrNameMap, | ||
defineProperty, | ||
getOwnPropertyDescriptor, | ||
isNull, | ||
isUndefined, | ||
keys, | ||
} from '@lwc/shared'; | ||
import { LightningElement } from '../../framework/base-lightning-element'; | ||
|
||
// Apply ARIA string reflection behavior to a prototype. | ||
// This is deliberately kept separate from @lwc/aria-reflection. @lwc/aria-reflection is a global polyfill that is | ||
// needed for backwards compatibility in LEX, whereas `applyAriaReflection` is designed to only apply to our own | ||
// LightningElement/BaseBridgeElement prototypes. | ||
export function applyAriaReflection(prototype: HTMLElement | LightningElement) { | ||
for (const propName of keys(AriaPropNameToAttrNameMap)) { | ||
const attrName = AriaPropNameToAttrNameMap[propName]; | ||
if (isUndefined(getOwnPropertyDescriptor(prototype, propName))) { | ||
// Note that we need to call this.{get,set,has,remove}Attribute rather than dereferencing | ||
// from Element.prototype, because these methods are overridden in LightningElement. | ||
defineProperty(prototype, propName, { | ||
get(this: HTMLElement): any { | ||
return this.getAttribute(attrName); | ||
ekashida marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
set(this: HTMLElement, newValue: any) { | ||
// TODO [#3284]: there is disagreement between browsers and the spec on how to treat undefined | ||
// Our historical behavior is to only treat null as removing the attribute | ||
// See also https://github.com/w3c/aria/issues/1858 | ||
if (isNull(newValue)) { | ||
this.removeAttribute(attrName); | ||
} else { | ||
this.setAttribute(attrName, newValue); | ||
} | ||
}, | ||
// configurable and enumerable to allow it to be overridden – this mimics Safari's/Chrome's behavior | ||
configurable: true, | ||
enumerable: true, | ||
}); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel we may as well test with synthetic shadow on vs off, especially since this flag will be set to true in LEX.