diff --git a/.gitignore b/.gitignore index cca22d9156..328af63790 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ js/core/biblio.js js/core/data-cite.js js/core/data-include.js js/core/default-root-attr.js +js/core/highlight.js js/core/include-config.js js/core/override-configuration.js js/core/post-process.js @@ -22,3 +23,4 @@ js/core/pre-process.js js/core/pubsubhub.js js/core/remove-respec.js js/core/respec-ready.js +js/core/worker.js diff --git a/js/core/highlight.js b/js/core/highlight.js deleted file mode 100644 index 4d2034769d..0000000000 --- a/js/core/highlight.js +++ /dev/null @@ -1,44 +0,0 @@ -// Module core/highlight -// Does syntax highlighting to all pre and code that have a class of "highlight" -// An improvement would be to use web workers to do the highlighting. -"use strict"; -define( - [ - "core/pubsubhub", - "core/utils", - "deps/highlight", - "deps/text!core/css/github.css", - ], - function(pubsubhub, utils, hljs, ghCss) { - // Opportunistically insert the style into the head to reduce FOUC. - var codeStyle = document.createElement("style"); - codeStyle.textContent = ghCss; - var swapStyleOwner = utils.makeOwnerSwapper(codeStyle); - swapStyleOwner(document.head); - return { - run: function(conf, doc, cb) { - // Nothing to do - if (conf.noHighlightCSS) { - return cb(); - } - - if (codeStyle.ownerDocument !== doc) { - swapStyleOwner(doc.head); - } - - if (doc.querySelector("highlight")) { - pubsubhub.pub("warn", "pre elements don't need a 'highlight' class anymore."); - } - - Array - .from( - doc.querySelectorAll("pre:not(.idl)") - ) - .forEach(function(element) { - hljs.highlightBlock(element); - }); - cb(); - } - }; - } -); diff --git a/js/core/markdown.js b/js/core/markdown.js index b2b0ecef0e..380b848487 100644 --- a/js/core/markdown.js +++ b/js/core/markdown.js @@ -270,8 +270,8 @@ define([ .replace(/\n\s*" item !== "highlight") + .map(item => item.toLowerCase()); +} +let doneResolver; +let doneRejector; +export const done = new Promise((resolve, reject) => { + doneResolver = resolve; + doneRejector = reject; +}); + +export async function run(conf, doc, cb) { + // Nothing to do + if (conf.noHighlightCSS) { + doneResolver(); + return cb(); + } + + if (codeStyle.ownerDocument !== doc) { + swapStyleOwner(doc.head); + } + + if (doc.querySelector(".highlight")) { + pub("warn", "pre elements don't need a 'highlight' class anymore."); + } + + const promisesToHighlight = Array + .from( + doc.querySelectorAll("pre:not(.idl):not(.highlightdone)") + ) + .map(element => { + return new Promise((resolve, reject) => { + if (element.textContent.trim() === "") { + return resolve(); // no work to do + } + const msg = { + action: "highlight", + code: element.textContent, + id: Math.random().toString(), + languages: getLanguageHint(element.classList), + }; + + worker.postMessage(msg); + worker.addEventListener("message", function listener(ev) { + if (ev.data.id !== msg.id) { + return; // not for us! + } + worker.removeEventListener("message", listener); + element.innerHTML = ev.data.value; + element.classList.add("hljs"); + resolve(); + }); + setTimeout(() => { + const errMsg = "Timeout error trying to process: " + msg.code; + const err = new Error(errMsg); + reject(err); + }, 5000); + }); + }); + try { + await Promise.all(promisesToHighlight); + doneResolver(); + } catch (err) { + console.error(err); + doneRejector(err); + } + cb(); +} diff --git a/src/core/worker.js b/src/core/worker.js new file mode 100644 index 0000000000..cdc4166d24 --- /dev/null +++ b/src/core/worker.js @@ -0,0 +1,21 @@ +/** + * Module core/worker + * + * Exports a Web Worker for ReSpec, allowing for + * multi-threaded processing of things. + */ + +// Opportunistically preload syntax highlighter, which is used by the worker +import utils from "core/utils"; +import workerScript from "deps/text!../../worker/respec-worker.js"; +// Opportunistically preload syntax highlighter +const hint = { + hint: "preload", + href: "https://www.w3.org/Tools/respec/respec-highlight.js", + as: "script", +}; +const link = utils.createResourceHint(hint); +document.head.appendChild(link); + +const workerURL = URL.createObjectURL(new Blob([workerScript], {type : 'application/javascript'})); +export const worker = new Worker(workerURL); diff --git a/tests/spec/core/highlight-spec.js b/tests/spec/core/highlight-spec.js index 10b4cefa8c..bd05aa9374 100644 --- a/tests/spec/core/highlight-spec.js +++ b/tests/spec/core/highlight-spec.js @@ -1,19 +1,20 @@ "use strict"; describe("Core — Highlight", function() { - afterAll(function(done) { + afterAll(done => { flushIframes(); done(); }); - it("should't highlight idl blocks", function(done){ + it("shouldn't highlight idl blocks", done => { var ops = { config: makeBasicConfig(), - body: makeDefaultBody() + - "
"+
-          "[Constructor]interface Dahut : Mammal {" +
-          "  const unsigned short DEXTROGYROUS = 1;" +
-          "  Dahut turnAround(float angle, boolean fall);" +
-          "};
" + body: makeDefaultBody() + ` +
+          [Constructor]interface Dahut : Mammal {
+            const unsigned short DEXTROGYROUS = 1;
+            Dahut turnAround(float angle, boolean fall);
+          };
+
` }; makeRSDoc(ops, function(doc) { var pre = doc.querySelector("pre"); @@ -22,11 +23,17 @@ describe("Core — Highlight", function() { }).then(done); }); - it("should automatically highlight", function(done) { + it("should automatically highlight", done => { var ops = { config: makeBasicConfig(), body: makeDefaultBody() + - "
function () {\n  alert('foo');\n}
" + `
+
+            function foo() {
+              alert('foo');
+            }
+          
+
` }; makeRSDoc(ops, function(doc) { var pre = doc.querySelector("div.example pre"); @@ -35,11 +42,17 @@ describe("Core — Highlight", function() { }).then(done); }); - it("shouldn't highlight pre elements when told not to", function(done) { + it("shouldn't highlight pre elements when told not to", done => { var ops = { config: makeBasicConfig(), body: makeDefaultBody() + - "
function () {\n  alert('foo');\n}
" + `
+
+            function foo() {
+              alert('foo');
+            }
+          
+
` }; makeRSDoc(ops, function(doc) { var pre = doc.querySelector("div.example pre"); @@ -48,11 +61,17 @@ describe("Core — Highlight", function() { }).then(done); }); - it("should respect the noHighlightCSS by not highlighting anything", function(done) { + it("should respect the noHighlightCSS by not highlighting anything", done => { var ops = { config: Object.assign(makeBasicConfig(), { noHighlightCSS: true }), body: makeDefaultBody() + - "
function () {\n  alert('foo');\n}
" + `
+
+            function foo() {
+              alert('foo');
+            }
+          
+
` }; makeRSDoc(ops, function(doc) { var pre = doc.querySelector("#test"); diff --git a/tests/spec/core/markdown-spec.js b/tests/spec/core/markdown-spec.js index e21b1735bd..d16a7d9834 100644 --- a/tests/spec/core/markdown-spec.js +++ b/tests/spec/core/markdown-spec.js @@ -205,12 +205,12 @@ describe("Core - Markdown", function() { it("automatically links URLs in pre when missing (smoke test)", function(done) { var ops = { config: makeBasicConfig(), - body: makeDefaultBody() + - "
\n" +
-          "\t this won't link \n" +
-          "\t this will link: http://no-links-foo.com \n" +
-          "\t so will this: http://no-links-bar.com \n" +
-          "
\n\n\n"
+        body: makeDefaultBody() + `
+          
+ this won't link + this will link: http://no-links-foo.com + so will this: http://no-links-bar.com +
` }; ops.config.format = "markdown"; makeRSDoc(ops, function(doc) { @@ -224,11 +224,12 @@ describe("Core - Markdown", function() { it("replaces HTMLAnchors when present", function(done) { var ops = { config: makeBasicConfig(), - body: makeDefaultBody() + - "