browser/devtools/shared/observable-object.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/devtools/shared/observable-object.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,129 @@
     1.4 +/* this source code form is subject to the terms of the mozilla public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +/**
     1.9 + * ObservableObject
    1.10 + *
    1.11 + * An observable object is a JSON-like object that throws
    1.12 + * events when its direct properties or properties of any
    1.13 + * contained objects, are getting accessed or set.
    1.14 + *
    1.15 + * Inherits from EventEmitter.
    1.16 + *
    1.17 + * Properties:
    1.18 + * ⬩ object: JSON-like object
    1.19 + *
    1.20 + * Events:
    1.21 + * ⬩ "get" / path (array of property names)
    1.22 + * ⬩ "set" / path / new value
    1.23 + *
    1.24 + * Example:
    1.25 + *
    1.26 + *   let emitter = new ObservableObject({ x: { y: [10] } });
    1.27 + *   emitter.on("set", console.log);
    1.28 + *   emitter.on("get", console.log);
    1.29 + *   let obj = emitter.object;
    1.30 + *   obj.x.y[0] = 50;
    1.31 + *
    1.32 + */
    1.33 +
    1.34 +"use strict";
    1.35 +
    1.36 +const EventEmitter = require("devtools/toolkit/event-emitter");
    1.37 +
    1.38 +function ObservableObject(object = {}) {
    1.39 +  EventEmitter.decorate(this);
    1.40 +  let handler = new Handler(this);
    1.41 +  this.object = new Proxy(object, handler);
    1.42 +  handler._wrappers.set(this.object, object);
    1.43 +  handler._paths.set(object, []);
    1.44 +}
    1.45 +
    1.46 +module.exports = ObservableObject;
    1.47 +
    1.48 +function isObject(x) {
    1.49 +  if (typeof x === "object")
    1.50 +    return x !== null;
    1.51 +  return typeof x === "function";
    1.52 +}
    1.53 +
    1.54 +function Handler(emitter) {
    1.55 +  this._emitter = emitter;
    1.56 +  this._wrappers = new WeakMap();
    1.57 +  this._values = new WeakMap();
    1.58 +  this._paths = new WeakMap();
    1.59 +}
    1.60 +
    1.61 +Handler.prototype = {
    1.62 +  wrap: function(target, key, value) {
    1.63 +    let path;
    1.64 +    if (!isObject(value)) {
    1.65 +      path = this._paths.get(target).concat(key);
    1.66 +    } else if (this._wrappers.has(value)) {
    1.67 +      path = this._paths.get(value);
    1.68 +    } else if (this._paths.has(value)) {
    1.69 +      path = this._paths.get(value);
    1.70 +      value = this._values.get(value);
    1.71 +    } else {
    1.72 +      path = this._paths.get(target).concat(key);
    1.73 +      this._paths.set(value, path);
    1.74 +      let wrapper = new Proxy(value, this);
    1.75 +      this._wrappers.set(wrapper, value);
    1.76 +      this._values.set(value, wrapper);
    1.77 +      value = wrapper;
    1.78 +    }
    1.79 +    return [value, path];
    1.80 +  },
    1.81 +  unwrap: function(target, key, value) {
    1.82 +    if (!isObject(value) || !this._wrappers.has(value)) {
    1.83 +      return [value, this._paths.get(target).concat(key)];
    1.84 +    }
    1.85 +    return [this._wrappers.get(value), this._paths.get(target).concat(key)];
    1.86 +  },
    1.87 +  get: function(target, key) {
    1.88 +    let value = target[key];
    1.89 +    let [wrapped, path] = this.wrap(target, key, value);
    1.90 +    this._emitter.emit("get", path, value);
    1.91 +    return wrapped;
    1.92 +  },
    1.93 +  set: function(target, key, value) {
    1.94 +    let [wrapped, path] = this.unwrap(target, key, value);
    1.95 +    target[key] = value;
    1.96 +    this._emitter.emit("set", path, value);
    1.97 +  },
    1.98 +  getOwnPropertyDescriptor: function(target, key) {
    1.99 +    let desc = Object.getOwnPropertyDescriptor(target, key);
   1.100 +    if (desc) {
   1.101 +      if ("value" in desc) {
   1.102 +        let [wrapped, path] = this.wrap(target, key, desc.value);
   1.103 +        desc.value = wrapped;
   1.104 +        this._emitter.emit("get", path, desc.value);
   1.105 +      } else {
   1.106 +        if ("get" in desc) {
   1.107 +          [desc.get] = this.wrap(target, "get "+key, desc.get);
   1.108 +        }
   1.109 +        if ("set" in desc) {
   1.110 +          [desc.set] = this.wrap(target, "set "+key, desc.set);
   1.111 +        }
   1.112 +      }
   1.113 +    }
   1.114 +    return desc;
   1.115 +  },
   1.116 +  defineProperty: function(target, key, desc) {
   1.117 +    if ("value" in desc) {
   1.118 +      let [unwrapped, path] = this.unwrap(target, key, desc.value);
   1.119 +      desc.value = unwrapped;
   1.120 +      Object.defineProperty(target, key, desc);
   1.121 +      this._emitter.emit("set", path, desc.value);
   1.122 +    } else {
   1.123 +      if ("get" in desc) {
   1.124 +        [desc.get] = this.unwrap(target, "get "+key, desc.get);
   1.125 +      }
   1.126 +      if ("set" in desc) {
   1.127 +        [desc.set] = this.unwrap(target, "set "+key, desc.set);
   1.128 +      }
   1.129 +      Object.defineProperty(target, key, desc);
   1.130 +    }
   1.131 +  }
   1.132 +};

mercurial