当前位置: 首页 > news >正文

跨域 iframe 内剪切板 Clipboard_API 报错

跨域 iframe 内剪切板 Clipboard_API 报错

    • 复现问题
    • 解决问题
      • 权限的查询、变更
      • 控制 iframe 的权限
    • 嵌入式内容的继承策略
    • [allowlist 的特殊值](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Guides/Permissions_Policy#允许列表)
      • 解决方案
      • 内层的网页导航到其他的网站
      • 如果内层的网页又嵌套其他的 `iframe` 怎么办???问题未解决
    • 参考

复现问题

有两个网页:

  • 内层网页 http://localhost:8081/index.html 实现了简单的复制粘贴功能。
  • 外层网页 http://localhost:8080/index.html 也实现了相同的复制粘贴功能(内外层网页实现复制粘贴的代码是完全相同的)。同时又以 iframe 的形式嵌套了内层网页。外层和内层的端口号分别为 8080 和 8081,所以是跨域的

效果如下图:

控制台打印了初始的权限状态。

image-20250501154103715

内层 index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>内层</title>
</head>
<body>
<h1>内层</h1>
1 <input id="input1" type="text" value="内层"/>
<button onclick="copy()">copy</button>
<br/>
2 <input id="input2" type="text" value=""/>
<button onclick="paste()">paste</button></body>
<!-- 引入 js -->
<script type="text/javascript" src="index.js"></script>
</html>

外层 index.html

在上述代码的基础上添加了 iframe 标签:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>外层</title>
</head>
<body>
<h1>外层</h1>
1 <input id="input1" type="text" value="外层"/>
<button onclick="copy()">copy</button>
<br/>
2 <input id="input2" type="text" value=""/>
<button onclick="paste()">paste</button>
<br/><!-- iframe 8081 端口 -->
<iframe style="margin-top: 10px" width="500px" height="200px"src="http://localhost:8081/index.html"></iframe>
</body>
<!-- 引入 js -->
<script type="text/javascript" src="index.js"></script>
</html>

index.js

const title = document.title;/*** 查询权限** @param permissionName*/
function queryPermission(permissionName) {navigator.permissions.query({ name: permissionName }).then((permissionStatus) => {console.log(`${title} ${permissionName} 权限状态为 ${permissionStatus.state}`);permissionStatus.onchange = () => {console.log(`${title} ${permissionName} 权限状态已更改为 ${permissionStatus.state}`);};});
}// 查询权限 clipboard-read、clipboard-write
queryPermission("clipboard-read")
queryPermission("clipboard-write")/*** 将输入框 1 的文本复制到剪贴板*/
function copy() {navigator.clipboard.writeText(document.getElementById('input1').value).then(() => {alert('复制成功');});
}/*** 将剪贴板的文本复制到 2 输入框*/
function paste() {navigator.clipboard.readText().then((clipText) => {document.getElementById('input2').value = clipText;alert('粘贴成功');})
}

测试时,外层的 copy、paste 都没问题,内层的 copy、paste 都会报权限相关的错误:

image-20250501154126557

解决问题

权限的查询、变更

查阅 Clipboard - Web API | MDN 发现要读写剪切板,需要分别获取 clipboard-read 、clipboard-write 两个权限。可以使用 Permissions:query() 方法 来查询权限,即 index.js 中的 queryPermission 方法。PermissionStatus:state 属性 - Web API | MDN 中指出权限有三种状态:

  • 'granted'

    用户或代表用户的用户代理已明确许可使用此技术特性。调用者可以使用该特性,而无需用户代理请求用户的许可。

  • 'denied'

    用户或代表用户的用户代理已拒绝访问此技术特性。调用者无法使用该特性。

  • 'prompt'

    用户未明确授予使用该特性的权限(即与拒绝相同)。这还意味着,如果调用者尝试使用该特性,用户代理将提示用户授予许可,否则对该特性的访问将被拒绝。

  1. 上图中外层 clipboard-write 的初始状态为 granted,所以可以直接调用 clipboard.writeText() 方法,无需任何授权。

  2. 外层 clipboard-read 的初始状态为 prompt,在调用 clipboard.readText() 方法时会弹出询问框,要求用户授权,如下图。

image-20250501153825442

授权后,控制台打印出权限变更:

image-20250501153905950

  1. 而内层的两个权限状态都是 denied,所以调用 Clipboard 的任何方法都会报错。

控制 iframe 的权限

<iframe>:内嵌框架元素 中提到 iframe 的 allow 属性可以控制 iframe 的全系:

  • allow

    用于为 <iframe> 指定其权限策略。该策略根据请求的来源规定 <iframe> 可以使用哪些特性(例如,访问麦克风、摄像头、电池、web 共享等)。示例请参见 Permissions-Policy 中的 iframe ( iframe 这个链接需要看英文版,中文版的翻译缺少一些内容)。

    备注: 通过 allow 属性指定的权限策略会在 Permissions-Policy 标头指定的策略基础上进一步地限制。它不会替换原有策略。

权限策略 - iframe 语法 - HTTP | MDN 有相似的内容。

对于一个 <iframe> 来说,其允许的源也必须在父页面的允许列表中。由于这种继承行为,最好在 HTTP 头中指定最广泛的可接受的特性支持,然后在每个 <iframe> 中指定你需要的支持子集。

一般的语法看起来像这样:

<iframe src="<origin>" allow="<directive> <allowlist>"></iframe>

要允许所有源访问地理位置信息的访问,你可以这样做:

<iframe src="https://example.com" allow="geolocation *"></iframe>

例如,要阻止对地理位置信息的访问,你可以这样做:

<iframe src="https://example.com" allow="geolocation 'none'"></iframe>

要将一种策略运用到当前的源和其他的源中,你可以这样做:

<iframesrc="https://example.com"allow="geolocation 'self' https://a.example.com https://b.example.com"></iframe>

这很重要:默认情况下,如果一个 <iframe> 导航到另一个源,策略就不会应用到 <iframe> 导航到的源。通过在 allow 属性中列出 <iframe> 导航到的源,应用于原始 <iframe> 的许可策略将被应用于 <iframe> 导航到的源。

通过在 allow 属性中包含一个分号分隔的策略指令列表,可以同时控制多个特性。

<iframesrc="https://example.com"allow="geolocation 'self' https://a.example.com https://b.example.com; fullscreen 'none'"></iframe>

值得特别一提的是 src 值。我们在上面提到,使用这个 allowlist 值意味着相关的特性在这个 <iframe> 中是被允许的,只要加载到其中的文档与它的 src 属性中的 URL 来自同一来源。这个值是 allow 中所列特性的默认 allowlist 值,所以下面这些是等价的:

<iframe src="https://example.com" allow="geolocation 'src'"><iframe src="https://example.com" allow="geolocation"></iframe
></iframe>

备注: 你会注意到,<iframe> 策略的语法与 Permissions-Policy 标头的语法有些不同。前者仍然使用与旧的特性策略规范相同的语法,它被权限策略所取代。

嵌入式内容的继承策略

脚本会继承其浏览上下文的策略,而不管其源如何。这意味着顶层的脚本会继承主文件的策略。

所有 <iframe> 都继承其父页的策略。如果 <iframe> 有一个 allow 属性,并且父页面有一个 Permissions-Policy 标头,父页面和 allow 属性的策略将被合并,使用最严格的子集。对于一个 <iframe> 来说,要启用一种特性,其源必须是在父页和 allow 属性的允许列表中。

在一个策略中禁用一个特性是单向的切换。如果子框架被它的父框架禁用了一个特性,那么这个子框架就不能重新启用它,这个子框架的任何后代也不能。

allowlist 的特殊值

一个允许列表是一系列源的列表,它采取一个或多个包含在括号中的下列值,用空格隔开:

  • *:该特性将被允许在本文档和所有嵌套浏览上下文(<iframe>)中使用,无论其源如何。
  • ()(空允许列表):该特性在顶层和嵌套浏览环境中被禁用。等价的 <iframe>allow 属性值是 'none'
  • self:该特性只允许在本文档和同一来源的所有嵌套浏览环境(<iframe>)中使用。在嵌套浏览的情况下,该特性不允许出现在跨源文件中。self 可以被认为是 https://your-site.example.com 的简写。对应的 <iframe>allow 属性值是 self
  • src:只要载入该框架的文件与该框架 src 属性中的 URL 来源相同,该特性在该 <iframe> 中就被允许。这个值只用于 <iframe>allow 属性,并且是 <iframe>默认 allowlist 值。
  • "<origin>":该特性允许用于特定的源(如 "https://a.example.com")。源应该用空格隔开。请注意,<iframe> allow 属性中的源是不加引号的。

备注: 指令有一个默认的允许列表,对于 Permissions-Policy HTTP 标头来说,它总是 *'、selfnone 之一,如果它们没有明确地列在策略中,则管理默认行为。这些都是在各个指令参考页中指定的。对于 <iframe>allow 属性,默认行为总是 src

解决方案

有了上面的理论基础,问题便很好解决,只需将 iframeallow 属性设为 clipboard-read 'src'; clipboard-write 'src' 即可,或者简写为 clipboard-read; clipboard-write,因为 iframe 默认的 就是 src

<!-- iframe 8081 端口 -->
<iframe style="margin-top: 10px" width="500px" height="200px"allow="clipboard-read 'src'; clipboard-write 'src'"src="http://localhost:8081/index.html"></iframe>
<!--        allow="clipboard-read; clipboard-write"-->
</body>

内层功能正常:

image-20250501164712521

内层的网页导航到其他的网站

修改后的内层 index.html

添加了一个跳转内层 2 的 <a> 标签

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>内层</title>
</head>
<body>
<h1>内层</h1>
1 <input id="input1" type="text" value="内层"/>
<button onclick="copy()">copy</button>
<br/>
2 <input id="input2" type="text" value=""/>
<button onclick="paste()">paste</button><br/>
<!-- 跳转内层2 -->
<a href="http://localhost:8082/index.html">内层2</a>
<br/>
</body>
<!-- 引入 js -->
<script type="text/javascript" src="index.js"></script>
</html>

初始状态:

image-20250501183927651

点击 “内层 2” 链接跳转后:

image-20250501184022300

从上图可以看出,点击链接跳转后的内层 2 的权限都是 denied,无法调用剪切板。解决办法有多个:

  • 如果明确知道 iframe 中可能导航到的网站有哪些,或者只想让 iframe 中的某些网站有剪切板权限,可以将 allow 设为 clipboard-read http://localhost:8081 http://localhost:8082; clipboard-write http://localhost:8081 http://localhost:8082
  • 如果想让 iframe 中的所有网站都有剪切板权限,可以将 allow 设为 clipboard-read *; clipboard-write *

修改后的外层 index.html iframe

<!-- iframe 8081 端口 -->
<iframe style="margin-top: 10px" width="500px" height="200px"allow="clipboard-read http://localhost:8081 http://localhost:8082; clipboard-write http://localhost:8081 http://localhost:8082"src="http://localhost:8081/index.html"></iframe>
<!--        allow="clipboard-read *; clipboard-write *"-->

测试结果:

image-20250501184413550

如果内层的网页又嵌套其他的 iframe 怎么办???问题未解决

修改后的内层 index.html

添加了一个嵌套内层 2 的 <iframe> 标签

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>内层</title>
</head>
<body>
<h1>内层</h1>
1 <input id="input1" type="text" value="内层"/>
<button onclick="copy()">copy</button>
<br/>
2 <input id="input2" type="text" value=""/>
<button onclick="paste()">paste</button><br/>
<!-- 跳转内层2 -->
<a href="http://localhost:8082/index.html">内层2</a>
<br/>
<!-- iframe 8082 端口 -->
<iframe style="margin-top: 10px" width="500px" height="200px"src="http://localhost:8082/index.html"></iframe>
</body>
<!-- 引入 js -->
<script type="text/javascript" src="index.js"></script>
</html>

修改后的外层 index.html iframe

<!-- iframe 8081 端口 -->
<iframe style="margin-top: 10px" width="550px" height="400px"allow="clipboard-read *; clipboard-write *"src="http://localhost:8081/index.html"></iframe>

初始状态:

image-20250501170016141

外层 iframeallow 设为 clipboard-read *; clipboard-write * 也不能解决问题,难道只能逐层去设置的 iframeallow???

参考

  1. Clipboard - Web API | MDN

  2. Clipboard.writeText() - Web API | MDN

  3. Clipboard.readText() - Web API | MDN

  4. 权限 API - Web API | MDN

  5. 使用权限 API - Web API | MDN

  6. Permissions:query() 方法 - Web API | MDN

  7. PermissionStatus - Web API | MDN

  8. PermissionStatus:state 属性 - Web API | MDN

  9. <iframe>:内嵌框架元素

  10. Permissions-Policy - HTTP | MDN

  11. 权限策略 - iframe 语法 - HTTP | MDN

  12. 权限策略 - allowlist- HTTP | MDN

相关文章:

  • Google NotebookLM正式支持中文!AI笔记助手开启中文创作新纪元
  • Spark-小练试刀
  • cdn服务器连接异常怎么办?cdn连接失败解决方法有哪些?
  • Python10天突击--编译过程通常涉及几个关键步骤
  • REST API、FastAPI与Flask API的对比分析
  • 日志之ClickHouse部署及替换ELK中的Elasticsearch
  • PV操作:宣帧闯江湖武林客栈版学习笔记【操作系统】
  • 网络安全零基础培训 L1-9 PHP连接MySQL数据库
  • 【C++】extern
  • Android Kotlin 项目完整集成 Bugly 异常监控指南
  • RISC-V GPU架构研究进展:在深度学习推理场景的可行性验证
  • 数字智慧方案6172丨智慧医院扩建信息化整体规划方案(60页PPT)(文末有下载方式)
  • Mysql常用函数解析
  • 五下单元习作:《智慧的力量》——读《草船借箭》有感
  • Java 期中考试试题考点剖析
  • CSS响应式与自适应设计
  • PMP-第三章 项目经理的角色
  • zephyr架构下扫描和解析Beacon数据
  • 安川机器人常见故障报警及解决办法
  • react有哪些生命周期
  • 买书的网站排名/seo排名方案
  • 深圳网站建设php/公司seo是指什么意思
  • 房产网站系统源码/google网页版
  • 重庆平台网站建设多少钱/黄冈网站推广策略
  • 有自己网站做淘宝客赚钱吗/公众号排名优化软件
  • 成都专业网站建设公司排名/seo关键词软件