You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

同源下Safari中COOP与window.open无法正常工作问题排查

COOP设置为same-origin时,Safari同源弹窗出现跨源行为问题

我将Cross-Origin-Opener-Policy(COOP)设置为same-origin,在同源环境下(当前页面为https://example.com/here)通过用户触发事件调用window.open打开弹窗:

// 在用户触发的事件中
const childWindow = window.open('https://example.com/there', 'child-window')

Chrome和Firefox中表现符合预期:父窗口能获取子窗口的location和closed状态,子窗口也能访问window.opener。

但桌面端Safari中行为却和跨源场景一致:

childWindow.closed // true

弹窗内执行:

window.opener // null

将COOP改为same-origin-allow-popups也无法解决问题。

这是预期行为?还是我理解有误?或是Safari的特殊实现?

补充说明:
将COOP设为unsafe-none能解决问题,但这显然不是合理方案,对吗?


系统信息

  • macOS 12.4
  • Safari 15.5 (17613.2.7.1.8)

响应头信息

Summary
URL: https://example.com/here
Status: 200
Source: Service Worker

Request
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15

Response
Content-Type: text/html; charset=utf-8
Content-Security-Policy: upgrade-in-secure-requests;default-src 'self';script-src 'self' 'unsafe-eval' 'sha256-<hereIsAHash>' 'sha256-<hereIsAnotherHash>';child-src 'self';connect-src 'self' https://example.com wss://example.com;font-src 'self' data:;form-action 'self';frame-ancestors 'self';frame-src *;img-src 'self' data: blob:;manifest-src 'self';media-src 'self';object-src 'self';sandbox allow-same-origin allow-downloads allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-scripts allow-forms allow-modals allow-presentation;style-src 'self' 'unsafe-inline';worker-src 'self' blob:;base-uri 'self';script-src-attr 'none';upgrade-insecure-requests
X-XSS-Protection: 0
Cross-Origin-Resource-Policy: cross-origin
Content-Encoding: gzip
Referrer-Policy: no-referrer
Date: Fri, 18 Nov 2022 08:46:17 GMT
Cross-Origin-Opener-Policy: same-origin
X-DNS-Prefetch-Control: off
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Vary: Accept-Encoding
Server: nginx/1.21.6
x-download-options: noopen
x-permitted-cross-domain-policies: none
expect-ct: max-age=604800, enforce
Strict-Transport-Security: max-age=15552000; includeSubDomains, max-age=31536000
origin-agent-cluster: ?1

Helmet配置(版本:helmet@6.0.0)

const self = '\'self\''
const data = 'data:'
const blob = 'blob:'
const unsafeEval = '\'unsafe-eval\''
const unsafeInline = '\'unsafe-inline\''
const connectSrc = ['https://example.com', 'wss://example.com']
const externalHostUrls = ['https://foobar.com']

export const helmetOptions = {
    crossOriginEmbedderPolicy: false,
    crossOriginOpenerPolicy: { policy: 'same-origin' },
    crossOriginResourcePolicy: { policy: 'cross-origin' },
    contentSecurityPolicy: {
      blockAllMixedContent: true,
      directives: {
        upgradeInSecureRequests: [],
        defaultSrc: [self],
        scriptSrc: [
          self,
          // 我们使用哈希允许动态导入时的unsafeEval
          unsafeEval,
          `'sha256-${hashes[0]}'`,
          `'sha256-${hashes[1]}'`
        ],
        childSrc: [self],
        connectSrc: connectSrc.concat(externalHostUrls),
        fontSrc: [self, data],
        formAction: [self],
        frameAncestors: [self],
        frameSrc: ['*'],
        imgSrc: [self, data, blob].concat(externalHostUrls),
        manifestSrc: [self],
        mediaSrc: [self],
        objectSrc: [self],
        sandbox: [
          'allow-same-origin',
          'allow-downloads',
          'allow-orientation-lock',
          'allow-pointer-lock',
          'allow-popups',
          'allow-popups-to-escape-sandbox',
          'allow-scripts',
          'allow-forms',
          'allow-modals',
          'allow-presentation'
        ],
        styleSrc: [self, unsafeInline],
        workerSrc: [self, blob]
      }
    },
    strictTransportSecurity: {
      maxAge: 15552000,
      includeSubDomains: true,
      preload: false
    },
    referrerPolicy: {
      policy: 'no-referrer'
    },
    expectCt: {
      enforce: true,
      maxAge: 604800
    },
    frameguard: {
      action: 'sameorigin'
    },
    dnsPrefetchControl: {
      allow: false
    },
    permittedCrossDomainPolicies: {
      permittedPolicies: 'none'
    },
    hidePoweredBy: true
  }
}

问题原因与解决方案

这是Safari 15.x版本对COOP策略的特殊实现bug,并非你的配置错误或理解偏差。

核心原因

Safari在处理same-origin或same-origin-allow-popups的COOP策略时,会错误地将同源弹窗放入独立的浏览上下文组,导致父窗口与子窗口无法互相引用,表现出跨源隔离的行为。

可行解决方案

  1. 升级Safari版本:该问题在Safari 16及以上版本中已被修复,升级后同源弹窗的COOP策略行为会与Chrome、Firefox保持一致。
  2. 临时兼容方案:若无法强制用户升级,可通过以下方式兼容:
    • 通过User-Agent判断Safari 15.x用户,临时将COOP设置为unsafe-none(仅作为过渡方案,注意该配置会取消跨源隔离保护,存在安全风险);
    • 使用postMessage替代直接的窗口引用交互,这种方式不受COOP隔离影响,即使窗口被隔离也能正常通信。

关于unsafe-none的安全性

unsafe-none会关闭页面的跨源隔离保护,使页面暴露在跨源窗口劫持等风险中,确实不是长期合理方案,仅适合作为旧版Safari的临时兼容手段,优先推荐升级浏览器版本。

内容的提问来源于stack exchange,提问作者Jankapunkt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 17:10:24