Frame 与 Window

弹窗与 window 方法

弹窗

弹窗自古以来就存在。最初的想法是,在不关闭主窗口的情况下显示其他内容。(也可以通过 fetch 动态请求并将其渲染在动态生成的 div 中也具有同样效果)

弹窗语法

window.open(url, name, params)
  • url:要在新窗口中加载的 URL。
  • name:新窗口的名称,每个窗口都有一个全局的 window.name
    • 当给定的 name 已存在窗口,则在已存在窗口打开新标签页。
    • 不存在则打开新窗口打开标签页。
  • params:新窗口的配置字符串。

    对于配置不合理的参数,浏览器会自行修复:

    • 窗口参数会使用默认值 no
    • 没有 width/height,那么新窗口的大小将与上次打开的窗口大小相同。
    • 没有 left/top,那么浏览器会尝试在最后打开的窗口附近打开一个新窗口。
设置项类型说明限制/注意
位置
left数字窗口左上角的 X 坐标不能将窗口置于屏幕外
top数字窗口左上角的 Y 坐标不能将窗口置于屏幕外
width数字窗口宽度有最小值限制,不可创建不可见窗口
height数字窗口高度有最小值限制,不可创建不可见窗口
窗口功能
menubaryes/no显示/隐藏浏览器菜单栏
toolbaryes/no显示/隐藏导航栏(后退、前进、刷新等)
locationyes/no显示/隐藏 URL 地址栏Firefox 和 IE 不允许默认隐藏
statusyes/no显示/隐藏状态栏大多数浏览器强制显示
resizableyes/no允许/禁止调整窗口大小不建议使用
scrollbarsyes/no允许/禁止显示滚动条不建议使用

更多内容:MDN window.open

移动端限制:移动设备无法同时显示多个窗口,但仍有部分场景在使用(如 OAuth 授权)。

弹窗优势

  • 是一个独立的窗口,具有自己的独立 JavaScript 环境。可以放心打开第三方链接。
  • 打开弹窗非常容易。
  • 弹窗可以导航(修改 URL),并将消息发送到 opener 窗口(即打开弹窗的窗口)。

⚠️ 安全限制:非用户手动触发的弹窗将会被浏览器拦截,并询问用户的同意/拒绝。

// ❌ 弹窗被阻止
window.open('https://javascript.info');

// ✅ 弹窗被允许
button.onclick = () => {
  window.open('https://javascript.info');
};

延迟限制

  • Firefox 中接受 2000ms 及以内的延迟允许打开弹窗,超过则默认不允许(用户同意后即可打开)。
  • Chrome 则 3000ms 也能够打开。

弹窗与 opener 窗口双向访问

只有在窗口是同源的时,窗口才能自由访问彼此的内容。两个不同站点是不能访问彼此内容的。

opener 窗口访问弹窗

window.open 方法会返回对新窗口的引用:

let newWin = window.open("about:blank", "hello", "width=200,height=200");

newWin.document.write("Hello, world!");

弹窗访问 opener 窗口

弹窗使用 window.opener 来访问 opener 窗口。除了弹窗之外,对其他所有窗口来说,window.opener 均为 null

let newWin = window.open("about:blank", "hello", "width=200,height=200");

newWin.document.write(
  "<script>window.opener.document.body.innerHTML = 'Test'<\/script>"
);

关闭弹窗

如果 window 不是通过 window.open() 创建的,那么大多数浏览器都会忽略 window.close()。因此,close() 只对弹窗起作用

关闭窗口语法

// 只对弹窗起作用
win.close()

检测窗口关闭状态

win.closed // true 表示已关闭

