Permissions.cpp (7485B)
1 /* -*- Mode: C++; tab-width: 8; 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/. */ 6 7 #include "mozilla/dom/Permissions.h" 8 9 #include "PermissionUtils.h" 10 #include "mozilla/StaticPrefs_permissions.h" 11 #include "mozilla/dom/Document.h" 12 #include "mozilla/dom/MidiPermissionStatus.h" 13 #include "mozilla/dom/PermissionSetParametersBinding.h" 14 #include "mozilla/dom/PermissionStatus.h" 15 #include "mozilla/dom/PermissionsBinding.h" 16 #include "mozilla/dom/Promise.h" 17 #include "mozilla/dom/RootedDictionary.h" 18 #include "mozilla/dom/StorageAccessPermissionStatus.h" 19 20 namespace mozilla::dom { 21 22 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Permissions) 23 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 24 NS_INTERFACE_MAP_ENTRY(nsISupports) 25 NS_INTERFACE_MAP_END 26 27 NS_IMPL_CYCLE_COLLECTING_ADDREF(Permissions) 28 NS_IMPL_CYCLE_COLLECTING_RELEASE(Permissions) 29 30 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(Permissions) 31 32 Permissions::Permissions(nsIGlobalObject* aGlobal) 33 : GlobalTeardownObserver(aGlobal) {} 34 35 Permissions::~Permissions() = default; 36 37 JSObject* Permissions::WrapObject(JSContext* aCx, 38 JS::Handle<JSObject*> aGivenProto) { 39 return Permissions_Binding::Wrap(aCx, this, aGivenProto); 40 } 41 42 namespace { 43 44 // Steps to parse PermissionDescriptor in 45 // https://w3c.github.io/permissions/#query-method and relevant WebDriver 46 // commands 47 RefPtr<PermissionStatus> CreatePermissionStatus( 48 JSContext* aCx, JS::Handle<JSObject*> aPermissionDesc, 49 nsIGlobalObject* aGlobal, ErrorResult& aRv) { 50 // Step 2: Let rootDesc be the object permissionDesc refers to, converted to 51 // an IDL value of type PermissionDescriptor. 52 PermissionDescriptor rootDesc; 53 JS::Rooted<JS::Value> permissionDescValue( 54 aCx, JS::ObjectOrNullValue(aPermissionDesc)); 55 if (NS_WARN_IF(!rootDesc.Init(aCx, permissionDescValue))) { 56 // Step 3: If the conversion throws an exception, return a promise rejected 57 // with that exception. 58 // Step 4: If rootDesc["name"] is not supported, return a promise rejected 59 // with a TypeError. (This is done by `enum PermissionName`, as the spec 60 // note says: "implementers are encouraged to use their own custom enum 61 // here") 62 aRv.NoteJSContextException(aCx); 63 return nullptr; 64 } 65 66 // Step 5: Let typedDescriptor be the object permissionDesc refers to, 67 // converted to an IDL value of rootDesc's name's permission descriptor type. 68 // Step 6: If the conversion throws an exception, return a promise rejected 69 // with that exception. 70 // Step 8.1: Let status be create a PermissionStatus with typedDescriptor. 71 // (The rest is done by the caller) 72 switch (rootDesc.mName) { 73 case PermissionName::Midi: { 74 MidiPermissionDescriptor midiPerm; 75 if (NS_WARN_IF(!midiPerm.Init(aCx, permissionDescValue))) { 76 aRv.NoteJSContextException(aCx); 77 return nullptr; 78 } 79 80 return new MidiPermissionStatus(aGlobal, midiPerm.mSysex); 81 } 82 case PermissionName::Storage_access: 83 return new StorageAccessPermissionStatus(aGlobal); 84 case PermissionName::Geolocation: 85 case PermissionName::Notifications: 86 case PermissionName::Push: 87 case PermissionName::Persistent_storage: 88 case PermissionName::Screen_wake_lock: 89 return new PermissionStatus(aGlobal, rootDesc.mName); 90 case PermissionName::Camera: 91 if (!StaticPrefs::permissions_media_query_enabled()) { 92 aRv.ThrowTypeError( 93 "'camera' (value of 'name' member of PermissionDescriptor) is not " 94 "a valid value for enumeration PermissionName."); 95 return nullptr; 96 } 97 return new PermissionStatus(aGlobal, rootDesc.mName); 98 case PermissionName::Microphone: 99 if (!StaticPrefs::permissions_media_query_enabled()) { 100 aRv.ThrowTypeError( 101 "'microphone' (value of 'name' member of PermissionDescriptor) is " 102 "not a valid value for enumeration PermissionName."); 103 return nullptr; 104 } 105 return new PermissionStatus(aGlobal, rootDesc.mName); 106 default: 107 MOZ_ASSERT_UNREACHABLE("Unhandled type"); 108 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); 109 return nullptr; 110 } 111 } 112 113 } // namespace 114 115 // https://w3c.github.io/permissions/#query-method 116 already_AddRefed<Promise> Permissions::Query(JSContext* aCx, 117 JS::Handle<JSObject*> aPermission, 118 ErrorResult& aRv) { 119 // Step 1: If this's relevant global object is a Window object, then: 120 // Step 1.1: If the current settings object's associated Document is not fully 121 // active, return a promise rejected with an "InvalidStateError" DOMException. 122 123 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal(); 124 if (NS_WARN_IF(!global)) { 125 aRv.ThrowInvalidStateError("The context is not fully active."); 126 return nullptr; 127 } 128 129 if (NS_IsMainThread()) { 130 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); 131 if (!window || !window->IsFullyActive()) { 132 aRv.ThrowInvalidStateError("The document is not fully active."); 133 return nullptr; 134 } 135 } 136 137 // Step 2 - 6 and 8.1: 138 RefPtr<PermissionStatus> status = 139 CreatePermissionStatus(aCx, aPermission, global, aRv); 140 if (!status) { 141 return nullptr; 142 } 143 144 // Step 7: Let promise be a new promise. 145 RefPtr<Promise> promise = Promise::Create(global, aRv); 146 if (NS_WARN_IF(aRv.Failed())) { 147 return nullptr; 148 } 149 150 // Step 8.2 - 8.3: (Done by the Init method) 151 // Step 8.4: Queue a global task on the permissions task source with this's 152 // relevant global object to resolve promise with status. 153 status->Init()->Then( 154 GetCurrentSerialEventTarget(), __func__, 155 [status, promise]() { 156 promise->MaybeResolve(status); 157 return; 158 }, 159 [promise](nsresult aError) { 160 MOZ_ASSERT(NS_FAILED(aError)); 161 NS_WARNING("Failed PermissionStatus creation"); 162 promise->MaybeReject(aError); 163 return; 164 }); 165 166 return promise.forget(); 167 } 168 169 already_AddRefed<PermissionStatus> Permissions::ParseSetParameters( 170 JSContext* aCx, const PermissionSetParameters& aParameters, 171 ErrorResult& aRv) { 172 // Step 1: Let parametersDict be the parameters argument, converted to an IDL 173 // value of type PermissionSetParameters. If this throws an exception, 174 // return an invalid argument error. 175 // (Done by IDL layer, and the error type should be handled by the caller) 176 177 // Step 2: If parametersDict.state is an inappropriate permission state for 178 // any implementation-defined reason, return a invalid argument error. 179 // (We don't do this) 180 181 // Step 3: Let rootDesc be parametersDict.descriptor. 182 JS::Rooted<JSObject*> rootDesc(aCx, aParameters.mDescriptor); 183 184 // Step 4: Let typedDescriptor be the object rootDesc refers to, converted 185 // to an IDL value of rootDesc.name's permission descriptor type. If this 186 // throws an exception, return a invalid argument error. 187 // 188 // We use PermissionStatus as the typed object. 189 RefPtr<PermissionStatus> status = 190 CreatePermissionStatus(aCx, rootDesc, nullptr, aRv); 191 if (aRv.Failed()) { 192 return nullptr; 193 } 194 195 // Set the state too so that the caller can use it for step 5. 196 status->SetState(aParameters.mState); 197 198 return status.forget(); 199 } 200 201 } // namespace mozilla::dom