Fix serialization and mutation of <textarea> elements (#1351)
* Fix serialization and mutation of <textarea> elements taking account the duality that the value can be set in either the child node, or in the value _parameter_ (not attribute)
* Backwards compatibility: Bug fix and regression test for #112
- this is to fix up 'historical' recordings, as duplicate textarea content should no longer be being created at record time
- new test shows what the snapshot generated by previous versions of rrweb used to look like, hence 'bad'
- original 0efe23f04a fix either didn't work or no longer works due to childNodes being appended subsequent to this part of the code
- during review, we also verified that the `_cssText` case should still be handled okay, as there's currently no scenario where csstext is present with css child nodes of a <style>
* Masking: Fix that textarea values were being missed by the masking system if the value was recorded as a child node
- I didn't notice that form.html was used in other tests, so lucky that I noticed that those tests also should have the 'pre value' masked out
* Simplify by always storing the textarea value in the `.value` attribute (from it's DOM property) and not as a childNode. It should still be rebuilt as a childNode rather than a property
---------
Authored-by: eoghanmurray <eoghan@getthere.ie>
This commit is contained in:
@@ -5,7 +5,6 @@ import {
|
||||
tagMap,
|
||||
elementNode,
|
||||
BuildCache,
|
||||
attributes,
|
||||
legacyAttributes,
|
||||
} from './types';
|
||||
import { isElement, Mirror, isNodeMetaEqual } from './utils';
|
||||
@@ -200,14 +199,9 @@ function buildNode(
|
||||
value = addHoverClass(value, cache);
|
||||
}
|
||||
if ((isTextarea || isRemoteOrDynamicCss) && typeof value === 'string') {
|
||||
const child = doc.createTextNode(value);
|
||||
node.appendChild(doc.createTextNode(value));
|
||||
// https://github.com/rrweb-io/rrweb/issues/112
|
||||
for (const c of Array.from(node.childNodes)) {
|
||||
if (c.nodeType === node.TEXT_NODE) {
|
||||
node.removeChild(c);
|
||||
}
|
||||
}
|
||||
node.appendChild(child);
|
||||
n.childNodes = []; // value overrides childNodes
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
MaskInputFn,
|
||||
KeepIframeSrcFn,
|
||||
ICanvas,
|
||||
elementNode,
|
||||
serializedElementNodeWithId,
|
||||
} from './types';
|
||||
import {
|
||||
@@ -672,10 +673,9 @@ function serializeElementNode(
|
||||
attributes.type !== 'button' &&
|
||||
value
|
||||
) {
|
||||
const type = getInputType(n);
|
||||
attributes.value = maskInputValue({
|
||||
element: n,
|
||||
type,
|
||||
type: getInputType(n),
|
||||
tagName,
|
||||
value,
|
||||
maskInputOptions,
|
||||
@@ -1090,10 +1090,19 @@ export function serializeNodeWithId(
|
||||
stylesheetLoadTimeout,
|
||||
keepIframeSrcFn,
|
||||
};
|
||||
for (const childN of Array.from(n.childNodes)) {
|
||||
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
|
||||
if (serializedChildNode) {
|
||||
serializedNode.childNodes.push(serializedChildNode);
|
||||
|
||||
if (
|
||||
serializedNode.type === NodeType.Element &&
|
||||
serializedNode.tagName === 'textarea' &&
|
||||
(serializedNode as elementNode).attributes.value !== undefined
|
||||
) {
|
||||
// value parameter in DOM reflects the correct value, so ignore childNode
|
||||
} else {
|
||||
for (const childN of Array.from(n.childNodes)) {
|
||||
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
|
||||
if (serializedChildNode) {
|
||||
serializedNode.childNodes.push(serializedChildNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user