Skip to content

Commit a6bcd6f

Browse files
authored
Storage: fix clear and removeItem (#1136)
* Storage: fix clear and removeItem The mutations array usually contains an index into StringContext for value of key/value in the operation. Historically, "delete" was modeled as 0th index into the array. Unfortunately, this didn't make sense if the first SET op was genuinely referring to the 0th string value (See #1035). While this fixed the first get/set operation, it also meant that deletes were no longer possible! This PR now models the delete operation as a -1 in the unsigned mutation array, corresponding to 2^16-1. * dont rely on overflow * switch to special casing 0-value * clean up test * switch to isolating change within LocalStorage processor * jridgewell found fix
1 parent 9cff888 commit a6bcd6f

File tree

9 files changed

+54
-55
lines changed

9 files changed

+54
-55
lines changed

src/main-thread/commands/storage.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ export const StorageProcessor: CommandExecutorInterface = (strings, nodeContext,
6464

6565
// TODO(choumx): Clean up key/value strings (or don't store them in the first place)
6666
// to avoid leaking memory.
67-
const key = keyIndex >= 0 ? strings.get(keyIndex) : '';
68-
const value = valueIndex >= 0 ? strings.get(valueIndex) : null;
67+
const key = keyIndex > 0 ? strings.get(keyIndex - 1) : '';
68+
const value = valueIndex > 0 ? strings.get(valueIndex - 1) : null;
6969

7070
if (operation === GetOrSet.GET) {
7171
get(location, key);
@@ -82,8 +82,8 @@ export const StorageProcessor: CommandExecutorInterface = (strings, nodeContext,
8282
const keyIndex = mutations[startPosition + StorageMutationIndex.Key];
8383
const valueIndex = mutations[startPosition + StorageMutationIndex.Value];
8484

85-
const key = keyIndex >= 0 ? strings.get(keyIndex) : null;
86-
const value = valueIndex >= 0 ? strings.get(valueIndex) : null;
85+
const key = keyIndex > 0 ? strings.get(keyIndex - 1) : null;
86+
const value = valueIndex > 0 ? strings.get(valueIndex - 1) : null;
8787

8888
return {
8989
type: 'STORAGE',

src/main-thread/strings.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ export class StringContext {
2424
/**
2525
* Stores a string in mapping and returns the index of the location.
2626
* @param value string to store
27-
* @return location in map
27+
* @return {number}
2828
*/
29-
store(value: string): void {
29+
store(value: string): number {
3030
this.strings.push(value);
31+
return this.strings.length - 1;
3132
}
3233

3334
/**

src/test/main-thread/commands/storage.test.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ import { TransferrableKeys } from '../../../transfer/TransferrableKeys';
1111

1212
const test = anyTest as TestInterface<{}>;
1313

14+
type SetStorageMeta = { location: StorageLocation; key: string | null; value: string | null };
15+
1416
function getStorageProcessor(strings: string[]): {
1517
processor: CommandExecutor;
1618
messages: Array<MessageToWorker>;
19+
getLastSetStorage: () => any;
1720
} {
1821
const stringCtx = new StringContext();
1922
stringCtx.storeValues(strings);
2023
const messages: Array<MessageToWorker> = [];
24+
let lastSetStorage: null | SetStorageMeta = null;
2125

2226
const processor = StorageProcessor(
2327
stringCtx,
@@ -34,12 +38,16 @@ function getStorageProcessor(strings: string[]): {
3438
getStorage() {
3539
return Promise.resolve({ hello: 'world' });
3640
},
41+
setStorage(location: StorageLocation, key: string | null, value: string | null) {
42+
lastSetStorage = { location, key, value };
43+
},
3744
} as unknown as Sanitizer,
3845
} as WorkerDOMConfiguration,
3946
);
4047
return {
4148
processor,
4249
messages,
50+
getLastSetStorage: () => lastSetStorage,
4351
};
4452
}
4553

@@ -48,7 +56,7 @@ test('StorageProcessor sends storage value event to worker', async (t) => {
4856
const mutation: number[] = [];
4957
mutation[StorageMutationIndex.Operation] = GetOrSet.GET;
5058
mutation[StorageMutationIndex.Location] = StorageLocation.AmpState;
51-
mutation[StorageMutationIndex.Key] = 0;
59+
mutation[StorageMutationIndex.Key] = 1;
5260
const mutations = new Uint16Array(mutation);
5361

5462
processor.execute(mutations, 0, true);
@@ -63,3 +71,18 @@ test('StorageProcessor sends storage value event to worker', async (t) => {
6371
t.is(messages.length, 1);
6472
t.deepEqual(messages, [expectedMessage]);
6573
});
74+
75+
test('StorageProcessor handles deletion event from worker', async (t) => {
76+
const { processor, getLastSetStorage } = getStorageProcessor(['t', 'hello']);
77+
const mutation: number[] = [];
78+
mutation[StorageMutationIndex.Operation] = GetOrSet.SET;
79+
mutation[StorageMutationIndex.Location] = StorageLocation.Local;
80+
mutation[StorageMutationIndex.Key] = 2;
81+
mutation[StorageMutationIndex.Value] = 0;
82+
const mutations = new Uint16Array(mutation);
83+
84+
processor.execute(mutations, 0, true);
85+
await Promise.resolve(setTimeout);
86+
87+
t.deepEqual(getLastSetStorage(), { location: StorageLocation.Local, key: 'hello', value: null });
88+
});

src/test/main-thread/deserializeTransferrableObject.test.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ test('Deserializes float arguments', (t) => {
5555
test('Deserializes string arguments', (t) => {
5656
const { stringContext, nodeContext } = t.context;
5757

58-
const serializedArgs = [TransferrableObjectType.String, storeString(stringContext, 'textArg')];
58+
const serializedArgs = [TransferrableObjectType.String, stringContext.store('textArg')];
5959
const buffer = new Uint16Array(serializedArgs);
6060
const { args: deserializedArgs } = deserializeTransferrableObject(buffer, 0, 1, stringContext, nodeContext);
6161

@@ -104,7 +104,7 @@ test('Deserializes varying types', (t) => {
104104

105105
// argument 2: String
106106
const stringArg = 'textArg';
107-
const stringId = storeString(stringContext, stringArg);
107+
const stringId = stringContext.store(stringArg);
108108

109109
// argument 3: Object
110110
const objectId = 7;
@@ -155,13 +155,6 @@ test('Returns the correct end offset', (t) => {
155155
t.is(buffer[endOffset], 32);
156156
});
157157

158-
// main-thread's strings API does not return an ID when storing a string
159-
// so for convenience:
160-
function storeString(stringContext: StringContext, text: string, currentIndex = -1) {
161-
stringContext.store(text);
162-
return ++currentIndex;
163-
}
164-
165158
/**
166159
* Used to compare float value similarity in tests.
167160
* @param expected

src/test/main-thread/object-creation.test.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ test('Object creation with mutation at a zero offset', (t) => {
9595
objectCreationProcessor.execute(
9696
new Uint16Array([
9797
TransferrableMutationType.OBJECT_CREATION,
98-
storeString(stringContext, methodName),
98+
stringContext.store(methodName),
9999
objectId,
100100
4, // arg count
101101
...serializedTarget,
@@ -141,7 +141,7 @@ test('Object creation with mutation at non-zero offset', (t) => {
141141

142142
const mutation = [
143143
TransferrableMutationType.OBJECT_CREATION,
144-
storeString(stringContext, methodName),
144+
stringContext.store(methodName),
145145
objectId,
146146
4, // arg count
147147
...serializedTarget,
@@ -180,7 +180,7 @@ test('Returns correct end offset', (t) => {
180180

181181
const mutation = [
182182
TransferrableMutationType.OBJECT_CREATION,
183-
storeString(stringContext, methodName),
183+
stringContext.store(methodName),
184184
1, // object ID (not important for this test)
185185
4, // arg count
186186
TransferrableObjectType.CanvasRenderingContext2D,
@@ -196,8 +196,3 @@ test('Returns correct end offset', (t) => {
196196

197197
t.is(mutationsArray[endOffset], 32);
198198
});
199-
200-
function storeString(stringContext: StringContext, text: string, currentIndex = -1) {
201-
stringContext.store(text);
202-
return ++currentIndex;
203-
}

src/test/main-thread/object-mutation.test.ts

+3-17
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ test('Mutation starts at a non-zero offset', (t) => {
242242

243243
const mutation = [
244244
TransferrableMutationType.OBJECT_MUTATION,
245-
storeString(stringContext, methodName),
245+
stringContext.store(methodName),
246246
4, // arg count
247247
TransferrableObjectType.CanvasRenderingContext2D,
248248
canvasElement._index_,
@@ -280,7 +280,7 @@ test('Returns correct end offset', (t) => {
280280

281281
const mutation = [
282282
TransferrableMutationType.OBJECT_MUTATION,
283-
storeString(stringContext, methodName),
283+
stringContext.store(methodName),
284284
4, // arg count
285285
TransferrableObjectType.CanvasRenderingContext2D,
286286
canvasElement._index_,
@@ -304,24 +304,10 @@ function executeCall(
304304
stringContext: StringContext,
305305
argCount: number,
306306
serializedArgs: number[],
307-
stringsIndex?: number,
308307
) {
309308
return mutationProcessor.execute(
310-
new Uint16Array([
311-
TransferrableMutationType.OBJECT_MUTATION,
312-
storeString(stringContext, fnName, stringsIndex),
313-
argCount,
314-
...serializedTargetObject,
315-
...serializedArgs,
316-
]),
309+
new Uint16Array([TransferrableMutationType.OBJECT_MUTATION, stringContext.store(fnName), argCount, ...serializedTargetObject, ...serializedArgs]),
317310
0,
318311
/* allow */ true,
319312
);
320313
}
321-
322-
// main-thread's strings API does not return an ID when storing a string
323-
// so for convenience:
324-
function storeString(stringContext: StringContext, text: string, currentIndex = -1) {
325-
stringContext.store(text);
326-
return ++currentIndex;
327-
}

src/test/main-thread/string-properties.test.ts

+5-10
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ test.beforeEach((t) => {
6767
test('setting property to a new string', (t) => {
6868
const { stringContext, propertyProcessor, inputElement } = t.context;
6969
const newValue = 'new value';
70-
const storedValueKey = storeString(stringContext, 'value');
71-
const storedNewValue = storeString(stringContext, newValue, storedValueKey);
70+
const storedValueKey = stringContext.store('value');
71+
const storedNewValue = stringContext.store(newValue);
7272

7373
propertyProcessor.execute(
7474
new Uint16Array([TransferrableMutationType.PROPERTIES, inputElement._index_, storedValueKey, NumericBoolean.FALSE, storedNewValue]),
@@ -82,9 +82,9 @@ test('setting property back to an empty string', (t) => {
8282
const { stringContext, propertyProcessor, inputElement } = t.context;
8383
const firstUpdateValue = 'new value';
8484
const secondUpdateValue = '';
85-
const storedValueKey = storeString(stringContext, 'value');
86-
const storedFirstUpdateValue = storeString(stringContext, firstUpdateValue, storedValueKey);
87-
const storedSecondUpdateValue = storeString(stringContext, secondUpdateValue, storedFirstUpdateValue);
85+
const storedValueKey = stringContext.store('value');
86+
const storedFirstUpdateValue = stringContext.store(firstUpdateValue);
87+
const storedSecondUpdateValue = stringContext.store(secondUpdateValue);
8888

8989
propertyProcessor.execute(
9090
new Uint16Array([TransferrableMutationType.PROPERTIES, inputElement._index_, storedValueKey, NumericBoolean.FALSE, storedFirstUpdateValue]),
@@ -100,8 +100,3 @@ test('setting property back to an empty string', (t) => {
100100
);
101101
t.is(inputElement.value, secondUpdateValue);
102102
});
103-
104-
function storeString(stringContext: StringContext, text: string, currentIndex = -1) {
105-
stringContext.store(text);
106-
return ++currentIndex;
107-
}

src/test/mutation-transfer/storage.test.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ test.serial.cb('Storage.setItem', (t) => {
4343
const { document, storage } = t.context;
4444

4545
expectMutations(document, (mutations) => {
46-
t.deepEqual(mutations, [TransferrableMutationType.STORAGE, GetOrSet.SET, StorageLocation.Local, getForTesting('foo'), getForTesting('bar')]);
46+
t.deepEqual(mutations, [
47+
TransferrableMutationType.STORAGE,
48+
GetOrSet.SET,
49+
StorageLocation.Local,
50+
getForTesting('foo')! + 1,
51+
getForTesting('bar')! + 1,
52+
]);
4753
t.end();
4854
});
4955

@@ -54,7 +60,7 @@ test.serial.cb('Storage.removeItem', (t) => {
5460
const { document, storage } = t.context;
5561

5662
expectMutations(document, (mutations) => {
57-
t.deepEqual(mutations, [TransferrableMutationType.STORAGE, GetOrSet.SET, StorageLocation.Local, getForTesting('foo'), 0]);
63+
t.deepEqual(mutations, [TransferrableMutationType.STORAGE, GetOrSet.SET, StorageLocation.Local, getForTesting('foo')! + 1, 0]);
5864
t.end();
5965
});
6066

src/worker-thread/Storage.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function createStorage(document: Document | DocumentStub, location: Stora
5151
const stringValue = String(value);
5252
this[key] = stringValue;
5353

54-
transfer(document, [TransferrableMutationType.STORAGE, GetOrSet.SET, location, store(key), store(stringValue)]);
54+
transfer(document, [TransferrableMutationType.STORAGE, GetOrSet.SET, location, store(key) + 1, store(stringValue) + 1]);
5555
},
5656
});
5757
define(storage, 'removeItem', {
@@ -62,7 +62,7 @@ export function createStorage(document: Document | DocumentStub, location: Stora
6262
TransferrableMutationType.STORAGE,
6363
GetOrSet.SET,
6464
location,
65-
store(key),
65+
store(key) + 1,
6666
0, // value == 0 represents deletion.
6767
]);
6868
},

0 commit comments

Comments
 (0)