зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 0876b808c677 (bug 1234880)
This commit is contained in:
Родитель
f02f7a61e4
Коммит
946f3384b6
|
@ -30,6 +30,7 @@ DevToolsModules(
|
|||
'inplace-editor.js',
|
||||
'Jsbeautify.jsm',
|
||||
'node-attribute-parser.js',
|
||||
'observable-object.js',
|
||||
'options-view.js',
|
||||
'output-parser.js',
|
||||
'poller.js',
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/* this source code form is subject to the terms of the mozilla public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* ObservableObject
|
||||
*
|
||||
* An observable object is a JSON-like object that throws
|
||||
* events when its direct properties or properties of any
|
||||
* contained objects, are getting accessed or set.
|
||||
*
|
||||
* Inherits from EventEmitter.
|
||||
*
|
||||
* Properties:
|
||||
* ⬩ object: JSON-like object
|
||||
*
|
||||
* Events:
|
||||
* ⬩ "get" / path (array of property names)
|
||||
* ⬩ "set" / path / new value
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* let emitter = new ObservableObject({ x: { y: [10] } });
|
||||
* emitter.on("set", console.log);
|
||||
* emitter.on("get", console.log);
|
||||
* let obj = emitter.object;
|
||||
* obj.x.y[0] = 50;
|
||||
*
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
function ObservableObject(object = {}) {
|
||||
EventEmitter.decorate(this);
|
||||
let handler = new Handler(this);
|
||||
this.object = new Proxy(object, handler);
|
||||
handler._wrappers.set(this.object, object);
|
||||
handler._paths.set(object, []);
|
||||
}
|
||||
|
||||
module.exports = ObservableObject;
|
||||
|
||||
function isObject(x) {
|
||||
if (typeof x === "object")
|
||||
return x !== null;
|
||||
return typeof x === "function";
|
||||
}
|
||||
|
||||
function Handler(emitter) {
|
||||
this._emitter = emitter;
|
||||
this._wrappers = new WeakMap();
|
||||
this._values = new WeakMap();
|
||||
this._paths = new WeakMap();
|
||||
}
|
||||
|
||||
Handler.prototype = {
|
||||
wrap: function(target, key, value) {
|
||||
let path;
|
||||
if (!isObject(value)) {
|
||||
path = this._paths.get(target).concat(key);
|
||||
} else if (this._wrappers.has(value)) {
|
||||
path = this._paths.get(value);
|
||||
} else if (this._paths.has(value)) {
|
||||
path = this._paths.get(value);
|
||||
value = this._values.get(value);
|
||||
} else {
|
||||
path = this._paths.get(target).concat(key);
|
||||
this._paths.set(value, path);
|
||||
let wrapper = new Proxy(value, this);
|
||||
this._wrappers.set(wrapper, value);
|
||||
this._values.set(value, wrapper);
|
||||
value = wrapper;
|
||||
}
|
||||
return [value, path];
|
||||
},
|
||||
unwrap: function(target, key, value) {
|
||||
if (!isObject(value) || !this._wrappers.has(value)) {
|
||||
return [value, this._paths.get(target).concat(key)];
|
||||
}
|
||||
return [this._wrappers.get(value), this._paths.get(target).concat(key)];
|
||||
},
|
||||
get: function(target, key) {
|
||||
let value = target[key];
|
||||
let [wrapped, path] = this.wrap(target, key, value);
|
||||
this._emitter.emit("get", path, value);
|
||||
return wrapped;
|
||||
},
|
||||
set: function(target, key, value) {
|
||||
let [wrapped, path] = this.unwrap(target, key, value);
|
||||
target[key] = value;
|
||||
this._emitter.emit("set", path, value);
|
||||
return true;
|
||||
},
|
||||
getOwnPropertyDescriptor: function(target, key) {
|
||||
let desc = Object.getOwnPropertyDescriptor(target, key);
|
||||
if (desc) {
|
||||
if ("value" in desc) {
|
||||
let [wrapped, path] = this.wrap(target, key, desc.value);
|
||||
desc.value = wrapped;
|
||||
this._emitter.emit("get", path, desc.value);
|
||||
} else {
|
||||
if ("get" in desc) {
|
||||
[desc.get] = this.wrap(target, "get "+key, desc.get);
|
||||
}
|
||||
if ("set" in desc) {
|
||||
[desc.set] = this.wrap(target, "set "+key, desc.set);
|
||||
}
|
||||
}
|
||||
}
|
||||
return desc;
|
||||
},
|
||||
defineProperty: function(target, key, desc) {
|
||||
if ("value" in desc) {
|
||||
let [unwrapped, path] = this.unwrap(target, key, desc.value);
|
||||
desc.value = unwrapped;
|
||||
Object.defineProperty(target, key, desc);
|
||||
this._emitter.emit("set", path, desc.value);
|
||||
} else {
|
||||
if ("get" in desc) {
|
||||
[desc.get] = this.unwrap(target, "get "+key, desc.get);
|
||||
}
|
||||
if ("set" in desc) {
|
||||
[desc.set] = this.unwrap(target, "set "+key, desc.set);
|
||||
}
|
||||
Object.defineProperty(target, key, desc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
|
@ -118,6 +118,7 @@ skip-if = e10s # Layouthelpers test should not run in a content page.
|
|||
[browser_mdn-docs-02.js]
|
||||
[browser_mdn-docs-03.js]
|
||||
[browser_num-l10n.js]
|
||||
[browser_observableobject.js]
|
||||
[browser_options-view-01.js]
|
||||
[browser_outputparser.js]
|
||||
skip-if = e10s # Test intermittently fails with e10s. Bug 1124162.
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
let ObservableObject = require("devtools/client/shared/observable-object");
|
||||
|
||||
let rawObject = {};
|
||||
let oe = new ObservableObject(rawObject);
|
||||
|
||||
function str(o) {
|
||||
return JSON.stringify(o);
|
||||
}
|
||||
|
||||
function areObjectsSynced() {
|
||||
is(str(rawObject), str(oe.object), "Objects are synced");
|
||||
}
|
||||
|
||||
areObjectsSynced();
|
||||
|
||||
let index = 0;
|
||||
let expected = [
|
||||
{type: "set", path: "foo", value: 4},
|
||||
{type: "get", path: "foo", value: 4},
|
||||
{type: "get", path: "foo", value: 4},
|
||||
{type: "get", path: "bar", value: undefined},
|
||||
{type: "get", path: "bar", value: undefined},
|
||||
{type: "set", path: "bar", value: {}},
|
||||
{type: "get", path: "bar", value: {}},
|
||||
{type: "get", path: "bar", value: {}},
|
||||
{type: "set", path: "bar.a", value: [1,2,3,4]},
|
||||
{type: "get", path: "bar", value: {a:[1,2,3,4]}},
|
||||
{type: "set", path: "bar.mop", value: 1},
|
||||
{type: "set", path: "bar", value: {}},
|
||||
{type: "set", path: "foo", value: [{a:42}]},
|
||||
{type: "get", path: "foo", value: [{a:42}]},
|
||||
{type: "get", path: "foo.0", value: {a:42}},
|
||||
{type: "get", path: "foo.0.a", value: 42},
|
||||
{type: "get", path: "foo", value: [{a:42}]},
|
||||
{type: "get", path: "foo.0", value: {a:42}},
|
||||
{type: "set", path: "foo.0.a", value: 2},
|
||||
{type: "get", path: "foo", value: [{a:2}]},
|
||||
{type: "get", path: "bar", value: {}},
|
||||
{type: "set", path: "foo.1", value: {}},
|
||||
];
|
||||
|
||||
function callback(event, path, value) {
|
||||
oe.off("get", callback);
|
||||
ok(event, "event defined");
|
||||
ok(path, "path defined");
|
||||
if (index >= expected.length) {
|
||||
return;
|
||||
}
|
||||
let e = expected[index];
|
||||
is(event, e.type, "[" + index + "] Right event received");
|
||||
is(path.join("."), e.path, "[" + index + "] Path valid");
|
||||
is(str(value), str(e.value), "[" + index + "] Value valid");
|
||||
index++;
|
||||
areObjectsSynced();
|
||||
oe.on("get", callback);
|
||||
}
|
||||
|
||||
oe.on("set", callback);
|
||||
oe.on("get", callback);
|
||||
|
||||
oe.object.foo = 4;
|
||||
oe.object.foo;
|
||||
Object.getOwnPropertyDescriptor(oe.object, "foo")
|
||||
oe.object["bar"];
|
||||
oe.object.bar;
|
||||
oe.object.bar = {};
|
||||
oe.object.bar;
|
||||
oe.object.bar.a = [1,2,3,4];
|
||||
Object.defineProperty(oe.object.bar, "mop", {value:1});
|
||||
oe.object.bar = {};
|
||||
oe.object.foo = [{a:42}];
|
||||
oe.object.foo[0].a;
|
||||
oe.object.foo[0].a = 2;
|
||||
oe.object.foo[1] = oe.object.bar;
|
||||
|
||||
is(index, expected.length, "Event count is right");
|
||||
is(oe.object.bar, oe.object.bar, "Object attributes are wrapped only once");
|
||||
|
||||
finish();
|
||||
}
|
Загрузка…
Ссылка в новой задаче