Skip to content

Commit

Permalink
Merge pull request #263 from miherlosev/I_259
Browse files Browse the repository at this point in the history
Script and style content added via a child text node must be overriden (close #259)
  • Loading branch information
churkin committed Nov 26, 2015
2 parents 9de9760 + bc065d2 commit c858039
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 81 deletions.
2 changes: 1 addition & 1 deletion src/client/sandbox/event/focus-blur.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default class FocusBlurSandbox extends SandboxBase {
nativeMethods.removeAttribute.call(this.lastFocusedElement, INTERNAL_ATTRS.focusPseudoClass);

if (domUtils.isElementFocusable(activeElement) &&
!(activeElement.tagName && activeElement.tagName.toLowerCase() === 'body' &&
!(domUtils.isBodyElement(activeElement) &&
activeElement.getAttribute('tabIndex') === null)) {
this.lastFocusedElement = activeElement;
nativeMethods.setAttribute.call(activeElement, INTERNAL_ATTRS.focusPseudoClass, true);
Expand Down
88 changes: 50 additions & 38 deletions src/client/sandbox/node/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import SandboxBase from '../base';
import nativeMethods from '../native-methods';
import domProcessor from '../../dom-processor';
import { processScript } from '../../../processing/script';
import { process as processStyle } from '../../../processing/style';
import * as urlUtils from '../../utils/url';
import * as domUtils from '../../utils/dom';
import * as hiddenInfo from '../upload/hidden-info';
Expand Down Expand Up @@ -153,30 +154,32 @@ export default class ElementSandbox extends SandboxBase {
return removeAttrFunc.apply(el, arg);
}

_prepareNodeForInsertion (node, parentNode) {
if (node.nodeType === 3)
ElementSandbox._processTextNodeContent(node, parentNode);

this.nodeSandbox.overrideDomMethods(node);
}

_createOverridedMethods () {
var overrideNewElement = el => this.nodeSandbox.overrideDomMethods(el);
var onElementAdded = el => this._onElementAdded(el);
var onElementRemoved = el => this.onElementRemoved(el);
var removeFileInputInfo = el => ElementSandbox._removeFileInputInfo(el);
var overridedGetAttributeCore = (el, attr, ns) => this._overridedGetAttributeCore(el, attr, ns);
var overridedSetAttributeCore = (el, attr, value, ns) => this._overridedSetAttributeCore(el, attr, value, ns);
var overridedRemoveAttributeCore = (el, ns, arg) => this._overridedRemoveAttributeCore(el, ns, arg);
// NOTE: We need the closure because a context of overridden methods is an html element
var sandbox = this;

this.overridedMethods = {
insertRow: function () {
insertRow () {
var tagName = this.tagName.toLowerCase();
var nativeMeth = tagName === 'table' ? nativeMethods.insertTableRow : nativeMethods.insertTBodyRow;
var row = nativeMeth.apply(this, arguments);

overrideNewElement(row);
sandbox.nodeSandbox.overrideDomMethods(row);

return row;
},

insertCell () {
var cell = nativeMethods.insertCell.apply(this, arguments);

overrideNewElement(cell);
sandbox.nodeSandbox.overrideDomMethods(cell);

return cell;
},
Expand All @@ -186,7 +189,7 @@ export default class ElementSandbox extends SandboxBase {
html = processHtml('' + html, this.parentNode && this.parentNode.tagName);

nativeMethods.insertAdjacentHTML.call(this, pos, html);
overrideNewElement(this.parentNode || this);
sandbox.nodeSandbox.overrideDomMethods(this.parentNode || this);
},

formSubmit () {
Expand All @@ -202,87 +205,96 @@ export default class ElementSandbox extends SandboxBase {
},

insertBefore (newNode, refNode) {
overrideNewElement(newNode);
sandbox._prepareNodeForInsertion(newNode, this);

var result = nativeMethods.insertBefore.call(this, newNode, refNode);
var result = null;

onElementAdded(newNode);
if (domUtils.isBodyElementWithChildren(this) && !refNode)
result = sandbox.shadowUI.insertBeforeRoot(newNode);
else
result = nativeMethods.insertBefore.call(this, newNode, refNode);

sandbox._onElementAdded(newNode);

return result;
},

appendChild (child) {
// NOTE: We need to process TextNode as a script if it is appended to a script element (B254284).
if (child.nodeType === 3 && this.tagName && this.tagName.toLowerCase() === 'script')
child.data = processScript(child.data, true, false);

overrideNewElement(child);
sandbox._prepareNodeForInsertion(child, this);

var result = null;

if (this.tagName && this.tagName.toLowerCase() === 'body' && this.children.length) {
// NOTE: We need to append the element before the shadow ui root.
var lastChild = this.children[this.children.length - 1];

result = nativeMethods.insertBefore.call(this, child, lastChild);
}
if (domUtils.isBodyElementWithChildren(this))
result = sandbox.shadowUI.insertBeforeRoot(child);
else
result = nativeMethods.appendChild.call(this, child);

onElementAdded(child);
sandbox._onElementAdded(child);

return result;
},

removeChild (child) {
if (domUtils.isDomElement(child)) {
domUtils.find(child, 'input[type=file]', removeFileInputInfo);
domUtils.find(child, 'input[type=file]', ElementSandbox._removeFileInputInfo);

if (domUtils.isFileInput(child))
removeFileInputInfo(child);
ElementSandbox._removeFileInputInfo(child);
}

var result = nativeMethods.removeChild.call(this, child);

onElementRemoved(child);
sandbox.onElementRemoved(child);

return result;
},

cloneNode () {
var clone = nativeMethods.cloneNode.apply(this, arguments);

overrideNewElement(clone);
sandbox.nodeSandbox.overrideDomMethods(clone);

return clone;
},

getAttribute (attr) {
return overridedGetAttributeCore(this, attr);
return sandbox._overridedGetAttributeCore(this, attr);
},

getAttributeNS (ns, attr) {
return overridedGetAttributeCore(this, attr, ns);
return sandbox._overridedGetAttributeCore(this, attr, ns);
},

setAttribute (attr, value) {
return overridedSetAttributeCore(this, attr, value);
return sandbox._overridedSetAttributeCore(this, attr, value);
},

setAttributeNS (ns, attr, value) {
return overridedSetAttributeCore(this, attr, value, ns);
return sandbox._overridedSetAttributeCore(this, attr, value, ns);
},

removeAttribute () {
return overridedRemoveAttributeCore(this, false, arguments);
return sandbox._overridedRemoveAttributeCore(this, false, arguments);
},

removeAttributeNS () {
return overridedRemoveAttributeCore(this, true, arguments);
return sandbox._overridedRemoveAttributeCore(this, true, arguments);
}
};
}

static _processTextNodeContent (node, parentNode) {
if (!parentNode.tagName)
return;

var parentTagName = parentNode.tagName.toLowerCase();

if (parentTagName === 'script')
node.data = processScript(node.data, true, false);
else if (parentTagName === 'style')
node.data = processStyle(node.data, urlUtils.getProxyUrl);
}

static _isUrlAttr (el, attr) {
var tagName = el.tagName.toLowerCase();

Expand All @@ -301,7 +313,7 @@ export default class ElementSandbox extends SandboxBase {
for (var i = 0; i < iframes.length; i++)
this.onIframeAddedToDOM(iframes[i]);
}
else if (el.tagName && el.tagName.toLowerCase() === 'body')
else if (domUtils.isBodyElement(el))
this.shadowUI.onBodyElementMutation();
}

Expand All @@ -314,7 +326,7 @@ export default class ElementSandbox extends SandboxBase {
}

onElementRemoved (el) {
if (el.nodeType === 1 && el.tagName && el.tagName.toLowerCase() === 'body')
if (domUtils.isBodyElement(el))
this.shadowUI.onBodyElementMutation();
}

Expand Down
21 changes: 14 additions & 7 deletions src/client/sandbox/shadow-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,11 @@ export default class ShadowUI extends SandboxBase {

if (!this.root) {
// NOTE: B254893
this.root = this.document.createElement('div');
nativeMethods.setAttribute.call(this.root, 'id', this.ROOT_ID);
this.root = nativeMethods.createElement.call(this.document, 'div');
nativeMethods.setAttribute.call(this.root, 'id', ShadowUI.patchId(this.ROOT_ID));
nativeMethods.setAttribute.call(this.root, 'contenteditable', 'false');
this.document.body.appendChild(this.root);

nativeMethods.setAttribute.call(this.root, 'id', ShadowUI.patchClassNames(this.ROOT_ID));

this.addClass(this.root, this.ROOT_CLASS);
nativeMethods.appendChild.call(this.document.body, this.root);

for (var i = 0; i < EVENTS.length; i++)
this.root.addEventListener(EVENTS[i], stopPropagation);
Expand All @@ -146,7 +143,7 @@ export default class ShadowUI extends SandboxBase {
nativeMethods.documentAddEventListener.call(this.document, 'DOMContentLoaded', () => this._bringRootToWindowTopLeft);
}
else
this.document.body.appendChild(this.root);
nativeMethods.appendChild.call(this.document.body, this.root);
}

return this.root;
Expand Down Expand Up @@ -377,6 +374,10 @@ export default class ShadowUI extends SandboxBase {
return domUtils.hasClass(el, patchedClass);
}

static patchId (value) {
return value + SHADOW_UI_CLASS_NAME.postfix;
}

static patchClassNames (value) {
var names = value.split(/\s+/);

Expand Down Expand Up @@ -422,4 +423,10 @@ export default class ShadowUI extends SandboxBase {
setLastActiveElement (el) {
this.lastActiveElement = el;
}

insertBeforeRoot (el) {
var rootParent = this.getRoot().parentNode;

return nativeMethods.insertBefore.call(rootParent, el, rootParent.lastChild);
}
}
8 changes: 8 additions & 0 deletions src/client/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,14 @@ export function isInputElement (el) {
return isDomElement(el) && el.tagName.toLowerCase() === 'input';
}

export function isBodyElement (el) {
return isDomElement(el) && el.tagName.toLowerCase() === 'body';
}

export function isBodyElementWithChildren (el) {
return isBodyElement(el) && el.children.length;
}

export function isInputWithoutSelectionPropertiesInFirefox (el) {
// NOTE: T101195, T133144, T101195
return isFirefox && matches(el, 'input[type=number]');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ asyncTest('body.innerHTML in iframe', function () {
.load(function () {
var iframe = this;
var haveShadowUIRoot = function () {
var root = iframe.contentDocument.body.children[0];
var iframeBody = iframe.contentDocument.body;
var root = iframeBody.children[iframeBody.children.length - 1];

return root && root.id.indexOf('root-') === 0;
};
Expand Down
58 changes: 28 additions & 30 deletions test/client/fixtures/sandbox/iframe-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ var urlUtils = hammerhead.get('./utils/url');
var settings = hammerhead.get('./settings');

var iframeSandbox = hammerhead.sandbox.iframe;
var browserUtils = hammerhead.utils.browser;
var nativeMethods = hammerhead.nativeMethods;
var browserUtils = hammerhead.utils.browser;

QUnit.testStart(function () {
// NOTE: The 'window.open' method used in QUnit.
Expand Down Expand Up @@ -61,37 +61,33 @@ test('document.write', function () {
iframe.parentNode.removeChild(iframe);
});

// NOTE: This test must be the last (IE11 hack).
asyncTest('element.setAttribute', function () {
var src = browserUtils.isFirefox ? ' src="javascript:&quot;<html><body></body></html>&quot;"' : '';

expect(12);

$('<iframe id="test20"' + src + '>').load(function () {
var iframe = this;
var iframeBody = iframe.contentDocument.body;
// NOTE: Firefox doesn't raise the 'load' event for double-nested iframes without src
var src = browserUtils.isFirefox ? 'javascript:"<html><body></body></html>"' : '';
var iframe = document.createElement('iframe');

// NOTE: IE hack part 1: catch hammerhead initialization exception.
var iframeSandbox = this.contentWindow['%hammerhead%'].sandbox.iframe;
var storedMeth = iframeSandbox.constructor.isIframeInitialized;
iframe.id = 'test20';
iframe.setAttribute('src', src);
iframe.addEventListener('load', function () {
var iframeHammerhead = this.contentWindow['%hammerhead%'];
var iframeIframeSandbox = iframeHammerhead.sandbox.iframe;

iframeSandbox.constructor.isIframeInitialized = function (iframe) {
iframe.contentWindow[INTERNAL_PROPS.overrideDomMethodName] =
iframe.contentWindow[INTERNAL_PROPS.overrideDomMethodName] || function () { };
iframeIframeSandbox.on(iframeIframeSandbox.IFRAME_READY_TO_INIT_EVENT, initIframeTestHandler);
iframeIframeSandbox.off(iframeIframeSandbox.IFRAME_READY_TO_INIT_EVENT, iframeSandbox.iframeReadyToInitHandler);

return storedMeth.call(iframeSandbox, iframe);
};
// --------------------------------------------------------
var iframeDocument = this.contentDocument;
var iframeBody = iframeDocument.body;
var nestedIframe = iframeDocument.createElement('iframe');

$('<iframe id="test21">').load(function () {
// NOTE: IE hack part 2: initialize hammerhead manually.
if (this.contentDocument.createElement.toString().indexOf('native') !== -1)
initIframeTestHandler({ iframe: this });
nestedIframe.id = 'test21';
nestedIframe.addEventListener('load', function () {
var nestedIframeHammerhead = this.contentWindow['%hammerhead%'];

var iframeDocument = this.contentDocument;
var subIframeBody = iframeDocument.body;
ok(nestedIframeHammerhead);

var testData = [
var nestedIframeDocument = this.contentDocument;
var nestedIframeBody = nestedIframeDocument.body;
var testData = [
[document.body, 'a', 'href', null, null],
[iframeBody, 'a', 'href', null, 'iframe'],
[document.body, 'form', 'action', null, null],
Expand All @@ -100,10 +96,10 @@ asyncTest('element.setAttribute', function () {
[iframeBody, 'area', 'href', null, null],
[document.body, 'a', 'href', '_top', null],
[iframeBody, 'a', 'href', '_top', null],
[subIframeBody, 'a', 'href', '_top', null],
[nestedIframeBody, 'a', 'href', '_top', null],
[document.body, 'a', 'href', '_parent', null],
[iframeBody, 'a', 'href', '_parent', null],
[subIframeBody, 'a', 'href', '_parent', 'iframe']
[nestedIframeBody, 'a', 'href', '_parent', 'iframe']
];

var testIframeFlag = function (body, tag, urlAttr, target, resultFlag) {
Expand All @@ -122,10 +118,12 @@ asyncTest('element.setAttribute', function () {
for (var i = 0; i < testData.length; i++)
testIframeFlag.apply(null, testData[i]);

iframe.parentNode.removeChild(iframe);
start();
$(iframe).remove();
}).appendTo(iframeBody);
}).appendTo('body');
});
iframeBody.appendChild(nestedIframe);
});
document.body.appendChild(iframe);
});

module('regression');
Expand Down
Loading

0 comments on commit c858039

Please sign in to comment.