cc: Skip commit state timer on sync compositor
[chromium-blink-merge.git] / ppapi / proxy / websocket_resource.cc
blobc998cd94b7206868cfa96ceb3d1566e823502482
1 // Copyright (c) 2012 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 "ppapi/proxy/websocket_resource.h"
7 #include <set>
8 #include <string>
9 #include <vector>
11 #include "base/bind.h"
12 #include "ppapi/c/pp_errors.h"
13 #include "ppapi/proxy/dispatch_reply_message.h"
14 #include "ppapi/proxy/ppapi_messages.h"
15 #include "ppapi/shared_impl/ppapi_globals.h"
16 #include "ppapi/shared_impl/var.h"
17 #include "ppapi/shared_impl/var_tracker.h"
18 #include "third_party/WebKit/public/web/WebSocket.h"
20 namespace {
22 const uint32_t kMaxReasonSizeInBytes = 123;
23 const size_t kBaseFramingOverhead = 2;
24 const size_t kMaskingKeyLength = 4;
25 const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
26 const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
28 uint64_t SaturateAdd(uint64_t a, uint64_t b) {
29 if (kuint64max - a < b)
30 return kuint64max;
31 return a + b;
34 uint64_t GetFrameSize(uint64_t payload_size) {
35 uint64_t overhead = kBaseFramingOverhead + kMaskingKeyLength;
36 if (payload_size > kMinimumPayloadSizeWithEightByteExtendedPayloadLength)
37 overhead += 8;
38 else if (payload_size > kMinimumPayloadSizeWithTwoByteExtendedPayloadLength)
39 overhead += 2;
40 return SaturateAdd(payload_size, overhead);
43 bool InValidStateToReceive(PP_WebSocketReadyState state) {
44 return state == PP_WEBSOCKETREADYSTATE_OPEN ||
45 state == PP_WEBSOCKETREADYSTATE_CLOSING;
48 } // namespace
51 namespace ppapi {
52 namespace proxy {
54 WebSocketResource::WebSocketResource(Connection connection,
55 PP_Instance instance)
56 : PluginResource(connection, instance),
57 state_(PP_WEBSOCKETREADYSTATE_INVALID),
58 error_was_received_(false),
59 receive_callback_var_(NULL),
60 empty_string_(new StringVar(std::string())),
61 close_code_(0),
62 close_reason_(NULL),
63 close_was_clean_(PP_FALSE),
64 extensions_(NULL),
65 protocol_(NULL),
66 url_(NULL),
67 buffered_amount_(0),
68 buffered_amount_after_close_(0) {
71 WebSocketResource::~WebSocketResource() {
74 thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
75 return this;
78 int32_t WebSocketResource::Connect(
79 const PP_Var& url,
80 const PP_Var protocols[],
81 uint32_t protocol_count,
82 scoped_refptr<TrackedCallback> callback) {
83 if (TrackedCallback::IsPending(connect_callback_))
84 return PP_ERROR_INPROGRESS;
86 // Connect() can be called at most once.
87 if (state_ != PP_WEBSOCKETREADYSTATE_INVALID)
88 return PP_ERROR_INPROGRESS;
89 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
91 // Get the URL.
92 url_ = StringVar::FromPPVar(url);
93 if (!url_.get())
94 return PP_ERROR_BADARGUMENT;
96 // Get the protocols.
97 std::set<std::string> protocol_set;
98 std::vector<std::string> protocol_strings;
99 protocol_strings.reserve(protocol_count);
100 for (uint32_t i = 0; i < protocol_count; ++i) {
101 scoped_refptr<StringVar> protocol(StringVar::FromPPVar(protocols[i]));
103 // Check invalid and empty entries.
104 if (!protocol.get() || !protocol->value().length())
105 return PP_ERROR_BADARGUMENT;
107 // Check duplicated protocol entries.
108 if (protocol_set.find(protocol->value()) != protocol_set.end())
109 return PP_ERROR_BADARGUMENT;
110 protocol_set.insert(protocol->value());
112 protocol_strings.push_back(protocol->value());
115 // Install callback.
116 connect_callback_ = callback;
118 // Create remote host in the renderer, then request to check the URL and
119 // establish the connection.
120 state_ = PP_WEBSOCKETREADYSTATE_CONNECTING;
121 SendCreate(RENDERER, PpapiHostMsg_WebSocket_Create());
122 PpapiHostMsg_WebSocket_Connect msg(url_->value(), protocol_strings);
123 Call<PpapiPluginMsg_WebSocket_ConnectReply>(RENDERER, msg,
124 base::Bind(&WebSocketResource::OnPluginMsgConnectReply, this));
126 return PP_OK_COMPLETIONPENDING;
129 int32_t WebSocketResource::Close(uint16_t code,
130 const PP_Var& reason,
131 scoped_refptr<TrackedCallback> callback) {
132 if (TrackedCallback::IsPending(close_callback_))
133 return PP_ERROR_INPROGRESS;
134 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID)
135 return PP_ERROR_FAILED;
137 // Validate |code| and |reason|.
138 scoped_refptr<StringVar> reason_string_var;
139 std::string reason_string;
140 blink::WebSocket::CloseEventCode event_code =
141 static_cast<blink::WebSocket::CloseEventCode>(code);
142 if (code == PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) {
143 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED and CloseEventCodeNotSpecified are
144 // assigned to different values. A conversion is needed if
145 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED is specified.
146 event_code = blink::WebSocket::CloseEventCodeNotSpecified;
147 } else {
148 if (!(code == PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE ||
149 (PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN <= code &&
150 code <= PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX)))
151 // RFC 6455 limits applications to use reserved connection close code in
152 // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/)
153 // defines this out of range error as InvalidAccessError in JavaScript.
154 return PP_ERROR_NOACCESS;
156 // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is
157 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED.
158 if (reason.type != PP_VARTYPE_UNDEFINED) {
159 // Validate |reason|.
160 reason_string_var = StringVar::FromPPVar(reason);
161 if (!reason_string_var.get() ||
162 reason_string_var->value().size() > kMaxReasonSizeInBytes)
163 return PP_ERROR_BADARGUMENT;
164 reason_string = reason_string_var->value();
168 // Check state.
169 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING)
170 return PP_ERROR_INPROGRESS;
171 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
172 return PP_OK;
174 // Install |callback|.
175 close_callback_ = callback;
177 // Abort ongoing connect.
178 if (TrackedCallback::IsPending(connect_callback_)) {
179 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
180 // Need to do a "Post" to avoid reentering the plugin.
181 connect_callback_->PostAbort();
182 connect_callback_ = NULL;
183 Post(RENDERER, PpapiHostMsg_WebSocket_Fail(
184 "WebSocket was closed before the connection was established."));
185 return PP_OK_COMPLETIONPENDING;
188 // Abort ongoing receive.
189 if (TrackedCallback::IsPending(receive_callback_)) {
190 receive_callback_var_ = NULL;
191 // Need to do a "Post" to avoid reentering the plugin.
192 receive_callback_->PostAbort();
193 receive_callback_ = NULL;
196 // Close connection.
197 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
198 PpapiHostMsg_WebSocket_Close msg(static_cast<int32_t>(event_code),
199 reason_string);
200 Call<PpapiPluginMsg_WebSocket_CloseReply>(RENDERER, msg,
201 base::Bind(&WebSocketResource::OnPluginMsgCloseReply, this));
202 return PP_OK_COMPLETIONPENDING;
205 int32_t WebSocketResource::ReceiveMessage(
206 PP_Var* message,
207 scoped_refptr<TrackedCallback> callback) {
208 if (TrackedCallback::IsPending(receive_callback_))
209 return PP_ERROR_INPROGRESS;
211 // Check state.
212 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
213 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
214 return PP_ERROR_BADARGUMENT;
216 // Just return received message if any received message is queued.
217 if (!received_messages_.empty()) {
218 receive_callback_var_ = message;
219 return DoReceive();
222 // Check state again. In CLOSED state, no more messages will be received.
223 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
224 return PP_ERROR_BADARGUMENT;
226 // Returns PP_ERROR_FAILED after an error is received and received messages
227 // is exhausted.
228 if (error_was_received_)
229 return PP_ERROR_FAILED;
231 // Or retain |message| as buffer to store and install |callback|.
232 receive_callback_var_ = message;
233 receive_callback_ = callback;
235 return PP_OK_COMPLETIONPENDING;
238 int32_t WebSocketResource::SendMessage(const PP_Var& message) {
239 // Check state.
240 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
241 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
242 return PP_ERROR_BADARGUMENT;
244 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING ||
245 state_ == PP_WEBSOCKETREADYSTATE_CLOSED) {
246 // Handle buffered_amount_after_close_.
247 uint64_t payload_size = 0;
248 if (message.type == PP_VARTYPE_STRING) {
249 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
250 if (message_string.get())
251 payload_size += message_string->value().length();
252 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
253 scoped_refptr<ArrayBufferVar> message_array_buffer =
254 ArrayBufferVar::FromPPVar(message);
255 if (message_array_buffer.get())
256 payload_size += message_array_buffer->ByteLength();
257 } else {
258 // TODO(toyoshim): Support Blob.
259 return PP_ERROR_NOTSUPPORTED;
262 buffered_amount_after_close_ =
263 SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size));
265 return PP_ERROR_FAILED;
268 // Send the message.
269 if (message.type == PP_VARTYPE_STRING) {
270 // Convert message to std::string, then send it.
271 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
272 if (!message_string.get())
273 return PP_ERROR_BADARGUMENT;
274 Post(RENDERER, PpapiHostMsg_WebSocket_SendText(message_string->value()));
275 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
276 // Convert message to std::vector<uint8_t>, then send it.
277 scoped_refptr<ArrayBufferVar> message_arraybuffer =
278 ArrayBufferVar::FromPPVar(message);
279 if (!message_arraybuffer.get())
280 return PP_ERROR_BADARGUMENT;
281 uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map());
282 uint32 message_length = message_arraybuffer->ByteLength();
283 std::vector<uint8_t> message_vector(message_data,
284 message_data + message_length);
285 Post(RENDERER, PpapiHostMsg_WebSocket_SendBinary(message_vector));
286 } else {
287 // TODO(toyoshim): Support Blob.
288 return PP_ERROR_NOTSUPPORTED;
290 return PP_OK;
293 uint64_t WebSocketResource::GetBufferedAmount() {
294 return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
297 uint16_t WebSocketResource::GetCloseCode() {
298 return close_code_;
301 PP_Var WebSocketResource::GetCloseReason() {
302 if (!close_reason_.get())
303 return empty_string_->GetPPVar();
304 return close_reason_->GetPPVar();
307 PP_Bool WebSocketResource::GetCloseWasClean() {
308 return close_was_clean_;
311 PP_Var WebSocketResource::GetExtensions() {
312 return StringVar::StringToPPVar(std::string());
315 PP_Var WebSocketResource::GetProtocol() {
316 if (!protocol_.get())
317 return empty_string_->GetPPVar();
318 return protocol_->GetPPVar();
321 PP_WebSocketReadyState WebSocketResource::GetReadyState() {
322 return state_;
325 PP_Var WebSocketResource::GetURL() {
326 if (!url_.get())
327 return empty_string_->GetPPVar();
328 return url_->GetPPVar();
331 void WebSocketResource::OnReplyReceived(
332 const ResourceMessageReplyParams& params,
333 const IPC::Message& msg) {
334 if (params.sequence()) {
335 PluginResource::OnReplyReceived(params, msg);
336 return;
339 IPC_BEGIN_MESSAGE_MAP(WebSocketResource, msg)
340 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
341 PpapiPluginMsg_WebSocket_ReceiveTextReply,
342 OnPluginMsgReceiveTextReply)
343 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
344 PpapiPluginMsg_WebSocket_ReceiveBinaryReply,
345 OnPluginMsgReceiveBinaryReply)
346 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_0(
347 PpapiPluginMsg_WebSocket_ErrorReply,
348 OnPluginMsgErrorReply)
349 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
350 PpapiPluginMsg_WebSocket_BufferedAmountReply,
351 OnPluginMsgBufferedAmountReply)
352 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
353 PpapiPluginMsg_WebSocket_StateReply,
354 OnPluginMsgStateReply)
355 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
356 PpapiPluginMsg_WebSocket_ClosedReply,
357 OnPluginMsgClosedReply)
358 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(NOTREACHED())
359 IPC_END_MESSAGE_MAP()
362 void WebSocketResource::OnPluginMsgConnectReply(
363 const ResourceMessageReplyParams& params,
364 const std::string& url,
365 const std::string& protocol) {
366 if (!TrackedCallback::IsPending(connect_callback_) ||
367 TrackedCallback::IsScheduledToRun(connect_callback_)) {
368 return;
371 int32_t result = params.result();
372 if (result == PP_OK) {
373 state_ = PP_WEBSOCKETREADYSTATE_OPEN;
374 protocol_ = new StringVar(protocol);
375 url_ = new StringVar(url);
377 connect_callback_->Run(params.result());
380 void WebSocketResource::OnPluginMsgCloseReply(
381 const ResourceMessageReplyParams& params,
382 unsigned long buffered_amount,
383 bool was_clean,
384 unsigned short code,
385 const std::string& reason) {
386 // Set close related properties.
387 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
388 buffered_amount_ = buffered_amount;
389 close_was_clean_ = PP_FromBool(was_clean);
390 close_code_ = code;
391 close_reason_ = new StringVar(reason);
393 if (TrackedCallback::IsPending(receive_callback_)) {
394 receive_callback_var_ = NULL;
395 if (!TrackedCallback::IsScheduledToRun(receive_callback_))
396 receive_callback_->PostRun(PP_ERROR_FAILED);
397 receive_callback_ = NULL;
400 if (TrackedCallback::IsPending(close_callback_)) {
401 if (!TrackedCallback::IsScheduledToRun(close_callback_))
402 close_callback_->PostRun(params.result());
403 close_callback_ = NULL;
407 void WebSocketResource::OnPluginMsgReceiveTextReply(
408 const ResourceMessageReplyParams& params,
409 const std::string& message) {
410 // Dispose packets after receiving an error or in invalid state.
411 if (error_was_received_ || !InValidStateToReceive(state_))
412 return;
414 // Append received data to queue.
415 received_messages_.push(scoped_refptr<Var>(new StringVar(message)));
417 if (!TrackedCallback::IsPending(receive_callback_) ||
418 TrackedCallback::IsScheduledToRun(receive_callback_)) {
419 return;
422 receive_callback_->Run(DoReceive());
425 void WebSocketResource::OnPluginMsgReceiveBinaryReply(
426 const ResourceMessageReplyParams& params,
427 const std::vector<uint8_t>& message) {
428 // Dispose packets after receiving an error or in invalid state.
429 if (error_was_received_ || !InValidStateToReceive(state_))
430 return;
432 // Append received data to queue.
433 scoped_refptr<Var> message_var(
434 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
435 message.size(),
436 &message.front()));
437 received_messages_.push(message_var);
439 if (!TrackedCallback::IsPending(receive_callback_) ||
440 TrackedCallback::IsScheduledToRun(receive_callback_)) {
441 return;
444 receive_callback_->Run(DoReceive());
447 void WebSocketResource::OnPluginMsgErrorReply(
448 const ResourceMessageReplyParams& params) {
449 error_was_received_ = true;
451 if (!TrackedCallback::IsPending(receive_callback_) ||
452 TrackedCallback::IsScheduledToRun(receive_callback_)) {
453 return;
456 // No more text or binary messages will be received. If there is ongoing
457 // ReceiveMessage(), we must invoke the callback with error code here.
458 receive_callback_var_ = NULL;
459 receive_callback_->Run(PP_ERROR_FAILED);
462 void WebSocketResource::OnPluginMsgBufferedAmountReply(
463 const ResourceMessageReplyParams& params,
464 unsigned long buffered_amount) {
465 buffered_amount_ = buffered_amount;
468 void WebSocketResource::OnPluginMsgStateReply(
469 const ResourceMessageReplyParams& params,
470 int32_t state) {
471 state_ = static_cast<PP_WebSocketReadyState>(state);
474 void WebSocketResource::OnPluginMsgClosedReply(
475 const ResourceMessageReplyParams& params,
476 unsigned long buffered_amount,
477 bool was_clean,
478 unsigned short code,
479 const std::string& reason) {
480 OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason);
483 int32_t WebSocketResource::DoReceive() {
484 if (!receive_callback_var_)
485 return PP_OK;
487 *receive_callback_var_ = received_messages_.front()->GetPPVar();
488 received_messages_.pop();
489 receive_callback_var_ = NULL;
490 return PP_OK;
493 } // namespace proxy
494 } // namespace ppapi