mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Added support for unserializable types (e.g. Set/Map, Immutable)
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import UnserializableProps from './UnserializableProps';
|
||||
import Contexts from './Contexts';
|
||||
import CustomHooks from './CustomHooks';
|
||||
import CustomObject from './CustomObject';
|
||||
import MapAndSet from './MapAndSet';
|
||||
import NestedProps from './NestedProps';
|
||||
import SimpleValues from './SimpleValues';
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function InspectableElements() {
|
||||
<Fragment>
|
||||
<h1>Inspectable elements</h1>
|
||||
<SimpleValues />
|
||||
<MapAndSet />
|
||||
<UnserializableProps />
|
||||
<NestedProps />
|
||||
<Contexts />
|
||||
<CustomHooks />
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const set = new Set();
|
||||
set.add('abc');
|
||||
set.add(123);
|
||||
|
||||
const map = new Map();
|
||||
map.set('name', 'Brian');
|
||||
map.set('food', 'sushi');
|
||||
|
||||
export default function MapAndSet() {
|
||||
return <ChildComponent map={map} set={set} />;
|
||||
}
|
||||
|
||||
function ChildComponent(props: any) {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import Immutable from 'immutable';
|
||||
|
||||
const set = new Set(['abc', 123]);
|
||||
const map = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||
const mapOfMaps = new Map([['first', map], ['second', map]]);
|
||||
const typedArray = Int8Array.from([100, -100, 0]);
|
||||
const immutable = Immutable.fromJS({
|
||||
a: [{ hello: 'there' }, 'fixed', true],
|
||||
b: 123,
|
||||
c: {
|
||||
'1': 'xyz',
|
||||
xyz: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default function UnserializableProps() {
|
||||
return (
|
||||
<ChildComponent
|
||||
map={map}
|
||||
set={set}
|
||||
mapOfMaps={mapOfMaps}
|
||||
setOfSets={setOfSets}
|
||||
typedArray={typedArray}
|
||||
immutable={immutable}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ChildComponent(props: any) {
|
||||
return null;
|
||||
}
|
||||
@@ -1,5 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`InspectedElementContext should dehydrate complex nested values when requested: 1: Initially inspect element 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
"owners": null,
|
||||
"context": null,
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"set_of_sets": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should dehydrate complex nested values when requested: 2: Inspect props.set_of_sets.0 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
"owners": null,
|
||||
"context": null,
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"set_of_sets": {
|
||||
"0": {
|
||||
"0": 1,
|
||||
"1": 2,
|
||||
"2": 3
|
||||
},
|
||||
"1": {}
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should include updates for nested values that were previously hydrated: 1: Initially inspect element 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
@@ -449,13 +485,38 @@ exports[`InspectedElementContext should support complex data types: 1: Inspected
|
||||
"context": null,
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"html_element": {},
|
||||
"fn": {},
|
||||
"symbol": {},
|
||||
"react_element": {},
|
||||
"array_buffer": {},
|
||||
"typed_array": {},
|
||||
"date": {}
|
||||
"date": {},
|
||||
"fn": {},
|
||||
"html_element": {},
|
||||
"immutable": {
|
||||
"0": {},
|
||||
"1": {},
|
||||
"2": {}
|
||||
},
|
||||
"map": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"map_of_maps": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"react_element": {},
|
||||
"set": {
|
||||
"0": "abc",
|
||||
"1": 123
|
||||
},
|
||||
"set_of_sets": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"symbol": {},
|
||||
"typed_array": {
|
||||
"0": 100,
|
||||
"1": -100,
|
||||
"2": 0
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
|
||||
@@ -390,23 +390,42 @@ describe('InspectedElementContext', () => {
|
||||
});
|
||||
|
||||
it('should support complex data types', async done => {
|
||||
const Immutable = require('immutable');
|
||||
|
||||
const Example = () => null;
|
||||
|
||||
const div = document.createElement('div');
|
||||
const exmapleFunction = () => {};
|
||||
const typedArray = new Uint8Array(3);
|
||||
const exampleFunction = () => {};
|
||||
const setShallow = new Set(['abc', 123]);
|
||||
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
|
||||
const typedArray = Int8Array.from([100, -100, 0]);
|
||||
const immutableMap = Immutable.fromJS({
|
||||
a: [{ hello: 'there' }, 'fixed', true],
|
||||
b: 123,
|
||||
c: {
|
||||
'1': 'xyz',
|
||||
xyz: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
await utils.actAsync(() =>
|
||||
ReactDOM.render(
|
||||
<Example
|
||||
html_element={div}
|
||||
fn={exmapleFunction}
|
||||
symbol={Symbol('symbol')}
|
||||
react_element={<span />}
|
||||
array_buffer={typedArray.buffer}
|
||||
typed_array={typedArray}
|
||||
date={new Date()}
|
||||
fn={exampleFunction}
|
||||
html_element={div}
|
||||
immutable={immutableMap}
|
||||
map={mapShallow}
|
||||
map_of_maps={mapOfMaps}
|
||||
react_element={<span />}
|
||||
set={setShallow}
|
||||
set_of_sets={setOfSets}
|
||||
symbol={Symbol('symbol')}
|
||||
typed_array={typedArray}
|
||||
/>,
|
||||
container
|
||||
)
|
||||
@@ -441,37 +460,77 @@ describe('InspectedElementContext', () => {
|
||||
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
|
||||
|
||||
const {
|
||||
html_element,
|
||||
fn,
|
||||
symbol,
|
||||
react_element,
|
||||
array_buffer,
|
||||
typed_array,
|
||||
date,
|
||||
fn,
|
||||
html_element,
|
||||
immutable,
|
||||
map,
|
||||
map_of_maps,
|
||||
react_element,
|
||||
set,
|
||||
set_of_sets,
|
||||
symbol,
|
||||
typed_array,
|
||||
} = (inspectedElement: any).props;
|
||||
expect(html_element[meta.inspectable]).toBe(false);
|
||||
expect(html_element[meta.name]).toBe('DIV');
|
||||
expect(html_element[meta.type]).toBe('html_element');
|
||||
expect(fn[meta.inspectable]).toBe(false);
|
||||
expect(fn[meta.name]).toBe('exmapleFunction');
|
||||
expect(fn[meta.type]).toBe('function');
|
||||
expect(symbol[meta.inspectable]).toBe(false);
|
||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||
expect(symbol[meta.type]).toBe('symbol');
|
||||
expect(react_element[meta.inspectable]).toBe(false);
|
||||
expect(react_element[meta.name]).toBe('span');
|
||||
expect(react_element[meta.type]).toBe('react_element');
|
||||
|
||||
expect(array_buffer[meta.size]).toBe(3);
|
||||
expect(array_buffer[meta.inspectable]).toBe(false);
|
||||
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
|
||||
expect(array_buffer[meta.type]).toBe('array_buffer');
|
||||
expect(typed_array[meta.size]).toBe(3);
|
||||
expect(typed_array[meta.inspectable]).toBe(false);
|
||||
expect(typed_array[meta.name]).toBe('Uint8Array');
|
||||
expect(typed_array[meta.type]).toBe('typed_array');
|
||||
|
||||
expect(date[meta.inspectable]).toBe(false);
|
||||
expect(date[meta.type]).toBe('date');
|
||||
|
||||
expect(fn[meta.inspectable]).toBe(false);
|
||||
expect(fn[meta.name]).toBe('exampleFunction');
|
||||
expect(fn[meta.type]).toBe('function');
|
||||
|
||||
expect(html_element[meta.inspectable]).toBe(false);
|
||||
expect(html_element[meta.name]).toBe('DIV');
|
||||
expect(html_element[meta.type]).toBe('html_element');
|
||||
|
||||
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(immutable[meta.name]).toBe('Map');
|
||||
expect(immutable[meta.type]).toBe('iterator');
|
||||
|
||||
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(map[meta.name]).toBe('Map');
|
||||
expect(map[meta.type]).toBe('iterator');
|
||||
expect(map[0][meta.type]).toBe('array');
|
||||
|
||||
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(map_of_maps[meta.name]).toBe('Map');
|
||||
expect(map_of_maps[meta.type]).toBe('iterator');
|
||||
expect(map_of_maps[0][meta.type]).toBe('array');
|
||||
|
||||
expect(react_element[meta.inspectable]).toBe(false);
|
||||
expect(react_element[meta.name]).toBe('span');
|
||||
expect(react_element[meta.type]).toBe('react_element');
|
||||
|
||||
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(set[meta.name]).toBe('Set');
|
||||
expect(set[meta.type]).toBe('iterator');
|
||||
expect(set[0]).toBe('abc');
|
||||
expect(set[1]).toBe(123);
|
||||
|
||||
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(set_of_sets[meta.name]).toBe('Set');
|
||||
expect(set_of_sets[meta.type]).toBe('iterator');
|
||||
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
|
||||
|
||||
expect(symbol[meta.inspectable]).toBe(false);
|
||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||
expect(symbol[meta.type]).toBe('symbol');
|
||||
|
||||
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(typed_array[meta.size]).toBe(3);
|
||||
expect(typed_array[meta.name]).toBe('Int8Array');
|
||||
expect(typed_array[meta.type]).toBe('typed_array');
|
||||
expect(typed_array[0]).toBe(100);
|
||||
expect(typed_array[1]).toBe(-100);
|
||||
expect(typed_array[2]).toBe(0);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -663,6 +722,62 @@ describe('InspectedElementContext', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should dehydrate complex nested values when requested', async done => {
|
||||
const Example = () => null;
|
||||
|
||||
const container = document.createElement('div');
|
||||
await utils.actAsync(() =>
|
||||
ReactDOM.render(
|
||||
<Example
|
||||
set_of_sets={new Set([new Set([1, 2, 3]), new Set(['a', 'b', 'c'])])}
|
||||
/>,
|
||||
container
|
||||
)
|
||||
);
|
||||
|
||||
const id = ((store.getElementIDAtIndex(0): any): number);
|
||||
|
||||
let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
|
||||
let inspectedElement = null;
|
||||
|
||||
function Suspender({ target }) {
|
||||
const context = React.useContext(InspectedElementContext);
|
||||
getInspectedElementPath = context.getInspectedElementPath;
|
||||
inspectedElement = context.getInspectedElement(target);
|
||||
return null;
|
||||
}
|
||||
|
||||
await utils.actAsync(
|
||||
() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={0}
|
||||
>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender target={id} />
|
||||
</React.Suspense>
|
||||
</Contexts>
|
||||
),
|
||||
false
|
||||
);
|
||||
expect(getInspectedElementPath).not.toBeNull();
|
||||
expect(inspectedElement).not.toBeNull();
|
||||
expect(inspectedElement).toMatchSnapshot('1: Initially inspect element');
|
||||
|
||||
inspectedElement = null;
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.act(() => {
|
||||
getInspectedElementPath(id, ['props', 'set_of_sets', 0]);
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
});
|
||||
expect(inspectedElement).not.toBeNull();
|
||||
expect(inspectedElement).toMatchSnapshot('2: Inspect props.set_of_sets.0');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should include updates for nested values that were previously hydrated', async done => {
|
||||
const Example = () => null;
|
||||
|
||||
|
||||
@@ -126,13 +126,38 @@ Object {
|
||||
"context": {},
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"html_element": {},
|
||||
"fn": {},
|
||||
"symbol": {},
|
||||
"react_element": {},
|
||||
"array_buffer": {},
|
||||
"typed_array": {},
|
||||
"date": {}
|
||||
"date": {},
|
||||
"fn": {},
|
||||
"html_element": {},
|
||||
"immutable": {
|
||||
"0": {},
|
||||
"1": {},
|
||||
"2": {}
|
||||
},
|
||||
"map": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"map_of_maps": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"react_element": {},
|
||||
"set": {
|
||||
"0": "abc",
|
||||
"1": 123
|
||||
},
|
||||
"set_of_sets": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"symbol": {},
|
||||
"typed_array": {
|
||||
"0": 100,
|
||||
"1": -100,
|
||||
"2": 0
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
},
|
||||
|
||||
@@ -23,7 +23,11 @@ describe('InspectedElementContext', () => {
|
||||
dehydratedData: DehydratedData | null
|
||||
): Object | null {
|
||||
if (dehydratedData !== null) {
|
||||
return hydrate(dehydratedData.data, dehydratedData.cleaned);
|
||||
return hydrate(
|
||||
dehydratedData.data,
|
||||
dehydratedData.cleaned,
|
||||
dehydratedData.unserializable
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -132,22 +136,41 @@ describe('InspectedElementContext', () => {
|
||||
});
|
||||
|
||||
it('should support complex data types', async done => {
|
||||
const Immutable = require('immutable');
|
||||
|
||||
const Example = () => null;
|
||||
|
||||
const div = document.createElement('div');
|
||||
const exmapleFunction = () => {};
|
||||
const typedArray = new Uint8Array(3);
|
||||
const exampleFunction = () => {};
|
||||
const setShallow = new Set(['abc', 123]);
|
||||
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
|
||||
const typedArray = Int8Array.from([100, -100, 0]);
|
||||
const immutableMap = Immutable.fromJS({
|
||||
a: [{ hello: 'there' }, 'fixed', true],
|
||||
b: 123,
|
||||
c: {
|
||||
'1': 'xyz',
|
||||
xyz: 1,
|
||||
},
|
||||
});
|
||||
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Example
|
||||
html_element={div}
|
||||
fn={exmapleFunction}
|
||||
symbol={Symbol('symbol')}
|
||||
react_element={<span />}
|
||||
array_buffer={typedArray.buffer}
|
||||
typed_array={typedArray}
|
||||
date={new Date()}
|
||||
fn={exampleFunction}
|
||||
html_element={div}
|
||||
immutable={immutableMap}
|
||||
map={mapShallow}
|
||||
map_of_maps={mapOfMaps}
|
||||
react_element={<span />}
|
||||
set={setShallow}
|
||||
set_of_sets={setOfSets}
|
||||
symbol={Symbol('symbol')}
|
||||
typed_array={typedArray}
|
||||
/>,
|
||||
document.createElement('div')
|
||||
)
|
||||
@@ -159,37 +182,77 @@ describe('InspectedElementContext', () => {
|
||||
expect(inspectedElement).toMatchSnapshot('1: Initial inspection');
|
||||
|
||||
const {
|
||||
html_element,
|
||||
fn,
|
||||
symbol,
|
||||
react_element,
|
||||
array_buffer,
|
||||
typed_array,
|
||||
date,
|
||||
fn,
|
||||
html_element,
|
||||
immutable,
|
||||
map,
|
||||
map_of_maps,
|
||||
react_element,
|
||||
set,
|
||||
set_of_sets,
|
||||
symbol,
|
||||
typed_array,
|
||||
} = inspectedElement.value.props;
|
||||
expect(html_element[meta.inspectable]).toBe(false);
|
||||
expect(html_element[meta.name]).toBe('DIV');
|
||||
expect(html_element[meta.type]).toBe('html_element');
|
||||
expect(fn[meta.inspectable]).toBe(false);
|
||||
expect(fn[meta.name]).toBe('exmapleFunction');
|
||||
expect(fn[meta.type]).toBe('function');
|
||||
expect(symbol[meta.inspectable]).toBe(false);
|
||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||
expect(symbol[meta.type]).toBe('symbol');
|
||||
expect(react_element[meta.inspectable]).toBe(false);
|
||||
expect(react_element[meta.name]).toBe('span');
|
||||
expect(react_element[meta.type]).toBe('react_element');
|
||||
|
||||
expect(array_buffer[meta.size]).toBe(3);
|
||||
expect(array_buffer[meta.inspectable]).toBe(false);
|
||||
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
|
||||
expect(array_buffer[meta.type]).toBe('array_buffer');
|
||||
expect(typed_array[meta.size]).toBe(3);
|
||||
expect(typed_array[meta.inspectable]).toBe(false);
|
||||
expect(typed_array[meta.name]).toBe('Uint8Array');
|
||||
expect(typed_array[meta.type]).toBe('typed_array');
|
||||
|
||||
expect(date[meta.inspectable]).toBe(false);
|
||||
expect(date[meta.type]).toBe('date');
|
||||
|
||||
expect(fn[meta.inspectable]).toBe(false);
|
||||
expect(fn[meta.name]).toBe('exampleFunction');
|
||||
expect(fn[meta.type]).toBe('function');
|
||||
|
||||
expect(html_element[meta.inspectable]).toBe(false);
|
||||
expect(html_element[meta.name]).toBe('DIV');
|
||||
expect(html_element[meta.type]).toBe('html_element');
|
||||
|
||||
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(immutable[meta.name]).toBe('Map');
|
||||
expect(immutable[meta.type]).toBe('iterator');
|
||||
|
||||
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(map[meta.name]).toBe('Map');
|
||||
expect(map[meta.type]).toBe('iterator');
|
||||
expect(map[0][meta.type]).toBe('array');
|
||||
|
||||
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(map_of_maps[meta.name]).toBe('Map');
|
||||
expect(map_of_maps[meta.type]).toBe('iterator');
|
||||
expect(map_of_maps[0][meta.type]).toBe('array');
|
||||
|
||||
expect(react_element[meta.inspectable]).toBe(false);
|
||||
expect(react_element[meta.name]).toBe('span');
|
||||
expect(react_element[meta.type]).toBe('react_element');
|
||||
|
||||
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(set[meta.name]).toBe('Set');
|
||||
expect(set[meta.type]).toBe('iterator');
|
||||
expect(set[0]).toBe('abc');
|
||||
expect(set[1]).toBe(123);
|
||||
|
||||
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(set_of_sets[meta.name]).toBe('Set');
|
||||
expect(set_of_sets[meta.type]).toBe('iterator');
|
||||
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
|
||||
|
||||
expect(symbol[meta.inspectable]).toBe(false);
|
||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||
expect(symbol[meta.type]).toBe('symbol');
|
||||
|
||||
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(typed_array[meta.size]).toBe(3);
|
||||
expect(typed_array[meta.name]).toBe('Int8Array');
|
||||
expect(typed_array[meta.type]).toBe('typed_array');
|
||||
expect(typed_array[0]).toBe(100);
|
||||
expect(typed_array[1]).toBe(-100);
|
||||
expect(typed_array[2]).toBe(0);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
+12
-3
@@ -10,11 +10,20 @@ export function cleanForBridge(
|
||||
path?: Array<string | number> = []
|
||||
): DehydratedData | null {
|
||||
if (data !== null) {
|
||||
const cleaned = [];
|
||||
const cleanedPaths = [];
|
||||
const unserializablePaths = [];
|
||||
const cleanedData = dehydrate(
|
||||
data,
|
||||
cleanedPaths,
|
||||
unserializablePaths,
|
||||
path,
|
||||
isPathWhitelisted
|
||||
);
|
||||
|
||||
return {
|
||||
data: dehydrate(data, cleaned, path, isPathWhitelisted),
|
||||
cleaned,
|
||||
data: cleanedData,
|
||||
cleaned: cleanedPaths,
|
||||
unserializable: unserializablePaths,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -134,7 +134,7 @@ function InspectedElementContextController({ children }: Props) {
|
||||
const value = hydrateHelper(data.value, data.path);
|
||||
const inspectedElement = { ...currentlyInspectedElement };
|
||||
|
||||
fillInPath(inspectedElement, data.path, value);
|
||||
fillInPath(inspectedElement, data.value, data.path, value);
|
||||
|
||||
resource.write(element, inspectedElement);
|
||||
|
||||
@@ -289,7 +289,7 @@ function hydrateHelper(
|
||||
path?: Array<string | number>
|
||||
): Object | null {
|
||||
if (dehydratedData !== null) {
|
||||
let { cleaned, data } = dehydratedData;
|
||||
let { cleaned, data, unserializable } = dehydratedData;
|
||||
|
||||
if (path) {
|
||||
const { length } = path;
|
||||
@@ -297,10 +297,13 @@ function hydrateHelper(
|
||||
// Hydration helper requires full paths, but inspection dehydrates with relative paths.
|
||||
// In that event it's important that we adjust the "cleaned" paths to match.
|
||||
cleaned = cleaned.map(cleanedPath => cleanedPath.slice(length));
|
||||
unserializable = unserializable.map(unserializablePath =>
|
||||
unserializablePath.slice(length)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return hydrate(data, cleaned);
|
||||
return hydrate(data, cleaned, unserializable);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ type KeyValueProps = {|
|
||||
depth: number,
|
||||
hidden?: boolean,
|
||||
inspectPath?: InspectPath,
|
||||
isReadOnly?: boolean,
|
||||
name: string,
|
||||
overrideValueFn?: ?OverrideValueFn,
|
||||
path: Array<any>,
|
||||
@@ -25,6 +26,7 @@ type KeyValueProps = {|
|
||||
export default function KeyValue({
|
||||
depth,
|
||||
inspectPath,
|
||||
isReadOnly,
|
||||
hidden,
|
||||
name,
|
||||
overrideValueFn,
|
||||
@@ -78,17 +80,18 @@ export default function KeyValue({
|
||||
displayValue = 'undefined';
|
||||
}
|
||||
|
||||
const nameClassName =
|
||||
typeof overrideValueFn === 'function' ? styles.EditableName : styles.Name;
|
||||
const isEditable = typeof overrideValueFn === 'function' && !isReadOnly;
|
||||
|
||||
children = (
|
||||
<div key="root" className={styles.Item} hidden={hidden} style={style}>
|
||||
<div className={styles.ExpandCollapseToggleSpacer} />
|
||||
<span className={nameClassName}>{name}</span>
|
||||
{typeof overrideValueFn === 'function' ? (
|
||||
<span className={isEditable ? styles.EditableName : styles.Name}>
|
||||
{name}
|
||||
</span>
|
||||
{isEditable ? (
|
||||
<EditableValue
|
||||
dataType={dataType}
|
||||
overrideValueFn={overrideValueFn}
|
||||
overrideValueFn={((overrideValueFn: any): OverrideValueFn)}
|
||||
path={path}
|
||||
value={value}
|
||||
/>
|
||||
@@ -97,7 +100,10 @@ export default function KeyValue({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else if (value.hasOwnProperty(meta.type)) {
|
||||
} else if (
|
||||
value.hasOwnProperty(meta.type) &&
|
||||
!value.hasOwnProperty(meta.unserializable)
|
||||
) {
|
||||
children = (
|
||||
<div key="root" className={styles.Item} hidden={hidden} style={style}>
|
||||
{isInspectable ? (
|
||||
@@ -123,6 +129,7 @@ export default function KeyValue({
|
||||
key={index}
|
||||
depth={depth + 1}
|
||||
inspectPath={inspectPath}
|
||||
isReadOnly={isReadOnly}
|
||||
hidden={hidden || !isOpen}
|
||||
name={index}
|
||||
overrideValueFn={overrideValueFn}
|
||||
@@ -156,12 +163,17 @@ export default function KeyValue({
|
||||
);
|
||||
} else {
|
||||
const hasChildren = Object.entries(value).length > 0;
|
||||
const displayName = value.hasOwnProperty(meta.unserializable)
|
||||
? getMetaValueLabel(value)
|
||||
: 'Object';
|
||||
|
||||
let areChildrenReadOnly = isReadOnly || !!value[meta.readonly];
|
||||
children = Object.entries(value).map<Element<any>>(([name, value]) => (
|
||||
<KeyValue
|
||||
key={name}
|
||||
depth={depth + 1}
|
||||
inspectPath={inspectPath}
|
||||
isReadOnly={areChildrenReadOnly}
|
||||
hidden={hidden || !isOpen}
|
||||
name={name}
|
||||
overrideValueFn={overrideValueFn}
|
||||
@@ -188,7 +200,7 @@ export default function KeyValue({
|
||||
{name}
|
||||
</span>
|
||||
<span>
|
||||
Object{' '}
|
||||
{`${displayName || ''} `}
|
||||
{hasChildren ? '' : <span className={styles.Empty}>(empty)</span>}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -83,5 +83,6 @@ export type InspectedElement = {|
|
||||
|
||||
export type DehydratedData = {|
|
||||
cleaned: Array<Array<string | number>>,
|
||||
unserializable: Array<Array<string | number>>,
|
||||
data: Object,
|
||||
|};
|
||||
|
||||
+140
-15
@@ -18,6 +18,8 @@ import {
|
||||
} from 'react-is';
|
||||
import { getDisplayName, getInObject, setInObject } from './utils';
|
||||
|
||||
import type { DehydratedData } from 'src/devtools/views/Components/types';
|
||||
|
||||
export const meta = {
|
||||
inspectable: Symbol('inspectable'),
|
||||
inspected: Symbol('inspected'),
|
||||
@@ -25,6 +27,7 @@ export const meta = {
|
||||
readonly: Symbol('readonly'),
|
||||
size: Symbol('size'),
|
||||
type: Symbol('type'),
|
||||
unserializable: Symbol('unserializable'),
|
||||
};
|
||||
|
||||
type Dehydrated = {|
|
||||
@@ -35,6 +38,18 @@ type Dehydrated = {|
|
||||
type: string,
|
||||
|};
|
||||
|
||||
// Typed arrays and other complex iteratable objects (e.g. Map, Set, ImmutableJS) need special handling.
|
||||
// These objects can't be serialized without losing type information,
|
||||
// so a "Unserializable" type wrapper is used (with meta-data keys) to send nested values-
|
||||
// while preserving the original type and name.
|
||||
type Unserializable = {
|
||||
name: string | null,
|
||||
readonly?: boolean,
|
||||
size?: number,
|
||||
type: string,
|
||||
unserializable: boolean,
|
||||
};
|
||||
|
||||
// This threshold determines the depth at which the bridge "dehydrates" nested data.
|
||||
// Dehydration means that we don't serialize the data for e.g. postMessage or stringify,
|
||||
// unless the frontend explicitly requests it (e.g. a user clicks to expand a props object).
|
||||
@@ -173,12 +188,19 @@ function createDehydrated(
|
||||
export function dehydrate(
|
||||
data: Object,
|
||||
cleaned: Array<Array<string | number>>,
|
||||
unserializable: Array<Array<string | number>>,
|
||||
path: Array<string | number>,
|
||||
isPathWhitelisted: (path: Array<string | number>) => boolean,
|
||||
level?: number = 0
|
||||
): string | Dehydrated | { [key: string]: string | Dehydrated } {
|
||||
):
|
||||
| string
|
||||
| Dehydrated
|
||||
| Unserializable
|
||||
| { [key: string]: string | Dehydrated | Unserializable } {
|
||||
const type = getDataType(data);
|
||||
|
||||
let isPathWhitelistedCheck;
|
||||
|
||||
switch (type) {
|
||||
case 'html_element':
|
||||
cleaned.push(path);
|
||||
@@ -229,23 +251,56 @@ export function dehydrate(
|
||||
};
|
||||
|
||||
case 'array':
|
||||
const arrayPathCheck = isPathWhitelisted(path);
|
||||
if (level >= LEVEL_THRESHOLD && !arrayPathCheck) {
|
||||
isPathWhitelistedCheck = isPathWhitelisted(path);
|
||||
if (level >= LEVEL_THRESHOLD && !isPathWhitelistedCheck) {
|
||||
return createDehydrated(type, true, data, cleaned, path);
|
||||
}
|
||||
return data.map((item, i) =>
|
||||
dehydrate(
|
||||
item,
|
||||
cleaned,
|
||||
unserializable,
|
||||
path.concat([i]),
|
||||
isPathWhitelisted,
|
||||
arrayPathCheck ? 1 : level + 1
|
||||
isPathWhitelistedCheck ? 1 : level + 1
|
||||
)
|
||||
);
|
||||
|
||||
case 'typed_array':
|
||||
case 'iterator':
|
||||
return createDehydrated(type, false, data, cleaned, path);
|
||||
isPathWhitelistedCheck = isPathWhitelisted(path);
|
||||
if (level >= LEVEL_THRESHOLD && !isPathWhitelistedCheck) {
|
||||
return createDehydrated(type, true, data, cleaned, path);
|
||||
} else {
|
||||
const unserializableValue: Unserializable = {
|
||||
unserializable: true,
|
||||
type: type,
|
||||
readonly: true,
|
||||
size: type === 'typed_array' ? data.length : undefined,
|
||||
name:
|
||||
!data.constructor || data.constructor.name === 'Object'
|
||||
? ''
|
||||
: data.constructor.name,
|
||||
};
|
||||
|
||||
if (typeof data[Symbol.iterator]) {
|
||||
[...data].forEach(
|
||||
(item, i) =>
|
||||
(unserializableValue[i] = dehydrate(
|
||||
item,
|
||||
cleaned,
|
||||
unserializable,
|
||||
path.concat([i]),
|
||||
isPathWhitelisted,
|
||||
isPathWhitelistedCheck ? 1 : level + 1
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
unserializable.push(path);
|
||||
|
||||
return unserializableValue;
|
||||
}
|
||||
|
||||
case 'date':
|
||||
cleaned.push(path);
|
||||
@@ -256,8 +311,8 @@ export function dehydrate(
|
||||
};
|
||||
|
||||
case 'object':
|
||||
const objectPathCheck = isPathWhitelisted(path);
|
||||
if (level >= LEVEL_THRESHOLD && !objectPathCheck) {
|
||||
isPathWhitelistedCheck = isPathWhitelisted(path);
|
||||
if (level >= LEVEL_THRESHOLD && !isPathWhitelistedCheck) {
|
||||
return createDehydrated(type, true, data, cleaned, path);
|
||||
} else {
|
||||
const object = {};
|
||||
@@ -265,9 +320,10 @@ export function dehydrate(
|
||||
object[name] = dehydrate(
|
||||
data[name],
|
||||
cleaned,
|
||||
unserializable,
|
||||
path.concat([name]),
|
||||
isPathWhitelisted,
|
||||
objectPathCheck ? 1 : level + 1
|
||||
isPathWhitelistedCheck ? 1 : level + 1
|
||||
);
|
||||
}
|
||||
return object;
|
||||
@@ -290,24 +346,33 @@ export function dehydrate(
|
||||
|
||||
export function fillInPath(
|
||||
object: Object,
|
||||
data: DehydratedData,
|
||||
path: Array<string | number>,
|
||||
value: any
|
||||
) {
|
||||
const target = getInObject(object, path);
|
||||
if (target != null) {
|
||||
delete target[meta.inspectable];
|
||||
delete target[meta.inspected];
|
||||
delete target[meta.name];
|
||||
delete target[meta.readonly];
|
||||
delete target[meta.size];
|
||||
delete target[meta.type];
|
||||
if (!target[meta.unserializable]) {
|
||||
delete target[meta.inspectable];
|
||||
delete target[meta.inspected];
|
||||
delete target[meta.name];
|
||||
delete target[meta.readonly];
|
||||
delete target[meta.size];
|
||||
delete target[meta.type];
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== null && data.unserializable.includes(path)) {
|
||||
upgradeUnserializable(value, value);
|
||||
}
|
||||
|
||||
setInObject(object, path, value);
|
||||
}
|
||||
|
||||
export function hydrate(
|
||||
object: Object,
|
||||
cleaned: Array<Array<string | number>>
|
||||
cleaned: Array<Array<string | number>>,
|
||||
unserializable: Array<Array<string | number>>
|
||||
): Object {
|
||||
cleaned.forEach((path: Array<string | number>) => {
|
||||
const length = path.length;
|
||||
@@ -338,9 +403,69 @@ export function hydrate(
|
||||
parent[last] = replaced;
|
||||
}
|
||||
});
|
||||
unserializable.forEach((path: Array<string | number>) => {
|
||||
const length = path.length;
|
||||
const last = path[length - 1];
|
||||
const parent = getInObject(object, path.slice(0, length - 1));
|
||||
if (!parent || !parent.hasOwnProperty(last)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = parent[last];
|
||||
|
||||
const replacement = {
|
||||
...node,
|
||||
};
|
||||
|
||||
upgradeUnserializable(replacement, node);
|
||||
|
||||
parent[last] = replacement;
|
||||
});
|
||||
return object;
|
||||
}
|
||||
|
||||
function upgradeUnserializable(destination: Object, source: Object) {
|
||||
Object.defineProperties(destination, {
|
||||
[meta.inspected]: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: !!source.inspected,
|
||||
},
|
||||
[meta.name]: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: source.name,
|
||||
},
|
||||
[meta.size]: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: source.size,
|
||||
},
|
||||
[meta.readonly]: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: !!source.readonly,
|
||||
},
|
||||
[meta.type]: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: source.type,
|
||||
},
|
||||
[meta.unserializable]: {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: !!source.unserializable,
|
||||
},
|
||||
});
|
||||
|
||||
delete destination.inspected;
|
||||
delete destination.name;
|
||||
delete destination.size;
|
||||
delete destination.readonly;
|
||||
delete destination.type;
|
||||
delete destination.unserializable;
|
||||
}
|
||||
|
||||
export function getDisplayNameForReactElement(
|
||||
element: React$Element<any>
|
||||
): string | null {
|
||||
|
||||
+10
-6
@@ -266,13 +266,17 @@ export function shallowDiffers(prev: Object, next: Object): boolean {
|
||||
|
||||
export function getInObject(object: Object, path: Array<string | number>): any {
|
||||
return path.reduce((reduced: Object, attr: string | number): any => {
|
||||
if (typeof reduced === 'object' && reduced !== null) {
|
||||
return reduced[attr];
|
||||
} else if (Array.isArray(reduced)) {
|
||||
return reduced[attr];
|
||||
} else {
|
||||
return null;
|
||||
if (reduced) {
|
||||
if (hasOwnProperty.call(reduced, attr)) {
|
||||
return reduced[attr];
|
||||
}
|
||||
if (typeof reduced[Symbol.iterator] === 'function') {
|
||||
// Convert iterable to array and return array[index]
|
||||
return [...reduced][attr];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, object);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user