prevent infinite loops when deepObserve is called over links with circular references
This commit is contained in:
parent
c7e3840a93
commit
5b3306fc10
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
import { WeakLink } from 'yjs'
|
||||||
import {
|
import {
|
||||||
readYArray,
|
readYArray,
|
||||||
readYMap,
|
readYMap,
|
||||||
@ -107,7 +108,19 @@ export class ContentType {
|
|||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
*/
|
*/
|
||||||
delete (transaction) {
|
delete (transaction) {
|
||||||
this.type._delete(transaction) // call custom destructor on AbstractType
|
if (this.type.constructor === WeakLink) {
|
||||||
|
// when removing weak links, remove references to them
|
||||||
|
// from type they're pointing to
|
||||||
|
const type = /** @type {WeakLink<any>} */ (this.type);
|
||||||
|
if (type._linkedItem !== null && !type._linkedItem.deleted) {
|
||||||
|
const item = /** @type {Item} */ (type._linkedItem)
|
||||||
|
if (item.linkedBy !== null) {
|
||||||
|
item.linkedBy.delete(type)
|
||||||
|
}
|
||||||
|
type._linkedItem = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let item = this.type._start
|
let item = this.type._start
|
||||||
while (item !== null) {
|
while (item !== null) {
|
||||||
if (!item.deleted) {
|
if (!item.deleted) {
|
||||||
|
@ -233,8 +233,9 @@ export const getTypeChildren = t => {
|
|||||||
* @param {AbstractType<EventType>} type
|
* @param {AbstractType<EventType>} type
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {EventType} event
|
* @param {EventType} event
|
||||||
|
* @param {Set<YWeakLink<any>>|null} visitedLinks
|
||||||
*/
|
*/
|
||||||
export const callTypeObservers = (type, transaction, event) => {
|
export const callTypeObservers = (type, transaction, event, visitedLinks = null) => {
|
||||||
const changedType = type
|
const changedType = type
|
||||||
const changedParentTypes = transaction.changedParentTypes
|
const changedParentTypes = transaction.changedParentTypes
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -243,9 +244,15 @@ export const callTypeObservers = (type, transaction, event) => {
|
|||||||
if (type._item === null) {
|
if (type._item === null) {
|
||||||
break
|
break
|
||||||
} else if (type._item.linkedBy !== null) {
|
} else if (type._item.linkedBy !== null) {
|
||||||
|
if (visitedLinks === null) {
|
||||||
|
visitedLinks = new Set()
|
||||||
|
}
|
||||||
for (let link of type._item.linkedBy) {
|
for (let link of type._item.linkedBy) {
|
||||||
// recursive call
|
if (!visitedLinks.has(link)) {
|
||||||
callTypeObservers(link, transaction, /** @type {any} */ (event))
|
visitedLinks.add(link)
|
||||||
|
// recursive call
|
||||||
|
callTypeObservers(link, transaction, /** @type {any} */ (event), visitedLinks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type = /** @type {AbstractType<any>} */ (type._item.parent)
|
type = /** @type {AbstractType<any>} */ (type._item.parent)
|
||||||
@ -314,11 +321,6 @@ export class AbstractType {
|
|||||||
this._item = item
|
this._item = item
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Transaction} transaction
|
|
||||||
*/
|
|
||||||
_delete (transaction) { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {AbstractType<EventType>}
|
* @return {AbstractType<EventType>}
|
||||||
*/
|
*/
|
||||||
|
@ -98,19 +98,6 @@ export class YWeakLink extends AbstractType {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Transaction} transaction
|
|
||||||
*/
|
|
||||||
_delete (transaction) {
|
|
||||||
if (this._item !== null && this._linkedItem !== null && !this._linkedItem.deleted) {
|
|
||||||
const item = /** @type {Item} */ (this._linkedItem)
|
|
||||||
if (item.linkedBy !== null) {
|
|
||||||
item.linkedBy.delete(this)
|
|
||||||
}
|
|
||||||
this._linkedItem = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {YWeakLink<T>}
|
* @return {YWeakLink<T>}
|
||||||
|
@ -347,7 +347,7 @@ export const testDeepObserveArray = tc => {
|
|||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
const testDeepObserveRecursive = tc => {
|
export const testDeepObserveRecursive = tc => {
|
||||||
// test observers in a face of linked chains of values
|
// test observers in a face of linked chains of values
|
||||||
const doc = new Y.Doc()
|
const doc = new Y.Doc()
|
||||||
const root = doc.getArray('array')
|
const root = doc.getArray('array')
|
||||||
@ -365,9 +365,9 @@ export const testDeepObserveArray = tc => {
|
|||||||
const l2 = root.link(2)
|
const l2 = root.link(2)
|
||||||
|
|
||||||
// create cyclic reference between links
|
// create cyclic reference between links
|
||||||
m0.set('k1', m1)
|
m0.set('k1', l1)
|
||||||
m1.set('k2', m2)
|
m1.set('k2', l2)
|
||||||
m2.set('k0', m0)
|
m2.set('k0', l0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<any>}
|
* @type {Array<any>}
|
||||||
@ -376,9 +376,18 @@ export const testDeepObserveArray = tc => {
|
|||||||
m0.observeDeep((e) => events = e)
|
m0.observeDeep((e) => events = e)
|
||||||
|
|
||||||
m1.set('test-key1', 'value1')
|
m1.set('test-key1', 'value1')
|
||||||
t.compare(events, [{}]) //TODO
|
t.compare(events.length, 1)
|
||||||
|
t.compare(events[0].target, m1)
|
||||||
|
t.compare(events[0].keys, new Map([['test-key1', {action:'add', oldValue: undefined}]]))
|
||||||
|
|
||||||
events = []
|
events = []
|
||||||
m2.set('test-key2', 'value2')
|
m2.set('test-key2', 'value2')
|
||||||
t.compare(events, [{}]) //TODO
|
t.compare(events.length, 1)
|
||||||
|
t.compare(events[0].target, m2)
|
||||||
|
t.compare(events[0].keys, new Map([['test-key2', {action:'add', oldValue: undefined}]]))
|
||||||
|
|
||||||
|
m1.delete('test-key1')
|
||||||
|
t.compare(events.length, 1)
|
||||||
|
t.compare(events[0].target, m1)
|
||||||
|
t.compare(events[0].keys, new Map([['test-key1', {action:'delete', oldValue: undefined}]]))
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user