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
 | 
			
		||||
      .filter(() => common.challengeType === challengeTypes.HTML)
 | 
			
		||||
      .flatMap(code => {
 | 
			
		||||
        if (
 | 
			
		||||
          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)
 | 
			
		||||
        return common.detectUnsafeCode$(code)
 | 
			
		||||
          .flatMap(code => common.updatePreview$(code))
 | 
			
		||||
          .catch(err => Observable.just({ err }));
 | 
			
		||||
      })
 | 
			
		||||
@@ -66,12 +55,12 @@ $(document).ready(function() {
 | 
			
		||||
        .catch(err => Observable.just({ err }));
 | 
			
		||||
    })
 | 
			
		||||
    .subscribe(
 | 
			
		||||
      ({ err, output, original }) => {
 | 
			
		||||
      ({ err, output, originalCode }) => {
 | 
			
		||||
        if (err) {
 | 
			
		||||
          console.error(err);
 | 
			
		||||
          return common.updateOutputDisplay('' + err);
 | 
			
		||||
        }
 | 
			
		||||
        common.codeStorage.updateStorage(challengeName, original);
 | 
			
		||||
        common.codeStorage.updateStorage(challengeName, originalCode);
 | 
			
		||||
        common.updateOutputDisplay('' + output);
 | 
			
		||||
      },
 | 
			
		||||
      (err) => {
 | 
			
		||||
@@ -102,11 +91,11 @@ $(document).ready(function() {
 | 
			
		||||
          if (common.challengeType === common.challengeTypes.HTML) {
 | 
			
		||||
            return common.updatePreview$(`
 | 
			
		||||
              <h1>${err}</h1>
 | 
			
		||||
            `).subscribe(() => {});
 | 
			
		||||
            `).first().subscribe(() => {});
 | 
			
		||||
          }
 | 
			
		||||
          return common.updateOutputDisplay('' + err);
 | 
			
		||||
        }
 | 
			
		||||
        common.updateOutputDisplay(output);
 | 
			
		||||
        common.updateOutputDisplay('' + output);
 | 
			
		||||
        common.displayTestResults(tests);
 | 
			
		||||
        if (solved) {
 | 
			
		||||
          common.showCompletion();
 | 
			
		||||
@@ -153,12 +142,12 @@ $(document).ready(function() {
 | 
			
		||||
      .flatMap(() => common.executeChallenge$())
 | 
			
		||||
      .catch(err => Observable.just({ err }))
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        ({ err, original, tests }) => {
 | 
			
		||||
        ({ err, originalCode, tests }) => {
 | 
			
		||||
          if (err) {
 | 
			
		||||
            console.error(err);
 | 
			
		||||
            return common.updateOutputDisplay('' + err);
 | 
			
		||||
          }
 | 
			
		||||
          common.codeStorage.updateStorage(challengeName, original);
 | 
			
		||||
          common.codeStorage.updateStorage(challengeName, originalCode);
 | 
			
		||||
          common.displayTestResults(tests);
 | 
			
		||||
        },
 | 
			
		||||
        (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) {
 | 
			
		||||
  const {
 | 
			
		||||
    ga,
 | 
			
		||||
    Rx: { Observable },
 | 
			
		||||
    common = { init: [] }
 | 
			
		||||
  } = global;
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    addLoopProtect,
 | 
			
		||||
    detectUnsafeCode$,
 | 
			
		||||
    updatePreview$,
 | 
			
		||||
    challengeType,
 | 
			
		||||
    challengeTypes
 | 
			
		||||
  } = common;
 | 
			
		||||
 | 
			
		||||
  let attempts = 0;
 | 
			
		||||
 | 
			
		||||
  common.executeChallenge$ = function executeChallenge$() {
 | 
			
		||||
@@ -18,67 +19,39 @@ window.common = (function(global) {
 | 
			
		||||
    const originalCode = code;
 | 
			
		||||
    const head = common.arrayToNewLineString(common.head);
 | 
			
		||||
    const tail = common.arrayToNewLineString(common.tail);
 | 
			
		||||
    const combinedCode = head + code + tail;
 | 
			
		||||
 | 
			
		||||
    attempts++;
 | 
			
		||||
 | 
			
		||||
    ga('send', 'event', 'Challenge', 'ran-code', common.challengeName);
 | 
			
		||||
 | 
			
		||||
    // run checks for unsafe code
 | 
			
		||||
    return common.detectUnsafeCode$(code)
 | 
			
		||||
    return detectUnsafeCode$(code)
 | 
			
		||||
      // add head and tail and detect loops
 | 
			
		||||
      .map(code => head + code + tail)
 | 
			
		||||
      .flatMap(code => {
 | 
			
		||||
        if (common.challengeType === common.challengeTypes.HTML) {
 | 
			
		||||
 | 
			
		||||
          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()
 | 
			
		||||
            }));
 | 
			
		||||
      .map(() => {
 | 
			
		||||
        if (challengeType !== challengeTypes.HTML) {
 | 
			
		||||
          return `<script>;${addLoopProtect(combinedCode)}/**/</script>`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // js challenge
 | 
			
		||||
        // remove comments and add tests to string
 | 
			
		||||
        return Observable.just(common.addTestsToString(Object.assign(
 | 
			
		||||
          {
 | 
			
		||||
            code: common.removeComments(code),
 | 
			
		||||
            tests: common.tests.slice()
 | 
			
		||||
          }
 | 
			
		||||
        )))
 | 
			
		||||
        .flatMap(common.detectLoops$)
 | 
			
		||||
        .flatMap(({ err, code, data, userTests }) => {
 | 
			
		||||
            if (err) {
 | 
			
		||||
              return Observable.throw(err);
 | 
			
		||||
            }
 | 
			
		||||
        return addLoopProtect(combinedCode);
 | 
			
		||||
      })
 | 
			
		||||
      .flatMap(code => updatePreview$(code))
 | 
			
		||||
      .flatMap(code => {
 | 
			
		||||
        let output;
 | 
			
		||||
 | 
			
		||||
            // run tests
 | 
			
		||||
            // for now these are running in the browser
 | 
			
		||||
            return common.runTests$({
 | 
			
		||||
              data,
 | 
			
		||||
              code,
 | 
			
		||||
              userTests,
 | 
			
		||||
              originalCode,
 | 
			
		||||
              output: data.output.replace(/\\\"/gi, '')
 | 
			
		||||
            });
 | 
			
		||||
        if (
 | 
			
		||||
          challengeType === challengeTypes.HTML &&
 | 
			
		||||
          common.hasJs(code)
 | 
			
		||||
        ) {
 | 
			
		||||
          output = common.getJsOutput(common.getJsFromHtml(code));
 | 
			
		||||
        } else if (challengeType !== challengeTypes.HTML) {
 | 
			
		||||
          output = common.getJsOutput(combinedCode);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
})(window);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,13 @@ window.common = (function(global) {
 | 
			
		||||
    common = { init: [] }
 | 
			
		||||
  } = 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 = `
 | 
			
		||||
<script>
 | 
			
		||||
  window.$ = parent.$.proxy(parent.$.fn.find, parent.$(document));
 | 
			
		||||
</script>
 | 
			
		||||
<link
 | 
			
		||||
  rel='stylesheet'
 | 
			
		||||
  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
 | 
			
		||||
  common.runPreviewTests$ =
 | 
			
		||||
    () => Observable.throw({ err: new Error('run preview not enabled') });
 | 
			
		||||
    () => Observable.throw(new Error('run preview not enabled'));
 | 
			
		||||
 | 
			
		||||
  common.updatePreview$ = function updatePreview$(code = '') {
 | 
			
		||||
    const previewFrame = document.getElementById('preview');
 | 
			
		||||
    const preview = previewFrame.contentDocument ||
 | 
			
		||||
      previewFrame.contentWindow.document;
 | 
			
		||||
    if (!preview) {
 | 
			
		||||
      return Observable.just(code);
 | 
			
		||||
    }
 | 
			
		||||
    const preview = common.getIframe('preview');
 | 
			
		||||
 | 
			
		||||
    return iFrameScript$
 | 
			
		||||
      .map(script => `<script>${script}</script>`)
 | 
			
		||||
@@ -55,7 +56,10 @@ window.common = (function(global) {
 | 
			
		||||
        // now we filter false values and wait for the first true
 | 
			
		||||
        return common.previewReady$
 | 
			
		||||
          .filter(ready => ready)
 | 
			
		||||
          .first();
 | 
			
		||||
          .first()
 | 
			
		||||
          // the delay here is to give code within the iframe
 | 
			
		||||
          // control to run
 | 
			
		||||
          .delay(100);
 | 
			
		||||
      })
 | 
			
		||||
      .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 */
 | 
			
		||||
window.__$ = parent.$;
 | 
			
		||||
window.__$(function() {
 | 
			
		||||
// the $ on the iframe window object is the same
 | 
			
		||||
// as the one used on the main site, but
 | 
			
		||||
// uses the iframe document as the context
 | 
			
		||||
window.$(document).ready(function() {
 | 
			
		||||
  var _ = parent._;
 | 
			
		||||
  var Rx = parent.Rx;
 | 
			
		||||
  var chai = parent.chai;
 | 
			
		||||
  var assert = chai.assert;
 | 
			
		||||
  var tests = parent.tests;
 | 
			
		||||
  var common = parent.common;
 | 
			
		||||
  var editor = common.editor.getValue();
 | 
			
		||||
  // change the context of $ so it uses the iFrame for testing
 | 
			
		||||
  var $ = __$.proxy(__$.fn.find, __$(document));
 | 
			
		||||
 | 
			
		||||
  common.getJsOutput = function evalJs(code = '') {
 | 
			
		||||
    let output;
 | 
			
		||||
    try {
 | 
			
		||||
      /* eslint-disable no-eval */
 | 
			
		||||
      output = eval(code);
 | 
			
		||||
      /* eslint-enable no-eval */
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      window.__err = e;
 | 
			
		||||
    }
 | 
			
		||||
    return output;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  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)
 | 
			
		||||
        .map(test => {
 | 
			
		||||
          const userTest = {};
 | 
			
		||||
          common.appendToOutputDisplay('');
 | 
			
		||||
          try {
 | 
			
		||||
            /* eslint-disable no-eval */
 | 
			
		||||
            eval(test);
 | 
			
		||||
@@ -32,12 +54,13 @@ window.__$(function() {
 | 
			
		||||
          return userTest;
 | 
			
		||||
        })
 | 
			
		||||
        .toArray()
 | 
			
		||||
        .map(tests => ({ ...rest, tests }));
 | 
			
		||||
        .map(tests => ({ ...rest, tests, originalCode }));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  // now that the runPreviewTest$ is defined
 | 
			
		||||
  // we set the subject to true
 | 
			
		||||
  // this will let the updatePreview
 | 
			
		||||
  // script now that we are ready.
 | 
			
		||||
  console.log('second');
 | 
			
		||||
  common.previewReady$.onNext(true);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								gulpfile.js
									
									
									
									
									
								
							@@ -18,6 +18,7 @@ var Rx = require('rx'),
 | 
			
		||||
  uglify = require('gulp-uglify'),
 | 
			
		||||
  merge = require('merge-stream'),
 | 
			
		||||
  babel = require('gulp-babel'),
 | 
			
		||||
  sourcemaps = require('gulp-sourcemaps'),
 | 
			
		||||
 | 
			
		||||
  // react app
 | 
			
		||||
  webpack = require('webpack-stream'),
 | 
			
		||||
@@ -85,7 +86,8 @@ var paths = {
 | 
			
		||||
    'public/bower_components/CodeMirror/mode/xml/xml.js',
 | 
			
		||||
    'public/bower_components/CodeMirror/mode/css/css.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: [
 | 
			
		||||
@@ -103,20 +105,20 @@ var paths = {
 | 
			
		||||
  js: [
 | 
			
		||||
    'client/main.js',
 | 
			
		||||
    'client/iFrameScripts.js',
 | 
			
		||||
    'client/plugin.js',
 | 
			
		||||
    'client/faux.js'
 | 
			
		||||
    'client/plugin.js'
 | 
			
		||||
  ],
 | 
			
		||||
 | 
			
		||||
  commonFramework: [
 | 
			
		||||
    'init',
 | 
			
		||||
    'bindings',
 | 
			
		||||
    'add-test-to-string',
 | 
			
		||||
    'add-faux-stream',
 | 
			
		||||
    'code-storage',
 | 
			
		||||
    'code-uri',
 | 
			
		||||
    'add-loop-protect',
 | 
			
		||||
    'get-iframe',
 | 
			
		||||
    'update-preview',
 | 
			
		||||
    'create-editor',
 | 
			
		||||
    'detect-unsafe-code-stream',
 | 
			
		||||
    'detect-loops-stream',
 | 
			
		||||
    'display-test-results',
 | 
			
		||||
    'execute-challenge-stream',
 | 
			
		||||
    'output-display',
 | 
			
		||||
@@ -125,7 +127,6 @@ var paths = {
 | 
			
		||||
    'run-tests-stream',
 | 
			
		||||
    'show-completion',
 | 
			
		||||
    'step-challenge',
 | 
			
		||||
    'update-preview',
 | 
			
		||||
    'end'
 | 
			
		||||
  ],
 | 
			
		||||
 | 
			
		||||
@@ -434,7 +435,9 @@ gulp.task('dependents', ['js'], function() {
 | 
			
		||||
  return gulp.src(formatCommonFrameworkPaths.call(paths.commonFramework))
 | 
			
		||||
    .pipe(plumber({ errorHandler: errorHandler }))
 | 
			
		||||
    .pipe(babel())
 | 
			
		||||
    .pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
 | 
			
		||||
    .pipe(concat('commonFramework.js'))
 | 
			
		||||
    .pipe(__DEV__ ? sourcemaps.write() : gutil.noop())
 | 
			
		||||
    .pipe(__DEV__ ? gutil.noop() : uglify())
 | 
			
		||||
    .pipe(revReplace({ manifest: manifest }))
 | 
			
		||||
    .pipe(gulp.dest(dest))
 | 
			
		||||
 
 | 
			
		||||
@@ -134,6 +134,7 @@
 | 
			
		||||
    "browser-sync": "^2.9.12",
 | 
			
		||||
    "chai": "^3.4.0",
 | 
			
		||||
    "envify": "^3.4.0",
 | 
			
		||||
    "gulp-sourcemaps": "^1.6.0",
 | 
			
		||||
    "istanbul": "~0.4.0",
 | 
			
		||||
    "jsonlint": "^1.6.2",
 | 
			
		||||
    "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