Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File chooser dialog can only be shown with a user activation #32

Open
lostvita opened this issue Apr 13, 2023 · 2 comments
Open

File chooser dialog can only be shown with a user activation #32

lostvita opened this issue Apr 13, 2023 · 2 comments

Comments

@lostvita
Copy link
Owner

lostvita commented Apr 13, 2023

日前,做需求过程碰到了这么一个问题:File chooser dialog can only be shown with a user activation,从描述看是在没有用户激活页面的情况下尝试去调起文件选择器,被浏览器拒绝了。
image

需求背景:页面加载时,通过代码触发file inputclick事件,达到调起文件选择器的目的。大致如下:

const autoClick = () => {
    const input = document.createElement('input')
    input.type = 'file'
    input.accept = '.gif,.jpg,.jpeg,.png,.bmp'
    input.onchange = (e) => {
        console.log('>>', e.target.files)
    }
    input.click()
}

setTimeout(autoClick, 0)

image

猜想这是浏览器考虑安全因素做的限制,确实也是。试想一下用户打开一个未知网页,不断调起文件选择器(或者新建窗口操作),用户的设备将直接被卡死。

但还是想探究验证一下浏览器内核是如何做限制的。chromium源码位置

void FileInputType::HandleDOMActivateEvent(Event& event) {
  // ...

  if (!LocalFrame::HasTransientUserActivation(document.GetFrame())) {
    String message =
        "File chooser dialog can only be shown with a user activation.";
    document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
        mojom::ConsoleMessageSource::kJavaScript,
        mojom::ConsoleMessageLevel::kWarning, message));
    return;
  }
 // ...
}

可以看出,浏览器在响应dom事件时会先判断是否有短暂的用户激活行为。但,短暂是多久?

bool HasTransientUserActivation() const {
    return user_activation_state_.IsActive();
}

浏览器用user_activation_state_维护了用户激活的状态,其中IsActive最终是通过IsActiveInternal实现的,里面维护了一个transient_state_expiry_time_变量,这就是浏览器用来标识短暂的用户激活的变量。

bool UserActivationState::IsActiveInternal() const {
  return base::TimeTicks::Now() <= transient_state_expiry_time_;
}

void UserActivationState::ActivateTransientState() {
  transient_state_expiry_time_ = base::TimeTicks::Now() + kActivationLifespan;
}

constexpr base::TimeDelta kActivationLifespan = base::Seconds(5);

浏览器每收到用户的交互行为时(单纯地移动鼠标可不算,浏览器应该维护了一套属于用户激活行为的事件白名单)ActivateTransientState,这里会更新激活态的过期时间,在kActivationLifespan时间后就会过期,也就是5s

搞清楚了原理,我们用demo来验证一下!

const autoClick = () => {
    console.log('>> autoClick')
    const input = document.createElement('input')
    input.type = 'file'
    input.accept = '.gif,.jpg,.jpeg,.png,.bmp'
    input.onchange = (e) => {
        console.log('>>', e.target)
    }
    input.click()
}

// 脚本加载时,3s后自动触发`input click`
setTimeout(autoClick, 3000)
  1. 纯加载网页,无用户激活行为,期望不调起:
    file-choose-1

  2. 加载后,用户在3s内点击网页,期望调起:
    file-choose-2

  3. 加载并且用户点击网页,5s后执行autoClick,期望不调起:
    file-choose-3

总结一下:在用户激活页面(点击,选中等)的5s内,自动执行的脚本有响应,否则浏览器无响应。

@zxh-994
Copy link

zxh-994 commented Apr 24, 2024

Is there any way to bypass user activation verification?

1 similar comment
@zxh-994
Copy link

zxh-994 commented Apr 24, 2024

Is there any way to bypass user activation verification?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants