[XForms] generate xforms-link-error for help, hint, alert and message elements. Bug 300255, r=smaug+allan

git-svn-id: svn://10.0.0.236/trunk@192790 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
aaronr%us.ibm.com
2006-03-22 18:05:49 +00:00
parent 8d81bda849
commit 0d627e8dde
9 changed files with 495 additions and 40 deletions

View File

@@ -78,6 +78,9 @@
#include "nsIDOMSerializer.h"
#include "nsIServiceManager.h"
#include "nsIDelegateInternal.h"
#include "nsNetUtil.h"
#include "nsIDOMDocumentEvent.h"
#include "nsIChannelEventSink.h"
#define EPHEMERAL_STYLE \
"position:absolute;z-index:2147483647; \
@@ -121,7 +124,10 @@ class nsXFormsEventListener;
*/
class nsXFormsMessageElement : public nsXFormsXMLVisualStub,
public nsIDOMEventListener,
public nsIXFormsActionModuleElement
public nsIXFormsActionModuleElement,
public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
public:
NS_DECL_ISUPPORTS_INHERITED
@@ -138,7 +144,13 @@ public:
NS_IMETHOD GetInsertionPoint(nsIDOMElement **aElement);
NS_IMETHOD ParentChanged(nsIDOMElement *aNewParent);
NS_IMETHOD WillChangeParent(nsIDOMElement *aNewParent);
NS_IMETHOD AttributeSet(nsIAtom *aName, const nsAString &aSrc);
NS_IMETHOD AttributeRemoved(nsIAtom *aName);
NS_DECL_NSICHANNELEVENTSINK
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSIDOMEVENTLISTENER
NS_DECL_NSIXFORMSACTIONMODULEELEMENT
@@ -161,8 +173,16 @@ public:
eType_Alert
};
enum StopType {
eStopType_None,
eStopType_Security,
eStopType_LinkError
};
nsXFormsMessageElement(MessageType aType) :
mType(aType), mElement(nsnull), mPosX(-1), mPosY(-1) {}
mType(aType), mElement(nsnull), mPosX(-1), mPosY(-1),
mStopType(eStopType_None) {}
private:
nsresult HandleEphemeralMessage(nsIDOMDocument* aDoc, nsIDOMEvent* aEvent);
nsresult HandleModalAndModelessMessage(nsIDOMDocument* aDoc, nsAString& aLevel);
@@ -173,6 +193,22 @@ private:
PRBool aIsLink,
/*out*/ nsAString& aURL);
/**
* Begin the process of testing to see if this message element could get held
* up later by linking to an external resource. Run this when the element is
* set up and it will try to access any external resource that this message
* may later try to access. If we can't get to it by the time the message
* is triggered, then throw a xforms-link-error.
*/
nsresult TestExternalFile();
/**
* Either add or subtract from the number of messages with external resources
* currently loading in this document. aAdd == PR_TRUE will add to the total
* otherwise we will subtract from the total (as long as it isn't already 0).
*/
void AddRemoveExternalResource(PRBool aAdd);
MessageType mType;
nsCOMPtr<nsIDOMElement> mVisualElement;
@@ -184,12 +220,17 @@ private:
nsCOMPtr<nsITimer> mEphemeralTimer;
nsCOMPtr<nsIDOMDocument> mDocument;
nsCOMPtr<nsIChannel> mChannel;
StopType mStopType;
};
NS_IMPL_ADDREF_INHERITED(nsXFormsMessageElement, nsXFormsXMLVisualStub)
NS_IMPL_RELEASE_INHERITED(nsXFormsMessageElement, nsXFormsXMLVisualStub)
NS_INTERFACE_MAP_BEGIN(nsXFormsMessageElement)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIXFormsActionModuleElement)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END_INHERITING(nsXFormsXMLVisualStub)
@@ -203,7 +244,9 @@ nsXFormsMessageElement::OnCreated(nsIXTFXMLVisualWrapper *aWrapper)
NS_ENSURE_SUCCESS(rv, rv);
aWrapper->SetNotificationMask(nsIXTFElement::NOTIFY_WILL_CHANGE_DOCUMENT |
nsIXTFElement::NOTIFY_PARENT_CHANGED);
nsIXTFElement::NOTIFY_PARENT_CHANGED |
nsIXTFElement::NOTIFY_ATTRIBUTE_SET |
nsIXTFElement::NOTIFY_ATTRIBUTE_REMOVED);
nsCOMPtr<nsIDOMElement> node;
rv = aWrapper->GetElementNode(getter_AddRefs(node));
@@ -261,6 +304,12 @@ nsXFormsMessageElement::WillChangeDocument(nsIDOMDocument *aNewDocument)
if (msg == this)
doc->UnsetProperty(nsXFormsAtoms::messageProperty);
}
// If we are currently trying to load an external message, cancel the
// request.
if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED);
}
}
mDocument = aNewDocument;
@@ -272,6 +321,7 @@ nsXFormsMessageElement::OnDestroyed()
{
mElement = nsnull;
mVisualElement = nsnull;
mChannel = nsnull;
return NS_OK;
}
@@ -391,6 +441,39 @@ nsXFormsMessageElement::ParentChanged(nsIDOMElement *aNewParent)
return NS_OK;
}
NS_IMETHODIMP
nsXFormsMessageElement::AttributeSet(nsIAtom *aName, const nsAString &aValue)
{
if (aName == nsXFormsAtoms::src) {
// If we are currently trying to load an external message, cancel the
// request.
if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED);
}
mStopType = eStopType_None;
TestExternalFile();
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsMessageElement::AttributeRemoved(nsIAtom *aName)
{
if (aName == nsXFormsAtoms::src) {
// If we are currently trying to test an external resource, cancel the
// request.
if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED);
}
mStopType = eStopType_None;
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsMessageElement::HandleAction(nsIDOMEvent* aEvent,
nsIXFormsActionElement *aParentAction)
@@ -398,6 +481,35 @@ nsXFormsMessageElement::HandleAction(nsIDOMEvent* aEvent,
if (!mElement)
return NS_OK;
// If TestExternalFile fails, then there is an external link that we need
// to use that we can't reach right now. If it won't load, then might as
// well stop here. We don't want to be popping up empty windows
// or windows that will just end up showing 404 messages.
if (mStopType == eStopType_LinkError) {
// we couldn't successfully link to our external resource. Better throw
// the xforms-link-error event
nsCOMPtr<nsIModelElementPrivate> modelPriv =
nsXFormsUtils::GetModel(mElement);
nsCOMPtr<nsIDOMNode> model = do_QueryInterface(modelPriv);
nsXFormsUtils::DispatchEvent(model, eEvent_LinkError);
return NS_OK;
}
// If we couldn't test the external link due to it not living in an
// acceptable domain, then no sense going any further down this path.
if (mStopType == eStopType_Security) {
return NS_OK;
}
// If there is still a channel, then someone must have changed the value of
// the src attribute since the document finished loading and we haven't yet
// determined whether the new link is valid or not. For now we'll assume
// that the value is good rather than returning a link error. Also
// canceling the channel since it is too late now.
if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED);
}
if (mType != eType_Normal) {
nsCOMPtr<nsIDOMEventTarget> target;
@@ -703,7 +815,6 @@ nsXFormsMessageElement::HandleModalAndModelessMessage(nsIDOMDocument* aDoc,
arg = nsXFormsAtoms::messageProperty;
}
//XXX Add support for xforms-link-error.
nsCOMPtr<nsIDOMWindow> messageWindow;
// The 2nd argument is the window name, and if a window with the name exists,
// it gets reused. Using "_blank" makes sure we get a new window each time.
@@ -926,6 +1037,274 @@ nsXFormsMessageElement::HideEphemeral()
nsITimer::TYPE_ONE_SHOT);
}
nsresult
nsXFormsMessageElement::TestExternalFile()
{
// Let's see if checking for any external resources is even necessary. Single
// node binding trumps linking attributes in order of precendence. If we
// find single node binding in evidence, then return NS_OK to show that
// this message element has access to the info that it needs.
nsAutoString snb;
mElement->GetAttribute(NS_LITERAL_STRING("bind"), snb);
if (!snb.IsEmpty()) {
return NS_OK;
}
mElement->GetAttribute(NS_LITERAL_STRING("ref"), snb);
if (!snb.IsEmpty()) {
return NS_OK;
}
// if no linking attribute, no need to go on
nsAutoString src;
mElement->GetAttribute(NS_LITERAL_STRING("src"), src);
if (src.IsEmpty()) {
return NS_OK;
}
nsCOMPtr<nsIDOMDocument> domDoc;
mElement->GetOwnerDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc(do_QueryInterface(domDoc));
NS_ENSURE_STATE(doc);
nsCOMPtr<nsIURI> uri;
NS_NewURI(getter_AddRefs(uri), src, doc->GetDocumentCharacterSet().get(),
doc->GetDocumentURI());
NS_ENSURE_STATE(uri);
if (!nsXFormsUtils::CheckSameOrigin(doc, uri)) {
nsAutoString tagName;
mElement->GetLocalName(tagName);
const PRUnichar *strings[] = { tagName.get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("externalLinkLoadOrigin"),
strings, 1, mElement, mElement);
// Keep the the dialog from popping up. Won't be able to reach the
// resource anyhow.
mStopType = eStopType_Security;
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
NS_WARN_IF_FALSE(loadGroup, "No load group!");
// Using the same load group as the main document and creating
// the channel with LOAD_NORMAL flag delays the dispatching of
// the 'load' event until message data document has been loaded.
nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), uri, nsnull, loadGroup,
this, nsIRequest::LOAD_NORMAL);
NS_ENSURE_TRUE(mChannel, rv);
// See if it's an http channel. We'll look at the http status code more
// closely and only request the "HEAD" to keep the response small.
// Especially since we are requesting this for every message in the document
// and in most cases we pass off the URL to another browser service to
// get and display the message so no good to try to look at the contents
// anyway.
// XXX ephemeral messages should probably get their full contents here once
// they are set up to handle external resources.
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (httpChannel) {
PRBool isReallyHTTP = PR_FALSE;
uri->SchemeIs("http", &isReallyHTTP);
if (!isReallyHTTP) {
uri->SchemeIs("https", &isReallyHTTP);
}
if (isReallyHTTP) {
httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD"));
}
}
rv = mChannel->AsyncOpen(this, nsnull);
if (NS_FAILED(rv)) {
mChannel = nsnull;
// URI doesn't exist; report error.
// set up the error strings
nsAutoString tagName;
mElement->GetLocalName(tagName);
const PRUnichar *strings[] = { src.get(), tagName.get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("externalLink1Error"),
strings, 2, mElement, mElement);
mStopType = eStopType_LinkError;
return NS_ERROR_FAILURE;
}
// channel should be running along smoothly, increment the count
AddRemoveExternalResource(PR_TRUE);
return NS_OK;
}
// nsIInterfaceRequestor
NS_IMETHODIMP
nsXFormsMessageElement::GetInterface(const nsIID &aIID, void **aResult)
{
*aResult = nsnull;
return QueryInterface(aIID, aResult);
}
// nsIChannelEventSink
NS_IMETHODIMP
nsXFormsMessageElement::OnChannelRedirect(nsIChannel *OldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags)
{
NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
nsCOMPtr<nsIURI> newURI;
nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_STATE(mElement);
nsCOMPtr<nsIDOMDocument> domDoc;
mElement->GetOwnerDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc(do_QueryInterface(domDoc));
NS_ENSURE_STATE(doc);
if (!nsXFormsUtils::CheckSameOrigin(doc, newURI)) {
nsAutoString tagName;
mElement->GetLocalName(tagName);
const PRUnichar *strings[] = { tagName.get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("externalLinkLoadOrigin"),
strings, 1, mElement, mElement);
return NS_ERROR_ABORT;
}
return NS_OK;
}
// nsIStreamListener
NS_IMETHODIMP
nsXFormsMessageElement::OnStartRequest(nsIRequest *aRequest,
nsISupports *aContext)
{
return NS_OK;
}
NS_IMETHODIMP
nsXFormsMessageElement::OnDataAvailable(nsIRequest *aRequest,
nsISupports *aContext,
nsIInputStream *aInputStream,
PRUint32 aOffset,
PRUint32 aCount)
{
return NS_OK;
}
NS_IMETHODIMP
nsXFormsMessageElement::OnStopRequest(nsIRequest *aRequest,
nsISupports *aContext,
nsresult aStatusCode)
{
// We are done with the load request, whatever the result, so make sure to
// null out mChannel before we return. Keep in mind that if this is the last
// message channel to be loaded for the xforms document then when
// AddRemoveExternalResource is called, it may result in xforms-ready firing.
// Should there be a message acting as a handler for xforms-ready, it will
// start the logic to display itself (HandleAction()). So we can't call
// AddRemoveExternalResource to remove this channel from the count until we've
// set the mStopType to be the proper value. Entering this function,
// mStopType will be eStopType_None, so if we need mStopType to be any other
// value (like in an error condition), please make sure it is set before
// AddRemoveExternalResource is called.
if (NS_FAILED(aStatusCode)) {
// NS_BINDING_ABORTED means that we have been cancelled by a later
// AttributeSet() call (which will also reset mStopType). So don't treat
// like an error.
if (aStatusCode != NS_BINDING_ABORTED) {
nsAutoString src, tagName;
mElement->GetLocalName(tagName);
mElement->GetAttribute(NS_LITERAL_STRING("src"), src);
const PRUnichar *strings[] = { tagName.get(), src.get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("externalLink2Error"),
strings, 2, mElement, mElement);
mStopType = eStopType_LinkError;
AddRemoveExternalResource(PR_FALSE);
mChannel = nsnull;
return NS_OK;
}
}
PRUint32 responseStatus;
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (NS_SUCCEEDED(aStatusCode) && httpChannel) {
nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
// If responseStatus is 4xx or 5xx, it is an error. 2xx is success.
// 3xx (various flavors of redirection) COULD be successful. Can't really
// follow those to conclusion so we'll assume they were successful.
if (NS_FAILED(rv) || (responseStatus >= 400)) {
nsAutoString src, tagName;
mElement->GetLocalName(tagName);
mElement->GetAttribute(NS_LITERAL_STRING("src"), src);
const PRUnichar *strings[] = { tagName.get(), src.get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("externalLink2Error"),
strings, 2, mElement, mElement);
mStopType = eStopType_LinkError;
}
}
AddRemoveExternalResource(PR_FALSE);
mChannel = nsnull;
return NS_OK;
}
void
nsXFormsMessageElement::AddRemoveExternalResource(PRBool aAdd)
{
// if this message doesn't have a channel established already or it has
// already returned, then no sense bumping the counter.
if (!mChannel) {
return;
}
nsCOMPtr<nsIDOMDocument> domDoc;
mElement->GetOwnerDocument(getter_AddRefs(domDoc));
if (!domDoc) {
return;
}
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
PRUint32 loadingMessages = NS_REINTERPRET_CAST(PRUint32,
doc->GetProperty(nsXFormsAtoms::externalMessagesProperty));
if (aAdd) {
loadingMessages++;
} else {
if (loadingMessages) {
loadingMessages--;
}
}
doc->SetProperty(nsXFormsAtoms::externalMessagesProperty,
NS_REINTERPRET_CAST(void *, loadingMessages), nsnull);
if (!loadingMessages) {
// no outstanding loads left, let the model in the document know in case
// the models are waiting to send out the xforms-ready event
nsCOMPtr<nsIModelElementPrivate> modelPriv =
nsXFormsUtils::GetModel(mElement);
if (modelPriv) {
// if there are no more messages loading then it is probably the case
// that my mChannel is going to get nulled out as soon as this function
// returns. If the model is waiting for this notification, then it may
// kick off the message right away and we should probably ensure that
// mChannel is gone before HandleAction is called. So...if the channel
// isn't pending, let's null it out right here.
PRBool isPending = PR_TRUE;
mChannel->IsPending(&isPending);
if (!isPending) {
mChannel = nsnull;
}
modelPriv->MessageLoadFinished();
}
}
}
NS_HIDDEN_(nsresult)
NS_NewXFormsMessageElement(nsIXTFElement **aResult)
{