本周我需要测试js将数据存储到空间已满的localStorage中的表现,为此,我想找到一种使用 JavaScript 将 localStorage 空间填满的方法。
我们知道检测 localStorage 何时已满的唯一方法是在调用 localStorage.setItem
时捕获 QuotaExceededError
,我最初的想法是编写一个循环,逐字节增加 localStorage 大小直到它已满,例如:
try {
for (let i = 1; i < Infinity; i++) {
localStorage.setItem("__test__", "x".repeat(i));
}
} catch (err) {}
但是,逐字节写入并不是一个好的选择。它的效率非常低,很容易使浏览器崩溃。
因此,受这个 StackOverflow 答案的启发,我选择先存储较大的字符串,然后回退到较小的字符串,直到 localStorage 已满:
/**
* Fill localStorage to its maximum capacity.
*
* First, we fill localStorage in chunks of 100.000 characters until it
* hits the exceeded quota exception.
* Then, we do it again with chunks of 1.000 characters.
* Finally, we do it again character by character, to ensure localStorage is
* completely full.
*
* To cleanup localStorage you can use localStorage.clear(). Still, just in case
* you wanted to clean up only the data stored by this function (maybe because
* you want to keep in the localStorage the stuff you stored before running it),
* we return a convenient cleanup function.
*
* @return A cleanup function to delete the data we stored.
*/
function fillLocalStorage(): () => void {
function storeIncreasinglyBigItem(
key: string,
charactersIncrement: number
): void {
const MAX_ITERATIONS = 10_000; // Safeguard against OOM & crashes
for (let i = 1; i <= MAX_ITERATIONS; i++) {
localStorage.setItem(key, "x".repeat(i * charactersIncrement));
}
}
try {
storeIncreasinglyBigItem("__1", 100_000);
} catch (_err1) {
try {
storeIncreasinglyBigItem("__2", 1_000);
} catch (_err2) {
try {
storeIncreasinglyBigItem("__3", 1);
} catch (_err3) {
// Storage is now completely full 🍟
}
}
}
return function cleanup() {
localStorage.removeItem("__1");
localStorage.removeItem("__2");
localStorage.removeItem("__3");
};
}
请注意,这个解决方案有一个很小的边缘情况,其中
fillLocalStorage
没有 100% 填充 localStorage:由于浏览器包含确定 localStorage 大小的值和键,在极少数情况下,可能会报告存储空间已满但可能还剩下大约 3 个字节。
上面的片段肯定可以被优化,但效果还行,这就是我最初在这篇文章中建议的。到我在 Reddit 上分享了它,其中 u/kyle1320和/u/palparepa提出了一种更优雅的方法:先找到最高阶的位,然后以递减的方式测试每个位,直到localStorage满了为止。
这种方法比我上面建议的方法性能更高,因为它涉及更少的分配和迭代,它使用单个 localStorage 项,并且还解决了我刚才提到的边缘情况。
/**
* Fill localStorage to its maximum capacity.
*
* First, we find the highest order bit (the bit with the highest place value)
* that fits in localStorage by storing an increasingly big string
* of length 2, 4, 8, 16, etc. until it won't fill localStorage anymore.
*
* Then, we fill the remaining space by increasing the string length
* in the opposite order.
*
* By working in iterations, starting with very long strings, and storing data
* in different items, we can keep a low memory profile and reduce the number of
* writes — making this process pretty fast.
*
* To cleanup localStorage you can use localStorage.clear(). Still, just in case
* you wanted to clean up only the data stored by this function (maybe because
* you want to keep in the localStorage the stuff you stored before running it),
* we return a convenient cleanup function.
*
* @return A cleanup function to delete the data we stored.
*/
function fillLocalStorage(): () => void {
const key = "__filling_localstorage__";
let max = 1; // This holds the highest order bit.
let data = "x"; // The string we're going to store in localStorage.
// Find the highest order bit.
try {
while (true) {
data = data + data;
localStorage.setItem(key, data);
max <<= 1;
}
} catch {}
// Fill the rest of the space by increasing the string length in the opposite
// order.
for (let bit = max >> 1; bit > 0; bit >>= 1) {
try {
localStorage.setItem(key, data.substring(0, max | bit));
max |= bit;
} catch {
// Storage is now completely full 🍟
}
}
// Cleanup
return function cleanup() {
localStorage.removeItem(key);
};
}
// Fill localStorage.
const cleanupLocalStorage = fillLocalStorage();
// The localStorage is full now. Do whatever you need to.
doSomething();
// Finally, delete the dummy data we used to fill localStorage.
cleanupLocalStorage();
Puppeteer 和 Playwright 中的示例用法:
test.afterEach(async ({ page }) => {
await page.evaluate(() => window.localStorage.clear());
});
test("Do something when localStorage is full", async ({ page }) => {
// Fill localStorage.
// Notice that the page is running in a different JavaScript context, so
// we serialize the "fillLocalStorage" utility (it's OK, it's a pure function)
// and run an eval on the browser side.
await page.evaluate(`(${fillLocalStorage.toString()})()`);
// The localStorage is full now. Do whatever you need to.
doSomething();
});
我用 chrome 测试了下,他的 localStorage 空间是5M。
[1]
StackOverflow 答案: https://stackoverflow.com/a/45760532[2]
u/kyle1320: https://old.reddit.com/user/kyle1320[3]
/u/palparepa: https://old.reddit.com/user/palparepa[4]
Puppeteer: https://github.com/puppeteer/puppeteer[5]
Playwright: https://github.com/microsoft/playwright