packages/remote-context/src/LocalSnapshot.js
/**
* Copyright 2017 Moshe Simantov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
equalValue,
equalDescriptors,
getOwnPropertyDescriptors,
cacheGetters,
} from './helpers/descriptors';
const kValue = Symbol('value');
const kPrototype = Symbol('prototype');
const kDescriptors = Symbol('descriptors');
const kCachedGetters = Symbol('cachedGetters');
export default class LocalSnapshot {
/**
* Create a snapshot from the given value.
*
* @param {*} value The given value
*/
constructor(value) {
/**
* @type {*}
* @private
*/
this[kValue] = value;
if (value instanceof Object) {
/**
* @type {object}
* @private
*/
this[kCachedGetters] = cacheGetters(value);
/**
* @type {Object}
* @private
*/
this[kPrototype] = Object.getPrototypeOf(value);
/**
* @type {object}
* @private
*/
this[kDescriptors] = getOwnPropertyDescriptors(value);
}
}
/**
* Get the original value.
*
* @type {*}
*/
get value() {
return this[kValue];
}
/**
* Get the value prototype snapshot.
*
* @type {object|undefined}
*/
get prototype() {
return this[kPrototype];
}
/**
* Get the value properties descriptors snapshot.
*
* @type {object|undefined}
*/
get ownPropertyDescriptors() {
return this[kDescriptors];
}
/**
* Get the value properties descriptors snapshot.
*
* @type {object|undefined}
*/
get cachedGetters() {
return this[kCachedGetters];
}
/**
* Update the local reference of this value with a new version of this snapshot value.
*
* @param {LocalReference} localReference The local reference of this snapshot value
* @return {void}
*/
update(localReference) {
if (this[kDescriptors] === undefined) return;
const value = this[kValue];
const cachedGetters = cacheGetters(value);
const prototype = Object.getPrototypeOf(value);
const descriptors = getOwnPropertyDescriptors(value);
// Update snapshot immediately before any other actions
const oldPrototype = this[kPrototype];
this[kPrototype] = prototype;
const oldDescriptors = this[kDescriptors];
this[kDescriptors] = descriptors;
const oldCachedGetters = this[kCachedGetters];
this[kCachedGetters] = cachedGetters;
// Update prototype
if (oldPrototype !== prototype) {
localReference.setPrototypeOf(prototype);
}
// Remove deleted properties
Object.keys(oldDescriptors).forEach(property => {
const newDesc = descriptors[property];
if (newDesc !== undefined) return;
localReference.deleteProperty(property);
if (cachedGetters !== undefined) {
// There is no need to delete cache for deleted property.
// Skip localReference#deletePropertyCache() later on.
delete oldCachedGetters[property];
}
});
// Add or update existed properties
Object.keys(descriptors).forEach(property => {
const newDesc = descriptors[property];
const oldDesc = oldDescriptors[property];
if (!equalDescriptors(newDesc, oldDesc)) {
localReference.defineProperty(property, newDesc);
}
});
// Remove deleted getters cache
Object.keys(oldCachedGetters).forEach(property => {
if (property in cachedGetters) return;
localReference.deletePropertyCache(property);
});
// Set new cached getters
Object.keys(cachedGetters).forEach(property => {
const newValue = cachedGetters[property];
const oldValue = oldCachedGetters[property];
if (!equalValue(newValue, oldValue)) {
localReference.setPropertyCache(property, newValue);
}
});
}
}