移动和调整大小

  • 为了防止滥用,浏览器通常会阻止这些方法。仅在我们打开的、没有其他选项卡的弹窗中能够可靠地工作。
  • JavaScript 无法最小化或者最大化一个窗口。
  • win.moveBy(x, y)
    相对于当前位置移动窗口:向右 x 像素,向下 y 像素。允许负值(向左/向上移动)。

    win.moveBy(100, 50);   // 向右 100px,向下 50px
    win.moveBy(-50, -30);  // 向左 50px,向上 30px
    
  • win.moveTo(x, y)
    将窗口移动到屏幕绝对坐标 (x, y) 处。

    win.moveTo(100, 100); // 移动到 (100, 100)
    win.moveTo(0, 0);     // 移动到左上角
    
  • win.resizeBy(width, height)
    相对于当前大小调整窗口:宽度增加 width,高度增加 height。允许负值(缩小)。

    win.resizeBy(200, 100);  // 增大
    win.resizeBy(-100, -50); // 缩小
    
  • win.resizeTo(width, height)
    将窗口调整为指定的绝对大小。

    win.resizeTo(800, 600);
    
  • window.onresize
    窗口大小改变时触发的事件处理器。

    window.addEventListener('resize', () => {
      console.log(`新尺寸: ${window.innerWidth}x${window.innerHeight}`);
    });
    

滚动窗口

  • win.scrollBy(x, y)
    相对于当前滚动位置滚动。

    window.scrollBy(0, 300); // 向下滚动 300px
    window.scrollBy({ top: 300, behavior: 'smooth' }); // 平滑滚动
    
  • win.scrollTo(x, y)
    滚动到文档中的绝对坐标。

    window.scrollTo(0, 0); // 回到顶部
    
  • elem.scrollIntoView(alignToTop)
    滚动使元素可见。

    element.scrollIntoView({
      behavior: 'smooth',
      block: 'center'
    });
    
  • window.onscroll
    滚动时触发(建议防抖优化)。

    window.addEventListener('scroll', debounce(() => {
      console.log('滚动中...');
    }, 100));
    

弹窗聚焦/失焦

  • window.focus()
    尝试使窗口获得焦点(将窗口置于最前)。

    ⚠️ 受浏览器严格限制:移动端忽略、需用户交互后执行、选项卡式弹窗无效。

  • window.blur()
    使窗口失去焦点。

    ❌ 几乎被所有浏览器禁用(防止恶意后台锁定)。

  • window.onfocus / window.onblur
    窗口获得/失去焦点时触发。

    window.addEventListener('focus', () => resume());
    window.addEventListener('blur', () => pause());
    
  • document.hasFocus()
    检查当前文档是否具有焦点。

  • document.visibilityState & visibilitychange(✅ 推荐)
    更可靠的页面可见性检测(能识别选项卡切换):

    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'visible') {
        resumeAnimations();
      } else {
        pauseAnimations();
      }
    });
    

实际应用场景

场景推荐方案
用户活跃度跟踪visibilitychange
视频/动画控制document.visibilityState
实时通信状态onfocus + onblur

浏览器限制总结

方法限制原因
focus()防止强制弹窗劫持
blur()防止强制后台运行
自动聚焦弹窗拦截器保护用户

跨窗口通信

同源策略

两个 URL 具有相同的协议、域名、端口,则称它们是“同源”的。

非同源示例

  • http://www.site.com(子域不同)
  • http://site.org(顶级域不同)
  • https://site.com(协议不同)
  • http://site.com:8080(端口不同)

对弹窗的权限

  • 同源:opener 对弹窗具有全部权限(读写 DOM、调用方法等)。
  • 非同源:仅允许修改 location.href,但不可读取 location 内容。
let newWin = window.open('https://azusatea.top');
console.log(newWin.location); // Location about:blank(无法读取真实 URL)
setTimeout(() => {
  newWin.location.href = 'https://fanyi.baidu.com/'; // ✅ 允许修改
}, 1000);

iframe

