mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 916012 - Clean up gUM to use webidl unions and filter by media-type. r=bz, r=bwc
This commit is contained in:
parent
4b3c68d60c
commit
37917f58c1
2
CLOBBER
2
CLOBBER
@ -22,4 +22,4 @@
|
||||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||
# don't change CLOBBER for WebIDL changes any more.
|
||||
|
||||
Bug 991256 requires a clobber because it renames generated mobile/android/base/widget files.
|
||||
Bug 916012 moves definition from one WEBIDL_FILE to another (Bug 979886)
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "GetUserMediaRequest.h"
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
#include "mozilla/dom/MediaStreamBinding.h"
|
||||
#include "mozilla/dom/GetUserMediaRequestBinding.h"
|
||||
#include "nsIScriptGlobalObject.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
@ -20,7 +20,7 @@ GetUserMediaRequest::GetUserMediaRequest(
|
||||
: mInnerWindowID(aInnerWindow->WindowID())
|
||||
, mOuterWindowID(aInnerWindow->GetOuterWindow()->WindowID())
|
||||
, mCallID(aCallID)
|
||||
, mConstraints(aConstraints)
|
||||
, mConstraints(new MediaStreamConstraintsInternal(aConstraints))
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
@ -62,7 +62,7 @@ uint64_t GetUserMediaRequest::InnerWindowID()
|
||||
void
|
||||
GetUserMediaRequest::GetConstraints(MediaStreamConstraintsInternal &result)
|
||||
{
|
||||
result = mConstraints;
|
||||
result = *mConstraints;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -40,7 +39,7 @@ public:
|
||||
private:
|
||||
uint64_t mInnerWindowID, mOuterWindowID;
|
||||
const nsString mCallID;
|
||||
MediaStreamConstraintsInternal mConstraints;
|
||||
nsAutoPtr<MediaStreamConstraintsInternal> mConstraints;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "mozilla/dom/MediaStreamBinding.h"
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
#include "mozilla/dom/GetUserMediaRequestBinding.h"
|
||||
|
||||
@ -79,6 +80,7 @@ using dom::MediaTrackConstraintSet; // Mandatory or optional constraints
|
||||
using dom::MediaTrackConstraints; // Raw mMandatory (as JSObject)
|
||||
using dom::GetUserMediaRequest;
|
||||
using dom::Sequence;
|
||||
using dom::OwningBooleanOrMediaTrackConstraintsInternal;
|
||||
|
||||
// Used to compare raw MediaTrackConstraintSet against normalized dictionary
|
||||
// version to detect member differences, e.g. unsupported constraints.
|
||||
@ -120,29 +122,6 @@ static nsresult CompareDictionaries(JSContext* aCx, JSObject *aA,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Look for and return any unknown mandatory constraint. Done by comparing
|
||||
// a raw MediaTrackConstraints against a normalized copy, both passed in.
|
||||
|
||||
static nsresult ValidateTrackConstraints(
|
||||
JSContext *aCx, JSObject *aRaw,
|
||||
const MediaTrackConstraintsInternal &aNormalized,
|
||||
nsString *aOutUnknownConstraint)
|
||||
{
|
||||
// First find raw mMandatory member (use MediaTrackConstraints as helper)
|
||||
JS::Rooted<JS::Value> rawval(aCx, JS::ObjectValue(*aRaw));
|
||||
dom::RootedDictionary<MediaTrackConstraints> track(aCx);
|
||||
bool success = track.Init(aCx, rawval);
|
||||
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
|
||||
|
||||
if (track.mMandatory.WasPassed()) {
|
||||
nsresult rv = CompareDictionaries(aCx, track.mMandatory.Value(),
|
||||
aNormalized.mMandatory,
|
||||
aOutUnknownConstraint);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
ErrorCallbackRunnable::ErrorCallbackRunnable(
|
||||
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aSuccess,
|
||||
nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aError,
|
||||
@ -692,6 +671,18 @@ private:
|
||||
nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
|
||||
};
|
||||
|
||||
static bool
|
||||
IsOn(const dom::OwningBooleanOrMediaTrackConstraintsInternal &aUnion) {
|
||||
return !aUnion.IsBoolean() || aUnion.GetAsBoolean();
|
||||
}
|
||||
|
||||
static JSObject *
|
||||
GetMandatoryJSObj(const dom::OwningBooleanOrMediaTrackConstraints &aUnion) {
|
||||
return (aUnion.IsMediaTrackConstraints() &&
|
||||
aUnion.GetAsMediaTrackConstraints().mMandatory.WasPassed()) ?
|
||||
aUnion.GetAsMediaTrackConstraints().mMandatory.Value() : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions that implement the constraints algorithm from
|
||||
* http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
|
||||
@ -728,10 +719,16 @@ typedef nsTArray<nsCOMPtr<nsIMediaDevice> > SourceSet;
|
||||
template<class SourceType>
|
||||
static SourceSet *
|
||||
GetSources(MediaEngine *engine,
|
||||
const MediaTrackConstraintsInternal &aConstraints,
|
||||
const OwningBooleanOrMediaTrackConstraintsInternal &aConstraints,
|
||||
void (MediaEngine::* aEnumerate)(nsTArray<nsRefPtr<SourceType> >*),
|
||||
char* media_device_name = nullptr)
|
||||
{
|
||||
ScopedDeletePtr<SourceSet> result(new SourceSet);
|
||||
|
||||
if (!IsOn(aConstraints)) {
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
const SourceType * const type = nullptr;
|
||||
nsString deviceName;
|
||||
// First collect sources
|
||||
@ -762,12 +759,19 @@ static SourceSet *
|
||||
}
|
||||
}
|
||||
|
||||
if (aConstraints.IsBoolean()) {
|
||||
MOZ_ASSERT(aConstraints.GetAsBoolean());
|
||||
result->MoveElementsFrom(candidateSet);
|
||||
return result.forget();
|
||||
}
|
||||
auto& constraints = aConstraints.GetAsMediaTrackConstraintsInternal();
|
||||
|
||||
// Then apply mandatory constraints
|
||||
|
||||
// Note: Iterator must be signed as it can dip below zero
|
||||
for (int i = 0; i < int(candidateSet.Length()); i++) {
|
||||
// Overloading instead of template specialization keeps things local
|
||||
if (!SatisfyConstraint(type, aConstraints.mMandatory, *candidateSet[i])) {
|
||||
if (!SatisfyConstraint(type, constraints.mMandatory, *candidateSet[i])) {
|
||||
candidateSet.RemoveElementAt(i--);
|
||||
}
|
||||
}
|
||||
@ -788,8 +792,8 @@ static SourceSet *
|
||||
|
||||
SourceSet tailSet;
|
||||
|
||||
if (aConstraints.mOptional.WasPassed()) {
|
||||
const Sequence<MediaTrackConstraintSet> &array = aConstraints.mOptional.Value();
|
||||
if (constraints.mOptional.WasPassed()) {
|
||||
const auto &array = constraints.mOptional.Value();
|
||||
for (int i = 0; i < int(array.Length()); i++) {
|
||||
SourceSet rejects;
|
||||
// Note: Iterator must be signed as it can dip below zero
|
||||
@ -803,10 +807,9 @@ static SourceSet *
|
||||
}
|
||||
}
|
||||
|
||||
SourceSet *result = new SourceSet;
|
||||
result->MoveElementsFrom(candidateSet);
|
||||
result->MoveElementsFrom(tailSet);
|
||||
return result;
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -896,7 +899,8 @@ public:
|
||||
}
|
||||
|
||||
// It is an error if audio or video are requested along with picture.
|
||||
if (mConstraints.mPicture && (mConstraints.mAudio || mConstraints.mVideo)) {
|
||||
if (mConstraints.mPicture &&
|
||||
(IsOn(mConstraints.mAudio) || IsOn(mConstraints.mVideo))) {
|
||||
Fail(NS_LITERAL_STRING("NOT_SUPPORTED_ERR"));
|
||||
return NS_OK;
|
||||
}
|
||||
@ -907,9 +911,9 @@ public:
|
||||
}
|
||||
|
||||
// There's a bug in the permission code that can leave us with mAudio but no audio device
|
||||
ProcessGetUserMedia(((mConstraints.mAudio && mAudioDevice) ?
|
||||
ProcessGetUserMedia(((IsOn(mConstraints.mAudio) && mAudioDevice) ?
|
||||
mAudioDevice->GetSource() : nullptr),
|
||||
((mConstraints.mVideo && mVideoDevice) ?
|
||||
((IsOn(mConstraints.mVideo) && mVideoDevice) ?
|
||||
mVideoDevice->GetSource() : nullptr));
|
||||
return NS_OK;
|
||||
}
|
||||
@ -975,9 +979,9 @@ public:
|
||||
{
|
||||
MOZ_ASSERT(mSuccess);
|
||||
MOZ_ASSERT(mError);
|
||||
if (mConstraints.mPicture || mConstraints.mVideo) {
|
||||
if (mConstraints.mPicture || IsOn(mConstraints.mVideo)) {
|
||||
ScopedDeletePtr<SourceSet> sources (GetSources(backend,
|
||||
mConstraints.mVideom, &MediaEngine::EnumerateVideoDevices));
|
||||
mConstraints.mVideo, &MediaEngine::EnumerateVideoDevices));
|
||||
|
||||
if (!sources->Length()) {
|
||||
Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
|
||||
@ -988,9 +992,9 @@ public:
|
||||
LOG(("Selected video device"));
|
||||
}
|
||||
|
||||
if (mConstraints.mAudio) {
|
||||
if (IsOn(mConstraints.mAudio)) {
|
||||
ScopedDeletePtr<SourceSet> sources (GetSources(backend,
|
||||
mConstraints.mAudiom, &MediaEngine::EnumerateAudioDevices));
|
||||
mConstraints.mAudio, &MediaEngine::EnumerateAudioDevices));
|
||||
|
||||
if (!sources->Length()) {
|
||||
Fail(NS_LITERAL_STRING("NO_DEVICES_FOUND"));
|
||||
@ -1126,11 +1130,11 @@ public:
|
||||
else
|
||||
backend = mManager->GetBackend(mWindowId);
|
||||
|
||||
ScopedDeletePtr<SourceSet> final (GetSources(backend, mConstraints.mVideom,
|
||||
ScopedDeletePtr<SourceSet> final (GetSources(backend, mConstraints.mVideo,
|
||||
&MediaEngine::EnumerateVideoDevices,
|
||||
mLoopbackVideoDevice));
|
||||
{
|
||||
ScopedDeletePtr<SourceSet> s (GetSources(backend, mConstraints.mAudiom,
|
||||
ScopedDeletePtr<SourceSet> s (GetSources(backend, mConstraints.mAudio,
|
||||
&MediaEngine::EnumerateAudioDevices,
|
||||
mLoopbackAudioDevice));
|
||||
final->MoveElementsFrom(*s);
|
||||
@ -1305,52 +1309,40 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
|
||||
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onError(aOnError);
|
||||
|
||||
Maybe<JSAutoCompartment> ac;
|
||||
if (aRawConstraints.mAudio.IsObject() || aRawConstraints.mVideo.IsObject()) {
|
||||
ac.construct(aCx, (aRawConstraints.mVideo.IsObject()?
|
||||
aRawConstraints.mVideo.GetAsObject() :
|
||||
aRawConstraints.mAudio.GetAsObject()));
|
||||
JS::Rooted<JSObject*> audioObj (aCx, GetMandatoryJSObj(aRawConstraints.mAudio));
|
||||
JS::Rooted<JSObject*> videoObj (aCx, GetMandatoryJSObj(aRawConstraints.mVideo));
|
||||
if (audioObj || videoObj) {
|
||||
ac.construct(aCx, audioObj? audioObj : videoObj);
|
||||
}
|
||||
|
||||
// aRawConstraints has JSObjects in it, so process it by copying it into
|
||||
// aRawConstraints may have JSObject in mMandatory, so copy everything into
|
||||
// MediaStreamConstraintsInternal which does not.
|
||||
|
||||
dom::RootedDictionary<MediaStreamConstraintsInternal> c(aCx);
|
||||
JS::Rooted<JS::Value> temp(aCx);
|
||||
// This isn't the fastest way to copy a MediaStreamConstraints into a
|
||||
// MediaStreamConstraintsInternal, but requires less code maintenance than an
|
||||
// explicit member-by-member copy, and should be safe given the circumstances.
|
||||
aRawConstraints.ToObject(aCx, &temp);
|
||||
bool success = c.Init(aCx, temp);
|
||||
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
|
||||
|
||||
// TODO: Simplify this part once Bug 767924 is fixed.
|
||||
// Since we cannot yet use unions on non-objects, we process the raw object
|
||||
// into discrete members for internal use until Bug 767924 is fixed
|
||||
// Validate mandatory constraints by detecting any unknown constraints.
|
||||
// Done by comparing the raw MediaTrackConstraints against the normalized copy
|
||||
|
||||
nsresult rv;
|
||||
nsString unknownConstraintFound;
|
||||
|
||||
if (aRawConstraints.mAudio.IsObject()) {
|
||||
JS::Rooted<JS::Value> temp(aCx,
|
||||
JS::ObjectValue(*aRawConstraints.mAudio.GetAsObject()));
|
||||
bool success = c.mAudiom.Init(aCx, temp);
|
||||
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
|
||||
|
||||
rv = ValidateTrackConstraints(aCx, aRawConstraints.mAudio.GetAsObject(),
|
||||
c.mAudiom, &unknownConstraintFound);
|
||||
if (audioObj) {
|
||||
nsresult rv = CompareDictionaries(aCx, audioObj,
|
||||
c.mAudio.GetAsMediaTrackConstraintsInternal().mMandatory,
|
||||
&unknownConstraintFound);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
c.mAudio = true;
|
||||
} else {
|
||||
c.mAudio = aRawConstraints.mAudio.GetAsBoolean();
|
||||
}
|
||||
if (aRawConstraints.mVideo.IsObject()) {
|
||||
JS::Rooted<JS::Value> temp(aCx,
|
||||
JS::ObjectValue(*aRawConstraints.mVideo.GetAsObject()));
|
||||
bool success = c.mVideom.Init(aCx, temp);
|
||||
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
|
||||
|
||||
rv = ValidateTrackConstraints(aCx, aRawConstraints.mVideo.GetAsObject(),
|
||||
c.mVideom, &unknownConstraintFound);
|
||||
if (videoObj) {
|
||||
nsresult rv = CompareDictionaries(aCx, videoObj,
|
||||
c.mVideo.GetAsMediaTrackConstraintsInternal().mMandatory,
|
||||
&unknownConstraintFound);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
c.mVideo = true;
|
||||
} else {
|
||||
c.mVideo = aRawConstraints.mVideo.GetAsBoolean();
|
||||
}
|
||||
c.mPicture = aRawConstraints.mPicture;
|
||||
c.mFake = aRawConstraints.mFake;
|
||||
|
||||
/**
|
||||
* If we were asked to get a picture, before getting a snapshot, we check if
|
||||
@ -1434,7 +1426,7 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
|
||||
aPrivileged = true;
|
||||
}
|
||||
if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
|
||||
c.mVideo = false;
|
||||
c.mVideo.SetAsBoolean() = false;
|
||||
}
|
||||
|
||||
// Pass callbacks and MediaStreamListener along to GetUserMediaRunnable.
|
||||
@ -1474,7 +1466,7 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint32_t audioPerm = nsIPermissionManager::UNKNOWN_ACTION;
|
||||
if (c.mAudio) {
|
||||
if (IsOn(c.mAudio)) {
|
||||
rv = permManager->TestExactPermissionFromPrincipal(
|
||||
aWindow->GetExtantDoc()->NodePrincipal(), "microphone", &audioPerm);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -1484,7 +1476,7 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
|
||||
}
|
||||
|
||||
uint32_t videoPerm = nsIPermissionManager::UNKNOWN_ACTION;
|
||||
if (c.mVideo) {
|
||||
if (IsOn(c.mVideo)) {
|
||||
rv = permManager->TestExactPermissionFromPrincipal(
|
||||
aWindow->GetExtantDoc()->NodePrincipal(), "camera", &videoPerm);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -1493,18 +1485,18 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
|
||||
}
|
||||
}
|
||||
|
||||
if ((!c.mAudio || audioPerm) && (!c.mVideo || videoPerm)) {
|
||||
if ((!IsOn(c.mAudio) || audioPerm) && (!IsOn(c.mVideo) || videoPerm)) {
|
||||
// All permissions we were about to request already have a saved value.
|
||||
if (c.mAudio && audioPerm == nsIPermissionManager::DENY_ACTION) {
|
||||
c.mAudio = false;
|
||||
if (IsOn(c.mAudio) && audioPerm == nsIPermissionManager::DENY_ACTION) {
|
||||
c.mAudio.SetAsBoolean() = false;
|
||||
runnable->SetContraints(c);
|
||||
}
|
||||
if (c.mVideo && videoPerm == nsIPermissionManager::DENY_ACTION) {
|
||||
c.mVideo = false;
|
||||
if (IsOn(c.mVideo) && videoPerm == nsIPermissionManager::DENY_ACTION) {
|
||||
c.mVideo.SetAsBoolean() = false;
|
||||
runnable->SetContraints(c);
|
||||
}
|
||||
|
||||
if (!c.mAudio && !c.mVideo) {
|
||||
if (!IsOn(c.mAudio) && !IsOn(c.mVideo)) {
|
||||
return runnable->Denied(NS_LITERAL_STRING("PERMISSION_DENIED"));
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/dom/MediaStreamBinding.h"
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
#include "prlog.h"
|
||||
#include "DOMMediaStream.h"
|
||||
|
@ -179,8 +179,8 @@ MediaPermissionRequest::MediaPermissionRequest(nsRefPtr<dom::GetUserMediaRequest
|
||||
dom::MediaStreamConstraintsInternal constraints;
|
||||
mRequest->GetConstraints(constraints);
|
||||
|
||||
mAudio = constraints.mAudio;
|
||||
mVideo = constraints.mVideo;
|
||||
mAudio = !constraints.mAudio.IsBoolean() || constraints.mAudio.GetAsBoolean();
|
||||
mVideo = !constraints.mVideo.IsBoolean() || constraints.mVideo.GetAsBoolean();
|
||||
|
||||
for (uint32_t i = 0; i < aDevices.Length(); ++i) {
|
||||
nsCOMPtr<nsIMediaDevice> device(aDevices[i]);
|
||||
|
@ -10,6 +10,23 @@
|
||||
* liability, trademark and document use rules apply.
|
||||
*/
|
||||
|
||||
// These dictionaries need to be in a separate file from their
|
||||
// MediaTrackConstraints* counterparts due to a webidl compiler limitation.
|
||||
|
||||
dictionary MediaStreamConstraints {
|
||||
(boolean or MediaTrackConstraints) audio = false;
|
||||
(boolean or MediaTrackConstraints) video = false;
|
||||
boolean picture = false;
|
||||
boolean fake = false;
|
||||
};
|
||||
|
||||
dictionary MediaStreamConstraintsInternal {
|
||||
(boolean or MediaTrackConstraintsInternal) audio;
|
||||
(boolean or MediaTrackConstraintsInternal) video;
|
||||
boolean picture = false;
|
||||
boolean fake = false;
|
||||
};
|
||||
|
||||
interface MediaStream {
|
||||
// readonly attribute DOMString id;
|
||||
sequence<AudioStreamTrack> getAudioTracks ();
|
||||
|
@ -29,31 +29,13 @@ dictionary MediaTrackConstraintSet {
|
||||
|
||||
// typedef MediaTrackConstraintSet MediaTrackConstraint; // TODO: Bug 913053
|
||||
|
||||
dictionary MediaStreamConstraints {
|
||||
(boolean or object) audio = false; // TODO: Once Bug 767924 fixed change to
|
||||
(boolean or object) video = false; // (boolean or MediaTrackConstraints)
|
||||
boolean picture = false;
|
||||
boolean fake = false;
|
||||
};
|
||||
|
||||
// Internal dictionary to process the raw objects in (boolean or object)
|
||||
// workaround above. Since we cannot yet use unions on non-objects, we process
|
||||
// the data into discrete members for internal use until Bug 767924 is fixed:
|
||||
|
||||
dictionary MediaStreamConstraintsInternal {
|
||||
boolean audio = false;
|
||||
boolean video = false;
|
||||
boolean picture = false;
|
||||
boolean fake = false;
|
||||
MediaTrackConstraintsInternal audiom;
|
||||
MediaTrackConstraintsInternal videom;
|
||||
};
|
||||
|
||||
dictionary MediaTrackConstraints {
|
||||
object mandatory; // so we can see unknown + unsupported constraints
|
||||
sequence<MediaTrackConstraintSet> _optional; // a.k.a. MediaTrackConstraint
|
||||
};
|
||||
|
||||
// Internal dictionary holds result of processing raw MediaTrackConstraints above
|
||||
|
||||
dictionary MediaTrackConstraintsInternal {
|
||||
MediaTrackConstraintSet mandatory; // holds only supported constraints
|
||||
sequence<MediaTrackConstraintSet> _optional; // a.k.a. MediaTrackConstraint
|
||||
|
Loading…
Reference in New Issue
Block a user