Bug 1587743 - Part 1: Pre-compute the delegated permissions for the top-level content and store it in the WindowContext. r=baku,nika
authorTim Huang <tihuang@mozilla.com>
Fri, 12 Jun 2020 16:31:49 +0000
changeset 535503 44e71580890195d163912b96f41ef09e32570ec8
parent 535502 a890be633ec35c79b8c8fe68e8b32ab7c991ff83
child 535504 a2bec965260d58e8f5bd814c179444875d9962cf
push id37501
push usernbeleuzu@mozilla.com
push dateSat, 13 Jun 2020 03:21:52 +0000
treeherdermozilla-central@80b6f21783a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, nika
bugs1587743
milestone79.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 1587743 - Part 1: Pre-compute the delegated permissions for the top-level content and store it in the WindowContext. r=baku,nika In order to delegate the permission to the top-level window, in this patch, we pre-compute the permissions of the top-level context and set them to the top-level WindowContext. So, the cross-origin iframe can know the permission of the top-level window through the WindowContext. Thus, the permission can be delegated in Fission. Differential Revision: https://phabricator.services.mozilla.com/D79132
docshell/base/WindowContext.cpp
docshell/base/WindowContext.h
dom/base/nsGlobalWindowInner.cpp
dom/ipc/PContent.ipdl
extensions/permissions/PermissionDelegateHandler.cpp
extensions/permissions/PermissionDelegateHandler.h
extensions/permissions/PermissionDelegateIPCUtils.h
extensions/permissions/moz.build
--- a/docshell/base/WindowContext.cpp
+++ b/docshell/base/WindowContext.cpp
@@ -161,16 +161,30 @@ bool WindowContext::CanSet(FieldIndex<ID
   return CheckOnlyOwningProcessCanSet(aSource);
 }
 
 bool WindowContext::CanSet(FieldIndex<IDX_AutoplayPermission>,
                            const uint32_t& aValue, ContentParent* aSource) {
   return CheckOnlyOwningProcessCanSet(aSource);
 }
 
