Skip to content

Commit

Permalink
Multiple document.write works incorrectly (fix #416) (#480)
Browse files Browse the repository at this point in the history
  • Loading branch information
LavrovArtem authored and churkin committed Apr 18, 2016
1 parent b97f015 commit 123d59d
Show file tree
Hide file tree
Showing 18 changed files with 687 additions and 1,047 deletions.
117 changes: 86 additions & 31 deletions src/client/sandbox/code-instrumentation/properties/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import INSTRUCTION from '../../../../processing/script/instruction';
import { shouldInstrumentProperty } from '../../../../processing/script/instrumented';
import nativeMethods from '../../native-methods';
import { emptyActionAttrFallbacksToTheLocation } from '../../../utils/feature-detection';
import { getPendingElementContent } from '../../../sandbox/node/document/writer';

const ORIGINAL_WINDOW_ON_ERROR_HANDLER_KEY = 'hammerhead|original-window-on-error-handler-key';

Expand Down Expand Up @@ -179,16 +180,34 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {

innerHTML: {
condition: el => domUtils.isElementNode(el),
get: el => cleanUpHtml(el.innerHTML, el.tagName),

get: el => {
if (domUtils.isScriptElement(el))
return getPendingElementContent(el) || removeProcessingHeader(el.innerHTML);
else if (domUtils.isStyleElement(el))
return getPendingElementContent(el) || styleProcessor.cleanUp(el.innerHTML, urlUtils.parseProxyUrl);

return cleanUpHtml(el.innerHTML, el.tagName);
},

set: (el, value) => {
if (domUtils.isStyleElement(el))
value = styleProcessor.process('' + value, urlUtils.getProxyUrl, true);
else if (value !== null)
value = processHtml('' + value, el.tagName);
var isStyleEl = domUtils.isStyleElement(el);
var isScriptEl = domUtils.isScriptElement(el);

if (value) {
if (isStyleEl)
value = styleProcessor.process('' + value, urlUtils.getProxyUrl, true);
else if (isScriptEl)
value = processScript('' + value, true, false);
else if (value !== null)
value = processHtml('' + value, el.tagName);
}

el.innerHTML = value;

if (isStyleEl || isScriptEl)
return value;

var parentDocument = domUtils.findDocument(el);
var parentWindow = parentDocument ? parentDocument.defaultView : null;

Expand Down Expand Up @@ -217,15 +236,28 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {
},

innerText: {
condition: el => typeof el.tagName === 'string' && domUtils.isScriptElement(el) &&
typeof el.innerText === 'string',
// NOTE: http://caniuse.com/#search=Node.innerText
condition: el => typeof el.innerText === 'string' &&
(domUtils.isScriptElement(el) || domUtils.isStyleElement(el)),

get: el => typeof el.innerText === 'string' ? removeProcessingHeader(el.innerText) : el.innerText,
get: el => {
if (domUtils.isScriptElement(el))
return getPendingElementContent(el) || removeProcessingHeader(el.innerText);
else if (domUtils.isStyleElement(el))
return getPendingElementContent(el) || styleProcessor.cleanUp(el.innerText, urlUtils.parseProxyUrl);
},

set: function (el, script) {
el.innerText = script ? processScript(script, true, false) : script;
set: (el, text) => {
if (text) {
if (domUtils.isScriptElement(el))
el.innerText = processScript(text, true, false);
else if (domUtils.isStyleElement(el))
el.innerText = styleProcessor.process(text, urlUtils.getProxyUrl, true);
}
else
el.innerText = text;

return script;
return text;
}
},

Expand Down Expand Up @@ -408,27 +440,50 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {
},

text: {
// NOTE: Check for tagName being a string, because it may be a function in an Angular app (T175340).
condition: el => typeof el.tagName === 'string' && domUtils.isScriptElement(el),
get: el => typeof el.text === 'string' ? removeProcessingHeader(el.text) : el.text,
condition: el => domUtils.isScriptElement(el) || domUtils.isStyleElement(el),

set: (el, script) => {
el.text = script ? processScript(script, true, false) : script;
get: el => {
if (domUtils.isScriptElement(el))
return getPendingElementContent(el) || removeProcessingHeader(el.text);
else if (domUtils.isStyleElement(el))
return getPendingElementContent(el) || styleProcessor.cleanUp(el.text, urlUtils.parseProxyUrl);
},

set: (el, text) => {
if (text) {
if (domUtils.isScriptElement(el))
el.text = processScript(text, true, false);
else if (domUtils.isStyleElement(el))
el.text = styleProcessor.process(text, urlUtils.getProxyUrl, true);
}
else
el.text = text;

return script;
return text;
}
},

textContent: {
// NOTE: Check for tagName being a string, because it may be a function in an Angular app (T175340).
condition: el => typeof el.tagName === 'string' && domUtils.isScriptElement(el),
get: el => typeof el.textContent ===
'string' ? removeProcessingHeader(el.textContent) : el.textContent,
condition: el => domUtils.isScriptElement(el) || domUtils.isStyleElement(el),

set: (el, script) => {
el.textContent = script ? processScript(script, true, false) : script;
get: el => {
if (domUtils.isScriptElement(el))
return getPendingElementContent(el) || removeProcessingHeader(el.textContent);
else if (domUtils.isStyleElement(el))
return getPendingElementContent(el) || styleProcessor.cleanUp(el.textContent, urlUtils.parseProxyUrl);
},

set: (el, text) => {
if (text) {
if (domUtils.isScriptElement(el))
el.textContent = processScript(text, true, false);
else if (domUtils.isStyleElement(el))
el.textContent = styleProcessor.process(text, urlUtils.getProxyUrl, true);
}
else
el.textContent = text;

return script;
return text;
}
},

Expand Down Expand Up @@ -501,7 +556,7 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {
// Style
background: {
condition: isStyle,
get: style => styleProcessor.cleanUp(style.background, urlUtils.parseProxyUrl, urlUtils.formatUrl),
get: style => styleProcessor.cleanUp(style.background, urlUtils.parseProxyUrl),

set: (style, value) => {
if (typeof value === 'string')
Expand All @@ -513,7 +568,7 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {

backgroundImage: {
condition: isStyle,
get: style => styleProcessor.cleanUp(style.backgroundImage, urlUtils.parseProxyUrl, urlUtils.formatUrl),
get: style => styleProcessor.cleanUp(style.backgroundImage, urlUtils.parseProxyUrl),

set: (style, value) => {
if (typeof value === 'string')
Expand All @@ -525,7 +580,7 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {

borderImage: {
condition: isStyle,
get: style => styleProcessor.cleanUp(style.borderImage, urlUtils.parseProxyUrl, urlUtils.formatUrl),
get: style => styleProcessor.cleanUp(style.borderImage, urlUtils.parseProxyUrl),

set: (style, value) => {
if (typeof value === 'string')
Expand All @@ -537,7 +592,7 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {

cssText: {
condition: isStyle,
get: style => styleProcessor.cleanUp(style.cssText, urlUtils.parseProxyUrl, urlUtils.formatUrl),
get: style => styleProcessor.cleanUp(style.cssText, urlUtils.parseProxyUrl),

set: (style, value) => {
if (typeof value === 'string')
Expand All @@ -549,7 +604,7 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {

cursor: {
condition: isStyle,
get: style => styleProcessor.cleanUp(style.cursor, urlUtils.parseProxyUrl, urlUtils.formatUrl),
get: style => styleProcessor.cleanUp(style.cursor, urlUtils.parseProxyUrl),

set: (style, value) => {
if (typeof value === 'string')
Expand All @@ -561,7 +616,7 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {

listStyle: {
condition: isStyle,
get: style => styleProcessor.cleanUp(style.listStyle, urlUtils.parseProxyUrl, urlUtils.formatUrl),
get: style => styleProcessor.cleanUp(style.listStyle, urlUtils.parseProxyUrl),

set: (style, value) => {
if (typeof value === 'string')
Expand All @@ -573,7 +628,7 @@ export default class PropertyAccessorsInstrumentation extends SandboxBase {

listStyleImage: {
condition: isStyle,
get: style => styleProcessor.cleanUp(style.listStyleImage, urlUtils.parseProxyUrl, urlUtils.formatUrl),
get: style => styleProcessor.cleanUp(style.listStyleImage, urlUtils.parseProxyUrl),

set: (style, value) => {
if (typeof value === 'string')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import SandboxBase from '../base';
import IframeSandbox from '../iframe';
import INTERNAL_LITERAL from '../../../processing/script/internal-literal';
import nativeMethods from '../native-methods';
import domProcessor from '../../dom-processor';
import * as htmlUtils from '../../utils/html';
import * as urlUtils from '../../utils/url';
import { isFirefox, isIE, isIE9, isIE10 } from '../../utils/browser';
import { isIframeWithoutSrc, getFrameElement } from '../../utils/dom';

// NOTE: We should avoid using native object prototype methods,
// since they can be overriden by the client code. (GH-245)
var arraySlice = Array.prototype.slice;
import SandboxBase from '../../base';
import IframeSandbox from '../../iframe';
import nativeMethods from '../../native-methods';
import domProcessor from '../../../dom-processor';
import * as urlUtils from '../../../utils/url';
import { isIE, isIE9, isIE10 } from '../../../utils/browser';
import { isIframeWithoutSrc, getFrameElement } from '../../../utils/dom';
import DocumentWriter from './writer';

export default class DocumentSandbox extends SandboxBase {
constructor (nodeSandbox) {
super();

this.storedDocumentWriteContent = '';
this.writeBlockCounter = 0;
this.nodeSandbox = nodeSandbox;
this.readyStateForIE = null;
this.nodeSandbox = nodeSandbox;
this.readyStateForIE = null;
this.documentWriter = null;
}

_isUninitializedIframeWithoutSrc (doc) {
Expand All @@ -45,49 +39,13 @@ export default class DocumentSandbox extends SandboxBase {
}

_overridedDocumentWrite (args, ln) {
args = arraySlice.call(args);

var lastArg = args.length ? args[args.length - 1] : '';
var isBegin = lastArg === INTERNAL_LITERAL.documentWriteBegin;
var isEnd = lastArg === INTERNAL_LITERAL.documentWriteEnd;

if (isBegin)
this.writeBlockCounter++;
else if (isEnd)
this.writeBlockCounter--;

if (isBegin || isEnd)
args.pop();

var str = args.join('');

var needWriteOnEndMarker = isEnd && !this.writeBlockCounter;

if (needWriteOnEndMarker || !this.storedDocumentWriteContent && htmlUtils.isWellFormattedHtml(str)) {
this.writeBlockCounter = 0;
str = this.storedDocumentWriteContent + str;
this.storedDocumentWriteContent = '';
}
else if (isBegin || this.storedDocumentWriteContent) {
this.storedDocumentWriteContent += str;

return null;
}

var shouldEmitEvents = (this.readyStateForIE || this.document.readyState) !== 'loading' &&
this.document.readyState !== 'uninitialized';

str = htmlUtils.processHtml('' + str);

if (shouldEmitEvents)
this._beforeDocumentCleaned();

// NOTE: Firefox and IE recreate a window instance during the document.write function execution (T213930).
if ((isFirefox || isIE) && !htmlUtils.isPageHtml(str))
str = htmlUtils.INIT_SCRIPT_FOR_IFRAME_TEMPLATE + str;

var targetNativeMethod = ln ? nativeMethods.documentWriteLn : nativeMethods.documentWrite;
var result = targetNativeMethod.call(this.document, str);
var result = this.documentWriter.write(args, ln);

if (shouldEmitEvents) {
this.nodeSandbox.mutation.onDocumentCleaned({
Expand All @@ -104,6 +62,9 @@ export default class DocumentSandbox extends SandboxBase {
}

attach (window, document) {
if (!this.documentWriter || this.window !== window || this.document !== document)
this.documentWriter = new DocumentWriter(window, document);

super.attach(window, document);

// NOTE: https://connect.microsoft.com/IE/feedback/details/792880/document-readystat
Expand Down
Loading

0 comments on commit 123d59d

Please sign in to comment.