From da66e195ff59959b5886fc2dc85827573647bbce Mon Sep 17 00:00:00 2001 From: Trevor Karjanis <2351292+TrevorKarjanis@users.noreply.github.com> Date: Mon, 14 Feb 2022 18:13:07 -0500 Subject: [PATCH] Add support for HTMLElement.removeAttributeNode. --- src/client/sandbox/native-methods.ts | 6 ++ src/client/sandbox/node/element.ts | 65 ++++++++++++++++--- .../fixtures/sandbox/node/element-test.js | 2 + .../fixtures/sandbox/node/methods-test.js | 40 ++++++++++++ 4 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/client/sandbox/native-methods.ts b/src/client/sandbox/native-methods.ts index a563330a75..d3db46cb60 100644 --- a/src/client/sandbox/native-methods.ts +++ b/src/client/sandbox/native-methods.ts @@ -60,6 +60,8 @@ class NativeMethods { elementQuerySelectorAll: any; getAttribute: any; getAttributeNS: any; + getAttributeNode: any; + getAttributeNodeNS: any; insertBefore: Node['insertBefore']; insertCell: any; insertTableRow: any; @@ -67,6 +69,7 @@ class NativeMethods { importScripts: (...urls: string[]) => void removeAttribute: any; removeAttributeNS: any; + removeAttributeNode: any; removeChild: Node['removeChild']; remove: Element['remove']; elementReplaceWith: Element['replaceWith']; @@ -537,12 +540,15 @@ class NativeMethods { this.elementQuerySelectorAll = nativeElement.querySelectorAll; this.getAttribute = nativeElement.getAttribute; this.getAttributeNS = nativeElement.getAttributeNS; + this.getAttributeNode = nativeElement.getAttributeNode; + this.getAttributeNodeNS = nativeElement.getAttributeNodeNS; this.insertBefore = nativeElement.insertBefore; this.insertCell = createElement('tr').insertCell; this.insertTableRow = createElement('table').insertRow; this.insertTBodyRow = createElement('tbody').insertRow; this.removeAttribute = nativeElement.removeAttribute; this.removeAttributeNS = nativeElement.removeAttributeNS; + this.removeAttributeNode = nativeElement.removeAttributeNode; this.removeChild = win.Node.prototype.removeChild; this.remove = win.Element.prototype.remove; this.elementReplaceWith = win.Element.prototype.replaceWith; diff --git a/src/client/sandbox/node/element.ts b/src/client/sandbox/node/element.ts index e71b39dfd9..4c8f7634d9 100644 --- a/src/client/sandbox/node/element.ts +++ b/src/client/sandbox/node/element.ts @@ -35,6 +35,11 @@ import { getDestinationUrl } from '../../utils/url'; const RESTRICTED_META_HTTP_EQUIV_VALUES = [BUILTIN_HEADERS.refresh, BUILTIN_HEADERS.contentSecurityPolicy]; +enum IsNsNode { + Ns, + Node +} + export default class ElementSandbox extends SandboxBase { overriddenMethods: any; @@ -377,10 +382,35 @@ export default class ElementSandbox extends SandboxBase { return hasAttrMeth.apply(el, args); } - removeAttributeCore (el: HTMLElement, args, isNs?: boolean) { - const attr = String(args[isNs ? 1 : 0]); + private static _removeStoredAttrNode (node: Node & { name: string, namespaceURI: string }) { + let storedNode: Node; + const storedAttr = DomProcessor.getStoredAttrName(node.name); + if (node.namespaceURI) + storedNode = nativeMethods.getAttributeNodeNS.call(this, node.namespaceURI, storedAttr); + else + storedNode = nativeMethods.getAttributeNode.call(this, storedAttr); + + if (storedNode) + nativeMethods.removeAttributeNode.call(this, storedNode); + } + + removeAttributeCore (el: HTMLElement, args, isNsNode?: IsNsNode) { + let attr: string; + let node: Node & { name: string, namespaceURI: string }; + let removeStoredAttrFunc: Function; + const isNs = isNsNode === IsNsNode.Ns; + const isNode = isNsNode === IsNsNode.Node; + if (isNode) { + node = args[0]; + attr = node.name; + removeStoredAttrFunc = ElementSandbox._removeStoredAttrNode; + } + else { + attr = String(args[isNs ? 1 : 0]); + removeStoredAttrFunc = isNs ? nativeMethods.removeAttributeNS : nativeMethods.removeAttribute; + } + const formatedAttr = attr.toLowerCase(); - const removeAttrFunc = isNs ? nativeMethods.removeAttributeNS : nativeMethods.removeAttribute; const tagName = domUtils.getTagName(el); let result = void 0; @@ -394,17 +424,17 @@ export default class ElementSandbox extends SandboxBase { if (formatedAttr === 'autocomplete') nativeMethods.setAttribute.call(el, storedAttr, domProcessor.AUTOCOMPLETE_ATTRIBUTE_ABSENCE_MARKER); else - removeAttrFunc.apply(el, isNs ? [args[0], storedAttr] : [storedAttr]); + removeStoredAttrFunc.apply(el, isNs ? [args[0], storedAttr] : [isNode ? node : storedAttr]); } else if (!isNs && formatedAttr === 'rel' && tagName === 'link') { const storedRelAttr = DomProcessor.getStoredAttrName(attr); - removeAttrFunc.apply(el, [storedRelAttr]); + removeStoredAttrFunc.apply(el, [isNode ? node : storedRelAttr]); } else if (!isNs && formatedAttr === 'required' && domUtils.isFileInput(el)) { const storedRequiredAttr = DomProcessor.getStoredAttrName(attr); - removeAttrFunc.call(el, storedRequiredAttr); + removeStoredAttrFunc.call(el, isNode ? node : storedRequiredAttr); } else if (!isNs && formatedAttr === 'type' && domUtils.isInputElement(el)) { const storedRequiredAttr = DomProcessor.getStoredAttrName('required'); @@ -420,8 +450,16 @@ export default class ElementSandbox extends SandboxBase { if (ElementSandbox._isHrefAttrForBaseElement(el, formatedAttr)) urlResolver.updateBase(getDestLocation(), this.document); - if (formatedAttr !== 'autocomplete') - result = removeAttrFunc.apply(el, args); + if (formatedAttr !== 'autocomplete') { + if (isNode) { + const original = nativeMethods.getAttributeNodeNS.call(el, node.namespaceURI, node.name); + result = nativeMethods.removeAttributeNode.apply(el, [original].concat(Array.from(args).slice(1))); + } + else { + const removeAttrFunc = isNs ? nativeMethods.removeAttributeNS : nativeMethods.removeAttribute; + result = removeAttrFunc.apply(el, args); + } + } if (formatedAttr === 'target' && DomProcessor.isTagWithTargetAttr(tagName) || formatedAttr === 'formtarget' && DomProcessor.isTagWithFormTargetAttr(tagName)) @@ -734,7 +772,15 @@ export default class ElementSandbox extends SandboxBase { }, removeAttributeNS () { - const result = sandbox.removeAttributeCore(this, arguments, true); + const result = sandbox.removeAttributeCore(this, arguments, IsNsNode.Ns); + + refreshAttributesWrapper(this); + + return result; + }, + + removeAttributeNode () { + const result = sandbox.removeAttributeCore(this, arguments, IsNsNode.Node); refreshAttributesWrapper(this); @@ -948,6 +994,7 @@ export default class ElementSandbox extends SandboxBase { overrideFunction(window.Element.prototype, 'getAttributeNS', this.overriddenMethods.getAttributeNS); overrideFunction(window.Element.prototype, 'removeAttribute', this.overriddenMethods.removeAttribute); overrideFunction(window.Element.prototype, 'removeAttributeNS', this.overriddenMethods.removeAttributeNS); + overrideFunction(window.Element.prototype, 'removeAttributeNode', this.overriddenMethods.removeAttributeNode); overrideFunction(window.Element.prototype, 'cloneNode', this.overriddenMethods.cloneNode); overrideFunction(window.Element.prototype, 'querySelector', this.overriddenMethods.querySelector); overrideFunction(window.Element.prototype, 'querySelectorAll', this.overriddenMethods.querySelectorAll); diff --git a/test/client/fixtures/sandbox/node/element-test.js b/test/client/fixtures/sandbox/node/element-test.js index 3cb1510015..0e11e43e1e 100644 --- a/test/client/fixtures/sandbox/node/element-test.js +++ b/test/client/fixtures/sandbox/node/element-test.js @@ -83,6 +83,8 @@ test('wrappers of native functions should return the correct string representati 'Element.prototype.removeAttribute'); window.checkStringRepresentation(window.Element.prototype.removeAttributeNS, nativeMethods.removeAttributeNS, 'Element.prototype.removeAttributeNS'); + window.checkStringRepresentation(window.Element.prototype.removeAttributeNode, nativeMethods.removeAttributeNode, + 'Element.prototype.removeAttributeNode'); window.checkStringRepresentation(window.Element.prototype.cloneNode, nativeMethods.cloneNode, 'Element.prototype.cloneNode'); window.checkStringRepresentation(window.Element.prototype.querySelector, nativeMethods.elementQuerySelector, diff --git a/test/client/fixtures/sandbox/node/methods-test.js b/test/client/fixtures/sandbox/node/methods-test.js index 7ad9be3a00..97e201022c 100644 --- a/test/client/fixtures/sandbox/node/methods-test.js +++ b/test/client/fixtures/sandbox/node/methods-test.js @@ -121,6 +121,46 @@ test('element.removeAttribute, element.removeAttributeNS', function () { ok(!nativeMethods.getAttributeNS.call(el, namespace, storedAttr)); }); +test('element.removeAttributeNode', function () { + var el = document.createElement('a'); + var attr = 'href'; + var storedAttr = DomProcessor.getStoredAttrName(attr); + var namespace = 'http://www.w3.org/1999/xhtml'; + var url = '/test.html'; + + el.setAttribute(attr, url); + el.setAttributeNS(namespace, attr, url); + + ok(nativeMethods.getAttributeNode.call(el, attr)); + ok(nativeMethods.getAttributeNode.call(el, storedAttr)); + ok(nativeMethods.getAttributeNodeNS.call(el, namespace, attr)); + ok(nativeMethods.getAttributeNodeNS.call(el, namespace, storedAttr)); + + wrapNativeFn('removeAttributeNode'); + + let node = nativeMethods.getAttributeNodeNS.call(el, namespace, attr); + + el.removeAttributeNode(node); + + ok(nativeMethodCalled); + ok(nativeMethods.getAttribute.call(el, attr)); + ok(nativeMethods.getAttribute.call(el, storedAttr)); + ok(!nativeMethods.getAttributeNS.call(el, namespace, attr)); + ok(!nativeMethods.getAttributeNS.call(el, namespace, storedAttr)); + + wrapNativeFn('removeAttributeNode'); + + node = nativeMethods.getAttributeNode.call(el, attr); + + el.removeAttributeNode(node); + + ok(nativeMethodCalled); + ok(!nativeMethods.getAttributeNode.call(el, attr)); + ok(!nativeMethods.getAttributeNode.call(el, storedAttr)); + ok(!nativeMethods.getAttributeNodeNS.call(el, namespace, attr)); + ok(!nativeMethods.getAttributeNodeNS.call(el, namespace, storedAttr)); +}); + test('element.getAttributeNS, element.setAttributeNS', function () { var image = document.createElementNS('xlink', 'image');