Bug 402272: Replace <link rel="offline-resource"> with manifests, and navigator.offlineResources/pendingOfflineLoads with window.applicationCache. p=dcamp/Honza Bombas, r=biesi, r+sr=jst, blocking1.9=jst

This commit is contained in:
dcamp@mozilla.com 2008-01-16 13:54:33 -08:00
Родитель a355d3804f
Коммит 3494bf7df2
37 изменённых файлов: 2514 добавлений и 838 удалений

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

@ -1152,6 +1152,21 @@ public:
nsNativeKeyEvent* aNativeEvent,
PRBool aGetCharCode);
/**
* Get the application manifest URI for this context. The manifest URI
* is specified in the manifest= attribute of the root element of the
* toplevel window.
*
* @param aWindow The context to check.
* @param aURI The manifest URI.
*/
static void GetOfflineAppManifest(nsIDOMWindow *aWindow, nsIURI **aURI);
/**
* Check whether an application should be allowed to use offline APIs.
*/
static PRBool OfflineAppAllowed(nsIURI *aURI);
private:
static PRBool InitializeEventTable();
@ -1223,7 +1238,6 @@ private:
static PRBool sInitialized;
};
#define NS_HOLD_JS_OBJECTS(obj, clazz) \
nsContentUtils::HoldJSObjects(NS_CYCLE_COLLECTION_UPCAST(obj, clazz), \
&NS_CYCLE_COLLECTION_NAME(clazz))

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

@ -70,10 +70,8 @@
#include "nsIPrincipal.h"
#include "nsIScriptGlobalObject.h"
#include "nsNetCID.h"
#include "nsICache.h"
#include "nsICacheService.h"
#include "nsICacheSession.h"
#include "nsIOfflineCacheUpdate.h"
#include "nsIScriptSecurityManager.h"
#include "nsIDOMLoadStatus.h"
#include "nsICookieService.h"
#include "nsIPrompt.h"
@ -94,6 +92,7 @@
#include "nsIDOMNode.h"
#include "nsThreadUtils.h"
#include "nsPresShellIterator.h"
#include "nsPIDOMWindow.h"
PRLogModuleInfo* gContentSinkLogModuleInfo;
@ -684,11 +683,6 @@ nsContentSink::ProcessLink(nsIContent* aElement,
PrefetchHref(aHref, aElement, hasPrefetch);
}
// fetch href into the offline cache if relation is "offline-resource"
if (linkTypes.IndexOf(NS_LITERAL_STRING("offline-resource")) != -1) {
AddOfflineResource(aHref, aElement);
}
// is it a stylesheet link?
if (linkTypes.IndexOf(NS_LITERAL_STRING("stylesheet")) == -1) {
return NS_OK;
@ -820,64 +814,62 @@ nsContentSink::PrefetchHref(const nsAString &aHref,
}
}
nsresult
nsContentSink::AddOfflineResource(const nsAString &aHref, nsIContent *aSource)
void
nsContentSink::ProcessOfflineManifest(nsIContent *aElement)
{
PRBool match;
nsresult rv;
// Check for a manifest= attribute.
nsAutoString manifestSpec;
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec);
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(mDocumentURI);
if (!innerURI)
return NS_ERROR_FAILURE;
if (!mHaveOfflineResources) {
mHaveOfflineResources = PR_TRUE;
// only let http and https urls add offline resources
nsresult rv = innerURI->SchemeIs("http", &match);
NS_ENSURE_SUCCESS(rv, rv);
if (!match) {
rv = innerURI->SchemeIs("https", &match);
NS_ENSURE_SUCCESS(rv, rv);
if (!match)
return NS_OK;
}
// create updater
mOfflineCacheUpdate =
do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString ownerDomain;
rv = innerURI->GetHostPort(ownerDomain);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString ownerSpec;
rv = mDocumentURI->GetSpec(ownerSpec);
NS_ENSURE_SUCCESS(rv, rv);
rv = mOfflineCacheUpdate->Init(PR_FALSE, ownerDomain,
ownerSpec, mDocumentURI);
NS_ENSURE_SUCCESS(rv, rv);
// Kick off this update when the document is done loading
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(mDocument);
mOfflineCacheUpdate->ScheduleOnDocumentStop(doc);
if (manifestSpec.IsEmpty() ||
manifestSpec.FindChar('#') != kNotFound) {
return;
}
if (!mOfflineCacheUpdate) return NS_OK;
// We only care about manifests in toplevel windows.
nsCOMPtr<nsPIDOMWindow> pwindow =
do_QueryInterface(mDocument->GetScriptGlobalObject());
if (!pwindow) {
return;
}
const nsACString &charset = mDocument->GetDocumentCharacterSet();
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri), aHref,
charset.IsEmpty() ? nsnull : PromiseFlatCString(charset).get(),
mDocumentBaseURI);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMWindow> window =
do_QueryInterface(pwindow->GetOuterWindow());
if (!window) {
return;
}
nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aSource);
nsCOMPtr<nsIDOMWindow> parent;
window->GetParent(getter_AddRefs(parent));
if (parent.get() != window.get()) {
return;
}
return mOfflineCacheUpdate->AddURI(uri, domNode);
// Only update if the document has permission to use offline APIs.
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return;
}
nsCOMPtr<nsIURI> manifestURI;
nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI),
manifestSpec, mDocument,
mDocumentURI);
if (!manifestURI) {
return;
}
// Documents must list a manifest from the same origin
nsresult rv = nsContentUtils::GetSecurityManager()->
CheckSameOriginURI(manifestURI, mDocumentURI, PR_TRUE);
if (NS_FAILED(rv)) {
return;
}
// Start the update
nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(mDocument);
nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI, domdoc);
}
void

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

@ -75,7 +75,6 @@ class nsIContent;
class nsIViewManager;
class nsNodeInfoManager;
class nsScriptLoader;
class nsIOfflineCacheUpdate;
#ifdef NS_DEBUG
@ -169,7 +168,7 @@ protected:
void PrefetchHref(const nsAString &aHref, nsIContent *aSource,
PRBool aExplicit);
nsresult AddOfflineResource(const nsAString &aHref, nsIContent *aSource);
void ProcessOfflineManifest(nsIContent *aElement);
void ScrollToRef();
nsresult RefreshIfEnabled(nsIViewManager* vm);
@ -260,9 +259,6 @@ protected:
// Do we notify based on time?
PRPackedBool mNotifyOnTimer;
// For saving <link rel="offline-resource"> links
nsCOMPtr<nsIOfflineCacheUpdate> mOfflineCacheUpdate;
// Have we already called BeginUpdate for this set of content changes?
PRUint8 mBeganUpdate : 1;
PRUint8 mLayoutStarted : 1;
@ -275,8 +271,6 @@ protected:
PRUint8 mChangeScrollPosWhenScrollingToRef : 1;
// If true, we deferred starting layout until sheets load
PRUint8 mDeferredLayoutStart : 1;
// true if an <link rel="offline-resource"> nodes have been encountered.
PRUint8 mHaveOfflineResources : 1;
// If true, we deferred notifications until sheets load
PRUint8 mDeferredFlushTags : 1;

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

@ -145,6 +145,7 @@ static NS_DEFINE_CID(kXTFServiceCID, NS_XTFSERVICE_CID);
#include "nsIDOMNSUIEvent.h"
#include "nsIDOMNSEvent.h"
#include "nsIPrivateDOMEvent.h"
#include "nsIPermissionManager.h"
#ifdef IBMBIDI
#include "nsIBidiKeyboard.h"
@ -675,6 +676,82 @@ nsContentUtils::IsPunctuationMark(PRUnichar aChar)
return CCMAP_HAS_CHAR(gPuncCharsCCMap, aChar);
}
/* static */
void
nsContentUtils::GetOfflineAppManifest(nsIDOMWindow *aWindow, nsIURI **aURI)
{
nsCOMPtr<nsIDOMWindow> top;
aWindow->GetTop(getter_AddRefs(top));
if (!top) {
return;
}
nsCOMPtr<nsIDOMDocument> topDOMDocument;
top->GetDocument(getter_AddRefs(topDOMDocument));
nsCOMPtr<nsIDocument> topDoc = do_QueryInterface(topDOMDocument);
if (!topDoc) {
return;
}
nsCOMPtr<nsIContent> docElement = topDoc->GetRootContent();
if (!docElement) {
return;
}
nsAutoString manifestSpec;
docElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec);
// Manifest URIs can't have fragment identifiers.
if (manifestSpec.IsEmpty() ||
manifestSpec.FindChar('#') != kNotFound) {
return;
}
nsContentUtils::NewURIWithDocumentCharset(aURI, manifestSpec,
topDoc, topDoc->GetBaseURI());
}
/* static */
PRBool
nsContentUtils::OfflineAppAllowed(nsIURI *aURI)
{
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
if (!innerURI)
return PR_FALSE;
// only http and https applications can use offline APIs.
PRBool match;
nsresult rv = innerURI->SchemeIs("http", &match);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
if (!match) {
rv = innerURI->SchemeIs("https", &match);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
if (!match) {
return PR_FALSE;
}
}
nsCOMPtr<nsIPermissionManager> permissionManager =
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
if (!permissionManager) {
return PR_FALSE;
}
PRUint32 perm;
permissionManager->TestExactPermission(innerURI, "offline-app", &perm);
if (perm == nsIPermissionManager::UNKNOWN_ACTION) {
return GetBoolPref("offline-apps.allow_by_default");
}
if (perm == nsIPermissionManager::DENY_ACTION) {
return PR_FALSE;
}
return PR_TRUE;
}
// static
void
nsContentUtils::Shutdown()

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

@ -476,6 +476,7 @@ GK_ATOM(lowest, "lowest")
GK_ATOM(lowsrc, "lowsrc")
GK_ATOM(ltr, "ltr")
GK_ATOM(map, "map")
GK_ATOM(manifest, "manifest")
GK_ATOM(marginheight, "marginheight")
GK_ATOM(marginwidth, "marginwidth")
GK_ATOM(marquee, "marquee")

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

@ -2346,6 +2346,7 @@ HTMLContentSink::OpenContainer(const nsIParserNode& aNode)
if (!mNotifiedRootInsertion) {
NotifyRootInsertion();
}
ProcessOfflineManifest(mRoot);
}
break;
case eHTMLTag_form:
@ -2992,13 +2993,6 @@ HTMLContentSink::ProcessLINKTag(const nsIParserNode& aNode)
PrefetchHref(hrefVal, element, hasPrefetch);
}
}
if (linkTypes.IndexOf(NS_LITERAL_STRING("offline-resource")) != -1) {
nsAutoString hrefVal;
element->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
if (!hrefVal.IsEmpty()) {
AddOfflineResource(hrefVal, element);
}
}
}
}
}

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

@ -72,8 +72,8 @@ class nsPresContext;
struct nsTimeout;
#define NS_PIDOMWINDOW_IID \
{ 0x51316cbc, 0x595e, 0x40b1, \
{ 0xab, 0x07, 0x21, 0xbf, 0xe0, 0x92, 0x62, 0xa9 } }
{ 0x909852b5, 0xb9e6, 0x4d94, \
{ 0x8d, 0xe3, 0x05, 0x16, 0x34, 0x80, 0x0b, 0x73 } }
class nsPIDOMWindow : public nsIDOMWindowInternal
{
@ -272,6 +272,8 @@ public:
// the window was frozen.
virtual nsresult FireDelayedDOMEvents() = 0;
virtual PRBool IsFrozen() const = 0;
// Add a timeout to this window.
virtual nsresult SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
PRInt32 interval,

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

@ -40,7 +40,7 @@
interface nsIDOMOfflineResourceList;
interface nsIDOMLoadStatusList;
[scriptable, uuid(02bb1271-05dd-4bde-a9ca-68571bf8c702)]
[scriptable, uuid(4de9dbd5-0eff-47aa-8520-c062c6933d6a)]
interface nsIDOMClientInformation : nsISupports
{
/**
@ -53,9 +53,6 @@ interface nsIDOMClientInformation : nsISupports
void registerProtocolHandler(in DOMString protocol, in DOMString uri, in DOMString title);
boolean isLocallyAvailable(in DOMString uri, in boolean whenOffline);
readonly attribute nsIDOMOfflineResourceList offlineResources;
readonly attribute nsIDOMLoadStatusList pendingOfflineLoads;
};

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

@ -37,7 +37,9 @@
#include "nsIDOMWindow.idl"
[scriptable, uuid(65455132-b96a-40ec-adea-52fa22b1028c)]
interface nsIDOMOfflineResourceList;
[scriptable, uuid(73c5fa35-3add-4c87-a303-a850ccf4d65a)]
interface nsIDOMWindow2 : nsIDOMWindow
{
/**
@ -46,4 +48,9 @@ interface nsIDOMWindow2 : nsIDOMWindow
* in the window root.
*/
[noscript] readonly attribute nsIDOMEventTarget windowRoot;
/**
* Get the application cache object for this window.
*/
readonly attribute nsIDOMOfflineResourceList applicationCache;
};

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

@ -41,13 +41,76 @@
[scriptable, uuid(8449bce2-0d8c-4c74-ab79-b41b8d81f1c4)]
interface nsIDOMOfflineResourceList : nsISupports
{
/**
* Enumerate the list of dynamically-managed entries.
*/
readonly attribute unsigned long length;
DOMString item(in unsigned long index);
/**
* Add an item to the list of dynamically-managed entries. The resource
* will be fetched into the application cache.
*
* @param uri
* The resource to add.
*/
void add(in DOMString uri);
void remove(in DOMString uri);
boolean has(in DOMString uri);
void clear();
void refresh();
/**
* Remove an item from the list of dynamically-managed entries. If this
* was the last reference to a URI in the application cache, the cache
* entry will be removed.
*
* @param uri
* The resource to remove.
*/
void remove(in DOMString uri);
/**
* State of the application cache this object is associated with.
*/
/* This object is not associated with an application cache. */
const unsigned short UNCACHED = 0;
/* The application cache is not being updated. */
const unsigned short IDLE = 1;
/* The manifest is being fetched and checked for updates */
const unsigned short CHECKING = 2;
/* Resources are being downloaded to be added to the cache */
const unsigned short DOWNLOADING = 3;
/**
* There is a new version of the application cache available
*
* Versioned application caches are not currently implemented, so this
* value will not yet be returned
*/
const unsigned short UPDATEREADY = 4;
readonly attribute unsigned short status;
/**
* Begin the application update process on the associated application cache.
*/
void update();
/**
* Swap in the newest version of the application cache.
*
* Versioned application caches are not currently implemented, so this
* method will throw an exception.
*/
void swapCache();
/* Events */
attribute nsIDOMEventListener onchecking;
attribute nsIDOMEventListener onerror;
attribute nsIDOMEventListener onnoupdate;
attribute nsIDOMEventListener ondownloading;
attribute nsIDOMEventListener onprogress;
attribute nsIDOMEventListener onupdateready;
attribute nsIDOMEventListener oncached;
};

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

@ -51,7 +51,6 @@
#include "nsBarProps.h"
#include "nsDOMStorage.h"
#include "nsDOMOfflineResourceList.h"
#include "nsDOMOfflineLoadStatusList.h"
#include "nsDOMError.h"
// Helper Classes
@ -764,6 +763,7 @@ nsGlobalWindow::CleanUp()
mScrollbars = nsnull;
mLocation = nsnull;
mFrames = nsnull;
mApplicationCache = nsnull;
ClearControllers();
@ -849,6 +849,11 @@ nsGlobalWindow::FreeInnerObjects(PRBool aClearScope)
mDocument = nsnull;
mDoc = nsnull;
if (mApplicationCache) {
static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->Disconnect();
mApplicationCache = nsnull;
}
if (aClearScope) {
PRUint32 lang_id;
NS_STID_FOR_ID(lang_id) {
@ -940,6 +945,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsGlobalWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOpenerScriptPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mListenerManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSessionStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mApplicationCache)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocumentPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDoc)
@ -983,6 +989,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOpenerScriptPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mListenerManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSessionStorage)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mApplicationCache)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocumentPrincipal)
// Unlink any associated preserved wrapper.
@ -2689,6 +2696,46 @@ nsGlobalWindow::GetFrames(nsIDOMWindowCollection** aFrames)
return NS_OK;
}
NS_IMETHODIMP
nsGlobalWindow::GetApplicationCache(nsIDOMOfflineResourceList **aApplicationCache)
{
FORWARD_TO_INNER(GetApplicationCache, (aApplicationCache), NS_ERROR_UNEXPECTED);
NS_ENSURE_ARG_POINTER(aApplicationCache);
if (!mApplicationCache) {
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(GetDocShell()));
if (!webNav) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = webNav->GetCurrentURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> manifestURI;
nsContentUtils::GetOfflineAppManifest(GetOuterWindowInternal(),
getter_AddRefs(manifestURI));
PRBool isToplevel;
nsCOMPtr<nsIDOMWindow> parentWindow;
GetParent(getter_AddRefs(parentWindow));
isToplevel = (parentWindow.get() == static_cast<nsIDOMWindow*>(GetOuterWindowInternal()));
nsDOMOfflineResourceList* applicationCache =
new nsDOMOfflineResourceList(isToplevel, manifestURI, uri, this);
if (!applicationCache)
return NS_ERROR_OUT_OF_MEMORY;
mApplicationCache = applicationCache;
}
NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
return NS_OK;
}
NS_IMETHODIMP
nsGlobalWindow::GetCrypto(nsIDOMCrypto** aCrypto)
{
@ -6653,6 +6700,10 @@ nsGlobalWindow::FireDelayedDOMEvents()
mPendingStorageEvents = nsnull;
}
if (mApplicationCache) {
static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents();
}
if (mFireOfflineStatusChangeEventOnThaw) {
mFireOfflineStatusChangeEventOnThaw = PR_FALSE;
FireOfflineStatusEvent();
@ -8996,8 +9047,6 @@ nsNavigator::LoadingNewDocument()
// arrays may have changed. See bug 150087.
mMimeTypes = nsnull;
mPlugins = nsnull;
mOfflineResources = nsnull;
mPendingOfflineLoads = nsnull;
}
nsresult
@ -9118,51 +9167,3 @@ nsNavigator::IsLocallyAvailable(const nsAString &aURI,
return NS_OK;
}
NS_IMETHODIMP
nsNavigator::GetOfflineResources(nsIDOMOfflineResourceList **aList)
{
NS_ENSURE_ARG_POINTER(aList);
if (!mOfflineResources) {
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(GetDocShell()));
if (!webNav) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = webNav->GetCurrentURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
mOfflineResources = new nsDOMOfflineResourceList(uri);
if (!mOfflineResources) return NS_ERROR_OUT_OF_MEMORY;
}
NS_IF_ADDREF(*aList = mOfflineResources);
return NS_OK;
}
NS_IMETHODIMP
nsNavigator::GetPendingOfflineLoads(nsIDOMLoadStatusList **aList)
{
NS_ENSURE_ARG_POINTER(aList);
if (!mPendingOfflineLoads) {
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(GetDocShell()));
if (!webNav) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = webNav->GetCurrentURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
mPendingOfflineLoads = new nsDOMOfflineLoadStatusList(uri);
if (!mPendingOfflineLoads) return NS_ERROR_OUT_OF_MEMORY;
}
NS_IF_ADDREF(*aList = mPendingOfflineLoads);
return NS_OK;
}

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

