mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-26 05:07:35 +00:00
Make sure scripts in md preview are properly evaluated
Fixes #243454 This restores the previous behavior. If the default security settings are used, scripts will still block blocked by the CSP. If you fully disable the security settings, then we'll try to run them
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
"highlight.js": "^11.8.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-front-matter": "^0.2.4",
|
||||
"morphdom": "^2.6.1",
|
||||
"morphdom": "^2.7.4",
|
||||
"picomatch": "^2.3.1",
|
||||
"punycode": "^2.3.1",
|
||||
"vscode-languageclient": "^8.0.2",
|
||||
@@ -501,9 +501,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/morphdom": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.6.1.tgz",
|
||||
"integrity": "sha512-Y8YRbAEP3eKykroIBWrjcfMw7mmwJfjhqdpSvoqinu8Y702nAwikpXcNFDiIkyvfCLxLM9Wu95RZqo4a9jFBaA=="
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.4.tgz",
|
||||
"integrity": "sha512-ATTbWMgGa+FaMU3FhnFYB6WgulCqwf6opOll4CBzmVDTLvPMmUPrEv8CudmLPK0MESa64+6B89fWOxP3+YIlxQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-html-parser": {
|
||||
"version": "6.1.13",
|
||||
|
||||
@@ -768,7 +768,7 @@
|
||||
"highlight.js": "^11.8.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-front-matter": "^0.2.4",
|
||||
"morphdom": "^2.6.1",
|
||||
"morphdom": "^2.7.4",
|
||||
"picomatch": "^2.3.1",
|
||||
"punycode": "^2.3.1",
|
||||
"vscode-languageclient": "^8.0.2",
|
||||
|
||||
@@ -68,7 +68,14 @@ onceDocumentLoaded(() => {
|
||||
getRawData('data-initial-md-content'),
|
||||
'text/html'
|
||||
);
|
||||
document.body.append(...markDownHtml.body.children);
|
||||
|
||||
const newElements = [...markDownHtml.body.children];
|
||||
document.body.append(...newElements);
|
||||
for (const el of newElements) {
|
||||
if (el instanceof HTMLElement) {
|
||||
domEval(el);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore
|
||||
const scrollProgress = state.scrollProgress;
|
||||
@@ -216,46 +223,11 @@ window.addEventListener('message', async event => {
|
||||
}
|
||||
|
||||
if (data.source !== documentResource) {
|
||||
root.replaceWith(newContent.querySelector('.markdown-body')!);
|
||||
documentResource = data.source;
|
||||
const newBody = newContent.querySelector('.markdown-body')!;
|
||||
root.replaceWith(newBody);
|
||||
domEval(newBody);
|
||||
} else {
|
||||
const skippedAttrs = [
|
||||
'open', // for details
|
||||
];
|
||||
|
||||
// Compare two elements but some elements
|
||||
const areEqual = (a: Element, b: Element): boolean => {
|
||||
if (a.isEqualNode(b)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.tagName !== b.tagName || a.textContent !== b.textContent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const aAttrs = [...a.attributes].filter(attr => !skippedAttrs.includes(attr.name));
|
||||
const bAttrs = [...b.attributes].filter(attr => !skippedAttrs.includes(attr.name));
|
||||
if (aAttrs.length !== bAttrs.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < aAttrs.length; ++i) {
|
||||
const aAttr = aAttrs[i];
|
||||
const bAttr = bAttrs[i];
|
||||
if (aAttr.name !== bAttr.name) {
|
||||
return false;
|
||||
}
|
||||
if (aAttr.value !== bAttr.value && aAttr.name !== 'data-line') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const aChildren = Array.from(a.children);
|
||||
const bChildren = Array.from(b.children);
|
||||
|
||||
return aChildren.length === bChildren.length && aChildren.every((x, i) => areEqual(x, bChildren[i]));
|
||||
};
|
||||
|
||||
const newRoot = newContent.querySelector('.markdown-body')!;
|
||||
|
||||
// Move styles to head
|
||||
@@ -265,10 +237,11 @@ window.addEventListener('message', async event => {
|
||||
style.remove();
|
||||
}
|
||||
newRoot.prepend(...styles);
|
||||
|
||||
morphdom(root, newRoot, {
|
||||
childrenOnly: true,
|
||||
onBeforeElUpdated: (fromEl, toEl) => {
|
||||
if (areEqual(fromEl, toEl)) {
|
||||
onBeforeElUpdated: (fromEl: Element, toEl: Element) => {
|
||||
if (areNodesEqual(fromEl, toEl)) {
|
||||
// areEqual doesn't look at `data-line` so copy those over manually
|
||||
const fromLines = fromEl.querySelectorAll('[data-line]');
|
||||
const toLines = toEl.querySelectorAll('[data-line]');
|
||||
@@ -294,8 +267,14 @@ window.addEventListener('message', async event => {
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
addChild: (parentNode: Node, childNode: Node) => {
|
||||
parentNode.appendChild(childNode);
|
||||
if (childNode instanceof HTMLElement) {
|
||||
domEval(childNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
} as any);
|
||||
}
|
||||
|
||||
++documentVersion;
|
||||
@@ -383,3 +362,72 @@ function updateScrollProgress() {
|
||||
vscode.setState(state);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares two nodes for morphdom to see if they are equal.
|
||||
*
|
||||
* This skips some attributes that should not cause equality to fail.
|
||||
*/
|
||||
function areNodesEqual(a: Element, b: Element): boolean {
|
||||
const skippedAttrs = [
|
||||
'open', // for details
|
||||
];
|
||||
|
||||
if (a.isEqualNode(b)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.tagName !== b.tagName || a.textContent !== b.textContent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const aAttrs = [...a.attributes].filter(attr => !skippedAttrs.includes(attr.name));
|
||||
const bAttrs = [...b.attributes].filter(attr => !skippedAttrs.includes(attr.name));
|
||||
if (aAttrs.length !== bAttrs.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < aAttrs.length; ++i) {
|
||||
const aAttr = aAttrs[i];
|
||||
const bAttr = bAttrs[i];
|
||||
if (aAttr.name !== bAttr.name) {
|
||||
return false;
|
||||
}
|
||||
if (aAttr.value !== bAttr.value && aAttr.name !== 'data-line') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const aChildren = Array.from(a.children);
|
||||
const bChildren = Array.from(b.children);
|
||||
|
||||
return aChildren.length === bChildren.length && aChildren.every((x, i) => areNodesEqual(x, bChildren[i]));
|
||||
}
|
||||
|
||||
|
||||
function domEval(el: Element): void {
|
||||
const preservedScriptAttributes: (keyof HTMLScriptElement)[] = [
|
||||
'type', 'src', 'nonce', 'noModule', 'async',
|
||||
];
|
||||
|
||||
const scriptNodes = el.tagName === 'SCRIPT' ? [el] : Array.from(el.getElementsByTagName('script'));
|
||||
|
||||
for (const node of scriptNodes) {
|
||||
if (!(node instanceof HTMLElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const scriptTag = document.createElement('script');
|
||||
const trustedScript = node.innerText;
|
||||
scriptTag.text = trustedScript as string;
|
||||
for (const key of preservedScriptAttributes) {
|
||||
const val = node.getAttribute && node.getAttribute(key);
|
||||
if (val) {
|
||||
scriptTag.setAttribute(key, val as any);
|
||||
}
|
||||
}
|
||||
|
||||
node.insertAdjacentElement('afterend', scriptTag);
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user