mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
always loadScript with a script tag (#6411)
to avoid Content Security Policy unsafe-line violations
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
import loadScript from "discourse/lib/load-script";
|
import loadScript from "discourse/lib/load-script";
|
||||||
import { observes } from "ember-addons/ember-computed-decorators";
|
import { observes } from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
const LOAD_ASYNC = !Ember.testing;
|
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
mode: "css",
|
mode: "css",
|
||||||
classNames: ["ace-wrapper"],
|
classNames: ["ace-wrapper"],
|
||||||
@@ -26,7 +24,7 @@ export default Ember.Component.extend({
|
|||||||
|
|
||||||
@observes("mode")
|
@observes("mode")
|
||||||
modeChanged() {
|
modeChanged() {
|
||||||
if (LOAD_ASYNC && this._editor && !this._skipContentChangeEvent) {
|
if (this._editor && !this._skipContentChangeEvent) {
|
||||||
this._editor.getSession().setMode("ace/mode/" + this.get("mode"));
|
this._editor.getSession().setMode("ace/mode/" + this.get("mode"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -71,21 +69,17 @@ export default Ember.Component.extend({
|
|||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
loadScript("/javascripts/ace/ace.js", { scriptTag: true }).then(() => {
|
loadScript("/javascripts/ace/ace.js").then(() => {
|
||||||
window.ace.require(["ace/ace"], loadedAce => {
|
window.ace.require(["ace/ace"], loadedAce => {
|
||||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
if (!this.element || this.isDestroying || this.isDestroyed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const editor = loadedAce.edit(this.$(".ace")[0]);
|
const editor = loadedAce.edit(this.$(".ace")[0]);
|
||||||
|
|
||||||
if (LOAD_ASYNC) {
|
editor.setTheme("ace/theme/chrome");
|
||||||
editor.setTheme("ace/theme/chrome");
|
|
||||||
}
|
|
||||||
editor.setShowPrintMargin(false);
|
editor.setShowPrintMargin(false);
|
||||||
editor.setOptions({ fontSize: "14px" });
|
editor.setOptions({ fontSize: "14px" });
|
||||||
if (LOAD_ASYNC) {
|
editor.getSession().setMode("ace/mode/" + this.get("mode"));
|
||||||
editor.getSession().setMode("ace/mode/" + this.get("mode"));
|
|
||||||
}
|
|
||||||
editor.on("change", () => {
|
editor.on("change", () => {
|
||||||
this._skipContentChangeEvent = true;
|
this._skipContentChangeEvent = true;
|
||||||
this.set("content", editor.getSession().getValue());
|
this.set("content", editor.getSession().getValue());
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function loadScript(url, opts) {
|
|||||||
$("script").each((i, tag) => {
|
$("script").each((i, tag) => {
|
||||||
const src = tag.getAttribute("src");
|
const src = tag.getAttribute("src");
|
||||||
|
|
||||||
if (src && (opts.scriptTag || src !== url)) {
|
if (src && src !== url) {
|
||||||
_loaded[tag.getAttribute("src")] = true;
|
_loaded[tag.getAttribute("src")] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -86,22 +86,15 @@ export default function loadScript(url, opts) {
|
|||||||
cdnUrl = Discourse.CDN.replace(/\/$/, "") + url;
|
cdnUrl = Discourse.CDN.replace(/\/$/, "") + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some javascript depends on the path of where it is loaded (ace editor)
|
if (opts.css) {
|
||||||
// to dynamically load more JS. In that case, add the `scriptTag: true`
|
|
||||||
// option.
|
|
||||||
if (opts.scriptTag) {
|
|
||||||
if (Ember.testing) {
|
|
||||||
throw new Error(
|
|
||||||
`In test mode scripts cannot be loaded async ${cdnUrl}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
loadWithTag(cdnUrl, cb);
|
|
||||||
} else {
|
|
||||||
ajax({
|
ajax({
|
||||||
url: cdnUrl,
|
url: cdnUrl,
|
||||||
dataType: opts.css ? "text" : "script",
|
dataType: "text",
|
||||||
cache: true
|
cache: true
|
||||||
}).then(cb);
|
}).then(cb);
|
||||||
|
} else {
|
||||||
|
// Always load JavaScript with script tag to avoid Content Security Policy inline violations
|
||||||
|
loadWithTag(cdnUrl, cb);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
24
test/javascripts/lib/load-script-test.js.es6
Normal file
24
test/javascripts/lib/load-script-test.js.es6
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import loadScript from "discourse/lib/load-script";
|
||||||
|
|
||||||
|
QUnit.module("lib:load-script");
|
||||||
|
|
||||||
|
QUnit.test(
|
||||||
|
"load with a script tag, and callbacks are only executed after script is loaded",
|
||||||
|
async assert => {
|
||||||
|
const src = "/javascripts/ace/ace.js";
|
||||||
|
|
||||||
|
await loadScript(src).then(() => {
|
||||||
|
assert.ok(
|
||||||
|
typeof ace !== "undefined",
|
||||||
|
"callbacks should only be executed after the script has fully loaded"
|
||||||
|
);
|
||||||
|
|
||||||
|
// cannot use the `find` test helper here because the script tag is injected outside of the test sandbox frame
|
||||||
|
const scriptTags = Array.from(document.getElementsByTagName("script"));
|
||||||
|
assert.ok(
|
||||||
|
scriptTags.some(scriptTag => scriptTag.src.includes(src)),
|
||||||
|
"the script should be loaded with a script tag"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -52,7 +52,6 @@ window.MessageBus.stop();
|
|||||||
|
|
||||||
// Trick JSHint into allow document.write
|
// Trick JSHint into allow document.write
|
||||||
var d = document;
|
var d = document;
|
||||||
d.write('<script src="/javascripts/ace/ace.js"></script>');
|
|
||||||
d.write(
|
d.write(
|
||||||
'<div id="ember-testing-container"><div id="ember-testing"></div></div>'
|
'<div id="ember-testing-container"><div id="ember-testing"></div></div>'
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user