Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.
43 changes: 36 additions & 7 deletions agent/Bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,27 +403,37 @@ class Bridge {
var protoclean = [];
if (inspectable) {
var val = getIn(inspectable, path);

var protod = false;
var isFn = typeof val === 'function';
Object.getOwnPropertyNames(val).forEach(name => {
if (name === '__proto__') {

// 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__' ) {
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') {
if (!protod && val.__proto__ && val.constructor.name !== 'Object' ) {
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;
}
newProto[name] = dehydrate(val.__proto__[name], protoclean, [name]);

// 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]);
});
proto = newProto;
}
Expand All @@ -439,8 +449,27 @@ class Bridge {
}

function getIn(base, path) {
let isPrototypeChain = false;

return path.reduce((obj, attr) => {
return obj ? obj[attr] : null;
if (!obj) {
return null;
}

// Mark the beginning of the prototype chain...
if (attr === '__proto__') {
isPrototypeChain = true;
return obj[attr];
}

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.__inner_state__ || obj )[attr];
}, base);
}

Expand Down
5 changes: 5 additions & 0 deletions agent/dehydrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
* and cleaned = [["some", "attr"], ["other"]]
*/
function dehydrate(data: Object, cleaned: Array<Array<string>>, path?: Array<string>, 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;
path = path || [];
if (typeof data === 'function') {
Expand Down
3 changes: 3 additions & 0 deletions backend/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions backend/innerStateInspectors.js
Original file line number Diff line number Diff line change
@@ -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 );
}
11 changes: 11 additions & 0 deletions backend/installGlobalHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ function installGlobalHook(window: Object) {
this._listeners[evt].map(fn => fn(data));
}
},
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),
});
}
Expand Down
2 changes: 2 additions & 0 deletions backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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, skipIfDefined? : boolean ) => void;
};