diff --git a/content/base/public/nsIImageLoadingContent.idl b/content/base/public/nsIImageLoadingContent.idl new file mode 100644 index 000000000000..c8f0d9581e96 --- /dev/null +++ b/content/base/public/nsIImageLoadingContent.idl @@ -0,0 +1,152 @@ +/* -*- 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 + * Boris Zbarsky . + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * + * 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "imgIDecoderObserver.idl" + +interface imgIRequest; + +/** + * This interface represents a content node that loads images. The interface + * exists to allow getting information on the images that the content node + * loads and to allow registration of observers for the image loads. + * + * Implementors of this interface should handle all the mechanics of actually + * loading an image -- getting the URI, checking with content policies and + * the security manager to see whether loading the URI is allowed, performing + * the load, firing any DOM events as needed. + * + * An implementation of this interface may support the concepts of a + * "current" image and a "pending" image. If it does, a request to change + * the currently loaded image will start a "pending" request which will + * become current only when the image is loaded. It is the responsibility of + * observers to check which request they are getting notifications for. + * + * Observers added in mid-load will not get any notifications they + * missed. We should NOT freeze this interface without considering + * this issue. (It could be that the image status on imgIRequest is + * sufficient, when combined with the isImageBlocked() information.) + * + * XXXbz Do not freeze without removing imageURIChanged! + */ + +[scriptable, uuid(f3138741-353d-4b2b-beb8-01c308428895)] +interface nsIImageLoadingContent : imgIDecoderObserver +{ + /** + * Request types. Image loading content nodes attempt to do atomic + * image changes when the image url is changed. This means that + * when the url changes the new image load will start, but the old + * image will remain the "current" request until the new image is + * fully loaded. At that point, the old "current" request will be + * discarded and the "pending" request will become "current". + */ + const long UNKNOWN_REQUEST = -1; + const long CURRENT_REQUEST = 0; + const long PENDING_REQUEST = 1; + + /** + * loadingEnabled is used to enable and disable loading in + * situations where loading images is unwanted. Note that enabling + * loading will *not* automatically trigger an image load. + */ + attribute boolean loadingEnabled; + + /** + * Returns true if the image load was blocked (by content policies, + * security manager, etc). Returns false otherwise. This always + * refers to the CURRENT_REQUEST load. + */ + readonly attribute boolean imageBlocked; + + /** + * Used to register an image decoder observer. Typically, this will + * be a proxy for a frame that wants to paint the image. + * Notifications from ongoing image loads will be passed to all + * registered observers. Notifications for all request types, + * current and pending, will be passed through. + * + * @param aObserver the observer to register + * + * @throws NS_ERROR_OUT_OF_MEMORY + */ + void addObserver(in imgIDecoderObserver aObserver); + + /** + * Used to unregister an image decoder observer. + * + * @param aObserver the observer to unregister + */ + void removeObserver(in imgIDecoderObserver aObserver); + + /** + * Accessor to get the image requests + * + * @param aRequestType a value saying which request is wanted + * + * @return the imgIRequest object (may be null, even when no error + * is thrown) + * + * @throws NS_ERROR_UNEXPECTED if the request type requested is not + * known + */ + imgIRequest getRequest(in long aRequestType); + + /** + * Used to find out what type of request one is dealing with (eg + * which request got passed through to the imgIDecoderObserver + * interface of an observer) + * + * @param aRequest the request whose type we want to know + * + * @return an enum value saying what type this request is + * + * @throws NS_ERROR_UNEXPECTED if aRequest is not known + */ + long getRequestType(in imgIRequest aRequest); + + /** + * ImageURIChanged is called when the appropriate attributes (eg + * 'src' for tags) change. The string passed in is the new + * uri string. + */ + // XXXbz The only reason this is not a protected method is that + // XXXbz and do everything from frames, not content. + // XXXbz Once those are moved to content, this method should become + // XXXbz a protected method on nsImageLoadingContent! + void imageURIChanged(in AString aNewURI); +}; diff --git a/content/base/src/nsImageLoadingContent.cpp b/content/base/src/nsImageLoadingContent.cpp new file mode 100644 index 000000000000..6a6c7acc74ed --- /dev/null +++ b/content/base/src/nsImageLoadingContent.cpp @@ -0,0 +1,576 @@ +/* -*- 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 + * Boris Zbarsky . + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * + * 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsImageLoadingContent.h" +#include "nsContentErrors.h" +#include "nsIContent.h" +#include "nsIHTMLContent.h" +#include "nsIDocument.h" +#include "nsINodeInfo.h" +#include "nsIScriptGlobalObject.h" +#include "nsIDOMWindow.h" +#include "nsIServiceManagerUtils.h" +#include "nsContentPolicyUtils.h" +#include "nsIURI.h" +#include "nsILoadGroup.h" +#include "imgIContainer.h" +#include "gfxIImageFrame.h" +#include "imgILoader.h" +#include "plevent.h" +#include "nsIEventQueueService.h" +#include "nsIEventQueue.h" +#include "nsNetUtil.h" + +#include "nsIDOMDocumentEvent.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMEvent.h" + +nsImageLoadingContent::nsImageLoadingContent() + : mObserverList(nsnull), + mLoadingEnabled(PR_TRUE), + mImageIsBlocked(PR_FALSE) +{ +} + +nsImageLoadingContent::~nsImageLoadingContent() +{ + // Cancel our requests so they won't hold stale refs to us + if (mCurrentRequest) { + mCurrentRequest->Cancel(NS_ERROR_FAILURE); + } + if (mPendingRequest) { + mPendingRequest->Cancel(NS_ERROR_FAILURE); + } + NS_ASSERTION(!mObserverList.mObserver && !mObserverList.mNext, + "Observers still registered?"); +} + +// Macro to call some func on each observer. This handles observers +// removing themselves. +#define LOOP_OVER_OBSERVERS(func_) \ + PR_BEGIN_MACRO \ + for (ImageObserver* observer = &mObserverList; observer; \ + observer = observer->mNext) { \ + if (observer->mObserver) { \ + observer->mObserver->func_; \ + } \ + } \ + PR_END_MACRO + + +/* + * imgIContainerObserver impl + */ +NS_IMETHODIMP +nsImageLoadingContent::FrameChanged(imgIContainer* aContainer, + gfxIImageFrame* aFrame, + nsRect* aDirtyRect) +{ + LOOP_OVER_OBSERVERS(FrameChanged(aContainer, aFrame, aDirtyRect)); + return NS_OK; +} + +/* + * imgIDecoderObserver impl + */ +NS_IMETHODIMP +nsImageLoadingContent::OnStartDecode(imgIRequest* aRequest) +{ + LOOP_OVER_OBSERVERS(OnStartDecode(aRequest)); + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::OnStartContainer(imgIRequest* aRequest, + imgIContainer* aContainer) +{ + LOOP_OVER_OBSERVERS(OnStartContainer(aRequest, aContainer)); + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::OnStartFrame(imgIRequest* aRequest, + gfxIImageFrame* aFrame) +{ + LOOP_OVER_OBSERVERS(OnStartFrame(aRequest, aFrame)); + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::OnDataAvailable(imgIRequest* aRequest, + gfxIImageFrame* aFrame, + const nsRect* aRect) +{ + LOOP_OVER_OBSERVERS(OnDataAvailable(aRequest, aFrame, aRect)); + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::OnStopFrame(imgIRequest* aRequest, + gfxIImageFrame* aFrame) +{ + LOOP_OVER_OBSERVERS(OnStopFrame(aRequest, aFrame)); + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::OnStopContainer(imgIRequest* aRequest, + imgIContainer* aContainer) +{ + LOOP_OVER_OBSERVERS(OnStopContainer(aRequest, aContainer)); + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::OnStopDecode(imgIRequest* aRequest, + nsresult aStatus, + const PRUnichar* aStatusArg) +{ + NS_PRECONDITION(aRequest == mCurrentRequest || aRequest == mPendingRequest, + "Unknown request"); + LOOP_OVER_OBSERVERS(OnStopDecode(aRequest, aStatus, aStatusArg)); + + if (aRequest == mPendingRequest) { + mCurrentRequest->Cancel(NS_ERROR_IMAGE_SRC_CHANGED); + mCurrentRequest = mPendingRequest; + mPendingRequest = nsnull; + } + + if (NS_SUCCEEDED(aStatus)) { + FireEvent(NS_LITERAL_STRING("load")); + } else { + FireEvent(NS_LITERAL_STRING("error")); + } + + return NS_OK; +} + +/* + * nsIImageLoadingElement impl + */ + +NS_IMETHODIMP +nsImageLoadingContent::GetLoadingEnabled(PRBool *aLoadingEnabled) +{ + *aLoadingEnabled = mLoadingEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::SetLoadingEnabled(PRBool aLoadingEnabled) +{ + mLoadingEnabled = aLoadingEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::GetImageBlocked(PRBool* aBlocked) +{ + NS_PRECONDITION(aBlocked, "Null out param"); + *aBlocked = mImageIsBlocked; + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::AddObserver(imgIDecoderObserver* aObserver) +{ + NS_ENSURE_ARG_POINTER(aObserver); + + if (!mObserverList.mObserver) { + mObserverList.mObserver = aObserver; + // Don't touch the linking of the list! + return NS_OK; + } + + // otherwise we have to create a new entry + + ImageObserver* observer = &mObserverList; + while (observer->mNext) { + observer = observer->mNext; + } + + observer->mNext = new ImageObserver(aObserver); + if (! observer->mNext) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::RemoveObserver(imgIDecoderObserver* aObserver) +{ + NS_ENSURE_ARG_POINTER(aObserver); + + if (mObserverList.mObserver == aObserver) { + mObserverList.mObserver = nsnull; + // Don't touch the linking of the list! + return NS_OK; + } + + // otherwise have to find it and splice it out + ImageObserver* observer = &mObserverList; + while (observer->mNext && observer->mNext->mObserver != aObserver) { + observer = observer->mNext; + } + + // At this point, we are pointing to the list element whose mNext is + // the right observer (assuming of course that mNext is not null) + if (observer->mNext) { + // splice it out + ImageObserver* oldObserver = observer->mNext; + observer->mNext = oldObserver->mNext; + oldObserver->mNext = nsnull; // so we don't destroy them all + delete oldObserver; + } +#ifdef DEBUG + else { + NS_WARNING("Asked to remove non-existent observer"); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsImageLoadingContent::GetRequest(const PRInt32 aRequestType, + imgIRequest** aRequest) +{ + switch(aRequestType) { + case CURRENT_REQUEST: + *aRequest = mCurrentRequest; + break; + case PENDING_REQUEST: + *aRequest = mPendingRequest; + break; + default: + NS_ERROR("Unknown request type"); + *aRequest = nsnull; + return NS_ERROR_UNEXPECTED; + } + + NS_IF_ADDREF(*aRequest); + return NS_OK; +} + + +NS_IMETHODIMP +nsImageLoadingContent::GetRequestType(imgIRequest* aRequest, + PRInt32* aRequestType) +{ + NS_PRECONDITION(aRequestType, "Null out param"); + + if (aRequest == mCurrentRequest) { + *aRequestType = CURRENT_REQUEST; + return NS_OK; + } + + if (aRequest == mPendingRequest) { + *aRequestType = PENDING_REQUEST; + return NS_OK; + } + + *aRequestType = UNKNOWN_REQUEST; + NS_ERROR("Unknown request"); + return NS_ERROR_UNEXPECTED; +} + +// XXX This should be a protected method, not an interface method!!! +NS_IMETHODIMP +nsImageLoadingContent::ImageURIChanged(const nsAString& aNewURI) { + return ImageURIChanged(NS_ConvertUCS2toUTF8(aNewURI)); +} + +/* + * Non-interface methods + */ +nsresult +nsImageLoadingContent::ImageURIChanged(const nsACString& aNewURI) +{ + if (!mLoadingEnabled) { + return NS_OK; + } + + if (aNewURI.IsEmpty()) { + // Do not take down the already loaded image... (for compat with + // the old code that loaded images from frames) + // XXXbz is this what we really want? + return NS_OK; + } + + nsresult rv; // XXXbz Should failures in this method fire onerror? + + // First, get a document (needed for security checks, base URI and the like) + nsCOMPtr doc; + rv = GetOurDocument(getter_AddRefs(doc)); + if (!doc) { + // No reason to bother, I think... + return rv; + } + + nsCOMPtr imageURI; + rv = StringToURI(aNewURI, doc, getter_AddRefs(imageURI)); + NS_ENSURE_SUCCESS(rv, rv); + + + // Now get the image loader... + nsCOMPtr loader = do_GetService("@mozilla.org/image/loader;1", &rv); + NS_ENSURE_TRUE(loader, rv); + + // If we'll be loading a new image, we want to cancel our existing + // requests; the question is what reason to pass in. If everything + // is going smoothly, that reason should be + // NS_ERROR_IMAGE_SRC_CHANGED so that our frame (if any) will know + // not to show the broken image icon. If the load is blocked by the + // content policy or security manager, we will want to cancel with + // the error code from those. + nsresult cancelResult = CanLoadImage(imageURI, doc); + if (NS_SUCCEEDED(cancelResult)) { + cancelResult = NS_ERROR_IMAGE_SRC_CHANGED; + } + + mImageIsBlocked = (cancelResult == NS_ERROR_IMAGE_BLOCKED); + + CancelImageRequests(cancelResult); + + if (cancelResult != NS_ERROR_IMAGE_SRC_CHANGED) { + // Don't actually load anything! This was blocked by CanLoadImage. + return NS_OK; + } + + nsCOMPtr loadGroup; + doc->GetDocumentLoadGroup(getter_AddRefs(loadGroup)); + NS_WARN_IF_FALSE(loadGroup, "Could not get loadgroup; onload may fire too early"); + + nsCOMPtr documentURI; + doc->GetDocumentURL(getter_AddRefs(documentURI)); + + nsCOMPtr & req = mCurrentRequest ? mPendingRequest : mCurrentRequest; + + // XXXbz using "documentURI" for the initialDocumentURI is not quite + // right, but the best we can do here... + return loader->LoadImage(imageURI, /* uri to load */ + documentURI, /* initialDocumentURI */ + documentURI, /* referrer */ + loadGroup, /* loadgroup */ + this, /* imgIDecoderObserver */ + doc, /* uniquification key */ + nsIRequest::LOAD_NORMAL, /* load flags */ + nsnull, /* cache key */ + nsnull, /* existing request*/ + getter_AddRefs(req)); +} + +void +nsImageLoadingContent::CancelImageRequests(nsresult aReason) +{ + // Cancel the pending request, if any + if (mPendingRequest) { + mPendingRequest->Cancel(aReason); + mPendingRequest = nsnull; + } + + // Cancel the current request if it has not progressed enough to + // have a size yet + if (mCurrentRequest) { + PRUint32 loadStatus = imgIRequest::STATUS_ERROR; + mCurrentRequest->GetImageStatus(&loadStatus); + + if (!(loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) { + mCurrentRequest->Cancel(aReason); + mCurrentRequest = nsnull; + } + } +} + +nsresult +nsImageLoadingContent::CanLoadImage(nsIURI* aURI, nsIDocument* aDocument) +{ + NS_PRECONDITION(aURI, "Null URI"); + NS_PRECONDITION(aDocument, "Null document!"); + + // Check with the content-policy things to make sure this load is permitted. + + nsCOMPtr globalScript; + nsresult rv = aDocument->GetScriptGlobalObject(getter_AddRefs(globalScript)); + if (NS_FAILED(rv)) { + // just let it load. Documents loaded as data should take care to + // prevent image loading themselves. + return NS_OK; + } + + nsCOMPtr domWin(do_QueryInterface(globalScript)); + + PRBool shouldLoad = PR_TRUE; + rv = NS_CheckContentLoadPolicy(nsIContentPolicy::IMAGE, aURI, this, + domWin, &shouldLoad); + if (NS_SUCCEEDED(rv) && !shouldLoad) { + return NS_ERROR_IMAGE_BLOCKED; + } + + return NS_OK; +} + + +nsresult +nsImageLoadingContent::GetOurDocument(nsIDocument** aDocument) +{ + NS_PRECONDITION(aDocument, "Null out param"); + + nsresult rv; + + nsCOMPtr thisContent = do_QueryInterface(this, &rv); + NS_ENSURE_TRUE(thisContent, rv); + + rv = thisContent->GetDocument(*aDocument); + if (!*aDocument) { // nodeinfo time + nsCOMPtr nodeInfo; + rv = thisContent->GetNodeInfo(*getter_AddRefs(nodeInfo)); + if (nodeInfo) { + rv = nodeInfo->GetDocument(*aDocument); + } + } + + return rv; +} + +nsresult +nsImageLoadingContent::StringToURI(const nsACString& aSpec, + nsIDocument* aDocument, + nsIURI** aURI) +{ + NS_PRECONDITION(aDocument, "Must have a document"); + NS_PRECONDITION(aURI, "Null out param"); + + nsresult rv; + + // (1) Get the base URI + nsCOMPtr baseURL; + nsCOMPtr thisContent = do_QueryInterface(this); + if (thisContent) { + rv = thisContent->GetBaseURL(*getter_AddRefs(baseURL)); + } else { + rv = aDocument->GetBaseURL(*getter_AddRefs(baseURL)); + if (!baseURL) { + rv = aDocument->GetDocumentURL(getter_AddRefs(baseURL)); + } + } + NS_ENSURE_SUCCESS(rv, rv); + + // (2) Get the charset + nsAutoString charset; + aDocument->GetDocumentCharacterSet(charset); + + // (3) Construct the silly thing + return NS_NewURI(aURI, + aSpec, + charset.IsEmpty() ? nsnull : NS_ConvertUCS2toUTF8(charset).get(), + baseURL); +} + + +/** + * Struct used to dispatch events + */ +struct ImageEvent : PLEvent { + ImageEvent(nsIDOMEventTarget* aTarget, nsIDOMEvent* aEvent) + : mTarget(aTarget), + mEvent(aEvent) + {} + + nsCOMPtr mTarget; + nsCOMPtr mEvent; +}; + +PR_STATIC_CALLBACK(void*) +HandleImagePLEvent(PLEvent* aEvent) +{ + ImageEvent* evt = NS_STATIC_CAST(ImageEvent*, aEvent); + PRBool preventDefaultCalled; // we don't care... + evt->mTarget->DispatchEvent(evt->mEvent, &preventDefaultCalled); + return nsnull; +} + +PR_STATIC_CALLBACK(void) +DestroyImagePLEvent(PLEvent* aEvent) +{ + ImageEvent* evt = NS_STATIC_CAST(ImageEvent*, aEvent); + delete evt; +} + +nsresult +nsImageLoadingContent::FireEvent(const nsAString& aEventType) +{ + // We have to fire the event asynchronously so that we won't go into infinite + // loops in cases when onLoad handlers reset the src and the new src is in + // cache. + + nsresult rv; + nsCOMPtr eventQService = + do_GetService("@mozilla.org/event-queue-service;1", &rv); + NS_ENSURE_TRUE(eventQService, rv); + + nsCOMPtr eventQ; + // Use the UI thread event queue (though we should not be getting called from + // off the UI thread in any case....) + rv = eventQService->GetSpecialEventQueue(nsIEventQueueService::UI_THREAD_EVENT_QUEUE, + getter_AddRefs(eventQ)); + NS_ENSURE_TRUE(eventQ, rv); + + nsCOMPtr document; + rv = GetOurDocument(getter_AddRefs(document)); + + nsCOMPtr eventTarget = do_QueryInterface(this); + NS_ENSURE_TRUE(eventTarget, NS_ERROR_UNEXPECTED); + + nsCOMPtr doc = do_QueryInterface(document); + NS_ENSURE_TRUE(doc, rv); + + nsCOMPtr domEvent; + rv = doc->CreateEvent(NS_LITERAL_STRING("HTMLEvents"), getter_AddRefs(domEvent)); + NS_ENSURE_TRUE(domEvent, rv); + + domEvent->InitEvent(aEventType, PR_FALSE, PR_FALSE); + + ImageEvent* evt = new ImageEvent(eventTarget, domEvent); + + NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY); + + PL_InitEvent(evt, this, ::HandleImagePLEvent, ::DestroyImagePLEvent); + + return eventQ->PostEvent(evt); +} diff --git a/content/base/src/nsImageLoadingContent.h b/content/base/src/nsImageLoadingContent.h new file mode 100644 index 000000000000..c63bf84760ec --- /dev/null +++ b/content/base/src/nsImageLoadingContent.h @@ -0,0 +1,164 @@ +/* -*- 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 + * Boris Zbarsky . + * Portions created by the Initial Developer are Copyright (C) 2003 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * + * 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 NPL, 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 NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsImageLoadingContent_h__ +#define nsImageLoadingContent_h__ + +#include "nsIImageLoadingContent.h" +#include "imgIRequest.h" +#include "prtypes.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +/** + * This is an implementation of nsIImageLoadingContent. This class + * can be subclassed by various content nodes that want to provide + * image-loading functionality (eg , , etc). + */ + +class nsIURI; +class nsIDocument; + +class nsImageLoadingContent : public nsIImageLoadingContent +{ + /* METHODS */ +public: + nsImageLoadingContent(); + virtual ~nsImageLoadingContent(); + + NS_DECL_IMGICONTAINEROBSERVER + NS_DECL_IMGIDECODEROBSERVER + NS_DECL_NSIIMAGELOADINGCONTENT + +protected: + /** + * ImageURIChanged is called by subclasses when the appropriate + * attributes (eg 'src' for tags) change. The string passed + * in is the new uri string; this consolidates the code for getting + * the charset and any other incidentals into this superclass. + * + * Note that this is different from the ImageURIChanged(AString) + * declared in nsIImageLoadingContent.idl -- that takes an + * nsAString, this takes an nsACString. + * + * @param aNewURI the URI spec to be loaded (may be a relative URI) + */ + nsresult ImageURIChanged(const nsACString& aNewURI); + +private: + /** + * Struct used to manage the image observers. + */ + struct ImageObserver { + ImageObserver(imgIDecoderObserver* aObserver) : + mObserver(aObserver), + mNext(nsnull) + {} + ~ImageObserver() { delete mNext; } + + nsCOMPtr mObserver; + ImageObserver* mNext; + }; + + /** + * CancelImageRequests can be called when we want to cancel the + * image requests. The "current" request will be canceled only if + * it has not progressed far enough to know the image size yet. + * + * @param aReason the reason the requests are being canceled + */ + void CancelImageRequests(nsresult aReason); + + /** + * Method to do security and content policy checks on the image URI + * + * @param aURI uri of the image to be loaded + * @param aDocument the document we belong to + */ + nsresult CanLoadImage(nsIURI* aURI, nsIDocument* aDocument); + + /** + * helper to get the document for this content (from the nodeinfo + * and such). Not named GetDocument to prevent ambiguous method + * names in subclasses (though why this private method leads to + * ambiguity is not clear to me....). + * + * @return the document we belong to + */ + nsresult GetOurDocument(nsIDocument** aDocument); + + /** + * Method to create an nsIURI object from the given string (will + * handle getting the right charset, base, etc). You MUST pass in a + * non-null document to this function. + * + * @param aSpec the string spec (from an HTML attribute, eg) + * @param aDocument the document we belong to + * @return the URI we want to be loading + */ + nsresult StringToURI(const nsACString& aSpec, nsIDocument* aDocument, + nsIURI** aURI); + + /** + * Method to fire an event once we know what's going on with the image load. + * + * @param aEventType "load" or "error" depending on how things went + */ + nsresult FireEvent(const nsAString& aEventType); + + /* MEMBERS */ +protected: + nsCOMPtr mCurrentRequest; + nsCOMPtr mPendingRequest; + +private: + /** + * Typically we will have only one observer (our frame in the screen + * prescontext), so we want to only make space for one and to + * heap-allocate anything past that (saves memory and malloc churn + * in the common case). The storage is a linked list, we just + * happen to actually hold the first observer instead of a pointer + * to it. + */ + ImageObserver mObserverList; + + PRPackedBool mLoadingEnabled; + PRPackedBool mImageIsBlocked; +}; + +#endif // nsImageLoadingContent_h__