diff --git a/package.json b/package.json
index 2f5e72ad3..aef2545cb 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "testcafe-hammerhead",
"description": "A powerful web-proxy used as a core for the TestCafe testing framework (https://github.com/DevExpress/testcafe).",
- "version": "9.1.0",
+ "version": "9.2.0",
"homepage": "https://github.com/DevExpress/testcafe-hammerhead",
"bugs": {
"url": "https://github.com/DevExpress/testcafe-hammerhead/issues"
diff --git a/src/client/index.js b/src/client/index.js
index 5c6d0b516..c5876dbcd 100644
--- a/src/client/index.js
+++ b/src/client/index.js
@@ -17,6 +17,8 @@ import * as styleUtils from './utils/style';
import trim from '../utils/string-trim';
import { getProxyUrl } from './utils/url';
import { processScript } from '../processing/script';
+import { SCRIPT_PROCESSING_START_COMMENT, SCRIPT_PROCESSING_END_HEADER_COMMENT, SCRIPT_PROCESSING_END_COMMENT } from '../processing/script/header';
+import { STYLESHEET_PROCESSING_START_COMMENT, STYLESHEET_PROCESSING_END_COMMENT } from '../processing/style';
import isJQueryObj from './utils/is-jquery-object';
import extend from './utils/extend';
@@ -42,6 +44,14 @@ class Hammerhead {
fetchSend: this.sandbox.fetch.FETCH_REQUEST_SEND_EVENT
};
+ this.PROCESSING_COMMENTS = {
+ stylesheetStart: STYLESHEET_PROCESSING_START_COMMENT,
+ stylesheetEnd: STYLESHEET_PROCESSING_END_COMMENT,
+ scriptStart: SCRIPT_PROCESSING_START_COMMENT,
+ scriptEndHeader: SCRIPT_PROCESSING_END_HEADER_COMMENT,
+ scriptEnd: SCRIPT_PROCESSING_END_COMMENT
+ };
+
this.EventEmitter = EventEmitter;
// Methods
diff --git a/src/processing/script/header.js b/src/processing/script/header.js
index 6b36dcac6..956f018f3 100644
--- a/src/processing/script/header.js
+++ b/src/processing/script/header.js
@@ -7,11 +7,13 @@ import reEscape from '../../utils/regexp-escape';
import INTERNAL_PROPS from '../../processing/dom/internal-properties';
import INSTRUCTION from './instruction';
-const PREFIX = '/*hammerhead|script-processing-header|start*/';
-const POSTFIX = '/*hammerhead|script-processing-header|end*/';
-export const HEADER = [
- PREFIX,
+export const SCRIPT_PROCESSING_START_COMMENT = '/*hammerhead|script|start*/';
+export const SCRIPT_PROCESSING_END_COMMENT = '/*hammerhead|script|end*/';
+export const SCRIPT_PROCESSING_END_HEADER_COMMENT = '/*hammerhead|script|processing-header-end*/';
+
+const HEADER = [
+ SCRIPT_PROCESSING_START_COMMENT,
'var __w$= typeof window!=="undefined"&&window;',
`__w$ && __w$["${INTERNAL_PROPS.processDomMethodName}"] && __w$["${INTERNAL_PROPS.processDomMethodName}"]();`,
`var ${ INSTRUCTION.getLocation }=__w$?__w$.${ INSTRUCTION.getLocation }:function(l){return l},`,
@@ -21,14 +23,21 @@ export const HEADER = [
`${ INSTRUCTION.callMethod }=__w$?__w$.${ INSTRUCTION.callMethod }:function(o,p,a){return o[p].apply(o,a)},`,
`${ INSTRUCTION.getEval }=__w$?__w$.${ INSTRUCTION.getEval }:function(e){return e},`,
`${ INSTRUCTION.processScript }=__w$?__w$.${ INSTRUCTION.processScript }:function(s){return s};`,
- POSTFIX,
+ SCRIPT_PROCESSING_END_HEADER_COMMENT,
'\n'
].join('');
// NOTE: IE removes trailing newlines in script.textContent,
// so a trailing newline in RegExp is optional
-const HEADER_RE = new RegExp(`${reEscape(PREFIX)}[\\S\\s]+?${reEscape(POSTFIX)}\n?`, 'i');
+const HEADER_RE = new RegExp(`${reEscape(SCRIPT_PROCESSING_START_COMMENT)}[\\S\\s]+?${reEscape(SCRIPT_PROCESSING_END_HEADER_COMMENT)}\n?`, 'i');
+const PROCESSING_END_COMMENT_RE = new RegExp(`\n?${ reEscape(SCRIPT_PROCESSING_END_COMMENT) }\\s*$`, 'gi');
export function remove (code) {
- return code.replace(HEADER_RE, '');
+ return code
+ .replace(HEADER_RE, '')
+ .replace(PROCESSING_END_COMMENT_RE, '');
+}
+
+export function add (code) {
+ return HEADER + code + '\n' + SCRIPT_PROCESSING_END_COMMENT;
}
diff --git a/src/processing/script/index.js b/src/processing/script/index.js
index 864cccf3d..153e49afa 100644
--- a/src/processing/script/index.js
+++ b/src/processing/script/index.js
@@ -5,7 +5,7 @@
import transform from './transform';
import INSTRUCTION from './instruction';
-import { HEADER, remove as removeHeader } from './header';
+import { add as addHeader, remove as removeHeader } from './header';
import { parse } from './tools/acorn';
import { generate, Syntax } from './tools/esotope';
import reEscape from '../../utils/regexp-escape';
@@ -50,7 +50,7 @@ function preprocess (code) {
function postprocess (processed, withHeader, bom) {
if (withHeader)
- processed = HEADER + processed;
+ processed = addHeader(processed);
return bom ? bom + processed : processed;
}
diff --git a/src/processing/style.js b/src/processing/style.js
index f0928ebd5..85027e796 100644
--- a/src/processing/style.js
+++ b/src/processing/style.js
@@ -4,51 +4,55 @@
// -------------------------------------------------------------
/* eslint hammerhead/proto-methods: 2 */
+import reEscape from '../utils/regexp-escape';
import INTERNAL_ATTRS from '../processing/dom/internal-attributes';
-const SOURCE_MAP_REG_EX = /#\s*sourceMappingURL\s*=\s*[^\s]+(\s|\*\/)/i;
-const CSS_URL_PROPERTY_VALUE_PATTERN = /(url\s*\(\s*)(?:(')([^\s']*)(')|(")([^\s"]*)(")|([^\s\)]*))(\s*\))|(@import\s+)(?:(')([^\s']*)(')|(")([^\s"]*)("))/g;
+const SOURCE_MAP_RE = /#\s*sourceMappingURL\s*=\s*[^\s]+(\s|\*\/)/i;
+const CSS_URL_PROPERTY_VALUE_PATTERN = /(url\s*\(\s*)(?:(')([^\s']*)(')|(")([^\s"]*)(")|([^\s\)]*))(\s*\))|(@import\s+)(?:(')([^\s']*)(')|(")([^\s"]*)("))/g;
+const STYLESHEET_PROCESSING_START_COMMENT = '/*hammerhead|stylesheet|start*/';
+const STYLESHEET_PROCESSING_END_COMMENT = '/*hammerhead|stylesheet|end*/';
+const HOVER_PSEUDO_CLASS_RE = /\s*:\s*hover(\W)/gi;
+const PSEUDO_CLASS_RE = new RegExp(`\\[${ INTERNAL_ATTRS.hoverPseudoClass }\\](\\W)`, 'ig');
+const IS_STYLE_SHEET_PROCESSED_RE = new RegExp(`^\\s*${ reEscape(STYLESHEET_PROCESSING_START_COMMENT) }`, 'gi');
+const STYLESHEET_PROCESSING_COMMENTS_RE = new RegExp(`^\\s*${ reEscape(STYLESHEET_PROCESSING_START_COMMENT) }\n?|` +
+ `\n?${ reEscape(STYLESHEET_PROCESSING_END_COMMENT) }\\s*$`, 'gi');
class StyleProcessor {
constructor () {
- this.IS_STYLESHEET_PROCESSED_COMMENT = '/* stylesheet processed via hammerhead */';
+ this.STYLESHEET_TEXT_START_COMMENT = STYLESHEET_PROCESSING_START_COMMENT;
+ this.STYLESHEET_TEXT_END_COMMENT = STYLESHEET_PROCESSING_END_COMMENT;
}
process (css, urlReplacer, isStylesheetTable) {
- var isStyleSheetProcessingRegEx = new RegExp('^\\s*' +
- this.IS_STYLESHEET_PROCESSED_COMMENT.replace(/\/|\*/g, '\\$&'));
- var isStylesheetProcessed = isStyleSheetProcessingRegEx.test(css);
+ if (typeof css !== 'string' || IS_STYLE_SHEET_PROCESSED_RE.test(css))
+ return css;
- if (typeof css === 'string' && !isStylesheetProcessed) {
- var prefix = isStylesheetTable ? this.IS_STYLESHEET_PROCESSED_COMMENT + '\n' : '';
+ var prefix = isStylesheetTable ? STYLESHEET_PROCESSING_START_COMMENT + '\n' : '';
+ var postfix = isStylesheetTable ? '\n' + STYLESHEET_PROCESSING_END_COMMENT : '';
- // NOTE: Replace the :hover pseudo-class.
- css = css.replace(/\s*:\s*hover(\W)/gi, '[' + INTERNAL_ATTRS.hoverPseudoClass + ']$1');
+ // NOTE: Replace the :hover pseudo-class.
+ css = css.replace(HOVER_PSEUDO_CLASS_RE, '[' + INTERNAL_ATTRS.hoverPseudoClass + ']$1');
- // NOTE: Remove the ‘source map’ directive.
- css = css.replace(SOURCE_MAP_REG_EX, '$1');
+ // NOTE: Remove the ‘source map’ directive.
+ css = css.replace(SOURCE_MAP_RE, '$1');
- // NOTE: Replace URLs in CSS rules with proxy URLs.
- return prefix + this._replaceStylsheetUrls(css, urlReplacer);
- }
-
- return css;
+ // NOTE: Replace URLs in CSS rules with proxy URLs.
+ return prefix + this._replaceStylsheetUrls(css, urlReplacer) + postfix;
}
cleanUp (css, parseProxyUrl) {
- if (typeof css === 'string') {
- css = css
- .replace(new RegExp('\\[' + INTERNAL_ATTRS.hoverPseudoClass + '\\](\\W)', 'ig'), ':hover$1')
- .replace(new RegExp('^\\s*' + this.IS_STYLESHEET_PROCESSED_COMMENT.replace(/\/|\*/g, '\\$&') + '\n?'), '');
+ if (typeof css !== 'string')
+ return css;
- return this._replaceStylsheetUrls(css, url => {
- var parsedProxyUrl = parseProxyUrl(url);
+ css = css
+ .replace(PSEUDO_CLASS_RE, ':hover$1')
+ .replace(STYLESHEET_PROCESSING_COMMENTS_RE, '');
- return parsedProxyUrl ? parsedProxyUrl.destUrl : url;
- });
- }
+ return this._replaceStylsheetUrls(css, url => {
+ var parsedProxyUrl = parseProxyUrl(url);
- return css;
+ return parsedProxyUrl ? parsedProxyUrl.destUrl : url;
+ });
}
_replaceStylsheetUrls (css, processor) {
diff --git a/test/client/fixtures/sandbox/node/document-write-test.js b/test/client/fixtures/sandbox/node/document-write-test.js
index e6a3f4638..b25713379 100644
--- a/test/client/fixtures/sandbox/node/document-write-test.js
+++ b/test/client/fixtures/sandbox/node/document-write-test.js
@@ -141,8 +141,9 @@ test('write style', function () {
strictEqual(getElems(iframeForWrite, 'style').length, getElems(iframeForNativeWrite, 'style').length);
strictEqual(innerHTML(getElems(iframeForWrite, 'style')[0], true), innerHTML(getElems(iframeForNativeWrite, 'style')[0]));
- strictEqual(innerHTML(getElems(iframeForWrite, 'style')[0]), styleProcessor.IS_STYLESHEET_PROCESSED_COMMENT +
- '\n\ndiv {}\n');
+ strictEqual(innerHTML(getElems(iframeForWrite, 'style')[0]), styleProcessor.STYLESHEET_TEXT_START_COMMENT +
+ '\n\ndiv {}\n\n' +
+ styleProcessor.STYLESHEET_TEXT_END_COMMENT);
strictEqual(innerHTML(getElems(iframeForWrite, 'style')[1], true), innerHTML(getElems(iframeForNativeWrite, 'style')[1]));
strictEqual(innerHTML(getElems(iframeForWrite, 'style')[1]), '');
diff --git a/test/client/fixtures/sandbox/node/dom-processor-test.js b/test/client/fixtures/sandbox/node/dom-processor-test.js
index 226ee418d..94fced6dc 100644
--- a/test/client/fixtures/sandbox/node/dom-processor-test.js
+++ b/test/client/fixtures/sandbox/node/dom-processor-test.js
@@ -335,8 +335,8 @@ test('stylesheet after innerHTML', function () {
nativeMethods.appendChild.call(document.body, style);
var check = function (cssText) {
- strictEqual(cssText.indexOf(styleProcessor.IS_STYLESHEET_PROCESSED_COMMENT), 0);
- strictEqual(cssText.indexOf(styleProcessor.IS_STYLESHEET_PROCESSED_COMMENT, 1), -1);
+ strictEqual(cssText.indexOf(styleProcessor.STYLESHEET_TEXT_START_COMMENT), 0);
+ strictEqual(cssText.indexOf(styleProcessor.STYLESHEET_TEXT_START_COMMENT, 1), -1);
strictEqual(cssText.replace(/^[\s\S]+url\(([\s\S]+)\)[\s\S]+$/, '$1'), urlUtils.getProxyUrl('http://domain.com'));
};
diff --git a/test/server/data/page/expected.html b/test/server/data/page/expected.html
index 78e1734a8..d4f9efd7f 100644
--- a/test/server/data/page/expected.html
+++ b/test/server/data/page/expected.html
@@ -9,7 +9,7 @@