Bug 96459, bug 96307 and Bugscape 8288. Implement readyState and onreadystatechange members to enable progress indicators for download. Make it possible to call send() again in onload without crashing. Send load event only after everything done (was sent too early if XML errors). Make some functions throw error if called at the wrong time (match IE). Make it possible to build SOAP again (patch from peterv, r=heikki). r=harishd, sr=vidur, a=asa.

This commit is contained in:
heikki%netscape.com 2006-04-20 03:37:31 +00:00
Родитель 27ecd1d149
Коммит fa2f3c1137
3 изменённых файлов: 256 добавлений и 76 удалений

Просмотреть файл

@ -26,6 +26,42 @@ interface nsIDOMDocument;
interface nsIDOMEventListener;
interface nsIChannel;
/**
* Mozilla's XMLHttpRequest is modelled after Microsoft's IXMLHttpRequest
* object. The goal has been to make Mozilla's version match Microsoft's
* version as closely as possible, but there are bound to be some differences.
*
* In general, Microsoft's documentation for IXMLHttpRequest can be used.
* Mozilla's interface definitions provide some additional documentation. The
* web page to look at is http://www.mozilla.org/xmlextras/
*
* Mozilla's XMLHttpRequest object can be created in JavaScript like this:
* new XMLHttpRequest()
* compare to Internet Explorer:
* new ActiveXObject("Msxml2.XMLHTTP")
*
* From JavaScript, the methods and properties visible in the XMLHttpRequest
* object are a combination of nsIXMLHttpRequest and nsIJSXMLHttpRequest;
* there is no need to differentiate between those interfaces.
*
* From native code, the way to set up onload and onerror handlers is a bit
* different. Here is a comment from Johnny Stenback <jst@netscape.com>:
*
* The mozilla implementation of nsIXMLHttpRequest implements the interface
* nsIDOMEventTarget and that's how you're supported to add event listeners.
* Try something like this:
*
* nsCOMPtr<nsIDOMEventTarget> target(do_QuertyInterface(myxmlhttpreq));
*
* target->AddEventListener(NS_LITERAL_STRING("load"), mylistener,
* PR_FALSE)
*
* where mylistener is your event listener object that implements the
* interface nsIDOMEventListener.
*
* The 'onload' and 'onerror' attributes moved to nsIJSXMLHttRequest,
* but if you're coding in C++ you should avoid using those.
*/
[scriptable, uuid(b7215e70-4157-11d4-9a42-000064657374)]
interface nsIXMLHttpRequest : nsISupports {
/**
@ -33,6 +69,8 @@ interface nsIXMLHttpRequest : nsISupports {
* request. This attribute represents the channel used
* for the request. NULL if the channel has not yet been
* created.
*
* Mozilla only.
*/
readonly attribute nsIChannel channel;
@ -150,15 +188,37 @@ interface nsIXMLHttpRequest : nsISupports {
void send(in nsISupports body);
/**
* Sets a HTTP request header for HTTP requests.
* Sets a HTTP request header for HTTP requests. You must call open
* before setting the request headers.
*
* @param header The name of the header to set in the request.
* @param value The body of the header.
*/
void setRequestHeader(in string header, in string value);
/**
* The state of the request.
*
* Possible values:
* 0 UNINITIALIZED open() has not been called yet.
* 1 LOADING send() has not been called yet.
* 2 LOADED send() has been called, headers and status are available.
* 3 INTERACTIVE Downloading, responseText holds the partial data.
* 4 COMPLETED Finished with all operations.
*/
readonly attribute long readyState;
};
[scriptable, function, uuid(6459B7CE-6B57-4934-A0AF-0133BA6F9085)]
interface nsIOnReadystatechangeHandler : nsISupports {
/**
* Helper to implement the onreadystatechange callback member.
* You should not need to use this.
*/
void handleEvent();
};
[scriptable, uuid(9deabc90-28d5-41d3-a660-474f2254f4ba)]
interface nsIJSXMLHttpRequest : nsISupports {
/**
@ -166,6 +226,8 @@ interface nsIJSXMLHttpRequest : nsISupports {
* The attribute is expected to be JavaScript function object. When
* the load event occurs, the function is invoked.
* This attribute should not be used from native code!!
*
* Mozilla only.
*/
attribute nsIDOMEventListener onload;
@ -174,8 +236,18 @@ interface nsIJSXMLHttpRequest : nsISupports {
* The attribute is expected to be JavaScript function object. When
* the error event occurs, the function is invoked.
* This attribute should not be used from native code!!
*
* Mozilla only.
*/
attribute nsIDOMEventListener onerror;
/**
* Meant to be a script-only mechanism for setting a callback function.
* The attribute is expected to be JavaScript function object. When the
* readyState changes, the callback function will be called.
* This attribute should not be used from native code!!
*/
attribute nsIOnReadystatechangeHandler onreadystatechange;
};
%{ C++

Просмотреть файл

@ -213,7 +213,7 @@ nsLoadListenerProxy::Error(nsIDOMEvent* aEvent)
nsXMLHttpRequest::nsXMLHttpRequest()
{
NS_INIT_ISUPPORTS();
mStatus = XML_HTTP_REQUEST_INITIALIZED;
ChangeState(XML_HTTP_REQUEST_UNINITIALIZED,PR_FALSE);
mAsync = PR_TRUE;
}
@ -316,6 +316,28 @@ nsXMLHttpRequest::DispatchEvent(nsIDOMEvent *evt)
return NS_OK;
}
/* attribute nsIOnReadystatechangeHandler onreadystatechange; */
NS_IMETHODIMP
nsXMLHttpRequest::GetOnreadystatechange(nsIOnReadystatechangeHandler * *aOnreadystatechange)
{
NS_ENSURE_ARG_POINTER(aOnreadystatechange);
*aOnreadystatechange = mOnReadystatechangeListener;
return NS_OK;
}
NS_IMETHODIMP
nsXMLHttpRequest::SetOnreadystatechange(nsIOnReadystatechangeHandler * aOnreadystatechange)
{
mOnReadystatechangeListener = aOnreadystatechange;
GetCurrentContext(getter_AddRefs(mScriptContext));
return NS_OK;
}
/* attribute nsIDOMEventListener onload; */
NS_IMETHODIMP
nsXMLHttpRequest::GetOnload(nsIDOMEventListener * *aOnLoad)
@ -518,11 +540,14 @@ NS_IMETHODIMP nsXMLHttpRequest::GetResponseText(PRUnichar **aResponseText)
{
NS_ENSURE_ARG_POINTER(aResponseText);
*aResponseText = nsnull;
if ((XML_HTTP_REQUEST_COMPLETED == mStatus)) {
if ((XML_HTTP_REQUEST_COMPLETED == mStatus) ||
(XML_HTTP_REQUEST_INTERACTIVE == mStatus)) {
// First check if we can represent the data as a string - if it contains
// nulls we won't try.
if (mResponseBody.FindChar('\0') >= 0)
return NS_ERROR_FAILURE;
return NS_OK;
nsresult rv = ConvertBodyToText(aResponseText);
if (NS_FAILED(rv))
return rv;
@ -657,7 +682,7 @@ nsXMLHttpRequest::OpenRequest(const char *method,
rv = httpChannel->SetRequestMethod(method);
}
mStatus = XML_HTTP_REQUEST_OPENED;
ChangeState(XML_HTTP_REQUEST_OPENED);
return rv;
}
@ -857,6 +882,8 @@ nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in,
}
}
xmlHttpRequest->ChangeState(XML_HTTP_REQUEST_INTERACTIVE);
if (NS_SUCCEEDED(rv)) {
*writeCount = count;
} else {
@ -889,6 +916,7 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
{
mReadRequest = request;
mContext = ctxt;
ChangeState(XML_HTTP_REQUEST_LOADED);
return mXMLParserStreamListener->OnStartRequest(request,ctxt);
}
@ -900,6 +928,82 @@ nsXMLHttpRequest::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult
mXMLParserStreamListener = nsnull;
mReadRequest = nsnull;
mContext = nsnull;
RequestCompleted();
return rv;
}
nsresult
nsXMLHttpRequest::RequestCompleted()
{
nsresult rv = NS_OK;
NS_WARN_IF_FALSE(mDelayedEvent,"no delayed event");
// We might have been sent non-XML data. If that was the case,
// we should null out the document member. The idea in this
// check here is that if there is no document element it is not
// an XML document. We might need a fancier check...
if (mDocument) {
nsCOMPtr<nsIDOMElement> root;
mDocument->GetDocumentElement(getter_AddRefs(root));
if (!root) {
mDocument = nsnull;
}
}
ChangeState(XML_HTTP_REQUEST_COMPLETED);
#ifdef IMPLEMENT_SYNC_LOAD
if (mChromeWindow) {
mChromeWindow->ExitModalEventLoop(NS_OK);
mChromeWindow = 0;
}
#endif
nsCOMPtr<nsIJSContextStack> stack;
JSContext *cx = nsnull;
if (mScriptContext) {
stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1");
if (stack) {
cx = (JSContext *)mScriptContext->GetNativeContext();
if (cx) {
stack->Push(cx);
}
}
}
if (mOnLoadListener && mDelayedEvent) {
mOnLoadListener->HandleEvent(mDelayedEvent);
}
if (mLoadEventListeners && mDelayedEvent) {
PRUint32 index, count;
mLoadEventListeners->Count(&count);
for (index = 0; index < count; index++) {
nsCOMPtr<nsIDOMEventListener> listener;
mLoadEventListeners->QueryElementAt(index,
NS_GET_IID(nsIDOMEventListener),
getter_AddRefs(listener));
if (listener) {
listener->HandleEvent(mDelayedEvent);
}
}
}
if (cx) {
stack->Pop(&cx);
}
mDelayedEvent = nsnull;
return rv;
}
@ -915,7 +1019,7 @@ nsXMLHttpRequest::Send(nsISupports *body)
}
// Make sure we've been opened
if (!mChannel) {
if (!mChannel || XML_HTTP_REQUEST_OPENED != mStatus) {
return NS_ERROR_NOT_INITIALIZED;
}
@ -1084,7 +1188,7 @@ nsXMLHttpRequest::Send(nsISupports *body)
#endif
// Start reading from the channel
mStatus = XML_HTTP_REQUEST_SENT;
ChangeState(XML_HTTP_REQUEST_SENT);
mXMLParserStreamListener = listener;
rv = mChannel->AsyncOpen(this, nsnull);
@ -1117,6 +1221,9 @@ nsXMLHttpRequest::Send(nsISupports *body)
NS_IMETHODIMP
nsXMLHttpRequest::SetRequestHeader(const char *header, const char *value)
{
if (!mChannel) // open() initializes mChannel, and open()
return NS_ERROR_FAILURE; // must be called before first setRequestHeader()
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
if (httpChannel)
@ -1125,6 +1232,19 @@ nsXMLHttpRequest::SetRequestHeader(const char *header, const char *value)
return NS_OK;
}
/* readonly attribute long readyState; */
NS_IMETHODIMP
nsXMLHttpRequest::GetReadyState(PRInt32 *aState)
{
NS_ENSURE_ARG_POINTER(aState);
if (mStatus == XML_HTTP_REQUEST_SENT) {
*aState = XML_HTTP_REQUEST_OPENED;
} else {
*aState = mStatus;
}
return NS_OK;
}
// nsIDOMEventListener
nsresult
nsXMLHttpRequest::HandleEvent(nsIDOMEvent* aEvent)
@ -1137,67 +1257,14 @@ nsXMLHttpRequest::HandleEvent(nsIDOMEvent* aEvent)
nsresult
nsXMLHttpRequest::Load(nsIDOMEvent* aEvent)
{
mStatus = XML_HTTP_REQUEST_COMPLETED;
// We might have been sent non-XML data. If that was the case,
// we should null out the document member. The idea in this
// check here is that if there is no document element it is not
// an XML document. We might need a fancier check...
if (mDocument) {
nsCOMPtr<nsIDOMElement> root;
mDocument->GetDocumentElement(getter_AddRefs(root));
if (!root) {
mDocument = nsnull;
}
}
#ifdef IMPLEMENT_SYNC_LOAD
if (mChromeWindow) {
mChromeWindow->ExitModalEventLoop(NS_OK);
mChromeWindow = 0;
}
#endif
nsCOMPtr<nsIJSContextStack> stack;
JSContext *cx = nsnull;
if (mScriptContext) {
stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1");
if (stack) {
cx = (JSContext *)mScriptContext->GetNativeContext();
if (cx) {
stack->Push(cx);
}
}
}
if (mOnLoadListener) {
mOnLoadListener->HandleEvent(aEvent);
}
if (mLoadEventListeners) {
PRUint32 index, count;
mLoadEventListeners->Count(&count);
for (index = 0; index < count; index++) {
nsCOMPtr<nsIDOMEventListener> listener;
mLoadEventListeners->QueryElementAt(index,
NS_GET_IID(nsIDOMEventListener),
getter_AddRefs(listener));
if (listener) {
listener->HandleEvent(aEvent);
}
}
}
if (cx) {
stack->Pop(&cx);
}
// If we had an XML error in the data, the parser terminated and
// we received the load event, even though we might still be
// loading data into responseBody/responseText. We will delay
// sending the load event until OnStopRequest(). In normal case
// there is no harm done, we will get OnStopRequest() immediately
// after the load event.
NS_WARN_IF_FALSE(!mDelayedEvent,"there should be no delayed event");
mDelayedEvent = aEvent;
return NS_OK;
}
@ -1210,8 +1277,8 @@ nsXMLHttpRequest::Unload(nsIDOMEvent* aEvent)
nsresult
nsXMLHttpRequest::Abort(nsIDOMEvent* aEvent)
{
mStatus = XML_HTTP_REQUEST_ABORTED;
mDocument = nsnull;
ChangeState(XML_HTTP_REQUEST_UNINITIALIZED);
#ifdef IMPLEMENT_SYNC_LOAD
if (mChromeWindow) {
mChromeWindow->ExitModalEventLoop(NS_OK);
@ -1225,8 +1292,8 @@ nsXMLHttpRequest::Abort(nsIDOMEvent* aEvent)
nsresult
nsXMLHttpRequest::Error(nsIDOMEvent* aEvent)
{
mStatus = XML_HTTP_REQUEST_ABORTED;
mDocument = nsnull;
ChangeState(XML_HTTP_REQUEST_UNINITIALIZED);
#ifdef IMPLEMENT_SYNC_LOAD
if (mChromeWindow) {
mChromeWindow->ExitModalEventLoop(NS_OK);
@ -1277,6 +1344,37 @@ nsXMLHttpRequest::Error(nsIDOMEvent* aEvent)
return NS_OK;
}
nsresult
nsXMLHttpRequest::ChangeState(nsXMLHttpRequestState aState, PRBool aBroadcast)
{
mStatus = aState;
nsresult rv = NS_OK;
if (mAsync && aBroadcast && mOnReadystatechangeListener) {
nsCOMPtr<nsIJSContextStack> stack;
JSContext *cx = nsnull;
if (mScriptContext) {
stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1");
if (stack) {
cx = (JSContext *)mScriptContext->GetNativeContext();
if (cx) {
stack->Push(cx);
}
}
}
rv = mOnReadystatechangeListener->HandleEvent();
if (cx) {
stack->Pop(&cx);
}
}
return rv;
}
NS_IMPL_ISUPPORTS1(nsXMLHttpRequest::nsHeaderVisitor, nsIHttpHeaderVisitor)
NS_IMETHODIMP nsXMLHttpRequest::
@ -1288,3 +1386,4 @@ nsHeaderVisitor::VisitHeader(const char *header, const char *value)
mHeaders.Append('\n');
return NS_OK;
}

Просмотреть файл

@ -45,12 +45,13 @@
#include "nsIScriptContext.h"
enum {
XML_HTTP_REQUEST_INITIALIZED,
XML_HTTP_REQUEST_OPENED,
XML_HTTP_REQUEST_SENT,
enum nsXMLHttpRequestState {
XML_HTTP_REQUEST_UNINITIALIZED = 0,
XML_HTTP_REQUEST_OPENED, // aka LOADING
XML_HTTP_REQUEST_LOADED,
XML_HTTP_REQUEST_INTERACTIVE,
XML_HTTP_REQUEST_COMPLETED,
XML_HTTP_REQUEST_ABORTED
XML_HTTP_REQUEST_SENT // This is Mozilla-internal only, LOADING in IE and external view
};
class nsXMLHttpRequest : public nsIXMLHttpRequest,
@ -102,6 +103,10 @@ protected:
PRUint32 toOffset,
PRUint32 count,
PRUint32 *writeCount);
// Change the state of the object with this. The broadcast member determines
// if the onreadystatechange listener should be called.
nsresult ChangeState(nsXMLHttpRequestState aState, PRBool aBroadcast = PR_TRUE);
nsresult RequestCompleted();
nsCOMPtr<nsISupports> mContext;
nsCOMPtr<nsIChannel> mChannel;
@ -118,7 +123,11 @@ protected:
nsCOMPtr<nsIDOMEventListener> mOnLoadListener;
nsCOMPtr<nsIDOMEventListener> mOnErrorListener;
nsCOMPtr<nsIOnReadystatechangeHandler> mOnReadystatechangeListener;
nsCOMPtr<nsIDOMEvent> mDelayedEvent;
// used to implement getAllResponseHeaders()
class nsHeaderVisitor : public nsIHttpHeaderVisitor {
public: