Bug 619494 - Make IndexedDB work in IPC Fennec. r=bent,mfinkle
authorAlon Zakai <azakai@mozilla.com>
Fri, 29 Apr 2011 16:46:20 -0700
changeset 68794 9e358195a677ff5fc5843b45b90552717b11b2ba
parent 68793 8a650b9e55dbc33c75b5ab072cf4e0dd92ffc921
child 68795 4d304d0b409dfb9d4563d3c0779c962720d9474c
push id19751
push userazakai@mozilla.com
push dateSat, 30 Apr 2011 00:08:47 +0000
treeherdermozilla-central@6a11745c96b6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent, mfinkle
bugs619494
milestone6.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 619494 - Make IndexedDB work in IPC Fennec. r=bent,mfinkle
dom/indexedDB/CheckPermissionsHelper.cpp
dom/indexedDB/CheckQuotaHelper.cpp
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IDBFactory.h
dom/indexedDB/Makefile.in
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/Makefile.in
dom/ipc/PContent.ipdl
mobile/app/mobile.js
mobile/chrome/content/IndexedDB.js
mobile/chrome/content/bindings/browser.js
mobile/chrome/content/browser-scripts.js
mobile/chrome/content/browser-ui.js
mobile/chrome/jar.mn
mobile/components/ContentPermissionPrompt.js
mobile/locales/en-US/chrome/browser.properties
--- a/dom/indexedDB/CheckPermissionsHelper.cpp
+++ b/dom/indexedDB/CheckPermissionsHelper.cpp
@@ -115,17 +115,22 @@ CheckPermissionsHelper::Run()
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   PRUint32 permission = mHasPrompted ?
                         mPromptResult :
                         GetIndexedDBPermissions(mASCIIOrigin, mWindow);
 
   nsresult rv;
   if (mHasPrompted) {
-    if (permission != nsIPermissionManager::UNKNOWN_ACTION) {
+    // Add permissions to the database, but only if we are in the parent
+    // process (if we are in the child process, we have already
+    // set the permission when the prompt was shown in the parent, as
+    // we cannot set the permission from the child).
+    if (permission != nsIPermissionManager::UNKNOWN_ACTION &&
+        XRE_GetProcessType() == GeckoProcessType_Default) {
       nsCOMPtr<nsIURI> uri;
       rv = NS_NewURI(getter_AddRefs(uri), mASCIIOrigin);
       NS_ENSURE_SUCCESS(rv, rv);
   
       nsCOMPtr<nsIPermissionManager> permissionManager =
         do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
       NS_ENSURE_STATE(permissionManager);
   
--- a/dom/indexedDB/CheckQuotaHelper.cpp
+++ b/dom/indexedDB/CheckQuotaHelper.cpp
@@ -40,16 +40,17 @@
 #include "CheckQuotaHelper.h"
 
 #include "nsIDOMWindow.h"
 #include "nsIObserverService.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIURI.h"
+#include "nsXULAppAPI.h"
 
 #include "nsContentUtils.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Services.h"
 
 #include "IDBFactory.h"
 
@@ -175,17 +176,22 @@ CheckQuotaHelper::Run()
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (!mHasPrompted) {
     mPromptResult = GetQuotaPermissions(mOrigin, mWindow);
   }
 
   nsresult rv;
   if (mHasPrompted) {
-    if (mPromptResult != nsIPermissionManager::UNKNOWN_ACTION) {
+    // Add permissions to the database, but only if we are in the parent
+    // process (if we are in the child process, we have already
+    // set the permission when the prompt was shown in the parent, as
+    // we cannot set the permission from the child).
+    if (mPromptResult != nsIPermissionManager::UNKNOWN_ACTION &&
+        XRE_GetProcessType() == GeckoProcessType_Default) {
       nsCOMPtr<nsIURI> uri;
       rv = NS_NewURI(getter_AddRefs(uri), mOrigin);
       NS_ENSURE_SUCCESS(rv, rv);
   
       nsCOMPtr<nsIPermissionManager> permissionManager =
         do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
       NS_ENSURE_STATE(permissionManager);
   
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -32,55 +32,62 @@
  * 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 ***** */
 
+#include "base/basictypes.h"
+
 #include "IDBFactory.h"
 
 #include "nsILocalFile.h"
 #include "nsIScriptContext.h"
 
 #include "mozilla/storage.h"
+#include "mozilla/dom/ContentChild.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsEscape.h"
 #include "nsHashKeys.h"
 #include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCID.h"
+#include "nsXULAppAPI.h"
 
 #include "AsyncConnectionHelper.h"
 #include "CheckPermissionsHelper.h"
 #include "DatabaseInfo.h"
 #include "IDBDatabase.h"
 #include "IDBKeyRange.h"
 #include "IndexedDatabaseManager.h"
 #include "LazyIdleThread.h"
+#include "nsIObserverService.h"
 
 #define PREF_INDEXEDDB_QUOTA "dom.indexedDB.warningQuota"
 
 // megabytes
 #define DEFAULT_QUOTA 50
 
 #define BAD_TLS_INDEX (PRUintn)-1
 
 #define DB_SCHEMA_VERSION 4
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
+GeckoProcessType gAllowedProcessType = GeckoProcessType_Invalid;
+
 PRUintn gCurrentDatabaseIndex = BAD_TLS_INDEX;
 
 PRInt32 gIndexedDBQuota = DEFAULT_QUOTA;
 
 class QuotaCallback : public mozIStorageQuotaCallback
 {
 public:
   NS_DECL_ISUPPORTS
@@ -352,22 +359,17 @@ CreateDatabaseConnection(const nsACStrin
                          mozIStorageConnection** aConnection)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!aASCIIOrigin.IsEmpty() && !aName.IsEmpty(), "Bad arguments!");
 
   aDatabaseFilePath.Truncate();
 
   nsCOMPtr<nsIFile> dbDirectory;
-  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-                                       getter_AddRefs(dbDirectory));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = dbDirectory->Append(NS_LITERAL_STRING("indexedDB"));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv = IDBFactory::GetDirectory(getter_AddRefs(dbDirectory));
 
   PRBool exists;
   rv = dbDirectory->Exists(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (exists) {
     PRBool isDirectory;
     rv = dbDirectory->IsDirectory(&isDirectory);
@@ -504,16 +506,21 @@ CreateDatabaseConnection(const nsACStrin
   NS_ENSURE_SUCCESS(rv, rv);
 
   connection.forget(aConnection);
   return NS_OK;
 }
 
 } // anonyomous namespace
 
+IDBFactory::IDBFactory()
+{
+  IDBFactory::NoteUsedByProcessType(XRE_GetProcessType());
+}
+
 // static
 already_AddRefed<nsIIDBFactory>
 IDBFactory::Create(nsPIDOMWindow* aWindow)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Must have a window!");
 
   if (aWindow->IsOuterWindow()) {
@@ -616,26 +623,54 @@ IDBFactory::SetCurrentDatabase(IDBDataba
 // static
 PRUint32
 IDBFactory::GetIndexedDBQuota()
 {
   return PRUint32(PR_MAX(gIndexedDBQuota, 0));
 }
 
 // static
+void
+IDBFactory::NoteUsedByProcessType(GeckoProcessType aProcessType)
+{
+  if (gAllowedProcessType == GeckoProcessType_Invalid) {
+    gAllowedProcessType = aProcessType;
+  } else if (aProcessType != gAllowedProcessType) {
+    NS_RUNTIMEABORT("More than one process type is accessing IndexedDB!");
+  }
+}
+
+// static
+nsresult
+IDBFactory::GetDirectory(nsIFile** aDirectory)
+{
+  nsresult rv;
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, aDirectory);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = (*aDirectory)->Append(NS_LITERAL_STRING("indexedDB"));
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    nsCOMPtr<nsILocalFile> localDirectory =
+      do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+    rv = localDirectory->InitWithPath(
+      ContentChild::GetSingleton()->GetIndexedDBPath());
+    NS_ENSURE_SUCCESS(rv, rv);
+    localDirectory.forget((nsILocalFile**)aDirectory);
+  }
+  return NS_OK;
+}
+
+// static
 nsresult
 IDBFactory::GetDirectoryForOrigin(const nsACString& aASCIIOrigin,
                                   nsIFile** aDirectory)
 {
   nsCOMPtr<nsIFile> directory;
-  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-                                       getter_AddRefs(directory));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = directory->Append(NS_LITERAL_STRING("indexedDB"));
+  nsresult rv = GetDirectory(getter_AddRefs(directory));
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ConvertASCIItoUTF16 originSanitized(aASCIIOrigin);
   originSanitized.ReplaceChar(":/", '+');
 
   rv = directory->Append(originSanitized);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -815,16 +850,24 @@ DOMCI_DATA(IDBFactory, IDBFactory)
 
 NS_IMETHODIMP
 IDBFactory::Open(const nsAString& aName,
                  JSContext* aCx,
                  nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    // Force ContentChild to cache the path from the parent, so that
+    // we do not end up in a side thread that asks for the path (which
+    // would make ContentChild try to send a message in a thread other
+    // than the main one).
+    ContentChild::GetSingleton()->GetIndexedDBPath();
+  }
+
   if (aName.IsEmpty()) {
     return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
   }
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
   NS_ENSURE_TRUE(window, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -41,16 +41,17 @@
 #define mozilla_dom_indexeddb_idbfactory_h__
 
 #include "mozilla/dom/indexedDB/IndexedDatabase.h"
 
 #include "mozIStorageConnection.h"
 #include "nsIIDBFactory.h"
 
 #include "nsIWeakReferenceUtils.h"
+#include "nsXULAppAPI.h"
 
 class nsPIDOMWindow;
 
 BEGIN_INDEXEDDB_NAMESPACE
 
 struct DatabaseInfo;
 class IDBDatabase;
 struct ObjectStoreInfo;
@@ -68,33 +69,43 @@ public:
   GetConnection(const nsAString& aDatabaseFilePath);
 
   static bool
   SetCurrentDatabase(IDBDatabase* aDatabase);
 
   static PRUint32
   GetIndexedDBQuota();
 
+  // Called when a process uses an IndexedDB factory. We only allow
+  // a single process type to use IndexedDB - the chrome/single process
+  // in Firefox, and the child process in Fennec - so access by more
+  // than one process type is a very serious error.
+  static void
+  NoteUsedByProcessType(GeckoProcessType aProcessType);
+
+  static nsresult
+  GetDirectory(nsIFile** aDirectory);
+
   static nsresult
   GetDirectoryForOrigin(const nsACString& aASCIIOrigin,
                         nsIFile** aDirectory);
 
   static nsresult
   LoadDatabaseInformation(mozIStorageConnection* aConnection,
                           PRUint32 aDatabaseId,
                           nsAString& aVersion,
                           ObjectStoreInfoArray& aObjectStores);
 
   static nsresult
   UpdateDatabaseMetadata(DatabaseInfo* aDatabaseInfo,
                          const nsAString& aVersion,
                          ObjectStoreInfoArray& aObjectStores);
 
 private:
-  IDBFactory() { }
+  IDBFactory();
   ~IDBFactory() { }
 
   nsCOMPtr<nsIWeakReference> mWindow;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_idbfactory_h__
--- a/dom/indexedDB/Makefile.in
+++ b/dom/indexedDB/Makefile.in
@@ -113,9 +113,11 @@ XPIDLSRCS = \
   nsIIDBVersionChangeRequest.idl \
   nsIIndexedDatabaseManager.idl \
   $(NULL)
 
 ifdef ENABLE_TESTS
 DIRS += test
 endif
 
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -107,16 +107,18 @@
 using namespace mozilla::ipc;
 using namespace mozilla::net;
 using namespace mozilla::places;
 using namespace mozilla::docshell;
 
 namespace mozilla {
 namespace dom {
 
+nsString* gIndexedDBPath = nsnull;
+
 class MemoryReportRequestChild : public PMemoryReportRequestChild
 {
 public:
     MemoryReportRequestChild();
     virtual ~MemoryReportRequestChild();
 };
 
 MemoryReportRequestChild::MemoryReportRequestChild()
@@ -224,16 +226,18 @@ ContentChild::ContentChild()
 #ifdef ANDROID
  : mScreenSize(0, 0)
 #endif
 {
 }
 
 ContentChild::~ContentChild()
 {
+    delete gIndexedDBPath;
+    gIndexedDBPath = nsnull;
 }
 
 bool
 ContentChild::Init(MessageLoop* aIOLoop,
                    base::ProcessHandle aParentHandle,
                    IPC::Channel* aChannel)
 {
 #ifdef MOZ_WIDGET_GTK2
@@ -665,10 +669,21 @@ ContentChild::RecvFlushMemory(const nsSt
 {
     nsCOMPtr<nsIObserverService> os =
         mozilla::services::GetObserverService();
     if (os)
         os->NotifyObservers(nsnull, "memory-pressure", reason.get());
   return true;
 }
 
+nsString&
+ContentChild::GetIndexedDBPath()
+{
+    if (!gIndexedDBPath) {
+        gIndexedDBPath = new nsString(); // cleaned up in the destructor
+        SendGetIndexedDBDirectory(gIndexedDBPath);
+    }
+
+    return *gIndexedDBPath;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -144,16 +144,20 @@ public:
     virtual bool RecvScreenSizeChanged(const gfxIntSize &size);
 
     virtual bool RecvFlushMemory(const nsString& reason);
 
 #ifdef ANDROID
     gfxIntSize GetScreenSize() { return mScreenSize; }
 #endif
 
+    // Get the directory for IndexedDB files. We query the parent for this and
+    // cache the value
+    nsString &GetIndexedDBPath();
+
 private:
     NS_OVERRIDE
     virtual void ActorDestroy(ActorDestroyReason why);
 
     NS_OVERRIDE
     virtual void ProcessingError(Result what);
 
     /**
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -63,16 +63,18 @@
 #include "nsCExternalHandlerService.h"
 #include "nsFrameMessageManager.h"
 #include "nsIAlertsService.h"
 #include "nsToolkitCompsCID.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIConsoleService.h"
 #include "nsIScriptError.h"
 #include "nsConsoleMessage.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "IDBFactory.h"
 #if defined(MOZ_SYDNEYAUDIO)
 #include "AudioParent.h"
 #endif
 
 #if defined(ANDROID) || defined(LINUX)
 #include <sys/time.h>
 #include <sys/resource.h>
 #endif
@@ -433,16 +435,34 @@ ContentParent::RecvReadPermissions(Infal
     // Ask for future changes
     permissionManager->ChildRequestPermissions();
 #endif
 
     return true;
 }
 
 bool
+ContentParent::RecvGetIndexedDBDirectory(nsString* aDirectory)
+{
+    indexedDB::IDBFactory::NoteUsedByProcessType(GeckoProcessType_Content);
+
+    nsCOMPtr<nsIFile> dbDirectory;
+    nsresult rv = indexedDB::IDBFactory::GetDirectory(getter_AddRefs(dbDirectory));
+
+    if (NS_FAILED(rv)) {
+        NS_ERROR("Failed to get IndexedDB directory");
+        return true;
+    }
+
+    dbDirectory->GetPath(*aDirectory);
+
+    return true;
+}
+
+bool
 ContentParent::RecvSetClipboardText(const nsString& text, const PRInt32& whichClipboard)
 {
     nsresult rv;
     nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
     NS_ENSURE_SUCCESS(rv, true);
 
     nsCOMPtr<nsISupportsString> dataWrapper =
         do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -152,16 +152,19 @@ private:
     virtual bool DeallocPStorage(PStorageParent* aActor);
 
     virtual bool RecvReadPrefsArray(InfallibleTArray<PrefTuple> *retValue);
     virtual bool RecvReadFontList(InfallibleTArray<FontListEntry>* retValue);
 
     void EnsurePrefService();
 
     virtual bool RecvReadPermissions(InfallibleTArray<IPC::Permission>* aPermissions);
+
+    virtual bool RecvGetIndexedDBDirectory(nsString* aDirectory);
+
     virtual bool RecvSetClipboardText(const nsString& text, const PRInt32& whichClipboard);
     virtual bool RecvGetClipboardText(const PRInt32& whichClipboard, nsString* text);
     virtual bool RecvEmptyClipboard();
     virtual bool RecvClipboardHasText(PRBool* hasText);
 
     virtual bool RecvStartVisitedQuery(const IPC::URI& uri);
 
     virtual bool RecvVisitURI(const IPC::URI& uri,
--- a/dom/ipc/Makefile.in
+++ b/dom/ipc/Makefile.in
@@ -96,16 +96,17 @@ LOCAL_INCLUDES += \
 	-I$(srcdir)/../../content/events/src \
 	-I$(srcdir)/../../toolkit/components/places \
 	-I$(topsrcdir)/chrome/src \
 	-I$(topsrcdir)/uriloader/exthandler \
 	-I$(srcdir)/../../netwerk/base/src \
 	-I$(srcdir)/../src/base \
 	-I$(srcdir)/../src/storage \
 	-I$(srcdir)/../../xpcom/base \
+	-I$(srcdir)/../indexedDB \
 	-I$(topsrcdir)/extensions/cookie \
 	$(NULL)
 
 DEFINES += -DBIN_SUFFIX='"$(BIN_SUFFIX)"'
 
 ifdef MOZ_PERMISSIONS
 DEFINES += -DMOZ_PERMISSIONS
 endif
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -178,16 +178,18 @@ parent:
     ConsoleMessage(nsString message);
     ScriptError(nsString message, nsString sourceName, nsString sourceLine,
                 PRUint32 lineNumber, PRUint32 colNumber, PRUint32 flags,
                 nsCString category); 
 
     // nsIPermissionManager messages
     sync ReadPermissions() returns (Permission[] permissions);
 
+    sync GetIndexedDBDirectory() returns (nsString directory);
+
     // These clipboard methods are only really used on Android since
     // the clipboard is not available in the content process.
     SetClipboardText(nsString text, PRInt32 whichClipboard);
     sync GetClipboardText(PRInt32 whichClipboard)
         returns (nsString text);
     EmptyClipboard();
     sync ClipboardHasText()
         returns (PRBool hasText);
--- a/mobile/app/mobile.js
+++ b/mobile/app/mobile.js
@@ -562,17 +562,17 @@ pref("layers.acceleration.disabled", fal
 #else
 pref("layers.acceleration.disabled", true);
 #endif
 
 pref("notification.feature.enabled", true);
 
 // prevent tooltips from showing up
 pref("browser.chrome.toolbar_tips", false);
-pref("indexedDB.feature.enabled", false);
+pref("indexedDB.feature.enabled", true);
 
 // prevent video elements from preloading too much data
 pref("media.preload.default", 1); // default to preload none
 pref("media.preload.auto", 2);    // preload metadata if preload=auto
 
 //  0: don't show fullscreen keyboard
 //  1: always show fullscreen keyboard
 // -1: show fullscreen keyboard based on threshold pref
new file mode 100644
--- /dev/null
+++ b/mobile/chrome/content/IndexedDB.js
@@ -0,0 +1,78 @@
+/**
+ * Helper class for IndexedDB, parent part. Listens to
+ * messages from the child and shows prompts for them.
+ */
+let IndexedDB = {
+  _permissionsPrompt: "indexedDB-permissions-prompt",
+  _permissionsResponse: "indexedDB-permissions-response",
+
+  _quotaPrompt: "indexedDB-quota-prompt",
+  _quotaResponse: "indexedDB-quota-response",
+  _quotaCancel: "indexedDB-quota-cancel",
+
+  _notificationIcon: "indexedDB-notification-icon",
+
+  receiveMessage: function(aMessage) {
+    switch (aMessage.name) {
+      case "IndexedDB:Prompt":
+        this.showPrompt(aMessage);
+    }
+  },
+
+  showPrompt: function(aMessage) {
+    let browser = aMessage.target;
+    let payload = aMessage.json;
+    let host = payload.host;
+    let topic = payload.topic;
+    let type;
+
+    if (topic == this._permissionsPrompt) {
+      type = "indexedDB";
+      payload.responseTopic = this._permissionsResponse;
+    } else if (topic == this._quotaPrompt) {
+      type = "indexedDBQuota";
+      payload.responseTopic = this._quotaResponse;
+    } else if (topic == this._quotaCancel) {
+      payload.permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+      browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
+      // XXX Need to actually save this?
+      return;
+    }
+
+    let prompt = Cc["@mozilla.org/content-permission/prompt;1"].createInstance(Ci.nsIContentPermissionPrompt);
+
+    // If the user waits a long time before responding, we default to UNKNOWN_ACTION.
+    let timeoutId = setTimeout(function() {
+      payload.permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+      browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
+      timeoutId = null;
+    }, 30000);
+ 
+    function checkTimeout() {
+      if (timeoutId === null) return true;
+      clearTimeout(timeoutId);
+      timeoutId = null;
+      return false;
+    }
+
+    prompt.prompt({
+      type: type,
+      uri: Services.io.newURI(payload.location, null, null),
+      window: null,
+      element: aMessage.target,
+
+      cancel: function() {
+        if (checkTimeout()) return;
+        payload.permission = Ci.nsIPermissionManager.DENY_ACTION;
+        browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
+      },
+
+      allow: function() {
+        if (checkTimeout()) return;
+        payload.permission = Ci.nsIPermissionManager.ALLOW_ACTION;
+        browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
+      },
+    });
+  },
+};
+
--- a/mobile/chrome/content/bindings/browser.js
+++ b/mobile/chrome/content/bindings/browser.js
@@ -448,8 +448,85 @@ let ContentActive =  {
       case "Content:Activate":
         docShell.isActive = true;
         break;
     }
   }
 };
 
 ContentActive.init();
+
+/**
+ * Helper class for IndexedDB, child part. Listens using
+ * the observer service for events regarding IndexedDB
+ * prompts, and sends messages to the parent to actually
+ * show the prompts.
+ */
+let IndexedDB = {
+  _permissionsPrompt: "indexedDB-permissions-prompt",
+  _permissionsResponse: "indexedDB-permissions-response",
+
+  _quotaPrompt: "indexedDB-quota-prompt",
+  _quotaResponse: "indexedDB-quota-response",
+  _quotaCancel: "indexedDB-quota-cancel",
+
+  waitingObservers: [],
+
+  init: function IndexedDBPromptHelper_init() {
+    let os = Services.obs;
+    os.addObserver(this, this._permissionsPrompt, false);
+    os.addObserver(this, this._quotaPrompt, false);
+    os.addObserver(this, this._quotaCancel, false);
+    addMessageListener("IndexedDB:Response", this);
+  },
+
+  observe: function IndexedDBPromptHelper_observe(aSubject, aTopic, aData) {
+    if (aTopic != this._permissionsPrompt && aTopic != this._quotaPrompt && aTopic != this._quotaCancel) {
+      throw new Error("Unexpected topic!");
+    }
+
+    let requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
+    let observer = requestor.getInterface(Ci.nsIObserver);
+
+    let contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+    let contentDocument = contentWindow.document;
+
+    if (aTopic == this._quotaCancel) {
+      observer.observe(null, this._quotaResponse, Ci.nsIPermissionManager.UNKNOWN_ACTION);
+      return;
+    }
+
+    // Remote to parent
+    sendAsyncMessage("IndexedDB:Prompt", {
+      topic: aTopic,
+      host: contentDocument.documentURIObject.asciiHost,
+      location: contentDocument.location.toString(),
+      data: aData,
+      observerId: this.addWaitingObserver(observer),
+    });
+  },
+
+  receiveMessage: function(aMessage) {
+    let payload = aMessage.json;
+    switch (aMessage.name) {
+      case "IndexedDB:Response":
+        let observer = this.getAndRemoveWaitingObserver(payload.observerId);
+        observer.observe(null, payload.responseTopic, payload.permission);
+    }
+  },
+
+  addWaitingObserver: function(aObserver) {
+    let observerId = 0;
+    while (observerId in this.waitingObservers)
+      observerId++;
+    this.waitingObservers[observerId] = aObserver;
+    return observerId;
+  },
+
+  getAndRemoveWaitingObserver: function(aObserverId) {
+    let observer = this.waitingObservers[aObserverId];
+    delete this.waitingObservers[aObserverId];
+    return observer;
+  },
+};
+
+IndexedDB.init();
+
--- a/mobile/chrome/content/browser-scripts.js
+++ b/mobile/chrome/content/browser-scripts.js
@@ -95,16 +95,17 @@ XPCOMUtils.defineLazyGetter(this, "Commo
   ["BookmarkPopup", "chrome://browser/content/BookmarkPopup.js"],
   ["CommandUpdater", "chrome://browser/content/commandUtil.js"],
   ["ContextCommands", "chrome://browser/content/ContextCommands.js"],
   ["ConsoleView", "chrome://browser/content/console.js"],
   ["DownloadsView", "chrome://browser/content/downloads.js"],
   ["ExtensionsView", "chrome://browser/content/extensions.js"],
   ["MenuListHelperUI", "chrome://browser/content/MenuListHelperUI.js"],
   ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
+  ["IndexedDB", "chrome://browser/content/IndexedDB.js"],
   ["PreferencesView", "chrome://browser/content/preferences.js"],
   ["Sanitizer", "chrome://browser/content/sanitize.js"],
   ["SelectHelperUI", "chrome://browser/content/SelectHelperUI.js"],
   ["SharingUI", "chrome://browser/content/SharingUI.js"],
 #ifdef MOZ_SERVICES_SYNC
   ["WeaveGlue", "chrome://browser/content/sync.js"],
 #endif
   ["SSLExceptions", "chrome://browser/content/exceptions.js"]
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -482,16 +482,21 @@ var BrowserUI = {
 
       setTimeout(function() {
         let event = document.createEvent("Events");
         event.initEvent("UIReadyDelayed", true, false);
         window.dispatchEvent(event);
       }, 0);
     });
 
+    // Only load IndexedDB.js when we actually need it. A general fix will happen in bug 647079.
+    messageManager.addMessageListener("IndexedDB:Prompt", function(aMessage) {
+      return IndexedDB.receiveMessage(aMessage);
+    });
+
     // Delay the panel UI and Sync initialization.
     window.addEventListener("UIReadyDelayed", function(aEvent) {
       window.removeEventListener(aEvent.type, arguments.callee, false);
 
       // We unhide the panelUI so the XBL and settings can initialize
       Elements.panelUI.hidden = false;
 
       // Login Manager and Form History initialization
--- a/mobile/chrome/jar.mn
+++ b/mobile/chrome/jar.mn
@@ -24,16 +24,17 @@ chrome.jar:
 * content/browser-scripts.js           (content/browser-scripts.js)
 * content/common-ui.js                 (content/common-ui.js)
 * content/AlertsHelper.js              (content/AlertsHelper.js)
   content/AppMenu.js                   (content/AppMenu.js)
   content/AwesomePanel.js              (content/AwesomePanel.js)
   content/BookmarkHelper.js            (content/BookmarkHelper.js)
   content/BookmarkPopup.js             (content/BookmarkPopup.js)
 * content/ContextCommands.js           (content/ContextCommands.js)
+  content/IndexedDB.js                 (content/IndexedDB.js)
   content/MenuListHelperUI.js          (content/MenuListHelperUI.js)
   content/OfflineApps.js               (content/OfflineApps.js)
   content/SelectHelperUI.js            (content/SelectHelperUI.js)
   content/SharingUI.js                 (content/SharingUI.js)
 * content/content.js                   (content/content.js)
   content/commandUtil.js               (content/commandUtil.js)
 * content/bindings.xml                 (content/bindings.xml)
   content/tabs.xml                     (content/tabs.xml)
--- a/mobile/components/ContentPermissionPrompt.js
+++ b/mobile/components/ContentPermissionPrompt.js
@@ -24,17 +24,18 @@ function setPagePermission(type, uri, al
     
   contentPrefs.setPref(uri, contentPrefName, count);
   if (count == kCountBeforeWeRemember)
     pm.add(uri, type, Ci.nsIPermissionManager.ALLOW_ACTION);
   else if (count == -kCountBeforeWeRemember)
     pm.add(uri, type, Ci.nsIPermissionManager.DENY_ACTION);
 }
 
-const kEntities = { "geolocation": "geolocation", "desktop-notification": "desktopNotification" };
+const kEntities = { "geolocation": "geolocation", "desktop-notification": "desktopNotification",
+                    "indexedDB": "offlineApps", "indexedDBQuota": "indexedDBQuota" };
 
 function ContentPermissionPrompt() {}
 
 ContentPermissionPrompt.prototype = {
   classID: Components.ID("{C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
 
--- a/mobile/locales/en-US/chrome/browser.properties
+++ b/mobile/locales/en-US/chrome/browser.properties
@@ -159,16 +159,25 @@ typeError=Error:
 typeWarning=Warning:
 
 # Offline web applications
 offlineApps.available=%S wants to store data on your computer for offline use.
 offlineApps.allow=Allow
 offlineApps.never=Don't Allow
 offlineApps.notNow=Not Now
 
+# New-style ContentPermissionPrompt values
+offlineApps.dontAllow=Don't Allow
+offlineApps.siteWantsTo=%S wants to store data on your computer for offline use.
+
+# IndexedDB Quota increases
+indexedDBQuota.allow=Allow
+indexedDBQuota.dontAllow=Don't Allow
+indexedDBQuota.siteWantsTo=%S wants to store a lot of data on your computer for offline use.
+
 # Bookmark List
 bookmarkList.desktop=Desktop Bookmarks
 
 # Closing Tabs
 tabs.closeWarningTitle=Confirm close
 
 # LOCALIZATION NOTE (tabs.closeWarning): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals