Add loop-protect
Remove webworkers
This commit is contained in:
@ -1,24 +0,0 @@
|
|||||||
window.common = (function(global) {
|
|
||||||
const {
|
|
||||||
common = { init: [] }
|
|
||||||
} = global;
|
|
||||||
|
|
||||||
|
|
||||||
const faux$ = common.getScriptContent$('/js/faux.js').shareReplay();
|
|
||||||
|
|
||||||
common.hasJs = function hasJs(code = '') {
|
|
||||||
return code.match(/\<\s?script\s?\>/gi) &&
|
|
||||||
code.match(/\<\s?\/\s?script\s?\>/gi);
|
|
||||||
};
|
|
||||||
|
|
||||||
common.addFaux$ = function addFaux$(code) {
|
|
||||||
// grab user javaScript
|
|
||||||
var scriptCode = code
|
|
||||||
.split(/\<\s?script\s?\>/gi)[1]
|
|
||||||
.split(/\<\s?\/\s?script\s?\>/gi)[0];
|
|
||||||
|
|
||||||
return faux$.map(faux => faux + scriptCode);
|
|
||||||
};
|
|
||||||
|
|
||||||
return common;
|
|
||||||
}(window));
|
|
17
client/commonFramework/add-loop-protect.js
Normal file
17
client/commonFramework/add-loop-protect.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
window.common = (function(global) {
|
||||||
|
const {
|
||||||
|
loopProtect,
|
||||||
|
common = { init: [] }
|
||||||
|
} = global;
|
||||||
|
|
||||||
|
loopProtect.hit = function hit(line) {
|
||||||
|
var err = `Error: Exiting potential infinite loop at line ${line}.`;
|
||||||
|
console.error(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
common.addLoopProtect = function addLoopProtect(code = '') {
|
||||||
|
return loopProtect(code);
|
||||||
|
};
|
||||||
|
|
||||||
|
return common;
|
||||||
|
})(window);
|
@ -1,38 +0,0 @@
|
|||||||
window.common = (function({ common = { init: [] }}) {
|
|
||||||
|
|
||||||
var BDDregex = new RegExp(
|
|
||||||
'(expect(\\s+)?\\(.*\\;)|' +
|
|
||||||
'(assert(\\s+)?\\(.*\\;)|' +
|
|
||||||
'(assert\\.\\w.*\\;)|' +
|
|
||||||
'(.*\\.should\\..*\\;)/'
|
|
||||||
);
|
|
||||||
|
|
||||||
common.addTestsToString = function({ code, tests = [], ...rest }) {
|
|
||||||
const userTests = [];
|
|
||||||
|
|
||||||
code = tests.reduce((code, test) => code + test + '\n', code + '\n');
|
|
||||||
|
|
||||||
var counter = 0;
|
|
||||||
var match = BDDregex.exec(code);
|
|
||||||
|
|
||||||
while (match) {
|
|
||||||
var replacement = '//' + counter + common.salt;
|
|
||||||
code = code.substring(0, match.index) +
|
|
||||||
replacement +
|
|
||||||
code.substring(match.index + match[0].length);
|
|
||||||
|
|
||||||
userTests.push({
|
|
||||||
err: null,
|
|
||||||
text: match[0],
|
|
||||||
line: counter
|
|
||||||
});
|
|
||||||
|
|
||||||
counter++;
|
|
||||||
match = BDDregex.exec(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...rest, code, userTests };
|
|
||||||
};
|
|
||||||
|
|
||||||
return common;
|
|
||||||
}(window));
|
|
@ -1,79 +0,0 @@
|
|||||||
window.common = (function(global) {
|
|
||||||
const {
|
|
||||||
jailed,
|
|
||||||
document: doc,
|
|
||||||
Rx: { Observable, Disposable },
|
|
||||||
common = { init: [] }
|
|
||||||
} = global;
|
|
||||||
|
|
||||||
if (!jailed) {
|
|
||||||
return (code, cb) => cb(new Error('Could not load jailed plugin'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// obtaining absolute path of this script
|
|
||||||
var scripts = doc.getElementsByTagName('script');
|
|
||||||
var path = scripts[scripts.length - 1].src
|
|
||||||
.split('?')[0]
|
|
||||||
.split('/')
|
|
||||||
.slice(0, -1)
|
|
||||||
.join('/') + '/';
|
|
||||||
|
|
||||||
var Sandbox = {
|
|
||||||
startTimeout() {
|
|
||||||
this.timeoutId = setTimeout(() => {
|
|
||||||
this.error = new Error('Plugin failed to initialize');
|
|
||||||
this.destroyPlugin();
|
|
||||||
}, 3000);
|
|
||||||
},
|
|
||||||
cancelTimout() {
|
|
||||||
if (this.timeoutId) {
|
|
||||||
clearTimeout(this.timeoutId);
|
|
||||||
this.timeoutId = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createPlugin() {
|
|
||||||
this.plugin = new jailed.Plugin(path + 'plugin.js');
|
|
||||||
this.startTimeout();
|
|
||||||
this.plugin.whenConnected(() => {
|
|
||||||
this.cancelTimout();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroyPlugin() {
|
|
||||||
this.plugin.disconnect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// sends the input to the plugin for evaluation
|
|
||||||
common.detectLoops$ = function detectLoops$({ code = '', ...rest }) {
|
|
||||||
return Observable.create(function(observer) {
|
|
||||||
const sandbox = Object.create(Sandbox);
|
|
||||||
|
|
||||||
sandbox.createPlugin();
|
|
||||||
sandbox.plugin.whenConnected(() => {
|
|
||||||
sandbox.plugin.remote.run(code, (err, data) => {
|
|
||||||
observer.onNext({ ...rest, err, code, data });
|
|
||||||
observer.onCompleted();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
sandbox.plugin.whenDisconnected(() => {
|
|
||||||
if (sandbox.disposed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sandbox.error) {
|
|
||||||
observer.onNext({ ...rest, err: sandbox.error, code, data: {} });
|
|
||||||
}
|
|
||||||
observer.onCompleted();
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Disposable(() => {
|
|
||||||
sandbox.disposed = true;
|
|
||||||
sandbox.destroyPlugin();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return common;
|
|
||||||
}(window));
|
|
@ -28,18 +28,7 @@ $(document).ready(function() {
|
|||||||
// only run for HTML
|
// only run for HTML
|
||||||
.filter(() => common.challengeType === challengeTypes.HTML)
|
.filter(() => common.challengeType === challengeTypes.HTML)
|
||||||
.flatMap(code => {
|
.flatMap(code => {
|
||||||
if (
|
return common.detectUnsafeCode$(code)
|
||||||
common.hasJs(code)
|
|
||||||
) {
|
|
||||||
return common.detectUnsafeCode$(code)
|
|
||||||
.flatMap(code => common.detectLoops$(code))
|
|
||||||
.flatMap(
|
|
||||||
({ err }) => err ? Observable.throw(err) : Observable.just(code)
|
|
||||||
)
|
|
||||||
.flatMap(code => common.updatePreview$(code))
|
|
||||||
.catch(err => Observable.just({ err }));
|
|
||||||
}
|
|
||||||
return Observable.just(code)
|
|
||||||
.flatMap(code => common.updatePreview$(code))
|
.flatMap(code => common.updatePreview$(code))
|
||||||
.catch(err => Observable.just({ err }));
|
.catch(err => Observable.just({ err }));
|
||||||
})
|
})
|
||||||
@ -66,12 +55,12 @@ $(document).ready(function() {
|
|||||||
.catch(err => Observable.just({ err }));
|
.catch(err => Observable.just({ err }));
|
||||||
})
|
})
|
||||||
.subscribe(
|
.subscribe(
|
||||||
({ err, output, original }) => {
|
({ err, output, originalCode }) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return common.updateOutputDisplay('' + err);
|
return common.updateOutputDisplay('' + err);
|
||||||
}
|
}
|
||||||
common.codeStorage.updateStorage(challengeName, original);
|
common.codeStorage.updateStorage(challengeName, originalCode);
|
||||||
common.updateOutputDisplay('' + output);
|
common.updateOutputDisplay('' + output);
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
@ -102,11 +91,11 @@ $(document).ready(function() {
|
|||||||
if (common.challengeType === common.challengeTypes.HTML) {
|
if (common.challengeType === common.challengeTypes.HTML) {
|
||||||
return common.updatePreview$(`
|
return common.updatePreview$(`
|
||||||
<h1>${err}</h1>
|
<h1>${err}</h1>
|
||||||
`).subscribe(() => {});
|
`).first().subscribe(() => {});
|
||||||
}
|
}
|
||||||
return common.updateOutputDisplay('' + err);
|
return common.updateOutputDisplay('' + err);
|
||||||
}
|
}
|
||||||
common.updateOutputDisplay(output);
|
common.updateOutputDisplay('' + output);
|
||||||
common.displayTestResults(tests);
|
common.displayTestResults(tests);
|
||||||
if (solved) {
|
if (solved) {
|
||||||
common.showCompletion();
|
common.showCompletion();
|
||||||
@ -153,12 +142,12 @@ $(document).ready(function() {
|
|||||||
.flatMap(() => common.executeChallenge$())
|
.flatMap(() => common.executeChallenge$())
|
||||||
.catch(err => Observable.just({ err }))
|
.catch(err => Observable.just({ err }))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
({ err, original, tests }) => {
|
({ err, originalCode, tests }) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return common.updateOutputDisplay('' + err);
|
return common.updateOutputDisplay('' + err);
|
||||||
}
|
}
|
||||||
common.codeStorage.updateStorage(challengeName, original);
|
common.codeStorage.updateStorage(challengeName, originalCode);
|
||||||
common.displayTestResults(tests);
|
common.displayTestResults(tests);
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
// what are the executeChallenge functions?
|
|
||||||
// Should be responsible for starting after a submit action
|
|
||||||
// Should not be responsible for displaying results
|
|
||||||
// Should return results
|
|
||||||
// should grab editor value
|
|
||||||
// depends on main editor
|
|
||||||
window.common = (function(global) {
|
window.common = (function(global) {
|
||||||
const {
|
const {
|
||||||
ga,
|
ga,
|
||||||
Rx: { Observable },
|
|
||||||
common = { init: [] }
|
common = { init: [] }
|
||||||
} = global;
|
} = global;
|
||||||
|
|
||||||
|
const {
|
||||||
|
addLoopProtect,
|
||||||
|
detectUnsafeCode$,
|
||||||
|
updatePreview$,
|
||||||
|
challengeType,
|
||||||
|
challengeTypes
|
||||||
|
} = common;
|
||||||
|
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
|
|
||||||
common.executeChallenge$ = function executeChallenge$() {
|
common.executeChallenge$ = function executeChallenge$() {
|
||||||
@ -18,67 +19,39 @@ window.common = (function(global) {
|
|||||||
const originalCode = code;
|
const originalCode = code;
|
||||||
const head = common.arrayToNewLineString(common.head);
|
const head = common.arrayToNewLineString(common.head);
|
||||||
const tail = common.arrayToNewLineString(common.tail);
|
const tail = common.arrayToNewLineString(common.tail);
|
||||||
|
const combinedCode = head + code + tail;
|
||||||
|
|
||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
ga('send', 'event', 'Challenge', 'ran-code', common.challengeName);
|
ga('send', 'event', 'Challenge', 'ran-code', common.challengeName);
|
||||||
|
|
||||||
// run checks for unsafe code
|
// run checks for unsafe code
|
||||||
return common.detectUnsafeCode$(code)
|
return detectUnsafeCode$(code)
|
||||||
// add head and tail and detect loops
|
// add head and tail and detect loops
|
||||||
.map(code => head + code + tail)
|
.map(() => {
|
||||||
.flatMap(code => {
|
if (challengeType !== challengeTypes.HTML) {
|
||||||
if (common.challengeType === common.challengeTypes.HTML) {
|
return `<script>;${addLoopProtect(combinedCode)}/**/</script>`;
|
||||||
|
|
||||||
if (common.hasJs(code)) {
|
|
||||||
// html has a script code
|
|
||||||
// add faux code and test in webworker
|
|
||||||
return common.addFaux$(code)
|
|
||||||
.flatMap(code => common.detectLoops$(code))
|
|
||||||
.flatMap(({ err }) => {
|
|
||||||
if (err) {
|
|
||||||
return Observable.throw(err);
|
|
||||||
}
|
|
||||||
return common.updatePreview$(code)
|
|
||||||
.flatMap(() => common.runPreviewTests$({
|
|
||||||
code,
|
|
||||||
tests: common.tests.slice()
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// no script code detected in html code
|
|
||||||
// Update preview and run tests in iframe
|
|
||||||
return common.updatePreview$(code)
|
|
||||||
.flatMap(code => common.runPreviewTests$({
|
|
||||||
code,
|
|
||||||
tests: common.tests.slice()
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// js challenge
|
return addLoopProtect(combinedCode);
|
||||||
// remove comments and add tests to string
|
})
|
||||||
return Observable.just(common.addTestsToString(Object.assign(
|
.flatMap(code => updatePreview$(code))
|
||||||
{
|
.flatMap(code => {
|
||||||
code: common.removeComments(code),
|
let output;
|
||||||
tests: common.tests.slice()
|
|
||||||
}
|
|
||||||
)))
|
|
||||||
.flatMap(common.detectLoops$)
|
|
||||||
.flatMap(({ err, code, data, userTests }) => {
|
|
||||||
if (err) {
|
|
||||||
return Observable.throw(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// run tests
|
if (
|
||||||
// for now these are running in the browser
|
challengeType === challengeTypes.HTML &&
|
||||||
return common.runTests$({
|
common.hasJs(code)
|
||||||
data,
|
) {
|
||||||
code,
|
output = common.getJsOutput(common.getJsFromHtml(code));
|
||||||
userTests,
|
} else if (challengeType !== challengeTypes.HTML) {
|
||||||
originalCode,
|
output = common.getJsOutput(combinedCode);
|
||||||
output: data.output.replace(/\\\"/gi, '')
|
}
|
||||||
});
|
|
||||||
|
return common.runPreviewTests$({
|
||||||
|
tests: common.tests.slice(),
|
||||||
|
originalCode,
|
||||||
|
output
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
23
client/commonFramework/get-iframe.js
Normal file
23
client/commonFramework/get-iframe.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
window.common = (function(global) {
|
||||||
|
const {
|
||||||
|
common = { init: [] },
|
||||||
|
document: doc
|
||||||
|
} = global;
|
||||||
|
|
||||||
|
common.getIframe = function getIframe(id = 'preview') {
|
||||||
|
let previewFrame = doc.getElementById(id);
|
||||||
|
|
||||||
|
// create and append a hidden preview frame
|
||||||
|
if (!previewFrame) {
|
||||||
|
previewFrame = doc.createElement('iframe');
|
||||||
|
previewFrame.id = id;
|
||||||
|
previewFrame.setAttribute('style', 'display: none');
|
||||||
|
doc.body.appendChild(previewFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
return previewFrame.contentDocument ||
|
||||||
|
previewFrame.contentWindow.document;
|
||||||
|
};
|
||||||
|
|
||||||
|
return common;
|
||||||
|
})(window);
|
@ -102,5 +102,19 @@ window.common = (function(global) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openScript = /\<\s?script\s?\>/gi;
|
||||||
|
const closingScript = /\<\s?\/\s?script\s?\>/gi;
|
||||||
|
|
||||||
|
// detects if there is JavaScript in the first script tag
|
||||||
|
common.hasJs = function hasJs(code) {
|
||||||
|
return !!common.getJsFromHtml(code);
|
||||||
|
};
|
||||||
|
|
||||||
|
// grabs the content from the first script tag in the code
|
||||||
|
common.getJsFromHtml = function getJsFromHtml(code) {
|
||||||
|
// grab user javaScript
|
||||||
|
return (code.split(openScript)[1] || '').split(closingScript)[0] || '';
|
||||||
|
};
|
||||||
|
|
||||||
return common;
|
return common;
|
||||||
})(window);
|
})(window);
|
||||||
|
@ -4,7 +4,13 @@ window.common = (function(global) {
|
|||||||
common = { init: [] }
|
common = { init: [] }
|
||||||
} = global;
|
} = global;
|
||||||
|
|
||||||
|
// the first script tag here is to proxy jQuery
|
||||||
|
// We use the same jQuery on the main window but we change the
|
||||||
|
// context to that of the iframe.
|
||||||
var libraryIncludes = `
|
var libraryIncludes = `
|
||||||
|
<script>
|
||||||
|
window.$ = parent.$.proxy(parent.$.fn.find, parent.$(document));
|
||||||
|
</script>
|
||||||
<link
|
<link
|
||||||
rel='stylesheet'
|
rel='stylesheet'
|
||||||
href='//cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.0/animate.min.css'
|
href='//cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.0/animate.min.css'
|
||||||
@ -33,15 +39,10 @@ window.common = (function(global) {
|
|||||||
|
|
||||||
// runPreviewTests$ should be set up in the preview window
|
// runPreviewTests$ should be set up in the preview window
|
||||||
common.runPreviewTests$ =
|
common.runPreviewTests$ =
|
||||||
() => Observable.throw({ err: new Error('run preview not enabled') });
|
() => Observable.throw(new Error('run preview not enabled'));
|
||||||
|
|
||||||
common.updatePreview$ = function updatePreview$(code = '') {
|
common.updatePreview$ = function updatePreview$(code = '') {
|
||||||
const previewFrame = document.getElementById('preview');
|
const preview = common.getIframe('preview');
|
||||||
const preview = previewFrame.contentDocument ||
|
|
||||||
previewFrame.contentWindow.document;
|
|
||||||
if (!preview) {
|
|
||||||
return Observable.just(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
return iFrameScript$
|
return iFrameScript$
|
||||||
.map(script => `<script>${script}</script>`)
|
.map(script => `<script>${script}</script>`)
|
||||||
@ -55,7 +56,10 @@ window.common = (function(global) {
|
|||||||
// now we filter false values and wait for the first true
|
// now we filter false values and wait for the first true
|
||||||
return common.previewReady$
|
return common.previewReady$
|
||||||
.filter(ready => ready)
|
.filter(ready => ready)
|
||||||
.first();
|
.first()
|
||||||
|
// the delay here is to give code within the iframe
|
||||||
|
// control to run
|
||||||
|
.delay(100);
|
||||||
})
|
})
|
||||||
.map(() => code);
|
.map(() => code);
|
||||||
};
|
};
|
||||||
|
275
client/faux.js
275
client/faux.js
@ -1,275 +0,0 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
|
||||||
var document = {};
|
|
||||||
var navigator = function() {
|
|
||||||
this.geolocation = function() {
|
|
||||||
this.getCurrentPosition = function() {
|
|
||||||
this.coords = {latitude: '', longitude: '' };
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
function $() {
|
|
||||||
if (!(this instanceof $)) {
|
|
||||||
return new $();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function returnThis() { return this; }
|
|
||||||
function return$() { return $; }
|
|
||||||
var methods = [
|
|
||||||
'add',
|
|
||||||
'addBack',
|
|
||||||
'addClass',
|
|
||||||
'after',
|
|
||||||
'ajaxComplete',
|
|
||||||
'ajaxError',
|
|
||||||
'ajaxSend',
|
|
||||||
'ajaxStart',
|
|
||||||
'ajaxStop',
|
|
||||||
'ajaxSuccess',
|
|
||||||
'andSelf',
|
|
||||||
'animate',
|
|
||||||
'append',
|
|
||||||
'appendTo',
|
|
||||||
'attr',
|
|
||||||
'before',
|
|
||||||
'bind',
|
|
||||||
'blur',
|
|
||||||
'callbacksadd',
|
|
||||||
'callbacksdisable',
|
|
||||||
'callbacksdisabled',
|
|
||||||
'callbacksempty',
|
|
||||||
'callbacksfire',
|
|
||||||
'callbacksfired',
|
|
||||||
'callbacksfireWith',
|
|
||||||
'callbackshas',
|
|
||||||
'callbackslock',
|
|
||||||
'callbackslocked',
|
|
||||||
'callbacksremove',
|
|
||||||
'change',
|
|
||||||
'children',
|
|
||||||
'clearQueue',
|
|
||||||
'click',
|
|
||||||
'clone',
|
|
||||||
'closest',
|
|
||||||
'contents',
|
|
||||||
'context',
|
|
||||||
'css',
|
|
||||||
'data',
|
|
||||||
'dblclick',
|
|
||||||
'delay',
|
|
||||||
'delegate',
|
|
||||||
'dequeue',
|
|
||||||
'detach',
|
|
||||||
'die',
|
|
||||||
'each',
|
|
||||||
'empty',
|
|
||||||
'end',
|
|
||||||
'eq',
|
|
||||||
'error',
|
|
||||||
'fadeIn',
|
|
||||||
'fadeOut',
|
|
||||||
'fadeTo',
|
|
||||||
'fadeToggle',
|
|
||||||
'filter',
|
|
||||||
'find',
|
|
||||||
'finish',
|
|
||||||
'first',
|
|
||||||
'focus',
|
|
||||||
'focusin',
|
|
||||||
'focusout',
|
|
||||||
'get',
|
|
||||||
'has',
|
|
||||||
'hasClass',
|
|
||||||
'height',
|
|
||||||
'hide',
|
|
||||||
'hover',
|
|
||||||
'html',
|
|
||||||
'index',
|
|
||||||
'innerHeight',
|
|
||||||
'innerWidth',
|
|
||||||
'insertAfter',
|
|
||||||
'insertBefore',
|
|
||||||
'is',
|
|
||||||
'jQuery',
|
|
||||||
'jquery',
|
|
||||||
'keydown',
|
|
||||||
'keypress',
|
|
||||||
'keyup',
|
|
||||||
'last',
|
|
||||||
'length',
|
|
||||||
'live',
|
|
||||||
'load',
|
|
||||||
'load',
|
|
||||||
'map',
|
|
||||||
'mousedown',
|
|
||||||
'mouseenter',
|
|
||||||
'mouseleave',
|
|
||||||
'mousemove',
|
|
||||||
'mouseout',
|
|
||||||
'mouseover',
|
|
||||||
'mouseup',
|
|
||||||
'next',
|
|
||||||
'nextAll',
|
|
||||||
'nextUntil',
|
|
||||||
'not',
|
|
||||||
'off',
|
|
||||||
'offset',
|
|
||||||
'offsetParent',
|
|
||||||
'on',
|
|
||||||
'one',
|
|
||||||
'outerHeight',
|
|
||||||
'outerWidth',
|
|
||||||
'parent',
|
|
||||||
'parents',
|
|
||||||
'parentsUntil',
|
|
||||||
'position',
|
|
||||||
'prepend',
|
|
||||||
'prependTo',
|
|
||||||
'prev',
|
|
||||||
'prevAll',
|
|
||||||
'prevUntil',
|
|
||||||
'promise',
|
|
||||||
'prop',
|
|
||||||
'pushStack',
|
|
||||||
'queue',
|
|
||||||
'ready',
|
|
||||||
'remove',
|
|
||||||
'removeAttr',
|
|
||||||
'removeClass',
|
|
||||||
'removeData',
|
|
||||||
'removeProp',
|
|
||||||
'replaceAll',
|
|
||||||
'replaceWith',
|
|
||||||
'resize',
|
|
||||||
'scroll',
|
|
||||||
'scrollLeft',
|
|
||||||
'scrollTop',
|
|
||||||
'select',
|
|
||||||
'selector',
|
|
||||||
'serialize',
|
|
||||||
'serializeArray',
|
|
||||||
'show',
|
|
||||||
'siblings',
|
|
||||||
'size',
|
|
||||||
'slice',
|
|
||||||
'slideDown',
|
|
||||||
'slideToggle',
|
|
||||||
'slideUp',
|
|
||||||
'stop',
|
|
||||||
'submit',
|
|
||||||
'text',
|
|
||||||
'toArray',
|
|
||||||
'toggle',
|
|
||||||
'toggle',
|
|
||||||
'toggleClass',
|
|
||||||
'trigger',
|
|
||||||
'triggerHandler',
|
|
||||||
'unbind',
|
|
||||||
'undelegate',
|
|
||||||
'unload',
|
|
||||||
'unwrap',
|
|
||||||
'val',
|
|
||||||
'width',
|
|
||||||
'wrap',
|
|
||||||
'wrapAll',
|
|
||||||
'wrapInner'
|
|
||||||
];
|
|
||||||
|
|
||||||
var statics = [
|
|
||||||
'ajax',
|
|
||||||
'ajaxPrefilter',
|
|
||||||
'ajaxSetup',
|
|
||||||
'ajaxTransport',
|
|
||||||
'boxModel',
|
|
||||||
'browser',
|
|
||||||
'Callbacks',
|
|
||||||
'contains',
|
|
||||||
'cssHooks',
|
|
||||||
'cssNumber',
|
|
||||||
'data',
|
|
||||||
'Deferred',
|
|
||||||
'dequeue',
|
|
||||||
'each',
|
|
||||||
'error',
|
|
||||||
'extend',
|
|
||||||
'fnextend',
|
|
||||||
'fxinterval',
|
|
||||||
'fxoff',
|
|
||||||
'get',
|
|
||||||
'getJSON',
|
|
||||||
'getScript',
|
|
||||||
'globalEval',
|
|
||||||
'grep',
|
|
||||||
'hasData',
|
|
||||||
'holdReady',
|
|
||||||
'inArray',
|
|
||||||
'isArray',
|
|
||||||
'isEmptyObject',
|
|
||||||
'isFunction',
|
|
||||||
'isNumeric',
|
|
||||||
'isPlainObject',
|
|
||||||
'isWindow',
|
|
||||||
'isXMLDoc',
|
|
||||||
'makeArray',
|
|
||||||
'map',
|
|
||||||
'merge',
|
|
||||||
'noConflict',
|
|
||||||
'noop',
|
|
||||||
'now',
|
|
||||||
'param',
|
|
||||||
'parseHTML',
|
|
||||||
'parseJSON',
|
|
||||||
'parseXML',
|
|
||||||
'post',
|
|
||||||
'proxy',
|
|
||||||
'queue',
|
|
||||||
'removeData',
|
|
||||||
'sub',
|
|
||||||
'support',
|
|
||||||
'trim',
|
|
||||||
'type',
|
|
||||||
'unique',
|
|
||||||
'when',
|
|
||||||
'always',
|
|
||||||
'done',
|
|
||||||
'fail',
|
|
||||||
'isRejected',
|
|
||||||
'isResolved',
|
|
||||||
'notify',
|
|
||||||
'notifyWith',
|
|
||||||
'pipe',
|
|
||||||
'progress',
|
|
||||||
'promise',
|
|
||||||
'reject',
|
|
||||||
'rejectWith',
|
|
||||||
'resolve',
|
|
||||||
'resolveWith',
|
|
||||||
'state',
|
|
||||||
'then',
|
|
||||||
'currentTarget',
|
|
||||||
'data',
|
|
||||||
'delegateTarget',
|
|
||||||
'isDefaultPrevented',
|
|
||||||
'isImmediatePropagationStopped',
|
|
||||||
'isPropagationStopped',
|
|
||||||
'metaKey',
|
|
||||||
'namespace',
|
|
||||||
'pageX',
|
|
||||||
'pageY',
|
|
||||||
'preventDefault',
|
|
||||||
'relatedTarget',
|
|
||||||
'result',
|
|
||||||
'stopImmediatePropagation',
|
|
||||||
'stopPropagation',
|
|
||||||
'target',
|
|
||||||
'timeStamp',
|
|
||||||
'type',
|
|
||||||
'which'
|
|
||||||
];
|
|
||||||
|
|
||||||
var $Proto = {};
|
|
||||||
methods.forEach(method => $Proto[method] = returnThis);
|
|
||||||
statics.forEach(staticMeth => $[staticMeth] = return$);
|
|
||||||
$.prototype = Object.create($);
|
|
@ -1,21 +1,43 @@
|
|||||||
/* eslint-disable no-undef, no-unused-vars, no-native-reassign */
|
/* eslint-disable no-undef, no-unused-vars, no-native-reassign */
|
||||||
window.__$ = parent.$;
|
// the $ on the iframe window object is the same
|
||||||
window.__$(function() {
|
// as the one used on the main site, but
|
||||||
|
// uses the iframe document as the context
|
||||||
|
window.$(document).ready(function() {
|
||||||
var _ = parent._;
|
var _ = parent._;
|
||||||
var Rx = parent.Rx;
|
var Rx = parent.Rx;
|
||||||
var chai = parent.chai;
|
var chai = parent.chai;
|
||||||
var assert = chai.assert;
|
var assert = chai.assert;
|
||||||
var tests = parent.tests;
|
var tests = parent.tests;
|
||||||
var common = parent.common;
|
var common = parent.common;
|
||||||
var editor = common.editor.getValue();
|
|
||||||
// change the context of $ so it uses the iFrame for testing
|
common.getJsOutput = function evalJs(code = '') {
|
||||||
var $ = __$.proxy(__$.fn.find, __$(document));
|
let output;
|
||||||
|
try {
|
||||||
|
/* eslint-disable no-eval */
|
||||||
|
output = eval(code);
|
||||||
|
/* eslint-enable no-eval */
|
||||||
|
} catch (e) {
|
||||||
|
window.__err = e;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
common.runPreviewTests$ =
|
common.runPreviewTests$ =
|
||||||
function runPreviewTests$({ tests = [], ...rest }) {
|
function runPreviewTests$({
|
||||||
|
tests = [],
|
||||||
|
originalCode,
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
|
const code = originalCode;
|
||||||
|
const editor = { getValue() { return originalCode; } };
|
||||||
|
if (window.__err) {
|
||||||
|
return Rx.Observable.throw(window.__err);
|
||||||
|
}
|
||||||
|
|
||||||
return Rx.Observable.from(tests)
|
return Rx.Observable.from(tests)
|
||||||
.map(test => {
|
.map(test => {
|
||||||
const userTest = {};
|
const userTest = {};
|
||||||
|
common.appendToOutputDisplay('');
|
||||||
try {
|
try {
|
||||||
/* eslint-disable no-eval */
|
/* eslint-disable no-eval */
|
||||||
eval(test);
|
eval(test);
|
||||||
@ -32,12 +54,13 @@ window.__$(function() {
|
|||||||
return userTest;
|
return userTest;
|
||||||
})
|
})
|
||||||
.toArray()
|
.toArray()
|
||||||
.map(tests => ({ ...rest, tests }));
|
.map(tests => ({ ...rest, tests, originalCode }));
|
||||||
};
|
};
|
||||||
|
|
||||||
// now that the runPreviewTest$ is defined
|
// now that the runPreviewTest$ is defined
|
||||||
// we set the subject to true
|
// we set the subject to true
|
||||||
// this will let the updatePreview
|
// this will let the updatePreview
|
||||||
// script now that we are ready.
|
// script now that we are ready.
|
||||||
|
console.log('second');
|
||||||
common.previewReady$.onNext(true);
|
common.previewReady$.onNext(true);
|
||||||
});
|
});
|
||||||
|
15
gulpfile.js
15
gulpfile.js
@ -18,6 +18,7 @@ var Rx = require('rx'),
|
|||||||
uglify = require('gulp-uglify'),
|
uglify = require('gulp-uglify'),
|
||||||
merge = require('merge-stream'),
|
merge = require('merge-stream'),
|
||||||
babel = require('gulp-babel'),
|
babel = require('gulp-babel'),
|
||||||
|
sourcemaps = require('gulp-sourcemaps'),
|
||||||
|
|
||||||
// react app
|
// react app
|
||||||
webpack = require('webpack-stream'),
|
webpack = require('webpack-stream'),
|
||||||
@ -85,7 +86,8 @@ var paths = {
|
|||||||
'public/bower_components/CodeMirror/mode/xml/xml.js',
|
'public/bower_components/CodeMirror/mode/xml/xml.js',
|
||||||
'public/bower_components/CodeMirror/mode/css/css.js',
|
'public/bower_components/CodeMirror/mode/css/css.js',
|
||||||
'public/bower_components/CodeMirror/mode/htmlmixed/htmlmixed.js',
|
'public/bower_components/CodeMirror/mode/htmlmixed/htmlmixed.js',
|
||||||
'node_modules/emmet-codemirror/dist/emmet.js'
|
'node_modules/emmet-codemirror/dist/emmet.js',
|
||||||
|
'public/js/lib/loop-protect/loop-protect.js'
|
||||||
],
|
],
|
||||||
|
|
||||||
vendorMain: [
|
vendorMain: [
|
||||||
@ -103,20 +105,20 @@ var paths = {
|
|||||||
js: [
|
js: [
|
||||||
'client/main.js',
|
'client/main.js',
|
||||||
'client/iFrameScripts.js',
|
'client/iFrameScripts.js',
|
||||||
'client/plugin.js',
|
'client/plugin.js'
|
||||||
'client/faux.js'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
commonFramework: [
|
commonFramework: [
|
||||||
'init',
|
'init',
|
||||||
'bindings',
|
'bindings',
|
||||||
'add-test-to-string',
|
'add-test-to-string',
|
||||||
'add-faux-stream',
|
|
||||||
'code-storage',
|
'code-storage',
|
||||||
'code-uri',
|
'code-uri',
|
||||||
|
'add-loop-protect',
|
||||||
|
'get-iframe',
|
||||||
|
'update-preview',
|
||||||
'create-editor',
|
'create-editor',
|
||||||
'detect-unsafe-code-stream',
|
'detect-unsafe-code-stream',
|
||||||
'detect-loops-stream',
|
|
||||||
'display-test-results',
|
'display-test-results',
|
||||||
'execute-challenge-stream',
|
'execute-challenge-stream',
|
||||||
'output-display',
|
'output-display',
|
||||||
@ -125,7 +127,6 @@ var paths = {
|
|||||||
'run-tests-stream',
|
'run-tests-stream',
|
||||||
'show-completion',
|
'show-completion',
|
||||||
'step-challenge',
|
'step-challenge',
|
||||||
'update-preview',
|
|
||||||
'end'
|
'end'
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -434,7 +435,9 @@ gulp.task('dependents', ['js'], function() {
|
|||||||
return gulp.src(formatCommonFrameworkPaths.call(paths.commonFramework))
|
return gulp.src(formatCommonFrameworkPaths.call(paths.commonFramework))
|
||||||
.pipe(plumber({ errorHandler: errorHandler }))
|
.pipe(plumber({ errorHandler: errorHandler }))
|
||||||
.pipe(babel())
|
.pipe(babel())
|
||||||
|
.pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
|
||||||
.pipe(concat('commonFramework.js'))
|
.pipe(concat('commonFramework.js'))
|
||||||
|
.pipe(__DEV__ ? sourcemaps.write() : gutil.noop())
|
||||||
.pipe(__DEV__ ? gutil.noop() : uglify())
|
.pipe(__DEV__ ? gutil.noop() : uglify())
|
||||||
.pipe(revReplace({ manifest: manifest }))
|
.pipe(revReplace({ manifest: manifest }))
|
||||||
.pipe(gulp.dest(dest))
|
.pipe(gulp.dest(dest))
|
||||||
|
@ -134,6 +134,7 @@
|
|||||||
"browser-sync": "^2.9.12",
|
"browser-sync": "^2.9.12",
|
||||||
"chai": "^3.4.0",
|
"chai": "^3.4.0",
|
||||||
"envify": "^3.4.0",
|
"envify": "^3.4.0",
|
||||||
|
"gulp-sourcemaps": "^1.6.0",
|
||||||
"istanbul": "~0.4.0",
|
"istanbul": "~0.4.0",
|
||||||
"jsonlint": "^1.6.2",
|
"jsonlint": "^1.6.2",
|
||||||
"loopback-component-explorer": "^2.1.1",
|
"loopback-component-explorer": "^2.1.1",
|
||||||
|
21
public/js/lib/loop-protect/LICENSE.md
Normal file
21
public/js/lib/loop-protect/LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 JS Bin Ltd
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
432
public/js/lib/loop-protect/loop-protect.js
Normal file
432
public/js/lib/loop-protect/loop-protect.js
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
if (typeof DEBUG === 'undefined') { DEBUG = true; }
|
||||||
|
|
||||||
|
(function (root, factory) {
|
||||||
|
'use strict';
|
||||||
|
/*global define*/
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define(factory(root));
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
module.exports = factory(root);
|
||||||
|
} else {
|
||||||
|
root.loopProtect = factory(root);
|
||||||
|
}
|
||||||
|
})(this, function loopProtectModule(root) {
|
||||||
|
/*global DEBUG*/
|
||||||
|
'use strict';
|
||||||
|
var debug = null;
|
||||||
|
|
||||||
|
// the standard loops - note that recursive is not supported
|
||||||
|
var re = /\b(for|while|do)\b/g;
|
||||||
|
var reSingle = /\b(for|while|do)\b/;
|
||||||
|
var labelRe = /\b([a-z_]{1}\w+:)/i;
|
||||||
|
var comments = /(?:\/\*(?:[\s\S]*?)\*\/)|(?:([\s;])+\/\/(?:.*)$)/gm;
|
||||||
|
|
||||||
|
var loopProtect = rewriteLoops;
|
||||||
|
|
||||||
|
// used in the loop detection
|
||||||
|
loopProtect.counters = {};
|
||||||
|
|
||||||
|
// expose debug info
|
||||||
|
loopProtect.debug = function debugSwitch(state) {
|
||||||
|
debug = state ? function () {
|
||||||
|
console.log.apply(console, [].slice.apply(arguments));
|
||||||
|
} : function () {};
|
||||||
|
};
|
||||||
|
|
||||||
|
loopProtect.debug(false); // off by default
|
||||||
|
|
||||||
|
// the method - as this could be aliased to something else
|
||||||
|
loopProtect.alias = 'loopProtect';
|
||||||
|
|
||||||
|
function inMultilineComment(lineNum, lines) {
|
||||||
|
if (lineNum === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var j = lineNum;
|
||||||
|
var closeCommentTags = 1; // let's assume we're inside a comment
|
||||||
|
var closePos = -1;
|
||||||
|
var openPos = -1;
|
||||||
|
|
||||||
|
do {
|
||||||
|
j -= 1;
|
||||||
|
DEBUG && debug('looking backwards ' + lines[j]); // jshint ignore:line
|
||||||
|
closePos = lines[j].indexOf('*/');
|
||||||
|
openPos = lines[j].indexOf('/*');
|
||||||
|
|
||||||
|
if (closePos !== -1) {
|
||||||
|
closeCommentTags++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openPos !== -1) {
|
||||||
|
closeCommentTags--;
|
||||||
|
|
||||||
|
if (closeCommentTags === 0) {
|
||||||
|
DEBUG && debug('- exit: part of a multiline comment'); // jshint ignore:line
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (j !== 0);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inCommentOrString(index, line) {
|
||||||
|
var character;
|
||||||
|
while (--index > -1) {
|
||||||
|
character = line.substr(index, 1);
|
||||||
|
if (character === '"' || character === '\'' || character === '.') {
|
||||||
|
// our loop keyword was actually either in a string or a property, so let's exit and ignore this line
|
||||||
|
DEBUG && debug('- exit: matched inside a string or property key'); // jshint ignore:line
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (character === '/' || character === '*') {
|
||||||
|
// looks like a comment, go back one to confirm or not
|
||||||
|
--index;
|
||||||
|
if (character === '/') {
|
||||||
|
// we've found a comment, so let's exit and ignore this line
|
||||||
|
DEBUG && debug('- exit: part of a comment'); // jshint ignore:line
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function directlyBeforeLoop(index, lineNum, lines) {
|
||||||
|
reSingle.lastIndex = 0;
|
||||||
|
labelRe.lastIndex = 0;
|
||||||
|
var beforeLoop = false;
|
||||||
|
|
||||||
|
var theRest = lines.slice(lineNum).join('\n').substr(index).replace(labelRe, '');
|
||||||
|
theRest.replace(reSingle, function commentStripper(match, capture, i) {
|
||||||
|
var target = theRest.substr(0, i).replace(comments, '').trim();
|
||||||
|
DEBUG && debug('- directlyBeforeLoop: ' + target); // jshint ignore:line
|
||||||
|
if (target.length === 0) {
|
||||||
|
beforeLoop = true;
|
||||||
|
}
|
||||||
|
// strip comments out of the target, and if there's nothing else
|
||||||
|
// it's a valid label...I hope!
|
||||||
|
});
|
||||||
|
|
||||||
|
return beforeLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for for, while and do loops, and inserts *just* at the start of the
|
||||||
|
* loop, a check function.
|
||||||
|
*/
|
||||||
|
function rewriteLoops(code, offset) {
|
||||||
|
var recompiled = [];
|
||||||
|
var lines = code.split('\n');
|
||||||
|
var disableLoopProtection = false;
|
||||||
|
var method = loopProtect.alias + '.protect';
|
||||||
|
var ignore = {};
|
||||||
|
var pushonly = {};
|
||||||
|
var labelPostion = null;
|
||||||
|
|
||||||
|
function insertReset(lineNum, line, matchPosition) {
|
||||||
|
// recompile the line with the reset **just** before the actual loop
|
||||||
|
// so that we insert in to the correct location (instead of possibly
|
||||||
|
// outside the logic
|
||||||
|
return line.slice(0, matchPosition) + ';' + method + '({ line: ' + lineNum + ', reset: true }); ' + line.slice(matchPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!offset) {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.forEach(function eachLine(line, lineNum) {
|
||||||
|
// reset our regexp each time.
|
||||||
|
re.lastIndex = 0;
|
||||||
|
labelRe.lastIndex = 0;
|
||||||
|
|
||||||
|
if (disableLoopProtection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.toLowerCase().indexOf('noprotect') !== -1) {
|
||||||
|
disableLoopProtection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = -1;
|
||||||
|
var matchPosition = -1;
|
||||||
|
var originalLineNum = lineNum;
|
||||||
|
// +1 since we're humans and don't read lines numbers from zero
|
||||||
|
var printLineNumber = lineNum - offset + 1;
|
||||||
|
var character = '';
|
||||||
|
// special case for `do` loops, as they're end with `while`
|
||||||
|
var dofound = false;
|
||||||
|
var findwhile = false;
|
||||||
|
var terminator = false;
|
||||||
|
var matches = line.match(re) || [];
|
||||||
|
var match = matches.length ? matches[0] : '';
|
||||||
|
var labelMatch = line.match(labelRe) || [];
|
||||||
|
var openBrackets = 0;
|
||||||
|
var openBraces = 0;
|
||||||
|
|
||||||
|
if (labelMatch.length) {
|
||||||
|
DEBUG && debug('- label match'); // jshint ignore:line
|
||||||
|
index = line.indexOf(labelMatch[1]);
|
||||||
|
if (!inCommentOrString(index, line)) {
|
||||||
|
if (!inMultilineComment(lineNum, lines)) {
|
||||||
|
if (directlyBeforeLoop(index, lineNum, lines)) {
|
||||||
|
DEBUG && debug('- found a label: "' + labelMatch[0] + '"'); // jshint ignore:line
|
||||||
|
labelPostion = lineNum;
|
||||||
|
} else {
|
||||||
|
DEBUG && debug('- ignored "label", false positive'); // jshint ignore:line
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG && debug('- ignored label in multline comment'); // jshint ignore:line
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG && debug('- ignored label in string or comment'); // jshint ignore:line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignore[lineNum]) {
|
||||||
|
DEBUG && debug(' -exit: ignoring line ' + lineNum +': ' + line); // jshint ignore:line
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pushonly[lineNum]) {
|
||||||
|
DEBUG && debug('- exit: ignoring, but adding line ' + lineNum + ': ' + line); // jshint ignore:line
|
||||||
|
recompiled.push(line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's more than one match, we just ignore this kind of loop
|
||||||
|
// otherwise I'm going to be writing a full JavaScript lexer...and god
|
||||||
|
// knows I've got better things to be doing.
|
||||||
|
if (match && matches.length === 1 && line.indexOf('jsbin') === -1) {
|
||||||
|
DEBUG && debug('match on ' + match + '\n'); // jshint ignore:line
|
||||||
|
|
||||||
|
// there's a special case for protecting `do` loops, we need to first
|
||||||
|
// prtect the `do`, but then ignore the closing `while` statement, so
|
||||||
|
// we reset the search state for this special case.
|
||||||
|
dofound = match === 'do';
|
||||||
|
|
||||||
|
// make sure this is an actual loop command by searching backwards
|
||||||
|
// to ensure it's not a string, comment or object property
|
||||||
|
matchPosition = index = line.indexOf(match);
|
||||||
|
|
||||||
|
// first we need to walk backwards to ensure that our match isn't part
|
||||||
|
// of a string or part of a comment
|
||||||
|
if (inCommentOrString(index, line)) {
|
||||||
|
recompiled.push(line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's quite possible we're in the middle of a multiline
|
||||||
|
// comment, so we'll cycle up looking for an opening comment,
|
||||||
|
// and if there's one (and not a closing `*/`), then we'll
|
||||||
|
// ignore this line as a comment
|
||||||
|
if (inMultilineComment(lineNum, lines)) {
|
||||||
|
recompiled.push(line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now work our way forward to look for '{'
|
||||||
|
index = line.indexOf(match) + match.length;
|
||||||
|
|
||||||
|
if (index === line.length) {
|
||||||
|
if (index === line.length && lineNum < (lines.length-1)) {
|
||||||
|
// move to the next line
|
||||||
|
DEBUG && debug('- moving to next line'); // jshint ignore:line
|
||||||
|
recompiled.push(line);
|
||||||
|
lineNum++;
|
||||||
|
line = lines[lineNum];
|
||||||
|
ignore[lineNum] = true;
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index < line.length) {
|
||||||
|
character = line.substr(index, 1);
|
||||||
|
// DEBUG && debug(character, index); // jshint ignore:line
|
||||||
|
|
||||||
|
if (character === '(') {
|
||||||
|
openBrackets++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character === ')') {
|
||||||
|
openBrackets--;
|
||||||
|
|
||||||
|
if (openBrackets === 0 && terminator === false) {
|
||||||
|
terminator = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character === '{') {
|
||||||
|
openBraces++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character === '}') {
|
||||||
|
openBraces--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openBrackets === 0 && (character === ';' || character === '{')) {
|
||||||
|
// if we're a non-curlies loop, then convert to curlies to get our code inserted
|
||||||
|
if (character === ';') {
|
||||||
|
if (lineNum !== originalLineNum) {
|
||||||
|
DEBUG && debug('- multiline inline loop'); // jshint ignore:line
|
||||||
|
// affect the compiled line
|
||||||
|
recompiled[originalLineNum] = recompiled[originalLineNum].substring(0, terminator + 1) + '{\nif (' + method + '({ line: ' + printLineNumber + ' })) break;\n' + recompiled[originalLineNum].substring(terminator + 1);
|
||||||
|
line += '\n}\n';
|
||||||
|
} else {
|
||||||
|
// simpler
|
||||||
|
DEBUG && debug('- single line inline loop'); // jshint ignore:line
|
||||||
|
line = line.substring(0, terminator + 1) + '{\nif (' + method + '({ line: ' + printLineNumber + ' })) break;\n' + line.substring(terminator + 1) + '\n}\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (character === '{') {
|
||||||
|
DEBUG && debug('- multiline with braces'); // jshint ignore:line
|
||||||
|
var insert = ';\nif (' + method + '({ line: ' + printLineNumber + ' })) break;\n';
|
||||||
|
line = line.substring(0, index + 1) + insert + line.substring(index + 1);
|
||||||
|
|
||||||
|
index += insert.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// work out where to put the reset
|
||||||
|
if (lineNum === originalLineNum && labelPostion === null) {
|
||||||
|
DEBUG && debug('- simple reset insert'); // jshint ignore:line
|
||||||
|
line = insertReset(printLineNumber, line, matchPosition);
|
||||||
|
index += (';' + method + '({ line: ' + lineNum + ', reset: true }); ').length;
|
||||||
|
} else {
|
||||||
|
// insert the reset above the originalLineNum OR if this loop used
|
||||||
|
// a label, we have to insert the reset *above* the label
|
||||||
|
if (labelPostion === null) {
|
||||||
|
DEBUG && debug('- reset inserted above original line'); // jshint ignore:line
|
||||||
|
recompiled[originalLineNum] = insertReset(printLineNumber, recompiled[originalLineNum], matchPosition);
|
||||||
|
} else {
|
||||||
|
DEBUG && debug('- reset inserted above matched label on line ' + labelPostion); // jshint ignore:line
|
||||||
|
if (recompiled[labelPostion] === undefined) {
|
||||||
|
labelPostion--;
|
||||||
|
matchPosition = 0;
|
||||||
|
}
|
||||||
|
recompiled[labelPostion] = insertReset(printLineNumber, recompiled[labelPostion], matchPosition);
|
||||||
|
labelPostion = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recompiled.push(line);
|
||||||
|
|
||||||
|
if (!dofound) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
DEBUG && debug('searching for closing `while` statement for: ' + line); // jshint ignore:line
|
||||||
|
// cycle forward until we find the close brace, after which should
|
||||||
|
// be our while statement to ignore
|
||||||
|
findwhile = false;
|
||||||
|
while (index < line.length) {
|
||||||
|
character = line.substr(index, 1);
|
||||||
|
|
||||||
|
if (character === '{') {
|
||||||
|
openBraces++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character === '}') {
|
||||||
|
openBraces--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openBraces === 0) {
|
||||||
|
findwhile = true;
|
||||||
|
} else {
|
||||||
|
findwhile = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openBraces === 0) {
|
||||||
|
DEBUG && debug('outside of closure, looking for `while` statement: ' + line); // jshint ignore:line
|
||||||
|
}
|
||||||
|
|
||||||
|
if (findwhile && line.indexOf('while') !== -1) {
|
||||||
|
DEBUG && debug('- exit as we found `while`: ' + line); // jshint ignore:line
|
||||||
|
pushonly[lineNum] = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
|
||||||
|
if (index === line.length && lineNum < (lines.length-1)) {
|
||||||
|
lineNum++;
|
||||||
|
line = lines[lineNum];
|
||||||
|
DEBUG && debug(line); // jshint ignore:line
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
|
||||||
|
if (index === line.length && lineNum < (lines.length-1)) {
|
||||||
|
// move to the next line
|
||||||
|
DEBUG && debug('- moving to next line'); // jshint ignore:line
|
||||||
|
recompiled.push(line);
|
||||||
|
lineNum++;
|
||||||
|
line = lines[lineNum];
|
||||||
|
ignore[lineNum] = true;
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// else we're a regular line, and we shouldn't be touched
|
||||||
|
DEBUG && debug('regular line ' + line); // jshint ignore:line
|
||||||
|
recompiled.push(line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DEBUG && debug('---- source ----'); // jshint ignore:line
|
||||||
|
DEBUG && debug(code); // jshint ignore:line
|
||||||
|
DEBUG && debug('---- rewrite ---'); // jshint ignore:line
|
||||||
|
DEBUG && debug(recompiled.join('\n')); // jshint ignore:line
|
||||||
|
DEBUG && debug(''); // jshint ignore:line
|
||||||
|
|
||||||
|
return disableLoopProtection ? code : recompiled.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injected code in to user's code to **try** to protect against infinite
|
||||||
|
* loops cropping up in the code, and killing the browser. Returns true
|
||||||
|
* when the loops has been running for more than 100ms.
|
||||||
|
*/
|
||||||
|
loopProtect.protect = function protect(state) {
|
||||||
|
loopProtect.counters[state.line] = loopProtect.counters[state.line] || {};
|
||||||
|
var line = loopProtect.counters[state.line];
|
||||||
|
var now = (new Date()).getTime();
|
||||||
|
|
||||||
|
if (state.reset) {
|
||||||
|
line.time = now;
|
||||||
|
line.hit = 0;
|
||||||
|
line.last = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.hit++;
|
||||||
|
if ((now - line.time) > 100) {//} && line.hit !== line.last+1) {
|
||||||
|
// We've spent over 100ms on this loop... smells infinite.
|
||||||
|
loopProtect.hit(state.line);
|
||||||
|
// Returning true prevents the loop running again
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
line.last++;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
loopProtect.hit = function hit(line) {
|
||||||
|
var msg = 'Exiting potential infinite loop at line ' + line + '. To disable loop protection: add "// noprotect" to your code';
|
||||||
|
if (root.proxyConsole) {
|
||||||
|
root.proxyConsole.error(msg);
|
||||||
|
} else {
|
||||||
|
console.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loopProtect.reset = function reset() {
|
||||||
|
// reset the counters
|
||||||
|
loopProtect.counters = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
return loopProtect;
|
||||||
|
});
|
Reference in New Issue
Block a user