@ -301,6 +301,10 @@ public:
virtual NS_HIDDEN_(nsresult) RestoreWindowState(nsISupports *aState);
virtual NS_HIDDEN_(nsresult) ResumeTimeouts();
virtual NS_HIDDEN_(nsresult) FireDelayedDOMEvents();
virtual NS_HIDDEN_(PRBool) IsFrozen() const
{
return mIsFrozen;
}
virtual NS_HIDDEN_(PRBool) WouldReuseInnerWindow(nsIDocument *aNewDocument);
@ -392,11 +396,6 @@ public:
return static_cast<nsGlobalWindow *>(EnsureInnerWindow());
}
PRBool IsFrozen() const
{
return mIsFrozen;
}
PRBool IsCreatingInnerWindow() const
{
return mCreatingInnerWindow;
@ -718,6 +717,8 @@ protected:
PRBool mSetOpenerWindowCalled;
#endif
nsCOMPtr<nsIDOMOfflineResourceList> mApplicationCache;
friend class nsDOMScriptableHelper;
friend class nsDOMWindowUtils;
static nsIFactory *sComputedDOMStyleFactory;
@ -804,8 +805,6 @@ public:
protected:
nsRefPtr<nsMimeTypeArray> mMimeTypes;
nsRefPtr<nsPluginArray> mPlugins;
nsRefPtr<nsDOMOfflineResourceList> mOfflineResources;
nsRefPtr<nsDOMOfflineLoadStatusList> mPendingOfflineLoads;
nsIDocShell* mDocShell; // weak reference
static jsval sPrefInternal_id;

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

@ -65,7 +65,6 @@ REQUIRES = xpcom \
CPPSRCS = \
nsDOMOfflineResourceList.cpp \
nsDOMOfflineLoadStatusList.cpp \
$(NULL)
# we don't want the shared lib, but we want to force the creation of a static lib.

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

@ -1,123 +0,0 @@
/* -*- 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 mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dave Camp <dcamp@mozilla.com>
*
* 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 MPL, 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 MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsDOMOfflineLoadStatusList_h___
#define nsDOMOfflineLoadStatusList_h___
#include "nscore.h"
#include "nsIDOMLoadStatus.h"
#include "nsIDOMLoadStatusEvent.h"
#include "nsIDOMLoadStatusList.h"
#include "nsIOfflineCacheUpdate.h"
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsIURI.h"
#include "nsDOMEvent.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMEventListener.h"
#include "nsIObserver.h"
#include "nsWeakReference.h"
#include "nsIScriptContext.h"
class nsDOMOfflineLoadStatus;
class nsDOMOfflineLoadStatusList : public nsIDOMLoadStatusList,
public nsIDOMEventTarget,
public nsIObserver,
public nsSupportsWeakReference
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMLOADSTATUSLIST
NS_DECL_NSIDOMEVENTTARGET
NS_DECL_NSIOBSERVER
nsDOMOfflineLoadStatusList(nsIURI *aURI);
virtual ~nsDOMOfflineLoadStatusList();
nsresult Init();
private :
nsresult UpdateAdded (nsIOfflineCacheUpdate *aUpdate);
nsresult UpdateCompleted (nsIOfflineCacheUpdate *aUpdate);
nsIDOMLoadStatus *FindWrapper (nsIDOMLoadStatus *aStatus,
PRUint32 *aIndex);
void NotifyEventListeners(const nsCOMArray<nsIDOMEventListener>& aListeners,
nsIDOMEvent* aEvent);
nsresult SendLoadEvent (const nsAString& aEventName,
const nsCOMArray<nsIDOMEventListener>& aListeners,
nsIDOMLoadStatus *aStatus);
PRBool mInitialized;
nsCOMPtr<nsIURI> mURI;
nsCOMArray<nsIDOMLoadStatus> mItems;
nsCString mHostPort;
nsCOMPtr<nsIScriptContext> mScriptContext;
nsCOMArray<nsIDOMEventListener> mLoadRequestedEventListeners;
nsCOMArray<nsIDOMEventListener> mLoadCompletedEventListeners;
nsCOMArray<nsIDOMEventListener> mUpdateCompletedEventListeners;
};
class nsDOMLoadStatusEvent : public nsDOMEvent,
public nsIDOMLoadStatusEvent
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMLOADSTATUSEVENT
NS_FORWARD_NSIDOMEVENT(nsDOMEvent::)
nsDOMLoadStatusEvent(const nsAString& aEventName, nsIDOMLoadStatus *aStatus)
: nsDOMEvent(nsnull, nsnull), mEventName(aEventName), mStatus(aStatus)
{
}
virtual ~nsDOMLoadStatusEvent() { }
nsresult Init();
private:
nsAutoString mEventName;
nsCOMPtr<nsIDOMLoadStatus> mStatus;
};
#endif

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

@ -50,6 +50,21 @@
#include "nsIDOMLoadStatus.h"
#include "nsAutoPtr.h"
#include "nsContentUtils.h"
#include "nsIJSContextStack.h"
#include "nsEventDispatcher.h"
#include "nsIPrivateDOMEvent.h"
#include "nsIObserverService.h"
#include "nsIScriptGlobalObject.h"
// Event names
#define CHECKING_STR "checking"
#define ERROR_STR "error"
#define NOUPDATE_STR "noupdate"
#define DOWNLOADING_STR "downloading"
#define PROGRESS_STR "progress"
#define CACHED_STR "cached"
#define UPDATEREADY_STR "updateready"
// To prevent abuse of the resource list for data storage, the number
// of offline urls and their length are limited.
@ -66,19 +81,89 @@ static PRUint32 gCachedKeysCount = 0;
// nsDOMOfflineResourceList
//
NS_INTERFACE_MAP_BEGIN(nsDOMOfflineResourceList)
NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMOfflineResourceList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMOfflineResourceList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCacheSession)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCacheUpdate)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCheckingListeners)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mErrorListeners)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mNoUpdateListeners)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mDownloadingListeners)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mProgressListeners)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCachedListeners)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mUpdateReadyListeners)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnCheckingListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnNoUpdateListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnDownloadingListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnProgressListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnCachedListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnUpdateReadyListener)
for (PRUint32 i = 0; i < tmp->mPendingEvents.Length(); i++) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPendingEvents[i].event);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPendingEvents[i].listener);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mPendingEvents[i].listeners);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMOfflineResourceList)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCacheSession)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCacheUpdate)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mCheckingListeners)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mErrorListeners)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mNoUpdateListeners)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mDownloadingListeners)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mProgressListeners)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mCachedListeners)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mUpdateReadyListeners)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnCheckingListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnErrorListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnNoUpdateListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnDownloadingListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnProgressListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnCachedListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnUpdateReadyListener)
for (PRUint32 i = 0; i < tmp->mPendingEvents.Length(); i++) {
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPendingEvents[i].event);
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPendingEvents[i].listener);
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mPendingEvents[i].listeners);
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMOfflineResourceList)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMOfflineResourceList)
NS_INTERFACE_MAP_ENTRY(nsIDOMOfflineResourceList)
NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdateObserver)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(OfflineResourceList)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsDOMOfflineResourceList)
NS_IMPL_RELEASE(nsDOMOfflineResourceList)
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMOfflineResourceList)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMOfflineResourceList)
nsDOMOfflineResourceList::nsDOMOfflineResourceList(nsIURI *aURI)
nsDOMOfflineResourceList::nsDOMOfflineResourceList(PRBool aToplevel,
nsIURI *aManifestURI,
nsIURI *aDocumentURI,
nsIDOMWindow *aWindow)
: mInitialized(PR_FALSE)
, mURI(aURI)
, mToplevel(aToplevel)
, mManifestURI(aManifestURI)
, mDocumentURI(aDocumentURI)
{
mWindow = do_GetWeakReference(aWindow);
}
nsDOMOfflineResourceList::~nsDOMOfflineResourceList()
@ -92,11 +177,26 @@ nsDOMOfflineResourceList::Init()
return NS_OK;
}
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(mURI);
if (!mManifestURI) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
nsresult rv = nsContentUtils::GetSecurityManager()->
CheckSameOriginURI(mManifestURI, mDocumentURI, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
// Dynamically-managed resources are stored as a separate ownership list
// from the manifest.
rv = mManifestURI->GetSpec(mDynamicOwnerSpec);
NS_ENSURE_SUCCESS(rv, rv);
mDynamicOwnerSpec.Append("#dynamic");
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(mDocumentURI);
if (!innerURI)
return NS_ERROR_FAILURE;
nsresult rv = innerURI->GetHostPort(mHostPort);
rv = innerURI->GetHostPort(mHostPort);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsICacheService> serv = do_GetService(NS_CACHESERVICE_CONTRACTID,
@ -113,11 +213,61 @@ nsDOMOfflineResourceList::Init()
mCacheSession = do_QueryInterface(session, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Check for in-progress cache updates
nsCOMPtr<nsIOfflineCacheUpdateService> cacheUpdateService =
do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 numUpdates;
rv = cacheUpdateService->GetNumUpdates(&numUpdates);
NS_ENSURE_SUCCESS(rv, rv);
for (PRUint32 i = 0; i < numUpdates; i++) {
nsCOMPtr<nsIOfflineCacheUpdate> cacheUpdate;
rv = cacheUpdateService->GetUpdate(i, getter_AddRefs(cacheUpdate));
NS_ENSURE_SUCCESS(rv, rv);
UpdateAdded(cacheUpdate);
NS_ENSURE_SUCCESS(rv, rv);
}
// watch for new offline cache updates
nsCOMPtr<nsIObserverService> observerServ =
do_GetService("@mozilla.org/observer-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = observerServ->AddObserver(this, "offline-cache-update-added", PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
rv = observerServ->AddObserver(this, "offline-cache-update-completed", PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
mInitialized = PR_TRUE;
return NS_OK;
}
void
nsDOMOfflineResourceList::Disconnect()
{
mCheckingListeners.Clear();
mErrorListeners.Clear();
mNoUpdateListeners.Clear();
mDownloadingListeners.Clear();
mProgressListeners.Clear();
mCachedListeners.Clear();
mUpdateReadyListeners.Clear();
mOnCheckingListener = nsnull;
mOnErrorListener = nsnull;
mOnNoUpdateListener = nsnull;
mOnDownloadingListener = nsnull;
mOnProgressListener = nsnull;
mOnCachedListener = nsnull;
mOnUpdateReadyListener = nsnull;
mPendingEvents.Clear();
}
//
// nsDOMOfflineResourceList::nsIDOMOfflineResourceList
//
@ -128,6 +278,10 @@ nsDOMOfflineResourceList::GetLength(PRUint32 *aLength)
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
rv = CacheKeys();
NS_ENSURE_SUCCESS(rv, rv);
@ -141,6 +295,10 @@ nsDOMOfflineResourceList::Item(PRUint32 aIndex, nsAString& aURI)
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
SetDOMStringToNull(aURI);
rv = CacheKeys();
@ -160,6 +318,10 @@ nsDOMOfflineResourceList::Add(const nsAString& aURI)
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (aURI.Length() > MAX_URI_LENGTH) return NS_ERROR_DOM_BAD_URI;
// this will fail if the URI is not absolute
@ -167,6 +329,18 @@ nsDOMOfflineResourceList::Add(const nsAString& aURI)
rv = NS_NewURI(getter_AddRefs(requestedURI), aURI);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString scheme;
rv = requestedURI->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
PRBool match;
rv = mManifestURI->SchemeIs(scheme.get(), &match);
NS_ENSURE_SUCCESS(rv, rv);
if (!match) {
return NS_ERROR_DOM_SECURITY_ERR;
}
PRUint32 length;
rv = GetLength(&length);
NS_ENSURE_SUCCESS(rv, rv);
@ -181,10 +355,10 @@ nsDOMOfflineResourceList::Add(const nsAString& aURI)
do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = update->Init(PR_TRUE, mHostPort, NS_LITERAL_CSTRING(""), mURI);
rv = update->Init(PR_TRUE, mManifestURI, mDocumentURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = update->AddURI(requestedURI, nsnull);
rv = update->AddDynamicURI(requestedURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = update->Schedule();
@ -199,61 +373,522 @@ nsDOMOfflineResourceList::Remove(const nsAString& aURI)
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCAutoString key;
rv = GetCacheKey(aURI, key);
NS_ENSURE_SUCCESS(rv, rv);
ClearCachedKeys();
return mCacheSession->RemoveOwnedKey(mHostPort,
NS_LITERAL_CSTRING(""),
key);
rv = mCacheSession->RemoveOwnedKey(mHostPort, mDynamicOwnerSpec, key);
NS_ENSURE_SUCCESS(rv, rv);
rv = mCacheSession->EvictUnownedEntries();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::Has(const nsAString& aURI, PRBool *aExists)
nsDOMOfflineResourceList::GetStatus(PRUint16 *aStatus)
{
nsresult rv = Init();
// It is OK to check the status without a manifest attribute (you'll
// just get "uncached").
if (rv == NS_ERROR_DOM_INVALID_STATE_ERR && !mManifestURI) {
*aStatus = nsIDOMOfflineResourceList::UNCACHED;
return NS_OK;
}
NS_ENSURE_SUCCESS(rv, rv);
if (mCacheUpdate) {
return mCacheUpdate->GetStatus(aStatus);
}
// XXX: the spec allows either UNCACHED or IDLE, depending on whether
// the application is associated with a specific offline cache. Until
// we have versioned application caches, the best approximation is
// probably IDLE if the offline-app permission is set, and UNCACHED
// otherwise.
if (nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
*aStatus = nsIDOMOfflineResourceList::IDLE;
} else {
*aStatus = nsIDOMOfflineResourceList::UNCACHED;
}
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::Update()
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString key;
rv = GetCacheKey(aURI, key);
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
return mCacheSession->KeyIsOwned(mHostPort, NS_LITERAL_CSTRING(""),
key, aExists);
nsCOMPtr<nsIOfflineCacheUpdate> update;
rv = updateService->ScheduleUpdate(mManifestURI, mDocumentURI,
getter_AddRefs(update));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::Clear()
nsDOMOfflineResourceList::SwapCache()
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
ClearCachedKeys();
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
return mCacheSession->SetOwnedKeys(mHostPort,
NS_LITERAL_CSTRING(""),
0, nsnull);
return NS_ERROR_NOT_IMPLEMENTED;
}
//
// nsDOMOfflineResourceList::nsIDOMEventTarget
//
NS_IMETHODIMP
nsDOMOfflineResourceList::Refresh()
nsDOMOfflineResourceList::GetOnchecking(nsIDOMEventListener **aOnchecking)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOfflineCacheUpdate> update =
do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv);
NS_ENSURE_ARG_POINTER(aOnchecking);
NS_IF_ADDREF(*aOnchecking = mOnCheckingListener);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::SetOnchecking(nsIDOMEventListener *aOnchecking)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
rv = update->Init(PR_FALSE, mHostPort, NS_LITERAL_CSTRING(""), mURI);
mOnCheckingListener = aOnchecking;
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetOnerror(nsIDOMEventListener **aOnerror)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
rv = update->Schedule();
NS_ENSURE_ARG_POINTER(aOnerror);
NS_IF_ADDREF(*aOnerror = mOnErrorListener);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::SetOnerror(nsIDOMEventListener *aOnerror)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
return rv;
mOnErrorListener = aOnerror;
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetOnnoupdate(nsIDOMEventListener **aOnnoupdate)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(aOnnoupdate);
NS_IF_ADDREF(*aOnnoupdate = mOnNoUpdateListener);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::SetOnnoupdate(nsIDOMEventListener *aOnnoupdate)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
mOnNoUpdateListener = aOnnoupdate;
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetOndownloading(nsIDOMEventListener **aOndownloading)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(aOndownloading);
NS_IF_ADDREF(*aOndownloading = mOnDownloadingListener);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::SetOndownloading(nsIDOMEventListener *aOndownloading)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
mOnDownloadingListener = aOndownloading;
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetOnprogress(nsIDOMEventListener **aOnprogress)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(aOnprogress);
NS_IF_ADDREF(*aOnprogress = mOnProgressListener);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::SetOnprogress(nsIDOMEventListener *aOnprogress)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
mOnProgressListener = aOnprogress;
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetOnupdateready(nsIDOMEventListener **aOnupdateready)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(aOnupdateready);
NS_IF_ADDREF(*aOnupdateready = mOnUpdateReadyListener);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::SetOncached(nsIDOMEventListener *aOncached)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
mOnCachedListener = aOncached;
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetOncached(nsIDOMEventListener **aOncached)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(aOncached);
NS_IF_ADDREF(*aOncached = mOnCachedListener);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::SetOnupdateready(nsIDOMEventListener *aOnupdateready)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
mOnUpdateReadyListener = aOnupdateready;
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::AddEventListener(const nsAString& aType,
nsIDOMEventListener *aListener,
PRBool aUseCapture)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG(aListener);
nsCOMArray<nsIDOMEventListener> *array;
#define IMPL_ADD_LISTENER(_type, _member) \
if (aType.EqualsLiteral(_type)) { \
array = &(_member); \
} else
IMPL_ADD_LISTENER(CHECKING_STR, mCheckingListeners)
IMPL_ADD_LISTENER(ERROR_STR, mErrorListeners)
IMPL_ADD_LISTENER(NOUPDATE_STR, mNoUpdateListeners)
IMPL_ADD_LISTENER(DOWNLOADING_STR, mDownloadingListeners)
IMPL_ADD_LISTENER(PROGRESS_STR, mProgressListeners)
IMPL_ADD_LISTENER(CACHED_STR, mCachedListeners)
IMPL_ADD_LISTENER(UPDATEREADY_STR, mUpdateReadyListeners)
{
return NS_ERROR_INVALID_ARG;
}
array->AppendObject(aListener);
#undef IMPL_ADD_LISTENER
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::RemoveEventListener(const nsAString &aType,
nsIDOMEventListener *aListener,
PRBool aUseCapture)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG(aListener);
nsCOMArray<nsIDOMEventListener> *array;
#define IMPL_REMOVE_LISTENER(_type, _member) \
if (aType.EqualsLiteral(_type)) { \
array = &(_member); \
} else
IMPL_REMOVE_LISTENER(CHECKING_STR, mCheckingListeners)
IMPL_REMOVE_LISTENER(ERROR_STR, mErrorListeners)
IMPL_REMOVE_LISTENER(NOUPDATE_STR, mNoUpdateListeners)
IMPL_REMOVE_LISTENER(DOWNLOADING_STR, mDownloadingListeners)
IMPL_REMOVE_LISTENER(PROGRESS_STR, mProgressListeners)
IMPL_REMOVE_LISTENER(CACHED_STR, mCachedListeners)
IMPL_REMOVE_LISTENER(UPDATEREADY_STR, mUpdateReadyListeners)
{
return NS_ERROR_INVALID_ARG;
}
// Allow a caller to remove O(N^2) behavior by removing end-to-start.
for (PRUint32 i = array->Count() - 1; i != PRUint32(-1); --i) {
if (array->ObjectAt(i) == aListener) {
array->RemoveObjectAt(i);
break;
}
}
#undef IMPL_REMOVE_LISTENER
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::DispatchEvent(nsIDOMEvent *evt, PRBool *_retval)
{
// Ignored
return NS_OK;
}
void
nsDOMOfflineResourceList::NotifyEventListeners(nsIDOMEventListener *aListener,
const nsCOMArray<nsIDOMEventListener>& aListeners,
nsIDOMEvent* aEvent)
{
// XXXbz wouldn't it be easier to just have an actual nsEventListenerManager
// to work with or something? I feel like we're duplicating code here...
//
// (and this was duplicated from XMLHttpRequest)
if (!aEvent)
return;
nsCOMPtr<nsIJSContextStack> stack;
JSContext *cx = nsnull;
nsCOMPtr<nsIScriptGlobalObject> scriptGlobal = do_QueryReferent(mWindow);
if (!scriptGlobal)
return;
nsCOMPtr<nsIScriptContext> context = scriptGlobal->GetContext();
if (context) {
stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1");
if (stack) {
cx = (JSContext *)context->GetNativeContext();
if (cx) {
stack->Push(cx);
}
}
}
if (aListener) {
aListener->HandleEvent(aEvent);
}
PRInt32 count = aListeners.Count();
for (PRInt32 index = 0; index < count; ++index) {
nsIDOMEventListener* listener = aListeners[index];
if (listener) {
listener->HandleEvent(aEvent);
}
}
if (cx) {
stack->Pop(&cx);
}
}
void
nsDOMOfflineResourceList::FirePendingEvents()
{
for (PRUint32 i = 0; i < mPendingEvents.Length(); i++) {
const PendingEvent &pending = mPendingEvents[i];
NotifyEventListeners(pending.listener, pending.listeners, pending.event);
}
mPendingEvents.Clear();
}
nsresult
nsDOMOfflineResourceList::SendEvent(const nsAString &aEventName,
nsIDOMEventListener *aListener,
const nsCOMArray<nsIDOMEventListener> &aListeners)
{
// Only toplevel windows get application cache events.
if (!mToplevel) {
return NS_OK;
}
if (!aListener && aListeners.Count() == 0) {
return NS_OK;
}
// Don't send events to closed windows
nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
if (!window) {
return NS_OK;
}
if (!window->GetDocShell()) {
return NS_OK;
}
nsCOMPtr<nsIDOMEvent> event;
nsresult rv = nsEventDispatcher::CreateEvent(nsnull, nsnull,
NS_LITERAL_STRING("Events"),
getter_AddRefs(event));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrivateDOMEvent> privevent = do_QueryInterface(event);
if (!privevent) {
return NS_ERROR_FAILURE;
}
event->InitEvent(aEventName, PR_FALSE, PR_TRUE);
privevent->SetTarget(this);
privevent->SetCurrentTarget(this);
privevent->SetOriginalTarget(this);
// We assume anyone that managed to call SendEvent is trusted
privevent->SetTrusted(PR_TRUE);
// If the window is frozen or we're still catching up on events that were
// queued while frozen, save the event for later.
if (window->IsFrozen() || mPendingEvents.Length() > 0) {
PendingEvent *pending = mPendingEvents.AppendElement();
pending->event = event;
pending->listener = aListener;
pending->listeners.SetCapacity(aListeners.Count());
pending->listeners.AppendObjects(aListeners);
return NS_OK;
}
NotifyEventListeners(aListener, aListeners, event);
return NS_OK;
}
//
// nsDOMOfflineResourceList::nsIObserver
//
NS_IMETHODIMP
nsDOMOfflineResourceList::Observe(nsISupports *aSubject,
const char *aTopic,
const PRUnichar *aData)
{
if (!strcmp(aTopic, "offline-cache-update-added")) {
nsCOMPtr<nsIOfflineCacheUpdate> update = do_QueryInterface(aSubject);
if (update) {
UpdateAdded(update);
}
} else if (!strcmp(aTopic, "offline-cache-update-completed")) {
nsCOMPtr<nsIOfflineCacheUpdate> update = do_QueryInterface(aSubject);
if (update) {
UpdateCompleted(update);
}
}
return NS_OK;
}
//
// nsDOMOfflineResourceList::nsIOfflineCacheUpdateObserver
//
NS_IMETHODIMP
nsDOMOfflineResourceList::Error(nsIOfflineCacheUpdate *aUpdate)
{
SendEvent(NS_LITERAL_STRING(ERROR_STR), mOnErrorListener, mErrorListeners);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::Checking(nsIOfflineCacheUpdate *aUpdate)
{
SendEvent(NS_LITERAL_STRING(CHECKING_STR),
mOnCheckingListener, mCheckingListeners);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::NoUpdate(nsIOfflineCacheUpdate *aUpdate)
{
SendEvent(NS_LITERAL_STRING(NOUPDATE_STR),
mOnNoUpdateListener, mNoUpdateListeners);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::Downloading(nsIOfflineCacheUpdate *aUpdate)
{
SendEvent(NS_LITERAL_STRING(DOWNLOADING_STR),
mOnDownloadingListener, mDownloadingListeners);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::ItemStarted(nsIOfflineCacheUpdate *aUpdate,
nsIDOMLoadStatus *aItem)
{
SendEvent(NS_LITERAL_STRING(PROGRESS_STR),
mOnProgressListener, mProgressListeners);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::ItemCompleted(nsIOfflineCacheUpdate *aUpdate,
nsIDOMLoadStatus *aItem)
{
return NS_OK;
}
nsresult
@ -266,6 +901,71 @@ nsDOMOfflineResourceList::GetCacheKey(const nsAString &aURI, nsCString &aKey)
return GetCacheKey(requestedURI, aKey);
}
nsresult
nsDOMOfflineResourceList::UpdateAdded(nsIOfflineCacheUpdate *aUpdate)
{
// Ignore partial updates.
PRBool partial;
nsresult rv = aUpdate->GetPartial(&partial);
NS_ENSURE_SUCCESS(rv, rv);
if (partial) {
return NS_OK;
}
nsCOMPtr<nsIURI> updateURI;
rv = aUpdate->GetManifestURI(getter_AddRefs(updateURI));
NS_ENSURE_SUCCESS(rv, rv);
PRBool equals;
rv = updateURI->Equals(mManifestURI, &equals);
NS_ENSURE_SUCCESS(rv, rv);
if (!equals) {
// This update doesn't belong to us
return NS_OK;
}
NS_ENSURE_TRUE(!mCacheUpdate, NS_ERROR_FAILURE);
// We don't need to emit signals here. Updates are either added
// when they are scheduled (in which case they are always IDLE) or
// they are added when the applicationCache object is initialized, so there
// are no listeners to accept signals anyway.
mCacheUpdate = aUpdate;
mCacheUpdate->AddObserver(this, PR_TRUE);
return NS_OK;
}
nsresult
nsDOMOfflineResourceList::UpdateCompleted(nsIOfflineCacheUpdate *aUpdate)
{
if (aUpdate != mCacheUpdate) {
// This isn't the update we're watching.
return NS_OK;
}
PRBool succeeded;
nsresult rv = mCacheUpdate->GetSucceeded(&succeeded);
mCacheUpdate->RemoveObserver(this);
mCacheUpdate = nsnull;
if (NS_SUCCEEDED(rv) && succeeded) {
// XXX: the spec requires a "cached" event to be sent if this is a
// first-time cache attempt, and "updateready" if this page was loaded
// from an existing application cache. Since we don't have versioned
// application caches yet, basically each update acts like a first-time
// update, so we'll always fire "cached" for now.
SendEvent(NS_LITERAL_STRING(CACHED_STR),
mOnCachedListener, mCachedListeners);
}
return NS_OK;
}
nsresult
nsDOMOfflineResourceList::GetCacheKey(nsIURI *aURI, nsCString &aKey)
{
@ -292,7 +992,7 @@ nsDOMOfflineResourceList::CacheKeys()
ClearCachedKeys();
nsresult rv = mCacheSession->GetOwnedKeys(mHostPort, NS_LITERAL_CSTRING(""),
nsresult rv = mCacheSession->GetOwnedKeys(mHostPort, mDynamicOwnerSpec,
&gCachedKeysCount, &gCachedKeys);
if (NS_SUCCEEDED(rv))

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

@ -42,23 +42,61 @@
#include "nscore.h"
#include "nsIDOMOfflineResourceList.h"
#include "nsIOfflineCacheSession.h"
#include "nsIOfflineCacheUpdate.h"
#include "nsTArray.h"
#include "nsString.h"
#include "nsIURI.h"
#include "nsCOMPtr.h"
#include "nsWeakReference.h"
#include "nsCOMArray.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMEventTarget.h"
#include "nsDOMEvent.h"
#include "nsIObserver.h"
#include "nsIScriptContext.h"
#include "nsCycleCollectionParticipant.h"
class nsDOMOfflineResourceList : public nsIDOMOfflineResourceList
class nsIDOMWindow;
class nsDOMOfflineResourceList : public nsIDOMOfflineResourceList,
public nsIObserver,
public nsIOfflineCacheUpdateObserver,
public nsIDOMEventTarget,
public nsSupportsWeakReference
{
public:
NS_DECL_ISUPPORTS
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIDOMOFFLINERESOURCELIST
NS_DECL_NSIOBSERVER
NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
NS_DECL_NSIDOMEVENTTARGET
nsDOMOfflineResourceList(nsIURI* aURI);
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMOfflineResourceList,
nsIDOMOfflineResourceList)
nsDOMOfflineResourceList(PRBool aToplevel,
nsIURI* aManifestURI,
nsIURI* aDocumentURI,
nsIDOMWindow* aWindow);
virtual ~nsDOMOfflineResourceList();
void FirePendingEvents();
void Disconnect();
private:
nsresult Init();
void NotifyEventListeners(nsIDOMEventListener *aListener,
const nsCOMArray<nsIDOMEventListener>& aListeners,
nsIDOMEvent* aEvent);
nsresult SendEvent(const nsAString &aEventName,
nsIDOMEventListener *aListener,
const nsCOMArray<nsIDOMEventListener> &aListeners);
nsresult UpdateAdded(nsIOfflineCacheUpdate *aUpdate);
nsresult UpdateCompleted(nsIOfflineCacheUpdate *aUpdate);
nsresult GetCacheKey(const nsAString &aURI, nsCString &aKey);
nsresult GetCacheKey(nsIURI *aURI, nsCString &aKey);
@ -66,9 +104,38 @@ private:
void ClearCachedKeys();
PRBool mInitialized;
nsCOMPtr<nsIURI> mURI;
PRBool mToplevel;
nsCOMPtr<nsIURI> mManifestURI;
nsCOMPtr<nsIURI> mDocumentURI;
nsCOMPtr<nsIWeakReference> mWindow;
nsCOMPtr<nsIOfflineCacheSession> mCacheSession;
nsCOMPtr<nsIOfflineCacheUpdate> mCacheUpdate;
nsCAutoString mHostPort;
nsCAutoString mDynamicOwnerSpec;
nsCOMArray<nsIDOMEventListener> mCheckingListeners;
nsCOMArray<nsIDOMEventListener> mErrorListeners;
nsCOMArray<nsIDOMEventListener> mNoUpdateListeners;
nsCOMArray<nsIDOMEventListener> mDownloadingListeners;
nsCOMArray<nsIDOMEventListener> mProgressListeners;
nsCOMArray<nsIDOMEventListener> mCachedListeners;
nsCOMArray<nsIDOMEventListener> mUpdateReadyListeners;
nsCOMPtr<nsIDOMEventListener> mOnCheckingListener;
nsCOMPtr<nsIDOMEventListener> mOnErrorListener;
nsCOMPtr<nsIDOMEventListener> mOnNoUpdateListener;
nsCOMPtr<nsIDOMEventListener> mOnDownloadingListener;
nsCOMPtr<nsIDOMEventListener> mOnProgressListener;
nsCOMPtr<nsIDOMEventListener> mOnCachedListener;
nsCOMPtr<nsIDOMEventListener> mOnUpdateReadyListener;
struct PendingEvent {
nsCOMPtr<nsIDOMEvent> event;
nsCOMPtr<nsIDOMEventListener> listener;
nsCOMArray<nsIDOMEventListener> listeners;
};
nsTArray<PendingEvent> mPendingEvents;
};
#endif

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

@ -46,13 +46,21 @@ include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
test_pendingOfflineLoads.html \
offlineTests.js \
test_badManifestMagic.html \
test_badManifestMime.html \
test_missingFile.html \
test_simpleManifest.html \
test_offlineIFrame.html \
badManifestMagic.cacheManifest \
badManifestMagic.cacheManifest^headers^ \
missingFile.cacheManifest \
missingFile.cacheManifest^headers^ \
simpleManifest.cacheManifest \
simpleManifest.cacheManifest^headers^ \
simpleManifest.notmanifest \
offlineChild.html \
$(NULL)
# XXX: disabled pending fixes
# test_isLocallyAvailable.html \
# test_offlineResources.html
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)

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

@ -0,0 +1,4 @@
# This doesn't start with the magic cache manifest line.
http://localhost:8888/tests/SimpleTest/SimpleTest.js
http://localhost:8888/MochiKit/packed.js
http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js

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

@ -0,0 +1,2 @@
Content-Type: text/cache-manifest

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

@ -0,0 +1,7 @@
CACHE MANIFEST
http://localhost:8888/tests/SimpleTest/SimpleTest.js
http://localhost:8888/MochiKit/packed.js
http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js
# The following item doesn't exist, and will cause an update error.
http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/doesntExist.html

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

@ -0,0 +1,2 @@
Content-Type: text/cache-manifest

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

@ -0,0 +1,34 @@
<html>
<head>
<title></title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<script type="text/javascript">
// This is called by the parent when the offline update is complete
// (since we don't get those events)
function doneLoading()
{
window.top.childFinished();
}
if (OfflineTest.setupChild()) {
// Child frames shouldn't be receiving cache events.
applicationCache.onerror = OfflineTest.failEvent;
applicationCache.onnoupdate = OfflineTest.failEvent;
applicationCache.ondownloading = OfflineTest.failEvent;
applicationCache.onprogress = OfflineTest.failEvent;
applicationCache.onupdateready = OfflineTest.failEvent;
applicationCache.oncached = OfflineTest.failEvent;
}
</script>
</head>
<body>
</body> </html>

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

@ -0,0 +1,211 @@
// Utility functions for offline tests.
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var Cc = Components.classes;
var Ci = Components.interfaces;
var OfflineTest = {
_slaveWindow: null,
// The window where test results should be sent.
_masterWindow: null,
setupChild: function()
{
if (window.parent.OfflineTest.hasSlave()) {
return false;
}
this._slaveWindow = null;
this._masterWindow = window.top;
return true;
},
// Setup the tests. This will reload the current page in a new window
// if necessary.
setup: function()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
if (!window.opener || !window.opener.OfflineTest ||
!window.opener.OfflineTest._isMaster) {
// Offline applications must be toplevel windows and have the
// offline-app permission. Because we were loaded without the
// offline-app permission and (probably) in an iframe, we need to
// enable the pref and spawn a new window to perform the actual
// tests. It will use this window to report successes and
// failures.
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(window.location.href, null, null);
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
// Tests must run as toplevel windows. Open a slave window to run
// the test.
this._isMaster = true;
this._slaveWindow = window.open(window.location, "offlinetest");
this._slaveWindow._OfflineSlaveWindow = true;
return false;
}
this._masterWindow = window.opener;
return true;
},
teardown: function()
{
// Remove the offline-app permission we gave ourselves.
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(window.location.href, null, null);
pm.remove(uri.host, "offline-app");
this.clear();
},
finish: function()
{
SimpleTest.finish();
if (this._masterWindow) {
this._masterWindow.OfflineTest.finish();
window.close();
}
},
hasSlave: function()
{
return (this._slaveWindow != null);
},
//
// Mochitest wrappers - These forward tests to the proper mochitest window.
//
ok: function(condition, name, diag)
{
return this._masterWindow.SimpleTest.ok(condition, name, diag);
},
is: function(a, b, name)
{
return this._masterWindow.SimpleTest.is(a, b, name);
},
clear: function()
{
// Clear the ownership list
var cacheService = Cc["@mozilla.org/network/cache-service;1"]
.getService(Ci.nsICacheService);
var cacheSession = cacheService.createSession("HTTP-offline",
Ci.nsICache.STORE_OFFLINE,
true)
.QueryInterface(Ci.nsIOfflineCacheSession);
// Clear manifest-owned urls
cacheSession.setOwnedKeys(window.location.host,
this.getManifestUrl() + "#manifest", 0, []);
// Clear dynamically-owned urls
cacheSession.setOwnedKeys(window.location.host,
this.getManifestUrl() + "#dynamic", 0, []);
cacheSession.evictUnownedEntries();
},
failEvent: function(e)
{
OfflineTest.ok(false, "Unexpected event: " + e.type);
},
// The offline API as specified has no way to watch the load of a resource
// added with applicationCache.add().
waitForAdd: function(url, onFinished) {
// Check every half second for ten seconds.
var numChecks = 20;
var waitFunc = function() {
var cacheService = Cc["@mozilla.org/network/cache-service;1"]
.getService(Ci.nsICacheService);
var cacheSession = cacheService.createSession("HTTP-offline",
Ci.nsICache.STORE_OFFLINE,
true);
var entry;
try {
var entry = cacheSession.openCacheEntry(url, Ci.nsICache.ACCESS_READ, true);
} catch (e) {
}
if (entry) {
entry.close();
onFinished();
return;
}
if (--numChecks == 0) {
onFinished();
return;
}
setTimeout(OfflineTest.priv(waitFunc), 500);
}
setTimeout(this.priv(waitFunc), 500);
},
getManifestUrl: function()
{
return window.top.document.documentElement.getAttribute("manifest");
},
priv: function(func)
{
var self = this;
return function() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
func();
}
},
checkCache: function(url, expectEntry)
{
var cacheService = Cc["@mozilla.org/network/cache-service;1"]
.getService(Ci.nsICacheService);
var cacheSession = cacheService.createSession("HTTP-offline",
Ci.nsICache.STORE_OFFLINE,
true);
try {
var entry = cacheSession.openCacheEntry(url, Ci.nsICache.ACCESS_READ, true);
if (expectEntry) {
this.ok(true, url + " should exist in the offline cache");
} else {
this.ok(false, url + " should not exist in the offline cache");
}
entry.close();
} catch (e) {
// this constant isn't in Components.results
const kNetBase = 2152398848; // 0x804B0000
var NS_ERROR_CACHE_KEY_NOT_FOUND = kNetBase + 61
if (e.result == NS_ERROR_CACHE_KEY_NOT_FOUND) {
if (expectEntry) {
this.ok(false, url + " should exist in the offline cache");
} else {
this.ok(true, url + " should not exist in the offline cache");
}
} else {
throw e;
}
}
}
};

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

@ -0,0 +1,12 @@
CACHE MANIFEST
http://localhost:8888/tests/SimpleTest/SimpleTest.js
http://localhost:8888/MochiKit/packed.js
http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js
# The following item doesn't have the same scheme as the manifest, and
# will be ignored.
https://localhost:8888/MochiKit/packed.js
# The following item is not a valid URI and will be ignored
bad:/uri/invalid

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

@ -0,0 +1,2 @@
Content-Type: text/cache-manifest

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

@ -0,0 +1,13 @@
CACHE MANIFEST
http://localhost:8888/tests/SimpleTest/SimpleTest.js
http://localhost:8888/MochiKit/packed.js
http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js
# The following item doesn't have the same scheme as the manifest, and
# will be ignored.
https://localhost:8888/MochiKit/packed.js
# The following item is not a valid URI and will be ignored
bad:/uri/invalid

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

@ -0,0 +1,50 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/badManifestMagic.cacheManifest">
<head>
<title>bad manifest magic</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<script type="text/javascript">
var gGotChecking = false;
function handleError() {
OfflineTest.ok(gGotChecking, "Expected checking event");
OfflineTest.ok(true, "Expected error event");
// These items are listed in the manifest, but the error should have
// prevented them from being committed to the cache.
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", false);
OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", false);
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", false);
OfflineTest.teardown();
OfflineTest.finish();
}
if (OfflineTest.setup()) {
// Don't expect a bunch of events.
applicationCache.ondownloading = OfflineTest.failEvent;
applicationCache.onupdateready = OfflineTest.failEvent;
applicationCache.oncached = OfflineTest.failEvent;
applicationCache.onnoupdate = OfflineTest.failEvent;
applicationCache.onprogress = OfflineTest.failEvent;
// ... but expect 'checking' and 'error'
applicationCache.onchecking = function() { gGotChecking = true; };
applicationCache.onerror = OfflineTest.priv(handleError);
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
</body>
</html>

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

@ -0,0 +1,49 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/simpleManifest.notmanifest">
<head>
<title>bad manifest content type</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<script type="text/javascript">
var gGotChecking = false;
function handleError() {
OfflineTest.ok(gGotChecking, "Expected checking event");
OfflineTest.ok(true, "Expected error event");
// These items are listed in the manifest, but the error should have
// prevented them from being committed to the cache.
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", false);
OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", false);
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", false);
OfflineTest.teardown();
OfflineTest.finish();
}
if (OfflineTest.setup()) {
// Don't expect a bunch of events.
applicationCache.ondownloading = OfflineTest.failEvent;
applicationCache.onupdateready = OfflineTest.failEvent;
applicationCache.oncached = OfflineTest.failEvent;
applicationCache.onnoupdate = OfflineTest.failEvent;
applicationCache.onprogress = OfflineTest.failEvent;
// ... but expect 'checking' and 'error'
applicationCache.onchecking = function() { gGotChecking = true; };
applicationCache.onerror = OfflineTest.priv(handleError);
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
</body>
</html>

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

@ -1,94 +0,0 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>navigator.isLocallyAvailable Test</title>
<link rel="offline-resource" href="http://localhost:8888/tests/SimpleTest/SimpleTest.js">
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript">
function run_test()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
// check invalid urls
try {
navigator.isLocallyAvailable("http://different.origin.com", false);
ok(false, "can't check from a different origin");
} catch(e) {
ok(true, "can't check from a different origin");
}
try {
navigator.isLocallyAvailable("ftp://localhost/blah/blah", false);
ok(false, "urls must be http or https");
} catch(e) {
ok(true, "urls must be http or https");
}
// check an URL that should definitely not be there
ok(navigator.isLocallyAvailable("http://localhost:8888/blah/blah/blah",
false) == false,
"unknown item shouldn't be available online");
ok(navigator.isLocallyAvailable("http://localhost:8888/blah/blah/blah",
true) == false,
"unknown item shouldn't be available offline");
// check a URL that should be available on and offline
var url = "http://localhost:8888/tests/SimpleTest/SimpleTest.js";
ok(navigator.isLocallyAvailable(url, false) == true,
url + " should be available online");
ok(navigator.isLocallyAvailable(url, true) == true,
url + " should be available offline");
// Pull it out of the disk cache
var cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
.getService(Components.interfaces.nsICacheService);
var session = cacheService.createSession
("HTTP",
Components.interfaces.nsICache.STORE_ON_DISK,
Components.interfaces.nsICache.STREAM_BASED);
var entry = session.openCacheEntry(url, Components.interfaces.nsICache.ACCESS_WRITE, false);
entry.doom();
entry.close();
// Now it should be available offline but not online
ok(navigator.isLocallyAvailable(url, false) == false,
url + " should not be available online");
ok(navigator.isLocallyAvailable(url, true) == true,
url + " should be available offline");
// Clear the offline cache/ownership on the way out
var cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
.getService(Components.interfaces.nsICacheService);
var cacheSession = cacheService.createSession("HTTP-offline",
Components.interfaces.nsICache.STORE_OFFLINE,
true)
.QueryInterface(Components.interfaces.nsIOfflineCacheSession);
cacheSession.setOwnedKeys(window.location.host,
window.location.protocol + "//" + window.location.host + window.location.pathname,
0, []);
cacheSession.setOwnedKeys(window.location.host, "", 0, []);
cacheSession.evictUnownedEntries();
cacheService.evictEntries(Components.interfaces.nsICache.STORE_OFFLINE);
SimpleTest.finish();
}
// Give the offline resources some time to load.
setTimeout("run_test()", 1000);
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
</body>
</html>

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

@ -0,0 +1,50 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/missingFile.cacheManifest">
<head>
<title>missing manifest file test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<script type="text/javascript">
var gGotChecking = false;
var gGotDownloading = false;
function handleError() {
OfflineTest.ok(gGotChecking, "Expected checking event");
OfflineTest.ok(gGotDownloading, "Expected downloading event");
OfflineTest.ok(true, "Expected error event");
// These items are listed in the manifest, but the error should have
// prevented them from being committed to the cache.
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", false);
OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", false);
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", false);
OfflineTest.teardown();
OfflineTest.finish();
}
if (OfflineTest.setup()) {
// Don't expect any "success" events.
applicationCache.onupdateready = function() { OfflineTest.failEvent("updateready"); }
applicationCache.oncached = function() { OfflineTest.failEvent("cached"); }
applicationCache.onnoupdate = function() { OfflineTest.failEvent("noupdate"); }
applicationCache.ondownloading = function() { gGotDownloading = true; };
applicationCache.onchecking = function() { gGotChecking = true; };
applicationCache.onerror = OfflineTest.priv(handleError);
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
</body>
</html>

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

@ -0,0 +1,47 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/simpleManifest.cacheManifest">
<head>
<title>offline iframe test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<script type="text/javascript">
function childFinished()
{
OfflineTest.teardown();
OfflineTest.finish();
}
function manifestUpdated()
{
// The manifest itself should be in the cache
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/simpleManifest.cacheManifest", true);
// The entries from the manifest should be in the cache
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", true);
OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", true);
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", true);
window.frames["offlineChild"].doneLoading();
}
if (OfflineTest.setup()) {
applicationCache.onerror = OfflineTest.failEvent;
applicationCache.oncached = OfflineTest.priv(manifestUpdated);
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
<iframe name="offlineChild" src="offlineChild.html"></iframe>
</body>
</html>

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

@ -1,150 +0,0 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>navigator.offlineResources Test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript">
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var nsICache = Components.interfaces.nsICache;
var base = window.location.protocol + "//" + window.location.host;
var packedURL = base + "/MochiKit/packed.js";
var simpleTestURL = base + "/tests/SimpleTest/SimpleTest.js";
var thisURL = base + window.location.pathname;
function check_cache(url, expectEntry)
{
var cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
.getService(Components.interfaces.nsICacheService);
var cacheSession = cacheService.createSession("HTTP-offline",
nsICache.STORE_OFFLINE,
true);
try {
var entry = cacheSession.openCacheEntry(url, nsICache.ACCESS_READ, true);
ok(expectEntry == true, url + " should exist in the offline cache");
entry.close();
} catch (e) {
// this constant isn't in Components.results
const kNetBase = 2152398848; // 0x804B0000
var NS_ERROR_CACHE_KEY_NOT_FOUND = kNetBase + 61
if (e.result == NS_ERROR_CACHE_KEY_NOT_FOUND) {
ok(expectEntry == false, url + " should not exist in the offline cache");
} else {
throw e;
}
}
}
function check_list(array)
{
var checkObj = {};
for (var i = 0; i < array.length; i++) {
checkObj[array[i]] = true;
}
for (var i = 0; i < navigator.offlineResources.length; i++) {
ok (navigator.offlineResources[i] in checkObj,
navigator.offlineResources[i] + " should not be in offlineResources");
delete checkObj[navigator.offlineResources[i]];
}
for (var key in checkObj) {
ok(false, key + " was not in navigator.offlineResources");
}
}
function run_eviction()
{
var cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
.getService(Components.interfaces.nsICacheService);
var cacheSession = cacheService.createSession("HTTP-offline",
nsICache.STORE_OFFLINE,
true)
.QueryInterface(Components.interfaces.nsIOfflineCacheSession);
cacheSession.evictUnownedEntries();
}
function clear_cache()
{
var cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
.getService(Components.interfaces.nsICacheService);
cacheService.evictEntries(nsICache.STORE_OFFLINE);
}
function load_done() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
check_list([packedURL, simpleTestURL, thisURL]);
check_cache(packedURL, true);
check_cache(simpleTestURL, true);
check_cache(thisURL, true);
navigator.offlineResources.remove(packedURL);
run_eviction();
check_list([simpleTestURL, thisURL]);
// Make sure the removed entry was evicted but the others weren't
check_cache(packedURL, false);
check_cache(simpleTestURL, true);
check_cache(thisURL, true);
navigator.offlineResources.clear();
run_eviction();
check_list([]);
check_cache(packedURL, false);
check_cache(simpleTestURL, false);
check_cache(thisURL, false);
// Clear out the offline cache on the way out
clear_cache();
SimpleTest.finish();
}
function run_test()
{
// Clear the offline cache to make sure we're the one adding to it
clear_cache();
// Check invalid urls
try {
navigator.offlineResources.add("/blah/blah");
ok(false, "relative URLs cannot be added");
} catch(e) {
ok(true, "relative URLs should throw an error");
}
try {
navigator.offlineResources.add("ftp://localhost/blah/blah");
ok(false, "urls must be http or https");
} catch(e) {
ok(true, "urls must be http or https");
}
// Add the correct resources
navigator.offlineResources.add(packedURL);
navigator.offlineResources.add(simpleTestURL);
navigator.offlineResources.add(thisURL);
// Give the offline resources some time to load; Once pendingOfflineLoads
// is implemented, we can use that instead.
setTimeout("load_done()", 1000);
SimpleTest.waitForExplicitFinish();
}
run_test();
</script>
</head>
<body>
</body>
</html>

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

@ -1,127 +0,0 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>navigator.pendingOfflineLoads Test</title>
<link id="load1" rel="offline-resource" href="http://localhost:8888/unknown1">
<link id="load2" rel="offline-resource" href="http://localhost:8888/MochiKit/packed.js">
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript">
var base = window.location.protocol + "//" + window.location.host;
var thisURL = base + window.location.pathname;
var expectedCompletions = {};
expectedCompletions["http://localhost:8888/unknown1"] = {
"source": document.getElementById("load1"),
"status": 404
};
expectedCompletions["http://localhost:8888/unknown2"] = {
"source": null,
"status": 404
};
expectedCompletions["http://localhost:8888/MochiKit/packed.js"] = {
"source": document.getElementById("load2"),
"status": 200
};
expectedCompletions[thisURL] = {
"source": null,
"status": 200
};
var expectedAdditions = { };
for (var uri in expectedCompletions) {
expectedAdditions[uri] = expectedCompletions[uri];
}
function check_load_added(load)
{
var source = load.source;
var uri = load.uri;
ok(expectedAdditions[uri], uri + " should be in the expected addition list");
ok(expectedAdditions[uri].source == source,
uri + " should come from the expected source");
ok(load.status == 0, "status should be 0");
delete expectedAdditions[uri];
}
function load_requested(e)
{
check_load_added(e.status);
}
function load_completed(e)
{
var load = e.status;
var uri = load.uri;
ok(expectedCompletions[uri],
uri + " should be in the expected completion list");
ok(expectedCompletions[uri].source == load.source,
uri + " should come from the expected source");
ok(expectedCompletions[uri].status == load.status,
uri + " should have a load status of " + expectedCompletions[uri].status);
delete expectedCompletions[uri];
if (navigator.pendingOfflineLoads.length == 0) {
for (var name in expectedAdditions) {
ok(false, name + " did not receive a request event");
}
for (var name in expectedCompletions) {
ok(false, name + " did not receive a completed event");
}
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
// Clear the ownership list
var cacheService = Components.classes["@mozilla.org/network/cache-service;1"]
.getService(Components.interfaces.nsICacheService);
var cacheSession = cacheService.createSession("HTTP-offline",
Components.interfaces.nsICache.STORE_OFFLINE,
true)
.QueryInterface(Components.interfaces.nsIOfflineCacheSession);
cacheSession.setOwnedKeys(window.location.host, thisURL, 0, []);
cacheSession.setOwnedKeys(window.location.host, "", 0, []);
cacheSession.evictUnownedEntries();
SimpleTest.finish();
}
}
function run_test()
{
navigator.pendingOfflineLoads.addEventListener("loadrequested",
load_requested,
false);
navigator.pendingOfflineLoads.addEventListener("loadcompleted",
load_completed,
false);
for (var i = 0; i < navigator.pendingOfflineLoads.length; i++) {
var load = navigator.pendingOfflineLoads[i];
check_load_added(navigator.pendingOfflineLoads[i]);
}
// Add the correct resources
navigator.offlineResources.add("http://localhost:8888/unknown2");
navigator.offlineResources.add(thisURL);
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body onload="run_test()">
</body>
</html>

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

@ -0,0 +1,116 @@
<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/simpleManifest.cacheManifest">
<head>
<title>simple manifest test</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
<script type="text/javascript">
var gGotChecking = false;
var gGotDownloading = false;
function addFinished()
{
// Check that the entry was added successfully
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/EventUtils.js",
true);
OfflineTest.ok(applicationCache.length == 1, "applicationCache should have one dynamic entry");
OfflineTest.ok(applicationCache.item(0) == "http://localhost:8888/tests/SimpleTest/EventUtils.js",
"applicationCache's dynamic entry should be the one we expect");
// Now test that removes work
applicationCache.remove("http://localhost:8888/tests/SimpleTest/EventUtils.js");
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/EventUtils.js",
false);
OfflineTest.ok(applicationCache.length == 0,
"applicationCache should have no dynamic entries");
// We're done
OfflineTest.teardown();
OfflineTest.finish();
}
function secondUpdate()
{
OfflineTest.ok(gGotChecking, "Should get a checking event");
OfflineTest.ok(gGotDownloading, "Should get a downloading event");
// The document that requested the manifest should be in the cache
OfflineTest.checkCache(window.location.href, true);
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", true);
OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", true);
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", true);
// Now add a file using the applicationCache API
applicationCache.add("http://localhost:8888/tests/SimpleTest/EventUtils.js");
// Wait for the add() to be downloaded
OfflineTest.waitForAdd("http://localhost:8888/tests/SimpleTest/EventUtils.js",
OfflineTest.priv(addFinished));
}
function manifestUpdated()
{
OfflineTest.ok(gGotChecking, "Should get a checking event");
OfflineTest.ok(gGotDownloading, "Should get a downloading event");
// The manifest itself should be in the cache
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/simpleManifest.cacheManifest", true);
// The document that requested the manifest should be in the cache
OfflineTest.checkCache(window.location.href, true);
// The entries from the manifest should be in the cache
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", true);
OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", true);
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", true);
// The bad entries from the manifest should not be in the cache
OfflineTest.checkCache("https://localhost:8888/MochiKit/packed.js", false);
OfflineTest.checkCache("bad:/uri/invalid", false);
// Remove items from the cache.
OfflineTest.clear();
// Make sure OfflineTest.clear() properly removed the items
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/SimpleTest.js", false);
OfflineTest.checkCache("http://localhost:8888/MochiKit/packed.js", false);
OfflineTest.checkCache("http://localhost:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", false);
OfflineTest.checkCache("http://localhost:8888/tests/SimpleTest/EventUtils.js",
false);
// Now make sure applicationCache.update() does what we expect.
applicationCache.oncached = OfflineTest.priv(secondUpdate);
gGotChecking = false;
gGotDownloading = false;
applicationCache.update();
}
if (OfflineTest.setup()) {
applicationCache.onerror = OfflineTest.failEvent;
applicationCache.onchecking = function() { gGotChecking = true; };
applicationCache.ondownloading = function() { gGotDownloading = true; };
applicationCache.oncached = OfflineTest.priv(manifestUpdated);
}
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
</body>
</html>

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

@ -42,19 +42,69 @@ interface nsIURI;
interface nsIDOMNode;
interface nsIDOMDocument;
interface nsIDOMLoadStatus;
interface nsIOfflineCacheUpdate;
[scriptable, uuid(e0785ebb-b3a1-426a-a70e-be2b923e973e)]
[scriptable, uuid(0aa38757-999c-44d6-bdb4-7dd32634fa83)]
interface nsIOfflineCacheUpdateObserver : nsISupports {
/**
* There was an error updating the cache.
*
* @param aUpdate
* The nsIOfflineCacheUpdate being processed.
*/
void error(in nsIOfflineCacheUpdate aUpdate);
/**
* The manifest is being checked for updates
*
* @param aUpdate
* The nsIOfflineCacheUpdate being processed.
*/
void checking(in nsIOfflineCacheUpdate aUpdate);
/**
* No update was necessary.
*
* @param aUpdate
* The nsIOfflineCacheUpdate being processed.
*/
void noUpdate(in nsIOfflineCacheUpdate aUpdate);
/**
* Starting to download resources
*
* @param aUpdate
* The nsIOfflineCacheUpdate being processed.
*/
void downloading(in nsIOfflineCacheUpdate aUpdate);
/**
* An item has started downloading.
*
* @param aUpdate
* The nsIOfflineCacheUpdate being processed.
* @param aItem
* load status for the item that is being downloaded.
*/
void itemStarted(in nsIOfflineCacheUpdate aUpdate,
in nsIDOMLoadStatus aItem);
/**
* An item has finished loading.
*
* @param aItem load status for the item that completed.
* @param aUpdate
* The nsIOfflineCacheUpdate being processed.
* @param aItem
* load status for the item that completed.
*/
void itemCompleted(in nsIDOMLoadStatus aItem);
void itemCompleted(in nsIOfflineCacheUpdate aUpdate,
in nsIDOMLoadStatus aItem);
};
/**
* An nsIOfflineCacheUpdate is used to update a domain's offline resources.
* An nsIOfflineCacheUpdate is used to update an application's offline
* resources.
*
* It can be used to perform partial or complete updates.
*
* Each update object maintains a list of nsIDOMLoadStatus items for the
@ -67,6 +117,18 @@ interface nsIOfflineCacheUpdateObserver : nsISupports {
*/
[scriptable, uuid(7dc06ede-1098-4384-b95e-65525ab254c9)]
interface nsIOfflineCacheUpdate : nsISupports {
/**
* Fetch the status of the running update. This will return a value
* defined in nsIDOMOfflineResourceList.
*/
readonly attribute unsigned short status;
/**
* TRUE if the update is being used to add specific resources.
* FALSE if the complete cache update process is happening.
*/
readonly attribute boolean partial;
/**
* The domain being updated, and the domain that will own any URIs added
* with this update.
@ -74,9 +136,14 @@ interface nsIOfflineCacheUpdate : nsISupports {
readonly attribute ACString updateDomain;
/**
* The URI that will own any URIs added by this update
* The manifest for the offline application being updated.
*/
readonly attribute ACString ownerURI;
readonly attribute nsIURI manifestURI;
/**
* TRUE if the cache update completed successfully.
*/
readonly attribute boolean succeeded;
/**
* Initialize the update.
@ -84,29 +151,23 @@ interface nsIOfflineCacheUpdate : nsISupports {
* @param aPartialUpdate
* TRUE if the update should just update the URIs given to it,
* FALSE if all URLs for the owner domain should be added.
* @param aUpdateDomain
* The domain which is being updated, and which will own any
* URIs added.
* @param aOwnerURI
* The owner URI for any URIs added.
* @param aReferrerURI
* @param aManifestURI
* The manifest URI to be checked, or for partial updates the
* manifest that should own resources that are added.
* @param aDocumentURI
* The page that is requesting the update.
*/
void init(in boolean aPartialUpdate,
in ACString aUpdateDomain,
in ACString aOwnerURI,
in nsIURI aReferrerURI);
in nsIURI aManifestURI,
in nsIURI aDocumentURI);
/**
* Add a URI to the offline cache as part of the update.
*
* @param aURI
* The URI to add.
* @param aSource
* The DOM node (<link> tag) associated with this node (for
* implementing nsIDOMLoadStatus).
*/
void addURI(in nsIURI aURI, in nsIDOMNode aSource);
void addDynamicURI(in nsIURI aURI);
/**
* Add the update to the offline update queue. An offline-cache-update-added
@ -114,14 +175,6 @@ interface nsIOfflineCacheUpdate : nsISupports {
*/
void schedule();
/**
* Request that the update be scheduled when a document finishes loading.
*
* @param aDocument
* When this document finishes loading, the update will be scheduled.
*/
void scheduleOnDocumentStop(in nsIDOMDocument aDocument);
/**
* Access to the list of items in the update.
*/
@ -149,11 +202,27 @@ interface nsIOfflineCacheUpdate : nsISupports {
void removeObserver(in nsIOfflineCacheUpdateObserver aObserver);
};
[scriptable, uuid(f99ca10f-5cde-4966-b845-433f2921a201)]
[scriptable, uuid(3abee04b-5bbb-4405-b659-35f780e38da0)]
interface nsIOfflineCacheUpdateService : nsISupports {
/**
* Access to the list of cache updates that have been scheduled.
*/
readonly attribute unsigned long numUpdates;
nsIOfflineCacheUpdate getUpdate(in unsigned long index);
/**
* Schedule a cache update for a given offline manifest. If an
* existing update is scheduled or running, that update will be returned.
* Otherwise a new update will be scheduled.
*/
nsIOfflineCacheUpdate scheduleUpdate(in nsIURI aManifestURI,
in nsIURI aDocumentURI);
/**
* Schedule a cache update for a manifest when the document finishes
* loading.
*/
void scheduleOnDocumentStop(in nsIURI aManifestURI,
in nsIURI aDocumentURI,
in nsIDOMDocument aDocument);
};

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

