From ea416933e3442789ab76e132388f1f4ca3a55cb8 Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Tue, 3 Jan 2017 15:15:55 -0500 Subject: [PATCH 01/13] Added _state abstraction --- agent/Bridge.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/agent/Bridge.js b/agent/Bridge.js index db41f48288..18b2e6e1b2 100644 --- a/agent/Bridge.js +++ b/agent/Bridge.js @@ -403,16 +403,20 @@ class Bridge { var protoclean = []; if (inspectable) { var val = getIn(inspectable, path); + + var protod = false; var isFn = typeof val === 'function'; - Object.getOwnPropertyNames(val).forEach(name => { + var data = val._state || val; + + Object.getOwnPropertyNames( data ).forEach(name => { if (name === '__proto__') { protod = true; } if (isFn && (name === 'arguments' || name === 'callee' || name === 'caller')) { return; } - result[name] = dehydrate(val[name], cleaned, [name]); + result[name] = dehydrate( data[name], cleaned, [name]); }); /* eslint-disable no-proto */ @@ -420,10 +424,13 @@ class Bridge { var newProto = {}; var pIsFn = typeof val.__proto__ === 'function'; Object.getOwnPropertyNames(val.__proto__).forEach(name => { - if (pIsFn && (name === 'arguments' || name === 'callee' || name === 'caller')) { + if( pIsFn && (name === 'arguments' || name === 'callee' || name === 'caller') ){ return; } - newProto[name] = dehydrate(val.__proto__[name], protoclean, [name]); + + var prop = Object.getOwnPropertyDescriptor( val.__proto__, name ); + + newProto[name] = dehydrate( prop.get ? prop.get : val.__proto__[name], protoclean, [name]); }); proto = newProto; } @@ -440,7 +447,7 @@ class Bridge { function getIn(base, path) { return path.reduce((obj, attr) => { - return obj ? obj[attr] : null; + return obj ? ( ( obj._state || obj )[ attr ] ): null; }, base); } From a3cd3d7bb26ddb2322ecedbb52e8b0d0d34803ab Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Tue, 3 Jan 2017 18:52:21 -0500 Subject: [PATCH 02/13] Fixed state and props for NestedReact --- agent/Bridge.js | 20 ++++++++++++-------- agent/dehydrate.js | 4 ++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/agent/Bridge.js b/agent/Bridge.js index 18b2e6e1b2..885d3499b9 100644 --- a/agent/Bridge.js +++ b/agent/Bridge.js @@ -404,23 +404,27 @@ class Bridge { if (inspectable) { var val = getIn(inspectable, path); - - var protod = false; + var protod = false, isWrapper = false; var isFn = typeof val === 'function'; - var data = val._state || val; - Object.getOwnPropertyNames( data ).forEach(name => { - if (name === '__proto__') { + if( val && val._state ){ + val = val._state; + isWrapper = true; + } + + Object.getOwnPropertyNames( val ).forEach(name => { + if (name === '__proto__' && !isWrapper ) { protod = true; } + if (isFn && (name === 'arguments' || name === 'callee' || name === 'caller')) { return; } - result[name] = dehydrate( data[name], cleaned, [name]); + result[name] = dehydrate( val[name], cleaned, [name]); }); /* eslint-disable no-proto */ - if (!protod && val.__proto__ && val.constructor.name !== 'Object') { + if (!protod && val.__proto__ && val.constructor.name !== 'Object' && !isWrapper ) { var newProto = {}; var pIsFn = typeof val.__proto__ === 'function'; Object.getOwnPropertyNames(val.__proto__).forEach(name => { @@ -447,7 +451,7 @@ class Bridge { function getIn(base, path) { return path.reduce((obj, attr) => { - return obj ? ( ( obj._state || obj )[ attr ] ): null; + return obj ? ( ( obj._state || obj : obj )[ attr ] ): null; }, base); } diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 0fddfac5a1..236c8a7746 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -31,6 +31,10 @@ * and cleaned = [["some", "attr"], ["other"]] */ function dehydrate(data: Object, cleaned: Array>, path?: Array, level?: number): string | Object { + if( data && data._state && path[ path.length - 1 ] === 'state' ){ + data = data._state; + } + level = level || 0; path = path || []; if (typeof data === 'function') { From 1b9c40eaaf6d818cc6bf9f2f282feac9661606c3 Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Wed, 4 Jan 2017 20:55:11 -0500 Subject: [PATCH 03/13] - Show prototype for objects with inner state - Do not call calculated properties on prototype chain --- agent/Bridge.js | 36 ++++++++++++++++++++++++++---------- agent/dehydrate.js | 5 +++-- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/agent/Bridge.js b/agent/Bridge.js index 885d3499b9..40b3ed4cbf 100644 --- a/agent/Bridge.js +++ b/agent/Bridge.js @@ -404,27 +404,25 @@ class Bridge { if (inspectable) { var val = getIn(inspectable, path); - var protod = false, isWrapper = false; + var protod = false; var isFn = typeof val === 'function'; - if( val && val._state ){ - val = val._state; - isWrapper = true; - } + // Extract inner state of the third-party frameworks data objects... + var source = ( val && val._innerState ) || val; - Object.getOwnPropertyNames( val ).forEach(name => { - if (name === '__proto__' && !isWrapper ) { + Object.getOwnPropertyNames( source ).forEach(name => { + if (name === '__proto__' ) { protod = true; } if (isFn && (name === 'arguments' || name === 'callee' || name === 'caller')) { return; } - result[name] = dehydrate( val[name], cleaned, [name]); + result[name] = dehydrate( source[name], cleaned, [name]); }); /* eslint-disable no-proto */ - if (!protod && val.__proto__ && val.constructor.name !== 'Object' && !isWrapper ) { + if (!protod && val.__proto__ && val.constructor.name !== 'Object' ) { var newProto = {}; var pIsFn = typeof val.__proto__ === 'function'; Object.getOwnPropertyNames(val.__proto__).forEach(name => { @@ -432,6 +430,7 @@ class Bridge { return; } + // Calculated properties should not be evaluated on prototype. var prop = Object.getOwnPropertyDescriptor( val.__proto__, name ); newProto[name] = dehydrate( prop.get ? prop.get : val.__proto__[name], protoclean, [name]); @@ -450,8 +449,25 @@ class Bridge { } function getIn(base, path) { + let isPrototypeChain = false; + return path.reduce((obj, attr) => { - return obj ? ( ( obj._state || obj : obj )[ attr ] ): null; + if( !obj ) return null; + + // Mark the beginning of the prototype chain... + if( attr === "__proto__" ){ + isPrototypeChain = true; + return obj.__proto__; + } + + if( isPrototypeChain ){ + // Avoid calling calculated properties on prototype. + const property = Object.getOwnPropertyDescriptor( obj, attr ); + return property.get || property.value; + } + + // Traverse inner state of the third-party data frameworks objects... + return ( obj._innerState || obj )[ attr ]; }, base); } diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 236c8a7746..30df73a1c8 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -31,8 +31,9 @@ * and cleaned = [["some", "attr"], ["other"]] */ function dehydrate(data: Object, cleaned: Array>, path?: Array, level?: number): string | Object { - if( data && data._state && path[ path.length - 1 ] === 'state' ){ - data = data._state; + // Support third-party frameworks data objects in react component state. + if( data && data._innerState && path[ path.length - 1 ] === 'state' ){ + data = data._innerState; } level = level || 0; From f0310a5f5f227e7a4899b798a003610d30c47f3f Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Wed, 4 Jan 2017 21:02:03 -0500 Subject: [PATCH 04/13] Fixed lint errors --- agent/Bridge.js | 14 ++++++++------ agent/dehydrate.js | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/agent/Bridge.js b/agent/Bridge.js index 40b3ed4cbf..168d4386b3 100644 --- a/agent/Bridge.js +++ b/agent/Bridge.js @@ -426,7 +426,7 @@ class Bridge { var newProto = {}; var pIsFn = typeof val.__proto__ === 'function'; Object.getOwnPropertyNames(val.__proto__).forEach(name => { - if( pIsFn && (name === 'arguments' || name === 'callee' || name === 'caller') ){ + if (pIsFn && (name === 'arguments' || name === 'callee' || name === 'caller') ) { return; } @@ -452,22 +452,24 @@ function getIn(base, path) { let isPrototypeChain = false; return path.reduce((obj, attr) => { - if( !obj ) return null; + if (!obj) { + return null; + } // Mark the beginning of the prototype chain... - if( attr === "__proto__" ){ + if (attr === '__proto__') { isPrototypeChain = true; - return obj.__proto__; + return obj[attr]; } - if( isPrototypeChain ){ + if (isPrototypeChain) { // Avoid calling calculated properties on prototype. const property = Object.getOwnPropertyDescriptor( obj, attr ); return property.get || property.value; } // Traverse inner state of the third-party data frameworks objects... - return ( obj._innerState || obj )[ attr ]; + return ( obj._innerState || obj )[attr]; }, base); } diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 30df73a1c8..860e49613c 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -32,7 +32,7 @@ */ function dehydrate(data: Object, cleaned: Array>, path?: Array, level?: number): string | Object { // Support third-party frameworks data objects in react component state. - if( data && data._innerState && path[ path.length - 1 ] === 'state' ){ + if (data && data._innerState && path[path.length - 1] === 'state') { data = data._innerState; } From d1473cbe4c6d185b26a25a6e75d5da8c3d1ddfa3 Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Wed, 4 Jan 2017 21:56:53 -0500 Subject: [PATCH 05/13] Fixed flow type error --- agent/dehydrate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 860e49613c..23501acc2a 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -32,7 +32,7 @@ */ function dehydrate(data: Object, cleaned: Array>, path?: Array, level?: number): string | Object { // Support third-party frameworks data objects in react component state. - if (data && data._innerState && path[path.length - 1] === 'state') { + if (data && data._innerState && path && path[path.length - 1] === 'state') { data = data._innerState; } From fb7b03dbbb5408227c88dfe2996f5487ea339c80 Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Thu, 5 Jan 2017 19:01:09 -0500 Subject: [PATCH 06/13] renamed _innerState to avoid potential name collisions. --- agent/Bridge.js | 2 -- agent/dehydrate.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/agent/Bridge.js b/agent/Bridge.js index 168d4386b3..aca9779085 100644 --- a/agent/Bridge.js +++ b/agent/Bridge.js @@ -408,7 +408,6 @@ class Bridge { var isFn = typeof val === 'function'; // Extract inner state of the third-party frameworks data objects... - var source = ( val && val._innerState ) || val; Object.getOwnPropertyNames( source ).forEach(name => { if (name === '__proto__' ) { @@ -469,7 +468,6 @@ function getIn(base, path) { } // Traverse inner state of the third-party data frameworks objects... - return ( obj._innerState || obj )[attr]; }, base); } diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 23501acc2a..9601075138 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -32,8 +32,6 @@ */ function dehydrate(data: Object, cleaned: Array>, path?: Array, level?: number): string | Object { // Support third-party frameworks data objects in react component state. - if (data && data._innerState && path && path[path.length - 1] === 'state') { - data = data._innerState; } level = level || 0; From 25bdd33d4457977f42b6690d44d147bd7fba04d4 Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Thu, 5 Jan 2017 19:02:18 -0500 Subject: [PATCH 07/13] fixed commit error --- agent/Bridge.js | 2 ++ agent/dehydrate.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/agent/Bridge.js b/agent/Bridge.js index aca9779085..7b8be3029d 100644 --- a/agent/Bridge.js +++ b/agent/Bridge.js @@ -408,6 +408,7 @@ class Bridge { var isFn = typeof val === 'function'; // Extract inner state of the third-party frameworks data objects... + var source = ( val && val.__inner_state__ ) || val; Object.getOwnPropertyNames( source ).forEach(name => { if (name === '__proto__' ) { @@ -468,6 +469,7 @@ function getIn(base, path) { } // Traverse inner state of the third-party data frameworks objects... + return ( obj.__inner_state__ || obj )[attr]; }, base); } diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 9601075138..b68e784207 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -32,6 +32,8 @@ */ function dehydrate(data: Object, cleaned: Array>, path?: Array, level?: number): string | Object { // Support third-party frameworks data objects in react component state. + if (data && data.__inner_state__ && path && path[path.length - 1] === 'state') { + data = data.__inner_state__; } level = level || 0; From 6ee5c9dfe6740b7c6f2d451ade6b882e6f42879d Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Thu, 5 Jan 2017 19:19:57 -0500 Subject: [PATCH 08/13] Prevent __inner_state__ from appearing in __proto__ --- agent/Bridge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/Bridge.js b/agent/Bridge.js index 7b8be3029d..9c21b0ef8e 100644 --- a/agent/Bridge.js +++ b/agent/Bridge.js @@ -426,7 +426,7 @@ class Bridge { var newProto = {}; var pIsFn = typeof val.__proto__ === 'function'; Object.getOwnPropertyNames(val.__proto__).forEach(name => { - if (pIsFn && (name === 'arguments' || name === 'callee' || name === 'caller') ) { + if ( name === '__inner_state__' || ( pIsFn && (name === 'arguments' || name === 'callee' || name === 'caller') ) ) { return; } From bf0c893a8fad9acc609e771d94728a8fe2588705 Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Thu, 5 Jan 2017 19:50:31 -0500 Subject: [PATCH 09/13] Implemented addInnerStateInspector hook API --- agent/Agent.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/agent/Agent.js b/agent/Agent.js index 2602f8505c..3dc966170f 100644 --- a/agent/Agent.js +++ b/agent/Agent.js @@ -410,4 +410,14 @@ function getIn(base, path) { }, base); } +function addInnerStateInspector(Ctor, getInnerState) { + Object.defineProperty( Ctor.prototype, '__inner_state__', { + get : function() { + return getInnerState( this ); + }, + }); +} + +window.__REACT_DEVTOOLS_GLOBAL_HOOK__.addInnerStateInspector = addInnerStateInspector; + module.exports = Agent; From bf581d677f3bcd4c504e8822164e6d3241289bd5 Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Thu, 5 Jan 2017 20:15:01 -0500 Subject: [PATCH 10/13] Fixed implementation of global hook --- agent/Agent.js | 10 ---------- backend/installGlobalHook.js | 9 ++++++++- backend/types.js | 2 ++ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/agent/Agent.js b/agent/Agent.js index 3dc966170f..2602f8505c 100644 --- a/agent/Agent.js +++ b/agent/Agent.js @@ -410,14 +410,4 @@ function getIn(base, path) { }, base); } -function addInnerStateInspector(Ctor, getInnerState) { - Object.defineProperty( Ctor.prototype, '__inner_state__', { - get : function() { - return getInnerState( this ); - }, - }); -} - -window.__REACT_DEVTOOLS_GLOBAL_HOOK__.addInnerStateInspector = addInnerStateInspector; - module.exports = Agent; diff --git a/backend/installGlobalHook.js b/backend/installGlobalHook.js index a9f0192178..906b93f667 100644 --- a/backend/installGlobalHook.js +++ b/backend/installGlobalHook.js @@ -57,7 +57,14 @@ function installGlobalHook(window: Object) { this._listeners[evt].map(fn => fn(data)); } }, - }: Hook), + addInnerStateInspector: function(Ctor, getInnerState) { + Object.defineProperty( Ctor.prototype, '__inner_state__', { + get : function() { + return getInnerState( this ); + }, + }); + } +}: Hook), }); } diff --git a/backend/types.js b/backend/types.js index 6d0dd5f733..0b3c03e6b4 100644 --- a/backend/types.js +++ b/backend/types.js @@ -80,6 +80,7 @@ export type Helpers = { }; export type Handler = (data: any) => void; +export type InnerStateInspector = (data: any) => any; export type Hook = { _renderers: {[key: string]: ReactRenderer}, @@ -91,4 +92,5 @@ export type Hook = { on: (evt: string, handler: Handler) => void, off: (evt: string, handler: Handler) => void, reactDevtoolsAgent?: ?Object, + addInnerStateInspector: ( Ctor : Function, handler : InnerStateInspector ) => void; }; From 855e9adbb8956185f3f933e54ef60de8034769cf Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Thu, 5 Jan 2017 20:50:59 -0500 Subject: [PATCH 11/13] Fixed flow type errors --- backend/installGlobalHook.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/installGlobalHook.js b/backend/installGlobalHook.js index 906b93f667..5d51aeed0e 100644 --- a/backend/installGlobalHook.js +++ b/backend/installGlobalHook.js @@ -58,11 +58,11 @@ function installGlobalHook(window: Object) { } }, addInnerStateInspector: function(Ctor, getInnerState) { - Object.defineProperty( Ctor.prototype, '__inner_state__', { + Object.defineProperty( Ctor.prototype, '__inner_state__', ({ get : function() { return getInnerState( this ); }, - }); + } : Object )); } }: Hook), }); From 1071583cd841315d2baeec39a056db1ff5c7c3d9 Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Thu, 5 Jan 2017 21:06:50 -0500 Subject: [PATCH 12/13] Fixed lint errors --- backend/installGlobalHook.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/installGlobalHook.js b/backend/installGlobalHook.js index 5d51aeed0e..796d4eafaa 100644 --- a/backend/installGlobalHook.js +++ b/backend/installGlobalHook.js @@ -59,12 +59,12 @@ function installGlobalHook(window: Object) { }, addInnerStateInspector: function(Ctor, getInnerState) { Object.defineProperty( Ctor.prototype, '__inner_state__', ({ - get : function() { - return getInnerState( this ); - }, + get : function() { + return getInnerState( this ); + }, } : Object )); - } -}: Hook), + }, + }: Hook), }); } From bfce3b6722c65cc4837628a1443023415add2d1a Mon Sep 17 00:00:00 2001 From: Vlad Balin Date: Fri, 6 Jan 2017 17:13:47 -0500 Subject: [PATCH 13/13] Added Date inner state inspector --- backend/backend.js | 3 +++ backend/innerStateInspectors.js | 18 ++++++++++++++++++ backend/installGlobalHook.js | 16 ++++++++++------ backend/types.js | 2 +- 4 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 backend/innerStateInspectors.js diff --git a/backend/backend.js b/backend/backend.js index ff64a5ccd9..92f555f692 100644 --- a/backend/backend.js +++ b/backend/backend.js @@ -24,10 +24,13 @@ 'use strict'; import type {Hook} from './types'; +import attachInnerStateInspectors from './innerStateInspectors'; var attachRenderer = require('./attachRenderer'); module.exports = function setupBackend(hook: Hook): boolean { + attachInnerStateInspectors( hook ); + var oldReact = window.React && window.React.__internals; if (oldReact && Object.keys(hook._renderers).length === 0) { hook.inject(oldReact); diff --git a/backend/innerStateInspectors.js b/backend/innerStateInspectors.js new file mode 100644 index 0000000000..cfbfc1ce34 --- /dev/null +++ b/backend/innerStateInspectors.js @@ -0,0 +1,18 @@ +/** + * @flow + * + * Inner state inspectors for the built in JS data types. + */ + +'use strict'; + +import type {Hook} from './types'; + +export default +function attachInnerStateInspectors({ addInnerStateInspector } : Hook ) { + addInnerStateInspector( Date, ( x : Date ) => ({ + local : `${ x.toDateString() } ${ x.toTimeString() }`, + iso : x.toISOString(), + timestamp : x.getTime(), + }), true ); +} diff --git a/backend/installGlobalHook.js b/backend/installGlobalHook.js index 796d4eafaa..fd37130f13 100644 --- a/backend/installGlobalHook.js +++ b/backend/installGlobalHook.js @@ -57,12 +57,16 @@ function installGlobalHook(window: Object) { this._listeners[evt].map(fn => fn(data)); } }, - addInnerStateInspector: function(Ctor, getInnerState) { - Object.defineProperty( Ctor.prototype, '__inner_state__', ({ - get : function() { - return getInnerState( this ); - }, - } : Object )); + addInnerStateInspector: function(Ctor, getInnerState, skipIfDefined) { + if ( !( skipIfDefined && Ctor.prototype.hasOwnProperty( '__inner_state__' ) ) ) { + Object.defineProperty(Ctor.prototype, '__inner_state__', ({ + get: function() { + return getInnerState(this); + }, + enumerable: false, + configurable: true, + } : Object )); + } }, }: Hook), }); diff --git a/backend/types.js b/backend/types.js index 0b3c03e6b4..cb497db6bd 100644 --- a/backend/types.js +++ b/backend/types.js @@ -92,5 +92,5 @@ export type Hook = { on: (evt: string, handler: Handler) => void, off: (evt: string, handler: Handler) => void, reactDevtoolsAgent?: ?Object, - addInnerStateInspector: ( Ctor : Function, handler : InnerStateInspector ) => void; + addInnerStateInspector: ( Ctor : Function, handler : InnerStateInspector, skipIfDefined? : boolean ) => void; };