# HG changeset patch # User Joe Drew # Parent 2b4eac0380239d2e508288c687ee4fe5879c2930 Bug 664299 - Support CORS in Imagelib by adding load flags for the various CORS modes. r=jrmuizel,bz diff --git a/modules/libpr0n/public/imgILoader.idl b/modules/libpr0n/public/imgILoader.idl --- a/modules/libpr0n/public/imgILoader.idl +++ b/modules/libpr0n/public/imgILoader.idl @@ -58,16 +58,21 @@ interface nsIChannelPolicy; * * @author Stuart Parmenter * @version 0.3 * @see imagelib2 */ [scriptable, uuid(20a5e3e9-0d5b-482c-9f41-942b5f19e5a3)] interface imgILoader : nsISupports { + // Extra flags to pass to loadImage if you want a load to use CORS + // validation. + const unsigned long LOAD_CORS_ANONYMOUS = 1 << 16; + const unsigned long LOAD_CORS_USE_CREDENTIALS = 1 << 17; + /** * Start the load and decode of an image. * @param aURI the URI to load * @param aInitialDocumentURI the URI that 'initiated' the load -- used for 3rd party cookie blocking * @param aReferrerURI the 'referring' URI * @param aLoadingPrincipal the principal of the loading document * @param aLoadGroup Loadgroup to put the image load into * @param aObserver the observer (may be null) diff --git a/modules/libpr0n/public/imgIRequest.idl b/modules/libpr0n/public/imgIRequest.idl --- a/modules/libpr0n/public/imgIRequest.idl +++ b/modules/libpr0n/public/imgIRequest.idl @@ -47,17 +47,17 @@ interface nsIPrincipal; /** * imgIRequest interface * * @author Stuart Parmenter * @version 0.1 * @see imagelib2 */ -[scriptable, uuid(62f58f12-3076-44fc-a742-5b648dac21bc)] +[scriptable, uuid(c3bf4e2a-f64b-4ac1-a84e-18631b1802ab)] interface imgIRequest : nsIRequest { /** * the image container... * @return the image object associated with the request. * @attention NEED DOCS */ readonly attribute imgIContainer image; @@ -128,16 +128,37 @@ interface imgIRequest : nsIRequest imgIRequest clone(in imgIDecoderObserver aObserver); /** * The principal gotten from the channel the image was loaded from. */ readonly attribute nsIPrincipal imagePrincipal; /** + * CORS modes images can be loaded with. + * + * By default, all images are loaded with CORS_NONE and cannot be used + * cross-origin in context like WebGL. + * + * If an HTML img element has the crossorigin attribute set, the imgIRequest + * will be validated for cross-origin usage with CORS, and, if successful, + * will have its CORS mode set to the relevant type. + */ + //@{ + const long CORS_NONE = 1; + const long CORS_ANONYMOUS = 2; + const long CORS_USE_CREDENTIALS = 3; + //@} + + /** + * The CORS mode that this image was loaded with. + */ + readonly attribute long CORSMode; + + /** * Cancels this request as in nsIRequest::Cancel(); further, also nulls out * decoderObserver so it gets no further notifications from us. * * NOTE: You should not use this in any new code; instead, use cancel(). Note * that cancel() is asynchronous, which means that some time after you call * it, the listener/observer will get an OnStopRequest(). This means that, if * you're the observer, you can't call cancel() from your destructor. */ diff --git a/modules/libpr0n/src/imgLoader.cpp b/modules/libpr0n/src/imgLoader.cpp --- a/modules/libpr0n/src/imgLoader.cpp +++ b/modules/libpr0n/src/imgLoader.cpp @@ -46,16 +46,18 @@ * gets changed. * This #undef needs to be in multiple places because we don't always pull * headers in in the same order. */ #undef LoadImage #include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsCrossSiteListenerProxy.h" #include "nsNetUtil.h" #include "nsStreamUtils.h" #include "nsIHttpChannel.h" #include "nsICachingChannel.h" #include "nsIInterfaceRequestor.h" #include "nsIProgressEventSink.h" #include "nsIChannelEventSink.h" #include "nsIAsyncVerifyRedirectCallback.h" @@ -416,16 +418,45 @@ static PRBool ShouldRevalidateEntry(imgC else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) { bValidateEntry = PR_TRUE; } } return bValidateEntry; } +// Returns true if this request is compatible with the given CORS mode on the +// given loading principal, and false if the request may not be reused due +// to CORS. +static bool +ValidateCORS(imgRequest* request, PRInt32 corsmode, nsIPrincipal* loadingPrincipal) +{ + // If the entry's CORS mode doesn't match, or the CORS mode matches but the + // document principal isn't the same, we can't use this request. + if (request->GetCORSMode() != corsmode) { + return false; + } else if (request->GetCORSMode() != imgIRequest::CORS_NONE) { + nsCOMPtr otherprincipal = request->GetLoadingPrincipal(); + + // If we previously had a principal, but we don't now, we can't use this + // request. + if (otherprincipal && !loadingPrincipal) { + return false; + } + + if (otherprincipal && loadingPrincipal) { + PRBool equals = PR_FALSE; + otherprincipal->Equals(loadingPrincipal, &equals); + return !equals; + } + } + + return true; +} + static nsresult NewImageChannel(nsIChannel **aResult, nsIURI *aURI, nsIURI *aInitialDocumentURI, nsIURI *aReferringURI, nsILoadGroup *aLoadGroup, const nsCString& aAcceptHeader, nsLoadFlags aLoadFlags, nsIChannelPolicy *aPolicy) @@ -1136,17 +1167,18 @@ PRBool imgLoader::ValidateRequestWithNew nsIURI *aReferrerURI, nsILoadGroup *aLoadGroup, imgIDecoderObserver *aObserver, nsISupports *aCX, nsLoadFlags aLoadFlags, imgIRequest *aExistingRequest, imgIRequest **aProxyRequest, nsIChannelPolicy *aPolicy, - nsIPrincipal* aLoadingPrincipal) + nsIPrincipal* aLoadingPrincipal, + PRInt32 aCORSMode) { // now we need to insert a new channel request object inbetween the real // request and the proxy that basically delays loading the image until it // gets a 304 or figures out that this needs to be a new request nsresult rv; // If we're currently in the middle of validating this request, just hand @@ -1200,18 +1232,28 @@ PRBool imgLoader::ValidateRequestWithNew // Make sure that OnStatus/OnProgress calls have the right request set... nsRefPtr progressproxy = new nsProgressNotificationProxy(newChannel, req); if (!progressproxy) return PR_FALSE; nsRefPtr hvc = new imgCacheValidator(progressproxy, request, aCX); - if (!hvc) { - return PR_FALSE; + + nsCOMPtr listener = hvc.get(); + + if (aCORSMode != imgIRequest::CORS_NONE) { + PRBool withCredentials = aCORSMode == imgIRequest::CORS_USE_CREDENTIALS; + nsCOMPtr corsproxy = + new nsCORSListenerProxy(hvc, aLoadingPrincipal, newChannel, withCredentials, &rv); + if (NS_FAILED(rv)) { + return PR_FALSE; + } + + listener = corsproxy; } newChannel->SetNotificationCallbacks(hvc); request->mValidator = hvc; imgRequestProxy* proxy = static_cast (static_cast(req.get())); @@ -1220,17 +1262,17 @@ PRBool imgLoader::ValidateRequestWithNew // In the mean time, we must defer notifications because we are added to // the imgRequest's proxy list, and we can get extra notifications // resulting from methods such as RequestDecode(). See bug 579122. proxy->SetNotificationsDeferred(PR_TRUE); // Add the proxy without notifying hvc->AddProxy(proxy); - rv = newChannel->AsyncOpen(static_cast(hvc), nsnull); + rv = newChannel->AsyncOpen(listener, nsnull); if (NS_SUCCEEDED(rv)) NS_ADDREF(*aProxyRequest = req.get()); return NS_SUCCEEDED(rv); } } PRBool imgLoader::ValidateEntry(imgCacheEntry *aEntry, @@ -1240,17 +1282,18 @@ PRBool imgLoader::ValidateEntry(imgCache nsILoadGroup *aLoadGroup, imgIDecoderObserver *aObserver, nsISupports *aCX, nsLoadFlags aLoadFlags, PRBool aCanMakeNewChannel, imgIRequest *aExistingRequest, imgIRequest **aProxyRequest, nsIChannelPolicy *aPolicy, - nsIPrincipal* aLoadingPrincipal) + nsIPrincipal* aLoadingPrincipal, + PRInt32 aCORSMode) { LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry"); PRBool hasExpired; PRUint32 expirationTime = aEntry->GetExpiryTime(); if (expirationTime <= SecondsFromPRTime(PR_Now())) { hasExpired = PR_TRUE; } else { @@ -1277,16 +1320,19 @@ PRBool imgLoader::ValidateEntry(imgCache } } nsRefPtr request(aEntry->GetRequest()); if (!request) return PR_FALSE; + if (!ValidateCORS(request, aCORSMode, aLoadingPrincipal)) + return PR_FALSE; + PRBool validateRequest = PR_FALSE; // If the request's loadId is the same as the aCX, then it is ok to use // this one because it has already been validated for this context. // // XXX: nsnull seems to be a 'special' key value that indicates that NO // validation is required. // @@ -1355,18 +1401,18 @@ PRBool imgLoader::ValidateEntry(imgCache if (validateRequest && aCanMakeNewChannel) { LOG_SCOPE(gImgLog, "imgLoader::ValidateRequest |cache hit| must validate"); return ValidateRequestWithNewChannel(request, aURI, aInitialDocumentURI, aReferrerURI, aLoadGroup, aObserver, aCX, aLoadFlags, aExistingRequest, aProxyRequest, aPolicy, - aLoadingPrincipal); - } + aLoadingPrincipal, aCORSMode); + } return !validateRequest; } PRBool imgLoader::RemoveFromCache(nsIURI *aKey) { if (!aKey) return PR_FALSE; @@ -1544,28 +1590,35 @@ NS_IMETHODIMP imgLoader::LoadImage(nsIUR requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) | (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK); } if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { // Propagate background loading... requestFlags |= nsIRequest::LOAD_BACKGROUND; } + PRInt32 corsmode = imgIRequest::CORS_NONE; + if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) { + corsmode = imgIRequest::CORS_ANONYMOUS; + } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) { + corsmode = imgIRequest::CORS_USE_CREDENTIALS; + } + nsRefPtr entry; // Look in the cache for our URI, and then validate it. // XXX For now ignore aCacheKey. We will need it in the future // for correctly dealing with image load requests that are a result // of post data. imgCacheTable &cache = GetCache(aURI); if (cache.Get(spec, getter_AddRefs(entry)) && entry) { if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI, aLoadGroup, aObserver, aCX, requestFlags, PR_TRUE, - aRequest, _retval, aPolicy, aLoadingPrincipal)) { + aRequest, _retval, aPolicy, aLoadingPrincipal, corsmode)) { request = getter_AddRefs(entry->GetRequest()); // If this entry has no proxies, its request has no reference to the entry. if (entry->HasNoProxies()) { LOG_FUNC_WITH_PARAM(gImgLog, "imgLoader::LoadImage() adding proxyless entry", "uri", spec.get()); NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!"); request->SetCacheEntry(entry); @@ -1613,39 +1666,47 @@ NS_IMETHODIMP imgLoader::LoadImage(nsIUR // Create a loadgroup for this new channel. This way if the channel // is redirected, we'll have a way to cancel the resulting channel. nsCOMPtr loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); newChannel->SetLoadGroup(loadGroup); void *cacheId = NS_GetCurrentThread(); request->Init(aURI, aURI, loadGroup, newChannel, entry, cacheId, aCX, - aLoadingPrincipal); + aLoadingPrincipal, corsmode); // Pass the windowID of the loading document, if possible. nsCOMPtr doc = do_QueryInterface(aCX); if (doc) { request->SetWindowID(doc->OuterWindowID()); } // create the proxy listener - ProxyListener *pl = new ProxyListener(static_cast(request.get())); - if (!pl) { - request->CancelAndAbort(NS_ERROR_OUT_OF_MEMORY); - return NS_ERROR_OUT_OF_MEMORY; + nsCOMPtr pl = new ProxyListener(request.get()); + + // See if we need to insert a CORS proxy between the proxy listener and the + // request. + nsCOMPtr listener = pl; + if (corsmode != imgIRequest::CORS_NONE) { + PRBool withCredentials = corsmode == imgIRequest::CORS_USE_CREDENTIALS; + + nsCOMPtr corsproxy = + new nsCORSListenerProxy(pl, aLoadingPrincipal, newChannel, + withCredentials, &rv); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + listener = corsproxy; } - NS_ADDREF(pl); - PR_LOG(gImgLog, PR_LOG_DEBUG, ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this)); - nsresult openRes = newChannel->AsyncOpen(static_cast(pl), nsnull); - - NS_RELEASE(pl); + nsresult openRes = newChannel->AsyncOpen(listener, nsnull); if (NS_FAILED(openRes)) { PR_LOG(gImgLog, PR_LOG_DEBUG, ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n", this, openRes)); request->CancelAndAbort(openRes); return openRes; } @@ -1743,17 +1804,17 @@ NS_IMETHODIMP imgLoader::LoadImageWithCh // ValidateEntry to only do validation without creating a new proxy. If // it says that the entry isn't valid any more, we'll only use the entry // we're getting if the channel is loading from the cache anyways. // // XXX -- should this be changed? it's pretty much verbatim from the old // code, but seems nonsensical. if (ValidateEntry(entry, uri, nsnull, nsnull, nsnull, aObserver, aCX, requestFlags, PR_FALSE, nsnull, nsnull, nsnull, - nsnull)) { + nsnull, imgIRequest::CORS_NONE)) { request = getter_AddRefs(entry->GetRequest()); } else { nsCOMPtr cacheChan(do_QueryInterface(channel)); PRBool bUseCacheCopy; if (cacheChan) cacheChan->IsFromCache(&bUseCacheCopy); else @@ -1802,17 +1863,17 @@ NS_IMETHODIMP imgLoader::LoadImageWithCh return NS_ERROR_OUT_OF_MEMORY; // We use originalURI here to fulfil the imgIRequest contract on GetURI. nsCOMPtr originalURI; channel->GetOriginalURI(getter_AddRefs(originalURI)); // No principal specified here, because we're not passed one. request->Init(originalURI, uri, channel, channel, entry, - NS_GetCurrentThread(), aCX, nsnull); + NS_GetCurrentThread(), aCX, nsnull, imgIRequest::CORS_NONE); ProxyListener *pl = new ProxyListener(static_cast(request.get())); NS_ADDREF(pl); *listener = static_cast(pl); NS_ADDREF(*listener); NS_RELEASE(pl); @@ -2096,33 +2157,33 @@ NS_IMETHODIMP imgCacheValidator::OnStart mRequest->GetURI(getter_AddRefs(uri)); #if defined(PR_LOGGING) nsCAutoString spec; uri->GetSpec(spec); LOG_MSG_WITH_PARAM(gImgLog, "imgCacheValidator::OnStartRequest creating new request", "uri", spec.get()); #endif + PRInt32 corsmode = mRequest->GetCORSMode(); nsCOMPtr loadingPrincipal = mRequest->GetLoadingPrincipal(); // Doom the old request's cache entry mRequest->RemoveFromCache(); mRequest->mValidator = nsnull; mRequest = nsnull; // We use originalURI here to fulfil the imgIRequest contract on GetURI. nsCOMPtr originalURI; channel->GetOriginalURI(getter_AddRefs(originalURI)); mNewRequest->Init(originalURI, uri, channel, channel, mNewEntry, - NS_GetCurrentThread(), mContext, loadingPrincipal); + NS_GetCurrentThread(), mContext, loadingPrincipal, + corsmode); - ProxyListener *pl = new ProxyListener(static_cast(mNewRequest)); - - mDestListener = static_cast(pl); + mDestListener = new ProxyListener(mNewRequest); // Try to add the new request into the cache. Note that the entry must be in // the cache before the proxies' ownership changes, because adding a proxy // changes the caching behaviour for imgRequests. sImgLoader.PutIntoCache(originalURI, mNewEntry); PRUint32 count = mProxies.Count(); for (PRInt32 i = count-1; i>=0; i--) { diff --git a/modules/libpr0n/src/imgLoader.h b/modules/libpr0n/src/imgLoader.h --- a/modules/libpr0n/src/imgLoader.h +++ b/modules/libpr0n/src/imgLoader.h @@ -304,27 +304,29 @@ private: // methods PRBool ValidateEntry(imgCacheEntry *aEntry, nsIURI *aKey, nsIURI *aInitialDocumentURI, nsIURI *aReferrerURI, nsILoadGroup *aLoadGroup, imgIDecoderObserver *aObserver, nsISupports *aCX, nsLoadFlags aLoadFlags, PRBool aCanMakeNewChannel, imgIRequest *aExistingRequest, imgIRequest **aProxyRequest, nsIChannelPolicy *aPolicy, - nsIPrincipal* aLoadingPrincipal); + nsIPrincipal* aLoadingPrincipal, + PRInt32 aCORSMode); PRBool ValidateRequestWithNewChannel(imgRequest *request, nsIURI *aURI, nsIURI *aInitialDocumentURI, nsIURI *aReferrerURI, nsILoadGroup *aLoadGroup, imgIDecoderObserver *aObserver, nsISupports *aCX, nsLoadFlags aLoadFlags, imgIRequest *aExistingRequest, imgIRequest **aProxyRequest, nsIChannelPolicy *aPolicy, - nsIPrincipal* aLoadingPrincipal); + nsIPrincipal* aLoadingPrincipal, + PRInt32 aCORSMode); nsresult CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup, imgIDecoderObserver *aObserver, nsLoadFlags aLoadFlags, imgIRequest *aRequestProxy, imgIRequest **_retval); void ReadAcceptHeaderPref(); diff --git a/modules/libpr0n/src/imgRequest.cpp b/modules/libpr0n/src/imgRequest.cpp --- a/modules/libpr0n/src/imgRequest.cpp +++ b/modules/libpr0n/src/imgRequest.cpp @@ -180,18 +180,18 @@ NS_IMPL_ISUPPORTS8(imgRequest, nsIStreamListener, nsIRequestObserver, nsISupportsWeakReference, nsIChannelEventSink, nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback) imgRequest::imgRequest() : mCacheId(0), mValidator(nsnull), mImageSniffers("image-sniffing-services"), - mWindowId(0), mDecodeRequested(PR_FALSE), mIsMultiPartChannel(PR_FALSE), - mGotData(PR_FALSE), mIsInCache(PR_FALSE) + mWindowId(0), mCORSMode(imgIRequest::CORS_NONE), mDecodeRequested(PR_FALSE), + mIsMultiPartChannel(PR_FALSE), mGotData(PR_FALSE), mIsInCache(PR_FALSE) {} imgRequest::~imgRequest() { if (mURI) { nsCAutoString spec; mURI->GetSpec(spec); LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::~imgRequest()", "keyuri", spec.get()); @@ -201,17 +201,18 @@ imgRequest::~imgRequest() nsresult imgRequest::Init(nsIURI *aURI, nsIURI *aCurrentURI, nsIRequest *aRequest, nsIChannel *aChannel, imgCacheEntry *aCacheEntry, void *aCacheId, void *aLoadId, - nsIPrincipal* aLoadingPrincipal) + nsIPrincipal* aLoadingPrincipal, + PRInt32 aCORSMode) { LOG_FUNC(gImgLog, "imgRequest::Init"); NS_ABORT_IF_FALSE(!mImage, "Multiple calls to init"); NS_ABORT_IF_FALSE(aURI, "No uri"); NS_ABORT_IF_FALSE(aCurrentURI, "No current uri"); NS_ABORT_IF_FALSE(aRequest, "No request"); NS_ABORT_IF_FALSE(aChannel, "No channel"); @@ -222,16 +223,17 @@ nsresult imgRequest::Init(nsIURI *aURI, mURI = aURI; mCurrentURI = aCurrentURI; mRequest = aRequest; mChannel = aChannel; mTimedChannel = do_QueryInterface(mChannel); mLoadingPrincipal = aLoadingPrincipal; + mCORSMode = aCORSMode; mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink)); NS_ASSERTION(mPrevChannelSink != this, "Initializing with a channel that already calls back to us!"); mChannel->SetNotificationCallbacks(this); diff --git a/modules/libpr0n/src/imgRequest.h b/modules/libpr0n/src/imgRequest.h --- a/modules/libpr0n/src/imgRequest.h +++ b/modules/libpr0n/src/imgRequest.h @@ -91,17 +91,18 @@ public: nsresult Init(nsIURI *aURI, nsIURI *aCurrentURI, nsIRequest *aRequest, nsIChannel *aChannel, imgCacheEntry *aCacheEntry, void *aCacheId, void *aLoadId, - nsIPrincipal* aLoadingPrincipal); + nsIPrincipal* aLoadingPrincipal, + PRInt32 aCORSMode); // Callers must call imgRequestProxy::Notify later. nsresult AddProxy(imgRequestProxy *proxy); // aNotify==PR_FALSE still sends OnStopRequest. nsresult RemoveProxy(imgRequestProxy *proxy, nsresult aStatus, PRBool aNotify); void SniffMimeType(const char *buf, PRUint32 len); @@ -131,16 +132,19 @@ public: } // Set the cache validation information (expiry time, whether we must // validate, etc) on the cache entry based on the request information. // If this function is called multiple times, the information set earliest // wins. static void SetCacheValidation(imgCacheEntry* aEntry, nsIRequest* aRequest); + // The CORS mode for which we loaded this image. + PRInt32 GetCORSMode() const { return mCORSMode; } + // The principal for the document that loaded this image. Used when trying to // validate a CORS image load. already_AddRefed GetLoadingPrincipal() const { nsCOMPtr principal = mLoadingPrincipal; return principal.forget(); } @@ -253,16 +257,20 @@ private: imgCacheValidator *mValidator; nsCategoryCache mImageSniffers; nsCOMPtr mRedirectCallback; nsCOMPtr mNewRedirectChannel; // Originating outer window ID. Used for error reporting. PRUint64 mWindowId; + // The CORS mode (defined in imgIRequest) this image was loaded with. By + // default, imgIRequest::CORS_NONE. + PRInt32 mCORSMode; + // Sometimes consumers want to do things before the image is ready. Let them, // and apply the action when the image becomes available. PRPackedBool mDecodeRequested : 1; PRPackedBool mIsMultiPartChannel : 1; PRPackedBool mGotData : 1; PRPackedBool mIsInCache : 1; }; diff --git a/modules/libpr0n/src/imgRequestProxy.cpp b/modules/libpr0n/src/imgRequestProxy.cpp --- a/modules/libpr0n/src/imgRequestProxy.cpp +++ b/modules/libpr0n/src/imgRequestProxy.cpp @@ -537,16 +537,27 @@ NS_IMETHODIMP imgRequestProxy::GetImageP if (!mPrincipal) return NS_ERROR_FAILURE; NS_ADDREF(*aPrincipal = mPrincipal); return NS_OK; } +/* readonly attribute PRInt32 CORSMode; */ +NS_IMETHODIMP imgRequestProxy::GetCORSMode(PRInt32* aCorsMode) +{ + if (!mOwner) + return NS_ERROR_FAILURE; + + *aCorsMode = mOwner->GetCORSMode(); + + return NS_OK; +} + /** nsISupportsPriority methods **/ NS_IMETHODIMP imgRequestProxy::GetPriority(PRInt32 *priority) { NS_ENSURE_STATE(mOwner); *priority = mOwner->Priority(); return NS_OK; }