세션 재생 작업 방법 섹션 2: 관찰자
나는 또한 이 게시물에 기술된 모든 함수를 포함하는 소스 라이브러리 rrweb 를 유지했다.
변동분 스냅샷
전체 스냅샷이 완료되면 상태를 변경한 이벤트를 기록해야 합니다.
현재 rrweb은 다음과 같은 이벤트를 기록합니다.
돌연변이 관측기
재생 중에 JavaScript를 실행하지 않기 때문에 문서에 대한 스크립트의 모든 변경 사항을 기록해야 합니다.
이 예제를 고려하십시오.
User clicks a button. A dropdown menu appears. User selects the first item. The dropdown menu disappears.
재생 중에 버튼 클릭을 실행하면 원래 JavaScript가 기록의 일부가 아니기 때문에 드롭다운 메뉴가 자동으로 나타나지 않습니다.따라서 드롭다운 메뉴 DOM 노드의 생성, 첫 번째 선택, 그리고 드롭다운 메뉴 DOM 노드의 후속 삭제를 기록해야 합니다.이것은 가장 어려운 부분이다.
다행히도 현대 브라우저는 우리에게 매우 강력한 API를 제공하여 이 점을 할 수 있다. MutationObserver.
본고는 Mutation Observer의 기본적인 용법을 설명하지 않았지만 rrweb와 관련된 부분에만 주목한다.
먼저 알아야 할 것은 MutationObserver가 대량의 비동기식 리셋을 사용한다는 것이다.구체적으로 말하면 일련의 DOM 변경이 발생한 후에 리셋이 있고 여러 개의 변이 기록 그룹을 전달할 것이다.
이러한 메커니즘은 정상적인 사용에 문제가 없다. 왜냐하면 우리는 변이 기록이 있을 뿐만 아니라 변이 노드와 모든 부모 노드, 하위 노드와 형제 노드의 DOM 대상에 직접 접근할 수 있기 때문이다.
그러나 rrweb에서 우리는 서열화 과정이 있기 때문에 더욱 복잡한 해결 방안으로 각종 장면을 처리해야 한다.
노드 추가
예를 들어, 다음 두 작업은 같은 DOM 구조를 생성하지만 다른 돌연변이 레코드 세트를 생성합니다.
body
n1
n2
주의: 첫 번째 상황에서 n1이 추가될 때 하위 노드가 없지만 상기 일괄 처리 비동기 리셋 메커니즘으로 인해 우리가 돌연변이 기록을 수신하고 n1 노드를 처리할 때 이것은 DOM에 이미 하위 노드 n2가 있다.
두 번째 경우, 새로운 노드를 처리할 때, 우리는 모든 하위 노드를 훑어보아서 모든 새로운 노드를 기록해야 한다. 그러나, 이러한 전략은 첫 번째 기록 기간에 n2를 기록하는 것을 초래할 것이다.그런 다음 두 번째 레코드를 처리할 때 노드를 다시 추가하면 재생 중에 DOM 구조가 원래 페이지와 일치하지 않습니다.
따라서 리셋에서 여러 개의 돌연변이 기록을 처리할 때, 우리는 새로 추가된 노드를 "타성적으로"처리해야 한다. 즉, 우리가 모든 돌연변이 기록을 훑어볼 때, 먼저 모든 원시적이고 처리되지 않은 노드를 수집한 다음에 모든 돌연변이 기록을 훑어본 후에 우리는 순서 노드가 DOM에 추가되는 것을 확정해야 한다.이 새 노드를 추가할 때, 우리는 각 노드가 한 번만 기록되고 어떤 노드도 누락되지 않도록 중복 제거를 실행한다.
우리는 이미 serialization design document에서 유지보수
id -> Node
가 필요한 맵을 소개했기 때문에 새 노드가 나타날 때 새 노드를 서열화하고 맵에 추가해야 한다.그러나 데이터 중복 제거를 수행하고자 하기 때문에 모든 돌연변이 기록을 처리한 후에만 서열화되기 때문에 다음과 같은 문제가 발생할 수 있습니다.노드 삭제
돌연변이 기록을 처리할 때, 우리는 아직 서열화되지 않은 삭제된 노드를 만날 수 있습니다.이것은 그것이 새로 추가된 노드임을 나타낸다.'노드 추가'돌연변이 기록도 우리가 받은 돌연변이 기록 중의 어느 곳에 있다.우리는 이 노드들을'버려진 노드'라고 표시한다.
우리는 두 가지 상황을 처리해야 한다.
속성 변경 사항
비록 MutationObserver는 비동기식 리셋이지만, 리셋에서 돌연변이가 발생하는 시간 간격이 매우 짧다고 가정할 수 있기 때문에, 우리는 DOM 속성 변경을 기록할 때 일부 데이터를 덮어써서 증량 스냅샷의 크기를 최적화할 수 있다.
예를 들어 a
<textarea>
의 크기를 조정하면 서로 다른 너비와 높은 속성을 가진 대량의 돌연변이 기록을 촉발할 것이다.전체 기록은 재생을 더욱 생동감 있게 하지만 증가분 스냅샷의 수를 크게 증가시킬 수도 있습니다.균형을 잡은 후에 우리는 같은 노드의 속성의 최종 값만 하나의 변이 리셋에 기록해야 한다고 생각한다. 즉, 모든 후속 변이 기록은 이전에 존재했던 변이 기록의 속성 변경 부분을 덮어쓸 것이다.마우스 이동
마우스의 이동 위치를 기록하여 재생 중의 마우스 이동 궤적을 시뮬레이션할 수 있습니다.
마우스가 재생되는 동안 안정적으로 이동하고 그에 상응하는 증량 스냅샷의 수를 최대한 줄여야 하기 때문에 마우스의 이동을 탐지할 때 두 개의 절류를 실행해야 한다.1층은 최대 20ms초에 한 번씩 마우스 좌표를 기록하고 2층은 최대 500ms초에 한 번씩 마우스 좌표 설정을 전송하여 하나의 스냅샷이 대량의 마우스 위치 데이터를 축적하지 않고 너무 커질 수 있도록 한다.
시간 반전
우리는 재생 중에 정확한 시간에 적용할 수 있도록 각 증량 스냅샷을 생성할 때 시간 스탬프를 기록합니다.그러나 절류의 영향으로 증량 빠른 사진에 대응하는 마우스 이동 시간 스탬프가 실제 기록 시간보다 늦기 때문에 재생 기간에 교정과 시간 교정을 하기 위해 마이너스 시차를 기록해야 한다.
입력
우리는 인력 입력과 프로그램 변경을 포함한 세 가지 요소
<input>
, <textarea>
, <select>
의 입력을 관찰해야 한다.인력 투입
인공 입력에 대해 우리는 주로 입력과 변경 사건에 귀를 기울인다.동일한 인공 입력 조작으로 촉발되는 서로 다른 사건의 중복 데이터를 제거할 필요가 있다.이 밖에
<input type="radio" />
도 특수한 컨트롤러이다.여러 무선 요소에 동일한 이름 속성이 있는 경우 하나를 선택하면 다른 무선 요소는 반전되지만 다른 무선 요소에서 이벤트를 트리거하지 않으므로 별도로 처리해야 합니다.프로그래밍 변경
이러한 요소의 속성을 코드를 통해 직접 설정하면 MutationObserver가 트리거되지 않습니다.우리는 여전히 상응하는 속성을 납치한setter를 통해 감시를 실현할 수 있다.예제 코드는 다음과 같습니다.
function hookSetter<T>(
target: T,
key: string | number | symbol,
d: PropertyDescriptor,
): hookResetter {
const original = Object.getOwnPropertyDescriptor(target, key);
Object.defineProperty(target, key, {
set(value) {
// put hooked setter into event loop to avoid of set latency
setTimeout(() => {
d.set!.call(this, value);
}, 0);
if (original && original.set) {
original.set.call(this, value);
}
},
});
return () => hookSetter(target, key, original || {});
}
setter의 논리가 기록된 페이지의 정상적인 상호작용을 막는 것을 방지하기 위해서 논리를 이벤트 순환에 넣고 비동기적으로 실행해야 합니다.
Reference
이 문제에 관하여(세션 재생 작업 방법 섹션 2: 관찰자), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/yuyz0112/how-does-session-replay-work-part2-observer-4jmg텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)