mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Switch macOS embedding to proc table embedder API (flutter/engine#21811)
Converts the macOS embedding to use the new proc table version of the embedding API, and adds one example unit test using it to demonstrate and validate the approach.
This commit is contained in:
parent
f381503150
commit
16113fd7f5
@ -1048,7 +1048,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDe
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm
|
||||
|
||||
@ -82,7 +82,10 @@ source_set("flutter_framework_source") {
|
||||
|
||||
public_configs = [ "//flutter:config" ]
|
||||
|
||||
defines = [ "FLUTTER_FRAMEWORK" ]
|
||||
defines = [
|
||||
"FLUTTER_FRAMEWORK",
|
||||
"FLUTTER_ENGINE_NO_PROTOTYPES",
|
||||
]
|
||||
|
||||
cflags_objcc = [ "-fobjc-arc" ]
|
||||
|
||||
@ -113,7 +116,7 @@ executable("flutter_desktop_darwin_unittests") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"framework/Source/FlutterEngineUnittests.mm",
|
||||
"framework/Source/FlutterEngineTest.mm",
|
||||
"framework/Source/FlutterViewControllerTest.mm",
|
||||
]
|
||||
|
||||
@ -126,6 +129,7 @@ executable("flutter_desktop_darwin_unittests") {
|
||||
":flutter_framework_source",
|
||||
"//flutter/shell/platform/darwin/common:framework_shared",
|
||||
"//flutter/shell/platform/embedder:embedder_as_internal_library",
|
||||
"//flutter/shell/platform/embedder:embedder_test_utils",
|
||||
"//flutter/testing",
|
||||
"//flutter/testing:dart",
|
||||
"//flutter/testing:skia",
|
||||
|
||||
@ -27,16 +27,6 @@ static FlutterLocale FlutterLocaleFromNSLocale(NSLocale* locale) {
|
||||
return flutterLocale;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct AotDataDeleter {
|
||||
void operator()(FlutterEngineAOTData aot_data) { FlutterEngineCollectAOTData(aot_data); }
|
||||
};
|
||||
|
||||
using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AotDataDeleter>;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Private interface declaration for FlutterEngine.
|
||||
*/
|
||||
@ -85,10 +75,10 @@ using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AotDataDeleter>;
|
||||
- (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime;
|
||||
|
||||
/**
|
||||
* Loads the AOT snapshots and instructions from the elf bundle (app_elf_snapshot.so) if it is
|
||||
* present in the assets directory.
|
||||
* Loads the AOT snapshots and instructions from the elf bundle (app_elf_snapshot.so) into _aotData,
|
||||
* if it is present in the assets directory.
|
||||
*/
|
||||
- (UniqueAotDataPtr)loadAOTData:(NSString*)assetsDir;
|
||||
- (void)loadAOTData:(NSString*)assetsDir;
|
||||
|
||||
@end
|
||||
|
||||
@ -105,6 +95,7 @@ using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AotDataDeleter>;
|
||||
@implementation FlutterEngineRegistrar {
|
||||
NSString* _pluginKey;
|
||||
FlutterEngine* _flutterEngine;
|
||||
FlutterEngineProcTable _embedderAPI;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine {
|
||||
@ -201,7 +192,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
NSMutableDictionary<NSNumber*, FlutterExternalTextureGL*>* _textures;
|
||||
|
||||
// Pointer to the Dart AOT snapshot and instruction data.
|
||||
UniqueAotDataPtr _aotData;
|
||||
_FlutterEngineAOTData* _aotData;
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
|
||||
@ -218,6 +209,8 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
_messageHandlers = [[NSMutableDictionary alloc] init];
|
||||
_textures = [[NSMutableDictionary alloc] init];
|
||||
_allowHeadlessExecution = allowHeadlessExecution;
|
||||
_embedderAPI.struct_size = sizeof(FlutterEngineProcTable);
|
||||
FlutterEngineGetProcAddresses(&_embedderAPI);
|
||||
|
||||
NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
[notificationCenter addObserver:self
|
||||
@ -230,6 +223,9 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
|
||||
- (void)dealloc {
|
||||
[self shutDownEngine];
|
||||
if (_aotData) {
|
||||
_embedderAPI.CollectAOTData(_aotData);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)runWithEntrypoint:(NSString*)entrypoint {
|
||||
@ -301,19 +297,19 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
};
|
||||
flutterArguments.custom_task_runners = &custom_task_runners;
|
||||
|
||||
_aotData = [self loadAOTData:_project.assetsPath];
|
||||
[self loadAOTData:_project.assetsPath];
|
||||
if (_aotData) {
|
||||
flutterArguments.aot_data = _aotData.get();
|
||||
flutterArguments.aot_data = _aotData;
|
||||
}
|
||||
|
||||
FlutterEngineResult result = FlutterEngineInitialize(
|
||||
FlutterEngineResult result = _embedderAPI.Initialize(
|
||||
FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine);
|
||||
if (result != kSuccess) {
|
||||
NSLog(@"Failed to initialize Flutter engine: error %d", result);
|
||||
return NO;
|
||||
}
|
||||
|
||||
result = FlutterEngineRunInitialized(_engine);
|
||||
result = _embedderAPI.RunInitialized(_engine);
|
||||
if (result != kSuccess) {
|
||||
NSLog(@"Failed to run an initialized engine: error %d", result);
|
||||
return NO;
|
||||
@ -326,9 +322,9 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UniqueAotDataPtr)loadAOTData:(NSString*)assetsDir {
|
||||
if (!FlutterEngineRunsAOTCompiledDartCode()) {
|
||||
return nullptr;
|
||||
- (void)loadAOTData:(NSString*)assetsDir {
|
||||
if (!_embedderAPI.RunsAOTCompiledDartCode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isDirOut = false; // required for NSFileManager fileExistsAtPath.
|
||||
@ -339,21 +335,17 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
NSString* elfPath = [NSString pathWithComponents:@[ assetsDir, @"app_elf_snapshot.so" ]];
|
||||
|
||||
if (![fileManager fileExistsAtPath:elfPath isDirectory:&isDirOut]) {
|
||||
return nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
FlutterEngineAOTDataSource source = {};
|
||||
source.type = kFlutterEngineAOTDataSourceTypeElfPath;
|
||||
source.elf_path = [elfPath cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
FlutterEngineAOTData data = nullptr;
|
||||
auto result = FlutterEngineCreateAOTData(&source, &data);
|
||||
auto result = _embedderAPI.CreateAOTData(&source, &_aotData);
|
||||
if (result != kSuccess) {
|
||||
NSLog(@"Failed to load AOT data from: %@", elfPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return UniqueAotDataPtr(data);
|
||||
}
|
||||
|
||||
- (void)setViewController:(FlutterViewController*)controller {
|
||||
@ -409,13 +401,17 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
display.refresh_rate = round(refreshRate);
|
||||
|
||||
std::vector<FlutterEngineDisplay> displays = {display};
|
||||
FlutterEngineNotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup,
|
||||
_embedderAPI.NotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup,
|
||||
displays.data(), displays.size());
|
||||
}
|
||||
|
||||
CVDisplayLinkRelease(displayLinkRef);
|
||||
}
|
||||
|
||||
- (FlutterEngineProcTable&)embedderAPI {
|
||||
return _embedderAPI;
|
||||
}
|
||||
|
||||
- (void)updateWindowMetrics {
|
||||
if (!_engine) {
|
||||
return;
|
||||
@ -433,11 +429,11 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
.left = static_cast<size_t>(scaledBounds.origin.x),
|
||||
.top = static_cast<size_t>(scaledBounds.origin.y),
|
||||
};
|
||||
FlutterEngineSendWindowMetricsEvent(_engine, &windowMetricsEvent);
|
||||
_embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent);
|
||||
}
|
||||
|
||||
- (void)sendPointerEvent:(const FlutterPointerEvent&)event {
|
||||
FlutterEngineSendPointerEvent(_engine, &event, 1);
|
||||
_embedderAPI.SendPointerEvent(_engine, &event, 1);
|
||||
}
|
||||
|
||||
#pragma mark - Private methods
|
||||
@ -462,7 +458,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
std::transform(
|
||||
flutterLocales.begin(), flutterLocales.end(), std::back_inserter(flutterLocaleList),
|
||||
[](const auto& arg) -> const auto* { return &arg; });
|
||||
FlutterEngineUpdateLocales(_engine, flutterLocaleList.data(), flutterLocaleList.size());
|
||||
_embedderAPI.UpdateLocales(_engine, flutterLocaleList.data(), flutterLocaleList.size());
|
||||
}
|
||||
|
||||
- (bool)engineCallbackOnMakeCurrent {
|
||||
@ -500,7 +496,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
|
||||
FlutterBinaryReply binaryResponseHandler = ^(NSData* response) {
|
||||
if (responseHandle) {
|
||||
FlutterEngineSendPlatformMessageResponse(self->_engine, responseHandle,
|
||||
_embedderAPI.SendPlatformMessageResponse(self->_engine, responseHandle,
|
||||
static_cast<const uint8_t*>(response.bytes),
|
||||
response.length);
|
||||
responseHandle = NULL;
|
||||
@ -527,7 +523,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
return;
|
||||
}
|
||||
|
||||
FlutterEngineResult result = FlutterEngineDeinitialize(_engine);
|
||||
FlutterEngineResult result = _embedderAPI.Deinitialize(_engine);
|
||||
if (result != kSuccess) {
|
||||
NSLog(@"Could not de-initialize the Flutter engine: error %d", result);
|
||||
}
|
||||
@ -535,7 +531,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
// Balancing release for the retain in the task runner dispatch table.
|
||||
CFRelease((CFTypeRef)self);
|
||||
|
||||
result = FlutterEngineShutdown(_engine);
|
||||
result = _embedderAPI.Shutdown(_engine);
|
||||
if (result != kSuccess) {
|
||||
NSLog(@"Failed to shut down Flutter engine: error %d", result);
|
||||
}
|
||||
@ -568,7 +564,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
delete captures;
|
||||
};
|
||||
|
||||
FlutterEngineResult create_result = FlutterPlatformMessageCreateResponseHandle(
|
||||
FlutterEngineResult create_result = _embedderAPI.PlatformMessageCreateResponseHandle(
|
||||
_engine, message_reply, captures.get(), &response_handle);
|
||||
if (create_result != kSuccess) {
|
||||
NSLog(@"Failed to create a FlutterPlatformMessageResponseHandle (%d)", create_result);
|
||||
@ -585,7 +581,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
.response_handle = response_handle,
|
||||
};
|
||||
|
||||
FlutterEngineResult message_result = FlutterEngineSendPlatformMessage(_engine, &platformMessage);
|
||||
FlutterEngineResult message_result = _embedderAPI.SendPlatformMessage(_engine, &platformMessage);
|
||||
if (message_result != kSuccess) {
|
||||
NSLog(@"Failed to send message to Flutter engine on channel '%@' (%d).", channel,
|
||||
message_result);
|
||||
@ -593,7 +589,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
|
||||
if (response_handle != nullptr) {
|
||||
FlutterEngineResult release_result =
|
||||
FlutterPlatformMessageReleaseResponseHandle(_engine, response_handle);
|
||||
_embedderAPI.PlatformMessageReleaseResponseHandle(_engine, response_handle);
|
||||
if (release_result != kSuccess) {
|
||||
NSLog(@"Failed to release the response handle (%d).", release_result);
|
||||
};
|
||||
@ -628,30 +624,30 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
FlutterExternalTextureGL* FlutterTexture =
|
||||
[[FlutterExternalTextureGL alloc] initWithFlutterTexture:texture];
|
||||
int64_t textureID = [FlutterTexture textureID];
|
||||
FlutterEngineRegisterExternalTexture(_engine, textureID);
|
||||
_embedderAPI.RegisterExternalTexture(_engine, textureID);
|
||||
_textures[@(textureID)] = FlutterTexture;
|
||||
return textureID;
|
||||
}
|
||||
|
||||
- (void)textureFrameAvailable:(int64_t)textureID {
|
||||
FlutterEngineMarkExternalTextureFrameAvailable(_engine, textureID);
|
||||
_embedderAPI.MarkExternalTextureFrameAvailable(_engine, textureID);
|
||||
}
|
||||
|
||||
- (void)unregisterTexture:(int64_t)textureID {
|
||||
FlutterEngineUnregisterExternalTexture(_engine, textureID);
|
||||
_embedderAPI.UnregisterExternalTexture(_engine, textureID);
|
||||
[_textures removeObjectForKey:@(textureID)];
|
||||
}
|
||||
|
||||
#pragma mark - Task runner integration
|
||||
|
||||
- (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime {
|
||||
const auto engine_time = FlutterEngineGetCurrentTime();
|
||||
const auto engine_time = _embedderAPI.GetCurrentTime();
|
||||
|
||||
__weak FlutterEngine* weak_self = self;
|
||||
auto worker = ^{
|
||||
FlutterEngine* strong_self = weak_self;
|
||||
if (strong_self && strong_self->_engine) {
|
||||
auto result = FlutterEngineRunTask(strong_self->_engine, &task);
|
||||
auto result = _embedderAPI.RunTask(strong_self->_engine, &task);
|
||||
if (result != kSuccess) {
|
||||
NSLog(@"Could not post a task to the Flutter engine.");
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
|
||||
#include "flutter/testing/testing.h"
|
||||
|
||||
namespace flutter::testing {
|
||||
|
||||
namespace {
|
||||
// Returns an engine configured for the text fixture resource configuration.
|
||||
FlutterEngine* CreateTestEngine() {
|
||||
NSString* fixtures = @(testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
initWithAssetsPath:fixtures
|
||||
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
|
||||
return [[FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(FlutterEngine, CanLaunch) {
|
||||
FlutterEngine* engine = CreateTestEngine();
|
||||
EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
|
||||
EXPECT_TRUE(engine.running);
|
||||
[engine shutDownEngine];
|
||||
}
|
||||
|
||||
TEST(FlutterEngine, MessengerSend) {
|
||||
FlutterEngine* engine = CreateTestEngine();
|
||||
EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
|
||||
|
||||
NSData* test_message = [@"a message" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
bool called = false;
|
||||
|
||||
engine.embedderAPI.SendPlatformMessage = MOCK_ENGINE_PROC(
|
||||
SendPlatformMessage, ([&called, test_message](auto engine, auto message) {
|
||||
called = true;
|
||||
EXPECT_STREQ(message->channel, "test");
|
||||
EXPECT_EQ(memcmp(message->message, test_message.bytes, message->message_size), 0);
|
||||
return kSuccess;
|
||||
}));
|
||||
|
||||
[engine.binaryMessenger sendOnChannel:@"test" message:test_message];
|
||||
EXPECT_TRUE(called);
|
||||
|
||||
[engine shutDownEngine];
|
||||
}
|
||||
|
||||
} // flutter::testing
|
||||
@ -1,25 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#include "flutter/testing/testing.h"
|
||||
|
||||
namespace flutter::testing {
|
||||
|
||||
TEST(FlutterEngineTest, FlutterEngineCanLaunch) {
|
||||
NSString* fixtures = @(testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
initWithAssetsPath:fixtures
|
||||
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test"
|
||||
project:project
|
||||
allowHeadlessExecution:true];
|
||||
ASSERT_TRUE([engine runWithEntrypoint:@"main"]);
|
||||
ASSERT_TRUE(engine.running);
|
||||
[engine shutDownEngine];
|
||||
}
|
||||
|
||||
} // flutter::testing
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
|
||||
@interface FlutterEngine ()
|
||||
|
||||
@ -21,6 +21,11 @@
|
||||
*/
|
||||
@property(nonatomic, readonly, nullable) NSOpenGLContext* resourceContext;
|
||||
|
||||
/**
|
||||
* Function pointers for interacting with the embedder.h API.
|
||||
*/
|
||||
@property(nonatomic) FlutterEngineProcTable& embedderAPI;
|
||||
|
||||
/**
|
||||
* Informs the engine that the associated view controller's view size has changed.
|
||||
*/
|
||||
|
||||
@ -34,7 +34,7 @@ id mockViewController(NSString* pasteboardString) {
|
||||
return viewControllerMock;
|
||||
}
|
||||
|
||||
TEST(FlutterViewControllerTest, HasStringsWhenPasteboardEmpty) {
|
||||
TEST(FlutterViewController, HasStringsWhenPasteboardEmpty) {
|
||||
// Mock FlutterViewController so that it behaves like the pasteboard is empty.
|
||||
id viewControllerMock = mockViewController(nil);
|
||||
|
||||
@ -49,11 +49,11 @@ TEST(FlutterViewControllerTest, HasStringsWhenPasteboardEmpty) {
|
||||
FlutterMethodCall* methodCallAfterClear =
|
||||
[FlutterMethodCall methodCallWithMethodName:@"Clipboard.hasStrings" arguments:nil];
|
||||
[viewControllerMock handleMethodCall:methodCallAfterClear result:resultAfterClear];
|
||||
ASSERT_TRUE(calledAfterClear);
|
||||
ASSERT_FALSE(valueAfterClear);
|
||||
EXPECT_TRUE(calledAfterClear);
|
||||
EXPECT_FALSE(valueAfterClear);
|
||||
}
|
||||
|
||||
TEST(FlutterViewControllerTest, HasStringsWhenPasteboardFull) {
|
||||
TEST(FlutterViewController, HasStringsWhenPasteboardFull) {
|
||||
// Mock FlutterViewController so that it behaves like the pasteboard has a
|
||||
// valid string.
|
||||
id viewControllerMock = mockViewController(@"some string");
|
||||
@ -69,8 +69,8 @@ TEST(FlutterViewControllerTest, HasStringsWhenPasteboardFull) {
|
||||
FlutterMethodCall* methodCall =
|
||||
[FlutterMethodCall methodCallWithMethodName:@"Clipboard.hasStrings" arguments:nil];
|
||||
[viewControllerMock handleMethodCall:methodCall result:result];
|
||||
ASSERT_TRUE(called);
|
||||
ASSERT_TRUE(value);
|
||||
EXPECT_TRUE(called);
|
||||
EXPECT_TRUE(value);
|
||||
}
|
||||
|
||||
} // flutter::testing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user