Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1011)

Side by Side Diff: third_party/WebKit/Source/core/page/EventSource.cpp

Issue 1642563002: Introduce EventSourceParser (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@event-source-retry-fix
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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)
tyoshino (SeeGerritForStatus) 2016/01/29 07:00:14 eventType
yhirano 2016/01/29 11:41:16 Done.
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());
tyoshino (SeeGerritForStatus) 2016/01/29 07:00:14 charactersWithNullTermination() appends \0 to the
yhirano 2016/01/29 11:41:16 Done.
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698