Skip to content

Commit 11a79fb

Browse files
committed
fix: Avoid autoreloading by Chrome-generated files
1 parent 4df33e9 commit 11a79fb

2 files changed

Lines changed: 117 additions & 3 deletions

File tree

src/watcher.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@ export default function onSourceChange({
2525
const ignored =
2626
watchIgnored && process.platform === 'win32'
2727
? watchIgnored.map((it) => it.replace(/\\/g, '/'))
28-
: watchIgnored;
28+
: (watchIgnored || []).slice(0);
29+
30+
// Chrome may write to the loaded extension directory:
31+
// https://github.com/mozilla/web-ext/issues/3468
32+
// Ignore these to avoid permanent auto-reload.
33+
// Although these files are only expected at the top level, we ignore them
34+
// globally in case a subdirectory is also loaded as a Chrome extension.
35+
ignored.push('**/_metadata');
36+
ignored.push('**/Cached Theme.pak');
2937

3038
// TODO: For network disks, we would need to add {poll: true}.
31-
const watcher = ignored ? new Watchpack({ ignored }) : new Watchpack();
39+
const watcher = new Watchpack({ ignored });
3240

3341
// Allow multiple files to be changed before reloading the extension
3442
const executeImmediately = false;

tests/unit/test.watcher.js

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ import { withTempDir } from '../../src/util/temp-dir.js';
1414
import { makeSureItFails } from './helpers.js';
1515

1616
describe('watcher', () => {
17-
const watchChange = ({ watchFile, touchedFile } = {}) =>
17+
const watchChange = ({ prepTempDir, watchFile, touchedFile } = {}) =>
1818
withTempDir(async (tmpDir) => {
1919
const artifactsDir = path.join(tmpDir.path(), 'web-ext-artifacts');
2020
const someFile = path.join(tmpDir.path(), touchedFile);
21+
if (prepTempDir) {
22+
await prepTempDir(tmpDir);
23+
}
2124

2225
if (watchFile) {
2326
watchFile = watchFile.map((f) => path.join(tmpDir.path(), f));
@@ -237,4 +240,107 @@ describe('watcher', () => {
237240
await waitDebounce();
238241
}));
239242
});
243+
244+
describe('watcher ignores _metadata', () => {
245+
// _metadata should be ignored to avoid reload loop, see:
246+
// https://github.com/mozilla/web-ext/issues/3468
247+
248+
// _metadata/generated_indexed_rulesets/_ruleset1 (original test case)
249+
let promiseTouchedMetadataDirContent;
250+
// Immediate child of _metadata without subdirectory.
251+
let promiseTouchedMetadataDirWithoutSub;
252+
// _metadata is also ignored when nested, not just at the top.
253+
let promiseTouchedMetadataDirNested;
254+
// _metadata is a directory, but the implementation also ignores files...
255+
let promiseTouchedMetadataFile;
256+
// Check behavior of --watch-file=_metadata
257+
let promiseTouchedMetadataFileWithWatchFile;
258+
// Chrome may also write "Cached Theme.pak" (outside _metadata directory).
259+
let promiseTouchedTheme;
260+
261+
it('ignores change to _metadata directory content (setup)', () => {
262+
// Simulates scenario from https://github.com/mozilla/web-ext/issues/3468
263+
promiseTouchedMetadataDirContent = watchChange({
264+
prepTempDir: async (tmpDir) => {
265+
const metadataDir = path.join(tmpDir.path(), '_metadata');
266+
await fs.mkdir(metadataDir);
267+
await fs.mkdir(path.join(metadataDir, 'generated_indexed_rulesets'));
268+
},
269+
touchedFile: path.join(
270+
'_metadata',
271+
'generated_indexed_rulesets',
272+
'_ruleset1',
273+
),
274+
});
275+
});
276+
277+
it('ignores change to _metadata directory without subdirectory (setup)', () => {
278+
// Simulates scenario from https://github.com/mozilla/web-ext/issues/3468
279+
promiseTouchedMetadataDirWithoutSub = watchChange({
280+
prepTempDir: async (tmpDir) => {
281+
await fs.mkdir(path.join(tmpDir.path(), '_metadata'));
282+
},
283+
touchedFile: path.join('_metadata', 'somefile'),
284+
});
285+
});
286+
287+
it('ignores change to non-toplevel _metadata directory (setup)', () => {
288+
promiseTouchedMetadataDirNested = watchChange({
289+
prepTempDir: async (tmpDir) => {
290+
const parentDir = path.join(tmpDir.path(), 'parent');
291+
await fs.mkdir(parentDir);
292+
await fs.mkdir(path.join(parentDir, '_metadata'));
293+
},
294+
touchedFile: path.join('parent', '_metadata', 'somefile'),
295+
});
296+
});
297+
298+
it('ignores change to _metadata file (setup)', () => {
299+
promiseTouchedMetadataFile = watchChange({ touchedFile: '_metadata' });
300+
});
301+
302+
it('ignores change to _metadata file despite --watch-file (setup)', () => {
303+
promiseTouchedMetadataFileWithWatchFile = watchChange({
304+
watchFile: ['_metadata'],
305+
touchedFile: '_metadata',
306+
});
307+
});
308+
309+
it('igmores change to Cached Theme.pak (setup)', () => {
310+
// When a theme is loaded in Chrome, it writes to "Cached Theme.pak" in
311+
// the source directory, which would result in permanent auto-reload
312+
// unless we disabled auto reload.
313+
promiseTouchedTheme = watchChange({ touchedFile: 'Cached Theme.pak' });
314+
});
315+
316+
it('ignores change to _metadata directory content (await)', async () => {
317+
const { onChange } = await promiseTouchedMetadataDirContent;
318+
sinon.assert.notCalled(onChange);
319+
});
320+
321+
it('ignores change to _metadata directory without subdirectory (await)', async () => {
322+
const { onChange } = await promiseTouchedMetadataDirWithoutSub;
323+
sinon.assert.notCalled(onChange);
324+
});
325+
326+
it('ignores change to non-toplevel _metadata directory (await)', async () => {
327+
const { onChange } = await promiseTouchedMetadataDirNested;
328+
sinon.assert.notCalled(onChange);
329+
});
330+
331+
it('ignores change to _metadata file (await)', async () => {
332+
const { onChange } = await promiseTouchedMetadataFile;
333+
sinon.assert.notCalled(onChange);
334+
});
335+
336+
it('ignores change to _metadata file despite --watch-file (await)', async () => {
337+
const { onChange } = await promiseTouchedMetadataFileWithWatchFile;
338+
sinon.assert.notCalled(onChange);
339+
});
340+
341+
it('igmores change to Cached Theme.pak (await)', async () => {
342+
const { onChange } = await promiseTouchedTheme;
343+
sinon.assert.notCalled(onChange);
344+
});
345+
});
240346
});

0 commit comments

Comments
 (0)