瞬态用户激活行为记录

某个场景下用到了showSaveFilePicker这个API,但是发现这个API时灵时不灵,尤其是涉及到异步任务的时候。特此测试并记录一下该API某些特性。

前因

项目里有个大文件下载场景,出于安全考虑后端要求必须要加authorization请求头,所以直接a标签下载的路子行不通了,只能先下载再保存了。但是涉及到大文件的时候用户的老旧机器,内存总是不够用报错,所以又引入了showSaveFilePicker这个API,配合fetchgetReaderAPI解决这个痛点。虽然兼容性差了点,但用户也配合,总比功能用不了强。

本来功能在本地测的好好的,但是上线之后有些用户反馈报错。一通检查之后发现,相同的代码,如果后端接口返回时间太长,就会触发浏览器的安全类型报错Error selecting file: SecurityError: Failed to execute 'showOpenFilePicker' on 'Window': Must be handling a user gesture to show a file picker.,大意就是为了安全着想,这个API必须由用户发起。

测试

  • html部分

    1
    2
    3
    4
    <button id="btn1">正常选择文件(会自动触发一次)</button>
    <button id="btn2">用户点击4ms后选择文件(then)</button>
    <button id="btn3">用户点击4885ms后选择文件(await)</button>
    <button id="btn4">用户点击5000ms后选择文件(await)</button>
  • js部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // 延迟
    const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));

    // 主体事件
    const handleOpenPicker = async () => {
    try {
    const [fileHandle] = await window.showOpenFilePicker();
    const file = await fileHandle.getFile();
    console.log('Selected file:', file.name);
    } catch (error) {
    console.error('Error selecting file:', error);
    }
    };

    // 事件绑定
    document.getElementById('btn1').addEventListener('click', async () => {
    await handleOpenPicker();
    });

    document.getElementById('btn2').addEventListener('click', () => {
    sleep(4).then(() => {
    handleOpenPicker();
    });
    });

    document.getElementById('btn3').addEventListener('click', async () => {
    await sleep(4000);
    await handleOpenPicker();
    });

    document.getElementById('btn4').addEventListener('click', async () => {
    await sleep(8000);
    await handleOpenPicker();
    });

    // 自动触发一次
    window.addEventListener('load', async () => {
    await handleOpenPicker();
    });
  • 页面加载完以后,四个按钮挨个点击一遍,测试结果如下图
    示例图片

收获

File System Access这几个API,除了测试环境localhost意外,必须https协议的环境才能触发。另外,出于安全性考虑,瞬态用户激活是必需的。用户必须与页面或 UI 元素进行交互才能使该特性正常运行。如果激活已被触发,则用户代理会区分两种类型的用户激活窗口状态:粘性(sticky)和瞬态(transient)。瞬态激活只持续很短时间(5s往上,不固定),并且在某些情况下,当使用受保护的功能时可能会被消耗(停用),而粘性激活会持续到会话结束。

参考文档

FileSystem - Web API | MDN
Window:showSaveFilePicker() 方法 - Web API | MDN
Features gated by user activation - Security | MDN