1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/service_worker/stashed_port_manager.h"
7 #include "content/browser/message_port_message_filter.h"
8 #include "content/browser/message_port_service.h"
9 #include "content/browser/service_worker/service_worker_context_wrapper.h"
10 #include "content/public/browser/browser_thread.h"
14 struct StashedPortManager::StashedPort
{
18 int64 service_worker_registration_id
;
19 GURL service_worker_origin
;
24 // The |route_id| to the port in the process this port currently lives in, or
25 // MSG_ROUTING_NONE if the port isn't currently associated with any running
26 // ServiceWorkerVersion.
28 // The running ServiceWorkerVersion this port is currently associated with.
29 // Set to null if the port does not currently exist in a running worker.
30 ServiceWorkerVersion
* service_worker
;
33 StashedPortManager::StashedPort::StashedPort() {
36 StashedPortManager::StashedPort::~StashedPort() {
39 StashedPortManager::StashedPortManager(
40 const scoped_refptr
<ServiceWorkerContextWrapper
>& service_worker_context
)
41 : service_worker_context_(service_worker_context
) {
42 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
45 StashedPortManager::~StashedPortManager() {
48 void StashedPortManager::Init() {
49 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
50 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
51 base::Bind(&StashedPortManager::InitOnIO
, this));
54 void StashedPortManager::Shutdown() {
55 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
56 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
,
57 base::Bind(&StashedPortManager::ShutdownOnIO
, this));
60 void StashedPortManager::InitOnIO() {
61 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
62 // TODO(mek): Restore ports between service workers from storage.
65 void StashedPortManager::ShutdownOnIO() {
66 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
67 for (ServiceWorkerVersion
* worker
: observed_service_workers_
) {
68 worker
->RemoveListener(this);
70 observed_service_workers_
.clear();
71 MessagePortService::GetInstance()->OnMessagePortDelegateClosing(this);
74 void StashedPortManager::AddPort(ServiceWorkerVersion
* service_worker
,
76 const base::string16
& name
) {
77 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
78 DCHECK(ports_
.find(message_port_id
) == ports_
.end());
80 StashedPort
& port
= ports_
[message_port_id
];
81 port
.service_worker_registration_id
= service_worker
->registration_id();
82 port
.service_worker_origin
= service_worker
->scope().GetOrigin();
83 port
.message_port_id
= message_port_id
;
86 // If service worker is currently running, get its current route id and
87 // start observing the worker.
88 if (service_worker
->running_status() == ServiceWorkerVersion::RUNNING
) {
89 MessagePortService::GetInstance()->GetMessagePortInfo(
90 message_port_id
, nullptr, &port
.route_id
);
91 port
.service_worker
= service_worker
;
92 if (observed_service_workers_
.insert(service_worker
).second
) {
93 // Service Worker wasn't already being observed
94 service_worker
->AddListener(this);
97 port
.route_id
= MSG_ROUTING_NONE
;
98 port
.service_worker
= nullptr;
101 MessagePortService::GetInstance()->UpdateMessagePort(message_port_id
, this,
104 // If service worker is not currently running, all messages to this port
105 // should be held in the browser process.
106 if (port
.route_id
== MSG_ROUTING_NONE
)
107 MessagePortService::GetInstance()->HoldMessages(message_port_id
);
110 void StashedPortManager::SendMessage(
112 const MessagePortMessage
& message
,
113 const std::vector
<TransferredMessagePort
>& sent_message_ports
) {
114 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
115 DCHECK(ports_
.find(message_port_id
) != ports_
.end());
117 const StashedPort
& port
= ports_
[message_port_id
];
118 DCHECK(port
.service_worker
);
119 DCHECK_NE(port
.route_id
, MSG_ROUTING_NONE
);
120 port
.service_worker
->embedded_worker()
121 ->message_port_message_filter()
122 ->SendMessage(port
.route_id
, message
, sent_message_ports
);
125 void StashedPortManager::MessageWasHeld(int message_port_id
) {
126 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
127 DCHECK(ports_
.find(message_port_id
) != ports_
.end());
129 // Messages are queued in the browser process when the stashed port isn't
130 // currently available in any running renderer processes. Try to transfer
131 // the port to a (potentially newly started) service worker to enable
133 const StashedPort
& port
= ports_
[message_port_id
];
134 service_worker_context_
->FindRegistrationForId(
135 port
.service_worker_registration_id
, port
.service_worker_origin
,
136 base::Bind(&StashedPortManager::TransferMessagePort
, this,
140 void StashedPortManager::SendMessagesAreQueued(int message_port_id
) {
141 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
142 DCHECK(ports_
.find(message_port_id
) != ports_
.end());
143 // TODO(mek): Support transfering a stashed message port to a different
147 void StashedPortManager::OnRunningStateChanged(
148 ServiceWorkerVersion
* service_worker
) {
149 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
150 // Listener is only added to service workers whose running state is RUNNING,
151 // so running state here should only ever be able to be STOPPING or STOPPED.
152 DCHECK(service_worker
->running_status() == ServiceWorkerVersion::STOPPING
||
153 service_worker
->running_status() == ServiceWorkerVersion::STOPPED
);
155 // Hold messages for all ports associated with the no longer running worker.
156 for (auto& port
: ports_
) {
157 if (port
.second
.service_worker
!= service_worker
)
159 port
.second
.route_id
= MSG_ROUTING_NONE
;
160 port
.second
.service_worker
= nullptr;
161 MessagePortService::GetInstance()->HoldMessages(port
.first
);
163 observed_service_workers_
.erase(service_worker
);
164 service_worker
->RemoveListener(this);
167 void StashedPortManager::TransferMessagePort(
169 ServiceWorkerStatusCode service_worker_status
,
170 const scoped_refptr
<ServiceWorkerRegistration
>&
171 service_worker_registration
) {
172 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
173 auto it
= ports_
.find(message_port_id
);
174 if (it
== ports_
.end()) {
175 // Port no longer owned by StashedPortManager, no need to do anything else.
178 const StashedPort
& port
= it
->second
;
180 if (service_worker_status
!= SERVICE_WORKER_OK
) {
181 // TODO(mek): SW no longer exists, somehow handle this.
185 // TODO(mek): Figure out and spec correct logic for determining which version
186 // of a service worker a port should be associated with.
187 scoped_refptr
<ServiceWorkerVersion
> version
=
188 service_worker_registration
->active_version();
190 version
= service_worker_registration
->waiting_version();
192 version
= service_worker_registration
->installing_version();
194 // TODO(mek): Do something when no version is found.
198 std::vector
<TransferredMessagePort
> ports(1);
199 std::vector
<base::string16
> port_names(1);
200 ports
[0].id
= port
.message_port_id
;
201 port_names
[0] = port
.name
;
202 version
->SendStashedMessagePorts(
203 ports
, port_names
, base::Bind(&StashedPortManager::OnPortsTransferred
,
204 this, version
, ports
));
207 void StashedPortManager::OnPortsTransferred(
208 const scoped_refptr
<ServiceWorkerVersion
>& service_worker
,
209 const std::vector
<TransferredMessagePort
>& ports
,
210 ServiceWorkerStatusCode service_worker_status
,
211 const std::vector
<int>& route_ids
) {
212 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
213 if (service_worker_status
!= SERVICE_WORKER_OK
) {
214 // TODO(mek): Handle failure.
218 if (service_worker
->running_status() != ServiceWorkerVersion::RUNNING
) {
219 // TODO(mek): Figure out what to do in this case.
223 // Port was transfered to a service worker, start observing the worker so
224 // messages can be held when the worker stops running.
225 if (observed_service_workers_
.insert(service_worker
.get()).second
) {
226 // Service Worker wasn't already being observed
227 service_worker
->AddListener(this);
230 // Update ports with the new route ids and service worker version.
231 DCHECK_EQ(ports
.size(), route_ids
.size());
232 for (size_t i
= 0; i
< ports
.size(); ++i
) {
233 DCHECK(ports_
.find(ports
[i
].id
) != ports_
.end());
234 StashedPort
& port
= ports_
[ports
[i
].id
];
235 port
.route_id
= route_ids
[i
];
236 port
.service_worker
= service_worker
.get();
240 } // namespace content