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_reconnectDelay(defaultReconnectDelay) | 69 , m_reconnectDelay(defaultReconnectDelay) |
74 { | 70 { |
75 } | 71 } |
76 | 72 |
77 EventSource* EventSource::create(ExecutionContext* context, const String& url, c
onst EventSourceInit& eventSourceInit, ExceptionState& exceptionState) | 73 EventSource* EventSource::create(ExecutionContext* context, const String& url, c
onst EventSourceInit& eventSourceInit, ExceptionState& exceptionState) |
78 { | 74 { |
79 if (url.isEmpty()) { | 75 if (url.isEmpty()) { |
80 exceptionState.throwDOMException(SyntaxError, "Cannot open an EventSourc
e to an empty URL."); | 76 exceptionState.throwDOMException(SyntaxError, "Cannot open an EventSourc
e to an empty URL."); |
81 return nullptr; | 77 return nullptr; |
82 } | 78 } |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
120 ASSERT(m_state == CONNECTING); | 116 ASSERT(m_state == CONNECTING); |
121 ASSERT(!m_loader); | 117 ASSERT(!m_loader); |
122 ASSERT(executionContext()); | 118 ASSERT(executionContext()); |
123 | 119 |
124 ExecutionContext& executionContext = *this->executionContext(); | 120 ExecutionContext& executionContext = *this->executionContext(); |
125 ResourceRequest request(m_url); | 121 ResourceRequest request(m_url); |
126 request.setHTTPMethod(HTTPNames::GET); | 122 request.setHTTPMethod(HTTPNames::GET); |
127 request.setHTTPHeaderField(HTTPNames::Accept, "text/event-stream"); | 123 request.setHTTPHeaderField(HTTPNames::Accept, "text/event-stream"); |
128 request.setHTTPHeaderField(HTTPNames::Cache_Control, "no-cache"); | 124 request.setHTTPHeaderField(HTTPNames::Cache_Control, "no-cache"); |
129 request.setRequestContext(WebURLRequest::RequestContextEventSource); | 125 request.setRequestContext(WebURLRequest::RequestContextEventSource); |
130 if (!m_lastEventId.isEmpty()) { | 126 if (m_parser && !m_parser->lastEventId().isEmpty()) { |
131 // HTTP headers are Latin-1 byte strings, but the Last-Event-ID header i
s encoded as UTF-8. | 127 // HTTP headers are Latin-1 byte strings, but the Last-Event-ID header i
s encoded as UTF-8. |
132 // TODO(davidben): This should be captured in the type of setHTTPHeaderF
ield's arguments. | 128 // TODO(davidben): This should be captured in the type of setHTTPHeaderF
ield's arguments. |
133 CString lastEventIdUtf8 = m_lastEventId.utf8(); | 129 CString lastEventIdUtf8 = m_parser->lastEventId().utf8(); |
134 request.setHTTPHeaderField(HTTPNames::Last_Event_ID, AtomicString(reinte
rpret_cast<const LChar*>(lastEventIdUtf8.data()), lastEventIdUtf8.length())); | 130 request.setHTTPHeaderField(HTTPNames::Last_Event_ID, AtomicString(reinte
rpret_cast<const LChar*>(lastEventIdUtf8.data()), lastEventIdUtf8.length())); |
135 } | 131 } |
136 | 132 |
137 SecurityOrigin* origin = executionContext.securityOrigin(); | 133 SecurityOrigin* origin = executionContext.securityOrigin(); |
138 | 134 |
139 ThreadableLoaderOptions options; | 135 ThreadableLoaderOptions options; |
140 options.preflightPolicy = PreventPreflight; | 136 options.preflightPolicy = PreventPreflight; |
141 options.crossOriginRequestPolicy = UseAccessControl; | 137 options.crossOriginRequestPolicy = UseAccessControl; |
142 options.contentSecurityPolicyEnforcement = ContentSecurityPolicy::shouldBypa
ssMainWorld(&executionContext) ? DoNotEnforceContentSecurityPolicy : EnforceConn
ectSrcDirective; | 138 options.contentSecurityPolicyEnforcement = ContentSecurityPolicy::shouldBypa
ssMainWorld(&executionContext) ? DoNotEnforceContentSecurityPolicy : EnforceConn
ectSrcDirective; |
143 | 139 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
189 { | 185 { |
190 return m_state; | 186 return m_state; |
191 } | 187 } |
192 | 188 |
193 void EventSource::close() | 189 void EventSource::close() |
194 { | 190 { |
195 if (m_state == CLOSED) { | 191 if (m_state == CLOSED) { |
196 ASSERT(!m_loader); | 192 ASSERT(!m_loader); |
197 return; | 193 return; |
198 } | 194 } |
| 195 if (m_parser) |
| 196 m_parser->stop(); |
199 | 197 |
200 // Stop trying to reconnect if EventSource was explicitly closed or if Activ
eDOMObject::stop() was called. | 198 // Stop trying to reconnect if EventSource was explicitly closed or if Activ
eDOMObject::stop() was called. |
201 if (m_connectTimer.isActive()) { | 199 if (m_connectTimer.isActive()) { |
202 m_connectTimer.stop(); | 200 m_connectTimer.stop(); |
203 } | 201 } |
204 | 202 |
205 if (m_loader) { | 203 if (m_loader) { |
206 m_loader->cancel(); | 204 m_loader->cancel(); |
207 m_loader = nullptr; | 205 m_loader = nullptr; |
208 } | 206 } |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
249 message.appendLiteral("EventSource's response has a MIME type (\""); | 247 message.appendLiteral("EventSource's response has a MIME type (\""); |
250 message.append(response.mimeType()); | 248 message.append(response.mimeType()); |
251 message.appendLiteral("\") that is not \"text/event-stream\". Aborti
ng the connection."); | 249 message.appendLiteral("\") that is not \"text/event-stream\". Aborti
ng the connection."); |
252 // FIXME: We are missing the source line. | 250 // FIXME: We are missing the source line. |
253 executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessa
geSource, ErrorMessageLevel, message.toString())); | 251 executionContext()->addConsoleMessage(ConsoleMessage::create(JSMessa
geSource, ErrorMessageLevel, message.toString())); |
254 } | 252 } |
255 } | 253 } |
256 | 254 |
257 if (responseIsValid) { | 255 if (responseIsValid) { |
258 m_state = OPEN; | 256 m_state = OPEN; |
| 257 AtomicString lastEventId; |
| 258 if (m_parser) { |
| 259 // The new parser takes over the event ID. |
| 260 lastEventId = m_parser->lastEventId(); |
| 261 } |
| 262 m_parser = new EventSourceParser(lastEventId, this); |
259 dispatchEvent(Event::create(EventTypeNames::open)); | 263 dispatchEvent(Event::create(EventTypeNames::open)); |
260 } else { | 264 } else { |
261 m_loader->cancel(); | 265 m_loader->cancel(); |
262 dispatchEvent(Event::create(EventTypeNames::error)); | 266 dispatchEvent(Event::create(EventTypeNames::error)); |
263 } | 267 } |
264 } | 268 } |
265 | 269 |
266 void EventSource::didReceiveData(const char* data, unsigned length) | 270 void EventSource::didReceiveData(const char* data, unsigned length) |
267 { | 271 { |
268 ASSERT(m_state == OPEN); | 272 ASSERT(m_state == OPEN); |
269 ASSERT(m_loader); | 273 ASSERT(m_loader); |
| 274 ASSERT(m_parser); |
270 | 275 |
271 append(m_receiveBuf, m_decoder->decode(data, length)); | 276 m_parser->addBytes(data, length); |
272 parseEventStream(); | |
273 } | 277 } |
274 | 278 |
275 void EventSource::didFinishLoading(unsigned long, double) | 279 void EventSource::didFinishLoading(unsigned long, double) |
276 { | 280 { |
277 ASSERT(m_state == OPEN); | 281 ASSERT(m_state == OPEN); |
278 ASSERT(m_loader); | 282 ASSERT(m_loader); |
279 | 283 |
280 if (m_receiveBuf.size() > 0 || m_data.size() > 0) { | |
281 parseEventStream(); | |
282 | |
283 // Discard everything that has not been dispatched by now. | |
284 m_receiveBuf.clear(); | |
285 m_data.clear(); | |
286 m_eventName = emptyAtom; | |
287 m_currentlyParsedEventId = nullAtom; | |
288 } | |
289 networkRequestEnded(); | 284 networkRequestEnded(); |
290 } | 285 } |
291 | 286 |
292 void EventSource::didFail(const ResourceError& error) | 287 void EventSource::didFail(const ResourceError& error) |
293 { | 288 { |
294 ASSERT(m_state != CLOSED); | 289 ASSERT(m_state != CLOSED); |
295 ASSERT(m_loader); | 290 ASSERT(m_loader); |
296 | 291 |
297 if (error.isCancellation()) | 292 if (error.isCancellation()) |
298 m_state = CLOSED; | 293 m_state = CLOSED; |
(...skipping 10 matching lines...) Expand all Loading... |
309 abortConnectionAttempt(); | 304 abortConnectionAttempt(); |
310 } | 305 } |
311 | 306 |
312 void EventSource::didFailRedirectCheck() | 307 void EventSource::didFailRedirectCheck() |
313 { | 308 { |
314 ASSERT(m_loader); | 309 ASSERT(m_loader); |
315 | 310 |
316 abortConnectionAttempt(); | 311 abortConnectionAttempt(); |
317 } | 312 } |
318 | 313 |
| 314 void EventSource::onMessageEvent(const AtomicString& eventType, const String& da
ta, const AtomicString& lastEventId) |
| 315 { |
| 316 RefPtrWillBeRawPtr<MessageEvent> e = MessageEvent::create(); |
| 317 e->initMessageEvent(eventType, false, false, SerializedScriptValueFactory::i
nstance().create(data), m_eventStreamOrigin, lastEventId, 0, nullptr); |
| 318 |
| 319 InspectorInstrumentation::willDispatchEventSourceEvent(executionContext(), t
his, eventType, lastEventId, data); |
| 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 | 343 |
434 bool EventSource::hasPendingActivity() const | 344 bool EventSource::hasPendingActivity() const |
435 { | 345 { |
436 return m_state != CLOSED; | 346 return m_state != CLOSED; |
437 } | 347 } |
438 | 348 |
439 PassRefPtrWillBeRawPtr<MessageEvent> EventSource::createMessageEvent() | |
440 { | |
441 RefPtrWillBeRawPtr<MessageEvent> event = MessageEvent::create(); | |
442 event->initMessageEvent(m_eventName.isEmpty() ? EventTypeNames::message : m_
eventName, false, false, SerializedScriptValueFactory::instance().create(String(
m_data)), m_eventStreamOrigin, m_lastEventId, 0, nullptr); | |
443 m_data.clear(); | |
444 return event.release(); | |
445 } | |
446 | |
447 DEFINE_TRACE(EventSource) | 349 DEFINE_TRACE(EventSource) |
448 { | 350 { |
| 351 visitor->trace(m_parser); |
449 RefCountedGarbageCollectedEventTargetWithInlineData::trace(visitor); | 352 RefCountedGarbageCollectedEventTargetWithInlineData::trace(visitor); |
450 ActiveDOMObject::trace(visitor); | 353 ActiveDOMObject::trace(visitor); |
| 354 EventSourceParser::Client::trace(visitor); |
451 } | 355 } |
452 | 356 |
453 } // namespace blink | 357 } // namespace blink |
OLD | NEW |