New issue
Advanced search Search tips

Issue 851123 link

Starred by 1 user

Issue metadata

Status: WontFix
Owner:
Closed: Jun 2018
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Linux , Android , Windows , Chrome , Mac
Pri: 2
Type: Bug



Sign in to add a comment

Script evaluation in AudioWorkletGlobalScope: global variables are unreachable

Project Member Reported by hongchan@chromium.org, Jun 8 2018

Issue description

Repro:
https://glitch.com/edit/#!/aw-example-1

Multiple addModule() call on AudioWorklet doesn't seem to be sharing the same GlobalScope. The code implicates Chrome only creates a single AWGS, so the global variables in the scope should be visible even if the code is loaded by different addModule() call. Consider the following example:

```
/** AWGS, file A **/
function rand() {
  return Math.random() * 2 - 1;
}
```

```
/** AWGS, file B **/
class MyProcessor extends AudioWorkletProcessor {
  
  process() {
    console.log(rand());
    return false;
  }
  
}
```

```
/** index.js **/
const audioContext = new AudioContext();

async function load() {
  await audioContext.audioWorklet.addModule('file-a.js');
  return await audioContext.audioWorklet.addModule('file-b.js');
}

load().then(() => {
  const workletNode = new AudioWorkletNode(audioContext, 'my-processor');
  workletNode.connect(audioContext.destination);
});

```

If the file A is fetched/invoked before the file B, rand() function call should work. Currently Chrome throws an exception:

Uncaught ReferenceError: rand is not defined
at AudioWorkletProcessor.process (awgs-processor.js:...)

WDYT? nhiroki@
 
This seems correct behavior according to the Worklet spec:

"Code is loaded as a module script which resulting in the code being executed in strict mode code without a shared this. This prevents two different module scripts sharing state be referencing shared objects on the global scope."
https://drafts.css-houdini.org/worklets/#code-idempotency

In this case, addModule('file-a.js') and addModule('file-b.js') result in different module scripts, so they shouldn't share states and functions. If file-b.js needs file-a.js, file-b.js should run static import like this:

// In file-b.js
import "./file-a.js";

class MyProcessor extends AudioWorkletProcessor {
  process() {
    console.log(rand());
    return false;
  } 
}

Comment 2 by aat...@gmail.com, Jun 10 2018

Hi, I filed the original issue on GitHub. I tried the suggestion by nhiroki, however it results in a new error for me: "Uncaught (in promise) DOMException: The user aborted a request." I've attached my test code. I'm running Chrome Version 66.0.3359.181 on Mac OS 10.11.6.
Best,
Aatish
index.html
262 bytes View Download
audio-processor.js
378 bytes View Download
loop.js
136 bytes View Download

Comment 3 by aat...@gmail.com, Jun 10 2018

Live version here: https://glitch.com/edit/#!/indigo-ptarmigan
It looks like the given module path for static import is invalid. Static import requires an absolute path or a relative path with "./" prefix like this: 

  import "./loop.js";

Unfortunately, an error of import failures on worklets is not really helpful (issue 782066). We'll rethink the priority of the issue.
nhiroki@ I was unaware that you can use 'import' in WorkletGlobalScope. 

This was the decision I knew - https://github.com/w3c/css-houdini-drafts/issues/506. Has it been changed somehow? Can you provide a reference for it? If this change is official, it could be a good news for many AudioWorklet developers.

On the other hand, this opens up the network stack with a blocking operation thus calling it while rendering audio can cause glitches.
I also can confirm that the global function in the imported script is not visible from the "file-b" side. Do we need an experimental flag on this?

(69.0.3453.3 Canary, MacOS)

Comment 7 by aat...@gmail.com, Jun 11 2018

@nhiroki Thanks for the info, this was helpful. Just using import did not work for me as this seems to run the module code but not import the variables (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import). 

However adding 

export {functionName};

to the end of file-a.js and adding

import {functionName} from './file-a.js'

to the start of file-b.js did the trick. 

My use case is somewhat specific.. I'm working on a wrapper for web audio worklet so that any user written code can be easily run on a worklet. (similar to: https://acarabott.github.io/audio-dsp-playground/ ) Ideally I would like to import all user defined functions (without knowing in advance what these might be called) into the scope of the worklet where they can be executed. Currently the workaround I'm using to do this is data URLs + template literals: https://github.com/aatishb/synthfromscratch/tree/master/sine-wave

That works fine for me, but I'm curious if there's any other simpler way to import all variables from a file into the worklet scope.
Status: WontFix (was: Assigned)
aatish@

Thanks for bringing this to my attention. Your use case is not really specific. I can assure you that many WebAudio developers in the slack channel will be happy to see this.

This is how we make the code scales. :)

There is still a question I have above, but that's not a part of the reported issue. I am closing this.
c#5: The issue disallows "dynamic import" that runs on script evaluation phase, but not disallow "static import" that runs script parse phase. This is because "dynamic import" potentially violates the "code idempotency" rule defined in the Worklets spec.

// hello-world.js
export const message = "Hello, world!"; 

// static import
import * as module from "./hello-world.js";
console.log(module.message);

// dynamic import
import('./hello-world.js').then(module => console.log(module.message));
Thanks for clarification!
c#7: I think the data URL is the simplest way if user code is not modifiable.

In general it would be better that user code imports a library than the library imports user code in terms of code modularity like this:

// library.js
export function doSomething() { ... }
export const value = "42";

// user.js
import * as library from './library.js'
library.doSomething();
userFunc(library.value);

Inlining libraries (variables and functions) may cause namescope conflict that ES Modules is trying to avoid.

Sign in to add a comment