@ -45,6 +45,7 @@
#include "nsICacheSession.h"
#include "nsICachingChannel.h"
#include "nsIDOMWindow.h"
#include "nsIDOMOfflineResourceList.h"
#include "nsIObserverService.h"
#include "nsIOfflineCacheSession.h"
#include "nsIWebProgress.h"
@ -101,7 +102,6 @@ NS_IMPL_ISUPPORTS6(nsOfflineCacheUpdateItem,
nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate,
nsIURI *aURI,
nsIURI *aReferrerURI,
nsIDOMNode *aSource,
const nsACString &aClientID)
: mURI(aURI)
, mReferrerURI(aReferrerURI)
@ -111,7 +111,6 @@ nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate
, mState(nsIDOMLoadStatus::UNINITIALIZED)
, mBytesRead(0)
{
mSource = do_GetWeakReference(aSource);
}
nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem()
@ -277,15 +276,13 @@ nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel,
}
}
nsCAutoString oldScheme;
mURI->GetScheme(oldScheme);
PRBool match;
rv = newURI->SchemeIs("http", &match);
if (NS_FAILED(rv) || !match) {
if (NS_FAILED(newURI->SchemeIs("https", &match)) ||
!match) {
LOG(("rejected: URL is not of type http\n"));
return NS_ERROR_ABORT;
}
if (NS_FAILED(newURI->SchemeIs(oldScheme.get(), &match)) || !match) {
LOG(("rejected: redirected to a different scheme\n"));
return NS_ERROR_ABORT;
}
// HTTP request headers are not automatically forwarded to the new channel.
@ -308,12 +305,8 @@ nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel,
NS_IMETHODIMP
nsOfflineCacheUpdateItem::GetSource(nsIDOMNode **aSource)
{
if (mSource) {
return CallQueryReferent(mSource.get(), aSource);
} else {
*aSource = nsnull;
return NS_OK;
}
*aSource = nsnull;
return NS_OK;
}
NS_IMETHODIMP
@ -385,6 +378,258 @@ nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus)
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsOfflineManifestItem
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// nsOfflineManifestItem <public>
//-----------------------------------------------------------------------------
nsOfflineManifestItem::nsOfflineManifestItem(nsOfflineCacheUpdate *aUpdate,
nsIURI *aURI,
nsIURI *aReferrerURI,
const nsACString &aClientID)
: nsOfflineCacheUpdateItem(aUpdate, aURI, aReferrerURI, aClientID)
, mParserState(PARSE_INIT)
, mNeedsUpdate(PR_TRUE)
{
}
nsOfflineManifestItem::~nsOfflineManifestItem()
{
}
//-----------------------------------------------------------------------------
// nsOfflineManifestItem <private>
//-----------------------------------------------------------------------------
/* static */
NS_METHOD
nsOfflineManifestItem::ReadManifest(nsIInputStream *aInputStream,
void *aClosure,
const char *aFromSegment,
PRUint32 aOffset,
PRUint32 aCount,
PRUint32 *aBytesConsumed)
{
nsOfflineManifestItem *manifest =
static_cast<nsOfflineManifestItem*>(aClosure);
*aBytesConsumed = aCount;
if (manifest->mParserState == PARSE_ERROR) {
// parse already failed, ignore this
return NS_OK;
}
manifest->mReadBuf.Append(aFromSegment, aCount);
nsCString::const_iterator begin, iter, end;
manifest->mReadBuf.BeginReading(begin);
manifest->mReadBuf.EndReading(end);
for (iter = begin; iter != end; iter++) {
if (*iter == '\r' || *iter == '\n') {
nsresult rv = manifest->HandleManifestLine(begin, iter);
if (NS_FAILED(rv)) {
LOG(("HandleManifestLine failed with 0x%08x", rv));
return NS_ERROR_ABORT;
}
begin = iter;
begin++;
}
}
// any leftovers are saved for next time
manifest->mReadBuf = Substring(begin, end);
return NS_OK;
}
nsresult
nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin,
const nsCString::const_iterator &aEnd)
{
nsCString::const_iterator begin = aBegin;
nsCString::const_iterator end = aEnd;
// all lines ignore trailing spaces and tabs
nsCString::const_iterator last = end;
--last;
while (end != begin && (*last == ' ' || *last == '\t')) {
--end;
--last;
}
if (mParserState == PARSE_INIT) {
// Allow a UTF-8 BOM
if (begin != end && static_cast<unsigned char>(*begin) == 0xef) {
if (++begin == end || static_cast<unsigned char>(*begin) != 0xbb ||
++begin == end || static_cast<unsigned char>(*begin) != 0xbf) {
mParserState = PARSE_ERROR;
return NS_OK;
}
++begin;
}
const nsCSubstring &magic = Substring(begin, end);
if (!magic.EqualsLiteral("CACHE MANIFEST")) {
mParserState = PARSE_ERROR;
return NS_OK;
}
mParserState = PARSE_CACHE_ENTRIES;
return NS_OK;
}
// lines other than the first ignore leading spaces and tabs
while (begin != end && (*begin == ' ' || *begin == '\t'))
begin++;
// ignore blank lines and comments
if (begin == end || *begin == '#')
return NS_OK;
const nsCSubstring &line = Substring(begin, end);
if (line.EqualsLiteral("CACHE:")) {
mParserState = PARSE_CACHE_ENTRIES;
return NS_OK;
}
if (line.EqualsLiteral("FALLBACK:")) {
mParserState = PARSE_FALLBACK_ENTRIES;
return NS_OK;
}
if (line.EqualsLiteral("NETWORK:")) {
mParserState = PARSE_NETWORK_ENTRIES;
return NS_OK;
}
nsresult rv;
switch(mParserState) {
case PARSE_INIT:
case PARSE_ERROR: {
// this should have been dealt with earlier
return NS_ERROR_FAILURE;
}
case PARSE_CACHE_ENTRIES: {
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri), line, nsnull, mURI);
if (NS_FAILED(rv))
break;
nsCAutoString scheme;
uri->GetScheme(scheme);
// Manifest URIs must have the same scheme as the manifest.
PRBool match;
if (NS_FAILED(mURI->SchemeIs(scheme.get(), &match)) || !match)
break;
mExplicitURIs.AppendObject(uri);
break;
}
case PARSE_FALLBACK_ENTRIES:
case PARSE_NETWORK_ENTRIES: {
// we don't currently implement fallbacks or whitelists,
// ignore these for now.
break;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsOfflineManifestItem::OnStartRequest(nsIRequest *aRequest,
nsISupports *aContext)
{
nsresult rv;
nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
NS_ENSURE_SUCCESS(rv, rv);
PRBool succeeded;
rv = channel->GetRequestSucceeded(&succeeded);
NS_ENSURE_SUCCESS(rv, rv);
if (!succeeded) {
LOG(("HTTP request failed"));
mParserState = PARSE_ERROR;
return NS_ERROR_ABORT;
}
nsCAutoString contentType;
rv = channel->GetContentType(contentType);
NS_ENSURE_SUCCESS(rv, rv);
if (!contentType.EqualsLiteral("text/cache-manifest")) {
LOG(("Rejected cache manifest with Content-Type %s (expecting text/cache-manifest)",
contentType.get()));
mParserState = PARSE_ERROR;
return NS_ERROR_ABORT;
}
return nsOfflineCacheUpdateItem::OnStartRequest(aRequest, aContext);
}
NS_IMETHODIMP
nsOfflineManifestItem::OnDataAvailable(nsIRequest *aRequest,
nsISupports *aContext,
nsIInputStream *aStream,
PRUint32 aOffset,
PRUint32 aCount)
{
PRUint32 bytesRead = 0;
aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead);
mBytesRead += bytesRead;
if (mParserState == PARSE_ERROR) {
LOG(("OnDataAvailable is canceling the request due a parse error\n"));
return NS_ERROR_ABORT;
}
LOG(("loaded %u bytes into offline cache [offset=%u]\n",
bytesRead, aOffset));
// All the parent method does is read and discard, don't bother
// chaining up.
return NS_OK;
}
NS_IMETHODIMP
nsOfflineManifestItem::OnStopRequest(nsIRequest *aRequest,
nsISupports *aContext,
nsresult aStatus)
{
// handle any leftover manifest data
nsCString::const_iterator begin, end;
mReadBuf.BeginReading(begin);
mReadBuf.EndReading(end);
nsresult rv = HandleManifestLine(begin, end);
NS_ENSURE_SUCCESS(rv, rv);
if (mBytesRead == 0) {
// we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
// specified.)
mNeedsUpdate = PR_FALSE;
// XXX: The spec calls for a byte-by-byte comparison of the manifest,
// with an exact match skipping the update. Need to implement this
// or we'll be fetching way too often.
}
return nsOfflineCacheUpdateItem::OnStopRequest(aRequest, aContext, aStatus);
}
//-----------------------------------------------------------------------------
// nsOfflineCacheUpdate::nsISupports
//-----------------------------------------------------------------------------
@ -412,9 +657,8 @@ nsOfflineCacheUpdate::~nsOfflineCacheUpdate()
nsresult
nsOfflineCacheUpdate::Init(PRBool aPartialUpdate,
const nsACString &aUpdateDomain,
const nsACString &aOwnerURI,
nsIURI *aReferrerURI)
nsIURI *aManifestURI,
nsIURI *aDocumentURI)
{
nsresult rv;
@ -427,9 +671,40 @@ nsOfflineCacheUpdate::Init(PRBool aPartialUpdate,
LOG(("nsOfflineCacheUpdate::Init [%p]", this));
mPartialUpdate = aPartialUpdate;
mUpdateDomain = aUpdateDomain;
mOwnerURI = aOwnerURI;
mReferrerURI = aReferrerURI;
// Only http and https applications are supported.
PRBool match;
rv = aManifestURI->SchemeIs("http", &match);
NS_ENSURE_SUCCESS(rv, rv);
if (!match) {
rv = aManifestURI->SchemeIs("https", &match);
NS_ENSURE_SUCCESS(rv, rv);
if (!match)
return NS_ERROR_ABORT;
}
mManifestURI = aManifestURI;
rv = mManifestURI->GetHostPort(mUpdateDomain);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString manifestSpec;
rv = mManifestURI->GetSpec(manifestSpec);
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 ref = manifestSpec.FindChar('#');
if (ref != kNotFound)
manifestSpec.Truncate(ref);
mManifestOwnerSpec = manifestSpec;
mManifestOwnerSpec.AppendLiteral("#manifest");
mDynamicOwnerSpec = manifestSpec;
mDynamicOwnerSpec.AppendLiteral("#dynamic");
mDocumentURI = aDocumentURI;
nsCOMPtr<nsICacheService> cacheService =
do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
@ -461,6 +736,11 @@ nsOfflineCacheUpdate::Init(PRBool aPartialUpdate,
mCacheSession = do_QueryInterface(session, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// The manifest implicitly owns itself.
rv = mCacheSession->AddOwnedKey(mUpdateDomain, mManifestOwnerSpec,
manifestSpec);
NS_ENSURE_SUCCESS(rv, rv);
}
mState = STATE_INITIALIZED;
@ -468,6 +748,45 @@ nsOfflineCacheUpdate::Init(PRBool aPartialUpdate,
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::HandleManifest(PRBool *aDoUpdate)
{
// Be pessimistic
*aDoUpdate = PR_FALSE;
PRUint16 status;
nsresult rv = mManifestItem->GetStatus(&status);
NS_ENSURE_SUCCESS(rv, rv);
if (status == 0 || status >= 400 || !mManifestItem->ParseSucceeded()) {
return NS_ERROR_FAILURE;
}
if (!mManifestItem->NeedsUpdate()) {
return NS_OK;
}
// Add items requested by the manifest.
const nsCOMArray<nsIURI> &manifestURIs = mManifestItem->GetExplicitURIs();
for (PRInt32 i = 0; i < manifestURIs.Count(); i++) {
rv = AddURI(manifestURIs[i], mManifestOwnerSpec);
NS_ENSURE_SUCCESS(rv, rv);
}
// The document that requested the manifest is implicitly included
// as part of that manifest update.
rv = AddURI(mDocumentURI, mManifestOwnerSpec);
NS_ENSURE_SUCCESS(rv, rv);
// Add items requested by the script API
rv = AddOwnedItems(mDynamicOwnerSpec);
NS_ENSURE_SUCCESS(rv, rv);
*aDoUpdate = PR_TRUE;
return NS_OK;
}
void
nsOfflineCacheUpdate::LoadCompleted()
{
@ -475,22 +794,55 @@ nsOfflineCacheUpdate::LoadCompleted()
LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this));
if (mState == STATE_CANCELLED) {
Finish();
return;
}
if (mState == STATE_CHECKING) {
// Manifest load finished.
NS_ASSERTION(mManifestItem,
"Must have a manifest item in STATE_CHECKING.");
PRBool doUpdate;
if (NS_FAILED(HandleManifest(&doUpdate))) {
mSucceeded = PR_FALSE;
NotifyError();
Finish();
return;
}
if (!doUpdate) {
mSucceeded = PR_FALSE;
NotifyNoUpdate();
Finish();
return;
}
mState = STATE_DOWNLOADING;
NotifyDownloading();
// Start fetching resources.
ProcessNextURI();
return;
}
// Normal load finished.
nsRefPtr<nsOfflineCacheUpdateItem> item = mItems[mCurrentItem];
mCurrentItem++;
PRUint16 status;
rv = item->GetStatus(&status);
// Check for failures. Only connection or server errors (5XX) will cause
// the update to fail.
if (NS_FAILED(rv) || status == 0 || status >= 500) {
// Only fail updates from this domain. Outside-of-domain updates
// are not guaranteeed to be updated.
nsCAutoString domain;
item->mURI->GetHostPort(domain);
if (domain == mUpdateDomain) {
mSucceeded = PR_FALSE;
}
// Check for failures. 4XX and 5XX errors will cause the update to fail.
if (NS_FAILED(rv) || status == 0 || status >= 400) {
mSucceeded = PR_FALSE;
NotifyError();
Finish();
return;
}
rv = NotifyCompleted(item);
@ -504,17 +856,31 @@ nsOfflineCacheUpdate::Begin()
{
LOG(("nsOfflineCacheUpdate::Begin [%p]", this));
if (!mPartialUpdate) {
// All offline items for a domain should be updated as a group; add
// the other offline items requested for this domain
nsresult rv = AddDomainItems();
NS_ENSURE_SUCCESS(rv, rv);
mCurrentItem = 0;
if (mPartialUpdate) {
mState = STATE_DOWNLOADING;
NotifyDownloading();
ProcessNextURI();
return NS_OK;
}
mState = STATE_RUNNING;
// Start checking the manifest.
nsCOMPtr<nsIURI> uri;
mCurrentItem = 0;
ProcessNextURI();
mManifestItem = new nsOfflineManifestItem(this, mManifestURI,
mDocumentURI, mClientID);
if (!mManifestItem) {
return NS_ERROR_OUT_OF_MEMORY;
}
mState = STATE_CHECKING;
NotifyChecking();
nsresult rv = mManifestItem->OpenChannel();
if (NS_FAILED(rv)) {
LoadCompleted();
}
return NS_OK;
}
@ -555,8 +921,8 @@ nsOfflineCacheUpdate::AddOwnedItems(const nsACString &aOwnerURI)
nsCOMPtr<nsIURI> uri;
if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) {
nsRefPtr<nsOfflineCacheUpdateItem> item =
new nsOfflineCacheUpdateItem(this, uri, mReferrerURI,
nsnull, mClientID);
new nsOfflineCacheUpdateItem(this, uri, mDocumentURI,
mClientID);
if (!item) return NS_ERROR_OUT_OF_MEMORY;
mItems.AppendElement(item);
@ -566,38 +932,16 @@ nsOfflineCacheUpdate::AddOwnedItems(const nsACString &aOwnerURI)
return NS_OK;
}
// Add all URIs needed by this domain to the update
nsresult
nsOfflineCacheUpdate::AddDomainItems()
{
PRUint32 count;
char **uris;
nsresult rv = mMainCacheSession->GetOwnerURIs(mUpdateDomain, &count, &uris);
NS_ENSURE_SUCCESS(rv, rv);
AutoFreeArray autoFree(count, uris);
for (PRUint32 i = 0; i < count; i++) {
const char *ownerURI = uris[i];
// if this update includes changes to this owner URI, ignore the
// set in the database.
if (!mAddedItems || !mOwnerURI.Equals(ownerURI)) {
rv = AddOwnedItems(nsDependentCString(ownerURI));
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::ProcessNextURI()
{
LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, current=%d, numItems=%d]",
this, mCurrentItem, mItems.Length()));
if (mState == STATE_CANCELLED ||
mCurrentItem >= static_cast<PRInt32>(mItems.Length())) {
NS_ASSERTION(mState == STATE_DOWNLOADING,
"ProcessNextURI should only be called from the DOWNLOADING state");
if (mCurrentItem >= static_cast<PRInt32>(mItems.Length())) {
return Finish();
}
@ -609,6 +953,8 @@ nsOfflineCacheUpdate::ProcessNextURI()
}
#endif
NotifyStarted(mItems[mCurrentItem]);
nsresult rv = mItems[mCurrentItem]->OpenChannel();
if (NS_FAILED(rv)) {
LoadCompleted();
@ -619,25 +965,115 @@ nsOfflineCacheUpdate::ProcessNextURI()
}
nsresult
nsOfflineCacheUpdate::NotifyCompleted(nsOfflineCacheUpdateItem *aItem)
nsOfflineCacheUpdate::GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers)
{
nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) {
nsCOMPtr<nsIOfflineCacheUpdateObserver> observer =
do_QueryReferent(mWeakObservers[i]);
if (observer)
observers.AppendObject(observer);
aObservers.AppendObject(observer);
else
mWeakObservers.RemoveObjectAt(i--);
}
for (PRInt32 i = 0; i < mObservers.Count(); i++) {
observers.AppendObject(mObservers[i]);
aObservers.AppendObject(mObservers[i]);
}
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::NotifyError()
{
LOG(("nsOfflineCacheUpdate::NotifyError [%p]", this));
nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
nsresult rv = GatherObservers(observers);
NS_ENSURE_SUCCESS(rv, rv);
for (PRInt32 i = 0; i < observers.Count(); i++) {
observers[i]->ItemCompleted(aItem);
observers[i]->Error(this);
}
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::NotifyChecking()
{
LOG(("nsOfflineCacheUpdate::NotifyChecking [%p]", this));
nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
nsresult rv = GatherObservers(observers);
NS_ENSURE_SUCCESS(rv, rv);
for (PRInt32 i = 0; i < observers.Count(); i++) {
observers[i]->Checking(this);
}
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::NotifyNoUpdate()
{
LOG(("nsOfflineCacheUpdate::NotifyNoUpdate [%p]", this));
nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
nsresult rv = GatherObservers(observers);
NS_ENSURE_SUCCESS(rv, rv);
for (PRInt32 i = 0; i < observers.Count(); i++) {
observers[i]->NoUpdate(this);
}
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::NotifyDownloading()
{
LOG(("nsOfflineCacheUpdate::NotifyDownloading [%p]", this));
nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
nsresult rv = GatherObservers(observers);
NS_ENSURE_SUCCESS(rv, rv);
for (PRInt32 i = 0; i < observers.Count(); i++) {
observers[i]->Downloading(this);
}
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::NotifyStarted(nsOfflineCacheUpdateItem *aItem)
{
LOG(("nsOfflineCacheUpdate::NotifyStarted [%p, %p]", this, aItem));
nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
nsresult rv = GatherObservers(observers);
NS_ENSURE_SUCCESS(rv, rv);
for (PRInt32 i = 0; i < observers.Count(); i++) {
observers[i]->ItemStarted(this, aItem);
}
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::NotifyCompleted(nsOfflineCacheUpdateItem *aItem)
{
LOG(("nsOfflineCacheUpdate::NotifyCompleted [%p, %p]", this, aItem));
nsCOMArray<nsIOfflineCacheUpdateObserver> observers;
nsresult rv = GatherObservers(observers);
NS_ENSURE_SUCCESS(rv, rv);
for (PRInt32 i = 0; i < observers.Count(); i++) {
observers[i]->ItemCompleted(this, aItem);
}
return NS_OK;
@ -653,11 +1089,16 @@ nsOfflineCacheUpdate::Finish()
nsOfflineCacheUpdateService* service =
nsOfflineCacheUpdateService::EnsureService();
if (!service)
return NS_ERROR_FAILURE;
if (!mPartialUpdate) {
if (mSucceeded) {
nsresult rv = mMainCacheSession->MergeTemporaryClientID(mClientID);
if (NS_FAILED(rv))
if (NS_FAILED(rv)) {
NotifyError();
mSucceeded = PR_FALSE;
}
}
if (!mSucceeded) {
@ -668,9 +1109,6 @@ nsOfflineCacheUpdate::Finish()
}
}
if (!service)
return NS_ERROR_FAILURE;
return service->UpdateFinished(this);
}
@ -688,37 +1126,65 @@ nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain)
}
NS_IMETHODIMP
nsOfflineCacheUpdate::GetOwnerURI(nsACString &aOwnerURI)
nsOfflineCacheUpdate::GetStatus(PRUint16 *aStatus)
{
NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
switch (mState) {
case STATE_CHECKING :
return nsIDOMOfflineResourceList::CHECKING;
case STATE_DOWNLOADING :
return nsIDOMOfflineResourceList::DOWNLOADING;
default :
return nsIDOMOfflineResourceList::IDLE;
}
aOwnerURI = mOwnerURI;
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsOfflineCacheUpdate::GetPartial(PRBool *aPartial)
{
*aPartial = mPartialUpdate;
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheUpdate::AddURI(nsIURI *aURI, nsIDOMNode *aSource)
nsOfflineCacheUpdate::GetManifestURI(nsIURI **aManifestURI)
{
NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
if (mState >= STATE_RUNNING)
NS_IF_ADDREF(*aManifestURI = mManifestURI);
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheUpdate::GetSucceeded(PRBool *aSucceeded)
{
NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE);
*aSucceeded = mSucceeded;
return NS_OK;
}
nsresult
nsOfflineCacheUpdate::AddURI(nsIURI *aURI, const nsACString &aOwnerSpec)
{
NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED);
if (mState >= STATE_DOWNLOADING)
return NS_ERROR_NOT_AVAILABLE;
// only http and https urls can be put in the offline cache
PRBool match;
nsresult rv = aURI->SchemeIs("http", &match);
NS_ENSURE_SUCCESS(rv, rv);
// Manifest URIs must have the same scheme as the manifest.
nsCAutoString scheme;
aURI->GetScheme(scheme);
if (!match) {
rv = aURI->SchemeIs("https", &match);
NS_ENSURE_SUCCESS(rv, rv);
if (!match)
return NS_ERROR_ABORT;
}
PRBool match;
if (NS_FAILED(mManifestURI->SchemeIs(scheme.get(), &match)) || !match)
return NS_ERROR_FAILURE;
// Save the cache key as an owned URI
nsCAutoString spec;
rv = aURI->GetSpec(spec);
nsresult rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
// url fragments aren't used in cache keys
@ -727,17 +1193,24 @@ nsOfflineCacheUpdate::AddURI(nsIURI *aURI, nsIDOMNode *aSource)
spec.EndReading(specEnd);
if (FindCharInReadable('#', specStart, specEnd)) {
spec.BeginReading(specEnd);
rv = mCacheSession->AddOwnedKey(mUpdateDomain, mOwnerURI,
rv = mCacheSession->AddOwnedKey(mUpdateDomain, aOwnerSpec,
Substring(specEnd, specStart));
NS_ENSURE_SUCCESS(rv, rv);
} else {
rv = mCacheSession->AddOwnedKey(mUpdateDomain, mOwnerURI, spec);
rv = mCacheSession->AddOwnedKey(mUpdateDomain, aOwnerSpec, spec);
NS_ENSURE_SUCCESS(rv, rv);
}
// Don't fetch the same URI twice.
for (PRUint32 i = 0; i < mItems.Length(); i++) {
PRBool equals;
if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals) {
return NS_OK;
}
}
nsRefPtr<nsOfflineCacheUpdateItem> item =
new nsOfflineCacheUpdateItem(this, aURI, mReferrerURI,
aSource, mClientID);
new nsOfflineCacheUpdateItem(this, aURI, mDocumentURI, mClientID);
if (!item) return NS_ERROR_OUT_OF_MEMORY;
mItems.AppendElement(item);
@ -746,6 +1219,12 @@ nsOfflineCacheUpdate::AddURI(nsIURI *aURI, nsIDOMNode *aSource)
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheUpdate::AddDynamicURI(nsIURI *aURI)
{
return AddURI(aURI, mDynamicOwnerSpec);
}
NS_IMETHODIMP
nsOfflineCacheUpdate::GetCount(PRUint32 *aNumItems)
{
@ -833,21 +1312,6 @@ nsOfflineCacheUpdate::Schedule()
return service->Schedule(this);
}
NS_IMETHODIMP
nsOfflineCacheUpdate::ScheduleOnDocumentStop(nsIDOMDocument *aDocument)
{
LOG(("nsOfflineCacheUpdate::ScheduleOnDocumentStop [%p]", this));
nsOfflineCacheUpdateService* service =
nsOfflineCacheUpdateService::EnsureService();
if (!service) {
return NS_ERROR_FAILURE;
}
return service->ScheduleOnDocumentStop(this, aDocument);
}
//-----------------------------------------------------------------------------
// nsOfflineCacheUpdateService::nsISupports
//-----------------------------------------------------------------------------
@ -910,6 +1374,7 @@ nsOfflineCacheUpdateService::Init()
return NS_OK;
}
/* static */
nsOfflineCacheUpdateService *
nsOfflineCacheUpdateService::GetInstance()
{
@ -931,6 +1396,7 @@ nsOfflineCacheUpdateService::GetInstance()
return gOfflineCacheUpdateService;
}
/* static */
nsOfflineCacheUpdateService *
nsOfflineCacheUpdateService::EnsureService()
{
@ -965,14 +1431,18 @@ nsOfflineCacheUpdateService::Schedule(nsOfflineCacheUpdate *aUpdate)
return NS_OK;
}
nsresult
nsOfflineCacheUpdateService::ScheduleOnDocumentStop(nsOfflineCacheUpdate *aUpdate,
NS_IMETHODIMP
nsOfflineCacheUpdateService::ScheduleOnDocumentStop(nsIURI *aManifestURI,
nsIURI *aDocumentURI,
nsIDOMDocument *aDocument)
{
LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, update=%p, doc=%p]",
this, aUpdate, aDocument));
LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, manifestURI=%p, documentURI=%p doc=%p]",
this, aManifestURI, aDocumentURI, aDocument));
if (!mDocUpdates.Put(aDocument, aUpdate))
PendingUpdate *update = new PendingUpdate();
update->mManifestURI = aManifestURI;
update->mDocumentURI = aDocumentURI;
if (!mDocUpdates.Put(aDocument, update))
return NS_ERROR_FAILURE;
return NS_OK;
@ -1058,6 +1528,55 @@ nsOfflineCacheUpdateService::GetUpdate(PRUint32 aIndex,
return NS_OK;
}
NS_IMETHODIMP
nsOfflineCacheUpdateService::ScheduleUpdate(nsIURI *aManifestURI,
nsIURI *aDocumentURI,
nsIOfflineCacheUpdate **aUpdate)
{
// Check for existing updates
nsresult rv;
for (PRUint32 i = 0; i < mUpdates.Length(); i++) {
nsRefPtr<nsOfflineCacheUpdate> update = mUpdates[i];
PRBool partial;
rv = update->GetPartial(&partial);
NS_ENSURE_SUCCESS(rv, rv);
if (partial) {
// Partial updates aren't considered
continue;
}
nsCOMPtr<nsIURI> manifestURI;
update->GetManifestURI(getter_AddRefs(manifestURI));
if (manifestURI) {
PRBool equals;
rv = manifestURI->Equals(aManifestURI, &equals);
if (equals) {
NS_ADDREF(*aUpdate = update);
return NS_OK;
}
}
}
// There is no existing update, start one.
nsRefPtr<nsOfflineCacheUpdate> update = new nsOfflineCacheUpdate();
if (!update)
return NS_ERROR_OUT_OF_MEMORY;
rv = update->Init(PR_FALSE, aManifestURI, aDocumentURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = update->Schedule();
NS_ENSURE_SUCCESS(rv, rv);
NS_ADDREF(*aUpdate = update);
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsOfflineCacheUpdateService::nsIObserver
//-----------------------------------------------------------------------------
@ -1114,9 +1633,16 @@ nsOfflineCacheUpdateService::OnStateChange(nsIWebProgress* aWebProgress,
LOG(("nsOfflineCacheUpdateService::OnStateChange [%p, doc=%p]",
this, doc.get()));
nsRefPtr<nsOfflineCacheUpdate> update;
if (mDocUpdates.Get(doc, getter_AddRefs(update))) {
Schedule(update);
PendingUpdate *pendingUpdate;
if (mDocUpdates.Get(doc, &pendingUpdate)) {
// Only schedule the update if the document loaded successfull
if (NS_SUCCEEDED(aStatus)) {
nsCOMPtr<nsIOfflineCacheUpdate> update;
ScheduleUpdate(pendingUpdate->mManifestURI,
pendingUpdate->mDocumentURI,
getter_AddRefs(update));
}
mDocUpdates.Remove(doc);
}

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

@ -59,7 +59,7 @@
#include "nsIStreamListener.h"
#include "nsIURI.h"
#include "nsIWebProgressListener.h"
#include "nsRefPtrHashtable.h"
#include "nsClassHashtable.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsWeakReference.h"
@ -84,13 +84,11 @@ public:
nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate,
nsIURI *aURI,
nsIURI *aReferrerURI,
nsIDOMNode *aSource,
const nsACString &aClientID);
~nsOfflineCacheUpdateItem();
virtual ~nsOfflineCacheUpdateItem();
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIURI> mReferrerURI;
nsCOMPtr<nsIWeakReference> mSource;
nsCString mClientID;
nsresult OpenChannel();
@ -100,9 +98,54 @@ private:
nsOfflineCacheUpdate* mUpdate;
nsCOMPtr<nsIChannel> mChannel;
PRUint16 mState;
protected:
PRInt32 mBytesRead;
};
class nsOfflineManifestItem : public nsOfflineCacheUpdateItem
{
public:
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
nsOfflineManifestItem(nsOfflineCacheUpdate *aUpdate,
nsIURI *aURI,
nsIURI *aReferrerURI,
const nsACString &aClientID);
virtual ~nsOfflineManifestItem();
nsCOMArray<nsIURI> &GetExplicitURIs() { return mExplicitURIs; }
PRBool ParseSucceeded()
{ return (mParserState != PARSE_INIT && mParserState != PARSE_ERROR); }
PRBool NeedsUpdate() { return mParserState != PARSE_INIT && mNeedsUpdate; }
private:
static NS_METHOD ReadManifest(nsIInputStream *aInputStream,
void *aClosure,
const char *aFromSegment,
PRUint32 aOffset,
PRUint32 aCount,
PRUint32 *aBytesConsumed);
nsresult HandleManifestLine(const nsCString::const_iterator &aBegin,
const nsCString::const_iterator &aEnd);
enum {
PARSE_INIT,
PARSE_CACHE_ENTRIES,
PARSE_FALLBACK_ENTRIES,
PARSE_NETWORK_ENTRIES,
PARSE_ERROR
} mParserState;
nsCString mReadBuf;
nsCOMArray<nsIURI> mExplicitURIs;
PRBool mNeedsUpdate;
};
class nsOfflineCacheUpdate : public nsIOfflineCacheUpdate
{
public:
@ -119,17 +162,27 @@ public:
void LoadCompleted();
private:
nsresult HandleManifest(PRBool *aDoUpdate);
nsresult AddURI(nsIURI *aURI, const nsACString &aOwnerSpec);
nsresult ProcessNextURI();
nsresult AddOwnedItems(const nsACString &aOwnerURI);
nsresult AddDomainItems();
nsresult NotifyAdded(nsOfflineCacheUpdateItem *aItem);
nsresult GatherObservers(nsCOMArray<nsIOfflineCacheUpdateObserver> &aObservers);
nsresult NotifyError();
nsresult NotifyChecking();
nsresult NotifyNoUpdate();
nsresult NotifyDownloading();
nsresult NotifyStarted(nsOfflineCacheUpdateItem *aItem);
nsresult NotifyCompleted(nsOfflineCacheUpdateItem *aItem);
nsresult Finish();
enum {
STATE_UNINITIALIZED,
STATE_INITIALIZED,
STATE_RUNNING,
STATE_CHECKING,
STATE_DOWNLOADING,
STATE_CANCELLED,
STATE_FINISHED
} mState;
@ -138,8 +191,11 @@ private:
PRBool mPartialUpdate;
PRBool mSucceeded;
nsCString mUpdateDomain;
nsCString mOwnerURI;
nsCOMPtr<nsIURI> mReferrerURI;
nsCOMPtr<nsIURI> mManifestURI;
nsCString mManifestOwnerSpec;
nsCString mDynamicOwnerSpec;
nsCOMPtr<nsIURI> mDocumentURI;
nsCString mClientID;
nsCOMPtr<nsIOfflineCacheSession> mCacheSession;
@ -147,6 +203,8 @@ private:
nsCOMPtr<nsIObserverService> mObserverService;
nsRefPtr<nsOfflineManifestItem> mManifestItem;
/* Items being updated */
PRInt32 mCurrentItem;
nsTArray<nsRefPtr<nsOfflineCacheUpdateItem> > mItems;
@ -173,8 +231,6 @@ public:
nsresult Init();
nsresult Schedule(nsOfflineCacheUpdate *aUpdate);
nsresult ScheduleOnDocumentStop(nsOfflineCacheUpdate *aUpdate,
nsIDOMDocument *aDocument);
nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate);
/**
@ -190,7 +246,12 @@ private:
nsresult ProcessNextUpdate();
nsTArray<nsRefPtr<nsOfflineCacheUpdate> > mUpdates;
nsRefPtrHashtable<nsVoidPtrHashKey, nsOfflineCacheUpdate> mDocUpdates;
struct PendingUpdate {
nsCOMPtr<nsIURI> mManifestURI;
nsCOMPtr<nsIURI> mDocumentURI;
};
nsClassHashtable<nsVoidPtrHashKey, PendingUpdate> mDocUpdates;
PRBool mDisabled;
PRBool mUpdateRunning;