tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

FileSystemObserver.js (13122B)


      1 'use strict';
      2 
      3 // This script depends on the following scripts:
      4 //    resources/test-helpers.js
      5 //    resources/collecting-file-system-observer.js
      6 //    resources/change-observer-scope-test.js
      7 //    script-tests/FileSystemObserver-writable-file-stream.js
      8 
      9 promise_test(async t => {
     10  try {
     11    const observer = new FileSystemObserver(() => {});
     12  } catch {
     13    assert_unreached();
     14  }
     15 }, 'Creating a FileSystemObserver from a supported global succeeds');
     16 
     17 directory_test(async (t, root_dir) => {
     18  const observer = new FileSystemObserver(() => {});
     19  try {
     20    observer.unobserve(root_dir);
     21  } catch {
     22    assert_unreached();
     23  }
     24 }, 'Calling unobserve() without a corresponding observe() shouldn\'t throw');
     25 
     26 directory_test(async (t, root_dir) => {
     27  const observer = new FileSystemObserver(() => {});
     28  try {
     29    observer.unobserve(root_dir);
     30    observer.unobserve(root_dir);
     31  } catch {
     32    assert_unreached();
     33  }
     34 }, 'unobserve() is idempotent');
     35 
     36 promise_test(async t => {
     37  const observer = new FileSystemObserver(() => {});
     38  try {
     39    observer.disconnect();
     40  } catch {
     41    assert_unreached();
     42  }
     43 }, 'Calling disconnect() without observing shouldn\'t throw');
     44 
     45 promise_test(async t => {
     46  const observer = new FileSystemObserver(() => {});
     47  try {
     48    observer.disconnect();
     49    observer.disconnect();
     50  } catch {
     51    assert_unreached();
     52  }
     53 }, 'disconnect() is idempotent');
     54 
     55 directory_test(async (t, root_dir) => {
     56  const observer = new FileSystemObserver(() => {});
     57 
     58  // Create a `FileSystemFileHandle` and delete its underlying file entry.
     59  const file = await root_dir.getFileHandle(getUniqueName(), {create: true});
     60  await file.remove();
     61 
     62  await promise_rejects_dom(t, 'NotFoundError', observer.observe(file));
     63 }, 'observe() fails when file does not exist');
     64 
     65 directory_test(async (t, root_dir) => {
     66  const observer = new FileSystemObserver(() => {});
     67 
     68  // Create a `FileSystemDirectoryHandle` and delete its underlying file entry.
     69  const dir =
     70      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
     71  await dir.remove();
     72 
     73  await promise_rejects_dom(t, 'NotFoundError', observer.observe(dir));
     74 }, 'observe() fails when directory does not exist');
     75 
     76 directory_test(async (t, root_dir) => {
     77  const dir =
     78      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
     79 
     80  const scope_test = new ScopeTest(t, dir);
     81  const watched_handle = await scope_test.watched_handle();
     82 
     83  for (const recursive of [false, true]) {
     84    for await (const path of scope_test.in_scope_paths(recursive)) {
     85      const observer = new CollectingFileSystemObserver(t, root_dir);
     86      await observer.observe([watched_handle], {recursive});
     87 
     88      // Create `file`.
     89      const file = await path.createHandle();
     90 
     91      // Expect one "appeared" event to happen on `file`.
     92      const records = await observer.getRecords();
     93      await assert_records_equal(
     94          watched_handle, records,
     95          [appearedEvent(file, path.relativePathComponents())]);
     96 
     97      observer.disconnect();
     98    }
     99  }
    100 }, 'Creating a file through FileSystemDirectoryHandle.getFileHandle is reported as an "appeared" event if in scope');
    101 
    102 directory_test(async (t, root_dir) => {
    103  const dir =
    104      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
    105 
    106  const scope_test = new ScopeTest(t, dir);
    107  const watched_handle = await scope_test.watched_handle();
    108 
    109  for (const recursive of [false, true]) {
    110    for await (const path of scope_test.in_scope_paths(recursive)) {
    111      const file = await path.createHandle();
    112 
    113      const observer = new CollectingFileSystemObserver(t, root_dir);
    114      await observer.observe([watched_handle], {recursive});
    115 
    116      // Remove `file`.
    117      await file.remove();
    118 
    119      // Expect one "disappeared" event to happen on `file`.
    120      const records = await observer.getRecords();
    121      await assert_records_equal(
    122          watched_handle, records,
    123          [disappearedEvent(path.relativePathComponents())]);
    124 
    125      observer.disconnect();
    126    }
    127  }
    128 }, 'Removing a file through FileSystemFileHandle.remove is reported as an "disappeared" event if in scope');
    129 
    130 directory_test(async (t, root_dir) => {
    131  const dir =
    132      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
    133 
    134  const scope_test = new ScopeTest(t, dir);
    135  const watched_handle = await scope_test.watched_handle();
    136 
    137  for (const recursive of [false, true]) {
    138    for await (const path of scope_test.out_of_scope_paths(recursive)) {
    139      const observer = new CollectingFileSystemObserver(t, root_dir);
    140      await observer.observe([watched_handle], {recursive});
    141 
    142      // Create and remove `file`.
    143      const file = await path.createHandle();
    144      await file.remove();
    145 
    146      // Expect the observer to receive no events.
    147      const records = await observer.getRecords();
    148      await assert_records_equal(watched_handle, records, []);
    149 
    150      observer.disconnect();
    151    }
    152  }
    153 }, 'Events outside the watch scope are not sent to the observer\'s callback');
    154 
    155 directory_test(async (t, root_dir) => {
    156  const dir =
    157      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
    158 
    159  const scope_test = new ScopeTest(t, dir);
    160  const watched_handle = await scope_test.watched_handle();
    161 
    162  for (const recursive of [false, true]) {
    163    for await (const src of scope_test.in_scope_paths(recursive)) {
    164      for await (const dest of scope_test.in_scope_paths(recursive)) {
    165        const file = await src.createHandle();
    166 
    167        const observer = new CollectingFileSystemObserver(t, root_dir);
    168        await observer.observe([watched_handle], {recursive});
    169 
    170        // Move `file`.
    171        await file.move(dest.parentHandle(), dest.fileName());
    172 
    173        // Expect one "moved" event to happen on `file`.
    174        const records = await observer.getRecords();
    175        await assert_records_equal(
    176            watched_handle, records, [movedEvent(
    177                                         file, dest.relativePathComponents(),
    178                                         src.relativePathComponents())]);
    179 
    180        observer.disconnect();
    181      }
    182    }
    183  }
    184 }, 'Moving a file through FileSystemFileHandle.move is reported as a "moved" event if destination and source are in scope');
    185 
    186 directory_test(async (t, root_dir) => {
    187  const dir =
    188      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
    189 
    190  const scope_test = new ScopeTest(t, dir);
    191  const watched_handle = await scope_test.watched_handle();
    192 
    193  for (const recursive of [false, true]) {
    194    for await (const src of scope_test.out_of_scope_paths(recursive)) {
    195      for await (const dest of scope_test.out_of_scope_paths(recursive)) {
    196        const file = await src.createHandle();
    197 
    198        const observer = new CollectingFileSystemObserver(t, root_dir);
    199        await observer.observe([watched_handle], {recursive});
    200 
    201        // Move `file`.
    202        await file.move(dest.parentHandle(), dest.fileName());
    203 
    204        // Expect the observer to not receive any events.
    205        const records = await observer.getRecords();
    206        await assert_records_equal(watched_handle, records, []);
    207      }
    208    }
    209  }
    210 }, 'Moving a file through FileSystemFileHandle.move is not reported if destination and source are not in scope');
    211 
    212 directory_test(async (t, root_dir) => {
    213  const dir =
    214      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
    215 
    216  const scope_test = new ScopeTest(t, dir);
    217  const watched_handle = await scope_test.watched_handle();
    218 
    219  for (const recursive of [false, true]) {
    220    for await (const src of scope_test.out_of_scope_paths(recursive)) {
    221      for await (const dest of scope_test.in_scope_paths(recursive)) {
    222        const file = await src.createHandle();
    223 
    224        const observer = new CollectingFileSystemObserver(t, root_dir);
    225        await observer.observe([watched_handle], {recursive});
    226 
    227        // Move `file`.
    228        await file.move(dest.parentHandle(), dest.fileName());
    229 
    230        // Expect one "appeared" event to happen on `file`.
    231        const records = await observer.getRecords();
    232        await assert_records_equal(
    233            watched_handle, records,
    234            [appearedEvent(file, dest.relativePathComponents())]);
    235      }
    236    }
    237  }
    238 }, 'Moving a file through FileSystemFileHandle.move is reported as a "appeared" event if only destination is in scope');
    239 
    240 directory_test(async (t, root_dir) => {
    241  const dir =
    242      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
    243 
    244  const scope_test = new ScopeTest(t, dir);
    245  const watched_handle = await scope_test.watched_handle();
    246 
    247  for (const recursive of [false, true]) {
    248    for await (const src of scope_test.in_scope_paths(recursive)) {
    249      for await (const dest of scope_test.out_of_scope_paths(recursive)) {
    250        // These both point to the same underlying file entry initially until
    251        // move is called on `fileToMove`. `file` is kept so that we have a
    252        // handle that still points at the source file entry.
    253        const file = await src.createHandle();
    254        const fileToMove = await src.createHandle();
    255 
    256        const observer = new CollectingFileSystemObserver(t, root_dir);
    257        await observer.observe([watched_handle], {recursive});
    258 
    259        // Move `fileToMove`.
    260        await fileToMove.move(dest.parentHandle(), dest.fileName());
    261 
    262        // Expect one "disappeared" event to happen on `file`.
    263        const records = await observer.getRecords();
    264        await assert_records_equal(
    265            watched_handle, records,
    266            [disappearedEvent(src.relativePathComponents())]);
    267      }
    268    }
    269  }
    270 }, 'Moving a file through FileSystemFileHandle.move is reported as a "disappeared" event if only source is in scope');
    271 
    272 // Wraps a `CollectingFileSystemObserver` and disconnects the observer after it's
    273 // received `num_of_records_to_observe`.
    274 class DisconnectingFileSystemObserver {
    275  #collectingObserver;
    276 
    277  #num_of_records_to_observe;
    278 
    279  #called_disconnect = false;
    280  #records_observed_count = 0;
    281 
    282  constructor(test, root_dir, num_of_records_to_observe) {
    283    this.#collectingObserver = new CollectingFileSystemObserver(
    284        test, root_dir, this.#callback.bind(this));
    285    this.#num_of_records_to_observe = num_of_records_to_observe;
    286  }
    287 
    288  #callback(records, observer) {
    289    this.#records_observed_count += records.length;
    290 
    291    const called_disconnect = this.#called_disconnect;
    292 
    293    // Call `disconnect` once after we've received `num_of_records_to_observe`.
    294    if (!called_disconnect &&
    295        this.#records_observed_count >= this.#num_of_records_to_observe) {
    296      observer.disconnect();
    297      this.#called_disconnect = true;
    298    }
    299 
    300    return {called_disconnect};
    301  }
    302 
    303  getRecordsWithCallbackInfo() {
    304    return this.#collectingObserver.getRecordsWithCallbackInfo();
    305  }
    306 
    307  observe(handles) {
    308    return this.#collectingObserver.observe(handles);
    309  }
    310 }
    311 
    312 
    313 directory_test(async (t, root_dir) => {
    314  const total_files_to_create = 100;
    315 
    316  const child_dir =
    317      await root_dir.getDirectoryHandle(getUniqueName(), {create: true});
    318 
    319  // Create a `FileSystemObserver` that will disconnect after its
    320  // received half of the total files we're going to create.
    321  const observer = new DisconnectingFileSystemObserver(
    322      t, root_dir, total_files_to_create / 2);
    323 
    324  // Observe the child directory and create files in it.
    325  await observer.observe([child_dir]);
    326  for (let i = 0; i < total_files_to_create; i++) {
    327    child_dir.getFileHandle(`file${i}`, {create: true});
    328  }
    329 
    330  // Wait for `disconnect` to be called.
    331  const records_with_disconnect_state =
    332      await observer.getRecordsWithCallbackInfo();
    333 
    334  // No observations should have been received after disconnected has been
    335  // called.
    336  assert_false(
    337      records_with_disconnect_state.some(
    338          ({called_disconnect}) => called_disconnect),
    339      'Received records after disconnect.');
    340 }, 'Observations stop after disconnect()');
    341 
    342 directory_test(async (t, root_dir) => {
    343  const num_of_child_dirs = 5;
    344  const num_files_to_create_per_directory = 100;
    345  const total_files_to_create =
    346      num_files_to_create_per_directory * num_of_child_dirs;
    347 
    348  const child_dirs = await createDirectoryHandles(
    349      root_dir, getUniqueName(), getUniqueName(), getUniqueName());
    350 
    351  // Create a `FileSystemObserver` that will disconnect after its received half
    352  // of the total files we're going to create.
    353  const observer = new DisconnectingFileSystemObserver(
    354      t, root_dir, total_files_to_create / 2);
    355 
    356  // Observe the child directories and create files in them.
    357  await observer.observe(child_dirs);
    358  for (let i = 0; i < num_files_to_create_per_directory; i++) {
    359    child_dirs.forEach(
    360        child_dir => child_dir.getFileHandle(`file${i}`, {create: true}));
    361  }
    362 
    363  // Wait for `disconnect` to be called.
    364  const records_with_disconnect_state =
    365      await observer.getRecordsWithCallbackInfo();
    366 
    367  // No observations should have been received after disconnected has been
    368  // called.
    369  assert_false(
    370      records_with_disconnect_state.some(
    371          ({called_disconnect}) => called_disconnect),
    372      'Received records after disconnect.');
    373 }, 'Observations stop for all observed handles after disconnect()');