+bool WindowContext::CanSet(
+    FieldIndex<IDX_DelegatedPermissions>,
+    const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+    ContentParent* aSource) {
+  return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(
+    FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
+    const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+    ContentParent* aSource) {
+  return CheckOnlyOwningProcessCanSet(aSource);
+}
+
 void WindowContext::CreateFromIPC(IPCInitializer&& aInit) {
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess(),
                      "Should be a WindowGlobalParent in the parent");
 
   RefPtr<BrowsingContext> bc = BrowsingContext::Get(aInit.mBrowsingContextId);
   MOZ_RELEASE_ASSERT(bc);
 
   if (bc->IsDiscarded()) {
--- a/docshell/base/WindowContext.h
+++ b/docshell/base/WindowContext.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_WindowContext_h
 #define mozilla_dom_WindowContext_h
 
+#include "mozilla/PermissionDelegateHandler.h"
 #include "mozilla/Span.h"
 #include "mozilla/dom/MaybeDiscarded.h"
 #include "mozilla/dom/SyncedContext.h"
 
 namespace mozilla {
 namespace dom {
 
 class WindowGlobalParent;
@@ -40,17 +41,21 @@ class BrowsingContextGroup;
   FIELD(IsSecureContext, bool)                                         \
   /* Mixed-Content: If the corresponding document URI is potentially   \
    * trustworthy, then this flag is true. */                           \
   FIELD(IsPotentiallyTrustWorthy, bool)                                \
   /* Whether the user has overriden the mixed content blocker to allow \
    * mixed content loads to happen */                                  \
   FIELD(AllowMixedContent, bool)                                       \
   FIELD(EmbedderPolicy, nsILoadInfo::CrossOriginEmbedderPolicy)        \
-  FIELD(AutoplayPermission, uint32_t)
+  FIELD(AutoplayPermission, uint32_t)                                  \
+  FIELD(DelegatedPermissions,                                          \
+        PermissionDelegateHandler::DelegatedPermissionList)            \
+  FIELD(DelegatedExactHostMatchPermissions,                            \
+        PermissionDelegateHandler::DelegatedPermissionList)
 
 class WindowContext : public nsISupports, public nsWrapperCache {
   MOZ_DECL_SYNCED_CONTEXT(WindowContext, MOZ_EACH_WC_FIELD)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WindowContext)
 
  public:
@@ -156,16 +161,22 @@ class WindowContext : public nsISupports
   bool CanSet(FieldIndex<IDX_IsSecureContext>, const bool& aIsSecureContext,
               ContentParent* aSource);
   bool CanSet(FieldIndex<IDX_AutoplayPermission>, const uint32_t& aValue,
               ContentParent* aSource);
   bool CanSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
               const bool& aSHEntryHasUserInteraction, ContentParent* aSource) {
     return true;
   }
+  bool CanSet(FieldIndex<IDX_DelegatedPermissions>,
+              const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+              ContentParent* aSource);
+  bool CanSet(FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
+              const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+              ContentParent* aSource);
 
   // Overload `DidSet` to get notifications for a particular field being set.
   //
   // You can also overload the variant that gets the old value if you need it.
   template <size_t I>
   void DidSet(FieldIndex<I>) {}
   template <size_t I, typename T>
   void DidSet(FieldIndex<I>, T&& aOldValue) {}
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -1576,16 +1576,23 @@ void nsGlobalWindowInner::InitDocumentDe
   ClearDocumentDependentSlots(aCx);
 
   if (!mWindowGlobalChild) {
     mWindowGlobalChild = WindowGlobalChild::Create(this);
   }
 
   UpdateAutoplayPermission();
 
+  RefPtr<PermissionDelegateHandler> permDelegateHandler =
+      mDoc->GetPermissionDelegateHandler();
+
+  if (permDelegateHandler) {
+    permDelegateHandler->PopulateAllDelegatedPermissions();
+  }
+
   if (mWindowGlobalChild && GetBrowsingContext()) {
     GetBrowsingContext()->NotifyResetUserGestureActivation();
   }
 
 #if defined(MOZ_WIDGET_ANDROID)
   // When we insert the new document to the window in the top-level browsing
   // context, we should reset the status of the request which is used for the
   // previous document.
@@ -4959,16 +4966,24 @@ nsresult nsGlobalWindowInner::Observe(ns
       return NS_OK;
     }
 
     nsAutoCString type;
     perm->GetType(type);
     if (type == NS_LITERAL_CSTRING("autoplay-media")) {
       UpdateAutoplayPermission();
     }
+
+    RefPtr<PermissionDelegateHandler> permDelegateHandler =
+        mDoc->GetPermissionDelegateHandler();
+
+    if (permDelegateHandler) {
+      permDelegateHandler->UpdateDelegatedPermission(type);
+    }
+
     return NS_OK;
   }
 
   if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
     MOZ_ASSERT(!NS_strcmp(aData, u"intl.accept_languages"));
 
     // The user preferred languages have changed, we need to fire an event on
     // Window object and invalidate the cache for navigator.languages. It is
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -120,16 +120,17 @@ using mozilla::dom::MediaSessionAction f
 using mozilla::dom::MediaSessionPlaybackState from "mozilla/dom/MediaSessionIPCUtils.h";
 using refcounted class nsDocShellLoadState from "nsDocShellLoadState.h";
 using mozilla::dom::ServiceWorkerShutdownState::Progress from "mozilla/dom/ServiceWorkerShutdownState.h";
 using mozilla::ContentBlockingNotifier::StorageAccessPermissionGrantedReason from "mozilla/ContentBlockingNotifier.h";
 using mozilla::ContentBlockingNotifier::BlockingDecision from "mozilla/ContentBlockingNotifier.h";
 using mozilla::ContentBlocking::StorageAccessPromptChoices from "mozilla/ContentBlocking.h";
 using JSActorMessageKind from "mozilla/dom/JSActor.h";
 using JSActorMessageMeta from "mozilla/dom/PWindowGlobal.h";
+using mozilla::PermissionDelegateHandler::DelegatedPermissionList from "mozilla/PermissionDelegateIPCUtils.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
--- a/extensions/permissions/PermissionDelegateHandler.cpp
+++ b/extensions/permissions/PermissionDelegateHandler.cpp
@@ -10,16 +10,17 @@
 #include "nsPIDOMWindow.h"
 #include "nsIPrincipal.h"
 #include "nsContentPermissionHelper.h"
 
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/StaticPrefs_permissions.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/FeaturePolicyUtils.h"
+#include "mozilla/dom/WindowContext.h"
 #include "mozilla/PermissionManager.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
 typedef PermissionDelegateHandler::PermissionDelegatePolicy DelegatePolicy;
 typedef PermissionDelegateHandler::PermissionDelegateInfo DelegateInfo;
@@ -39,16 +40,22 @@ static const DelegateInfo sPermissionsMa
     {"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
     {"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
     {"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy},
     {"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy},
     {"screen", u"display-capture", DelegatePolicy::eDelegateUseFeaturePolicy},
     {"xr", u"xr-spatial-tracking", DelegatePolicy::eDelegateUseFeaturePolicy},
 };
 
+static_assert(PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT ==
+                  (sizeof(sPermissionsMap) / sizeof(DelegateInfo)),
+              "The PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT must "
+              "match to the "
+              "length of sPermissionsMap. Please update it.");
+
 NS_IMPL_CYCLE_COLLECTION(PermissionDelegateHandler)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PermissionDelegateHandler)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PermissionDelegateHandler)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PermissionDelegateHandler)
   NS_INTERFACE_MAP_ENTRY(nsIPermissionDelegateHandler)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
@@ -145,23 +152,16 @@ bool PermissionDelegateHandler::Initiali
   nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(window);
   if (innerWindow) {
     mTopLevelPrincipal = innerWindow->GetTopLevelAntiTrackingPrincipal();
   }
 
   return true;
 }
 
-static bool IsTopWindowContent(Document* aDocument) {
-  MOZ_ASSERT(aDocument);
-
-  BrowsingContext* browsingContext = aDocument->GetBrowsingContext();
-  return browsingContext && browsingContext->IsTopContent();
-}
-
 bool PermissionDelegateHandler::HasFeaturePolicyAllowed(
     const DelegateInfo* info) const {
   if (info->mPolicy != DelegatePolicy::eDelegateUseFeaturePolicy ||
       !info->mFeatureName) {
     return true;
   }
 
   nsAutoString featureName(info->mFeatureName);
@@ -183,17 +183,17 @@ bool PermissionDelegateHandler::HasPermi
     return false;
   }
 
   if (!StaticPrefs::permissions_delegation_enabled()) {
     return true;
   }
 
   if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin &&
-      !IsTopWindowContent(mDocument) &&
+      !mDocument->IsTopLevelContentDocument() &&
       !mPrincipal->Subsumes(mTopLevelPrincipal)) {
     return false;
   }
 
   return true;
 }
 
 nsresult PermissionDelegateHandler::GetPermission(const nsACString& aType,
@@ -219,17 +219,17 @@ nsresult PermissionDelegateHandler::GetP
                       : &nsIPermissionManager::TestPermissionFromPrincipal;
 
   if (!StaticPrefs::permissions_delegation_enabled()) {
     return (mPermissionManager->*testPermission)(mPrincipal, aType,
                                                  aPermission);
   }
 
   if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin &&
-      !IsTopWindowContent(mDocument) &&
+      !mDocument->IsTopLevelContentDocument() &&
       !mPrincipal->Subsumes(mTopLevelPrincipal)) {
     *aPermission = nsIPermissionManager::DENY_ACTION;
     return NS_OK;
   }
 
   nsIPrincipal* principal = mPrincipal;
   if (mTopLevelPrincipal &&
       (info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin ||
@@ -241,9 +241,113 @@ nsresult PermissionDelegateHandler::GetP
   return (mPermissionManager->*testPermission)(principal, aType, aPermission);
 }
 
 nsresult PermissionDelegateHandler::GetPermissionForPermissionsAPI(
     const nsACString& aType, uint32_t* aPermission) {
   return GetPermission(aType, aPermission, false);
 }
 
+void PermissionDelegateHandler::PopulateAllDelegatedPermissions() {
+  MOZ_ASSERT(mDocument);
+  MOZ_ASSERT(mPermissionManager);
+
+  // We only populate the delegated permissions for the top-level content.
+  if (!mDocument->IsTopLevelContentDocument()) {
+    return;
+  }
+
+  RefPtr<WindowContext> wc = mDocument->GetWindowContext();
+  NS_ENSURE_TRUE_VOID(wc);
+
+  DelegatedPermissionList list;
+  DelegatedPermissionList exactHostMatchList;
+
+  for (const auto& perm : sPermissionsMap) {
+    size_t idx = std::distance(sPermissionsMap, &perm);
+
+    nsDependentCString type(perm.mPermissionName);
+    // Populate the permission.
+    uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
+    Unused << mPermissionManager->TestPermissionFromPrincipal(mPrincipal, type,
+                                                              &permission);
+    list.mPermissions[idx] = permission;
+
+    // Populate the exact-host-match permission.
+    permission = nsIPermissionManager::UNKNOWN_ACTION;
+    Unused << mPermissionManager->TestExactPermissionFromPrincipal(
+        mPrincipal, type, &permission);
+    exactHostMatchList.mPermissions[idx] = permission;
+  }
+
+  WindowContext::Transaction txn;
+  txn.SetDelegatedPermissions(list);
+  txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList);
+  txn.Commit(wc);
+}
+
+void PermissionDelegateHandler::UpdateDelegatedPermission(
+    const nsACString& aType) {
+  MOZ_ASSERT(mDocument);
+  MOZ_ASSERT(mPermissionManager);
+
+  // We only update the delegated permission for the top-level content.
+  if (!mDocument->IsTopLevelContentDocument()) {
+    return;
+  }
+
+  RefPtr<WindowContext> wc = mDocument->GetWindowContext();
+  NS_ENSURE_TRUE_VOID(wc);
+
+  const DelegateInfo* info =
+      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
+  NS_ENSURE_TRUE_VOID(info);
+  size_t idx = std::distance(sPermissionsMap, info);
+
+  WindowContext::Transaction txn;
+  bool changed = false;
+  DelegatedPermissionList list = wc->GetDelegatedPermissions();
+
+  if (UpdateDelegatePermissionInternal(
+          list, aType, idx,
+          &nsIPermissionManager::TestPermissionFromPrincipal)) {
+    txn.SetDelegatedPermissions(list);
+    changed = true;
+  }
+
+  DelegatedPermissionList exactHostMatchList =
+      wc->GetDelegatedExactHostMatchPermissions();
+
+  if (UpdateDelegatePermissionInternal(
+          exactHostMatchList, aType, idx,
+          &nsIPermissionManager::TestExactPermissionFromPrincipal)) {
+    txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList);
+    changed = true;
+  }
+
+  // We only commit if there is any change of permissions.
+  if (changed) {
+    txn.Commit(wc);
+  }
+}
+
+bool PermissionDelegateHandler::UpdateDelegatePermissionInternal(
+    PermissionDelegateHandler::DelegatedPermissionList& aList,
+    const nsACString& aType, size_t aIdx,
+    nsresult (NS_STDCALL nsIPermissionManager::*aTestFunc)(nsIPrincipal*,
+                                                           const nsACString&,
+                                                           uint32_t*)) {
+  MOZ_ASSERT(aTestFunc);
+  MOZ_ASSERT(mPermissionManager);
+  MOZ_ASSERT(mPrincipal);
+
+  uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
+  Unused << (mPermissionManager->*aTestFunc)(mPrincipal, aType, &permission);
+
+  if (aList.mPermissions[aIdx] != permission) {
+    aList.mPermissions[aIdx] = permission;
+    return true;
+  }
+
+  return false;
+}
+
 }  // namespace mozilla
