Bug 619494 - Make IndexedDB work in IPC Fennec. r=bent,mfinkle

This commit is contained in:
Alon Zakai 2011-04-29 16:46:20 -07:00
parent 85ef3b518f
commit b8874744ed
19 changed files with 300 additions and 16 deletions

View File

@ -120,7 +120,12 @@ CheckPermissionsHelper::Run()
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);

View File

@ -45,6 +45,7 @@
#include "nsIPrincipal.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIURI.h"
#include "nsXULAppAPI.h"
#include "nsContentUtils.h"
#include "nsNetUtil.h"
@ -180,7 +181,12 @@ CheckQuotaHelper::Run()
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);

View File

@ -37,12 +37,15 @@
*
* ***** 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"
@ -54,6 +57,7 @@
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsXPCOMCID.h"
#include "nsXULAppAPI.h"
#include "AsyncConnectionHelper.h"
#include "CheckPermissionsHelper.h"
@ -62,6 +66,7 @@
#include "IDBKeyRange.h"
#include "IndexedDatabaseManager.h"
#include "LazyIdleThread.h"
#include "nsIObserverService.h"
#define PREF_INDEXEDDB_QUOTA "dom.indexedDB.warningQuota"
@ -76,6 +81,8 @@ USING_INDEXEDDB_NAMESPACE
namespace {
GeckoProcessType gAllowedProcessType = GeckoProcessType_Invalid;
PRUintn gCurrentDatabaseIndex = BAD_TLS_INDEX;
PRInt32 gIndexedDBQuota = DEFAULT_QUOTA;
@ -357,12 +364,7 @@ CreateDatabaseConnection(const nsACString& aASCIIOrigin,
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);
@ -509,6 +511,11 @@ CreateDatabaseConnection(const nsACString& aASCIIOrigin,
} // anonyomous namespace
IDBFactory::IDBFactory()
{
IDBFactory::NoteUsedByProcessType(XRE_GetProcessType());
}
// static
already_AddRefed<nsIIDBFactory>
IDBFactory::Create(nsPIDOMWindow* aWindow)
@ -620,17 +627,45 @@ 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);
@ -820,6 +855,14 @@ IDBFactory::Open(const nsAString& aName,
{
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;
}

View File

@ -46,6 +46,7 @@
#include "nsIIDBFactory.h"
#include "nsIWeakReferenceUtils.h"
#include "nsXULAppAPI.h"
class nsPIDOMWindow;
@ -73,6 +74,16 @@ public:
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);
@ -89,7 +100,7 @@ public:
ObjectStoreInfoArray& aObjectStores);
private:
IDBFactory() { }
IDBFactory();
~IDBFactory() { }
nsCOMPtr<nsIWeakReference> mWindow;

View File

@ -118,4 +118,6 @@ ifdef ENABLE_TESTS
DIRS += test
endif
include $(topsrcdir)/config/config.mk
include $(topsrcdir)/ipc/chromium/chromium-config.mk
include $(topsrcdir)/config/rules.mk

View File

@ -112,6 +112,8 @@ using namespace mozilla::docshell;
namespace mozilla {
namespace dom {
nsString* gIndexedDBPath = nsnull;
class MemoryReportRequestChild : public PMemoryReportRequestChild
{
public:
@ -229,6 +231,8 @@ ContentChild::ContentChild()
ContentChild::~ContentChild()
{
delete gIndexedDBPath;
gIndexedDBPath = nsnull;
}
bool
@ -670,5 +674,16 @@ ContentChild::RecvFlushMemory(const nsString& reason)
return true;
}
nsString&
ContentChild::GetIndexedDBPath()
{
if (!gIndexedDBPath) {
gIndexedDBPath = new nsString(); // cleaned up in the destructor
SendGetIndexedDBDirectory(gIndexedDBPath);
}
return *gIndexedDBPath;
}
} // namespace dom
} // namespace mozilla

View File

@ -149,6 +149,10 @@ public:
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);

View File

@ -68,6 +68,8 @@
#include "nsIConsoleService.h"
#include "nsIScriptError.h"
#include "nsConsoleMessage.h"
#include "nsAppDirectoryServiceDefs.h"
#include "IDBFactory.h"
#if defined(MOZ_SYDNEYAUDIO)
#include "AudioParent.h"
#endif
@ -437,6 +439,24 @@ ContentParent::RecvReadPermissions(InfallibleTArray<IPC::Permission>* aPermissio
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)
{

View File

@ -157,6 +157,9 @@ private:
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();

View File

@ -101,6 +101,7 @@ LOCAL_INCLUDES += \
-I$(srcdir)/../src/base \
-I$(srcdir)/../src/storage \
-I$(srcdir)/../../xpcom/base \
-I$(srcdir)/../indexedDB \
-I$(topsrcdir)/extensions/cookie \
$(NULL)

View File

@ -183,6 +183,8 @@ parent:
// 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);

View File

@ -567,7 +567,7 @@ 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

View File

@ -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);
},
});
},
};

View File

@ -453,3 +453,80 @@ let ContentActive = {
};
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();

View File

@ -100,6 +100,7 @@ XPCOMUtils.defineLazyGetter(this, "CommonUI", function() {
["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"],

View File

@ -487,6 +487,11 @@ var BrowserUI = {
}, 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);

View File

@ -29,6 +29,7 @@ chrome.jar:
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)

View File

@ -29,7 +29,8 @@ function setPagePermission(type, uri, allow) {
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() {}

View File

@ -164,6 +164,15 @@ 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