Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save maple3142/c6593f0aff180bc0e4be5d549259da2c to your computer and use it in GitHub Desktop.

Select an option

Save maple3142/c6593f0aff180bc0e4be5d549259da2c to your computer and use it in GitHub Desktop.
polyfill of 'beforescriptexecute' event
// polyfill of 'beforescriptexecute' event
// original version: https://gist.github.com/jspenguin2017/cd568a50128c71e515738413cd09a890
{
;('use strict')
const Event = class {
constructor(script, target) {
this.script = script
this.target = target
this._cancel = false
this._replace = null
this._stop = false
}
preventDefault() {
this._cancel = true
}
stopPropagation() {
this._stop = true
}
replacePayload(payload) {
this._replace = payload
}
}
let callbacks = []
window.addBeforeScriptExecuteListener = f => {
if (!f instanceof Function) {
throw new Error('Event handler must be a function.')
}
callbacks.push(f)
}
window.removeBeforeScriptExecuteListener = f => {
let i = callbacks.length
while (i--) {
if (callbacks[i] === f) {
callbacks.splice(i, 1)
}
}
}
const dispatch = (script, target) => {
const e = new Event(script, target)
if (window.onbeforescriptexecute instanceof Function) {
try {
window.onbeforescriptexecute(e)
} catch (err) {
console.error(err)
}
}
for (let i = 0; i < callbacks.length; i++) {
if (e._stop) {
break
}
try {
callbacks[i](e)
} catch (err) {
console.error(err)
}
}
return e
}
const observer = new MutationObserver(mutations => {
for (let i = 0; i < mutations.length; i++) {
for (let j = 0; j < mutations[i].addedNodes.length; j++) {
const el = mutations[i].addedNodes[j]
if (el.tagName !== 'SCRIPT') continue
const e = dispatch(el, mutations[i].target)
if (e._cancel) {
el.remove()
} else if (typeof e._replace === 'string') {
el.textContent = e._replace
}
}
}
})
observer.observe(document, {
childList: true,
subtree: true
})
const create = document.createElement.bind(document)
document.createElement = t => {
if (t.toLowerCase() !== 'script') return create(t)
const s = create('script')
for (const key of ['textContent', 'innerHTML', 'src']) {
let value = ''
Object.defineProperty(s, key, {
get() {
return value
},
set(t) {
value = t
const e = dispatch(s, s)
if (!e._cancel) {
s.remove()
} else if (typeof e._replace === 'string' && key !== 'src') {
Reflect.set(s, key, e._replace)
}
}
})
}
return s
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src="onbeforescriptexecute.js"></script>
<script>
window.onbeforescriptexecute = e => {
if (e.script.textContent.includes('alert') && !e.script.textContent.includes('document')) {
e.preventDefault()
}
if (e.script.innerHTML.includes('hello')) {
e.replacePayload(e.script.innerHTML.replace('hello', 'world'))
}
if (e.script.src.includes('xxx')) {
e.preventDefault()
}
}
</script>
<script>
// won't execute
alert('hello')
</script>
<script>
// will execute, but 'hello' will be convert to 'world'
console.log('hello')
</script>
<script>
// will execute, but the dynamically added script won't
const el=document.createElement('script')
el.textContent=`
alert('1')
`
document.body.appendChild(el)
</script>
<!-- won't execute -->
<script src="xxx.js"></script>
<script>
const el2=document.createElement('script')
el2.src='xxx.js'
document.body.appendChild(el2)
</script>
<script>
const el3=document.createElement('script')
el3.innerHTML=`
alert(789)
`
document.body.appendChild(el3)
</script>
</body>
</html>
alert('C8763')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment