From fa6002965a53f94d05080fddfb03291be74e2b6c Mon Sep 17 00:00:00 2001 From: Pavel Smirnov Date: Mon, 19 Aug 2024 21:31:35 -0400 Subject: [PATCH] Access the top window in a safe manner (#550) Fixes Uncaught DOMException: Failed to read a named property 'addEventListener' from 'Window': Blocked a frame with origin "xxx" from accessing a cross-origin frame. --- packages/miew/demo/scripts/ui/Menu.js | 2 +- packages/miew/src/Miew.js | 6 ++-- packages/miew/src/ui/ObjectControls.js | 3 +- packages/miew/src/utils/getTopWindow.js | 11 +++++++ packages/miew/src/utils/getTopWindow.test.js | 31 ++++++++++++++++++++ 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 packages/miew/src/utils/getTopWindow.js create mode 100644 packages/miew/src/utils/getTopWindow.test.js diff --git a/packages/miew/demo/scripts/ui/Menu.js b/packages/miew/demo/scripts/ui/Menu.js index 40ffaf65c..158a91a4b 100644 --- a/packages/miew/demo/scripts/ui/Menu.js +++ b/packages/miew/demo/scripts/ui/Menu.js @@ -2039,7 +2039,7 @@ Menu.prototype._onTerminalOff = function () { }; Menu.prototype._fixKeyboard = function () { - // do IFRAME related hack + // do IFRAME related hack // NOTE: embedding the demo is not recommended/supported anymore if (window !== window.top) { const parentDocument = window.top.document; let button = parentDocument.querySelector('button'); diff --git a/packages/miew/src/Miew.js b/packages/miew/src/Miew.js index 57e44ffa5..014cf47c8 100644 --- a/packages/miew/src/Miew.js +++ b/packages/miew/src/Miew.js @@ -43,6 +43,7 @@ import capabilities from './gfx/capabilities'; import WebVRPoC from './gfx/vr/WebVRPoC'; import vertexScreenQuadShader from './gfx/shaders/ScreenQuad.vert'; import fragmentScreenQuadFromDistTex from './gfx/shaders/ScreenQuadFromDistortionTex.frag'; +import getTopWindow from './utils/getTopWindow'; const { selectors, @@ -298,11 +299,12 @@ Miew.prototype.init = function () { zIndex: 700, }); - window.top.addEventListener('keydown', (event) => { + const target = getTopWindow(); + target.addEventListener('keydown', (event) => { self._onKeyDown(event); }); - window.top.addEventListener('keyup', (event) => { + target.addEventListener('keyup', (event) => { self._onKeyUp(event); }); diff --git a/packages/miew/src/ui/ObjectControls.js b/packages/miew/src/ui/ObjectControls.js index 3a366a3de..2afaca49a 100644 --- a/packages/miew/src/ui/ObjectControls.js +++ b/packages/miew/src/ui/ObjectControls.js @@ -2,6 +2,7 @@ import * as THREE from 'three'; import Timer from '../Timer'; import settings from '../settings'; import EventDispatcher from '../utils/EventDispatcher'; +import getTopWindow from '../utils/getTopWindow'; const VK_LEFT = 37; const VK_UP = 38; @@ -806,7 +807,7 @@ ObjectControls.prototype.keydownup = function (event) { }; ObjectControls.prototype.getKeyBindObject = function () { - return window.top; + return getTopWindow(); }; ObjectControls.prototype.dispose = function () { diff --git a/packages/miew/src/utils/getTopWindow.js b/packages/miew/src/utils/getTopWindow.js new file mode 100644 index 000000000..ae0a3ecad --- /dev/null +++ b/packages/miew/src/utils/getTopWindow.js @@ -0,0 +1,11 @@ +export default function getTopWindow() { + // intercept the exception if we have cross-origin iframe + try { + if (window.top.location.href !== undefined) { + return window.top; + } + } catch (e) { + // provide fallback + } + return window; +} diff --git a/packages/miew/src/utils/getTopWindow.test.js b/packages/miew/src/utils/getTopWindow.test.js new file mode 100644 index 000000000..d9a8dbf21 --- /dev/null +++ b/packages/miew/src/utils/getTopWindow.test.js @@ -0,0 +1,31 @@ +import { expect } from 'chai'; +import getTopWindow from './getTopWindow'; + +describe('utils/getTopWindow()', () => { + it('returns top window if no iframe', () => { + const window = { location: { href: 'http://example.com' } }; + window.top = window; + + global.window = window; + expect(getTopWindow()).to.equal(window.top); + delete global.window; + }); + + it('returns top window if no cross-origin iframe', () => { + const window = { location: { href: 'http://example.com/viewer.html' } }; + window.top = { location: { href: 'http://example.com/index.html' } }; + + global.window = window; + expect(getTopWindow()).to.equal(window.top); + delete global.window; + }); + + it('returns window if called inside cross-origin iframe', () => { + const window = { location: { href: 'http://example.com:8000' } }; + window.top = { locationRestricted: { href: 'http://example.com:8001' } }; + + global.window = window; + expect(getTopWindow()).to.equal(window); + delete global.window; + }); +});