Skip to content

Commit

Permalink
Add support for HTMLElement.removeAttributeNode.
Browse files Browse the repository at this point in the history
  • Loading branch information
TrevorKarjanis committed Feb 15, 2022
1 parent 3615919 commit a155d3f
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 9 deletions.
6 changes: 6 additions & 0 deletions src/client/sandbox/native-methods.ts
Expand Up @@ -60,13 +60,16 @@ class NativeMethods {
elementQuerySelectorAll: any;
getAttribute: any;
getAttributeNS: any;
getAttributeNode: any;
getAttributeNodeNS: any;
insertBefore: Node['insertBefore'];
insertCell: any;
insertTableRow: any;
insertTBodyRow: any;
importScripts: (...urls: string[]) => void
removeAttribute: any;
removeAttributeNS: any;
removeAttributeNode: any;
removeChild: Node['removeChild'];
remove: Element['remove'];
elementReplaceWith: Element['replaceWith'];
Expand Down Expand Up @@ -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;
Expand Down
65 changes: 56 additions & 9 deletions src/client/sandbox/node/element.ts
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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');
Expand All @@ -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))
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions test/client/fixtures/sandbox/node/element-test.js
Expand Up @@ -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,
Expand Down
41 changes: 41 additions & 0 deletions test/client/fixtures/sandbox/node/methods-test.js
Expand Up @@ -121,6 +121,47 @@ 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(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(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');

Expand Down

0 comments on commit a155d3f

Please sign in to comment.