fix(dnfs): 前端代码节点改为隔离iframe沙箱运行,特效不再污染父页面导致运行后无法进入节点
This commit is contained in:
1 parent
2ff7908bc7
commit
f7ed1f785b
1 file changed
+25
-81
+25
-81
@@ -492,12 +492,13 @@ function renderSpLink(container, sp, appendNote) {
|
|||||||
appendNote();
|
appendNote();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 前端代码节点:审计(展开源码,textContent 防注入)+ 运行(注入当前页面执行,支持完整 HTML/CSS/JS)。
|
/* 前端代码节点:审计(展开源码,textContent 防注入)+ 运行(在隔离 iframe 沙箱中执行完整 HTML/CSS/JS)。
|
||||||
* 不使用 new Function/eval(CSP 未开 unsafe-eval);解析完整 HTML 文档后注入当前页面:
|
* 采用 iframe 沙箱而非注入父页面全局作用域,原因:
|
||||||
* - <style> 注入到页面 head
|
* - 特效脚本运行在独立 iframe 内,无法访问父页面 DOM/JS,任何报错、死循环、变量覆盖
|
||||||
* - <script> 在当前页面全局作用域执行(可操作整页 DOM 加特效)
|
* 都不会污染父页面,从而保证"运行特效后卡片点击/节点导航依然正常";
|
||||||
* - <body> 内容渲染到节点下方容器
|
* - iframe 全屏铺底且 pointer-events:none,不拦截父页面点击,视觉特效悬浮展示在页面上方;
|
||||||
* console.log/error/warn 输出捕获到下方输出区。仅管理员可发布(后端强制)。 */
|
* - 不使用 new Function/eval(CSP 未开 unsafe-eval),srcdoc 内联脚本由 CSP 'unsafe-inline' 放行。
|
||||||
|
* 仅管理员可发布(后端强制)。 */
|
||||||
function renderSpJs(container, sp, appendNote) {
|
function renderSpJs(container, sp, appendNote) {
|
||||||
var bar = document.createElement('div');
|
var bar = document.createElement('div');
|
||||||
bar.className = 'sp-actions';
|
bar.className = 'sp-actions';
|
||||||
@@ -514,6 +515,7 @@ function renderSpJs(container, sp, appendNote) {
|
|||||||
var out = document.createElement('div');
|
var out = document.createElement('div');
|
||||||
out.className = 'sp-run';
|
out.className = 'sp-run';
|
||||||
out.style.display = 'none';
|
out.style.display = 'none';
|
||||||
|
var frame = null; /* 当前特效 iframe 沙箱实例,null 表示未运行 */
|
||||||
audit_btn.addEventListener('click', function(e) {
|
audit_btn.addEventListener('click', function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var hidden = (pre.style.display === 'none');
|
var hidden = (pre.style.display === 'none');
|
||||||
@@ -522,83 +524,25 @@ function renderSpJs(container, sp, appendNote) {
|
|||||||
});
|
});
|
||||||
run_btn.addEventListener('click', function(e) {
|
run_btn.addEventListener('click', function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
out.style.display = out.style.display === 'none' ? 'block' : 'none';
|
/* 已运行:停止并移除特效沙箱 */
|
||||||
if (out.dataset.executed) return; // 已执行过,仅切换显示
|
if (frame) {
|
||||||
out.dataset.executed = '1';
|
frame.remove();
|
||||||
/* 本次执行专属输出缓冲:捕获脚本执行(含早期异步)期间的 console 输出,捕获窗口结束后恢复
|
frame = null;
|
||||||
* 应用自身 console,避免把应用日志(如 load_notes 打印的 pid/节点对象)刷进输出框干扰交互。 */
|
run_btn.textContent = '运行';
|
||||||
var lines = [];
|
out.style.display = 'none';
|
||||||
var _orig = window.console;
|
|
||||||
var restore = function() {
|
|
||||||
window.console.log = _orig.log.bind(_orig);
|
|
||||||
window.console.error = _orig.error.bind(_orig);
|
|
||||||
window.console.warn = _orig.warn.bind(_orig);
|
|
||||||
};
|
|
||||||
window.console.log = function() {
|
|
||||||
lines.push(Array.prototype.slice.call(arguments).join(' '));
|
|
||||||
_orig.log.apply(_orig, arguments);
|
|
||||||
};
|
|
||||||
window.console.error = function() {
|
|
||||||
lines.push('Error: ' + Array.prototype.slice.call(arguments).join(' '));
|
|
||||||
_orig.error.apply(_orig, arguments);
|
|
||||||
};
|
|
||||||
window.console.warn = function() {
|
|
||||||
lines.push('Warn: ' + Array.prototype.slice.call(arguments).join(' '));
|
|
||||||
_orig.warn.apply(_orig, arguments);
|
|
||||||
};
|
|
||||||
var errHandler = function(ev) {
|
|
||||||
lines.push('Error: ' + ev.message);
|
|
||||||
};
|
|
||||||
window.addEventListener('error', errHandler);
|
|
||||||
/* 解析完整 HTML 文档 */
|
|
||||||
var doc;
|
|
||||||
try {
|
|
||||||
doc = new DOMParser().parseFromString(sp.code, 'text/html');
|
|
||||||
} catch (err) {
|
|
||||||
lines.push('Error: 解析 HTML 失败: ' + (err.message || err));
|
|
||||||
restore();
|
|
||||||
window.removeEventListener('error', errHandler);
|
|
||||||
out.textContent = lines.join('\n');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* 注入 <style> 到页面 head */
|
/* 创建隔离 iframe 沙箱:完整前端代码(含 <style>/<script>/<body>)由此 srcdoc 承载,
|
||||||
Array.prototype.forEach.call(doc.querySelectorAll('style'), function(st) {
|
* 脚本在独立上下文执行,无法影响父页面导航;pointer-events:none 保证不拦截父页面点击。 */
|
||||||
var s = document.createElement('style');
|
frame = document.createElement('iframe');
|
||||||
s.textContent = st.textContent;
|
frame.className = 'sp-effect-frame';
|
||||||
document.head.appendChild(s);
|
frame.setAttribute('sandbox', 'allow-scripts');
|
||||||
});
|
frame.srcdoc = sp.code;
|
||||||
/* 注入 <script>:包一层 IIFE 隔离变量作用域,避免污染页面全局(如覆盖 getNode/stack 等同名
|
frame.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;border:0;pointer-events:none;z-index:9999;';
|
||||||
* 变量导致卡片点击失效);函数体内仍可访问 document/window 对整页施加特效。 */
|
document.body.appendChild(frame);
|
||||||
Array.prototype.forEach.call(doc.querySelectorAll('script'), function(sc) {
|
run_btn.textContent = '停止特效';
|
||||||
var s = document.createElement('script');
|
out.style.display = 'block';
|
||||||
if (sc.getAttribute('src')) {
|
out.textContent = '特效已启动(运行于隔离沙箱,不影响页面导航)';
|
||||||
s.src = sc.getAttribute('src');
|
|
||||||
} else {
|
|
||||||
s.textContent = '(function(){\n' + sc.textContent + '\n})();';
|
|
||||||
}
|
|
||||||
document.head.appendChild(s);
|
|
||||||
});
|
|
||||||
/* 渲染 <body> 内容到节点下方容器(跳过 script,避免 appendChild 触发其重复执行) */
|
|
||||||
if (doc.body) {
|
|
||||||
Array.prototype.forEach.call(doc.body.childNodes, function(node) {
|
|
||||||
if (node.nodeName && node.nodeName.toLowerCase() === 'script') return;
|
|
||||||
out.appendChild(node);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/* console 输出区 */
|
|
||||||
var logBox = document.createElement('div');
|
|
||||||
logBox.className = 'sp-log';
|
|
||||||
out.appendChild(logBox);
|
|
||||||
var render = function() {
|
|
||||||
logBox.textContent = lines.join('\n') || '执行完成';
|
|
||||||
};
|
|
||||||
render();
|
|
||||||
/* 异步输出(setTimeout 等)轮询刷新;捕获窗口 3 秒后恢复应用自身 console,避免永久污染全局日志 */
|
|
||||||
setInterval(render, 400);
|
|
||||||
setTimeout(function() {
|
|
||||||
restore();
|
|
||||||
window.removeEventListener('error', errHandler);
|
|
||||||
}, 3000);
|
|
||||||
});
|
});
|
||||||
bar.appendChild(audit_btn);
|
bar.appendChild(audit_btn);
|
||||||
bar.appendChild(run_btn);
|
bar.appendChild(run_btn);
|
||||||
|
|||||||
Reference in new issue
Block a user