mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Bug 1036275
- Add Packaged App Service r=honzab
This commit is contained in:
parent
332b9471a3
commit
b2e6518351
@ -67,6 +67,7 @@ XPIDL_SOURCES += [
|
||||
'nsINSSErrorsService.idl',
|
||||
'nsINullChannel.idl',
|
||||
'nsIPACGenerator.idl',
|
||||
'nsIPackagedAppService.idl',
|
||||
'nsIParentChannel.idl',
|
||||
'nsIParentRedirectingChannel.idl',
|
||||
'nsIPermission.idl',
|
||||
|
42
netwerk/base/nsIPackagedAppService.idl
Normal file
42
netwerk/base/nsIPackagedAppService.idl
Normal file
@ -0,0 +1,42 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIURI;
|
||||
interface nsILoadContextInfo;
|
||||
interface nsICacheEntryOpenCallback;
|
||||
|
||||
%{C++
|
||||
#define PACKAGED_APP_TOKEN "!//"
|
||||
%}
|
||||
|
||||
/**
|
||||
* nsIPackagedAppService
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(77f9a34d-d082-43f1-9f83-e852d0173cd5)]
|
||||
interface nsIPackagedAppService : nsISupports
|
||||
{
|
||||
/**
|
||||
* @aURI is a URL to a packaged resource
|
||||
* - format: package_url + PACKAGED_APP_TOKEN + resource_path
|
||||
* - example: http://test.com/path/to/package!//resource.html
|
||||
* @aCallback is an object implementing nsICacheEntryOpenCallback
|
||||
* - this is the target of the async result of the operation
|
||||
* - aCallback->OnCacheEntryCheck() is called to verify the entry is valid
|
||||
* - aCallback->OnCacheEntryAvailable() is called with a pointer to the
|
||||
* the cached entry, if one exists, or an error code otherwise
|
||||
* - aCallback is kept alive using an nsCOMPtr until OnCacheEntryAvailable
|
||||
* is called
|
||||
* @aInfo is an object used to determine the cache jar this resource goes in.
|
||||
* - usually created by calling GetLoadContextInfo(requestingChannel)
|
||||
*
|
||||
* Calling this method will either download the package containing the given
|
||||
* resource URI, store it in the cache and pass the cache entry to aCallback,
|
||||
* or if that resource has already been downloaded it will be served from
|
||||
* the cache.
|
||||
*/
|
||||
void requestURI(in nsIURI aURI, in nsILoadContextInfo aInfo, in nsICacheEntryOpenCallback aCallback);
|
||||
};
|
@ -868,6 +868,16 @@
|
||||
{ 0x85, 0x44, 0x5a, 0x8d, 0x1a, 0xb7, 0x95, 0x37 } \
|
||||
}
|
||||
|
||||
#define NS_PACKAGEDAPPSERVICE_CONTRACTID \
|
||||
"@mozilla.org/network/packaged-app-service;1"
|
||||
#define NS_PACKAGEDAPPSERVICE_CID \
|
||||
{ /* adef6762-41b9-4470-a06a-dc29cf8de381 */ \
|
||||
0xadef6762, \
|
||||
0x41b9, \
|
||||
0x4470, \
|
||||
{ 0xa0, 0x6a, 0xdc, 0x29, 0xcf, 0x8d, 0xe3, 0x81 } \
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* netwerk/cookie classes
|
||||
|
@ -251,9 +251,11 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDigestAuth)
|
||||
#endif // !NECKO_PROTOCOL_http
|
||||
|
||||
#include "mozilla/net/Dashboard.h"
|
||||
#include "mozilla/net/PackagedAppService.h"
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(Dashboard)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(PackagedAppService)
|
||||
}
|
||||
}
|
||||
#include "AppProtocolHandler.h"
|
||||
@ -709,6 +711,7 @@ NS_DEFINE_NAMED_CID(NS_BUFFEREDOUTPUTSTREAM_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_MIMEINPUTSTREAM_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_PROTOCOLPROXYSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_STREAMCONVERTERSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_PACKAGEDAPPSERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_DASHBOARD_CID);
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
NS_DEFINE_NAMED_CID(NS_FTPDIRLISTINGCONVERTER_CID);
|
||||
@ -853,6 +856,7 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
|
||||
{ &kNS_MIMEINPUTSTREAM_CID, false, nullptr, nsMIMEInputStreamConstructor },
|
||||
{ &kNS_PROTOCOLPROXYSERVICE_CID, true, nullptr, nsProtocolProxyServiceConstructor },
|
||||
{ &kNS_STREAMCONVERTERSERVICE_CID, false, nullptr, CreateNewStreamConvServiceFactory },
|
||||
{ &kNS_PACKAGEDAPPSERVICE_CID, false, NULL, mozilla::net::PackagedAppServiceConstructor },
|
||||
{ &kNS_DASHBOARD_CID, false, nullptr, mozilla::net::DashboardConstructor },
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
{ &kNS_FTPDIRLISTINGCONVERTER_CID, false, nullptr, CreateNewFTPDirListingConv },
|
||||
@ -999,6 +1003,7 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
|
||||
{ NS_MIMEINPUTSTREAM_CONTRACTID, &kNS_MIMEINPUTSTREAM_CID },
|
||||
{ NS_PROTOCOLPROXYSERVICE_CONTRACTID, &kNS_PROTOCOLPROXYSERVICE_CID },
|
||||
{ NS_STREAMCONVERTERSERVICE_CONTRACTID, &kNS_STREAMCONVERTERSERVICE_CID },
|
||||
{ NS_PACKAGEDAPPSERVICE_CONTRACTID, &kNS_PACKAGEDAPPSERVICE_CID },
|
||||
{ NS_DASHBOARD_CONTRACTID, &kNS_DASHBOARD_CID },
|
||||
#ifdef NECKO_PROTOCOL_ftp
|
||||
{ NS_ISTREAMCONVERTER_KEY FTP_TO_INDEX, &kNS_FTPDIRLISTINGCONVERTER_CID },
|
||||
|
554
netwerk/protocol/http/PackagedAppService.cpp
Normal file
554
netwerk/protocol/http/PackagedAppService.cpp
Normal file
@ -0,0 +1,554 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
#include "PackagedAppService.h"
|
||||
#include "nsICacheStorage.h"
|
||||
#include "LoadContextInfo.h"
|
||||
#include "nsICacheStorageService.h"
|
||||
#include "nsIResponseHeadProvider.h"
|
||||
#include "nsIMultiPartChannel.h"
|
||||
#include "../../cache2/CacheFileUtils.h"
|
||||
#include "nsStreamUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
static PackagedAppService *gPackagedAppService = nullptr;
|
||||
|
||||
NS_IMPL_ISUPPORTS(PackagedAppService, nsIPackagedAppService)
|
||||
|
||||
NS_IMPL_ISUPPORTS(PackagedAppService::CacheEntryWriter, nsIStreamListener)
|
||||
|
||||
/* static */ nsresult
|
||||
PackagedAppService::CacheEntryWriter::Create(nsIURI *aURI,
|
||||
nsICacheStorage *aStorage,
|
||||
CacheEntryWriter **aResult)
|
||||
{
|
||||
nsRefPtr<CacheEntryWriter> writer = new CacheEntryWriter();
|
||||
nsresult rv = aStorage->OpenTruncate(aURI, EmptyCString(),
|
||||
getter_AddRefs(writer->mEntry));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = writer->mEntry->ForceValidFor(PR_UINT32_MAX);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
writer.forget(aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_METHOD
|
||||
PackagedAppService::CacheEntryWriter::ConsumeData(nsIInputStream *aStream,
|
||||
void *aClosure,
|
||||
const char *aFromRawSegment,
|
||||
uint32_t aToOffset,
|
||||
uint32_t aCount,
|
||||
uint32_t *aWriteCount)
|
||||
{
|
||||
MOZ_ASSERT(aClosure, "The closure must not be null");
|
||||
CacheEntryWriter *self = static_cast<CacheEntryWriter*>(aClosure);
|
||||
MOZ_ASSERT(self->mOutputStream, "The stream should not be null");
|
||||
return self->mOutputStream->Write(aFromRawSegment, aCount, aWriteCount);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::CacheEntryWriter::OnStartRequest(nsIRequest *aRequest,
|
||||
nsISupports *aContext)
|
||||
{
|
||||
nsCOMPtr<nsIResponseHeadProvider> provider(do_QueryInterface(aRequest));
|
||||
if (!provider) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
nsHttpResponseHead *responseHead = provider->GetResponseHead();
|
||||
if (!responseHead) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mEntry->SetPredictedDataSize(responseHead->TotalEntitySize());
|
||||
|
||||
nsAutoCString head;
|
||||
responseHead->Flatten(head, true);
|
||||
nsresult rv = mEntry->SetMetaDataElement("response-head", head.get());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mEntry->SetMetaDataElement("request-method", "GET");
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mEntry->OpenOutputStream(0, getter_AddRefs(mOutputStream));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::CacheEntryWriter::OnStopRequest(nsIRequest *aRequest,
|
||||
nsISupports *aContext,
|
||||
nsresult aStatusCode)
|
||||
{
|
||||
if (mOutputStream) {
|
||||
mOutputStream->Close();
|
||||
mOutputStream = nullptr;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::CacheEntryWriter::OnDataAvailable(nsIRequest *aRequest,
|
||||
nsISupports *aContext,
|
||||
nsIInputStream *aInputStream,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
if (!aInputStream) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
// Calls ConsumeData to read the data into the cache entry
|
||||
uint32_t n;
|
||||
return aInputStream->ReadSegments(ConsumeData, this, aCount, &n);
|
||||
}
|
||||
|
||||
|
||||
NS_IMPL_ISUPPORTS(PackagedAppService::PackagedAppDownloader, nsIStreamListener)
|
||||
|
||||
nsresult
|
||||
PackagedAppService::PackagedAppDownloader::Init(nsILoadContextInfo* aInfo,
|
||||
const nsCString& aKey)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsICacheStorageService> cacheStorageService =
|
||||
do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = cacheStorageService->DiskCacheStorage(aInfo, false,
|
||||
getter_AddRefs(mCacheStorage));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mPackageKey = aKey;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::PackagedAppDownloader::OnStartRequest(nsIRequest *aRequest,
|
||||
nsISupports *aContext)
|
||||
{
|
||||
// In case an error occurs in this method mWriter should be null
|
||||
// so we don't accidentally write to the previous resource's cache entry.
|
||||
mWriter = nullptr;
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = GetSubresourceURI(aRequest, getter_AddRefs(uri));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
rv = CacheEntryWriter::Create(uri, mCacheStorage, getter_AddRefs(mWriter));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mWriter);
|
||||
rv = mWriter->OnStartRequest(aRequest, aContext);
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
PackagedAppService::PackagedAppDownloader::GetSubresourceURI(nsIRequest * aRequest,
|
||||
nsIURI ** aResult)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIResponseHeadProvider> provider(do_QueryInterface(aRequest));
|
||||
nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
|
||||
|
||||
if (NS_WARN_IF(!provider || !chan)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nsHttpResponseHead *responseHead = provider->GetResponseHead();
|
||||
if (NS_WARN_IF(!responseHead)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsAutoCString contentLocation;
|
||||
rv = responseHead->GetHeader(nsHttp::ResolveAtom("Content-Location"), contentLocation);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = chan->GetURI(getter_AddRefs(uri));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoCString path;
|
||||
rv = uri->GetPath(path);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
path += PACKAGED_APP_TOKEN;
|
||||
|
||||
// TODO: make sure the path is normalized
|
||||
if (StringBeginsWith(contentLocation, NS_LITERAL_CSTRING("/"))) {
|
||||
contentLocation = Substring(contentLocation, 1);
|
||||
}
|
||||
|
||||
path += contentLocation;
|
||||
|
||||
nsCOMPtr<nsIURI> partURI;
|
||||
rv = uri->CloneIgnoringRef(getter_AddRefs(partURI));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = partURI->SetPath(path);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
partURI.forget(aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::PackagedAppDownloader::OnStopRequest(nsIRequest *aRequest,
|
||||
nsISupports *aContext,
|
||||
nsresult aStatusCode)
|
||||
{
|
||||
nsCOMPtr<nsIMultiPartChannel> multiChannel(do_QueryInterface(aRequest));
|
||||
nsresult rv;
|
||||
|
||||
|
||||
// The request is normally a multiPartChannel. If it isn't, it generally means
|
||||
// an error has occurred in nsMultiMixedConv.
|
||||
// If an error occurred in OnStartRequest, mWriter could be null.
|
||||
if (multiChannel && mWriter) {
|
||||
mWriter->OnStopRequest(aRequest, aContext, aStatusCode);
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = GetSubresourceURI(aRequest, getter_AddRefs(uri));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICacheEntry> entry;
|
||||
mWriter->mEntry.swap(entry);
|
||||
|
||||
// We don't need the writer anymore - this will close its stream
|
||||
mWriter = nullptr;
|
||||
CallCallbacks(uri, entry, aStatusCode);
|
||||
}
|
||||
|
||||
bool lastPart = false;
|
||||
if (multiChannel) {
|
||||
rv = multiChannel->GetIsLastPart(&lastPart);
|
||||
if (NS_SUCCEEDED(rv) && !lastPart) {
|
||||
// If this isn't the last part, we don't do the cleanup yet
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the last part of the package, it means the requested resources
|
||||
// have not been found in the package so we return an appropriate error.
|
||||
if (NS_SUCCEEDED(aStatusCode) && lastPart) {
|
||||
aStatusCode = NS_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
nsRefPtr<PackagedAppDownloader> kungFuDeathGrip(this);
|
||||
// NotifyPackageDownloaded removes the ref from the array. Keep a temp ref
|
||||
if (gPackagedAppService) {
|
||||
gPackagedAppService->NotifyPackageDownloaded(mPackageKey);
|
||||
}
|
||||
ClearCallbacks(aStatusCode);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::PackagedAppDownloader::OnDataAvailable(nsIRequest *aRequest,
|
||||
nsISupports *aContext,
|
||||
nsIInputStream *aInputStream,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
if (!mWriter) {
|
||||
uint32_t n;
|
||||
return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &n);
|
||||
}
|
||||
return mWriter->OnDataAvailable(aRequest, aContext, aInputStream, aOffset,
|
||||
aCount);
|
||||
}
|
||||
|
||||
nsresult
|
||||
PackagedAppService::PackagedAppDownloader::AddCallback(nsIURI *aURI,
|
||||
nsICacheEntryOpenCallback *aCallback)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mCallbacks hashtable is not thread safe");
|
||||
nsAutoCString spec;
|
||||
aURI->GetAsciiSpec(spec);
|
||||
|
||||
// Check if we already have a resource waiting for this resource
|
||||
nsCOMArray<nsICacheEntryOpenCallback>* array = mCallbacks.Get(spec);
|
||||
if (array) {
|
||||
// Add this resource to the callback array
|
||||
array->AppendObject(aCallback);
|
||||
} else {
|
||||
// This is the first callback for this URI.
|
||||
// Create a new array and add the callback
|
||||
nsCOMArray<nsICacheEntryOpenCallback>* newArray =
|
||||
new nsCOMArray<nsICacheEntryOpenCallback>();
|
||||
newArray->AppendObject(aCallback);
|
||||
mCallbacks.Put(spec, newArray);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
PackagedAppService::PackagedAppDownloader::CallCallbacks(nsIURI *aURI,
|
||||
nsICacheEntry *aEntry,
|
||||
nsresult aResult)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mCallbacks hashtable is not thread safe");
|
||||
// Hold on to this entry while calling the callbacks
|
||||
nsCOMPtr<nsICacheEntry> handle(aEntry);
|
||||
|
||||
nsAutoCString spec;
|
||||
aURI->GetSpec(spec);
|
||||
|
||||
nsCOMArray<nsICacheEntryOpenCallback>* array = mCallbacks.Get(spec);
|
||||
if (array) {
|
||||
// Call all the callbacks for this URI
|
||||
for (uint32_t i = 0; i < array->Length(); ++i) {
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> callback(array->ObjectAt(i));
|
||||
// We call to AsyncOpenURI which automatically calls the callback.
|
||||
mCacheStorage->AsyncOpenURI(aURI, EmptyCString(),
|
||||
nsICacheStorage::OPEN_READONLY, callback);
|
||||
}
|
||||
// Clear the array and remove it from the hashtable
|
||||
array->Clear();
|
||||
mCallbacks.Remove(spec);
|
||||
aEntry->ForceValidFor(0);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PLDHashOperator
|
||||
PackagedAppService::PackagedAppDownloader::ClearCallbacksEnumerator(const nsACString& key,
|
||||
nsAutoPtr<nsCOMArray<nsICacheEntryOpenCallback> >& callbackArray,
|
||||
void* arg)
|
||||
{
|
||||
MOZ_ASSERT(arg, "The void* parameter should be a pointer to nsresult");
|
||||
nsresult *result = static_cast<nsresult*>(arg);
|
||||
for (uint32_t i = 0; i < callbackArray->Length(); ++i) {
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> callback = callbackArray->ObjectAt(i);
|
||||
callback->OnCacheEntryAvailable(nullptr, false, nullptr, *result);
|
||||
}
|
||||
// Remove entry from hashtable
|
||||
return PL_DHASH_REMOVE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
PackagedAppService::PackagedAppDownloader::ClearCallbacks(nsresult aResult)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mCallbacks hashtable is not thread safe");
|
||||
mCallbacks.Enumerate(ClearCallbacksEnumerator, &aResult);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(PackagedAppService::CacheEntryChecker, nsICacheEntryOpenCallback)
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::CacheEntryChecker::OnCacheEntryCheck(nsICacheEntry *aEntry,
|
||||
nsIApplicationCache *aApplicationCache,
|
||||
uint32_t *_retval)
|
||||
{
|
||||
return mCallback->OnCacheEntryCheck(aEntry, aApplicationCache, _retval);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::CacheEntryChecker::OnCacheEntryAvailable(nsICacheEntry *aEntry,
|
||||
bool aNew,
|
||||
nsIApplicationCache *aApplicationCache,
|
||||
nsresult aResult)
|
||||
{
|
||||
if (aResult == NS_ERROR_CACHE_KEY_NOT_FOUND) {
|
||||
MOZ_ASSERT(!aEntry, "No entry");
|
||||
// trigger download
|
||||
// download checks if package download is already in progress
|
||||
gPackagedAppService->OpenNewPackageInternal(mURI, mCallback,
|
||||
mLoadContextInfo);
|
||||
} else {
|
||||
// TODO: if aResult is another error code, should we pass it off to the
|
||||
// consumer, or should we try to download the package again?
|
||||
mCallback->OnCacheEntryAvailable(aEntry, aNew, aApplicationCache, aResult);
|
||||
// TODO: update last access entry for the entire package
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PackagedAppService::PackagedAppService()
|
||||
{
|
||||
gPackagedAppService = this;
|
||||
}
|
||||
|
||||
PackagedAppService::~PackagedAppService()
|
||||
{
|
||||
gPackagedAppService = nullptr;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PackagedAppService::RequestURI(nsIURI *aURI,
|
||||
nsILoadContextInfo *aInfo,
|
||||
nsICacheEntryOpenCallback *aCallback)
|
||||
{
|
||||
// Check arguments are not null
|
||||
if (!aURI || !aCallback || !aInfo) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nsAutoCString path;
|
||||
aURI->GetPath(path);
|
||||
int32_t pos = path.Find(PACKAGED_APP_TOKEN);
|
||||
if (pos == kNotFound) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsICacheStorageService> cacheStorageService =
|
||||
do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICacheStorage> cacheStorage;
|
||||
|
||||
rv = cacheStorageService->DiskCacheStorage(aInfo, false,
|
||||
getter_AddRefs(cacheStorage));
|
||||
|
||||
nsRefPtr<CacheEntryChecker> checker = new CacheEntryChecker(aURI, aCallback, aInfo);
|
||||
return cacheStorage->AsyncOpenURI(aURI, EmptyCString(),
|
||||
nsICacheStorage::OPEN_READONLY, checker);
|
||||
}
|
||||
|
||||
nsresult
|
||||
PackagedAppService::NotifyPackageDownloaded(nsCString aKey)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mDownloadingPackages hashtable is not thread safe");
|
||||
mDownloadingPackages.Remove(aKey);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
PackagedAppService::OpenNewPackageInternal(nsIURI *aURI,
|
||||
nsICacheEntryOpenCallback *aCallback,
|
||||
nsILoadContextInfo *aInfo)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mDownloadingPackages hashtable is not thread safe");
|
||||
|
||||
nsAutoCString path;
|
||||
nsresult rv = aURI->GetPath(path);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
int32_t pos = path.Find(PACKAGED_APP_TOKEN);
|
||||
MOZ_ASSERT(pos != kNotFound,
|
||||
"This should never be called if the token is missing");
|
||||
|
||||
nsCOMPtr<nsIURI> packageURI;
|
||||
rv = aURI->CloneIgnoringRef(getter_AddRefs(packageURI));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = packageURI->SetPath(Substring(path, 0, pos));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoCString key;
|
||||
CacheFileUtils::AppendKeyPrefix(aInfo, key);
|
||||
|
||||
{
|
||||
nsAutoCString spec;
|
||||
packageURI->GetAsciiSpec(spec);
|
||||
key += ":";
|
||||
key += spec;
|
||||
}
|
||||
|
||||
nsRefPtr<PackagedAppDownloader> downloader;
|
||||
if (mDownloadingPackages.Get(key, getter_AddRefs(downloader))) {
|
||||
// We have determined that the file is not in the cache.
|
||||
// If we find that the package that the file belongs to is currently being
|
||||
// downloaded, we will add the callback to the package's queue, and it will
|
||||
// be called once the file is processed and saved in the cache.
|
||||
|
||||
downloader->AddCallback(aURI, aCallback);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
rv = NS_NewChannel(
|
||||
getter_AddRefs(channel), packageURI, nsContentUtils::GetSystemPrincipal(),
|
||||
nsILoadInfo::SEC_NORMAL, nsIContentPolicy::TYPE_OTHER, nullptr, nullptr,
|
||||
nsIRequest::LOAD_NORMAL);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(channel));
|
||||
if (cacheChan) {
|
||||
// Each resource in the package will be put in its own cache entry
|
||||
// during the first load of the package, so we only want the channel to
|
||||
// cache the response head, not the entire content of the package.
|
||||
cacheChan->SetCacheOnlyMetadata(true);
|
||||
}
|
||||
|
||||
downloader = new PackagedAppDownloader();
|
||||
rv = downloader->Init(aInfo, key);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
downloader->AddCallback(aURI, aCallback);
|
||||
|
||||
nsCOMPtr<nsIStreamConverterService> streamconv =
|
||||
do_GetService("@mozilla.org/streamConverters;1", &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIStreamListener> mimeConverter;
|
||||
rv = streamconv->AsyncConvertData("multipart/mixed", "*/*", downloader, nullptr,
|
||||
getter_AddRefs(mimeConverter));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Add the package to the hashtable.
|
||||
mDownloadingPackages.Put(key, downloader);
|
||||
|
||||
return channel->AsyncOpen(mimeConverter, nullptr);
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
191
netwerk/protocol/http/PackagedAppService.h
Normal file
191
netwerk/protocol/http/PackagedAppService.h
Normal file
@ -0,0 +1,191 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set sw=2 ts=8 et tw=80 : */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_net_PackagedAppService_h
|
||||
#define mozilla_net_PackagedAppService_h
|
||||
|
||||
#include "nsIPackagedAppService.h"
|
||||
#include "nsILoadContextInfo.h"
|
||||
#include "nsICacheStorage.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
// This service is used to download packages from the web.
|
||||
// Individual resources in the package are saved in the browser cache. It also
|
||||
// provides an interface to asynchronously request resources from packages,
|
||||
// which are either returned from the cache if they exist and are valid,
|
||||
// or downloads the package.
|
||||
// The package format is defined at:
|
||||
// https://w3ctag.github.io/packaging-on-the-web/#streamable-package-format
|
||||
// Downloading the package is triggered by calling requestURI(aURI, aInfo, aCallback)
|
||||
// aURI is the subresource uri - http://domain.com/path/package!//resource.html
|
||||
// aInfo is a nsILoadContextInfo used to pick the cache jar the resource goes into
|
||||
// aCallback is the target of the async call to requestURI
|
||||
// When requestURI is called, a CacheEntryChecker is created to verify if the
|
||||
// resource is already in the cache. If it is, it passes it to the callback.
|
||||
// Otherwise, it starts downloading the package. When the packaged resource has
|
||||
// been downloaded, its cache entry gets passed to the callback.
|
||||
class PackagedAppService final
|
||||
: public nsIPackagedAppService
|
||||
{
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIPACKAGEDAPPSERVICE
|
||||
|
||||
PackagedAppService();
|
||||
|
||||
private:
|
||||
~PackagedAppService();
|
||||
|
||||
// This method is called if an entry wasn't found in the cache.
|
||||
// It checks to see if the package is currently being downloaded.
|
||||
// If so, then it simply adds the callback to that PackageAppDownloader
|
||||
// Else it begins downloading the new package and adds it to mDownloadingPackages
|
||||
// - aURI is the packaged resource's URL
|
||||
// - aCallback is the listener which gets called when the requested
|
||||
// resource is available.
|
||||
// - aInfo is needed because cache entries are located in separate cache jars
|
||||
// If a resource isn't found in the package, aCallback->OnCacheEntryAvailable
|
||||
// will be called with a null entry and an error result as a status.
|
||||
nsresult OpenNewPackageInternal(nsIURI *aURI,
|
||||
nsICacheEntryOpenCallback *aCallback,
|
||||
nsILoadContextInfo *aInfo);
|
||||
|
||||
// Called by PackageAppDownloader once the download has finished
|
||||
// (or encountered an error) to remove the package from mDownloadingPackages
|
||||
// Should be called on the main thread.
|
||||
nsresult NotifyPackageDownloaded(nsCString aKey);
|
||||
|
||||
// This class is used to write data into the cache entry corresponding to the
|
||||
// packaged resource being downloaded.
|
||||
// The PackagedAppDownloader will hold a ref to a CacheEntryWriter that
|
||||
// corresponds to the entry that is currently being downloaded.
|
||||
class CacheEntryWriter final
|
||||
: public nsIStreamListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
|
||||
// If successful, calling this static method will create a new
|
||||
// CacheEntryWriter and will create the cache entry associated to the
|
||||
// resource and open an output stream which we use for writing the resource's
|
||||
// content into the cache entry.
|
||||
static nsresult Create(nsIURI*, nsICacheStorage*, CacheEntryWriter**);
|
||||
|
||||
nsCOMPtr<nsICacheEntry> mEntry;
|
||||
private:
|
||||
CacheEntryWriter() { }
|
||||
~CacheEntryWriter() { }
|
||||
|
||||
// Static method used to write data into the cache entry
|
||||
// Called from OnDataAvailable
|
||||
static NS_METHOD ConsumeData(nsIInputStream *in, void *closure,
|
||||
const char *fromRawSegment, uint32_t toOffset,
|
||||
uint32_t count, uint32_t *writeCount);
|
||||
// We write the data we read from the network into this stream which goes
|
||||
// to the cache entry.
|
||||
nsCOMPtr<nsIOutputStream> mOutputStream;
|
||||
};
|
||||
|
||||
// This class is used to download a packaged app. It acts as a listener
|
||||
// for the nsMultiMixedConv object that parses the package.
|
||||
// There is an OnStartRequest, OnDataAvailable*, OnStopRequest sequence called
|
||||
// for each resource
|
||||
// The PackagedAppService holds a hash-table of the PackagedAppDownloaders
|
||||
// that are in progress to coalesce same loads.
|
||||
// Once the downloading is completed, it should call
|
||||
// NotifyPackageDownloaded(packageURI), so the service releases the ref.
|
||||
class PackagedAppDownloader final
|
||||
: public nsIStreamListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
|
||||
// Initializes mCacheStorage and saves aKey as mPackageKey which is later
|
||||
// used to remove this object from PackagedAppService::mDownloadingPackages
|
||||
// - aKey is a string which uniquely identifies this package within the
|
||||
// packagedAppService
|
||||
nsresult Init(nsILoadContextInfo* aInfo, const nsCString &aKey);
|
||||
// Registers a callback which gets called when the given nsIURI is downloaded
|
||||
// aURI is the full URI of a subresource, composed of packageURI + !// + subresourcePath
|
||||
nsresult AddCallback(nsIURI *aURI, nsICacheEntryOpenCallback *aCallback);
|
||||
|
||||
private:
|
||||
~PackagedAppDownloader() { }
|
||||
|
||||
// Calls all the callbacks registered for the given URI.
|
||||
// aURI is the full URI of a subresource, composed of packageURI + !// + subresourcePath
|
||||
// It passes the cache entry and the result when calling OnCacheEntryAvailable
|
||||
nsresult CallCallbacks(nsIURI *aURI, nsICacheEntry *aEntry, nsresult aResult);
|
||||
// Clears all the callbacks for this package
|
||||
// This would get called at the end of downloading the package and would
|
||||
// cause us to call OnCacheEntryAvailable with a null entry. This would be
|
||||
// equivalent to a 404 when loading from the net.
|
||||
nsresult ClearCallbacks(nsresult aResult);
|
||||
static PLDHashOperator ClearCallbacksEnumerator(const nsACString& key,
|
||||
nsAutoPtr<nsCOMArray<nsICacheEntryOpenCallback>>& callbackArray,
|
||||
void* arg);
|
||||
// Returns a URI with the subresource's full URI
|
||||
// The request must be QIable to nsIResponseHeadProvider since it looks
|
||||
// at the Content-Location header to compute the full path.
|
||||
static nsresult GetSubresourceURI(nsIRequest * aRequest, nsIURI **aResult);
|
||||
// Used to write data into the cache entry of the resource currently being
|
||||
// downloaded. It is kept alive until the downloader receives OnStopRequest
|
||||
nsRefPtr<CacheEntryWriter> mWriter;
|
||||
// Cached value of nsICacheStorage
|
||||
nsCOMPtr<nsICacheStorage> mCacheStorage;
|
||||
// A hastable containing all the consumers which requested a resource and need
|
||||
// to be notified once it is inserted into the cache.
|
||||
// The key is a subresource URI - http://example.com/package.pak!//res.html
|
||||
// Should only be used on the main thread.
|
||||
nsClassHashtable<nsCStringHashKey, nsCOMArray<nsICacheEntryOpenCallback>> mCallbacks;
|
||||
// The key with which this package is inserted in
|
||||
// PackagedAppService::mDownloadingPackages
|
||||
nsCString mPackageKey;
|
||||
};
|
||||
|
||||
// This class is used to check if a packaged resource has already been
|
||||
// downloaded and saved into the cache.
|
||||
// It calls aCallback->OnCacheEntryAvailable if the resource exists in the
|
||||
// cache or PackagedAppService::OpenNewPackageInternal if it needs
|
||||
// to be downloaded
|
||||
class CacheEntryChecker final
|
||||
: public nsICacheEntryOpenCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICACHEENTRYOPENCALLBACK
|
||||
|
||||
CacheEntryChecker(nsIURI *aURI, nsICacheEntryOpenCallback * aCallback,
|
||||
nsILoadContextInfo *aInfo)
|
||||
: mURI(aURI)
|
||||
, mCallback(aCallback)
|
||||
, mLoadContextInfo(aInfo)
|
||||
{
|
||||
}
|
||||
private:
|
||||
~CacheEntryChecker() { }
|
||||
|
||||
nsCOMPtr<nsIURI> mURI;
|
||||
nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
|
||||
nsCOMPtr<nsILoadContextInfo> mLoadContextInfo;
|
||||
};
|
||||
|
||||
// A hashtable of packages that are currently being downloaded.
|
||||
// The key is a string formed by concatenating LoadContextInfo and package URI
|
||||
// Should only be used on the main thread.
|
||||
nsRefPtrHashtable<nsCStringHashKey, PackagedAppDownloader> mDownloadingPackages;
|
||||
};
|
||||
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_net_PackagedAppService_h
|
@ -34,6 +34,7 @@ EXPORTS.mozilla.net += [
|
||||
'HttpChannelParent.h',
|
||||
'HttpInfo.h',
|
||||
'NullHttpChannel.h',
|
||||
'PackagedAppService.h',
|
||||
'PHttpChannelParams.h',
|
||||
'PSpdyPush.h',
|
||||
'TimingStruct.h',
|
||||
@ -78,6 +79,7 @@ UNIFIED_SOURCES += [
|
||||
'nsHttpTransaction.cpp',
|
||||
'NullHttpChannel.cpp',
|
||||
'NullHttpTransaction.cpp',
|
||||
'PackagedAppService.cpp',
|
||||
'SpdyPush31.cpp',
|
||||
'SpdySession31.cpp',
|
||||
'SpdyStream31.cpp',
|
||||
|
255
netwerk/test/unit/test_packaged_app_service.js
Normal file
255
netwerk/test/unit/test_packaged_app_service.js
Normal file
@ -0,0 +1,255 @@
|
||||
//
|
||||
// This file tests the packaged app service - nsIPackagedAppService
|
||||
// NOTE: The order in which tests are run is important
|
||||
// If you need to add more tests, it's best to define them at the end
|
||||
// of the file and to add them at the end of run_test
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// test_bad_args
|
||||
// - checks that calls to nsIPackagedAppService::requestURI do not accept a null argument
|
||||
// test_callback_gets_called
|
||||
// - checks the regular use case -> requesting a resource should asynchronously return an entry
|
||||
// test_same_content
|
||||
// - makes another request for the same file, and checks that the same content is returned
|
||||
// test_request_number
|
||||
// - this test does not make a request, but checks that the package has only
|
||||
// been requested once. The entry returned by the call to requestURI in
|
||||
// test_same_content should be returned from the cache.
|
||||
//
|
||||
// test_package_does_not_exist
|
||||
// - checks that requesting a file from a <package that does not exist>
|
||||
// calls the listener with an error code
|
||||
// test_file_does_not_exist
|
||||
// - checks that requesting a <subresource that doesn't exist> inside a
|
||||
// package calls the listener with an error code
|
||||
//
|
||||
// test_bad_package
|
||||
// - tests that a package with missing headers for some of the files
|
||||
// will still return files that are correct
|
||||
// test_bad_package_404
|
||||
// - tests that a request for a missing subresource doesn't hang if
|
||||
// if the last file in the package is missing some headers
|
||||
|
||||
Cu.import('resource://gre/modules/LoadContextInfo.jsm');
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// The number of times this package has been requested
|
||||
// This number might be reset by tests that use it
|
||||
var packagedAppRequestsMade = 0;
|
||||
// The default content handler. It just responds by sending the package data
|
||||
// with an application/package content type
|
||||
function packagedAppContentHandler(metadata, response)
|
||||
{
|
||||
packagedAppRequestsMade++;
|
||||
response.setHeader("Content-Type", 'application/package');
|
||||
var body = testData.getData();
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
// The package content
|
||||
// getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
|
||||
var testData = {
|
||||
content: [
|
||||
{ headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n <head>\r\n <script src=\"/scripts/app.js\"></script>\r\n ...\r\n </head>\r\n ...\r\n</html>\r\n", type: "text/html" },
|
||||
{ headers: ["Content-Location: /scripts/app.js", "Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" },
|
||||
{ headers: ["Content-Location: /scripts/helpers/math.js", "Content-Type: text/javascript"], data: "export function sum(nums) { ... }\r\n...\r\n", type: "text/javascript" }
|
||||
],
|
||||
token : "gc0pJq0M:08jU534c0p",
|
||||
getData: function() {
|
||||
var str = "";
|
||||
for (var i in this.content) {
|
||||
str += "--" + this.token + "\r\n";
|
||||
for (var j in this.content[i].headers) {
|
||||
str += this.content[i].headers[j] + "\r\n";
|
||||
}
|
||||
str += "\r\n";
|
||||
str += this.content[i].data + "\r\n";
|
||||
}
|
||||
|
||||
str += "--" + this.token + "--";
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "uri", function() {
|
||||
return "http://localhost:" + httpserver.identity.primaryPort;
|
||||
});
|
||||
|
||||
// The active http server initialized in run_test
|
||||
var httpserver = null;
|
||||
// The packaged app service initialized in run_test
|
||||
var paservice = null;
|
||||
// This variable is set before requestURI is called. The listener uses this variable
|
||||
// to check the correct resource path for the returned entry
|
||||
var packagePath = null;
|
||||
|
||||
function run_test()
|
||||
{
|
||||
// setup test
|
||||
httpserver = new HttpServer();
|
||||
httpserver.registerPathHandler("/package", packagedAppContentHandler);
|
||||
httpserver.registerPathHandler("/304Package", packagedAppContentHandler);
|
||||
httpserver.registerPathHandler("/badPackage", packagedAppBadContentHandler);
|
||||
httpserver.start(-1);
|
||||
|
||||
paservice = Cc["@mozilla.org/network/packaged-app-service;1"]
|
||||
.getService(Ci.nsIPackagedAppService);
|
||||
ok(!!paservice, "test service exists");
|
||||
|
||||
add_test(test_bad_args);
|
||||
|
||||
add_test(test_callback_gets_called);
|
||||
add_test(test_same_content);
|
||||
add_test(test_request_number);
|
||||
|
||||
add_test(test_package_does_not_exist);
|
||||
add_test(test_file_does_not_exist);
|
||||
|
||||
add_test(test_bad_package);
|
||||
add_test(test_bad_package_404);
|
||||
|
||||
// run tests
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// This checks the proper metadata is on the entry
|
||||
var metadataListener = {
|
||||
onMetaDataElement: function(key, value) {
|
||||
if (key == 'response-head')
|
||||
equal(value, "HTTP/1.1 200 \r\nContent-Location: /index.html\r\nContent-Type: text/html\r\n");
|
||||
else if (key == 'request-method')
|
||||
equal(value, "GET");
|
||||
else
|
||||
ok(false, "unexpected metadata key")
|
||||
}
|
||||
}
|
||||
|
||||
// A listener we use to check the proper cache entry is returned by the service
|
||||
// NOTE: this listener only checks the content of index.html
|
||||
// Don't use it when requesting other packaged resources! :)
|
||||
var cacheListener = {
|
||||
onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
|
||||
onCacheEntryAvailable: function (entry, isnew, appcache, status) {
|
||||
ok(!!entry, "Needs to have an entry");
|
||||
equal(status, Cr.NS_OK, "status is NS_OK");
|
||||
equal(entry.key, uri + packagePath + "!//index.html", "Check entry has correct name");
|
||||
entry.visitMetaData(metadataListener);
|
||||
var inputStream = entry.openInputStream(0);
|
||||
pumpReadStream(inputStream, function(read) {
|
||||
inputStream.close();
|
||||
equal(read,"<html>\r\n <head>\r\n <script src=\"/scripts/app.js\"></script>\r\n ...\r\n </head>\r\n ...\r\n</html>\r\n"); // not using do_check_eq since logger will fail for the 1/4MB string
|
||||
});
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// These calls should fail, since one of the arguments is invalid or null
|
||||
function test_bad_args() {
|
||||
Assert.throws(() => { paservice.requestURI(createURI("http://test.com"), LoadContextInfo.default, cacheListener); }, "url's with no !// aren't allowed");
|
||||
Assert.throws(() => { paservice.requestURI(createURI("http://test.com/package!//test"), LoadContextInfo.default, null); }, "should have a callback");
|
||||
Assert.throws(() => { paservice.requestURI(null, LoadContextInfo.default, cacheListener); }, "should have a URI");
|
||||
Assert.throws(() => { paservice.requestURI(createURI("http://test.com/package!//test"), null, cacheListener); }, "should have a LoadContextInfo");
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// This tests that the callback gets called, and the cacheListener gets the proper content.
|
||||
function test_callback_gets_called() {
|
||||
packagePath = "/package";
|
||||
paservice.requestURI(createURI(uri + packagePath + "!//index.html"), LoadContextInfo.default, cacheListener);
|
||||
}
|
||||
|
||||
// Tests that requesting the same resource returns the same content
|
||||
function test_same_content() {
|
||||
packagePath = "/package";
|
||||
paservice.requestURI(createURI(uri + packagePath + "!//index.html"), LoadContextInfo.default, cacheListener);
|
||||
}
|
||||
|
||||
// Check the package has only been requested once.
|
||||
function test_request_number() {
|
||||
equal(packagedAppRequestsMade, 1, "only one request should be made. Second should be loaded from cache");
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// This listener checks that the requested resources are not returned
|
||||
// either because the package does not exist, or because the requested resource
|
||||
// is not contained in the package.
|
||||
var listener404 = {
|
||||
onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
|
||||
onCacheEntryAvailable: function (entry, isnew, appcache, status) {
|
||||
// XXX: it returns NS_ERROR_FAILURE for a missing package
|
||||
// and NS_ERROR_FILE_NOT_FOUND for a missing file from the package.
|
||||
// Maybe make them both return NS_ERROR_FILE_NOT_FOUND?
|
||||
notEqual(status, Cr.NS_OK, "NOT FOUND");
|
||||
ok(!entry, "There should be no entry");
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
// Tests that an error is returned for a non existing package
|
||||
function test_package_does_not_exist() {
|
||||
packagePath = "/package_non_existent";
|
||||
paservice.requestURI(createURI(uri + packagePath + "!//index.html"), LoadContextInfo.default, listener404);
|
||||
}
|
||||
|
||||
// Tests that an error is returned for a non existing resource in a package
|
||||
function test_file_does_not_exist() {
|
||||
packagePath = "/package"; // This package exists
|
||||
paservice.requestURI(createURI(uri + packagePath + "!//file_non_existent.html"), LoadContextInfo.default, listener404);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Broken package. The first and last resources do not contain a "Content-Location" header
|
||||
// and should be ignored.
|
||||
var badTestData = {
|
||||
content: [
|
||||
{ headers: ["Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" },
|
||||
{ headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n <head>\r\n <script src=\"/scripts/app.js\"></script>\r\n ...\r\n </head>\r\n ...\r\n</html>\r\n", type: "text/html" },
|
||||
{ headers: ["Content-Type: text/javascript"], data: "export function sum(nums) { ... }\r\n...\r\n", type: "text/javascript" }
|
||||
],
|
||||
token : "gc0pJq0M:08jU534c0p",
|
||||
getData: function() {
|
||||
var str = "";
|
||||
for (var i in this.content) {
|
||||
str += "--" + this.token + "\r\n";
|
||||
for (var j in this.content[i].headers) {
|
||||
str += this.content[i].headers[j] + "\r\n";
|
||||
}
|
||||
str += "\r\n";
|
||||
str += this.content[i].data + "\r\n";
|
||||
}
|
||||
|
||||
str += "--" + this.token + "--";
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the content of the package with "Content-Location" headers missing for the first and last resource
|
||||
function packagedAppBadContentHandler(metadata, response)
|
||||
{
|
||||
response.setHeader("Content-Type", 'application/package');
|
||||
var body = badTestData.getData();
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
// Checks that the resource with the proper headers inside the bad package is still returned
|
||||
function test_bad_package() {
|
||||
packagePath = "/badPackage";
|
||||
paservice.requestURI(createURI(uri + packagePath + "!//index.html"), LoadContextInfo.default, cacheListener);
|
||||
}
|
||||
|
||||
// Checks that the request for a non-existent resource doesn't hang for a bad package
|
||||
function test_bad_package_404() {
|
||||
packagePath = "/badPackage";
|
||||
paservice.requestURI(createURI(uri + packagePath + "!//file_non_existent.html"), LoadContextInfo.default, listener404);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
@ -316,3 +316,4 @@ skip-if = os == "android"
|
||||
[test_1073747.js]
|
||||
[test_multipart_streamconv_application_package.js]
|
||||
[test_safeoutputstream_append.js]
|
||||
[test_packaged_app_service.js]
|
||||
|
Loading…
Reference in New Issue
Block a user