<iframe> 具有属于自己的 windowdocument

  • iframe.contentWindow → 获取 iframe 的 window
  • iframe.contentDocument → 获取 iframe 的 document(等价于 iframe.contentWindow.document

同源规则同样适用

  • 同源:可完全访问和修改内容。
  • 非同源:拒绝访问(location 例外,只允许修改)。

注意:创建 iframe 后,它立即拥有一个空文档,但不同于最终加载的文档。应使用 iframe.onload 确保文档加载完成后再操作。

<iframe src="/" id="iframe"></iframe>
<script>
  iframe.onload = () => {
    console.log("iframe 文档已加载");
    iframe.contentDocument.body.style.background = "lightblue";
  };
</script>

iframe 集合

通过 window.frames 访问:

  • window.frames[0]:第一个 iframe 的 window
  • window.frames.iframeNamename="iframeName" 的 iframe 的 window
<iframe src="/" name="win" id="iframe"></iframe>
<script>
  console.log(iframe.contentWindow === frames[0]); // true
  console.log(iframe.contentWindow === frames.win); // true
</script>

iframe 层级关系

  • window.frames:子窗口集合(嵌套 iframe)
  • window.parent:父窗口引用
  • window.top:最顶级窗口引用

    window === top 可判断当前是否在 iframe 中

沙盒 iframe

通过 sandbox 属性严格限制 iframe 能力:

属性值作用默认
allow-same-origin允许同源(移除跨域限制)
allow-top-navigation允许修改 parent.location
allow-forms允许提交表单
allow-scripts允许执行 JS
allow-popups允许 window.open
sandbox(空)完全禁止一切
<!-- 最安全:仅静态展示 -->
<iframe src="ad.html" sandbox></iframe>

<!-- 可信组件:允许脚本 + 同源 -->
<iframe src="widget.html" sandbox="allow-scripts allow-same- origin"></iframe>

⚠️ 如果 iframe 来自其他源,即使设置 allow-same-origin 也无法放宽同源策略。

跨窗口通信:postMessage

发送方

targetWindow.postMessage(data, targetOrigin);
  • data:要发送的数据(支持对象,IE 需 JSON.stringify
  • targetOrigin:目标源(如 'https://example.com'),'*' 表示不限制(敏感数据慎用
let win = iframe.contentWindow;
win.postMessage({ user: 'John' }, 'https://trusted.com');

接收方

window.addEventListener('message', (event) => {
  // 1. 验证来源(安全必需!)
  if (event.origin !== 'https://trusted.com') return;
  
  // 2. 处理数据
  console.log('收到:', event.data);
  
  // 3. 回传消息
  event.source.postMessage('OK', event.origin);
});

安全要点

措施说明
✅ 验证 event.origin防止伪造消息
✅ 指定 targetOrigin避免使用 *
✅ 验证 event.data防注入攻击
✅ 使用 event.source 回传确保回复正确窗口

点击劫持攻击(Clickjacking)

原理

  1. 放置一个无害链接。
  2. 在其上方用 z-index 覆盖一个透明 iframesrc 指向银行/社交网站。
  3. 用户点击“无害链接”,实际点击了 iframe 中的“删除账户”按钮。

仅针对点击事件,键盘事件无效。

防御措施

1. 阻止顶级导航(旧方法)

window.onbeforeunload = () => false;

用户点击 iframe 时会弹出确认框,通常会选择“取消”,从而阻止跳转。
已过时,现代浏览器限制此行为。

2. 沙箱 iframe(开发者控制)

<iframe sandbox="allow-scripts allow-forms" src="..."></iframe>

未设置 allow-top-navigation,因此 iframe 无法修改 top.location

3. X-Frame-Options(服务端控制)

HTTP 响应头,三种值:

  • DENY:禁止任何 frame 嵌入
  • SAMEORIGIN:仅允许同源 frame 嵌入
  • ALLOW-FROM uri:仅允许指定域嵌入(已废弃
X-Frame-Options: DENY

4. 显示禁用(前端防护)

用全屏 div 覆盖页面,仅当 window === top 时移除:

<div id="protector">请直接访问本网站</div>
<script>
  if (window === top) {
    protector.remove();
  }
</script>

5. SameSite Cookie(服务端防护)

Set-Cookie: auth=secret; SameSite=Lax

通过 iframe 加载的页面不会携带 SameSite Cookie,从而无法执行敏感操作。

总结

防御层级措施
服务端X-Frame-OptionsContent-Security-Policy: frame-ancestorsSameSite Cookie
前端检测 window !== top 并遮罩、沙箱 iframe
用户保持警惕,避免点击可疑链接

最佳实践:服务端设置 X-Frame-Options: DENYCSP frame-ancestors 'self',前端辅以遮罩检测。