--- a/extensions/permissions/PermissionDelegateHandler.h
+++ b/extensions/permissions/PermissionDelegateHandler.h
@@ -21,40 +21,52 @@
  * access to geolocation, and the iframe has been granted access to geolocation
  * by Feature Policy, a request from the cross-origin iframe would trigger a
  * prompt using of the top-level origin.
  */
 
 #ifndef mozilla_PermissionDelegateHandler_h
 #define mozilla_PermissionDelegateHandler_h
 
+#include "mozilla/Array.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsISupports.h"
 #include "nsIPermissionDelegateHandler.h"
 #include "nsIPermissionManager.h"
 #include "nsCOMPtr.h"
 
 class nsIPrincipal;
 class nsIContentPermissionRequest;
 
 namespace mozilla {
 namespace dom {
 class Document;
-}
+class WindowContext;
+}  // namespace dom
 
 class PermissionDelegateHandler final : public nsIPermissionDelegateHandler {
  public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(PermissionDelegateHandler)
 
   NS_DECL_NSIPERMISSIONDELEGATEHANDLER
 
   explicit PermissionDelegateHandler() = default;
   explicit PermissionDelegateHandler(mozilla::dom::Document* aDocument);
 
+  static constexpr size_t DELEGATED_PERMISSION_COUNT = 11;
+
+  typedef struct DelegatedPermissionList {
+    Array<uint32_t, DELEGATED_PERMISSION_COUNT> mPermissions;
+
+    bool operator==(const DelegatedPermissionList& aOther) const {
+      return mPermissions == aOther.mPermissions;
+    }
+  } DelegatedPermissionList;
+
   bool Initialize();
 
   /*
    * Indicates if we has the right to make permission request with aType
    */
   bool HasPermissionDelegated(const nsACString& aType);
 
   /*
@@ -139,28 +151,51 @@ class PermissionDelegateHandler final : 
    * @param aRequest  The request which the principal is get from.
    * @param aResult out argument which will be a principal that we
    *                will return from this function.
    */
   static nsresult GetDelegatePrincipal(const nsACString& aType,
                                        nsIContentPermissionRequest* aRequest,
                                        nsIPrincipal** aResult);
 
+  /**
+   * Populate all delegated permissions to the WindowContext of the associated
+   * document. We only populate the permissions for the top-level content.
+   */
+  void PopulateAllDelegatedPermissions();
+
+  /**
+   * Update the given delegated permission to the WindowContext. We only
+   * update it for the top-level content.
+   */
+  void UpdateDelegatedPermission(const nsACString& aType);
+
  private:
   ~PermissionDelegateHandler() = default;
 
   /*
    * Check whether the permission is blocked by FeaturePolicy directive.
    * Default allowlist for a featureName of permission used in permissions
    * delegate should be set to eSelf, to ensure that permission is denied by
    * default and only have the opportunity to request permission with allow
    * attribute.
    */
   bool HasFeaturePolicyAllowed(const PermissionDelegateInfo* info) const;
 
+  /**
+   * A helper function to test the permission and set the result to the given
+   * list. It will return true if the permission is changed, otherwise false.
+   */
+  bool UpdateDelegatePermissionInternal(
+      PermissionDelegateHandler::DelegatedPermissionList& aList,
+      const nsACString& aType, size_t aIdx,
+      nsresult (NS_STDCALL nsIPermissionManager::*aTestFunc)(nsIPrincipal*,
+                                                             const nsACString&,
+                                                             uint32_t*));
+
   // A weak pointer to our document. Nulled out by DropDocumentReference.
   mozilla::dom::Document* mDocument;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
   RefPtr<nsIPermissionManager> mPermissionManager;
 };
 
