Bug 1888590 - Mark some subtests on trusted-types-event-handlers.html as failing...
[gecko.git] / extensions / permissions / PermissionDelegateHandler.cpp
blobc25afb560e67f0b9c33604ea8b327027043a1d5e
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/PermissionDelegateHandler.h"
9 #include "nsPIDOMWindow.h"
10 #include "nsIPrincipal.h"
11 #include "nsContentPermissionHelper.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/StaticPrefs_permissions.h"
15 #include "mozilla/dom/BrowsingContext.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/FeaturePolicyUtils.h"
18 #include "mozilla/dom/WindowContext.h"
19 #include "mozilla/PermissionManager.h"
21 using namespace mozilla::dom;
23 namespace mozilla {
25 typedef PermissionDelegateHandler::PermissionDelegatePolicy DelegatePolicy;
26 typedef PermissionDelegateHandler::PermissionDelegateInfo DelegateInfo;
28 // Particular type of permissions to care about. We decide cases by case and
29 // give various types of controls over each of these.
30 static const DelegateInfo sPermissionsMap[] = {
31 // Permissions API map. All permission names have to be in lowercase.
32 {"geo", u"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy},
33 // The same with geo, but we support both to save some conversions between
34 // "geo" and "geolocation"
35 {"geolocation", u"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy},
36 {"desktop-notification", nullptr,
37 DelegatePolicy::ePersistDeniedCrossOrigin},
38 {"persistent-storage", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
39 {"vibration", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
40 {"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
41 // Like "midi" but with sysex support.
42 {"midi-sysex", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
43 {"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
44 {"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy},
45 {"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy},
46 {"screen", u"display-capture", DelegatePolicy::eDelegateUseFeaturePolicy},
47 {"xr", u"xr-spatial-tracking", DelegatePolicy::eDelegateUseFeaturePolicy},
48 {"screen-wake-lock", u"screen-wake-lock",
49 DelegatePolicy::eDelegateUseFeaturePolicy}};
51 static_assert(PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT ==
52 (sizeof(sPermissionsMap) / sizeof(DelegateInfo)),
53 "The PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT must "
54 "match to the "
55 "length of sPermissionsMap. Please update it.");
57 NS_IMPL_CYCLE_COLLECTION(PermissionDelegateHandler)
58 NS_IMPL_CYCLE_COLLECTING_ADDREF(PermissionDelegateHandler)
59 NS_IMPL_CYCLE_COLLECTING_RELEASE(PermissionDelegateHandler)
61 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PermissionDelegateHandler)
62 NS_INTERFACE_MAP_ENTRY(nsIPermissionDelegateHandler)
63 NS_INTERFACE_MAP_ENTRY(nsISupports)
64 NS_INTERFACE_MAP_END
66 PermissionDelegateHandler::PermissionDelegateHandler(dom::Document* aDocument)
67 : mDocument(aDocument) {
68 MOZ_ASSERT(aDocument);
71 /* static */
72 const DelegateInfo* PermissionDelegateHandler::GetPermissionDelegateInfo(
73 const nsAString& aPermissionName) {
74 nsAutoString lowerContent(aPermissionName);
75 ToLowerCase(lowerContent);
77 for (const auto& perm : sPermissionsMap) {
78 if (lowerContent.EqualsASCII(perm.mPermissionName)) {
79 return &perm;
83 return nullptr;
86 NS_IMETHODIMP
87 PermissionDelegateHandler::MaybeUnsafePermissionDelegate(
88 const nsTArray<nsCString>& aTypes, bool* aMaybeUnsafe) {
89 *aMaybeUnsafe = false;
90 for (auto& type : aTypes) {
91 const DelegateInfo* info =
92 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(type));
93 if (!info) {
94 continue;
97 nsAutoString featureName(info->mFeatureName);
98 if (FeaturePolicyUtils::IsFeatureUnsafeAllowedAll(mDocument, featureName)) {
99 *aMaybeUnsafe = true;
100 return NS_OK;
104 return NS_OK;
107 /* static */
108 nsresult PermissionDelegateHandler::GetDelegatePrincipal(
109 const nsACString& aType, nsIContentPermissionRequest* aRequest,
110 nsIPrincipal** aResult) {
111 MOZ_ASSERT(aRequest);
113 const DelegateInfo* info =
114 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
115 if (!info) {
116 *aResult = nullptr;
117 return NS_OK;
120 if (info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin ||
121 info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) {
122 return aRequest->GetTopLevelPrincipal(aResult);
125 return aRequest->GetPrincipal(aResult);
128 bool PermissionDelegateHandler::Initialize() {
129 MOZ_ASSERT(mDocument);
131 mPermissionManager = PermissionManager::GetInstance();
132 if (!mPermissionManager) {
133 return false;
136 mPrincipal = mDocument->NodePrincipal();
137 return true;
140 static bool IsCrossOriginContentToTop(Document* aDocument) {
141 MOZ_ASSERT(aDocument);
143 RefPtr<BrowsingContext> bc = aDocument->GetBrowsingContext();
144 if (!bc) {
145 return true;
147 RefPtr<BrowsingContext> topBC = bc->Top();
149 // In Fission, we can know if it is cross-origin by checking whether both
150 // contexts in the same process. So, If they are not in the same process, we
151 // can say that it's cross-origin.
152 if (!topBC->IsInProcess()) {
153 return true;
156 RefPtr<Document> topDoc = topBC->GetDocument();
157 if (!topDoc) {
158 return true;
161 nsCOMPtr<nsIPrincipal> topLevelPrincipal = topDoc->NodePrincipal();
163 return !aDocument->NodePrincipal()->Subsumes(topLevelPrincipal);
166 bool PermissionDelegateHandler::HasFeaturePolicyAllowed(
167 const DelegateInfo* info) const {
168 if (info->mPolicy != DelegatePolicy::eDelegateUseFeaturePolicy ||
169 !info->mFeatureName) {
170 return true;
173 nsAutoString featureName(info->mFeatureName);
174 return FeaturePolicyUtils::IsFeatureAllowed(mDocument, featureName);
177 bool PermissionDelegateHandler::HasPermissionDelegated(
178 const nsACString& aType) const {
179 MOZ_ASSERT(mDocument);
181 // System principal should have right to make permission request
182 if (mPrincipal->IsSystemPrincipal()) {
183 return true;
186 const DelegateInfo* info =
187 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
188 if (!info || !HasFeaturePolicyAllowed(info)) {
189 return false;
192 if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin &&
193 !mDocument->IsTopLevelContentDocument() &&
194 IsCrossOriginContentToTop(mDocument)) {
195 return false;
198 return true;
201 nsresult PermissionDelegateHandler::GetPermission(const nsACString& aType,
202 uint32_t* aPermission,
203 bool aExactHostMatch) {
204 MOZ_ASSERT(mDocument);
205 MOZ_ASSERT(mPrincipal);
207 if (mPrincipal->IsSystemPrincipal()) {
208 *aPermission = nsIPermissionManager::ALLOW_ACTION;
209 return NS_OK;
212 const DelegateInfo* info =
213 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
214 if (!info || !HasFeaturePolicyAllowed(info)) {
215 *aPermission = nsIPermissionManager::DENY_ACTION;
216 return NS_OK;
219 nsresult (NS_STDCALL nsIPermissionManager::*testPermission)(
220 nsIPrincipal*, const nsACString&, uint32_t*) =
221 aExactHostMatch ? &nsIPermissionManager::TestExactPermissionFromPrincipal
222 : &nsIPermissionManager::TestPermissionFromPrincipal;
224 if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin &&
225 !mDocument->IsTopLevelContentDocument() &&
226 IsCrossOriginContentToTop(mDocument)) {
227 *aPermission = nsIPermissionManager::DENY_ACTION;
228 return NS_OK;
231 nsIPrincipal* principal = mPrincipal;
232 // If we cannot get the browsing context from the document, we fallback to use
233 // the prinicpal of the document to test the permission.
234 RefPtr<BrowsingContext> bc = mDocument->GetBrowsingContext();
236 if ((info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin ||
237 info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) &&
238 bc) {
239 RefPtr<WindowContext> topWC = bc->GetTopWindowContext();
241 if (topWC && topWC->IsInProcess()) {
242 // If the top-level window context is in the same process, we directly get
243 // the node principal from the top-level document to test the permission.
244 // We cannot check the lists in the window context in this case since the
245 // 'perm-changed' could be notified in the iframe before the top-level in
246 // certain cases, for example, request permissions in first-party iframes.
247 // In this case, the list in window context hasn't gotten updated, so it
248 // would has an out-dated value until the top-level window get the
249 // observer. So, we have to test permission manager directly if we can.
250 RefPtr<Document> topDoc = topWC->GetBrowsingContext()->GetDocument();
252 if (topDoc) {
253 principal = topDoc->NodePrincipal();
255 } else if (topWC) {
256 // Get the delegated permissions from the top-level window context.
257 DelegatedPermissionList list =
258 aExactHostMatch ? topWC->GetDelegatedExactHostMatchPermissions()
259 : topWC->GetDelegatedPermissions();
260 size_t idx = std::distance(sPermissionsMap, info);
261 *aPermission = list.mPermissions[idx];
262 return NS_OK;
266 return (mPermissionManager->*testPermission)(principal, aType, aPermission);
269 nsresult PermissionDelegateHandler::GetPermissionForPermissionsAPI(
270 const nsACString& aType, uint32_t* aPermission) {
271 return GetPermission(aType, aPermission, false);
274 void PermissionDelegateHandler::PopulateAllDelegatedPermissions() {
275 MOZ_ASSERT(mDocument);
276 MOZ_ASSERT(mPermissionManager);
278 // We only populate the delegated permissions for the top-level content.
279 if (!mDocument->IsTopLevelContentDocument()) {
280 return;
283 RefPtr<WindowContext> wc = mDocument->GetWindowContext();
284 NS_ENSURE_TRUE_VOID(wc && !wc->IsDiscarded());
286 DelegatedPermissionList list;
287 DelegatedPermissionList exactHostMatchList;
289 for (const auto& perm : sPermissionsMap) {
290 size_t idx = std::distance(sPermissionsMap, &perm);
292 nsDependentCString type(perm.mPermissionName);
293 // Populate the permission.
294 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
295 Unused << mPermissionManager->TestPermissionFromPrincipal(mPrincipal, type,
296 &permission);
297 list.mPermissions[idx] = permission;
299 // Populate the exact-host-match permission.
300 permission = nsIPermissionManager::UNKNOWN_ACTION;
301 Unused << mPermissionManager->TestExactPermissionFromPrincipal(
302 mPrincipal, type, &permission);
303 exactHostMatchList.mPermissions[idx] = permission;
306 WindowContext::Transaction txn;
307 txn.SetDelegatedPermissions(list);
308 txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList);
309 MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc));
312 void PermissionDelegateHandler::UpdateDelegatedPermission(
313 const nsACString& aType) {
314 MOZ_ASSERT(mDocument);
315 MOZ_ASSERT(mPermissionManager);
317 // We only update the delegated permission for the top-level content.
318 if (!mDocument->IsTopLevelContentDocument()) {
319 return;
322 RefPtr<WindowContext> wc = mDocument->GetWindowContext();
323 NS_ENSURE_TRUE_VOID(wc);
325 const DelegateInfo* info =
326 GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
327 if (!info) {
328 return;
330 size_t idx = std::distance(sPermissionsMap, info);
332 WindowContext::Transaction txn;
333 bool changed = false;
334 DelegatedPermissionList list = wc->GetDelegatedPermissions();
336 if (UpdateDelegatePermissionInternal(
337 list, aType, idx,
338 &nsIPermissionManager::TestPermissionFromPrincipal)) {
339 txn.SetDelegatedPermissions(list);
340 changed = true;
343 DelegatedPermissionList exactHostMatchList =
344 wc->GetDelegatedExactHostMatchPermissions();
346 if (UpdateDelegatePermissionInternal(
347 exactHostMatchList, aType, idx,
348 &nsIPermissionManager::TestExactPermissionFromPrincipal)) {
349 txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList);
350 changed = true;
353 // We only commit if there is any change of permissions.
354 if (changed) {
355 MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc));
359 bool PermissionDelegateHandler::UpdateDelegatePermissionInternal(
360 PermissionDelegateHandler::DelegatedPermissionList& aList,
361 const nsACString& aType, size_t aIdx,
362 nsresult (NS_STDCALL nsIPermissionManager::*aTestFunc)(nsIPrincipal*,
363 const nsACString&,
364 uint32_t*)) {
365 MOZ_ASSERT(aTestFunc);
366 MOZ_ASSERT(mPermissionManager);
367 MOZ_ASSERT(mPrincipal);
369 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
370 Unused << (mPermissionManager->*aTestFunc)(mPrincipal, aType, &permission);
372 if (aList.mPermissions[aIdx] != permission) {
373 aList.mPermissions[aIdx] = permission;
374 return true;
377 return false;
380 } // namespace mozilla