Angular 8动态注入Pentaho CTools脚本报异步document.write执行错误
这个报错是浏览器的明确安全限制:document.write 原本只设计用于文档同步解析阶段的内容插入,当脚本是在文档完成解析后异步加载执行的,再调用document.write就会被浏览器拦截。
Pentaho返回的初始化脚本是服务端生成的固定逻辑,内部通过document.write按固定顺序加载整套C Tools依赖,我们没办法直接修改脚本源码。你之前尝试用postscribe没效果的原因是:postscribe只能处理你手动传入DOM的渲染内容,没法介入浏览器原生加载外部脚本时的内部执行流程,自然拦不住脚本内部发起的document.write调用。
注意:报错提示里提到的“可以显式打开文档”是陷阱,调用
document.open()会直接清空整个页面的DOM内容,把你的Angular应用全部冲掉,完全不能用。
方案1:注入脚本前临时劫持document.write方法(最适配现有代码逻辑)
在动态注入Pentaho主脚本之前,临时替换原生的document.write实现,把脚本内部要输出的内容抓下来,转成正常的DOM节点插入,完全绕开原生document.write的调用限制,等主脚本执行完成后再恢复原方法。
核心实现逻辑:
- 把原生
document.write方法存为临时变量备用 - 替换后的新
document.write逻辑:把传入的脚本字符串解析成DOM节点,按原顺序提取所有带外部src的依赖脚本,逐个插入到<head>中 - 插入依赖脚本时必须设置
async = false,保证脚本按固定顺序同步执行,和Pentaho原本设计的加载顺序完全一致,避免require.js配置错乱 - 等Pentaho主脚本的
onload事件触发后,立刻把原生document.write方法恢复回去,避免影响其他页面逻辑
参考适配你现有代码的实现:
const url = `${pentahoHost}${PENTAHO_URL}userid=${userId};password=${password};port=433`; const scriptId = 'pgi-pentaho'; // 1. 临时劫持document.write const nativeWrite = document.write.bind(document); document.write = (content: string) => { // 解析脚本要写入的内容 const tempDoc = new DOMParser().parseFromString(content, 'text/html'); // 按顺序提取所有需要加载的外部脚本 const depScripts = tempDoc.querySelectorAll('script[src]'); depScripts.forEach(dep => { const depEl = document.createElement('script'); depEl.src = (dep as HTMLScriptElement).src; depEl.async = false; // 关键:保证顺序执行 document.head.appendChild(depEl); }); }; // 2. 正常注入Pentaho主脚本 const script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; script.id = scriptId; // 3. 主脚本加载完成后恢复原方法 script.onload = () => { document.write = nativeWrite; // 此处可后续执行Dashboard渲染初始化逻辑 }; document.head.appendChild(script);
方案2:把Pentaho脚本的注入时机提前到应用初始化最早期
浏览器允许document.write的唯一场景是在文档处于同步解析阶段(document.readyState === 'loading')时的同步调用。你之前的代码是在Angular业务组件的生命周期里注入脚本,这个时候文档早已解析完成,自然会触发报错。
调整方式:把原来写在组件里的脚本注入逻辑,移到Angular应用启动的最早期(比如main.ts中、platformBrowserDynamic().bootstrapModule()调用之前),在这个时机动态拼好带userId、密码、Pentaho地址的脚本URL,以同步方式(设置async = false)插入脚本。此时文档还处于可写入的解析阶段,Pentaho脚本内部的document.write调用不会被浏览器拦截,完全和静态引入的效果一致。
- 不要尝试调用
document.open()绕过报错,该操作会清空整个页面的DOM树,直接导致Angular应用崩溃 - 动态插入Pentaho依赖脚本时,必须关闭异步执行模式,否则会打乱require.js的模块加载顺序,触发大量模块找不到的报错
- 一定要在Pentaho主脚本加载完成后及时恢复原生
document.write方法,避免劫持逻辑长期存在影响其他页面功能
内容的提问来源于stack exchange,提问作者Marco Duran

