fix: Ensure attributes are lowercased when checking (#1183)

* fix: Ensure attributes are lowercased when checking

* add changeset

* fix to lower case

* Apply formatting changes

---------

Co-authored-by: mydea <mydea@users.noreply.github.com>
This commit is contained in:
Francesco Novy
2026-04-01 12:00:00 +08:00
committed by GitHub
parent a539fd8f5b
commit ff54a2b097
6 changed files with 29 additions and 12 deletions

View File

@@ -0,0 +1,6 @@
---
'rrweb-snapshot': patch
'rrweb': patch
---
fix: Ensure attributes are lowercased when checking

View File

@@ -21,6 +21,7 @@ import {
isNativeShadowDom, isNativeShadowDom,
getCssRulesString, getCssRulesString,
getInputType, getInputType,
toLowerCase,
} from './utils'; } from './utils';
let _id = 1; let _id = 1;
@@ -32,12 +33,12 @@ export function genId(): number {
return _id++; return _id++;
} }
function getValidTagName(element: HTMLElement): string { function getValidTagName(element: HTMLElement): Lowercase<string> {
if (element instanceof HTMLFormElement) { if (element instanceof HTMLFormElement) {
return 'form'; return 'form';
} }
const processedTagName = element.tagName.toLowerCase().trim(); const processedTagName = toLowerCase(element.tagName);
if (tagNameRegex.test(processedTagName)) { if (tagNameRegex.test(processedTagName)) {
// if the tag name is odd and we cannot extract // if the tag name is odd and we cannot extract
@@ -222,8 +223,8 @@ function getHref() {
export function transformAttribute( export function transformAttribute(
doc: Document, doc: Document,
tagName: string, tagName: Lowercase<string>,
name: string, name: Lowercase<string>,
value: string | null, value: string | null,
): string | null { ): string | null {
if (!value) { if (!value) {
@@ -638,7 +639,7 @@ function serializeElementNode(
attributes[attr.name] = transformAttribute( attributes[attr.name] = transformAttribute(
doc, doc,
tagName, tagName,
attr.name, toLowerCase(attr.name),
attr.value, attr.value,
); );
} }

View File

@@ -169,7 +169,7 @@ export function maskInputValue({
maskInputFn?: MaskInputFn; maskInputFn?: MaskInputFn;
}): string { }): string {
let text = value || ''; let text = value || '';
const actualType = type && type.toLowerCase(); const actualType = type && toLowerCase(type);
if ( if (
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] || maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
@@ -184,6 +184,10 @@ export function maskInputValue({
return text; return text;
} }
export function toLowerCase<T extends string>(str: T): Lowercase<T> {
return str.toLowerCase() as unknown as Lowercase<T>;
}
const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__'; const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__';
type PatchedGetImageData = { type PatchedGetImageData = {
[ORIGINAL_ATTRIBUTE_NAME]: CanvasImageData['getImageData']; [ORIGINAL_ATTRIBUTE_NAME]: CanvasImageData['getImageData'];
@@ -265,6 +269,6 @@ export function getInputType(element: HTMLElement): Lowercase<string> | null {
? 'password' ? 'password'
: type : type
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion ? // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
(type.toLowerCase() as Lowercase<string>) toLowerCase(type)
: null; : null;
} }

View File

@@ -9,6 +9,7 @@ import {
Mirror, Mirror,
isNativeShadowDom, isNativeShadowDom,
getInputType, getInputType,
toLowerCase,
} from 'rrweb-snapshot'; } from 'rrweb-snapshot';
import type { observerParam, MutationBufferParam } from '../types'; import type { observerParam, MutationBufferParam } from '../types';
import type { import type {
@@ -597,8 +598,8 @@ export default class MutationBuffer {
// overwrite attribute if the mutations was triggered in same time // overwrite attribute if the mutations was triggered in same time
item.attributes[attributeName] = transformAttribute( item.attributes[attributeName] = transformAttribute(
this.doc, this.doc,
target.tagName, toLowerCase(target.tagName),
attributeName, toLowerCase(attributeName),
value, value,
); );
} }

View File

@@ -3,6 +3,7 @@ import {
maskInputValue, maskInputValue,
Mirror, Mirror,
getInputType, getInputType,
toLowerCase,
} from 'rrweb-snapshot'; } from 'rrweb-snapshot';
import type { FontFaceSet } from 'css-font-loading-module'; import type { FontFaceSet } from 'css-font-loading-module';
import { import {
@@ -309,13 +310,16 @@ function initMouseInteractionObserver({
disableMap[key] !== false, disableMap[key] !== false,
) )
.forEach((eventKey: keyof typeof MouseInteractions) => { .forEach((eventKey: keyof typeof MouseInteractions) => {
let eventName = eventKey.toLowerCase(); let eventName = toLowerCase(eventKey);
const handler = getHandler(eventKey); const handler = getHandler(eventKey);
if (window.PointerEvent) { if (window.PointerEvent) {
switch (MouseInteractions[eventKey]) { switch (MouseInteractions[eventKey]) {
case MouseInteractions.MouseDown: case MouseInteractions.MouseDown:
case MouseInteractions.MouseUp: case MouseInteractions.MouseUp:
eventName = eventName.replace('mouse', 'pointer'); eventName = eventName.replace(
'mouse',
'pointer',
) as unknown as typeof eventName;
break; break;
case MouseInteractions.TouchStart: case MouseInteractions.TouchStart:
case MouseInteractions.TouchEnd: case MouseInteractions.TouchEnd:

View File

@@ -8,6 +8,7 @@ import {
createMirror, createMirror,
attributes, attributes,
serializedElementNodeWithId, serializedElementNodeWithId,
toLowerCase,
} from 'rrweb-snapshot'; } from 'rrweb-snapshot';
import { import {
RRDocument, RRDocument,
@@ -1120,7 +1121,7 @@ export class Replayer {
if (d.id === -1) { if (d.id === -1) {
break; break;
} }
const event = new Event(MouseInteractions[d.type].toLowerCase()); const event = new Event(toLowerCase(MouseInteractions[d.type]));
const target = this.mirror.getNode(d.id); const target = this.mirror.getNode(d.id);
if (!target) { if (!target) {
return this.debugNodeNotFound(d, d.id); return this.debugNodeNotFound(d, d.id);