Bug 1355608 - Part 1: Add tools to nsPermissionManager to await permissions becoming avaliable, r=baku

MozReview-Commit-ID: 1HDS8zw6dpF
This commit is contained in:
Michael Layzell 2017-04-11 16:36:37 -04:00
parent 4966d64d16
commit d9adddc93f
3 changed files with 95 additions and 9 deletions

View File

@ -40,11 +40,12 @@
#include "nsToolkitCompsCID.h"
#include "nsIObserverService.h"
#include "nsPrintfCString.h"
#include "mozilla/AbstractThread.h"
static nsPermissionManager *gPermissionManager = nullptr;
using mozilla::dom::ContentParent;
using mozilla::Unused; // ha!
using namespace mozilla;
using namespace mozilla::dom;
static bool
IsChildProcess()
@ -848,6 +849,15 @@ nsPermissionManager::nsPermissionManager()
nsPermissionManager::~nsPermissionManager()
{
// NOTE: Make sure to reject each of the promises in mPermissionKeyPromiseMap
// before destroying.
for (auto iter = mPermissionKeyPromiseMap.Iter(); !iter.Done(); iter.Next()) {
if (iter.Data()) {
iter.Data()->Reject(NS_ERROR_FAILURE, __func__);
}
}
mPermissionKeyPromiseMap.Clear();
RemoveAllFromMemory();
gPermissionManager = nullptr;
}
@ -3044,13 +3054,20 @@ nsPermissionManager::SetPermissionsWithKey(const nsACString& aPermissionKey,
return NS_ERROR_NOT_AVAILABLE;
}
// Record that we have seen the permissions with the given permission key.
if (NS_WARN_IF(mAvailablePermissionKeys.Contains(aPermissionKey))) {
RefPtr<GenericPromise::Private> promise;
bool foundKey = mPermissionKeyPromiseMap.Get(aPermissionKey, getter_AddRefs(promise));
if (promise) {
MOZ_ASSERT(foundKey);
// NOTE: This will resolve asynchronously, so we can mark it as resolved
// now, and be confident that we will have filled in the database before any
// callbacks run.
promise->Resolve(true, __func__);
} else if (foundKey) {
// NOTE: We shouldn't be sent two InitializePermissionsWithKey for the same
// key, but it's possible.
return NS_OK;
}
mAvailablePermissionKeys.PutEntry(aPermissionKey);
mPermissionKeyPromiseMap.Put(aPermissionKey, nullptr);
// Add the permissions locally to our process
for (IPC::Permission& perm : aPerms) {
@ -3171,7 +3188,11 @@ nsPermissionManager::PermissionAvaliable(nsIPrincipal* aPrincipal, const char* a
nsAutoCString permissionKey;
// NOTE: GetKeyForPermission accepts a null aType.
GetKeyForPermission(aPrincipal, aType, permissionKey);
if (!mAvailablePermissionKeys.Contains(permissionKey)) {
// If we have a pending promise for the permission key in question, we don't
// have the permission avaliable, so report a warning and return false.
RefPtr<GenericPromise::Private> promise;
if (!mPermissionKeyPromiseMap.Get(permissionKey, getter_AddRefs(promise)) || promise) {
// Emit a useful diagnostic warning with the permissionKey for the process
// which hasn't received permissions yet.
NS_WARNING(nsPrintfCString("This content process hasn't received the "
@ -3181,3 +3202,49 @@ nsPermissionManager::PermissionAvaliable(nsIPrincipal* aPrincipal, const char* a
}
return true;
}
NS_IMETHODIMP
nsPermissionManager::WhenPermissionsAvailable(nsIPrincipal* aPrincipal,
nsIRunnable* aRunnable)
{
MOZ_ASSERT(aRunnable);
if (!XRE_IsContentProcess()) {
aRunnable->Run();
return NS_OK;
}
nsTArray<RefPtr<GenericPromise>> promises;
for (auto& key : GetAllKeysForPrincipal(aPrincipal)) {
RefPtr<GenericPromise::Private> promise;
if (!mPermissionKeyPromiseMap.Get(key, getter_AddRefs(promise))) {
// In this case we have found a permission which isn't avaliable in the
// content process and hasn't been requested yet. We need to create a new
// promise, and send the request to the parent (if we have not already
// done so).
promise = new GenericPromise::Private(__func__);
mPermissionKeyPromiseMap.Put(key, RefPtr<GenericPromise::Private>(promise).forget());
}
if (promise) {
promises.AppendElement(Move(promise));
}
}
// If all of our permissions are avaliable, immediately run the runnable. This
// avoids any extra overhead during fetch interception which is performance
// sensitive.
if (promises.IsEmpty()) {
aRunnable->Run();
return NS_OK;
}
RefPtr<nsIRunnable> runnable = aRunnable;
GenericPromise::All(AbstractThread::GetCurrent(), promises)->Then(
AbstractThread::GetCurrent(), __func__,
[runnable] () { runnable->Run(); },
[] () {
NS_WARNING("nsPermissionManager permission promise rejected. We're probably shutting down.");
});
return NS_OK;
}

View File

@ -18,6 +18,9 @@
#include "nsHashKeys.h"
#include "nsCOMArray.h"
#include "nsDataHashtable.h"
#include "nsIRunnable.h"
#include "nsRefPtrHashtable.h"
#include "mozilla/MozPromise.h"
namespace mozilla {
class OriginAttributesPattern;
@ -323,6 +326,8 @@ private:
*/
bool PermissionAvaliable(nsIPrincipal* aPrincipal, const char* aType);
nsRefPtrHashtable<nsCStringHashKey, mozilla::GenericPromise::Private> mPermissionKeyPromiseMap;
nsCOMPtr<mozIStorageConnection> mDBConn;
nsCOMPtr<mozIStorageAsyncStatement> mStmtInsert;
nsCOMPtr<mozIStorageAsyncStatement> mStmtDelete;
@ -337,9 +342,6 @@ private:
// An array to store the strings identifying the different types.
nsTArray<nsCString> mTypeArray;
// The base domains which have their permissions loaded in the current process.
nsTHashtable<nsCStringHashKey> mAvailablePermissionKeys;
// Initially, |false|. Set to |true| once shutdown has started, to avoid
// reopening the database.
bool mIsShuttingDown;

View File

@ -36,6 +36,7 @@ interface nsIPrincipal;
interface mozIDOMWindow;
interface nsIPermission;
interface nsISimpleEnumerator;
interface nsIRunnable;
%{ C++
namespace IPC {
@ -326,6 +327,22 @@ interface nsIPermissionManager : nsISupports
* @param aPrincipal The principal to broadcast permissions for.
*/
void broadcastPermissionsForPrincipalToAllContentProcesses(in nsIPrincipal aPrincipal);
/**
* Add a callback which should be run when all permissions are available for
* the given nsIPrincipal. This method invokes the callback runnable
* synchronously when the permissions are already available.
*
* NOTE: This method will not request the permissions be sent by the parent
* process. This should only be used to wait for permissions which may not
* have arrived yet in order to ensure they are present.
*
* @param aPrincipal The principal to wait for permissions to be available for.
* @param aRunnable The runnable to run when permissions are available for the
* given principal.
*/
void whenPermissionsAvailable(in nsIPrincipal aPrincipal,
in nsIRunnable aRunnable);
};
%{ C++