Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: impore how we replace this in third-party code` #453

Merged
merged 2 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions src/lib/web-worker/worker-exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,50 @@ export const runScriptContent = (
return errorMsg;
};

/**
* Replace some `this` symbols with a new value.
* Still not perfect, but might be better than a more naive regex.
* Check out the tests for examples: tests/unit/worker-exec.spec.ts
*/
export const replaceThisInSource = (scriptContent: string, newThis: string): string => {
// Best for now but not perfect
// Use a more complex regex to match potential preceding character and adjust replacement accordingly
const r0 = /(?<!([a-zA-Z0-9_$\.\'\"\`]))(\.\.\.)?this(?![a-zA-Z0-9_$:])/g;
const r2 = /([a-zA-Z0-9_$\.\'\"\`])?(\.\.\.)?this(?![a-zA-Z0-9_$:])/g;

return scriptContent.replace(r2, (match, p1, p2) => {
// console.log('\n');
// console.log('input: ' + scriptContent);
// console.log('match: ', match);
// console.log('p1: ', p1);
// console.log('p2: ', p2);
// console.log('\n');
if (p1 != null) {
return (p1 || '') + (p2 || '') + 'this';
}
// If there was a preceding character, include it unchanged
// console.log('===', scriptContent, '----', p1, p2);
return (p1 || '') + (p2 || '') + newThis;
});

// 3.5
// const r = /(^|[^a-zA-Z0-9_$.\'\"`])\.\.\.?this(?![a-zA-Z0-9_$:])/g;
// return scriptContent.replace(r, '$1' + newThis);
// const r = /(?<!([a-zA-Z0-9_$\.\'\"\`]))(\.\.\.)?this(?![a-zA-Z0-9_$:])/g;
// return scriptContent.replace(r, '$2' + newThis);
};

export const run = (env: WebWorkerEnvironment, scriptContent: string, scriptUrl?: string) => {
env.$runWindowLoadEvent$ = 1;

// First we want to replace all `this` symbols
let sourceWithReplacedThis = replaceThisInSource(scriptContent, '(thi$(this)?window:this)');

scriptContent =
`with(this){${scriptContent
.replace(/\bthis\b/g, (match, offset, originalStr) =>
offset > 0 && originalStr[offset - 1] !== '$' ? '(thi$(this)?window:this)' : match
)
.replace(/\/\/# so/g, '//Xso')}\n;function thi$(t){return t===this}};${(
webWorkerCtx.$config$.globalFns || []
)
`with(this){${sourceWithReplacedThis.replace(
/\/\/# so/g,
'//Xso'
)}\n;function thi$(t){return t===this}};${(webWorkerCtx.$config$.globalFns || [])
.filter((globalFnName) => /[a-zA-Z_$][0-9a-zA-Z_$]*/.test(globalFnName))
.map((g) => `(typeof ${g}=='function'&&(this.${g}=${g}))`)
.join(';')};` + (scriptUrl ? '\n//# sourceURL=' + scriptUrl : '');
Expand Down
68 changes: 66 additions & 2 deletions tests/unit/worker-exec.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as assert from 'uvu/assert';
import { run } from '../../src/lib/web-worker/worker-exec';
import { run, replaceThisInSource } from '../../src/lib/web-worker/worker-exec';
import { suite } from './utils';

const test = suite();

test('add window id to postMessage() when cross-origin', ({ env, win, winId }) => {
Expand Down Expand Up @@ -93,4 +92,69 @@ test('window is window', ({ env, win }) => {
assert.is(win.result, true);
});

test('We should replace `this` keyword more or less sane', ({ env, win }) => {
const replaceSymbol = '__this';
const r = function (code: string) {
return replaceThisInSource(code, replaceSymbol);
};

// Should replace:
// assert.is(r('`sadly we fail at this simple string`'), '`sadly we fail at this simple string`');
assert.is(r('{this:123}'), '{this:123}');
assert.is(r('a.this'), 'a.this');
assert.is(r('[`kathis`]'), '[`kathis`]');
assert.is(r('{ ...this.opts };'), '{ ...__this.opts };');
assert.is(r('{ ...this.opts };this.lol;'), '{ ...__this.opts };__this.lol;');

const input = `
// Should replace:
{ ...this.opts };this.lol;
this
this.test
log(this.variable)
\`hello \${CONST} is \${this.CONST2}\`

// Should not replace:
['this', "this", \`this\`]
{this:123}
{ this: 123 }
'sadly we fail at this simple string'
"same as this"
\`and this is \${false} too\`;
a.b.this
let _this, This, $this
`;
// const rez = replaceThisInSource(input, `__this`);

const out = `
// Should replace:
{ ...__this.opts };__this.lol;
__this
__this.test
log(__this.variable)
\`hello \${CONST} is \${__this.CONST2}\`

// Should not replace:
['this', "this", \`this\`]
{this:123}
{ this: 123 }
'sadly we fail at __this simple string'
"same as __this"
\`and __this is \${false} too\`;
a.b.this
let _this, This, $this
`;

// assert.is(rez, out);
});

// test('properly replaces this is js window context', ({ env, win }) => {
// const s = `
// let a = { this: 123 };
// window.result = a.this;
// `;
// run(env, s);
// assert.is(win.result, 123);
// });

test.run();