Skip to content

Commit

Permalink
Allow posting a SharedArrayBuffer to AudioWorklet
Browse files Browse the repository at this point in the history
The HTML spec only allows passing SharedArrayBuffers to an agent in the
same agent cluster. Worklets currently do not belong to any agent
cluster, so a SharedArrayBuffer can not be shared with a worklet.

However, it is intended that this is possible; see, for example,
w3c/css-houdini-drafts#380 and the
AudioWorklet article here:
https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern#webaudio_powerhouse_audio_worklet_and_sharedarraybuffer

This change funnels the agent cluster ID through when creating a
ThreadedWorklet, so a SharedArrayBuffer can be shared as long as the
creator of the Worklet's thread is in the same agent cluster. It is not
clear that this is the behavior that will be specified, however.

Bug: chromium:892067
Change-Id: If1a2187ae38da41f2389538c07e7b04921c6128f
Reviewed-on: https://chromium-review.googlesource.com/c/1262932
Commit-Queue: Ben Smith <binji@chromium.org>
Reviewed-by: Hongchan Choi <hongchan@chromium.org>
Reviewed-by: Hiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/master@{#598619}
  • Loading branch information
binji authored and Commit Bot committed Oct 11, 2018
1 parent cdaf0b1 commit 97167bd
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 6 deletions.
@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test passing SharedArrayBuffer to an AudioWorklet
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();

let context = new AudioContext();

let filePath = 'processors/sharedarraybuffer-processor.js';

if (window.SharedArrayBuffer) {
audit.define(
'Test postMessage from AudioWorkletProcessor to AudioWorkletNode',
(task, should) => {
let workletNode =
new AudioWorkletNode(context, 'sharedarraybuffer-processor');

// After it is created, the worklet will send a new
// SharedArrayBuffer to the main thread.
//
// The worklet will then wait to receive a message from the main
// thread.
//
// When it receives the message, it will check whether it is a
// SharedArrayBuffer, and send this information back to the main
// thread.

workletNode.port.onmessage = (event) => {
let data = event.data;
switch (data.state) {
case 'created':
should(
data.sab instanceof SharedArrayBuffer,
'event.data.sab from worklet is an instance of SharedArrayBuffer')
.beTrue();

// Send a SharedArrayBuffer back to the worklet.
let sab = new SharedArrayBuffer(8);
workletNode.port.postMessage(sab);
break;

case 'received message':
should(data.isSab, 'event.data from main thread is an instance of SharedArrayBuffer')
.beTrue();
task.done();
break;

default:
should(false,
`Got unexpected message from worklet: ${data.state}`)
.beTrue();
task.done();
break;
}
};

workletNode.port.onmessageerror = (event) => {
should(false, 'Got messageerror from worklet').beTrue();
task.done();
};
});
} else {
// NOTE(binji): SharedArrayBuffer is only enabled where we have site
// isolation.
audit.define('Skipping test because SharedArrayBuffer is not defined',
(task, should) => {
task.done();
});
}

context.audioWorklet.addModule(filePath).then(() => {
audit.run();
});
</script>
</body>
</html>

@@ -0,0 +1,35 @@
/**
* @class SharedArrayBufferProcessor
* @extends AudioWorkletProcessor
*
* This processor class demonstrates passing SharedArrayBuffers to and from
* workers.
*/
class SharedArrayBufferProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.port.onmessage = this.handleMessage.bind(this);
this.port.onmessageerror = this.handleMessageError.bind(this);
let sab = new SharedArrayBuffer(8);
this.port.postMessage({state: 'created', sab});
}

handleMessage(event) {
this.port.postMessage({
state: 'received message',
isSab: event.data instanceof SharedArrayBuffer
});
}

handleMessageError(event) {
this.port.postMessage({
state: 'received messageerror'
});
}

process() {
return true;
}
}

registerProcessor('sharedarraybuffer-processor', SharedArrayBufferProcessor);
Expand Up @@ -63,7 +63,10 @@ void ThreadedWorkletMessagingProxy::Initialize(
OriginTrialContext::GetTokens(document).get(),
base::UnguessableToken::Create(),
std::make_unique<WorkerSettings>(document->GetSettings()),
kV8CacheOptionsDefault, module_responses_map);
kV8CacheOptionsDefault, module_responses_map,
service_manager::mojom::blink::InterfaceProviderPtrInfo(),
BeginFrameProviderParams(), nullptr /* parent_feature_policy */,
document->GetAgentClusterID());

// Worklets share the pre-initialized backing thread so that we don't have to
// specify the backing thread startup data.
Expand Down
Expand Up @@ -39,7 +39,10 @@ WorkletGlobalScope::WorkletGlobalScope(
document_secure_context_(creation_params->starter_secure_context),
module_responses_map_(creation_params->module_responses_map),
// Step 4. "Let inheritedHTTPSState be outsideSettings's HTTPS state."
https_state_(creation_params->starter_https_state) {
https_state_(creation_params->starter_https_state),
agent_cluster_id_(creation_params->agent_cluster_id.is_empty()
? base::UnguessableToken::Create()
: creation_params->agent_cluster_id) {
// Step 2: "Let inheritedAPIBaseURL be outsideSettings's API base URL."
// |url_| is the inheritedAPIBaseURL passed from the parent Document.

Expand Down
15 changes: 11 additions & 4 deletions third_party/blink/renderer/core/workers/worklet_global_scope.h
Expand Up @@ -50,11 +50,16 @@ class CORE_EXPORT WorkletGlobalScope
String UserAgent() const final { return user_agent_; }
SecurityContext& GetSecurityContext() final { return *this; }
bool IsSecureContext(String& error_message) const final;

// Currently, worklet agents have no clearly defined owner. See
// https://html.spec.whatwg.org/multipage/webappapis.html#integration-with-the-javascript-agent-cluster-formalism
const base::UnguessableToken& GetAgentClusterID() const final {
return base::UnguessableToken::Null();
// Currently, worklet agents have no clearly defined owner. See
// https://html.spec.whatwg.org/multipage/webappapis.html#integration-with-the-javascript-agent-cluster-formalism
//
// However, it is intended that a SharedArrayBuffer can be shared with a
// worklet, e.g. the AudioWorklet. If this WorkletGlobalScope's creation
// params included an agent cluster ID, we'll assume that this worklet is
// in the same agent cluster. See
// https://bugs.chromium.org/p/chromium/issues/detail?id=892067.
return agent_cluster_id_;
}

DOMTimerCoordinator* Timers() final {
Expand Down Expand Up @@ -123,6 +128,8 @@ class CORE_EXPORT WorkletGlobalScope
CrossThreadPersistent<WorkletModuleResponsesMap> module_responses_map_;

const HttpsState https_state_;

const base::UnguessableToken agent_cluster_id_;
};

DEFINE_TYPE_CASTS(WorkletGlobalScope,
Expand Down

0 comments on commit 97167bd

Please sign in to comment.