Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(37)

Unified Diff: sky/framework/sky-element/observe.sky

Issue 951673003: Remove outdated examples and framework pieces (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « sky/framework/sky-element/element-registry.sky ('k') | sky/framework/sky-element/sky-binder.sky » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sky/framework/sky-element/observe.sky
diff --git a/sky/framework/sky-element/observe.sky b/sky/framework/sky-element/observe.sky
deleted file mode 100644
index 415b4b8f108d50e615ff3387b5e9bd54c5aada7d..0000000000000000000000000000000000000000
--- a/sky/framework/sky-element/observe.sky
+++ /dev/null
@@ -1,1361 +0,0 @@
-<!--
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
--->
-
-<script>
-function detectEval() {
- // Don't test for eval if we're running in a Chrome App environment.
- // We check for APIs set that only exist in a Chrome App context.
- if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
- return false;
- }
-
- // Firefox OS Apps do not allow eval. This feature detection is very hacky
- // but even if some other platform adds support for this function this code
- // will continue to work.
- if (typeof navigator != 'undefined' && navigator.getDeviceStorage) {
- return false;
- }
-
- try {
- var f = new Function('', 'return true;');
- return f();
- } catch (ex) {
- return false;
- }
-}
-
-var hasEval = detectEval();
-
-function isIndex(s) {
- return +s === s >>> 0 && s !== '';
-}
-
-function toNumber(s) {
- return +s;
-}
-
-function isObject(obj) {
- return obj === Object(obj);
-}
-
-function areSameValue(left, right) {
- if (left === right)
- return left !== 0 || 1 / left === 1 / right;
- if (Number.isNaN(left) && Number.isNaN(right))
- return true;
-
- return left !== left && right !== right;
-}
-
-var createObject = ('__proto__' in {}) ?
- function(obj) { return obj; } :
- function(obj) {
- var proto = obj.__proto__;
- if (!proto)
- return obj;
- var newObject = Object.create(proto);
- Object.getOwnPropertyNames(obj).forEach(function(name) {
- Object.defineProperty(newObject, name,
- Object.getOwnPropertyDescriptor(obj, name));
- });
- return newObject;
- };
-
-var identStart = '[\$_a-zA-Z]';
-var identPart = '[\$_a-zA-Z0-9]';
-var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$');
-
-function getPathCharType(char) {
- if (char === undefined)
- return 'eof';
-
- var code = char.charCodeAt(0);
-
- switch(code) {
- case 0x5B: // [
- case 0x5D: // ]
- case 0x2E: // .
- case 0x22: // "
- case 0x27: // '
- case 0x30: // 0
- return char;
-
- case 0x5F: // _
- case 0x24: // $
- return 'ident';
-
- case 0x20: // Space
- case 0x09: // Tab
- case 0x0A: // Newline
- case 0x0D: // Return
- case 0xA0: // No-break space
- case 0xFEFF: // Byte Order Mark
- case 0x2028: // Line Separator
- case 0x2029: // Paragraph Separator
- return 'ws';
- }
-
- // a-z, A-Z
- if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A))
- return 'ident';
-
- // 1-9
- if (0x31 <= code && code <= 0x39)
- return 'number';
-
- return 'else';
-}
-
-var pathStateMachine = {
- 'beforePath': {
- 'ws': ['beforePath'],
- 'ident': ['inIdent', 'append'],
- '[': ['beforeElement'],
- 'eof': ['afterPath']
- },
-
- 'inPath': {
- 'ws': ['inPath'],
- '.': ['beforeIdent'],
- '[': ['beforeElement'],
- 'eof': ['afterPath']
- },
-
- 'beforeIdent': {
- 'ws': ['beforeIdent'],
- 'ident': ['inIdent', 'append']
- },
-
- 'inIdent': {
- 'ident': ['inIdent', 'append'],
- '0': ['inIdent', 'append'],
- 'number': ['inIdent', 'append'],
- 'ws': ['inPath', 'push'],
- '.': ['beforeIdent', 'push'],
- '[': ['beforeElement', 'push'],
- 'eof': ['afterPath', 'push']
- },
-
- 'beforeElement': {
- 'ws': ['beforeElement'],
- '0': ['afterZero', 'append'],
- 'number': ['inIndex', 'append'],
- "'": ['inSingleQuote', 'append', ''],
- '"': ['inDoubleQuote', 'append', '']
- },
-
- 'afterZero': {
- 'ws': ['afterElement', 'push'],
- ']': ['inPath', 'push']
- },
-
- 'inIndex': {
- '0': ['inIndex', 'append'],
- 'number': ['inIndex', 'append'],
- 'ws': ['afterElement'],
- ']': ['inPath', 'push']
- },
-
- 'inSingleQuote': {
- "'": ['afterElement'],
- 'eof': ['error'],
- 'else': ['inSingleQuote', 'append']
- },
-
- 'inDoubleQuote': {
- '"': ['afterElement'],
- 'eof': ['error'],
- 'else': ['inDoubleQuote', 'append']
- },
-
- 'afterElement': {
- 'ws': ['afterElement'],
- ']': ['inPath', 'push']
- }
-}
-
-function noop() {}
-
-function parsePath(path) {
- var keys = [];
- var index = -1;
- var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath';
-
- var actions = {
- push: function() {
- if (key === undefined)
- return;
-
- keys.push(key);
- key = undefined;
- },
-
- append: function() {
- if (key === undefined)
- key = newChar
- else
- key += newChar;
- }
- };
-
- function maybeUnescapeQuote() {
- if (index >= path.length)
- return;
-
- var nextChar = path[index + 1];
- if ((mode == 'inSingleQuote' && nextChar == "'") ||
- (mode == 'inDoubleQuote' && nextChar == '"')) {
- index++;
- newChar = nextChar;
- actions.append();
- return true;
- }
- }
-
- while (mode) {
- index++;
- c = path[index];
-
- if (c == '\\' && maybeUnescapeQuote(mode))
- continue;
-
- type = getPathCharType(c);
- typeMap = pathStateMachine[mode];
- transition = typeMap[type] || typeMap['else'] || 'error';
-
- if (transition == 'error')
- return; // parse error;
-
- mode = transition[0];
- action = actions[transition[1]] || noop;
- newChar = transition[2] === undefined ? c : transition[2];
- action();
-
- if (mode === 'afterPath') {
- return keys;
- }
- }
-
- return; // parse error
-}
-
-function isIdent(s) {
- return identRegExp.test(s);
-}
-
-var constructorIsPrivate = {};
-
-function Path(parts, privateToken) {
- if (privateToken !== constructorIsPrivate)
- throw Error('Use Path.get to retrieve path objects');
-
- for (var i = 0; i < parts.length; i++) {
- this.push(String(parts[i]));
- }
-
- if (hasEval && this.length) {
- this.getValueFrom = this.compiledGetValueFromFn();
- }
-}
-
-// TODO(rafaelw): Make simple LRU cache
-var pathCache = {};
-
-function getPath(pathString) {
- if (pathString instanceof Path)
- return pathString;
-
- if (pathString == null || pathString.length == 0)
- pathString = '';
-
- if (typeof pathString != 'string') {
- if (isIndex(pathString.length)) {
- // Constructed with array-like (pre-parsed) keys
- return new Path(pathString, constructorIsPrivate);
- }
-
- pathString = String(pathString);
- }
-
- var path = pathCache[pathString];
- if (path)
- return path;
-
- var parts = parsePath(pathString);
- if (!parts)
- return invalidPath;
-
- var path = new Path(parts, constructorIsPrivate);
- pathCache[pathString] = path;
- return path;
-}
-
-Path.get = getPath;
-
-function formatAccessor(key) {
- if (isIndex(key)) {
- return '[' + key + ']';
- } else {
- return '["' + key.replace(/"/g, '\\"') + '"]';
- }
-}
-
-Path.prototype = createObject({
- __proto__: [],
- valid: true,
-
- toString: function() {
- var pathString = '';
- for (var i = 0; i < this.length; i++) {
- var key = this[i];
- if (isIdent(key)) {
- pathString += i ? '.' + key : key;
- } else {
- pathString += formatAccessor(key);
- }
- }
-
- return pathString;
- },
-
- getValueFrom: function(obj, directObserver) {
- for (var i = 0; i < this.length; i++) {
- if (obj == null)
- return;
- obj = obj[this[i]];
- }
- return obj;
- },
-
- iterateObjects: function(obj, observe) {
- for (var i = 0; i < this.length; i++) {
- if (i)
- obj = obj[this[i - 1]];
- if (!isObject(obj))
- return;
- observe(obj, this[i]);
- }
- },
-
- compiledGetValueFromFn: function() {
- var str = '';
- var pathString = 'obj';
- str += 'if (obj != null';
- var i = 0;
- var key;
- for (; i < (this.length - 1); i++) {
- key = this[i];
- pathString += isIdent(key) ? '.' + key : formatAccessor(key);
- str += ' &&\n ' + pathString + ' != null';
- }
- str += ')\n';
-
- var key = this[i];
- pathString += isIdent(key) ? '.' + key : formatAccessor(key);
-
- str += ' return ' + pathString + ';\nelse\n return undefined;';
- return new Function('obj', str);
- },
-
- setValueFrom: function(obj, value) {
- if (!this.length)
- return false;
-
- for (var i = 0; i < this.length - 1; i++) {
- if (!isObject(obj))
- return false;
- obj = obj[this[i]];
- }
-
- if (!isObject(obj))
- return false;
-
- obj[this[i]] = value;
- return true;
- }
-});
-
-var invalidPath = new Path('', constructorIsPrivate);
-invalidPath.valid = false;
-invalidPath.getValueFrom = invalidPath.setValueFrom = function() {};
-
-var MAX_DIRTY_CHECK_CYCLES = 1000;
-
-function dirtyCheck(observer) {
- var cycles = 0;
- while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) {
- cycles++;
- }
-
- return cycles > 0;
-}
-
-function runEOM(fn) {
- return Promise.resolve().then(fn);
-}
-
-var observedObjectCache = [];
-
-function newObservedObject() {
- var observer;
- var object;
- var discardRecords = false;
- var first = true;
-
- function callback(records) {
- if (observer && observer.state_ === OPENED && !discardRecords)
- observer.check_(records);
- }
-
- return {
- open: function(obs) {
- if (observer)
- throw Error('ObservedObject in use');
-
- if (!first)
- Object.deliverChangeRecords(callback);
-
- observer = obs;
- first = false;
- },
- observe: function(obj, arrayObserve) {
- object = obj;
- if (arrayObserve)
- Array.observe(object, callback);
- else
- Object.observe(object, callback);
- },
- deliver: function(discard) {
- discardRecords = discard;
- Object.deliverChangeRecords(callback);
- discardRecords = false;
- },
- close: function() {
- observer = undefined;
- Object.unobserve(object, callback);
- observedObjectCache.push(this);
- }
- };
-}
-
-/*
- * The observedSet abstraction is a perf optimization which reduces the total
- * number of Object.observe observations of a set of objects. The idea is that
- * groups of Observers will have some object dependencies in common and this
- * observed set ensures that each object in the transitive closure of
- * dependencies is only observed once. The observedSet acts as a write barrier
- * such that whenever any change comes through, all Observers are checked for
- * changed values.
- *
- * Note that this optimization is explicitly moving work from setup-time to
- * change-time.
- *
- * TODO(rafaelw): Implement "garbage collection". In order to move work off
- * the critical path, when Observers are closed, their observed objects are
- * not Object.unobserve(d). As a result, it's possible that if the observedSet
- * is kept open, but some Observers have been closed, it could cause "leaks"
- * (prevent otherwise collectable objects from being collected). At some
- * point, we should implement incremental "gc" which keeps a list of
- * observedSets which may need clean-up and does small amounts of cleanup on a
- * timeout until all is clean.
- */
-
-function getObservedObject(observer, object, arrayObserve) {
- var dir = observedObjectCache.pop() || newObservedObject();
- dir.open(observer);
- dir.observe(object, arrayObserve);
- return dir;
-}
-
-var observedSetCache = [];
-
-function newObservedSet() {
- var observerCount = 0;
- var observers = [];
- var objects = [];
- var rootObj;
- var rootObjProps;
-
- function observe(obj, prop) {
- if (!obj)
- return;
-
- if (obj === rootObj)
- rootObjProps[prop] = true;
-
- if (objects.indexOf(obj) < 0) {
- objects.push(obj);
- Object.observe(obj, callback);
- }
-
- observe(Object.getPrototypeOf(obj), prop);
- }
-
- function allRootObjNonObservedProps(recs) {
- for (var i = 0; i < recs.length; i++) {
- var rec = recs[i];
- if (rec.object !== rootObj ||
- rootObjProps[rec.name] ||
- rec.type === 'setPrototype') {
- return false;
- }
- }
- return true;
- }
-
- function callback(recs) {
- if (allRootObjNonObservedProps(recs))
- return;
-
- var observer;
- for (var i = 0; i < observers.length; i++) {
- observer = observers[i];
- if (observer.state_ == OPENED) {
- observer.iterateObjects_(observe);
- }
- }
-
- for (var i = 0; i < observers.length; i++) {
- observer = observers[i];
- if (observer.state_ == OPENED) {
- observer.check_();
- }
- }
- }
-
- var record = {
- objects: objects,
- get rootObject() { return rootObj; },
- set rootObject(value) {
- rootObj = value;
- rootObjProps = {};
- },
- open: function(obs, object) {
- observers.push(obs);
- observerCount++;
- obs.iterateObjects_(observe);
- },
- close: function(obs) {
- observerCount--;
- if (observerCount > 0) {
- return;
- }
-
- for (var i = 0; i < objects.length; i++) {
- Object.unobserve(objects[i], callback);
- Observer.unobservedCount++;
- }
-
- observers.length = 0;
- objects.length = 0;
- rootObj = undefined;
- rootObjProps = undefined;
- observedSetCache.push(this);
- if (lastObservedSet === this)
- lastObservedSet = null;
- },
- };
-
- return record;
-}
-
-var lastObservedSet;
-
-function getObservedSet(observer, obj) {
- if (!lastObservedSet || lastObservedSet.rootObject !== obj) {
- lastObservedSet = observedSetCache.pop() || newObservedSet();
- lastObservedSet.rootObject = obj;
- }
- lastObservedSet.open(observer, obj);
- return lastObservedSet;
-}
-
-var UNOPENED = 0;
-var OPENED = 1;
-var CLOSED = 2;
-var RESETTING = 3;
-
-var nextObserverId = 1;
-
-function Observer() {
- this.state_ = UNOPENED;
- this.callback_ = undefined;
- this.target_ = undefined; // TODO(rafaelw): Should be WeakRef
- this.directObserver_ = undefined;
- this.value_ = undefined;
- this.id_ = nextObserverId++;
-}
-
-Observer.prototype = {
- open: function(callback, target) {
- if (this.state_ != UNOPENED)
- throw Error('Observer has already been opened.');
-
- this.callback_ = callback;
- this.target_ = target;
- this.connect_();
- this.state_ = OPENED;
- return this.value_;
- },
-
- setValue: function(newValue) {
- },
-
- close: function() {
- if (this.state_ != OPENED)
- return;
-
- this.disconnect_();
- this.value_ = undefined;
- this.callback_ = undefined;
- this.target_ = undefined;
- this.state_ = CLOSED;
- },
-
- deliver: function() {
- if (this.state_ != OPENED)
- return;
-
- dirtyCheck(this);
- },
-
- report_: function(changes) {
- try {
- this.callback_.apply(this.target_, changes);
- } catch (ex) {
- Observer._errorThrownDuringCallback = true;
- console.error('Exception caught during observer callback: ' +
- (ex.stack || ex));
- }
- },
-
- discardChanges: function() {
- this.check_(undefined, true);
- return this.value_;
- }
-}
-
-function ArrayObserver(array) {
- if (!Array.isArray(array))
- throw Error('Provided object is not an Array');
- Observer.call(this);
- this.value_ = array;
- this.oldObject_ = undefined;
-}
-
-ArrayObserver.prototype = createObject({
-
- __proto__: Observer.prototype,
-
- connect_: function(callback, target) {
- this.directObserver_ = getObservedObject(this, this.value_,
- true /* arrayObserve */);
- },
-
- copyObject: function(arr) {
- return arr.slice();
- },
-
- check_: function(changeRecords) {
- var splices;
- if (!changeRecords)
- return false;
- splices = projectArraySplices(this.value_, changeRecords);
-
- if (!splices || !splices.length)
- return false;
-
- this.report_([splices]);
- return true;
- },
-
- disconnect_: function() {
- this.directObserver_.close();
- this.directObserver_ = undefined;
- },
-
- deliver: function() {
- if (this.state_ != OPENED)
- return;
-
- this.directObserver_.deliver(false);
- },
-
- discardChanges: function() {
- if (this.directObserver_)
- this.directObserver_.deliver(true);
- else
- this.oldObject_ = this.copyObject(this.value_);
-
- return this.value_;
- }
-});
-
-ArrayObserver.applySplices = function(previous, current, splices) {
- splices.forEach(function(splice) {
- var spliceArgs = [splice.index, splice.removed.length];
- var addIndex = splice.index;
- while (addIndex < splice.index + splice.addedCount) {
- spliceArgs.push(current[addIndex]);
- addIndex++;
- }
-
- Array.prototype.splice.apply(previous, spliceArgs);
- });
-};
-
-function PathObserver(object, path) {
- Observer.call(this);
-
- this.object_ = object;
- this.path_ = getPath(path);
- this.directObserver_ = undefined;
-}
-
-PathObserver.prototype = createObject({
- __proto__: Observer.prototype,
-
- get path() {
- return this.path_;
- },
-
- connect_: function() {
- this.directObserver_ = getObservedSet(this, this.object_);
- this.check_(undefined, true);
- },
-
- disconnect_: function() {
- this.value_ = undefined;
-
- if (this.directObserver_) {
- this.directObserver_.close(this);
- this.directObserver_ = undefined;
- }
- },
-
- iterateObjects_: function(observe) {
- this.path_.iterateObjects(this.object_, observe);
- },
-
- check_: function(changeRecords, skipChanges) {
- var oldValue = this.value_;
- this.value_ = this.path_.getValueFrom(this.object_);
- if (skipChanges || areSameValue(this.value_, oldValue))
- return false;
-
- this.report_([this.value_, oldValue, this]);
- return true;
- },
-
- setValue: function(newValue) {
- if (this.path_)
- this.path_.setValueFrom(this.object_, newValue);
- }
-});
-
-function CompoundObserver(reportChangesOnOpen) {
- Observer.call(this);
-
- this.reportChangesOnOpen_ = reportChangesOnOpen;
- this.value_ = [];
- this.directObserver_ = undefined;
- this.observed_ = [];
-}
-
-var observerSentinel = {};
-
-CompoundObserver.prototype = createObject({
- __proto__: Observer.prototype,
-
- connect_: function() {
- var object;
- var needsDirectObserver = false;
- for (var i = 0; i < this.observed_.length; i += 2) {
- object = this.observed_[i]
- if (object !== observerSentinel) {
- needsDirectObserver = true;
- break;
- }
- }
-
- if (needsDirectObserver)
- this.directObserver_ = getObservedSet(this, object);
-
- this.check_(undefined, !this.reportChangesOnOpen_);
- },
-
- disconnect_: function() {
- for (var i = 0; i < this.observed_.length; i += 2) {
- if (this.observed_[i] === observerSentinel)
- this.observed_[i + 1].close();
- }
- this.observed_.length = 0;
- this.value_.length = 0;
-
- if (this.directObserver_) {
- this.directObserver_.close(this);
- this.directObserver_ = undefined;
- }
- },
-
- addPath: function(object, path) {
- if (this.state_ != UNOPENED && this.state_ != RESETTING)
- throw Error('Cannot add paths once started.');
-
- var path = getPath(path);
- this.observed_.push(object, path);
- if (!this.reportChangesOnOpen_)
- return;
- var index = this.observed_.length / 2 - 1;
- this.value_[index] = path.getValueFrom(object);
- },
-
- addObserver: function(observer) {
- if (this.state_ != UNOPENED && this.state_ != RESETTING)
- throw Error('Cannot add observers once started.');
-
- this.observed_.push(observerSentinel, observer);
- if (!this.reportChangesOnOpen_)
- return;
- var index = this.observed_.length / 2 - 1;
- this.value_[index] = observer.open(this.deliver, this);
- },
-
- startReset: function() {
- if (this.state_ != OPENED)
- throw Error('Can only reset while open');
-
- this.state_ = RESETTING;
- this.disconnect_();
- },
-
- finishReset: function() {
- if (this.state_ != RESETTING)
- throw Error('Can only finishReset after startReset');
- this.state_ = OPENED;
- this.connect_();
-
- return this.value_;
- },
-
- iterateObjects_: function(observe) {
- var object;
- for (var i = 0; i < this.observed_.length; i += 2) {
- object = this.observed_[i]
- if (object !== observerSentinel)
- this.observed_[i + 1].iterateObjects(object, observe)
- }
- },
-
- check_: function(changeRecords, skipChanges) {
- var oldValues;
- for (var i = 0; i < this.observed_.length; i += 2) {
- var object = this.observed_[i];
- var path = this.observed_[i+1];
- var value;
- if (object === observerSentinel) {
- var observable = path;
- value = this.state_ === UNOPENED ?
- observable.open(this.deliver, this) :
- observable.discardChanges();
- } else {
- value = path.getValueFrom(object);
- }
-
- if (skipChanges) {
- this.value_[i / 2] = value;
- continue;
- }
-
- if (areSameValue(value, this.value_[i / 2]))
- continue;
-
- oldValues = oldValues || [];
- oldValues[i / 2] = this.value_[i / 2];
- this.value_[i / 2] = value;
- }
-
- if (!oldValues)
- return false;
-
- // TODO(rafaelw): Having observed_ as the third callback arg here is
- // pretty lame API. Fix.
- this.report_([this.value_, oldValues, this.observed_]);
- return true;
- }
-});
-
-function identFn(value) { return value; }
-
-function ObserverTransform(observable, getValueFn) {
- this.callback_ = undefined;
- this.target_ = undefined;
- this.value_ = undefined;
- this.observable_ = observable;
- this.getValueFn_ = getValueFn || identFn;
-}
-
-ObserverTransform.prototype = {
- open: function(callback, target) {
- this.callback_ = callback;
- this.target_ = target;
- this.value_ =
- this.getValueFn_(this.observable_.open(this.observedCallback_, this));
- return this.value_;
- },
-
- observedCallback_: function(value) {
- value = this.getValueFn_(value);
- if (areSameValue(value, this.value_))
- return;
- var oldValue = this.value_;
- this.value_ = value;
- this.callback_.call(this.target_, this.value_, oldValue);
- },
-
- setValue: function(oldValue) {
- },
-
- discardChanges: function() {
- this.value_ = this.getValueFn_(this.observable_.discardChanges());
- return this.value_;
- },
-
- deliver: function() {
- return this.observable_.deliver();
- },
-
- close: function() {
- if (this.observable_)
- this.observable_.close();
- this.callback_ = undefined;
- this.target_ = undefined;
- this.observable_ = undefined;
- this.value_ = undefined;
- this.getValueFn_ = undefined;
- }
-}
-
-function newSplice(index, removed, addedCount) {
- return {
- index: index,
- removed: removed,
- addedCount: addedCount
- };
-}
-
-var EDIT_LEAVE = 0;
-var EDIT_UPDATE = 1;
-var EDIT_ADD = 2;
-var EDIT_DELETE = 3;
-
-function ArraySplice() {}
-
-ArraySplice.prototype = {
-
- // Note: This function is *based* on the computation of the Levenshtein
- // "edit" distance. The one change is that "updates" are treated as two
- // edits - not one. With Array splices, an update is really a delete
- // followed by an add. By retaining this, we optimize for "keeping" the
- // maximum array items in the original array. For example:
- //
- // 'xxxx123' -> '123yyyy'
- //
- // With 1-edit updates, the shortest path would be just to update all seven
- // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
- // leaves the substring '123' intact.
- calcEditDistances: function(current, currentStart, currentEnd,
- old, oldStart, oldEnd) {
- // "Deletion" columns
- var rowCount = oldEnd - oldStart + 1;
- var columnCount = currentEnd - currentStart + 1;
- var distances = new Array(rowCount);
-
- // "Addition" rows. Initialize null column.
- for (var i = 0; i < rowCount; i++) {
- distances[i] = new Array(columnCount);
- distances[i][0] = i;
- }
-
- // Initialize null row
- for (var j = 0; j < columnCount; j++)
- distances[0][j] = j;
-
- for (var i = 1; i < rowCount; i++) {
- for (var j = 1; j < columnCount; j++) {
- if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
- distances[i][j] = distances[i - 1][j - 1];
- else {
- var north = distances[i - 1][j] + 1;
- var west = distances[i][j - 1] + 1;
- distances[i][j] = north < west ? north : west;
- }
- }
- }
-
- return distances;
- },
-
- // This starts at the final weight, and walks "backward" by finding
- // the minimum previous weight recursively until the origin of the weight
- // matrix.
- spliceOperationsFromEditDistances: function(distances) {
- var i = distances.length - 1;
- var j = distances[0].length - 1;
- var current = distances[i][j];
- var edits = [];
- while (i > 0 || j > 0) {
- if (i == 0) {
- edits.push(EDIT_ADD);
- j--;
- continue;
- }
- if (j == 0) {
- edits.push(EDIT_DELETE);
- i--;
- continue;
- }
- var northWest = distances[i - 1][j - 1];
- var west = distances[i - 1][j];
- var north = distances[i][j - 1];
-
- var min;
- if (west < north)
- min = west < northWest ? west : northWest;
- else
- min = north < northWest ? north : northWest;
-
- if (min == northWest) {
- if (northWest == current) {
- edits.push(EDIT_LEAVE);
- } else {
- edits.push(EDIT_UPDATE);
- current = northWest;
- }
- i--;
- j--;
- } else if (min == west) {
- edits.push(EDIT_DELETE);
- i--;
- current = west;
- } else {
- edits.push(EDIT_ADD);
- j--;
- current = north;
- }
- }
-
- edits.reverse();
- return edits;
- },
-
- /**
- * Splice Projection functions:
- *
- * A splice map is a representation of how a previous array of items
- * was transformed into a new array of items. Conceptually it is a list of
- * tuples of
- *
- * <index, removed, addedCount>
- *
- * which are kept in ascending index order of. The tuple represents that at
- * the |index|, |removed| sequence of items were removed, and counting forward
- * from |index|, |addedCount| items were added.
- */
-
- /**
- * Lacking individual splice mutation information, the minimal set of
- * splices can be synthesized given the previous state and final state of an
- * array. The basic approach is to calculate the edit distance matrix and
- * choose the shortest path through it.
- *
- * Complexity: O(l * p)
- * l: The length of the current array
- * p: The length of the old array
- */
- calcSplices: function(current, currentStart, currentEnd,
- old, oldStart, oldEnd) {
- var prefixCount = 0;
- var suffixCount = 0;
-
- var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
- if (currentStart == 0 && oldStart == 0)
- prefixCount = this.sharedPrefix(current, old, minLength);
-
- if (currentEnd == current.length && oldEnd == old.length)
- suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
-
- currentStart += prefixCount;
- oldStart += prefixCount;
- currentEnd -= suffixCount;
- oldEnd -= suffixCount;
-
- if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
- return [];
-
- if (currentStart == currentEnd) {
- var splice = newSplice(currentStart, [], 0);
- while (oldStart < oldEnd)
- splice.removed.push(old[oldStart++]);
-
- return [ splice ];
- } else if (oldStart == oldEnd)
- return [ newSplice(currentStart, [], currentEnd - currentStart) ];
-
- var ops = this.spliceOperationsFromEditDistances(
- this.calcEditDistances(current, currentStart, currentEnd,
- old, oldStart, oldEnd));
-
- var splice = undefined;
- var splices = [];
- var index = currentStart;
- var oldIndex = oldStart;
- for (var i = 0; i < ops.length; i++) {
- switch(ops[i]) {
- case EDIT_LEAVE:
- if (splice) {
- splices.push(splice);
- splice = undefined;
- }
-
- index++;
- oldIndex++;
- break;
- case EDIT_UPDATE:
- if (!splice)
- splice = newSplice(index, [], 0);
-
- splice.addedCount++;
- index++;
-
- splice.removed.push(old[oldIndex]);
- oldIndex++;
- break;
- case EDIT_ADD:
- if (!splice)
- splice = newSplice(index, [], 0);
-
- splice.addedCount++;
- index++;
- break;
- case EDIT_DELETE:
- if (!splice)
- splice = newSplice(index, [], 0);
-
- splice.removed.push(old[oldIndex]);
- oldIndex++;
- break;
- }
- }
-
- if (splice) {
- splices.push(splice);
- }
- return splices;
- },
-
- sharedPrefix: function(current, old, searchLength) {
- for (var i = 0; i < searchLength; i++)
- if (!this.equals(current[i], old[i]))
- return i;
- return searchLength;
- },
-
- sharedSuffix: function(current, old, searchLength) {
- var index1 = current.length;
- var index2 = old.length;
- var count = 0;
- while (count < searchLength &&
- this.equals(current[--index1], old[--index2])) {
- count++;
- }
-
- return count;
- },
-
- calculateSplices: function(current, previous) {
- return this.calcSplices(current, 0, current.length, previous, 0,
- previous.length);
- },
-
- equals: function(currentValue, previousValue) {
- return currentValue === previousValue;
- }
-};
-
-var arraySplice = new ArraySplice();
-
-function calcSplices(current, currentStart, currentEnd,
- old, oldStart, oldEnd) {
- return arraySplice.calcSplices(current, currentStart, currentEnd,
- old, oldStart, oldEnd);
-}
-
-function intersect(start1, end1, start2, end2) {
- // Disjoint
- if (end1 < start2 || end2 < start1)
- return -1;
-
- // Adjacent
- if (end1 == start2 || end2 == start1)
- return 0;
-
- // Non-zero intersect, span1 first
- if (start1 < start2) {
- if (end1 < end2)
- return end1 - start2; // Overlap
- else
- return end2 - start2; // Contained
- } else {
- // Non-zero intersect, span2 first
- if (end2 < end1)
- return end2 - start1; // Overlap
- else
- return end1 - start1; // Contained
- }
-}
-
-function mergeSplice(splices, index, removed, addedCount) {
-
- var splice = newSplice(index, removed, addedCount);
-
- var inserted = false;
- var insertionOffset = 0;
-
- for (var i = 0; i < splices.length; i++) {
- var current = splices[i];
- current.index += insertionOffset;
-
- if (inserted)
- continue;
-
- var intersectCount = intersect(splice.index,
- splice.index + splice.removed.length,
- current.index,
- current.index + current.addedCount);
-
- if (intersectCount >= 0) {
- // Merge the two splices
-
- splices.splice(i, 1);
- i--;
-
- insertionOffset -= current.addedCount - current.removed.length;
-
- splice.addedCount += current.addedCount - intersectCount;
- var deleteCount = splice.removed.length +
- current.removed.length - intersectCount;
-
- if (!splice.addedCount && !deleteCount) {
- // merged splice is a noop. discard.
- inserted = true;
- } else {
- var removed = current.removed;
-
- if (splice.index < current.index) {
- // some prefix of splice.removed is prepended to current.removed.
- var prepend = splice.removed.slice(0, current.index - splice.index);
- Array.prototype.push.apply(prepend, removed);
- removed = prepend;
- }
-
- if (splice.index + splice.removed.length >
- current.index + current.addedCount) {
- // some suffix of splice.removed is appended to current.removed.
- var append = splice.removed.slice(
- current.index + current.addedCount - splice.index);
- Array.prototype.push.apply(removed, append);
- }
-
- splice.removed = removed;
- if (current.index < splice.index) {
- splice.index = current.index;
- }
- }
- } else if (splice.index < current.index) {
- // Insert splice here.
-
- inserted = true;
-
- splices.splice(i, 0, splice);
- i++;
-
- var offset = splice.addedCount - splice.removed.length
- current.index += offset;
- insertionOffset += offset;
- }
- }
-
- if (!inserted)
- splices.push(splice);
-}
-
-function createInitialSplices(array, changeRecords) {
- var splices = [];
-
- for (var i = 0; i < changeRecords.length; i++) {
- var record = changeRecords[i];
- switch(record.type) {
- case 'splice':
- mergeSplice(splices, record.index, record.removed.slice(),
- record.addedCount);
- break;
- case 'add':
- case 'update':
- case 'delete':
- if (!isIndex(record.name))
- continue;
- var index = toNumber(record.name);
- if (index < 0)
- continue;
- mergeSplice(splices, index, [record.oldValue], 1);
- break;
- default:
- console.error('Unexpected record type: ' + JSON.stringify(record));
- break;
- }
- }
-
- return splices;
-}
-
-function projectArraySplices(array, changeRecords) {
- var splices = [];
-
- createInitialSplices(array, changeRecords).forEach(function(splice) {
- if (splice.addedCount == 1 && splice.removed.length == 1) {
- if (splice.removed[0] !== array[splice.index])
- splices.push(splice);
-
- return
- };
-
- splices = splices.concat(calcSplices(array, splice.index,
- splice.index + splice.addedCount,
- splice.removed,
- 0,
- splice.removed.length));
- });
-
- return splices;
-}
-
-ArrayObserver.calculateSplices = function(current, previous) {
- return arraySplice.calculateSplices(current, previous);
-};
-
-module.exports = {
- Observer: Observer,
- runEOM_: runEOM,
- observerSentinel_: observerSentinel,
- PathObserver: PathObserver,
- ArrayObserver: ArrayObserver,
- ArraySplice: ArraySplice,
- CompoundObserver: CompoundObserver,
- Path: Path,
- ObserverTransform: ObserverTransform
-}
-</script>
« no previous file with comments | « sky/framework/sky-element/element-registry.sky ('k') | sky/framework/sky-element/sky-binder.sky » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698