| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2009, 2012 Ericsson AB. All rights reserved. | 2 * Copyright (C) 2009, 2012 Ericsson AB. All rights reserved. |
| 3 * Copyright (C) 2010 Apple Inc. All rights reserved. | 3 * Copyright (C) 2010 Apple Inc. All rights reserved. |
| 4 * Copyright (C) 2011, Code Aurora Forum. All rights reserved. | 4 * Copyright (C) 2011, Code Aurora Forum. All rights reserved. |
| 5 * | 5 * |
| 6 * Redistribution and use in source and binary forms, with or without | 6 * Redistribution and use in source and binary forms, with or without |
| 7 * modification, are permitted provided that the following conditions | 7 * modification, are permitted provided that the following conditions |
| 8 * are met: | 8 * are met: |
| 9 * | 9 * |
| 10 * 1. Redistributions of source code must retain the above copyright | 10 * 1. Redistributions of source code must retain the above copyright |
| (...skipping 26 matching lines...) Expand all Loading... |
| 37 #include "bindings/core/v8/SerializedScriptValue.h" | 37 #include "bindings/core/v8/SerializedScriptValue.h" |
| 38 #include "bindings/core/v8/SerializedScriptValueFactory.h" | 38 #include "bindings/core/v8/SerializedScriptValueFactory.h" |
| 39 #include "core/dom/Document.h" | 39 #include "core/dom/Document.h" |
| 40 #include "core/dom/ExceptionCode.h" | 40 #include "core/dom/ExceptionCode.h" |
| 41 #include "core/dom/ExecutionContext.h" | 41 #include "core/dom/ExecutionContext.h" |
| 42 #include "core/events/Event.h" | 42 #include "core/events/Event.h" |
| 43 #include "core/events/MessageEvent.h" | 43 #include "core/events/MessageEvent.h" |
| 44 #include "core/frame/LocalDOMWindow.h" | 44 #include "core/frame/LocalDOMWindow.h" |
| 45 #include "core/frame/LocalFrame.h" | 45 #include "core/frame/LocalFrame.h" |
| 46 #include "core/frame/csp/ContentSecurityPolicy.h" | 46 #include "core/frame/csp/ContentSecurityPolicy.h" |
| 47 #include "core/html/parser/TextResourceDecoder.h" | |
| 48 #include "core/inspector/ConsoleMessage.h" | 47 #include "core/inspector/ConsoleMessage.h" |
| 49 #include "core/inspector/InspectorInstrumentation.h" | 48 #include "core/inspector/InspectorInstrumentation.h" |
| 50 #include "core/loader/ThreadableLoader.h" | 49 #include "core/loader/ThreadableLoader.h" |
| 51 #include "core/page/EventSourceInit.h" | 50 #include "core/page/EventSourceInit.h" |
| 52 #include "platform/HTTPNames.h" | 51 #include "platform/HTTPNames.h" |
| 53 #include "platform/network/ResourceError.h" | 52 #include "platform/network/ResourceError.h" |
| 54 #include "platform/network/ResourceRequest.h" | 53 #include "platform/network/ResourceRequest.h" |
| 55 #include "platform/network/ResourceResponse.h" | 54 #include "platform/network/ResourceResponse.h" |
| 56 #include "platform/weborigin/SecurityOrigin.h" | 55 #include "platform/weborigin/SecurityOrigin.h" |
| 57 #include "public/platform/WebURLRequest.h" | 56 #include "public/platform/WebURLRequest.h" |
| 58 #include "wtf/ASCIICType.h" | |
| 59 #include "wtf/text/StringBuilder.h" | 57 #include "wtf/text/StringBuilder.h" |
| 60 | 58 |
| 61 namespace blink { | 59 namespace blink { |
| 62 | 60 |
| 63 const unsigned long long EventSource::defaultReconnectDelay = 3000; | 61 const unsigned long long EventSource::defaultReconnectDelay = 3000; |
| 64 | 62 |
| 65 inline EventSource::EventSource(ExecutionContext* context, const KURL& url, cons
t EventSourceInit& eventSourceInit) | 63 inline EventSource::EventSource(ExecutionContext* context, const KURL& url, cons
t EventSourceInit& eventSourceInit) |
| 66 : ActiveDOMObject(context) | 64 : ActiveDOMObject(context) |
| 67 , m_url(url) | 65 , m_url(url) |
| 68 , m_withCredentials(eventSourceInit.withCredentials()) | 66 , m_withCredentials(eventSourceInit.withCredentials()) |
| 69 , m_state(CONNECTING) | 67 , m_state(CONNECTING) |
| 70 , m_decoder(TextResourceDecoder::create("text/plain", "UTF-8")) | |
| 71 , m_connectTimer(this, &EventSource::connectTimerFired) | 68 , m_connectTimer(this, &EventSource::connectTimerFired) |
| 72 , m_discardTrailingNewline(false) | |
| 73 , m_requestInFlight(false) | 69 , m_requestInFlight(false) |
| 74 , m_reconnectDelay(defaultReconnectDelay) | 70 , m_reconnectDelay(defaultReconnectDelay) |
| 75 { | 71 { |
| 76 } | 72 } |
| 77 | 73 |
| 78 EventSource* EventSource::create(ExecutionContext* context, const String& url, c
onst EventSourceInit& eventSourceInit, ExceptionState& exceptionState) | 74 EventSource* EventSource::create(ExecutionContext* context, const String& url, c
onst EventSourceInit& eventSourceInit, ExceptionState& exceptionState) |
| 79 { | 75 { |
| 80 if (url.isEmpty()) { | 76 if (url.isEmpty()) { |
| 81 exceptionState.throwDOMException(SyntaxError, "Cannot open an EventSourc
e to an empty URL."); | 77 exceptionState.throwDOMException(SyntaxError, "Cannot open an EventSourc
e to an empty URL."); |
| 82 return nullptr; | 78 return nullptr; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 ASSERT(m_state == CONNECTING); | 117 ASSERT(m_state == CONNECTING); |
| 122 ASSERT(!m_requestInFlight); | 118 ASSERT(!m_requestInFlight); |
| 123 ASSERT(executionContext()); | 119 ASSERT(executionContext()); |
| 124 | 120 |
| 125 ExecutionContext& executionContext = *this->executionContext(); | 121 ExecutionContext& executionContext = *this->executionContext(); |
| 126 ResourceRequest request(m_url); | 122 ResourceRequest request(m_url); |
| 127 request.setHTTPMethod(HTTPNames::GET); | 123 request.setHTTPMethod(HTTPNames::GET); |
| 128 request.setHTTPHeaderField(HTTPNames::Accept, "text/event-stream"); | 124 request.setHTTPHeaderField(HTTPNames::Accept, "text/event-stream"); |
| 129 request.setHTTPHeaderField(HTTPNames::Cache_Control, "no-cache"); | 125 request.setHTTPHeaderField(HTTPNames::Cache_Control, "no-cache"); |
| 130 request.setRequestContext(WebURLRequest::RequestContextEventSource); | 126 request.setRequestContext(WebURLRequest::RequestContextEventSource); |
| 131 if (!m_lastEventId.isEmpty()) { | 127 if (m_parser && !m_parser->lastEventId().isEmpty()) { |
| 132 // HTTP headers are Latin-1 byte strings, but the Last-Event-ID header i
s encoded as UTF-8. | 128 // HTTP headers are Latin-1 byte strings, but the Last-Event-ID header i
s encoded as UTF-8. |
| 133 // TODO(davidben): This should be captured in the type of setHTTPHeaderF
ield's arguments. | 129 // TODO(davidben): This should be captured in the type of setHTTPHeaderF
ield's arguments. |
| 134 CString lastEventIdUtf8 = m_lastEventId.utf8(); | 130 CString lastEventIdUtf8 = m_parser->lastEventId().utf8(); |
| 135 request.setHTTPHeaderField(HTTPNames::Last_Event_ID, AtomicString(reinte
rpret_cast<const LChar*>(lastEventIdUtf8.data()), lastEventIdUtf8.length())); | 131 request.setHTTPHeaderField(HTTPNames::Last_Event_ID, AtomicString(reinte
rpret_cast<const LChar*>(lastEventIdUtf8.data()), lastEventIdUtf8.length())); |
| 136 } | 132 } |
| 137 | 133 |
| 138 SecurityOrigin* origin = executionContext.securityOrigin(); | 134 SecurityOrigin* origin = executionContext.securityOrigin(); |
| 139 | 135 |
| 140 ThreadableLoaderOptions options; | 136 ThreadableLoaderOptions options; |
| 141 options.preflightPolicy = PreventPreflight; | 137 options.preflightPolicy = PreventPreflight; |
| 142 options.crossOriginRequestPolicy = UseAccessControl; | 138 options.crossOriginRequestPolicy = UseAccessControl; |
| 143 options.contentSecurityPolicyEnforcement = ContentSecurityPolicy::shouldBypa
ssMainWorld(&executionContext) ? DoNotEnforceContentSecurityPolicy : EnforceConn
ectSrcDirective; | 139 options.contentSecurityPolicyEnforcement = ContentSecurityPolicy::shouldBypa
ssMainWorld(&executionContext) ? DoNotEnforceContentSecurityPolicy : EnforceConn
ectSrcDirective; |
| 144 | 140 |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 195 { | 191 { |
| 196 return m_state; | 192 return m_state; |
| 197 } | 193 } |
| 198 | 194 |
| 199 void EventSource::close() | 195 void EventSource::close() |
| 200 { | 196 { |
| 201 if (m_state == CLOSED) { | 197 if (m_state == CLOSED) { |
| 202 ASSERT(!m_requestInFlight); | 198 ASSERT(!m_requestInFlight); |
| 203 return; | 199 return; |
| 204 } | 200 } |
| 201 if (m_parser) |
| 202 m_parser->stop(); |
| 205 | 203 |
| 206 // Stop trying to reconnect if EventSource was explicitly closed or if Activ
eDOMObject::stop() was called. | 204 // Stop trying to reconnect if EventSource was explicitly closed or if Activ
eDOMObject::stop() was called. |
| 207 if (m_connectTimer.isActive()) { | 205 if (m_connectTimer.isActive()) { |
| 208 m_connectTimer.stop(); | 206 m_connectTimer.stop(); |
| 209 } | 207 } |
| 210 | 208 |
| 211 if (m_requestInFlight) | 209 if (m_requestInFlight) |
| 212 m_loader->cancel(); | 210 m_loader->cancel(); |
| 213 | 211 |
| 214 m_state = CLOSED; | 212 m_state = CLOSED; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 253 message.appendLiteral("EventSource's response has a MIME type (\""); | 251 message.appendLiteral("EventSource's response has a MIME type (\""); |
| 254 message.append(response.mimeType()); | 252 message.append(response.mimeType()); |
| 255 message.appendLiteral("\") that is not \"text/event-stream\". Aborti
ng the connection."); | 253 message.appendLiteral("\") that is not \"text/event-stream\". Aborti
ng the connection."); |
| 256 // FIXME: We are missing the source line. | 254 // FIXME: We are missing the source line. |
| 257 executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessa
geSource, ErrorMessageLevel, message.toString())); | 255 executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessa
geSource, ErrorMessageLevel, message.toString())); |
| 258 } | 256 } |
| 259 } | 257 } |
| 260 | 258 |
| 261 if (responseIsValid) { | 259 if (responseIsValid) { |
| 262 m_state = OPEN; | 260 m_state = OPEN; |
| 261 AtomicString lastEventId; |
| 262 if (m_parser) { |
| 263 // The new parser takes over the event ID. |
| 264 lastEventId = m_parser->lastEventId(); |
| 265 } |
| 266 m_parser = new EventSourceParser(lastEventId, this); |
| 263 dispatchEvent(Event::create(EventTypeNames::open)); | 267 dispatchEvent(Event::create(EventTypeNames::open)); |
| 264 } else { | 268 } else { |
| 265 m_loader->cancel(); | 269 m_loader->cancel(); |
| 266 dispatchEvent(Event::create(EventTypeNames::error)); | 270 dispatchEvent(Event::create(EventTypeNames::error)); |
| 267 } | 271 } |
| 268 } | 272 } |
| 269 | 273 |
| 270 void EventSource::didReceiveData(const char* data, unsigned length) | 274 void EventSource::didReceiveData(const char* data, unsigned length) |
| 271 { | 275 { |
| 272 ASSERT(m_state == OPEN); | 276 ASSERT(m_state == OPEN); |
| 273 ASSERT(m_requestInFlight); | 277 ASSERT(m_requestInFlight); |
| 278 ASSERT(m_parser); |
| 274 | 279 |
| 275 append(m_receiveBuf, m_decoder->decode(data, length)); | 280 m_parser->addBytes(data, length); |
| 276 parseEventStream(); | |
| 277 } | 281 } |
| 278 | 282 |
| 279 void EventSource::didFinishLoading(unsigned long, double) | 283 void EventSource::didFinishLoading(unsigned long, double) |
| 280 { | 284 { |
| 281 ASSERT(m_state == OPEN); | 285 ASSERT(m_state == OPEN); |
| 282 ASSERT(m_requestInFlight); | 286 ASSERT(m_requestInFlight); |
| 283 | 287 |
| 284 if (m_receiveBuf.size() > 0 || m_data.size() > 0) { | |
| 285 parseEventStream(); | |
| 286 | |
| 287 // Discard everything that has not been dispatched by now. | |
| 288 m_receiveBuf.clear(); | |
| 289 m_data.clear(); | |
| 290 m_eventName = emptyAtom; | |
| 291 m_currentlyParsedEventId = nullAtom; | |
| 292 } | |
| 293 networkRequestEnded(); | 288 networkRequestEnded(); |
| 294 } | 289 } |
| 295 | 290 |
| 296 void EventSource::didFail(const ResourceError& error) | 291 void EventSource::didFail(const ResourceError& error) |
| 297 { | 292 { |
| 298 ASSERT(m_state != CLOSED); | 293 ASSERT(m_state != CLOSED); |
| 299 ASSERT(m_requestInFlight); | 294 ASSERT(m_requestInFlight); |
| 300 | 295 |
| 301 if (error.isCancellation()) | 296 if (error.isCancellation()) |
| 302 m_state = CLOSED; | 297 m_state = CLOSED; |
| 303 networkRequestEnded(); | 298 networkRequestEnded(); |
| 304 } | 299 } |
| 305 | 300 |
| 306 void EventSource::didFailAccessControlCheck(const ResourceError& error) | 301 void EventSource::didFailAccessControlCheck(const ResourceError& error) |
| 307 { | 302 { |
| 308 String message = "EventSource cannot load " + error.failingURL() + ". " + er
ror.localizedDescription(); | 303 String message = "EventSource cannot load " + error.failingURL() + ". " + er
ror.localizedDescription(); |
| 309 executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessageSource
, ErrorMessageLevel, message)); | 304 executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessageSource
, ErrorMessageLevel, message)); |
| 310 | 305 |
| 311 abortConnectionAttempt(); | 306 abortConnectionAttempt(); |
| 312 } | 307 } |
| 313 | 308 |
| 314 void EventSource::didFailRedirectCheck() | 309 void EventSource::didFailRedirectCheck() |
| 315 { | 310 { |
| 316 abortConnectionAttempt(); | 311 abortConnectionAttempt(); |
| 317 } | 312 } |
| 318 | 313 |
| 314 void EventSource::onMessageEvent(const AtomicString& event, const String& data,
const AtomicString& lastEventId) |
| 315 { |
| 316 RefPtrWillBeRawPtr<MessageEvent> e = MessageEvent::create(); |
| 317 e->initMessageEvent(event, false, false, SerializedScriptValueFactory::insta
nce().create(data), m_eventStreamOrigin, lastEventId, 0, nullptr); |
| 318 |
| 319 InspectorInstrumentation::willDispachEventSourceEvent(executionContext(), th
is, event, lastEventId, data.charactersWithNullTermination()); |
| 320 dispatchEvent(e); |
| 321 } |
| 322 |
| 323 void EventSource::onReconnectionTimeSet(unsigned long long reconnectionTime) |
| 324 { |
| 325 m_reconnectDelay = reconnectionTime; |
| 326 } |
| 327 |
| 319 void EventSource::abortConnectionAttempt() | 328 void EventSource::abortConnectionAttempt() |
| 320 { | 329 { |
| 321 ASSERT(m_state == CONNECTING); | 330 ASSERT(m_state == CONNECTING); |
| 322 | 331 |
| 323 m_loader = nullptr; | 332 m_loader = nullptr; |
| 324 m_state = CLOSED; | 333 m_state = CLOSED; |
| 325 networkRequestEnded(); | 334 networkRequestEnded(); |
| 326 | 335 |
| 327 dispatchEvent(Event::create(EventTypeNames::error)); | 336 dispatchEvent(Event::create(EventTypeNames::error)); |
| 328 } | 337 } |
| 329 | 338 |
| 330 void EventSource::parseEventStream() | |
| 331 { | |
| 332 unsigned bufPos = 0; | |
| 333 unsigned bufSize = m_receiveBuf.size(); | |
| 334 while (bufPos < bufSize) { | |
| 335 if (m_discardTrailingNewline) { | |
| 336 if (m_receiveBuf[bufPos] == '\n') | |
| 337 bufPos++; | |
| 338 m_discardTrailingNewline = false; | |
| 339 } | |
| 340 | |
| 341 int lineLength = -1; | |
| 342 int fieldLength = -1; | |
| 343 for (unsigned i = bufPos; lineLength < 0 && i < bufSize; i++) { | |
| 344 switch (m_receiveBuf[i]) { | |
| 345 case ':': | |
| 346 if (fieldLength < 0) | |
| 347 fieldLength = i - bufPos; | |
| 348 break; | |
| 349 case '\r': | |
| 350 m_discardTrailingNewline = true; | |
| 351 case '\n': | |
| 352 lineLength = i - bufPos; | |
| 353 break; | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 if (lineLength < 0) | |
| 358 break; | |
| 359 | |
| 360 parseEventStreamLine(bufPos, fieldLength, lineLength); | |
| 361 bufPos += lineLength + 1; | |
| 362 | |
| 363 // EventSource.close() might've been called by one of the message event
handlers. | |
| 364 // Per spec, no further messages should be fired after that. | |
| 365 if (m_state == CLOSED) | |
| 366 break; | |
| 367 } | |
| 368 | |
| 369 if (bufPos == bufSize) | |
| 370 m_receiveBuf.clear(); | |
| 371 else if (bufPos) | |
| 372 m_receiveBuf.remove(0, bufPos); | |
| 373 } | |
| 374 | |
| 375 void EventSource::parseEventStreamLine(unsigned bufPos, int fieldLength, int lin
eLength) | |
| 376 { | |
| 377 if (!lineLength) { | |
| 378 if (!m_data.isEmpty()) { | |
| 379 m_data.removeLast(); | |
| 380 if (!m_currentlyParsedEventId.isNull()) { | |
| 381 m_lastEventId = m_currentlyParsedEventId; | |
| 382 m_currentlyParsedEventId = nullAtom; | |
| 383 } | |
| 384 InspectorInstrumentation::willDispachEventSourceEvent(executionConte
xt(), this, m_eventName.isEmpty() ? EventTypeNames::message : m_eventName, m_las
tEventId, m_data); | |
| 385 dispatchEvent(createMessageEvent()); | |
| 386 } | |
| 387 if (!m_eventName.isEmpty()) | |
| 388 m_eventName = emptyAtom; | |
| 389 } else if (fieldLength) { | |
| 390 bool noValue = fieldLength < 0; | |
| 391 | |
| 392 String field(&m_receiveBuf[bufPos], noValue ? lineLength : fieldLength); | |
| 393 int step; | |
| 394 if (noValue) | |
| 395 step = lineLength; | |
| 396 else if (m_receiveBuf[bufPos + fieldLength + 1] != ' ') | |
| 397 step = fieldLength + 1; | |
| 398 else | |
| 399 step = fieldLength + 2; | |
| 400 bufPos += step; | |
| 401 int valueLength = lineLength - step; | |
| 402 | |
| 403 if (field == "data") { | |
| 404 if (valueLength) | |
| 405 m_data.append(&m_receiveBuf[bufPos], valueLength); | |
| 406 m_data.append('\n'); | |
| 407 } else if (field == "event") { | |
| 408 m_eventName = valueLength ? AtomicString(&m_receiveBuf[bufPos], valu
eLength) : ""; | |
| 409 } else if (field == "id") { | |
| 410 m_currentlyParsedEventId = valueLength ? AtomicString(&m_receiveBuf[
bufPos], valueLength) : ""; | |
| 411 } else if (field == "retry") { | |
| 412 bool hasOnlyDigits = true; | |
| 413 for (int i = 0; i < valueLength && hasOnlyDigits; ++i) { | |
| 414 hasOnlyDigits = isASCIIDigit(m_receiveBuf[bufPos + i]); | |
| 415 } | |
| 416 if (!valueLength) { | |
| 417 m_reconnectDelay = defaultReconnectDelay; | |
| 418 } else if (hasOnlyDigits) { | |
| 419 String value(&m_receiveBuf[bufPos], valueLength); | |
| 420 bool ok; | |
| 421 unsigned long long retry = value.toUInt64(&ok); | |
| 422 if (ok) | |
| 423 m_reconnectDelay = retry; | |
| 424 } | |
| 425 } | |
| 426 } | |
| 427 } | |
| 428 | |
| 429 void EventSource::stop() | 339 void EventSource::stop() |
| 430 { | 340 { |
| 431 close(); | 341 close(); |
| 432 | 342 |
| 433 // (Non)Oilpan: In order to make Worker shutdowns clean, | 343 // (Non)Oilpan: In order to make Worker shutdowns clean, |
| 434 // deref the loader. This will in turn deref its | 344 // deref the loader. This will in turn deref its |
| 435 // RefPtr<WorkerGlobalScope>. | 345 // RefPtr<WorkerGlobalScope>. |
| 436 // | 346 // |
| 437 // Worth doing regardless, it is no longer of use. | 347 // Worth doing regardless, it is no longer of use. |
| 438 m_loader = nullptr; | 348 m_loader = nullptr; |
| 439 } | 349 } |
| 440 | 350 |
| 441 bool EventSource::hasPendingActivity() const | 351 bool EventSource::hasPendingActivity() const |
| 442 { | 352 { |
| 443 return m_state != CLOSED; | 353 return m_state != CLOSED; |
| 444 } | 354 } |
| 445 | 355 |
| 446 PassRefPtrWillBeRawPtr<MessageEvent> EventSource::createMessageEvent() | |
| 447 { | |
| 448 RefPtrWillBeRawPtr<MessageEvent> event = MessageEvent::create(); | |
| 449 event->initMessageEvent(m_eventName.isEmpty() ? EventTypeNames::message : m_
eventName, false, false, SerializedScriptValueFactory::instance().create(String(
m_data)), m_eventStreamOrigin, m_lastEventId, 0, nullptr); | |
| 450 m_data.clear(); | |
| 451 return event.release(); | |
| 452 } | |
| 453 | |
| 454 DEFINE_TRACE(EventSource) | 356 DEFINE_TRACE(EventSource) |
| 455 { | 357 { |
| 358 visitor->trace(m_parser); |
| 456 RefCountedGarbageCollectedEventTargetWithInlineData::trace(visitor); | 359 RefCountedGarbageCollectedEventTargetWithInlineData::trace(visitor); |
| 457 ActiveDOMObject::trace(visitor); | 360 ActiveDOMObject::trace(visitor); |
| 361 EventSourceParser::Client::trace(visitor); |
| 458 } | 362 } |
| 459 | 363 |
| 460 } // namespace blink | 364 } // namespace blink |
| OLD | NEW |