diff --git a/app/assets/javascripts/discourse/lib/highlight-syntax.js.es6 b/app/assets/javascripts/discourse/lib/highlight-syntax.js.es6 index a30c86e303d..39be2a1d330 100644 --- a/app/assets/javascripts/discourse/lib/highlight-syntax.js.es6 +++ b/app/assets/javascripts/discourse/lib/highlight-syntax.js.es6 @@ -3,8 +3,12 @@ import loadScript from 'discourse/lib/load-script'; export default function highlightSyntax($elem) { - const selector = Discourse.SiteSettings.autohighlight_all_code ? 'pre code' : 'pre code[class]'; + const selector = Discourse.SiteSettings.autohighlight_all_code ? 'pre code' : 'pre code[class]', + path = Discourse.HighlightJSPath; + + if (!path) { return; } + $(selector, $elem).each(function(i, e) { - loadScript(Discourse.HighlightJSPath).then(() => hljs.highlightBlock(e)); + loadScript(path).then(() => hljs.highlightBlock(e)); }); } diff --git a/app/assets/javascripts/discourse/mixins/ajax.js b/app/assets/javascripts/discourse/mixins/ajax.js index 37e0ccc65a2..cc85af1db86 100644 --- a/app/assets/javascripts/discourse/mixins/ajax.js +++ b/app/assets/javascripts/discourse/mixins/ajax.js @@ -49,9 +49,11 @@ Discourse.Ajax = Em.Mixin.create({ var performAjax = function(resolve, reject) { + args.headers = args.headers || {}; + if (_trackView && (!args.type || args.type === "GET")) { _trackView = false; - args.headers = { 'Discourse-Track-View': true }; + args.headers['Discourse-Track-View'] = true; } args.success = function(xhr) { @@ -80,6 +82,10 @@ Discourse.Ajax = Em.Mixin.create({ if (!args.type) args.type = 'GET'; if (!args.dataType && args.type.toUpperCase() === 'GET') args.dataType = 'json'; + if (args.dataType === "script") { + args.headers['Discourse-Script'] = true; + } + if (args.type === 'GET' && args.cache !== true) { args.cache = false; } diff --git a/test/javascripts/helpers/create-pretender.js.es6 b/test/javascripts/helpers/create-pretender.js.es6 index 8eb11ad1315..06ebebbd741 100644 --- a/test/javascripts/helpers/create-pretender.js.es6 +++ b/test/javascripts/helpers/create-pretender.js.es6 @@ -1,7 +1,7 @@ function parsePostData(query) { - var result = {}; + const result = {}; query.split("&").forEach(function(part) { - var item = part.split("="); + const item = part.split("="); result[item[0]] = decodeURIComponent(item[1]); }); return result; @@ -25,15 +25,15 @@ const _widgets = [ ]; export default function() { - var server = new Pretender(function() { + const server = new Pretender(function() { // Load any fixtures automatically - var self = this; + const self = this; Ember.keys(require._eak_seen).forEach(function(entry) { if (/^fixtures/.test(entry)) { - var fixture = require(entry, null, null, true); + const fixture = require(entry, null, null, true); if (fixture && fixture.default) { - var obj = fixture.default; + const obj = fixture.default; Ember.keys(obj).forEach(function(url) { self.get(url, function() { return response(obj[url]); @@ -55,17 +55,8 @@ export default function() { return response({}); }); - this.get('/javascripts/jquery.magnific-popup-min.js', function() { - return response({}); - }); - - - this.get('/highlight.js', function() { - return response({}); - }); - this.post('/session', function(request) { - var data = parsePostData(request.requestBody); + const data = parsePostData(request.requestBody); if (data.password === 'correct') { return response({username: 'eviltrout'}); @@ -130,10 +121,14 @@ export default function() { }; server.unhandledRequest = function(verb, path) { - var error = 'Unhandled request in test environment: ' + path + ' (' + verb + ')'; + const error = 'Unhandled request in test environment: ' + path + ' (' + verb + ')'; window.console.error(error); throw error; }; + server.checkPassthrough = function(request) { + return request.requestHeaders['Discourse-Script']; + }; + return server; } diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js index 5935a81552b..bf87e3d2549 100644 --- a/test/javascripts/test_helper.js +++ b/test/javascripts/test_helper.js @@ -75,9 +75,6 @@ Discourse.injectTestHelpers(); Discourse.runInitializers(); Discourse.start(); Discourse.Route.mapRoutes(); -Discourse.HighlightJSPath = "/highlight.js"; -// messy but we need to pass tests -window.hljs = {highlightBlock: function(){}} // disable logster error reporting if (window.Logster) { diff --git a/vendor/assets/javascripts/pretender.js b/vendor/assets/javascripts/pretender.js index b416deb6a4c..d86f40dc1ca 100644 --- a/vendor/assets/javascripts/pretender.js +++ b/vendor/assets/javascripts/pretender.js @@ -19,6 +19,7 @@ function Pretender(maps){ this.handlers = []; this.handledRequests = []; + this.passthroughRequests = []; this.unhandledRequests = []; // reference the native XMLHttpRequest object so @@ -29,6 +30,9 @@ function Pretender(maps){ // the route map. window.XMLHttpRequest = interceptor(this); + // "start" the server + this.running = true; + // trigger the route map DSL. maps.call(this); } @@ -41,10 +45,65 @@ function interceptor(pretender) { // extend var proto = new FakeXMLHttpRequest(); proto.send = function send(){ - FakeXMLHttpRequest.prototype.send.apply(this, arguments); - pretender.handleRequest(this); + if (!pretender.running) { + throw new Error('You shut down a Pretender instance while there was a pending request. '+ + 'That request just tried to complete. Check to see if you accidentally shut down '+ + 'a pretender earlier than you intended to'); + } + + if (!pretender.checkPassthrough(this)) { + FakeXMLHttpRequest.prototype.send.apply(this, arguments); + pretender.handleRequest(this); + } + else { + var xhr = createPassthrough(this); + xhr.send.apply(xhr, arguments); + } }; + // passthrough handling + var evts = ['load', 'error', 'timeout', 'progress', 'abort', 'readystatechange']; + var lifecycleProps = ['readyState', 'responseText', 'responseXML', 'status', 'statusText']; + function createPassthrough(fakeXHR) { + var xhr = fakeXHR._passthroughRequest = new pretender._nativeXMLHttpRequest(); + // listen to all events to update lifecycle properties + for (var i = 0; i < evts.length; i++) (function(evt) { + xhr['on' + evt] = function(e) { + // update lifecycle props on each event + for (var i = 0; i < lifecycleProps.length; i++) { + var prop = lifecycleProps[i]; + if (xhr[prop]) { + fakeXHR[prop] = xhr[prop]; + } + } + // fire fake events where applicable + fakeXHR.dispatchEvent(evt, e); + if (fakeXHR['on' + evt]) { + fakeXHR['on' + evt](e); + } + }; + })(evts[i]); + xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password); + xhr.timeout = fakeXHR.timeout; + xhr.withCredentials = fakeXHR.withCredentials; + return xhr; + } + proto._passthroughCheck = function(method, arguments) { + if (this._passthroughRequest) { + return this._passthroughRequest[method].apply(this._passthroughRequest, arguments); + } + return FakeXMLHttpRequest.prototype[method].apply(this, arguments); + } + proto.abort = function abort(){ + return this._passthroughCheck('abort', arguments); + } + proto.getResponseHeader = function getResponseHeader(){ + return this._passthroughCheck('getResponseHeader', arguments); + } + proto.getAllResponseHeaders = function getAllResponseHeaders(){ + return this._passthroughCheck('getAllResponseHeaders', arguments); + } + FakeRequest.prototype = proto; return FakeRequest; } @@ -55,6 +114,22 @@ function verbify(verb){ }; } +function throwIfURLDetected(url){ + var HTTP_REGEXP = /^https?/; + var message; + + if(HTTP_REGEXP.test(url)) { + var parser = window.document.createElement('a'); + parser.href = url; + + message = "Pretender will not respond to requests for URLs. It is not possible to accurately simluate the browser's CSP. "+ + "Remove the " + parser.protocol +"//"+ parser.hostname +" from " + url + " and try again"; + throw new Error(message) + } +} + +var PASSTHROUGH = {}; + Pretender.prototype = { get: verbify('GET'), post: verbify('POST'), @@ -69,9 +144,29 @@ Pretender.prototype = { var registry = this.registry[verb]; registry.add([{path: path, handler: handler}]); }, + passthrough: PASSTHROUGH, + checkPassthrough: function(request) { + var verb = request.method.toUpperCase(); + var path = request.url; + + throwIfURLDetected(path); + + verb = verb.toUpperCase(); + + var recognized = this.registry[verb].recognize(path); + var match = recognized && recognized[0]; + if (match && match.handler == PASSTHROUGH) { + this.passthroughRequests.push(request); + this.passthroughRequest(verb, path, request); + return true; + } + + return false; + }, handleRequest: function handleRequest(request){ var verb = request.method.toUpperCase(); var path = request.url; + var handler = this._handlerFor(verb, path, request); if (handler) { @@ -81,9 +176,10 @@ Pretender.prototype = { try { var statusHeadersAndBody = handler.handler(request), status = statusHeadersAndBody[0], - headers = statusHeadersAndBody[1], + headers = this.prepareHeaders(statusHeadersAndBody[1]), body = this.prepareBody(statusHeadersAndBody[2]); request.respond(status, headers, body); + this.handledRequest(verb, path, request); } catch (error) { this.erroredRequest(verb, path, request, error); @@ -93,8 +189,10 @@ Pretender.prototype = { this.unhandledRequest(verb, path, request); } }, - prepareBody: function(body){ return body; }, - handledRequest: function(verb, path, request){/* no-op */}, + prepareBody: function(body) { return body; }, + prepareHeaders: function(headers) { return headers; }, + handledRequest: function(verb, path, request) { /* no-op */}, + passthroughRequest: function(verb, path, request) { /* no-op */}, unhandledRequest: function(verb, path, request) { throw new Error("Pretender intercepted "+verb+" "+path+" but no handler was defined for this type of request"); }, @@ -116,6 +214,9 @@ Pretender.prototype = { }, shutdown: function shutdown(){ window.XMLHttpRequest = this._nativeXMLHttpRequest; + + // "stop" the server + this.running = false; } };