--- a/content/base/public/Makefile.in
+++ b/content/base/public/Makefile.in
@@ -119,12 +119,13 @@ XPIDLSRCS = \
nsIScriptEventManager.idl \
nsIImageLoadingContent.idl \
nsIObjectLoadingContent.idl \
nsIFrameLoader.idl \
nsIXMLHttpRequest.idl \
nsIContentSecurityPolicy.idl \
nsIFrameMessageManager.idl \
nsIWebSocket.idl \
+ nsIEventSource.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/base/public/nsIEventSource.idl
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * France Telecom Research and Development.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Joshua Randall <jcrandall@alum.mit.edu> (original author)
+ * Wellington Fernando de Macedo <wfernandom2004@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * The nsIEventSource interface is the interface for server-sent
+ * DOM events as described in
+ * http://dev.w3.org/html5/eventsource/#eventsource
+ *
+ */
+
+#include "nsISupports.idl"
+
+interface nsIDOMEventListener;
+interface nsIPrincipal;
+interface nsIScriptContext;
+interface nsPIDOMWindow;
+
+[scriptable, uuid(755e2d2d-a836-4539-83f4-16b51156341f)]
+interface nsIEventSource : nsISupports
+{
+ readonly attribute DOMString url;
+
+ // ready state
+ const unsigned short CONNECTING = 0;
+ const unsigned short OPEN = 1;
+ const unsigned short CLOSED = 2;
+ readonly attribute long readyState;
+
+ // event handler attributes
+ attribute nsIDOMEventListener onopen;
+ attribute nsIDOMEventListener onmessage;
+ attribute nsIDOMEventListener onerror;
+
+ /**
+ * Close the connection, if any, and set the readyState attribute to CLOSED.
+ * If the connection is already closed, the method does nothing.
+ */
+ void close();
+
+ /**
+ * Initialize the object for use from C++ code with the principal, script
+ * context, and owner window that should be used.
+ *
+ * @param principal The principal to use for the request. This must not be
+ * null.
+ * @param scriptContext The script context to use for the request. May be
+ * null.
+ * @param ownerWindow The associated window for the request. May be null.
+ * @param url The EventSource's url. This must not be empty.
+ */
+ [noscript] void init(in nsIPrincipal principal,
+ in nsIScriptContext scriptContext,
+ in nsPIDOMWindow ownerWindow,
+ in DOMString url);
+};
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -147,16 +147,17 @@ CPPSRCS = \
nsXMLContentSerializer.cpp \
nsXMLHttpRequest.cpp \
nsXMLNameSpaceMap.cpp \
Link.cpp \
nsFileDataProtocolHandler.cpp \
nsFrameMessageManager.cpp \
nsInProcessTabChildGlobal.cpp \
ThirdPartyUtil.cpp \
+ nsEventSource.cpp \
$(NULL)
# Are we targeting x86-32 or x86-64? If so, we want to include SSE2 code for
# nsTextFragment.cpp
ifneq (,$(INTEL_ARCHITECTURE))
CPPSRCS += nsTextFragmentSSE2.cpp
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -592,16 +592,18 @@ nsContentUtils::InitializeEventTable() {
{ nsGkAtoms::onMozBeforeResize, NS_BEFORERESIZE_EVENT, EventNameType_None, NS_EVENT },
{ nsGkAtoms::onresize, NS_RESIZE_EVENT,
(EventNameType_HTMLXUL | EventNameType_SVGSVG), NS_EVENT },
{ nsGkAtoms::onscroll, NS_SCROLL_EVENT,
(EventNameType_HTMLXUL | EventNameType_SVGSVG), NS_EVENT_NULL },
{ nsGkAtoms::oncopy, NS_COPY, EventNameType_HTMLXUL, NS_EVENT },
{ nsGkAtoms::oncut, NS_CUT, EventNameType_HTMLXUL, NS_EVENT },
{ nsGkAtoms::onpaste, NS_PASTE, EventNameType_HTMLXUL, NS_EVENT },
+ { nsGkAtoms::onopen, NS_OPEN, EventNameType_None, NS_EVENT },
+ { nsGkAtoms::onmessage, NS_MESSAGE, EventNameType_None, NS_EVENT },
// XUL specific events
{ nsGkAtoms::ontext, NS_TEXT_TEXT, EventNameType_XUL, NS_EVENT_NULL },
{ nsGkAtoms::oncompositionstart, NS_COMPOSITION_START, EventNameType_XUL, NS_COMPOSITION_EVENT },
{ nsGkAtoms::oncompositionend, NS_COMPOSITION_END, EventNameType_XUL, NS_COMPOSITION_EVENT },
{ nsGkAtoms::oncommand, NS_XUL_COMMAND, EventNameType_XUL, NS_INPUT_EVENT },
new file mode 100755
--- /dev/null
+++ b/content/base/src/nsEventSource.cpp
@@ -0,0 +1,1633 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Wellington Fernando de Macedo and Clayton Williams.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Wellington Fernando de Macedo <wfernandom2004@gmail.com>
+ * Clayton Williams <claytonw@mit.edu>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsEventSource.h"
+#include "nsNetUtil.h"
+#include "nsMimeTypes.h"
+#include "nsDOMMessageEvent.h"
+#include "nsIJSContextStack.h"
+#include "nsIPromptFactory.h"
+#include "nsIWindowWatcher.h"
+#include "nsPresContext.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIConsoleService.h"
+#include "nsIObserverService.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "jsdbgapi.h"
+#include "nsJSUtils.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIScriptError.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIChannelPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+
+#define REPLACEMENT_CHAR (PRUnichar)0xFFFD
+#define BOM_CHAR (PRUnichar)0xFEFF
+#define SPACE_CHAR (PRUnichar)0x0020
+#define CR_CHAR (PRUnichar)0x000D
+#define LF_CHAR (PRUnichar)0x000A
+#define COLON_CHAR (PRUnichar)0x003A
+
+#define DEFAULT_BUFFER_SIZE 4096
+
+// Reconnection time related values in milliseconds. The default one is equal
+// to the default value of the pref dom.server-events.default-reconnection-time
+#define MIN_RECONNECTION_TIME_VALUE 500
+#define DEFAULT_RECONNECTION_TIME_VALUE 5000
+#define MAX_RECONNECTION_TIME_VALUE PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
+
+nsEventSource::nsEventSource() :
+ mStatus(PARSE_STATE_OFF),
+ mFrozen(PR_FALSE),
+ mErrorLoadOnRedirect(PR_FALSE),
+ mGoingToDispatchAllMessages(PR_FALSE),
+ mLastConvertionResult(NS_OK),
+ mReadyState(nsIEventSource::CONNECTING),
+ mScriptLine(0),
+ mWindowID(0)
+{
+}
+
+nsEventSource::~nsEventSource()
+{
+ Close();
+
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ mListenerManager = nsnull;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsEventSource::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsEventSource)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsEventSource,
+ nsDOMEventTargetWrapperCache)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSrc)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNotificationCallbacks)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mLoadGroup)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannelEventSink)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mHttpChannel)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mTimer)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnOpenListener)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnMessageListener)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mUnicodeDecoder)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsEventSource, nsDOMEventTargetWrapperCache)
+ tmp->Close();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnOpenListener)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnMessageListener)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnErrorListener)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+DOMCI_DATA(EventSource, nsEventSource)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsEventSource)
+ NS_INTERFACE_MAP_ENTRY(nsIEventSource)
+ NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(EventSource)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetWrapperCache)
+
+NS_IMPL_ADDREF_INHERITED(nsEventSource, nsDOMEventTargetWrapperCache)
+NS_IMPL_RELEASE_INHERITED(nsEventSource, nsDOMEventTargetWrapperCache)
+
+//-----------------------------------------------------------------------------
+// nsEventSource::nsIEventSource
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsEventSource::GetUrl(nsAString& aURL)
+{
+ aURL = mOriginalURL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEventSource::GetReadyState(PRInt32 *aReadyState)
+{
+ NS_ENSURE_ARG_POINTER(aReadyState);
+ *aReadyState = mReadyState;
+ return NS_OK;
+}
+
+#define NS_EVENTSRC_IMPL_DOMEVENTLISTENER(_eventlistenername, _eventlistener) \
+ NS_IMETHODIMP \
+ nsEventSource::GetOn##_eventlistenername(nsIDOMEventListener * *aListener) \
+ { \
+ return GetInnerEventListener(_eventlistener, aListener); \
+ } \
+ \
+ NS_IMETHODIMP \
+ nsEventSource::SetOn##_eventlistenername(nsIDOMEventListener * aListener) \
+ { \
+ return RemoveAddEventListener(NS_LITERAL_STRING(#_eventlistenername), \
+ _eventlistener, aListener); \
+ }
+
+NS_EVENTSRC_IMPL_DOMEVENTLISTENER(open, mOnOpenListener)
+NS_EVENTSRC_IMPL_DOMEVENTLISTENER(error, mOnErrorListener)
+NS_EVENTSRC_IMPL_DOMEVENTLISTENER(message, mOnMessageListener)
+
+NS_IMETHODIMP
+nsEventSource::Close()
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
+ os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
+ os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nsnull;
+ }
+
+ ResetConnection();
+
+ ClearFields();
+
+ while (mMessagesToDispatch.GetSize() != 0) {
+ delete static_cast<Message*>(mMessagesToDispatch.PopFront());
+ }
+
+ mSrc = nsnull;
+ mFrozen = PR_FALSE;
+
+ mScriptContext = nsnull;
+ mOwner = nsnull;
+
+ mUnicodeDecoder = nsnull;
+
+ mReadyState = nsIEventSource::CLOSED;
+
+ return NS_OK;
+}
+
+/**
+ * This Init method should only be called by C++ consumers.
+ */
+NS_IMETHODIMP
+nsEventSource::Init(nsIPrincipal* aPrincipal,
+ nsIScriptContext* aScriptContext,
+ nsPIDOMWindow* aOwnerWindow,
+ const nsAString& aURL)
+{
+ NS_ENSURE_ARG(aPrincipal);
+
+ if (mReadyState != nsIEventSource::CONNECTING || !PrefEnabled()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ mPrincipal = aPrincipal;
+ mScriptContext = aScriptContext;
+ if (aOwnerWindow) {
+ mOwner = aOwnerWindow->IsOuterWindow() ?
+ aOwnerWindow->GetCurrentInnerWindow() : aOwnerWindow;
+ } else {
+ mOwner = nsnull;
+ }
+
+ nsCOMPtr<nsIJSContextStack> stack =
+ do_GetService("@mozilla.org/js/xpc/ContextStack;1");
+ JSContext* cx = nsnull;
+ if (stack && NS_SUCCEEDED(stack->Peek(&cx)) && cx) {
+ const char *filename;
+ if (nsJSUtils::GetCallingLocation(cx, &filename, &mScriptLine)) {
+ mScriptFile.AssignASCII(filename);
+ }
+
+ mWindowID = nsJSUtils::GetCurrentlyRunningCodeWindowID(cx);
+ }
+
+ // Get the load group for the page. When requesting we'll add ourselves to it.
+ // This way any pending requests will be automatically aborted if the user
+ // leaves the page.
+ if (mScriptContext) {
+ nsCOMPtr<nsIDocument> doc =
+ nsContentUtils::GetDocumentFromScriptContext(mScriptContext);
+ if (doc) {
+ mLoadGroup = doc->GetDocumentLoadGroup();
+ }
+ }
+
+ // get the src
+ nsCOMPtr<nsIURI> baseURI;
+ nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> srcURI;
+ rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nsnull, baseURI);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ // we observe when the window freezes and thaws
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, PR_TRUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, PR_TRUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = os->AddObserver(this, DOM_WINDOW_THAWED_TOPIC, PR_TRUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsXPIDLCString origin;
+ rv = mPrincipal->GetOrigin(getter_Copies(origin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCAutoString spec;
+ rv = srcURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOriginalURL = NS_ConvertUTF8toUTF16(spec);
+ mSrc = srcURI;
+ mOrigin = origin;
+
+ mReconnectionTime =
+ nsContentUtils::GetIntPref("dom.server-events.default-reconnection-time",
+ DEFAULT_RECONNECTION_TIME_VALUE);
+
+ nsCOMPtr<nsICharsetConverterManager> convManager =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = convManager->GetUnicodeDecoder("UTF-8", getter_AddRefs(mUnicodeDecoder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mUnicodeDecoder->SetInputErrorBehavior(nsIUnicodeDecoder::kOnError_Recover);
+
+ // the constructor should throw a SYNTAX_ERROR only if it fails resolving the
+ // url parameter, so we don't care about the InitChannelAndRequestEventSource
+ // result.
+ InitChannelAndRequestEventSource();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsEventSource::nsIJSNativeInitializer methods:
+//-----------------------------------------------------------------------------
+
+/**
+ * This Initialize method is called from XPConnect via nsIJSNativeInitializer.
+ * It is used for constructing our nsEventSource from javascript. It expects a
+ * URL string parameter. Also, initializes the principal, the script context
+ * and the window owner.
+ */
+NS_IMETHODIMP
+nsEventSource::Initialize(nsISupports* aOwner,
+ JSContext* aContext,
+ JSObject* aObject,
+ PRUint32 aArgc,
+ jsval* aArgv)
+{
+ if (mReadyState != nsIEventSource::CONNECTING || !PrefEnabled() ||
+ aArgc < 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSAutoRequest ar(aContext);
+
+ JSString* jsstr = JS_ValueToString(aContext, aArgv[0]);
+ if (!jsstr) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ JS::Anchor<JSString *> deleteProtector(jsstr);
+ size_t length;
+ const jschar *chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
+ if (!chars) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoString urlParam;
+
+ urlParam.Assign(chars, length);
+
+ nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aOwner);
+ NS_ENSURE_STATE(ownerWindow);
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
+ NS_ENSURE_STATE(sgo);
+ nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
+ NS_ENSURE_STATE(scriptContext);
+
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
+ do_QueryInterface(aOwner);
+ NS_ENSURE_STATE(scriptPrincipal);
+ nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
+ NS_ENSURE_STATE(principal);
+
+ return Init(principal, scriptContext, ownerWindow, urlParam);
+}
+
+//-----------------------------------------------------------------------------
+// nsEventSource::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsEventSource::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const PRUnichar* aData)
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
+ if (!mOwner || window != mOwner) {
+ return NS_OK;
+ }
+
+ mozilla::DebugOnly<nsresult> rv;
+ if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
+ rv = Freeze();
+ NS_ASSERTION(rv, "Freeze() failed");
+ } else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
+ rv = Thaw();
+ NS_ASSERTION(rv, "Thaw() failed");
+ } else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsEventSource::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsEventSource::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *ctxt)
+{
+ nsresult rv = CheckHealthOfRequestCallback(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRBool requestSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCAutoString contentType;
+ rv = httpChannel->GetContentType(contentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!requestSucceeded || !contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
+ DispatchFailConnection();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = mPrincipal;
+ if (nsContentUtils::IsSystemPrincipal(principal)) {
+ // Don't give this channel the system principal.
+ principal = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = httpChannel->SetOwner(principal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableMethod(this, &nsEventSource::AnnounceConnection);
+ NS_ENSURE_STATE(event);
+
+ rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_STREAM;
+
+ return NS_OK;
+}
+
+// this method parses the characters as they become available instead of
+// buffering them.
+NS_METHOD
+nsEventSource::StreamReaderFunc(nsIInputStream *aInputStream,
+ void *aClosure,
+ const char *aFromRawSegment,
+ PRUint32 aToOffset,
+ PRUint32 aCount,
+ PRUint32 *aWriteCount)
+{
+ nsEventSource* thisObject = static_cast<nsEventSource*>(aClosure);
+ if (!thisObject || !aWriteCount) {
+ NS_WARNING("nsEventSource cannot read from stream: no aClosure or aWriteCount");
+ return NS_ERROR_FAILURE;
+ }
+
+ *aWriteCount = 0;
+
+ PRInt32 srcCount, outCount;
+ PRUnichar out[2];
+ nsresult rv;
+
+ const char *p = aFromRawSegment,
+ *end = aFromRawSegment + aCount;
+
+ do {
+ srcCount = aCount - (p - aFromRawSegment);
+ outCount = 2;
+
+ thisObject->mLastConvertionResult =
+ thisObject->mUnicodeDecoder->Convert(p, &srcCount, out, &outCount);
+
+ if (thisObject->mLastConvertionResult == NS_ERROR_ILLEGAL_INPUT) {
+ // There's an illegal byte in the input. It's now the responsibility
+ // of this calling code to output a U+FFFD REPLACEMENT CHARACTER, advance
+ // over the bad byte and reset the decoder.
+ rv = thisObject->ParseCharacter(REPLACEMENT_CHAR);
+ NS_ENSURE_SUCCESS(rv, rv);
+ p = p + srcCount + 1;
+ thisObject->mUnicodeDecoder->Reset();
+ } else {
+ for (PRInt32 i = 0; i < outCount; ++i) {
+ rv = thisObject->ParseCharacter(out[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ p = p + srcCount;
+ }
+ } while (p < end &&
+ thisObject->mLastConvertionResult != NS_PARTIAL_MORE_INPUT &&
+ thisObject->mLastConvertionResult != NS_OK);
+
+ // check if the last byte was a bad one and
+ // clear the state since it was handled above.
+ if (thisObject->mLastConvertionResult == NS_ERROR_ILLEGAL_INPUT) {
+ thisObject->mLastConvertionResult = NS_OK;
+ }
+
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEventSource::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ PRUint32 aOffset,
+ PRUint32 aCount)
+{
+ NS_ENSURE_ARG_POINTER(aInputStream);
+
+ nsresult rv = CheckHealthOfRequestCallback(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRUint32 totalRead;
+ return aInputStream->ReadSegments(nsEventSource::StreamReaderFunc, this,
+ aCount, &totalRead);
+}
+
+NS_IMETHODIMP
+nsEventSource::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return NS_ERROR_ABORT;
+ }
+
+ if (NS_FAILED(aStatusCode)) {
+ DispatchFailConnection();
+ return aStatusCode;
+ }
+
+ nsresult rv;
+ nsresult healthOfRequestResult = CheckHealthOfRequestCallback(aRequest);
+ if (NS_SUCCEEDED(healthOfRequestResult)) {
+ // check if we had an incomplete UTF8 char at the end of the stream
+ if (mLastConvertionResult == NS_PARTIAL_MORE_INPUT) {
+ rv = ParseCharacter(REPLACEMENT_CHAR);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // once we reach the end of the stream we must
+ // dispatch the current event
+ switch (mStatus)
+ {
+ case PARSE_STATE_CR_CHAR:
+ case PARSE_STATE_COMMENT:
+ case PARSE_STATE_FIELD_NAME:
+ case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
+ case PARSE_STATE_FIELD_VALUE:
+ case PARSE_STATE_BEGIN_OF_LINE:
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ break;
+
+ // Just for not getting warnings when compiling
+ case PARSE_STATE_OFF:
+ case PARSE_STATE_BEGIN_OF_STREAM:
+ case PARSE_STATE_BOM_WAS_READ:
+ break;
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableMethod(this, &nsEventSource::ReestablishConnection);
+ NS_ENSURE_STATE(event);
+
+ rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return healthOfRequestResult;
+}
+
+/**
+ * Simple helper class that just forwards the redirect callback back
+ * to the nsEventSource.
+ */
+class AsyncVerifyRedirectCallbackFwr : public nsIAsyncVerifyRedirectCallback
+{
+public:
+ AsyncVerifyRedirectCallbackFwr(nsEventSource* aEventsource)
+ : mEventSource(aEventsource)
+ {
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackFwr)
+
+ // nsIAsyncVerifyRedirectCallback implementation
+ NS_IMETHOD OnRedirectVerifyCallback(nsresult aResult)
+ {
+ nsresult rv = mEventSource->OnRedirectVerifyCallback(aResult);
+ if (NS_FAILED(rv)) {
+ mEventSource->mErrorLoadOnRedirect = PR_TRUE;
+ mEventSource->DispatchFailConnection();
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsRefPtr<nsEventSource> mEventSource;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncVerifyRedirectCallbackFwr)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncVerifyRedirectCallbackFwr)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mEventSource, nsIEventSource)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncVerifyRedirectCallbackFwr)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventSource)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncVerifyRedirectCallbackFwr)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncVerifyRedirectCallbackFwr)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncVerifyRedirectCallbackFwr)
+
+//-----------------------------------------------------------------------------
+// nsEventSource::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsEventSource::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ PRUint32 aFlags,
+ nsIAsyncVerifyRedirectCallback *aCallback)
+{
+ nsCOMPtr<nsIRequest> aOldRequest = do_QueryInterface(aOldChannel);
+ NS_PRECONDITION(aOldRequest, "Redirect from a null request?");
+
+ nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!CheckCanRequestSrc(newURI)) {
+ DispatchFailConnection();
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Prepare to receive callback
+ mRedirectFlags = aFlags;
+ mRedirectCallback = aCallback;
+ mNewRedirectChannel = aNewChannel;
+
+ if (mChannelEventSink) {
+ nsRefPtr<AsyncVerifyRedirectCallbackFwr> fwd =
+ new AsyncVerifyRedirectCallbackFwr(this);
+
+ rv = mChannelEventSink->AsyncOnChannelRedirect(aOldChannel,
+ aNewChannel,
+ aFlags, fwd);
+ if (NS_FAILED(rv)) {
+ mRedirectCallback = nsnull;
+ mNewRedirectChannel = nsnull;
+ mErrorLoadOnRedirect = PR_TRUE;
+ DispatchFailConnection();
+ }
+ return rv;
+ }
+ OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::OnRedirectVerifyCallback(nsresult aResult)
+{
+ NS_ABORT_IF_FALSE(mRedirectCallback, "mRedirectCallback not set in callback");
+ NS_ABORT_IF_FALSE(mNewRedirectChannel,
+ "mNewRedirectChannel not set in callback");
+
+ NS_ENSURE_SUCCESS(aResult, aResult);
+
+ // update our channel
+
+ mHttpChannel = do_QueryInterface(mNewRedirectChannel);
+ NS_ENSURE_STATE(mHttpChannel);
+
+ nsresult rv = SetupHttpChannel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((mRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
+ rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mNewRedirectChannel = nsnull;
+
+ mRedirectCallback->OnRedirectVerifyCallback(aResult);
+ mRedirectCallback = nsnull;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsEventSource::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsEventSource::GetInterface(const nsIID & aIID,
+ void **aResult)
+{
+ // Make sure to return ourselves for the channel event sink interface,
+ // no matter what. We can forward these to mNotificationCallbacks
+ // if it wants to get notifications for them. But we
+ // need to see these notifications for proper functioning.
+ if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ mChannelEventSink = do_GetInterface(mNotificationCallbacks);
+ *aResult = static_cast<nsIChannelEventSink*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ // Now give mNotificationCallbacks (if non-null) a chance to return the
+ // desired interface.
+ if (mNotificationCallbacks) {
+ nsresult rv = mNotificationCallbacks->GetInterface(aIID, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(*aResult, "Lying nsIInterfaceRequestor implementation!");
+ return rv;
+ }
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsresult rv = CheckInnerWindowCorrectness();
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the an auth prompter for our window so that the parenting
+ // of the dialogs works as it should when using tabs.
+
+ nsCOMPtr<nsIDOMWindow> window;
+ if (mOwner) {
+ window = mOwner->GetOuterWindow();
+ }
+
+ return wwatch->GetPrompt(window, aIID, aResult);
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+// static
+PRBool
+nsEventSource::PrefEnabled()
+{
+ return nsContentUtils::GetBoolPref("dom.server-events.enabled", PR_FALSE);
+}
+
+nsresult
+nsEventSource::GetBaseURI(nsIURI **aBaseURI)
+{
+ NS_ENSURE_ARG_POINTER(aBaseURI);
+
+ *aBaseURI = nsnull;
+
+ nsCOMPtr<nsIURI> baseURI;
+
+ // first we try from document->GetBaseURI()
+ nsCOMPtr<nsIDocument> doc =
+ nsContentUtils::GetDocumentFromScriptContext(mScriptContext);
+ if (doc) {
+ baseURI = doc->GetBaseURI();
+ }
+
+ // otherwise we get from the doc's principal
+ if (!baseURI) {
+ nsresult rv = mPrincipal->GetURI(getter_AddRefs(baseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ENSURE_STATE(baseURI);
+
+ baseURI.forget(aBaseURI);
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::SetupHttpChannel()
+{
+ mHttpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET"));
+
+ /* set the http request headers */
+
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ NS_LITERAL_CSTRING(TEXT_EVENT_STREAM), PR_FALSE);
+
+ // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
+
+ if (!mLastEventID.IsEmpty()) {
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Last-Event-ID"),
+ NS_ConvertUTF16toUTF8(mLastEventID), PR_FALSE);
+ }
+
+ nsCOMPtr<nsIURI> codebase;
+ nsresult rv = GetBaseURI(getter_AddRefs(codebase));
+ if (NS_SUCCEEDED(rv)) {
+ rv = mHttpChannel->SetReferrer(codebase);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
+ mHttpChannel->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
+ if (notificationCallbacks != this) {
+ mNotificationCallbacks = notificationCallbacks;
+ mHttpChannel->SetNotificationCallbacks(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::InitChannelAndRequestEventSource()
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return NS_ERROR_ABORT;
+ }
+
+ // eventsource validation
+
+ if (!CheckCanRequestSrc()) {
+ DispatchFailConnection();
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsLoadFlags loadFlags;
+ loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE;
+
+ // get Content Security Policy from principal to pass into channel
+ nsCOMPtr<nsIChannelPolicy> channelPolicy;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ nsresult rv = mPrincipal->GetCsp(getter_AddRefs(csp));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (csp) {
+ channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
+ channelPolicy->SetContentSecurityPolicy(csp);
+ channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT);
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), mSrc, nsnull, mLoadGroup,
+ nsnull, loadFlags, channelPolicy);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHttpChannel = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
+
+ rv = SetupHttpChannel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Start reading from the channel
+ return mHttpChannel->AsyncOpen(this, nsnull);
+}
+
+void
+nsEventSource::AnnounceConnection()
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return;
+ }
+
+ if (mReadyState != nsIEventSource::CONNECTING) {
+ NS_WARNING("Unexpected mReadyState!!!");
+ return;
+ }
+
+ // When a user agent is to announce the connection, the user agent must set
+ // the readyState attribute to OPEN and queue a task to fire a simple event
+ // named open at the EventSource object.
+
+ mReadyState = nsIEventSource::OPEN;
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMEvent> event;
+ rv = NS_NewDOMEvent(getter_AddRefs(event), nsnull, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create the open event!!!");
+ return;
+ }
+
+ // it doesn't bubble, and it isn't cancelable
+ rv = event->InitEvent(NS_LITERAL_STRING("open"), PR_FALSE, PR_FALSE);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to init the open event!!!");
+ return;
+ }
+
+ nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
+ privateEvent->SetTrusted(PR_TRUE);
+
+ rv = DispatchDOMEvent(nsnull, event, nsnull, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the open event!!!");
+ return;
+ }
+}
+
+nsresult
+nsEventSource::ResetConnection()
+{
+ if (mHttpChannel) {
+ mHttpChannel->Cancel(NS_ERROR_ABORT);
+ }
+
+ if (mUnicodeDecoder) {
+ mUnicodeDecoder->Reset();
+ }
+ mLastConvertionResult = NS_OK;
+
+ mHttpChannel = nsnull;
+ mNotificationCallbacks = nsnull;
+ mChannelEventSink = nsnull;
+ mStatus = PARSE_STATE_OFF;
+ mRedirectCallback = nsnull;
+ mNewRedirectChannel = nsnull;
+
+ mReadyState = nsIEventSource::CONNECTING;
+
+ return NS_OK;
+}
+
+void
+nsEventSource::ReestablishConnection()
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return;
+ }
+
+ if (mReadyState != nsIEventSource::OPEN) {
+ NS_WARNING("Unexpected mReadyState!!!");
+ return;
+ }
+
+ nsresult rv = ResetConnection();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to reset the connection!!!");
+ return;
+ }
+
+ rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMEvent> event;
+ rv = NS_NewDOMEvent(getter_AddRefs(event), nsnull, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create the error event!!!");
+ return;
+ }
+
+ // it doesn't bubble, and it isn't cancelable
+ rv = event->InitEvent(NS_LITERAL_STRING("error"), PR_FALSE, PR_FALSE);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to init the error event!!!");
+ return;
+ }
+
+ nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
+ privateEvent->SetTrusted(PR_TRUE);
+
+ rv = DispatchDOMEvent(nsnull, event, nsnull, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event!!!");
+ return;
+ }
+
+ rv = SetReconnectionTimeout();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to set the timeout for reestablishing the connection!!!");
+ return;
+ }
+}
+
+nsresult
+nsEventSource::SetReconnectionTimeout()
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return NS_ERROR_ABORT;
+ }
+
+ // the timer will be used whenever the requests are going finished.
+ if (!mTimer) {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ NS_ENSURE_STATE(mTimer);
+ }
+
+ NS_ASSERTION(mReconnectionTime >= 0, "mReconnectionTime lies");
+ nsresult rv = mTimer->InitWithFuncCallback(TimerCallback, this,
+ mReconnectionTime,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::PrintErrorOnConsole(const char *aBundleURI,
+ const PRUnichar *aError,
+ const PRUnichar **aFormatStrings,
+ PRUint32 aFormatStringsLen)
+{
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_STATE(bundleService);
+
+ nsCOMPtr<nsIStringBundle> strBundle;
+ nsresult rv =
+ bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptError2> errObj(
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Localize the error message
+ nsXPIDLString message;
+ if (aFormatStrings) {
+ rv = strBundle->FormatStringFromName(aError, aFormatStrings,
+ aFormatStringsLen,
+ getter_Copies(message));
+ } else {
+ rv = strBundle->GetStringFromName(aError, getter_Copies(message));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ errObj->InitWithWindowID(message.get(),
+ mScriptFile.get(),
+ nsnull,
+ mScriptLine, 0,
+ nsIScriptError::errorFlag,
+ "Event Source", mWindowID);
+
+ // print the error message directly to the JS console
+ nsCOMPtr<nsIScriptError> logError = do_QueryInterface(errObj);
+ rv = console->LogMessage(logError);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::ConsoleError()
+{
+ nsCAutoString targetSpec;
+ nsresult rv = mSrc->GetSpec(targetSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
+ const PRUnichar *formatStrings[] = { specUTF16.get() };
+
+ if (mReadyState == nsIEventSource::CONNECTING) {
+ rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ NS_LITERAL_STRING("connectionFailure").get(),
+ formatStrings, NS_ARRAY_LENGTH(formatStrings));
+ } else {
+ rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ NS_LITERAL_STRING("netInterrupt").get(),
+ formatStrings, NS_ARRAY_LENGTH(formatStrings));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::DispatchFailConnection()
+{
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableMethod(this, &nsEventSource::FailConnection);
+ NS_ENSURE_STATE(event);
+
+ return NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+}
+
+void
+nsEventSource::FailConnection()
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return;
+ }
+
+ nsresult rv = ConsoleError();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to print to the console error");
+ }
+
+ // When a user agent is to fail the connection, the user agent must set the
+ // readyState attribute to CLOSED and queue a task to fire a simple event
+ // named error at the EventSource object.
+
+ Close(); // it sets mReadyState to CLOSED
+
+ rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMEvent> event;
+ rv = NS_NewDOMEvent(getter_AddRefs(event), nsnull, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create the error event!!!");
+ return;
+ }
+
+ // it doesn't bubble, and it isn't cancelable
+ rv = event->InitEvent(NS_LITERAL_STRING("error"), PR_FALSE, PR_FALSE);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to init the error event!!!");
+ return;
+ }
+
+ nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
+ privateEvent->SetTrusted(PR_TRUE);
+
+ rv = DispatchDOMEvent(nsnull, event, nsnull, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event!!!");
+ return;
+ }
+}
+
+PRBool
+nsEventSource::CheckCanRequestSrc(nsIURI* aSrc)
+{
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return PR_FALSE;
+ }
+
+ PRBool isSameOrigin = PR_FALSE;
+ PRBool isValidURI = PR_FALSE;
+ PRBool isValidContentLoadPolicy = PR_FALSE;
+ PRBool isValidProtocol = PR_FALSE;
+
+ nsCOMPtr<nsIURI> srcToTest = aSrc ? aSrc : mSrc.get();
+ NS_ENSURE_TRUE(srcToTest, PR_FALSE);
+
+ isSameOrigin = NS_SUCCEEDED(mPrincipal->CheckMayLoad(srcToTest, PR_FALSE));
+
+ PRUint32 aCheckURIFlags =
+ nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL |
+ nsIScriptSecurityManager::DISALLOW_SCRIPT;
+
+ nsresult rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(mPrincipal,
+ srcToTest,
+ aCheckURIFlags);
+ isValidURI = NS_SUCCEEDED(rv);
+
+ // After the security manager, the content-policy check
+
+ nsCOMPtr<nsIDocument> doc =
+ nsContentUtils::GetDocumentFromScriptContext(mScriptContext);
+
+ // mScriptContext should be initialized because of GetBaseURI() above.
+ // Still need to consider the case that doc is nsnull however.
+ rv = CheckInnerWindowCorrectness();
+ NS_ENSURE_SUCCESS(rv, PR_FALSE);
+ PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
+ srcToTest,
+ mPrincipal,
+ doc,
+ NS_LITERAL_CSTRING(TEXT_EVENT_STREAM),
+ nsnull, // extra
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+ isValidContentLoadPolicy = NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad);
+
+ nsCAutoString targetURIScheme;
+ rv = srcToTest->GetScheme(targetURIScheme);
+ if (NS_SUCCEEDED(rv)) {
+ // We only have the http support for now
+ isValidProtocol = targetURIScheme.EqualsLiteral("http") ||
+ targetURIScheme.EqualsLiteral("https");
+ }
+
+ return isSameOrigin && isValidURI && isValidContentLoadPolicy &&
+ isValidProtocol;
+}
+
+// static
+void
+nsEventSource::TimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ nsRefPtr<nsEventSource> thisObject = static_cast<nsEventSource*>(aClosure);
+
+ if (thisObject->mReadyState == nsIEventSource::CLOSED) {
+ return;
+ }
+
+ NS_PRECONDITION(!thisObject->mHttpChannel,
+ "the channel hasn't been cancelled!!");
+
+ if (!thisObject->mFrozen) {
+ nsresult rv = thisObject->InitChannelAndRequestEventSource();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("thisObject->InitChannelAndRequestEventSource() failed");
+ return;
+ }
+ }
+}
+
+nsresult
+nsEventSource::Thaw()
+{
+ if (mReadyState == nsIEventSource::CLOSED || !mFrozen) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mHttpChannel, "the connection hasn't been closed!!!");
+
+ mFrozen = PR_FALSE;
+ nsresult rv;
+ if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableMethod(this, &nsEventSource::DispatchAllMessageEvents);
+ NS_ENSURE_STATE(event);
+
+ mGoingToDispatchAllMessages = PR_TRUE;
+
+ rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = InitChannelAndRequestEventSource();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::Freeze()
+{
+ if (mReadyState == nsIEventSource::CLOSED || mFrozen) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mHttpChannel, "the connection hasn't been closed!!!");
+ mFrozen = PR_TRUE;
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::DispatchCurrentMessageEvent()
+{
+ nsAutoPtr<Message> message(new Message());
+ *message = mCurrentMessage;
+
+ ClearFields();
+
+ if (message->mData.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // removes the trailing LF from mData
+ NS_ASSERTION(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
+ "Invalid trailing character! LF was expected instead.");
+ message->mData.SetLength(message->mData.Length() - 1);
+
+ if (message->mEventName.IsEmpty()) {
+ message->mEventName.AssignLiteral("message");
+ }
+
+ if (message->mLastEventID.IsEmpty() && !mLastEventID.IsEmpty()) {
+ message->mLastEventID.Assign(mLastEventID);
+ }
+
+ PRInt32 sizeBefore = mMessagesToDispatch.GetSize();
+ mMessagesToDispatch.Push(message.forget());
+ NS_ENSURE_TRUE(mMessagesToDispatch.GetSize() == sizeBefore + 1,
+ NS_ERROR_OUT_OF_MEMORY);
+
+
+ if (!mGoingToDispatchAllMessages) {
+ nsCOMPtr<nsIRunnable> event =
+ NS_NewRunnableMethod(this, &nsEventSource::DispatchAllMessageEvents);
+ NS_ENSURE_STATE(event);
+
+ mGoingToDispatchAllMessages = PR_TRUE;
+
+ return NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+void
+nsEventSource::DispatchAllMessageEvents()
+{
+ if (mReadyState == nsIEventSource::CLOSED || mFrozen) {
+ return;
+ }
+
+ mGoingToDispatchAllMessages = PR_FALSE;
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ while (mMessagesToDispatch.GetSize() > 0) {
+ nsAutoPtr<Message>
+ message(static_cast<Message*>(mMessagesToDispatch.PopFront()));
+
+ // create an event that uses the MessageEvent interface,
+ // which does not bubble, is not cancelable, and has no default action
+
+ nsCOMPtr<nsIDOMEvent> event;
+ rv = NS_NewDOMMessageEvent(getter_AddRefs(event), nsnull, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create the message event!!!");
+ return;
+ }
+
+ nsCOMPtr<nsIDOMMessageEvent> messageEvent = do_QueryInterface(event);
+ rv = messageEvent->InitMessageEvent(message->mEventName,
+ PR_FALSE, PR_FALSE,
+ message->mData,
+ NS_ConvertUTF8toUTF16(mOrigin),
+ message->mLastEventID, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to init the message event!!!");
+ return;
+ }
+
+ nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
+ privateEvent->SetTrusted(PR_TRUE);
+
+ rv = DispatchDOMEvent(nsnull, event, nsnull, nsnull);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the message event!!!");
+ return;
+ }
+ }
+}
+
+nsresult
+nsEventSource::ClearFields()
+{
+ // mLastEventID and mReconnectionTime must be cached
+
+ mCurrentMessage.mEventName.Truncate();
+ mCurrentMessage.mLastEventID.Truncate();
+ mCurrentMessage.mData.Truncate();
+
+ mLastFieldName.Truncate();
+ mLastFieldValue.Truncate();
+
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::SetFieldAndClear()
+{
+ if (mLastFieldName.IsEmpty()) {
+ mLastFieldValue.Truncate();
+ return NS_OK;
+ }
+
+ PRUnichar first_char;
+ first_char = mLastFieldName.CharAt(0);
+
+ switch (first_char) // with no case folding performed
+ {
+ case PRUnichar('d'):
+ if (mLastFieldName.EqualsLiteral("data")) {
+ // If the field name is "data" append the field value to the data
+ // buffer, then append a single U+000A LINE FEED (LF) character
+ // to the data buffer.
+ mCurrentMessage.mData.Append(mLastFieldValue);
+ mCurrentMessage.mData.Append(LF_CHAR);
+ }
+ break;
+
+ case PRUnichar('e'):
+ if (mLastFieldName.EqualsLiteral("event")) {
+ mCurrentMessage.mEventName.Assign(mLastFieldValue);
+ }
+ break;
+
+ case PRUnichar('i'):
+ if (mLastFieldName.EqualsLiteral("id")) {
+ mCurrentMessage.mLastEventID.Assign(mLastFieldValue);
+ mLastEventID.Assign(mLastFieldValue);
+ }
+ break;
+
+ case PRUnichar('r'):
+ if (mLastFieldName.EqualsLiteral("retry")) {
+ PRUint32 newValue=0;
+ PRUint32 i = 0; // we must ensure that there are only digits
+ PRBool assign = PR_TRUE;
+ for (i = 0; i < mLastFieldValue.Length(); ++i) {
+ if (mLastFieldValue.CharAt(i) < (PRUnichar)'0' ||
+ mLastFieldValue.CharAt(i) > (PRUnichar)'9') {
+ assign = PR_FALSE;
+ break;
+ }
+ newValue = newValue*10 +
+ (((PRUint32)mLastFieldValue.CharAt(i))-
+ ((PRUint32)((PRUnichar)'0')));
+ }
+
+ if (assign) {
+ if (newValue < MIN_RECONNECTION_TIME_VALUE) {
+ mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
+ } else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
+ mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
+ } else {
+ mReconnectionTime = newValue;
+ }
+ }
+ break;
+ }
+ break;
+ }
+
+ mLastFieldName.Truncate();
+ mLastFieldValue.Truncate();
+
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::CheckHealthOfRequestCallback(nsIRequest *aRequestCallback)
+{
+ // check if we have been closed or if the request has been canceled
+ // or if we have been frozen
+ if (mReadyState == nsIEventSource::CLOSED || !mHttpChannel ||
+ mFrozen || mErrorLoadOnRedirect) {
+ return NS_ERROR_ABORT;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
+ NS_ENSURE_STATE(httpChannel);
+
+ if (httpChannel != mHttpChannel) {
+ NS_WARNING("wrong channel from request callback");
+ return NS_ERROR_ABORT;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsEventSource::ParseCharacter(PRUnichar aChr)
+{
+ nsresult rv;
+
+ if (mReadyState == nsIEventSource::CLOSED) {
+ return NS_ERROR_ABORT;
+ }
+
+ switch (mStatus)
+ {
+ case PARSE_STATE_OFF:
+ NS_ERROR("Invalid state");
+ return NS_ERROR_FAILURE;
+ break;
+
+ case PARSE_STATE_BEGIN_OF_STREAM:
+ if (aChr == BOM_CHAR) {
+ mStatus = PARSE_STATE_BOM_WAS_READ; // ignore it
+ } else if (aChr == CR_CHAR) {
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_COMMENT;
+ } else {
+ mLastFieldName += aChr;
+ mStatus = PARSE_STATE_FIELD_NAME;
+ }
+
+ break;
+
+ case PARSE_STATE_BOM_WAS_READ:
+ if (aChr == CR_CHAR) {
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_COMMENT;
+ } else {
+ mLastFieldName += aChr;
+ mStatus = PARSE_STATE_FIELD_NAME;
+ }
+ break;
+
+ case PARSE_STATE_CR_CHAR:
+ if (aChr == CR_CHAR) {
+ rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (aChr == LF_CHAR) {
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_COMMENT;
+ } else {
+ mLastFieldName += aChr;
+ mStatus = PARSE_STATE_FIELD_NAME;
+ }
+
+ break;
+
+ case PARSE_STATE_COMMENT:
+ if (aChr == CR_CHAR) {
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ }
+
+ break;
+
+ case PARSE_STATE_FIELD_NAME:
+ if (aChr == CR_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
+ } else {
+ mLastFieldName += aChr;
+ }
+
+ break;
+
+ case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
+ if (aChr == CR_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == SPACE_CHAR) {
+ mStatus = PARSE_STATE_FIELD_VALUE;
+ } else {
+ mLastFieldValue += aChr;
+ mStatus = PARSE_STATE_FIELD_VALUE;
+ }
+
+ break;
+
+ case PARSE_STATE_FIELD_VALUE:
+ if (aChr == CR_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ rv = SetFieldAndClear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else {
+ mLastFieldValue += aChr;
+ }
+
+ break;
+
+ case PARSE_STATE_BEGIN_OF_LINE:
+ if (aChr == CR_CHAR) {
+ rv = DispatchCurrentMessageEvent(); // there is an empty line
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_CR_CHAR;
+ } else if (aChr == LF_CHAR) {
+ rv = DispatchCurrentMessageEvent(); // there is an empty line
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = PARSE_STATE_BEGIN_OF_LINE;
+ } else if (aChr == COLON_CHAR) {
+ mStatus = PARSE_STATE_COMMENT;
+ } else {
+ mLastFieldName += aChr;
+ mStatus = PARSE_STATE_FIELD_NAME;
+ }
+
+ break;
+ }
+
+ return NS_OK;
+}
new file mode 100755
--- /dev/null
+++ b/content/base/src/nsEventSource.h
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Wellington Fernando de Macedo.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Wellington Fernando de Macedo <wfernandom2004@gmail.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * This implementation has support only for http requests. It is because the
+ * spec has defined event streams only for http. HTTP is required because
+ * this implementation uses some http headers: "Last-Event-ID", "Cache-Control"
+ * and "Accept".
+ */
+
+#ifndef nsEventSource_h__
+#define nsEventSource_h__
+
+#include "nsIEventSource.h"
+#include "nsIJSNativeInitializer.h"
+#include "nsDOMEventTargetWrapperCache.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+#include "nsIHttpChannel.h"
+#include "nsWeakReference.h"
+#include "nsDeque.h"
+#include "nsIUnicodeDecoder.h"
+
+#define NS_EVENTSOURCE_CID \
+ { /* 755e2d2d-a836-4539-83f4-16b51156341f */ \
+ 0x755e2d2d, 0xa836, 0x4539, \
+ {0x83, 0xf4, 0x16, 0xb5, 0x11, 0x56, 0x34, 0x1f} }
+
+#define NS_EVENTSOURCE_CONTRACTID "@mozilla.org/eventsource;1"
+
+class AsyncVerifyRedirectCallbackFwr;
+class nsAutoClearFields;
+
+class nsEventSource: public nsDOMEventTargetWrapperCache,
+ public nsIEventSource,
+ public nsIJSNativeInitializer,
+ public nsIObserver,
+ public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIInterfaceRequestor,
+ public nsSupportsWeakReference
+{
+friend class AsyncVerifyRedirectCallbackFwr;
+
+public:
+ nsEventSource();
+ virtual ~nsEventSource();
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsEventSource,
+ nsDOMEventTargetWrapperCache)
+
+ NS_DECL_NSIEVENTSOURCE
+
+ // nsIJSNativeInitializer
+ NS_IMETHOD Initialize(nsISupports* aOwner, JSContext* cx, JSObject* obj,
+ PRUint32 argc, jsval* argv);
+
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // Determine if preferences allow EventSource
+ static PRBool PrefEnabled();
+
+protected:
+ nsresult GetBaseURI(nsIURI **aBaseURI);
+
+ nsresult SetupHttpChannel();
+ nsresult InitChannelAndRequestEventSource();
+ nsresult ResetConnection();
+ nsresult DispatchFailConnection();
+ nsresult SetReconnectionTimeout();
+
+ void AnnounceConnection();
+ void DispatchAllMessageEvents();
+ void ReestablishConnection();
+ void FailConnection();
+
+ nsresult Thaw();
+ nsresult Freeze();
+
+ static void TimerCallback(nsITimer *aTimer, void *aClosure);
+
+ nsresult PrintErrorOnConsole(const char *aBundleURI,
+ const PRUnichar *aError,
+ const PRUnichar **aFormatStrings,
+ PRUint32 aFormatStringsLen);
+ nsresult ConsoleError();
+
+ static NS_METHOD StreamReaderFunc(nsIInputStream *aInputStream,
+ void *aClosure,
+ const char *aFromRawSegment,
+ PRUint32 aToOffset,
+ PRUint32 aCount,
+ PRUint32 *aWriteCount);
+ nsresult SetFieldAndClear();
+ nsresult ClearFields();
+ nsresult ResetEvent();
+ nsresult DispatchCurrentMessageEvent();
+ nsresult ParseCharacter(PRUnichar aChr);
+ PRBool CheckCanRequestSrc(nsIURI* aSrc = nsnull); // if null, it tests mSrc
+ nsresult CheckHealthOfRequestCallback(nsIRequest *aRequestCallback);
+ nsresult OnRedirectVerifyCallback(nsresult result);
+
+ nsCOMPtr<nsIURI> mSrc;
+
+ nsString mLastEventID;
+ PRUint32 mReconnectionTime; // in ms
+
+ struct Message {
+ nsString mEventName;
+ nsString mLastEventID;
+ nsString mData;
+ };
+ nsDeque mMessagesToDispatch;
+ Message mCurrentMessage;
+
+ /**
+ * A simple state machine used to manage the event-source's line buffer
+ *
+ * PARSE_STATE_OFF -> PARSE_STATE_BEGIN_OF_STREAM
+ *
+ * PARSE_STATE_BEGIN_OF_STREAM -> PARSE_STATE_BOM_WAS_READ |
+ * PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME
+ *
+ * PARSE_STATE_BOM_WAS_READ -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME
+ *
+ * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_FIELD_NAME -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
+ *
+ * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE -> PARSE_STATE_FIELD_VALUE |
+ * PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_FIELD_VALUE -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_BEGIN_OF_LINE -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * Whenever the parser find an empty line or the end-of-file
+ * it dispatches the stacked event.
+ *
+ */
+ enum ParserStatus {
+ PARSE_STATE_OFF,
+ PARSE_STATE_BEGIN_OF_STREAM,
+ PARSE_STATE_BOM_WAS_READ,
+ PARSE_STATE_CR_CHAR,
+ PARSE_STATE_COMMENT,
+ PARSE_STATE_FIELD_NAME,
+ PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
+ PARSE_STATE_FIELD_VALUE,
+ PARSE_STATE_BEGIN_OF_LINE
+ };
+ ParserStatus mStatus;
+
+ PRPackedBool mFrozen;
+ PRPackedBool mErrorLoadOnRedirect;
+ PRPackedBool mGoingToDispatchAllMessages;
+
+ // used while reading the input streams
+ nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
+ nsresult mLastConvertionResult;
+ nsString mLastFieldName;
+ nsString mLastFieldValue;
+
+ nsRefPtr<nsDOMEventListenerWrapper> mOnOpenListener;
+ nsRefPtr<nsDOMEventListenerWrapper> mOnErrorListener;
+ nsRefPtr<nsDOMEventListenerWrapper> mOnMessageListener;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+
+ /**
+ * The notification callbacks the channel had initially.
+ * We want to forward things here as needed.
+ */
+ nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks;
+ nsCOMPtr<nsIChannelEventSink> mChannelEventSink;
+
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ PRInt32 mReadyState;
+ nsString mOriginalURL;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCString mOrigin;
+
+ PRUint32 mRedirectFlags;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIChannel> mNewRedirectChannel;
+
+ // Event Source owner information:
+ // - the script file name
+ // - source code line number where the Event Source object was constructed.
+ // - the window ID of the outer window where the script lives. Note that this
+ // may not be the same as the Event Source owner window.
+ // These attributes are used for error reporting.
+ nsString mScriptFile;
+ PRUint32 mScriptLine;
+ PRUint64 mWindowID;
+
+private:
+ nsEventSource(const nsEventSource& x); // prevent bad usage
+ nsEventSource& operator=(const nsEventSource& x);
+};
+
+#endif // nsEventSource_h__
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -687,28 +687,30 @@ GK_ATOM(oninput, "oninput")
GK_ATOM(oninvalid, "oninvalid")
GK_ATOM(onkeydown, "onkeydown")
GK_ATOM(onkeypress, "onkeypress")
GK_ATOM(onkeyup, "onkeyup")
GK_ATOM(onLoad, "onLoad")
GK_ATOM(onload, "onload")
GK_ATOM(onpopstate, "onpopstate")
GK_ATOM(only, "only") // this one is not an event
+GK_ATOM(onmessage, "onmessage")
GK_ATOM(onmousedown, "onmousedown")
GK_ATOM(onmousemove, "onmousemove")
GK_ATOM(onmouseout, "onmouseout")
GK_ATOM(onmouseover, "onmouseover")
GK_ATOM(onMozMouseHittest, "onMozMouseHittest")
GK_ATOM(onmouseup, "onmouseup")
GK_ATOM(onMozAfterPaint, "onMozAfterPaint")
GK_ATOM(onMozBeforePaint, "onMozBeforePaint")
GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
GK_ATOM(onMozScrolledAreaChanged, "onMozScrolledAreaChanged")
GK_ATOM(ononline, "ononline")
GK_ATOM(onoffline, "onoffline")
+GK_ATOM(onopen, "onopen")
GK_ATOM(onoverflow, "onoverflow")
GK_ATOM(onoverflowchanged, "onoverflowchanged")
GK_ATOM(onpagehide, "onpagehide")
GK_ATOM(onpageshow, "onpageshow")
GK_ATOM(onpaint, "onpaint")
GK_ATOM(onpaste, "onpaste")
GK_ATOM(onpopuphidden, "onpopuphidden")
GK_ATOM(onpopuphiding, "onpopuphiding")
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -478,16 +478,36 @@ include $(topsrcdir)/config/rules.mk
test_bug626262.html \
test_plugin_freezing.html \
test_bug638112.html \
bug638112-response.txt \
bug638112.sjs \
test_bug656283.html \
test_blobbuilder.html \
fileutils.js \
+ test_bug338583.html \
+ eventsource.resource \
+ eventsource.resource^headers^ \
+ badContentType.eventsource \
+ badContentType.eventsource^headers^ \
+ badEventFieldName.eventsource \
+ badEventFieldName.eventsource^headers^ \
+ badHTTPResponseCode.eventsource \
+ badHTTPResponseCode.eventsource^headers^ \
+ badMessageEvent.eventsource \
+ badMessageEvent.eventsource^headers^ \
+ forRemoval.resource \
+ forRemoval.resource^headers^ \
+ accesscontrol.resource \
+ accesscontrol.resource^headers^ \
+ invalid_accesscontrol.resource \
+ invalid_accesscontrol.resource^headers^ \
+ somedatas.resource \
+ somedatas.resource^headers^ \
+ delayedServerEvents.sjs \
$(NULL)
# This test fails on the Mac for some reason
ifneq (,$(filter gtk2 windows,$(MOZ_WIDGET_TOOLKIT)))
_TEST_FILES2 += test_copyimage.html \
$(NULL)
endif
new file mode 100644
--- /dev/null
+++ b/content/base/test/accesscontrol.resource
@@ -0,0 +1,5 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+event: message
+data: 1
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/content/base/test/accesscontrol.resource^headers^
@@ -0,0 +1,4 @@
+Access-Control-Allow-Origin: http://localhost:8888
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/badContentType.eventsource
@@ -0,0 +1,3 @@
+retry:500
+event: message
+data: 1
new file mode 100644
--- /dev/null
+++ b/content/base/test/badContentType.eventsource^headers^
@@ -0,0 +1,1 @@
+Content-Type: text/plain
new file mode 100644
--- /dev/null
+++ b/content/base/test/badEventFieldName.eventsource
@@ -0,0 +1,4 @@
+retry:500
+:space isn't a valid NCName char, am I right?
+event: message event
+data: 1
new file mode 100644
--- /dev/null
+++ b/content/base/test/badEventFieldName.eventsource^headers^
@@ -0,0 +1,1 @@
+Content-Type: text/event-stream
new file mode 100644
--- /dev/null
+++ b/content/base/test/badHTTPResponseCode.eventsource
@@ -0,0 +1,3 @@
+retry:500
+event: message
+data: 1
new file mode 100644
--- /dev/null
+++ b/content/base/test/badHTTPResponseCode.eventsource^headers^
@@ -0,0 +1,2 @@
+HTTP 404 Not Found
+Content-Type: text/event-stream
new file mode 100644
--- /dev/null
+++ b/content/base/test/badMessageEvent.eventsource
@@ -0,0 +1,2 @@
+retry:500
+event: message
new file mode 100644
--- /dev/null
+++ b/content/base/test/badMessageEvent.eventsource^headers^
@@ -0,0 +1,1 @@
+Content-Type: text/event-stream
new file mode 100644
--- /dev/null
+++ b/content/base/test/delayedServerEvents.sjs
@@ -0,0 +1,42 @@
+// this will take strings_to_send.length*5000 ms = 50 sec
+
+var timer = null;
+var strings_to_send = ["data\r\n\nda", "ta", ":", "de", "layed1\n\n",
+ "",
+ "",
+ "data:delayed2", "", ""];
+var resp = null;
+
+function sendNextString()
+{
+ if (strings_to_send.length == 0) {
+ timer.cancel();
+ resp.finish();
+ timer = null;
+ resp = null;
+ return;
+ }
+
+ resp.write(strings_to_send[0]);
+ strings_to_send = strings_to_send.splice(1, strings_to_send.length - 1);
+}
+
+function handleRequest(request, response)
+{
+ var b = 0;
+ for (var i=0; i < strings_to_send.length; ++i) {
+ b += strings_to_send[i].length;
+ }
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n")
+ response.write("Content-Length: " + b + "\r\n");
+ response.write("Content-Type: text/event-stream; charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache, must-revalidate\r\n");
+ response.write("\r\n");
+
+ resp = response;
+
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(sendNextString, 5000, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
+}
new file mode 100644
--- /dev/null
+++ b/content/base/test/eventsource.resource
@@ -0,0 +1,20 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+retry:500
+data: 1
+unknow: unknow
+
+event: other_event_name
+retry:500
+data: 1
+unknow: unknow
+
+event: click
+retry:500
+
+event: blur
+retry:500
+
+event:keypress
+retry:500
new file mode 100644
--- /dev/null
+++ b/content/base/test/eventsource.resource^headers^
@@ -0,0 +1,3 @@
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/forRemoval.resource
@@ -0,0 +1,23 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+retry:500
+event: message
+data: 1
+
+retry:500
+event: message
+data: 2
+
+retry:500
+event: message
+data: 3
+
+retry:500
+event: message
+data: 4
+
+retry:500
+event: message
+data: 5
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/forRemoval.resource^headers^
@@ -0,0 +1,3 @@
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/invalid_accesscontrol.resource
@@ -0,0 +1,5 @@
+:this file must be enconded in utf8
+:and its Content-Type must be equal to text/event-stream
+
+event: message
+data: 1
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/content/base/test/invalid_accesscontrol.resource^headers^
@@ -0,0 +1,4 @@
+Access-Control-Allow-Origin: http://an.accesscrontrol.domain:80
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
new file mode 100644
--- /dev/null
+++ b/content/base/test/somedatas.resource
@@ -0,0 +1,17 @@
+
+
+retry: 500
+
+data:123456789
+data: 123456789123456789
+data:123456789123456789123456789123456789
+data: 123456789123456789123456789123456789123456789123456789123456789123456789
+:some utf-8 characteres
+data:çãá"'@`~Ý Ḿyyyy
+
+:test if the character ":"(which is used for comments) isn't misunderstood
+data: :xxabcdefghij
+data:çãá"'@`~Ý Ḿyyyy : zz
+
+:a message dispatched because of eof
+data:1
new file mode 100644
--- /dev/null
+++ b/content/base/test/somedatas.resource^headers^
@@ -0,0 +1,3 @@
+Content-Type: text/event-stream
+Cache-Control: no-cache, must-revalidate
+
new file mode 100755
--- /dev/null
+++ b/content/base/test/test_bug338583.html
@@ -0,0 +1,454 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=338583
+-->
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Test for Bug 338583</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+</head>
+<body bgColor=white>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=338583">Mozilla Bug 338583</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 338583 **/
+
+// we test:
+// 1) the EventSource behaviour
+// 2) if the events are trusted
+// 3) possible invalid eventsources
+// 4) the close method when the object is just been used
+// 5) access-control
+// 6) the data parameter
+// 7) delayed server responses
+
+// --
+
+ function fn_onmessage(e) {
+ if (e.currentTarget == e.target && e.target.hits != null)
+ e.target.hits['fn_onmessage']++;
+ }
+
+ function fn_event_listener_message(e) {
+ if (e.currentTarget == e.target && e.target.hits != null)
+ e.target.hits['fn_event_listener_message']++;
+ }
+
+ function fn_other_event_name(e) {
+ if (e.currentTarget == e.target && e.target.hits != null)
+ e.target.hits['fn_other_event_name']++;
+ }
+
+ var domBranch;
+ var oldPrefVal;
+
+ var gEventSourceObj1 = null;
+ var gEventSourceObj2 = null;
+ var gEventSourceObj3 = null;
+ var gEventSourceObj4 = null;
+ var gEventSourceObj5 = null;
+ var gEventSourceObj6 = null;
+ var gEventSourceObj7 = null;
+ var stress_factor; // used in the setTimeouts in order to help
+ // the test when running in slow machines
+
+ function hasBeenHitFor1And2(obj, min) {
+ if (obj.hits['fn_onmessage'] < min ||
+ obj.hits['fn_event_listener_message'] < min ||
+ obj.hits['fn_other_event_name'] < min)
+ return false;
+ return true;
+ }
+
+// in order to test (1):
+// a) if the EventSource constructor parameter is equal to its url attribute
+// b) let its fn_onmessage, fn_event_listener_message, and fn_other_event_name functions listeners be hit four times each
+// c) the close method (we expect readyState == CLOSED)
+// d) the close method (we expect no message events anymore)
+
+ function doTest1() {
+ gEventSourceObj1 = new EventSource("eventsource.resource");
+ ok(gEventSourceObj1.url == "http://mochi.test:8888/tests/content/base/test/eventsource.resource", "Test 1.a failed.");
+ ok(gEventSourceObj1.readyState == 0 || gEventSourceObj1.readyState == 1, "Test 1.a failed.");
+
+ doTest1_b();
+ }
+
+ function doTest1_b() {
+ gEventSourceObj1.hits = [];
+ gEventSourceObj1.hits['fn_onmessage'] = 0;
+ gEventSourceObj1.onmessage = fn_onmessage;
+ gEventSourceObj1.hits['fn_event_listener_message'] = 0;
+ gEventSourceObj1.addEventListener('message', fn_event_listener_message, true);
+ gEventSourceObj1.hits['fn_other_event_name'] = 0;
+ gEventSourceObj1.addEventListener('other_event_name', fn_other_event_name, true);
+
+ // the eventsources.res always use a retry of 0.5 second, so for four hits a timeout of 3 seconds is enough
+ setTimeout(function(){
+ bhits = hasBeenHitFor1And2(gEventSourceObj1, 4);
+ ok(bhits, "Test 1.b failed.");
+
+ doTest1_c();
+ }, parseInt(3000*stress_factor));
+ }
+
+ function doTest1_c() {
+ gEventSourceObj1.close();
+ ok(gEventSourceObj1.readyState == 2, "Test 1.c failed.");
+
+ doTest1_d();
+ }
+
+ function doTest1_d() {
+ gEventSourceObj1.hits['fn_onmessage'] = 0;
+ gEventSourceObj1.hits['fn_event_listener_message'] = 0;
+ gEventSourceObj1.hits['fn_other_event_name'] = 0;
+
+ setTimeout(function(){
+ bhits = hasBeenHitFor1And2(gEventSourceObj1, 1);
+ ok(!bhits, "Test 1.d failed.");
+ gEventSourceObj1.close();
+
+ doTest2();
+ }, parseInt(2000*stress_factor));
+ }
+
+// in order to test (2)
+// a) set a eventsource that give the dom events messages
+// b) expect trusted events
+
+ function doTest2() {
+ var func = function(e) {
+ ok(e.isTrusted, "Test 2 failed");
+ gEventSourceObj2.close();
+ };
+
+ gEventSourceObj2 = new EventSource("eventsource.resource");
+ gEventSourceObj2.onmessage = func;
+
+ setTimeout(function(){ // just to clean...
+ gEventSourceObj2.close();
+ }, parseInt(5000*stress_factor));
+
+ doTest3();
+ }
+
+// in order to test (3)
+// a) XSite domain error test
+// b) protocol file:/// test
+// c) protocol javascript: test
+// d) wrong Content-Type test
+// e) bad http response code test
+// f) message eventsource without a data test
+// g) eventsource with invalid NCName char in the event field test
+// h) DNS error
+
+ function doTest3() {
+ gEventSourceObj3 = new EventSource("http://example.org/tests/content/base/test/eventsource.resource");
+
+ gEventSourceObj3.onmessage = fn_onmessage;
+ gEventSourceObj3.hits = [];
+ gEventSourceObj3.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3.hits['fn_onmessage'] == 0, "Test 3.a failed");
+ gEventSourceObj3.close();
+
+ doTest3_b();
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_b() {
+ gEventSourceObj3 = new EventSource("file:///home/wellington/src/content/base/test/eventsource.resource");
+
+ gEventSourceObj3.onmessage = fn_onmessage;
+ gEventSourceObj3.hits = [];
+ gEventSourceObj3.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3.hits['fn_onmessage'] == 0, "Test 3.b failed");
+ gEventSourceObj3.close();
+
+ doTest3_c();
+ }, parseInt(1500*stress_factor));
+ }
+
+ function jsEvtSource()
+ {
+ return "event: message\n" +
+ "data: 1\n\n";
+ }
+
+ function doTest3_c() {
+ gEventSourceObj3 = new EventSource("javascript: return jsEvtSource()");
+
+ gEventSourceObj3.onmessage = fn_onmessage;
+ gEventSourceObj3.hits = [];
+ gEventSourceObj3.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3.hits['fn_onmessage'] == 0, "Test 3.c failed");
+ gEventSourceObj3.close();
+
+ doTest3_d();
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_d() {
+ gEventSourceObj3 = new EventSource("badContentType.eventsource");
+
+ gEventSourceObj3.onmessage = fn_onmessage;
+ gEventSourceObj3.hits = [];
+ gEventSourceObj3.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3.hits['fn_onmessage'] == 0, "Test 3.d failed");
+ gEventSourceObj3.close();
+
+ doTest3_e();
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_e() {
+ gEventSourceObj3 = new EventSource("badHTTPResponseCode.eventsource");
+
+ gEventSourceObj3.onmessage = fn_onmessage;
+ gEventSourceObj3.hits = [];
+ gEventSourceObj3.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3.hits['fn_onmessage'] == 0, "Test 3.e failed");
+ gEventSourceObj3.close();
+
+ doTest3_f();
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_f() {
+ gEventSourceObj3 = new EventSource("badMessageEvent.eventsource");
+
+ gEventSourceObj3.onmessage = fn_onmessage;
+ gEventSourceObj3.hits = [];
+ gEventSourceObj3.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3.hits['fn_onmessage'] == 0, "Test 3.f failed");
+ gEventSourceObj3.close();
+
+ doTest3_g();
+ }, parseInt(1500*stress_factor));
+ }
+
+ function fnInvalidNCName() {
+ fnInvalidNCName.hits++;
+ }
+
+ function doTest3_g() {
+ gEventSourceObj3 = new EventSource("badEventFieldName.eventsource");
+
+ fnInvalidNCName.hits = 0;
+ gEventSourceObj3.addEventListener('message event', fnInvalidNCName, true);
+
+ setTimeout(function() {
+ ok(fnInvalidNCName.hits != 0, "Test 3.g failed");
+ gEventSourceObj3.close();
+
+ doTest3_h();
+ }, parseInt(1500*stress_factor));
+ }
+
+ function doTest3_h() {
+ gEventSourceObj3 = new EventSource("http://hdfskjghsbg.jtiyoejowe.dafsgbhjab.com");
+
+ gEventSourceObj3.onmessage = fn_onmessage;
+ gEventSourceObj3.hits = [];
+ gEventSourceObj3.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj3.hits['fn_onmessage'] == 0, "Test 3.h failed");
+ gEventSourceObj3.close();
+
+ doTest4();
+ }, parseInt(1500*stress_factor));
+ }
+
+// in order to test (4)
+// a) close the object when it is in use, which is being processed and that is expected
+// to dispatch more eventlisteners
+// b) remove an eventlistener in use
+
+ function fn_onmessage4_a(e)
+ {
+ if (e.data > gEventSourceObj4.lastData)
+ gEventSourceObj4.lastData = e.data;
+ if (e.data == 2)
+ gEventSourceObj4.close();
+ }
+
+ function fn_onmessage4_b(e)
+ {
+ if (e.data > gEventSourceObj4.lastData)
+ gEventSourceObj4.lastData = e.data;
+ if (e.data == 2)
+ gEventSourceObj4.removeEventListener('message', fn_onmessage4_b, true);
+ }
+
+ function doTest4() {
+ gEventSourceObj4 = new EventSource("forRemoval.resource");
+ gEventSourceObj4.lastData = 0;
+ gEventSourceObj4.onmessage = fn_onmessage4_a;
+
+ setTimeout(function() {
+ ok(gEventSourceObj4.lastData == 2, "Test 4.a failed");
+ gEventSourceObj4.close();
+
+ doTest4_b();
+ }, parseInt(3000*stress_factor));
+ }
+
+ function doTest4_b()
+ {
+ gEventSourceObj4 = new EventSource("forRemoval.resource");
+ gEventSourceObj4.lastData = 0;
+ gEventSourceObj4.addEventListener('message', fn_onmessage4_b, true);
+
+ setTimeout(function() {
+ ok(gEventSourceObj4.lastData == 2, "Test 4.b failed");
+ gEventSourceObj4.close();
+
+ doTest5();
+ }, parseInt(3000*stress_factor));
+ }
+
+// in order to test (5)
+// a) valid access-control xsite request (but must fail)
+// b) invalid access-control xsite request
+
+ function doTest5()
+ {
+ gEventSourceObj5 = new EventSource("http://example.org/tests/content/base/test/accesscontrol.resource");
+
+ gEventSourceObj5.onmessage = fn_onmessage;
+ gEventSourceObj5.hits = [];
+ gEventSourceObj5.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj5.hits['fn_onmessage'] == 0, "Test 5.a failed");
+ gEventSourceObj5.close();
+
+ doTest5_b();
+ }, parseInt(3000*stress_factor));
+ }
+
+ function doTest5_b()
+ {
+ gEventSourceObj5 = new EventSource("http://example.org/tests/content/base/test/invalid_accesscontrol.resource");
+
+ gEventSourceObj5.onmessage = fn_onmessage;
+ gEventSourceObj5.hits = [];
+ gEventSourceObj5.hits['fn_onmessage'] = 0;
+
+ setTimeout(function() {
+ ok(gEventSourceObj5.hits['fn_onmessage'] == 0, "Test 5.b failed");
+ gEventSourceObj5.close();
+
+ doTest6();
+ }, parseInt(3000*stress_factor));
+ }
+
+ function doTest6()
+ {
+ gEventSourceObj6 = new EventSource("somedatas.resource");
+ var fn_somedata = function(e) {
+ if (fn_somedata.expected == 0) {
+ ok(e.data == "123456789\n123456789123456789\n123456789123456789123456789123456789\n 123456789123456789123456789123456789123456789123456789123456789123456789\nçãá\"\'@`~Ý Ḿyyyy",
+ "Test 6.a failed");
+ } else if (fn_somedata.expected == 1) {
+ ok(e.data == " :xxabcdefghij\nçãá\"\'@`~Ý Ḿyyyy : zz",
+ "Test 6.b failed");
+ } else if (fn_somedata.expected == 2) {
+ ok(e.data == "1", "Test 6.c failed");
+ gEventSourceObj6.close();
+ } else {
+ ok(false, "Test 6 failed (unexpected message event)");
+ }
+ fn_somedata.expected++;
+ }
+ fn_somedata.expected = 0;
+ gEventSourceObj6.onmessage = fn_somedata;
+
+ setTimeout(function() {
+ gEventSourceObj6.close();
+
+ doTest7();
+ }, parseInt(2500*stress_factor));
+ }
+
+ function doTest7()
+ {
+ gEventSourceObj7 = new EventSource("delayedServerEvents.sjs");
+ gEventSourceObj7.msg_received = [];
+ gEventSourceObj7.onmessage = function(e)
+ {
+ e.target.msg_received.push(e.data);
+ }
+
+ setTimeout(function() {
+ gEventSourceObj7.close();
+
+ ok(gEventSourceObj7.msg_received[0] == "" &&
+ gEventSourceObj7.msg_received[1] == "delayed1" &&
+ gEventSourceObj7.msg_received[2] == "delayed2", "Test 7 failed");
+
+ SpecialPowers.setBoolPref("dom.server-events.enabled", oldPrefVal);
+ document.getElementById('waitSpan').innerHTML = '';
+ SimpleTest.finish();
+ }, parseInt(50000*stress_factor));
+ }
+
+ function doTest()
+ {
+ oldPrefVal = SpecialPowers.getBoolPref("dom.server-events.enabled");
+ SpecialPowers.setBoolPref("dom.server-events.enabled", true);
+
+ // we get a good stress_factor by testing 10 setTimeouts and some float
+ // arithmetic taking my machine as stress_factor==1 (time=589)
+
+ var begin_time = (new Date()).getTime();
+
+ var f = function() {
+ for (var j=0; j<f.i; ++j)
+ eval("Math.log(Math.atan(Math.sqrt(Math.pow(3.1415, 13.1415))/0.0007))");
+ if (f.i < 10) {
+ f.i++;
+ setTimeout(f, 10 + 10*f.i);
+ } else {
+ stress_factor = ((new Date()).getTime()-begin_time)*1/589;
+ stress_factor *= 1.10; // also, a margin of 10%
+
+ doTest1();
+ }
+ }
+ f.i = 0;
+
+ setTimeout(f, 10);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(doTest);
+
+</script>
+</pre>
+ <span id=waitSpan>Wait please...</span>
+</body>
+</html>
+
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -70,17 +70,17 @@ static const char* const sEventNames[] =
"dragenter", "dragover", "dragexit", "dragdrop", "draggesture",
"drag", "dragend", "dragstart", "dragleave", "drop", "resize",
"scroll", "overflow", "underflow", "overflowchanged",
"DOMSubtreeModified", "DOMNodeInserted", "DOMNodeRemoved",
"DOMNodeRemovedFromDocument", "DOMNodeInsertedIntoDocument",
"DOMAttrModified", "DOMCharacterDataModified",
"DOMActivate", "DOMFocusIn", "DOMFocusOut",
"pageshow", "pagehide", "DOMMouseScroll", "MozMousePixelScroll",
- "offline", "online", "copy", "cut", "paste",
+ "offline", "online", "copy", "cut", "paste", "open", "message",
#ifdef MOZ_SVG
"SVGLoad", "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll",
"SVGZoom",
#endif // MOZ_SVG
#ifdef MOZ_SMIL
"beginEvent", "endEvent", "repeatEvent",
#endif // MOZ_SMIL
#ifdef MOZ_MEDIA
@@ -1256,16 +1256,20 @@ const char* nsDOMEvent::GetEventName(PRU
case NS_ONLINE:
return sEventNames[eDOMEvents_online];
case NS_COPY:
return sEventNames[eDOMEvents_copy];
case NS_CUT:
return sEventNames[eDOMEvents_cut];
case NS_PASTE:
return sEventNames[eDOMEvents_paste];
+ case NS_OPEN:
+ return sEventNames[eDOMEvents_open];
+ case NS_MESSAGE:
+ return sEventNames[eDOMEvents_message];
#ifdef MOZ_SVG
case NS_SVG_LOAD:
return sEventNames[eDOMEvents_SVGLoad];
case NS_SVG_UNLOAD:
return sEventNames[eDOMEvents_SVGUnload];
case NS_SVG_ABORT:
return sEventNames[eDOMEvents_SVGAbort];
case NS_SVG_ERROR:
--- a/content/events/src/nsDOMEvent.h
+++ b/content/events/src/nsDOMEvent.h
@@ -131,16 +131,18 @@ public:
eDOMEvents_pagehide,
eDOMEvents_DOMMouseScroll,
eDOMEvents_MozMousePixelScroll,
eDOMEvents_offline,
eDOMEvents_online,
eDOMEvents_copy,
eDOMEvents_cut,
eDOMEvents_paste,
+ eDOMEvents_open,
+ eDOMEvents_message,
#ifdef MOZ_SVG
eDOMEvents_SVGLoad,
eDOMEvents_SVGUnload,
eDOMEvents_SVGAbort,
eDOMEvents_SVGError,
eDOMEvents_SVGResize,
eDOMEvents_SVGScroll,
eDOMEvents_SVGZoom,
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -208,16 +208,17 @@
#include "nsIXSLTProcessorPrivate.h"
#include "nsIDOMLSProgressEvent.h"
#include "nsIDOMParser.h"
#include "nsIDOMSerializer.h"
#include "nsXMLHttpRequest.h"
#include "nsWebSocket.h"
#include "nsIDOMCloseEvent.h"
+#include "nsEventSource.h"
// includes needed for the prototype chain interfaces
#include "nsIDOMNavigator.h"
#include "nsIDOMBarProp.h"
#include "nsIDOMScreen.h"
#include "nsIDOMDocumentType.h"
#include "nsIDOMDOMImplementation.h"
#include "nsIDOMDocumentFragment.h"
@@ -1316,16 +1317,19 @@ static nsDOMClassInfoData sClassInfoData
NS_DEFINE_CLASSINFO_DATA(XMLSerializer, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(XMLHttpProgressEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(XMLHttpRequest, nsEventTargetSH,
EVENTTARGET_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CLASSINFO_DATA(EventSource, nsEventTargetSH,
+ EVENTTARGET_SCRIPTABLE_FLAGS)
+
NS_DEFINE_CLASSINFO_DATA(ClientRect, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(ClientRectList, nsClientRectListSH,
ARRAY_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(SVGForeignObjectElement, nsElementSH,
ELEMENT_SCRIPTABLE_FLAGS)
@@ -1556,16 +1560,17 @@ static const nsContractIDMapData kConstr
NS_DEFINE_CONSTRUCTOR_DATA(FileReader, NS_FILEREADER_CONTRACTID)
NS_DEFINE_CONSTRUCTOR_DATA(FormData, NS_FORMDATA_CONTRACTID)
NS_DEFINE_CONSTRUCTOR_DATA(XMLSerializer, NS_XMLSERIALIZER_CONTRACTID)
NS_DEFINE_CONSTRUCTOR_DATA(XMLHttpRequest, NS_XMLHTTPREQUEST_CONTRACTID)
NS_DEFINE_CONSTRUCTOR_DATA(WebSocket, NS_WEBSOCKET_CONTRACTID)
NS_DEFINE_CONSTRUCTOR_DATA(XPathEvaluator, NS_XPATH_EVALUATOR_CONTRACTID)
NS_DEFINE_CONSTRUCTOR_DATA(XSLTProcessor,
"@mozilla.org/document-transformer;1?type=xslt")
+ NS_DEFINE_CONSTRUCTOR_DATA(EventSource, NS_EVENTSOURCE_CONTRACTID)
};
struct nsConstructorFuncMapData
{
PRInt32 mDOMClassInfoID;
nsDOMConstructorFunc mConstructorFunc;
};
@@ -3960,16 +3965,22 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XMLHttpProgressEvent, nsIDOMEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMLSProgressEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMProgressEvent)
DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
+ DOM_CLASSINFO_MAP_BEGIN(EventSource, nsIEventSource)
+ DOM_CLASSINFO_MAP_ENTRY(nsIEventSource)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSEventTarget)
+ DOM_CLASSINFO_MAP_END
+
DOM_CLASSINFO_MAP_BEGIN(SVGForeignObjectElement, nsIDOMSVGForeignObjectElement)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGForeignObjectElement)
DOM_CLASSINFO_SVG_GRAPHIC_ELEMENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(XULCommandEvent, nsIDOMXULCommandEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULCommandEvent)
DOM_CLASSINFO_UI_EVENT_MAP_ENTRIES
@@ -6551,16 +6562,23 @@ nsWindowSH::GlobalResolve(nsGlobalWindow
// For now don't expose web sockets unless user has explicitly enabled them
if (name_struct->mDOMClassInfoID == eDOMClassInfo_WebSocket_id) {
if (!nsWebSocket::PrefEnabled()) {
return NS_OK;
}
}
+ // For now don't expose server events unless user has explicitly enabled them
+ if (name_struct->mDOMClassInfoID == eDOMClassInfo_EventSource_id) {
+ if (!nsEventSource::PrefEnabled()) {
+ return NS_OK;
+ }
+ }
+
// Create the XPConnect prototype for our classinfo, PostCreateProto will
// set up the prototype chain.
nsCOMPtr<nsIXPConnectJSObjectHolder> proto_holder;
rv = GetXPCProto(sXPConnect, cx, aWin, name_struct,
getter_AddRefs(proto_holder));
if (NS_SUCCEEDED(rv) && obj != aWin->GetGlobalJSObject()) {
JSObject* dot_prototype;
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -383,16 +383,19 @@ DOMCI_CLASS(StorageEventObsolete)
// DOMParser, XMLSerializer
DOMCI_CLASS(DOMParser)
DOMCI_CLASS(XMLSerializer)
// XMLHttpRequest
DOMCI_CLASS(XMLHttpProgressEvent)
DOMCI_CLASS(XMLHttpRequest)
+// Server-sent events
+DOMCI_CLASS(EventSource)
+
DOMCI_CLASS(ClientRect)
DOMCI_CLASS(ClientRectList)
#ifdef MOZ_SVG
DOMCI_CLASS(SVGForeignObjectElement)
#endif
DOMCI_CLASS(XULCommandEvent)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -289,17 +289,17 @@ nsGlobalWindow::DOMMinTimeoutValue() con
// The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
// uses 5.
#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
// The longest interval (as PRIntervalTime) we permit, or that our
// timer code can handle, really. See DELAY_INTERVAL_LIMIT in
// nsTimerImpl.h for details.
-#define DOM_MAX_TIMEOUT_VALUE PR_BIT(8 * sizeof(PRIntervalTime) - 1)
+#define DOM_MAX_TIMEOUT_VALUE DELAY_INTERVAL_LIMIT
#define FORWARD_TO_OUTER(method, args, err_rval) \
PR_BEGIN_MACRO \
if (IsInnerWindow()) { \
nsGlobalWindow *outer = GetOuterWindowInternal(); \
if (!outer) { \
NS_WARNING("No outer window available!"); \
return err_rval; \
@@ -6532,16 +6532,44 @@ nsGlobalWindow::NotifyWindowIDDestroyed(
{
nsRefPtr<nsIRunnable> runnable = new WindowDestroyedEvent(mWindowID, aTopic);
nsresult rv = NS_DispatchToCurrentThread(runnable);
if (NS_SUCCEEDED(rv)) {
mNotifiedIDDestroyed = PR_TRUE;
}
}
+// static
+void
+nsGlobalWindow::NotifyDOMWindowFrozen(nsGlobalWindow* aWindow) {
+ if (aWindow && aWindow->IsInnerWindow()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+ if (observerService) {
+ observerService->
+ NotifyObservers(static_cast<nsIScriptGlobalObject*>(aWindow),
+ DOM_WINDOW_FROZEN_TOPIC, nsnull);
+ }
+ }
+}
+
+// static
+void
+nsGlobalWindow::NotifyDOMWindowThawed(nsGlobalWindow* aWindow) {
+ if (aWindow && aWindow->IsInnerWindow()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+ if (observerService) {
+ observerService->
+ NotifyObservers(static_cast<nsIScriptGlobalObject*>(aWindow),
+ DOM_WINDOW_THAWED_TOPIC, nsnull);
+ }
+ }
+}
+
void
nsGlobalWindow::InitJavaProperties()
{
nsIScriptContext *scx = GetContextInternal();
if (mDidInitJavaProperties || IsOuterWindow() || !scx || !mJSObject) {
return;
}
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -755,21 +755,23 @@ protected:
already_AddRefed<nsIWidget> GetMainWidget();
nsIWidget* GetNearestWidget();
void Freeze()
{
NS_ASSERTION(!IsFrozen(), "Double-freezing?");
mIsFrozen = PR_TRUE;
+ NotifyDOMWindowFrozen(this);
}
void Thaw()
{
mIsFrozen = PR_FALSE;
+ NotifyDOMWindowThawed(this);
}
PRBool IsInModalState();
nsTimeout* FirstTimeout() {
// Note: might not actually return an nsTimeout. Use IsTimeout to check.
return static_cast<nsTimeout*>(PR_LIST_HEAD(&mTimeouts));
}
@@ -805,16 +807,19 @@ protected:
PRBool* aShowFocusRings);
void UpdateCanvasFocus(PRBool aFocusChanged, nsIContent* aNewContent);
already_AddRefed<nsPIWindowRoot> GetTopWindowRoot();
static void NotifyDOMWindowDestroyed(nsGlobalWindow* aWindow);
void NotifyWindowIDDestroyed(const char* aTopic);
+
+ static void NotifyDOMWindowFrozen(nsGlobalWindow* aWindow);
+ static void NotifyDOMWindowThawed(nsGlobalWindow* aWindow);
void ClearStatus();
virtual void UpdateParentTarget();
PRBool GetIsTabModalPromptAllowed();
inline PRInt32 DOMMinTimeoutValue() const;
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -47,16 +47,18 @@
#include "nsIDOMWindowInternal.h"
#include "nsPIDOMEventTarget.h"
#include "nsIDOMDocument.h"
#include "nsCOMPtr.h"
#include "nsEvent.h"
#include "nsIURI.h"
#define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed"
+#define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
+#define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
class nsIPrincipal;
// Popup control state enum. The values in this enum must go from most
// permissive to least permissive so that it's safe to push state in
// all situations. Pushing popup state onto the stack never makes the
// current popup state less permissive (see
// nsGlobalWindow::PushPopupControlState()).
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -106,16 +106,17 @@
#include "txNodeSetAdaptor.h"
#include "nsDOMParser.h"
#include "nsDOMSerializer.h"
#include "nsXMLHttpRequest.h"
#include "nsChannelPolicy.h"
#include "nsWebSocket.h"
#include "nsDOMWorker.h"
+#include "nsEventSource.h"
// view stuff
#include "nsViewsCID.h"
#include "nsViewManager.h"
#include "nsContentCreatorFunctions.h"
// DOM includes
#include "nsDOMException.h"
@@ -303,16 +304,17 @@ static void Shutdown();
"@mozilla.org/transformiix-nodeset;1"
// Factory Constructor
NS_GENERIC_FACTORY_CONSTRUCTOR(txMozillaXSLTProcessor)
NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsXPathEvaluator, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(txNodeSetAdaptor, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMSerializer)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsXMLHttpRequest, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsEventSource)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebSocket)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDOMFileReader, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormData)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsFileDataProtocolHandler)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMParser)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsDOMStorageManager,
nsDOMStorageManager::GetInstance)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsChannelPolicy)
@@ -834,16 +836,17 @@ NS_DEFINE_NAMED_CID(NS_STYLESHEETSERVICE
NS_DEFINE_NAMED_CID(TRANSFORMIIX_XSLT_PROCESSOR_CID);
NS_DEFINE_NAMED_CID(TRANSFORMIIX_XPATH_EVALUATOR_CID);
NS_DEFINE_NAMED_CID(TRANSFORMIIX_NODESET_CID);
NS_DEFINE_NAMED_CID(NS_XMLSERIALIZER_CID);
NS_DEFINE_NAMED_CID(NS_FILEREADER_CID);
NS_DEFINE_NAMED_CID(NS_FORMDATA_CID);
NS_DEFINE_NAMED_CID(NS_FILEDATAPROTOCOLHANDLER_CID);
NS_DEFINE_NAMED_CID(NS_XMLHTTPREQUEST_CID);
+NS_DEFINE_NAMED_CID(NS_EVENTSOURCE_CID);
NS_DEFINE_NAMED_CID(NS_WEBSOCKET_CID);
NS_DEFINE_NAMED_CID(NS_DOMPARSER_CID);
NS_DEFINE_NAMED_CID(NS_DOMSTORAGE_CID);
NS_DEFINE_NAMED_CID(NS_DOMSTORAGE2_CID);
NS_DEFINE_NAMED_CID(NS_DOMSTORAGEMANAGER_CID);
NS_DEFINE_NAMED_CID(NS_DOMJSON_CID);
NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
NS_DEFINE_NAMED_CID(INDEXEDDB_MANAGER_CID );
@@ -981,16 +984,17 @@ static const mozilla::Module::CIDEntry k
{ &kTRANSFORMIIX_XSLT_PROCESSOR_CID, false, NULL, txMozillaXSLTProcessorConstructor },
{ &kTRANSFORMIIX_XPATH_EVALUATOR_CID, false, NULL, nsXPathEvaluatorConstructor },
{ &kTRANSFORMIIX_NODESET_CID, false, NULL, txNodeSetAdaptorConstructor },
{ &kNS_XMLSERIALIZER_CID, false, NULL, nsDOMSerializerConstructor },
{ &kNS_FILEREADER_CID, false, NULL, nsDOMFileReaderConstructor },
{ &kNS_FORMDATA_CID, false, NULL, nsFormDataConstructor },
{ &kNS_FILEDATAPROTOCOLHANDLER_CID, false, NULL, nsFileDataProtocolHandlerConstructor },
{ &kNS_XMLHTTPREQUEST_CID, false, NULL, nsXMLHttpRequestConstructor },
+ { &kNS_EVENTSOURCE_CID, false, NULL, nsEventSourceConstructor },
{ &kNS_WEBSOCKET_CID, false, NULL, nsWebSocketConstructor },
{ &kNS_DOMPARSER_CID, false, NULL, nsDOMParserConstructor },
{ &kNS_DOMSTORAGE_CID, false, NULL, NS_NewDOMStorage },
{ &kNS_DOMSTORAGE2_CID, false, NULL, NS_NewDOMStorage2 },
{ &kNS_DOMSTORAGEMANAGER_CID, false, NULL, nsDOMStorageManagerConstructor },
{ &kNS_DOMJSON_CID, false, NULL, NS_NewJSON },
{ &kNS_TEXTEDITOR_CID, false, NULL, nsPlaintextEditorConstructor },
{ &kINDEXEDDB_MANAGER_CID, false, NULL, IndexedDatabaseManagerConstructor },
@@ -1123,16 +1127,17 @@ static const mozilla::Module::ContractID
{ TRANSFORMIIX_XSLT_PROCESSOR_CONTRACTID, &kTRANSFORMIIX_XSLT_PROCESSOR_CID },
{ NS_XPATH_EVALUATOR_CONTRACTID, &kTRANSFORMIIX_XPATH_EVALUATOR_CID },
{ TRANSFORMIIX_NODESET_CONTRACTID, &kTRANSFORMIIX_NODESET_CID },
{ NS_XMLSERIALIZER_CONTRACTID, &kNS_XMLSERIALIZER_CID },
{ NS_FILEREADER_CONTRACTID, &kNS_FILEREADER_CID },
{ NS_FORMDATA_CONTRACTID, &kNS_FORMDATA_CID },
{ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX FILEDATA_SCHEME, &kNS_FILEDATAPROTOCOLHANDLER_CID },
{ NS_XMLHTTPREQUEST_CONTRACTID, &kNS_XMLHTTPREQUEST_CID },
+ { NS_EVENTSOURCE_CONTRACTID, &kNS_EVENTSOURCE_CID },
{ NS_WEBSOCKET_CONTRACTID, &kNS_WEBSOCKET_CID },
{ NS_DOMPARSER_CONTRACTID, &kNS_DOMPARSER_CID },
{ "@mozilla.org/dom/storage;1", &kNS_DOMSTORAGE_CID },
{ "@mozilla.org/dom/storage;2", &kNS_DOMSTORAGE2_CID },
{ "@mozilla.org/dom/storagemanager;1", &kNS_DOMSTORAGEMANAGER_CID },
{ "@mozilla.org/dom/json;1", &kNS_DOMJSON_CID },
{ "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID },
{ INDEXEDDB_MANAGER_CONTRACTID, &kINDEXEDDB_MANAGER_CID },
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -806,16 +806,22 @@ pref("network.websocket.timeout.ping.req
pref("network.websocket.timeout.ping.response", 10);
// Defines whether or not to try and negotiate the stream-deflate compression
// extension with the websocket server
pref("network.websocket.extensions.stream-deflate", false);
// </ws>
+// Server-Sent Events
+
+pref("dom.server-events.enabled", true);
+// Equal to the DEFAULT_RECONNECTION_TIME_VALUE value in nsEventSource.cpp
+pref("dom.server-events.default-reconnection-time", 5000); // in milliseconds
+
// If false, remote JAR files that are served with a content type other than
// application/java-archive or application/x-jar will not be opened
// by the jar channel.
pref("network.jar.open-unsafe-types", false);
// This preference controls whether or not internationalized domain names (IDN)
// are handled. IDN requires a nsIIDNService implementation.
pref("network.enableIDN", true);
--- a/netwerk/mime/nsMimeTypes.h
+++ b/netwerk/mime/nsMimeTypes.h
@@ -149,16 +149,17 @@
#define TEXT_CSS "text/css"
#define TEXT_JSSS "text/jsss"
#define TEXT_XML "text/xml"
#define TEXT_RDF "text/rdf"
#define TEXT_XUL "application/vnd.mozilla.xul+xml"
#define TEXT_ECMASCRIPT "text/ecmascript"
#define TEXT_JAVASCRIPT "text/javascript"
#define TEXT_XSL "text/xsl"
+#define TEXT_EVENT_STREAM "text/event-stream"
#define VIDEO_MPEG "video/mpeg"
#define VIDEO_RAW "video/x-raw-yuv"
#define VIDEO_OGG "video/ogg"
#define VIDEO_WEBM "video/webm"
#define APPLICATION_OGG "application/ogg"
/* x-uuencode-apple-single. QuickMail made me do this. */
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -526,16 +526,24 @@ class nsHashKey;
#define NS_NOTIFYSCRIPT_START 4500
#define NS_BEFORE_SCRIPT_EXECUTE (NS_NOTIFYSCRIPT_START)
#define NS_AFTER_SCRIPT_EXECUTE (NS_NOTIFYSCRIPT_START+1)
#define NS_PRINT_EVENT_START 4600
#define NS_BEFOREPRINT (NS_PRINT_EVENT_START)
#define NS_AFTERPRINT (NS_PRINT_EVENT_START + 1)
+#define NS_MESSAGE_EVENT_START 4700
+#define NS_MESSAGE (NS_MESSAGE_EVENT_START)
+
+// Open and close events
+#define NS_OPENCLOSE_EVENT_START 4800
+#define NS_OPEN (NS_OPENCLOSE_EVENT_START)
+#define NS_CLOSE (NS_OPENCLOSE_EVENT_START+1)
+
/**
* Return status for event processors, nsEventStatus, is defined in
* nsEvent.h.
*/
/**
* different types of (top-level) window z-level positioning
*/
--- a/xpcom/threads/nsITimer.idl
+++ b/xpcom/threads/nsITimer.idl
@@ -71,16 +71,20 @@ interface nsITimer;
interface nsITimerCallback : nsISupports
{
/**
* @param aTimer the timer which has expired
*/
void notify(in nsITimer timer);
};
+%{C++
+// Two timer deadlines must differ by less than half the PRIntervalTime domain.
+#define DELAY_INTERVAL_LIMIT PR_BIT(8 * sizeof(PRIntervalTime) - 1)
+%}
/**
* nsITimer instances must be initialized by calling one of the "init" methods
* documented below. You may also re-initialize (using one of the init()
* methods) an existing instance to avoid the overhead of destroying and
* creating a timer. It is not necessary to cancel the timer in that case.
*/
[scriptable, uuid(193fc37a-8aa4-4d29-aa57-1acd87c26b66)]