main
js 167 lines 4.8 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {ReactContext} from 'shared/ReactTypes';
11
12 import * as React from 'react';
13 import {createContext, useCallback, useContext, useEffect} from 'react';
14 import {createResource} from '../../cache';
15 import {BridgeContext, StoreContext} from '../context';
16 import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
17 import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils';
18
19 import type {OwnersList} from 'react-devtools-shared/src/backend/types';
20 import type {
21 Element,
22 SerializedElement,
23 } from 'react-devtools-shared/src/frontend/types';
24 import type {Resource, Thenable} from '../../cache';
25
26 type Context = (id: number) => Array<SerializedElement> | null;
27
28 const OwnersListContext: ReactContext<Context> = createContext<Context>(
29 null as any as Context,
30 );
31 OwnersListContext.displayName = 'OwnersListContext';
32
33 type ResolveFn = (ownersList: Array<SerializedElement> | null) => void;
34 type InProgressRequest = {
35 promise: Thenable<Array<SerializedElement>>,
36 resolveFn: ResolveFn,
37 };
38
39 const inProgressRequests: WeakMap<Element, InProgressRequest> = new WeakMap();
40 const resource: Resource<
41 Element,
42 Element,
43 Array<SerializedElement>,
44 > = createResource(
45 (element: Element) => {
46 const request = inProgressRequests.get(element);
47 if (request != null) {
48 return request.promise;
49 }
50
51 let resolveFn:
52 | ResolveFn
53 | ((
54 result: Promise<Array<SerializedElement>> | Array<SerializedElement>,
55 ) => void) = null as any as ResolveFn;
56 const promise = new Promise(resolve => {
57 resolveFn = resolve;
58 });
59
60 // $FlowFixMe[incompatible-type] found when upgrading Flow
61 inProgressRequests.set(element, {promise, resolveFn});
62
63 return promise as $FlowFixMe;
64 },
65 (element: Element) => element,
66 {useWeakMap: true},
67 );
68
69 type Props = {
70 children: React$Node,
71 };
72
73 function useChangeOwnerAction(): (nextOwnerID: number) => void {
74 const bridge = useContext(BridgeContext);
75 const store = useContext(StoreContext);
76 const treeAction = useContext(TreeDispatcherContext);
77
78 return useCallback(
79 function changeOwnerAction(nextOwnerID: number) {
80 treeAction({type: 'SELECT_OWNER', payload: nextOwnerID});
81
82 const element = store.getElementByID(nextOwnerID);
83 if (element !== null) {
84 if (!inProgressRequests.has(element)) {
85 let resolveFn:
86 | ResolveFn
87 | ((
88 result:
89 | Promise<Array<SerializedElement>>
90 | Array<SerializedElement>,
91 ) => void) = null as any as ResolveFn;
92 const promise = new Promise(resolve => {
93 resolveFn = resolve;
94 });
95
96 // $FlowFixMe[incompatible-type] found when upgrading Flow
97 inProgressRequests.set(element, {promise, resolveFn});
98 }
99
100 const rendererID = store.getRendererIDForElement(nextOwnerID);
101 if (rendererID !== null) {
102 bridge.send('getOwnersList', {id: nextOwnerID, rendererID});
103 }
104 }
105 },
106 [bridge, store],
107 );
108 }
109
110 function OwnersListContextController({children}: Props): React.Node {
111 const bridge = useContext(BridgeContext);
112 const store = useContext(StoreContext);
113 const {ownerID} = useContext(TreeStateContext);
114
115 const read = useCallback(
116 (id: number) => {
117 const element = store.getElementByID(id);
118 if (element !== null) {
119 return resource.read(element);
120 } else {
121 return null;
122 }
123 },
124 [store],
125 );
126
127 useEffect(() => {
128 const onOwnersList = (ownersList: OwnersList) => {
129 const id = ownersList.id;
130
131 const element = store.getElementByID(id);
132 if (element !== null) {
133 const request = inProgressRequests.get(element);
134 if (request != null) {
135 request.resolveFn(
136 ownersList.owners === null
137 ? null
138 : ownersList.owners.map(backendToFrontendSerializedElementMapper),
139 );
140 }
141 }
142 };
143
144 bridge.addListener('ownersList', onOwnersList);
145 return () => bridge.removeListener('ownersList', onOwnersList);
146 }, [bridge, store]);
147
148 // This effect requests an updated owners list any time the selected owner changes
149 useEffect(() => {
150 if (ownerID !== null) {
151 const rendererID = store.getRendererIDForElement(ownerID);
152 if (rendererID !== null) {
153 bridge.send('getOwnersList', {id: ownerID, rendererID});
154 }
155 }
156
157 return () => {};
158 }, [bridge, ownerID, store]);
159
160 return (
161 <OwnersListContext.Provider value={read}>
162 {children}
163 </OwnersListContext.Provider>
164 );
165 }
166
167 export {OwnersListContext, OwnersListContextController, useChangeOwnerAction};