Skip to content

Commit

Permalink
Merge pull request #1375 from jeffwcx/master
Browse files Browse the repository at this point in the history
feat: [#825] Attribute iframe.sandbox should return a DOMTokenList
  • Loading branch information
capricorn86 committed Apr 3, 2024
2 parents f9d4343 + 662964d commit 75041b2
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 8 deletions.
1 change: 1 addition & 0 deletions packages/happy-dom/src/PropertySymbol.ts
Expand Up @@ -158,3 +158,4 @@ export const navigator = Symbol('navigator');
export const screen = Symbol('screen');
export const sessionStorage = Symbol('sessionStorage');
export const localStorage = Symbol('localStorage');
export const sandbox = Symbol('sandbox');
Expand Up @@ -9,6 +9,7 @@ import HTMLIFrameElementNamedNodeMap from './HTMLIFrameElementNamedNodeMap.js';
import CrossOriginBrowserWindow from '../../window/CrossOriginBrowserWindow.js';
import IBrowserFrame from '../../browser/types/IBrowserFrame.js';
import HTMLIFrameElementPageLoader from './HTMLIFrameElementPageLoader.js';
import DOMTokenList from '../../dom-token-list/DOMTokenList.js';

/**
* HTML Iframe Element.
Expand All @@ -23,7 +24,7 @@ export default class HTMLIFrameElement extends HTMLElement {

// Internal properties
public override [PropertySymbol.attributes]: NamedNodeMap;

public [PropertySymbol.sandbox]: DOMTokenList = null;
// Private properties
#contentWindowContainer: { window: BrowserWindow | CrossOriginBrowserWindow | null } = {
window: null
Expand Down Expand Up @@ -140,14 +141,15 @@ export default class HTMLIFrameElement extends HTMLElement {
*
* @returns Sandbox.
*/
public get sandbox(): string {
return this.getAttribute('sandbox') || '';
public get sandbox(): DOMTokenList {
if (!this[PropertySymbol.sandbox]) {
this[PropertySymbol.sandbox] = new DOMTokenList(this, 'sandbox');
}
return <DOMTokenList>this[PropertySymbol.sandbox];
}

/**
* Sets sandbox.
*
* @param sandbox Sandbox.
*/
public set sandbox(sandbox: string) {
this.setAttribute('sandbox', sandbox);
Expand All @@ -163,7 +165,7 @@ export default class HTMLIFrameElement extends HTMLElement {
}

/**
* Sets sandbox.
* Sets srcdoc.
*
* @param srcdoc Srcdoc.
*/
Expand Down
Expand Up @@ -3,6 +3,23 @@ import Element from '../element/Element.js';
import HTMLElementNamedNodeMap from '../html-element/HTMLElementNamedNodeMap.js';
import HTMLIFrameElementPageLoader from './HTMLIFrameElementPageLoader.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import DOMTokenList from '../../dom-token-list/DOMTokenList.js';

const SANDBOX_FLAGS = [
'allow-downloads',
'allow-forms',
'allow-modals',
'allow-orientation-lock',
'allow-pointer-lock',
'allow-popups',
'allow-popups-to-escape-sandbox',
'allow-presentation',
'allow-same-origin',
'allow-scripts',
'allow-top-navigation',
'allow-top-navigation-by-user-activation',
'allow-top-navigation-to-custom-protocols'
];

/**
* Named Node Map.
Expand Down Expand Up @@ -37,6 +54,49 @@ export default class HTMLIFrameElementNamedNodeMap extends HTMLElementNamedNodeM
this.#pageLoader.loadPage();
}

if (item[PropertySymbol.name] === 'sandbox') {
if (!this[PropertySymbol.ownerElement][PropertySymbol.sandbox]) {
this[PropertySymbol.ownerElement][PropertySymbol.sandbox] = new DOMTokenList(
this[PropertySymbol.ownerElement],
'sandbox'
);
} else {
this[PropertySymbol.ownerElement][PropertySymbol.sandbox][PropertySymbol.updateIndices]();
}

this.#validateSandboxFlags();
}

return replacedAttribute || null;
}

/**
*
* @param tokens
* @param vconsole
*/
#validateSandboxFlags(): void {
const window =
this[PropertySymbol.ownerElement][PropertySymbol.ownerDocument][PropertySymbol.ownerWindow];
const values = this[PropertySymbol.ownerElement][PropertySymbol.sandbox].values();
const invalidFlags: string[] = [];

for (const token of values) {
if (!SANDBOX_FLAGS.includes(token)) {
invalidFlags.push(token);
}
}

if (invalidFlags.length === 1) {
window.console.error(
`Error while parsing the 'sandbox' attribute: '${invalidFlags[0]}' is an invalid sandbox flag.`
);
} else if (invalidFlags.length > 1) {
window.console.error(
`Error while parsing the 'sandbox' attribute: '${invalidFlags.join(
`', '`
)}' are invalid sandbox flags.`
);
}
}
}
Expand Up @@ -85,6 +85,7 @@ describe('DOMTokenList', () => {
it('Sets the attribute value.', () => {
classList.value = 'class1 class2 class3';
expect(element.className).toBe('class1 class2 class3');
expect(classList[2]).toBe('class3');
});
});

Expand Down
@@ -1,6 +1,5 @@
import Window from '../../../src/window/Window.js';
import BrowserWindow from '../../../src/window/BrowserWindow.js';
import Window from '../../../src/window/Window.js';
import Document from '../../../src/nodes/document/Document.js';
import HTMLIFrameElement from '../../../src/nodes/html-iframe-element/HTMLIFrameElement.js';
import Response from '../../../src/fetch/Response.js';
Expand All @@ -13,6 +12,7 @@ import { beforeEach, describe, it, expect, vi, afterEach } from 'vitest';
import IRequestInfo from '../../../src/fetch/types/IRequestInfo.js';
import Headers from '../../../src/fetch/Headers.js';
import Browser from '../../../src/browser/Browser.js';
import DOMTokenList from '../../../src/dom-token-list/DOMTokenList.js';

describe('HTMLIFrameElement', () => {
let window: Window;
Expand All @@ -35,7 +35,7 @@ describe('HTMLIFrameElement', () => {
});
});

for (const property of ['src', 'allow', 'height', 'width', 'name', 'sandbox', 'srcdoc']) {
for (const property of ['src', 'allow', 'height', 'width', 'name', 'srcdoc']) {
describe(`get ${property}()`, () => {
it(`Returns the "${property}" attribute.`, () => {
element.setAttribute(property, 'value');
Expand All @@ -51,6 +51,81 @@ describe('HTMLIFrameElement', () => {
});
}

describe('get sandbox()', () => {
it('Returns DOMTokenList', () => {
expect(element.sandbox).toBeInstanceOf(DOMTokenList);
element.sandbox.add('allow-forms', 'allow-scripts');
expect(element.sandbox.toString()).toBe('allow-forms allow-scripts');
});

it('Returns values from attribute', () => {
element.setAttribute('sandbox', 'allow-forms allow-scripts');
expect(element.sandbox.toString()).toBe('allow-forms allow-scripts');
});
});

describe('set sandbox()', () => {
it('Sets attribute', () => {
element.sandbox = 'allow-forms allow-scripts';
expect(element.getAttribute('sandbox')).toBe('allow-forms allow-scripts');

element.sandbox =
'allow-downloads allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts allow-top-navigation allow-top-navigation-by-user-activation allow-top-navigation-to-custom-protocols';
expect(element.sandbox.toString()).toBe(
'allow-downloads allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts allow-top-navigation allow-top-navigation-by-user-activation allow-top-navigation-to-custom-protocols'
);
});

it('Updates the DOMTokenList indicies when setting the sandbox attribute', () => {
element.sandbox = 'allow-forms allow-scripts';
expect(element.sandbox.length).toBe(2);
expect(element.sandbox[0]).toBe('allow-forms');
expect(element.sandbox[1]).toBe('allow-scripts');

element.sandbox = 'allow-scripts allow-forms';
expect(element.sandbox.length).toBe(2);
expect(element.sandbox[0]).toBe('allow-scripts');
expect(element.sandbox[1]).toBe('allow-forms');

element.sandbox = 'allow-forms';
expect(element.sandbox.length).toBe(1);
expect(element.sandbox[0]).toBe('allow-forms');
expect(element.sandbox[1]).toBe(undefined);

element.sandbox = '';

expect(element.sandbox.length).toBe(0);
expect(element.sandbox[0]).toBe(undefined);

element.sandbox = 'allow-forms allow-scripts allow-forms';
expect(element.sandbox.length).toBe(2);
expect(element.sandbox[0]).toBe('allow-forms');
expect(element.sandbox[1]).toBe('allow-scripts');

element.sandbox = 'allow-forms allow-scripts allow-modals';
expect(element.sandbox.length).toBe(3);
expect(element.sandbox[0]).toBe('allow-forms');
expect(element.sandbox[1]).toBe('allow-scripts');
expect(element.sandbox[2]).toBe('allow-modals');
});

it('Console error occurs when add an invalid sandbox flag', () => {
element.sandbox = 'invalid';
expect(window.happyDOM.virtualConsolePrinter.readAsString()).toBe(
`Error while parsing the 'sandbox' attribute: 'invalid' is an invalid sandbox flag.\n`
);
expect(element.sandbox.toString()).toBe('invalid');
expect(element.getAttribute('sandbox')).toBe('invalid');

element.setAttribute('sandbox', 'first-invalid second-invalid');
expect(window.happyDOM.virtualConsolePrinter.readAsString()).toBe(
`Error while parsing the 'sandbox' attribute: 'first-invalid', 'second-invalid' are invalid sandbox flags.\n`
);
expect(element.sandbox.toString()).toBe('first-invalid second-invalid');
expect(element.getAttribute('sandbox')).toBe('first-invalid second-invalid');
});
});

describe('get contentWindow()', () => {
it('Returns content window for "about:blank".', () => {
element.src = 'about:blank';
Expand Down

0 comments on commit 75041b2

Please sign in to comment.