new file mode 100644
--- /dev/null
+++ b/extensions/permissions/PermissionDelegateIPCUtils.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_permissiondelegateipcutils_h
+#define mozilla_permissiondelegateipcutils_h
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "mozilla/PermissionDelegateHandler.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<
+    mozilla::PermissionDelegateHandler::DelegatedPermissionList> {
+  typedef mozilla::PermissionDelegateHandler::DelegatedPermissionList paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam) {
+    for (auto& permission : aParam.mPermissions) {
+      WriteParam(aMsg, permission);
+    }
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    for (auto& permission : aResult->mPermissions) {
+      if (!ReadParam(aMsg, aIter, &permission)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+}  // namespace IPC
+
+#endif  // mozilla_permissiondelegateipcutils_h
--- a/extensions/permissions/moz.build
+++ b/extensions/permissions/moz.build
@@ -8,16 +8,17 @@ TEST_DIRS += ['test']
 
 TESTING_JS_MODULES += [
     'test/PermissionTestUtils.jsm',
 ]
 
 EXPORTS.mozilla += [
     'Permission.h',
     'PermissionDelegateHandler.h',
+    'PermissionDelegateIPCUtils.h',
     'PermissionManager.h',
 ]
 
 UNIFIED_SOURCES += [
     'Permission.cpp',
     'PermissionDelegateHandler.cpp',
     'PermissionManager.cpp',
 ]