patch makeReadOnly into playground
Mofei Zhang committed
Oct 3, 2022 at 14:37 UTC
896ef251b4f2c802313bb7019e49aaf004c8c4e6
1 file changed
+102
compiler/forget/packages/playground/lib/patchUseMemoCache.ts
+102
@@ -28,6 +28,108 @@ export default function patchUseMemoCache(reactSource: string) {
28
return value;
29
};
30
31
+ // Utility functions
32
+ function isWriteable(desc) {
33
+ return (desc.writable || desc.set) && desc.configurable;
34
+ }
35
+ function getOrInsertDefault(m, k) {
36
+ const entry = m.get(k);
37
+ if (entry) {
38
+ return { existed: true, entry };
39
+ }
40
+ else {
41
+ const newEntry = new Map();
42
+ m.set(k, newEntry);
43
+ return { existed: false, entry: newEntry };
44
+ }
45
+ }
46
+ function buildMakeReadOnly(logger, skippedClasses, isInROMode) {
47
+ // All saved proxys
48
+ const savedROObjects = new WeakMap();
49
+ // Overwrites an object property with its proxy and saves its original value
50
+ function addProperty(obj, key, prop, savedEntries) {
51
+ const proxy = {
52
+ get() {
53
+ // read from backing cache entry
54
+ return makeReadOnly(savedEntries.get(key).savedVal);
55
+ },
56
+ set(newVal) {
57
+ if (isInROMode()) {
58
+ logger("FORGET_MUTATE_IMMUT", key, newVal);
59
+ }
60
+ // update backing cache entry
61
+ savedEntries.get(key).savedVal = newVal;
62
+ },
63
+ };
64
+ if (prop.configurable != null) {
65
+ proxy.configurable = prop.configurable;
66
+ }
67
+ if (prop.enumerable != null) {
68
+ proxy.enumerable = prop.enumerable;
69
+ }
70
+ savedEntries.set(key, { savedVal: obj[key], getter: proxy.get });
71
+ Object.defineProperty(obj, key, proxy);
72
+ }
73
+ // Changes an object to be read-only, returns its input
74
+ function makeReadOnly(o) {
75
+ if (typeof o !== "object" || o == null) {
76
+ return o;
77
+ }
78
+ else if (skippedClasses.includes(o.constructor.name)) {
79
+ return o;
80
+ }
81
+ const { existed, entry: cache } = getOrInsertDefault(savedROObjects, o);
82
+ for (const [k, entry] of cache.entries()) {
83
+ const currentProp = Object.getOwnPropertyDescriptor(o, k);
84
+ if (currentProp && !isWriteable(currentProp)) {
85
+ continue;
86
+ }
87
+ const currentPropGetter = currentProp?.get;
88
+ const cachedGetter = entry.getter;
89
+ if (currentPropGetter !== cachedGetter) {
90
+ // cache is currently holding an old property
91
+ // - it may have been deleted
92
+ // - it may have been deleted + re-set
93
+ // (meaning that new value is not proxied,
94
+ // and the current proxied value is stale)
95
+ cache.delete(k);
96
+ if (!currentProp && isInROMode()) {
97
+ logger("FORGET_DELETE_PROP_IMMUT", k);
98
+ }
99
+ else if (currentProp) {
100
+ if (isInROMode()) {
101
+ logger("FORGET_CHANGE_PROP_IMMUT", k);
102
+ }
103
+ addProperty(o, k, currentProp, cache);
104
+ }
105
+ }
106
+ }
107
+ for (const [k, prop] of Object.entries(Object.getOwnPropertyDescriptors(o))) {
108
+ if (!cache.has(k) && isWriteable(prop)) {
109
+ if (isInROMode() && existed) {
110
+ logger("FORGET_ADD_PROP_IMMUT", k);
111
+ }
112
+ addProperty(o, k, prop, cache);
113
+ }
114
+ }
115
+ return o;
116
+ }
117
+ return makeReadOnly;
118
+ }
119
+
120
+ useMemoCache.makeReadOnly = buildMakeReadOnly(
121
+ console.log,
122
+ [
123
+ // some immutVals have a transitive reference to global (e.g. LexicalEditor)
124
+ 'Window',
125
+ 'Storage', // useBrowserStorage
126
+ // some immutVals (e.g. ref.current) have a ref to DOM nodes (e.g. BaseDOMContainer)
127
+ // - since FiberNodes -should- be interior mutable, this is safe
128
+ 'FiberNode',
129
+ ],
130
+ () => true, // always read-only
131
+ );
132
+
133
React.useMemoCache = useMemoCache;
134
135
export default React;