From 86ef0e9c0c732623b5ea38a316a4d8b44aea941b Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 19 Sep 2022 15:13:41 +0200 Subject: [PATCH] Add Highlight.js webworker Added a webworker for highlighting content with Highlight.js --- .../components/source_viewer/constants.js | 3 -- .../plugins/utils/dependency_linker_util.js | 13 +----- .../plugins/utils/gemspec_linker.js | 5 +-- .../plugins/utils/package_json_linker.js | 5 +-- .../source_viewer/workers/highlight.js | 10 +++++ .../source_viewer/workers/highlight_utils.js | 15 +++++++ .../source_viewer/highlight_util_spec.js | 44 +++++++++++++++++++ .../utils/dependency_linker_util_spec.js | 2 +- 8 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight.js create mode 100644 app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js create mode 100644 spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js index 30f57f506a63de..e2d1a81ee2bd84 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js @@ -146,6 +146,3 @@ export const BIDI_CHAR_TOOLTIP = __( export const HLJS_COMMENT_SELECTOR = 'hljs-comment'; export const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight'; - -export const NPM_URL = 'https://npmjs.com/package'; -export const GEM_URL = 'https://rubygems.org/gems'; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util.js index dbe6812cf16d15..49704421d6eaed 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util.js @@ -1,16 +1,7 @@ import { escape } from 'lodash'; -import { setAttributes } from '~/lib/utils/dom_utils'; -export const createLink = (href, innerText) => { - // eslint-disable-next-line @gitlab/require-i18n-strings - const rel = 'nofollow noreferrer noopener'; - const link = document.createElement('a'); - - setAttributes(link, { href: escape(href), rel }); - link.textContent = innerText; - - return link.outerHTML; -}; +export const createLink = (href, innerText) => + `${escape(innerText)}`; export const generateHLJSOpenTag = (type, delimiter = '"') => `${delimiter}`; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/gemspec_linker.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/gemspec_linker.js index 35de8fd13d62fc..46c9dc38300a4b 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/gemspec_linker.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/gemspec_linker.js @@ -1,7 +1,6 @@ -import { joinPaths } from '~/lib/utils/url_utility'; -import { GEM_URL } from '../../constants'; import { createLink, generateHLJSOpenTag } from './dependency_linker_util'; +const GEM_URL = 'https://rubygems.org/gems/'; const methodRegex = '.*add_dependency.*|.*add_runtime_dependency.*|.*add_development_dependency.*'; const openTagRegex = generateHLJSOpenTag('string', '(&.*;)'); const closeTagRegex = '&.*'; @@ -24,7 +23,7 @@ const DEPENDENCY_REGEX = new RegExp( const handleReplace = (method, delimiter, packageName, closeTag, rest) => { // eslint-disable-next-line @gitlab/require-i18n-strings const openTag = generateHLJSOpenTag('string linked', delimiter); - const href = joinPaths(GEM_URL, packageName); + const href = `${GEM_URL}${packageName}`; const packageLink = createLink(href, packageName); return `${method}${openTag}${packageLink}${closeTag}${rest}`; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/package_json_linker.js b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/package_json_linker.js index 3c6fc23c138ced..4bfd5ec2431da1 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/package_json_linker.js +++ b/app/assets/javascripts/vue_shared/components/source_viewer/plugins/utils/package_json_linker.js @@ -1,8 +1,7 @@ import { unescape } from 'lodash'; -import { joinPaths } from '~/lib/utils/url_utility'; -import { NPM_URL } from '../../constants'; import { createLink, generateHLJSOpenTag } from './dependency_linker_util'; +const NPM_URL = 'https://npmjs.com/package/'; const attrOpenTag = generateHLJSOpenTag('attr'); const stringOpenTag = generateHLJSOpenTag('string'); const closeTag = '"'; @@ -20,7 +19,7 @@ const DEPENDENCY_REGEX = new RegExp( const handleReplace = (original, packageName, version, dependenciesToLink) => { const unescapedPackageName = unescape(packageName); const unescapedVersion = unescape(version); - const href = joinPaths(NPM_URL, unescapedPackageName); + const href = `${NPM_URL}${unescapedPackageName}`; const packageLink = createLink(href, unescapedPackageName); const versionLink = createLink(href, unescapedVersion); const closeAndOpenTag = `${closeTag}: ${attrOpenTag}`; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight.js b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight.js new file mode 100644 index 00000000000000..535e857d7a94b5 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight.js @@ -0,0 +1,10 @@ +import { highlight } from './highlight_utils'; + +/** + * A webworker for highlighting large amounts of content with Highlight.js + */ +// eslint-disable-next-line no-restricted-globals +self.addEventListener('message', ({ data: { fileType, content, language } }) => { + // eslint-disable-next-line no-restricted-globals + self.postMessage(highlight(fileType, content, language)); +}); diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js new file mode 100644 index 00000000000000..0da57f9e6fa417 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/workers/highlight_utils.js @@ -0,0 +1,15 @@ +import hljs from 'highlight.js/lib/core'; +import languageLoader from '~/content_editor/services/highlight_js_language_loader'; +import { registerPlugins } from '../plugins/index'; + +const initHighlightJs = async (fileType, content, language) => { + const languageDefinition = await languageLoader[language](); + + registerPlugins(hljs, fileType, content); + hljs.registerLanguage(language, languageDefinition.default); +}; + +export const highlight = (fileType, content, language) => { + initHighlightJs(fileType, content, language); + return hljs.highlight(content, { language }).value; +}; diff --git a/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js b/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js new file mode 100644 index 00000000000000..4a995e2fde16bc --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js @@ -0,0 +1,44 @@ +import hljs from 'highlight.js/lib/core'; +import languageLoader from '~/content_editor/services/highlight_js_language_loader'; +import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index'; +import { highlight } from '~/vue_shared/components/source_viewer/workers/highlight_utils'; + +jest.mock('highlight.js/lib/core', () => ({ + highlight: jest.fn().mockReturnValue({}), + registerLanguage: jest.fn(), +})); + +jest.mock('~/content_editor/services/highlight_js_language_loader', () => ({ + javascript: jest.fn().mockReturnValue({ default: jest.fn() }), +})); + +jest.mock('~/vue_shared/components/source_viewer/plugins/index', () => ({ + registerPlugins: jest.fn(), +})); + +const fileType = 'text'; +const content = 'function test() { return true };'; +const language = 'javascript'; + +describe('Highlight utility', () => { + beforeEach(() => highlight(fileType, content, language)); + + it('loads the language', () => { + expect(languageLoader.javascript).toHaveBeenCalled(); + }); + + it('registers the plugins', () => { + expect(registerPlugins).toHaveBeenCalled(); + }); + + it('registers the language', () => { + expect(hljs.registerLanguage).toHaveBeenCalledWith( + language, + languageLoader[language]().default, + ); + }); + + it('highlights the content', () => { + expect(hljs.highlight).toHaveBeenCalledWith(content, { language }); + }); +}); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js index 8079d5ad99a30e..e4ce07ec668eed 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js @@ -15,7 +15,7 @@ describe('createLink', () => { it('escapes the user-controlled content', () => { const unescapedXSS = ''; const escapedPackageName = '<script>XSS</script>'; - const escapedHref = '&lt;script&gt;XSS&lt;/script&gt;'; + const escapedHref = '<script>XSS</script>'; const href = `http://test.com/${unescapedXSS}`; const innerText = `testing${unescapedXSS}`; const result = `testing${escapedPackageName}`; -- GitLab