Search code examples
javascriptwebapi

Why postMessage fails on FileSystemFileHandle?


I have index.html:

const openButton = document.querySelector('#open'),
  pickerOpts = {
    types: [{
      accept: {
        "text/*": [".txt"],
      },
    }, ],
    excludeAcceptAllOption: true,
    multiple: false,
  }

openButton.addEventListener('click', async() => {
  const handle = await window.showOpenFilePicker(pickerOpts),
    obj = {
      a: 'a'
    },
    tab = window.open('tab.html')
  setTimeout(() => {
    // not work, the tab.html receives nothing
    tab.postMessage(handle, '*')
    // works fine
    // tab.postMessage(obj, '*')
  }, 1000)
})
<button id="open">open</button>

and tab.html:

window.addEventListener('message', (e) => {
  console.log(e.data)
})

The postMessage works fine for general JavaScript objects but fails to send a FileSystemFileHandle. How should I fix this?

The structured clone algorithm - Web APIs | MDN indicates that FileSystemFileHandle should be supported.


Solution

  • FileSystemFileHandle objects can indeed be cloned through the structured cloning algorithm used by postMessage(), however, if your two documents are not on the same origin, this operation should throw a DataCloneError exception on the receiving end when it will try to deserialize the object.

    Note that two documents on the file:// system will be seen as cross-origin.

    To handle such an error, you should be able to add a messageerror event on the receiving end, however this is the case only in Firefox (when using navigator.storage.getDirectory()), but not in Chrome.
    In that browser, there is thus nothing that tells us that it failed, apart from the fact that data will be null. But this is against the specs, so may be considered a bug.

    But anyway, you can know that it will throw if you can't access the window you're posting to, so that would help you only a little.
    Instead, what you will probably have to do is to set up a proxy API where your main page will request the FileSystem, and then let the popup navigate it through calls to postMessage. My advice would be to set up a unique MessageChannel to this effect, so that you can promisify the communication.

    However rewriting the whole API is not a small project, so you may to instead start only with the few methods you're sure you'll be using.